ZhengyangZhang commited on
Commit
9a4e1b3
·
verified ·
1 Parent(s): 297cb90

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/certifi/__init__.py +4 -0
  2. lib/python3.12/site-packages/pip/_vendor/certifi/__main__.py +12 -0
  3. lib/python3.12/site-packages/pip/_vendor/certifi/__pycache__/__init__.cpython-312.pyc +0 -0
  4. lib/python3.12/site-packages/pip/_vendor/certifi/__pycache__/__main__.cpython-312.pyc +0 -0
  5. lib/python3.12/site-packages/pip/_vendor/certifi/__pycache__/core.cpython-312.pyc +0 -0
  6. lib/python3.12/site-packages/pip/_vendor/certifi/cacert.pem +0 -0
  7. lib/python3.12/site-packages/pip/_vendor/certifi/core.py +114 -0
  8. lib/python3.12/site-packages/pip/_vendor/certifi/py.typed +0 -0
  9. lib/python3.12/site-packages/pip/_vendor/distlib/compat.py +1137 -0
  10. lib/python3.12/site-packages/pip/_vendor/distlib/database.py +1329 -0
  11. lib/python3.12/site-packages/pip/_vendor/distlib/index.py +508 -0
  12. lib/python3.12/site-packages/pip/_vendor/distlib/locators.py +1295 -0
  13. lib/python3.12/site-packages/pip/_vendor/distlib/manifest.py +384 -0
  14. lib/python3.12/site-packages/pip/_vendor/distlib/metadata.py +1031 -0
  15. lib/python3.12/site-packages/pip/_vendor/distlib/resources.py +358 -0
  16. lib/python3.12/site-packages/pip/_vendor/distlib/util.py +1984 -0
  17. lib/python3.12/site-packages/pip/_vendor/distro/__init__.py +54 -0
  18. lib/python3.12/site-packages/pip/_vendor/distro/__main__.py +4 -0
  19. lib/python3.12/site-packages/pip/_vendor/distro/__pycache__/__init__.cpython-312.pyc +0 -0
  20. lib/python3.12/site-packages/pip/_vendor/distro/__pycache__/__main__.cpython-312.pyc +0 -0
  21. lib/python3.12/site-packages/pip/_vendor/distro/__pycache__/distro.cpython-312.pyc +0 -0
  22. lib/python3.12/site-packages/pip/_vendor/distro/distro.py +1403 -0
  23. lib/python3.12/site-packages/pip/_vendor/distro/py.typed +0 -0
  24. lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/__init__.cpython-312.pyc +0 -0
  25. lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_parser.cpython-312.pyc +0 -0
  26. lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_structures.cpython-312.pyc +0 -0
  27. lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/metadata.cpython-312.pyc +0 -0
  28. lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/tags.cpython-312.pyc +0 -0
  29. lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/utils.cpython-312.pyc +0 -0
  30. lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/version.cpython-312.pyc +0 -0
  31. lib/python3.12/site-packages/pip/_vendor/pygments/__init__.py +82 -0
  32. lib/python3.12/site-packages/pip/_vendor/pygments/__main__.py +17 -0
  33. lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/lexer.cpython-312.pyc +0 -0
  34. lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/modeline.cpython-312.pyc +0 -0
  35. lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/style.cpython-312.pyc +0 -0
  36. lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/unistring.cpython-312.pyc +0 -0
  37. lib/python3.12/site-packages/pip/_vendor/pygments/cmdline.py +668 -0
  38. lib/python3.12/site-packages/pip/_vendor/pygments/console.py +70 -0
  39. lib/python3.12/site-packages/pip/_vendor/pygments/filter.py +70 -0
  40. lib/python3.12/site-packages/pip/_vendor/pygments/filters/__init__.py +940 -0
  41. lib/python3.12/site-packages/pip/_vendor/pygments/filters/__pycache__/__init__.cpython-312.pyc +0 -0
  42. lib/python3.12/site-packages/pip/_vendor/pygments/formatter.py +129 -0
  43. lib/python3.12/site-packages/pip/_vendor/pygments/lexer.py +963 -0
  44. lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__init__.py +362 -0
  45. lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__pycache__/__init__.cpython-312.pyc +0 -0
  46. lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__pycache__/_mapping.cpython-312.pyc +0 -0
  47. lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__pycache__/python.cpython-312.pyc +0 -0
  48. lib/python3.12/site-packages/pip/_vendor/pygments/lexers/_mapping.py +589 -0
  49. lib/python3.12/site-packages/pip/_vendor/pygments/lexers/python.py +1198 -0
  50. lib/python3.12/site-packages/pip/_vendor/pygments/modeline.py +43 -0
lib/python3.12/site-packages/pip/_vendor/certifi/__init__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ from .core import contents, where
2
+
3
+ __all__ = ["contents", "where"]
4
+ __version__ = "2024.08.30"
lib/python3.12/site-packages/pip/_vendor/certifi/__main__.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+
3
+ from pip._vendor.certifi import contents, where
4
+
5
+ parser = argparse.ArgumentParser()
6
+ parser.add_argument("-c", "--contents", action="store_true")
7
+ args = parser.parse_args()
8
+
9
+ if args.contents:
10
+ print(contents())
11
+ else:
12
+ print(where())
lib/python3.12/site-packages/pip/_vendor/certifi/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (325 Bytes). View file
 
lib/python3.12/site-packages/pip/_vendor/certifi/__pycache__/__main__.cpython-312.pyc ADDED
Binary file (652 Bytes). View file
 
lib/python3.12/site-packages/pip/_vendor/certifi/__pycache__/core.cpython-312.pyc ADDED
Binary file (3.22 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/certifi/cacert.pem ADDED
The diff for this file is too large to render. See raw diff
 
lib/python3.12/site-packages/pip/_vendor/certifi/core.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ certifi.py
3
+ ~~~~~~~~~~
4
+
5
+ This module returns the installation location of cacert.pem or its contents.
6
+ """
7
+ import sys
8
+ import atexit
9
+
10
+ def exit_cacert_ctx() -> None:
11
+ _CACERT_CTX.__exit__(None, None, None) # type: ignore[union-attr]
12
+
13
+
14
+ if sys.version_info >= (3, 11):
15
+
16
+ from importlib.resources import as_file, files
17
+
18
+ _CACERT_CTX = None
19
+ _CACERT_PATH = None
20
+
21
+ def where() -> str:
22
+ # This is slightly terrible, but we want to delay extracting the file
23
+ # in cases where we're inside of a zipimport situation until someone
24
+ # actually calls where(), but we don't want to re-extract the file
25
+ # on every call of where(), so we'll do it once then store it in a
26
+ # global variable.
27
+ global _CACERT_CTX
28
+ global _CACERT_PATH
29
+ if _CACERT_PATH is None:
30
+ # This is slightly janky, the importlib.resources API wants you to
31
+ # manage the cleanup of this file, so it doesn't actually return a
32
+ # path, it returns a context manager that will give you the path
33
+ # when you enter it and will do any cleanup when you leave it. In
34
+ # the common case of not needing a temporary file, it will just
35
+ # return the file system location and the __exit__() is a no-op.
36
+ #
37
+ # We also have to hold onto the actual context manager, because
38
+ # it will do the cleanup whenever it gets garbage collected, so
39
+ # we will also store that at the global level as well.
40
+ _CACERT_CTX = as_file(files("pip._vendor.certifi").joinpath("cacert.pem"))
41
+ _CACERT_PATH = str(_CACERT_CTX.__enter__())
42
+ atexit.register(exit_cacert_ctx)
43
+
44
+ return _CACERT_PATH
45
+
46
+ def contents() -> str:
47
+ return files("pip._vendor.certifi").joinpath("cacert.pem").read_text(encoding="ascii")
48
+
49
+ elif sys.version_info >= (3, 7):
50
+
51
+ from importlib.resources import path as get_path, read_text
52
+
53
+ _CACERT_CTX = None
54
+ _CACERT_PATH = None
55
+
56
+ def where() -> str:
57
+ # This is slightly terrible, but we want to delay extracting the
58
+ # file in cases where we're inside of a zipimport situation until
59
+ # someone actually calls where(), but we don't want to re-extract
60
+ # the file on every call of where(), so we'll do it once then store
61
+ # it in a global variable.
62
+ global _CACERT_CTX
63
+ global _CACERT_PATH
64
+ if _CACERT_PATH is None:
65
+ # This is slightly janky, the importlib.resources API wants you
66
+ # to manage the cleanup of this file, so it doesn't actually
67
+ # return a path, it returns a context manager that will give
68
+ # you the path when you enter it and will do any cleanup when
69
+ # you leave it. In the common case of not needing a temporary
70
+ # file, it will just return the file system location and the
71
+ # __exit__() is a no-op.
72
+ #
73
+ # We also have to hold onto the actual context manager, because
74
+ # it will do the cleanup whenever it gets garbage collected, so
75
+ # we will also store that at the global level as well.
76
+ _CACERT_CTX = get_path("pip._vendor.certifi", "cacert.pem")
77
+ _CACERT_PATH = str(_CACERT_CTX.__enter__())
78
+ atexit.register(exit_cacert_ctx)
79
+
80
+ return _CACERT_PATH
81
+
82
+ def contents() -> str:
83
+ return read_text("pip._vendor.certifi", "cacert.pem", encoding="ascii")
84
+
85
+ else:
86
+ import os
87
+ import types
88
+ from typing import Union
89
+
90
+ Package = Union[types.ModuleType, str]
91
+ Resource = Union[str, "os.PathLike"]
92
+
93
+ # This fallback will work for Python versions prior to 3.7 that lack the
94
+ # importlib.resources module but relies on the existing `where` function
95
+ # so won't address issues with environments like PyOxidizer that don't set
96
+ # __file__ on modules.
97
+ def read_text(
98
+ package: Package,
99
+ resource: Resource,
100
+ encoding: str = 'utf-8',
101
+ errors: str = 'strict'
102
+ ) -> str:
103
+ with open(where(), encoding=encoding) as data:
104
+ return data.read()
105
+
106
+ # If we don't have importlib.resources, then we will just do the old logic
107
+ # of assuming we're on the filesystem and munge the path directly.
108
+ def where() -> str:
109
+ f = os.path.dirname(__file__)
110
+
111
+ return os.path.join(f, "cacert.pem")
112
+
113
+ def contents() -> str:
114
+ return read_text("pip._vendor.certifi", "cacert.pem", encoding="ascii")
lib/python3.12/site-packages/pip/_vendor/certifi/py.typed ADDED
File without changes
lib/python3.12/site-packages/pip/_vendor/distlib/compat.py ADDED
@@ -0,0 +1,1137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2013-2017 Vinay Sajip.
4
+ # Licensed to the Python Software Foundation under a contributor agreement.
5
+ # See LICENSE.txt and CONTRIBUTORS.txt.
6
+ #
7
+ from __future__ import absolute_import
8
+
9
+ import os
10
+ import re
11
+ import shutil
12
+ import sys
13
+
14
+ try:
15
+ import ssl
16
+ except ImportError: # pragma: no cover
17
+ ssl = None
18
+
19
+ if sys.version_info[0] < 3: # pragma: no cover
20
+ from StringIO import StringIO
21
+ string_types = basestring,
22
+ text_type = unicode
23
+ from types import FileType as file_type
24
+ import __builtin__ as builtins
25
+ import ConfigParser as configparser
26
+ from urlparse import urlparse, urlunparse, urljoin, urlsplit, urlunsplit
27
+ from urllib import (urlretrieve, quote as _quote, unquote, url2pathname,
28
+ pathname2url, ContentTooShortError, splittype)
29
+
30
+ def quote(s):
31
+ if isinstance(s, unicode):
32
+ s = s.encode('utf-8')
33
+ return _quote(s)
34
+
35
+ import urllib2
36
+ from urllib2 import (Request, urlopen, URLError, HTTPError,
37
+ HTTPBasicAuthHandler, HTTPPasswordMgr, HTTPHandler,
38
+ HTTPRedirectHandler, build_opener)
39
+ if ssl:
40
+ from urllib2 import HTTPSHandler
41
+ import httplib
42
+ import xmlrpclib
43
+ import Queue as queue
44
+ from HTMLParser import HTMLParser
45
+ import htmlentitydefs
46
+ raw_input = raw_input
47
+ from itertools import ifilter as filter
48
+ from itertools import ifilterfalse as filterfalse
49
+
50
+ # Leaving this around for now, in case it needs resurrecting in some way
51
+ # _userprog = None
52
+ # def splituser(host):
53
+ # """splituser('user[:passwd]@host[:port]') --> 'user[:passwd]', 'host[:port]'."""
54
+ # global _userprog
55
+ # if _userprog is None:
56
+ # import re
57
+ # _userprog = re.compile('^(.*)@(.*)$')
58
+
59
+ # match = _userprog.match(host)
60
+ # if match: return match.group(1, 2)
61
+ # return None, host
62
+
63
+ else: # pragma: no cover
64
+ from io import StringIO
65
+ string_types = str,
66
+ text_type = str
67
+ from io import TextIOWrapper as file_type
68
+ import builtins
69
+ import configparser
70
+ from urllib.parse import (urlparse, urlunparse, urljoin, quote, unquote,
71
+ urlsplit, urlunsplit, splittype)
72
+ from urllib.request import (urlopen, urlretrieve, Request, url2pathname,
73
+ pathname2url, HTTPBasicAuthHandler,
74
+ HTTPPasswordMgr, HTTPHandler,
75
+ HTTPRedirectHandler, build_opener)
76
+ if ssl:
77
+ from urllib.request import HTTPSHandler
78
+ from urllib.error import HTTPError, URLError, ContentTooShortError
79
+ import http.client as httplib
80
+ import urllib.request as urllib2
81
+ import xmlrpc.client as xmlrpclib
82
+ import queue
83
+ from html.parser import HTMLParser
84
+ import html.entities as htmlentitydefs
85
+ raw_input = input
86
+ from itertools import filterfalse
87
+ filter = filter
88
+
89
+ try:
90
+ from ssl import match_hostname, CertificateError
91
+ except ImportError: # pragma: no cover
92
+
93
+ class CertificateError(ValueError):
94
+ pass
95
+
96
+ def _dnsname_match(dn, hostname, max_wildcards=1):
97
+ """Matching according to RFC 6125, section 6.4.3
98
+
99
+ http://tools.ietf.org/html/rfc6125#section-6.4.3
100
+ """
101
+ pats = []
102
+ if not dn:
103
+ return False
104
+
105
+ parts = dn.split('.')
106
+ leftmost, remainder = parts[0], parts[1:]
107
+
108
+ wildcards = leftmost.count('*')
109
+ if wildcards > max_wildcards:
110
+ # Issue #17980: avoid denials of service by refusing more
111
+ # than one wildcard per fragment. A survey of established
112
+ # policy among SSL implementations showed it to be a
113
+ # reasonable choice.
114
+ raise CertificateError(
115
+ "too many wildcards in certificate DNS name: " + repr(dn))
116
+
117
+ # speed up common case w/o wildcards
118
+ if not wildcards:
119
+ return dn.lower() == hostname.lower()
120
+
121
+ # RFC 6125, section 6.4.3, subitem 1.
122
+ # The client SHOULD NOT attempt to match a presented identifier in which
123
+ # the wildcard character comprises a label other than the left-most label.
124
+ if leftmost == '*':
125
+ # When '*' is a fragment by itself, it matches a non-empty dotless
126
+ # fragment.
127
+ pats.append('[^.]+')
128
+ elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
129
+ # RFC 6125, section 6.4.3, subitem 3.
130
+ # The client SHOULD NOT attempt to match a presented identifier
131
+ # where the wildcard character is embedded within an A-label or
132
+ # U-label of an internationalized domain name.
133
+ pats.append(re.escape(leftmost))
134
+ else:
135
+ # Otherwise, '*' matches any dotless string, e.g. www*
136
+ pats.append(re.escape(leftmost).replace(r'\*', '[^.]*'))
137
+
138
+ # add the remaining fragments, ignore any wildcards
139
+ for frag in remainder:
140
+ pats.append(re.escape(frag))
141
+
142
+ pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
143
+ return pat.match(hostname)
144
+
145
+ def match_hostname(cert, hostname):
146
+ """Verify that *cert* (in decoded format as returned by
147
+ SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125
148
+ rules are followed, but IP addresses are not accepted for *hostname*.
149
+
150
+ CertificateError is raised on failure. On success, the function
151
+ returns nothing.
152
+ """
153
+ if not cert:
154
+ raise ValueError("empty or no certificate, match_hostname needs a "
155
+ "SSL socket or SSL context with either "
156
+ "CERT_OPTIONAL or CERT_REQUIRED")
157
+ dnsnames = []
158
+ san = cert.get('subjectAltName', ())
159
+ for key, value in san:
160
+ if key == 'DNS':
161
+ if _dnsname_match(value, hostname):
162
+ return
163
+ dnsnames.append(value)
164
+ if not dnsnames:
165
+ # The subject is only checked when there is no dNSName entry
166
+ # in subjectAltName
167
+ for sub in cert.get('subject', ()):
168
+ for key, value in sub:
169
+ # XXX according to RFC 2818, the most specific Common Name
170
+ # must be used.
171
+ if key == 'commonName':
172
+ if _dnsname_match(value, hostname):
173
+ return
174
+ dnsnames.append(value)
175
+ if len(dnsnames) > 1:
176
+ raise CertificateError("hostname %r "
177
+ "doesn't match either of %s" %
178
+ (hostname, ', '.join(map(repr, dnsnames))))
179
+ elif len(dnsnames) == 1:
180
+ raise CertificateError("hostname %r "
181
+ "doesn't match %r" %
182
+ (hostname, dnsnames[0]))
183
+ else:
184
+ raise CertificateError("no appropriate commonName or "
185
+ "subjectAltName fields were found")
186
+
187
+
188
+ try:
189
+ from types import SimpleNamespace as Container
190
+ except ImportError: # pragma: no cover
191
+
192
+ class Container(object):
193
+ """
194
+ A generic container for when multiple values need to be returned
195
+ """
196
+
197
+ def __init__(self, **kwargs):
198
+ self.__dict__.update(kwargs)
199
+
200
+
201
+ try:
202
+ from shutil import which
203
+ except ImportError: # pragma: no cover
204
+ # Implementation from Python 3.3
205
+ def which(cmd, mode=os.F_OK | os.X_OK, path=None):
206
+ """Given a command, mode, and a PATH string, return the path which
207
+ conforms to the given mode on the PATH, or None if there is no such
208
+ file.
209
+
210
+ `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result
211
+ of os.environ.get("PATH"), or can be overridden with a custom search
212
+ path.
213
+
214
+ """
215
+
216
+ # Check that a given file can be accessed with the correct mode.
217
+ # Additionally check that `file` is not a directory, as on Windows
218
+ # directories pass the os.access check.
219
+ def _access_check(fn, mode):
220
+ return (os.path.exists(fn) and os.access(fn, mode) and not os.path.isdir(fn))
221
+
222
+ # If we're given a path with a directory part, look it up directly rather
223
+ # than referring to PATH directories. This includes checking relative to the
224
+ # current directory, e.g. ./script
225
+ if os.path.dirname(cmd):
226
+ if _access_check(cmd, mode):
227
+ return cmd
228
+ return None
229
+
230
+ if path is None:
231
+ path = os.environ.get("PATH", os.defpath)
232
+ if not path:
233
+ return None
234
+ path = path.split(os.pathsep)
235
+
236
+ if sys.platform == "win32":
237
+ # The current directory takes precedence on Windows.
238
+ if os.curdir not in path:
239
+ path.insert(0, os.curdir)
240
+
241
+ # PATHEXT is necessary to check on Windows.
242
+ pathext = os.environ.get("PATHEXT", "").split(os.pathsep)
243
+ # See if the given file matches any of the expected path extensions.
244
+ # This will allow us to short circuit when given "python.exe".
245
+ # If it does match, only test that one, otherwise we have to try
246
+ # others.
247
+ if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
248
+ files = [cmd]
249
+ else:
250
+ files = [cmd + ext for ext in pathext]
251
+ else:
252
+ # On other platforms you don't have things like PATHEXT to tell you
253
+ # what file suffixes are executable, so just pass on cmd as-is.
254
+ files = [cmd]
255
+
256
+ seen = set()
257
+ for dir in path:
258
+ normdir = os.path.normcase(dir)
259
+ if normdir not in seen:
260
+ seen.add(normdir)
261
+ for thefile in files:
262
+ name = os.path.join(dir, thefile)
263
+ if _access_check(name, mode):
264
+ return name
265
+ return None
266
+
267
+
268
+ # ZipFile is a context manager in 2.7, but not in 2.6
269
+
270
+ from zipfile import ZipFile as BaseZipFile
271
+
272
+ if hasattr(BaseZipFile, '__enter__'): # pragma: no cover
273
+ ZipFile = BaseZipFile
274
+ else: # pragma: no cover
275
+ from zipfile import ZipExtFile as BaseZipExtFile
276
+
277
+ class ZipExtFile(BaseZipExtFile):
278
+
279
+ def __init__(self, base):
280
+ self.__dict__.update(base.__dict__)
281
+
282
+ def __enter__(self):
283
+ return self
284
+
285
+ def __exit__(self, *exc_info):
286
+ self.close()
287
+ # return None, so if an exception occurred, it will propagate
288
+
289
+ class ZipFile(BaseZipFile):
290
+
291
+ def __enter__(self):
292
+ return self
293
+
294
+ def __exit__(self, *exc_info):
295
+ self.close()
296
+ # return None, so if an exception occurred, it will propagate
297
+
298
+ def open(self, *args, **kwargs):
299
+ base = BaseZipFile.open(self, *args, **kwargs)
300
+ return ZipExtFile(base)
301
+
302
+
303
+ try:
304
+ from platform import python_implementation
305
+ except ImportError: # pragma: no cover
306
+
307
+ def python_implementation():
308
+ """Return a string identifying the Python implementation."""
309
+ if 'PyPy' in sys.version:
310
+ return 'PyPy'
311
+ if os.name == 'java':
312
+ return 'Jython'
313
+ if sys.version.startswith('IronPython'):
314
+ return 'IronPython'
315
+ return 'CPython'
316
+
317
+
318
+ import sysconfig
319
+
320
+ try:
321
+ callable = callable
322
+ except NameError: # pragma: no cover
323
+ from collections.abc import Callable
324
+
325
+ def callable(obj):
326
+ return isinstance(obj, Callable)
327
+
328
+
329
+ try:
330
+ fsencode = os.fsencode
331
+ fsdecode = os.fsdecode
332
+ except AttributeError: # pragma: no cover
333
+ # Issue #99: on some systems (e.g. containerised),
334
+ # sys.getfilesystemencoding() returns None, and we need a real value,
335
+ # so fall back to utf-8. From the CPython 2.7 docs relating to Unix and
336
+ # sys.getfilesystemencoding(): the return value is "the user’s preference
337
+ # according to the result of nl_langinfo(CODESET), or None if the
338
+ # nl_langinfo(CODESET) failed."
339
+ _fsencoding = sys.getfilesystemencoding() or 'utf-8'
340
+ if _fsencoding == 'mbcs':
341
+ _fserrors = 'strict'
342
+ else:
343
+ _fserrors = 'surrogateescape'
344
+
345
+ def fsencode(filename):
346
+ if isinstance(filename, bytes):
347
+ return filename
348
+ elif isinstance(filename, text_type):
349
+ return filename.encode(_fsencoding, _fserrors)
350
+ else:
351
+ raise TypeError("expect bytes or str, not %s" %
352
+ type(filename).__name__)
353
+
354
+ def fsdecode(filename):
355
+ if isinstance(filename, text_type):
356
+ return filename
357
+ elif isinstance(filename, bytes):
358
+ return filename.decode(_fsencoding, _fserrors)
359
+ else:
360
+ raise TypeError("expect bytes or str, not %s" %
361
+ type(filename).__name__)
362
+
363
+
364
+ try:
365
+ from tokenize import detect_encoding
366
+ except ImportError: # pragma: no cover
367
+ from codecs import BOM_UTF8, lookup
368
+
369
+ cookie_re = re.compile(r"coding[:=]\s*([-\w.]+)")
370
+
371
+ def _get_normal_name(orig_enc):
372
+ """Imitates get_normal_name in tokenizer.c."""
373
+ # Only care about the first 12 characters.
374
+ enc = orig_enc[:12].lower().replace("_", "-")
375
+ if enc == "utf-8" or enc.startswith("utf-8-"):
376
+ return "utf-8"
377
+ if enc in ("latin-1", "iso-8859-1", "iso-latin-1") or \
378
+ enc.startswith(("latin-1-", "iso-8859-1-", "iso-latin-1-")):
379
+ return "iso-8859-1"
380
+ return orig_enc
381
+
382
+ def detect_encoding(readline):
383
+ """
384
+ The detect_encoding() function is used to detect the encoding that should
385
+ be used to decode a Python source file. It requires one argument, readline,
386
+ in the same way as the tokenize() generator.
387
+
388
+ It will call readline a maximum of twice, and return the encoding used
389
+ (as a string) and a list of any lines (left as bytes) it has read in.
390
+
391
+ It detects the encoding from the presence of a utf-8 bom or an encoding
392
+ cookie as specified in pep-0263. If both a bom and a cookie are present,
393
+ but disagree, a SyntaxError will be raised. If the encoding cookie is an
394
+ invalid charset, raise a SyntaxError. Note that if a utf-8 bom is found,
395
+ 'utf-8-sig' is returned.
396
+
397
+ If no encoding is specified, then the default of 'utf-8' will be returned.
398
+ """
399
+ try:
400
+ filename = readline.__self__.name
401
+ except AttributeError:
402
+ filename = None
403
+ bom_found = False
404
+ encoding = None
405
+ default = 'utf-8'
406
+
407
+ def read_or_stop():
408
+ try:
409
+ return readline()
410
+ except StopIteration:
411
+ return b''
412
+
413
+ def find_cookie(line):
414
+ try:
415
+ # Decode as UTF-8. Either the line is an encoding declaration,
416
+ # in which case it should be pure ASCII, or it must be UTF-8
417
+ # per default encoding.
418
+ line_string = line.decode('utf-8')
419
+ except UnicodeDecodeError:
420
+ msg = "invalid or missing encoding declaration"
421
+ if filename is not None:
422
+ msg = '{} for {!r}'.format(msg, filename)
423
+ raise SyntaxError(msg)
424
+
425
+ matches = cookie_re.findall(line_string)
426
+ if not matches:
427
+ return None
428
+ encoding = _get_normal_name(matches[0])
429
+ try:
430
+ codec = lookup(encoding)
431
+ except LookupError:
432
+ # This behaviour mimics the Python interpreter
433
+ if filename is None:
434
+ msg = "unknown encoding: " + encoding
435
+ else:
436
+ msg = "unknown encoding for {!r}: {}".format(
437
+ filename, encoding)
438
+ raise SyntaxError(msg)
439
+
440
+ if bom_found:
441
+ if codec.name != 'utf-8':
442
+ # This behaviour mimics the Python interpreter
443
+ if filename is None:
444
+ msg = 'encoding problem: utf-8'
445
+ else:
446
+ msg = 'encoding problem for {!r}: utf-8'.format(
447
+ filename)
448
+ raise SyntaxError(msg)
449
+ encoding += '-sig'
450
+ return encoding
451
+
452
+ first = read_or_stop()
453
+ if first.startswith(BOM_UTF8):
454
+ bom_found = True
455
+ first = first[3:]
456
+ default = 'utf-8-sig'
457
+ if not first:
458
+ return default, []
459
+
460
+ encoding = find_cookie(first)
461
+ if encoding:
462
+ return encoding, [first]
463
+
464
+ second = read_or_stop()
465
+ if not second:
466
+ return default, [first]
467
+
468
+ encoding = find_cookie(second)
469
+ if encoding:
470
+ return encoding, [first, second]
471
+
472
+ return default, [first, second]
473
+
474
+
475
+ # For converting & <-> &amp; etc.
476
+ try:
477
+ from html import escape
478
+ except ImportError:
479
+ from cgi import escape
480
+ if sys.version_info[:2] < (3, 4):
481
+ unescape = HTMLParser().unescape
482
+ else:
483
+ from html import unescape
484
+
485
+ try:
486
+ from collections import ChainMap
487
+ except ImportError: # pragma: no cover
488
+ from collections import MutableMapping
489
+
490
+ try:
491
+ from reprlib import recursive_repr as _recursive_repr
492
+ except ImportError:
493
+
494
+ def _recursive_repr(fillvalue='...'):
495
+ '''
496
+ Decorator to make a repr function return fillvalue for a recursive
497
+ call
498
+ '''
499
+
500
+ def decorating_function(user_function):
501
+ repr_running = set()
502
+
503
+ def wrapper(self):
504
+ key = id(self), get_ident()
505
+ if key in repr_running:
506
+ return fillvalue
507
+ repr_running.add(key)
508
+ try:
509
+ result = user_function(self)
510
+ finally:
511
+ repr_running.discard(key)
512
+ return result
513
+
514
+ # Can't use functools.wraps() here because of bootstrap issues
515
+ wrapper.__module__ = getattr(user_function, '__module__')
516
+ wrapper.__doc__ = getattr(user_function, '__doc__')
517
+ wrapper.__name__ = getattr(user_function, '__name__')
518
+ wrapper.__annotations__ = getattr(user_function,
519
+ '__annotations__', {})
520
+ return wrapper
521
+
522
+ return decorating_function
523
+
524
+ class ChainMap(MutableMapping):
525
+ '''
526
+ A ChainMap groups multiple dicts (or other mappings) together
527
+ to create a single, updateable view.
528
+
529
+ The underlying mappings are stored in a list. That list is public and can
530
+ accessed or updated using the *maps* attribute. There is no other state.
531
+
532
+ Lookups search the underlying mappings successively until a key is found.
533
+ In contrast, writes, updates, and deletions only operate on the first
534
+ mapping.
535
+ '''
536
+
537
+ def __init__(self, *maps):
538
+ '''Initialize a ChainMap by setting *maps* to the given mappings.
539
+ If no mappings are provided, a single empty dictionary is used.
540
+
541
+ '''
542
+ self.maps = list(maps) or [{}] # always at least one map
543
+
544
+ def __missing__(self, key):
545
+ raise KeyError(key)
546
+
547
+ def __getitem__(self, key):
548
+ for mapping in self.maps:
549
+ try:
550
+ return mapping[
551
+ key] # can't use 'key in mapping' with defaultdict
552
+ except KeyError:
553
+ pass
554
+ return self.__missing__(
555
+ key) # support subclasses that define __missing__
556
+
557
+ def get(self, key, default=None):
558
+ return self[key] if key in self else default
559
+
560
+ def __len__(self):
561
+ return len(set().union(
562
+ *self.maps)) # reuses stored hash values if possible
563
+
564
+ def __iter__(self):
565
+ return iter(set().union(*self.maps))
566
+
567
+ def __contains__(self, key):
568
+ return any(key in m for m in self.maps)
569
+
570
+ def __bool__(self):
571
+ return any(self.maps)
572
+
573
+ @_recursive_repr()
574
+ def __repr__(self):
575
+ return '{0.__class__.__name__}({1})'.format(
576
+ self, ', '.join(map(repr, self.maps)))
577
+
578
+ @classmethod
579
+ def fromkeys(cls, iterable, *args):
580
+ 'Create a ChainMap with a single dict created from the iterable.'
581
+ return cls(dict.fromkeys(iterable, *args))
582
+
583
+ def copy(self):
584
+ 'New ChainMap or subclass with a new copy of maps[0] and refs to maps[1:]'
585
+ return self.__class__(self.maps[0].copy(), *self.maps[1:])
586
+
587
+ __copy__ = copy
588
+
589
+ def new_child(self): # like Django's Context.push()
590
+ 'New ChainMap with a new dict followed by all previous maps.'
591
+ return self.__class__({}, *self.maps)
592
+
593
+ @property
594
+ def parents(self): # like Django's Context.pop()
595
+ 'New ChainMap from maps[1:].'
596
+ return self.__class__(*self.maps[1:])
597
+
598
+ def __setitem__(self, key, value):
599
+ self.maps[0][key] = value
600
+
601
+ def __delitem__(self, key):
602
+ try:
603
+ del self.maps[0][key]
604
+ except KeyError:
605
+ raise KeyError(
606
+ 'Key not found in the first mapping: {!r}'.format(key))
607
+
608
+ def popitem(self):
609
+ 'Remove and return an item pair from maps[0]. Raise KeyError is maps[0] is empty.'
610
+ try:
611
+ return self.maps[0].popitem()
612
+ except KeyError:
613
+ raise KeyError('No keys found in the first mapping.')
614
+
615
+ def pop(self, key, *args):
616
+ 'Remove *key* from maps[0] and return its value. Raise KeyError if *key* not in maps[0].'
617
+ try:
618
+ return self.maps[0].pop(key, *args)
619
+ except KeyError:
620
+ raise KeyError(
621
+ 'Key not found in the first mapping: {!r}'.format(key))
622
+
623
+ def clear(self):
624
+ 'Clear maps[0], leaving maps[1:] intact.'
625
+ self.maps[0].clear()
626
+
627
+
628
+ try:
629
+ from importlib.util import cache_from_source # Python >= 3.4
630
+ except ImportError: # pragma: no cover
631
+
632
+ def cache_from_source(path, debug_override=None):
633
+ assert path.endswith('.py')
634
+ if debug_override is None:
635
+ debug_override = __debug__
636
+ if debug_override:
637
+ suffix = 'c'
638
+ else:
639
+ suffix = 'o'
640
+ return path + suffix
641
+
642
+
643
+ try:
644
+ from collections import OrderedDict
645
+ except ImportError: # pragma: no cover
646
+ # {{{ http://code.activestate.com/recipes/576693/ (r9)
647
+ # Backport of OrderedDict() class that runs on Python 2.4, 2.5, 2.6, 2.7 and pypy.
648
+ # Passes Python2.7's test suite and incorporates all the latest updates.
649
+ try:
650
+ from thread import get_ident as _get_ident
651
+ except ImportError:
652
+ from dummy_thread import get_ident as _get_ident
653
+
654
+ try:
655
+ from _abcoll import KeysView, ValuesView, ItemsView
656
+ except ImportError:
657
+ pass
658
+
659
+ class OrderedDict(dict):
660
+ 'Dictionary that remembers insertion order'
661
+
662
+ # An inherited dict maps keys to values.
663
+ # The inherited dict provides __getitem__, __len__, __contains__, and get.
664
+ # The remaining methods are order-aware.
665
+ # Big-O running times for all methods are the same as for regular dictionaries.
666
+
667
+ # The internal self.__map dictionary maps keys to links in a doubly linked list.
668
+ # The circular doubly linked list starts and ends with a sentinel element.
669
+ # The sentinel element never gets deleted (this simplifies the algorithm).
670
+ # Each link is stored as a list of length three: [PREV, NEXT, KEY].
671
+
672
+ def __init__(self, *args, **kwds):
673
+ '''Initialize an ordered dictionary. Signature is the same as for
674
+ regular dictionaries, but keyword arguments are not recommended
675
+ because their insertion order is arbitrary.
676
+
677
+ '''
678
+ if len(args) > 1:
679
+ raise TypeError('expected at most 1 arguments, got %d' %
680
+ len(args))
681
+ try:
682
+ self.__root
683
+ except AttributeError:
684
+ self.__root = root = [] # sentinel node
685
+ root[:] = [root, root, None]
686
+ self.__map = {}
687
+ self.__update(*args, **kwds)
688
+
689
+ def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
690
+ 'od.__setitem__(i, y) <==> od[i]=y'
691
+ # Setting a new item creates a new link which goes at the end of the linked
692
+ # list, and the inherited dictionary is updated with the new key/value pair.
693
+ if key not in self:
694
+ root = self.__root
695
+ last = root[0]
696
+ last[1] = root[0] = self.__map[key] = [last, root, key]
697
+ dict_setitem(self, key, value)
698
+
699
+ def __delitem__(self, key, dict_delitem=dict.__delitem__):
700
+ 'od.__delitem__(y) <==> del od[y]'
701
+ # Deleting an existing item uses self.__map to find the link which is
702
+ # then removed by updating the links in the predecessor and successor nodes.
703
+ dict_delitem(self, key)
704
+ link_prev, link_next, key = self.__map.pop(key)
705
+ link_prev[1] = link_next
706
+ link_next[0] = link_prev
707
+
708
+ def __iter__(self):
709
+ 'od.__iter__() <==> iter(od)'
710
+ root = self.__root
711
+ curr = root[1]
712
+ while curr is not root:
713
+ yield curr[2]
714
+ curr = curr[1]
715
+
716
+ def __reversed__(self):
717
+ 'od.__reversed__() <==> reversed(od)'
718
+ root = self.__root
719
+ curr = root[0]
720
+ while curr is not root:
721
+ yield curr[2]
722
+ curr = curr[0]
723
+
724
+ def clear(self):
725
+ 'od.clear() -> None. Remove all items from od.'
726
+ try:
727
+ for node in self.__map.itervalues():
728
+ del node[:]
729
+ root = self.__root
730
+ root[:] = [root, root, None]
731
+ self.__map.clear()
732
+ except AttributeError:
733
+ pass
734
+ dict.clear(self)
735
+
736
+ def popitem(self, last=True):
737
+ '''od.popitem() -> (k, v), return and remove a (key, value) pair.
738
+ Pairs are returned in LIFO order if last is true or FIFO order if false.
739
+
740
+ '''
741
+ if not self:
742
+ raise KeyError('dictionary is empty')
743
+ root = self.__root
744
+ if last:
745
+ link = root[0]
746
+ link_prev = link[0]
747
+ link_prev[1] = root
748
+ root[0] = link_prev
749
+ else:
750
+ link = root[1]
751
+ link_next = link[1]
752
+ root[1] = link_next
753
+ link_next[0] = root
754
+ key = link[2]
755
+ del self.__map[key]
756
+ value = dict.pop(self, key)
757
+ return key, value
758
+
759
+ # -- the following methods do not depend on the internal structure --
760
+
761
+ def keys(self):
762
+ 'od.keys() -> list of keys in od'
763
+ return list(self)
764
+
765
+ def values(self):
766
+ 'od.values() -> list of values in od'
767
+ return [self[key] for key in self]
768
+
769
+ def items(self):
770
+ 'od.items() -> list of (key, value) pairs in od'
771
+ return [(key, self[key]) for key in self]
772
+
773
+ def iterkeys(self):
774
+ 'od.iterkeys() -> an iterator over the keys in od'
775
+ return iter(self)
776
+
777
+ def itervalues(self):
778
+ 'od.itervalues -> an iterator over the values in od'
779
+ for k in self:
780
+ yield self[k]
781
+
782
+ def iteritems(self):
783
+ 'od.iteritems -> an iterator over the (key, value) items in od'
784
+ for k in self:
785
+ yield (k, self[k])
786
+
787
+ def update(*args, **kwds):
788
+ '''od.update(E, **F) -> None. Update od from dict/iterable E and F.
789
+
790
+ If E is a dict instance, does: for k in E: od[k] = E[k]
791
+ If E has a .keys() method, does: for k in E.keys(): od[k] = E[k]
792
+ Or if E is an iterable of items, does: for k, v in E: od[k] = v
793
+ In either case, this is followed by: for k, v in F.items(): od[k] = v
794
+
795
+ '''
796
+ if len(args) > 2:
797
+ raise TypeError('update() takes at most 2 positional '
798
+ 'arguments (%d given)' % (len(args), ))
799
+ elif not args:
800
+ raise TypeError('update() takes at least 1 argument (0 given)')
801
+ self = args[0]
802
+ # Make progressively weaker assumptions about "other"
803
+ other = ()
804
+ if len(args) == 2:
805
+ other = args[1]
806
+ if isinstance(other, dict):
807
+ for key in other:
808
+ self[key] = other[key]
809
+ elif hasattr(other, 'keys'):
810
+ for key in other.keys():
811
+ self[key] = other[key]
812
+ else:
813
+ for key, value in other:
814
+ self[key] = value
815
+ for key, value in kwds.items():
816
+ self[key] = value
817
+
818
+ __update = update # let subclasses override update without breaking __init__
819
+
820
+ __marker = object()
821
+
822
+ def pop(self, key, default=__marker):
823
+ '''od.pop(k[,d]) -> v, remove specified key and return the corresponding value.
824
+ If key is not found, d is returned if given, otherwise KeyError is raised.
825
+
826
+ '''
827
+ if key in self:
828
+ result = self[key]
829
+ del self[key]
830
+ return result
831
+ if default is self.__marker:
832
+ raise KeyError(key)
833
+ return default
834
+
835
+ def setdefault(self, key, default=None):
836
+ 'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od'
837
+ if key in self:
838
+ return self[key]
839
+ self[key] = default
840
+ return default
841
+
842
+ def __repr__(self, _repr_running=None):
843
+ 'od.__repr__() <==> repr(od)'
844
+ if not _repr_running:
845
+ _repr_running = {}
846
+ call_key = id(self), _get_ident()
847
+ if call_key in _repr_running:
848
+ return '...'
849
+ _repr_running[call_key] = 1
850
+ try:
851
+ if not self:
852
+ return '%s()' % (self.__class__.__name__, )
853
+ return '%s(%r)' % (self.__class__.__name__, self.items())
854
+ finally:
855
+ del _repr_running[call_key]
856
+
857
+ def __reduce__(self):
858
+ 'Return state information for pickling'
859
+ items = [[k, self[k]] for k in self]
860
+ inst_dict = vars(self).copy()
861
+ for k in vars(OrderedDict()):
862
+ inst_dict.pop(k, None)
863
+ if inst_dict:
864
+ return (self.__class__, (items, ), inst_dict)
865
+ return self.__class__, (items, )
866
+
867
+ def copy(self):
868
+ 'od.copy() -> a shallow copy of od'
869
+ return self.__class__(self)
870
+
871
+ @classmethod
872
+ def fromkeys(cls, iterable, value=None):
873
+ '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S
874
+ and values equal to v (which defaults to None).
875
+
876
+ '''
877
+ d = cls()
878
+ for key in iterable:
879
+ d[key] = value
880
+ return d
881
+
882
+ def __eq__(self, other):
883
+ '''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive
884
+ while comparison to a regular mapping is order-insensitive.
885
+
886
+ '''
887
+ if isinstance(other, OrderedDict):
888
+ return len(self) == len(
889
+ other) and self.items() == other.items()
890
+ return dict.__eq__(self, other)
891
+
892
+ def __ne__(self, other):
893
+ return not self == other
894
+
895
+ # -- the following methods are only used in Python 2.7 --
896
+
897
+ def viewkeys(self):
898
+ "od.viewkeys() -> a set-like object providing a view on od's keys"
899
+ return KeysView(self)
900
+
901
+ def viewvalues(self):
902
+ "od.viewvalues() -> an object providing a view on od's values"
903
+ return ValuesView(self)
904
+
905
+ def viewitems(self):
906
+ "od.viewitems() -> a set-like object providing a view on od's items"
907
+ return ItemsView(self)
908
+
909
+
910
+ try:
911
+ from logging.config import BaseConfigurator, valid_ident
912
+ except ImportError: # pragma: no cover
913
+ IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I)
914
+
915
+ def valid_ident(s):
916
+ m = IDENTIFIER.match(s)
917
+ if not m:
918
+ raise ValueError('Not a valid Python identifier: %r' % s)
919
+ return True
920
+
921
+ # The ConvertingXXX classes are wrappers around standard Python containers,
922
+ # and they serve to convert any suitable values in the container. The
923
+ # conversion converts base dicts, lists and tuples to their wrapped
924
+ # equivalents, whereas strings which match a conversion format are converted
925
+ # appropriately.
926
+ #
927
+ # Each wrapper should have a configurator attribute holding the actual
928
+ # configurator to use for conversion.
929
+
930
+ class ConvertingDict(dict):
931
+ """A converting dictionary wrapper."""
932
+
933
+ def __getitem__(self, key):
934
+ value = dict.__getitem__(self, key)
935
+ result = self.configurator.convert(value)
936
+ # If the converted value is different, save for next time
937
+ if value is not result:
938
+ self[key] = result
939
+ if type(result) in (ConvertingDict, ConvertingList,
940
+ ConvertingTuple):
941
+ result.parent = self
942
+ result.key = key
943
+ return result
944
+
945
+ def get(self, key, default=None):
946
+ value = dict.get(self, key, default)
947
+ result = self.configurator.convert(value)
948
+ # If the converted value is different, save for next time
949
+ if value is not result:
950
+ self[key] = result
951
+ if type(result) in (ConvertingDict, ConvertingList,
952
+ ConvertingTuple):
953
+ result.parent = self
954
+ result.key = key
955
+ return result
956
+
957
+ def pop(self, key, default=None):
958
+ value = dict.pop(self, key, default)
959
+ result = self.configurator.convert(value)
960
+ if value is not result:
961
+ if type(result) in (ConvertingDict, ConvertingList,
962
+ ConvertingTuple):
963
+ result.parent = self
964
+ result.key = key
965
+ return result
966
+
967
+ class ConvertingList(list):
968
+ """A converting list wrapper."""
969
+
970
+ def __getitem__(self, key):
971
+ value = list.__getitem__(self, key)
972
+ result = self.configurator.convert(value)
973
+ # If the converted value is different, save for next time
974
+ if value is not result:
975
+ self[key] = result
976
+ if type(result) in (ConvertingDict, ConvertingList,
977
+ ConvertingTuple):
978
+ result.parent = self
979
+ result.key = key
980
+ return result
981
+
982
+ def pop(self, idx=-1):
983
+ value = list.pop(self, idx)
984
+ result = self.configurator.convert(value)
985
+ if value is not result:
986
+ if type(result) in (ConvertingDict, ConvertingList,
987
+ ConvertingTuple):
988
+ result.parent = self
989
+ return result
990
+
991
+ class ConvertingTuple(tuple):
992
+ """A converting tuple wrapper."""
993
+
994
+ def __getitem__(self, key):
995
+ value = tuple.__getitem__(self, key)
996
+ result = self.configurator.convert(value)
997
+ if value is not result:
998
+ if type(result) in (ConvertingDict, ConvertingList,
999
+ ConvertingTuple):
1000
+ result.parent = self
1001
+ result.key = key
1002
+ return result
1003
+
1004
+ class BaseConfigurator(object):
1005
+ """
1006
+ The configurator base class which defines some useful defaults.
1007
+ """
1008
+
1009
+ CONVERT_PATTERN = re.compile(r'^(?P<prefix>[a-z]+)://(?P<suffix>.*)$')
1010
+
1011
+ WORD_PATTERN = re.compile(r'^\s*(\w+)\s*')
1012
+ DOT_PATTERN = re.compile(r'^\.\s*(\w+)\s*')
1013
+ INDEX_PATTERN = re.compile(r'^\[\s*(\w+)\s*\]\s*')
1014
+ DIGIT_PATTERN = re.compile(r'^\d+$')
1015
+
1016
+ value_converters = {
1017
+ 'ext': 'ext_convert',
1018
+ 'cfg': 'cfg_convert',
1019
+ }
1020
+
1021
+ # We might want to use a different one, e.g. importlib
1022
+ importer = staticmethod(__import__)
1023
+
1024
+ def __init__(self, config):
1025
+ self.config = ConvertingDict(config)
1026
+ self.config.configurator = self
1027
+
1028
+ def resolve(self, s):
1029
+ """
1030
+ Resolve strings to objects using standard import and attribute
1031
+ syntax.
1032
+ """
1033
+ name = s.split('.')
1034
+ used = name.pop(0)
1035
+ try:
1036
+ found = self.importer(used)
1037
+ for frag in name:
1038
+ used += '.' + frag
1039
+ try:
1040
+ found = getattr(found, frag)
1041
+ except AttributeError:
1042
+ self.importer(used)
1043
+ found = getattr(found, frag)
1044
+ return found
1045
+ except ImportError:
1046
+ e, tb = sys.exc_info()[1:]
1047
+ v = ValueError('Cannot resolve %r: %s' % (s, e))
1048
+ v.__cause__, v.__traceback__ = e, tb
1049
+ raise v
1050
+
1051
+ def ext_convert(self, value):
1052
+ """Default converter for the ext:// protocol."""
1053
+ return self.resolve(value)
1054
+
1055
+ def cfg_convert(self, value):
1056
+ """Default converter for the cfg:// protocol."""
1057
+ rest = value
1058
+ m = self.WORD_PATTERN.match(rest)
1059
+ if m is None:
1060
+ raise ValueError("Unable to convert %r" % value)
1061
+ else:
1062
+ rest = rest[m.end():]
1063
+ d = self.config[m.groups()[0]]
1064
+ while rest:
1065
+ m = self.DOT_PATTERN.match(rest)
1066
+ if m:
1067
+ d = d[m.groups()[0]]
1068
+ else:
1069
+ m = self.INDEX_PATTERN.match(rest)
1070
+ if m:
1071
+ idx = m.groups()[0]
1072
+ if not self.DIGIT_PATTERN.match(idx):
1073
+ d = d[idx]
1074
+ else:
1075
+ try:
1076
+ n = int(
1077
+ idx
1078
+ ) # try as number first (most likely)
1079
+ d = d[n]
1080
+ except TypeError:
1081
+ d = d[idx]
1082
+ if m:
1083
+ rest = rest[m.end():]
1084
+ else:
1085
+ raise ValueError('Unable to convert '
1086
+ '%r at %r' % (value, rest))
1087
+ # rest should be empty
1088
+ return d
1089
+
1090
+ def convert(self, value):
1091
+ """
1092
+ Convert values to an appropriate type. dicts, lists and tuples are
1093
+ replaced by their converting alternatives. Strings are checked to
1094
+ see if they have a conversion format and are converted if they do.
1095
+ """
1096
+ if not isinstance(value, ConvertingDict) and isinstance(
1097
+ value, dict):
1098
+ value = ConvertingDict(value)
1099
+ value.configurator = self
1100
+ elif not isinstance(value, ConvertingList) and isinstance(
1101
+ value, list):
1102
+ value = ConvertingList(value)
1103
+ value.configurator = self
1104
+ elif not isinstance(value, ConvertingTuple) and isinstance(value, tuple):
1105
+ value = ConvertingTuple(value)
1106
+ value.configurator = self
1107
+ elif isinstance(value, string_types):
1108
+ m = self.CONVERT_PATTERN.match(value)
1109
+ if m:
1110
+ d = m.groupdict()
1111
+ prefix = d['prefix']
1112
+ converter = self.value_converters.get(prefix, None)
1113
+ if converter:
1114
+ suffix = d['suffix']
1115
+ converter = getattr(self, converter)
1116
+ value = converter(suffix)
1117
+ return value
1118
+
1119
+ def configure_custom(self, config):
1120
+ """Configure an object with a user-supplied factory."""
1121
+ c = config.pop('()')
1122
+ if not callable(c):
1123
+ c = self.resolve(c)
1124
+ props = config.pop('.', None)
1125
+ # Check for valid identifiers
1126
+ kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
1127
+ result = c(**kwargs)
1128
+ if props:
1129
+ for name, value in props.items():
1130
+ setattr(result, name, value)
1131
+ return result
1132
+
1133
+ def as_tuple(self, value):
1134
+ """Utility function which converts lists to tuples."""
1135
+ if isinstance(value, list):
1136
+ value = tuple(value)
1137
+ return value
lib/python3.12/site-packages/pip/_vendor/distlib/database.py ADDED
@@ -0,0 +1,1329 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2012-2023 The Python Software Foundation.
4
+ # See LICENSE.txt and CONTRIBUTORS.txt.
5
+ #
6
+ """PEP 376 implementation."""
7
+
8
+ from __future__ import unicode_literals
9
+
10
+ import base64
11
+ import codecs
12
+ import contextlib
13
+ import hashlib
14
+ import logging
15
+ import os
16
+ import posixpath
17
+ import sys
18
+ import zipimport
19
+
20
+ from . import DistlibException, resources
21
+ from .compat import StringIO
22
+ from .version import get_scheme, UnsupportedVersionError
23
+ from .metadata import (Metadata, METADATA_FILENAME, WHEEL_METADATA_FILENAME, LEGACY_METADATA_FILENAME)
24
+ from .util import (parse_requirement, cached_property, parse_name_and_version, read_exports, write_exports, CSVReader,
25
+ CSVWriter)
26
+
27
+ __all__ = [
28
+ 'Distribution', 'BaseInstalledDistribution', 'InstalledDistribution', 'EggInfoDistribution', 'DistributionPath'
29
+ ]
30
+
31
+ logger = logging.getLogger(__name__)
32
+
33
+ EXPORTS_FILENAME = 'pydist-exports.json'
34
+ COMMANDS_FILENAME = 'pydist-commands.json'
35
+
36
+ DIST_FILES = ('INSTALLER', METADATA_FILENAME, 'RECORD', 'REQUESTED', 'RESOURCES', EXPORTS_FILENAME, 'SHARED')
37
+
38
+ DISTINFO_EXT = '.dist-info'
39
+
40
+
41
+ class _Cache(object):
42
+ """
43
+ A simple cache mapping names and .dist-info paths to distributions
44
+ """
45
+
46
+ def __init__(self):
47
+ """
48
+ Initialise an instance. There is normally one for each DistributionPath.
49
+ """
50
+ self.name = {}
51
+ self.path = {}
52
+ self.generated = False
53
+
54
+ def clear(self):
55
+ """
56
+ Clear the cache, setting it to its initial state.
57
+ """
58
+ self.name.clear()
59
+ self.path.clear()
60
+ self.generated = False
61
+
62
+ def add(self, dist):
63
+ """
64
+ Add a distribution to the cache.
65
+ :param dist: The distribution to add.
66
+ """
67
+ if dist.path not in self.path:
68
+ self.path[dist.path] = dist
69
+ self.name.setdefault(dist.key, []).append(dist)
70
+
71
+
72
+ class DistributionPath(object):
73
+ """
74
+ Represents a set of distributions installed on a path (typically sys.path).
75
+ """
76
+
77
+ def __init__(self, path=None, include_egg=False):
78
+ """
79
+ Create an instance from a path, optionally including legacy (distutils/
80
+ setuptools/distribute) distributions.
81
+ :param path: The path to use, as a list of directories. If not specified,
82
+ sys.path is used.
83
+ :param include_egg: If True, this instance will look for and return legacy
84
+ distributions as well as those based on PEP 376.
85
+ """
86
+ if path is None:
87
+ path = sys.path
88
+ self.path = path
89
+ self._include_dist = True
90
+ self._include_egg = include_egg
91
+
92
+ self._cache = _Cache()
93
+ self._cache_egg = _Cache()
94
+ self._cache_enabled = True
95
+ self._scheme = get_scheme('default')
96
+
97
+ def _get_cache_enabled(self):
98
+ return self._cache_enabled
99
+
100
+ def _set_cache_enabled(self, value):
101
+ self._cache_enabled = value
102
+
103
+ cache_enabled = property(_get_cache_enabled, _set_cache_enabled)
104
+
105
+ def clear_cache(self):
106
+ """
107
+ Clears the internal cache.
108
+ """
109
+ self._cache.clear()
110
+ self._cache_egg.clear()
111
+
112
+ def _yield_distributions(self):
113
+ """
114
+ Yield .dist-info and/or .egg(-info) distributions.
115
+ """
116
+ # We need to check if we've seen some resources already, because on
117
+ # some Linux systems (e.g. some Debian/Ubuntu variants) there are
118
+ # symlinks which alias other files in the environment.
119
+ seen = set()
120
+ for path in self.path:
121
+ finder = resources.finder_for_path(path)
122
+ if finder is None:
123
+ continue
124
+ r = finder.find('')
125
+ if not r or not r.is_container:
126
+ continue
127
+ rset = sorted(r.resources)
128
+ for entry in rset:
129
+ r = finder.find(entry)
130
+ if not r or r.path in seen:
131
+ continue
132
+ try:
133
+ if self._include_dist and entry.endswith(DISTINFO_EXT):
134
+ possible_filenames = [METADATA_FILENAME, WHEEL_METADATA_FILENAME, LEGACY_METADATA_FILENAME]
135
+ for metadata_filename in possible_filenames:
136
+ metadata_path = posixpath.join(entry, metadata_filename)
137
+ pydist = finder.find(metadata_path)
138
+ if pydist:
139
+ break
140
+ else:
141
+ continue
142
+
143
+ with contextlib.closing(pydist.as_stream()) as stream:
144
+ metadata = Metadata(fileobj=stream, scheme='legacy')
145
+ logger.debug('Found %s', r.path)
146
+ seen.add(r.path)
147
+ yield new_dist_class(r.path, metadata=metadata, env=self)
148
+ elif self._include_egg and entry.endswith(('.egg-info', '.egg')):
149
+ logger.debug('Found %s', r.path)
150
+ seen.add(r.path)
151
+ yield old_dist_class(r.path, self)
152
+ except Exception as e:
153
+ msg = 'Unable to read distribution at %s, perhaps due to bad metadata: %s'
154
+ logger.warning(msg, r.path, e)
155
+ import warnings
156
+ warnings.warn(msg % (r.path, e), stacklevel=2)
157
+
158
+ def _generate_cache(self):
159
+ """
160
+ Scan the path for distributions and populate the cache with
161
+ those that are found.
162
+ """
163
+ gen_dist = not self._cache.generated
164
+ gen_egg = self._include_egg and not self._cache_egg.generated
165
+ if gen_dist or gen_egg:
166
+ for dist in self._yield_distributions():
167
+ if isinstance(dist, InstalledDistribution):
168
+ self._cache.add(dist)
169
+ else:
170
+ self._cache_egg.add(dist)
171
+
172
+ if gen_dist:
173
+ self._cache.generated = True
174
+ if gen_egg:
175
+ self._cache_egg.generated = True
176
+
177
+ @classmethod
178
+ def distinfo_dirname(cls, name, version):
179
+ """
180
+ The *name* and *version* parameters are converted into their
181
+ filename-escaped form, i.e. any ``'-'`` characters are replaced
182
+ with ``'_'`` other than the one in ``'dist-info'`` and the one
183
+ separating the name from the version number.
184
+
185
+ :parameter name: is converted to a standard distribution name by replacing
186
+ any runs of non- alphanumeric characters with a single
187
+ ``'-'``.
188
+ :type name: string
189
+ :parameter version: is converted to a standard version string. Spaces
190
+ become dots, and all other non-alphanumeric characters
191
+ (except dots) become dashes, with runs of multiple
192
+ dashes condensed to a single dash.
193
+ :type version: string
194
+ :returns: directory name
195
+ :rtype: string"""
196
+ name = name.replace('-', '_')
197
+ return '-'.join([name, version]) + DISTINFO_EXT
198
+
199
+ def get_distributions(self):
200
+ """
201
+ Provides an iterator that looks for distributions and returns
202
+ :class:`InstalledDistribution` or
203
+ :class:`EggInfoDistribution` instances for each one of them.
204
+
205
+ :rtype: iterator of :class:`InstalledDistribution` and
206
+ :class:`EggInfoDistribution` instances
207
+ """
208
+ if not self._cache_enabled:
209
+ for dist in self._yield_distributions():
210
+ yield dist
211
+ else:
212
+ self._generate_cache()
213
+
214
+ for dist in self._cache.path.values():
215
+ yield dist
216
+
217
+ if self._include_egg:
218
+ for dist in self._cache_egg.path.values():
219
+ yield dist
220
+
221
+ def get_distribution(self, name):
222
+ """
223
+ Looks for a named distribution on the path.
224
+
225
+ This function only returns the first result found, as no more than one
226
+ value is expected. If nothing is found, ``None`` is returned.
227
+
228
+ :rtype: :class:`InstalledDistribution`, :class:`EggInfoDistribution`
229
+ or ``None``
230
+ """
231
+ result = None
232
+ name = name.lower()
233
+ if not self._cache_enabled:
234
+ for dist in self._yield_distributions():
235
+ if dist.key == name:
236
+ result = dist
237
+ break
238
+ else:
239
+ self._generate_cache()
240
+
241
+ if name in self._cache.name:
242
+ result = self._cache.name[name][0]
243
+ elif self._include_egg and name in self._cache_egg.name:
244
+ result = self._cache_egg.name[name][0]
245
+ return result
246
+
247
+ def provides_distribution(self, name, version=None):
248
+ """
249
+ Iterates over all distributions to find which distributions provide *name*.
250
+ If a *version* is provided, it will be used to filter the results.
251
+
252
+ This function only returns the first result found, since no more than
253
+ one values are expected. If the directory is not found, returns ``None``.
254
+
255
+ :parameter version: a version specifier that indicates the version
256
+ required, conforming to the format in ``PEP-345``
257
+
258
+ :type name: string
259
+ :type version: string
260
+ """
261
+ matcher = None
262
+ if version is not None:
263
+ try:
264
+ matcher = self._scheme.matcher('%s (%s)' % (name, version))
265
+ except ValueError:
266
+ raise DistlibException('invalid name or version: %r, %r' % (name, version))
267
+
268
+ for dist in self.get_distributions():
269
+ # We hit a problem on Travis where enum34 was installed and doesn't
270
+ # have a provides attribute ...
271
+ if not hasattr(dist, 'provides'):
272
+ logger.debug('No "provides": %s', dist)
273
+ else:
274
+ provided = dist.provides
275
+
276
+ for p in provided:
277
+ p_name, p_ver = parse_name_and_version(p)
278
+ if matcher is None:
279
+ if p_name == name:
280
+ yield dist
281
+ break
282
+ else:
283
+ if p_name == name and matcher.match(p_ver):
284
+ yield dist
285
+ break
286
+
287
+ def get_file_path(self, name, relative_path):
288
+ """
289
+ Return the path to a resource file.
290
+ """
291
+ dist = self.get_distribution(name)
292
+ if dist is None:
293
+ raise LookupError('no distribution named %r found' % name)
294
+ return dist.get_resource_path(relative_path)
295
+
296
+ def get_exported_entries(self, category, name=None):
297
+ """
298
+ Return all of the exported entries in a particular category.
299
+
300
+ :param category: The category to search for entries.
301
+ :param name: If specified, only entries with that name are returned.
302
+ """
303
+ for dist in self.get_distributions():
304
+ r = dist.exports
305
+ if category in r:
306
+ d = r[category]
307
+ if name is not None:
308
+ if name in d:
309
+ yield d[name]
310
+ else:
311
+ for v in d.values():
312
+ yield v
313
+
314
+
315
+ class Distribution(object):
316
+ """
317
+ A base class for distributions, whether installed or from indexes.
318
+ Either way, it must have some metadata, so that's all that's needed
319
+ for construction.
320
+ """
321
+
322
+ build_time_dependency = False
323
+ """
324
+ Set to True if it's known to be only a build-time dependency (i.e.
325
+ not needed after installation).
326
+ """
327
+
328
+ requested = False
329
+ """A boolean that indicates whether the ``REQUESTED`` metadata file is
330
+ present (in other words, whether the package was installed by user
331
+ request or it was installed as a dependency)."""
332
+
333
+ def __init__(self, metadata):
334
+ """
335
+ Initialise an instance.
336
+ :param metadata: The instance of :class:`Metadata` describing this
337
+ distribution.
338
+ """
339
+ self.metadata = metadata
340
+ self.name = metadata.name
341
+ self.key = self.name.lower() # for case-insensitive comparisons
342
+ self.version = metadata.version
343
+ self.locator = None
344
+ self.digest = None
345
+ self.extras = None # additional features requested
346
+ self.context = None # environment marker overrides
347
+ self.download_urls = set()
348
+ self.digests = {}
349
+
350
+ @property
351
+ def source_url(self):
352
+ """
353
+ The source archive download URL for this distribution.
354
+ """
355
+ return self.metadata.source_url
356
+
357
+ download_url = source_url # Backward compatibility
358
+
359
+ @property
360
+ def name_and_version(self):
361
+ """
362
+ A utility property which displays the name and version in parentheses.
363
+ """
364
+ return '%s (%s)' % (self.name, self.version)
365
+
366
+ @property
367
+ def provides(self):
368
+ """
369
+ A set of distribution names and versions provided by this distribution.
370
+ :return: A set of "name (version)" strings.
371
+ """
372
+ plist = self.metadata.provides
373
+ s = '%s (%s)' % (self.name, self.version)
374
+ if s not in plist:
375
+ plist.append(s)
376
+ return plist
377
+
378
+ def _get_requirements(self, req_attr):
379
+ md = self.metadata
380
+ reqts = getattr(md, req_attr)
381
+ logger.debug('%s: got requirements %r from metadata: %r', self.name, req_attr, reqts)
382
+ return set(md.get_requirements(reqts, extras=self.extras, env=self.context))
383
+
384
+ @property
385
+ def run_requires(self):
386
+ return self._get_requirements('run_requires')
387
+
388
+ @property
389
+ def meta_requires(self):
390
+ return self._get_requirements('meta_requires')
391
+
392
+ @property
393
+ def build_requires(self):
394
+ return self._get_requirements('build_requires')
395
+
396
+ @property
397
+ def test_requires(self):
398
+ return self._get_requirements('test_requires')
399
+
400
+ @property
401
+ def dev_requires(self):
402
+ return self._get_requirements('dev_requires')
403
+
404
+ def matches_requirement(self, req):
405
+ """
406
+ Say if this instance matches (fulfills) a requirement.
407
+ :param req: The requirement to match.
408
+ :rtype req: str
409
+ :return: True if it matches, else False.
410
+ """
411
+ # Requirement may contain extras - parse to lose those
412
+ # from what's passed to the matcher
413
+ r = parse_requirement(req)
414
+ scheme = get_scheme(self.metadata.scheme)
415
+ try:
416
+ matcher = scheme.matcher(r.requirement)
417
+ except UnsupportedVersionError:
418
+ # XXX compat-mode if cannot read the version
419
+ logger.warning('could not read version %r - using name only', req)
420
+ name = req.split()[0]
421
+ matcher = scheme.matcher(name)
422
+
423
+ name = matcher.key # case-insensitive
424
+
425
+ result = False
426
+ for p in self.provides:
427
+ p_name, p_ver = parse_name_and_version(p)
428
+ if p_name != name:
429
+ continue
430
+ try:
431
+ result = matcher.match(p_ver)
432
+ break
433
+ except UnsupportedVersionError:
434
+ pass
435
+ return result
436
+
437
+ def __repr__(self):
438
+ """
439
+ Return a textual representation of this instance,
440
+ """
441
+ if self.source_url:
442
+ suffix = ' [%s]' % self.source_url
443
+ else:
444
+ suffix = ''
445
+ return '<Distribution %s (%s)%s>' % (self.name, self.version, suffix)
446
+
447
+ def __eq__(self, other):
448
+ """
449
+ See if this distribution is the same as another.
450
+ :param other: The distribution to compare with. To be equal to one
451
+ another. distributions must have the same type, name,
452
+ version and source_url.
453
+ :return: True if it is the same, else False.
454
+ """
455
+ if type(other) is not type(self):
456
+ result = False
457
+ else:
458
+ result = (self.name == other.name and self.version == other.version and self.source_url == other.source_url)
459
+ return result
460
+
461
+ def __hash__(self):
462
+ """
463
+ Compute hash in a way which matches the equality test.
464
+ """
465
+ return hash(self.name) + hash(self.version) + hash(self.source_url)
466
+
467
+
468
+ class BaseInstalledDistribution(Distribution):
469
+ """
470
+ This is the base class for installed distributions (whether PEP 376 or
471
+ legacy).
472
+ """
473
+
474
+ hasher = None
475
+
476
+ def __init__(self, metadata, path, env=None):
477
+ """
478
+ Initialise an instance.
479
+ :param metadata: An instance of :class:`Metadata` which describes the
480
+ distribution. This will normally have been initialised
481
+ from a metadata file in the ``path``.
482
+ :param path: The path of the ``.dist-info`` or ``.egg-info``
483
+ directory for the distribution.
484
+ :param env: This is normally the :class:`DistributionPath`
485
+ instance where this distribution was found.
486
+ """
487
+ super(BaseInstalledDistribution, self).__init__(metadata)
488
+ self.path = path
489
+ self.dist_path = env
490
+
491
+ def get_hash(self, data, hasher=None):
492
+ """
493
+ Get the hash of some data, using a particular hash algorithm, if
494
+ specified.
495
+
496
+ :param data: The data to be hashed.
497
+ :type data: bytes
498
+ :param hasher: The name of a hash implementation, supported by hashlib,
499
+ or ``None``. Examples of valid values are ``'sha1'``,
500
+ ``'sha224'``, ``'sha384'``, '``sha256'``, ``'md5'`` and
501
+ ``'sha512'``. If no hasher is specified, the ``hasher``
502
+ attribute of the :class:`InstalledDistribution` instance
503
+ is used. If the hasher is determined to be ``None``, MD5
504
+ is used as the hashing algorithm.
505
+ :returns: The hash of the data. If a hasher was explicitly specified,
506
+ the returned hash will be prefixed with the specified hasher
507
+ followed by '='.
508
+ :rtype: str
509
+ """
510
+ if hasher is None:
511
+ hasher = self.hasher
512
+ if hasher is None:
513
+ hasher = hashlib.md5
514
+ prefix = ''
515
+ else:
516
+ hasher = getattr(hashlib, hasher)
517
+ prefix = '%s=' % self.hasher
518
+ digest = hasher(data).digest()
519
+ digest = base64.urlsafe_b64encode(digest).rstrip(b'=').decode('ascii')
520
+ return '%s%s' % (prefix, digest)
521
+
522
+
523
+ class InstalledDistribution(BaseInstalledDistribution):
524
+ """
525
+ Created with the *path* of the ``.dist-info`` directory provided to the
526
+ constructor. It reads the metadata contained in ``pydist.json`` when it is
527
+ instantiated., or uses a passed in Metadata instance (useful for when
528
+ dry-run mode is being used).
529
+ """
530
+
531
+ hasher = 'sha256'
532
+
533
+ def __init__(self, path, metadata=None, env=None):
534
+ self.modules = []
535
+ self.finder = finder = resources.finder_for_path(path)
536
+ if finder is None:
537
+ raise ValueError('finder unavailable for %s' % path)
538
+ if env and env._cache_enabled and path in env._cache.path:
539
+ metadata = env._cache.path[path].metadata
540
+ elif metadata is None:
541
+ r = finder.find(METADATA_FILENAME)
542
+ # Temporary - for Wheel 0.23 support
543
+ if r is None:
544
+ r = finder.find(WHEEL_METADATA_FILENAME)
545
+ # Temporary - for legacy support
546
+ if r is None:
547
+ r = finder.find(LEGACY_METADATA_FILENAME)
548
+ if r is None:
549
+ raise ValueError('no %s found in %s' % (METADATA_FILENAME, path))
550
+ with contextlib.closing(r.as_stream()) as stream:
551
+ metadata = Metadata(fileobj=stream, scheme='legacy')
552
+
553
+ super(InstalledDistribution, self).__init__(metadata, path, env)
554
+
555
+ if env and env._cache_enabled:
556
+ env._cache.add(self)
557
+
558
+ r = finder.find('REQUESTED')
559
+ self.requested = r is not None
560
+ p = os.path.join(path, 'top_level.txt')
561
+ if os.path.exists(p):
562
+ with open(p, 'rb') as f:
563
+ data = f.read().decode('utf-8')
564
+ self.modules = data.splitlines()
565
+
566
+ def __repr__(self):
567
+ return '<InstalledDistribution %r %s at %r>' % (self.name, self.version, self.path)
568
+
569
+ def __str__(self):
570
+ return "%s %s" % (self.name, self.version)
571
+
572
+ def _get_records(self):
573
+ """
574
+ Get the list of installed files for the distribution
575
+ :return: A list of tuples of path, hash and size. Note that hash and
576
+ size might be ``None`` for some entries. The path is exactly
577
+ as stored in the file (which is as in PEP 376).
578
+ """
579
+ results = []
580
+ r = self.get_distinfo_resource('RECORD')
581
+ with contextlib.closing(r.as_stream()) as stream:
582
+ with CSVReader(stream=stream) as record_reader:
583
+ # Base location is parent dir of .dist-info dir
584
+ # base_location = os.path.dirname(self.path)
585
+ # base_location = os.path.abspath(base_location)
586
+ for row in record_reader:
587
+ missing = [None for i in range(len(row), 3)]
588
+ path, checksum, size = row + missing
589
+ # if not os.path.isabs(path):
590
+ # path = path.replace('/', os.sep)
591
+ # path = os.path.join(base_location, path)
592
+ results.append((path, checksum, size))
593
+ return results
594
+
595
+ @cached_property
596
+ def exports(self):
597
+ """
598
+ Return the information exported by this distribution.
599
+ :return: A dictionary of exports, mapping an export category to a dict
600
+ of :class:`ExportEntry` instances describing the individual
601
+ export entries, and keyed by name.
602
+ """
603
+ result = {}
604
+ r = self.get_distinfo_resource(EXPORTS_FILENAME)
605
+ if r:
606
+ result = self.read_exports()
607
+ return result
608
+
609
+ def read_exports(self):
610
+ """
611
+ Read exports data from a file in .ini format.
612
+
613
+ :return: A dictionary of exports, mapping an export category to a list
614
+ of :class:`ExportEntry` instances describing the individual
615
+ export entries.
616
+ """
617
+ result = {}
618
+ r = self.get_distinfo_resource(EXPORTS_FILENAME)
619
+ if r:
620
+ with contextlib.closing(r.as_stream()) as stream:
621
+ result = read_exports(stream)
622
+ return result
623
+
624
+ def write_exports(self, exports):
625
+ """
626
+ Write a dictionary of exports to a file in .ini format.
627
+ :param exports: A dictionary of exports, mapping an export category to
628
+ a list of :class:`ExportEntry` instances describing the
629
+ individual export entries.
630
+ """
631
+ rf = self.get_distinfo_file(EXPORTS_FILENAME)
632
+ with open(rf, 'w') as f:
633
+ write_exports(exports, f)
634
+
635
+ def get_resource_path(self, relative_path):
636
+ """
637
+ NOTE: This API may change in the future.
638
+
639
+ Return the absolute path to a resource file with the given relative
640
+ path.
641
+
642
+ :param relative_path: The path, relative to .dist-info, of the resource
643
+ of interest.
644
+ :return: The absolute path where the resource is to be found.
645
+ """
646
+ r = self.get_distinfo_resource('RESOURCES')
647
+ with contextlib.closing(r.as_stream()) as stream:
648
+ with CSVReader(stream=stream) as resources_reader:
649
+ for relative, destination in resources_reader:
650
+ if relative == relative_path:
651
+ return destination
652
+ raise KeyError('no resource file with relative path %r '
653
+ 'is installed' % relative_path)
654
+
655
+ def list_installed_files(self):
656
+ """
657
+ Iterates over the ``RECORD`` entries and returns a tuple
658
+ ``(path, hash, size)`` for each line.
659
+
660
+ :returns: iterator of (path, hash, size)
661
+ """
662
+ for result in self._get_records():
663
+ yield result
664
+
665
+ def write_installed_files(self, paths, prefix, dry_run=False):
666
+ """
667
+ Writes the ``RECORD`` file, using the ``paths`` iterable passed in. Any
668
+ existing ``RECORD`` file is silently overwritten.
669
+
670
+ prefix is used to determine when to write absolute paths.
671
+ """
672
+ prefix = os.path.join(prefix, '')
673
+ base = os.path.dirname(self.path)
674
+ base_under_prefix = base.startswith(prefix)
675
+ base = os.path.join(base, '')
676
+ record_path = self.get_distinfo_file('RECORD')
677
+ logger.info('creating %s', record_path)
678
+ if dry_run:
679
+ return None
680
+ with CSVWriter(record_path) as writer:
681
+ for path in paths:
682
+ if os.path.isdir(path) or path.endswith(('.pyc', '.pyo')):
683
+ # do not put size and hash, as in PEP-376
684
+ hash_value = size = ''
685
+ else:
686
+ size = '%d' % os.path.getsize(path)
687
+ with open(path, 'rb') as fp:
688
+ hash_value = self.get_hash(fp.read())
689
+ if path.startswith(base) or (base_under_prefix and path.startswith(prefix)):
690
+ path = os.path.relpath(path, base)
691
+ writer.writerow((path, hash_value, size))
692
+
693
+ # add the RECORD file itself
694
+ if record_path.startswith(base):
695
+ record_path = os.path.relpath(record_path, base)
696
+ writer.writerow((record_path, '', ''))
697
+ return record_path
698
+
699
+ def check_installed_files(self):
700
+ """
701
+ Checks that the hashes and sizes of the files in ``RECORD`` are
702
+ matched by the files themselves. Returns a (possibly empty) list of
703
+ mismatches. Each entry in the mismatch list will be a tuple consisting
704
+ of the path, 'exists', 'size' or 'hash' according to what didn't match
705
+ (existence is checked first, then size, then hash), the expected
706
+ value and the actual value.
707
+ """
708
+ mismatches = []
709
+ base = os.path.dirname(self.path)
710
+ record_path = self.get_distinfo_file('RECORD')
711
+ for path, hash_value, size in self.list_installed_files():
712
+ if not os.path.isabs(path):
713
+ path = os.path.join(base, path)
714
+ if path == record_path:
715
+ continue
716
+ if not os.path.exists(path):
717
+ mismatches.append((path, 'exists', True, False))
718
+ elif os.path.isfile(path):
719
+ actual_size = str(os.path.getsize(path))
720
+ if size and actual_size != size:
721
+ mismatches.append((path, 'size', size, actual_size))
722
+ elif hash_value:
723
+ if '=' in hash_value:
724
+ hasher = hash_value.split('=', 1)[0]
725
+ else:
726
+ hasher = None
727
+
728
+ with open(path, 'rb') as f:
729
+ actual_hash = self.get_hash(f.read(), hasher)
730
+ if actual_hash != hash_value:
731
+ mismatches.append((path, 'hash', hash_value, actual_hash))
732
+ return mismatches
733
+
734
+ @cached_property
735
+ def shared_locations(self):
736
+ """
737
+ A dictionary of shared locations whose keys are in the set 'prefix',
738
+ 'purelib', 'platlib', 'scripts', 'headers', 'data' and 'namespace'.
739
+ The corresponding value is the absolute path of that category for
740
+ this distribution, and takes into account any paths selected by the
741
+ user at installation time (e.g. via command-line arguments). In the
742
+ case of the 'namespace' key, this would be a list of absolute paths
743
+ for the roots of namespace packages in this distribution.
744
+
745
+ The first time this property is accessed, the relevant information is
746
+ read from the SHARED file in the .dist-info directory.
747
+ """
748
+ result = {}
749
+ shared_path = os.path.join(self.path, 'SHARED')
750
+ if os.path.isfile(shared_path):
751
+ with codecs.open(shared_path, 'r', encoding='utf-8') as f:
752
+ lines = f.read().splitlines()
753
+ for line in lines:
754
+ key, value = line.split('=', 1)
755
+ if key == 'namespace':
756
+ result.setdefault(key, []).append(value)
757
+ else:
758
+ result[key] = value
759
+ return result
760
+
761
+ def write_shared_locations(self, paths, dry_run=False):
762
+ """
763
+ Write shared location information to the SHARED file in .dist-info.
764
+ :param paths: A dictionary as described in the documentation for
765
+ :meth:`shared_locations`.
766
+ :param dry_run: If True, the action is logged but no file is actually
767
+ written.
768
+ :return: The path of the file written to.
769
+ """
770
+ shared_path = os.path.join(self.path, 'SHARED')
771
+ logger.info('creating %s', shared_path)
772
+ if dry_run:
773
+ return None
774
+ lines = []
775
+ for key in ('prefix', 'lib', 'headers', 'scripts', 'data'):
776
+ path = paths[key]
777
+ if os.path.isdir(paths[key]):
778
+ lines.append('%s=%s' % (key, path))
779
+ for ns in paths.get('namespace', ()):
780
+ lines.append('namespace=%s' % ns)
781
+
782
+ with codecs.open(shared_path, 'w', encoding='utf-8') as f:
783
+ f.write('\n'.join(lines))
784
+ return shared_path
785
+
786
+ def get_distinfo_resource(self, path):
787
+ if path not in DIST_FILES:
788
+ raise DistlibException('invalid path for a dist-info file: '
789
+ '%r at %r' % (path, self.path))
790
+ finder = resources.finder_for_path(self.path)
791
+ if finder is None:
792
+ raise DistlibException('Unable to get a finder for %s' % self.path)
793
+ return finder.find(path)
794
+
795
+ def get_distinfo_file(self, path):
796
+ """
797
+ Returns a path located under the ``.dist-info`` directory. Returns a
798
+ string representing the path.
799
+
800
+ :parameter path: a ``'/'``-separated path relative to the
801
+ ``.dist-info`` directory or an absolute path;
802
+ If *path* is an absolute path and doesn't start
803
+ with the ``.dist-info`` directory path,
804
+ a :class:`DistlibException` is raised
805
+ :type path: str
806
+ :rtype: str
807
+ """
808
+ # Check if it is an absolute path # XXX use relpath, add tests
809
+ if path.find(os.sep) >= 0:
810
+ # it's an absolute path?
811
+ distinfo_dirname, path = path.split(os.sep)[-2:]
812
+ if distinfo_dirname != self.path.split(os.sep)[-1]:
813
+ raise DistlibException('dist-info file %r does not belong to the %r %s '
814
+ 'distribution' % (path, self.name, self.version))
815
+
816
+ # The file must be relative
817
+ if path not in DIST_FILES:
818
+ raise DistlibException('invalid path for a dist-info file: '
819
+ '%r at %r' % (path, self.path))
820
+
821
+ return os.path.join(self.path, path)
822
+
823
+ def list_distinfo_files(self):
824
+ """
825
+ Iterates over the ``RECORD`` entries and returns paths for each line if
826
+ the path is pointing to a file located in the ``.dist-info`` directory
827
+ or one of its subdirectories.
828
+
829
+ :returns: iterator of paths
830
+ """
831
+ base = os.path.dirname(self.path)
832
+ for path, checksum, size in self._get_records():
833
+ # XXX add separator or use real relpath algo
834
+ if not os.path.isabs(path):
835
+ path = os.path.join(base, path)
836
+ if path.startswith(self.path):
837
+ yield path
838
+
839
+ def __eq__(self, other):
840
+ return (isinstance(other, InstalledDistribution) and self.path == other.path)
841
+
842
+ # See http://docs.python.org/reference/datamodel#object.__hash__
843
+ __hash__ = object.__hash__
844
+
845
+
846
+ class EggInfoDistribution(BaseInstalledDistribution):
847
+ """Created with the *path* of the ``.egg-info`` directory or file provided
848
+ to the constructor. It reads the metadata contained in the file itself, or
849
+ if the given path happens to be a directory, the metadata is read from the
850
+ file ``PKG-INFO`` under that directory."""
851
+
852
+ requested = True # as we have no way of knowing, assume it was
853
+ shared_locations = {}
854
+
855
+ def __init__(self, path, env=None):
856
+
857
+ def set_name_and_version(s, n, v):
858
+ s.name = n
859
+ s.key = n.lower() # for case-insensitive comparisons
860
+ s.version = v
861
+
862
+ self.path = path
863
+ self.dist_path = env
864
+ if env and env._cache_enabled and path in env._cache_egg.path:
865
+ metadata = env._cache_egg.path[path].metadata
866
+ set_name_and_version(self, metadata.name, metadata.version)
867
+ else:
868
+ metadata = self._get_metadata(path)
869
+
870
+ # Need to be set before caching
871
+ set_name_and_version(self, metadata.name, metadata.version)
872
+
873
+ if env and env._cache_enabled:
874
+ env._cache_egg.add(self)
875
+ super(EggInfoDistribution, self).__init__(metadata, path, env)
876
+
877
+ def _get_metadata(self, path):
878
+ requires = None
879
+
880
+ def parse_requires_data(data):
881
+ """Create a list of dependencies from a requires.txt file.
882
+
883
+ *data*: the contents of a setuptools-produced requires.txt file.
884
+ """
885
+ reqs = []
886
+ lines = data.splitlines()
887
+ for line in lines:
888
+ line = line.strip()
889
+ # sectioned files have bare newlines (separating sections)
890
+ if not line: # pragma: no cover
891
+ continue
892
+ if line.startswith('['): # pragma: no cover
893
+ logger.warning('Unexpected line: quitting requirement scan: %r', line)
894
+ break
895
+ r = parse_requirement(line)
896
+ if not r: # pragma: no cover
897
+ logger.warning('Not recognised as a requirement: %r', line)
898
+ continue
899
+ if r.extras: # pragma: no cover
900
+ logger.warning('extra requirements in requires.txt are '
901
+ 'not supported')
902
+ if not r.constraints:
903
+ reqs.append(r.name)
904
+ else:
905
+ cons = ', '.join('%s%s' % c for c in r.constraints)
906
+ reqs.append('%s (%s)' % (r.name, cons))
907
+ return reqs
908
+
909
+ def parse_requires_path(req_path):
910
+ """Create a list of dependencies from a requires.txt file.
911
+
912
+ *req_path*: the path to a setuptools-produced requires.txt file.
913
+ """
914
+
915
+ reqs = []
916
+ try:
917
+ with codecs.open(req_path, 'r', 'utf-8') as fp:
918
+ reqs = parse_requires_data(fp.read())
919
+ except IOError:
920
+ pass
921
+ return reqs
922
+
923
+ tl_path = tl_data = None
924
+ if path.endswith('.egg'):
925
+ if os.path.isdir(path):
926
+ p = os.path.join(path, 'EGG-INFO')
927
+ meta_path = os.path.join(p, 'PKG-INFO')
928
+ metadata = Metadata(path=meta_path, scheme='legacy')
929
+ req_path = os.path.join(p, 'requires.txt')
930
+ tl_path = os.path.join(p, 'top_level.txt')
931
+ requires = parse_requires_path(req_path)
932
+ else:
933
+ # FIXME handle the case where zipfile is not available
934
+ zipf = zipimport.zipimporter(path)
935
+ fileobj = StringIO(zipf.get_data('EGG-INFO/PKG-INFO').decode('utf8'))
936
+ metadata = Metadata(fileobj=fileobj, scheme='legacy')
937
+ try:
938
+ data = zipf.get_data('EGG-INFO/requires.txt')
939
+ tl_data = zipf.get_data('EGG-INFO/top_level.txt').decode('utf-8')
940
+ requires = parse_requires_data(data.decode('utf-8'))
941
+ except IOError:
942
+ requires = None
943
+ elif path.endswith('.egg-info'):
944
+ if os.path.isdir(path):
945
+ req_path = os.path.join(path, 'requires.txt')
946
+ requires = parse_requires_path(req_path)
947
+ path = os.path.join(path, 'PKG-INFO')
948
+ tl_path = os.path.join(path, 'top_level.txt')
949
+ metadata = Metadata(path=path, scheme='legacy')
950
+ else:
951
+ raise DistlibException('path must end with .egg-info or .egg, '
952
+ 'got %r' % path)
953
+
954
+ if requires:
955
+ metadata.add_requirements(requires)
956
+ # look for top-level modules in top_level.txt, if present
957
+ if tl_data is None:
958
+ if tl_path is not None and os.path.exists(tl_path):
959
+ with open(tl_path, 'rb') as f:
960
+ tl_data = f.read().decode('utf-8')
961
+ if not tl_data:
962
+ tl_data = []
963
+ else:
964
+ tl_data = tl_data.splitlines()
965
+ self.modules = tl_data
966
+ return metadata
967
+
968
+ def __repr__(self):
969
+ return '<EggInfoDistribution %r %s at %r>' % (self.name, self.version, self.path)
970
+
971
+ def __str__(self):
972
+ return "%s %s" % (self.name, self.version)
973
+
974
+ def check_installed_files(self):
975
+ """
976
+ Checks that the hashes and sizes of the files in ``RECORD`` are
977
+ matched by the files themselves. Returns a (possibly empty) list of
978
+ mismatches. Each entry in the mismatch list will be a tuple consisting
979
+ of the path, 'exists', 'size' or 'hash' according to what didn't match
980
+ (existence is checked first, then size, then hash), the expected
981
+ value and the actual value.
982
+ """
983
+ mismatches = []
984
+ record_path = os.path.join(self.path, 'installed-files.txt')
985
+ if os.path.exists(record_path):
986
+ for path, _, _ in self.list_installed_files():
987
+ if path == record_path:
988
+ continue
989
+ if not os.path.exists(path):
990
+ mismatches.append((path, 'exists', True, False))
991
+ return mismatches
992
+
993
+ def list_installed_files(self):
994
+ """
995
+ Iterates over the ``installed-files.txt`` entries and returns a tuple
996
+ ``(path, hash, size)`` for each line.
997
+
998
+ :returns: a list of (path, hash, size)
999
+ """
1000
+
1001
+ def _md5(path):
1002
+ f = open(path, 'rb')
1003
+ try:
1004
+ content = f.read()
1005
+ finally:
1006
+ f.close()
1007
+ return hashlib.md5(content).hexdigest()
1008
+
1009
+ def _size(path):
1010
+ return os.stat(path).st_size
1011
+
1012
+ record_path = os.path.join(self.path, 'installed-files.txt')
1013
+ result = []
1014
+ if os.path.exists(record_path):
1015
+ with codecs.open(record_path, 'r', encoding='utf-8') as f:
1016
+ for line in f:
1017
+ line = line.strip()
1018
+ p = os.path.normpath(os.path.join(self.path, line))
1019
+ # "./" is present as a marker between installed files
1020
+ # and installation metadata files
1021
+ if not os.path.exists(p):
1022
+ logger.warning('Non-existent file: %s', p)
1023
+ if p.endswith(('.pyc', '.pyo')):
1024
+ continue
1025
+ # otherwise fall through and fail
1026
+ if not os.path.isdir(p):
1027
+ result.append((p, _md5(p), _size(p)))
1028
+ result.append((record_path, None, None))
1029
+ return result
1030
+
1031
+ def list_distinfo_files(self, absolute=False):
1032
+ """
1033
+ Iterates over the ``installed-files.txt`` entries and returns paths for
1034
+ each line if the path is pointing to a file located in the
1035
+ ``.egg-info`` directory or one of its subdirectories.
1036
+
1037
+ :parameter absolute: If *absolute* is ``True``, each returned path is
1038
+ transformed into a local absolute path. Otherwise the
1039
+ raw value from ``installed-files.txt`` is returned.
1040
+ :type absolute: boolean
1041
+ :returns: iterator of paths
1042
+ """
1043
+ record_path = os.path.join(self.path, 'installed-files.txt')
1044
+ if os.path.exists(record_path):
1045
+ skip = True
1046
+ with codecs.open(record_path, 'r', encoding='utf-8') as f:
1047
+ for line in f:
1048
+ line = line.strip()
1049
+ if line == './':
1050
+ skip = False
1051
+ continue
1052
+ if not skip:
1053
+ p = os.path.normpath(os.path.join(self.path, line))
1054
+ if p.startswith(self.path):
1055
+ if absolute:
1056
+ yield p
1057
+ else:
1058
+ yield line
1059
+
1060
+ def __eq__(self, other):
1061
+ return (isinstance(other, EggInfoDistribution) and self.path == other.path)
1062
+
1063
+ # See http://docs.python.org/reference/datamodel#object.__hash__
1064
+ __hash__ = object.__hash__
1065
+
1066
+
1067
+ new_dist_class = InstalledDistribution
1068
+ old_dist_class = EggInfoDistribution
1069
+
1070
+
1071
+ class DependencyGraph(object):
1072
+ """
1073
+ Represents a dependency graph between distributions.
1074
+
1075
+ The dependency relationships are stored in an ``adjacency_list`` that maps
1076
+ distributions to a list of ``(other, label)`` tuples where ``other``
1077
+ is a distribution and the edge is labeled with ``label`` (i.e. the version
1078
+ specifier, if such was provided). Also, for more efficient traversal, for
1079
+ every distribution ``x``, a list of predecessors is kept in
1080
+ ``reverse_list[x]``. An edge from distribution ``a`` to
1081
+ distribution ``b`` means that ``a`` depends on ``b``. If any missing
1082
+ dependencies are found, they are stored in ``missing``, which is a
1083
+ dictionary that maps distributions to a list of requirements that were not
1084
+ provided by any other distributions.
1085
+ """
1086
+
1087
+ def __init__(self):
1088
+ self.adjacency_list = {}
1089
+ self.reverse_list = {}
1090
+ self.missing = {}
1091
+
1092
+ def add_distribution(self, distribution):
1093
+ """Add the *distribution* to the graph.
1094
+
1095
+ :type distribution: :class:`distutils2.database.InstalledDistribution`
1096
+ or :class:`distutils2.database.EggInfoDistribution`
1097
+ """
1098
+ self.adjacency_list[distribution] = []
1099
+ self.reverse_list[distribution] = []
1100
+ # self.missing[distribution] = []
1101
+
1102
+ def add_edge(self, x, y, label=None):
1103
+ """Add an edge from distribution *x* to distribution *y* with the given
1104
+ *label*.
1105
+
1106
+ :type x: :class:`distutils2.database.InstalledDistribution` or
1107
+ :class:`distutils2.database.EggInfoDistribution`
1108
+ :type y: :class:`distutils2.database.InstalledDistribution` or
1109
+ :class:`distutils2.database.EggInfoDistribution`
1110
+ :type label: ``str`` or ``None``
1111
+ """
1112
+ self.adjacency_list[x].append((y, label))
1113
+ # multiple edges are allowed, so be careful
1114
+ if x not in self.reverse_list[y]:
1115
+ self.reverse_list[y].append(x)
1116
+
1117
+ def add_missing(self, distribution, requirement):
1118
+ """
1119
+ Add a missing *requirement* for the given *distribution*.
1120
+
1121
+ :type distribution: :class:`distutils2.database.InstalledDistribution`
1122
+ or :class:`distutils2.database.EggInfoDistribution`
1123
+ :type requirement: ``str``
1124
+ """
1125
+ logger.debug('%s missing %r', distribution, requirement)
1126
+ self.missing.setdefault(distribution, []).append(requirement)
1127
+
1128
+ def _repr_dist(self, dist):
1129
+ return '%s %s' % (dist.name, dist.version)
1130
+
1131
+ def repr_node(self, dist, level=1):
1132
+ """Prints only a subgraph"""
1133
+ output = [self._repr_dist(dist)]
1134
+ for other, label in self.adjacency_list[dist]:
1135
+ dist = self._repr_dist(other)
1136
+ if label is not None:
1137
+ dist = '%s [%s]' % (dist, label)
1138
+ output.append(' ' * level + str(dist))
1139
+ suboutput = self.repr_node(other, level + 1)
1140
+ subs = suboutput.split('\n')
1141
+ output.extend(subs[1:])
1142
+ return '\n'.join(output)
1143
+
1144
+ def to_dot(self, f, skip_disconnected=True):
1145
+ """Writes a DOT output for the graph to the provided file *f*.
1146
+
1147
+ If *skip_disconnected* is set to ``True``, then all distributions
1148
+ that are not dependent on any other distribution are skipped.
1149
+
1150
+ :type f: has to support ``file``-like operations
1151
+ :type skip_disconnected: ``bool``
1152
+ """
1153
+ disconnected = []
1154
+
1155
+ f.write("digraph dependencies {\n")
1156
+ for dist, adjs in self.adjacency_list.items():
1157
+ if len(adjs) == 0 and not skip_disconnected:
1158
+ disconnected.append(dist)
1159
+ for other, label in adjs:
1160
+ if label is not None:
1161
+ f.write('"%s" -> "%s" [label="%s"]\n' % (dist.name, other.name, label))
1162
+ else:
1163
+ f.write('"%s" -> "%s"\n' % (dist.name, other.name))
1164
+ if not skip_disconnected and len(disconnected) > 0:
1165
+ f.write('subgraph disconnected {\n')
1166
+ f.write('label = "Disconnected"\n')
1167
+ f.write('bgcolor = red\n')
1168
+
1169
+ for dist in disconnected:
1170
+ f.write('"%s"' % dist.name)
1171
+ f.write('\n')
1172
+ f.write('}\n')
1173
+ f.write('}\n')
1174
+
1175
+ def topological_sort(self):
1176
+ """
1177
+ Perform a topological sort of the graph.
1178
+ :return: A tuple, the first element of which is a topologically sorted
1179
+ list of distributions, and the second element of which is a
1180
+ list of distributions that cannot be sorted because they have
1181
+ circular dependencies and so form a cycle.
1182
+ """
1183
+ result = []
1184
+ # Make a shallow copy of the adjacency list
1185
+ alist = {}
1186
+ for k, v in self.adjacency_list.items():
1187
+ alist[k] = v[:]
1188
+ while True:
1189
+ # See what we can remove in this run
1190
+ to_remove = []
1191
+ for k, v in list(alist.items())[:]:
1192
+ if not v:
1193
+ to_remove.append(k)
1194
+ del alist[k]
1195
+ if not to_remove:
1196
+ # What's left in alist (if anything) is a cycle.
1197
+ break
1198
+ # Remove from the adjacency list of others
1199
+ for k, v in alist.items():
1200
+ alist[k] = [(d, r) for d, r in v if d not in to_remove]
1201
+ logger.debug('Moving to result: %s', ['%s (%s)' % (d.name, d.version) for d in to_remove])
1202
+ result.extend(to_remove)
1203
+ return result, list(alist.keys())
1204
+
1205
+ def __repr__(self):
1206
+ """Representation of the graph"""
1207
+ output = []
1208
+ for dist, adjs in self.adjacency_list.items():
1209
+ output.append(self.repr_node(dist))
1210
+ return '\n'.join(output)
1211
+
1212
+
1213
+ def make_graph(dists, scheme='default'):
1214
+ """Makes a dependency graph from the given distributions.
1215
+
1216
+ :parameter dists: a list of distributions
1217
+ :type dists: list of :class:`distutils2.database.InstalledDistribution` and
1218
+ :class:`distutils2.database.EggInfoDistribution` instances
1219
+ :rtype: a :class:`DependencyGraph` instance
1220
+ """
1221
+ scheme = get_scheme(scheme)
1222
+ graph = DependencyGraph()
1223
+ provided = {} # maps names to lists of (version, dist) tuples
1224
+
1225
+ # first, build the graph and find out what's provided
1226
+ for dist in dists:
1227
+ graph.add_distribution(dist)
1228
+
1229
+ for p in dist.provides:
1230
+ name, version = parse_name_and_version(p)
1231
+ logger.debug('Add to provided: %s, %s, %s', name, version, dist)
1232
+ provided.setdefault(name, []).append((version, dist))
1233
+
1234
+ # now make the edges
1235
+ for dist in dists:
1236
+ requires = (dist.run_requires | dist.meta_requires | dist.build_requires | dist.dev_requires)
1237
+ for req in requires:
1238
+ try:
1239
+ matcher = scheme.matcher(req)
1240
+ except UnsupportedVersionError:
1241
+ # XXX compat-mode if cannot read the version
1242
+ logger.warning('could not read version %r - using name only', req)
1243
+ name = req.split()[0]
1244
+ matcher = scheme.matcher(name)
1245
+
1246
+ name = matcher.key # case-insensitive
1247
+
1248
+ matched = False
1249
+ if name in provided:
1250
+ for version, provider in provided[name]:
1251
+ try:
1252
+ match = matcher.match(version)
1253
+ except UnsupportedVersionError:
1254
+ match = False
1255
+
1256
+ if match:
1257
+ graph.add_edge(dist, provider, req)
1258
+ matched = True
1259
+ break
1260
+ if not matched:
1261
+ graph.add_missing(dist, req)
1262
+ return graph
1263
+
1264
+
1265
+ def get_dependent_dists(dists, dist):
1266
+ """Recursively generate a list of distributions from *dists* that are
1267
+ dependent on *dist*.
1268
+
1269
+ :param dists: a list of distributions
1270
+ :param dist: a distribution, member of *dists* for which we are interested
1271
+ """
1272
+ if dist not in dists:
1273
+ raise DistlibException('given distribution %r is not a member '
1274
+ 'of the list' % dist.name)
1275
+ graph = make_graph(dists)
1276
+
1277
+ dep = [dist] # dependent distributions
1278
+ todo = graph.reverse_list[dist] # list of nodes we should inspect
1279
+
1280
+ while todo:
1281
+ d = todo.pop()
1282
+ dep.append(d)
1283
+ for succ in graph.reverse_list[d]:
1284
+ if succ not in dep:
1285
+ todo.append(succ)
1286
+
1287
+ dep.pop(0) # remove dist from dep, was there to prevent infinite loops
1288
+ return dep
1289
+
1290
+
1291
+ def get_required_dists(dists, dist):
1292
+ """Recursively generate a list of distributions from *dists* that are
1293
+ required by *dist*.
1294
+
1295
+ :param dists: a list of distributions
1296
+ :param dist: a distribution, member of *dists* for which we are interested
1297
+ in finding the dependencies.
1298
+ """
1299
+ if dist not in dists:
1300
+ raise DistlibException('given distribution %r is not a member '
1301
+ 'of the list' % dist.name)
1302
+ graph = make_graph(dists)
1303
+
1304
+ req = set() # required distributions
1305
+ todo = graph.adjacency_list[dist] # list of nodes we should inspect
1306
+ seen = set(t[0] for t in todo) # already added to todo
1307
+
1308
+ while todo:
1309
+ d = todo.pop()[0]
1310
+ req.add(d)
1311
+ pred_list = graph.adjacency_list[d]
1312
+ for pred in pred_list:
1313
+ d = pred[0]
1314
+ if d not in req and d not in seen:
1315
+ seen.add(d)
1316
+ todo.append(pred)
1317
+ return req
1318
+
1319
+
1320
+ def make_dist(name, version, **kwargs):
1321
+ """
1322
+ A convenience method for making a dist given just a name and version.
1323
+ """
1324
+ summary = kwargs.pop('summary', 'Placeholder for summary')
1325
+ md = Metadata(**kwargs)
1326
+ md.name = name
1327
+ md.version = version
1328
+ md.summary = summary or 'Placeholder for summary'
1329
+ return Distribution(md)
lib/python3.12/site-packages/pip/_vendor/distlib/index.py ADDED
@@ -0,0 +1,508 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2013-2023 Vinay Sajip.
4
+ # Licensed to the Python Software Foundation under a contributor agreement.
5
+ # See LICENSE.txt and CONTRIBUTORS.txt.
6
+ #
7
+ import hashlib
8
+ import logging
9
+ import os
10
+ import shutil
11
+ import subprocess
12
+ import tempfile
13
+ try:
14
+ from threading import Thread
15
+ except ImportError: # pragma: no cover
16
+ from dummy_threading import Thread
17
+
18
+ from . import DistlibException
19
+ from .compat import (HTTPBasicAuthHandler, Request, HTTPPasswordMgr,
20
+ urlparse, build_opener, string_types)
21
+ from .util import zip_dir, ServerProxy
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+ DEFAULT_INDEX = 'https://pypi.org/pypi'
26
+ DEFAULT_REALM = 'pypi'
27
+
28
+
29
+ class PackageIndex(object):
30
+ """
31
+ This class represents a package index compatible with PyPI, the Python
32
+ Package Index.
33
+ """
34
+
35
+ boundary = b'----------ThIs_Is_tHe_distlib_index_bouNdaRY_$'
36
+
37
+ def __init__(self, url=None):
38
+ """
39
+ Initialise an instance.
40
+
41
+ :param url: The URL of the index. If not specified, the URL for PyPI is
42
+ used.
43
+ """
44
+ self.url = url or DEFAULT_INDEX
45
+ self.read_configuration()
46
+ scheme, netloc, path, params, query, frag = urlparse(self.url)
47
+ if params or query or frag or scheme not in ('http', 'https'):
48
+ raise DistlibException('invalid repository: %s' % self.url)
49
+ self.password_handler = None
50
+ self.ssl_verifier = None
51
+ self.gpg = None
52
+ self.gpg_home = None
53
+ with open(os.devnull, 'w') as sink:
54
+ # Use gpg by default rather than gpg2, as gpg2 insists on
55
+ # prompting for passwords
56
+ for s in ('gpg', 'gpg2'):
57
+ try:
58
+ rc = subprocess.check_call([s, '--version'], stdout=sink,
59
+ stderr=sink)
60
+ if rc == 0:
61
+ self.gpg = s
62
+ break
63
+ except OSError:
64
+ pass
65
+
66
+ def _get_pypirc_command(self):
67
+ """
68
+ Get the distutils command for interacting with PyPI configurations.
69
+ :return: the command.
70
+ """
71
+ from .util import _get_pypirc_command as cmd
72
+ return cmd()
73
+
74
+ def read_configuration(self):
75
+ """
76
+ Read the PyPI access configuration as supported by distutils. This populates
77
+ ``username``, ``password``, ``realm`` and ``url`` attributes from the
78
+ configuration.
79
+ """
80
+ from .util import _load_pypirc
81
+ cfg = _load_pypirc(self)
82
+ self.username = cfg.get('username')
83
+ self.password = cfg.get('password')
84
+ self.realm = cfg.get('realm', 'pypi')
85
+ self.url = cfg.get('repository', self.url)
86
+
87
+ def save_configuration(self):
88
+ """
89
+ Save the PyPI access configuration. You must have set ``username`` and
90
+ ``password`` attributes before calling this method.
91
+ """
92
+ self.check_credentials()
93
+ from .util import _store_pypirc
94
+ _store_pypirc(self)
95
+
96
+ def check_credentials(self):
97
+ """
98
+ Check that ``username`` and ``password`` have been set, and raise an
99
+ exception if not.
100
+ """
101
+ if self.username is None or self.password is None:
102
+ raise DistlibException('username and password must be set')
103
+ pm = HTTPPasswordMgr()
104
+ _, netloc, _, _, _, _ = urlparse(self.url)
105
+ pm.add_password(self.realm, netloc, self.username, self.password)
106
+ self.password_handler = HTTPBasicAuthHandler(pm)
107
+
108
+ def register(self, metadata): # pragma: no cover
109
+ """
110
+ Register a distribution on PyPI, using the provided metadata.
111
+
112
+ :param metadata: A :class:`Metadata` instance defining at least a name
113
+ and version number for the distribution to be
114
+ registered.
115
+ :return: The HTTP response received from PyPI upon submission of the
116
+ request.
117
+ """
118
+ self.check_credentials()
119
+ metadata.validate()
120
+ d = metadata.todict()
121
+ d[':action'] = 'verify'
122
+ request = self.encode_request(d.items(), [])
123
+ self.send_request(request)
124
+ d[':action'] = 'submit'
125
+ request = self.encode_request(d.items(), [])
126
+ return self.send_request(request)
127
+
128
+ def _reader(self, name, stream, outbuf):
129
+ """
130
+ Thread runner for reading lines of from a subprocess into a buffer.
131
+
132
+ :param name: The logical name of the stream (used for logging only).
133
+ :param stream: The stream to read from. This will typically a pipe
134
+ connected to the output stream of a subprocess.
135
+ :param outbuf: The list to append the read lines to.
136
+ """
137
+ while True:
138
+ s = stream.readline()
139
+ if not s:
140
+ break
141
+ s = s.decode('utf-8').rstrip()
142
+ outbuf.append(s)
143
+ logger.debug('%s: %s' % (name, s))
144
+ stream.close()
145
+
146
+ def get_sign_command(self, filename, signer, sign_password, keystore=None): # pragma: no cover
147
+ """
148
+ Return a suitable command for signing a file.
149
+
150
+ :param filename: The pathname to the file to be signed.
151
+ :param signer: The identifier of the signer of the file.
152
+ :param sign_password: The passphrase for the signer's
153
+ private key used for signing.
154
+ :param keystore: The path to a directory which contains the keys
155
+ used in verification. If not specified, the
156
+ instance's ``gpg_home`` attribute is used instead.
157
+ :return: The signing command as a list suitable to be
158
+ passed to :class:`subprocess.Popen`.
159
+ """
160
+ cmd = [self.gpg, '--status-fd', '2', '--no-tty']
161
+ if keystore is None:
162
+ keystore = self.gpg_home
163
+ if keystore:
164
+ cmd.extend(['--homedir', keystore])
165
+ if sign_password is not None:
166
+ cmd.extend(['--batch', '--passphrase-fd', '0'])
167
+ td = tempfile.mkdtemp()
168
+ sf = os.path.join(td, os.path.basename(filename) + '.asc')
169
+ cmd.extend(['--detach-sign', '--armor', '--local-user',
170
+ signer, '--output', sf, filename])
171
+ logger.debug('invoking: %s', ' '.join(cmd))
172
+ return cmd, sf
173
+
174
+ def run_command(self, cmd, input_data=None):
175
+ """
176
+ Run a command in a child process , passing it any input data specified.
177
+
178
+ :param cmd: The command to run.
179
+ :param input_data: If specified, this must be a byte string containing
180
+ data to be sent to the child process.
181
+ :return: A tuple consisting of the subprocess' exit code, a list of
182
+ lines read from the subprocess' ``stdout``, and a list of
183
+ lines read from the subprocess' ``stderr``.
184
+ """
185
+ kwargs = {
186
+ 'stdout': subprocess.PIPE,
187
+ 'stderr': subprocess.PIPE,
188
+ }
189
+ if input_data is not None:
190
+ kwargs['stdin'] = subprocess.PIPE
191
+ stdout = []
192
+ stderr = []
193
+ p = subprocess.Popen(cmd, **kwargs)
194
+ # We don't use communicate() here because we may need to
195
+ # get clever with interacting with the command
196
+ t1 = Thread(target=self._reader, args=('stdout', p.stdout, stdout))
197
+ t1.start()
198
+ t2 = Thread(target=self._reader, args=('stderr', p.stderr, stderr))
199
+ t2.start()
200
+ if input_data is not None:
201
+ p.stdin.write(input_data)
202
+ p.stdin.close()
203
+
204
+ p.wait()
205
+ t1.join()
206
+ t2.join()
207
+ return p.returncode, stdout, stderr
208
+
209
+ def sign_file(self, filename, signer, sign_password, keystore=None): # pragma: no cover
210
+ """
211
+ Sign a file.
212
+
213
+ :param filename: The pathname to the file to be signed.
214
+ :param signer: The identifier of the signer of the file.
215
+ :param sign_password: The passphrase for the signer's
216
+ private key used for signing.
217
+ :param keystore: The path to a directory which contains the keys
218
+ used in signing. If not specified, the instance's
219
+ ``gpg_home`` attribute is used instead.
220
+ :return: The absolute pathname of the file where the signature is
221
+ stored.
222
+ """
223
+ cmd, sig_file = self.get_sign_command(filename, signer, sign_password,
224
+ keystore)
225
+ rc, stdout, stderr = self.run_command(cmd,
226
+ sign_password.encode('utf-8'))
227
+ if rc != 0:
228
+ raise DistlibException('sign command failed with error '
229
+ 'code %s' % rc)
230
+ return sig_file
231
+
232
+ def upload_file(self, metadata, filename, signer=None, sign_password=None,
233
+ filetype='sdist', pyversion='source', keystore=None):
234
+ """
235
+ Upload a release file to the index.
236
+
237
+ :param metadata: A :class:`Metadata` instance defining at least a name
238
+ and version number for the file to be uploaded.
239
+ :param filename: The pathname of the file to be uploaded.
240
+ :param signer: The identifier of the signer of the file.
241
+ :param sign_password: The passphrase for the signer's
242
+ private key used for signing.
243
+ :param filetype: The type of the file being uploaded. This is the
244
+ distutils command which produced that file, e.g.
245
+ ``sdist`` or ``bdist_wheel``.
246
+ :param pyversion: The version of Python which the release relates
247
+ to. For code compatible with any Python, this would
248
+ be ``source``, otherwise it would be e.g. ``3.2``.
249
+ :param keystore: The path to a directory which contains the keys
250
+ used in signing. If not specified, the instance's
251
+ ``gpg_home`` attribute is used instead.
252
+ :return: The HTTP response received from PyPI upon submission of the
253
+ request.
254
+ """
255
+ self.check_credentials()
256
+ if not os.path.exists(filename):
257
+ raise DistlibException('not found: %s' % filename)
258
+ metadata.validate()
259
+ d = metadata.todict()
260
+ sig_file = None
261
+ if signer:
262
+ if not self.gpg:
263
+ logger.warning('no signing program available - not signed')
264
+ else:
265
+ sig_file = self.sign_file(filename, signer, sign_password,
266
+ keystore)
267
+ with open(filename, 'rb') as f:
268
+ file_data = f.read()
269
+ md5_digest = hashlib.md5(file_data).hexdigest()
270
+ sha256_digest = hashlib.sha256(file_data).hexdigest()
271
+ d.update({
272
+ ':action': 'file_upload',
273
+ 'protocol_version': '1',
274
+ 'filetype': filetype,
275
+ 'pyversion': pyversion,
276
+ 'md5_digest': md5_digest,
277
+ 'sha256_digest': sha256_digest,
278
+ })
279
+ files = [('content', os.path.basename(filename), file_data)]
280
+ if sig_file:
281
+ with open(sig_file, 'rb') as f:
282
+ sig_data = f.read()
283
+ files.append(('gpg_signature', os.path.basename(sig_file),
284
+ sig_data))
285
+ shutil.rmtree(os.path.dirname(sig_file))
286
+ request = self.encode_request(d.items(), files)
287
+ return self.send_request(request)
288
+
289
+ def upload_documentation(self, metadata, doc_dir): # pragma: no cover
290
+ """
291
+ Upload documentation to the index.
292
+
293
+ :param metadata: A :class:`Metadata` instance defining at least a name
294
+ and version number for the documentation to be
295
+ uploaded.
296
+ :param doc_dir: The pathname of the directory which contains the
297
+ documentation. This should be the directory that
298
+ contains the ``index.html`` for the documentation.
299
+ :return: The HTTP response received from PyPI upon submission of the
300
+ request.
301
+ """
302
+ self.check_credentials()
303
+ if not os.path.isdir(doc_dir):
304
+ raise DistlibException('not a directory: %r' % doc_dir)
305
+ fn = os.path.join(doc_dir, 'index.html')
306
+ if not os.path.exists(fn):
307
+ raise DistlibException('not found: %r' % fn)
308
+ metadata.validate()
309
+ name, version = metadata.name, metadata.version
310
+ zip_data = zip_dir(doc_dir).getvalue()
311
+ fields = [(':action', 'doc_upload'),
312
+ ('name', name), ('version', version)]
313
+ files = [('content', name, zip_data)]
314
+ request = self.encode_request(fields, files)
315
+ return self.send_request(request)
316
+
317
+ def get_verify_command(self, signature_filename, data_filename,
318
+ keystore=None):
319
+ """
320
+ Return a suitable command for verifying a file.
321
+
322
+ :param signature_filename: The pathname to the file containing the
323
+ signature.
324
+ :param data_filename: The pathname to the file containing the
325
+ signed data.
326
+ :param keystore: The path to a directory which contains the keys
327
+ used in verification. If not specified, the
328
+ instance's ``gpg_home`` attribute is used instead.
329
+ :return: The verifying command as a list suitable to be
330
+ passed to :class:`subprocess.Popen`.
331
+ """
332
+ cmd = [self.gpg, '--status-fd', '2', '--no-tty']
333
+ if keystore is None:
334
+ keystore = self.gpg_home
335
+ if keystore:
336
+ cmd.extend(['--homedir', keystore])
337
+ cmd.extend(['--verify', signature_filename, data_filename])
338
+ logger.debug('invoking: %s', ' '.join(cmd))
339
+ return cmd
340
+
341
+ def verify_signature(self, signature_filename, data_filename,
342
+ keystore=None):
343
+ """
344
+ Verify a signature for a file.
345
+
346
+ :param signature_filename: The pathname to the file containing the
347
+ signature.
348
+ :param data_filename: The pathname to the file containing the
349
+ signed data.
350
+ :param keystore: The path to a directory which contains the keys
351
+ used in verification. If not specified, the
352
+ instance's ``gpg_home`` attribute is used instead.
353
+ :return: True if the signature was verified, else False.
354
+ """
355
+ if not self.gpg:
356
+ raise DistlibException('verification unavailable because gpg '
357
+ 'unavailable')
358
+ cmd = self.get_verify_command(signature_filename, data_filename,
359
+ keystore)
360
+ rc, stdout, stderr = self.run_command(cmd)
361
+ if rc not in (0, 1):
362
+ raise DistlibException('verify command failed with error code %s' % rc)
363
+ return rc == 0
364
+
365
+ def download_file(self, url, destfile, digest=None, reporthook=None):
366
+ """
367
+ This is a convenience method for downloading a file from an URL.
368
+ Normally, this will be a file from the index, though currently
369
+ no check is made for this (i.e. a file can be downloaded from
370
+ anywhere).
371
+
372
+ The method is just like the :func:`urlretrieve` function in the
373
+ standard library, except that it allows digest computation to be
374
+ done during download and checking that the downloaded data
375
+ matched any expected value.
376
+
377
+ :param url: The URL of the file to be downloaded (assumed to be
378
+ available via an HTTP GET request).
379
+ :param destfile: The pathname where the downloaded file is to be
380
+ saved.
381
+ :param digest: If specified, this must be a (hasher, value)
382
+ tuple, where hasher is the algorithm used (e.g.
383
+ ``'md5'``) and ``value`` is the expected value.
384
+ :param reporthook: The same as for :func:`urlretrieve` in the
385
+ standard library.
386
+ """
387
+ if digest is None:
388
+ digester = None
389
+ logger.debug('No digest specified')
390
+ else:
391
+ if isinstance(digest, (list, tuple)):
392
+ hasher, digest = digest
393
+ else:
394
+ hasher = 'md5'
395
+ digester = getattr(hashlib, hasher)()
396
+ logger.debug('Digest specified: %s' % digest)
397
+ # The following code is equivalent to urlretrieve.
398
+ # We need to do it this way so that we can compute the
399
+ # digest of the file as we go.
400
+ with open(destfile, 'wb') as dfp:
401
+ # addinfourl is not a context manager on 2.x
402
+ # so we have to use try/finally
403
+ sfp = self.send_request(Request(url))
404
+ try:
405
+ headers = sfp.info()
406
+ blocksize = 8192
407
+ size = -1
408
+ read = 0
409
+ blocknum = 0
410
+ if "content-length" in headers:
411
+ size = int(headers["Content-Length"])
412
+ if reporthook:
413
+ reporthook(blocknum, blocksize, size)
414
+ while True:
415
+ block = sfp.read(blocksize)
416
+ if not block:
417
+ break
418
+ read += len(block)
419
+ dfp.write(block)
420
+ if digester:
421
+ digester.update(block)
422
+ blocknum += 1
423
+ if reporthook:
424
+ reporthook(blocknum, blocksize, size)
425
+ finally:
426
+ sfp.close()
427
+
428
+ # check that we got the whole file, if we can
429
+ if size >= 0 and read < size:
430
+ raise DistlibException(
431
+ 'retrieval incomplete: got only %d out of %d bytes'
432
+ % (read, size))
433
+ # if we have a digest, it must match.
434
+ if digester:
435
+ actual = digester.hexdigest()
436
+ if digest != actual:
437
+ raise DistlibException('%s digest mismatch for %s: expected '
438
+ '%s, got %s' % (hasher, destfile,
439
+ digest, actual))
440
+ logger.debug('Digest verified: %s', digest)
441
+
442
+ def send_request(self, req):
443
+ """
444
+ Send a standard library :class:`Request` to PyPI and return its
445
+ response.
446
+
447
+ :param req: The request to send.
448
+ :return: The HTTP response from PyPI (a standard library HTTPResponse).
449
+ """
450
+ handlers = []
451
+ if self.password_handler:
452
+ handlers.append(self.password_handler)
453
+ if self.ssl_verifier:
454
+ handlers.append(self.ssl_verifier)
455
+ opener = build_opener(*handlers)
456
+ return opener.open(req)
457
+
458
+ def encode_request(self, fields, files):
459
+ """
460
+ Encode fields and files for posting to an HTTP server.
461
+
462
+ :param fields: The fields to send as a list of (fieldname, value)
463
+ tuples.
464
+ :param files: The files to send as a list of (fieldname, filename,
465
+ file_bytes) tuple.
466
+ """
467
+ # Adapted from packaging, which in turn was adapted from
468
+ # http://code.activestate.com/recipes/146306
469
+
470
+ parts = []
471
+ boundary = self.boundary
472
+ for k, values in fields:
473
+ if not isinstance(values, (list, tuple)):
474
+ values = [values]
475
+
476
+ for v in values:
477
+ parts.extend((
478
+ b'--' + boundary,
479
+ ('Content-Disposition: form-data; name="%s"' %
480
+ k).encode('utf-8'),
481
+ b'',
482
+ v.encode('utf-8')))
483
+ for key, filename, value in files:
484
+ parts.extend((
485
+ b'--' + boundary,
486
+ ('Content-Disposition: form-data; name="%s"; filename="%s"' %
487
+ (key, filename)).encode('utf-8'),
488
+ b'',
489
+ value))
490
+
491
+ parts.extend((b'--' + boundary + b'--', b''))
492
+
493
+ body = b'\r\n'.join(parts)
494
+ ct = b'multipart/form-data; boundary=' + boundary
495
+ headers = {
496
+ 'Content-type': ct,
497
+ 'Content-length': str(len(body))
498
+ }
499
+ return Request(self.url, body, headers)
500
+
501
+ def search(self, terms, operator=None): # pragma: no cover
502
+ if isinstance(terms, string_types):
503
+ terms = {'name': terms}
504
+ rpc_proxy = ServerProxy(self.url, timeout=3.0)
505
+ try:
506
+ return rpc_proxy.search(terms, operator or 'and')
507
+ finally:
508
+ rpc_proxy('close')()
lib/python3.12/site-packages/pip/_vendor/distlib/locators.py ADDED
@@ -0,0 +1,1295 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2012-2023 Vinay Sajip.
4
+ # Licensed to the Python Software Foundation under a contributor agreement.
5
+ # See LICENSE.txt and CONTRIBUTORS.txt.
6
+ #
7
+
8
+ import gzip
9
+ from io import BytesIO
10
+ import json
11
+ import logging
12
+ import os
13
+ import posixpath
14
+ import re
15
+ try:
16
+ import threading
17
+ except ImportError: # pragma: no cover
18
+ import dummy_threading as threading
19
+ import zlib
20
+
21
+ from . import DistlibException
22
+ from .compat import (urljoin, urlparse, urlunparse, url2pathname, pathname2url, queue, quote, unescape, build_opener,
23
+ HTTPRedirectHandler as BaseRedirectHandler, text_type, Request, HTTPError, URLError)
24
+ from .database import Distribution, DistributionPath, make_dist
25
+ from .metadata import Metadata, MetadataInvalidError
26
+ from .util import (cached_property, ensure_slash, split_filename, get_project_data, parse_requirement,
27
+ parse_name_and_version, ServerProxy, normalize_name)
28
+ from .version import get_scheme, UnsupportedVersionError
29
+ from .wheel import Wheel, is_compatible
30
+
31
+ logger = logging.getLogger(__name__)
32
+
33
+ HASHER_HASH = re.compile(r'^(\w+)=([a-f0-9]+)')
34
+ CHARSET = re.compile(r';\s*charset\s*=\s*(.*)\s*$', re.I)
35
+ HTML_CONTENT_TYPE = re.compile('text/html|application/x(ht)?ml')
36
+ DEFAULT_INDEX = 'https://pypi.org/pypi'
37
+
38
+
39
+ def get_all_distribution_names(url=None):
40
+ """
41
+ Return all distribution names known by an index.
42
+ :param url: The URL of the index.
43
+ :return: A list of all known distribution names.
44
+ """
45
+ if url is None:
46
+ url = DEFAULT_INDEX
47
+ client = ServerProxy(url, timeout=3.0)
48
+ try:
49
+ return client.list_packages()
50
+ finally:
51
+ client('close')()
52
+
53
+
54
+ class RedirectHandler(BaseRedirectHandler):
55
+ """
56
+ A class to work around a bug in some Python 3.2.x releases.
57
+ """
58
+
59
+ # There's a bug in the base version for some 3.2.x
60
+ # (e.g. 3.2.2 on Ubuntu Oneiric). If a Location header
61
+ # returns e.g. /abc, it bails because it says the scheme ''
62
+ # is bogus, when actually it should use the request's
63
+ # URL for the scheme. See Python issue #13696.
64
+ def http_error_302(self, req, fp, code, msg, headers):
65
+ # Some servers (incorrectly) return multiple Location headers
66
+ # (so probably same goes for URI). Use first header.
67
+ newurl = None
68
+ for key in ('location', 'uri'):
69
+ if key in headers:
70
+ newurl = headers[key]
71
+ break
72
+ if newurl is None: # pragma: no cover
73
+ return
74
+ urlparts = urlparse(newurl)
75
+ if urlparts.scheme == '':
76
+ newurl = urljoin(req.get_full_url(), newurl)
77
+ if hasattr(headers, 'replace_header'):
78
+ headers.replace_header(key, newurl)
79
+ else:
80
+ headers[key] = newurl
81
+ return BaseRedirectHandler.http_error_302(self, req, fp, code, msg, headers)
82
+
83
+ http_error_301 = http_error_303 = http_error_307 = http_error_302
84
+
85
+
86
+ class Locator(object):
87
+ """
88
+ A base class for locators - things that locate distributions.
89
+ """
90
+ source_extensions = ('.tar.gz', '.tar.bz2', '.tar', '.zip', '.tgz', '.tbz')
91
+ binary_extensions = ('.egg', '.exe', '.whl')
92
+ excluded_extensions = ('.pdf', )
93
+
94
+ # A list of tags indicating which wheels you want to match. The default
95
+ # value of None matches against the tags compatible with the running
96
+ # Python. If you want to match other values, set wheel_tags on a locator
97
+ # instance to a list of tuples (pyver, abi, arch) which you want to match.
98
+ wheel_tags = None
99
+
100
+ downloadable_extensions = source_extensions + ('.whl', )
101
+
102
+ def __init__(self, scheme='default'):
103
+ """
104
+ Initialise an instance.
105
+ :param scheme: Because locators look for most recent versions, they
106
+ need to know the version scheme to use. This specifies
107
+ the current PEP-recommended scheme - use ``'legacy'``
108
+ if you need to support existing distributions on PyPI.
109
+ """
110
+ self._cache = {}
111
+ self.scheme = scheme
112
+ # Because of bugs in some of the handlers on some of the platforms,
113
+ # we use our own opener rather than just using urlopen.
114
+ self.opener = build_opener(RedirectHandler())
115
+ # If get_project() is called from locate(), the matcher instance
116
+ # is set from the requirement passed to locate(). See issue #18 for
117
+ # why this can be useful to know.
118
+ self.matcher = None
119
+ self.errors = queue.Queue()
120
+
121
+ def get_errors(self):
122
+ """
123
+ Return any errors which have occurred.
124
+ """
125
+ result = []
126
+ while not self.errors.empty(): # pragma: no cover
127
+ try:
128
+ e = self.errors.get(False)
129
+ result.append(e)
130
+ except self.errors.Empty:
131
+ continue
132
+ self.errors.task_done()
133
+ return result
134
+
135
+ def clear_errors(self):
136
+ """
137
+ Clear any errors which may have been logged.
138
+ """
139
+ # Just get the errors and throw them away
140
+ self.get_errors()
141
+
142
+ def clear_cache(self):
143
+ self._cache.clear()
144
+
145
+ def _get_scheme(self):
146
+ return self._scheme
147
+
148
+ def _set_scheme(self, value):
149
+ self._scheme = value
150
+
151
+ scheme = property(_get_scheme, _set_scheme)
152
+
153
+ def _get_project(self, name):
154
+ """
155
+ For a given project, get a dictionary mapping available versions to Distribution
156
+ instances.
157
+
158
+ This should be implemented in subclasses.
159
+
160
+ If called from a locate() request, self.matcher will be set to a
161
+ matcher for the requirement to satisfy, otherwise it will be None.
162
+ """
163
+ raise NotImplementedError('Please implement in the subclass')
164
+
165
+ def get_distribution_names(self):
166
+ """
167
+ Return all the distribution names known to this locator.
168
+ """
169
+ raise NotImplementedError('Please implement in the subclass')
170
+
171
+ def get_project(self, name):
172
+ """
173
+ For a given project, get a dictionary mapping available versions to Distribution
174
+ instances.
175
+
176
+ This calls _get_project to do all the work, and just implements a caching layer on top.
177
+ """
178
+ if self._cache is None: # pragma: no cover
179
+ result = self._get_project(name)
180
+ elif name in self._cache:
181
+ result = self._cache[name]
182
+ else:
183
+ self.clear_errors()
184
+ result = self._get_project(name)
185
+ self._cache[name] = result
186
+ return result
187
+
188
+ def score_url(self, url):
189
+ """
190
+ Give an url a score which can be used to choose preferred URLs
191
+ for a given project release.
192
+ """
193
+ t = urlparse(url)
194
+ basename = posixpath.basename(t.path)
195
+ compatible = True
196
+ is_wheel = basename.endswith('.whl')
197
+ is_downloadable = basename.endswith(self.downloadable_extensions)
198
+ if is_wheel:
199
+ compatible = is_compatible(Wheel(basename), self.wheel_tags)
200
+ return (t.scheme == 'https', 'pypi.org' in t.netloc, is_downloadable, is_wheel, compatible, basename)
201
+
202
+ def prefer_url(self, url1, url2):
203
+ """
204
+ Choose one of two URLs where both are candidates for distribution
205
+ archives for the same version of a distribution (for example,
206
+ .tar.gz vs. zip).
207
+
208
+ The current implementation favours https:// URLs over http://, archives
209
+ from PyPI over those from other locations, wheel compatibility (if a
210
+ wheel) and then the archive name.
211
+ """
212
+ result = url2
213
+ if url1:
214
+ s1 = self.score_url(url1)
215
+ s2 = self.score_url(url2)
216
+ if s1 > s2:
217
+ result = url1
218
+ if result != url2:
219
+ logger.debug('Not replacing %r with %r', url1, url2)
220
+ else:
221
+ logger.debug('Replacing %r with %r', url1, url2)
222
+ return result
223
+
224
+ def split_filename(self, filename, project_name):
225
+ """
226
+ Attempt to split a filename in project name, version and Python version.
227
+ """
228
+ return split_filename(filename, project_name)
229
+
230
+ def convert_url_to_download_info(self, url, project_name):
231
+ """
232
+ See if a URL is a candidate for a download URL for a project (the URL
233
+ has typically been scraped from an HTML page).
234
+
235
+ If it is, a dictionary is returned with keys "name", "version",
236
+ "filename" and "url"; otherwise, None is returned.
237
+ """
238
+
239
+ def same_project(name1, name2):
240
+ return normalize_name(name1) == normalize_name(name2)
241
+
242
+ result = None
243
+ scheme, netloc, path, params, query, frag = urlparse(url)
244
+ if frag.lower().startswith('egg='): # pragma: no cover
245
+ logger.debug('%s: version hint in fragment: %r', project_name, frag)
246
+ m = HASHER_HASH.match(frag)
247
+ if m:
248
+ algo, digest = m.groups()
249
+ else:
250
+ algo, digest = None, None
251
+ origpath = path
252
+ if path and path[-1] == '/': # pragma: no cover
253
+ path = path[:-1]
254
+ if path.endswith('.whl'):
255
+ try:
256
+ wheel = Wheel(path)
257
+ if not is_compatible(wheel, self.wheel_tags):
258
+ logger.debug('Wheel not compatible: %s', path)
259
+ else:
260
+ if project_name is None:
261
+ include = True
262
+ else:
263
+ include = same_project(wheel.name, project_name)
264
+ if include:
265
+ result = {
266
+ 'name': wheel.name,
267
+ 'version': wheel.version,
268
+ 'filename': wheel.filename,
269
+ 'url': urlunparse((scheme, netloc, origpath, params, query, '')),
270
+ 'python-version': ', '.join(['.'.join(list(v[2:])) for v in wheel.pyver]),
271
+ }
272
+ except Exception: # pragma: no cover
273
+ logger.warning('invalid path for wheel: %s', path)
274
+ elif not path.endswith(self.downloadable_extensions): # pragma: no cover
275
+ logger.debug('Not downloadable: %s', path)
276
+ else: # downloadable extension
277
+ path = filename = posixpath.basename(path)
278
+ for ext in self.downloadable_extensions:
279
+ if path.endswith(ext):
280
+ path = path[:-len(ext)]
281
+ t = self.split_filename(path, project_name)
282
+ if not t: # pragma: no cover
283
+ logger.debug('No match for project/version: %s', path)
284
+ else:
285
+ name, version, pyver = t
286
+ if not project_name or same_project(project_name, name):
287
+ result = {
288
+ 'name': name,
289
+ 'version': version,
290
+ 'filename': filename,
291
+ 'url': urlunparse((scheme, netloc, origpath, params, query, '')),
292
+ }
293
+ if pyver: # pragma: no cover
294
+ result['python-version'] = pyver
295
+ break
296
+ if result and algo:
297
+ result['%s_digest' % algo] = digest
298
+ return result
299
+
300
+ def _get_digest(self, info):
301
+ """
302
+ Get a digest from a dictionary by looking at a "digests" dictionary
303
+ or keys of the form 'algo_digest'.
304
+
305
+ Returns a 2-tuple (algo, digest) if found, else None. Currently
306
+ looks only for SHA256, then MD5.
307
+ """
308
+ result = None
309
+ if 'digests' in info:
310
+ digests = info['digests']
311
+ for algo in ('sha256', 'md5'):
312
+ if algo in digests:
313
+ result = (algo, digests[algo])
314
+ break
315
+ if not result:
316
+ for algo in ('sha256', 'md5'):
317
+ key = '%s_digest' % algo
318
+ if key in info:
319
+ result = (algo, info[key])
320
+ break
321
+ return result
322
+
323
+ def _update_version_data(self, result, info):
324
+ """
325
+ Update a result dictionary (the final result from _get_project) with a
326
+ dictionary for a specific version, which typically holds information
327
+ gleaned from a filename or URL for an archive for the distribution.
328
+ """
329
+ name = info.pop('name')
330
+ version = info.pop('version')
331
+ if version in result:
332
+ dist = result[version]
333
+ md = dist.metadata
334
+ else:
335
+ dist = make_dist(name, version, scheme=self.scheme)
336
+ md = dist.metadata
337
+ dist.digest = digest = self._get_digest(info)
338
+ url = info['url']
339
+ result['digests'][url] = digest
340
+ if md.source_url != info['url']:
341
+ md.source_url = self.prefer_url(md.source_url, url)
342
+ result['urls'].setdefault(version, set()).add(url)
343
+ dist.locator = self
344
+ result[version] = dist
345
+
346
+ def locate(self, requirement, prereleases=False):
347
+ """
348
+ Find the most recent distribution which matches the given
349
+ requirement.
350
+
351
+ :param requirement: A requirement of the form 'foo (1.0)' or perhaps
352
+ 'foo (>= 1.0, < 2.0, != 1.3)'
353
+ :param prereleases: If ``True``, allow pre-release versions
354
+ to be located. Otherwise, pre-release versions
355
+ are not returned.
356
+ :return: A :class:`Distribution` instance, or ``None`` if no such
357
+ distribution could be located.
358
+ """
359
+ result = None
360
+ r = parse_requirement(requirement)
361
+ if r is None: # pragma: no cover
362
+ raise DistlibException('Not a valid requirement: %r' % requirement)
363
+ scheme = get_scheme(self.scheme)
364
+ self.matcher = matcher = scheme.matcher(r.requirement)
365
+ logger.debug('matcher: %s (%s)', matcher, type(matcher).__name__)
366
+ versions = self.get_project(r.name)
367
+ if len(versions) > 2: # urls and digests keys are present
368
+ # sometimes, versions are invalid
369
+ slist = []
370
+ vcls = matcher.version_class
371
+ for k in versions:
372
+ if k in ('urls', 'digests'):
373
+ continue
374
+ try:
375
+ if not matcher.match(k):
376
+ pass # logger.debug('%s did not match %r', matcher, k)
377
+ else:
378
+ if prereleases or not vcls(k).is_prerelease:
379
+ slist.append(k)
380
+ except Exception: # pragma: no cover
381
+ logger.warning('error matching %s with %r', matcher, k)
382
+ pass # slist.append(k)
383
+ if len(slist) > 1:
384
+ slist = sorted(slist, key=scheme.key)
385
+ if slist:
386
+ logger.debug('sorted list: %s', slist)
387
+ version = slist[-1]
388
+ result = versions[version]
389
+ if result:
390
+ if r.extras:
391
+ result.extras = r.extras
392
+ result.download_urls = versions.get('urls', {}).get(version, set())
393
+ d = {}
394
+ sd = versions.get('digests', {})
395
+ for url in result.download_urls:
396
+ if url in sd: # pragma: no cover
397
+ d[url] = sd[url]
398
+ result.digests = d
399
+ self.matcher = None
400
+ return result
401
+
402
+
403
+ class PyPIRPCLocator(Locator):
404
+ """
405
+ This locator uses XML-RPC to locate distributions. It therefore
406
+ cannot be used with simple mirrors (that only mirror file content).
407
+ """
408
+
409
+ def __init__(self, url, **kwargs):
410
+ """
411
+ Initialise an instance.
412
+
413
+ :param url: The URL to use for XML-RPC.
414
+ :param kwargs: Passed to the superclass constructor.
415
+ """
416
+ super(PyPIRPCLocator, self).__init__(**kwargs)
417
+ self.base_url = url
418
+ self.client = ServerProxy(url, timeout=3.0)
419
+
420
+ def get_distribution_names(self):
421
+ """
422
+ Return all the distribution names known to this locator.
423
+ """
424
+ return set(self.client.list_packages())
425
+
426
+ def _get_project(self, name):
427
+ result = {'urls': {}, 'digests': {}}
428
+ versions = self.client.package_releases(name, True)
429
+ for v in versions:
430
+ urls = self.client.release_urls(name, v)
431
+ data = self.client.release_data(name, v)
432
+ metadata = Metadata(scheme=self.scheme)
433
+ metadata.name = data['name']
434
+ metadata.version = data['version']
435
+ metadata.license = data.get('license')
436
+ metadata.keywords = data.get('keywords', [])
437
+ metadata.summary = data.get('summary')
438
+ dist = Distribution(metadata)
439
+ if urls:
440
+ info = urls[0]
441
+ metadata.source_url = info['url']
442
+ dist.digest = self._get_digest(info)
443
+ dist.locator = self
444
+ result[v] = dist
445
+ for info in urls:
446
+ url = info['url']
447
+ digest = self._get_digest(info)
448
+ result['urls'].setdefault(v, set()).add(url)
449
+ result['digests'][url] = digest
450
+ return result
451
+
452
+
453
+ class PyPIJSONLocator(Locator):
454
+ """
455
+ This locator uses PyPI's JSON interface. It's very limited in functionality
456
+ and probably not worth using.
457
+ """
458
+
459
+ def __init__(self, url, **kwargs):
460
+ super(PyPIJSONLocator, self).__init__(**kwargs)
461
+ self.base_url = ensure_slash(url)
462
+
463
+ def get_distribution_names(self):
464
+ """
465
+ Return all the distribution names known to this locator.
466
+ """
467
+ raise NotImplementedError('Not available from this locator')
468
+
469
+ def _get_project(self, name):
470
+ result = {'urls': {}, 'digests': {}}
471
+ url = urljoin(self.base_url, '%s/json' % quote(name))
472
+ try:
473
+ resp = self.opener.open(url)
474
+ data = resp.read().decode() # for now
475
+ d = json.loads(data)
476
+ md = Metadata(scheme=self.scheme)
477
+ data = d['info']
478
+ md.name = data['name']
479
+ md.version = data['version']
480
+ md.license = data.get('license')
481
+ md.keywords = data.get('keywords', [])
482
+ md.summary = data.get('summary')
483
+ dist = Distribution(md)
484
+ dist.locator = self
485
+ # urls = d['urls']
486
+ result[md.version] = dist
487
+ for info in d['urls']:
488
+ url = info['url']
489
+ dist.download_urls.add(url)
490
+ dist.digests[url] = self._get_digest(info)
491
+ result['urls'].setdefault(md.version, set()).add(url)
492
+ result['digests'][url] = self._get_digest(info)
493
+ # Now get other releases
494
+ for version, infos in d['releases'].items():
495
+ if version == md.version:
496
+ continue # already done
497
+ omd = Metadata(scheme=self.scheme)
498
+ omd.name = md.name
499
+ omd.version = version
500
+ odist = Distribution(omd)
501
+ odist.locator = self
502
+ result[version] = odist
503
+ for info in infos:
504
+ url = info['url']
505
+ odist.download_urls.add(url)
506
+ odist.digests[url] = self._get_digest(info)
507
+ result['urls'].setdefault(version, set()).add(url)
508
+ result['digests'][url] = self._get_digest(info)
509
+
510
+
511
+ # for info in urls:
512
+ # md.source_url = info['url']
513
+ # dist.digest = self._get_digest(info)
514
+ # dist.locator = self
515
+ # for info in urls:
516
+ # url = info['url']
517
+ # result['urls'].setdefault(md.version, set()).add(url)
518
+ # result['digests'][url] = self._get_digest(info)
519
+ except Exception as e:
520
+ self.errors.put(text_type(e))
521
+ logger.exception('JSON fetch failed: %s', e)
522
+ return result
523
+
524
+
525
+ class Page(object):
526
+ """
527
+ This class represents a scraped HTML page.
528
+ """
529
+ # The following slightly hairy-looking regex just looks for the contents of
530
+ # an anchor link, which has an attribute "href" either immediately preceded
531
+ # or immediately followed by a "rel" attribute. The attribute values can be
532
+ # declared with double quotes, single quotes or no quotes - which leads to
533
+ # the length of the expression.
534
+ _href = re.compile(
535
+ """
536
+ (rel\\s*=\\s*(?:"(?P<rel1>[^"]*)"|'(?P<rel2>[^']*)'|(?P<rel3>[^>\\s\n]*))\\s+)?
537
+ href\\s*=\\s*(?:"(?P<url1>[^"]*)"|'(?P<url2>[^']*)'|(?P<url3>[^>\\s\n]*))
538
+ (\\s+rel\\s*=\\s*(?:"(?P<rel4>[^"]*)"|'(?P<rel5>[^']*)'|(?P<rel6>[^>\\s\n]*)))?
539
+ """, re.I | re.S | re.X)
540
+ _base = re.compile(r"""<base\s+href\s*=\s*['"]?([^'">]+)""", re.I | re.S)
541
+
542
+ def __init__(self, data, url):
543
+ """
544
+ Initialise an instance with the Unicode page contents and the URL they
545
+ came from.
546
+ """
547
+ self.data = data
548
+ self.base_url = self.url = url
549
+ m = self._base.search(self.data)
550
+ if m:
551
+ self.base_url = m.group(1)
552
+
553
+ _clean_re = re.compile(r'[^a-z0-9$&+,/:;=?@.#%_\\|-]', re.I)
554
+
555
+ @cached_property
556
+ def links(self):
557
+ """
558
+ Return the URLs of all the links on a page together with information
559
+ about their "rel" attribute, for determining which ones to treat as
560
+ downloads and which ones to queue for further scraping.
561
+ """
562
+
563
+ def clean(url):
564
+ "Tidy up an URL."
565
+ scheme, netloc, path, params, query, frag = urlparse(url)
566
+ return urlunparse((scheme, netloc, quote(path), params, query, frag))
567
+
568
+ result = set()
569
+ for match in self._href.finditer(self.data):
570
+ d = match.groupdict('')
571
+ rel = (d['rel1'] or d['rel2'] or d['rel3'] or d['rel4'] or d['rel5'] or d['rel6'])
572
+ url = d['url1'] or d['url2'] or d['url3']
573
+ url = urljoin(self.base_url, url)
574
+ url = unescape(url)
575
+ url = self._clean_re.sub(lambda m: '%%%2x' % ord(m.group(0)), url)
576
+ result.add((url, rel))
577
+ # We sort the result, hoping to bring the most recent versions
578
+ # to the front
579
+ result = sorted(result, key=lambda t: t[0], reverse=True)
580
+ return result
581
+
582
+
583
+ class SimpleScrapingLocator(Locator):
584
+ """
585
+ A locator which scrapes HTML pages to locate downloads for a distribution.
586
+ This runs multiple threads to do the I/O; performance is at least as good
587
+ as pip's PackageFinder, which works in an analogous fashion.
588
+ """
589
+
590
+ # These are used to deal with various Content-Encoding schemes.
591
+ decoders = {
592
+ 'deflate': zlib.decompress,
593
+ 'gzip': lambda b: gzip.GzipFile(fileobj=BytesIO(b)).read(),
594
+ 'none': lambda b: b,
595
+ }
596
+
597
+ def __init__(self, url, timeout=None, num_workers=10, **kwargs):
598
+ """
599
+ Initialise an instance.
600
+ :param url: The root URL to use for scraping.
601
+ :param timeout: The timeout, in seconds, to be applied to requests.
602
+ This defaults to ``None`` (no timeout specified).
603
+ :param num_workers: The number of worker threads you want to do I/O,
604
+ This defaults to 10.
605
+ :param kwargs: Passed to the superclass.
606
+ """
607
+ super(SimpleScrapingLocator, self).__init__(**kwargs)
608
+ self.base_url = ensure_slash(url)
609
+ self.timeout = timeout
610
+ self._page_cache = {}
611
+ self._seen = set()
612
+ self._to_fetch = queue.Queue()
613
+ self._bad_hosts = set()
614
+ self.skip_externals = False
615
+ self.num_workers = num_workers
616
+ self._lock = threading.RLock()
617
+ # See issue #45: we need to be resilient when the locator is used
618
+ # in a thread, e.g. with concurrent.futures. We can't use self._lock
619
+ # as it is for coordinating our internal threads - the ones created
620
+ # in _prepare_threads.
621
+ self._gplock = threading.RLock()
622
+ self.platform_check = False # See issue #112
623
+
624
+ def _prepare_threads(self):
625
+ """
626
+ Threads are created only when get_project is called, and terminate
627
+ before it returns. They are there primarily to parallelise I/O (i.e.
628
+ fetching web pages).
629
+ """
630
+ self._threads = []
631
+ for i in range(self.num_workers):
632
+ t = threading.Thread(target=self._fetch)
633
+ t.daemon = True
634
+ t.start()
635
+ self._threads.append(t)
636
+
637
+ def _wait_threads(self):
638
+ """
639
+ Tell all the threads to terminate (by sending a sentinel value) and
640
+ wait for them to do so.
641
+ """
642
+ # Note that you need two loops, since you can't say which
643
+ # thread will get each sentinel
644
+ for t in self._threads:
645
+ self._to_fetch.put(None) # sentinel
646
+ for t in self._threads:
647
+ t.join()
648
+ self._threads = []
649
+
650
+ def _get_project(self, name):
651
+ result = {'urls': {}, 'digests': {}}
652
+ with self._gplock:
653
+ self.result = result
654
+ self.project_name = name
655
+ url = urljoin(self.base_url, '%s/' % quote(name))
656
+ self._seen.clear()
657
+ self._page_cache.clear()
658
+ self._prepare_threads()
659
+ try:
660
+ logger.debug('Queueing %s', url)
661
+ self._to_fetch.put(url)
662
+ self._to_fetch.join()
663
+ finally:
664
+ self._wait_threads()
665
+ del self.result
666
+ return result
667
+
668
+ platform_dependent = re.compile(r'\b(linux_(i\d86|x86_64|arm\w+)|'
669
+ r'win(32|_amd64)|macosx_?\d+)\b', re.I)
670
+
671
+ def _is_platform_dependent(self, url):
672
+ """
673
+ Does an URL refer to a platform-specific download?
674
+ """
675
+ return self.platform_dependent.search(url)
676
+
677
+ def _process_download(self, url):
678
+ """
679
+ See if an URL is a suitable download for a project.
680
+
681
+ If it is, register information in the result dictionary (for
682
+ _get_project) about the specific version it's for.
683
+
684
+ Note that the return value isn't actually used other than as a boolean
685
+ value.
686
+ """
687
+ if self.platform_check and self._is_platform_dependent(url):
688
+ info = None
689
+ else:
690
+ info = self.convert_url_to_download_info(url, self.project_name)
691
+ logger.debug('process_download: %s -> %s', url, info)
692
+ if info:
693
+ with self._lock: # needed because self.result is shared
694
+ self._update_version_data(self.result, info)
695
+ return info
696
+
697
+ def _should_queue(self, link, referrer, rel):
698
+ """
699
+ Determine whether a link URL from a referring page and with a
700
+ particular "rel" attribute should be queued for scraping.
701
+ """
702
+ scheme, netloc, path, _, _, _ = urlparse(link)
703
+ if path.endswith(self.source_extensions + self.binary_extensions + self.excluded_extensions):
704
+ result = False
705
+ elif self.skip_externals and not link.startswith(self.base_url):
706
+ result = False
707
+ elif not referrer.startswith(self.base_url):
708
+ result = False
709
+ elif rel not in ('homepage', 'download'):
710
+ result = False
711
+ elif scheme not in ('http', 'https', 'ftp'):
712
+ result = False
713
+ elif self._is_platform_dependent(link):
714
+ result = False
715
+ else:
716
+ host = netloc.split(':', 1)[0]
717
+ if host.lower() == 'localhost':
718
+ result = False
719
+ else:
720
+ result = True
721
+ logger.debug('should_queue: %s (%s) from %s -> %s', link, rel, referrer, result)
722
+ return result
723
+
724
+ def _fetch(self):
725
+ """
726
+ Get a URL to fetch from the work queue, get the HTML page, examine its
727
+ links for download candidates and candidates for further scraping.
728
+
729
+ This is a handy method to run in a thread.
730
+ """
731
+ while True:
732
+ url = self._to_fetch.get()
733
+ try:
734
+ if url:
735
+ page = self.get_page(url)
736
+ if page is None: # e.g. after an error
737
+ continue
738
+ for link, rel in page.links:
739
+ if link not in self._seen:
740
+ try:
741
+ self._seen.add(link)
742
+ if (not self._process_download(link) and self._should_queue(link, url, rel)):
743
+ logger.debug('Queueing %s from %s', link, url)
744
+ self._to_fetch.put(link)
745
+ except MetadataInvalidError: # e.g. invalid versions
746
+ pass
747
+ except Exception as e: # pragma: no cover
748
+ self.errors.put(text_type(e))
749
+ finally:
750
+ # always do this, to avoid hangs :-)
751
+ self._to_fetch.task_done()
752
+ if not url:
753
+ # logger.debug('Sentinel seen, quitting.')
754
+ break
755
+
756
+ def get_page(self, url):
757
+ """
758
+ Get the HTML for an URL, possibly from an in-memory cache.
759
+
760
+ XXX TODO Note: this cache is never actually cleared. It's assumed that
761
+ the data won't get stale over the lifetime of a locator instance (not
762
+ necessarily true for the default_locator).
763
+ """
764
+ # http://peak.telecommunity.com/DevCenter/EasyInstall#package-index-api
765
+ scheme, netloc, path, _, _, _ = urlparse(url)
766
+ if scheme == 'file' and os.path.isdir(url2pathname(path)):
767
+ url = urljoin(ensure_slash(url), 'index.html')
768
+
769
+ if url in self._page_cache:
770
+ result = self._page_cache[url]
771
+ logger.debug('Returning %s from cache: %s', url, result)
772
+ else:
773
+ host = netloc.split(':', 1)[0]
774
+ result = None
775
+ if host in self._bad_hosts:
776
+ logger.debug('Skipping %s due to bad host %s', url, host)
777
+ else:
778
+ req = Request(url, headers={'Accept-encoding': 'identity'})
779
+ try:
780
+ logger.debug('Fetching %s', url)
781
+ resp = self.opener.open(req, timeout=self.timeout)
782
+ logger.debug('Fetched %s', url)
783
+ headers = resp.info()
784
+ content_type = headers.get('Content-Type', '')
785
+ if HTML_CONTENT_TYPE.match(content_type):
786
+ final_url = resp.geturl()
787
+ data = resp.read()
788
+ encoding = headers.get('Content-Encoding')
789
+ if encoding:
790
+ decoder = self.decoders[encoding] # fail if not found
791
+ data = decoder(data)
792
+ encoding = 'utf-8'
793
+ m = CHARSET.search(content_type)
794
+ if m:
795
+ encoding = m.group(1)
796
+ try:
797
+ data = data.decode(encoding)
798
+ except UnicodeError: # pragma: no cover
799
+ data = data.decode('latin-1') # fallback
800
+ result = Page(data, final_url)
801
+ self._page_cache[final_url] = result
802
+ except HTTPError as e:
803
+ if e.code != 404:
804
+ logger.exception('Fetch failed: %s: %s', url, e)
805
+ except URLError as e: # pragma: no cover
806
+ logger.exception('Fetch failed: %s: %s', url, e)
807
+ with self._lock:
808
+ self._bad_hosts.add(host)
809
+ except Exception as e: # pragma: no cover
810
+ logger.exception('Fetch failed: %s: %s', url, e)
811
+ finally:
812
+ self._page_cache[url] = result # even if None (failure)
813
+ return result
814
+
815
+ _distname_re = re.compile('<a href=[^>]*>([^<]+)<')
816
+
817
+ def get_distribution_names(self):
818
+ """
819
+ Return all the distribution names known to this locator.
820
+ """
821
+ result = set()
822
+ page = self.get_page(self.base_url)
823
+ if not page:
824
+ raise DistlibException('Unable to get %s' % self.base_url)
825
+ for match in self._distname_re.finditer(page.data):
826
+ result.add(match.group(1))
827
+ return result
828
+
829
+
830
+ class DirectoryLocator(Locator):
831
+ """
832
+ This class locates distributions in a directory tree.
833
+ """
834
+
835
+ def __init__(self, path, **kwargs):
836
+ """
837
+ Initialise an instance.
838
+ :param path: The root of the directory tree to search.
839
+ :param kwargs: Passed to the superclass constructor,
840
+ except for:
841
+ * recursive - if True (the default), subdirectories are
842
+ recursed into. If False, only the top-level directory
843
+ is searched,
844
+ """
845
+ self.recursive = kwargs.pop('recursive', True)
846
+ super(DirectoryLocator, self).__init__(**kwargs)
847
+ path = os.path.abspath(path)
848
+ if not os.path.isdir(path): # pragma: no cover
849
+ raise DistlibException('Not a directory: %r' % path)
850
+ self.base_dir = path
851
+
852
+ def should_include(self, filename, parent):
853
+ """
854
+ Should a filename be considered as a candidate for a distribution
855
+ archive? As well as the filename, the directory which contains it
856
+ is provided, though not used by the current implementation.
857
+ """
858
+ return filename.endswith(self.downloadable_extensions)
859
+
860
+ def _get_project(self, name):
861
+ result = {'urls': {}, 'digests': {}}
862
+ for root, dirs, files in os.walk(self.base_dir):
863
+ for fn in files:
864
+ if self.should_include(fn, root):
865
+ fn = os.path.join(root, fn)
866
+ url = urlunparse(('file', '', pathname2url(os.path.abspath(fn)), '', '', ''))
867
+ info = self.convert_url_to_download_info(url, name)
868
+ if info:
869
+ self._update_version_data(result, info)
870
+ if not self.recursive:
871
+ break
872
+ return result
873
+
874
+ def get_distribution_names(self):
875
+ """
876
+ Return all the distribution names known to this locator.
877
+ """
878
+ result = set()
879
+ for root, dirs, files in os.walk(self.base_dir):
880
+ for fn in files:
881
+ if self.should_include(fn, root):
882
+ fn = os.path.join(root, fn)
883
+ url = urlunparse(('file', '', pathname2url(os.path.abspath(fn)), '', '', ''))
884
+ info = self.convert_url_to_download_info(url, None)
885
+ if info:
886
+ result.add(info['name'])
887
+ if not self.recursive:
888
+ break
889
+ return result
890
+
891
+
892
+ class JSONLocator(Locator):
893
+ """
894
+ This locator uses special extended metadata (not available on PyPI) and is
895
+ the basis of performant dependency resolution in distlib. Other locators
896
+ require archive downloads before dependencies can be determined! As you
897
+ might imagine, that can be slow.
898
+ """
899
+
900
+ def get_distribution_names(self):
901
+ """
902
+ Return all the distribution names known to this locator.
903
+ """
904
+ raise NotImplementedError('Not available from this locator')
905
+
906
+ def _get_project(self, name):
907
+ result = {'urls': {}, 'digests': {}}
908
+ data = get_project_data(name)
909
+ if data:
910
+ for info in data.get('files', []):
911
+ if info['ptype'] != 'sdist' or info['pyversion'] != 'source':
912
+ continue
913
+ # We don't store summary in project metadata as it makes
914
+ # the data bigger for no benefit during dependency
915
+ # resolution
916
+ dist = make_dist(data['name'],
917
+ info['version'],
918
+ summary=data.get('summary', 'Placeholder for summary'),
919
+ scheme=self.scheme)
920
+ md = dist.metadata
921
+ md.source_url = info['url']
922
+ # TODO SHA256 digest
923
+ if 'digest' in info and info['digest']:
924
+ dist.digest = ('md5', info['digest'])
925
+ md.dependencies = info.get('requirements', {})
926
+ dist.exports = info.get('exports', {})
927
+ result[dist.version] = dist
928
+ result['urls'].setdefault(dist.version, set()).add(info['url'])
929
+ return result
930
+
931
+
932
+ class DistPathLocator(Locator):
933
+ """
934
+ This locator finds installed distributions in a path. It can be useful for
935
+ adding to an :class:`AggregatingLocator`.
936
+ """
937
+
938
+ def __init__(self, distpath, **kwargs):
939
+ """
940
+ Initialise an instance.
941
+
942
+ :param distpath: A :class:`DistributionPath` instance to search.
943
+ """
944
+ super(DistPathLocator, self).__init__(**kwargs)
945
+ assert isinstance(distpath, DistributionPath)
946
+ self.distpath = distpath
947
+
948
+ def _get_project(self, name):
949
+ dist = self.distpath.get_distribution(name)
950
+ if dist is None:
951
+ result = {'urls': {}, 'digests': {}}
952
+ else:
953
+ result = {
954
+ dist.version: dist,
955
+ 'urls': {
956
+ dist.version: set([dist.source_url])
957
+ },
958
+ 'digests': {
959
+ dist.version: set([None])
960
+ }
961
+ }
962
+ return result
963
+
964
+
965
+ class AggregatingLocator(Locator):
966
+ """
967
+ This class allows you to chain and/or merge a list of locators.
968
+ """
969
+
970
+ def __init__(self, *locators, **kwargs):
971
+ """
972
+ Initialise an instance.
973
+
974
+ :param locators: The list of locators to search.
975
+ :param kwargs: Passed to the superclass constructor,
976
+ except for:
977
+ * merge - if False (the default), the first successful
978
+ search from any of the locators is returned. If True,
979
+ the results from all locators are merged (this can be
980
+ slow).
981
+ """
982
+ self.merge = kwargs.pop('merge', False)
983
+ self.locators = locators
984
+ super(AggregatingLocator, self).__init__(**kwargs)
985
+
986
+ def clear_cache(self):
987
+ super(AggregatingLocator, self).clear_cache()
988
+ for locator in self.locators:
989
+ locator.clear_cache()
990
+
991
+ def _set_scheme(self, value):
992
+ self._scheme = value
993
+ for locator in self.locators:
994
+ locator.scheme = value
995
+
996
+ scheme = property(Locator.scheme.fget, _set_scheme)
997
+
998
+ def _get_project(self, name):
999
+ result = {}
1000
+ for locator in self.locators:
1001
+ d = locator.get_project(name)
1002
+ if d:
1003
+ if self.merge:
1004
+ files = result.get('urls', {})
1005
+ digests = result.get('digests', {})
1006
+ # next line could overwrite result['urls'], result['digests']
1007
+ result.update(d)
1008
+ df = result.get('urls')
1009
+ if files and df:
1010
+ for k, v in files.items():
1011
+ if k in df:
1012
+ df[k] |= v
1013
+ else:
1014
+ df[k] = v
1015
+ dd = result.get('digests')
1016
+ if digests and dd:
1017
+ dd.update(digests)
1018
+ else:
1019
+ # See issue #18. If any dists are found and we're looking
1020
+ # for specific constraints, we only return something if
1021
+ # a match is found. For example, if a DirectoryLocator
1022
+ # returns just foo (1.0) while we're looking for
1023
+ # foo (>= 2.0), we'll pretend there was nothing there so
1024
+ # that subsequent locators can be queried. Otherwise we
1025
+ # would just return foo (1.0) which would then lead to a
1026
+ # failure to find foo (>= 2.0), because other locators
1027
+ # weren't searched. Note that this only matters when
1028
+ # merge=False.
1029
+ if self.matcher is None:
1030
+ found = True
1031
+ else:
1032
+ found = False
1033
+ for k in d:
1034
+ if self.matcher.match(k):
1035
+ found = True
1036
+ break
1037
+ if found:
1038
+ result = d
1039
+ break
1040
+ return result
1041
+
1042
+ def get_distribution_names(self):
1043
+ """
1044
+ Return all the distribution names known to this locator.
1045
+ """
1046
+ result = set()
1047
+ for locator in self.locators:
1048
+ try:
1049
+ result |= locator.get_distribution_names()
1050
+ except NotImplementedError:
1051
+ pass
1052
+ return result
1053
+
1054
+
1055
+ # We use a legacy scheme simply because most of the dists on PyPI use legacy
1056
+ # versions which don't conform to PEP 440.
1057
+ default_locator = AggregatingLocator(
1058
+ # JSONLocator(), # don't use as PEP 426 is withdrawn
1059
+ SimpleScrapingLocator('https://pypi.org/simple/', timeout=3.0),
1060
+ scheme='legacy')
1061
+
1062
+ locate = default_locator.locate
1063
+
1064
+
1065
+ class DependencyFinder(object):
1066
+ """
1067
+ Locate dependencies for distributions.
1068
+ """
1069
+
1070
+ def __init__(self, locator=None):
1071
+ """
1072
+ Initialise an instance, using the specified locator
1073
+ to locate distributions.
1074
+ """
1075
+ self.locator = locator or default_locator
1076
+ self.scheme = get_scheme(self.locator.scheme)
1077
+
1078
+ def add_distribution(self, dist):
1079
+ """
1080
+ Add a distribution to the finder. This will update internal information
1081
+ about who provides what.
1082
+ :param dist: The distribution to add.
1083
+ """
1084
+ logger.debug('adding distribution %s', dist)
1085
+ name = dist.key
1086
+ self.dists_by_name[name] = dist
1087
+ self.dists[(name, dist.version)] = dist
1088
+ for p in dist.provides:
1089
+ name, version = parse_name_and_version(p)
1090
+ logger.debug('Add to provided: %s, %s, %s', name, version, dist)
1091
+ self.provided.setdefault(name, set()).add((version, dist))
1092
+
1093
+ def remove_distribution(self, dist):
1094
+ """
1095
+ Remove a distribution from the finder. This will update internal
1096
+ information about who provides what.
1097
+ :param dist: The distribution to remove.
1098
+ """
1099
+ logger.debug('removing distribution %s', dist)
1100
+ name = dist.key
1101
+ del self.dists_by_name[name]
1102
+ del self.dists[(name, dist.version)]
1103
+ for p in dist.provides:
1104
+ name, version = parse_name_and_version(p)
1105
+ logger.debug('Remove from provided: %s, %s, %s', name, version, dist)
1106
+ s = self.provided[name]
1107
+ s.remove((version, dist))
1108
+ if not s:
1109
+ del self.provided[name]
1110
+
1111
+ def get_matcher(self, reqt):
1112
+ """
1113
+ Get a version matcher for a requirement.
1114
+ :param reqt: The requirement
1115
+ :type reqt: str
1116
+ :return: A version matcher (an instance of
1117
+ :class:`distlib.version.Matcher`).
1118
+ """
1119
+ try:
1120
+ matcher = self.scheme.matcher(reqt)
1121
+ except UnsupportedVersionError: # pragma: no cover
1122
+ # XXX compat-mode if cannot read the version
1123
+ name = reqt.split()[0]
1124
+ matcher = self.scheme.matcher(name)
1125
+ return matcher
1126
+
1127
+ def find_providers(self, reqt):
1128
+ """
1129
+ Find the distributions which can fulfill a requirement.
1130
+
1131
+ :param reqt: The requirement.
1132
+ :type reqt: str
1133
+ :return: A set of distribution which can fulfill the requirement.
1134
+ """
1135
+ matcher = self.get_matcher(reqt)
1136
+ name = matcher.key # case-insensitive
1137
+ result = set()
1138
+ provided = self.provided
1139
+ if name in provided:
1140
+ for version, provider in provided[name]:
1141
+ try:
1142
+ match = matcher.match(version)
1143
+ except UnsupportedVersionError:
1144
+ match = False
1145
+
1146
+ if match:
1147
+ result.add(provider)
1148
+ break
1149
+ return result
1150
+
1151
+ def try_to_replace(self, provider, other, problems):
1152
+ """
1153
+ Attempt to replace one provider with another. This is typically used
1154
+ when resolving dependencies from multiple sources, e.g. A requires
1155
+ (B >= 1.0) while C requires (B >= 1.1).
1156
+
1157
+ For successful replacement, ``provider`` must meet all the requirements
1158
+ which ``other`` fulfills.
1159
+
1160
+ :param provider: The provider we are trying to replace with.
1161
+ :param other: The provider we're trying to replace.
1162
+ :param problems: If False is returned, this will contain what
1163
+ problems prevented replacement. This is currently
1164
+ a tuple of the literal string 'cantreplace',
1165
+ ``provider``, ``other`` and the set of requirements
1166
+ that ``provider`` couldn't fulfill.
1167
+ :return: True if we can replace ``other`` with ``provider``, else
1168
+ False.
1169
+ """
1170
+ rlist = self.reqts[other]
1171
+ unmatched = set()
1172
+ for s in rlist:
1173
+ matcher = self.get_matcher(s)
1174
+ if not matcher.match(provider.version):
1175
+ unmatched.add(s)
1176
+ if unmatched:
1177
+ # can't replace other with provider
1178
+ problems.add(('cantreplace', provider, other, frozenset(unmatched)))
1179
+ result = False
1180
+ else:
1181
+ # can replace other with provider
1182
+ self.remove_distribution(other)
1183
+ del self.reqts[other]
1184
+ for s in rlist:
1185
+ self.reqts.setdefault(provider, set()).add(s)
1186
+ self.add_distribution(provider)
1187
+ result = True
1188
+ return result
1189
+
1190
+ def find(self, requirement, meta_extras=None, prereleases=False):
1191
+ """
1192
+ Find a distribution and all distributions it depends on.
1193
+
1194
+ :param requirement: The requirement specifying the distribution to
1195
+ find, or a Distribution instance.
1196
+ :param meta_extras: A list of meta extras such as :test:, :build: and
1197
+ so on.
1198
+ :param prereleases: If ``True``, allow pre-release versions to be
1199
+ returned - otherwise, don't return prereleases
1200
+ unless they're all that's available.
1201
+
1202
+ Return a set of :class:`Distribution` instances and a set of
1203
+ problems.
1204
+
1205
+ The distributions returned should be such that they have the
1206
+ :attr:`required` attribute set to ``True`` if they were
1207
+ from the ``requirement`` passed to ``find()``, and they have the
1208
+ :attr:`build_time_dependency` attribute set to ``True`` unless they
1209
+ are post-installation dependencies of the ``requirement``.
1210
+
1211
+ The problems should be a tuple consisting of the string
1212
+ ``'unsatisfied'`` and the requirement which couldn't be satisfied
1213
+ by any distribution known to the locator.
1214
+ """
1215
+
1216
+ self.provided = {}
1217
+ self.dists = {}
1218
+ self.dists_by_name = {}
1219
+ self.reqts = {}
1220
+
1221
+ meta_extras = set(meta_extras or [])
1222
+ if ':*:' in meta_extras:
1223
+ meta_extras.remove(':*:')
1224
+ # :meta: and :run: are implicitly included
1225
+ meta_extras |= set([':test:', ':build:', ':dev:'])
1226
+
1227
+ if isinstance(requirement, Distribution):
1228
+ dist = odist = requirement
1229
+ logger.debug('passed %s as requirement', odist)
1230
+ else:
1231
+ dist = odist = self.locator.locate(requirement, prereleases=prereleases)
1232
+ if dist is None:
1233
+ raise DistlibException('Unable to locate %r' % requirement)
1234
+ logger.debug('located %s', odist)
1235
+ dist.requested = True
1236
+ problems = set()
1237
+ todo = set([dist])
1238
+ install_dists = set([odist])
1239
+ while todo:
1240
+ dist = todo.pop()
1241
+ name = dist.key # case-insensitive
1242
+ if name not in self.dists_by_name:
1243
+ self.add_distribution(dist)
1244
+ else:
1245
+ # import pdb; pdb.set_trace()
1246
+ other = self.dists_by_name[name]
1247
+ if other != dist:
1248
+ self.try_to_replace(dist, other, problems)
1249
+
1250
+ ireqts = dist.run_requires | dist.meta_requires
1251
+ sreqts = dist.build_requires
1252
+ ereqts = set()
1253
+ if meta_extras and dist in install_dists:
1254
+ for key in ('test', 'build', 'dev'):
1255
+ e = ':%s:' % key
1256
+ if e in meta_extras:
1257
+ ereqts |= getattr(dist, '%s_requires' % key)
1258
+ all_reqts = ireqts | sreqts | ereqts
1259
+ for r in all_reqts:
1260
+ providers = self.find_providers(r)
1261
+ if not providers:
1262
+ logger.debug('No providers found for %r', r)
1263
+ provider = self.locator.locate(r, prereleases=prereleases)
1264
+ # If no provider is found and we didn't consider
1265
+ # prereleases, consider them now.
1266
+ if provider is None and not prereleases:
1267
+ provider = self.locator.locate(r, prereleases=True)
1268
+ if provider is None:
1269
+ logger.debug('Cannot satisfy %r', r)
1270
+ problems.add(('unsatisfied', r))
1271
+ else:
1272
+ n, v = provider.key, provider.version
1273
+ if (n, v) not in self.dists:
1274
+ todo.add(provider)
1275
+ providers.add(provider)
1276
+ if r in ireqts and dist in install_dists:
1277
+ install_dists.add(provider)
1278
+ logger.debug('Adding %s to install_dists', provider.name_and_version)
1279
+ for p in providers:
1280
+ name = p.key
1281
+ if name not in self.dists_by_name:
1282
+ self.reqts.setdefault(p, set()).add(r)
1283
+ else:
1284
+ other = self.dists_by_name[name]
1285
+ if other != p:
1286
+ # see if other can be replaced by p
1287
+ self.try_to_replace(p, other, problems)
1288
+
1289
+ dists = set(self.dists.values())
1290
+ for dist in dists:
1291
+ dist.build_time_dependency = dist not in install_dists
1292
+ if dist.build_time_dependency:
1293
+ logger.debug('%s is a build-time dependency only.', dist.name_and_version)
1294
+ logger.debug('find done for %s', odist)
1295
+ return dists, problems
lib/python3.12/site-packages/pip/_vendor/distlib/manifest.py ADDED
@@ -0,0 +1,384 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2012-2023 Python Software Foundation.
4
+ # See LICENSE.txt and CONTRIBUTORS.txt.
5
+ #
6
+ """
7
+ Class representing the list of files in a distribution.
8
+
9
+ Equivalent to distutils.filelist, but fixes some problems.
10
+ """
11
+ import fnmatch
12
+ import logging
13
+ import os
14
+ import re
15
+ import sys
16
+
17
+ from . import DistlibException
18
+ from .compat import fsdecode
19
+ from .util import convert_path
20
+
21
+
22
+ __all__ = ['Manifest']
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+ # a \ followed by some spaces + EOL
27
+ _COLLAPSE_PATTERN = re.compile('\\\\w*\n', re.M)
28
+ _COMMENTED_LINE = re.compile('#.*?(?=\n)|\n(?=$)', re.M | re.S)
29
+
30
+ #
31
+ # Due to the different results returned by fnmatch.translate, we need
32
+ # to do slightly different processing for Python 2.7 and 3.2 ... this needed
33
+ # to be brought in for Python 3.6 onwards.
34
+ #
35
+ _PYTHON_VERSION = sys.version_info[:2]
36
+
37
+
38
+ class Manifest(object):
39
+ """
40
+ A list of files built by exploring the filesystem and filtered by applying various
41
+ patterns to what we find there.
42
+ """
43
+
44
+ def __init__(self, base=None):
45
+ """
46
+ Initialise an instance.
47
+
48
+ :param base: The base directory to explore under.
49
+ """
50
+ self.base = os.path.abspath(os.path.normpath(base or os.getcwd()))
51
+ self.prefix = self.base + os.sep
52
+ self.allfiles = None
53
+ self.files = set()
54
+
55
+ #
56
+ # Public API
57
+ #
58
+
59
+ def findall(self):
60
+ """Find all files under the base and set ``allfiles`` to the absolute
61
+ pathnames of files found.
62
+ """
63
+ from stat import S_ISREG, S_ISDIR, S_ISLNK
64
+
65
+ self.allfiles = allfiles = []
66
+ root = self.base
67
+ stack = [root]
68
+ pop = stack.pop
69
+ push = stack.append
70
+
71
+ while stack:
72
+ root = pop()
73
+ names = os.listdir(root)
74
+
75
+ for name in names:
76
+ fullname = os.path.join(root, name)
77
+
78
+ # Avoid excess stat calls -- just one will do, thank you!
79
+ stat = os.stat(fullname)
80
+ mode = stat.st_mode
81
+ if S_ISREG(mode):
82
+ allfiles.append(fsdecode(fullname))
83
+ elif S_ISDIR(mode) and not S_ISLNK(mode):
84
+ push(fullname)
85
+
86
+ def add(self, item):
87
+ """
88
+ Add a file to the manifest.
89
+
90
+ :param item: The pathname to add. This can be relative to the base.
91
+ """
92
+ if not item.startswith(self.prefix):
93
+ item = os.path.join(self.base, item)
94
+ self.files.add(os.path.normpath(item))
95
+
96
+ def add_many(self, items):
97
+ """
98
+ Add a list of files to the manifest.
99
+
100
+ :param items: The pathnames to add. These can be relative to the base.
101
+ """
102
+ for item in items:
103
+ self.add(item)
104
+
105
+ def sorted(self, wantdirs=False):
106
+ """
107
+ Return sorted files in directory order
108
+ """
109
+
110
+ def add_dir(dirs, d):
111
+ dirs.add(d)
112
+ logger.debug('add_dir added %s', d)
113
+ if d != self.base:
114
+ parent, _ = os.path.split(d)
115
+ assert parent not in ('', '/')
116
+ add_dir(dirs, parent)
117
+
118
+ result = set(self.files) # make a copy!
119
+ if wantdirs:
120
+ dirs = set()
121
+ for f in result:
122
+ add_dir(dirs, os.path.dirname(f))
123
+ result |= dirs
124
+ return [os.path.join(*path_tuple) for path_tuple in
125
+ sorted(os.path.split(path) for path in result)]
126
+
127
+ def clear(self):
128
+ """Clear all collected files."""
129
+ self.files = set()
130
+ self.allfiles = []
131
+
132
+ def process_directive(self, directive):
133
+ """
134
+ Process a directive which either adds some files from ``allfiles`` to
135
+ ``files``, or removes some files from ``files``.
136
+
137
+ :param directive: The directive to process. This should be in a format
138
+ compatible with distutils ``MANIFEST.in`` files:
139
+
140
+ http://docs.python.org/distutils/sourcedist.html#commands
141
+ """
142
+ # Parse the line: split it up, make sure the right number of words
143
+ # is there, and return the relevant words. 'action' is always
144
+ # defined: it's the first word of the line. Which of the other
145
+ # three are defined depends on the action; it'll be either
146
+ # patterns, (dir and patterns), or (dirpattern).
147
+ action, patterns, thedir, dirpattern = self._parse_directive(directive)
148
+
149
+ # OK, now we know that the action is valid and we have the
150
+ # right number of words on the line for that action -- so we
151
+ # can proceed with minimal error-checking.
152
+ if action == 'include':
153
+ for pattern in patterns:
154
+ if not self._include_pattern(pattern, anchor=True):
155
+ logger.warning('no files found matching %r', pattern)
156
+
157
+ elif action == 'exclude':
158
+ for pattern in patterns:
159
+ self._exclude_pattern(pattern, anchor=True)
160
+
161
+ elif action == 'global-include':
162
+ for pattern in patterns:
163
+ if not self._include_pattern(pattern, anchor=False):
164
+ logger.warning('no files found matching %r '
165
+ 'anywhere in distribution', pattern)
166
+
167
+ elif action == 'global-exclude':
168
+ for pattern in patterns:
169
+ self._exclude_pattern(pattern, anchor=False)
170
+
171
+ elif action == 'recursive-include':
172
+ for pattern in patterns:
173
+ if not self._include_pattern(pattern, prefix=thedir):
174
+ logger.warning('no files found matching %r '
175
+ 'under directory %r', pattern, thedir)
176
+
177
+ elif action == 'recursive-exclude':
178
+ for pattern in patterns:
179
+ self._exclude_pattern(pattern, prefix=thedir)
180
+
181
+ elif action == 'graft':
182
+ if not self._include_pattern(None, prefix=dirpattern):
183
+ logger.warning('no directories found matching %r',
184
+ dirpattern)
185
+
186
+ elif action == 'prune':
187
+ if not self._exclude_pattern(None, prefix=dirpattern):
188
+ logger.warning('no previously-included directories found '
189
+ 'matching %r', dirpattern)
190
+ else: # pragma: no cover
191
+ # This should never happen, as it should be caught in
192
+ # _parse_template_line
193
+ raise DistlibException(
194
+ 'invalid action %r' % action)
195
+
196
+ #
197
+ # Private API
198
+ #
199
+
200
+ def _parse_directive(self, directive):
201
+ """
202
+ Validate a directive.
203
+ :param directive: The directive to validate.
204
+ :return: A tuple of action, patterns, thedir, dir_patterns
205
+ """
206
+ words = directive.split()
207
+ if len(words) == 1 and words[0] not in ('include', 'exclude',
208
+ 'global-include',
209
+ 'global-exclude',
210
+ 'recursive-include',
211
+ 'recursive-exclude',
212
+ 'graft', 'prune'):
213
+ # no action given, let's use the default 'include'
214
+ words.insert(0, 'include')
215
+
216
+ action = words[0]
217
+ patterns = thedir = dir_pattern = None
218
+
219
+ if action in ('include', 'exclude',
220
+ 'global-include', 'global-exclude'):
221
+ if len(words) < 2:
222
+ raise DistlibException(
223
+ '%r expects <pattern1> <pattern2> ...' % action)
224
+
225
+ patterns = [convert_path(word) for word in words[1:]]
226
+
227
+ elif action in ('recursive-include', 'recursive-exclude'):
228
+ if len(words) < 3:
229
+ raise DistlibException(
230
+ '%r expects <dir> <pattern1> <pattern2> ...' % action)
231
+
232
+ thedir = convert_path(words[1])
233
+ patterns = [convert_path(word) for word in words[2:]]
234
+
235
+ elif action in ('graft', 'prune'):
236
+ if len(words) != 2:
237
+ raise DistlibException(
238
+ '%r expects a single <dir_pattern>' % action)
239
+
240
+ dir_pattern = convert_path(words[1])
241
+
242
+ else:
243
+ raise DistlibException('unknown action %r' % action)
244
+
245
+ return action, patterns, thedir, dir_pattern
246
+
247
+ def _include_pattern(self, pattern, anchor=True, prefix=None,
248
+ is_regex=False):
249
+ """Select strings (presumably filenames) from 'self.files' that
250
+ match 'pattern', a Unix-style wildcard (glob) pattern.
251
+
252
+ Patterns are not quite the same as implemented by the 'fnmatch'
253
+ module: '*' and '?' match non-special characters, where "special"
254
+ is platform-dependent: slash on Unix; colon, slash, and backslash on
255
+ DOS/Windows; and colon on Mac OS.
256
+
257
+ If 'anchor' is true (the default), then the pattern match is more
258
+ stringent: "*.py" will match "foo.py" but not "foo/bar.py". If
259
+ 'anchor' is false, both of these will match.
260
+
261
+ If 'prefix' is supplied, then only filenames starting with 'prefix'
262
+ (itself a pattern) and ending with 'pattern', with anything in between
263
+ them, will match. 'anchor' is ignored in this case.
264
+
265
+ If 'is_regex' is true, 'anchor' and 'prefix' are ignored, and
266
+ 'pattern' is assumed to be either a string containing a regex or a
267
+ regex object -- no translation is done, the regex is just compiled
268
+ and used as-is.
269
+
270
+ Selected strings will be added to self.files.
271
+
272
+ Return True if files are found.
273
+ """
274
+ # XXX docstring lying about what the special chars are?
275
+ found = False
276
+ pattern_re = self._translate_pattern(pattern, anchor, prefix, is_regex)
277
+
278
+ # delayed loading of allfiles list
279
+ if self.allfiles is None:
280
+ self.findall()
281
+
282
+ for name in self.allfiles:
283
+ if pattern_re.search(name):
284
+ self.files.add(name)
285
+ found = True
286
+ return found
287
+
288
+ def _exclude_pattern(self, pattern, anchor=True, prefix=None,
289
+ is_regex=False):
290
+ """Remove strings (presumably filenames) from 'files' that match
291
+ 'pattern'.
292
+
293
+ Other parameters are the same as for 'include_pattern()', above.
294
+ The list 'self.files' is modified in place. Return True if files are
295
+ found.
296
+
297
+ This API is public to allow e.g. exclusion of SCM subdirs, e.g. when
298
+ packaging source distributions
299
+ """
300
+ found = False
301
+ pattern_re = self._translate_pattern(pattern, anchor, prefix, is_regex)
302
+ for f in list(self.files):
303
+ if pattern_re.search(f):
304
+ self.files.remove(f)
305
+ found = True
306
+ return found
307
+
308
+ def _translate_pattern(self, pattern, anchor=True, prefix=None,
309
+ is_regex=False):
310
+ """Translate a shell-like wildcard pattern to a compiled regular
311
+ expression.
312
+
313
+ Return the compiled regex. If 'is_regex' true,
314
+ then 'pattern' is directly compiled to a regex (if it's a string)
315
+ or just returned as-is (assumes it's a regex object).
316
+ """
317
+ if is_regex:
318
+ if isinstance(pattern, str):
319
+ return re.compile(pattern)
320
+ else:
321
+ return pattern
322
+
323
+ if _PYTHON_VERSION > (3, 2):
324
+ # ditch start and end characters
325
+ start, _, end = self._glob_to_re('_').partition('_')
326
+
327
+ if pattern:
328
+ pattern_re = self._glob_to_re(pattern)
329
+ if _PYTHON_VERSION > (3, 2):
330
+ assert pattern_re.startswith(start) and pattern_re.endswith(end)
331
+ else:
332
+ pattern_re = ''
333
+
334
+ base = re.escape(os.path.join(self.base, ''))
335
+ if prefix is not None:
336
+ # ditch end of pattern character
337
+ if _PYTHON_VERSION <= (3, 2):
338
+ empty_pattern = self._glob_to_re('')
339
+ prefix_re = self._glob_to_re(prefix)[:-len(empty_pattern)]
340
+ else:
341
+ prefix_re = self._glob_to_re(prefix)
342
+ assert prefix_re.startswith(start) and prefix_re.endswith(end)
343
+ prefix_re = prefix_re[len(start): len(prefix_re) - len(end)]
344
+ sep = os.sep
345
+ if os.sep == '\\':
346
+ sep = r'\\'
347
+ if _PYTHON_VERSION <= (3, 2):
348
+ pattern_re = '^' + base + sep.join((prefix_re,
349
+ '.*' + pattern_re))
350
+ else:
351
+ pattern_re = pattern_re[len(start): len(pattern_re) - len(end)]
352
+ pattern_re = r'%s%s%s%s.*%s%s' % (start, base, prefix_re, sep,
353
+ pattern_re, end)
354
+ else: # no prefix -- respect anchor flag
355
+ if anchor:
356
+ if _PYTHON_VERSION <= (3, 2):
357
+ pattern_re = '^' + base + pattern_re
358
+ else:
359
+ pattern_re = r'%s%s%s' % (start, base, pattern_re[len(start):])
360
+
361
+ return re.compile(pattern_re)
362
+
363
+ def _glob_to_re(self, pattern):
364
+ """Translate a shell-like glob pattern to a regular expression.
365
+
366
+ Return a string containing the regex. Differs from
367
+ 'fnmatch.translate()' in that '*' does not match "special characters"
368
+ (which are platform-specific).
369
+ """
370
+ pattern_re = fnmatch.translate(pattern)
371
+
372
+ # '?' and '*' in the glob pattern become '.' and '.*' in the RE, which
373
+ # IMHO is wrong -- '?' and '*' aren't supposed to match slash in Unix,
374
+ # and by extension they shouldn't match such "special characters" under
375
+ # any OS. So change all non-escaped dots in the RE to match any
376
+ # character except the special characters (currently: just os.sep).
377
+ sep = os.sep
378
+ if os.sep == '\\':
379
+ # we're using a regex to manipulate a regex, so we need
380
+ # to escape the backslash twice
381
+ sep = r'\\\\'
382
+ escaped = r'\1[^%s]' % sep
383
+ pattern_re = re.sub(r'((?<!\\)(\\\\)*)\.', escaped, pattern_re)
384
+ return pattern_re
lib/python3.12/site-packages/pip/_vendor/distlib/metadata.py ADDED
@@ -0,0 +1,1031 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2012 The Python Software Foundation.
4
+ # See LICENSE.txt and CONTRIBUTORS.txt.
5
+ #
6
+ """Implementation of the Metadata for Python packages PEPs.
7
+
8
+ Supports all metadata formats (1.0, 1.1, 1.2, 1.3/2.1 and 2.2).
9
+ """
10
+ from __future__ import unicode_literals
11
+
12
+ import codecs
13
+ from email import message_from_file
14
+ import json
15
+ import logging
16
+ import re
17
+
18
+ from . import DistlibException, __version__
19
+ from .compat import StringIO, string_types, text_type
20
+ from .markers import interpret
21
+ from .util import extract_by_key, get_extras
22
+ from .version import get_scheme, PEP440_VERSION_RE
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ class MetadataMissingError(DistlibException):
28
+ """A required metadata is missing"""
29
+
30
+
31
+ class MetadataConflictError(DistlibException):
32
+ """Attempt to read or write metadata fields that are conflictual."""
33
+
34
+
35
+ class MetadataUnrecognizedVersionError(DistlibException):
36
+ """Unknown metadata version number."""
37
+
38
+
39
+ class MetadataInvalidError(DistlibException):
40
+ """A metadata value is invalid"""
41
+
42
+
43
+ # public API of this module
44
+ __all__ = ['Metadata', 'PKG_INFO_ENCODING', 'PKG_INFO_PREFERRED_VERSION']
45
+
46
+ # Encoding used for the PKG-INFO files
47
+ PKG_INFO_ENCODING = 'utf-8'
48
+
49
+ # preferred version. Hopefully will be changed
50
+ # to 1.2 once PEP 345 is supported everywhere
51
+ PKG_INFO_PREFERRED_VERSION = '1.1'
52
+
53
+ _LINE_PREFIX_1_2 = re.compile('\n \\|')
54
+ _LINE_PREFIX_PRE_1_2 = re.compile('\n ')
55
+ _241_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', 'Summary', 'Description', 'Keywords', 'Home-page',
56
+ 'Author', 'Author-email', 'License')
57
+
58
+ _314_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', 'Supported-Platform', 'Summary', 'Description',
59
+ 'Keywords', 'Home-page', 'Author', 'Author-email', 'License', 'Classifier', 'Download-URL', 'Obsoletes',
60
+ 'Provides', 'Requires')
61
+
62
+ _314_MARKERS = ('Obsoletes', 'Provides', 'Requires', 'Classifier', 'Download-URL')
63
+
64
+ _345_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', 'Supported-Platform', 'Summary', 'Description',
65
+ 'Keywords', 'Home-page', 'Author', 'Author-email', 'Maintainer', 'Maintainer-email', 'License',
66
+ 'Classifier', 'Download-URL', 'Obsoletes-Dist', 'Project-URL', 'Provides-Dist', 'Requires-Dist',
67
+ 'Requires-Python', 'Requires-External')
68
+
69
+ _345_MARKERS = ('Provides-Dist', 'Requires-Dist', 'Requires-Python', 'Obsoletes-Dist', 'Requires-External',
70
+ 'Maintainer', 'Maintainer-email', 'Project-URL')
71
+
72
+ _426_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', 'Supported-Platform', 'Summary', 'Description',
73
+ 'Keywords', 'Home-page', 'Author', 'Author-email', 'Maintainer', 'Maintainer-email', 'License',
74
+ 'Classifier', 'Download-URL', 'Obsoletes-Dist', 'Project-URL', 'Provides-Dist', 'Requires-Dist',
75
+ 'Requires-Python', 'Requires-External', 'Private-Version', 'Obsoleted-By', 'Setup-Requires-Dist',
76
+ 'Extension', 'Provides-Extra')
77
+
78
+ _426_MARKERS = ('Private-Version', 'Provides-Extra', 'Obsoleted-By', 'Setup-Requires-Dist', 'Extension')
79
+
80
+ # See issue #106: Sometimes 'Requires' and 'Provides' occur wrongly in
81
+ # the metadata. Include them in the tuple literal below to allow them
82
+ # (for now).
83
+ # Ditto for Obsoletes - see issue #140.
84
+ _566_FIELDS = _426_FIELDS + ('Description-Content-Type', 'Requires', 'Provides', 'Obsoletes')
85
+
86
+ _566_MARKERS = ('Description-Content-Type', )
87
+
88
+ _643_MARKERS = ('Dynamic', 'License-File')
89
+
90
+ _643_FIELDS = _566_FIELDS + _643_MARKERS
91
+
92
+ _ALL_FIELDS = set()
93
+ _ALL_FIELDS.update(_241_FIELDS)
94
+ _ALL_FIELDS.update(_314_FIELDS)
95
+ _ALL_FIELDS.update(_345_FIELDS)
96
+ _ALL_FIELDS.update(_426_FIELDS)
97
+ _ALL_FIELDS.update(_566_FIELDS)
98
+ _ALL_FIELDS.update(_643_FIELDS)
99
+
100
+ EXTRA_RE = re.compile(r'''extra\s*==\s*("([^"]+)"|'([^']+)')''')
101
+
102
+
103
+ def _version2fieldlist(version):
104
+ if version == '1.0':
105
+ return _241_FIELDS
106
+ elif version == '1.1':
107
+ return _314_FIELDS
108
+ elif version == '1.2':
109
+ return _345_FIELDS
110
+ elif version in ('1.3', '2.1'):
111
+ # avoid adding field names if already there
112
+ return _345_FIELDS + tuple(f for f in _566_FIELDS if f not in _345_FIELDS)
113
+ elif version == '2.0':
114
+ raise ValueError('Metadata 2.0 is withdrawn and not supported')
115
+ # return _426_FIELDS
116
+ elif version == '2.2':
117
+ return _643_FIELDS
118
+ raise MetadataUnrecognizedVersionError(version)
119
+
120
+
121
+ def _best_version(fields):
122
+ """Detect the best version depending on the fields used."""
123
+
124
+ def _has_marker(keys, markers):
125
+ return any(marker in keys for marker in markers)
126
+
127
+ keys = [key for key, value in fields.items() if value not in ([], 'UNKNOWN', None)]
128
+ possible_versions = ['1.0', '1.1', '1.2', '1.3', '2.1', '2.2'] # 2.0 removed
129
+
130
+ # first let's try to see if a field is not part of one of the version
131
+ for key in keys:
132
+ if key not in _241_FIELDS and '1.0' in possible_versions:
133
+ possible_versions.remove('1.0')
134
+ logger.debug('Removed 1.0 due to %s', key)
135
+ if key not in _314_FIELDS and '1.1' in possible_versions:
136
+ possible_versions.remove('1.1')
137
+ logger.debug('Removed 1.1 due to %s', key)
138
+ if key not in _345_FIELDS and '1.2' in possible_versions:
139
+ possible_versions.remove('1.2')
140
+ logger.debug('Removed 1.2 due to %s', key)
141
+ if key not in _566_FIELDS and '1.3' in possible_versions:
142
+ possible_versions.remove('1.3')
143
+ logger.debug('Removed 1.3 due to %s', key)
144
+ if key not in _566_FIELDS and '2.1' in possible_versions:
145
+ if key != 'Description': # In 2.1, description allowed after headers
146
+ possible_versions.remove('2.1')
147
+ logger.debug('Removed 2.1 due to %s', key)
148
+ if key not in _643_FIELDS and '2.2' in possible_versions:
149
+ possible_versions.remove('2.2')
150
+ logger.debug('Removed 2.2 due to %s', key)
151
+ # if key not in _426_FIELDS and '2.0' in possible_versions:
152
+ # possible_versions.remove('2.0')
153
+ # logger.debug('Removed 2.0 due to %s', key)
154
+
155
+ # possible_version contains qualified versions
156
+ if len(possible_versions) == 1:
157
+ return possible_versions[0] # found !
158
+ elif len(possible_versions) == 0:
159
+ logger.debug('Out of options - unknown metadata set: %s', fields)
160
+ raise MetadataConflictError('Unknown metadata set')
161
+
162
+ # let's see if one unique marker is found
163
+ is_1_1 = '1.1' in possible_versions and _has_marker(keys, _314_MARKERS)
164
+ is_1_2 = '1.2' in possible_versions and _has_marker(keys, _345_MARKERS)
165
+ is_2_1 = '2.1' in possible_versions and _has_marker(keys, _566_MARKERS)
166
+ # is_2_0 = '2.0' in possible_versions and _has_marker(keys, _426_MARKERS)
167
+ is_2_2 = '2.2' in possible_versions and _has_marker(keys, _643_MARKERS)
168
+ if int(is_1_1) + int(is_1_2) + int(is_2_1) + int(is_2_2) > 1:
169
+ raise MetadataConflictError('You used incompatible 1.1/1.2/2.1/2.2 fields')
170
+
171
+ # we have the choice, 1.0, or 1.2, 2.1 or 2.2
172
+ # - 1.0 has a broken Summary field but works with all tools
173
+ # - 1.1 is to avoid
174
+ # - 1.2 fixes Summary but has little adoption
175
+ # - 2.1 adds more features
176
+ # - 2.2 is the latest
177
+ if not is_1_1 and not is_1_2 and not is_2_1 and not is_2_2:
178
+ # we couldn't find any specific marker
179
+ if PKG_INFO_PREFERRED_VERSION in possible_versions:
180
+ return PKG_INFO_PREFERRED_VERSION
181
+ if is_1_1:
182
+ return '1.1'
183
+ if is_1_2:
184
+ return '1.2'
185
+ if is_2_1:
186
+ return '2.1'
187
+ # if is_2_2:
188
+ # return '2.2'
189
+
190
+ return '2.2'
191
+
192
+
193
+ # This follows the rules about transforming keys as described in
194
+ # https://www.python.org/dev/peps/pep-0566/#id17
195
+ _ATTR2FIELD = {name.lower().replace("-", "_"): name for name in _ALL_FIELDS}
196
+ _FIELD2ATTR = {field: attr for attr, field in _ATTR2FIELD.items()}
197
+
198
+ _PREDICATE_FIELDS = ('Requires-Dist', 'Obsoletes-Dist', 'Provides-Dist')
199
+ _VERSIONS_FIELDS = ('Requires-Python', )
200
+ _VERSION_FIELDS = ('Version', )
201
+ _LISTFIELDS = ('Platform', 'Classifier', 'Obsoletes', 'Requires', 'Provides', 'Obsoletes-Dist', 'Provides-Dist',
202
+ 'Requires-Dist', 'Requires-External', 'Project-URL', 'Supported-Platform', 'Setup-Requires-Dist',
203
+ 'Provides-Extra', 'Extension', 'License-File')
204
+ _LISTTUPLEFIELDS = ('Project-URL', )
205
+
206
+ _ELEMENTSFIELD = ('Keywords', )
207
+
208
+ _UNICODEFIELDS = ('Author', 'Maintainer', 'Summary', 'Description')
209
+
210
+ _MISSING = object()
211
+
212
+ _FILESAFE = re.compile('[^A-Za-z0-9.]+')
213
+
214
+
215
+ def _get_name_and_version(name, version, for_filename=False):
216
+ """Return the distribution name with version.
217
+
218
+ If for_filename is true, return a filename-escaped form."""
219
+ if for_filename:
220
+ # For both name and version any runs of non-alphanumeric or '.'
221
+ # characters are replaced with a single '-'. Additionally any
222
+ # spaces in the version string become '.'
223
+ name = _FILESAFE.sub('-', name)
224
+ version = _FILESAFE.sub('-', version.replace(' ', '.'))
225
+ return '%s-%s' % (name, version)
226
+
227
+
228
+ class LegacyMetadata(object):
229
+ """The legacy metadata of a release.
230
+
231
+ Supports versions 1.0, 1.1, 1.2, 2.0 and 1.3/2.1 (auto-detected). You can
232
+ instantiate the class with one of these arguments (or none):
233
+ - *path*, the path to a metadata file
234
+ - *fileobj* give a file-like object with metadata as content
235
+ - *mapping* is a dict-like object
236
+ - *scheme* is a version scheme name
237
+ """
238
+
239
+ # TODO document the mapping API and UNKNOWN default key
240
+
241
+ def __init__(self, path=None, fileobj=None, mapping=None, scheme='default'):
242
+ if [path, fileobj, mapping].count(None) < 2:
243
+ raise TypeError('path, fileobj and mapping are exclusive')
244
+ self._fields = {}
245
+ self.requires_files = []
246
+ self._dependencies = None
247
+ self.scheme = scheme
248
+ if path is not None:
249
+ self.read(path)
250
+ elif fileobj is not None:
251
+ self.read_file(fileobj)
252
+ elif mapping is not None:
253
+ self.update(mapping)
254
+ self.set_metadata_version()
255
+
256
+ def set_metadata_version(self):
257
+ self._fields['Metadata-Version'] = _best_version(self._fields)
258
+
259
+ def _write_field(self, fileobj, name, value):
260
+ fileobj.write('%s: %s\n' % (name, value))
261
+
262
+ def __getitem__(self, name):
263
+ return self.get(name)
264
+
265
+ def __setitem__(self, name, value):
266
+ return self.set(name, value)
267
+
268
+ def __delitem__(self, name):
269
+ field_name = self._convert_name(name)
270
+ try:
271
+ del self._fields[field_name]
272
+ except KeyError:
273
+ raise KeyError(name)
274
+
275
+ def __contains__(self, name):
276
+ return (name in self._fields or self._convert_name(name) in self._fields)
277
+
278
+ def _convert_name(self, name):
279
+ if name in _ALL_FIELDS:
280
+ return name
281
+ name = name.replace('-', '_').lower()
282
+ return _ATTR2FIELD.get(name, name)
283
+
284
+ def _default_value(self, name):
285
+ if name in _LISTFIELDS or name in _ELEMENTSFIELD:
286
+ return []
287
+ return 'UNKNOWN'
288
+
289
+ def _remove_line_prefix(self, value):
290
+ if self.metadata_version in ('1.0', '1.1'):
291
+ return _LINE_PREFIX_PRE_1_2.sub('\n', value)
292
+ else:
293
+ return _LINE_PREFIX_1_2.sub('\n', value)
294
+
295
+ def __getattr__(self, name):
296
+ if name in _ATTR2FIELD:
297
+ return self[name]
298
+ raise AttributeError(name)
299
+
300
+ #
301
+ # Public API
302
+ #
303
+
304
+ def get_fullname(self, filesafe=False):
305
+ """
306
+ Return the distribution name with version.
307
+
308
+ If filesafe is true, return a filename-escaped form.
309
+ """
310
+ return _get_name_and_version(self['Name'], self['Version'], filesafe)
311
+
312
+ def is_field(self, name):
313
+ """return True if name is a valid metadata key"""
314
+ name = self._convert_name(name)
315
+ return name in _ALL_FIELDS
316
+
317
+ def is_multi_field(self, name):
318
+ name = self._convert_name(name)
319
+ return name in _LISTFIELDS
320
+
321
+ def read(self, filepath):
322
+ """Read the metadata values from a file path."""
323
+ fp = codecs.open(filepath, 'r', encoding='utf-8')
324
+ try:
325
+ self.read_file(fp)
326
+ finally:
327
+ fp.close()
328
+
329
+ def read_file(self, fileob):
330
+ """Read the metadata values from a file object."""
331
+ msg = message_from_file(fileob)
332
+ self._fields['Metadata-Version'] = msg['metadata-version']
333
+
334
+ # When reading, get all the fields we can
335
+ for field in _ALL_FIELDS:
336
+ if field not in msg:
337
+ continue
338
+ if field in _LISTFIELDS:
339
+ # we can have multiple lines
340
+ values = msg.get_all(field)
341
+ if field in _LISTTUPLEFIELDS and values is not None:
342
+ values = [tuple(value.split(',')) for value in values]
343
+ self.set(field, values)
344
+ else:
345
+ # single line
346
+ value = msg[field]
347
+ if value is not None and value != 'UNKNOWN':
348
+ self.set(field, value)
349
+
350
+ # PEP 566 specifies that the body be used for the description, if
351
+ # available
352
+ body = msg.get_payload()
353
+ self["Description"] = body if body else self["Description"]
354
+ # logger.debug('Attempting to set metadata for %s', self)
355
+ # self.set_metadata_version()
356
+
357
+ def write(self, filepath, skip_unknown=False):
358
+ """Write the metadata fields to filepath."""
359
+ fp = codecs.open(filepath, 'w', encoding='utf-8')
360
+ try:
361
+ self.write_file(fp, skip_unknown)
362
+ finally:
363
+ fp.close()
364
+
365
+ def write_file(self, fileobject, skip_unknown=False):
366
+ """Write the PKG-INFO format data to a file object."""
367
+ self.set_metadata_version()
368
+
369
+ for field in _version2fieldlist(self['Metadata-Version']):
370
+ values = self.get(field)
371
+ if skip_unknown and values in ('UNKNOWN', [], ['UNKNOWN']):
372
+ continue
373
+ if field in _ELEMENTSFIELD:
374
+ self._write_field(fileobject, field, ','.join(values))
375
+ continue
376
+ if field not in _LISTFIELDS:
377
+ if field == 'Description':
378
+ if self.metadata_version in ('1.0', '1.1'):
379
+ values = values.replace('\n', '\n ')
380
+ else:
381
+ values = values.replace('\n', '\n |')
382
+ values = [values]
383
+
384
+ if field in _LISTTUPLEFIELDS:
385
+ values = [','.join(value) for value in values]
386
+
387
+ for value in values:
388
+ self._write_field(fileobject, field, value)
389
+
390
+ def update(self, other=None, **kwargs):
391
+ """Set metadata values from the given iterable `other` and kwargs.
392
+
393
+ Behavior is like `dict.update`: If `other` has a ``keys`` method,
394
+ they are looped over and ``self[key]`` is assigned ``other[key]``.
395
+ Else, ``other`` is an iterable of ``(key, value)`` iterables.
396
+
397
+ Keys that don't match a metadata field or that have an empty value are
398
+ dropped.
399
+ """
400
+
401
+ def _set(key, value):
402
+ if key in _ATTR2FIELD and value:
403
+ self.set(self._convert_name(key), value)
404
+
405
+ if not other:
406
+ # other is None or empty container
407
+ pass
408
+ elif hasattr(other, 'keys'):
409
+ for k in other.keys():
410
+ _set(k, other[k])
411
+ else:
412
+ for k, v in other:
413
+ _set(k, v)
414
+
415
+ if kwargs:
416
+ for k, v in kwargs.items():
417
+ _set(k, v)
418
+
419
+ def set(self, name, value):
420
+ """Control then set a metadata field."""
421
+ name = self._convert_name(name)
422
+
423
+ if ((name in _ELEMENTSFIELD or name == 'Platform') and not isinstance(value, (list, tuple))):
424
+ if isinstance(value, string_types):
425
+ value = [v.strip() for v in value.split(',')]
426
+ else:
427
+ value = []
428
+ elif (name in _LISTFIELDS and not isinstance(value, (list, tuple))):
429
+ if isinstance(value, string_types):
430
+ value = [value]
431
+ else:
432
+ value = []
433
+
434
+ if logger.isEnabledFor(logging.WARNING):
435
+ project_name = self['Name']
436
+
437
+ scheme = get_scheme(self.scheme)
438
+ if name in _PREDICATE_FIELDS and value is not None:
439
+ for v in value:
440
+ # check that the values are valid
441
+ if not scheme.is_valid_matcher(v.split(';')[0]):
442
+ logger.warning("'%s': '%s' is not valid (field '%s')", project_name, v, name)
443
+ # FIXME this rejects UNKNOWN, is that right?
444
+ elif name in _VERSIONS_FIELDS and value is not None:
445
+ if not scheme.is_valid_constraint_list(value):
446
+ logger.warning("'%s': '%s' is not a valid version (field '%s')", project_name, value, name)
447
+ elif name in _VERSION_FIELDS and value is not None:
448
+ if not scheme.is_valid_version(value):
449
+ logger.warning("'%s': '%s' is not a valid version (field '%s')", project_name, value, name)
450
+
451
+ if name in _UNICODEFIELDS:
452
+ if name == 'Description':
453
+ value = self._remove_line_prefix(value)
454
+
455
+ self._fields[name] = value
456
+
457
+ def get(self, name, default=_MISSING):
458
+ """Get a metadata field."""
459
+ name = self._convert_name(name)
460
+ if name not in self._fields:
461
+ if default is _MISSING:
462
+ default = self._default_value(name)
463
+ return default
464
+ if name in _UNICODEFIELDS:
465
+ value = self._fields[name]
466
+ return value
467
+ elif name in _LISTFIELDS:
468
+ value = self._fields[name]
469
+ if value is None:
470
+ return []
471
+ res = []
472
+ for val in value:
473
+ if name not in _LISTTUPLEFIELDS:
474
+ res.append(val)
475
+ else:
476
+ # That's for Project-URL
477
+ res.append((val[0], val[1]))
478
+ return res
479
+
480
+ elif name in _ELEMENTSFIELD:
481
+ value = self._fields[name]
482
+ if isinstance(value, string_types):
483
+ return value.split(',')
484
+ return self._fields[name]
485
+
486
+ def check(self, strict=False):
487
+ """Check if the metadata is compliant. If strict is True then raise if
488
+ no Name or Version are provided"""
489
+ self.set_metadata_version()
490
+
491
+ # XXX should check the versions (if the file was loaded)
492
+ missing, warnings = [], []
493
+
494
+ for attr in ('Name', 'Version'): # required by PEP 345
495
+ if attr not in self:
496
+ missing.append(attr)
497
+
498
+ if strict and missing != []:
499
+ msg = 'missing required metadata: %s' % ', '.join(missing)
500
+ raise MetadataMissingError(msg)
501
+
502
+ for attr in ('Home-page', 'Author'):
503
+ if attr not in self:
504
+ missing.append(attr)
505
+
506
+ # checking metadata 1.2 (XXX needs to check 1.1, 1.0)
507
+ if self['Metadata-Version'] != '1.2':
508
+ return missing, warnings
509
+
510
+ scheme = get_scheme(self.scheme)
511
+
512
+ def are_valid_constraints(value):
513
+ for v in value:
514
+ if not scheme.is_valid_matcher(v.split(';')[0]):
515
+ return False
516
+ return True
517
+
518
+ for fields, controller in ((_PREDICATE_FIELDS, are_valid_constraints),
519
+ (_VERSIONS_FIELDS, scheme.is_valid_constraint_list), (_VERSION_FIELDS,
520
+ scheme.is_valid_version)):
521
+ for field in fields:
522
+ value = self.get(field, None)
523
+ if value is not None and not controller(value):
524
+ warnings.append("Wrong value for '%s': %s" % (field, value))
525
+
526
+ return missing, warnings
527
+
528
+ def todict(self, skip_missing=False):
529
+ """Return fields as a dict.
530
+
531
+ Field names will be converted to use the underscore-lowercase style
532
+ instead of hyphen-mixed case (i.e. home_page instead of Home-page).
533
+ This is as per https://www.python.org/dev/peps/pep-0566/#id17.
534
+ """
535
+ self.set_metadata_version()
536
+
537
+ fields = _version2fieldlist(self['Metadata-Version'])
538
+
539
+ data = {}
540
+
541
+ for field_name in fields:
542
+ if not skip_missing or field_name in self._fields:
543
+ key = _FIELD2ATTR[field_name]
544
+ if key != 'project_url':
545
+ data[key] = self[field_name]
546
+ else:
547
+ data[key] = [','.join(u) for u in self[field_name]]
548
+
549
+ return data
550
+
551
+ def add_requirements(self, requirements):
552
+ if self['Metadata-Version'] == '1.1':
553
+ # we can't have 1.1 metadata *and* Setuptools requires
554
+ for field in ('Obsoletes', 'Requires', 'Provides'):
555
+ if field in self:
556
+ del self[field]
557
+ self['Requires-Dist'] += requirements
558
+
559
+ # Mapping API
560
+ # TODO could add iter* variants
561
+
562
+ def keys(self):
563
+ return list(_version2fieldlist(self['Metadata-Version']))
564
+
565
+ def __iter__(self):
566
+ for key in self.keys():
567
+ yield key
568
+
569
+ def values(self):
570
+ return [self[key] for key in self.keys()]
571
+
572
+ def items(self):
573
+ return [(key, self[key]) for key in self.keys()]
574
+
575
+ def __repr__(self):
576
+ return '<%s %s %s>' % (self.__class__.__name__, self.name, self.version)
577
+
578
+
579
+ METADATA_FILENAME = 'pydist.json'
580
+ WHEEL_METADATA_FILENAME = 'metadata.json'
581
+ LEGACY_METADATA_FILENAME = 'METADATA'
582
+
583
+
584
+ class Metadata(object):
585
+ """
586
+ The metadata of a release. This implementation uses 2.1
587
+ metadata where possible. If not possible, it wraps a LegacyMetadata
588
+ instance which handles the key-value metadata format.
589
+ """
590
+
591
+ METADATA_VERSION_MATCHER = re.compile(r'^\d+(\.\d+)*$')
592
+
593
+ NAME_MATCHER = re.compile('^[0-9A-Z]([0-9A-Z_.-]*[0-9A-Z])?$', re.I)
594
+
595
+ FIELDNAME_MATCHER = re.compile('^[A-Z]([0-9A-Z-]*[0-9A-Z])?$', re.I)
596
+
597
+ VERSION_MATCHER = PEP440_VERSION_RE
598
+
599
+ SUMMARY_MATCHER = re.compile('.{1,2047}')
600
+
601
+ METADATA_VERSION = '2.0'
602
+
603
+ GENERATOR = 'distlib (%s)' % __version__
604
+
605
+ MANDATORY_KEYS = {
606
+ 'name': (),
607
+ 'version': (),
608
+ 'summary': ('legacy', ),
609
+ }
610
+
611
+ INDEX_KEYS = ('name version license summary description author '
612
+ 'author_email keywords platform home_page classifiers '
613
+ 'download_url')
614
+
615
+ DEPENDENCY_KEYS = ('extras run_requires test_requires build_requires '
616
+ 'dev_requires provides meta_requires obsoleted_by '
617
+ 'supports_environments')
618
+
619
+ SYNTAX_VALIDATORS = {
620
+ 'metadata_version': (METADATA_VERSION_MATCHER, ()),
621
+ 'name': (NAME_MATCHER, ('legacy', )),
622
+ 'version': (VERSION_MATCHER, ('legacy', )),
623
+ 'summary': (SUMMARY_MATCHER, ('legacy', )),
624
+ 'dynamic': (FIELDNAME_MATCHER, ('legacy', )),
625
+ }
626
+
627
+ __slots__ = ('_legacy', '_data', 'scheme')
628
+
629
+ def __init__(self, path=None, fileobj=None, mapping=None, scheme='default'):
630
+ if [path, fileobj, mapping].count(None) < 2:
631
+ raise TypeError('path, fileobj and mapping are exclusive')
632
+ self._legacy = None
633
+ self._data = None
634
+ self.scheme = scheme
635
+ # import pdb; pdb.set_trace()
636
+ if mapping is not None:
637
+ try:
638
+ self._validate_mapping(mapping, scheme)
639
+ self._data = mapping
640
+ except MetadataUnrecognizedVersionError:
641
+ self._legacy = LegacyMetadata(mapping=mapping, scheme=scheme)
642
+ self.validate()
643
+ else:
644
+ data = None
645
+ if path:
646
+ with open(path, 'rb') as f:
647
+ data = f.read()
648
+ elif fileobj:
649
+ data = fileobj.read()
650
+ if data is None:
651
+ # Initialised with no args - to be added
652
+ self._data = {
653
+ 'metadata_version': self.METADATA_VERSION,
654
+ 'generator': self.GENERATOR,
655
+ }
656
+ else:
657
+ if not isinstance(data, text_type):
658
+ data = data.decode('utf-8')
659
+ try:
660
+ self._data = json.loads(data)
661
+ self._validate_mapping(self._data, scheme)
662
+ except ValueError:
663
+ # Note: MetadataUnrecognizedVersionError does not
664
+ # inherit from ValueError (it's a DistlibException,
665
+ # which should not inherit from ValueError).
666
+ # The ValueError comes from the json.load - if that
667
+ # succeeds and we get a validation error, we want
668
+ # that to propagate
669
+ self._legacy = LegacyMetadata(fileobj=StringIO(data), scheme=scheme)
670
+ self.validate()
671
+
672
+ common_keys = set(('name', 'version', 'license', 'keywords', 'summary'))
673
+
674
+ none_list = (None, list)
675
+ none_dict = (None, dict)
676
+
677
+ mapped_keys = {
678
+ 'run_requires': ('Requires-Dist', list),
679
+ 'build_requires': ('Setup-Requires-Dist', list),
680
+ 'dev_requires': none_list,
681
+ 'test_requires': none_list,
682
+ 'meta_requires': none_list,
683
+ 'extras': ('Provides-Extra', list),
684
+ 'modules': none_list,
685
+ 'namespaces': none_list,
686
+ 'exports': none_dict,
687
+ 'commands': none_dict,
688
+ 'classifiers': ('Classifier', list),
689
+ 'source_url': ('Download-URL', None),
690
+ 'metadata_version': ('Metadata-Version', None),
691
+ }
692
+
693
+ del none_list, none_dict
694
+
695
+ def __getattribute__(self, key):
696
+ common = object.__getattribute__(self, 'common_keys')
697
+ mapped = object.__getattribute__(self, 'mapped_keys')
698
+ if key in mapped:
699
+ lk, maker = mapped[key]
700
+ if self._legacy:
701
+ if lk is None:
702
+ result = None if maker is None else maker()
703
+ else:
704
+ result = self._legacy.get(lk)
705
+ else:
706
+ value = None if maker is None else maker()
707
+ if key not in ('commands', 'exports', 'modules', 'namespaces', 'classifiers'):
708
+ result = self._data.get(key, value)
709
+ else:
710
+ # special cases for PEP 459
711
+ sentinel = object()
712
+ result = sentinel
713
+ d = self._data.get('extensions')
714
+ if d:
715
+ if key == 'commands':
716
+ result = d.get('python.commands', value)
717
+ elif key == 'classifiers':
718
+ d = d.get('python.details')
719
+ if d:
720
+ result = d.get(key, value)
721
+ else:
722
+ d = d.get('python.exports')
723
+ if not d:
724
+ d = self._data.get('python.exports')
725
+ if d:
726
+ result = d.get(key, value)
727
+ if result is sentinel:
728
+ result = value
729
+ elif key not in common:
730
+ result = object.__getattribute__(self, key)
731
+ elif self._legacy:
732
+ result = self._legacy.get(key)
733
+ else:
734
+ result = self._data.get(key)
735
+ return result
736
+
737
+ def _validate_value(self, key, value, scheme=None):
738
+ if key in self.SYNTAX_VALIDATORS:
739
+ pattern, exclusions = self.SYNTAX_VALIDATORS[key]
740
+ if (scheme or self.scheme) not in exclusions:
741
+ m = pattern.match(value)
742
+ if not m:
743
+ raise MetadataInvalidError("'%s' is an invalid value for "
744
+ "the '%s' property" % (value, key))
745
+
746
+ def __setattr__(self, key, value):
747
+ self._validate_value(key, value)
748
+ common = object.__getattribute__(self, 'common_keys')
749
+ mapped = object.__getattribute__(self, 'mapped_keys')
750
+ if key in mapped:
751
+ lk, _ = mapped[key]
752
+ if self._legacy:
753
+ if lk is None:
754
+ raise NotImplementedError
755
+ self._legacy[lk] = value
756
+ elif key not in ('commands', 'exports', 'modules', 'namespaces', 'classifiers'):
757
+ self._data[key] = value
758
+ else:
759
+ # special cases for PEP 459
760
+ d = self._data.setdefault('extensions', {})
761
+ if key == 'commands':
762
+ d['python.commands'] = value
763
+ elif key == 'classifiers':
764
+ d = d.setdefault('python.details', {})
765
+ d[key] = value
766
+ else:
767
+ d = d.setdefault('python.exports', {})
768
+ d[key] = value
769
+ elif key not in common:
770
+ object.__setattr__(self, key, value)
771
+ else:
772
+ if key == 'keywords':
773
+ if isinstance(value, string_types):
774
+ value = value.strip()
775
+ if value:
776
+ value = value.split()
777
+ else:
778
+ value = []
779
+ if self._legacy:
780
+ self._legacy[key] = value
781
+ else:
782
+ self._data[key] = value
783
+
784
+ @property
785
+ def name_and_version(self):
786
+ return _get_name_and_version(self.name, self.version, True)
787
+
788
+ @property
789
+ def provides(self):
790
+ if self._legacy:
791
+ result = self._legacy['Provides-Dist']
792
+ else:
793
+ result = self._data.setdefault('provides', [])
794
+ s = '%s (%s)' % (self.name, self.version)
795
+ if s not in result:
796
+ result.append(s)
797
+ return result
798
+
799
+ @provides.setter
800
+ def provides(self, value):
801
+ if self._legacy:
802
+ self._legacy['Provides-Dist'] = value
803
+ else:
804
+ self._data['provides'] = value
805
+
806
+ def get_requirements(self, reqts, extras=None, env=None):
807
+ """
808
+ Base method to get dependencies, given a set of extras
809
+ to satisfy and an optional environment context.
810
+ :param reqts: A list of sometimes-wanted dependencies,
811
+ perhaps dependent on extras and environment.
812
+ :param extras: A list of optional components being requested.
813
+ :param env: An optional environment for marker evaluation.
814
+ """
815
+ if self._legacy:
816
+ result = reqts
817
+ else:
818
+ result = []
819
+ extras = get_extras(extras or [], self.extras)
820
+ for d in reqts:
821
+ if 'extra' not in d and 'environment' not in d:
822
+ # unconditional
823
+ include = True
824
+ else:
825
+ if 'extra' not in d:
826
+ # Not extra-dependent - only environment-dependent
827
+ include = True
828
+ else:
829
+ include = d.get('extra') in extras
830
+ if include:
831
+ # Not excluded because of extras, check environment
832
+ marker = d.get('environment')
833
+ if marker:
834
+ include = interpret(marker, env)
835
+ if include:
836
+ result.extend(d['requires'])
837
+ for key in ('build', 'dev', 'test'):
838
+ e = ':%s:' % key
839
+ if e in extras:
840
+ extras.remove(e)
841
+ # A recursive call, but it should terminate since 'test'
842
+ # has been removed from the extras
843
+ reqts = self._data.get('%s_requires' % key, [])
844
+ result.extend(self.get_requirements(reqts, extras=extras, env=env))
845
+ return result
846
+
847
+ @property
848
+ def dictionary(self):
849
+ if self._legacy:
850
+ return self._from_legacy()
851
+ return self._data
852
+
853
+ @property
854
+ def dependencies(self):
855
+ if self._legacy:
856
+ raise NotImplementedError
857
+ else:
858
+ return extract_by_key(self._data, self.DEPENDENCY_KEYS)
859
+
860
+ @dependencies.setter
861
+ def dependencies(self, value):
862
+ if self._legacy:
863
+ raise NotImplementedError
864
+ else:
865
+ self._data.update(value)
866
+
867
+ def _validate_mapping(self, mapping, scheme):
868
+ if mapping.get('metadata_version') != self.METADATA_VERSION:
869
+ raise MetadataUnrecognizedVersionError()
870
+ missing = []
871
+ for key, exclusions in self.MANDATORY_KEYS.items():
872
+ if key not in mapping:
873
+ if scheme not in exclusions:
874
+ missing.append(key)
875
+ if missing:
876
+ msg = 'Missing metadata items: %s' % ', '.join(missing)
877
+ raise MetadataMissingError(msg)
878
+ for k, v in mapping.items():
879
+ self._validate_value(k, v, scheme)
880
+
881
+ def validate(self):
882
+ if self._legacy:
883
+ missing, warnings = self._legacy.check(True)
884
+ if missing or warnings:
885
+ logger.warning('Metadata: missing: %s, warnings: %s', missing, warnings)
886
+ else:
887
+ self._validate_mapping(self._data, self.scheme)
888
+
889
+ def todict(self):
890
+ if self._legacy:
891
+ return self._legacy.todict(True)
892
+ else:
893
+ result = extract_by_key(self._data, self.INDEX_KEYS)
894
+ return result
895
+
896
+ def _from_legacy(self):
897
+ assert self._legacy and not self._data
898
+ result = {
899
+ 'metadata_version': self.METADATA_VERSION,
900
+ 'generator': self.GENERATOR,
901
+ }
902
+ lmd = self._legacy.todict(True) # skip missing ones
903
+ for k in ('name', 'version', 'license', 'summary', 'description', 'classifier'):
904
+ if k in lmd:
905
+ if k == 'classifier':
906
+ nk = 'classifiers'
907
+ else:
908
+ nk = k
909
+ result[nk] = lmd[k]
910
+ kw = lmd.get('Keywords', [])
911
+ if kw == ['']:
912
+ kw = []
913
+ result['keywords'] = kw
914
+ keys = (('requires_dist', 'run_requires'), ('setup_requires_dist', 'build_requires'))
915
+ for ok, nk in keys:
916
+ if ok in lmd and lmd[ok]:
917
+ result[nk] = [{'requires': lmd[ok]}]
918
+ result['provides'] = self.provides
919
+ # author = {}
920
+ # maintainer = {}
921
+ return result
922
+
923
+ LEGACY_MAPPING = {
924
+ 'name': 'Name',
925
+ 'version': 'Version',
926
+ ('extensions', 'python.details', 'license'): 'License',
927
+ 'summary': 'Summary',
928
+ 'description': 'Description',
929
+ ('extensions', 'python.project', 'project_urls', 'Home'): 'Home-page',
930
+ ('extensions', 'python.project', 'contacts', 0, 'name'): 'Author',
931
+ ('extensions', 'python.project', 'contacts', 0, 'email'): 'Author-email',
932
+ 'source_url': 'Download-URL',
933
+ ('extensions', 'python.details', 'classifiers'): 'Classifier',
934
+ }
935
+
936
+ def _to_legacy(self):
937
+
938
+ def process_entries(entries):
939
+ reqts = set()
940
+ for e in entries:
941
+ extra = e.get('extra')
942
+ env = e.get('environment')
943
+ rlist = e['requires']
944
+ for r in rlist:
945
+ if not env and not extra:
946
+ reqts.add(r)
947
+ else:
948
+ marker = ''
949
+ if extra:
950
+ marker = 'extra == "%s"' % extra
951
+ if env:
952
+ if marker:
953
+ marker = '(%s) and %s' % (env, marker)
954
+ else:
955
+ marker = env
956
+ reqts.add(';'.join((r, marker)))
957
+ return reqts
958
+
959
+ assert self._data and not self._legacy
960
+ result = LegacyMetadata()
961
+ nmd = self._data
962
+ # import pdb; pdb.set_trace()
963
+ for nk, ok in self.LEGACY_MAPPING.items():
964
+ if not isinstance(nk, tuple):
965
+ if nk in nmd:
966
+ result[ok] = nmd[nk]
967
+ else:
968
+ d = nmd
969
+ found = True
970
+ for k in nk:
971
+ try:
972
+ d = d[k]
973
+ except (KeyError, IndexError):
974
+ found = False
975
+ break
976
+ if found:
977
+ result[ok] = d
978
+ r1 = process_entries(self.run_requires + self.meta_requires)
979
+ r2 = process_entries(self.build_requires + self.dev_requires)
980
+ if self.extras:
981
+ result['Provides-Extra'] = sorted(self.extras)
982
+ result['Requires-Dist'] = sorted(r1)
983
+ result['Setup-Requires-Dist'] = sorted(r2)
984
+ # TODO: any other fields wanted
985
+ return result
986
+
987
+ def write(self, path=None, fileobj=None, legacy=False, skip_unknown=True):
988
+ if [path, fileobj].count(None) != 1:
989
+ raise ValueError('Exactly one of path and fileobj is needed')
990
+ self.validate()
991
+ if legacy:
992
+ if self._legacy:
993
+ legacy_md = self._legacy
994
+ else:
995
+ legacy_md = self._to_legacy()
996
+ if path:
997
+ legacy_md.write(path, skip_unknown=skip_unknown)
998
+ else:
999
+ legacy_md.write_file(fileobj, skip_unknown=skip_unknown)
1000
+ else:
1001
+ if self._legacy:
1002
+ d = self._from_legacy()
1003
+ else:
1004
+ d = self._data
1005
+ if fileobj:
1006
+ json.dump(d, fileobj, ensure_ascii=True, indent=2, sort_keys=True)
1007
+ else:
1008
+ with codecs.open(path, 'w', 'utf-8') as f:
1009
+ json.dump(d, f, ensure_ascii=True, indent=2, sort_keys=True)
1010
+
1011
+ def add_requirements(self, requirements):
1012
+ if self._legacy:
1013
+ self._legacy.add_requirements(requirements)
1014
+ else:
1015
+ run_requires = self._data.setdefault('run_requires', [])
1016
+ always = None
1017
+ for entry in run_requires:
1018
+ if 'environment' not in entry and 'extra' not in entry:
1019
+ always = entry
1020
+ break
1021
+ if always is None:
1022
+ always = {'requires': requirements}
1023
+ run_requires.insert(0, always)
1024
+ else:
1025
+ rset = set(always['requires']) | set(requirements)
1026
+ always['requires'] = sorted(rset)
1027
+
1028
+ def __repr__(self):
1029
+ name = self.name or '(no name)'
1030
+ version = self.version or 'no version'
1031
+ return '<%s %s %s (%s)>' % (self.__class__.__name__, self.metadata_version, name, version)
lib/python3.12/site-packages/pip/_vendor/distlib/resources.py ADDED
@@ -0,0 +1,358 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2013-2017 Vinay Sajip.
4
+ # Licensed to the Python Software Foundation under a contributor agreement.
5
+ # See LICENSE.txt and CONTRIBUTORS.txt.
6
+ #
7
+ from __future__ import unicode_literals
8
+
9
+ import bisect
10
+ import io
11
+ import logging
12
+ import os
13
+ import pkgutil
14
+ import sys
15
+ import types
16
+ import zipimport
17
+
18
+ from . import DistlibException
19
+ from .util import cached_property, get_cache_base, Cache
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ cache = None # created when needed
25
+
26
+
27
+ class ResourceCache(Cache):
28
+ def __init__(self, base=None):
29
+ if base is None:
30
+ # Use native string to avoid issues on 2.x: see Python #20140.
31
+ base = os.path.join(get_cache_base(), str('resource-cache'))
32
+ super(ResourceCache, self).__init__(base)
33
+
34
+ def is_stale(self, resource, path):
35
+ """
36
+ Is the cache stale for the given resource?
37
+
38
+ :param resource: The :class:`Resource` being cached.
39
+ :param path: The path of the resource in the cache.
40
+ :return: True if the cache is stale.
41
+ """
42
+ # Cache invalidation is a hard problem :-)
43
+ return True
44
+
45
+ def get(self, resource):
46
+ """
47
+ Get a resource into the cache,
48
+
49
+ :param resource: A :class:`Resource` instance.
50
+ :return: The pathname of the resource in the cache.
51
+ """
52
+ prefix, path = resource.finder.get_cache_info(resource)
53
+ if prefix is None:
54
+ result = path
55
+ else:
56
+ result = os.path.join(self.base, self.prefix_to_dir(prefix), path)
57
+ dirname = os.path.dirname(result)
58
+ if not os.path.isdir(dirname):
59
+ os.makedirs(dirname)
60
+ if not os.path.exists(result):
61
+ stale = True
62
+ else:
63
+ stale = self.is_stale(resource, path)
64
+ if stale:
65
+ # write the bytes of the resource to the cache location
66
+ with open(result, 'wb') as f:
67
+ f.write(resource.bytes)
68
+ return result
69
+
70
+
71
+ class ResourceBase(object):
72
+ def __init__(self, finder, name):
73
+ self.finder = finder
74
+ self.name = name
75
+
76
+
77
+ class Resource(ResourceBase):
78
+ """
79
+ A class representing an in-package resource, such as a data file. This is
80
+ not normally instantiated by user code, but rather by a
81
+ :class:`ResourceFinder` which manages the resource.
82
+ """
83
+ is_container = False # Backwards compatibility
84
+
85
+ def as_stream(self):
86
+ """
87
+ Get the resource as a stream.
88
+
89
+ This is not a property to make it obvious that it returns a new stream
90
+ each time.
91
+ """
92
+ return self.finder.get_stream(self)
93
+
94
+ @cached_property
95
+ def file_path(self):
96
+ global cache
97
+ if cache is None:
98
+ cache = ResourceCache()
99
+ return cache.get(self)
100
+
101
+ @cached_property
102
+ def bytes(self):
103
+ return self.finder.get_bytes(self)
104
+
105
+ @cached_property
106
+ def size(self):
107
+ return self.finder.get_size(self)
108
+
109
+
110
+ class ResourceContainer(ResourceBase):
111
+ is_container = True # Backwards compatibility
112
+
113
+ @cached_property
114
+ def resources(self):
115
+ return self.finder.get_resources(self)
116
+
117
+
118
+ class ResourceFinder(object):
119
+ """
120
+ Resource finder for file system resources.
121
+ """
122
+
123
+ if sys.platform.startswith('java'):
124
+ skipped_extensions = ('.pyc', '.pyo', '.class')
125
+ else:
126
+ skipped_extensions = ('.pyc', '.pyo')
127
+
128
+ def __init__(self, module):
129
+ self.module = module
130
+ self.loader = getattr(module, '__loader__', None)
131
+ self.base = os.path.dirname(getattr(module, '__file__', ''))
132
+
133
+ def _adjust_path(self, path):
134
+ return os.path.realpath(path)
135
+
136
+ def _make_path(self, resource_name):
137
+ # Issue #50: need to preserve type of path on Python 2.x
138
+ # like os.path._get_sep
139
+ if isinstance(resource_name, bytes): # should only happen on 2.x
140
+ sep = b'/'
141
+ else:
142
+ sep = '/'
143
+ parts = resource_name.split(sep)
144
+ parts.insert(0, self.base)
145
+ result = os.path.join(*parts)
146
+ return self._adjust_path(result)
147
+
148
+ def _find(self, path):
149
+ return os.path.exists(path)
150
+
151
+ def get_cache_info(self, resource):
152
+ return None, resource.path
153
+
154
+ def find(self, resource_name):
155
+ path = self._make_path(resource_name)
156
+ if not self._find(path):
157
+ result = None
158
+ else:
159
+ if self._is_directory(path):
160
+ result = ResourceContainer(self, resource_name)
161
+ else:
162
+ result = Resource(self, resource_name)
163
+ result.path = path
164
+ return result
165
+
166
+ def get_stream(self, resource):
167
+ return open(resource.path, 'rb')
168
+
169
+ def get_bytes(self, resource):
170
+ with open(resource.path, 'rb') as f:
171
+ return f.read()
172
+
173
+ def get_size(self, resource):
174
+ return os.path.getsize(resource.path)
175
+
176
+ def get_resources(self, resource):
177
+ def allowed(f):
178
+ return (f != '__pycache__' and not
179
+ f.endswith(self.skipped_extensions))
180
+ return set([f for f in os.listdir(resource.path) if allowed(f)])
181
+
182
+ def is_container(self, resource):
183
+ return self._is_directory(resource.path)
184
+
185
+ _is_directory = staticmethod(os.path.isdir)
186
+
187
+ def iterator(self, resource_name):
188
+ resource = self.find(resource_name)
189
+ if resource is not None:
190
+ todo = [resource]
191
+ while todo:
192
+ resource = todo.pop(0)
193
+ yield resource
194
+ if resource.is_container:
195
+ rname = resource.name
196
+ for name in resource.resources:
197
+ if not rname:
198
+ new_name = name
199
+ else:
200
+ new_name = '/'.join([rname, name])
201
+ child = self.find(new_name)
202
+ if child.is_container:
203
+ todo.append(child)
204
+ else:
205
+ yield child
206
+
207
+
208
+ class ZipResourceFinder(ResourceFinder):
209
+ """
210
+ Resource finder for resources in .zip files.
211
+ """
212
+ def __init__(self, module):
213
+ super(ZipResourceFinder, self).__init__(module)
214
+ archive = self.loader.archive
215
+ self.prefix_len = 1 + len(archive)
216
+ # PyPy doesn't have a _files attr on zipimporter, and you can't set one
217
+ if hasattr(self.loader, '_files'):
218
+ self._files = self.loader._files
219
+ else:
220
+ self._files = zipimport._zip_directory_cache[archive]
221
+ self.index = sorted(self._files)
222
+
223
+ def _adjust_path(self, path):
224
+ return path
225
+
226
+ def _find(self, path):
227
+ path = path[self.prefix_len:]
228
+ if path in self._files:
229
+ result = True
230
+ else:
231
+ if path and path[-1] != os.sep:
232
+ path = path + os.sep
233
+ i = bisect.bisect(self.index, path)
234
+ try:
235
+ result = self.index[i].startswith(path)
236
+ except IndexError:
237
+ result = False
238
+ if not result:
239
+ logger.debug('_find failed: %r %r', path, self.loader.prefix)
240
+ else:
241
+ logger.debug('_find worked: %r %r', path, self.loader.prefix)
242
+ return result
243
+
244
+ def get_cache_info(self, resource):
245
+ prefix = self.loader.archive
246
+ path = resource.path[1 + len(prefix):]
247
+ return prefix, path
248
+
249
+ def get_bytes(self, resource):
250
+ return self.loader.get_data(resource.path)
251
+
252
+ def get_stream(self, resource):
253
+ return io.BytesIO(self.get_bytes(resource))
254
+
255
+ def get_size(self, resource):
256
+ path = resource.path[self.prefix_len:]
257
+ return self._files[path][3]
258
+
259
+ def get_resources(self, resource):
260
+ path = resource.path[self.prefix_len:]
261
+ if path and path[-1] != os.sep:
262
+ path += os.sep
263
+ plen = len(path)
264
+ result = set()
265
+ i = bisect.bisect(self.index, path)
266
+ while i < len(self.index):
267
+ if not self.index[i].startswith(path):
268
+ break
269
+ s = self.index[i][plen:]
270
+ result.add(s.split(os.sep, 1)[0]) # only immediate children
271
+ i += 1
272
+ return result
273
+
274
+ def _is_directory(self, path):
275
+ path = path[self.prefix_len:]
276
+ if path and path[-1] != os.sep:
277
+ path += os.sep
278
+ i = bisect.bisect(self.index, path)
279
+ try:
280
+ result = self.index[i].startswith(path)
281
+ except IndexError:
282
+ result = False
283
+ return result
284
+
285
+
286
+ _finder_registry = {
287
+ type(None): ResourceFinder,
288
+ zipimport.zipimporter: ZipResourceFinder
289
+ }
290
+
291
+ try:
292
+ # In Python 3.6, _frozen_importlib -> _frozen_importlib_external
293
+ try:
294
+ import _frozen_importlib_external as _fi
295
+ except ImportError:
296
+ import _frozen_importlib as _fi
297
+ _finder_registry[_fi.SourceFileLoader] = ResourceFinder
298
+ _finder_registry[_fi.FileFinder] = ResourceFinder
299
+ # See issue #146
300
+ _finder_registry[_fi.SourcelessFileLoader] = ResourceFinder
301
+ del _fi
302
+ except (ImportError, AttributeError):
303
+ pass
304
+
305
+
306
+ def register_finder(loader, finder_maker):
307
+ _finder_registry[type(loader)] = finder_maker
308
+
309
+
310
+ _finder_cache = {}
311
+
312
+
313
+ def finder(package):
314
+ """
315
+ Return a resource finder for a package.
316
+ :param package: The name of the package.
317
+ :return: A :class:`ResourceFinder` instance for the package.
318
+ """
319
+ if package in _finder_cache:
320
+ result = _finder_cache[package]
321
+ else:
322
+ if package not in sys.modules:
323
+ __import__(package)
324
+ module = sys.modules[package]
325
+ path = getattr(module, '__path__', None)
326
+ if path is None:
327
+ raise DistlibException('You cannot get a finder for a module, '
328
+ 'only for a package')
329
+ loader = getattr(module, '__loader__', None)
330
+ finder_maker = _finder_registry.get(type(loader))
331
+ if finder_maker is None:
332
+ raise DistlibException('Unable to locate finder for %r' % package)
333
+ result = finder_maker(module)
334
+ _finder_cache[package] = result
335
+ return result
336
+
337
+
338
+ _dummy_module = types.ModuleType(str('__dummy__'))
339
+
340
+
341
+ def finder_for_path(path):
342
+ """
343
+ Return a resource finder for a path, which should represent a container.
344
+
345
+ :param path: The path.
346
+ :return: A :class:`ResourceFinder` instance for the path.
347
+ """
348
+ result = None
349
+ # calls any path hooks, gets importer into cache
350
+ pkgutil.get_importer(path)
351
+ loader = sys.path_importer_cache.get(path)
352
+ finder = _finder_registry.get(type(loader))
353
+ if finder:
354
+ module = _dummy_module
355
+ module.__file__ = os.path.join(path, '')
356
+ module.__loader__ = loader
357
+ result = finder(module)
358
+ return result
lib/python3.12/site-packages/pip/_vendor/distlib/util.py ADDED
@@ -0,0 +1,1984 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # Copyright (C) 2012-2023 The Python Software Foundation.
3
+ # See LICENSE.txt and CONTRIBUTORS.txt.
4
+ #
5
+ import codecs
6
+ from collections import deque
7
+ import contextlib
8
+ import csv
9
+ from glob import iglob as std_iglob
10
+ import io
11
+ import json
12
+ import logging
13
+ import os
14
+ import py_compile
15
+ import re
16
+ import socket
17
+ try:
18
+ import ssl
19
+ except ImportError: # pragma: no cover
20
+ ssl = None
21
+ import subprocess
22
+ import sys
23
+ import tarfile
24
+ import tempfile
25
+ import textwrap
26
+
27
+ try:
28
+ import threading
29
+ except ImportError: # pragma: no cover
30
+ import dummy_threading as threading
31
+ import time
32
+
33
+ from . import DistlibException
34
+ from .compat import (string_types, text_type, shutil, raw_input, StringIO, cache_from_source, urlopen, urljoin, httplib,
35
+ xmlrpclib, HTTPHandler, BaseConfigurator, valid_ident, Container, configparser, URLError, ZipFile,
36
+ fsdecode, unquote, urlparse)
37
+
38
+ logger = logging.getLogger(__name__)
39
+
40
+ #
41
+ # Requirement parsing code as per PEP 508
42
+ #
43
+
44
+ IDENTIFIER = re.compile(r'^([\w\.-]+)\s*')
45
+ VERSION_IDENTIFIER = re.compile(r'^([\w\.*+-]+)\s*')
46
+ COMPARE_OP = re.compile(r'^(<=?|>=?|={2,3}|[~!]=)\s*')
47
+ MARKER_OP = re.compile(r'^((<=?)|(>=?)|={2,3}|[~!]=|in|not\s+in)\s*')
48
+ OR = re.compile(r'^or\b\s*')
49
+ AND = re.compile(r'^and\b\s*')
50
+ NON_SPACE = re.compile(r'(\S+)\s*')
51
+ STRING_CHUNK = re.compile(r'([\s\w\.{}()*+#:;,/?!~`@$%^&=|<>\[\]-]+)')
52
+
53
+
54
+ def parse_marker(marker_string):
55
+ """
56
+ Parse a marker string and return a dictionary containing a marker expression.
57
+
58
+ The dictionary will contain keys "op", "lhs" and "rhs" for non-terminals in
59
+ the expression grammar, or strings. A string contained in quotes is to be
60
+ interpreted as a literal string, and a string not contained in quotes is a
61
+ variable (such as os_name).
62
+ """
63
+
64
+ def marker_var(remaining):
65
+ # either identifier, or literal string
66
+ m = IDENTIFIER.match(remaining)
67
+ if m:
68
+ result = m.groups()[0]
69
+ remaining = remaining[m.end():]
70
+ elif not remaining:
71
+ raise SyntaxError('unexpected end of input')
72
+ else:
73
+ q = remaining[0]
74
+ if q not in '\'"':
75
+ raise SyntaxError('invalid expression: %s' % remaining)
76
+ oq = '\'"'.replace(q, '')
77
+ remaining = remaining[1:]
78
+ parts = [q]
79
+ while remaining:
80
+ # either a string chunk, or oq, or q to terminate
81
+ if remaining[0] == q:
82
+ break
83
+ elif remaining[0] == oq:
84
+ parts.append(oq)
85
+ remaining = remaining[1:]
86
+ else:
87
+ m = STRING_CHUNK.match(remaining)
88
+ if not m:
89
+ raise SyntaxError('error in string literal: %s' % remaining)
90
+ parts.append(m.groups()[0])
91
+ remaining = remaining[m.end():]
92
+ else:
93
+ s = ''.join(parts)
94
+ raise SyntaxError('unterminated string: %s' % s)
95
+ parts.append(q)
96
+ result = ''.join(parts)
97
+ remaining = remaining[1:].lstrip() # skip past closing quote
98
+ return result, remaining
99
+
100
+ def marker_expr(remaining):
101
+ if remaining and remaining[0] == '(':
102
+ result, remaining = marker(remaining[1:].lstrip())
103
+ if remaining[0] != ')':
104
+ raise SyntaxError('unterminated parenthesis: %s' % remaining)
105
+ remaining = remaining[1:].lstrip()
106
+ else:
107
+ lhs, remaining = marker_var(remaining)
108
+ while remaining:
109
+ m = MARKER_OP.match(remaining)
110
+ if not m:
111
+ break
112
+ op = m.groups()[0]
113
+ remaining = remaining[m.end():]
114
+ rhs, remaining = marker_var(remaining)
115
+ lhs = {'op': op, 'lhs': lhs, 'rhs': rhs}
116
+ result = lhs
117
+ return result, remaining
118
+
119
+ def marker_and(remaining):
120
+ lhs, remaining = marker_expr(remaining)
121
+ while remaining:
122
+ m = AND.match(remaining)
123
+ if not m:
124
+ break
125
+ remaining = remaining[m.end():]
126
+ rhs, remaining = marker_expr(remaining)
127
+ lhs = {'op': 'and', 'lhs': lhs, 'rhs': rhs}
128
+ return lhs, remaining
129
+
130
+ def marker(remaining):
131
+ lhs, remaining = marker_and(remaining)
132
+ while remaining:
133
+ m = OR.match(remaining)
134
+ if not m:
135
+ break
136
+ remaining = remaining[m.end():]
137
+ rhs, remaining = marker_and(remaining)
138
+ lhs = {'op': 'or', 'lhs': lhs, 'rhs': rhs}
139
+ return lhs, remaining
140
+
141
+ return marker(marker_string)
142
+
143
+
144
+ def parse_requirement(req):
145
+ """
146
+ Parse a requirement passed in as a string. Return a Container
147
+ whose attributes contain the various parts of the requirement.
148
+ """
149
+ remaining = req.strip()
150
+ if not remaining or remaining.startswith('#'):
151
+ return None
152
+ m = IDENTIFIER.match(remaining)
153
+ if not m:
154
+ raise SyntaxError('name expected: %s' % remaining)
155
+ distname = m.groups()[0]
156
+ remaining = remaining[m.end():]
157
+ extras = mark_expr = versions = uri = None
158
+ if remaining and remaining[0] == '[':
159
+ i = remaining.find(']', 1)
160
+ if i < 0:
161
+ raise SyntaxError('unterminated extra: %s' % remaining)
162
+ s = remaining[1:i]
163
+ remaining = remaining[i + 1:].lstrip()
164
+ extras = []
165
+ while s:
166
+ m = IDENTIFIER.match(s)
167
+ if not m:
168
+ raise SyntaxError('malformed extra: %s' % s)
169
+ extras.append(m.groups()[0])
170
+ s = s[m.end():]
171
+ if not s:
172
+ break
173
+ if s[0] != ',':
174
+ raise SyntaxError('comma expected in extras: %s' % s)
175
+ s = s[1:].lstrip()
176
+ if not extras:
177
+ extras = None
178
+ if remaining:
179
+ if remaining[0] == '@':
180
+ # it's a URI
181
+ remaining = remaining[1:].lstrip()
182
+ m = NON_SPACE.match(remaining)
183
+ if not m:
184
+ raise SyntaxError('invalid URI: %s' % remaining)
185
+ uri = m.groups()[0]
186
+ t = urlparse(uri)
187
+ # there are issues with Python and URL parsing, so this test
188
+ # is a bit crude. See bpo-20271, bpo-23505. Python doesn't
189
+ # always parse invalid URLs correctly - it should raise
190
+ # exceptions for malformed URLs
191
+ if not (t.scheme and t.netloc):
192
+ raise SyntaxError('Invalid URL: %s' % uri)
193
+ remaining = remaining[m.end():].lstrip()
194
+ else:
195
+
196
+ def get_versions(ver_remaining):
197
+ """
198
+ Return a list of operator, version tuples if any are
199
+ specified, else None.
200
+ """
201
+ m = COMPARE_OP.match(ver_remaining)
202
+ versions = None
203
+ if m:
204
+ versions = []
205
+ while True:
206
+ op = m.groups()[0]
207
+ ver_remaining = ver_remaining[m.end():]
208
+ m = VERSION_IDENTIFIER.match(ver_remaining)
209
+ if not m:
210
+ raise SyntaxError('invalid version: %s' % ver_remaining)
211
+ v = m.groups()[0]
212
+ versions.append((op, v))
213
+ ver_remaining = ver_remaining[m.end():]
214
+ if not ver_remaining or ver_remaining[0] != ',':
215
+ break
216
+ ver_remaining = ver_remaining[1:].lstrip()
217
+ # Some packages have a trailing comma which would break things
218
+ # See issue #148
219
+ if not ver_remaining:
220
+ break
221
+ m = COMPARE_OP.match(ver_remaining)
222
+ if not m:
223
+ raise SyntaxError('invalid constraint: %s' % ver_remaining)
224
+ if not versions:
225
+ versions = None
226
+ return versions, ver_remaining
227
+
228
+ if remaining[0] != '(':
229
+ versions, remaining = get_versions(remaining)
230
+ else:
231
+ i = remaining.find(')', 1)
232
+ if i < 0:
233
+ raise SyntaxError('unterminated parenthesis: %s' % remaining)
234
+ s = remaining[1:i]
235
+ remaining = remaining[i + 1:].lstrip()
236
+ # As a special diversion from PEP 508, allow a version number
237
+ # a.b.c in parentheses as a synonym for ~= a.b.c (because this
238
+ # is allowed in earlier PEPs)
239
+ if COMPARE_OP.match(s):
240
+ versions, _ = get_versions(s)
241
+ else:
242
+ m = VERSION_IDENTIFIER.match(s)
243
+ if not m:
244
+ raise SyntaxError('invalid constraint: %s' % s)
245
+ v = m.groups()[0]
246
+ s = s[m.end():].lstrip()
247
+ if s:
248
+ raise SyntaxError('invalid constraint: %s' % s)
249
+ versions = [('~=', v)]
250
+
251
+ if remaining:
252
+ if remaining[0] != ';':
253
+ raise SyntaxError('invalid requirement: %s' % remaining)
254
+ remaining = remaining[1:].lstrip()
255
+
256
+ mark_expr, remaining = parse_marker(remaining)
257
+
258
+ if remaining and remaining[0] != '#':
259
+ raise SyntaxError('unexpected trailing data: %s' % remaining)
260
+
261
+ if not versions:
262
+ rs = distname
263
+ else:
264
+ rs = '%s %s' % (distname, ', '.join(['%s %s' % con for con in versions]))
265
+ return Container(name=distname, extras=extras, constraints=versions, marker=mark_expr, url=uri, requirement=rs)
266
+
267
+
268
+ def get_resources_dests(resources_root, rules):
269
+ """Find destinations for resources files"""
270
+
271
+ def get_rel_path(root, path):
272
+ # normalizes and returns a lstripped-/-separated path
273
+ root = root.replace(os.path.sep, '/')
274
+ path = path.replace(os.path.sep, '/')
275
+ assert path.startswith(root)
276
+ return path[len(root):].lstrip('/')
277
+
278
+ destinations = {}
279
+ for base, suffix, dest in rules:
280
+ prefix = os.path.join(resources_root, base)
281
+ for abs_base in iglob(prefix):
282
+ abs_glob = os.path.join(abs_base, suffix)
283
+ for abs_path in iglob(abs_glob):
284
+ resource_file = get_rel_path(resources_root, abs_path)
285
+ if dest is None: # remove the entry if it was here
286
+ destinations.pop(resource_file, None)
287
+ else:
288
+ rel_path = get_rel_path(abs_base, abs_path)
289
+ rel_dest = dest.replace(os.path.sep, '/').rstrip('/')
290
+ destinations[resource_file] = rel_dest + '/' + rel_path
291
+ return destinations
292
+
293
+
294
+ def in_venv():
295
+ if hasattr(sys, 'real_prefix'):
296
+ # virtualenv venvs
297
+ result = True
298
+ else:
299
+ # PEP 405 venvs
300
+ result = sys.prefix != getattr(sys, 'base_prefix', sys.prefix)
301
+ return result
302
+
303
+
304
+ def get_executable():
305
+ # The __PYVENV_LAUNCHER__ dance is apparently no longer needed, as
306
+ # changes to the stub launcher mean that sys.executable always points
307
+ # to the stub on OS X
308
+ # if sys.platform == 'darwin' and ('__PYVENV_LAUNCHER__'
309
+ # in os.environ):
310
+ # result = os.environ['__PYVENV_LAUNCHER__']
311
+ # else:
312
+ # result = sys.executable
313
+ # return result
314
+ # Avoid normcasing: see issue #143
315
+ # result = os.path.normcase(sys.executable)
316
+ result = sys.executable
317
+ if not isinstance(result, text_type):
318
+ result = fsdecode(result)
319
+ return result
320
+
321
+
322
+ def proceed(prompt, allowed_chars, error_prompt=None, default=None):
323
+ p = prompt
324
+ while True:
325
+ s = raw_input(p)
326
+ p = prompt
327
+ if not s and default:
328
+ s = default
329
+ if s:
330
+ c = s[0].lower()
331
+ if c in allowed_chars:
332
+ break
333
+ if error_prompt:
334
+ p = '%c: %s\n%s' % (c, error_prompt, prompt)
335
+ return c
336
+
337
+
338
+ def extract_by_key(d, keys):
339
+ if isinstance(keys, string_types):
340
+ keys = keys.split()
341
+ result = {}
342
+ for key in keys:
343
+ if key in d:
344
+ result[key] = d[key]
345
+ return result
346
+
347
+
348
+ def read_exports(stream):
349
+ if sys.version_info[0] >= 3:
350
+ # needs to be a text stream
351
+ stream = codecs.getreader('utf-8')(stream)
352
+ # Try to load as JSON, falling back on legacy format
353
+ data = stream.read()
354
+ stream = StringIO(data)
355
+ try:
356
+ jdata = json.load(stream)
357
+ result = jdata['extensions']['python.exports']['exports']
358
+ for group, entries in result.items():
359
+ for k, v in entries.items():
360
+ s = '%s = %s' % (k, v)
361
+ entry = get_export_entry(s)
362
+ assert entry is not None
363
+ entries[k] = entry
364
+ return result
365
+ except Exception:
366
+ stream.seek(0, 0)
367
+
368
+ def read_stream(cp, stream):
369
+ if hasattr(cp, 'read_file'):
370
+ cp.read_file(stream)
371
+ else:
372
+ cp.readfp(stream)
373
+
374
+ cp = configparser.ConfigParser()
375
+ try:
376
+ read_stream(cp, stream)
377
+ except configparser.MissingSectionHeaderError:
378
+ stream.close()
379
+ data = textwrap.dedent(data)
380
+ stream = StringIO(data)
381
+ read_stream(cp, stream)
382
+
383
+ result = {}
384
+ for key in cp.sections():
385
+ result[key] = entries = {}
386
+ for name, value in cp.items(key):
387
+ s = '%s = %s' % (name, value)
388
+ entry = get_export_entry(s)
389
+ assert entry is not None
390
+ # entry.dist = self
391
+ entries[name] = entry
392
+ return result
393
+
394
+
395
+ def write_exports(exports, stream):
396
+ if sys.version_info[0] >= 3:
397
+ # needs to be a text stream
398
+ stream = codecs.getwriter('utf-8')(stream)
399
+ cp = configparser.ConfigParser()
400
+ for k, v in exports.items():
401
+ # TODO check k, v for valid values
402
+ cp.add_section(k)
403
+ for entry in v.values():
404
+ if entry.suffix is None:
405
+ s = entry.prefix
406
+ else:
407
+ s = '%s:%s' % (entry.prefix, entry.suffix)
408
+ if entry.flags:
409
+ s = '%s [%s]' % (s, ', '.join(entry.flags))
410
+ cp.set(k, entry.name, s)
411
+ cp.write(stream)
412
+
413
+
414
+ @contextlib.contextmanager
415
+ def tempdir():
416
+ td = tempfile.mkdtemp()
417
+ try:
418
+ yield td
419
+ finally:
420
+ shutil.rmtree(td)
421
+
422
+
423
+ @contextlib.contextmanager
424
+ def chdir(d):
425
+ cwd = os.getcwd()
426
+ try:
427
+ os.chdir(d)
428
+ yield
429
+ finally:
430
+ os.chdir(cwd)
431
+
432
+
433
+ @contextlib.contextmanager
434
+ def socket_timeout(seconds=15):
435
+ cto = socket.getdefaulttimeout()
436
+ try:
437
+ socket.setdefaulttimeout(seconds)
438
+ yield
439
+ finally:
440
+ socket.setdefaulttimeout(cto)
441
+
442
+
443
+ class cached_property(object):
444
+
445
+ def __init__(self, func):
446
+ self.func = func
447
+ # for attr in ('__name__', '__module__', '__doc__'):
448
+ # setattr(self, attr, getattr(func, attr, None))
449
+
450
+ def __get__(self, obj, cls=None):
451
+ if obj is None:
452
+ return self
453
+ value = self.func(obj)
454
+ object.__setattr__(obj, self.func.__name__, value)
455
+ # obj.__dict__[self.func.__name__] = value = self.func(obj)
456
+ return value
457
+
458
+
459
+ def convert_path(pathname):
460
+ """Return 'pathname' as a name that will work on the native filesystem.
461
+
462
+ The path is split on '/' and put back together again using the current
463
+ directory separator. Needed because filenames in the setup script are
464
+ always supplied in Unix style, and have to be converted to the local
465
+ convention before we can actually use them in the filesystem. Raises
466
+ ValueError on non-Unix-ish systems if 'pathname' either starts or
467
+ ends with a slash.
468
+ """
469
+ if os.sep == '/':
470
+ return pathname
471
+ if not pathname:
472
+ return pathname
473
+ if pathname[0] == '/':
474
+ raise ValueError("path '%s' cannot be absolute" % pathname)
475
+ if pathname[-1] == '/':
476
+ raise ValueError("path '%s' cannot end with '/'" % pathname)
477
+
478
+ paths = pathname.split('/')
479
+ while os.curdir in paths:
480
+ paths.remove(os.curdir)
481
+ if not paths:
482
+ return os.curdir
483
+ return os.path.join(*paths)
484
+
485
+
486
+ class FileOperator(object):
487
+
488
+ def __init__(self, dry_run=False):
489
+ self.dry_run = dry_run
490
+ self.ensured = set()
491
+ self._init_record()
492
+
493
+ def _init_record(self):
494
+ self.record = False
495
+ self.files_written = set()
496
+ self.dirs_created = set()
497
+
498
+ def record_as_written(self, path):
499
+ if self.record:
500
+ self.files_written.add(path)
501
+
502
+ def newer(self, source, target):
503
+ """Tell if the target is newer than the source.
504
+
505
+ Returns true if 'source' exists and is more recently modified than
506
+ 'target', or if 'source' exists and 'target' doesn't.
507
+
508
+ Returns false if both exist and 'target' is the same age or younger
509
+ than 'source'. Raise PackagingFileError if 'source' does not exist.
510
+
511
+ Note that this test is not very accurate: files created in the same
512
+ second will have the same "age".
513
+ """
514
+ if not os.path.exists(source):
515
+ raise DistlibException("file '%r' does not exist" % os.path.abspath(source))
516
+ if not os.path.exists(target):
517
+ return True
518
+
519
+ return os.stat(source).st_mtime > os.stat(target).st_mtime
520
+
521
+ def copy_file(self, infile, outfile, check=True):
522
+ """Copy a file respecting dry-run and force flags.
523
+ """
524
+ self.ensure_dir(os.path.dirname(outfile))
525
+ logger.info('Copying %s to %s', infile, outfile)
526
+ if not self.dry_run:
527
+ msg = None
528
+ if check:
529
+ if os.path.islink(outfile):
530
+ msg = '%s is a symlink' % outfile
531
+ elif os.path.exists(outfile) and not os.path.isfile(outfile):
532
+ msg = '%s is a non-regular file' % outfile
533
+ if msg:
534
+ raise ValueError(msg + ' which would be overwritten')
535
+ shutil.copyfile(infile, outfile)
536
+ self.record_as_written(outfile)
537
+
538
+ def copy_stream(self, instream, outfile, encoding=None):
539
+ assert not os.path.isdir(outfile)
540
+ self.ensure_dir(os.path.dirname(outfile))
541
+ logger.info('Copying stream %s to %s', instream, outfile)
542
+ if not self.dry_run:
543
+ if encoding is None:
544
+ outstream = open(outfile, 'wb')
545
+ else:
546
+ outstream = codecs.open(outfile, 'w', encoding=encoding)
547
+ try:
548
+ shutil.copyfileobj(instream, outstream)
549
+ finally:
550
+ outstream.close()
551
+ self.record_as_written(outfile)
552
+
553
+ def write_binary_file(self, path, data):
554
+ self.ensure_dir(os.path.dirname(path))
555
+ if not self.dry_run:
556
+ if os.path.exists(path):
557
+ os.remove(path)
558
+ with open(path, 'wb') as f:
559
+ f.write(data)
560
+ self.record_as_written(path)
561
+
562
+ def write_text_file(self, path, data, encoding):
563
+ self.write_binary_file(path, data.encode(encoding))
564
+
565
+ def set_mode(self, bits, mask, files):
566
+ if os.name == 'posix' or (os.name == 'java' and os._name == 'posix'):
567
+ # Set the executable bits (owner, group, and world) on
568
+ # all the files specified.
569
+ for f in files:
570
+ if self.dry_run:
571
+ logger.info("changing mode of %s", f)
572
+ else:
573
+ mode = (os.stat(f).st_mode | bits) & mask
574
+ logger.info("changing mode of %s to %o", f, mode)
575
+ os.chmod(f, mode)
576
+
577
+ set_executable_mode = lambda s, f: s.set_mode(0o555, 0o7777, f)
578
+
579
+ def ensure_dir(self, path):
580
+ path = os.path.abspath(path)
581
+ if path not in self.ensured and not os.path.exists(path):
582
+ self.ensured.add(path)
583
+ d, f = os.path.split(path)
584
+ self.ensure_dir(d)
585
+ logger.info('Creating %s' % path)
586
+ if not self.dry_run:
587
+ os.mkdir(path)
588
+ if self.record:
589
+ self.dirs_created.add(path)
590
+
591
+ def byte_compile(self, path, optimize=False, force=False, prefix=None, hashed_invalidation=False):
592
+ dpath = cache_from_source(path, not optimize)
593
+ logger.info('Byte-compiling %s to %s', path, dpath)
594
+ if not self.dry_run:
595
+ if force or self.newer(path, dpath):
596
+ if not prefix:
597
+ diagpath = None
598
+ else:
599
+ assert path.startswith(prefix)
600
+ diagpath = path[len(prefix):]
601
+ compile_kwargs = {}
602
+ if hashed_invalidation and hasattr(py_compile, 'PycInvalidationMode'):
603
+ if not isinstance(hashed_invalidation, py_compile.PycInvalidationMode):
604
+ hashed_invalidation = py_compile.PycInvalidationMode.CHECKED_HASH
605
+ compile_kwargs['invalidation_mode'] = hashed_invalidation
606
+ py_compile.compile(path, dpath, diagpath, True, **compile_kwargs) # raise error
607
+ self.record_as_written(dpath)
608
+ return dpath
609
+
610
+ def ensure_removed(self, path):
611
+ if os.path.exists(path):
612
+ if os.path.isdir(path) and not os.path.islink(path):
613
+ logger.debug('Removing directory tree at %s', path)
614
+ if not self.dry_run:
615
+ shutil.rmtree(path)
616
+ if self.record:
617
+ if path in self.dirs_created:
618
+ self.dirs_created.remove(path)
619
+ else:
620
+ if os.path.islink(path):
621
+ s = 'link'
622
+ else:
623
+ s = 'file'
624
+ logger.debug('Removing %s %s', s, path)
625
+ if not self.dry_run:
626
+ os.remove(path)
627
+ if self.record:
628
+ if path in self.files_written:
629
+ self.files_written.remove(path)
630
+
631
+ def is_writable(self, path):
632
+ result = False
633
+ while not result:
634
+ if os.path.exists(path):
635
+ result = os.access(path, os.W_OK)
636
+ break
637
+ parent = os.path.dirname(path)
638
+ if parent == path:
639
+ break
640
+ path = parent
641
+ return result
642
+
643
+ def commit(self):
644
+ """
645
+ Commit recorded changes, turn off recording, return
646
+ changes.
647
+ """
648
+ assert self.record
649
+ result = self.files_written, self.dirs_created
650
+ self._init_record()
651
+ return result
652
+
653
+ def rollback(self):
654
+ if not self.dry_run:
655
+ for f in list(self.files_written):
656
+ if os.path.exists(f):
657
+ os.remove(f)
658
+ # dirs should all be empty now, except perhaps for
659
+ # __pycache__ subdirs
660
+ # reverse so that subdirs appear before their parents
661
+ dirs = sorted(self.dirs_created, reverse=True)
662
+ for d in dirs:
663
+ flist = os.listdir(d)
664
+ if flist:
665
+ assert flist == ['__pycache__']
666
+ sd = os.path.join(d, flist[0])
667
+ os.rmdir(sd)
668
+ os.rmdir(d) # should fail if non-empty
669
+ self._init_record()
670
+
671
+
672
+ def resolve(module_name, dotted_path):
673
+ if module_name in sys.modules:
674
+ mod = sys.modules[module_name]
675
+ else:
676
+ mod = __import__(module_name)
677
+ if dotted_path is None:
678
+ result = mod
679
+ else:
680
+ parts = dotted_path.split('.')
681
+ result = getattr(mod, parts.pop(0))
682
+ for p in parts:
683
+ result = getattr(result, p)
684
+ return result
685
+
686
+
687
+ class ExportEntry(object):
688
+
689
+ def __init__(self, name, prefix, suffix, flags):
690
+ self.name = name
691
+ self.prefix = prefix
692
+ self.suffix = suffix
693
+ self.flags = flags
694
+
695
+ @cached_property
696
+ def value(self):
697
+ return resolve(self.prefix, self.suffix)
698
+
699
+ def __repr__(self): # pragma: no cover
700
+ return '<ExportEntry %s = %s:%s %s>' % (self.name, self.prefix, self.suffix, self.flags)
701
+
702
+ def __eq__(self, other):
703
+ if not isinstance(other, ExportEntry):
704
+ result = False
705
+ else:
706
+ result = (self.name == other.name and self.prefix == other.prefix and self.suffix == other.suffix and
707
+ self.flags == other.flags)
708
+ return result
709
+
710
+ __hash__ = object.__hash__
711
+
712
+
713
+ ENTRY_RE = re.compile(
714
+ r'''(?P<name>([^\[]\S*))
715
+ \s*=\s*(?P<callable>(\w+)([:\.]\w+)*)
716
+ \s*(\[\s*(?P<flags>[\w-]+(=\w+)?(,\s*\w+(=\w+)?)*)\s*\])?
717
+ ''', re.VERBOSE)
718
+
719
+
720
+ def get_export_entry(specification):
721
+ m = ENTRY_RE.search(specification)
722
+ if not m:
723
+ result = None
724
+ if '[' in specification or ']' in specification:
725
+ raise DistlibException("Invalid specification "
726
+ "'%s'" % specification)
727
+ else:
728
+ d = m.groupdict()
729
+ name = d['name']
730
+ path = d['callable']
731
+ colons = path.count(':')
732
+ if colons == 0:
733
+ prefix, suffix = path, None
734
+ else:
735
+ if colons != 1:
736
+ raise DistlibException("Invalid specification "
737
+ "'%s'" % specification)
738
+ prefix, suffix = path.split(':')
739
+ flags = d['flags']
740
+ if flags is None:
741
+ if '[' in specification or ']' in specification:
742
+ raise DistlibException("Invalid specification "
743
+ "'%s'" % specification)
744
+ flags = []
745
+ else:
746
+ flags = [f.strip() for f in flags.split(',')]
747
+ result = ExportEntry(name, prefix, suffix, flags)
748
+ return result
749
+
750
+
751
+ def get_cache_base(suffix=None):
752
+ """
753
+ Return the default base location for distlib caches. If the directory does
754
+ not exist, it is created. Use the suffix provided for the base directory,
755
+ and default to '.distlib' if it isn't provided.
756
+
757
+ On Windows, if LOCALAPPDATA is defined in the environment, then it is
758
+ assumed to be a directory, and will be the parent directory of the result.
759
+ On POSIX, and on Windows if LOCALAPPDATA is not defined, the user's home
760
+ directory - using os.expanduser('~') - will be the parent directory of
761
+ the result.
762
+
763
+ The result is just the directory '.distlib' in the parent directory as
764
+ determined above, or with the name specified with ``suffix``.
765
+ """
766
+ if suffix is None:
767
+ suffix = '.distlib'
768
+ if os.name == 'nt' and 'LOCALAPPDATA' in os.environ:
769
+ result = os.path.expandvars('$localappdata')
770
+ else:
771
+ # Assume posix, or old Windows
772
+ result = os.path.expanduser('~')
773
+ # we use 'isdir' instead of 'exists', because we want to
774
+ # fail if there's a file with that name
775
+ if os.path.isdir(result):
776
+ usable = os.access(result, os.W_OK)
777
+ if not usable:
778
+ logger.warning('Directory exists but is not writable: %s', result)
779
+ else:
780
+ try:
781
+ os.makedirs(result)
782
+ usable = True
783
+ except OSError:
784
+ logger.warning('Unable to create %s', result, exc_info=True)
785
+ usable = False
786
+ if not usable:
787
+ result = tempfile.mkdtemp()
788
+ logger.warning('Default location unusable, using %s', result)
789
+ return os.path.join(result, suffix)
790
+
791
+
792
+ def path_to_cache_dir(path, use_abspath=True):
793
+ """
794
+ Convert an absolute path to a directory name for use in a cache.
795
+
796
+ The algorithm used is:
797
+
798
+ #. On Windows, any ``':'`` in the drive is replaced with ``'---'``.
799
+ #. Any occurrence of ``os.sep`` is replaced with ``'--'``.
800
+ #. ``'.cache'`` is appended.
801
+ """
802
+ d, p = os.path.splitdrive(os.path.abspath(path) if use_abspath else path)
803
+ if d:
804
+ d = d.replace(':', '---')
805
+ p = p.replace(os.sep, '--')
806
+ return d + p + '.cache'
807
+
808
+
809
+ def ensure_slash(s):
810
+ if not s.endswith('/'):
811
+ return s + '/'
812
+ return s
813
+
814
+
815
+ def parse_credentials(netloc):
816
+ username = password = None
817
+ if '@' in netloc:
818
+ prefix, netloc = netloc.rsplit('@', 1)
819
+ if ':' not in prefix:
820
+ username = prefix
821
+ else:
822
+ username, password = prefix.split(':', 1)
823
+ if username:
824
+ username = unquote(username)
825
+ if password:
826
+ password = unquote(password)
827
+ return username, password, netloc
828
+
829
+
830
+ def get_process_umask():
831
+ result = os.umask(0o22)
832
+ os.umask(result)
833
+ return result
834
+
835
+
836
+ def is_string_sequence(seq):
837
+ result = True
838
+ i = None
839
+ for i, s in enumerate(seq):
840
+ if not isinstance(s, string_types):
841
+ result = False
842
+ break
843
+ assert i is not None
844
+ return result
845
+
846
+
847
+ PROJECT_NAME_AND_VERSION = re.compile('([a-z0-9_]+([.-][a-z_][a-z0-9_]*)*)-'
848
+ '([a-z0-9_.+-]+)', re.I)
849
+ PYTHON_VERSION = re.compile(r'-py(\d\.?\d?)')
850
+
851
+
852
+ def split_filename(filename, project_name=None):
853
+ """
854
+ Extract name, version, python version from a filename (no extension)
855
+
856
+ Return name, version, pyver or None
857
+ """
858
+ result = None
859
+ pyver = None
860
+ filename = unquote(filename).replace(' ', '-')
861
+ m = PYTHON_VERSION.search(filename)
862
+ if m:
863
+ pyver = m.group(1)
864
+ filename = filename[:m.start()]
865
+ if project_name and len(filename) > len(project_name) + 1:
866
+ m = re.match(re.escape(project_name) + r'\b', filename)
867
+ if m:
868
+ n = m.end()
869
+ result = filename[:n], filename[n + 1:], pyver
870
+ if result is None:
871
+ m = PROJECT_NAME_AND_VERSION.match(filename)
872
+ if m:
873
+ result = m.group(1), m.group(3), pyver
874
+ return result
875
+
876
+
877
+ # Allow spaces in name because of legacy dists like "Twisted Core"
878
+ NAME_VERSION_RE = re.compile(r'(?P<name>[\w .-]+)\s*'
879
+ r'\(\s*(?P<ver>[^\s)]+)\)$')
880
+
881
+
882
+ def parse_name_and_version(p):
883
+ """
884
+ A utility method used to get name and version from a string.
885
+
886
+ From e.g. a Provides-Dist value.
887
+
888
+ :param p: A value in a form 'foo (1.0)'
889
+ :return: The name and version as a tuple.
890
+ """
891
+ m = NAME_VERSION_RE.match(p)
892
+ if not m:
893
+ raise DistlibException('Ill-formed name/version string: \'%s\'' % p)
894
+ d = m.groupdict()
895
+ return d['name'].strip().lower(), d['ver']
896
+
897
+
898
+ def get_extras(requested, available):
899
+ result = set()
900
+ requested = set(requested or [])
901
+ available = set(available or [])
902
+ if '*' in requested:
903
+ requested.remove('*')
904
+ result |= available
905
+ for r in requested:
906
+ if r == '-':
907
+ result.add(r)
908
+ elif r.startswith('-'):
909
+ unwanted = r[1:]
910
+ if unwanted not in available:
911
+ logger.warning('undeclared extra: %s' % unwanted)
912
+ if unwanted in result:
913
+ result.remove(unwanted)
914
+ else:
915
+ if r not in available:
916
+ logger.warning('undeclared extra: %s' % r)
917
+ result.add(r)
918
+ return result
919
+
920
+
921
+ #
922
+ # Extended metadata functionality
923
+ #
924
+
925
+
926
+ def _get_external_data(url):
927
+ result = {}
928
+ try:
929
+ # urlopen might fail if it runs into redirections,
930
+ # because of Python issue #13696. Fixed in locators
931
+ # using a custom redirect handler.
932
+ resp = urlopen(url)
933
+ headers = resp.info()
934
+ ct = headers.get('Content-Type')
935
+ if not ct.startswith('application/json'):
936
+ logger.debug('Unexpected response for JSON request: %s', ct)
937
+ else:
938
+ reader = codecs.getreader('utf-8')(resp)
939
+ # data = reader.read().decode('utf-8')
940
+ # result = json.loads(data)
941
+ result = json.load(reader)
942
+ except Exception as e:
943
+ logger.exception('Failed to get external data for %s: %s', url, e)
944
+ return result
945
+
946
+
947
+ _external_data_base_url = 'https://www.red-dove.com/pypi/projects/'
948
+
949
+
950
+ def get_project_data(name):
951
+ url = '%s/%s/project.json' % (name[0].upper(), name)
952
+ url = urljoin(_external_data_base_url, url)
953
+ result = _get_external_data(url)
954
+ return result
955
+
956
+
957
+ def get_package_data(name, version):
958
+ url = '%s/%s/package-%s.json' % (name[0].upper(), name, version)
959
+ url = urljoin(_external_data_base_url, url)
960
+ return _get_external_data(url)
961
+
962
+
963
+ class Cache(object):
964
+ """
965
+ A class implementing a cache for resources that need to live in the file system
966
+ e.g. shared libraries. This class was moved from resources to here because it
967
+ could be used by other modules, e.g. the wheel module.
968
+ """
969
+
970
+ def __init__(self, base):
971
+ """
972
+ Initialise an instance.
973
+
974
+ :param base: The base directory where the cache should be located.
975
+ """
976
+ # we use 'isdir' instead of 'exists', because we want to
977
+ # fail if there's a file with that name
978
+ if not os.path.isdir(base): # pragma: no cover
979
+ os.makedirs(base)
980
+ if (os.stat(base).st_mode & 0o77) != 0:
981
+ logger.warning('Directory \'%s\' is not private', base)
982
+ self.base = os.path.abspath(os.path.normpath(base))
983
+
984
+ def prefix_to_dir(self, prefix, use_abspath=True):
985
+ """
986
+ Converts a resource prefix to a directory name in the cache.
987
+ """
988
+ return path_to_cache_dir(prefix, use_abspath=use_abspath)
989
+
990
+ def clear(self):
991
+ """
992
+ Clear the cache.
993
+ """
994
+ not_removed = []
995
+ for fn in os.listdir(self.base):
996
+ fn = os.path.join(self.base, fn)
997
+ try:
998
+ if os.path.islink(fn) or os.path.isfile(fn):
999
+ os.remove(fn)
1000
+ elif os.path.isdir(fn):
1001
+ shutil.rmtree(fn)
1002
+ except Exception:
1003
+ not_removed.append(fn)
1004
+ return not_removed
1005
+
1006
+
1007
+ class EventMixin(object):
1008
+ """
1009
+ A very simple publish/subscribe system.
1010
+ """
1011
+
1012
+ def __init__(self):
1013
+ self._subscribers = {}
1014
+
1015
+ def add(self, event, subscriber, append=True):
1016
+ """
1017
+ Add a subscriber for an event.
1018
+
1019
+ :param event: The name of an event.
1020
+ :param subscriber: The subscriber to be added (and called when the
1021
+ event is published).
1022
+ :param append: Whether to append or prepend the subscriber to an
1023
+ existing subscriber list for the event.
1024
+ """
1025
+ subs = self._subscribers
1026
+ if event not in subs:
1027
+ subs[event] = deque([subscriber])
1028
+ else:
1029
+ sq = subs[event]
1030
+ if append:
1031
+ sq.append(subscriber)
1032
+ else:
1033
+ sq.appendleft(subscriber)
1034
+
1035
+ def remove(self, event, subscriber):
1036
+ """
1037
+ Remove a subscriber for an event.
1038
+
1039
+ :param event: The name of an event.
1040
+ :param subscriber: The subscriber to be removed.
1041
+ """
1042
+ subs = self._subscribers
1043
+ if event not in subs:
1044
+ raise ValueError('No subscribers: %r' % event)
1045
+ subs[event].remove(subscriber)
1046
+
1047
+ def get_subscribers(self, event):
1048
+ """
1049
+ Return an iterator for the subscribers for an event.
1050
+ :param event: The event to return subscribers for.
1051
+ """
1052
+ return iter(self._subscribers.get(event, ()))
1053
+
1054
+ def publish(self, event, *args, **kwargs):
1055
+ """
1056
+ Publish a event and return a list of values returned by its
1057
+ subscribers.
1058
+
1059
+ :param event: The event to publish.
1060
+ :param args: The positional arguments to pass to the event's
1061
+ subscribers.
1062
+ :param kwargs: The keyword arguments to pass to the event's
1063
+ subscribers.
1064
+ """
1065
+ result = []
1066
+ for subscriber in self.get_subscribers(event):
1067
+ try:
1068
+ value = subscriber(event, *args, **kwargs)
1069
+ except Exception:
1070
+ logger.exception('Exception during event publication')
1071
+ value = None
1072
+ result.append(value)
1073
+ logger.debug('publish %s: args = %s, kwargs = %s, result = %s', event, args, kwargs, result)
1074
+ return result
1075
+
1076
+
1077
+ #
1078
+ # Simple sequencing
1079
+ #
1080
+ class Sequencer(object):
1081
+
1082
+ def __init__(self):
1083
+ self._preds = {}
1084
+ self._succs = {}
1085
+ self._nodes = set() # nodes with no preds/succs
1086
+
1087
+ def add_node(self, node):
1088
+ self._nodes.add(node)
1089
+
1090
+ def remove_node(self, node, edges=False):
1091
+ if node in self._nodes:
1092
+ self._nodes.remove(node)
1093
+ if edges:
1094
+ for p in set(self._preds.get(node, ())):
1095
+ self.remove(p, node)
1096
+ for s in set(self._succs.get(node, ())):
1097
+ self.remove(node, s)
1098
+ # Remove empties
1099
+ for k, v in list(self._preds.items()):
1100
+ if not v:
1101
+ del self._preds[k]
1102
+ for k, v in list(self._succs.items()):
1103
+ if not v:
1104
+ del self._succs[k]
1105
+
1106
+ def add(self, pred, succ):
1107
+ assert pred != succ
1108
+ self._preds.setdefault(succ, set()).add(pred)
1109
+ self._succs.setdefault(pred, set()).add(succ)
1110
+
1111
+ def remove(self, pred, succ):
1112
+ assert pred != succ
1113
+ try:
1114
+ preds = self._preds[succ]
1115
+ succs = self._succs[pred]
1116
+ except KeyError: # pragma: no cover
1117
+ raise ValueError('%r not a successor of anything' % succ)
1118
+ try:
1119
+ preds.remove(pred)
1120
+ succs.remove(succ)
1121
+ except KeyError: # pragma: no cover
1122
+ raise ValueError('%r not a successor of %r' % (succ, pred))
1123
+
1124
+ def is_step(self, step):
1125
+ return (step in self._preds or step in self._succs or step in self._nodes)
1126
+
1127
+ def get_steps(self, final):
1128
+ if not self.is_step(final):
1129
+ raise ValueError('Unknown: %r' % final)
1130
+ result = []
1131
+ todo = []
1132
+ seen = set()
1133
+ todo.append(final)
1134
+ while todo:
1135
+ step = todo.pop(0)
1136
+ if step in seen:
1137
+ # if a step was already seen,
1138
+ # move it to the end (so it will appear earlier
1139
+ # when reversed on return) ... but not for the
1140
+ # final step, as that would be confusing for
1141
+ # users
1142
+ if step != final:
1143
+ result.remove(step)
1144
+ result.append(step)
1145
+ else:
1146
+ seen.add(step)
1147
+ result.append(step)
1148
+ preds = self._preds.get(step, ())
1149
+ todo.extend(preds)
1150
+ return reversed(result)
1151
+
1152
+ @property
1153
+ def strong_connections(self):
1154
+ # http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
1155
+ index_counter = [0]
1156
+ stack = []
1157
+ lowlinks = {}
1158
+ index = {}
1159
+ result = []
1160
+
1161
+ graph = self._succs
1162
+
1163
+ def strongconnect(node):
1164
+ # set the depth index for this node to the smallest unused index
1165
+ index[node] = index_counter[0]
1166
+ lowlinks[node] = index_counter[0]
1167
+ index_counter[0] += 1
1168
+ stack.append(node)
1169
+
1170
+ # Consider successors
1171
+ try:
1172
+ successors = graph[node]
1173
+ except Exception:
1174
+ successors = []
1175
+ for successor in successors:
1176
+ if successor not in lowlinks:
1177
+ # Successor has not yet been visited
1178
+ strongconnect(successor)
1179
+ lowlinks[node] = min(lowlinks[node], lowlinks[successor])
1180
+ elif successor in stack:
1181
+ # the successor is in the stack and hence in the current
1182
+ # strongly connected component (SCC)
1183
+ lowlinks[node] = min(lowlinks[node], index[successor])
1184
+
1185
+ # If `node` is a root node, pop the stack and generate an SCC
1186
+ if lowlinks[node] == index[node]:
1187
+ connected_component = []
1188
+
1189
+ while True:
1190
+ successor = stack.pop()
1191
+ connected_component.append(successor)
1192
+ if successor == node:
1193
+ break
1194
+ component = tuple(connected_component)
1195
+ # storing the result
1196
+ result.append(component)
1197
+
1198
+ for node in graph:
1199
+ if node not in lowlinks:
1200
+ strongconnect(node)
1201
+
1202
+ return result
1203
+
1204
+ @property
1205
+ def dot(self):
1206
+ result = ['digraph G {']
1207
+ for succ in self._preds:
1208
+ preds = self._preds[succ]
1209
+ for pred in preds:
1210
+ result.append(' %s -> %s;' % (pred, succ))
1211
+ for node in self._nodes:
1212
+ result.append(' %s;' % node)
1213
+ result.append('}')
1214
+ return '\n'.join(result)
1215
+
1216
+
1217
+ #
1218
+ # Unarchiving functionality for zip, tar, tgz, tbz, whl
1219
+ #
1220
+
1221
+ ARCHIVE_EXTENSIONS = ('.tar.gz', '.tar.bz2', '.tar', '.zip', '.tgz', '.tbz', '.whl')
1222
+
1223
+
1224
+ def unarchive(archive_filename, dest_dir, format=None, check=True):
1225
+
1226
+ def check_path(path):
1227
+ if not isinstance(path, text_type):
1228
+ path = path.decode('utf-8')
1229
+ p = os.path.abspath(os.path.join(dest_dir, path))
1230
+ if not p.startswith(dest_dir) or p[plen] != os.sep:
1231
+ raise ValueError('path outside destination: %r' % p)
1232
+
1233
+ dest_dir = os.path.abspath(dest_dir)
1234
+ plen = len(dest_dir)
1235
+ archive = None
1236
+ if format is None:
1237
+ if archive_filename.endswith(('.zip', '.whl')):
1238
+ format = 'zip'
1239
+ elif archive_filename.endswith(('.tar.gz', '.tgz')):
1240
+ format = 'tgz'
1241
+ mode = 'r:gz'
1242
+ elif archive_filename.endswith(('.tar.bz2', '.tbz')):
1243
+ format = 'tbz'
1244
+ mode = 'r:bz2'
1245
+ elif archive_filename.endswith('.tar'):
1246
+ format = 'tar'
1247
+ mode = 'r'
1248
+ else: # pragma: no cover
1249
+ raise ValueError('Unknown format for %r' % archive_filename)
1250
+ try:
1251
+ if format == 'zip':
1252
+ archive = ZipFile(archive_filename, 'r')
1253
+ if check:
1254
+ names = archive.namelist()
1255
+ for name in names:
1256
+ check_path(name)
1257
+ else:
1258
+ archive = tarfile.open(archive_filename, mode)
1259
+ if check:
1260
+ names = archive.getnames()
1261
+ for name in names:
1262
+ check_path(name)
1263
+ if format != 'zip' and sys.version_info[0] < 3:
1264
+ # See Python issue 17153. If the dest path contains Unicode,
1265
+ # tarfile extraction fails on Python 2.x if a member path name
1266
+ # contains non-ASCII characters - it leads to an implicit
1267
+ # bytes -> unicode conversion using ASCII to decode.
1268
+ for tarinfo in archive.getmembers():
1269
+ if not isinstance(tarinfo.name, text_type):
1270
+ tarinfo.name = tarinfo.name.decode('utf-8')
1271
+
1272
+ # Limit extraction of dangerous items, if this Python
1273
+ # allows it easily. If not, just trust the input.
1274
+ # See: https://docs.python.org/3/library/tarfile.html#extraction-filters
1275
+ def extraction_filter(member, path):
1276
+ """Run tarfile.tar_filter, but raise the expected ValueError"""
1277
+ # This is only called if the current Python has tarfile filters
1278
+ try:
1279
+ return tarfile.tar_filter(member, path)
1280
+ except tarfile.FilterError as exc:
1281
+ raise ValueError(str(exc))
1282
+
1283
+ archive.extraction_filter = extraction_filter
1284
+
1285
+ archive.extractall(dest_dir)
1286
+
1287
+ finally:
1288
+ if archive:
1289
+ archive.close()
1290
+
1291
+
1292
+ def zip_dir(directory):
1293
+ """zip a directory tree into a BytesIO object"""
1294
+ result = io.BytesIO()
1295
+ dlen = len(directory)
1296
+ with ZipFile(result, "w") as zf:
1297
+ for root, dirs, files in os.walk(directory):
1298
+ for name in files:
1299
+ full = os.path.join(root, name)
1300
+ rel = root[dlen:]
1301
+ dest = os.path.join(rel, name)
1302
+ zf.write(full, dest)
1303
+ return result
1304
+
1305
+
1306
+ #
1307
+ # Simple progress bar
1308
+ #
1309
+
1310
+ UNITS = ('', 'K', 'M', 'G', 'T', 'P')
1311
+
1312
+
1313
+ class Progress(object):
1314
+ unknown = 'UNKNOWN'
1315
+
1316
+ def __init__(self, minval=0, maxval=100):
1317
+ assert maxval is None or maxval >= minval
1318
+ self.min = self.cur = minval
1319
+ self.max = maxval
1320
+ self.started = None
1321
+ self.elapsed = 0
1322
+ self.done = False
1323
+
1324
+ def update(self, curval):
1325
+ assert self.min <= curval
1326
+ assert self.max is None or curval <= self.max
1327
+ self.cur = curval
1328
+ now = time.time()
1329
+ if self.started is None:
1330
+ self.started = now
1331
+ else:
1332
+ self.elapsed = now - self.started
1333
+
1334
+ def increment(self, incr):
1335
+ assert incr >= 0
1336
+ self.update(self.cur + incr)
1337
+
1338
+ def start(self):
1339
+ self.update(self.min)
1340
+ return self
1341
+
1342
+ def stop(self):
1343
+ if self.max is not None:
1344
+ self.update(self.max)
1345
+ self.done = True
1346
+
1347
+ @property
1348
+ def maximum(self):
1349
+ return self.unknown if self.max is None else self.max
1350
+
1351
+ @property
1352
+ def percentage(self):
1353
+ if self.done:
1354
+ result = '100 %'
1355
+ elif self.max is None:
1356
+ result = ' ?? %'
1357
+ else:
1358
+ v = 100.0 * (self.cur - self.min) / (self.max - self.min)
1359
+ result = '%3d %%' % v
1360
+ return result
1361
+
1362
+ def format_duration(self, duration):
1363
+ if (duration <= 0) and self.max is None or self.cur == self.min:
1364
+ result = '??:??:??'
1365
+ # elif duration < 1:
1366
+ # result = '--:--:--'
1367
+ else:
1368
+ result = time.strftime('%H:%M:%S', time.gmtime(duration))
1369
+ return result
1370
+
1371
+ @property
1372
+ def ETA(self):
1373
+ if self.done:
1374
+ prefix = 'Done'
1375
+ t = self.elapsed
1376
+ # import pdb; pdb.set_trace()
1377
+ else:
1378
+ prefix = 'ETA '
1379
+ if self.max is None:
1380
+ t = -1
1381
+ elif self.elapsed == 0 or (self.cur == self.min):
1382
+ t = 0
1383
+ else:
1384
+ # import pdb; pdb.set_trace()
1385
+ t = float(self.max - self.min)
1386
+ t /= self.cur - self.min
1387
+ t = (t - 1) * self.elapsed
1388
+ return '%s: %s' % (prefix, self.format_duration(t))
1389
+
1390
+ @property
1391
+ def speed(self):
1392
+ if self.elapsed == 0:
1393
+ result = 0.0
1394
+ else:
1395
+ result = (self.cur - self.min) / self.elapsed
1396
+ for unit in UNITS:
1397
+ if result < 1000:
1398
+ break
1399
+ result /= 1000.0
1400
+ return '%d %sB/s' % (result, unit)
1401
+
1402
+
1403
+ #
1404
+ # Glob functionality
1405
+ #
1406
+
1407
+ RICH_GLOB = re.compile(r'\{([^}]*)\}')
1408
+ _CHECK_RECURSIVE_GLOB = re.compile(r'[^/\\,{]\*\*|\*\*[^/\\,}]')
1409
+ _CHECK_MISMATCH_SET = re.compile(r'^[^{]*\}|\{[^}]*$')
1410
+
1411
+
1412
+ def iglob(path_glob):
1413
+ """Extended globbing function that supports ** and {opt1,opt2,opt3}."""
1414
+ if _CHECK_RECURSIVE_GLOB.search(path_glob):
1415
+ msg = """invalid glob %r: recursive glob "**" must be used alone"""
1416
+ raise ValueError(msg % path_glob)
1417
+ if _CHECK_MISMATCH_SET.search(path_glob):
1418
+ msg = """invalid glob %r: mismatching set marker '{' or '}'"""
1419
+ raise ValueError(msg % path_glob)
1420
+ return _iglob(path_glob)
1421
+
1422
+
1423
+ def _iglob(path_glob):
1424
+ rich_path_glob = RICH_GLOB.split(path_glob, 1)
1425
+ if len(rich_path_glob) > 1:
1426
+ assert len(rich_path_glob) == 3, rich_path_glob
1427
+ prefix, set, suffix = rich_path_glob
1428
+ for item in set.split(','):
1429
+ for path in _iglob(''.join((prefix, item, suffix))):
1430
+ yield path
1431
+ else:
1432
+ if '**' not in path_glob:
1433
+ for item in std_iglob(path_glob):
1434
+ yield item
1435
+ else:
1436
+ prefix, radical = path_glob.split('**', 1)
1437
+ if prefix == '':
1438
+ prefix = '.'
1439
+ if radical == '':
1440
+ radical = '*'
1441
+ else:
1442
+ # we support both
1443
+ radical = radical.lstrip('/')
1444
+ radical = radical.lstrip('\\')
1445
+ for path, dir, files in os.walk(prefix):
1446
+ path = os.path.normpath(path)
1447
+ for fn in _iglob(os.path.join(path, radical)):
1448
+ yield fn
1449
+
1450
+
1451
+ if ssl:
1452
+ from .compat import (HTTPSHandler as BaseHTTPSHandler, match_hostname, CertificateError)
1453
+
1454
+ #
1455
+ # HTTPSConnection which verifies certificates/matches domains
1456
+ #
1457
+
1458
+ class HTTPSConnection(httplib.HTTPSConnection):
1459
+ ca_certs = None # set this to the path to the certs file (.pem)
1460
+ check_domain = True # only used if ca_certs is not None
1461
+
1462
+ # noinspection PyPropertyAccess
1463
+ def connect(self):
1464
+ sock = socket.create_connection((self.host, self.port), self.timeout)
1465
+ if getattr(self, '_tunnel_host', False):
1466
+ self.sock = sock
1467
+ self._tunnel()
1468
+
1469
+ context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
1470
+ if hasattr(ssl, 'OP_NO_SSLv2'):
1471
+ context.options |= ssl.OP_NO_SSLv2
1472
+ if getattr(self, 'cert_file', None):
1473
+ context.load_cert_chain(self.cert_file, self.key_file)
1474
+ kwargs = {}
1475
+ if self.ca_certs:
1476
+ context.verify_mode = ssl.CERT_REQUIRED
1477
+ context.load_verify_locations(cafile=self.ca_certs)
1478
+ if getattr(ssl, 'HAS_SNI', False):
1479
+ kwargs['server_hostname'] = self.host
1480
+
1481
+ self.sock = context.wrap_socket(sock, **kwargs)
1482
+ if self.ca_certs and self.check_domain:
1483
+ try:
1484
+ match_hostname(self.sock.getpeercert(), self.host)
1485
+ logger.debug('Host verified: %s', self.host)
1486
+ except CertificateError: # pragma: no cover
1487
+ self.sock.shutdown(socket.SHUT_RDWR)
1488
+ self.sock.close()
1489
+ raise
1490
+
1491
+ class HTTPSHandler(BaseHTTPSHandler):
1492
+
1493
+ def __init__(self, ca_certs, check_domain=True):
1494
+ BaseHTTPSHandler.__init__(self)
1495
+ self.ca_certs = ca_certs
1496
+ self.check_domain = check_domain
1497
+
1498
+ def _conn_maker(self, *args, **kwargs):
1499
+ """
1500
+ This is called to create a connection instance. Normally you'd
1501
+ pass a connection class to do_open, but it doesn't actually check for
1502
+ a class, and just expects a callable. As long as we behave just as a
1503
+ constructor would have, we should be OK. If it ever changes so that
1504
+ we *must* pass a class, we'll create an UnsafeHTTPSConnection class
1505
+ which just sets check_domain to False in the class definition, and
1506
+ choose which one to pass to do_open.
1507
+ """
1508
+ result = HTTPSConnection(*args, **kwargs)
1509
+ if self.ca_certs:
1510
+ result.ca_certs = self.ca_certs
1511
+ result.check_domain = self.check_domain
1512
+ return result
1513
+
1514
+ def https_open(self, req):
1515
+ try:
1516
+ return self.do_open(self._conn_maker, req)
1517
+ except URLError as e:
1518
+ if 'certificate verify failed' in str(e.reason):
1519
+ raise CertificateError('Unable to verify server certificate '
1520
+ 'for %s' % req.host)
1521
+ else:
1522
+ raise
1523
+
1524
+ #
1525
+ # To prevent against mixing HTTP traffic with HTTPS (examples: A Man-In-The-
1526
+ # Middle proxy using HTTP listens on port 443, or an index mistakenly serves
1527
+ # HTML containing a http://xyz link when it should be https://xyz),
1528
+ # you can use the following handler class, which does not allow HTTP traffic.
1529
+ #
1530
+ # It works by inheriting from HTTPHandler - so build_opener won't add a
1531
+ # handler for HTTP itself.
1532
+ #
1533
+ class HTTPSOnlyHandler(HTTPSHandler, HTTPHandler):
1534
+
1535
+ def http_open(self, req):
1536
+ raise URLError('Unexpected HTTP request on what should be a secure '
1537
+ 'connection: %s' % req)
1538
+
1539
+
1540
+ #
1541
+ # XML-RPC with timeouts
1542
+ #
1543
+ class Transport(xmlrpclib.Transport):
1544
+
1545
+ def __init__(self, timeout, use_datetime=0):
1546
+ self.timeout = timeout
1547
+ xmlrpclib.Transport.__init__(self, use_datetime)
1548
+
1549
+ def make_connection(self, host):
1550
+ h, eh, x509 = self.get_host_info(host)
1551
+ if not self._connection or host != self._connection[0]:
1552
+ self._extra_headers = eh
1553
+ self._connection = host, httplib.HTTPConnection(h)
1554
+ return self._connection[1]
1555
+
1556
+
1557
+ if ssl:
1558
+
1559
+ class SafeTransport(xmlrpclib.SafeTransport):
1560
+
1561
+ def __init__(self, timeout, use_datetime=0):
1562
+ self.timeout = timeout
1563
+ xmlrpclib.SafeTransport.__init__(self, use_datetime)
1564
+
1565
+ def make_connection(self, host):
1566
+ h, eh, kwargs = self.get_host_info(host)
1567
+ if not kwargs:
1568
+ kwargs = {}
1569
+ kwargs['timeout'] = self.timeout
1570
+ if not self._connection or host != self._connection[0]:
1571
+ self._extra_headers = eh
1572
+ self._connection = host, httplib.HTTPSConnection(h, None, **kwargs)
1573
+ return self._connection[1]
1574
+
1575
+
1576
+ class ServerProxy(xmlrpclib.ServerProxy):
1577
+
1578
+ def __init__(self, uri, **kwargs):
1579
+ self.timeout = timeout = kwargs.pop('timeout', None)
1580
+ # The above classes only come into play if a timeout
1581
+ # is specified
1582
+ if timeout is not None:
1583
+ # scheme = splittype(uri) # deprecated as of Python 3.8
1584
+ scheme = urlparse(uri)[0]
1585
+ use_datetime = kwargs.get('use_datetime', 0)
1586
+ if scheme == 'https':
1587
+ tcls = SafeTransport
1588
+ else:
1589
+ tcls = Transport
1590
+ kwargs['transport'] = t = tcls(timeout, use_datetime=use_datetime)
1591
+ self.transport = t
1592
+ xmlrpclib.ServerProxy.__init__(self, uri, **kwargs)
1593
+
1594
+
1595
+ #
1596
+ # CSV functionality. This is provided because on 2.x, the csv module can't
1597
+ # handle Unicode. However, we need to deal with Unicode in e.g. RECORD files.
1598
+ #
1599
+
1600
+
1601
+ def _csv_open(fn, mode, **kwargs):
1602
+ if sys.version_info[0] < 3:
1603
+ mode += 'b'
1604
+ else:
1605
+ kwargs['newline'] = ''
1606
+ # Python 3 determines encoding from locale. Force 'utf-8'
1607
+ # file encoding to match other forced utf-8 encoding
1608
+ kwargs['encoding'] = 'utf-8'
1609
+ return open(fn, mode, **kwargs)
1610
+
1611
+
1612
+ class CSVBase(object):
1613
+ defaults = {
1614
+ 'delimiter': str(','), # The strs are used because we need native
1615
+ 'quotechar': str('"'), # str in the csv API (2.x won't take
1616
+ 'lineterminator': str('\n') # Unicode)
1617
+ }
1618
+
1619
+ def __enter__(self):
1620
+ return self
1621
+
1622
+ def __exit__(self, *exc_info):
1623
+ self.stream.close()
1624
+
1625
+
1626
+ class CSVReader(CSVBase):
1627
+
1628
+ def __init__(self, **kwargs):
1629
+ if 'stream' in kwargs:
1630
+ stream = kwargs['stream']
1631
+ if sys.version_info[0] >= 3:
1632
+ # needs to be a text stream
1633
+ stream = codecs.getreader('utf-8')(stream)
1634
+ self.stream = stream
1635
+ else:
1636
+ self.stream = _csv_open(kwargs['path'], 'r')
1637
+ self.reader = csv.reader(self.stream, **self.defaults)
1638
+
1639
+ def __iter__(self):
1640
+ return self
1641
+
1642
+ def next(self):
1643
+ result = next(self.reader)
1644
+ if sys.version_info[0] < 3:
1645
+ for i, item in enumerate(result):
1646
+ if not isinstance(item, text_type):
1647
+ result[i] = item.decode('utf-8')
1648
+ return result
1649
+
1650
+ __next__ = next
1651
+
1652
+
1653
+ class CSVWriter(CSVBase):
1654
+
1655
+ def __init__(self, fn, **kwargs):
1656
+ self.stream = _csv_open(fn, 'w')
1657
+ self.writer = csv.writer(self.stream, **self.defaults)
1658
+
1659
+ def writerow(self, row):
1660
+ if sys.version_info[0] < 3:
1661
+ r = []
1662
+ for item in row:
1663
+ if isinstance(item, text_type):
1664
+ item = item.encode('utf-8')
1665
+ r.append(item)
1666
+ row = r
1667
+ self.writer.writerow(row)
1668
+
1669
+
1670
+ #
1671
+ # Configurator functionality
1672
+ #
1673
+
1674
+
1675
+ class Configurator(BaseConfigurator):
1676
+
1677
+ value_converters = dict(BaseConfigurator.value_converters)
1678
+ value_converters['inc'] = 'inc_convert'
1679
+
1680
+ def __init__(self, config, base=None):
1681
+ super(Configurator, self).__init__(config)
1682
+ self.base = base or os.getcwd()
1683
+
1684
+ def configure_custom(self, config):
1685
+
1686
+ def convert(o):
1687
+ if isinstance(o, (list, tuple)):
1688
+ result = type(o)([convert(i) for i in o])
1689
+ elif isinstance(o, dict):
1690
+ if '()' in o:
1691
+ result = self.configure_custom(o)
1692
+ else:
1693
+ result = {}
1694
+ for k in o:
1695
+ result[k] = convert(o[k])
1696
+ else:
1697
+ result = self.convert(o)
1698
+ return result
1699
+
1700
+ c = config.pop('()')
1701
+ if not callable(c):
1702
+ c = self.resolve(c)
1703
+ props = config.pop('.', None)
1704
+ # Check for valid identifiers
1705
+ args = config.pop('[]', ())
1706
+ if args:
1707
+ args = tuple([convert(o) for o in args])
1708
+ items = [(k, convert(config[k])) for k in config if valid_ident(k)]
1709
+ kwargs = dict(items)
1710
+ result = c(*args, **kwargs)
1711
+ if props:
1712
+ for n, v in props.items():
1713
+ setattr(result, n, convert(v))
1714
+ return result
1715
+
1716
+ def __getitem__(self, key):
1717
+ result = self.config[key]
1718
+ if isinstance(result, dict) and '()' in result:
1719
+ self.config[key] = result = self.configure_custom(result)
1720
+ return result
1721
+
1722
+ def inc_convert(self, value):
1723
+ """Default converter for the inc:// protocol."""
1724
+ if not os.path.isabs(value):
1725
+ value = os.path.join(self.base, value)
1726
+ with codecs.open(value, 'r', encoding='utf-8') as f:
1727
+ result = json.load(f)
1728
+ return result
1729
+
1730
+
1731
+ class SubprocessMixin(object):
1732
+ """
1733
+ Mixin for running subprocesses and capturing their output
1734
+ """
1735
+
1736
+ def __init__(self, verbose=False, progress=None):
1737
+ self.verbose = verbose
1738
+ self.progress = progress
1739
+
1740
+ def reader(self, stream, context):
1741
+ """
1742
+ Read lines from a subprocess' output stream and either pass to a progress
1743
+ callable (if specified) or write progress information to sys.stderr.
1744
+ """
1745
+ progress = self.progress
1746
+ verbose = self.verbose
1747
+ while True:
1748
+ s = stream.readline()
1749
+ if not s:
1750
+ break
1751
+ if progress is not None:
1752
+ progress(s, context)
1753
+ else:
1754
+ if not verbose:
1755
+ sys.stderr.write('.')
1756
+ else:
1757
+ sys.stderr.write(s.decode('utf-8'))
1758
+ sys.stderr.flush()
1759
+ stream.close()
1760
+
1761
+ def run_command(self, cmd, **kwargs):
1762
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs)
1763
+ t1 = threading.Thread(target=self.reader, args=(p.stdout, 'stdout'))
1764
+ t1.start()
1765
+ t2 = threading.Thread(target=self.reader, args=(p.stderr, 'stderr'))
1766
+ t2.start()
1767
+ p.wait()
1768
+ t1.join()
1769
+ t2.join()
1770
+ if self.progress is not None:
1771
+ self.progress('done.', 'main')
1772
+ elif self.verbose:
1773
+ sys.stderr.write('done.\n')
1774
+ return p
1775
+
1776
+
1777
+ def normalize_name(name):
1778
+ """Normalize a python package name a la PEP 503"""
1779
+ # https://www.python.org/dev/peps/pep-0503/#normalized-names
1780
+ return re.sub('[-_.]+', '-', name).lower()
1781
+
1782
+
1783
+ # def _get_pypirc_command():
1784
+ # """
1785
+ # Get the distutils command for interacting with PyPI configurations.
1786
+ # :return: the command.
1787
+ # """
1788
+ # from distutils.core import Distribution
1789
+ # from distutils.config import PyPIRCCommand
1790
+ # d = Distribution()
1791
+ # return PyPIRCCommand(d)
1792
+
1793
+
1794
+ class PyPIRCFile(object):
1795
+
1796
+ DEFAULT_REPOSITORY = 'https://upload.pypi.org/legacy/'
1797
+ DEFAULT_REALM = 'pypi'
1798
+
1799
+ def __init__(self, fn=None, url=None):
1800
+ if fn is None:
1801
+ fn = os.path.join(os.path.expanduser('~'), '.pypirc')
1802
+ self.filename = fn
1803
+ self.url = url
1804
+
1805
+ def read(self):
1806
+ result = {}
1807
+
1808
+ if os.path.exists(self.filename):
1809
+ repository = self.url or self.DEFAULT_REPOSITORY
1810
+
1811
+ config = configparser.RawConfigParser()
1812
+ config.read(self.filename)
1813
+ sections = config.sections()
1814
+ if 'distutils' in sections:
1815
+ # let's get the list of servers
1816
+ index_servers = config.get('distutils', 'index-servers')
1817
+ _servers = [server.strip() for server in index_servers.split('\n') if server.strip() != '']
1818
+ if _servers == []:
1819
+ # nothing set, let's try to get the default pypi
1820
+ if 'pypi' in sections:
1821
+ _servers = ['pypi']
1822
+ else:
1823
+ for server in _servers:
1824
+ result = {'server': server}
1825
+ result['username'] = config.get(server, 'username')
1826
+
1827
+ # optional params
1828
+ for key, default in (('repository', self.DEFAULT_REPOSITORY), ('realm', self.DEFAULT_REALM),
1829
+ ('password', None)):
1830
+ if config.has_option(server, key):
1831
+ result[key] = config.get(server, key)
1832
+ else:
1833
+ result[key] = default
1834
+
1835
+ # work around people having "repository" for the "pypi"
1836
+ # section of their config set to the HTTP (rather than
1837
+ # HTTPS) URL
1838
+ if (server == 'pypi' and repository in (self.DEFAULT_REPOSITORY, 'pypi')):
1839
+ result['repository'] = self.DEFAULT_REPOSITORY
1840
+ elif (result['server'] != repository and result['repository'] != repository):
1841
+ result = {}
1842
+ elif 'server-login' in sections:
1843
+ # old format
1844
+ server = 'server-login'
1845
+ if config.has_option(server, 'repository'):
1846
+ repository = config.get(server, 'repository')
1847
+ else:
1848
+ repository = self.DEFAULT_REPOSITORY
1849
+ result = {
1850
+ 'username': config.get(server, 'username'),
1851
+ 'password': config.get(server, 'password'),
1852
+ 'repository': repository,
1853
+ 'server': server,
1854
+ 'realm': self.DEFAULT_REALM
1855
+ }
1856
+ return result
1857
+
1858
+ def update(self, username, password):
1859
+ # import pdb; pdb.set_trace()
1860
+ config = configparser.RawConfigParser()
1861
+ fn = self.filename
1862
+ config.read(fn)
1863
+ if not config.has_section('pypi'):
1864
+ config.add_section('pypi')
1865
+ config.set('pypi', 'username', username)
1866
+ config.set('pypi', 'password', password)
1867
+ with open(fn, 'w') as f:
1868
+ config.write(f)
1869
+
1870
+
1871
+ def _load_pypirc(index):
1872
+ """
1873
+ Read the PyPI access configuration as supported by distutils.
1874
+ """
1875
+ return PyPIRCFile(url=index.url).read()
1876
+
1877
+
1878
+ def _store_pypirc(index):
1879
+ PyPIRCFile().update(index.username, index.password)
1880
+
1881
+
1882
+ #
1883
+ # get_platform()/get_host_platform() copied from Python 3.10.a0 source, with some minor
1884
+ # tweaks
1885
+ #
1886
+
1887
+
1888
+ def get_host_platform():
1889
+ """Return a string that identifies the current platform. This is used mainly to
1890
+ distinguish platform-specific build directories and platform-specific built
1891
+ distributions. Typically includes the OS name and version and the
1892
+ architecture (as supplied by 'os.uname()'), although the exact information
1893
+ included depends on the OS; eg. on Linux, the kernel version isn't
1894
+ particularly important.
1895
+
1896
+ Examples of returned values:
1897
+ linux-i586
1898
+ linux-alpha (?)
1899
+ solaris-2.6-sun4u
1900
+
1901
+ Windows will return one of:
1902
+ win-amd64 (64bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc)
1903
+ win32 (all others - specifically, sys.platform is returned)
1904
+
1905
+ For other non-POSIX platforms, currently just returns 'sys.platform'.
1906
+
1907
+ """
1908
+ if os.name == 'nt':
1909
+ if 'amd64' in sys.version.lower():
1910
+ return 'win-amd64'
1911
+ if '(arm)' in sys.version.lower():
1912
+ return 'win-arm32'
1913
+ if '(arm64)' in sys.version.lower():
1914
+ return 'win-arm64'
1915
+ return sys.platform
1916
+
1917
+ # Set for cross builds explicitly
1918
+ if "_PYTHON_HOST_PLATFORM" in os.environ:
1919
+ return os.environ["_PYTHON_HOST_PLATFORM"]
1920
+
1921
+ if os.name != 'posix' or not hasattr(os, 'uname'):
1922
+ # XXX what about the architecture? NT is Intel or Alpha,
1923
+ # Mac OS is M68k or PPC, etc.
1924
+ return sys.platform
1925
+
1926
+ # Try to distinguish various flavours of Unix
1927
+
1928
+ (osname, host, release, version, machine) = os.uname()
1929
+
1930
+ # Convert the OS name to lowercase, remove '/' characters, and translate
1931
+ # spaces (for "Power Macintosh")
1932
+ osname = osname.lower().replace('/', '')
1933
+ machine = machine.replace(' ', '_').replace('/', '-')
1934
+
1935
+ if osname[:5] == 'linux':
1936
+ # At least on Linux/Intel, 'machine' is the processor --
1937
+ # i386, etc.
1938
+ # XXX what about Alpha, SPARC, etc?
1939
+ return "%s-%s" % (osname, machine)
1940
+
1941
+ elif osname[:5] == 'sunos':
1942
+ if release[0] >= '5': # SunOS 5 == Solaris 2
1943
+ osname = 'solaris'
1944
+ release = '%d.%s' % (int(release[0]) - 3, release[2:])
1945
+ # We can't use 'platform.architecture()[0]' because a
1946
+ # bootstrap problem. We use a dict to get an error
1947
+ # if some suspicious happens.
1948
+ bitness = {2147483647: '32bit', 9223372036854775807: '64bit'}
1949
+ machine += '.%s' % bitness[sys.maxsize]
1950
+ # fall through to standard osname-release-machine representation
1951
+ elif osname[:3] == 'aix':
1952
+ from _aix_support import aix_platform
1953
+ return aix_platform()
1954
+ elif osname[:6] == 'cygwin':
1955
+ osname = 'cygwin'
1956
+ rel_re = re.compile(r'[\d.]+', re.ASCII)
1957
+ m = rel_re.match(release)
1958
+ if m:
1959
+ release = m.group()
1960
+ elif osname[:6] == 'darwin':
1961
+ import _osx_support
1962
+ try:
1963
+ from distutils import sysconfig
1964
+ except ImportError:
1965
+ import sysconfig
1966
+ osname, release, machine = _osx_support.get_platform_osx(sysconfig.get_config_vars(), osname, release, machine)
1967
+
1968
+ return '%s-%s-%s' % (osname, release, machine)
1969
+
1970
+
1971
+ _TARGET_TO_PLAT = {
1972
+ 'x86': 'win32',
1973
+ 'x64': 'win-amd64',
1974
+ 'arm': 'win-arm32',
1975
+ }
1976
+
1977
+
1978
+ def get_platform():
1979
+ if os.name != 'nt':
1980
+ return get_host_platform()
1981
+ cross_compilation_target = os.environ.get('VSCMD_ARG_TGT_ARCH')
1982
+ if cross_compilation_target not in _TARGET_TO_PLAT:
1983
+ return get_host_platform()
1984
+ return _TARGET_TO_PLAT[cross_compilation_target]
lib/python3.12/site-packages/pip/_vendor/distro/__init__.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .distro import (
2
+ NORMALIZED_DISTRO_ID,
3
+ NORMALIZED_LSB_ID,
4
+ NORMALIZED_OS_ID,
5
+ LinuxDistribution,
6
+ __version__,
7
+ build_number,
8
+ codename,
9
+ distro_release_attr,
10
+ distro_release_info,
11
+ id,
12
+ info,
13
+ like,
14
+ linux_distribution,
15
+ lsb_release_attr,
16
+ lsb_release_info,
17
+ major_version,
18
+ minor_version,
19
+ name,
20
+ os_release_attr,
21
+ os_release_info,
22
+ uname_attr,
23
+ uname_info,
24
+ version,
25
+ version_parts,
26
+ )
27
+
28
+ __all__ = [
29
+ "NORMALIZED_DISTRO_ID",
30
+ "NORMALIZED_LSB_ID",
31
+ "NORMALIZED_OS_ID",
32
+ "LinuxDistribution",
33
+ "build_number",
34
+ "codename",
35
+ "distro_release_attr",
36
+ "distro_release_info",
37
+ "id",
38
+ "info",
39
+ "like",
40
+ "linux_distribution",
41
+ "lsb_release_attr",
42
+ "lsb_release_info",
43
+ "major_version",
44
+ "minor_version",
45
+ "name",
46
+ "os_release_attr",
47
+ "os_release_info",
48
+ "uname_attr",
49
+ "uname_info",
50
+ "version",
51
+ "version_parts",
52
+ ]
53
+
54
+ __version__ = __version__
lib/python3.12/site-packages/pip/_vendor/distro/__main__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ from .distro import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
lib/python3.12/site-packages/pip/_vendor/distro/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (967 Bytes). View file
 
lib/python3.12/site-packages/pip/_vendor/distro/__pycache__/__main__.cpython-312.pyc ADDED
Binary file (299 Bytes). View file
 
lib/python3.12/site-packages/pip/_vendor/distro/__pycache__/distro.cpython-312.pyc ADDED
Binary file (53.8 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/distro/distro.py ADDED
@@ -0,0 +1,1403 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ # Copyright 2015-2021 Nir Cohen
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ """
17
+ The ``distro`` package (``distro`` stands for Linux Distribution) provides
18
+ information about the Linux distribution it runs on, such as a reliable
19
+ machine-readable distro ID, or version information.
20
+
21
+ It is the recommended replacement for Python's original
22
+ :py:func:`platform.linux_distribution` function, but it provides much more
23
+ functionality. An alternative implementation became necessary because Python
24
+ 3.5 deprecated this function, and Python 3.8 removed it altogether. Its
25
+ predecessor function :py:func:`platform.dist` was already deprecated since
26
+ Python 2.6 and removed in Python 3.8. Still, there are many cases in which
27
+ access to OS distribution information is needed. See `Python issue 1322
28
+ <https://bugs.python.org/issue1322>`_ for more information.
29
+ """
30
+
31
+ import argparse
32
+ import json
33
+ import logging
34
+ import os
35
+ import re
36
+ import shlex
37
+ import subprocess
38
+ import sys
39
+ import warnings
40
+ from typing import (
41
+ Any,
42
+ Callable,
43
+ Dict,
44
+ Iterable,
45
+ Optional,
46
+ Sequence,
47
+ TextIO,
48
+ Tuple,
49
+ Type,
50
+ )
51
+
52
+ try:
53
+ from typing import TypedDict
54
+ except ImportError:
55
+ # Python 3.7
56
+ TypedDict = dict
57
+
58
+ __version__ = "1.9.0"
59
+
60
+
61
+ class VersionDict(TypedDict):
62
+ major: str
63
+ minor: str
64
+ build_number: str
65
+
66
+
67
+ class InfoDict(TypedDict):
68
+ id: str
69
+ version: str
70
+ version_parts: VersionDict
71
+ like: str
72
+ codename: str
73
+
74
+
75
+ _UNIXCONFDIR = os.environ.get("UNIXCONFDIR", "/etc")
76
+ _UNIXUSRLIBDIR = os.environ.get("UNIXUSRLIBDIR", "/usr/lib")
77
+ _OS_RELEASE_BASENAME = "os-release"
78
+
79
+ #: Translation table for normalizing the "ID" attribute defined in os-release
80
+ #: files, for use by the :func:`distro.id` method.
81
+ #:
82
+ #: * Key: Value as defined in the os-release file, translated to lower case,
83
+ #: with blanks translated to underscores.
84
+ #:
85
+ #: * Value: Normalized value.
86
+ NORMALIZED_OS_ID = {
87
+ "ol": "oracle", # Oracle Linux
88
+ "opensuse-leap": "opensuse", # Newer versions of OpenSuSE report as opensuse-leap
89
+ }
90
+
91
+ #: Translation table for normalizing the "Distributor ID" attribute returned by
92
+ #: the lsb_release command, for use by the :func:`distro.id` method.
93
+ #:
94
+ #: * Key: Value as returned by the lsb_release command, translated to lower
95
+ #: case, with blanks translated to underscores.
96
+ #:
97
+ #: * Value: Normalized value.
98
+ NORMALIZED_LSB_ID = {
99
+ "enterpriseenterpriseas": "oracle", # Oracle Enterprise Linux 4
100
+ "enterpriseenterpriseserver": "oracle", # Oracle Linux 5
101
+ "redhatenterpriseworkstation": "rhel", # RHEL 6, 7 Workstation
102
+ "redhatenterpriseserver": "rhel", # RHEL 6, 7 Server
103
+ "redhatenterprisecomputenode": "rhel", # RHEL 6 ComputeNode
104
+ }
105
+
106
+ #: Translation table for normalizing the distro ID derived from the file name
107
+ #: of distro release files, for use by the :func:`distro.id` method.
108
+ #:
109
+ #: * Key: Value as derived from the file name of a distro release file,
110
+ #: translated to lower case, with blanks translated to underscores.
111
+ #:
112
+ #: * Value: Normalized value.
113
+ NORMALIZED_DISTRO_ID = {
114
+ "redhat": "rhel", # RHEL 6.x, 7.x
115
+ }
116
+
117
+ # Pattern for content of distro release file (reversed)
118
+ _DISTRO_RELEASE_CONTENT_REVERSED_PATTERN = re.compile(
119
+ r"(?:[^)]*\)(.*)\()? *(?:STL )?([\d.+\-a-z]*\d) *(?:esaeler *)?(.+)"
120
+ )
121
+
122
+ # Pattern for base file name of distro release file
123
+ _DISTRO_RELEASE_BASENAME_PATTERN = re.compile(r"(\w+)[-_](release|version)$")
124
+
125
+ # Base file names to be looked up for if _UNIXCONFDIR is not readable.
126
+ _DISTRO_RELEASE_BASENAMES = [
127
+ "SuSE-release",
128
+ "altlinux-release",
129
+ "arch-release",
130
+ "base-release",
131
+ "centos-release",
132
+ "fedora-release",
133
+ "gentoo-release",
134
+ "mageia-release",
135
+ "mandrake-release",
136
+ "mandriva-release",
137
+ "mandrivalinux-release",
138
+ "manjaro-release",
139
+ "oracle-release",
140
+ "redhat-release",
141
+ "rocky-release",
142
+ "sl-release",
143
+ "slackware-version",
144
+ ]
145
+
146
+ # Base file names to be ignored when searching for distro release file
147
+ _DISTRO_RELEASE_IGNORE_BASENAMES = (
148
+ "debian_version",
149
+ "lsb-release",
150
+ "oem-release",
151
+ _OS_RELEASE_BASENAME,
152
+ "system-release",
153
+ "plesk-release",
154
+ "iredmail-release",
155
+ "board-release",
156
+ "ec2_version",
157
+ )
158
+
159
+
160
+ def linux_distribution(full_distribution_name: bool = True) -> Tuple[str, str, str]:
161
+ """
162
+ .. deprecated:: 1.6.0
163
+
164
+ :func:`distro.linux_distribution()` is deprecated. It should only be
165
+ used as a compatibility shim with Python's
166
+ :py:func:`platform.linux_distribution()`. Please use :func:`distro.id`,
167
+ :func:`distro.version` and :func:`distro.name` instead.
168
+
169
+ Return information about the current OS distribution as a tuple
170
+ ``(id_name, version, codename)`` with items as follows:
171
+
172
+ * ``id_name``: If *full_distribution_name* is false, the result of
173
+ :func:`distro.id`. Otherwise, the result of :func:`distro.name`.
174
+
175
+ * ``version``: The result of :func:`distro.version`.
176
+
177
+ * ``codename``: The extra item (usually in parentheses) after the
178
+ os-release version number, or the result of :func:`distro.codename`.
179
+
180
+ The interface of this function is compatible with the original
181
+ :py:func:`platform.linux_distribution` function, supporting a subset of
182
+ its parameters.
183
+
184
+ The data it returns may not exactly be the same, because it uses more data
185
+ sources than the original function, and that may lead to different data if
186
+ the OS distribution is not consistent across multiple data sources it
187
+ provides (there are indeed such distributions ...).
188
+
189
+ Another reason for differences is the fact that the :func:`distro.id`
190
+ method normalizes the distro ID string to a reliable machine-readable value
191
+ for a number of popular OS distributions.
192
+ """
193
+ warnings.warn(
194
+ "distro.linux_distribution() is deprecated. It should only be used as a "
195
+ "compatibility shim with Python's platform.linux_distribution(). Please use "
196
+ "distro.id(), distro.version() and distro.name() instead.",
197
+ DeprecationWarning,
198
+ stacklevel=2,
199
+ )
200
+ return _distro.linux_distribution(full_distribution_name)
201
+
202
+
203
+ def id() -> str:
204
+ """
205
+ Return the distro ID of the current distribution, as a
206
+ machine-readable string.
207
+
208
+ For a number of OS distributions, the returned distro ID value is
209
+ *reliable*, in the sense that it is documented and that it does not change
210
+ across releases of the distribution.
211
+
212
+ This package maintains the following reliable distro ID values:
213
+
214
+ ============== =========================================
215
+ Distro ID Distribution
216
+ ============== =========================================
217
+ "ubuntu" Ubuntu
218
+ "debian" Debian
219
+ "rhel" RedHat Enterprise Linux
220
+ "centos" CentOS
221
+ "fedora" Fedora
222
+ "sles" SUSE Linux Enterprise Server
223
+ "opensuse" openSUSE
224
+ "amzn" Amazon Linux
225
+ "arch" Arch Linux
226
+ "buildroot" Buildroot
227
+ "cloudlinux" CloudLinux OS
228
+ "exherbo" Exherbo Linux
229
+ "gentoo" GenToo Linux
230
+ "ibm_powerkvm" IBM PowerKVM
231
+ "kvmibm" KVM for IBM z Systems
232
+ "linuxmint" Linux Mint
233
+ "mageia" Mageia
234
+ "mandriva" Mandriva Linux
235
+ "parallels" Parallels
236
+ "pidora" Pidora
237
+ "raspbian" Raspbian
238
+ "oracle" Oracle Linux (and Oracle Enterprise Linux)
239
+ "scientific" Scientific Linux
240
+ "slackware" Slackware
241
+ "xenserver" XenServer
242
+ "openbsd" OpenBSD
243
+ "netbsd" NetBSD
244
+ "freebsd" FreeBSD
245
+ "midnightbsd" MidnightBSD
246
+ "rocky" Rocky Linux
247
+ "aix" AIX
248
+ "guix" Guix System
249
+ "altlinux" ALT Linux
250
+ ============== =========================================
251
+
252
+ If you have a need to get distros for reliable IDs added into this set,
253
+ or if you find that the :func:`distro.id` function returns a different
254
+ distro ID for one of the listed distros, please create an issue in the
255
+ `distro issue tracker`_.
256
+
257
+ **Lookup hierarchy and transformations:**
258
+
259
+ First, the ID is obtained from the following sources, in the specified
260
+ order. The first available and non-empty value is used:
261
+
262
+ * the value of the "ID" attribute of the os-release file,
263
+
264
+ * the value of the "Distributor ID" attribute returned by the lsb_release
265
+ command,
266
+
267
+ * the first part of the file name of the distro release file,
268
+
269
+ The so determined ID value then passes the following transformations,
270
+ before it is returned by this method:
271
+
272
+ * it is translated to lower case,
273
+
274
+ * blanks (which should not be there anyway) are translated to underscores,
275
+
276
+ * a normalization of the ID is performed, based upon
277
+ `normalization tables`_. The purpose of this normalization is to ensure
278
+ that the ID is as reliable as possible, even across incompatible changes
279
+ in the OS distributions. A common reason for an incompatible change is
280
+ the addition of an os-release file, or the addition of the lsb_release
281
+ command, with ID values that differ from what was previously determined
282
+ from the distro release file name.
283
+ """
284
+ return _distro.id()
285
+
286
+
287
+ def name(pretty: bool = False) -> str:
288
+ """
289
+ Return the name of the current OS distribution, as a human-readable
290
+ string.
291
+
292
+ If *pretty* is false, the name is returned without version or codename.
293
+ (e.g. "CentOS Linux")
294
+
295
+ If *pretty* is true, the version and codename are appended.
296
+ (e.g. "CentOS Linux 7.1.1503 (Core)")
297
+
298
+ **Lookup hierarchy:**
299
+
300
+ The name is obtained from the following sources, in the specified order.
301
+ The first available and non-empty value is used:
302
+
303
+ * If *pretty* is false:
304
+
305
+ - the value of the "NAME" attribute of the os-release file,
306
+
307
+ - the value of the "Distributor ID" attribute returned by the lsb_release
308
+ command,
309
+
310
+ - the value of the "<name>" field of the distro release file.
311
+
312
+ * If *pretty* is true:
313
+
314
+ - the value of the "PRETTY_NAME" attribute of the os-release file,
315
+
316
+ - the value of the "Description" attribute returned by the lsb_release
317
+ command,
318
+
319
+ - the value of the "<name>" field of the distro release file, appended
320
+ with the value of the pretty version ("<version_id>" and "<codename>"
321
+ fields) of the distro release file, if available.
322
+ """
323
+ return _distro.name(pretty)
324
+
325
+
326
+ def version(pretty: bool = False, best: bool = False) -> str:
327
+ """
328
+ Return the version of the current OS distribution, as a human-readable
329
+ string.
330
+
331
+ If *pretty* is false, the version is returned without codename (e.g.
332
+ "7.0").
333
+
334
+ If *pretty* is true, the codename in parenthesis is appended, if the
335
+ codename is non-empty (e.g. "7.0 (Maipo)").
336
+
337
+ Some distributions provide version numbers with different precisions in
338
+ the different sources of distribution information. Examining the different
339
+ sources in a fixed priority order does not always yield the most precise
340
+ version (e.g. for Debian 8.2, or CentOS 7.1).
341
+
342
+ Some other distributions may not provide this kind of information. In these
343
+ cases, an empty string would be returned. This behavior can be observed
344
+ with rolling releases distributions (e.g. Arch Linux).
345
+
346
+ The *best* parameter can be used to control the approach for the returned
347
+ version:
348
+
349
+ If *best* is false, the first non-empty version number in priority order of
350
+ the examined sources is returned.
351
+
352
+ If *best* is true, the most precise version number out of all examined
353
+ sources is returned.
354
+
355
+ **Lookup hierarchy:**
356
+
357
+ In all cases, the version number is obtained from the following sources.
358
+ If *best* is false, this order represents the priority order:
359
+
360
+ * the value of the "VERSION_ID" attribute of the os-release file,
361
+ * the value of the "Release" attribute returned by the lsb_release
362
+ command,
363
+ * the version number parsed from the "<version_id>" field of the first line
364
+ of the distro release file,
365
+ * the version number parsed from the "PRETTY_NAME" attribute of the
366
+ os-release file, if it follows the format of the distro release files.
367
+ * the version number parsed from the "Description" attribute returned by
368
+ the lsb_release command, if it follows the format of the distro release
369
+ files.
370
+ """
371
+ return _distro.version(pretty, best)
372
+
373
+
374
+ def version_parts(best: bool = False) -> Tuple[str, str, str]:
375
+ """
376
+ Return the version of the current OS distribution as a tuple
377
+ ``(major, minor, build_number)`` with items as follows:
378
+
379
+ * ``major``: The result of :func:`distro.major_version`.
380
+
381
+ * ``minor``: The result of :func:`distro.minor_version`.
382
+
383
+ * ``build_number``: The result of :func:`distro.build_number`.
384
+
385
+ For a description of the *best* parameter, see the :func:`distro.version`
386
+ method.
387
+ """
388
+ return _distro.version_parts(best)
389
+
390
+
391
+ def major_version(best: bool = False) -> str:
392
+ """
393
+ Return the major version of the current OS distribution, as a string,
394
+ if provided.
395
+ Otherwise, the empty string is returned. The major version is the first
396
+ part of the dot-separated version string.
397
+
398
+ For a description of the *best* parameter, see the :func:`distro.version`
399
+ method.
400
+ """
401
+ return _distro.major_version(best)
402
+
403
+
404
+ def minor_version(best: bool = False) -> str:
405
+ """
406
+ Return the minor version of the current OS distribution, as a string,
407
+ if provided.
408
+ Otherwise, the empty string is returned. The minor version is the second
409
+ part of the dot-separated version string.
410
+
411
+ For a description of the *best* parameter, see the :func:`distro.version`
412
+ method.
413
+ """
414
+ return _distro.minor_version(best)
415
+
416
+
417
+ def build_number(best: bool = False) -> str:
418
+ """
419
+ Return the build number of the current OS distribution, as a string,
420
+ if provided.
421
+ Otherwise, the empty string is returned. The build number is the third part
422
+ of the dot-separated version string.
423
+
424
+ For a description of the *best* parameter, see the :func:`distro.version`
425
+ method.
426
+ """
427
+ return _distro.build_number(best)
428
+
429
+
430
+ def like() -> str:
431
+ """
432
+ Return a space-separated list of distro IDs of distributions that are
433
+ closely related to the current OS distribution in regards to packaging
434
+ and programming interfaces, for example distributions the current
435
+ distribution is a derivative from.
436
+
437
+ **Lookup hierarchy:**
438
+
439
+ This information item is only provided by the os-release file.
440
+ For details, see the description of the "ID_LIKE" attribute in the
441
+ `os-release man page
442
+ <http://www.freedesktop.org/software/systemd/man/os-release.html>`_.
443
+ """
444
+ return _distro.like()
445
+
446
+
447
+ def codename() -> str:
448
+ """
449
+ Return the codename for the release of the current OS distribution,
450
+ as a string.
451
+
452
+ If the distribution does not have a codename, an empty string is returned.
453
+
454
+ Note that the returned codename is not always really a codename. For
455
+ example, openSUSE returns "x86_64". This function does not handle such
456
+ cases in any special way and just returns the string it finds, if any.
457
+
458
+ **Lookup hierarchy:**
459
+
460
+ * the codename within the "VERSION" attribute of the os-release file, if
461
+ provided,
462
+
463
+ * the value of the "Codename" attribute returned by the lsb_release
464
+ command,
465
+
466
+ * the value of the "<codename>" field of the distro release file.
467
+ """
468
+ return _distro.codename()
469
+
470
+
471
+ def info(pretty: bool = False, best: bool = False) -> InfoDict:
472
+ """
473
+ Return certain machine-readable information items about the current OS
474
+ distribution in a dictionary, as shown in the following example:
475
+
476
+ .. sourcecode:: python
477
+
478
+ {
479
+ 'id': 'rhel',
480
+ 'version': '7.0',
481
+ 'version_parts': {
482
+ 'major': '7',
483
+ 'minor': '0',
484
+ 'build_number': ''
485
+ },
486
+ 'like': 'fedora',
487
+ 'codename': 'Maipo'
488
+ }
489
+
490
+ The dictionary structure and keys are always the same, regardless of which
491
+ information items are available in the underlying data sources. The values
492
+ for the various keys are as follows:
493
+
494
+ * ``id``: The result of :func:`distro.id`.
495
+
496
+ * ``version``: The result of :func:`distro.version`.
497
+
498
+ * ``version_parts -> major``: The result of :func:`distro.major_version`.
499
+
500
+ * ``version_parts -> minor``: The result of :func:`distro.minor_version`.
501
+
502
+ * ``version_parts -> build_number``: The result of
503
+ :func:`distro.build_number`.
504
+
505
+ * ``like``: The result of :func:`distro.like`.
506
+
507
+ * ``codename``: The result of :func:`distro.codename`.
508
+
509
+ For a description of the *pretty* and *best* parameters, see the
510
+ :func:`distro.version` method.
511
+ """
512
+ return _distro.info(pretty, best)
513
+
514
+
515
+ def os_release_info() -> Dict[str, str]:
516
+ """
517
+ Return a dictionary containing key-value pairs for the information items
518
+ from the os-release file data source of the current OS distribution.
519
+
520
+ See `os-release file`_ for details about these information items.
521
+ """
522
+ return _distro.os_release_info()
523
+
524
+
525
+ def lsb_release_info() -> Dict[str, str]:
526
+ """
527
+ Return a dictionary containing key-value pairs for the information items
528
+ from the lsb_release command data source of the current OS distribution.
529
+
530
+ See `lsb_release command output`_ for details about these information
531
+ items.
532
+ """
533
+ return _distro.lsb_release_info()
534
+
535
+
536
+ def distro_release_info() -> Dict[str, str]:
537
+ """
538
+ Return a dictionary containing key-value pairs for the information items
539
+ from the distro release file data source of the current OS distribution.
540
+
541
+ See `distro release file`_ for details about these information items.
542
+ """
543
+ return _distro.distro_release_info()
544
+
545
+
546
+ def uname_info() -> Dict[str, str]:
547
+ """
548
+ Return a dictionary containing key-value pairs for the information items
549
+ from the distro release file data source of the current OS distribution.
550
+ """
551
+ return _distro.uname_info()
552
+
553
+
554
+ def os_release_attr(attribute: str) -> str:
555
+ """
556
+ Return a single named information item from the os-release file data source
557
+ of the current OS distribution.
558
+
559
+ Parameters:
560
+
561
+ * ``attribute`` (string): Key of the information item.
562
+
563
+ Returns:
564
+
565
+ * (string): Value of the information item, if the item exists.
566
+ The empty string, if the item does not exist.
567
+
568
+ See `os-release file`_ for details about these information items.
569
+ """
570
+ return _distro.os_release_attr(attribute)
571
+
572
+
573
+ def lsb_release_attr(attribute: str) -> str:
574
+ """
575
+ Return a single named information item from the lsb_release command output
576
+ data source of the current OS distribution.
577
+
578
+ Parameters:
579
+
580
+ * ``attribute`` (string): Key of the information item.
581
+
582
+ Returns:
583
+
584
+ * (string): Value of the information item, if the item exists.
585
+ The empty string, if the item does not exist.
586
+
587
+ See `lsb_release command output`_ for details about these information
588
+ items.
589
+ """
590
+ return _distro.lsb_release_attr(attribute)
591
+
592
+
593
+ def distro_release_attr(attribute: str) -> str:
594
+ """
595
+ Return a single named information item from the distro release file
596
+ data source of the current OS distribution.
597
+
598
+ Parameters:
599
+
600
+ * ``attribute`` (string): Key of the information item.
601
+
602
+ Returns:
603
+
604
+ * (string): Value of the information item, if the item exists.
605
+ The empty string, if the item does not exist.
606
+
607
+ See `distro release file`_ for details about these information items.
608
+ """
609
+ return _distro.distro_release_attr(attribute)
610
+
611
+
612
+ def uname_attr(attribute: str) -> str:
613
+ """
614
+ Return a single named information item from the distro release file
615
+ data source of the current OS distribution.
616
+
617
+ Parameters:
618
+
619
+ * ``attribute`` (string): Key of the information item.
620
+
621
+ Returns:
622
+
623
+ * (string): Value of the information item, if the item exists.
624
+ The empty string, if the item does not exist.
625
+ """
626
+ return _distro.uname_attr(attribute)
627
+
628
+
629
+ try:
630
+ from functools import cached_property
631
+ except ImportError:
632
+ # Python < 3.8
633
+ class cached_property: # type: ignore
634
+ """A version of @property which caches the value. On access, it calls the
635
+ underlying function and sets the value in `__dict__` so future accesses
636
+ will not re-call the property.
637
+ """
638
+
639
+ def __init__(self, f: Callable[[Any], Any]) -> None:
640
+ self._fname = f.__name__
641
+ self._f = f
642
+
643
+ def __get__(self, obj: Any, owner: Type[Any]) -> Any:
644
+ assert obj is not None, f"call {self._fname} on an instance"
645
+ ret = obj.__dict__[self._fname] = self._f(obj)
646
+ return ret
647
+
648
+
649
+ class LinuxDistribution:
650
+ """
651
+ Provides information about a OS distribution.
652
+
653
+ This package creates a private module-global instance of this class with
654
+ default initialization arguments, that is used by the
655
+ `consolidated accessor functions`_ and `single source accessor functions`_.
656
+ By using default initialization arguments, that module-global instance
657
+ returns data about the current OS distribution (i.e. the distro this
658
+ package runs on).
659
+
660
+ Normally, it is not necessary to create additional instances of this class.
661
+ However, in situations where control is needed over the exact data sources
662
+ that are used, instances of this class can be created with a specific
663
+ distro release file, or a specific os-release file, or without invoking the
664
+ lsb_release command.
665
+ """
666
+
667
+ def __init__(
668
+ self,
669
+ include_lsb: Optional[bool] = None,
670
+ os_release_file: str = "",
671
+ distro_release_file: str = "",
672
+ include_uname: Optional[bool] = None,
673
+ root_dir: Optional[str] = None,
674
+ include_oslevel: Optional[bool] = None,
675
+ ) -> None:
676
+ """
677
+ The initialization method of this class gathers information from the
678
+ available data sources, and stores that in private instance attributes.
679
+ Subsequent access to the information items uses these private instance
680
+ attributes, so that the data sources are read only once.
681
+
682
+ Parameters:
683
+
684
+ * ``include_lsb`` (bool): Controls whether the
685
+ `lsb_release command output`_ is included as a data source.
686
+
687
+ If the lsb_release command is not available in the program execution
688
+ path, the data source for the lsb_release command will be empty.
689
+
690
+ * ``os_release_file`` (string): The path name of the
691
+ `os-release file`_ that is to be used as a data source.
692
+
693
+ An empty string (the default) will cause the default path name to
694
+ be used (see `os-release file`_ for details).
695
+
696
+ If the specified or defaulted os-release file does not exist, the
697
+ data source for the os-release file will be empty.
698
+
699
+ * ``distro_release_file`` (string): The path name of the
700
+ `distro release file`_ that is to be used as a data source.
701
+
702
+ An empty string (the default) will cause a default search algorithm
703
+ to be used (see `distro release file`_ for details).
704
+
705
+ If the specified distro release file does not exist, or if no default
706
+ distro release file can be found, the data source for the distro
707
+ release file will be empty.
708
+
709
+ * ``include_uname`` (bool): Controls whether uname command output is
710
+ included as a data source. If the uname command is not available in
711
+ the program execution path the data source for the uname command will
712
+ be empty.
713
+
714
+ * ``root_dir`` (string): The absolute path to the root directory to use
715
+ to find distro-related information files. Note that ``include_*``
716
+ parameters must not be enabled in combination with ``root_dir``.
717
+
718
+ * ``include_oslevel`` (bool): Controls whether (AIX) oslevel command
719
+ output is included as a data source. If the oslevel command is not
720
+ available in the program execution path the data source will be
721
+ empty.
722
+
723
+ Public instance attributes:
724
+
725
+ * ``os_release_file`` (string): The path name of the
726
+ `os-release file`_ that is actually used as a data source. The
727
+ empty string if no distro release file is used as a data source.
728
+
729
+ * ``distro_release_file`` (string): The path name of the
730
+ `distro release file`_ that is actually used as a data source. The
731
+ empty string if no distro release file is used as a data source.
732
+
733
+ * ``include_lsb`` (bool): The result of the ``include_lsb`` parameter.
734
+ This controls whether the lsb information will be loaded.
735
+
736
+ * ``include_uname`` (bool): The result of the ``include_uname``
737
+ parameter. This controls whether the uname information will
738
+ be loaded.
739
+
740
+ * ``include_oslevel`` (bool): The result of the ``include_oslevel``
741
+ parameter. This controls whether (AIX) oslevel information will be
742
+ loaded.
743
+
744
+ * ``root_dir`` (string): The result of the ``root_dir`` parameter.
745
+ The absolute path to the root directory to use to find distro-related
746
+ information files.
747
+
748
+ Raises:
749
+
750
+ * :py:exc:`ValueError`: Initialization parameters combination is not
751
+ supported.
752
+
753
+ * :py:exc:`OSError`: Some I/O issue with an os-release file or distro
754
+ release file.
755
+
756
+ * :py:exc:`UnicodeError`: A data source has unexpected characters or
757
+ uses an unexpected encoding.
758
+ """
759
+ self.root_dir = root_dir
760
+ self.etc_dir = os.path.join(root_dir, "etc") if root_dir else _UNIXCONFDIR
761
+ self.usr_lib_dir = (
762
+ os.path.join(root_dir, "usr/lib") if root_dir else _UNIXUSRLIBDIR
763
+ )
764
+
765
+ if os_release_file:
766
+ self.os_release_file = os_release_file
767
+ else:
768
+ etc_dir_os_release_file = os.path.join(self.etc_dir, _OS_RELEASE_BASENAME)
769
+ usr_lib_os_release_file = os.path.join(
770
+ self.usr_lib_dir, _OS_RELEASE_BASENAME
771
+ )
772
+
773
+ # NOTE: The idea is to respect order **and** have it set
774
+ # at all times for API backwards compatibility.
775
+ if os.path.isfile(etc_dir_os_release_file) or not os.path.isfile(
776
+ usr_lib_os_release_file
777
+ ):
778
+ self.os_release_file = etc_dir_os_release_file
779
+ else:
780
+ self.os_release_file = usr_lib_os_release_file
781
+
782
+ self.distro_release_file = distro_release_file or "" # updated later
783
+
784
+ is_root_dir_defined = root_dir is not None
785
+ if is_root_dir_defined and (include_lsb or include_uname or include_oslevel):
786
+ raise ValueError(
787
+ "Including subprocess data sources from specific root_dir is disallowed"
788
+ " to prevent false information"
789
+ )
790
+ self.include_lsb = (
791
+ include_lsb if include_lsb is not None else not is_root_dir_defined
792
+ )
793
+ self.include_uname = (
794
+ include_uname if include_uname is not None else not is_root_dir_defined
795
+ )
796
+ self.include_oslevel = (
797
+ include_oslevel if include_oslevel is not None else not is_root_dir_defined
798
+ )
799
+
800
+ def __repr__(self) -> str:
801
+ """Return repr of all info"""
802
+ return (
803
+ "LinuxDistribution("
804
+ "os_release_file={self.os_release_file!r}, "
805
+ "distro_release_file={self.distro_release_file!r}, "
806
+ "include_lsb={self.include_lsb!r}, "
807
+ "include_uname={self.include_uname!r}, "
808
+ "include_oslevel={self.include_oslevel!r}, "
809
+ "root_dir={self.root_dir!r}, "
810
+ "_os_release_info={self._os_release_info!r}, "
811
+ "_lsb_release_info={self._lsb_release_info!r}, "
812
+ "_distro_release_info={self._distro_release_info!r}, "
813
+ "_uname_info={self._uname_info!r}, "
814
+ "_oslevel_info={self._oslevel_info!r})".format(self=self)
815
+ )
816
+
817
+ def linux_distribution(
818
+ self, full_distribution_name: bool = True
819
+ ) -> Tuple[str, str, str]:
820
+ """
821
+ Return information about the OS distribution that is compatible
822
+ with Python's :func:`platform.linux_distribution`, supporting a subset
823
+ of its parameters.
824
+
825
+ For details, see :func:`distro.linux_distribution`.
826
+ """
827
+ return (
828
+ self.name() if full_distribution_name else self.id(),
829
+ self.version(),
830
+ self._os_release_info.get("release_codename") or self.codename(),
831
+ )
832
+
833
+ def id(self) -> str:
834
+ """Return the distro ID of the OS distribution, as a string.
835
+
836
+ For details, see :func:`distro.id`.
837
+ """
838
+
839
+ def normalize(distro_id: str, table: Dict[str, str]) -> str:
840
+ distro_id = distro_id.lower().replace(" ", "_")
841
+ return table.get(distro_id, distro_id)
842
+
843
+ distro_id = self.os_release_attr("id")
844
+ if distro_id:
845
+ return normalize(distro_id, NORMALIZED_OS_ID)
846
+
847
+ distro_id = self.lsb_release_attr("distributor_id")
848
+ if distro_id:
849
+ return normalize(distro_id, NORMALIZED_LSB_ID)
850
+
851
+ distro_id = self.distro_release_attr("id")
852
+ if distro_id:
853
+ return normalize(distro_id, NORMALIZED_DISTRO_ID)
854
+
855
+ distro_id = self.uname_attr("id")
856
+ if distro_id:
857
+ return normalize(distro_id, NORMALIZED_DISTRO_ID)
858
+
859
+ return ""
860
+
861
+ def name(self, pretty: bool = False) -> str:
862
+ """
863
+ Return the name of the OS distribution, as a string.
864
+
865
+ For details, see :func:`distro.name`.
866
+ """
867
+ name = (
868
+ self.os_release_attr("name")
869
+ or self.lsb_release_attr("distributor_id")
870
+ or self.distro_release_attr("name")
871
+ or self.uname_attr("name")
872
+ )
873
+ if pretty:
874
+ name = self.os_release_attr("pretty_name") or self.lsb_release_attr(
875
+ "description"
876
+ )
877
+ if not name:
878
+ name = self.distro_release_attr("name") or self.uname_attr("name")
879
+ version = self.version(pretty=True)
880
+ if version:
881
+ name = f"{name} {version}"
882
+ return name or ""
883
+
884
+ def version(self, pretty: bool = False, best: bool = False) -> str:
885
+ """
886
+ Return the version of the OS distribution, as a string.
887
+
888
+ For details, see :func:`distro.version`.
889
+ """
890
+ versions = [
891
+ self.os_release_attr("version_id"),
892
+ self.lsb_release_attr("release"),
893
+ self.distro_release_attr("version_id"),
894
+ self._parse_distro_release_content(self.os_release_attr("pretty_name")).get(
895
+ "version_id", ""
896
+ ),
897
+ self._parse_distro_release_content(
898
+ self.lsb_release_attr("description")
899
+ ).get("version_id", ""),
900
+ self.uname_attr("release"),
901
+ ]
902
+ if self.uname_attr("id").startswith("aix"):
903
+ # On AIX platforms, prefer oslevel command output.
904
+ versions.insert(0, self.oslevel_info())
905
+ elif self.id() == "debian" or "debian" in self.like().split():
906
+ # On Debian-like, add debian_version file content to candidates list.
907
+ versions.append(self._debian_version)
908
+ version = ""
909
+ if best:
910
+ # This algorithm uses the last version in priority order that has
911
+ # the best precision. If the versions are not in conflict, that
912
+ # does not matter; otherwise, using the last one instead of the
913
+ # first one might be considered a surprise.
914
+ for v in versions:
915
+ if v.count(".") > version.count(".") or version == "":
916
+ version = v
917
+ else:
918
+ for v in versions:
919
+ if v != "":
920
+ version = v
921
+ break
922
+ if pretty and version and self.codename():
923
+ version = f"{version} ({self.codename()})"
924
+ return version
925
+
926
+ def version_parts(self, best: bool = False) -> Tuple[str, str, str]:
927
+ """
928
+ Return the version of the OS distribution, as a tuple of version
929
+ numbers.
930
+
931
+ For details, see :func:`distro.version_parts`.
932
+ """
933
+ version_str = self.version(best=best)
934
+ if version_str:
935
+ version_regex = re.compile(r"(\d+)\.?(\d+)?\.?(\d+)?")
936
+ matches = version_regex.match(version_str)
937
+ if matches:
938
+ major, minor, build_number = matches.groups()
939
+ return major, minor or "", build_number or ""
940
+ return "", "", ""
941
+
942
+ def major_version(self, best: bool = False) -> str:
943
+ """
944
+ Return the major version number of the current distribution.
945
+
946
+ For details, see :func:`distro.major_version`.
947
+ """
948
+ return self.version_parts(best)[0]
949
+
950
+ def minor_version(self, best: bool = False) -> str:
951
+ """
952
+ Return the minor version number of the current distribution.
953
+
954
+ For details, see :func:`distro.minor_version`.
955
+ """
956
+ return self.version_parts(best)[1]
957
+
958
+ def build_number(self, best: bool = False) -> str:
959
+ """
960
+ Return the build number of the current distribution.
961
+
962
+ For details, see :func:`distro.build_number`.
963
+ """
964
+ return self.version_parts(best)[2]
965
+
966
+ def like(self) -> str:
967
+ """
968
+ Return the IDs of distributions that are like the OS distribution.
969
+
970
+ For details, see :func:`distro.like`.
971
+ """
972
+ return self.os_release_attr("id_like") or ""
973
+
974
+ def codename(self) -> str:
975
+ """
976
+ Return the codename of the OS distribution.
977
+
978
+ For details, see :func:`distro.codename`.
979
+ """
980
+ try:
981
+ # Handle os_release specially since distros might purposefully set
982
+ # this to empty string to have no codename
983
+ return self._os_release_info["codename"]
984
+ except KeyError:
985
+ return (
986
+ self.lsb_release_attr("codename")
987
+ or self.distro_release_attr("codename")
988
+ or ""
989
+ )
990
+
991
+ def info(self, pretty: bool = False, best: bool = False) -> InfoDict:
992
+ """
993
+ Return certain machine-readable information about the OS
994
+ distribution.
995
+
996
+ For details, see :func:`distro.info`.
997
+ """
998
+ return InfoDict(
999
+ id=self.id(),
1000
+ version=self.version(pretty, best),
1001
+ version_parts=VersionDict(
1002
+ major=self.major_version(best),
1003
+ minor=self.minor_version(best),
1004
+ build_number=self.build_number(best),
1005
+ ),
1006
+ like=self.like(),
1007
+ codename=self.codename(),
1008
+ )
1009
+
1010
+ def os_release_info(self) -> Dict[str, str]:
1011
+ """
1012
+ Return a dictionary containing key-value pairs for the information
1013
+ items from the os-release file data source of the OS distribution.
1014
+
1015
+ For details, see :func:`distro.os_release_info`.
1016
+ """
1017
+ return self._os_release_info
1018
+
1019
+ def lsb_release_info(self) -> Dict[str, str]:
1020
+ """
1021
+ Return a dictionary containing key-value pairs for the information
1022
+ items from the lsb_release command data source of the OS
1023
+ distribution.
1024
+
1025
+ For details, see :func:`distro.lsb_release_info`.
1026
+ """
1027
+ return self._lsb_release_info
1028
+
1029
+ def distro_release_info(self) -> Dict[str, str]:
1030
+ """
1031
+ Return a dictionary containing key-value pairs for the information
1032
+ items from the distro release file data source of the OS
1033
+ distribution.
1034
+
1035
+ For details, see :func:`distro.distro_release_info`.
1036
+ """
1037
+ return self._distro_release_info
1038
+
1039
+ def uname_info(self) -> Dict[str, str]:
1040
+ """
1041
+ Return a dictionary containing key-value pairs for the information
1042
+ items from the uname command data source of the OS distribution.
1043
+
1044
+ For details, see :func:`distro.uname_info`.
1045
+ """
1046
+ return self._uname_info
1047
+
1048
+ def oslevel_info(self) -> str:
1049
+ """
1050
+ Return AIX' oslevel command output.
1051
+ """
1052
+ return self._oslevel_info
1053
+
1054
+ def os_release_attr(self, attribute: str) -> str:
1055
+ """
1056
+ Return a single named information item from the os-release file data
1057
+ source of the OS distribution.
1058
+
1059
+ For details, see :func:`distro.os_release_attr`.
1060
+ """
1061
+ return self._os_release_info.get(attribute, "")
1062
+
1063
+ def lsb_release_attr(self, attribute: str) -> str:
1064
+ """
1065
+ Return a single named information item from the lsb_release command
1066
+ output data source of the OS distribution.
1067
+
1068
+ For details, see :func:`distro.lsb_release_attr`.
1069
+ """
1070
+ return self._lsb_release_info.get(attribute, "")
1071
+
1072
+ def distro_release_attr(self, attribute: str) -> str:
1073
+ """
1074
+ Return a single named information item from the distro release file
1075
+ data source of the OS distribution.
1076
+
1077
+ For details, see :func:`distro.distro_release_attr`.
1078
+ """
1079
+ return self._distro_release_info.get(attribute, "")
1080
+
1081
+ def uname_attr(self, attribute: str) -> str:
1082
+ """
1083
+ Return a single named information item from the uname command
1084
+ output data source of the OS distribution.
1085
+
1086
+ For details, see :func:`distro.uname_attr`.
1087
+ """
1088
+ return self._uname_info.get(attribute, "")
1089
+
1090
+ @cached_property
1091
+ def _os_release_info(self) -> Dict[str, str]:
1092
+ """
1093
+ Get the information items from the specified os-release file.
1094
+
1095
+ Returns:
1096
+ A dictionary containing all information items.
1097
+ """
1098
+ if os.path.isfile(self.os_release_file):
1099
+ with open(self.os_release_file, encoding="utf-8") as release_file:
1100
+ return self._parse_os_release_content(release_file)
1101
+ return {}
1102
+
1103
+ @staticmethod
1104
+ def _parse_os_release_content(lines: TextIO) -> Dict[str, str]:
1105
+ """
1106
+ Parse the lines of an os-release file.
1107
+
1108
+ Parameters:
1109
+
1110
+ * lines: Iterable through the lines in the os-release file.
1111
+ Each line must be a unicode string or a UTF-8 encoded byte
1112
+ string.
1113
+
1114
+ Returns:
1115
+ A dictionary containing all information items.
1116
+ """
1117
+ props = {}
1118
+ lexer = shlex.shlex(lines, posix=True)
1119
+ lexer.whitespace_split = True
1120
+
1121
+ tokens = list(lexer)
1122
+ for token in tokens:
1123
+ # At this point, all shell-like parsing has been done (i.e.
1124
+ # comments processed, quotes and backslash escape sequences
1125
+ # processed, multi-line values assembled, trailing newlines
1126
+ # stripped, etc.), so the tokens are now either:
1127
+ # * variable assignments: var=value
1128
+ # * commands or their arguments (not allowed in os-release)
1129
+ # Ignore any tokens that are not variable assignments
1130
+ if "=" in token:
1131
+ k, v = token.split("=", 1)
1132
+ props[k.lower()] = v
1133
+
1134
+ if "version" in props:
1135
+ # extract release codename (if any) from version attribute
1136
+ match = re.search(r"\((\D+)\)|,\s*(\D+)", props["version"])
1137
+ if match:
1138
+ release_codename = match.group(1) or match.group(2)
1139
+ props["codename"] = props["release_codename"] = release_codename
1140
+
1141
+ if "version_codename" in props:
1142
+ # os-release added a version_codename field. Use that in
1143
+ # preference to anything else Note that some distros purposefully
1144
+ # do not have code names. They should be setting
1145
+ # version_codename=""
1146
+ props["codename"] = props["version_codename"]
1147
+ elif "ubuntu_codename" in props:
1148
+ # Same as above but a non-standard field name used on older Ubuntus
1149
+ props["codename"] = props["ubuntu_codename"]
1150
+
1151
+ return props
1152
+
1153
+ @cached_property
1154
+ def _lsb_release_info(self) -> Dict[str, str]:
1155
+ """
1156
+ Get the information items from the lsb_release command output.
1157
+
1158
+ Returns:
1159
+ A dictionary containing all information items.
1160
+ """
1161
+ if not self.include_lsb:
1162
+ return {}
1163
+ try:
1164
+ cmd = ("lsb_release", "-a")
1165
+ stdout = subprocess.check_output(cmd, stderr=subprocess.DEVNULL)
1166
+ # Command not found or lsb_release returned error
1167
+ except (OSError, subprocess.CalledProcessError):
1168
+ return {}
1169
+ content = self._to_str(stdout).splitlines()
1170
+ return self._parse_lsb_release_content(content)
1171
+
1172
+ @staticmethod
1173
+ def _parse_lsb_release_content(lines: Iterable[str]) -> Dict[str, str]:
1174
+ """
1175
+ Parse the output of the lsb_release command.
1176
+
1177
+ Parameters:
1178
+
1179
+ * lines: Iterable through the lines of the lsb_release output.
1180
+ Each line must be a unicode string or a UTF-8 encoded byte
1181
+ string.
1182
+
1183
+ Returns:
1184
+ A dictionary containing all information items.
1185
+ """
1186
+ props = {}
1187
+ for line in lines:
1188
+ kv = line.strip("\n").split(":", 1)
1189
+ if len(kv) != 2:
1190
+ # Ignore lines without colon.
1191
+ continue
1192
+ k, v = kv
1193
+ props.update({k.replace(" ", "_").lower(): v.strip()})
1194
+ return props
1195
+
1196
+ @cached_property
1197
+ def _uname_info(self) -> Dict[str, str]:
1198
+ if not self.include_uname:
1199
+ return {}
1200
+ try:
1201
+ cmd = ("uname", "-rs")
1202
+ stdout = subprocess.check_output(cmd, stderr=subprocess.DEVNULL)
1203
+ except OSError:
1204
+ return {}
1205
+ content = self._to_str(stdout).splitlines()
1206
+ return self._parse_uname_content(content)
1207
+
1208
+ @cached_property
1209
+ def _oslevel_info(self) -> str:
1210
+ if not self.include_oslevel:
1211
+ return ""
1212
+ try:
1213
+ stdout = subprocess.check_output("oslevel", stderr=subprocess.DEVNULL)
1214
+ except (OSError, subprocess.CalledProcessError):
1215
+ return ""
1216
+ return self._to_str(stdout).strip()
1217
+
1218
+ @cached_property
1219
+ def _debian_version(self) -> str:
1220
+ try:
1221
+ with open(
1222
+ os.path.join(self.etc_dir, "debian_version"), encoding="ascii"
1223
+ ) as fp:
1224
+ return fp.readline().rstrip()
1225
+ except FileNotFoundError:
1226
+ return ""
1227
+
1228
+ @staticmethod
1229
+ def _parse_uname_content(lines: Sequence[str]) -> Dict[str, str]:
1230
+ if not lines:
1231
+ return {}
1232
+ props = {}
1233
+ match = re.search(r"^([^\s]+)\s+([\d\.]+)", lines[0].strip())
1234
+ if match:
1235
+ name, version = match.groups()
1236
+
1237
+ # This is to prevent the Linux kernel version from
1238
+ # appearing as the 'best' version on otherwise
1239
+ # identifiable distributions.
1240
+ if name == "Linux":
1241
+ return {}
1242
+ props["id"] = name.lower()
1243
+ props["name"] = name
1244
+ props["release"] = version
1245
+ return props
1246
+
1247
+ @staticmethod
1248
+ def _to_str(bytestring: bytes) -> str:
1249
+ encoding = sys.getfilesystemencoding()
1250
+ return bytestring.decode(encoding)
1251
+
1252
+ @cached_property
1253
+ def _distro_release_info(self) -> Dict[str, str]:
1254
+ """
1255
+ Get the information items from the specified distro release file.
1256
+
1257
+ Returns:
1258
+ A dictionary containing all information items.
1259
+ """
1260
+ if self.distro_release_file:
1261
+ # If it was specified, we use it and parse what we can, even if
1262
+ # its file name or content does not match the expected pattern.
1263
+ distro_info = self._parse_distro_release_file(self.distro_release_file)
1264
+ basename = os.path.basename(self.distro_release_file)
1265
+ # The file name pattern for user-specified distro release files
1266
+ # is somewhat more tolerant (compared to when searching for the
1267
+ # file), because we want to use what was specified as best as
1268
+ # possible.
1269
+ match = _DISTRO_RELEASE_BASENAME_PATTERN.match(basename)
1270
+ else:
1271
+ try:
1272
+ basenames = [
1273
+ basename
1274
+ for basename in os.listdir(self.etc_dir)
1275
+ if basename not in _DISTRO_RELEASE_IGNORE_BASENAMES
1276
+ and os.path.isfile(os.path.join(self.etc_dir, basename))
1277
+ ]
1278
+ # We sort for repeatability in cases where there are multiple
1279
+ # distro specific files; e.g. CentOS, Oracle, Enterprise all
1280
+ # containing `redhat-release` on top of their own.
1281
+ basenames.sort()
1282
+ except OSError:
1283
+ # This may occur when /etc is not readable but we can't be
1284
+ # sure about the *-release files. Check common entries of
1285
+ # /etc for information. If they turn out to not be there the
1286
+ # error is handled in `_parse_distro_release_file()`.
1287
+ basenames = _DISTRO_RELEASE_BASENAMES
1288
+ for basename in basenames:
1289
+ match = _DISTRO_RELEASE_BASENAME_PATTERN.match(basename)
1290
+ if match is None:
1291
+ continue
1292
+ filepath = os.path.join(self.etc_dir, basename)
1293
+ distro_info = self._parse_distro_release_file(filepath)
1294
+ # The name is always present if the pattern matches.
1295
+ if "name" not in distro_info:
1296
+ continue
1297
+ self.distro_release_file = filepath
1298
+ break
1299
+ else: # the loop didn't "break": no candidate.
1300
+ return {}
1301
+
1302
+ if match is not None:
1303
+ distro_info["id"] = match.group(1)
1304
+
1305
+ # CloudLinux < 7: manually enrich info with proper id.
1306
+ if "cloudlinux" in distro_info.get("name", "").lower():
1307
+ distro_info["id"] = "cloudlinux"
1308
+
1309
+ return distro_info
1310
+
1311
+ def _parse_distro_release_file(self, filepath: str) -> Dict[str, str]:
1312
+ """
1313
+ Parse a distro release file.
1314
+
1315
+ Parameters:
1316
+
1317
+ * filepath: Path name of the distro release file.
1318
+
1319
+ Returns:
1320
+ A dictionary containing all information items.
1321
+ """
1322
+ try:
1323
+ with open(filepath, encoding="utf-8") as fp:
1324
+ # Only parse the first line. For instance, on SLES there
1325
+ # are multiple lines. We don't want them...
1326
+ return self._parse_distro_release_content(fp.readline())
1327
+ except OSError:
1328
+ # Ignore not being able to read a specific, seemingly version
1329
+ # related file.
1330
+ # See https://github.com/python-distro/distro/issues/162
1331
+ return {}
1332
+
1333
+ @staticmethod
1334
+ def _parse_distro_release_content(line: str) -> Dict[str, str]:
1335
+ """
1336
+ Parse a line from a distro release file.
1337
+
1338
+ Parameters:
1339
+ * line: Line from the distro release file. Must be a unicode string
1340
+ or a UTF-8 encoded byte string.
1341
+
1342
+ Returns:
1343
+ A dictionary containing all information items.
1344
+ """
1345
+ matches = _DISTRO_RELEASE_CONTENT_REVERSED_PATTERN.match(line.strip()[::-1])
1346
+ distro_info = {}
1347
+ if matches:
1348
+ # regexp ensures non-None
1349
+ distro_info["name"] = matches.group(3)[::-1]
1350
+ if matches.group(2):
1351
+ distro_info["version_id"] = matches.group(2)[::-1]
1352
+ if matches.group(1):
1353
+ distro_info["codename"] = matches.group(1)[::-1]
1354
+ elif line:
1355
+ distro_info["name"] = line.strip()
1356
+ return distro_info
1357
+
1358
+
1359
+ _distro = LinuxDistribution()
1360
+
1361
+
1362
+ def main() -> None:
1363
+ logger = logging.getLogger(__name__)
1364
+ logger.setLevel(logging.DEBUG)
1365
+ logger.addHandler(logging.StreamHandler(sys.stdout))
1366
+
1367
+ parser = argparse.ArgumentParser(description="OS distro info tool")
1368
+ parser.add_argument(
1369
+ "--json", "-j", help="Output in machine readable format", action="store_true"
1370
+ )
1371
+
1372
+ parser.add_argument(
1373
+ "--root-dir",
1374
+ "-r",
1375
+ type=str,
1376
+ dest="root_dir",
1377
+ help="Path to the root filesystem directory (defaults to /)",
1378
+ )
1379
+
1380
+ args = parser.parse_args()
1381
+
1382
+ if args.root_dir:
1383
+ dist = LinuxDistribution(
1384
+ include_lsb=False,
1385
+ include_uname=False,
1386
+ include_oslevel=False,
1387
+ root_dir=args.root_dir,
1388
+ )
1389
+ else:
1390
+ dist = _distro
1391
+
1392
+ if args.json:
1393
+ logger.info(json.dumps(dist.info(), indent=4, sort_keys=True))
1394
+ else:
1395
+ logger.info("Name: %s", dist.name(pretty=True))
1396
+ distribution_version = dist.version(pretty=True)
1397
+ logger.info("Version: %s", distribution_version)
1398
+ distribution_codename = dist.codename()
1399
+ logger.info("Codename: %s", distribution_codename)
1400
+
1401
+
1402
+ if __name__ == "__main__":
1403
+ main()
lib/python3.12/site-packages/pip/_vendor/distro/py.typed ADDED
File without changes
lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (565 Bytes). View file
 
lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_parser.cpython-312.pyc ADDED
Binary file (14 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_structures.cpython-312.pyc ADDED
Binary file (3.25 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/metadata.cpython-312.pyc ADDED
Binary file (27.2 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/tags.cpython-312.pyc ADDED
Binary file (23 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/utils.cpython-312.pyc ADDED
Binary file (6.64 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/version.cpython-312.pyc ADDED
Binary file (20.5 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/__init__.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Pygments
3
+ ~~~~~~~~
4
+
5
+ Pygments is a syntax highlighting package written in Python.
6
+
7
+ It is a generic syntax highlighter for general use in all kinds of software
8
+ such as forum systems, wikis or other applications that need to prettify
9
+ source code. Highlights are:
10
+
11
+ * a wide range of common languages and markup formats is supported
12
+ * special attention is paid to details, increasing quality by a fair amount
13
+ * support for new languages and formats are added easily
14
+ * a number of output formats, presently HTML, LaTeX, RTF, SVG, all image
15
+ formats that PIL supports, and ANSI sequences
16
+ * it is usable as a command-line tool and as a library
17
+ * ... and it highlights even Brainfuck!
18
+
19
+ The `Pygments master branch`_ is installable with ``easy_install Pygments==dev``.
20
+
21
+ .. _Pygments master branch:
22
+ https://github.com/pygments/pygments/archive/master.zip#egg=Pygments-dev
23
+
24
+ :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
25
+ :license: BSD, see LICENSE for details.
26
+ """
27
+ from io import StringIO, BytesIO
28
+
29
+ __version__ = '2.18.0'
30
+ __docformat__ = 'restructuredtext'
31
+
32
+ __all__ = ['lex', 'format', 'highlight']
33
+
34
+
35
+ def lex(code, lexer):
36
+ """
37
+ Lex `code` with the `lexer` (must be a `Lexer` instance)
38
+ and return an iterable of tokens. Currently, this only calls
39
+ `lexer.get_tokens()`.
40
+ """
41
+ try:
42
+ return lexer.get_tokens(code)
43
+ except TypeError:
44
+ # Heuristic to catch a common mistake.
45
+ from pip._vendor.pygments.lexer import RegexLexer
46
+ if isinstance(lexer, type) and issubclass(lexer, RegexLexer):
47
+ raise TypeError('lex() argument must be a lexer instance, '
48
+ 'not a class')
49
+ raise
50
+
51
+
52
+ def format(tokens, formatter, outfile=None): # pylint: disable=redefined-builtin
53
+ """
54
+ Format ``tokens`` (an iterable of tokens) with the formatter ``formatter``
55
+ (a `Formatter` instance).
56
+
57
+ If ``outfile`` is given and a valid file object (an object with a
58
+ ``write`` method), the result will be written to it, otherwise it
59
+ is returned as a string.
60
+ """
61
+ try:
62
+ if not outfile:
63
+ realoutfile = getattr(formatter, 'encoding', None) and BytesIO() or StringIO()
64
+ formatter.format(tokens, realoutfile)
65
+ return realoutfile.getvalue()
66
+ else:
67
+ formatter.format(tokens, outfile)
68
+ except TypeError:
69
+ # Heuristic to catch a common mistake.
70
+ from pip._vendor.pygments.formatter import Formatter
71
+ if isinstance(formatter, type) and issubclass(formatter, Formatter):
72
+ raise TypeError('format() argument must be a formatter instance, '
73
+ 'not a class')
74
+ raise
75
+
76
+
77
+ def highlight(code, lexer, formatter, outfile=None):
78
+ """
79
+ This is the most high-level highlighting function. It combines `lex` and
80
+ `format` in one function.
81
+ """
82
+ return format(lex(code, lexer), formatter, outfile)
lib/python3.12/site-packages/pip/_vendor/pygments/__main__.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ pygments.__main__
3
+ ~~~~~~~~~~~~~~~~~
4
+
5
+ Main entry point for ``python -m pygments``.
6
+
7
+ :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
8
+ :license: BSD, see LICENSE for details.
9
+ """
10
+
11
+ import sys
12
+ from pip._vendor.pygments.cmdline import main
13
+
14
+ try:
15
+ sys.exit(main(sys.argv))
16
+ except KeyboardInterrupt:
17
+ sys.exit(1)
lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/lexer.cpython-312.pyc ADDED
Binary file (38.4 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/modeline.cpython-312.pyc ADDED
Binary file (1.57 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/style.cpython-312.pyc ADDED
Binary file (6.7 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/unistring.cpython-312.pyc ADDED
Binary file (33 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/cmdline.py ADDED
@@ -0,0 +1,668 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ pygments.cmdline
3
+ ~~~~~~~~~~~~~~~~
4
+
5
+ Command line interface.
6
+
7
+ :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
8
+ :license: BSD, see LICENSE for details.
9
+ """
10
+
11
+ import os
12
+ import sys
13
+ import shutil
14
+ import argparse
15
+ from textwrap import dedent
16
+
17
+ from pip._vendor.pygments import __version__, highlight
18
+ from pip._vendor.pygments.util import ClassNotFound, OptionError, docstring_headline, \
19
+ guess_decode, guess_decode_from_terminal, terminal_encoding, \
20
+ UnclosingTextIOWrapper
21
+ from pip._vendor.pygments.lexers import get_all_lexers, get_lexer_by_name, guess_lexer, \
22
+ load_lexer_from_file, get_lexer_for_filename, find_lexer_class_for_filename
23
+ from pip._vendor.pygments.lexers.special import TextLexer
24
+ from pip._vendor.pygments.formatters.latex import LatexEmbeddedLexer, LatexFormatter
25
+ from pip._vendor.pygments.formatters import get_all_formatters, get_formatter_by_name, \
26
+ load_formatter_from_file, get_formatter_for_filename, find_formatter_class
27
+ from pip._vendor.pygments.formatters.terminal import TerminalFormatter
28
+ from pip._vendor.pygments.formatters.terminal256 import Terminal256Formatter, TerminalTrueColorFormatter
29
+ from pip._vendor.pygments.filters import get_all_filters, find_filter_class
30
+ from pip._vendor.pygments.styles import get_all_styles, get_style_by_name
31
+
32
+
33
+ def _parse_options(o_strs):
34
+ opts = {}
35
+ if not o_strs:
36
+ return opts
37
+ for o_str in o_strs:
38
+ if not o_str.strip():
39
+ continue
40
+ o_args = o_str.split(',')
41
+ for o_arg in o_args:
42
+ o_arg = o_arg.strip()
43
+ try:
44
+ o_key, o_val = o_arg.split('=', 1)
45
+ o_key = o_key.strip()
46
+ o_val = o_val.strip()
47
+ except ValueError:
48
+ opts[o_arg] = True
49
+ else:
50
+ opts[o_key] = o_val
51
+ return opts
52
+
53
+
54
+ def _parse_filters(f_strs):
55
+ filters = []
56
+ if not f_strs:
57
+ return filters
58
+ for f_str in f_strs:
59
+ if ':' in f_str:
60
+ fname, fopts = f_str.split(':', 1)
61
+ filters.append((fname, _parse_options([fopts])))
62
+ else:
63
+ filters.append((f_str, {}))
64
+ return filters
65
+
66
+
67
+ def _print_help(what, name):
68
+ try:
69
+ if what == 'lexer':
70
+ cls = get_lexer_by_name(name)
71
+ print(f"Help on the {cls.name} lexer:")
72
+ print(dedent(cls.__doc__))
73
+ elif what == 'formatter':
74
+ cls = find_formatter_class(name)
75
+ print(f"Help on the {cls.name} formatter:")
76
+ print(dedent(cls.__doc__))
77
+ elif what == 'filter':
78
+ cls = find_filter_class(name)
79
+ print(f"Help on the {name} filter:")
80
+ print(dedent(cls.__doc__))
81
+ return 0
82
+ except (AttributeError, ValueError):
83
+ print(f"{what} not found!", file=sys.stderr)
84
+ return 1
85
+
86
+
87
+ def _print_list(what):
88
+ if what == 'lexer':
89
+ print()
90
+ print("Lexers:")
91
+ print("~~~~~~~")
92
+
93
+ info = []
94
+ for fullname, names, exts, _ in get_all_lexers():
95
+ tup = (', '.join(names)+':', fullname,
96
+ exts and '(filenames ' + ', '.join(exts) + ')' or '')
97
+ info.append(tup)
98
+ info.sort()
99
+ for i in info:
100
+ print(('* {}\n {} {}').format(*i))
101
+
102
+ elif what == 'formatter':
103
+ print()
104
+ print("Formatters:")
105
+ print("~~~~~~~~~~~")
106
+
107
+ info = []
108
+ for cls in get_all_formatters():
109
+ doc = docstring_headline(cls)
110
+ tup = (', '.join(cls.aliases) + ':', doc, cls.filenames and
111
+ '(filenames ' + ', '.join(cls.filenames) + ')' or '')
112
+ info.append(tup)
113
+ info.sort()
114
+ for i in info:
115
+ print(('* {}\n {} {}').format(*i))
116
+
117
+ elif what == 'filter':
118
+ print()
119
+ print("Filters:")
120
+ print("~~~~~~~~")
121
+
122
+ for name in get_all_filters():
123
+ cls = find_filter_class(name)
124
+ print("* " + name + ':')
125
+ print(f" {docstring_headline(cls)}")
126
+
127
+ elif what == 'style':
128
+ print()
129
+ print("Styles:")
130
+ print("~~~~~~~")
131
+
132
+ for name in get_all_styles():
133
+ cls = get_style_by_name(name)
134
+ print("* " + name + ':')
135
+ print(f" {docstring_headline(cls)}")
136
+
137
+
138
+ def _print_list_as_json(requested_items):
139
+ import json
140
+ result = {}
141
+ if 'lexer' in requested_items:
142
+ info = {}
143
+ for fullname, names, filenames, mimetypes in get_all_lexers():
144
+ info[fullname] = {
145
+ 'aliases': names,
146
+ 'filenames': filenames,
147
+ 'mimetypes': mimetypes
148
+ }
149
+ result['lexers'] = info
150
+
151
+ if 'formatter' in requested_items:
152
+ info = {}
153
+ for cls in get_all_formatters():
154
+ doc = docstring_headline(cls)
155
+ info[cls.name] = {
156
+ 'aliases': cls.aliases,
157
+ 'filenames': cls.filenames,
158
+ 'doc': doc
159
+ }
160
+ result['formatters'] = info
161
+
162
+ if 'filter' in requested_items:
163
+ info = {}
164
+ for name in get_all_filters():
165
+ cls = find_filter_class(name)
166
+ info[name] = {
167
+ 'doc': docstring_headline(cls)
168
+ }
169
+ result['filters'] = info
170
+
171
+ if 'style' in requested_items:
172
+ info = {}
173
+ for name in get_all_styles():
174
+ cls = get_style_by_name(name)
175
+ info[name] = {
176
+ 'doc': docstring_headline(cls)
177
+ }
178
+ result['styles'] = info
179
+
180
+ json.dump(result, sys.stdout)
181
+
182
+ def main_inner(parser, argns):
183
+ if argns.help:
184
+ parser.print_help()
185
+ return 0
186
+
187
+ if argns.V:
188
+ print(f'Pygments version {__version__}, (c) 2006-2024 by Georg Brandl, Matthäus '
189
+ 'Chajdas and contributors.')
190
+ return 0
191
+
192
+ def is_only_option(opt):
193
+ return not any(v for (k, v) in vars(argns).items() if k != opt)
194
+
195
+ # handle ``pygmentize -L``
196
+ if argns.L is not None:
197
+ arg_set = set()
198
+ for k, v in vars(argns).items():
199
+ if v:
200
+ arg_set.add(k)
201
+
202
+ arg_set.discard('L')
203
+ arg_set.discard('json')
204
+
205
+ if arg_set:
206
+ parser.print_help(sys.stderr)
207
+ return 2
208
+
209
+ # print version
210
+ if not argns.json:
211
+ main(['', '-V'])
212
+ allowed_types = {'lexer', 'formatter', 'filter', 'style'}
213
+ largs = [arg.rstrip('s') for arg in argns.L]
214
+ if any(arg not in allowed_types for arg in largs):
215
+ parser.print_help(sys.stderr)
216
+ return 0
217
+ if not largs:
218
+ largs = allowed_types
219
+ if not argns.json:
220
+ for arg in largs:
221
+ _print_list(arg)
222
+ else:
223
+ _print_list_as_json(largs)
224
+ return 0
225
+
226
+ # handle ``pygmentize -H``
227
+ if argns.H:
228
+ if not is_only_option('H'):
229
+ parser.print_help(sys.stderr)
230
+ return 2
231
+ what, name = argns.H
232
+ if what not in ('lexer', 'formatter', 'filter'):
233
+ parser.print_help(sys.stderr)
234
+ return 2
235
+ return _print_help(what, name)
236
+
237
+ # parse -O options
238
+ parsed_opts = _parse_options(argns.O or [])
239
+
240
+ # parse -P options
241
+ for p_opt in argns.P or []:
242
+ try:
243
+ name, value = p_opt.split('=', 1)
244
+ except ValueError:
245
+ parsed_opts[p_opt] = True
246
+ else:
247
+ parsed_opts[name] = value
248
+
249
+ # encodings
250
+ inencoding = parsed_opts.get('inencoding', parsed_opts.get('encoding'))
251
+ outencoding = parsed_opts.get('outencoding', parsed_opts.get('encoding'))
252
+
253
+ # handle ``pygmentize -N``
254
+ if argns.N:
255
+ lexer = find_lexer_class_for_filename(argns.N)
256
+ if lexer is None:
257
+ lexer = TextLexer
258
+
259
+ print(lexer.aliases[0])
260
+ return 0
261
+
262
+ # handle ``pygmentize -C``
263
+ if argns.C:
264
+ inp = sys.stdin.buffer.read()
265
+ try:
266
+ lexer = guess_lexer(inp, inencoding=inencoding)
267
+ except ClassNotFound:
268
+ lexer = TextLexer
269
+
270
+ print(lexer.aliases[0])
271
+ return 0
272
+
273
+ # handle ``pygmentize -S``
274
+ S_opt = argns.S
275
+ a_opt = argns.a
276
+ if S_opt is not None:
277
+ f_opt = argns.f
278
+ if not f_opt:
279
+ parser.print_help(sys.stderr)
280
+ return 2
281
+ if argns.l or argns.INPUTFILE:
282
+ parser.print_help(sys.stderr)
283
+ return 2
284
+
285
+ try:
286
+ parsed_opts['style'] = S_opt
287
+ fmter = get_formatter_by_name(f_opt, **parsed_opts)
288
+ except ClassNotFound as err:
289
+ print(err, file=sys.stderr)
290
+ return 1
291
+
292
+ print(fmter.get_style_defs(a_opt or ''))
293
+ return 0
294
+
295
+ # if no -S is given, -a is not allowed
296
+ if argns.a is not None:
297
+ parser.print_help(sys.stderr)
298
+ return 2
299
+
300
+ # parse -F options
301
+ F_opts = _parse_filters(argns.F or [])
302
+
303
+ # -x: allow custom (eXternal) lexers and formatters
304
+ allow_custom_lexer_formatter = bool(argns.x)
305
+
306
+ # select lexer
307
+ lexer = None
308
+
309
+ # given by name?
310
+ lexername = argns.l
311
+ if lexername:
312
+ # custom lexer, located relative to user's cwd
313
+ if allow_custom_lexer_formatter and '.py' in lexername:
314
+ try:
315
+ filename = None
316
+ name = None
317
+ if ':' in lexername:
318
+ filename, name = lexername.rsplit(':', 1)
319
+
320
+ if '.py' in name:
321
+ # This can happen on Windows: If the lexername is
322
+ # C:\lexer.py -- return to normal load path in that case
323
+ name = None
324
+
325
+ if filename and name:
326
+ lexer = load_lexer_from_file(filename, name,
327
+ **parsed_opts)
328
+ else:
329
+ lexer = load_lexer_from_file(lexername, **parsed_opts)
330
+ except ClassNotFound as err:
331
+ print('Error:', err, file=sys.stderr)
332
+ return 1
333
+ else:
334
+ try:
335
+ lexer = get_lexer_by_name(lexername, **parsed_opts)
336
+ except (OptionError, ClassNotFound) as err:
337
+ print('Error:', err, file=sys.stderr)
338
+ return 1
339
+
340
+ # read input code
341
+ code = None
342
+
343
+ if argns.INPUTFILE:
344
+ if argns.s:
345
+ print('Error: -s option not usable when input file specified',
346
+ file=sys.stderr)
347
+ return 2
348
+
349
+ infn = argns.INPUTFILE
350
+ try:
351
+ with open(infn, 'rb') as infp:
352
+ code = infp.read()
353
+ except Exception as err:
354
+ print('Error: cannot read infile:', err, file=sys.stderr)
355
+ return 1
356
+ if not inencoding:
357
+ code, inencoding = guess_decode(code)
358
+
359
+ # do we have to guess the lexer?
360
+ if not lexer:
361
+ try:
362
+ lexer = get_lexer_for_filename(infn, code, **parsed_opts)
363
+ except ClassNotFound as err:
364
+ if argns.g:
365
+ try:
366
+ lexer = guess_lexer(code, **parsed_opts)
367
+ except ClassNotFound:
368
+ lexer = TextLexer(**parsed_opts)
369
+ else:
370
+ print('Error:', err, file=sys.stderr)
371
+ return 1
372
+ except OptionError as err:
373
+ print('Error:', err, file=sys.stderr)
374
+ return 1
375
+
376
+ elif not argns.s: # treat stdin as full file (-s support is later)
377
+ # read code from terminal, always in binary mode since we want to
378
+ # decode ourselves and be tolerant with it
379
+ code = sys.stdin.buffer.read() # use .buffer to get a binary stream
380
+ if not inencoding:
381
+ code, inencoding = guess_decode_from_terminal(code, sys.stdin)
382
+ # else the lexer will do the decoding
383
+ if not lexer:
384
+ try:
385
+ lexer = guess_lexer(code, **parsed_opts)
386
+ except ClassNotFound:
387
+ lexer = TextLexer(**parsed_opts)
388
+
389
+ else: # -s option needs a lexer with -l
390
+ if not lexer:
391
+ print('Error: when using -s a lexer has to be selected with -l',
392
+ file=sys.stderr)
393
+ return 2
394
+
395
+ # process filters
396
+ for fname, fopts in F_opts:
397
+ try:
398
+ lexer.add_filter(fname, **fopts)
399
+ except ClassNotFound as err:
400
+ print('Error:', err, file=sys.stderr)
401
+ return 1
402
+
403
+ # select formatter
404
+ outfn = argns.o
405
+ fmter = argns.f
406
+ if fmter:
407
+ # custom formatter, located relative to user's cwd
408
+ if allow_custom_lexer_formatter and '.py' in fmter:
409
+ try:
410
+ filename = None
411
+ name = None
412
+ if ':' in fmter:
413
+ # Same logic as above for custom lexer
414
+ filename, name = fmter.rsplit(':', 1)
415
+
416
+ if '.py' in name:
417
+ name = None
418
+
419
+ if filename and name:
420
+ fmter = load_formatter_from_file(filename, name,
421
+ **parsed_opts)
422
+ else:
423
+ fmter = load_formatter_from_file(fmter, **parsed_opts)
424
+ except ClassNotFound as err:
425
+ print('Error:', err, file=sys.stderr)
426
+ return 1
427
+ else:
428
+ try:
429
+ fmter = get_formatter_by_name(fmter, **parsed_opts)
430
+ except (OptionError, ClassNotFound) as err:
431
+ print('Error:', err, file=sys.stderr)
432
+ return 1
433
+
434
+ if outfn:
435
+ if not fmter:
436
+ try:
437
+ fmter = get_formatter_for_filename(outfn, **parsed_opts)
438
+ except (OptionError, ClassNotFound) as err:
439
+ print('Error:', err, file=sys.stderr)
440
+ return 1
441
+ try:
442
+ outfile = open(outfn, 'wb')
443
+ except Exception as err:
444
+ print('Error: cannot open outfile:', err, file=sys.stderr)
445
+ return 1
446
+ else:
447
+ if not fmter:
448
+ if os.environ.get('COLORTERM','') in ('truecolor', '24bit'):
449
+ fmter = TerminalTrueColorFormatter(**parsed_opts)
450
+ elif '256' in os.environ.get('TERM', ''):
451
+ fmter = Terminal256Formatter(**parsed_opts)
452
+ else:
453
+ fmter = TerminalFormatter(**parsed_opts)
454
+ outfile = sys.stdout.buffer
455
+
456
+ # determine output encoding if not explicitly selected
457
+ if not outencoding:
458
+ if outfn:
459
+ # output file? use lexer encoding for now (can still be None)
460
+ fmter.encoding = inencoding
461
+ else:
462
+ # else use terminal encoding
463
+ fmter.encoding = terminal_encoding(sys.stdout)
464
+
465
+ # provide coloring under Windows, if possible
466
+ if not outfn and sys.platform in ('win32', 'cygwin') and \
467
+ fmter.name in ('Terminal', 'Terminal256'): # pragma: no cover
468
+ # unfortunately colorama doesn't support binary streams on Py3
469
+ outfile = UnclosingTextIOWrapper(outfile, encoding=fmter.encoding)
470
+ fmter.encoding = None
471
+ try:
472
+ import colorama.initialise
473
+ except ImportError:
474
+ pass
475
+ else:
476
+ outfile = colorama.initialise.wrap_stream(
477
+ outfile, convert=None, strip=None, autoreset=False, wrap=True)
478
+
479
+ # When using the LaTeX formatter and the option `escapeinside` is
480
+ # specified, we need a special lexer which collects escaped text
481
+ # before running the chosen language lexer.
482
+ escapeinside = parsed_opts.get('escapeinside', '')
483
+ if len(escapeinside) == 2 and isinstance(fmter, LatexFormatter):
484
+ left = escapeinside[0]
485
+ right = escapeinside[1]
486
+ lexer = LatexEmbeddedLexer(left, right, lexer)
487
+
488
+ # ... and do it!
489
+ if not argns.s:
490
+ # process whole input as per normal...
491
+ try:
492
+ highlight(code, lexer, fmter, outfile)
493
+ finally:
494
+ if outfn:
495
+ outfile.close()
496
+ return 0
497
+ else:
498
+ # line by line processing of stdin (eg: for 'tail -f')...
499
+ try:
500
+ while 1:
501
+ line = sys.stdin.buffer.readline()
502
+ if not line:
503
+ break
504
+ if not inencoding:
505
+ line = guess_decode_from_terminal(line, sys.stdin)[0]
506
+ highlight(line, lexer, fmter, outfile)
507
+ if hasattr(outfile, 'flush'):
508
+ outfile.flush()
509
+ return 0
510
+ except KeyboardInterrupt: # pragma: no cover
511
+ return 0
512
+ finally:
513
+ if outfn:
514
+ outfile.close()
515
+
516
+
517
+ class HelpFormatter(argparse.HelpFormatter):
518
+ def __init__(self, prog, indent_increment=2, max_help_position=16, width=None):
519
+ if width is None:
520
+ try:
521
+ width = shutil.get_terminal_size().columns - 2
522
+ except Exception:
523
+ pass
524
+ argparse.HelpFormatter.__init__(self, prog, indent_increment,
525
+ max_help_position, width)
526
+
527
+
528
+ def main(args=sys.argv):
529
+ """
530
+ Main command line entry point.
531
+ """
532
+ desc = "Highlight an input file and write the result to an output file."
533
+ parser = argparse.ArgumentParser(description=desc, add_help=False,
534
+ formatter_class=HelpFormatter)
535
+
536
+ operation = parser.add_argument_group('Main operation')
537
+ lexersel = operation.add_mutually_exclusive_group()
538
+ lexersel.add_argument(
539
+ '-l', metavar='LEXER',
540
+ help='Specify the lexer to use. (Query names with -L.) If not '
541
+ 'given and -g is not present, the lexer is guessed from the filename.')
542
+ lexersel.add_argument(
543
+ '-g', action='store_true',
544
+ help='Guess the lexer from the file contents, or pass through '
545
+ 'as plain text if nothing can be guessed.')
546
+ operation.add_argument(
547
+ '-F', metavar='FILTER[:options]', action='append',
548
+ help='Add a filter to the token stream. (Query names with -L.) '
549
+ 'Filter options are given after a colon if necessary.')
550
+ operation.add_argument(
551
+ '-f', metavar='FORMATTER',
552
+ help='Specify the formatter to use. (Query names with -L.) '
553
+ 'If not given, the formatter is guessed from the output filename, '
554
+ 'and defaults to the terminal formatter if the output is to the '
555
+ 'terminal or an unknown file extension.')
556
+ operation.add_argument(
557
+ '-O', metavar='OPTION=value[,OPTION=value,...]', action='append',
558
+ help='Give options to the lexer and formatter as a comma-separated '
559
+ 'list of key-value pairs. '
560
+ 'Example: `-O bg=light,python=cool`.')
561
+ operation.add_argument(
562
+ '-P', metavar='OPTION=value', action='append',
563
+ help='Give a single option to the lexer and formatter - with this '
564
+ 'you can pass options whose value contains commas and equal signs. '
565
+ 'Example: `-P "heading=Pygments, the Python highlighter"`.')
566
+ operation.add_argument(
567
+ '-o', metavar='OUTPUTFILE',
568
+ help='Where to write the output. Defaults to standard output.')
569
+
570
+ operation.add_argument(
571
+ 'INPUTFILE', nargs='?',
572
+ help='Where to read the input. Defaults to standard input.')
573
+
574
+ flags = parser.add_argument_group('Operation flags')
575
+ flags.add_argument(
576
+ '-v', action='store_true',
577
+ help='Print a detailed traceback on unhandled exceptions, which '
578
+ 'is useful for debugging and bug reports.')
579
+ flags.add_argument(
580
+ '-s', action='store_true',
581
+ help='Process lines one at a time until EOF, rather than waiting to '
582
+ 'process the entire file. This only works for stdin, only for lexers '
583
+ 'with no line-spanning constructs, and is intended for streaming '
584
+ 'input such as you get from `tail -f`. '
585
+ 'Example usage: `tail -f sql.log | pygmentize -s -l sql`.')
586
+ flags.add_argument(
587
+ '-x', action='store_true',
588
+ help='Allow custom lexers and formatters to be loaded from a .py file '
589
+ 'relative to the current working directory. For example, '
590
+ '`-l ./customlexer.py -x`. By default, this option expects a file '
591
+ 'with a class named CustomLexer or CustomFormatter; you can also '
592
+ 'specify your own class name with a colon (`-l ./lexer.py:MyLexer`). '
593
+ 'Users should be very careful not to use this option with untrusted '
594
+ 'files, because it will import and run them.')
595
+ flags.add_argument('--json', help='Output as JSON. This can '
596
+ 'be only used in conjunction with -L.',
597
+ default=False,
598
+ action='store_true')
599
+
600
+ special_modes_group = parser.add_argument_group(
601
+ 'Special modes - do not do any highlighting')
602
+ special_modes = special_modes_group.add_mutually_exclusive_group()
603
+ special_modes.add_argument(
604
+ '-S', metavar='STYLE -f formatter',
605
+ help='Print style definitions for STYLE for a formatter '
606
+ 'given with -f. The argument given by -a is formatter '
607
+ 'dependent.')
608
+ special_modes.add_argument(
609
+ '-L', nargs='*', metavar='WHAT',
610
+ help='List lexers, formatters, styles or filters -- '
611
+ 'give additional arguments for the thing(s) you want to list '
612
+ '(e.g. "styles"), or omit them to list everything.')
613
+ special_modes.add_argument(
614
+ '-N', metavar='FILENAME',
615
+ help='Guess and print out a lexer name based solely on the given '
616
+ 'filename. Does not take input or highlight anything. If no specific '
617
+ 'lexer can be determined, "text" is printed.')
618
+ special_modes.add_argument(
619
+ '-C', action='store_true',
620
+ help='Like -N, but print out a lexer name based solely on '
621
+ 'a given content from standard input.')
622
+ special_modes.add_argument(
623
+ '-H', action='store', nargs=2, metavar=('NAME', 'TYPE'),
624
+ help='Print detailed help for the object <name> of type <type>, '
625
+ 'where <type> is one of "lexer", "formatter" or "filter".')
626
+ special_modes.add_argument(
627
+ '-V', action='store_true',
628
+ help='Print the package version.')
629
+ special_modes.add_argument(
630
+ '-h', '--help', action='store_true',
631
+ help='Print this help.')
632
+ special_modes_group.add_argument(
633
+ '-a', metavar='ARG',
634
+ help='Formatter-specific additional argument for the -S (print '
635
+ 'style sheet) mode.')
636
+
637
+ argns = parser.parse_args(args[1:])
638
+
639
+ try:
640
+ return main_inner(parser, argns)
641
+ except BrokenPipeError:
642
+ # someone closed our stdout, e.g. by quitting a pager.
643
+ return 0
644
+ except Exception:
645
+ if argns.v:
646
+ print(file=sys.stderr)
647
+ print('*' * 65, file=sys.stderr)
648
+ print('An unhandled exception occurred while highlighting.',
649
+ file=sys.stderr)
650
+ print('Please report the whole traceback to the issue tracker at',
651
+ file=sys.stderr)
652
+ print('<https://github.com/pygments/pygments/issues>.',
653
+ file=sys.stderr)
654
+ print('*' * 65, file=sys.stderr)
655
+ print(file=sys.stderr)
656
+ raise
657
+ import traceback
658
+ info = traceback.format_exception(*sys.exc_info())
659
+ msg = info[-1].strip()
660
+ if len(info) >= 3:
661
+ # extract relevant file and position info
662
+ msg += '\n (f{})'.format(info[-2].split('\n')[0].strip()[1:])
663
+ print(file=sys.stderr)
664
+ print('*** Error while highlighting:', file=sys.stderr)
665
+ print(msg, file=sys.stderr)
666
+ print('*** If this is a bug you want to report, please rerun with -v.',
667
+ file=sys.stderr)
668
+ return 1
lib/python3.12/site-packages/pip/_vendor/pygments/console.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ pygments.console
3
+ ~~~~~~~~~~~~~~~~
4
+
5
+ Format colored console output.
6
+
7
+ :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
8
+ :license: BSD, see LICENSE for details.
9
+ """
10
+
11
+ esc = "\x1b["
12
+
13
+ codes = {}
14
+ codes[""] = ""
15
+ codes["reset"] = esc + "39;49;00m"
16
+
17
+ codes["bold"] = esc + "01m"
18
+ codes["faint"] = esc + "02m"
19
+ codes["standout"] = esc + "03m"
20
+ codes["underline"] = esc + "04m"
21
+ codes["blink"] = esc + "05m"
22
+ codes["overline"] = esc + "06m"
23
+
24
+ dark_colors = ["black", "red", "green", "yellow", "blue",
25
+ "magenta", "cyan", "gray"]
26
+ light_colors = ["brightblack", "brightred", "brightgreen", "brightyellow", "brightblue",
27
+ "brightmagenta", "brightcyan", "white"]
28
+
29
+ x = 30
30
+ for dark, light in zip(dark_colors, light_colors):
31
+ codes[dark] = esc + "%im" % x
32
+ codes[light] = esc + "%im" % (60 + x)
33
+ x += 1
34
+
35
+ del dark, light, x
36
+
37
+ codes["white"] = codes["bold"]
38
+
39
+
40
+ def reset_color():
41
+ return codes["reset"]
42
+
43
+
44
+ def colorize(color_key, text):
45
+ return codes[color_key] + text + codes["reset"]
46
+
47
+
48
+ def ansiformat(attr, text):
49
+ """
50
+ Format ``text`` with a color and/or some attributes::
51
+
52
+ color normal color
53
+ *color* bold color
54
+ _color_ underlined color
55
+ +color+ blinking color
56
+ """
57
+ result = []
58
+ if attr[:1] == attr[-1:] == '+':
59
+ result.append(codes['blink'])
60
+ attr = attr[1:-1]
61
+ if attr[:1] == attr[-1:] == '*':
62
+ result.append(codes['bold'])
63
+ attr = attr[1:-1]
64
+ if attr[:1] == attr[-1:] == '_':
65
+ result.append(codes['underline'])
66
+ attr = attr[1:-1]
67
+ result.append(codes[attr])
68
+ result.append(text)
69
+ result.append(codes['reset'])
70
+ return ''.join(result)
lib/python3.12/site-packages/pip/_vendor/pygments/filter.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ pygments.filter
3
+ ~~~~~~~~~~~~~~~
4
+
5
+ Module that implements the default filter.
6
+
7
+ :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
8
+ :license: BSD, see LICENSE for details.
9
+ """
10
+
11
+
12
+ def apply_filters(stream, filters, lexer=None):
13
+ """
14
+ Use this method to apply an iterable of filters to
15
+ a stream. If lexer is given it's forwarded to the
16
+ filter, otherwise the filter receives `None`.
17
+ """
18
+ def _apply(filter_, stream):
19
+ yield from filter_.filter(lexer, stream)
20
+ for filter_ in filters:
21
+ stream = _apply(filter_, stream)
22
+ return stream
23
+
24
+
25
+ def simplefilter(f):
26
+ """
27
+ Decorator that converts a function into a filter::
28
+
29
+ @simplefilter
30
+ def lowercase(self, lexer, stream, options):
31
+ for ttype, value in stream:
32
+ yield ttype, value.lower()
33
+ """
34
+ return type(f.__name__, (FunctionFilter,), {
35
+ '__module__': getattr(f, '__module__'),
36
+ '__doc__': f.__doc__,
37
+ 'function': f,
38
+ })
39
+
40
+
41
+ class Filter:
42
+ """
43
+ Default filter. Subclass this class or use the `simplefilter`
44
+ decorator to create own filters.
45
+ """
46
+
47
+ def __init__(self, **options):
48
+ self.options = options
49
+
50
+ def filter(self, lexer, stream):
51
+ raise NotImplementedError()
52
+
53
+
54
+ class FunctionFilter(Filter):
55
+ """
56
+ Abstract class used by `simplefilter` to create simple
57
+ function filters on the fly. The `simplefilter` decorator
58
+ automatically creates subclasses of this class for
59
+ functions passed to it.
60
+ """
61
+ function = None
62
+
63
+ def __init__(self, **options):
64
+ if not hasattr(self, 'function'):
65
+ raise TypeError(f'{self.__class__.__name__!r} used without bound function')
66
+ Filter.__init__(self, **options)
67
+
68
+ def filter(self, lexer, stream):
69
+ # pylint: disable=not-callable
70
+ yield from self.function(lexer, stream, self.options)
lib/python3.12/site-packages/pip/_vendor/pygments/filters/__init__.py ADDED
@@ -0,0 +1,940 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ pygments.filters
3
+ ~~~~~~~~~~~~~~~~
4
+
5
+ Module containing filter lookup functions and default
6
+ filters.
7
+
8
+ :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
9
+ :license: BSD, see LICENSE for details.
10
+ """
11
+
12
+ import re
13
+
14
+ from pip._vendor.pygments.token import String, Comment, Keyword, Name, Error, Whitespace, \
15
+ string_to_tokentype
16
+ from pip._vendor.pygments.filter import Filter
17
+ from pip._vendor.pygments.util import get_list_opt, get_int_opt, get_bool_opt, \
18
+ get_choice_opt, ClassNotFound, OptionError
19
+ from pip._vendor.pygments.plugin import find_plugin_filters
20
+
21
+
22
+ def find_filter_class(filtername):
23
+ """Lookup a filter by name. Return None if not found."""
24
+ if filtername in FILTERS:
25
+ return FILTERS[filtername]
26
+ for name, cls in find_plugin_filters():
27
+ if name == filtername:
28
+ return cls
29
+ return None
30
+
31
+
32
+ def get_filter_by_name(filtername, **options):
33
+ """Return an instantiated filter.
34
+
35
+ Options are passed to the filter initializer if wanted.
36
+ Raise a ClassNotFound if not found.
37
+ """
38
+ cls = find_filter_class(filtername)
39
+ if cls:
40
+ return cls(**options)
41
+ else:
42
+ raise ClassNotFound(f'filter {filtername!r} not found')
43
+
44
+
45
+ def get_all_filters():
46
+ """Return a generator of all filter names."""
47
+ yield from FILTERS
48
+ for name, _ in find_plugin_filters():
49
+ yield name
50
+
51
+
52
+ def _replace_special(ttype, value, regex, specialttype,
53
+ replacefunc=lambda x: x):
54
+ last = 0
55
+ for match in regex.finditer(value):
56
+ start, end = match.start(), match.end()
57
+ if start != last:
58
+ yield ttype, value[last:start]
59
+ yield specialttype, replacefunc(value[start:end])
60
+ last = end
61
+ if last != len(value):
62
+ yield ttype, value[last:]
63
+
64
+
65
+ class CodeTagFilter(Filter):
66
+ """Highlight special code tags in comments and docstrings.
67
+
68
+ Options accepted:
69
+
70
+ `codetags` : list of strings
71
+ A list of strings that are flagged as code tags. The default is to
72
+ highlight ``XXX``, ``TODO``, ``FIXME``, ``BUG`` and ``NOTE``.
73
+
74
+ .. versionchanged:: 2.13
75
+ Now recognizes ``FIXME`` by default.
76
+ """
77
+
78
+ def __init__(self, **options):
79
+ Filter.__init__(self, **options)
80
+ tags = get_list_opt(options, 'codetags',
81
+ ['XXX', 'TODO', 'FIXME', 'BUG', 'NOTE'])
82
+ self.tag_re = re.compile(r'\b({})\b'.format('|'.join([
83
+ re.escape(tag) for tag in tags if tag
84
+ ])))
85
+
86
+ def filter(self, lexer, stream):
87
+ regex = self.tag_re
88
+ for ttype, value in stream:
89
+ if ttype in String.Doc or \
90
+ ttype in Comment and \
91
+ ttype not in Comment.Preproc:
92
+ yield from _replace_special(ttype, value, regex, Comment.Special)
93
+ else:
94
+ yield ttype, value
95
+
96
+
97
+ class SymbolFilter(Filter):
98
+ """Convert mathematical symbols such as \\<longrightarrow> in Isabelle
99
+ or \\longrightarrow in LaTeX into Unicode characters.
100
+
101
+ This is mostly useful for HTML or console output when you want to
102
+ approximate the source rendering you'd see in an IDE.
103
+
104
+ Options accepted:
105
+
106
+ `lang` : string
107
+ The symbol language. Must be one of ``'isabelle'`` or
108
+ ``'latex'``. The default is ``'isabelle'``.
109
+ """
110
+
111
+ latex_symbols = {
112
+ '\\alpha' : '\U000003b1',
113
+ '\\beta' : '\U000003b2',
114
+ '\\gamma' : '\U000003b3',
115
+ '\\delta' : '\U000003b4',
116
+ '\\varepsilon' : '\U000003b5',
117
+ '\\zeta' : '\U000003b6',
118
+ '\\eta' : '\U000003b7',
119
+ '\\vartheta' : '\U000003b8',
120
+ '\\iota' : '\U000003b9',
121
+ '\\kappa' : '\U000003ba',
122
+ '\\lambda' : '\U000003bb',
123
+ '\\mu' : '\U000003bc',
124
+ '\\nu' : '\U000003bd',
125
+ '\\xi' : '\U000003be',
126
+ '\\pi' : '\U000003c0',
127
+ '\\varrho' : '\U000003c1',
128
+ '\\sigma' : '\U000003c3',
129
+ '\\tau' : '\U000003c4',
130
+ '\\upsilon' : '\U000003c5',
131
+ '\\varphi' : '\U000003c6',
132
+ '\\chi' : '\U000003c7',
133
+ '\\psi' : '\U000003c8',
134
+ '\\omega' : '\U000003c9',
135
+ '\\Gamma' : '\U00000393',
136
+ '\\Delta' : '\U00000394',
137
+ '\\Theta' : '\U00000398',
138
+ '\\Lambda' : '\U0000039b',
139
+ '\\Xi' : '\U0000039e',
140
+ '\\Pi' : '\U000003a0',
141
+ '\\Sigma' : '\U000003a3',
142
+ '\\Upsilon' : '\U000003a5',
143
+ '\\Phi' : '\U000003a6',
144
+ '\\Psi' : '\U000003a8',
145
+ '\\Omega' : '\U000003a9',
146
+ '\\leftarrow' : '\U00002190',
147
+ '\\longleftarrow' : '\U000027f5',
148
+ '\\rightarrow' : '\U00002192',
149
+ '\\longrightarrow' : '\U000027f6',
150
+ '\\Leftarrow' : '\U000021d0',
151
+ '\\Longleftarrow' : '\U000027f8',
152
+ '\\Rightarrow' : '\U000021d2',
153
+ '\\Longrightarrow' : '\U000027f9',
154
+ '\\leftrightarrow' : '\U00002194',
155
+ '\\longleftrightarrow' : '\U000027f7',
156
+ '\\Leftrightarrow' : '\U000021d4',
157
+ '\\Longleftrightarrow' : '\U000027fa',
158
+ '\\mapsto' : '\U000021a6',
159
+ '\\longmapsto' : '\U000027fc',
160
+ '\\relbar' : '\U00002500',
161
+ '\\Relbar' : '\U00002550',
162
+ '\\hookleftarrow' : '\U000021a9',
163
+ '\\hookrightarrow' : '\U000021aa',
164
+ '\\leftharpoondown' : '\U000021bd',
165
+ '\\rightharpoondown' : '\U000021c1',
166
+ '\\leftharpoonup' : '\U000021bc',
167
+ '\\rightharpoonup' : '\U000021c0',
168
+ '\\rightleftharpoons' : '\U000021cc',
169
+ '\\leadsto' : '\U0000219d',
170
+ '\\downharpoonleft' : '\U000021c3',
171
+ '\\downharpoonright' : '\U000021c2',
172
+ '\\upharpoonleft' : '\U000021bf',
173
+ '\\upharpoonright' : '\U000021be',
174
+ '\\restriction' : '\U000021be',
175
+ '\\uparrow' : '\U00002191',
176
+ '\\Uparrow' : '\U000021d1',
177
+ '\\downarrow' : '\U00002193',
178
+ '\\Downarrow' : '\U000021d3',
179
+ '\\updownarrow' : '\U00002195',
180
+ '\\Updownarrow' : '\U000021d5',
181
+ '\\langle' : '\U000027e8',
182
+ '\\rangle' : '\U000027e9',
183
+ '\\lceil' : '\U00002308',
184
+ '\\rceil' : '\U00002309',
185
+ '\\lfloor' : '\U0000230a',
186
+ '\\rfloor' : '\U0000230b',
187
+ '\\flqq' : '\U000000ab',
188
+ '\\frqq' : '\U000000bb',
189
+ '\\bot' : '\U000022a5',
190
+ '\\top' : '\U000022a4',
191
+ '\\wedge' : '\U00002227',
192
+ '\\bigwedge' : '\U000022c0',
193
+ '\\vee' : '\U00002228',
194
+ '\\bigvee' : '\U000022c1',
195
+ '\\forall' : '\U00002200',
196
+ '\\exists' : '\U00002203',
197
+ '\\nexists' : '\U00002204',
198
+ '\\neg' : '\U000000ac',
199
+ '\\Box' : '\U000025a1',
200
+ '\\Diamond' : '\U000025c7',
201
+ '\\vdash' : '\U000022a2',
202
+ '\\models' : '\U000022a8',
203
+ '\\dashv' : '\U000022a3',
204
+ '\\surd' : '\U0000221a',
205
+ '\\le' : '\U00002264',
206
+ '\\ge' : '\U00002265',
207
+ '\\ll' : '\U0000226a',
208
+ '\\gg' : '\U0000226b',
209
+ '\\lesssim' : '\U00002272',
210
+ '\\gtrsim' : '\U00002273',
211
+ '\\lessapprox' : '\U00002a85',
212
+ '\\gtrapprox' : '\U00002a86',
213
+ '\\in' : '\U00002208',
214
+ '\\notin' : '\U00002209',
215
+ '\\subset' : '\U00002282',
216
+ '\\supset' : '\U00002283',
217
+ '\\subseteq' : '\U00002286',
218
+ '\\supseteq' : '\U00002287',
219
+ '\\sqsubset' : '\U0000228f',
220
+ '\\sqsupset' : '\U00002290',
221
+ '\\sqsubseteq' : '\U00002291',
222
+ '\\sqsupseteq' : '\U00002292',
223
+ '\\cap' : '\U00002229',
224
+ '\\bigcap' : '\U000022c2',
225
+ '\\cup' : '\U0000222a',
226
+ '\\bigcup' : '\U000022c3',
227
+ '\\sqcup' : '\U00002294',
228
+ '\\bigsqcup' : '\U00002a06',
229
+ '\\sqcap' : '\U00002293',
230
+ '\\Bigsqcap' : '\U00002a05',
231
+ '\\setminus' : '\U00002216',
232
+ '\\propto' : '\U0000221d',
233
+ '\\uplus' : '\U0000228e',
234
+ '\\bigplus' : '\U00002a04',
235
+ '\\sim' : '\U0000223c',
236
+ '\\doteq' : '\U00002250',
237
+ '\\simeq' : '\U00002243',
238
+ '\\approx' : '\U00002248',
239
+ '\\asymp' : '\U0000224d',
240
+ '\\cong' : '\U00002245',
241
+ '\\equiv' : '\U00002261',
242
+ '\\Join' : '\U000022c8',
243
+ '\\bowtie' : '\U00002a1d',
244
+ '\\prec' : '\U0000227a',
245
+ '\\succ' : '\U0000227b',
246
+ '\\preceq' : '\U0000227c',
247
+ '\\succeq' : '\U0000227d',
248
+ '\\parallel' : '\U00002225',
249
+ '\\mid' : '\U000000a6',
250
+ '\\pm' : '\U000000b1',
251
+ '\\mp' : '\U00002213',
252
+ '\\times' : '\U000000d7',
253
+ '\\div' : '\U000000f7',
254
+ '\\cdot' : '\U000022c5',
255
+ '\\star' : '\U000022c6',
256
+ '\\circ' : '\U00002218',
257
+ '\\dagger' : '\U00002020',
258
+ '\\ddagger' : '\U00002021',
259
+ '\\lhd' : '\U000022b2',
260
+ '\\rhd' : '\U000022b3',
261
+ '\\unlhd' : '\U000022b4',
262
+ '\\unrhd' : '\U000022b5',
263
+ '\\triangleleft' : '\U000025c3',
264
+ '\\triangleright' : '\U000025b9',
265
+ '\\triangle' : '\U000025b3',
266
+ '\\triangleq' : '\U0000225c',
267
+ '\\oplus' : '\U00002295',
268
+ '\\bigoplus' : '\U00002a01',
269
+ '\\otimes' : '\U00002297',
270
+ '\\bigotimes' : '\U00002a02',
271
+ '\\odot' : '\U00002299',
272
+ '\\bigodot' : '\U00002a00',
273
+ '\\ominus' : '\U00002296',
274
+ '\\oslash' : '\U00002298',
275
+ '\\dots' : '\U00002026',
276
+ '\\cdots' : '\U000022ef',
277
+ '\\sum' : '\U00002211',
278
+ '\\prod' : '\U0000220f',
279
+ '\\coprod' : '\U00002210',
280
+ '\\infty' : '\U0000221e',
281
+ '\\int' : '\U0000222b',
282
+ '\\oint' : '\U0000222e',
283
+ '\\clubsuit' : '\U00002663',
284
+ '\\diamondsuit' : '\U00002662',
285
+ '\\heartsuit' : '\U00002661',
286
+ '\\spadesuit' : '\U00002660',
287
+ '\\aleph' : '\U00002135',
288
+ '\\emptyset' : '\U00002205',
289
+ '\\nabla' : '\U00002207',
290
+ '\\partial' : '\U00002202',
291
+ '\\flat' : '\U0000266d',
292
+ '\\natural' : '\U0000266e',
293
+ '\\sharp' : '\U0000266f',
294
+ '\\angle' : '\U00002220',
295
+ '\\copyright' : '\U000000a9',
296
+ '\\textregistered' : '\U000000ae',
297
+ '\\textonequarter' : '\U000000bc',
298
+ '\\textonehalf' : '\U000000bd',
299
+ '\\textthreequarters' : '\U000000be',
300
+ '\\textordfeminine' : '\U000000aa',
301
+ '\\textordmasculine' : '\U000000ba',
302
+ '\\euro' : '\U000020ac',
303
+ '\\pounds' : '\U000000a3',
304
+ '\\yen' : '\U000000a5',
305
+ '\\textcent' : '\U000000a2',
306
+ '\\textcurrency' : '\U000000a4',
307
+ '\\textdegree' : '\U000000b0',
308
+ }
309
+
310
+ isabelle_symbols = {
311
+ '\\<zero>' : '\U0001d7ec',
312
+ '\\<one>' : '\U0001d7ed',
313
+ '\\<two>' : '\U0001d7ee',
314
+ '\\<three>' : '\U0001d7ef',
315
+ '\\<four>' : '\U0001d7f0',
316
+ '\\<five>' : '\U0001d7f1',
317
+ '\\<six>' : '\U0001d7f2',
318
+ '\\<seven>' : '\U0001d7f3',
319
+ '\\<eight>' : '\U0001d7f4',
320
+ '\\<nine>' : '\U0001d7f5',
321
+ '\\<A>' : '\U0001d49c',
322
+ '\\<B>' : '\U0000212c',
323
+ '\\<C>' : '\U0001d49e',
324
+ '\\<D>' : '\U0001d49f',
325
+ '\\<E>' : '\U00002130',
326
+ '\\<F>' : '\U00002131',
327
+ '\\<G>' : '\U0001d4a2',
328
+ '\\<H>' : '\U0000210b',
329
+ '\\<I>' : '\U00002110',
330
+ '\\<J>' : '\U0001d4a5',
331
+ '\\<K>' : '\U0001d4a6',
332
+ '\\<L>' : '\U00002112',
333
+ '\\<M>' : '\U00002133',
334
+ '\\<N>' : '\U0001d4a9',
335
+ '\\<O>' : '\U0001d4aa',
336
+ '\\<P>' : '\U0001d4ab',
337
+ '\\<Q>' : '\U0001d4ac',
338
+ '\\<R>' : '\U0000211b',
339
+ '\\<S>' : '\U0001d4ae',
340
+ '\\<T>' : '\U0001d4af',
341
+ '\\<U>' : '\U0001d4b0',
342
+ '\\<V>' : '\U0001d4b1',
343
+ '\\<W>' : '\U0001d4b2',
344
+ '\\<X>' : '\U0001d4b3',
345
+ '\\<Y>' : '\U0001d4b4',
346
+ '\\<Z>' : '\U0001d4b5',
347
+ '\\<a>' : '\U0001d5ba',
348
+ '\\<b>' : '\U0001d5bb',
349
+ '\\<c>' : '\U0001d5bc',
350
+ '\\<d>' : '\U0001d5bd',
351
+ '\\<e>' : '\U0001d5be',
352
+ '\\<f>' : '\U0001d5bf',
353
+ '\\<g>' : '\U0001d5c0',
354
+ '\\<h>' : '\U0001d5c1',
355
+ '\\<i>' : '\U0001d5c2',
356
+ '\\<j>' : '\U0001d5c3',
357
+ '\\<k>' : '\U0001d5c4',
358
+ '\\<l>' : '\U0001d5c5',
359
+ '\\<m>' : '\U0001d5c6',
360
+ '\\<n>' : '\U0001d5c7',
361
+ '\\<o>' : '\U0001d5c8',
362
+ '\\<p>' : '\U0001d5c9',
363
+ '\\<q>' : '\U0001d5ca',
364
+ '\\<r>' : '\U0001d5cb',
365
+ '\\<s>' : '\U0001d5cc',
366
+ '\\<t>' : '\U0001d5cd',
367
+ '\\<u>' : '\U0001d5ce',
368
+ '\\<v>' : '\U0001d5cf',
369
+ '\\<w>' : '\U0001d5d0',
370
+ '\\<x>' : '\U0001d5d1',
371
+ '\\<y>' : '\U0001d5d2',
372
+ '\\<z>' : '\U0001d5d3',
373
+ '\\<AA>' : '\U0001d504',
374
+ '\\<BB>' : '\U0001d505',
375
+ '\\<CC>' : '\U0000212d',
376
+ '\\<DD>' : '\U0001d507',
377
+ '\\<EE>' : '\U0001d508',
378
+ '\\<FF>' : '\U0001d509',
379
+ '\\<GG>' : '\U0001d50a',
380
+ '\\<HH>' : '\U0000210c',
381
+ '\\<II>' : '\U00002111',
382
+ '\\<JJ>' : '\U0001d50d',
383
+ '\\<KK>' : '\U0001d50e',
384
+ '\\<LL>' : '\U0001d50f',
385
+ '\\<MM>' : '\U0001d510',
386
+ '\\<NN>' : '\U0001d511',
387
+ '\\<OO>' : '\U0001d512',
388
+ '\\<PP>' : '\U0001d513',
389
+ '\\<QQ>' : '\U0001d514',
390
+ '\\<RR>' : '\U0000211c',
391
+ '\\<SS>' : '\U0001d516',
392
+ '\\<TT>' : '\U0001d517',
393
+ '\\<UU>' : '\U0001d518',
394
+ '\\<VV>' : '\U0001d519',
395
+ '\\<WW>' : '\U0001d51a',
396
+ '\\<XX>' : '\U0001d51b',
397
+ '\\<YY>' : '\U0001d51c',
398
+ '\\<ZZ>' : '\U00002128',
399
+ '\\<aa>' : '\U0001d51e',
400
+ '\\<bb>' : '\U0001d51f',
401
+ '\\<cc>' : '\U0001d520',
402
+ '\\<dd>' : '\U0001d521',
403
+ '\\<ee>' : '\U0001d522',
404
+ '\\<ff>' : '\U0001d523',
405
+ '\\<gg>' : '\U0001d524',
406
+ '\\<hh>' : '\U0001d525',
407
+ '\\<ii>' : '\U0001d526',
408
+ '\\<jj>' : '\U0001d527',
409
+ '\\<kk>' : '\U0001d528',
410
+ '\\<ll>' : '\U0001d529',
411
+ '\\<mm>' : '\U0001d52a',
412
+ '\\<nn>' : '\U0001d52b',
413
+ '\\<oo>' : '\U0001d52c',
414
+ '\\<pp>' : '\U0001d52d',
415
+ '\\<qq>' : '\U0001d52e',
416
+ '\\<rr>' : '\U0001d52f',
417
+ '\\<ss>' : '\U0001d530',
418
+ '\\<tt>' : '\U0001d531',
419
+ '\\<uu>' : '\U0001d532',
420
+ '\\<vv>' : '\U0001d533',
421
+ '\\<ww>' : '\U0001d534',
422
+ '\\<xx>' : '\U0001d535',
423
+ '\\<yy>' : '\U0001d536',
424
+ '\\<zz>' : '\U0001d537',
425
+ '\\<alpha>' : '\U000003b1',
426
+ '\\<beta>' : '\U000003b2',
427
+ '\\<gamma>' : '\U000003b3',
428
+ '\\<delta>' : '\U000003b4',
429
+ '\\<epsilon>' : '\U000003b5',
430
+ '\\<zeta>' : '\U000003b6',
431
+ '\\<eta>' : '\U000003b7',
432
+ '\\<theta>' : '\U000003b8',
433
+ '\\<iota>' : '\U000003b9',
434
+ '\\<kappa>' : '\U000003ba',
435
+ '\\<lambda>' : '\U000003bb',
436
+ '\\<mu>' : '\U000003bc',
437
+ '\\<nu>' : '\U000003bd',
438
+ '\\<xi>' : '\U000003be',
439
+ '\\<pi>' : '\U000003c0',
440
+ '\\<rho>' : '\U000003c1',
441
+ '\\<sigma>' : '\U000003c3',
442
+ '\\<tau>' : '\U000003c4',
443
+ '\\<upsilon>' : '\U000003c5',
444
+ '\\<phi>' : '\U000003c6',
445
+ '\\<chi>' : '\U000003c7',
446
+ '\\<psi>' : '\U000003c8',
447
+ '\\<omega>' : '\U000003c9',
448
+ '\\<Gamma>' : '\U00000393',
449
+ '\\<Delta>' : '\U00000394',
450
+ '\\<Theta>' : '\U00000398',
451
+ '\\<Lambda>' : '\U0000039b',
452
+ '\\<Xi>' : '\U0000039e',
453
+ '\\<Pi>' : '\U000003a0',
454
+ '\\<Sigma>' : '\U000003a3',
455
+ '\\<Upsilon>' : '\U000003a5',
456
+ '\\<Phi>' : '\U000003a6',
457
+ '\\<Psi>' : '\U000003a8',
458
+ '\\<Omega>' : '\U000003a9',
459
+ '\\<bool>' : '\U0001d539',
460
+ '\\<complex>' : '\U00002102',
461
+ '\\<nat>' : '\U00002115',
462
+ '\\<rat>' : '\U0000211a',
463
+ '\\<real>' : '\U0000211d',
464
+ '\\<int>' : '\U00002124',
465
+ '\\<leftarrow>' : '\U00002190',
466
+ '\\<longleftarrow>' : '\U000027f5',
467
+ '\\<rightarrow>' : '\U00002192',
468
+ '\\<longrightarrow>' : '\U000027f6',
469
+ '\\<Leftarrow>' : '\U000021d0',
470
+ '\\<Longleftarrow>' : '\U000027f8',
471
+ '\\<Rightarrow>' : '\U000021d2',
472
+ '\\<Longrightarrow>' : '\U000027f9',
473
+ '\\<leftrightarrow>' : '\U00002194',
474
+ '\\<longleftrightarrow>' : '\U000027f7',
475
+ '\\<Leftrightarrow>' : '\U000021d4',
476
+ '\\<Longleftrightarrow>' : '\U000027fa',
477
+ '\\<mapsto>' : '\U000021a6',
478
+ '\\<longmapsto>' : '\U000027fc',
479
+ '\\<midarrow>' : '\U00002500',
480
+ '\\<Midarrow>' : '\U00002550',
481
+ '\\<hookleftarrow>' : '\U000021a9',
482
+ '\\<hookrightarrow>' : '\U000021aa',
483
+ '\\<leftharpoondown>' : '\U000021bd',
484
+ '\\<rightharpoondown>' : '\U000021c1',
485
+ '\\<leftharpoonup>' : '\U000021bc',
486
+ '\\<rightharpoonup>' : '\U000021c0',
487
+ '\\<rightleftharpoons>' : '\U000021cc',
488
+ '\\<leadsto>' : '\U0000219d',
489
+ '\\<downharpoonleft>' : '\U000021c3',
490
+ '\\<downharpoonright>' : '\U000021c2',
491
+ '\\<upharpoonleft>' : '\U000021bf',
492
+ '\\<upharpoonright>' : '\U000021be',
493
+ '\\<restriction>' : '\U000021be',
494
+ '\\<Colon>' : '\U00002237',
495
+ '\\<up>' : '\U00002191',
496
+ '\\<Up>' : '\U000021d1',
497
+ '\\<down>' : '\U00002193',
498
+ '\\<Down>' : '\U000021d3',
499
+ '\\<updown>' : '\U00002195',
500
+ '\\<Updown>' : '\U000021d5',
501
+ '\\<langle>' : '\U000027e8',
502
+ '\\<rangle>' : '\U000027e9',
503
+ '\\<lceil>' : '\U00002308',
504
+ '\\<rceil>' : '\U00002309',
505
+ '\\<lfloor>' : '\U0000230a',
506
+ '\\<rfloor>' : '\U0000230b',
507
+ '\\<lparr>' : '\U00002987',
508
+ '\\<rparr>' : '\U00002988',
509
+ '\\<lbrakk>' : '\U000027e6',
510
+ '\\<rbrakk>' : '\U000027e7',
511
+ '\\<lbrace>' : '\U00002983',
512
+ '\\<rbrace>' : '\U00002984',
513
+ '\\<guillemotleft>' : '\U000000ab',
514
+ '\\<guillemotright>' : '\U000000bb',
515
+ '\\<bottom>' : '\U000022a5',
516
+ '\\<top>' : '\U000022a4',
517
+ '\\<and>' : '\U00002227',
518
+ '\\<And>' : '\U000022c0',
519
+ '\\<or>' : '\U00002228',
520
+ '\\<Or>' : '\U000022c1',
521
+ '\\<forall>' : '\U00002200',
522
+ '\\<exists>' : '\U00002203',
523
+ '\\<nexists>' : '\U00002204',
524
+ '\\<not>' : '\U000000ac',
525
+ '\\<box>' : '\U000025a1',
526
+ '\\<diamond>' : '\U000025c7',
527
+ '\\<turnstile>' : '\U000022a2',
528
+ '\\<Turnstile>' : '\U000022a8',
529
+ '\\<tturnstile>' : '\U000022a9',
530
+ '\\<TTurnstile>' : '\U000022ab',
531
+ '\\<stileturn>' : '\U000022a3',
532
+ '\\<surd>' : '\U0000221a',
533
+ '\\<le>' : '\U00002264',
534
+ '\\<ge>' : '\U00002265',
535
+ '\\<lless>' : '\U0000226a',
536
+ '\\<ggreater>' : '\U0000226b',
537
+ '\\<lesssim>' : '\U00002272',
538
+ '\\<greatersim>' : '\U00002273',
539
+ '\\<lessapprox>' : '\U00002a85',
540
+ '\\<greaterapprox>' : '\U00002a86',
541
+ '\\<in>' : '\U00002208',
542
+ '\\<notin>' : '\U00002209',
543
+ '\\<subset>' : '\U00002282',
544
+ '\\<supset>' : '\U00002283',
545
+ '\\<subseteq>' : '\U00002286',
546
+ '\\<supseteq>' : '\U00002287',
547
+ '\\<sqsubset>' : '\U0000228f',
548
+ '\\<sqsupset>' : '\U00002290',
549
+ '\\<sqsubseteq>' : '\U00002291',
550
+ '\\<sqsupseteq>' : '\U00002292',
551
+ '\\<inter>' : '\U00002229',
552
+ '\\<Inter>' : '\U000022c2',
553
+ '\\<union>' : '\U0000222a',
554
+ '\\<Union>' : '\U000022c3',
555
+ '\\<squnion>' : '\U00002294',
556
+ '\\<Squnion>' : '\U00002a06',
557
+ '\\<sqinter>' : '\U00002293',
558
+ '\\<Sqinter>' : '\U00002a05',
559
+ '\\<setminus>' : '\U00002216',
560
+ '\\<propto>' : '\U0000221d',
561
+ '\\<uplus>' : '\U0000228e',
562
+ '\\<Uplus>' : '\U00002a04',
563
+ '\\<noteq>' : '\U00002260',
564
+ '\\<sim>' : '\U0000223c',
565
+ '\\<doteq>' : '\U00002250',
566
+ '\\<simeq>' : '\U00002243',
567
+ '\\<approx>' : '\U00002248',
568
+ '\\<asymp>' : '\U0000224d',
569
+ '\\<cong>' : '\U00002245',
570
+ '\\<smile>' : '\U00002323',
571
+ '\\<equiv>' : '\U00002261',
572
+ '\\<frown>' : '\U00002322',
573
+ '\\<Join>' : '\U000022c8',
574
+ '\\<bowtie>' : '\U00002a1d',
575
+ '\\<prec>' : '\U0000227a',
576
+ '\\<succ>' : '\U0000227b',
577
+ '\\<preceq>' : '\U0000227c',
578
+ '\\<succeq>' : '\U0000227d',
579
+ '\\<parallel>' : '\U00002225',
580
+ '\\<bar>' : '\U000000a6',
581
+ '\\<plusminus>' : '\U000000b1',
582
+ '\\<minusplus>' : '\U00002213',
583
+ '\\<times>' : '\U000000d7',
584
+ '\\<div>' : '\U000000f7',
585
+ '\\<cdot>' : '\U000022c5',
586
+ '\\<star>' : '\U000022c6',
587
+ '\\<bullet>' : '\U00002219',
588
+ '\\<circ>' : '\U00002218',
589
+ '\\<dagger>' : '\U00002020',
590
+ '\\<ddagger>' : '\U00002021',
591
+ '\\<lhd>' : '\U000022b2',
592
+ '\\<rhd>' : '\U000022b3',
593
+ '\\<unlhd>' : '\U000022b4',
594
+ '\\<unrhd>' : '\U000022b5',
595
+ '\\<triangleleft>' : '\U000025c3',
596
+ '\\<triangleright>' : '\U000025b9',
597
+ '\\<triangle>' : '\U000025b3',
598
+ '\\<triangleq>' : '\U0000225c',
599
+ '\\<oplus>' : '\U00002295',
600
+ '\\<Oplus>' : '\U00002a01',
601
+ '\\<otimes>' : '\U00002297',
602
+ '\\<Otimes>' : '\U00002a02',
603
+ '\\<odot>' : '\U00002299',
604
+ '\\<Odot>' : '\U00002a00',
605
+ '\\<ominus>' : '\U00002296',
606
+ '\\<oslash>' : '\U00002298',
607
+ '\\<dots>' : '\U00002026',
608
+ '\\<cdots>' : '\U000022ef',
609
+ '\\<Sum>' : '\U00002211',
610
+ '\\<Prod>' : '\U0000220f',
611
+ '\\<Coprod>' : '\U00002210',
612
+ '\\<infinity>' : '\U0000221e',
613
+ '\\<integral>' : '\U0000222b',
614
+ '\\<ointegral>' : '\U0000222e',
615
+ '\\<clubsuit>' : '\U00002663',
616
+ '\\<diamondsuit>' : '\U00002662',
617
+ '\\<heartsuit>' : '\U00002661',
618
+ '\\<spadesuit>' : '\U00002660',
619
+ '\\<aleph>' : '\U00002135',
620
+ '\\<emptyset>' : '\U00002205',
621
+ '\\<nabla>' : '\U00002207',
622
+ '\\<partial>' : '\U00002202',
623
+ '\\<flat>' : '\U0000266d',
624
+ '\\<natural>' : '\U0000266e',
625
+ '\\<sharp>' : '\U0000266f',
626
+ '\\<angle>' : '\U00002220',
627
+ '\\<copyright>' : '\U000000a9',
628
+ '\\<registered>' : '\U000000ae',
629
+ '\\<hyphen>' : '\U000000ad',
630
+ '\\<inverse>' : '\U000000af',
631
+ '\\<onequarter>' : '\U000000bc',
632
+ '\\<onehalf>' : '\U000000bd',
633
+ '\\<threequarters>' : '\U000000be',
634
+ '\\<ordfeminine>' : '\U000000aa',
635
+ '\\<ordmasculine>' : '\U000000ba',
636
+ '\\<section>' : '\U000000a7',
637
+ '\\<paragraph>' : '\U000000b6',
638
+ '\\<exclamdown>' : '\U000000a1',
639
+ '\\<questiondown>' : '\U000000bf',
640
+ '\\<euro>' : '\U000020ac',
641
+ '\\<pounds>' : '\U000000a3',
642
+ '\\<yen>' : '\U000000a5',
643
+ '\\<cent>' : '\U000000a2',
644
+ '\\<currency>' : '\U000000a4',
645
+ '\\<degree>' : '\U000000b0',
646
+ '\\<amalg>' : '\U00002a3f',
647
+ '\\<mho>' : '\U00002127',
648
+ '\\<lozenge>' : '\U000025ca',
649
+ '\\<wp>' : '\U00002118',
650
+ '\\<wrong>' : '\U00002240',
651
+ '\\<struct>' : '\U000022c4',
652
+ '\\<acute>' : '\U000000b4',
653
+ '\\<index>' : '\U00000131',
654
+ '\\<dieresis>' : '\U000000a8',
655
+ '\\<cedilla>' : '\U000000b8',
656
+ '\\<hungarumlaut>' : '\U000002dd',
657
+ '\\<some>' : '\U000003f5',
658
+ '\\<newline>' : '\U000023ce',
659
+ '\\<open>' : '\U00002039',
660
+ '\\<close>' : '\U0000203a',
661
+ '\\<here>' : '\U00002302',
662
+ '\\<^sub>' : '\U000021e9',
663
+ '\\<^sup>' : '\U000021e7',
664
+ '\\<^bold>' : '\U00002759',
665
+ '\\<^bsub>' : '\U000021d8',
666
+ '\\<^esub>' : '\U000021d9',
667
+ '\\<^bsup>' : '\U000021d7',
668
+ '\\<^esup>' : '\U000021d6',
669
+ }
670
+
671
+ lang_map = {'isabelle' : isabelle_symbols, 'latex' : latex_symbols}
672
+
673
+ def __init__(self, **options):
674
+ Filter.__init__(self, **options)
675
+ lang = get_choice_opt(options, 'lang',
676
+ ['isabelle', 'latex'], 'isabelle')
677
+ self.symbols = self.lang_map[lang]
678
+
679
+ def filter(self, lexer, stream):
680
+ for ttype, value in stream:
681
+ if value in self.symbols:
682
+ yield ttype, self.symbols[value]
683
+ else:
684
+ yield ttype, value
685
+
686
+
687
+ class KeywordCaseFilter(Filter):
688
+ """Convert keywords to lowercase or uppercase or capitalize them, which
689
+ means first letter uppercase, rest lowercase.
690
+
691
+ This can be useful e.g. if you highlight Pascal code and want to adapt the
692
+ code to your styleguide.
693
+
694
+ Options accepted:
695
+
696
+ `case` : string
697
+ The casing to convert keywords to. Must be one of ``'lower'``,
698
+ ``'upper'`` or ``'capitalize'``. The default is ``'lower'``.
699
+ """
700
+
701
+ def __init__(self, **options):
702
+ Filter.__init__(self, **options)
703
+ case = get_choice_opt(options, 'case',
704
+ ['lower', 'upper', 'capitalize'], 'lower')
705
+ self.convert = getattr(str, case)
706
+
707
+ def filter(self, lexer, stream):
708
+ for ttype, value in stream:
709
+ if ttype in Keyword:
710
+ yield ttype, self.convert(value)
711
+ else:
712
+ yield ttype, value
713
+
714
+
715
+ class NameHighlightFilter(Filter):
716
+ """Highlight a normal Name (and Name.*) token with a different token type.
717
+
718
+ Example::
719
+
720
+ filter = NameHighlightFilter(
721
+ names=['foo', 'bar', 'baz'],
722
+ tokentype=Name.Function,
723
+ )
724
+
725
+ This would highlight the names "foo", "bar" and "baz"
726
+ as functions. `Name.Function` is the default token type.
727
+
728
+ Options accepted:
729
+
730
+ `names` : list of strings
731
+ A list of names that should be given the different token type.
732
+ There is no default.
733
+ `tokentype` : TokenType or string
734
+ A token type or a string containing a token type name that is
735
+ used for highlighting the strings in `names`. The default is
736
+ `Name.Function`.
737
+ """
738
+
739
+ def __init__(self, **options):
740
+ Filter.__init__(self, **options)
741
+ self.names = set(get_list_opt(options, 'names', []))
742
+ tokentype = options.get('tokentype')
743
+ if tokentype:
744
+ self.tokentype = string_to_tokentype(tokentype)
745
+ else:
746
+ self.tokentype = Name.Function
747
+
748
+ def filter(self, lexer, stream):
749
+ for ttype, value in stream:
750
+ if ttype in Name and value in self.names:
751
+ yield self.tokentype, value
752
+ else:
753
+ yield ttype, value
754
+
755
+
756
+ class ErrorToken(Exception):
757
+ pass
758
+
759
+
760
+ class RaiseOnErrorTokenFilter(Filter):
761
+ """Raise an exception when the lexer generates an error token.
762
+
763
+ Options accepted:
764
+
765
+ `excclass` : Exception class
766
+ The exception class to raise.
767
+ The default is `pygments.filters.ErrorToken`.
768
+
769
+ .. versionadded:: 0.8
770
+ """
771
+
772
+ def __init__(self, **options):
773
+ Filter.__init__(self, **options)
774
+ self.exception = options.get('excclass', ErrorToken)
775
+ try:
776
+ # issubclass() will raise TypeError if first argument is not a class
777
+ if not issubclass(self.exception, Exception):
778
+ raise TypeError
779
+ except TypeError:
780
+ raise OptionError('excclass option is not an exception class')
781
+
782
+ def filter(self, lexer, stream):
783
+ for ttype, value in stream:
784
+ if ttype is Error:
785
+ raise self.exception(value)
786
+ yield ttype, value
787
+
788
+
789
+ class VisibleWhitespaceFilter(Filter):
790
+ """Convert tabs, newlines and/or spaces to visible characters.
791
+
792
+ Options accepted:
793
+
794
+ `spaces` : string or bool
795
+ If this is a one-character string, spaces will be replaces by this string.
796
+ If it is another true value, spaces will be replaced by ``·`` (unicode
797
+ MIDDLE DOT). If it is a false value, spaces will not be replaced. The
798
+ default is ``False``.
799
+ `tabs` : string or bool
800
+ The same as for `spaces`, but the default replacement character is ``»``
801
+ (unicode RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK). The default value
802
+ is ``False``. Note: this will not work if the `tabsize` option for the
803
+ lexer is nonzero, as tabs will already have been expanded then.
804
+ `tabsize` : int
805
+ If tabs are to be replaced by this filter (see the `tabs` option), this
806
+ is the total number of characters that a tab should be expanded to.
807
+ The default is ``8``.
808
+ `newlines` : string or bool
809
+ The same as for `spaces`, but the default replacement character is ``¶``
810
+ (unicode PILCROW SIGN). The default value is ``False``.
811
+ `wstokentype` : bool
812
+ If true, give whitespace the special `Whitespace` token type. This allows
813
+ styling the visible whitespace differently (e.g. greyed out), but it can
814
+ disrupt background colors. The default is ``True``.
815
+
816
+ .. versionadded:: 0.8
817
+ """
818
+
819
+ def __init__(self, **options):
820
+ Filter.__init__(self, **options)
821
+ for name, default in [('spaces', '·'),
822
+ ('tabs', '»'),
823
+ ('newlines', '¶')]:
824
+ opt = options.get(name, False)
825
+ if isinstance(opt, str) and len(opt) == 1:
826
+ setattr(self, name, opt)
827
+ else:
828
+ setattr(self, name, (opt and default or ''))
829
+ tabsize = get_int_opt(options, 'tabsize', 8)
830
+ if self.tabs:
831
+ self.tabs += ' ' * (tabsize - 1)
832
+ if self.newlines:
833
+ self.newlines += '\n'
834
+ self.wstt = get_bool_opt(options, 'wstokentype', True)
835
+
836
+ def filter(self, lexer, stream):
837
+ if self.wstt:
838
+ spaces = self.spaces or ' '
839
+ tabs = self.tabs or '\t'
840
+ newlines = self.newlines or '\n'
841
+ regex = re.compile(r'\s')
842
+
843
+ def replacefunc(wschar):
844
+ if wschar == ' ':
845
+ return spaces
846
+ elif wschar == '\t':
847
+ return tabs
848
+ elif wschar == '\n':
849
+ return newlines
850
+ return wschar
851
+
852
+ for ttype, value in stream:
853
+ yield from _replace_special(ttype, value, regex, Whitespace,
854
+ replacefunc)
855
+ else:
856
+ spaces, tabs, newlines = self.spaces, self.tabs, self.newlines
857
+ # simpler processing
858
+ for ttype, value in stream:
859
+ if spaces:
860
+ value = value.replace(' ', spaces)
861
+ if tabs:
862
+ value = value.replace('\t', tabs)
863
+ if newlines:
864
+ value = value.replace('\n', newlines)
865
+ yield ttype, value
866
+
867
+
868
+ class GobbleFilter(Filter):
869
+ """Gobbles source code lines (eats initial characters).
870
+
871
+ This filter drops the first ``n`` characters off every line of code. This
872
+ may be useful when the source code fed to the lexer is indented by a fixed
873
+ amount of space that isn't desired in the output.
874
+
875
+ Options accepted:
876
+
877
+ `n` : int
878
+ The number of characters to gobble.
879
+
880
+ .. versionadded:: 1.2
881
+ """
882
+ def __init__(self, **options):
883
+ Filter.__init__(self, **options)
884
+ self.n = get_int_opt(options, 'n', 0)
885
+
886
+ def gobble(self, value, left):
887
+ if left < len(value):
888
+ return value[left:], 0
889
+ else:
890
+ return '', left - len(value)
891
+
892
+ def filter(self, lexer, stream):
893
+ n = self.n
894
+ left = n # How many characters left to gobble.
895
+ for ttype, value in stream:
896
+ # Remove ``left`` tokens from first line, ``n`` from all others.
897
+ parts = value.split('\n')
898
+ (parts[0], left) = self.gobble(parts[0], left)
899
+ for i in range(1, len(parts)):
900
+ (parts[i], left) = self.gobble(parts[i], n)
901
+ value = '\n'.join(parts)
902
+
903
+ if value != '':
904
+ yield ttype, value
905
+
906
+
907
+ class TokenMergeFilter(Filter):
908
+ """Merges consecutive tokens with the same token type in the output
909
+ stream of a lexer.
910
+
911
+ .. versionadded:: 1.2
912
+ """
913
+ def __init__(self, **options):
914
+ Filter.__init__(self, **options)
915
+
916
+ def filter(self, lexer, stream):
917
+ current_type = None
918
+ current_value = None
919
+ for ttype, value in stream:
920
+ if ttype is current_type:
921
+ current_value += value
922
+ else:
923
+ if current_type is not None:
924
+ yield current_type, current_value
925
+ current_type = ttype
926
+ current_value = value
927
+ if current_type is not None:
928
+ yield current_type, current_value
929
+
930
+
931
+ FILTERS = {
932
+ 'codetagify': CodeTagFilter,
933
+ 'keywordcase': KeywordCaseFilter,
934
+ 'highlight': NameHighlightFilter,
935
+ 'raiseonerror': RaiseOnErrorTokenFilter,
936
+ 'whitespace': VisibleWhitespaceFilter,
937
+ 'gobble': GobbleFilter,
938
+ 'tokenmerge': TokenMergeFilter,
939
+ 'symbols': SymbolFilter,
940
+ }
lib/python3.12/site-packages/pip/_vendor/pygments/filters/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (37.9 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/formatter.py ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ pygments.formatter
3
+ ~~~~~~~~~~~~~~~~~~
4
+
5
+ Base formatter class.
6
+
7
+ :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
8
+ :license: BSD, see LICENSE for details.
9
+ """
10
+
11
+ import codecs
12
+
13
+ from pip._vendor.pygments.util import get_bool_opt
14
+ from pip._vendor.pygments.styles import get_style_by_name
15
+
16
+ __all__ = ['Formatter']
17
+
18
+
19
+ def _lookup_style(style):
20
+ if isinstance(style, str):
21
+ return get_style_by_name(style)
22
+ return style
23
+
24
+
25
+ class Formatter:
26
+ """
27
+ Converts a token stream to text.
28
+
29
+ Formatters should have attributes to help selecting them. These
30
+ are similar to the corresponding :class:`~pygments.lexer.Lexer`
31
+ attributes.
32
+
33
+ .. autoattribute:: name
34
+ :no-value:
35
+
36
+ .. autoattribute:: aliases
37
+ :no-value:
38
+
39
+ .. autoattribute:: filenames
40
+ :no-value:
41
+
42
+ You can pass options as keyword arguments to the constructor.
43
+ All formatters accept these basic options:
44
+
45
+ ``style``
46
+ The style to use, can be a string or a Style subclass
47
+ (default: "default"). Not used by e.g. the
48
+ TerminalFormatter.
49
+ ``full``
50
+ Tells the formatter to output a "full" document, i.e.
51
+ a complete self-contained document. This doesn't have
52
+ any effect for some formatters (default: false).
53
+ ``title``
54
+ If ``full`` is true, the title that should be used to
55
+ caption the document (default: '').
56
+ ``encoding``
57
+ If given, must be an encoding name. This will be used to
58
+ convert the Unicode token strings to byte strings in the
59
+ output. If it is "" or None, Unicode strings will be written
60
+ to the output file, which most file-like objects do not
61
+ support (default: None).
62
+ ``outencoding``
63
+ Overrides ``encoding`` if given.
64
+
65
+ """
66
+
67
+ #: Full name for the formatter, in human-readable form.
68
+ name = None
69
+
70
+ #: A list of short, unique identifiers that can be used to lookup
71
+ #: the formatter from a list, e.g. using :func:`.get_formatter_by_name()`.
72
+ aliases = []
73
+
74
+ #: A list of fnmatch patterns that match filenames for which this
75
+ #: formatter can produce output. The patterns in this list should be unique
76
+ #: among all formatters.
77
+ filenames = []
78
+
79
+ #: If True, this formatter outputs Unicode strings when no encoding
80
+ #: option is given.
81
+ unicodeoutput = True
82
+
83
+ def __init__(self, **options):
84
+ """
85
+ As with lexers, this constructor takes arbitrary optional arguments,
86
+ and if you override it, you should first process your own options, then
87
+ call the base class implementation.
88
+ """
89
+ self.style = _lookup_style(options.get('style', 'default'))
90
+ self.full = get_bool_opt(options, 'full', False)
91
+ self.title = options.get('title', '')
92
+ self.encoding = options.get('encoding', None) or None
93
+ if self.encoding in ('guess', 'chardet'):
94
+ # can happen for e.g. pygmentize -O encoding=guess
95
+ self.encoding = 'utf-8'
96
+ self.encoding = options.get('outencoding') or self.encoding
97
+ self.options = options
98
+
99
+ def get_style_defs(self, arg=''):
100
+ """
101
+ This method must return statements or declarations suitable to define
102
+ the current style for subsequent highlighted text (e.g. CSS classes
103
+ in the `HTMLFormatter`).
104
+
105
+ The optional argument `arg` can be used to modify the generation and
106
+ is formatter dependent (it is standardized because it can be given on
107
+ the command line).
108
+
109
+ This method is called by the ``-S`` :doc:`command-line option <cmdline>`,
110
+ the `arg` is then given by the ``-a`` option.
111
+ """
112
+ return ''
113
+
114
+ def format(self, tokensource, outfile):
115
+ """
116
+ This method must format the tokens from the `tokensource` iterable and
117
+ write the formatted version to the file object `outfile`.
118
+
119
+ Formatter options can control how exactly the tokens are converted.
120
+ """
121
+ if self.encoding:
122
+ # wrap the outfile in a StreamWriter
123
+ outfile = codecs.lookup(self.encoding)[3](outfile)
124
+ return self.format_unencoded(tokensource, outfile)
125
+
126
+ # Allow writing Formatter[str] or Formatter[bytes]. That's equivalent to
127
+ # Formatter. This helps when using third-party type stubs from typeshed.
128
+ def __class_getitem__(cls, name):
129
+ return cls
lib/python3.12/site-packages/pip/_vendor/pygments/lexer.py ADDED
@@ -0,0 +1,963 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ pygments.lexer
3
+ ~~~~~~~~~~~~~~
4
+
5
+ Base lexer classes.
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 time
14
+
15
+ from pip._vendor.pygments.filter import apply_filters, Filter
16
+ from pip._vendor.pygments.filters import get_filter_by_name
17
+ from pip._vendor.pygments.token import Error, Text, Other, Whitespace, _TokenType
18
+ from pip._vendor.pygments.util import get_bool_opt, get_int_opt, get_list_opt, \
19
+ make_analysator, Future, guess_decode
20
+ from pip._vendor.pygments.regexopt import regex_opt
21
+
22
+ __all__ = ['Lexer', 'RegexLexer', 'ExtendedRegexLexer', 'DelegatingLexer',
23
+ 'LexerContext', 'include', 'inherit', 'bygroups', 'using', 'this',
24
+ 'default', 'words', 'line_re']
25
+
26
+ line_re = re.compile('.*?\n')
27
+
28
+ _encoding_map = [(b'\xef\xbb\xbf', 'utf-8'),
29
+ (b'\xff\xfe\0\0', 'utf-32'),
30
+ (b'\0\0\xfe\xff', 'utf-32be'),
31
+ (b'\xff\xfe', 'utf-16'),
32
+ (b'\xfe\xff', 'utf-16be')]
33
+
34
+ _default_analyse = staticmethod(lambda x: 0.0)
35
+
36
+
37
+ class LexerMeta(type):
38
+ """
39
+ This metaclass automagically converts ``analyse_text`` methods into
40
+ static methods which always return float values.
41
+ """
42
+
43
+ def __new__(mcs, name, bases, d):
44
+ if 'analyse_text' in d:
45
+ d['analyse_text'] = make_analysator(d['analyse_text'])
46
+ return type.__new__(mcs, name, bases, d)
47
+
48
+
49
+ class Lexer(metaclass=LexerMeta):
50
+ """
51
+ Lexer for a specific language.
52
+
53
+ See also :doc:`lexerdevelopment`, a high-level guide to writing
54
+ lexers.
55
+
56
+ Lexer classes have attributes used for choosing the most appropriate
57
+ lexer based on various criteria.
58
+
59
+ .. autoattribute:: name
60
+ :no-value:
61
+ .. autoattribute:: aliases
62
+ :no-value:
63
+ .. autoattribute:: filenames
64
+ :no-value:
65
+ .. autoattribute:: alias_filenames
66
+ .. autoattribute:: mimetypes
67
+ :no-value:
68
+ .. autoattribute:: priority
69
+
70
+ Lexers included in Pygments should have two additional attributes:
71
+
72
+ .. autoattribute:: url
73
+ :no-value:
74
+ .. autoattribute:: version_added
75
+ :no-value:
76
+
77
+ Lexers included in Pygments may have additional attributes:
78
+
79
+ .. autoattribute:: _example
80
+ :no-value:
81
+
82
+ You can pass options to the constructor. The basic options recognized
83
+ by all lexers and processed by the base `Lexer` class are:
84
+
85
+ ``stripnl``
86
+ Strip leading and trailing newlines from the input (default: True).
87
+ ``stripall``
88
+ Strip all leading and trailing whitespace from the input
89
+ (default: False).
90
+ ``ensurenl``
91
+ Make sure that the input ends with a newline (default: True). This
92
+ is required for some lexers that consume input linewise.
93
+
94
+ .. versionadded:: 1.3
95
+
96
+ ``tabsize``
97
+ If given and greater than 0, expand tabs in the input (default: 0).
98
+ ``encoding``
99
+ If given, must be an encoding name. This encoding will be used to
100
+ convert the input string to Unicode, if it is not already a Unicode
101
+ string (default: ``'guess'``, which uses a simple UTF-8 / Locale /
102
+ Latin1 detection. Can also be ``'chardet'`` to use the chardet
103
+ library, if it is installed.
104
+ ``inencoding``
105
+ Overrides the ``encoding`` if given.
106
+ """
107
+
108
+ #: Full name of the lexer, in human-readable form
109
+ name = None
110
+
111
+ #: A list of short, unique identifiers that can be used to look
112
+ #: up the lexer from a list, e.g., using `get_lexer_by_name()`.
113
+ aliases = []
114
+
115
+ #: A list of `fnmatch` patterns that match filenames which contain
116
+ #: content for this lexer. The patterns in this list should be unique among
117
+ #: all lexers.
118
+ filenames = []
119
+
120
+ #: A list of `fnmatch` patterns that match filenames which may or may not
121
+ #: contain content for this lexer. This list is used by the
122
+ #: :func:`.guess_lexer_for_filename()` function, to determine which lexers
123
+ #: are then included in guessing the correct one. That means that
124
+ #: e.g. every lexer for HTML and a template language should include
125
+ #: ``\*.html`` in this list.
126
+ alias_filenames = []
127
+
128
+ #: A list of MIME types for content that can be lexed with this lexer.
129
+ mimetypes = []
130
+
131
+ #: Priority, should multiple lexers match and no content is provided
132
+ priority = 0
133
+
134
+ #: URL of the language specification/definition. Used in the Pygments
135
+ #: documentation. Set to an empty string to disable.
136
+ url = None
137
+
138
+ #: Version of Pygments in which the lexer was added.
139
+ version_added = None
140
+
141
+ #: Example file name. Relative to the ``tests/examplefiles`` directory.
142
+ #: This is used by the documentation generator to show an example.
143
+ _example = None
144
+
145
+ def __init__(self, **options):
146
+ """
147
+ This constructor takes arbitrary options as keyword arguments.
148
+ Every subclass must first process its own options and then call
149
+ the `Lexer` constructor, since it processes the basic
150
+ options like `stripnl`.
151
+
152
+ An example looks like this:
153
+
154
+ .. sourcecode:: python
155
+
156
+ def __init__(self, **options):
157
+ self.compress = options.get('compress', '')
158
+ Lexer.__init__(self, **options)
159
+
160
+ As these options must all be specifiable as strings (due to the
161
+ command line usage), there are various utility functions
162
+ available to help with that, see `Utilities`_.
163
+ """
164
+ self.options = options
165
+ self.stripnl = get_bool_opt(options, 'stripnl', True)
166
+ self.stripall = get_bool_opt(options, 'stripall', False)
167
+ self.ensurenl = get_bool_opt(options, 'ensurenl', True)
168
+ self.tabsize = get_int_opt(options, 'tabsize', 0)
169
+ self.encoding = options.get('encoding', 'guess')
170
+ self.encoding = options.get('inencoding') or self.encoding
171
+ self.filters = []
172
+ for filter_ in get_list_opt(options, 'filters', ()):
173
+ self.add_filter(filter_)
174
+
175
+ def __repr__(self):
176
+ if self.options:
177
+ return f'<pygments.lexers.{self.__class__.__name__} with {self.options!r}>'
178
+ else:
179
+ return f'<pygments.lexers.{self.__class__.__name__}>'
180
+
181
+ def add_filter(self, filter_, **options):
182
+ """
183
+ Add a new stream filter to this lexer.
184
+ """
185
+ if not isinstance(filter_, Filter):
186
+ filter_ = get_filter_by_name(filter_, **options)
187
+ self.filters.append(filter_)
188
+
189
+ def analyse_text(text):
190
+ """
191
+ A static method which is called for lexer guessing.
192
+
193
+ It should analyse the text and return a float in the range
194
+ from ``0.0`` to ``1.0``. If it returns ``0.0``, the lexer
195
+ will not be selected as the most probable one, if it returns
196
+ ``1.0``, it will be selected immediately. This is used by
197
+ `guess_lexer`.
198
+
199
+ The `LexerMeta` metaclass automatically wraps this function so
200
+ that it works like a static method (no ``self`` or ``cls``
201
+ parameter) and the return value is automatically converted to
202
+ `float`. If the return value is an object that is boolean `False`
203
+ it's the same as if the return values was ``0.0``.
204
+ """
205
+
206
+ def _preprocess_lexer_input(self, text):
207
+ """Apply preprocessing such as decoding the input, removing BOM and normalizing newlines."""
208
+
209
+ if not isinstance(text, str):
210
+ if self.encoding == 'guess':
211
+ text, _ = guess_decode(text)
212
+ elif self.encoding == 'chardet':
213
+ try:
214
+ # pip vendoring note: this code is not reachable by pip,
215
+ # removed import of chardet to make it clear.
216
+ raise ImportError('chardet is not vendored by pip')
217
+ except ImportError as e:
218
+ raise ImportError('To enable chardet encoding guessing, '
219
+ 'please install the chardet library '
220
+ 'from http://chardet.feedparser.org/') from e
221
+ # check for BOM first
222
+ decoded = None
223
+ for bom, encoding in _encoding_map:
224
+ if text.startswith(bom):
225
+ decoded = text[len(bom):].decode(encoding, 'replace')
226
+ break
227
+ # no BOM found, so use chardet
228
+ if decoded is None:
229
+ enc = chardet.detect(text[:1024]) # Guess using first 1KB
230
+ decoded = text.decode(enc.get('encoding') or 'utf-8',
231
+ 'replace')
232
+ text = decoded
233
+ else:
234
+ text = text.decode(self.encoding)
235
+ if text.startswith('\ufeff'):
236
+ text = text[len('\ufeff'):]
237
+ else:
238
+ if text.startswith('\ufeff'):
239
+ text = text[len('\ufeff'):]
240
+
241
+ # text now *is* a unicode string
242
+ text = text.replace('\r\n', '\n')
243
+ text = text.replace('\r', '\n')
244
+ if self.stripall:
245
+ text = text.strip()
246
+ elif self.stripnl:
247
+ text = text.strip('\n')
248
+ if self.tabsize > 0:
249
+ text = text.expandtabs(self.tabsize)
250
+ if self.ensurenl and not text.endswith('\n'):
251
+ text += '\n'
252
+
253
+ return text
254
+
255
+ def get_tokens(self, text, unfiltered=False):
256
+ """
257
+ This method is the basic interface of a lexer. It is called by
258
+ the `highlight()` function. It must process the text and return an
259
+ iterable of ``(tokentype, value)`` pairs from `text`.
260
+
261
+ Normally, you don't need to override this method. The default
262
+ implementation processes the options recognized by all lexers
263
+ (`stripnl`, `stripall` and so on), and then yields all tokens
264
+ from `get_tokens_unprocessed()`, with the ``index`` dropped.
265
+
266
+ If `unfiltered` is set to `True`, the filtering mechanism is
267
+ bypassed even if filters are defined.
268
+ """
269
+ text = self._preprocess_lexer_input(text)
270
+
271
+ def streamer():
272
+ for _, t, v in self.get_tokens_unprocessed(text):
273
+ yield t, v
274
+ stream = streamer()
275
+ if not unfiltered:
276
+ stream = apply_filters(stream, self.filters, self)
277
+ return stream
278
+
279
+ def get_tokens_unprocessed(self, text):
280
+ """
281
+ This method should process the text and return an iterable of
282
+ ``(index, tokentype, value)`` tuples where ``index`` is the starting
283
+ position of the token within the input text.
284
+
285
+ It must be overridden by subclasses. It is recommended to
286
+ implement it as a generator to maximize effectiveness.
287
+ """
288
+ raise NotImplementedError
289
+
290
+
291
+ class DelegatingLexer(Lexer):
292
+ """
293
+ This lexer takes two lexer as arguments. A root lexer and
294
+ a language lexer. First everything is scanned using the language
295
+ lexer, afterwards all ``Other`` tokens are lexed using the root
296
+ lexer.
297
+
298
+ The lexers from the ``template`` lexer package use this base lexer.
299
+ """
300
+
301
+ def __init__(self, _root_lexer, _language_lexer, _needle=Other, **options):
302
+ self.root_lexer = _root_lexer(**options)
303
+ self.language_lexer = _language_lexer(**options)
304
+ self.needle = _needle
305
+ Lexer.__init__(self, **options)
306
+
307
+ def get_tokens_unprocessed(self, text):
308
+ buffered = ''
309
+ insertions = []
310
+ lng_buffer = []
311
+ for i, t, v in self.language_lexer.get_tokens_unprocessed(text):
312
+ if t is self.needle:
313
+ if lng_buffer:
314
+ insertions.append((len(buffered), lng_buffer))
315
+ lng_buffer = []
316
+ buffered += v
317
+ else:
318
+ lng_buffer.append((i, t, v))
319
+ if lng_buffer:
320
+ insertions.append((len(buffered), lng_buffer))
321
+ return do_insertions(insertions,
322
+ self.root_lexer.get_tokens_unprocessed(buffered))
323
+
324
+
325
+ # ------------------------------------------------------------------------------
326
+ # RegexLexer and ExtendedRegexLexer
327
+ #
328
+
329
+
330
+ class include(str): # pylint: disable=invalid-name
331
+ """
332
+ Indicates that a state should include rules from another state.
333
+ """
334
+ pass
335
+
336
+
337
+ class _inherit:
338
+ """
339
+ Indicates the a state should inherit from its superclass.
340
+ """
341
+ def __repr__(self):
342
+ return 'inherit'
343
+
344
+ inherit = _inherit() # pylint: disable=invalid-name
345
+
346
+
347
+ class combined(tuple): # pylint: disable=invalid-name
348
+ """
349
+ Indicates a state combined from multiple states.
350
+ """
351
+
352
+ def __new__(cls, *args):
353
+ return tuple.__new__(cls, args)
354
+
355
+ def __init__(self, *args):
356
+ # tuple.__init__ doesn't do anything
357
+ pass
358
+
359
+
360
+ class _PseudoMatch:
361
+ """
362
+ A pseudo match object constructed from a string.
363
+ """
364
+
365
+ def __init__(self, start, text):
366
+ self._text = text
367
+ self._start = start
368
+
369
+ def start(self, arg=None):
370
+ return self._start
371
+
372
+ def end(self, arg=None):
373
+ return self._start + len(self._text)
374
+
375
+ def group(self, arg=None):
376
+ if arg:
377
+ raise IndexError('No such group')
378
+ return self._text
379
+
380
+ def groups(self):
381
+ return (self._text,)
382
+
383
+ def groupdict(self):
384
+ return {}
385
+
386
+
387
+ def bygroups(*args):
388
+ """
389
+ Callback that yields multiple actions for each group in the match.
390
+ """
391
+ def callback(lexer, match, ctx=None):
392
+ for i, action in enumerate(args):
393
+ if action is None:
394
+ continue
395
+ elif type(action) is _TokenType:
396
+ data = match.group(i + 1)
397
+ if data:
398
+ yield match.start(i + 1), action, data
399
+ else:
400
+ data = match.group(i + 1)
401
+ if data is not None:
402
+ if ctx:
403
+ ctx.pos = match.start(i + 1)
404
+ for item in action(lexer,
405
+ _PseudoMatch(match.start(i + 1), data), ctx):
406
+ if item:
407
+ yield item
408
+ if ctx:
409
+ ctx.pos = match.end()
410
+ return callback
411
+
412
+
413
+ class _This:
414
+ """
415
+ Special singleton used for indicating the caller class.
416
+ Used by ``using``.
417
+ """
418
+
419
+ this = _This()
420
+
421
+
422
+ def using(_other, **kwargs):
423
+ """
424
+ Callback that processes the match with a different lexer.
425
+
426
+ The keyword arguments are forwarded to the lexer, except `state` which
427
+ is handled separately.
428
+
429
+ `state` specifies the state that the new lexer will start in, and can
430
+ be an enumerable such as ('root', 'inline', 'string') or a simple
431
+ string which is assumed to be on top of the root state.
432
+
433
+ Note: For that to work, `_other` must not be an `ExtendedRegexLexer`.
434
+ """
435
+ gt_kwargs = {}
436
+ if 'state' in kwargs:
437
+ s = kwargs.pop('state')
438
+ if isinstance(s, (list, tuple)):
439
+ gt_kwargs['stack'] = s
440
+ else:
441
+ gt_kwargs['stack'] = ('root', s)
442
+
443
+ if _other is this:
444
+ def callback(lexer, match, ctx=None):
445
+ # if keyword arguments are given the callback
446
+ # function has to create a new lexer instance
447
+ if kwargs:
448
+ # XXX: cache that somehow
449
+ kwargs.update(lexer.options)
450
+ lx = lexer.__class__(**kwargs)
451
+ else:
452
+ lx = lexer
453
+ s = match.start()
454
+ for i, t, v in lx.get_tokens_unprocessed(match.group(), **gt_kwargs):
455
+ yield i + s, t, v
456
+ if ctx:
457
+ ctx.pos = match.end()
458
+ else:
459
+ def callback(lexer, match, ctx=None):
460
+ # XXX: cache that somehow
461
+ kwargs.update(lexer.options)
462
+ lx = _other(**kwargs)
463
+
464
+ s = match.start()
465
+ for i, t, v in lx.get_tokens_unprocessed(match.group(), **gt_kwargs):
466
+ yield i + s, t, v
467
+ if ctx:
468
+ ctx.pos = match.end()
469
+ return callback
470
+
471
+
472
+ class default:
473
+ """
474
+ Indicates a state or state action (e.g. #pop) to apply.
475
+ For example default('#pop') is equivalent to ('', Token, '#pop')
476
+ Note that state tuples may be used as well.
477
+
478
+ .. versionadded:: 2.0
479
+ """
480
+ def __init__(self, state):
481
+ self.state = state
482
+
483
+
484
+ class words(Future):
485
+ """
486
+ Indicates a list of literal words that is transformed into an optimized
487
+ regex that matches any of the words.
488
+
489
+ .. versionadded:: 2.0
490
+ """
491
+ def __init__(self, words, prefix='', suffix=''):
492
+ self.words = words
493
+ self.prefix = prefix
494
+ self.suffix = suffix
495
+
496
+ def get(self):
497
+ return regex_opt(self.words, prefix=self.prefix, suffix=self.suffix)
498
+
499
+
500
+ class RegexLexerMeta(LexerMeta):
501
+ """
502
+ Metaclass for RegexLexer, creates the self._tokens attribute from
503
+ self.tokens on the first instantiation.
504
+ """
505
+
506
+ def _process_regex(cls, regex, rflags, state):
507
+ """Preprocess the regular expression component of a token definition."""
508
+ if isinstance(regex, Future):
509
+ regex = regex.get()
510
+ return re.compile(regex, rflags).match
511
+
512
+ def _process_token(cls, token):
513
+ """Preprocess the token component of a token definition."""
514
+ assert type(token) is _TokenType or callable(token), \
515
+ f'token type must be simple type or callable, not {token!r}'
516
+ return token
517
+
518
+ def _process_new_state(cls, new_state, unprocessed, processed):
519
+ """Preprocess the state transition action of a token definition."""
520
+ if isinstance(new_state, str):
521
+ # an existing state
522
+ if new_state == '#pop':
523
+ return -1
524
+ elif new_state in unprocessed:
525
+ return (new_state,)
526
+ elif new_state == '#push':
527
+ return new_state
528
+ elif new_state[:5] == '#pop:':
529
+ return -int(new_state[5:])
530
+ else:
531
+ assert False, f'unknown new state {new_state!r}'
532
+ elif isinstance(new_state, combined):
533
+ # combine a new state from existing ones
534
+ tmp_state = '_tmp_%d' % cls._tmpname
535
+ cls._tmpname += 1
536
+ itokens = []
537
+ for istate in new_state:
538
+ assert istate != new_state, f'circular state ref {istate!r}'
539
+ itokens.extend(cls._process_state(unprocessed,
540
+ processed, istate))
541
+ processed[tmp_state] = itokens
542
+ return (tmp_state,)
543
+ elif isinstance(new_state, tuple):
544
+ # push more than one state
545
+ for istate in new_state:
546
+ assert (istate in unprocessed or
547
+ istate in ('#pop', '#push')), \
548
+ 'unknown new state ' + istate
549
+ return new_state
550
+ else:
551
+ assert False, f'unknown new state def {new_state!r}'
552
+
553
+ def _process_state(cls, unprocessed, processed, state):
554
+ """Preprocess a single state definition."""
555
+ assert isinstance(state, str), f"wrong state name {state!r}"
556
+ assert state[0] != '#', f"invalid state name {state!r}"
557
+ if state in processed:
558
+ return processed[state]
559
+ tokens = processed[state] = []
560
+ rflags = cls.flags
561
+ for tdef in unprocessed[state]:
562
+ if isinstance(tdef, include):
563
+ # it's a state reference
564
+ assert tdef != state, f"circular state reference {state!r}"
565
+ tokens.extend(cls._process_state(unprocessed, processed,
566
+ str(tdef)))
567
+ continue
568
+ if isinstance(tdef, _inherit):
569
+ # should be processed already, but may not in the case of:
570
+ # 1. the state has no counterpart in any parent
571
+ # 2. the state includes more than one 'inherit'
572
+ continue
573
+ if isinstance(tdef, default):
574
+ new_state = cls._process_new_state(tdef.state, unprocessed, processed)
575
+ tokens.append((re.compile('').match, None, new_state))
576
+ continue
577
+
578
+ assert type(tdef) is tuple, f"wrong rule def {tdef!r}"
579
+
580
+ try:
581
+ rex = cls._process_regex(tdef[0], rflags, state)
582
+ except Exception as err:
583
+ raise ValueError(f"uncompilable regex {tdef[0]!r} in state {state!r} of {cls!r}: {err}") from err
584
+
585
+ token = cls._process_token(tdef[1])
586
+
587
+ if len(tdef) == 2:
588
+ new_state = None
589
+ else:
590
+ new_state = cls._process_new_state(tdef[2],
591
+ unprocessed, processed)
592
+
593
+ tokens.append((rex, token, new_state))
594
+ return tokens
595
+
596
+ def process_tokendef(cls, name, tokendefs=None):
597
+ """Preprocess a dictionary of token definitions."""
598
+ processed = cls._all_tokens[name] = {}
599
+ tokendefs = tokendefs or cls.tokens[name]
600
+ for state in list(tokendefs):
601
+ cls._process_state(tokendefs, processed, state)
602
+ return processed
603
+
604
+ def get_tokendefs(cls):
605
+ """
606
+ Merge tokens from superclasses in MRO order, returning a single tokendef
607
+ dictionary.
608
+
609
+ Any state that is not defined by a subclass will be inherited
610
+ automatically. States that *are* defined by subclasses will, by
611
+ default, override that state in the superclass. If a subclass wishes to
612
+ inherit definitions from a superclass, it can use the special value
613
+ "inherit", which will cause the superclass' state definition to be
614
+ included at that point in the state.
615
+ """
616
+ tokens = {}
617
+ inheritable = {}
618
+ for c in cls.__mro__:
619
+ toks = c.__dict__.get('tokens', {})
620
+
621
+ for state, items in toks.items():
622
+ curitems = tokens.get(state)
623
+ if curitems is None:
624
+ # N.b. because this is assigned by reference, sufficiently
625
+ # deep hierarchies are processed incrementally (e.g. for
626
+ # A(B), B(C), C(RegexLexer), B will be premodified so X(B)
627
+ # will not see any inherits in B).
628
+ tokens[state] = items
629
+ try:
630
+ inherit_ndx = items.index(inherit)
631
+ except ValueError:
632
+ continue
633
+ inheritable[state] = inherit_ndx
634
+ continue
635
+
636
+ inherit_ndx = inheritable.pop(state, None)
637
+ if inherit_ndx is None:
638
+ continue
639
+
640
+ # Replace the "inherit" value with the items
641
+ curitems[inherit_ndx:inherit_ndx+1] = items
642
+ try:
643
+ # N.b. this is the index in items (that is, the superclass
644
+ # copy), so offset required when storing below.
645
+ new_inh_ndx = items.index(inherit)
646
+ except ValueError:
647
+ pass
648
+ else:
649
+ inheritable[state] = inherit_ndx + new_inh_ndx
650
+
651
+ return tokens
652
+
653
+ def __call__(cls, *args, **kwds):
654
+ """Instantiate cls after preprocessing its token definitions."""
655
+ if '_tokens' not in cls.__dict__:
656
+ cls._all_tokens = {}
657
+ cls._tmpname = 0
658
+ if hasattr(cls, 'token_variants') and cls.token_variants:
659
+ # don't process yet
660
+ pass
661
+ else:
662
+ cls._tokens = cls.process_tokendef('', cls.get_tokendefs())
663
+
664
+ return type.__call__(cls, *args, **kwds)
665
+
666
+
667
+ class RegexLexer(Lexer, metaclass=RegexLexerMeta):
668
+ """
669
+ Base for simple stateful regular expression-based lexers.
670
+ Simplifies the lexing process so that you need only
671
+ provide a list of states and regular expressions.
672
+ """
673
+
674
+ #: Flags for compiling the regular expressions.
675
+ #: Defaults to MULTILINE.
676
+ flags = re.MULTILINE
677
+
678
+ #: At all time there is a stack of states. Initially, the stack contains
679
+ #: a single state 'root'. The top of the stack is called "the current state".
680
+ #:
681
+ #: Dict of ``{'state': [(regex, tokentype, new_state), ...], ...}``
682
+ #:
683
+ #: ``new_state`` can be omitted to signify no state transition.
684
+ #: If ``new_state`` is a string, it is pushed on the stack. This ensure
685
+ #: the new current state is ``new_state``.
686
+ #: If ``new_state`` is a tuple of strings, all of those strings are pushed
687
+ #: on the stack and the current state will be the last element of the list.
688
+ #: ``new_state`` can also be ``combined('state1', 'state2', ...)``
689
+ #: to signify a new, anonymous state combined from the rules of two
690
+ #: or more existing ones.
691
+ #: Furthermore, it can be '#pop' to signify going back one step in
692
+ #: the state stack, or '#push' to push the current state on the stack
693
+ #: again. Note that if you push while in a combined state, the combined
694
+ #: state itself is pushed, and not only the state in which the rule is
695
+ #: defined.
696
+ #:
697
+ #: The tuple can also be replaced with ``include('state')``, in which
698
+ #: case the rules from the state named by the string are included in the
699
+ #: current one.
700
+ tokens = {}
701
+
702
+ def get_tokens_unprocessed(self, text, stack=('root',)):
703
+ """
704
+ Split ``text`` into (tokentype, text) pairs.
705
+
706
+ ``stack`` is the initial stack (default: ``['root']``)
707
+ """
708
+ pos = 0
709
+ tokendefs = self._tokens
710
+ statestack = list(stack)
711
+ statetokens = tokendefs[statestack[-1]]
712
+ while 1:
713
+ for rexmatch, action, new_state in statetokens:
714
+ m = rexmatch(text, pos)
715
+ if m:
716
+ if action is not None:
717
+ if type(action) is _TokenType:
718
+ yield pos, action, m.group()
719
+ else:
720
+ yield from action(self, m)
721
+ pos = m.end()
722
+ if new_state is not None:
723
+ # state transition
724
+ if isinstance(new_state, tuple):
725
+ for state in new_state:
726
+ if state == '#pop':
727
+ if len(statestack) > 1:
728
+ statestack.pop()
729
+ elif state == '#push':
730
+ statestack.append(statestack[-1])
731
+ else:
732
+ statestack.append(state)
733
+ elif isinstance(new_state, int):
734
+ # pop, but keep at least one state on the stack
735
+ # (random code leading to unexpected pops should
736
+ # not allow exceptions)
737
+ if abs(new_state) >= len(statestack):
738
+ del statestack[1:]
739
+ else:
740
+ del statestack[new_state:]
741
+ elif new_state == '#push':
742
+ statestack.append(statestack[-1])
743
+ else:
744
+ assert False, f"wrong state def: {new_state!r}"
745
+ statetokens = tokendefs[statestack[-1]]
746
+ break
747
+ else:
748
+ # We are here only if all state tokens have been considered
749
+ # and there was not a match on any of them.
750
+ try:
751
+ if text[pos] == '\n':
752
+ # at EOL, reset state to "root"
753
+ statestack = ['root']
754
+ statetokens = tokendefs['root']
755
+ yield pos, Whitespace, '\n'
756
+ pos += 1
757
+ continue
758
+ yield pos, Error, text[pos]
759
+ pos += 1
760
+ except IndexError:
761
+ break
762
+
763
+
764
+ class LexerContext:
765
+ """
766
+ A helper object that holds lexer position data.
767
+ """
768
+
769
+ def __init__(self, text, pos, stack=None, end=None):
770
+ self.text = text
771
+ self.pos = pos
772
+ self.end = end or len(text) # end=0 not supported ;-)
773
+ self.stack = stack or ['root']
774
+
775
+ def __repr__(self):
776
+ return f'LexerContext({self.text!r}, {self.pos!r}, {self.stack!r})'
777
+
778
+
779
+ class ExtendedRegexLexer(RegexLexer):
780
+ """
781
+ A RegexLexer that uses a context object to store its state.
782
+ """
783
+
784
+ def get_tokens_unprocessed(self, text=None, context=None):
785
+ """
786
+ Split ``text`` into (tokentype, text) pairs.
787
+ If ``context`` is given, use this lexer context instead.
788
+ """
789
+ tokendefs = self._tokens
790
+ if not context:
791
+ ctx = LexerContext(text, 0)
792
+ statetokens = tokendefs['root']
793
+ else:
794
+ ctx = context
795
+ statetokens = tokendefs[ctx.stack[-1]]
796
+ text = ctx.text
797
+ while 1:
798
+ for rexmatch, action, new_state in statetokens:
799
+ m = rexmatch(text, ctx.pos, ctx.end)
800
+ if m:
801
+ if action is not None:
802
+ if type(action) is _TokenType:
803
+ yield ctx.pos, action, m.group()
804
+ ctx.pos = m.end()
805
+ else:
806
+ yield from action(self, m, ctx)
807
+ if not new_state:
808
+ # altered the state stack?
809
+ statetokens = tokendefs[ctx.stack[-1]]
810
+ # CAUTION: callback must set ctx.pos!
811
+ if new_state is not None:
812
+ # state transition
813
+ if isinstance(new_state, tuple):
814
+ for state in new_state:
815
+ if state == '#pop':
816
+ if len(ctx.stack) > 1:
817
+ ctx.stack.pop()
818
+ elif state == '#push':
819
+ ctx.stack.append(ctx.stack[-1])
820
+ else:
821
+ ctx.stack.append(state)
822
+ elif isinstance(new_state, int):
823
+ # see RegexLexer for why this check is made
824
+ if abs(new_state) >= len(ctx.stack):
825
+ del ctx.stack[1:]
826
+ else:
827
+ del ctx.stack[new_state:]
828
+ elif new_state == '#push':
829
+ ctx.stack.append(ctx.stack[-1])
830
+ else:
831
+ assert False, f"wrong state def: {new_state!r}"
832
+ statetokens = tokendefs[ctx.stack[-1]]
833
+ break
834
+ else:
835
+ try:
836
+ if ctx.pos >= ctx.end:
837
+ break
838
+ if text[ctx.pos] == '\n':
839
+ # at EOL, reset state to "root"
840
+ ctx.stack = ['root']
841
+ statetokens = tokendefs['root']
842
+ yield ctx.pos, Text, '\n'
843
+ ctx.pos += 1
844
+ continue
845
+ yield ctx.pos, Error, text[ctx.pos]
846
+ ctx.pos += 1
847
+ except IndexError:
848
+ break
849
+
850
+
851
+ def do_insertions(insertions, tokens):
852
+ """
853
+ Helper for lexers which must combine the results of several
854
+ sublexers.
855
+
856
+ ``insertions`` is a list of ``(index, itokens)`` pairs.
857
+ Each ``itokens`` iterable should be inserted at position
858
+ ``index`` into the token stream given by the ``tokens``
859
+ argument.
860
+
861
+ The result is a combined token stream.
862
+
863
+ TODO: clean up the code here.
864
+ """
865
+ insertions = iter(insertions)
866
+ try:
867
+ index, itokens = next(insertions)
868
+ except StopIteration:
869
+ # no insertions
870
+ yield from tokens
871
+ return
872
+
873
+ realpos = None
874
+ insleft = True
875
+
876
+ # iterate over the token stream where we want to insert
877
+ # the tokens from the insertion list.
878
+ for i, t, v in tokens:
879
+ # first iteration. store the position of first item
880
+ if realpos is None:
881
+ realpos = i
882
+ oldi = 0
883
+ while insleft and i + len(v) >= index:
884
+ tmpval = v[oldi:index - i]
885
+ if tmpval:
886
+ yield realpos, t, tmpval
887
+ realpos += len(tmpval)
888
+ for it_index, it_token, it_value in itokens:
889
+ yield realpos, it_token, it_value
890
+ realpos += len(it_value)
891
+ oldi = index - i
892
+ try:
893
+ index, itokens = next(insertions)
894
+ except StopIteration:
895
+ insleft = False
896
+ break # not strictly necessary
897
+ if oldi < len(v):
898
+ yield realpos, t, v[oldi:]
899
+ realpos += len(v) - oldi
900
+
901
+ # leftover tokens
902
+ while insleft:
903
+ # no normal tokens, set realpos to zero
904
+ realpos = realpos or 0
905
+ for p, t, v in itokens:
906
+ yield realpos, t, v
907
+ realpos += len(v)
908
+ try:
909
+ index, itokens = next(insertions)
910
+ except StopIteration:
911
+ insleft = False
912
+ break # not strictly necessary
913
+
914
+
915
+ class ProfilingRegexLexerMeta(RegexLexerMeta):
916
+ """Metaclass for ProfilingRegexLexer, collects regex timing info."""
917
+
918
+ def _process_regex(cls, regex, rflags, state):
919
+ if isinstance(regex, words):
920
+ rex = regex_opt(regex.words, prefix=regex.prefix,
921
+ suffix=regex.suffix)
922
+ else:
923
+ rex = regex
924
+ compiled = re.compile(rex, rflags)
925
+
926
+ def match_func(text, pos, endpos=sys.maxsize):
927
+ info = cls._prof_data[-1].setdefault((state, rex), [0, 0.0])
928
+ t0 = time.time()
929
+ res = compiled.match(text, pos, endpos)
930
+ t1 = time.time()
931
+ info[0] += 1
932
+ info[1] += t1 - t0
933
+ return res
934
+ return match_func
935
+
936
+
937
+ class ProfilingRegexLexer(RegexLexer, metaclass=ProfilingRegexLexerMeta):
938
+ """Drop-in replacement for RegexLexer that does profiling of its regexes."""
939
+
940
+ _prof_data = []
941
+ _prof_sort_index = 4 # defaults to time per call
942
+
943
+ def get_tokens_unprocessed(self, text, stack=('root',)):
944
+ # this needs to be a stack, since using(this) will produce nested calls
945
+ self.__class__._prof_data.append({})
946
+ yield from RegexLexer.get_tokens_unprocessed(self, text, stack)
947
+ rawdata = self.__class__._prof_data.pop()
948
+ data = sorted(((s, repr(r).strip('u\'').replace('\\\\', '\\')[:65],
949
+ n, 1000 * t, 1000 * t / n)
950
+ for ((s, r), (n, t)) in rawdata.items()),
951
+ key=lambda x: x[self._prof_sort_index],
952
+ reverse=True)
953
+ sum_total = sum(x[3] for x in data)
954
+
955
+ print()
956
+ print('Profiling result for %s lexing %d chars in %.3f ms' %
957
+ (self.__class__.__name__, len(text), sum_total))
958
+ print('=' * 110)
959
+ print('%-20s %-64s ncalls tottime percall' % ('state', 'regex'))
960
+ print('-' * 110)
961
+ for d in data:
962
+ print('%-20s %-65s %5d %8.4f %8.4f' % d)
963
+ print('=' * 110)
lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__init__.py ADDED
@@ -0,0 +1,362 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ pygments.lexers
3
+ ~~~~~~~~~~~~~~~
4
+
5
+ Pygments lexers.
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.lexers._mapping import LEXERS
18
+ from pip._vendor.pygments.modeline import get_filetype_from_buffer
19
+ from pip._vendor.pygments.plugin import find_plugin_lexers
20
+ from pip._vendor.pygments.util import ClassNotFound, guess_decode
21
+
22
+ COMPAT = {
23
+ 'Python3Lexer': 'PythonLexer',
24
+ 'Python3TracebackLexer': 'PythonTracebackLexer',
25
+ 'LeanLexer': 'Lean3Lexer',
26
+ }
27
+
28
+ __all__ = ['get_lexer_by_name', 'get_lexer_for_filename', 'find_lexer_class',
29
+ 'guess_lexer', 'load_lexer_from_file'] + list(LEXERS) + list(COMPAT)
30
+
31
+ _lexer_cache = {}
32
+ _pattern_cache = {}
33
+
34
+
35
+ def _fn_matches(fn, glob):
36
+ """Return whether the supplied file name fn matches pattern filename."""
37
+ if glob not in _pattern_cache:
38
+ pattern = _pattern_cache[glob] = re.compile(fnmatch.translate(glob))
39
+ return pattern.match(fn)
40
+ return _pattern_cache[glob].match(fn)
41
+
42
+
43
+ def _load_lexers(module_name):
44
+ """Load a lexer (and all others in the module too)."""
45
+ mod = __import__(module_name, None, None, ['__all__'])
46
+ for lexer_name in mod.__all__:
47
+ cls = getattr(mod, lexer_name)
48
+ _lexer_cache[cls.name] = cls
49
+
50
+
51
+ def get_all_lexers(plugins=True):
52
+ """Return a generator of tuples in the form ``(name, aliases,
53
+ filenames, mimetypes)`` of all know lexers.
54
+
55
+ If *plugins* is true (the default), plugin lexers supplied by entrypoints
56
+ are also returned. Otherwise, only builtin ones are considered.
57
+ """
58
+ for item in LEXERS.values():
59
+ yield item[1:]
60
+ if plugins:
61
+ for lexer in find_plugin_lexers():
62
+ yield lexer.name, lexer.aliases, lexer.filenames, lexer.mimetypes
63
+
64
+
65
+ def find_lexer_class(name):
66
+ """
67
+ Return the `Lexer` subclass that with the *name* attribute as given by
68
+ the *name* argument.
69
+ """
70
+ if name in _lexer_cache:
71
+ return _lexer_cache[name]
72
+ # lookup builtin lexers
73
+ for module_name, lname, aliases, _, _ in LEXERS.values():
74
+ if name == lname:
75
+ _load_lexers(module_name)
76
+ return _lexer_cache[name]
77
+ # continue with lexers from setuptools entrypoints
78
+ for cls in find_plugin_lexers():
79
+ if cls.name == name:
80
+ return cls
81
+
82
+
83
+ def find_lexer_class_by_name(_alias):
84
+ """
85
+ Return the `Lexer` subclass that has `alias` in its aliases list, without
86
+ instantiating it.
87
+
88
+ Like `get_lexer_by_name`, but does not instantiate the class.
89
+
90
+ Will raise :exc:`pygments.util.ClassNotFound` if no lexer with that alias is
91
+ found.
92
+
93
+ .. versionadded:: 2.2
94
+ """
95
+ if not _alias:
96
+ raise ClassNotFound(f'no lexer for alias {_alias!r} found')
97
+ # lookup builtin lexers
98
+ for module_name, name, aliases, _, _ in LEXERS.values():
99
+ if _alias.lower() in aliases:
100
+ if name not in _lexer_cache:
101
+ _load_lexers(module_name)
102
+ return _lexer_cache[name]
103
+ # continue with lexers from setuptools entrypoints
104
+ for cls in find_plugin_lexers():
105
+ if _alias.lower() in cls.aliases:
106
+ return cls
107
+ raise ClassNotFound(f'no lexer for alias {_alias!r} found')
108
+
109
+
110
+ def get_lexer_by_name(_alias, **options):
111
+ """
112
+ Return an instance of a `Lexer` subclass that has `alias` in its
113
+ aliases list. The lexer is given the `options` at its
114
+ instantiation.
115
+
116
+ Will raise :exc:`pygments.util.ClassNotFound` if no lexer with that alias is
117
+ found.
118
+ """
119
+ if not _alias:
120
+ raise ClassNotFound(f'no lexer for alias {_alias!r} found')
121
+
122
+ # lookup builtin lexers
123
+ for module_name, name, aliases, _, _ in LEXERS.values():
124
+ if _alias.lower() in aliases:
125
+ if name not in _lexer_cache:
126
+ _load_lexers(module_name)
127
+ return _lexer_cache[name](**options)
128
+ # continue with lexers from setuptools entrypoints
129
+ for cls in find_plugin_lexers():
130
+ if _alias.lower() in cls.aliases:
131
+ return cls(**options)
132
+ raise ClassNotFound(f'no lexer for alias {_alias!r} found')
133
+
134
+
135
+ def load_lexer_from_file(filename, lexername="CustomLexer", **options):
136
+ """Load a lexer from a file.
137
+
138
+ This method expects a file located relative to the current working
139
+ directory, which contains a Lexer class. By default, it expects the
140
+ Lexer to be name CustomLexer; you can specify your own class name
141
+ as the second argument to this function.
142
+
143
+ Users should be very careful with the input, because this method
144
+ is equivalent to running eval on the input file.
145
+
146
+ Raises ClassNotFound if there are any problems importing the Lexer.
147
+
148
+ .. versionadded:: 2.2
149
+ """
150
+ try:
151
+ # This empty dict will contain the namespace for the exec'd file
152
+ custom_namespace = {}
153
+ with open(filename, 'rb') as f:
154
+ exec(f.read(), custom_namespace)
155
+ # Retrieve the class `lexername` from that namespace
156
+ if lexername not in custom_namespace:
157
+ raise ClassNotFound(f'no valid {lexername} class found in {filename}')
158
+ lexer_class = custom_namespace[lexername]
159
+ # And finally instantiate it with the options
160
+ return lexer_class(**options)
161
+ except OSError as err:
162
+ raise ClassNotFound(f'cannot read {filename}: {err}')
163
+ except ClassNotFound:
164
+ raise
165
+ except Exception as err:
166
+ raise ClassNotFound(f'error when loading custom lexer: {err}')
167
+
168
+
169
+ def find_lexer_class_for_filename(_fn, code=None):
170
+ """Get a lexer for a filename.
171
+
172
+ If multiple lexers match the filename pattern, use ``analyse_text()`` to
173
+ figure out which one is more appropriate.
174
+
175
+ Returns None if not found.
176
+ """
177
+ matches = []
178
+ fn = basename(_fn)
179
+ for modname, name, _, filenames, _ in LEXERS.values():
180
+ for filename in filenames:
181
+ if _fn_matches(fn, filename):
182
+ if name not in _lexer_cache:
183
+ _load_lexers(modname)
184
+ matches.append((_lexer_cache[name], filename))
185
+ for cls in find_plugin_lexers():
186
+ for filename in cls.filenames:
187
+ if _fn_matches(fn, filename):
188
+ matches.append((cls, filename))
189
+
190
+ if isinstance(code, bytes):
191
+ # decode it, since all analyse_text functions expect unicode
192
+ code = guess_decode(code)
193
+
194
+ def get_rating(info):
195
+ cls, filename = info
196
+ # explicit patterns get a bonus
197
+ bonus = '*' not in filename and 0.5 or 0
198
+ # The class _always_ defines analyse_text because it's included in
199
+ # the Lexer class. The default implementation returns None which
200
+ # gets turned into 0.0. Run scripts/detect_missing_analyse_text.py
201
+ # to find lexers which need it overridden.
202
+ if code:
203
+ return cls.analyse_text(code) + bonus, cls.__name__
204
+ return cls.priority + bonus, cls.__name__
205
+
206
+ if matches:
207
+ matches.sort(key=get_rating)
208
+ # print "Possible lexers, after sort:", matches
209
+ return matches[-1][0]
210
+
211
+
212
+ def get_lexer_for_filename(_fn, code=None, **options):
213
+ """Get a lexer for a filename.
214
+
215
+ Return a `Lexer` subclass instance that has a filename pattern
216
+ matching `fn`. The lexer is given the `options` at its
217
+ instantiation.
218
+
219
+ Raise :exc:`pygments.util.ClassNotFound` if no lexer for that filename
220
+ is found.
221
+
222
+ If multiple lexers match the filename pattern, use their ``analyse_text()``
223
+ methods to figure out which one is more appropriate.
224
+ """
225
+ res = find_lexer_class_for_filename(_fn, code)
226
+ if not res:
227
+ raise ClassNotFound(f'no lexer for filename {_fn!r} found')
228
+ return res(**options)
229
+
230
+
231
+ def get_lexer_for_mimetype(_mime, **options):
232
+ """
233
+ Return a `Lexer` subclass instance that has `mime` in its mimetype
234
+ list. The lexer is given the `options` at its instantiation.
235
+
236
+ Will raise :exc:`pygments.util.ClassNotFound` if not lexer for that mimetype
237
+ is found.
238
+ """
239
+ for modname, name, _, _, mimetypes in LEXERS.values():
240
+ if _mime in mimetypes:
241
+ if name not in _lexer_cache:
242
+ _load_lexers(modname)
243
+ return _lexer_cache[name](**options)
244
+ for cls in find_plugin_lexers():
245
+ if _mime in cls.mimetypes:
246
+ return cls(**options)
247
+ raise ClassNotFound(f'no lexer for mimetype {_mime!r} found')
248
+
249
+
250
+ def _iter_lexerclasses(plugins=True):
251
+ """Return an iterator over all lexer classes."""
252
+ for key in sorted(LEXERS):
253
+ module_name, name = LEXERS[key][:2]
254
+ if name not in _lexer_cache:
255
+ _load_lexers(module_name)
256
+ yield _lexer_cache[name]
257
+ if plugins:
258
+ yield from find_plugin_lexers()
259
+
260
+
261
+ def guess_lexer_for_filename(_fn, _text, **options):
262
+ """
263
+ As :func:`guess_lexer()`, but only lexers which have a pattern in `filenames`
264
+ or `alias_filenames` that matches `filename` are taken into consideration.
265
+
266
+ :exc:`pygments.util.ClassNotFound` is raised if no lexer thinks it can
267
+ handle the content.
268
+ """
269
+ fn = basename(_fn)
270
+ primary = {}
271
+ matching_lexers = set()
272
+ for lexer in _iter_lexerclasses():
273
+ for filename in lexer.filenames:
274
+ if _fn_matches(fn, filename):
275
+ matching_lexers.add(lexer)
276
+ primary[lexer] = True
277
+ for filename in lexer.alias_filenames:
278
+ if _fn_matches(fn, filename):
279
+ matching_lexers.add(lexer)
280
+ primary[lexer] = False
281
+ if not matching_lexers:
282
+ raise ClassNotFound(f'no lexer for filename {fn!r} found')
283
+ if len(matching_lexers) == 1:
284
+ return matching_lexers.pop()(**options)
285
+ result = []
286
+ for lexer in matching_lexers:
287
+ rv = lexer.analyse_text(_text)
288
+ if rv == 1.0:
289
+ return lexer(**options)
290
+ result.append((rv, lexer))
291
+
292
+ def type_sort(t):
293
+ # sort by:
294
+ # - analyse score
295
+ # - is primary filename pattern?
296
+ # - priority
297
+ # - last resort: class name
298
+ return (t[0], primary[t[1]], t[1].priority, t[1].__name__)
299
+ result.sort(key=type_sort)
300
+
301
+ return result[-1][1](**options)
302
+
303
+
304
+ def guess_lexer(_text, **options):
305
+ """
306
+ Return a `Lexer` subclass instance that's guessed from the text in
307
+ `text`. For that, the :meth:`.analyse_text()` method of every known lexer
308
+ class is called with the text as argument, and the lexer which returned the
309
+ highest value will be instantiated and returned.
310
+
311
+ :exc:`pygments.util.ClassNotFound` is raised if no lexer thinks it can
312
+ handle the content.
313
+ """
314
+
315
+ if not isinstance(_text, str):
316
+ inencoding = options.get('inencoding', options.get('encoding'))
317
+ if inencoding:
318
+ _text = _text.decode(inencoding or 'utf8')
319
+ else:
320
+ _text, _ = guess_decode(_text)
321
+
322
+ # try to get a vim modeline first
323
+ ft = get_filetype_from_buffer(_text)
324
+
325
+ if ft is not None:
326
+ try:
327
+ return get_lexer_by_name(ft, **options)
328
+ except ClassNotFound:
329
+ pass
330
+
331
+ best_lexer = [0.0, None]
332
+ for lexer in _iter_lexerclasses():
333
+ rv = lexer.analyse_text(_text)
334
+ if rv == 1.0:
335
+ return lexer(**options)
336
+ if rv > best_lexer[0]:
337
+ best_lexer[:] = (rv, lexer)
338
+ if not best_lexer[0] or best_lexer[1] is None:
339
+ raise ClassNotFound('no lexer matching the text found')
340
+ return best_lexer[1](**options)
341
+
342
+
343
+ class _automodule(types.ModuleType):
344
+ """Automatically import lexers."""
345
+
346
+ def __getattr__(self, name):
347
+ info = LEXERS.get(name)
348
+ if info:
349
+ _load_lexers(info[0])
350
+ cls = _lexer_cache[info[1]]
351
+ setattr(self, name, cls)
352
+ return cls
353
+ if name in COMPAT:
354
+ return getattr(self, COMPAT[name])
355
+ raise AttributeError(name)
356
+
357
+
358
+ oldmod = sys.modules[__name__]
359
+ newmod = _automodule(__name__)
360
+ newmod.__dict__.update(oldmod.__dict__)
361
+ sys.modules[__name__] = newmod
362
+ del newmod.newmod, newmod.oldmod, newmod.sys, newmod.types
lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (14.6 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__pycache__/_mapping.cpython-312.pyc ADDED
Binary file (68.3 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__pycache__/python.cpython-312.pyc ADDED
Binary file (43 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/lexers/_mapping.py ADDED
@@ -0,0 +1,589 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Automatically generated by scripts/gen_mapfiles.py.
2
+ # DO NOT EDIT BY HAND; run `tox -e mapfiles` instead.
3
+
4
+ LEXERS = {
5
+ 'ABAPLexer': ('pip._vendor.pygments.lexers.business', 'ABAP', ('abap',), ('*.abap', '*.ABAP'), ('text/x-abap',)),
6
+ 'AMDGPULexer': ('pip._vendor.pygments.lexers.amdgpu', 'AMDGPU', ('amdgpu',), ('*.isa',), ()),
7
+ 'APLLexer': ('pip._vendor.pygments.lexers.apl', 'APL', ('apl',), ('*.apl', '*.aplf', '*.aplo', '*.apln', '*.aplc', '*.apli', '*.dyalog'), ()),
8
+ 'AbnfLexer': ('pip._vendor.pygments.lexers.grammar_notation', 'ABNF', ('abnf',), ('*.abnf',), ('text/x-abnf',)),
9
+ 'ActionScript3Lexer': ('pip._vendor.pygments.lexers.actionscript', 'ActionScript 3', ('actionscript3', 'as3'), ('*.as',), ('application/x-actionscript3', 'text/x-actionscript3', 'text/actionscript3')),
10
+ 'ActionScriptLexer': ('pip._vendor.pygments.lexers.actionscript', 'ActionScript', ('actionscript', 'as'), ('*.as',), ('application/x-actionscript', 'text/x-actionscript', 'text/actionscript')),
11
+ 'AdaLexer': ('pip._vendor.pygments.lexers.ada', 'Ada', ('ada', 'ada95', 'ada2005'), ('*.adb', '*.ads', '*.ada'), ('text/x-ada',)),
12
+ 'AdlLexer': ('pip._vendor.pygments.lexers.archetype', 'ADL', ('adl',), ('*.adl', '*.adls', '*.adlf', '*.adlx'), ()),
13
+ 'AgdaLexer': ('pip._vendor.pygments.lexers.haskell', 'Agda', ('agda',), ('*.agda',), ('text/x-agda',)),
14
+ 'AheuiLexer': ('pip._vendor.pygments.lexers.esoteric', 'Aheui', ('aheui',), ('*.aheui',), ()),
15
+ 'AlloyLexer': ('pip._vendor.pygments.lexers.dsls', 'Alloy', ('alloy',), ('*.als',), ('text/x-alloy',)),
16
+ 'AmbientTalkLexer': ('pip._vendor.pygments.lexers.ambient', 'AmbientTalk', ('ambienttalk', 'ambienttalk/2', 'at'), ('*.at',), ('text/x-ambienttalk',)),
17
+ 'AmplLexer': ('pip._vendor.pygments.lexers.ampl', 'Ampl', ('ampl',), ('*.run',), ()),
18
+ 'Angular2HtmlLexer': ('pip._vendor.pygments.lexers.templates', 'HTML + Angular2', ('html+ng2',), ('*.ng2',), ()),
19
+ 'Angular2Lexer': ('pip._vendor.pygments.lexers.templates', 'Angular2', ('ng2',), (), ()),
20
+ 'AntlrActionScriptLexer': ('pip._vendor.pygments.lexers.parsers', 'ANTLR With ActionScript Target', ('antlr-actionscript', 'antlr-as'), ('*.G', '*.g'), ()),
21
+ 'AntlrCSharpLexer': ('pip._vendor.pygments.lexers.parsers', 'ANTLR With C# Target', ('antlr-csharp', 'antlr-c#'), ('*.G', '*.g'), ()),
22
+ 'AntlrCppLexer': ('pip._vendor.pygments.lexers.parsers', 'ANTLR With CPP Target', ('antlr-cpp',), ('*.G', '*.g'), ()),
23
+ 'AntlrJavaLexer': ('pip._vendor.pygments.lexers.parsers', 'ANTLR With Java Target', ('antlr-java',), ('*.G', '*.g'), ()),
24
+ 'AntlrLexer': ('pip._vendor.pygments.lexers.parsers', 'ANTLR', ('antlr',), (), ()),
25
+ 'AntlrObjectiveCLexer': ('pip._vendor.pygments.lexers.parsers', 'ANTLR With ObjectiveC Target', ('antlr-objc',), ('*.G', '*.g'), ()),
26
+ 'AntlrPerlLexer': ('pip._vendor.pygments.lexers.parsers', 'ANTLR With Perl Target', ('antlr-perl',), ('*.G', '*.g'), ()),
27
+ 'AntlrPythonLexer': ('pip._vendor.pygments.lexers.parsers', 'ANTLR With Python Target', ('antlr-python',), ('*.G', '*.g'), ()),
28
+ 'AntlrRubyLexer': ('pip._vendor.pygments.lexers.parsers', 'ANTLR With Ruby Target', ('antlr-ruby', 'antlr-rb'), ('*.G', '*.g'), ()),
29
+ 'ApacheConfLexer': ('pip._vendor.pygments.lexers.configs', 'ApacheConf', ('apacheconf', 'aconf', 'apache'), ('.htaccess', 'apache.conf', 'apache2.conf'), ('text/x-apacheconf',)),
30
+ 'AppleScriptLexer': ('pip._vendor.pygments.lexers.scripting', 'AppleScript', ('applescript',), ('*.applescript',), ()),
31
+ 'ArduinoLexer': ('pip._vendor.pygments.lexers.c_like', 'Arduino', ('arduino',), ('*.ino',), ('text/x-arduino',)),
32
+ 'ArrowLexer': ('pip._vendor.pygments.lexers.arrow', 'Arrow', ('arrow',), ('*.arw',), ()),
33
+ 'ArturoLexer': ('pip._vendor.pygments.lexers.arturo', 'Arturo', ('arturo', 'art'), ('*.art',), ()),
34
+ 'AscLexer': ('pip._vendor.pygments.lexers.asc', 'ASCII armored', ('asc', 'pem'), ('*.asc', '*.pem', 'id_dsa', 'id_ecdsa', 'id_ecdsa_sk', 'id_ed25519', 'id_ed25519_sk', 'id_rsa'), ('application/pgp-keys', 'application/pgp-encrypted', 'application/pgp-signature', 'application/pem-certificate-chain')),
35
+ 'Asn1Lexer': ('pip._vendor.pygments.lexers.asn1', 'ASN.1', ('asn1',), ('*.asn1',), ()),
36
+ 'AspectJLexer': ('pip._vendor.pygments.lexers.jvm', 'AspectJ', ('aspectj',), ('*.aj',), ('text/x-aspectj',)),
37
+ 'AsymptoteLexer': ('pip._vendor.pygments.lexers.graphics', 'Asymptote', ('asymptote', 'asy'), ('*.asy',), ('text/x-asymptote',)),
38
+ 'AugeasLexer': ('pip._vendor.pygments.lexers.configs', 'Augeas', ('augeas',), ('*.aug',), ()),
39
+ 'AutoItLexer': ('pip._vendor.pygments.lexers.automation', 'AutoIt', ('autoit',), ('*.au3',), ('text/x-autoit',)),
40
+ 'AutohotkeyLexer': ('pip._vendor.pygments.lexers.automation', 'autohotkey', ('autohotkey', 'ahk'), ('*.ahk', '*.ahkl'), ('text/x-autohotkey',)),
41
+ 'AwkLexer': ('pip._vendor.pygments.lexers.textedit', 'Awk', ('awk', 'gawk', 'mawk', 'nawk'), ('*.awk',), ('application/x-awk',)),
42
+ 'BBCBasicLexer': ('pip._vendor.pygments.lexers.basic', 'BBC Basic', ('bbcbasic',), ('*.bbc',), ()),
43
+ 'BBCodeLexer': ('pip._vendor.pygments.lexers.markup', 'BBCode', ('bbcode',), (), ('text/x-bbcode',)),
44
+ 'BCLexer': ('pip._vendor.pygments.lexers.algebra', 'BC', ('bc',), ('*.bc',), ()),
45
+ 'BQNLexer': ('pip._vendor.pygments.lexers.bqn', 'BQN', ('bqn',), ('*.bqn',), ()),
46
+ 'BSTLexer': ('pip._vendor.pygments.lexers.bibtex', 'BST', ('bst', 'bst-pybtex'), ('*.bst',), ()),
47
+ 'BareLexer': ('pip._vendor.pygments.lexers.bare', 'BARE', ('bare',), ('*.bare',), ()),
48
+ 'BaseMakefileLexer': ('pip._vendor.pygments.lexers.make', 'Base Makefile', ('basemake',), (), ()),
49
+ 'BashLexer': ('pip._vendor.pygments.lexers.shell', 'Bash', ('bash', 'sh', 'ksh', 'zsh', 'shell', 'openrc'), ('*.sh', '*.ksh', '*.bash', '*.ebuild', '*.eclass', '*.exheres-0', '*.exlib', '*.zsh', '.bashrc', 'bashrc', '.bash_*', 'bash_*', 'zshrc', '.zshrc', '.kshrc', 'kshrc', 'PKGBUILD'), ('application/x-sh', 'application/x-shellscript', 'text/x-shellscript')),
50
+ 'BashSessionLexer': ('pip._vendor.pygments.lexers.shell', 'Bash Session', ('console', 'shell-session'), ('*.sh-session', '*.shell-session'), ('application/x-shell-session', 'application/x-sh-session')),
51
+ 'BatchLexer': ('pip._vendor.pygments.lexers.shell', 'Batchfile', ('batch', 'bat', 'dosbatch', 'winbatch'), ('*.bat', '*.cmd'), ('application/x-dos-batch',)),
52
+ 'BddLexer': ('pip._vendor.pygments.lexers.bdd', 'Bdd', ('bdd',), ('*.feature',), ('text/x-bdd',)),
53
+ 'BefungeLexer': ('pip._vendor.pygments.lexers.esoteric', 'Befunge', ('befunge',), ('*.befunge',), ('application/x-befunge',)),
54
+ 'BerryLexer': ('pip._vendor.pygments.lexers.berry', 'Berry', ('berry', 'be'), ('*.be',), ('text/x-berry', 'application/x-berry')),
55
+ 'BibTeXLexer': ('pip._vendor.pygments.lexers.bibtex', 'BibTeX', ('bibtex', 'bib'), ('*.bib',), ('text/x-bibtex',)),
56
+ 'BlitzBasicLexer': ('pip._vendor.pygments.lexers.basic', 'BlitzBasic', ('blitzbasic', 'b3d', 'bplus'), ('*.bb', '*.decls'), ('text/x-bb',)),
57
+ 'BlitzMaxLexer': ('pip._vendor.pygments.lexers.basic', 'BlitzMax', ('blitzmax', 'bmax'), ('*.bmx',), ('text/x-bmx',)),
58
+ 'BlueprintLexer': ('pip._vendor.pygments.lexers.blueprint', 'Blueprint', ('blueprint',), ('*.blp',), ('text/x-blueprint',)),
59
+ 'BnfLexer': ('pip._vendor.pygments.lexers.grammar_notation', 'BNF', ('bnf',), ('*.bnf',), ('text/x-bnf',)),
60
+ 'BoaLexer': ('pip._vendor.pygments.lexers.boa', 'Boa', ('boa',), ('*.boa',), ()),
61
+ 'BooLexer': ('pip._vendor.pygments.lexers.dotnet', 'Boo', ('boo',), ('*.boo',), ('text/x-boo',)),
62
+ 'BoogieLexer': ('pip._vendor.pygments.lexers.verification', 'Boogie', ('boogie',), ('*.bpl',), ()),
63
+ 'BrainfuckLexer': ('pip._vendor.pygments.lexers.esoteric', 'Brainfuck', ('brainfuck', 'bf'), ('*.bf', '*.b'), ('application/x-brainfuck',)),
64
+ 'BugsLexer': ('pip._vendor.pygments.lexers.modeling', 'BUGS', ('bugs', 'winbugs', 'openbugs'), ('*.bug',), ()),
65
+ 'CAmkESLexer': ('pip._vendor.pygments.lexers.esoteric', 'CAmkES', ('camkes', 'idl4'), ('*.camkes', '*.idl4'), ()),
66
+ 'CLexer': ('pip._vendor.pygments.lexers.c_cpp', 'C', ('c',), ('*.c', '*.h', '*.idc', '*.x[bp]m'), ('text/x-chdr', 'text/x-csrc', 'image/x-xbitmap', 'image/x-xpixmap')),
67
+ 'CMakeLexer': ('pip._vendor.pygments.lexers.make', 'CMake', ('cmake',), ('*.cmake', 'CMakeLists.txt'), ('text/x-cmake',)),
68
+ 'CObjdumpLexer': ('pip._vendor.pygments.lexers.asm', 'c-objdump', ('c-objdump',), ('*.c-objdump',), ('text/x-c-objdump',)),
69
+ 'CPSALexer': ('pip._vendor.pygments.lexers.lisp', 'CPSA', ('cpsa',), ('*.cpsa',), ()),
70
+ 'CSSUL4Lexer': ('pip._vendor.pygments.lexers.ul4', 'CSS+UL4', ('css+ul4',), ('*.cssul4',), ()),
71
+ 'CSharpAspxLexer': ('pip._vendor.pygments.lexers.dotnet', 'aspx-cs', ('aspx-cs',), ('*.aspx', '*.asax', '*.ascx', '*.ashx', '*.asmx', '*.axd'), ()),
72
+ 'CSharpLexer': ('pip._vendor.pygments.lexers.dotnet', 'C#', ('csharp', 'c#', 'cs'), ('*.cs',), ('text/x-csharp',)),
73
+ 'Ca65Lexer': ('pip._vendor.pygments.lexers.asm', 'ca65 assembler', ('ca65',), ('*.s',), ()),
74
+ 'CadlLexer': ('pip._vendor.pygments.lexers.archetype', 'cADL', ('cadl',), ('*.cadl',), ()),
75
+ 'CapDLLexer': ('pip._vendor.pygments.lexers.esoteric', 'CapDL', ('capdl',), ('*.cdl',), ()),
76
+ 'CapnProtoLexer': ('pip._vendor.pygments.lexers.capnproto', "Cap'n Proto", ('capnp',), ('*.capnp',), ()),
77
+ 'CarbonLexer': ('pip._vendor.pygments.lexers.carbon', 'Carbon', ('carbon',), ('*.carbon',), ('text/x-carbon',)),
78
+ 'CbmBasicV2Lexer': ('pip._vendor.pygments.lexers.basic', 'CBM BASIC V2', ('cbmbas',), ('*.bas',), ()),
79
+ 'CddlLexer': ('pip._vendor.pygments.lexers.cddl', 'CDDL', ('cddl',), ('*.cddl',), ('text/x-cddl',)),
80
+ 'CeylonLexer': ('pip._vendor.pygments.lexers.jvm', 'Ceylon', ('ceylon',), ('*.ceylon',), ('text/x-ceylon',)),
81
+ 'Cfengine3Lexer': ('pip._vendor.pygments.lexers.configs', 'CFEngine3', ('cfengine3', 'cf3'), ('*.cf',), ()),
82
+ 'ChaiscriptLexer': ('pip._vendor.pygments.lexers.scripting', 'ChaiScript', ('chaiscript', 'chai'), ('*.chai',), ('text/x-chaiscript', 'application/x-chaiscript')),
83
+ 'ChapelLexer': ('pip._vendor.pygments.lexers.chapel', 'Chapel', ('chapel', 'chpl'), ('*.chpl',), ()),
84
+ 'CharmciLexer': ('pip._vendor.pygments.lexers.c_like', 'Charmci', ('charmci',), ('*.ci',), ()),
85
+ 'CheetahHtmlLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Cheetah', ('html+cheetah', 'html+spitfire', 'htmlcheetah'), (), ('text/html+cheetah', 'text/html+spitfire')),
86
+ 'CheetahJavascriptLexer': ('pip._vendor.pygments.lexers.templates', 'JavaScript+Cheetah', ('javascript+cheetah', 'js+cheetah', 'javascript+spitfire', 'js+spitfire'), (), ('application/x-javascript+cheetah', 'text/x-javascript+cheetah', 'text/javascript+cheetah', 'application/x-javascript+spitfire', 'text/x-javascript+spitfire', 'text/javascript+spitfire')),
87
+ 'CheetahLexer': ('pip._vendor.pygments.lexers.templates', 'Cheetah', ('cheetah', 'spitfire'), ('*.tmpl', '*.spt'), ('application/x-cheetah', 'application/x-spitfire')),
88
+ 'CheetahXmlLexer': ('pip._vendor.pygments.lexers.templates', 'XML+Cheetah', ('xml+cheetah', 'xml+spitfire'), (), ('application/xml+cheetah', 'application/xml+spitfire')),
89
+ 'CirruLexer': ('pip._vendor.pygments.lexers.webmisc', 'Cirru', ('cirru',), ('*.cirru',), ('text/x-cirru',)),
90
+ 'ClayLexer': ('pip._vendor.pygments.lexers.c_like', 'Clay', ('clay',), ('*.clay',), ('text/x-clay',)),
91
+ 'CleanLexer': ('pip._vendor.pygments.lexers.clean', 'Clean', ('clean',), ('*.icl', '*.dcl'), ()),
92
+ 'ClojureLexer': ('pip._vendor.pygments.lexers.jvm', 'Clojure', ('clojure', 'clj'), ('*.clj', '*.cljc'), ('text/x-clojure', 'application/x-clojure')),
93
+ 'ClojureScriptLexer': ('pip._vendor.pygments.lexers.jvm', 'ClojureScript', ('clojurescript', 'cljs'), ('*.cljs',), ('text/x-clojurescript', 'application/x-clojurescript')),
94
+ 'CobolFreeformatLexer': ('pip._vendor.pygments.lexers.business', 'COBOLFree', ('cobolfree',), ('*.cbl', '*.CBL'), ()),
95
+ 'CobolLexer': ('pip._vendor.pygments.lexers.business', 'COBOL', ('cobol',), ('*.cob', '*.COB', '*.cpy', '*.CPY'), ('text/x-cobol',)),
96
+ 'CoffeeScriptLexer': ('pip._vendor.pygments.lexers.javascript', 'CoffeeScript', ('coffeescript', 'coffee-script', 'coffee'), ('*.coffee',), ('text/coffeescript',)),
97
+ 'ColdfusionCFCLexer': ('pip._vendor.pygments.lexers.templates', 'Coldfusion CFC', ('cfc',), ('*.cfc',), ()),
98
+ 'ColdfusionHtmlLexer': ('pip._vendor.pygments.lexers.templates', 'Coldfusion HTML', ('cfm',), ('*.cfm', '*.cfml'), ('application/x-coldfusion',)),
99
+ 'ColdfusionLexer': ('pip._vendor.pygments.lexers.templates', 'cfstatement', ('cfs',), (), ()),
100
+ 'Comal80Lexer': ('pip._vendor.pygments.lexers.comal', 'COMAL-80', ('comal', 'comal80'), ('*.cml', '*.comal'), ()),
101
+ 'CommonLispLexer': ('pip._vendor.pygments.lexers.lisp', 'Common Lisp', ('common-lisp', 'cl', 'lisp'), ('*.cl', '*.lisp'), ('text/x-common-lisp',)),
102
+ 'ComponentPascalLexer': ('pip._vendor.pygments.lexers.oberon', 'Component Pascal', ('componentpascal', 'cp'), ('*.cp', '*.cps'), ('text/x-component-pascal',)),
103
+ 'CoqLexer': ('pip._vendor.pygments.lexers.theorem', 'Coq', ('coq',), ('*.v',), ('text/x-coq',)),
104
+ 'CplintLexer': ('pip._vendor.pygments.lexers.cplint', 'cplint', ('cplint',), ('*.ecl', '*.prolog', '*.pro', '*.pl', '*.P', '*.lpad', '*.cpl'), ('text/x-cplint',)),
105
+ 'CppLexer': ('pip._vendor.pygments.lexers.c_cpp', 'C++', ('cpp', 'c++'), ('*.cpp', '*.hpp', '*.c++', '*.h++', '*.cc', '*.hh', '*.cxx', '*.hxx', '*.C', '*.H', '*.cp', '*.CPP', '*.tpp'), ('text/x-c++hdr', 'text/x-c++src')),
106
+ 'CppObjdumpLexer': ('pip._vendor.pygments.lexers.asm', 'cpp-objdump', ('cpp-objdump', 'c++-objdumb', 'cxx-objdump'), ('*.cpp-objdump', '*.c++-objdump', '*.cxx-objdump'), ('text/x-cpp-objdump',)),
107
+ 'CrmshLexer': ('pip._vendor.pygments.lexers.dsls', 'Crmsh', ('crmsh', 'pcmk'), ('*.crmsh', '*.pcmk'), ()),
108
+ 'CrocLexer': ('pip._vendor.pygments.lexers.d', 'Croc', ('croc',), ('*.croc',), ('text/x-crocsrc',)),
109
+ 'CryptolLexer': ('pip._vendor.pygments.lexers.haskell', 'Cryptol', ('cryptol', 'cry'), ('*.cry',), ('text/x-cryptol',)),
110
+ 'CrystalLexer': ('pip._vendor.pygments.lexers.crystal', 'Crystal', ('cr', 'crystal'), ('*.cr',), ('text/x-crystal',)),
111
+ 'CsoundDocumentLexer': ('pip._vendor.pygments.lexers.csound', 'Csound Document', ('csound-document', 'csound-csd'), ('*.csd',), ()),
112
+ 'CsoundOrchestraLexer': ('pip._vendor.pygments.lexers.csound', 'Csound Orchestra', ('csound', 'csound-orc'), ('*.orc', '*.udo'), ()),
113
+ 'CsoundScoreLexer': ('pip._vendor.pygments.lexers.csound', 'Csound Score', ('csound-score', 'csound-sco'), ('*.sco',), ()),
114
+ 'CssDjangoLexer': ('pip._vendor.pygments.lexers.templates', 'CSS+Django/Jinja', ('css+django', 'css+jinja'), ('*.css.j2', '*.css.jinja2'), ('text/css+django', 'text/css+jinja')),
115
+ 'CssErbLexer': ('pip._vendor.pygments.lexers.templates', 'CSS+Ruby', ('css+ruby', 'css+erb'), (), ('text/css+ruby',)),
116
+ 'CssGenshiLexer': ('pip._vendor.pygments.lexers.templates', 'CSS+Genshi Text', ('css+genshitext', 'css+genshi'), (), ('text/css+genshi',)),
117
+ 'CssLexer': ('pip._vendor.pygments.lexers.css', 'CSS', ('css',), ('*.css',), ('text/css',)),
118
+ 'CssPhpLexer': ('pip._vendor.pygments.lexers.templates', 'CSS+PHP', ('css+php',), (), ('text/css+php',)),
119
+ 'CssSmartyLexer': ('pip._vendor.pygments.lexers.templates', 'CSS+Smarty', ('css+smarty',), (), ('text/css+smarty',)),
120
+ 'CudaLexer': ('pip._vendor.pygments.lexers.c_like', 'CUDA', ('cuda', 'cu'), ('*.cu', '*.cuh'), ('text/x-cuda',)),
121
+ 'CypherLexer': ('pip._vendor.pygments.lexers.graph', 'Cypher', ('cypher',), ('*.cyp', '*.cypher'), ()),
122
+ 'CythonLexer': ('pip._vendor.pygments.lexers.python', 'Cython', ('cython', 'pyx', 'pyrex'), ('*.pyx', '*.pxd', '*.pxi'), ('text/x-cython', 'application/x-cython')),
123
+ 'DLexer': ('pip._vendor.pygments.lexers.d', 'D', ('d',), ('*.d', '*.di'), ('text/x-dsrc',)),
124
+ 'DObjdumpLexer': ('pip._vendor.pygments.lexers.asm', 'd-objdump', ('d-objdump',), ('*.d-objdump',), ('text/x-d-objdump',)),
125
+ 'DarcsPatchLexer': ('pip._vendor.pygments.lexers.diff', 'Darcs Patch', ('dpatch',), ('*.dpatch', '*.darcspatch'), ()),
126
+ 'DartLexer': ('pip._vendor.pygments.lexers.javascript', 'Dart', ('dart',), ('*.dart',), ('text/x-dart',)),
127
+ 'Dasm16Lexer': ('pip._vendor.pygments.lexers.asm', 'DASM16', ('dasm16',), ('*.dasm16', '*.dasm'), ('text/x-dasm16',)),
128
+ 'DaxLexer': ('pip._vendor.pygments.lexers.dax', 'Dax', ('dax',), ('*.dax',), ()),
129
+ 'DebianControlLexer': ('pip._vendor.pygments.lexers.installers', 'Debian Control file', ('debcontrol', 'control'), ('control',), ()),
130
+ 'DelphiLexer': ('pip._vendor.pygments.lexers.pascal', 'Delphi', ('delphi', 'pas', 'pascal', 'objectpascal'), ('*.pas', '*.dpr'), ('text/x-pascal',)),
131
+ 'DesktopLexer': ('pip._vendor.pygments.lexers.configs', 'Desktop file', ('desktop',), ('*.desktop',), ('application/x-desktop',)),
132
+ 'DevicetreeLexer': ('pip._vendor.pygments.lexers.devicetree', 'Devicetree', ('devicetree', 'dts'), ('*.dts', '*.dtsi'), ('text/x-c',)),
133
+ 'DgLexer': ('pip._vendor.pygments.lexers.python', 'dg', ('dg',), ('*.dg',), ('text/x-dg',)),
134
+ 'DiffLexer': ('pip._vendor.pygments.lexers.diff', 'Diff', ('diff', 'udiff'), ('*.diff', '*.patch'), ('text/x-diff', 'text/x-patch')),
135
+ 'DjangoLexer': ('pip._vendor.pygments.lexers.templates', 'Django/Jinja', ('django', 'jinja'), (), ('application/x-django-templating', 'application/x-jinja')),
136
+ 'DnsZoneLexer': ('pip._vendor.pygments.lexers.dns', 'Zone', ('zone',), ('*.zone',), ('text/dns',)),
137
+ 'DockerLexer': ('pip._vendor.pygments.lexers.configs', 'Docker', ('docker', 'dockerfile'), ('Dockerfile', '*.docker'), ('text/x-dockerfile-config',)),
138
+ 'DtdLexer': ('pip._vendor.pygments.lexers.html', 'DTD', ('dtd',), ('*.dtd',), ('application/xml-dtd',)),
139
+ 'DuelLexer': ('pip._vendor.pygments.lexers.webmisc', 'Duel', ('duel', 'jbst', 'jsonml+bst'), ('*.duel', '*.jbst'), ('text/x-duel', 'text/x-jbst')),
140
+ 'DylanConsoleLexer': ('pip._vendor.pygments.lexers.dylan', 'Dylan session', ('dylan-console', 'dylan-repl'), ('*.dylan-console',), ('text/x-dylan-console',)),
141
+ 'DylanLexer': ('pip._vendor.pygments.lexers.dylan', 'Dylan', ('dylan',), ('*.dylan', '*.dyl', '*.intr'), ('text/x-dylan',)),
142
+ 'DylanLidLexer': ('pip._vendor.pygments.lexers.dylan', 'DylanLID', ('dylan-lid', 'lid'), ('*.lid', '*.hdp'), ('text/x-dylan-lid',)),
143
+ 'ECLLexer': ('pip._vendor.pygments.lexers.ecl', 'ECL', ('ecl',), ('*.ecl',), ('application/x-ecl',)),
144
+ 'ECLexer': ('pip._vendor.pygments.lexers.c_like', 'eC', ('ec',), ('*.ec', '*.eh'), ('text/x-echdr', 'text/x-ecsrc')),
145
+ 'EarlGreyLexer': ('pip._vendor.pygments.lexers.javascript', 'Earl Grey', ('earl-grey', 'earlgrey', 'eg'), ('*.eg',), ('text/x-earl-grey',)),
146
+ 'EasytrieveLexer': ('pip._vendor.pygments.lexers.scripting', 'Easytrieve', ('easytrieve',), ('*.ezt', '*.mac'), ('text/x-easytrieve',)),
147
+ 'EbnfLexer': ('pip._vendor.pygments.lexers.parsers', 'EBNF', ('ebnf',), ('*.ebnf',), ('text/x-ebnf',)),
148
+ 'EiffelLexer': ('pip._vendor.pygments.lexers.eiffel', 'Eiffel', ('eiffel',), ('*.e',), ('text/x-eiffel',)),
149
+ 'ElixirConsoleLexer': ('pip._vendor.pygments.lexers.erlang', 'Elixir iex session', ('iex',), (), ('text/x-elixir-shellsession',)),
150
+ 'ElixirLexer': ('pip._vendor.pygments.lexers.erlang', 'Elixir', ('elixir', 'ex', 'exs'), ('*.ex', '*.eex', '*.exs', '*.leex'), ('text/x-elixir',)),
151
+ 'ElmLexer': ('pip._vendor.pygments.lexers.elm', 'Elm', ('elm',), ('*.elm',), ('text/x-elm',)),
152
+ 'ElpiLexer': ('pip._vendor.pygments.lexers.elpi', 'Elpi', ('elpi',), ('*.elpi',), ('text/x-elpi',)),
153
+ 'EmacsLispLexer': ('pip._vendor.pygments.lexers.lisp', 'EmacsLisp', ('emacs-lisp', 'elisp', 'emacs'), ('*.el',), ('text/x-elisp', 'application/x-elisp')),
154
+ 'EmailLexer': ('pip._vendor.pygments.lexers.email', 'E-mail', ('email', 'eml'), ('*.eml',), ('message/rfc822',)),
155
+ 'ErbLexer': ('pip._vendor.pygments.lexers.templates', 'ERB', ('erb',), (), ('application/x-ruby-templating',)),
156
+ 'ErlangLexer': ('pip._vendor.pygments.lexers.erlang', 'Erlang', ('erlang',), ('*.erl', '*.hrl', '*.es', '*.escript'), ('text/x-erlang',)),
157
+ 'ErlangShellLexer': ('pip._vendor.pygments.lexers.erlang', 'Erlang erl session', ('erl',), ('*.erl-sh',), ('text/x-erl-shellsession',)),
158
+ 'EvoqueHtmlLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Evoque', ('html+evoque',), ('*.html',), ('text/html+evoque',)),
159
+ 'EvoqueLexer': ('pip._vendor.pygments.lexers.templates', 'Evoque', ('evoque',), ('*.evoque',), ('application/x-evoque',)),
160
+ 'EvoqueXmlLexer': ('pip._vendor.pygments.lexers.templates', 'XML+Evoque', ('xml+evoque',), ('*.xml',), ('application/xml+evoque',)),
161
+ 'ExeclineLexer': ('pip._vendor.pygments.lexers.shell', 'execline', ('execline',), ('*.exec',), ()),
162
+ 'EzhilLexer': ('pip._vendor.pygments.lexers.ezhil', 'Ezhil', ('ezhil',), ('*.n',), ('text/x-ezhil',)),
163
+ 'FSharpLexer': ('pip._vendor.pygments.lexers.dotnet', 'F#', ('fsharp', 'f#'), ('*.fs', '*.fsi', '*.fsx'), ('text/x-fsharp',)),
164
+ 'FStarLexer': ('pip._vendor.pygments.lexers.ml', 'FStar', ('fstar',), ('*.fst', '*.fsti'), ('text/x-fstar',)),
165
+ 'FactorLexer': ('pip._vendor.pygments.lexers.factor', 'Factor', ('factor',), ('*.factor',), ('text/x-factor',)),
166
+ 'FancyLexer': ('pip._vendor.pygments.lexers.ruby', 'Fancy', ('fancy', 'fy'), ('*.fy', '*.fancypack'), ('text/x-fancysrc',)),
167
+ 'FantomLexer': ('pip._vendor.pygments.lexers.fantom', 'Fantom', ('fan',), ('*.fan',), ('application/x-fantom',)),
168
+ 'FelixLexer': ('pip._vendor.pygments.lexers.felix', 'Felix', ('felix', 'flx'), ('*.flx', '*.flxh'), ('text/x-felix',)),
169
+ 'FennelLexer': ('pip._vendor.pygments.lexers.lisp', 'Fennel', ('fennel', 'fnl'), ('*.fnl',), ()),
170
+ 'FiftLexer': ('pip._vendor.pygments.lexers.fift', 'Fift', ('fift', 'fif'), ('*.fif',), ()),
171
+ 'FishShellLexer': ('pip._vendor.pygments.lexers.shell', 'Fish', ('fish', 'fishshell'), ('*.fish', '*.load'), ('application/x-fish',)),
172
+ 'FlatlineLexer': ('pip._vendor.pygments.lexers.dsls', 'Flatline', ('flatline',), (), ('text/x-flatline',)),
173
+ 'FloScriptLexer': ('pip._vendor.pygments.lexers.floscript', 'FloScript', ('floscript', 'flo'), ('*.flo',), ()),
174
+ 'ForthLexer': ('pip._vendor.pygments.lexers.forth', 'Forth', ('forth',), ('*.frt', '*.fs'), ('application/x-forth',)),
175
+ 'FortranFixedLexer': ('pip._vendor.pygments.lexers.fortran', 'FortranFixed', ('fortranfixed',), ('*.f', '*.F'), ()),
176
+ 'FortranLexer': ('pip._vendor.pygments.lexers.fortran', 'Fortran', ('fortran', 'f90'), ('*.f03', '*.f90', '*.F03', '*.F90'), ('text/x-fortran',)),
177
+ 'FoxProLexer': ('pip._vendor.pygments.lexers.foxpro', 'FoxPro', ('foxpro', 'vfp', 'clipper', 'xbase'), ('*.PRG', '*.prg'), ()),
178
+ 'FreeFemLexer': ('pip._vendor.pygments.lexers.freefem', 'Freefem', ('freefem',), ('*.edp',), ('text/x-freefem',)),
179
+ 'FuncLexer': ('pip._vendor.pygments.lexers.func', 'FunC', ('func', 'fc'), ('*.fc', '*.func'), ()),
180
+ 'FutharkLexer': ('pip._vendor.pygments.lexers.futhark', 'Futhark', ('futhark',), ('*.fut',), ('text/x-futhark',)),
181
+ 'GAPConsoleLexer': ('pip._vendor.pygments.lexers.algebra', 'GAP session', ('gap-console', 'gap-repl'), ('*.tst',), ()),
182
+ 'GAPLexer': ('pip._vendor.pygments.lexers.algebra', 'GAP', ('gap',), ('*.g', '*.gd', '*.gi', '*.gap'), ()),
183
+ 'GDScriptLexer': ('pip._vendor.pygments.lexers.gdscript', 'GDScript', ('gdscript', 'gd'), ('*.gd',), ('text/x-gdscript', 'application/x-gdscript')),
184
+ 'GLShaderLexer': ('pip._vendor.pygments.lexers.graphics', 'GLSL', ('glsl',), ('*.vert', '*.frag', '*.geo'), ('text/x-glslsrc',)),
185
+ 'GSQLLexer': ('pip._vendor.pygments.lexers.gsql', 'GSQL', ('gsql',), ('*.gsql',), ()),
186
+ 'GasLexer': ('pip._vendor.pygments.lexers.asm', 'GAS', ('gas', 'asm'), ('*.s', '*.S'), ('text/x-gas',)),
187
+ 'GcodeLexer': ('pip._vendor.pygments.lexers.gcodelexer', 'g-code', ('gcode',), ('*.gcode',), ()),
188
+ 'GenshiLexer': ('pip._vendor.pygments.lexers.templates', 'Genshi', ('genshi', 'kid', 'xml+genshi', 'xml+kid'), ('*.kid',), ('application/x-genshi', 'application/x-kid')),
189
+ 'GenshiTextLexer': ('pip._vendor.pygments.lexers.templates', 'Genshi Text', ('genshitext',), (), ('application/x-genshi-text', 'text/x-genshi')),
190
+ 'GettextLexer': ('pip._vendor.pygments.lexers.textfmts', 'Gettext Catalog', ('pot', 'po'), ('*.pot', '*.po'), ('application/x-gettext', 'text/x-gettext', 'text/gettext')),
191
+ 'GherkinLexer': ('pip._vendor.pygments.lexers.testing', 'Gherkin', ('gherkin', 'cucumber'), ('*.feature',), ('text/x-gherkin',)),
192
+ 'GnuplotLexer': ('pip._vendor.pygments.lexers.graphics', 'Gnuplot', ('gnuplot',), ('*.plot', '*.plt'), ('text/x-gnuplot',)),
193
+ 'GoLexer': ('pip._vendor.pygments.lexers.go', 'Go', ('go', 'golang'), ('*.go',), ('text/x-gosrc',)),
194
+ 'GoloLexer': ('pip._vendor.pygments.lexers.jvm', 'Golo', ('golo',), ('*.golo',), ()),
195
+ 'GoodDataCLLexer': ('pip._vendor.pygments.lexers.business', 'GoodData-CL', ('gooddata-cl',), ('*.gdc',), ('text/x-gooddata-cl',)),
196
+ 'GosuLexer': ('pip._vendor.pygments.lexers.jvm', 'Gosu', ('gosu',), ('*.gs', '*.gsx', '*.gsp', '*.vark'), ('text/x-gosu',)),
197
+ 'GosuTemplateLexer': ('pip._vendor.pygments.lexers.jvm', 'Gosu Template', ('gst',), ('*.gst',), ('text/x-gosu-template',)),
198
+ 'GraphQLLexer': ('pip._vendor.pygments.lexers.graphql', 'GraphQL', ('graphql',), ('*.graphql',), ()),
199
+ 'GraphvizLexer': ('pip._vendor.pygments.lexers.graphviz', 'Graphviz', ('graphviz', 'dot'), ('*.gv', '*.dot'), ('text/x-graphviz', 'text/vnd.graphviz')),
200
+ 'GroffLexer': ('pip._vendor.pygments.lexers.markup', 'Groff', ('groff', 'nroff', 'man'), ('*.[1-9]', '*.man', '*.1p', '*.3pm'), ('application/x-troff', 'text/troff')),
201
+ 'GroovyLexer': ('pip._vendor.pygments.lexers.jvm', 'Groovy', ('groovy',), ('*.groovy', '*.gradle'), ('text/x-groovy',)),
202
+ 'HLSLShaderLexer': ('pip._vendor.pygments.lexers.graphics', 'HLSL', ('hlsl',), ('*.hlsl', '*.hlsli'), ('text/x-hlsl',)),
203
+ 'HTMLUL4Lexer': ('pip._vendor.pygments.lexers.ul4', 'HTML+UL4', ('html+ul4',), ('*.htmlul4',), ()),
204
+ 'HamlLexer': ('pip._vendor.pygments.lexers.html', 'Haml', ('haml',), ('*.haml',), ('text/x-haml',)),
205
+ 'HandlebarsHtmlLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Handlebars', ('html+handlebars',), ('*.handlebars', '*.hbs'), ('text/html+handlebars', 'text/x-handlebars-template')),
206
+ 'HandlebarsLexer': ('pip._vendor.pygments.lexers.templates', 'Handlebars', ('handlebars',), (), ()),
207
+ 'HaskellLexer': ('pip._vendor.pygments.lexers.haskell', 'Haskell', ('haskell', 'hs'), ('*.hs',), ('text/x-haskell',)),
208
+ 'HaxeLexer': ('pip._vendor.pygments.lexers.haxe', 'Haxe', ('haxe', 'hxsl', 'hx'), ('*.hx', '*.hxsl'), ('text/haxe', 'text/x-haxe', 'text/x-hx')),
209
+ 'HexdumpLexer': ('pip._vendor.pygments.lexers.hexdump', 'Hexdump', ('hexdump',), (), ()),
210
+ 'HsailLexer': ('pip._vendor.pygments.lexers.asm', 'HSAIL', ('hsail', 'hsa'), ('*.hsail',), ('text/x-hsail',)),
211
+ 'HspecLexer': ('pip._vendor.pygments.lexers.haskell', 'Hspec', ('hspec',), ('*Spec.hs',), ()),
212
+ 'HtmlDjangoLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Django/Jinja', ('html+django', 'html+jinja', 'htmldjango'), ('*.html.j2', '*.htm.j2', '*.xhtml.j2', '*.html.jinja2', '*.htm.jinja2', '*.xhtml.jinja2'), ('text/html+django', 'text/html+jinja')),
213
+ 'HtmlGenshiLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Genshi', ('html+genshi', 'html+kid'), (), ('text/html+genshi',)),
214
+ 'HtmlLexer': ('pip._vendor.pygments.lexers.html', 'HTML', ('html',), ('*.html', '*.htm', '*.xhtml', '*.xslt'), ('text/html', 'application/xhtml+xml')),
215
+ 'HtmlPhpLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+PHP', ('html+php',), ('*.phtml',), ('application/x-php', 'application/x-httpd-php', 'application/x-httpd-php3', 'application/x-httpd-php4', 'application/x-httpd-php5')),
216
+ 'HtmlSmartyLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Smarty', ('html+smarty',), (), ('text/html+smarty',)),
217
+ 'HttpLexer': ('pip._vendor.pygments.lexers.textfmts', 'HTTP', ('http',), (), ()),
218
+ 'HxmlLexer': ('pip._vendor.pygments.lexers.haxe', 'Hxml', ('haxeml', 'hxml'), ('*.hxml',), ()),
219
+ 'HyLexer': ('pip._vendor.pygments.lexers.lisp', 'Hy', ('hylang', 'hy'), ('*.hy',), ('text/x-hy', 'application/x-hy')),
220
+ 'HybrisLexer': ('pip._vendor.pygments.lexers.scripting', 'Hybris', ('hybris',), ('*.hyb',), ('text/x-hybris', 'application/x-hybris')),
221
+ 'IDLLexer': ('pip._vendor.pygments.lexers.idl', 'IDL', ('idl',), ('*.pro',), ('text/idl',)),
222
+ 'IconLexer': ('pip._vendor.pygments.lexers.unicon', 'Icon', ('icon',), ('*.icon', '*.ICON'), ()),
223
+ 'IdrisLexer': ('pip._vendor.pygments.lexers.haskell', 'Idris', ('idris', 'idr'), ('*.idr',), ('text/x-idris',)),
224
+ 'IgorLexer': ('pip._vendor.pygments.lexers.igor', 'Igor', ('igor', 'igorpro'), ('*.ipf',), ('text/ipf',)),
225
+ 'Inform6Lexer': ('pip._vendor.pygments.lexers.int_fiction', 'Inform 6', ('inform6', 'i6'), ('*.inf',), ()),
226
+ 'Inform6TemplateLexer': ('pip._vendor.pygments.lexers.int_fiction', 'Inform 6 template', ('i6t',), ('*.i6t',), ()),
227
+ 'Inform7Lexer': ('pip._vendor.pygments.lexers.int_fiction', 'Inform 7', ('inform7', 'i7'), ('*.ni', '*.i7x'), ()),
228
+ 'IniLexer': ('pip._vendor.pygments.lexers.configs', 'INI', ('ini', 'cfg', 'dosini'), ('*.ini', '*.cfg', '*.inf', '.editorconfig'), ('text/x-ini', 'text/inf')),
229
+ 'IoLexer': ('pip._vendor.pygments.lexers.iolang', 'Io', ('io',), ('*.io',), ('text/x-iosrc',)),
230
+ 'IokeLexer': ('pip._vendor.pygments.lexers.jvm', 'Ioke', ('ioke', 'ik'), ('*.ik',), ('text/x-iokesrc',)),
231
+ 'IrcLogsLexer': ('pip._vendor.pygments.lexers.textfmts', 'IRC logs', ('irc',), ('*.weechatlog',), ('text/x-irclog',)),
232
+ 'IsabelleLexer': ('pip._vendor.pygments.lexers.theorem', 'Isabelle', ('isabelle',), ('*.thy',), ('text/x-isabelle',)),
233
+ 'JLexer': ('pip._vendor.pygments.lexers.j', 'J', ('j',), ('*.ijs',), ('text/x-j',)),
234
+ 'JMESPathLexer': ('pip._vendor.pygments.lexers.jmespath', 'JMESPath', ('jmespath', 'jp'), ('*.jp',), ()),
235
+ 'JSLTLexer': ('pip._vendor.pygments.lexers.jslt', 'JSLT', ('jslt',), ('*.jslt',), ('text/x-jslt',)),
236
+ 'JagsLexer': ('pip._vendor.pygments.lexers.modeling', 'JAGS', ('jags',), ('*.jag', '*.bug'), ()),
237
+ 'JanetLexer': ('pip._vendor.pygments.lexers.lisp', 'Janet', ('janet',), ('*.janet', '*.jdn'), ('text/x-janet', 'application/x-janet')),
238
+ 'JasminLexer': ('pip._vendor.pygments.lexers.jvm', 'Jasmin', ('jasmin', 'jasminxt'), ('*.j',), ()),
239
+ 'JavaLexer': ('pip._vendor.pygments.lexers.jvm', 'Java', ('java',), ('*.java',), ('text/x-java',)),
240
+ 'JavascriptDjangoLexer': ('pip._vendor.pygments.lexers.templates', 'JavaScript+Django/Jinja', ('javascript+django', 'js+django', 'javascript+jinja', 'js+jinja'), ('*.js.j2', '*.js.jinja2'), ('application/x-javascript+django', 'application/x-javascript+jinja', 'text/x-javascript+django', 'text/x-javascript+jinja', 'text/javascript+django', 'text/javascript+jinja')),
241
+ 'JavascriptErbLexer': ('pip._vendor.pygments.lexers.templates', 'JavaScript+Ruby', ('javascript+ruby', 'js+ruby', 'javascript+erb', 'js+erb'), (), ('application/x-javascript+ruby', 'text/x-javascript+ruby', 'text/javascript+ruby')),
242
+ 'JavascriptGenshiLexer': ('pip._vendor.pygments.lexers.templates', 'JavaScript+Genshi Text', ('js+genshitext', 'js+genshi', 'javascript+genshitext', 'javascript+genshi'), (), ('application/x-javascript+genshi', 'text/x-javascript+genshi', 'text/javascript+genshi')),
243
+ 'JavascriptLexer': ('pip._vendor.pygments.lexers.javascript', 'JavaScript', ('javascript', 'js'), ('*.js', '*.jsm', '*.mjs', '*.cjs'), ('application/javascript', 'application/x-javascript', 'text/x-javascript', 'text/javascript')),
244
+ 'JavascriptPhpLexer': ('pip._vendor.pygments.lexers.templates', 'JavaScript+PHP', ('javascript+php', 'js+php'), (), ('application/x-javascript+php', 'text/x-javascript+php', 'text/javascript+php')),
245
+ 'JavascriptSmartyLexer': ('pip._vendor.pygments.lexers.templates', 'JavaScript+Smarty', ('javascript+smarty', 'js+smarty'), (), ('application/x-javascript+smarty', 'text/x-javascript+smarty', 'text/javascript+smarty')),
246
+ 'JavascriptUL4Lexer': ('pip._vendor.pygments.lexers.ul4', 'Javascript+UL4', ('js+ul4',), ('*.jsul4',), ()),
247
+ 'JclLexer': ('pip._vendor.pygments.lexers.scripting', 'JCL', ('jcl',), ('*.jcl',), ('text/x-jcl',)),
248
+ 'JsgfLexer': ('pip._vendor.pygments.lexers.grammar_notation', 'JSGF', ('jsgf',), ('*.jsgf',), ('application/jsgf', 'application/x-jsgf', 'text/jsgf')),
249
+ 'JsonBareObjectLexer': ('pip._vendor.pygments.lexers.data', 'JSONBareObject', (), (), ()),
250
+ 'JsonLdLexer': ('pip._vendor.pygments.lexers.data', 'JSON-LD', ('jsonld', 'json-ld'), ('*.jsonld',), ('application/ld+json',)),
251
+ 'JsonLexer': ('pip._vendor.pygments.lexers.data', 'JSON', ('json', 'json-object'), ('*.json', '*.jsonl', '*.ndjson', 'Pipfile.lock'), ('application/json', 'application/json-object', 'application/x-ndjson', 'application/jsonl', 'application/json-seq')),
252
+ 'JsonnetLexer': ('pip._vendor.pygments.lexers.jsonnet', 'Jsonnet', ('jsonnet',), ('*.jsonnet', '*.libsonnet'), ()),
253
+ 'JspLexer': ('pip._vendor.pygments.lexers.templates', 'Java Server Page', ('jsp',), ('*.jsp',), ('application/x-jsp',)),
254
+ 'JsxLexer': ('pip._vendor.pygments.lexers.jsx', 'JSX', ('jsx', 'react'), ('*.jsx', '*.react'), ('text/jsx', 'text/typescript-jsx')),
255
+ 'JuliaConsoleLexer': ('pip._vendor.pygments.lexers.julia', 'Julia console', ('jlcon', 'julia-repl'), (), ()),
256
+ 'JuliaLexer': ('pip._vendor.pygments.lexers.julia', 'Julia', ('julia', 'jl'), ('*.jl',), ('text/x-julia', 'application/x-julia')),
257
+ 'JuttleLexer': ('pip._vendor.pygments.lexers.javascript', 'Juttle', ('juttle',), ('*.juttle',), ('application/juttle', 'application/x-juttle', 'text/x-juttle', 'text/juttle')),
258
+ 'KLexer': ('pip._vendor.pygments.lexers.q', 'K', ('k',), ('*.k',), ()),
259
+ 'KalLexer': ('pip._vendor.pygments.lexers.javascript', 'Kal', ('kal',), ('*.kal',), ('text/kal', 'application/kal')),
260
+ 'KconfigLexer': ('pip._vendor.pygments.lexers.configs', 'Kconfig', ('kconfig', 'menuconfig', 'linux-config', 'kernel-config'), ('Kconfig*', '*Config.in*', 'external.in*', 'standard-modules.in'), ('text/x-kconfig',)),
261
+ 'KernelLogLexer': ('pip._vendor.pygments.lexers.textfmts', 'Kernel log', ('kmsg', 'dmesg'), ('*.kmsg', '*.dmesg'), ()),
262
+ 'KokaLexer': ('pip._vendor.pygments.lexers.haskell', 'Koka', ('koka',), ('*.kk', '*.kki'), ('text/x-koka',)),
263
+ 'KotlinLexer': ('pip._vendor.pygments.lexers.jvm', 'Kotlin', ('kotlin',), ('*.kt', '*.kts'), ('text/x-kotlin',)),
264
+ 'KuinLexer': ('pip._vendor.pygments.lexers.kuin', 'Kuin', ('kuin',), ('*.kn',), ()),
265
+ 'KustoLexer': ('pip._vendor.pygments.lexers.kusto', 'Kusto', ('kql', 'kusto'), ('*.kql', '*.kusto', '.csl'), ()),
266
+ 'LSLLexer': ('pip._vendor.pygments.lexers.scripting', 'LSL', ('lsl',), ('*.lsl',), ('text/x-lsl',)),
267
+ 'LassoCssLexer': ('pip._vendor.pygments.lexers.templates', 'CSS+Lasso', ('css+lasso',), (), ('text/css+lasso',)),
268
+ 'LassoHtmlLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Lasso', ('html+lasso',), (), ('text/html+lasso', 'application/x-httpd-lasso', 'application/x-httpd-lasso[89]')),
269
+ 'LassoJavascriptLexer': ('pip._vendor.pygments.lexers.templates', 'JavaScript+Lasso', ('javascript+lasso', 'js+lasso'), (), ('application/x-javascript+lasso', 'text/x-javascript+lasso', 'text/javascript+lasso')),
270
+ 'LassoLexer': ('pip._vendor.pygments.lexers.javascript', 'Lasso', ('lasso', 'lassoscript'), ('*.lasso', '*.lasso[89]'), ('text/x-lasso',)),
271
+ 'LassoXmlLexer': ('pip._vendor.pygments.lexers.templates', 'XML+Lasso', ('xml+lasso',), (), ('application/xml+lasso',)),
272
+ 'LdaprcLexer': ('pip._vendor.pygments.lexers.ldap', 'LDAP configuration file', ('ldapconf', 'ldaprc'), ('.ldaprc', 'ldaprc', 'ldap.conf'), ('text/x-ldapconf',)),
273
+ 'LdifLexer': ('pip._vendor.pygments.lexers.ldap', 'LDIF', ('ldif',), ('*.ldif',), ('text/x-ldif',)),
274
+ 'Lean3Lexer': ('pip._vendor.pygments.lexers.lean', 'Lean', ('lean', 'lean3'), ('*.lean',), ('text/x-lean', 'text/x-lean3')),
275
+ 'Lean4Lexer': ('pip._vendor.pygments.lexers.lean', 'Lean4', ('lean4',), ('*.lean',), ('text/x-lean4',)),
276
+ 'LessCssLexer': ('pip._vendor.pygments.lexers.css', 'LessCss', ('less',), ('*.less',), ('text/x-less-css',)),
277
+ 'LighttpdConfLexer': ('pip._vendor.pygments.lexers.configs', 'Lighttpd configuration file', ('lighttpd', 'lighty'), ('lighttpd.conf',), ('text/x-lighttpd-conf',)),
278
+ 'LilyPondLexer': ('pip._vendor.pygments.lexers.lilypond', 'LilyPond', ('lilypond',), ('*.ly',), ()),
279
+ 'LimboLexer': ('pip._vendor.pygments.lexers.inferno', 'Limbo', ('limbo',), ('*.b',), ('text/limbo',)),
280
+ 'LiquidLexer': ('pip._vendor.pygments.lexers.templates', 'liquid', ('liquid',), ('*.liquid',), ()),
281
+ 'LiterateAgdaLexer': ('pip._vendor.pygments.lexers.haskell', 'Literate Agda', ('literate-agda', 'lagda'), ('*.lagda',), ('text/x-literate-agda',)),
282
+ 'LiterateCryptolLexer': ('pip._vendor.pygments.lexers.haskell', 'Literate Cryptol', ('literate-cryptol', 'lcryptol', 'lcry'), ('*.lcry',), ('text/x-literate-cryptol',)),
283
+ 'LiterateHaskellLexer': ('pip._vendor.pygments.lexers.haskell', 'Literate Haskell', ('literate-haskell', 'lhaskell', 'lhs'), ('*.lhs',), ('text/x-literate-haskell',)),
284
+ 'LiterateIdrisLexer': ('pip._vendor.pygments.lexers.haskell', 'Literate Idris', ('literate-idris', 'lidris', 'lidr'), ('*.lidr',), ('text/x-literate-idris',)),
285
+ 'LiveScriptLexer': ('pip._vendor.pygments.lexers.javascript', 'LiveScript', ('livescript', 'live-script'), ('*.ls',), ('text/livescript',)),
286
+ 'LlvmLexer': ('pip._vendor.pygments.lexers.asm', 'LLVM', ('llvm',), ('*.ll',), ('text/x-llvm',)),
287
+ 'LlvmMirBodyLexer': ('pip._vendor.pygments.lexers.asm', 'LLVM-MIR Body', ('llvm-mir-body',), (), ()),
288
+ 'LlvmMirLexer': ('pip._vendor.pygments.lexers.asm', 'LLVM-MIR', ('llvm-mir',), ('*.mir',), ()),
289
+ 'LogosLexer': ('pip._vendor.pygments.lexers.objective', 'Logos', ('logos',), ('*.x', '*.xi', '*.xm', '*.xmi'), ('text/x-logos',)),
290
+ 'LogtalkLexer': ('pip._vendor.pygments.lexers.prolog', 'Logtalk', ('logtalk',), ('*.lgt', '*.logtalk'), ('text/x-logtalk',)),
291
+ 'LuaLexer': ('pip._vendor.pygments.lexers.scripting', 'Lua', ('lua',), ('*.lua', '*.wlua'), ('text/x-lua', 'application/x-lua')),
292
+ 'LuauLexer': ('pip._vendor.pygments.lexers.scripting', 'Luau', ('luau',), ('*.luau',), ()),
293
+ 'MCFunctionLexer': ('pip._vendor.pygments.lexers.minecraft', 'MCFunction', ('mcfunction', 'mcf'), ('*.mcfunction',), ('text/mcfunction',)),
294
+ 'MCSchemaLexer': ('pip._vendor.pygments.lexers.minecraft', 'MCSchema', ('mcschema',), ('*.mcschema',), ('text/mcschema',)),
295
+ 'MIMELexer': ('pip._vendor.pygments.lexers.mime', 'MIME', ('mime',), (), ('multipart/mixed', 'multipart/related', 'multipart/alternative')),
296
+ 'MIPSLexer': ('pip._vendor.pygments.lexers.mips', 'MIPS', ('mips',), ('*.mips', '*.MIPS'), ()),
297
+ 'MOOCodeLexer': ('pip._vendor.pygments.lexers.scripting', 'MOOCode', ('moocode', 'moo'), ('*.moo',), ('text/x-moocode',)),
298
+ 'MSDOSSessionLexer': ('pip._vendor.pygments.lexers.shell', 'MSDOS Session', ('doscon',), (), ()),
299
+ 'Macaulay2Lexer': ('pip._vendor.pygments.lexers.macaulay2', 'Macaulay2', ('macaulay2',), ('*.m2',), ()),
300
+ 'MakefileLexer': ('pip._vendor.pygments.lexers.make', 'Makefile', ('make', 'makefile', 'mf', 'bsdmake'), ('*.mak', '*.mk', 'Makefile', 'makefile', 'Makefile.*', 'GNUmakefile'), ('text/x-makefile',)),
301
+ 'MakoCssLexer': ('pip._vendor.pygments.lexers.templates', 'CSS+Mako', ('css+mako',), (), ('text/css+mako',)),
302
+ 'MakoHtmlLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Mako', ('html+mako',), (), ('text/html+mako',)),
303
+ 'MakoJavascriptLexer': ('pip._vendor.pygments.lexers.templates', 'JavaScript+Mako', ('javascript+mako', 'js+mako'), (), ('application/x-javascript+mako', 'text/x-javascript+mako', 'text/javascript+mako')),
304
+ 'MakoLexer': ('pip._vendor.pygments.lexers.templates', 'Mako', ('mako',), ('*.mao',), ('application/x-mako',)),
305
+ 'MakoXmlLexer': ('pip._vendor.pygments.lexers.templates', 'XML+Mako', ('xml+mako',), (), ('application/xml+mako',)),
306
+ 'MaqlLexer': ('pip._vendor.pygments.lexers.business', 'MAQL', ('maql',), ('*.maql',), ('text/x-gooddata-maql', 'application/x-gooddata-maql')),
307
+ 'MarkdownLexer': ('pip._vendor.pygments.lexers.markup', 'Markdown', ('markdown', 'md'), ('*.md', '*.markdown'), ('text/x-markdown',)),
308
+ 'MaskLexer': ('pip._vendor.pygments.lexers.javascript', 'Mask', ('mask',), ('*.mask',), ('text/x-mask',)),
309
+ 'MasonLexer': ('pip._vendor.pygments.lexers.templates', 'Mason', ('mason',), ('*.m', '*.mhtml', '*.mc', '*.mi', 'autohandler', 'dhandler'), ('application/x-mason',)),
310
+ 'MathematicaLexer': ('pip._vendor.pygments.lexers.algebra', 'Mathematica', ('mathematica', 'mma', 'nb'), ('*.nb', '*.cdf', '*.nbp', '*.ma'), ('application/mathematica', 'application/vnd.wolfram.mathematica', 'application/vnd.wolfram.mathematica.package', 'application/vnd.wolfram.cdf')),
311
+ 'MatlabLexer': ('pip._vendor.pygments.lexers.matlab', 'Matlab', ('matlab',), ('*.m',), ('text/matlab',)),
312
+ 'MatlabSessionLexer': ('pip._vendor.pygments.lexers.matlab', 'Matlab session', ('matlabsession',), (), ()),
313
+ 'MaximaLexer': ('pip._vendor.pygments.lexers.maxima', 'Maxima', ('maxima', 'macsyma'), ('*.mac', '*.max'), ()),
314
+ 'MesonLexer': ('pip._vendor.pygments.lexers.meson', 'Meson', ('meson', 'meson.build'), ('meson.build', 'meson_options.txt'), ('text/x-meson',)),
315
+ 'MiniDLexer': ('pip._vendor.pygments.lexers.d', 'MiniD', ('minid',), (), ('text/x-minidsrc',)),
316
+ 'MiniScriptLexer': ('pip._vendor.pygments.lexers.scripting', 'MiniScript', ('miniscript', 'ms'), ('*.ms',), ('text/x-minicript', 'application/x-miniscript')),
317
+ 'ModelicaLexer': ('pip._vendor.pygments.lexers.modeling', 'Modelica', ('modelica',), ('*.mo',), ('text/x-modelica',)),
318
+ 'Modula2Lexer': ('pip._vendor.pygments.lexers.modula2', 'Modula-2', ('modula2', 'm2'), ('*.def', '*.mod'), ('text/x-modula2',)),
319
+ 'MoinWikiLexer': ('pip._vendor.pygments.lexers.markup', 'MoinMoin/Trac Wiki markup', ('trac-wiki', 'moin'), (), ('text/x-trac-wiki',)),
320
+ 'MojoLexer': ('pip._vendor.pygments.lexers.mojo', 'Mojo', ('mojo', '🔥'), ('*.mojo', '*.🔥'), ('text/x-mojo', 'application/x-mojo')),
321
+ 'MonkeyLexer': ('pip._vendor.pygments.lexers.basic', 'Monkey', ('monkey',), ('*.monkey',), ('text/x-monkey',)),
322
+ 'MonteLexer': ('pip._vendor.pygments.lexers.monte', 'Monte', ('monte',), ('*.mt',), ()),
323
+ 'MoonScriptLexer': ('pip._vendor.pygments.lexers.scripting', 'MoonScript', ('moonscript', 'moon'), ('*.moon',), ('text/x-moonscript', 'application/x-moonscript')),
324
+ 'MoselLexer': ('pip._vendor.pygments.lexers.mosel', 'Mosel', ('mosel',), ('*.mos',), ()),
325
+ 'MozPreprocCssLexer': ('pip._vendor.pygments.lexers.markup', 'CSS+mozpreproc', ('css+mozpreproc',), ('*.css.in',), ()),
326
+ 'MozPreprocHashLexer': ('pip._vendor.pygments.lexers.markup', 'mozhashpreproc', ('mozhashpreproc',), (), ()),
327
+ 'MozPreprocJavascriptLexer': ('pip._vendor.pygments.lexers.markup', 'Javascript+mozpreproc', ('javascript+mozpreproc',), ('*.js.in',), ()),
328
+ 'MozPreprocPercentLexer': ('pip._vendor.pygments.lexers.markup', 'mozpercentpreproc', ('mozpercentpreproc',), (), ()),
329
+ 'MozPreprocXulLexer': ('pip._vendor.pygments.lexers.markup', 'XUL+mozpreproc', ('xul+mozpreproc',), ('*.xul.in',), ()),
330
+ 'MqlLexer': ('pip._vendor.pygments.lexers.c_like', 'MQL', ('mql', 'mq4', 'mq5', 'mql4', 'mql5'), ('*.mq4', '*.mq5', '*.mqh'), ('text/x-mql',)),
331
+ 'MscgenLexer': ('pip._vendor.pygments.lexers.dsls', 'Mscgen', ('mscgen', 'msc'), ('*.msc',), ()),
332
+ 'MuPADLexer': ('pip._vendor.pygments.lexers.algebra', 'MuPAD', ('mupad',), ('*.mu',), ()),
333
+ 'MxmlLexer': ('pip._vendor.pygments.lexers.actionscript', 'MXML', ('mxml',), ('*.mxml',), ()),
334
+ 'MySqlLexer': ('pip._vendor.pygments.lexers.sql', 'MySQL', ('mysql',), (), ('text/x-mysql',)),
335
+ 'MyghtyCssLexer': ('pip._vendor.pygments.lexers.templates', 'CSS+Myghty', ('css+myghty',), (), ('text/css+myghty',)),
336
+ 'MyghtyHtmlLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Myghty', ('html+myghty',), (), ('text/html+myghty',)),
337
+ 'MyghtyJavascriptLexer': ('pip._vendor.pygments.lexers.templates', 'JavaScript+Myghty', ('javascript+myghty', 'js+myghty'), (), ('application/x-javascript+myghty', 'text/x-javascript+myghty', 'text/javascript+mygthy')),
338
+ 'MyghtyLexer': ('pip._vendor.pygments.lexers.templates', 'Myghty', ('myghty',), ('*.myt', 'autodelegate'), ('application/x-myghty',)),
339
+ 'MyghtyXmlLexer': ('pip._vendor.pygments.lexers.templates', 'XML+Myghty', ('xml+myghty',), (), ('application/xml+myghty',)),
340
+ 'NCLLexer': ('pip._vendor.pygments.lexers.ncl', 'NCL', ('ncl',), ('*.ncl',), ('text/ncl',)),
341
+ 'NSISLexer': ('pip._vendor.pygments.lexers.installers', 'NSIS', ('nsis', 'nsi', 'nsh'), ('*.nsi', '*.nsh'), ('text/x-nsis',)),
342
+ 'NasmLexer': ('pip._vendor.pygments.lexers.asm', 'NASM', ('nasm',), ('*.asm', '*.ASM', '*.nasm'), ('text/x-nasm',)),
343
+ 'NasmObjdumpLexer': ('pip._vendor.pygments.lexers.asm', 'objdump-nasm', ('objdump-nasm',), ('*.objdump-intel',), ('text/x-nasm-objdump',)),
344
+ 'NemerleLexer': ('pip._vendor.pygments.lexers.dotnet', 'Nemerle', ('nemerle',), ('*.n',), ('text/x-nemerle',)),
345
+ 'NesCLexer': ('pip._vendor.pygments.lexers.c_like', 'nesC', ('nesc',), ('*.nc',), ('text/x-nescsrc',)),
346
+ 'NestedTextLexer': ('pip._vendor.pygments.lexers.configs', 'NestedText', ('nestedtext', 'nt'), ('*.nt',), ()),
347
+ 'NewLispLexer': ('pip._vendor.pygments.lexers.lisp', 'NewLisp', ('newlisp',), ('*.lsp', '*.nl', '*.kif'), ('text/x-newlisp', 'application/x-newlisp')),
348
+ 'NewspeakLexer': ('pip._vendor.pygments.lexers.smalltalk', 'Newspeak', ('newspeak',), ('*.ns2',), ('text/x-newspeak',)),
349
+ 'NginxConfLexer': ('pip._vendor.pygments.lexers.configs', 'Nginx configuration file', ('nginx',), ('nginx.conf',), ('text/x-nginx-conf',)),
350
+ 'NimrodLexer': ('pip._vendor.pygments.lexers.nimrod', 'Nimrod', ('nimrod', 'nim'), ('*.nim', '*.nimrod'), ('text/x-nim',)),
351
+ 'NitLexer': ('pip._vendor.pygments.lexers.nit', 'Nit', ('nit',), ('*.nit',), ()),
352
+ 'NixLexer': ('pip._vendor.pygments.lexers.nix', 'Nix', ('nixos', 'nix'), ('*.nix',), ('text/x-nix',)),
353
+ 'NodeConsoleLexer': ('pip._vendor.pygments.lexers.javascript', 'Node.js REPL console session', ('nodejsrepl',), (), ('text/x-nodejsrepl',)),
354
+ 'NotmuchLexer': ('pip._vendor.pygments.lexers.textfmts', 'Notmuch', ('notmuch',), (), ()),
355
+ 'NuSMVLexer': ('pip._vendor.pygments.lexers.smv', 'NuSMV', ('nusmv',), ('*.smv',), ()),
356
+ 'NumPyLexer': ('pip._vendor.pygments.lexers.python', 'NumPy', ('numpy',), (), ()),
357
+ 'ObjdumpLexer': ('pip._vendor.pygments.lexers.asm', 'objdump', ('objdump',), ('*.objdump',), ('text/x-objdump',)),
358
+ 'ObjectiveCLexer': ('pip._vendor.pygments.lexers.objective', 'Objective-C', ('objective-c', 'objectivec', 'obj-c', 'objc'), ('*.m', '*.h'), ('text/x-objective-c',)),
359
+ 'ObjectiveCppLexer': ('pip._vendor.pygments.lexers.objective', 'Objective-C++', ('objective-c++', 'objectivec++', 'obj-c++', 'objc++'), ('*.mm', '*.hh'), ('text/x-objective-c++',)),
360
+ 'ObjectiveJLexer': ('pip._vendor.pygments.lexers.javascript', 'Objective-J', ('objective-j', 'objectivej', 'obj-j', 'objj'), ('*.j',), ('text/x-objective-j',)),
361
+ 'OcamlLexer': ('pip._vendor.pygments.lexers.ml', 'OCaml', ('ocaml',), ('*.ml', '*.mli', '*.mll', '*.mly'), ('text/x-ocaml',)),
362
+ 'OctaveLexer': ('pip._vendor.pygments.lexers.matlab', 'Octave', ('octave',), ('*.m',), ('text/octave',)),
363
+ 'OdinLexer': ('pip._vendor.pygments.lexers.archetype', 'ODIN', ('odin',), ('*.odin',), ('text/odin',)),
364
+ 'OmgIdlLexer': ('pip._vendor.pygments.lexers.c_like', 'OMG Interface Definition Language', ('omg-idl',), ('*.idl', '*.pidl'), ()),
365
+ 'OocLexer': ('pip._vendor.pygments.lexers.ooc', 'Ooc', ('ooc',), ('*.ooc',), ('text/x-ooc',)),
366
+ 'OpaLexer': ('pip._vendor.pygments.lexers.ml', 'Opa', ('opa',), ('*.opa',), ('text/x-opa',)),
367
+ 'OpenEdgeLexer': ('pip._vendor.pygments.lexers.business', 'OpenEdge ABL', ('openedge', 'abl', 'progress'), ('*.p', '*.cls'), ('text/x-openedge', 'application/x-openedge')),
368
+ 'OpenScadLexer': ('pip._vendor.pygments.lexers.openscad', 'OpenSCAD', ('openscad',), ('*.scad',), ('application/x-openscad',)),
369
+ 'OrgLexer': ('pip._vendor.pygments.lexers.markup', 'Org Mode', ('org', 'orgmode', 'org-mode'), ('*.org',), ('text/org',)),
370
+ 'OutputLexer': ('pip._vendor.pygments.lexers.special', 'Text output', ('output',), (), ()),
371
+ 'PacmanConfLexer': ('pip._vendor.pygments.lexers.configs', 'PacmanConf', ('pacmanconf',), ('pacman.conf',), ()),
372
+ 'PanLexer': ('pip._vendor.pygments.lexers.dsls', 'Pan', ('pan',), ('*.pan',), ()),
373
+ 'ParaSailLexer': ('pip._vendor.pygments.lexers.parasail', 'ParaSail', ('parasail',), ('*.psi', '*.psl'), ('text/x-parasail',)),
374
+ 'PawnLexer': ('pip._vendor.pygments.lexers.pawn', 'Pawn', ('pawn',), ('*.p', '*.pwn', '*.inc'), ('text/x-pawn',)),
375
+ 'PegLexer': ('pip._vendor.pygments.lexers.grammar_notation', 'PEG', ('peg',), ('*.peg',), ('text/x-peg',)),
376
+ 'Perl6Lexer': ('pip._vendor.pygments.lexers.perl', 'Perl6', ('perl6', 'pl6', 'raku'), ('*.pl', '*.pm', '*.nqp', '*.p6', '*.6pl', '*.p6l', '*.pl6', '*.6pm', '*.p6m', '*.pm6', '*.t', '*.raku', '*.rakumod', '*.rakutest', '*.rakudoc'), ('text/x-perl6', 'application/x-perl6')),
377
+ 'PerlLexer': ('pip._vendor.pygments.lexers.perl', 'Perl', ('perl', 'pl'), ('*.pl', '*.pm', '*.t', '*.perl'), ('text/x-perl', 'application/x-perl')),
378
+ 'PhixLexer': ('pip._vendor.pygments.lexers.phix', 'Phix', ('phix',), ('*.exw',), ('text/x-phix',)),
379
+ 'PhpLexer': ('pip._vendor.pygments.lexers.php', 'PHP', ('php', 'php3', 'php4', 'php5'), ('*.php', '*.php[345]', '*.inc'), ('text/x-php',)),
380
+ 'PigLexer': ('pip._vendor.pygments.lexers.jvm', 'Pig', ('pig',), ('*.pig',), ('text/x-pig',)),
381
+ 'PikeLexer': ('pip._vendor.pygments.lexers.c_like', 'Pike', ('pike',), ('*.pike', '*.pmod'), ('text/x-pike',)),
382
+ 'PkgConfigLexer': ('pip._vendor.pygments.lexers.configs', 'PkgConfig', ('pkgconfig',), ('*.pc',), ()),
383
+ 'PlPgsqlLexer': ('pip._vendor.pygments.lexers.sql', 'PL/pgSQL', ('plpgsql',), (), ('text/x-plpgsql',)),
384
+ 'PointlessLexer': ('pip._vendor.pygments.lexers.pointless', 'Pointless', ('pointless',), ('*.ptls',), ()),
385
+ 'PonyLexer': ('pip._vendor.pygments.lexers.pony', 'Pony', ('pony',), ('*.pony',), ()),
386
+ 'PortugolLexer': ('pip._vendor.pygments.lexers.pascal', 'Portugol', ('portugol',), ('*.alg', '*.portugol'), ()),
387
+ 'PostScriptLexer': ('pip._vendor.pygments.lexers.graphics', 'PostScript', ('postscript', 'postscr'), ('*.ps', '*.eps'), ('application/postscript',)),
388
+ 'PostgresConsoleLexer': ('pip._vendor.pygments.lexers.sql', 'PostgreSQL console (psql)', ('psql', 'postgresql-console', 'postgres-console'), (), ('text/x-postgresql-psql',)),
389
+ 'PostgresExplainLexer': ('pip._vendor.pygments.lexers.sql', 'PostgreSQL EXPLAIN dialect', ('postgres-explain',), ('*.explain',), ('text/x-postgresql-explain',)),
390
+ 'PostgresLexer': ('pip._vendor.pygments.lexers.sql', 'PostgreSQL SQL dialect', ('postgresql', 'postgres'), (), ('text/x-postgresql',)),
391
+ 'PovrayLexer': ('pip._vendor.pygments.lexers.graphics', 'POVRay', ('pov',), ('*.pov', '*.inc'), ('text/x-povray',)),
392
+ 'PowerShellLexer': ('pip._vendor.pygments.lexers.shell', 'PowerShell', ('powershell', 'pwsh', 'posh', 'ps1', 'psm1'), ('*.ps1', '*.psm1'), ('text/x-powershell',)),
393
+ 'PowerShellSessionLexer': ('pip._vendor.pygments.lexers.shell', 'PowerShell Session', ('pwsh-session', 'ps1con'), (), ()),
394
+ 'PraatLexer': ('pip._vendor.pygments.lexers.praat', 'Praat', ('praat',), ('*.praat', '*.proc', '*.psc'), ()),
395
+ 'ProcfileLexer': ('pip._vendor.pygments.lexers.procfile', 'Procfile', ('procfile',), ('Procfile',), ()),
396
+ 'PrologLexer': ('pip._vendor.pygments.lexers.prolog', 'Prolog', ('prolog',), ('*.ecl', '*.prolog', '*.pro', '*.pl'), ('text/x-prolog',)),
397
+ 'PromQLLexer': ('pip._vendor.pygments.lexers.promql', 'PromQL', ('promql',), ('*.promql',), ()),
398
+ 'PromelaLexer': ('pip._vendor.pygments.lexers.c_like', 'Promela', ('promela',), ('*.pml', '*.prom', '*.prm', '*.promela', '*.pr', '*.pm'), ('text/x-promela',)),
399
+ 'PropertiesLexer': ('pip._vendor.pygments.lexers.configs', 'Properties', ('properties', 'jproperties'), ('*.properties',), ('text/x-java-properties',)),
400
+ 'ProtoBufLexer': ('pip._vendor.pygments.lexers.dsls', 'Protocol Buffer', ('protobuf', 'proto'), ('*.proto',), ()),
401
+ 'PrqlLexer': ('pip._vendor.pygments.lexers.prql', 'PRQL', ('prql',), ('*.prql',), ('application/prql', 'application/x-prql')),
402
+ 'PsyshConsoleLexer': ('pip._vendor.pygments.lexers.php', 'PsySH console session for PHP', ('psysh',), (), ()),
403
+ 'PtxLexer': ('pip._vendor.pygments.lexers.ptx', 'PTX', ('ptx',), ('*.ptx',), ('text/x-ptx',)),
404
+ 'PugLexer': ('pip._vendor.pygments.lexers.html', 'Pug', ('pug', 'jade'), ('*.pug', '*.jade'), ('text/x-pug', 'text/x-jade')),
405
+ 'PuppetLexer': ('pip._vendor.pygments.lexers.dsls', 'Puppet', ('puppet',), ('*.pp',), ()),
406
+ 'PyPyLogLexer': ('pip._vendor.pygments.lexers.console', 'PyPy Log', ('pypylog', 'pypy'), ('*.pypylog',), ('application/x-pypylog',)),
407
+ 'Python2Lexer': ('pip._vendor.pygments.lexers.python', 'Python 2.x', ('python2', 'py2'), (), ('text/x-python2', 'application/x-python2')),
408
+ 'Python2TracebackLexer': ('pip._vendor.pygments.lexers.python', 'Python 2.x Traceback', ('py2tb',), ('*.py2tb',), ('text/x-python2-traceback',)),
409
+ 'PythonConsoleLexer': ('pip._vendor.pygments.lexers.python', 'Python console session', ('pycon', 'python-console'), (), ('text/x-python-doctest',)),
410
+ 'PythonLexer': ('pip._vendor.pygments.lexers.python', 'Python', ('python', 'py', 'sage', 'python3', 'py3', 'bazel', 'starlark'), ('*.py', '*.pyw', '*.pyi', '*.jy', '*.sage', '*.sc', 'SConstruct', 'SConscript', '*.bzl', 'BUCK', 'BUILD', 'BUILD.bazel', 'WORKSPACE', '*.tac'), ('text/x-python', 'application/x-python', 'text/x-python3', 'application/x-python3')),
411
+ 'PythonTracebackLexer': ('pip._vendor.pygments.lexers.python', 'Python Traceback', ('pytb', 'py3tb'), ('*.pytb', '*.py3tb'), ('text/x-python-traceback', 'text/x-python3-traceback')),
412
+ 'PythonUL4Lexer': ('pip._vendor.pygments.lexers.ul4', 'Python+UL4', ('py+ul4',), ('*.pyul4',), ()),
413
+ 'QBasicLexer': ('pip._vendor.pygments.lexers.basic', 'QBasic', ('qbasic', 'basic'), ('*.BAS', '*.bas'), ('text/basic',)),
414
+ 'QLexer': ('pip._vendor.pygments.lexers.q', 'Q', ('q',), ('*.q',), ()),
415
+ 'QVToLexer': ('pip._vendor.pygments.lexers.qvt', 'QVTO', ('qvto', 'qvt'), ('*.qvto',), ()),
416
+ 'QlikLexer': ('pip._vendor.pygments.lexers.qlik', 'Qlik', ('qlik', 'qlikview', 'qliksense', 'qlikscript'), ('*.qvs', '*.qvw'), ()),
417
+ 'QmlLexer': ('pip._vendor.pygments.lexers.webmisc', 'QML', ('qml', 'qbs'), ('*.qml', '*.qbs'), ('application/x-qml', 'application/x-qt.qbs+qml')),
418
+ 'RConsoleLexer': ('pip._vendor.pygments.lexers.r', 'RConsole', ('rconsole', 'rout'), ('*.Rout',), ()),
419
+ 'RNCCompactLexer': ('pip._vendor.pygments.lexers.rnc', 'Relax-NG Compact', ('rng-compact', 'rnc'), ('*.rnc',), ()),
420
+ 'RPMSpecLexer': ('pip._vendor.pygments.lexers.installers', 'RPMSpec', ('spec',), ('*.spec',), ('text/x-rpm-spec',)),
421
+ 'RacketLexer': ('pip._vendor.pygments.lexers.lisp', 'Racket', ('racket', 'rkt'), ('*.rkt', '*.rktd', '*.rktl'), ('text/x-racket', 'application/x-racket')),
422
+ 'RagelCLexer': ('pip._vendor.pygments.lexers.parsers', 'Ragel in C Host', ('ragel-c',), ('*.rl',), ()),
423
+ 'RagelCppLexer': ('pip._vendor.pygments.lexers.parsers', 'Ragel in CPP Host', ('ragel-cpp',), ('*.rl',), ()),
424
+ 'RagelDLexer': ('pip._vendor.pygments.lexers.parsers', 'Ragel in D Host', ('ragel-d',), ('*.rl',), ()),
425
+ 'RagelEmbeddedLexer': ('pip._vendor.pygments.lexers.parsers', 'Embedded Ragel', ('ragel-em',), ('*.rl',), ()),
426
+ 'RagelJavaLexer': ('pip._vendor.pygments.lexers.parsers', 'Ragel in Java Host', ('ragel-java',), ('*.rl',), ()),
427
+ 'RagelLexer': ('pip._vendor.pygments.lexers.parsers', 'Ragel', ('ragel',), (), ()),
428
+ 'RagelObjectiveCLexer': ('pip._vendor.pygments.lexers.parsers', 'Ragel in Objective C Host', ('ragel-objc',), ('*.rl',), ()),
429
+ 'RagelRubyLexer': ('pip._vendor.pygments.lexers.parsers', 'Ragel in Ruby Host', ('ragel-ruby', 'ragel-rb'), ('*.rl',), ()),
430
+ 'RawTokenLexer': ('pip._vendor.pygments.lexers.special', 'Raw token data', (), (), ('application/x-pygments-tokens',)),
431
+ 'RdLexer': ('pip._vendor.pygments.lexers.r', 'Rd', ('rd',), ('*.Rd',), ('text/x-r-doc',)),
432
+ 'ReasonLexer': ('pip._vendor.pygments.lexers.ml', 'ReasonML', ('reasonml', 'reason'), ('*.re', '*.rei'), ('text/x-reasonml',)),
433
+ 'RebolLexer': ('pip._vendor.pygments.lexers.rebol', 'REBOL', ('rebol',), ('*.r', '*.r3', '*.reb'), ('text/x-rebol',)),
434
+ 'RedLexer': ('pip._vendor.pygments.lexers.rebol', 'Red', ('red', 'red/system'), ('*.red', '*.reds'), ('text/x-red', 'text/x-red-system')),
435
+ 'RedcodeLexer': ('pip._vendor.pygments.lexers.esoteric', 'Redcode', ('redcode',), ('*.cw',), ()),
436
+ 'RegeditLexer': ('pip._vendor.pygments.lexers.configs', 'reg', ('registry',), ('*.reg',), ('text/x-windows-registry',)),
437
+ 'ResourceLexer': ('pip._vendor.pygments.lexers.resource', 'ResourceBundle', ('resourcebundle', 'resource'), (), ()),
438
+ 'RexxLexer': ('pip._vendor.pygments.lexers.scripting', 'Rexx', ('rexx', 'arexx'), ('*.rexx', '*.rex', '*.rx', '*.arexx'), ('text/x-rexx',)),
439
+ 'RhtmlLexer': ('pip._vendor.pygments.lexers.templates', 'RHTML', ('rhtml', 'html+erb', 'html+ruby'), ('*.rhtml',), ('text/html+ruby',)),
440
+ 'RideLexer': ('pip._vendor.pygments.lexers.ride', 'Ride', ('ride',), ('*.ride',), ('text/x-ride',)),
441
+ 'RitaLexer': ('pip._vendor.pygments.lexers.rita', 'Rita', ('rita',), ('*.rita',), ('text/rita',)),
442
+ 'RoboconfGraphLexer': ('pip._vendor.pygments.lexers.roboconf', 'Roboconf Graph', ('roboconf-graph',), ('*.graph',), ()),
443
+ 'RoboconfInstancesLexer': ('pip._vendor.pygments.lexers.roboconf', 'Roboconf Instances', ('roboconf-instances',), ('*.instances',), ()),
444
+ 'RobotFrameworkLexer': ('pip._vendor.pygments.lexers.robotframework', 'RobotFramework', ('robotframework',), ('*.robot', '*.resource'), ('text/x-robotframework',)),
445
+ 'RqlLexer': ('pip._vendor.pygments.lexers.sql', 'RQL', ('rql',), ('*.rql',), ('text/x-rql',)),
446
+ 'RslLexer': ('pip._vendor.pygments.lexers.dsls', 'RSL', ('rsl',), ('*.rsl',), ('text/rsl',)),
447
+ 'RstLexer': ('pip._vendor.pygments.lexers.markup', 'reStructuredText', ('restructuredtext', 'rst', 'rest'), ('*.rst', '*.rest'), ('text/x-rst', 'text/prs.fallenstein.rst')),
448
+ 'RtsLexer': ('pip._vendor.pygments.lexers.trafficscript', 'TrafficScript', ('trafficscript', 'rts'), ('*.rts',), ()),
449
+ 'RubyConsoleLexer': ('pip._vendor.pygments.lexers.ruby', 'Ruby irb session', ('rbcon', 'irb'), (), ('text/x-ruby-shellsession',)),
450
+ 'RubyLexer': ('pip._vendor.pygments.lexers.ruby', 'Ruby', ('ruby', 'rb', 'duby'), ('*.rb', '*.rbw', 'Rakefile', '*.rake', '*.gemspec', '*.rbx', '*.duby', 'Gemfile', 'Vagrantfile'), ('text/x-ruby', 'application/x-ruby')),
451
+ 'RustLexer': ('pip._vendor.pygments.lexers.rust', 'Rust', ('rust', 'rs'), ('*.rs', '*.rs.in'), ('text/rust', 'text/x-rust')),
452
+ 'SASLexer': ('pip._vendor.pygments.lexers.sas', 'SAS', ('sas',), ('*.SAS', '*.sas'), ('text/x-sas', 'text/sas', 'application/x-sas')),
453
+ 'SLexer': ('pip._vendor.pygments.lexers.r', 'S', ('splus', 's', 'r'), ('*.S', '*.R', '.Rhistory', '.Rprofile', '.Renviron'), ('text/S-plus', 'text/S', 'text/x-r-source', 'text/x-r', 'text/x-R', 'text/x-r-history', 'text/x-r-profile')),
454
+ 'SMLLexer': ('pip._vendor.pygments.lexers.ml', 'Standard ML', ('sml',), ('*.sml', '*.sig', '*.fun'), ('text/x-standardml', 'application/x-standardml')),
455
+ 'SNBTLexer': ('pip._vendor.pygments.lexers.minecraft', 'SNBT', ('snbt',), ('*.snbt',), ('text/snbt',)),
456
+ 'SarlLexer': ('pip._vendor.pygments.lexers.jvm', 'SARL', ('sarl',), ('*.sarl',), ('text/x-sarl',)),
457
+ 'SassLexer': ('pip._vendor.pygments.lexers.css', 'Sass', ('sass',), ('*.sass',), ('text/x-sass',)),
458
+ 'SaviLexer': ('pip._vendor.pygments.lexers.savi', 'Savi', ('savi',), ('*.savi',), ()),
459
+ 'ScalaLexer': ('pip._vendor.pygments.lexers.jvm', 'Scala', ('scala',), ('*.scala',), ('text/x-scala',)),
460
+ 'ScamlLexer': ('pip._vendor.pygments.lexers.html', 'Scaml', ('scaml',), ('*.scaml',), ('text/x-scaml',)),
461
+ 'ScdocLexer': ('pip._vendor.pygments.lexers.scdoc', 'scdoc', ('scdoc', 'scd'), ('*.scd', '*.scdoc'), ()),
462
+ 'SchemeLexer': ('pip._vendor.pygments.lexers.lisp', 'Scheme', ('scheme', 'scm'), ('*.scm', '*.ss'), ('text/x-scheme', 'application/x-scheme')),
463
+ 'ScilabLexer': ('pip._vendor.pygments.lexers.matlab', 'Scilab', ('scilab',), ('*.sci', '*.sce', '*.tst'), ('text/scilab',)),
464
+ 'ScssLexer': ('pip._vendor.pygments.lexers.css', 'SCSS', ('scss',), ('*.scss',), ('text/x-scss',)),
465
+ 'SedLexer': ('pip._vendor.pygments.lexers.textedit', 'Sed', ('sed', 'gsed', 'ssed'), ('*.sed', '*.[gs]sed'), ('text/x-sed',)),
466
+ 'ShExCLexer': ('pip._vendor.pygments.lexers.rdf', 'ShExC', ('shexc', 'shex'), ('*.shex',), ('text/shex',)),
467
+ 'ShenLexer': ('pip._vendor.pygments.lexers.lisp', 'Shen', ('shen',), ('*.shen',), ('text/x-shen', 'application/x-shen')),
468
+ 'SieveLexer': ('pip._vendor.pygments.lexers.sieve', 'Sieve', ('sieve',), ('*.siv', '*.sieve'), ()),
469
+ 'SilverLexer': ('pip._vendor.pygments.lexers.verification', 'Silver', ('silver',), ('*.sil', '*.vpr'), ()),
470
+ 'SingularityLexer': ('pip._vendor.pygments.lexers.configs', 'Singularity', ('singularity',), ('*.def', 'Singularity'), ()),
471
+ 'SlashLexer': ('pip._vendor.pygments.lexers.slash', 'Slash', ('slash',), ('*.sla',), ()),
472
+ 'SlimLexer': ('pip._vendor.pygments.lexers.webmisc', 'Slim', ('slim',), ('*.slim',), ('text/x-slim',)),
473
+ 'SlurmBashLexer': ('pip._vendor.pygments.lexers.shell', 'Slurm', ('slurm', 'sbatch'), ('*.sl',), ()),
474
+ 'SmaliLexer': ('pip._vendor.pygments.lexers.dalvik', 'Smali', ('smali',), ('*.smali',), ('text/smali',)),
475
+ 'SmalltalkLexer': ('pip._vendor.pygments.lexers.smalltalk', 'Smalltalk', ('smalltalk', 'squeak', 'st'), ('*.st',), ('text/x-smalltalk',)),
476
+ 'SmartGameFormatLexer': ('pip._vendor.pygments.lexers.sgf', 'SmartGameFormat', ('sgf',), ('*.sgf',), ()),
477
+ 'SmartyLexer': ('pip._vendor.pygments.lexers.templates', 'Smarty', ('smarty',), ('*.tpl',), ('application/x-smarty',)),
478
+ 'SmithyLexer': ('pip._vendor.pygments.lexers.smithy', 'Smithy', ('smithy',), ('*.smithy',), ()),
479
+ 'SnobolLexer': ('pip._vendor.pygments.lexers.snobol', 'Snobol', ('snobol',), ('*.snobol',), ('text/x-snobol',)),
480
+ 'SnowballLexer': ('pip._vendor.pygments.lexers.dsls', 'Snowball', ('snowball',), ('*.sbl',), ()),
481
+ 'SolidityLexer': ('pip._vendor.pygments.lexers.solidity', 'Solidity', ('solidity',), ('*.sol',), ()),
482
+ 'SoongLexer': ('pip._vendor.pygments.lexers.soong', 'Soong', ('androidbp', 'bp', 'soong'), ('Android.bp',), ()),
483
+ 'SophiaLexer': ('pip._vendor.pygments.lexers.sophia', 'Sophia', ('sophia',), ('*.aes',), ()),
484
+ 'SourcePawnLexer': ('pip._vendor.pygments.lexers.pawn', 'SourcePawn', ('sp',), ('*.sp',), ('text/x-sourcepawn',)),
485
+ 'SourcesListLexer': ('pip._vendor.pygments.lexers.installers', 'Debian Sourcelist', ('debsources', 'sourceslist', 'sources.list'), ('sources.list',), ()),
486
+ 'SparqlLexer': ('pip._vendor.pygments.lexers.rdf', 'SPARQL', ('sparql',), ('*.rq', '*.sparql'), ('application/sparql-query',)),
487
+ 'SpiceLexer': ('pip._vendor.pygments.lexers.spice', 'Spice', ('spice', 'spicelang'), ('*.spice',), ('text/x-spice',)),
488
+ 'SqlJinjaLexer': ('pip._vendor.pygments.lexers.templates', 'SQL+Jinja', ('sql+jinja',), ('*.sql', '*.sql.j2', '*.sql.jinja2'), ()),
489
+ 'SqlLexer': ('pip._vendor.pygments.lexers.sql', 'SQL', ('sql',), ('*.sql',), ('text/x-sql',)),
490
+ 'SqliteConsoleLexer': ('pip._vendor.pygments.lexers.sql', 'sqlite3con', ('sqlite3',), ('*.sqlite3-console',), ('text/x-sqlite3-console',)),
491
+ 'SquidConfLexer': ('pip._vendor.pygments.lexers.configs', 'SquidConf', ('squidconf', 'squid.conf', 'squid'), ('squid.conf',), ('text/x-squidconf',)),
492
+ 'SrcinfoLexer': ('pip._vendor.pygments.lexers.srcinfo', 'Srcinfo', ('srcinfo',), ('.SRCINFO',), ()),
493
+ 'SspLexer': ('pip._vendor.pygments.lexers.templates', 'Scalate Server Page', ('ssp',), ('*.ssp',), ('application/x-ssp',)),
494
+ 'StanLexer': ('pip._vendor.pygments.lexers.modeling', 'Stan', ('stan',), ('*.stan',), ()),
495
+ 'StataLexer': ('pip._vendor.pygments.lexers.stata', 'Stata', ('stata', 'do'), ('*.do', '*.ado'), ('text/x-stata', 'text/stata', 'application/x-stata')),
496
+ 'SuperColliderLexer': ('pip._vendor.pygments.lexers.supercollider', 'SuperCollider', ('supercollider', 'sc'), ('*.sc', '*.scd'), ('application/supercollider', 'text/supercollider')),
497
+ 'SwiftLexer': ('pip._vendor.pygments.lexers.objective', 'Swift', ('swift',), ('*.swift',), ('text/x-swift',)),
498
+ 'SwigLexer': ('pip._vendor.pygments.lexers.c_like', 'SWIG', ('swig',), ('*.swg', '*.i'), ('text/swig',)),
499
+ 'SystemVerilogLexer': ('pip._vendor.pygments.lexers.hdl', 'systemverilog', ('systemverilog', 'sv'), ('*.sv', '*.svh'), ('text/x-systemverilog',)),
500
+ 'SystemdLexer': ('pip._vendor.pygments.lexers.configs', 'Systemd', ('systemd',), ('*.service', '*.socket', '*.device', '*.mount', '*.automount', '*.swap', '*.target', '*.path', '*.timer', '*.slice', '*.scope'), ()),
501
+ 'TAPLexer': ('pip._vendor.pygments.lexers.testing', 'TAP', ('tap',), ('*.tap',), ()),
502
+ 'TNTLexer': ('pip._vendor.pygments.lexers.tnt', 'Typographic Number Theory', ('tnt',), ('*.tnt',), ()),
503
+ 'TOMLLexer': ('pip._vendor.pygments.lexers.configs', 'TOML', ('toml',), ('*.toml', 'Pipfile', 'poetry.lock'), ('application/toml',)),
504
+ 'TactLexer': ('pip._vendor.pygments.lexers.tact', 'Tact', ('tact',), ('*.tact',), ()),
505
+ 'Tads3Lexer': ('pip._vendor.pygments.lexers.int_fiction', 'TADS 3', ('tads3',), ('*.t',), ()),
506
+ 'TalLexer': ('pip._vendor.pygments.lexers.tal', 'Tal', ('tal', 'uxntal'), ('*.tal',), ('text/x-uxntal',)),
507
+ 'TasmLexer': ('pip._vendor.pygments.lexers.asm', 'TASM', ('tasm',), ('*.asm', '*.ASM', '*.tasm'), ('text/x-tasm',)),
508
+ 'TclLexer': ('pip._vendor.pygments.lexers.tcl', 'Tcl', ('tcl',), ('*.tcl', '*.rvt'), ('text/x-tcl', 'text/x-script.tcl', 'application/x-tcl')),
509
+ 'TcshLexer': ('pip._vendor.pygments.lexers.shell', 'Tcsh', ('tcsh', 'csh'), ('*.tcsh', '*.csh'), ('application/x-csh',)),
510
+ 'TcshSessionLexer': ('pip._vendor.pygments.lexers.shell', 'Tcsh Session', ('tcshcon',), (), ()),
511
+ 'TeaTemplateLexer': ('pip._vendor.pygments.lexers.templates', 'Tea', ('tea',), ('*.tea',), ('text/x-tea',)),
512
+ 'TealLexer': ('pip._vendor.pygments.lexers.teal', 'teal', ('teal',), ('*.teal',), ()),
513
+ 'TeraTermLexer': ('pip._vendor.pygments.lexers.teraterm', 'Tera Term macro', ('teratermmacro', 'teraterm', 'ttl'), ('*.ttl',), ('text/x-teratermmacro',)),
514
+ 'TermcapLexer': ('pip._vendor.pygments.lexers.configs', 'Termcap', ('termcap',), ('termcap', 'termcap.src'), ()),
515
+ 'TerminfoLexer': ('pip._vendor.pygments.lexers.configs', 'Terminfo', ('terminfo',), ('terminfo', 'terminfo.src'), ()),
516
+ 'TerraformLexer': ('pip._vendor.pygments.lexers.configs', 'Terraform', ('terraform', 'tf', 'hcl'), ('*.tf', '*.hcl'), ('application/x-tf', 'application/x-terraform')),
517
+ 'TexLexer': ('pip._vendor.pygments.lexers.markup', 'TeX', ('tex', 'latex'), ('*.tex', '*.aux', '*.toc'), ('text/x-tex', 'text/x-latex')),
518
+ 'TextLexer': ('pip._vendor.pygments.lexers.special', 'Text only', ('text',), ('*.txt',), ('text/plain',)),
519
+ 'ThingsDBLexer': ('pip._vendor.pygments.lexers.thingsdb', 'ThingsDB', ('ti', 'thingsdb'), ('*.ti',), ()),
520
+ 'ThriftLexer': ('pip._vendor.pygments.lexers.dsls', 'Thrift', ('thrift',), ('*.thrift',), ('application/x-thrift',)),
521
+ 'TiddlyWiki5Lexer': ('pip._vendor.pygments.lexers.markup', 'tiddler', ('tid',), ('*.tid',), ('text/vnd.tiddlywiki',)),
522
+ 'TlbLexer': ('pip._vendor.pygments.lexers.tlb', 'Tl-b', ('tlb',), ('*.tlb',), ()),
523
+ 'TlsLexer': ('pip._vendor.pygments.lexers.tls', 'TLS Presentation Language', ('tls',), (), ()),
524
+ 'TodotxtLexer': ('pip._vendor.pygments.lexers.textfmts', 'Todotxt', ('todotxt',), ('todo.txt', '*.todotxt'), ('text/x-todo',)),
525
+ 'TransactSqlLexer': ('pip._vendor.pygments.lexers.sql', 'Transact-SQL', ('tsql', 't-sql'), ('*.sql',), ('text/x-tsql',)),
526
+ 'TreetopLexer': ('pip._vendor.pygments.lexers.parsers', 'Treetop', ('treetop',), ('*.treetop', '*.tt'), ()),
527
+ 'TurtleLexer': ('pip._vendor.pygments.lexers.rdf', 'Turtle', ('turtle',), ('*.ttl',), ('text/turtle', 'application/x-turtle')),
528
+ 'TwigHtmlLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Twig', ('html+twig',), ('*.twig',), ('text/html+twig',)),
529
+ 'TwigLexer': ('pip._vendor.pygments.lexers.templates', 'Twig', ('twig',), (), ('application/x-twig',)),
530
+ 'TypeScriptLexer': ('pip._vendor.pygments.lexers.javascript', 'TypeScript', ('typescript', 'ts'), ('*.ts',), ('application/x-typescript', 'text/x-typescript')),
531
+ 'TypoScriptCssDataLexer': ('pip._vendor.pygments.lexers.typoscript', 'TypoScriptCssData', ('typoscriptcssdata',), (), ()),
532
+ 'TypoScriptHtmlDataLexer': ('pip._vendor.pygments.lexers.typoscript', 'TypoScriptHtmlData', ('typoscripthtmldata',), (), ()),
533
+ 'TypoScriptLexer': ('pip._vendor.pygments.lexers.typoscript', 'TypoScript', ('typoscript',), ('*.typoscript',), ('text/x-typoscript',)),
534
+ 'TypstLexer': ('pip._vendor.pygments.lexers.typst', 'Typst', ('typst',), ('*.typ',), ('text/x-typst',)),
535
+ 'UL4Lexer': ('pip._vendor.pygments.lexers.ul4', 'UL4', ('ul4',), ('*.ul4',), ()),
536
+ 'UcodeLexer': ('pip._vendor.pygments.lexers.unicon', 'ucode', ('ucode',), ('*.u', '*.u1', '*.u2'), ()),
537
+ 'UniconLexer': ('pip._vendor.pygments.lexers.unicon', 'Unicon', ('unicon',), ('*.icn',), ('text/unicon',)),
538
+ 'UnixConfigLexer': ('pip._vendor.pygments.lexers.configs', 'Unix/Linux config files', ('unixconfig', 'linuxconfig'), (), ()),
539
+ 'UrbiscriptLexer': ('pip._vendor.pygments.lexers.urbi', 'UrbiScript', ('urbiscript',), ('*.u',), ('application/x-urbiscript',)),
540
+ 'UrlEncodedLexer': ('pip._vendor.pygments.lexers.html', 'urlencoded', ('urlencoded',), (), ('application/x-www-form-urlencoded',)),
541
+ 'UsdLexer': ('pip._vendor.pygments.lexers.usd', 'USD', ('usd', 'usda'), ('*.usd', '*.usda'), ()),
542
+ 'VBScriptLexer': ('pip._vendor.pygments.lexers.basic', 'VBScript', ('vbscript',), ('*.vbs', '*.VBS'), ()),
543
+ 'VCLLexer': ('pip._vendor.pygments.lexers.varnish', 'VCL', ('vcl',), ('*.vcl',), ('text/x-vclsrc',)),
544
+ 'VCLSnippetLexer': ('pip._vendor.pygments.lexers.varnish', 'VCLSnippets', ('vclsnippets', 'vclsnippet'), (), ('text/x-vclsnippet',)),
545
+ 'VCTreeStatusLexer': ('pip._vendor.pygments.lexers.console', 'VCTreeStatus', ('vctreestatus',), (), ()),
546
+ 'VGLLexer': ('pip._vendor.pygments.lexers.dsls', 'VGL', ('vgl',), ('*.rpf',), ()),
547
+ 'ValaLexer': ('pip._vendor.pygments.lexers.c_like', 'Vala', ('vala', 'vapi'), ('*.vala', '*.vapi'), ('text/x-vala',)),
548
+ 'VbNetAspxLexer': ('pip._vendor.pygments.lexers.dotnet', 'aspx-vb', ('aspx-vb',), ('*.aspx', '*.asax', '*.ascx', '*.ashx', '*.asmx', '*.axd'), ()),
549
+ 'VbNetLexer': ('pip._vendor.pygments.lexers.dotnet', 'VB.net', ('vb.net', 'vbnet', 'lobas', 'oobas', 'sobas', 'visual-basic', 'visualbasic'), ('*.vb', '*.bas'), ('text/x-vbnet', 'text/x-vba')),
550
+ 'VelocityHtmlLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Velocity', ('html+velocity',), (), ('text/html+velocity',)),
551
+ 'VelocityLexer': ('pip._vendor.pygments.lexers.templates', 'Velocity', ('velocity',), ('*.vm', '*.fhtml'), ()),
552
+ 'VelocityXmlLexer': ('pip._vendor.pygments.lexers.templates', 'XML+Velocity', ('xml+velocity',), (), ('application/xml+velocity',)),
553
+ 'VerifpalLexer': ('pip._vendor.pygments.lexers.verifpal', 'Verifpal', ('verifpal',), ('*.vp',), ('text/x-verifpal',)),
554
+ 'VerilogLexer': ('pip._vendor.pygments.lexers.hdl', 'verilog', ('verilog', 'v'), ('*.v',), ('text/x-verilog',)),
555
+ 'VhdlLexer': ('pip._vendor.pygments.lexers.hdl', 'vhdl', ('vhdl',), ('*.vhdl', '*.vhd'), ('text/x-vhdl',)),
556
+ 'VimLexer': ('pip._vendor.pygments.lexers.textedit', 'VimL', ('vim',), ('*.vim', '.vimrc', '.exrc', '.gvimrc', '_vimrc', '_exrc', '_gvimrc', 'vimrc', 'gvimrc'), ('text/x-vim',)),
557
+ 'VisualPrologGrammarLexer': ('pip._vendor.pygments.lexers.vip', 'Visual Prolog Grammar', ('visualprologgrammar',), ('*.vipgrm',), ()),
558
+ 'VisualPrologLexer': ('pip._vendor.pygments.lexers.vip', 'Visual Prolog', ('visualprolog',), ('*.pro', '*.cl', '*.i', '*.pack', '*.ph'), ()),
559
+ 'VyperLexer': ('pip._vendor.pygments.lexers.vyper', 'Vyper', ('vyper',), ('*.vy',), ()),
560
+ 'WDiffLexer': ('pip._vendor.pygments.lexers.diff', 'WDiff', ('wdiff',), ('*.wdiff',), ()),
561
+ 'WatLexer': ('pip._vendor.pygments.lexers.webassembly', 'WebAssembly', ('wast', 'wat'), ('*.wat', '*.wast'), ()),
562
+ 'WebIDLLexer': ('pip._vendor.pygments.lexers.webidl', 'Web IDL', ('webidl',), ('*.webidl',), ()),
563
+ 'WgslLexer': ('pip._vendor.pygments.lexers.wgsl', 'WebGPU Shading Language', ('wgsl',), ('*.wgsl',), ('text/wgsl',)),
564
+ 'WhileyLexer': ('pip._vendor.pygments.lexers.whiley', 'Whiley', ('whiley',), ('*.whiley',), ('text/x-whiley',)),
565
+ 'WikitextLexer': ('pip._vendor.pygments.lexers.markup', 'Wikitext', ('wikitext', 'mediawiki'), (), ('text/x-wiki',)),
566
+ 'WoWTocLexer': ('pip._vendor.pygments.lexers.wowtoc', 'World of Warcraft TOC', ('wowtoc',), ('*.toc',), ()),
567
+ 'WrenLexer': ('pip._vendor.pygments.lexers.wren', 'Wren', ('wren',), ('*.wren',), ()),
568
+ 'X10Lexer': ('pip._vendor.pygments.lexers.x10', 'X10', ('x10', 'xten'), ('*.x10',), ('text/x-x10',)),
569
+ 'XMLUL4Lexer': ('pip._vendor.pygments.lexers.ul4', 'XML+UL4', ('xml+ul4',), ('*.xmlul4',), ()),
570
+ 'XQueryLexer': ('pip._vendor.pygments.lexers.webmisc', 'XQuery', ('xquery', 'xqy', 'xq', 'xql', 'xqm'), ('*.xqy', '*.xquery', '*.xq', '*.xql', '*.xqm'), ('text/xquery', 'application/xquery')),
571
+ 'XmlDjangoLexer': ('pip._vendor.pygments.lexers.templates', 'XML+Django/Jinja', ('xml+django', 'xml+jinja'), ('*.xml.j2', '*.xml.jinja2'), ('application/xml+django', 'application/xml+jinja')),
572
+ 'XmlErbLexer': ('pip._vendor.pygments.lexers.templates', 'XML+Ruby', ('xml+ruby', 'xml+erb'), (), ('application/xml+ruby',)),
573
+ 'XmlLexer': ('pip._vendor.pygments.lexers.html', 'XML', ('xml',), ('*.xml', '*.xsl', '*.rss', '*.xslt', '*.xsd', '*.wsdl', '*.wsf'), ('text/xml', 'application/xml', 'image/svg+xml', 'application/rss+xml', 'application/atom+xml')),
574
+ 'XmlPhpLexer': ('pip._vendor.pygments.lexers.templates', 'XML+PHP', ('xml+php',), (), ('application/xml+php',)),
575
+ 'XmlSmartyLexer': ('pip._vendor.pygments.lexers.templates', 'XML+Smarty', ('xml+smarty',), (), ('application/xml+smarty',)),
576
+ 'XorgLexer': ('pip._vendor.pygments.lexers.xorg', 'Xorg', ('xorg.conf',), ('xorg.conf',), ()),
577
+ 'XppLexer': ('pip._vendor.pygments.lexers.dotnet', 'X++', ('xpp', 'x++'), ('*.xpp',), ()),
578
+ 'XsltLexer': ('pip._vendor.pygments.lexers.html', 'XSLT', ('xslt',), ('*.xsl', '*.xslt', '*.xpl'), ('application/xsl+xml', 'application/xslt+xml')),
579
+ 'XtendLexer': ('pip._vendor.pygments.lexers.jvm', 'Xtend', ('xtend',), ('*.xtend',), ('text/x-xtend',)),
580
+ 'XtlangLexer': ('pip._vendor.pygments.lexers.lisp', 'xtlang', ('extempore',), ('*.xtm',), ()),
581
+ 'YamlJinjaLexer': ('pip._vendor.pygments.lexers.templates', 'YAML+Jinja', ('yaml+jinja', 'salt', 'sls'), ('*.sls', '*.yaml.j2', '*.yml.j2', '*.yaml.jinja2', '*.yml.jinja2'), ('text/x-yaml+jinja', 'text/x-sls')),
582
+ 'YamlLexer': ('pip._vendor.pygments.lexers.data', 'YAML', ('yaml',), ('*.yaml', '*.yml'), ('text/x-yaml',)),
583
+ 'YangLexer': ('pip._vendor.pygments.lexers.yang', 'YANG', ('yang',), ('*.yang',), ('application/yang',)),
584
+ 'YaraLexer': ('pip._vendor.pygments.lexers.yara', 'YARA', ('yara', 'yar'), ('*.yar',), ('text/x-yara',)),
585
+ 'ZeekLexer': ('pip._vendor.pygments.lexers.dsls', 'Zeek', ('zeek', 'bro'), ('*.zeek', '*.bro'), ()),
586
+ 'ZephirLexer': ('pip._vendor.pygments.lexers.php', 'Zephir', ('zephir',), ('*.zep',), ()),
587
+ 'ZigLexer': ('pip._vendor.pygments.lexers.zig', 'Zig', ('zig',), ('*.zig',), ('text/zig',)),
588
+ 'apdlexer': ('pip._vendor.pygments.lexers.apdlexer', 'ANSYS parametric design language', ('ansys', 'apdl'), ('*.ans',), ()),
589
+ }
lib/python3.12/site-packages/pip/_vendor/pygments/lexers/python.py ADDED
@@ -0,0 +1,1198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ pygments.lexers.python
3
+ ~~~~~~~~~~~~~~~~~~~~~~
4
+
5
+ Lexers for Python and related languages.
6
+
7
+ :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
8
+ :license: BSD, see LICENSE for details.
9
+ """
10
+
11
+ import keyword
12
+
13
+ from pip._vendor.pygments.lexer import DelegatingLexer, RegexLexer, include, \
14
+ bygroups, using, default, words, combined, this
15
+ from pip._vendor.pygments.util import get_bool_opt, shebang_matches
16
+ from pip._vendor.pygments.token import Text, Comment, Operator, Keyword, Name, String, \
17
+ Number, Punctuation, Generic, Other, Error, Whitespace
18
+ from pip._vendor.pygments import unistring as uni
19
+
20
+ __all__ = ['PythonLexer', 'PythonConsoleLexer', 'PythonTracebackLexer',
21
+ 'Python2Lexer', 'Python2TracebackLexer',
22
+ 'CythonLexer', 'DgLexer', 'NumPyLexer']
23
+
24
+
25
+ class PythonLexer(RegexLexer):
26
+ """
27
+ For Python source code (version 3.x).
28
+
29
+ .. versionchanged:: 2.5
30
+ This is now the default ``PythonLexer``. It is still available as the
31
+ alias ``Python3Lexer``.
32
+ """
33
+
34
+ name = 'Python'
35
+ url = 'https://www.python.org'
36
+ aliases = ['python', 'py', 'sage', 'python3', 'py3', 'bazel', 'starlark']
37
+ filenames = [
38
+ '*.py',
39
+ '*.pyw',
40
+ # Type stubs
41
+ '*.pyi',
42
+ # Jython
43
+ '*.jy',
44
+ # Sage
45
+ '*.sage',
46
+ # SCons
47
+ '*.sc',
48
+ 'SConstruct',
49
+ 'SConscript',
50
+ # Skylark/Starlark (used by Bazel, Buck, and Pants)
51
+ '*.bzl',
52
+ 'BUCK',
53
+ 'BUILD',
54
+ 'BUILD.bazel',
55
+ 'WORKSPACE',
56
+ # Twisted Application infrastructure
57
+ '*.tac',
58
+ ]
59
+ mimetypes = ['text/x-python', 'application/x-python',
60
+ 'text/x-python3', 'application/x-python3']
61
+ version_added = '0.10'
62
+
63
+ uni_name = f"[{uni.xid_start}][{uni.xid_continue}]*"
64
+
65
+ def innerstring_rules(ttype):
66
+ return [
67
+ # the old style '%s' % (...) string formatting (still valid in Py3)
68
+ (r'%(\(\w+\))?[-#0 +]*([0-9]+|[*])?(\.([0-9]+|[*]))?'
69
+ '[hlL]?[E-GXc-giorsaux%]', String.Interpol),
70
+ # the new style '{}'.format(...) string formatting
71
+ (r'\{'
72
+ r'((\w+)((\.\w+)|(\[[^\]]+\]))*)?' # field name
73
+ r'(\![sra])?' # conversion
74
+ r'(\:(.?[<>=\^])?[-+ ]?#?0?(\d+)?,?(\.\d+)?[E-GXb-gnosx%]?)?'
75
+ r'\}', String.Interpol),
76
+
77
+ # backslashes, quotes and formatting signs must be parsed one at a time
78
+ (r'[^\\\'"%{\n]+', ttype),
79
+ (r'[\'"\\]', ttype),
80
+ # unhandled string formatting sign
81
+ (r'%|(\{{1,2})', ttype)
82
+ # newlines are an error (use "nl" state)
83
+ ]
84
+
85
+ def fstring_rules(ttype):
86
+ return [
87
+ # Assuming that a '}' is the closing brace after format specifier.
88
+ # Sadly, this means that we won't detect syntax error. But it's
89
+ # more important to parse correct syntax correctly, than to
90
+ # highlight invalid syntax.
91
+ (r'\}', String.Interpol),
92
+ (r'\{', String.Interpol, 'expr-inside-fstring'),
93
+ # backslashes, quotes and formatting signs must be parsed one at a time
94
+ (r'[^\\\'"{}\n]+', ttype),
95
+ (r'[\'"\\]', ttype),
96
+ # newlines are an error (use "nl" state)
97
+ ]
98
+
99
+ tokens = {
100
+ 'root': [
101
+ (r'\n', Whitespace),
102
+ (r'^(\s*)([rRuUbB]{,2})("""(?:.|\n)*?""")',
103
+ bygroups(Whitespace, String.Affix, String.Doc)),
104
+ (r"^(\s*)([rRuUbB]{,2})('''(?:.|\n)*?''')",
105
+ bygroups(Whitespace, String.Affix, String.Doc)),
106
+ (r'\A#!.+$', Comment.Hashbang),
107
+ (r'#.*$', Comment.Single),
108
+ (r'\\\n', Text),
109
+ (r'\\', Text),
110
+ include('keywords'),
111
+ include('soft-keywords'),
112
+ (r'(def)((?:\s|\\\s)+)', bygroups(Keyword, Text), 'funcname'),
113
+ (r'(class)((?:\s|\\\s)+)', bygroups(Keyword, Text), 'classname'),
114
+ (r'(from)((?:\s|\\\s)+)', bygroups(Keyword.Namespace, Text),
115
+ 'fromimport'),
116
+ (r'(import)((?:\s|\\\s)+)', bygroups(Keyword.Namespace, Text),
117
+ 'import'),
118
+ include('expr'),
119
+ ],
120
+ 'expr': [
121
+ # raw f-strings
122
+ ('(?i)(rf|fr)(""")',
123
+ bygroups(String.Affix, String.Double),
124
+ combined('rfstringescape', 'tdqf')),
125
+ ("(?i)(rf|fr)(''')",
126
+ bygroups(String.Affix, String.Single),
127
+ combined('rfstringescape', 'tsqf')),
128
+ ('(?i)(rf|fr)(")',
129
+ bygroups(String.Affix, String.Double),
130
+ combined('rfstringescape', 'dqf')),
131
+ ("(?i)(rf|fr)(')",
132
+ bygroups(String.Affix, String.Single),
133
+ combined('rfstringescape', 'sqf')),
134
+ # non-raw f-strings
135
+ ('([fF])(""")', bygroups(String.Affix, String.Double),
136
+ combined('fstringescape', 'tdqf')),
137
+ ("([fF])(''')", bygroups(String.Affix, String.Single),
138
+ combined('fstringescape', 'tsqf')),
139
+ ('([fF])(")', bygroups(String.Affix, String.Double),
140
+ combined('fstringescape', 'dqf')),
141
+ ("([fF])(')", bygroups(String.Affix, String.Single),
142
+ combined('fstringescape', 'sqf')),
143
+ # raw bytes and strings
144
+ ('(?i)(rb|br|r)(""")',
145
+ bygroups(String.Affix, String.Double), 'tdqs'),
146
+ ("(?i)(rb|br|r)(''')",
147
+ bygroups(String.Affix, String.Single), 'tsqs'),
148
+ ('(?i)(rb|br|r)(")',
149
+ bygroups(String.Affix, String.Double), 'dqs'),
150
+ ("(?i)(rb|br|r)(')",
151
+ bygroups(String.Affix, String.Single), 'sqs'),
152
+ # non-raw strings
153
+ ('([uU]?)(""")', bygroups(String.Affix, String.Double),
154
+ combined('stringescape', 'tdqs')),
155
+ ("([uU]?)(''')", bygroups(String.Affix, String.Single),
156
+ combined('stringescape', 'tsqs')),
157
+ ('([uU]?)(")', bygroups(String.Affix, String.Double),
158
+ combined('stringescape', 'dqs')),
159
+ ("([uU]?)(')", bygroups(String.Affix, String.Single),
160
+ combined('stringescape', 'sqs')),
161
+ # non-raw bytes
162
+ ('([bB])(""")', bygroups(String.Affix, String.Double),
163
+ combined('bytesescape', 'tdqs')),
164
+ ("([bB])(''')", bygroups(String.Affix, String.Single),
165
+ combined('bytesescape', 'tsqs')),
166
+ ('([bB])(")', bygroups(String.Affix, String.Double),
167
+ combined('bytesescape', 'dqs')),
168
+ ("([bB])(')", bygroups(String.Affix, String.Single),
169
+ combined('bytesescape', 'sqs')),
170
+
171
+ (r'[^\S\n]+', Text),
172
+ include('numbers'),
173
+ (r'!=|==|<<|>>|:=|[-~+/*%=<>&^|.]', Operator),
174
+ (r'[]{}:(),;[]', Punctuation),
175
+ (r'(in|is|and|or|not)\b', Operator.Word),
176
+ include('expr-keywords'),
177
+ include('builtins'),
178
+ include('magicfuncs'),
179
+ include('magicvars'),
180
+ include('name'),
181
+ ],
182
+ 'expr-inside-fstring': [
183
+ (r'[{([]', Punctuation, 'expr-inside-fstring-inner'),
184
+ # without format specifier
185
+ (r'(=\s*)?' # debug (https://bugs.python.org/issue36817)
186
+ r'(\![sraf])?' # conversion
187
+ r'\}', String.Interpol, '#pop'),
188
+ # with format specifier
189
+ # we'll catch the remaining '}' in the outer scope
190
+ (r'(=\s*)?' # debug (https://bugs.python.org/issue36817)
191
+ r'(\![sraf])?' # conversion
192
+ r':', String.Interpol, '#pop'),
193
+ (r'\s+', Whitespace), # allow new lines
194
+ include('expr'),
195
+ ],
196
+ 'expr-inside-fstring-inner': [
197
+ (r'[{([]', Punctuation, 'expr-inside-fstring-inner'),
198
+ (r'[])}]', Punctuation, '#pop'),
199
+ (r'\s+', Whitespace), # allow new lines
200
+ include('expr'),
201
+ ],
202
+ 'expr-keywords': [
203
+ # Based on https://docs.python.org/3/reference/expressions.html
204
+ (words((
205
+ 'async for', 'await', 'else', 'for', 'if', 'lambda',
206
+ 'yield', 'yield from'), suffix=r'\b'),
207
+ Keyword),
208
+ (words(('True', 'False', 'None'), suffix=r'\b'), Keyword.Constant),
209
+ ],
210
+ 'keywords': [
211
+ (words((
212
+ 'assert', 'async', 'await', 'break', 'continue', 'del', 'elif',
213
+ 'else', 'except', 'finally', 'for', 'global', 'if', 'lambda',
214
+ 'pass', 'raise', 'nonlocal', 'return', 'try', 'while', 'yield',
215
+ 'yield from', 'as', 'with'), suffix=r'\b'),
216
+ Keyword),
217
+ (words(('True', 'False', 'None'), suffix=r'\b'), Keyword.Constant),
218
+ ],
219
+ 'soft-keywords': [
220
+ # `match`, `case` and `_` soft keywords
221
+ (r'(^[ \t]*)' # at beginning of line + possible indentation
222
+ r'(match|case)\b' # a possible keyword
223
+ r'(?![ \t]*(?:' # not followed by...
224
+ r'[:,;=^&|@~)\]}]|(?:' + # characters and keywords that mean this isn't
225
+ # pattern matching (but None/True/False is ok)
226
+ r'|'.join(k for k in keyword.kwlist if k[0].islower()) + r')\b))',
227
+ bygroups(Text, Keyword), 'soft-keywords-inner'),
228
+ ],
229
+ 'soft-keywords-inner': [
230
+ # optional `_` keyword
231
+ (r'(\s+)([^\n_]*)(_\b)', bygroups(Whitespace, using(this), Keyword)),
232
+ default('#pop')
233
+ ],
234
+ 'builtins': [
235
+ (words((
236
+ '__import__', 'abs', 'aiter', 'all', 'any', 'bin', 'bool', 'bytearray',
237
+ 'breakpoint', 'bytes', 'callable', 'chr', 'classmethod', 'compile',
238
+ 'complex', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval',
239
+ 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals',
240
+ 'hasattr', 'hash', 'hex', 'id', 'input', 'int', 'isinstance',
241
+ 'issubclass', 'iter', 'len', 'list', 'locals', 'map', 'max',
242
+ 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow',
243
+ 'print', 'property', 'range', 'repr', 'reversed', 'round', 'set',
244
+ 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super',
245
+ 'tuple', 'type', 'vars', 'zip'), prefix=r'(?<!\.)', suffix=r'\b'),
246
+ Name.Builtin),
247
+ (r'(?<!\.)(self|Ellipsis|NotImplemented|cls)\b', Name.Builtin.Pseudo),
248
+ (words((
249
+ 'ArithmeticError', 'AssertionError', 'AttributeError',
250
+ 'BaseException', 'BufferError', 'BytesWarning', 'DeprecationWarning',
251
+ 'EOFError', 'EnvironmentError', 'Exception', 'FloatingPointError',
252
+ 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError',
253
+ 'ImportWarning', 'IndentationError', 'IndexError', 'KeyError',
254
+ 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError',
255
+ 'NotImplementedError', 'OSError', 'OverflowError',
256
+ 'PendingDeprecationWarning', 'ReferenceError', 'ResourceWarning',
257
+ 'RuntimeError', 'RuntimeWarning', 'StopIteration',
258
+ 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit',
259
+ 'TabError', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError',
260
+ 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError',
261
+ 'UnicodeWarning', 'UserWarning', 'ValueError', 'VMSError',
262
+ 'Warning', 'WindowsError', 'ZeroDivisionError',
263
+ # new builtin exceptions from PEP 3151
264
+ 'BlockingIOError', 'ChildProcessError', 'ConnectionError',
265
+ 'BrokenPipeError', 'ConnectionAbortedError', 'ConnectionRefusedError',
266
+ 'ConnectionResetError', 'FileExistsError', 'FileNotFoundError',
267
+ 'InterruptedError', 'IsADirectoryError', 'NotADirectoryError',
268
+ 'PermissionError', 'ProcessLookupError', 'TimeoutError',
269
+ # others new in Python 3
270
+ 'StopAsyncIteration', 'ModuleNotFoundError', 'RecursionError',
271
+ 'EncodingWarning'),
272
+ prefix=r'(?<!\.)', suffix=r'\b'),
273
+ Name.Exception),
274
+ ],
275
+ 'magicfuncs': [
276
+ (words((
277
+ '__abs__', '__add__', '__aenter__', '__aexit__', '__aiter__',
278
+ '__and__', '__anext__', '__await__', '__bool__', '__bytes__',
279
+ '__call__', '__complex__', '__contains__', '__del__', '__delattr__',
280
+ '__delete__', '__delitem__', '__dir__', '__divmod__', '__enter__',
281
+ '__eq__', '__exit__', '__float__', '__floordiv__', '__format__',
282
+ '__ge__', '__get__', '__getattr__', '__getattribute__',
283
+ '__getitem__', '__gt__', '__hash__', '__iadd__', '__iand__',
284
+ '__ifloordiv__', '__ilshift__', '__imatmul__', '__imod__',
285
+ '__imul__', '__index__', '__init__', '__instancecheck__',
286
+ '__int__', '__invert__', '__ior__', '__ipow__', '__irshift__',
287
+ '__isub__', '__iter__', '__itruediv__', '__ixor__', '__le__',
288
+ '__len__', '__length_hint__', '__lshift__', '__lt__', '__matmul__',
289
+ '__missing__', '__mod__', '__mul__', '__ne__', '__neg__',
290
+ '__new__', '__next__', '__or__', '__pos__', '__pow__',
291
+ '__prepare__', '__radd__', '__rand__', '__rdivmod__', '__repr__',
292
+ '__reversed__', '__rfloordiv__', '__rlshift__', '__rmatmul__',
293
+ '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__',
294
+ '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__',
295
+ '__rxor__', '__set__', '__setattr__', '__setitem__', '__str__',
296
+ '__sub__', '__subclasscheck__', '__truediv__',
297
+ '__xor__'), suffix=r'\b'),
298
+ Name.Function.Magic),
299
+ ],
300
+ 'magicvars': [
301
+ (words((
302
+ '__annotations__', '__bases__', '__class__', '__closure__',
303
+ '__code__', '__defaults__', '__dict__', '__doc__', '__file__',
304
+ '__func__', '__globals__', '__kwdefaults__', '__module__',
305
+ '__mro__', '__name__', '__objclass__', '__qualname__',
306
+ '__self__', '__slots__', '__weakref__'), suffix=r'\b'),
307
+ Name.Variable.Magic),
308
+ ],
309
+ 'numbers': [
310
+ (r'(\d(?:_?\d)*\.(?:\d(?:_?\d)*)?|(?:\d(?:_?\d)*)?\.\d(?:_?\d)*)'
311
+ r'([eE][+-]?\d(?:_?\d)*)?', Number.Float),
312
+ (r'\d(?:_?\d)*[eE][+-]?\d(?:_?\d)*j?', Number.Float),
313
+ (r'0[oO](?:_?[0-7])+', Number.Oct),
314
+ (r'0[bB](?:_?[01])+', Number.Bin),
315
+ (r'0[xX](?:_?[a-fA-F0-9])+', Number.Hex),
316
+ (r'\d(?:_?\d)*', Number.Integer),
317
+ ],
318
+ 'name': [
319
+ (r'@' + uni_name, Name.Decorator),
320
+ (r'@', Operator), # new matrix multiplication operator
321
+ (uni_name, Name),
322
+ ],
323
+ 'funcname': [
324
+ include('magicfuncs'),
325
+ (uni_name, Name.Function, '#pop'),
326
+ default('#pop'),
327
+ ],
328
+ 'classname': [
329
+ (uni_name, Name.Class, '#pop'),
330
+ ],
331
+ 'import': [
332
+ (r'(\s+)(as)(\s+)', bygroups(Text, Keyword, Text)),
333
+ (r'\.', Name.Namespace),
334
+ (uni_name, Name.Namespace),
335
+ (r'(\s*)(,)(\s*)', bygroups(Text, Operator, Text)),
336
+ default('#pop') # all else: go back
337
+ ],
338
+ 'fromimport': [
339
+ (r'(\s+)(import)\b', bygroups(Text, Keyword.Namespace), '#pop'),
340
+ (r'\.', Name.Namespace),
341
+ # if None occurs here, it's "raise x from None", since None can
342
+ # never be a module name
343
+ (r'None\b', Keyword.Constant, '#pop'),
344
+ (uni_name, Name.Namespace),
345
+ default('#pop'),
346
+ ],
347
+ 'rfstringescape': [
348
+ (r'\{\{', String.Escape),
349
+ (r'\}\}', String.Escape),
350
+ ],
351
+ 'fstringescape': [
352
+ include('rfstringescape'),
353
+ include('stringescape'),
354
+ ],
355
+ 'bytesescape': [
356
+ (r'\\([\\abfnrtv"\']|\n|x[a-fA-F0-9]{2}|[0-7]{1,3})', String.Escape)
357
+ ],
358
+ 'stringescape': [
359
+ (r'\\(N\{.*?\}|u[a-fA-F0-9]{4}|U[a-fA-F0-9]{8})', String.Escape),
360
+ include('bytesescape')
361
+ ],
362
+ 'fstrings-single': fstring_rules(String.Single),
363
+ 'fstrings-double': fstring_rules(String.Double),
364
+ 'strings-single': innerstring_rules(String.Single),
365
+ 'strings-double': innerstring_rules(String.Double),
366
+ 'dqf': [
367
+ (r'"', String.Double, '#pop'),
368
+ (r'\\\\|\\"|\\\n', String.Escape), # included here for raw strings
369
+ include('fstrings-double')
370
+ ],
371
+ 'sqf': [
372
+ (r"'", String.Single, '#pop'),
373
+ (r"\\\\|\\'|\\\n", String.Escape), # included here for raw strings
374
+ include('fstrings-single')
375
+ ],
376
+ 'dqs': [
377
+ (r'"', String.Double, '#pop'),
378
+ (r'\\\\|\\"|\\\n', String.Escape), # included here for raw strings
379
+ include('strings-double')
380
+ ],
381
+ 'sqs': [
382
+ (r"'", String.Single, '#pop'),
383
+ (r"\\\\|\\'|\\\n", String.Escape), # included here for raw strings
384
+ include('strings-single')
385
+ ],
386
+ 'tdqf': [
387
+ (r'"""', String.Double, '#pop'),
388
+ include('fstrings-double'),
389
+ (r'\n', String.Double)
390
+ ],
391
+ 'tsqf': [
392
+ (r"'''", String.Single, '#pop'),
393
+ include('fstrings-single'),
394
+ (r'\n', String.Single)
395
+ ],
396
+ 'tdqs': [
397
+ (r'"""', String.Double, '#pop'),
398
+ include('strings-double'),
399
+ (r'\n', String.Double)
400
+ ],
401
+ 'tsqs': [
402
+ (r"'''", String.Single, '#pop'),
403
+ include('strings-single'),
404
+ (r'\n', String.Single)
405
+ ],
406
+ }
407
+
408
+ def analyse_text(text):
409
+ return shebang_matches(text, r'pythonw?(3(\.\d)?)?') or \
410
+ 'import ' in text[:1000]
411
+
412
+
413
+ Python3Lexer = PythonLexer
414
+
415
+
416
+ class Python2Lexer(RegexLexer):
417
+ """
418
+ For Python 2.x source code.
419
+
420
+ .. versionchanged:: 2.5
421
+ This class has been renamed from ``PythonLexer``. ``PythonLexer`` now
422
+ refers to the Python 3 variant. File name patterns like ``*.py`` have
423
+ been moved to Python 3 as well.
424
+ """
425
+
426
+ name = 'Python 2.x'
427
+ url = 'https://www.python.org'
428
+ aliases = ['python2', 'py2']
429
+ filenames = [] # now taken over by PythonLexer (3.x)
430
+ mimetypes = ['text/x-python2', 'application/x-python2']
431
+ version_added = ''
432
+
433
+ def innerstring_rules(ttype):
434
+ return [
435
+ # the old style '%s' % (...) string formatting
436
+ (r'%(\(\w+\))?[-#0 +]*([0-9]+|[*])?(\.([0-9]+|[*]))?'
437
+ '[hlL]?[E-GXc-giorsux%]', String.Interpol),
438
+ # backslashes, quotes and formatting signs must be parsed one at a time
439
+ (r'[^\\\'"%\n]+', ttype),
440
+ (r'[\'"\\]', ttype),
441
+ # unhandled string formatting sign
442
+ (r'%', ttype),
443
+ # newlines are an error (use "nl" state)
444
+ ]
445
+
446
+ tokens = {
447
+ 'root': [
448
+ (r'\n', Whitespace),
449
+ (r'^(\s*)([rRuUbB]{,2})("""(?:.|\n)*?""")',
450
+ bygroups(Whitespace, String.Affix, String.Doc)),
451
+ (r"^(\s*)([rRuUbB]{,2})('''(?:.|\n)*?''')",
452
+ bygroups(Whitespace, String.Affix, String.Doc)),
453
+ (r'[^\S\n]+', Text),
454
+ (r'\A#!.+$', Comment.Hashbang),
455
+ (r'#.*$', Comment.Single),
456
+ (r'[]{}:(),;[]', Punctuation),
457
+ (r'\\\n', Text),
458
+ (r'\\', Text),
459
+ (r'(in|is|and|or|not)\b', Operator.Word),
460
+ (r'!=|==|<<|>>|[-~+/*%=<>&^|.]', Operator),
461
+ include('keywords'),
462
+ (r'(def)((?:\s|\\\s)+)', bygroups(Keyword, Text), 'funcname'),
463
+ (r'(class)((?:\s|\\\s)+)', bygroups(Keyword, Text), 'classname'),
464
+ (r'(from)((?:\s|\\\s)+)', bygroups(Keyword.Namespace, Text),
465
+ 'fromimport'),
466
+ (r'(import)((?:\s|\\\s)+)', bygroups(Keyword.Namespace, Text),
467
+ 'import'),
468
+ include('builtins'),
469
+ include('magicfuncs'),
470
+ include('magicvars'),
471
+ include('backtick'),
472
+ ('([rR]|[uUbB][rR]|[rR][uUbB])(""")',
473
+ bygroups(String.Affix, String.Double), 'tdqs'),
474
+ ("([rR]|[uUbB][rR]|[rR][uUbB])(''')",
475
+ bygroups(String.Affix, String.Single), 'tsqs'),
476
+ ('([rR]|[uUbB][rR]|[rR][uUbB])(")',
477
+ bygroups(String.Affix, String.Double), 'dqs'),
478
+ ("([rR]|[uUbB][rR]|[rR][uUbB])(')",
479
+ bygroups(String.Affix, String.Single), 'sqs'),
480
+ ('([uUbB]?)(""")', bygroups(String.Affix, String.Double),
481
+ combined('stringescape', 'tdqs')),
482
+ ("([uUbB]?)(''')", bygroups(String.Affix, String.Single),
483
+ combined('stringescape', 'tsqs')),
484
+ ('([uUbB]?)(")', bygroups(String.Affix, String.Double),
485
+ combined('stringescape', 'dqs')),
486
+ ("([uUbB]?)(')", bygroups(String.Affix, String.Single),
487
+ combined('stringescape', 'sqs')),
488
+ include('name'),
489
+ include('numbers'),
490
+ ],
491
+ 'keywords': [
492
+ (words((
493
+ 'assert', 'break', 'continue', 'del', 'elif', 'else', 'except',
494
+ 'exec', 'finally', 'for', 'global', 'if', 'lambda', 'pass',
495
+ 'print', 'raise', 'return', 'try', 'while', 'yield',
496
+ 'yield from', 'as', 'with'), suffix=r'\b'),
497
+ Keyword),
498
+ ],
499
+ 'builtins': [
500
+ (words((
501
+ '__import__', 'abs', 'all', 'any', 'apply', 'basestring', 'bin',
502
+ 'bool', 'buffer', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod',
503
+ 'cmp', 'coerce', 'compile', 'complex', 'delattr', 'dict', 'dir', 'divmod',
504
+ 'enumerate', 'eval', 'execfile', 'exit', 'file', 'filter', 'float',
505
+ 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'hex', 'id',
506
+ 'input', 'int', 'intern', 'isinstance', 'issubclass', 'iter', 'len',
507
+ 'list', 'locals', 'long', 'map', 'max', 'min', 'next', 'object',
508
+ 'oct', 'open', 'ord', 'pow', 'property', 'range', 'raw_input', 'reduce',
509
+ 'reload', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice',
510
+ 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type',
511
+ 'unichr', 'unicode', 'vars', 'xrange', 'zip'),
512
+ prefix=r'(?<!\.)', suffix=r'\b'),
513
+ Name.Builtin),
514
+ (r'(?<!\.)(self|None|Ellipsis|NotImplemented|False|True|cls'
515
+ r')\b', Name.Builtin.Pseudo),
516
+ (words((
517
+ 'ArithmeticError', 'AssertionError', 'AttributeError',
518
+ 'BaseException', 'DeprecationWarning', 'EOFError', 'EnvironmentError',
519
+ 'Exception', 'FloatingPointError', 'FutureWarning', 'GeneratorExit',
520
+ 'IOError', 'ImportError', 'ImportWarning', 'IndentationError',
521
+ 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError',
522
+ 'MemoryError', 'NameError',
523
+ 'NotImplementedError', 'OSError', 'OverflowError', 'OverflowWarning',
524
+ 'PendingDeprecationWarning', 'ReferenceError',
525
+ 'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration',
526
+ 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit',
527
+ 'TabError', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError',
528
+ 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError',
529
+ 'UnicodeWarning', 'UserWarning', 'ValueError', 'VMSError', 'Warning',
530
+ 'WindowsError', 'ZeroDivisionError'), prefix=r'(?<!\.)', suffix=r'\b'),
531
+ Name.Exception),
532
+ ],
533
+ 'magicfuncs': [
534
+ (words((
535
+ '__abs__', '__add__', '__and__', '__call__', '__cmp__', '__coerce__',
536
+ '__complex__', '__contains__', '__del__', '__delattr__', '__delete__',
537
+ '__delitem__', '__delslice__', '__div__', '__divmod__', '__enter__',
538
+ '__eq__', '__exit__', '__float__', '__floordiv__', '__ge__', '__get__',
539
+ '__getattr__', '__getattribute__', '__getitem__', '__getslice__', '__gt__',
540
+ '__hash__', '__hex__', '__iadd__', '__iand__', '__idiv__', '__ifloordiv__',
541
+ '__ilshift__', '__imod__', '__imul__', '__index__', '__init__',
542
+ '__instancecheck__', '__int__', '__invert__', '__iop__', '__ior__',
543
+ '__ipow__', '__irshift__', '__isub__', '__iter__', '__itruediv__',
544
+ '__ixor__', '__le__', '__len__', '__long__', '__lshift__', '__lt__',
545
+ '__missing__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__',
546
+ '__nonzero__', '__oct__', '__op__', '__or__', '__pos__', '__pow__',
547
+ '__radd__', '__rand__', '__rcmp__', '__rdiv__', '__rdivmod__', '__repr__',
548
+ '__reversed__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__',
549
+ '__rop__', '__ror__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__',
550
+ '__rtruediv__', '__rxor__', '__set__', '__setattr__', '__setitem__',
551
+ '__setslice__', '__str__', '__sub__', '__subclasscheck__', '__truediv__',
552
+ '__unicode__', '__xor__'), suffix=r'\b'),
553
+ Name.Function.Magic),
554
+ ],
555
+ 'magicvars': [
556
+ (words((
557
+ '__bases__', '__class__', '__closure__', '__code__', '__defaults__',
558
+ '__dict__', '__doc__', '__file__', '__func__', '__globals__',
559
+ '__metaclass__', '__module__', '__mro__', '__name__', '__self__',
560
+ '__slots__', '__weakref__'),
561
+ suffix=r'\b'),
562
+ Name.Variable.Magic),
563
+ ],
564
+ 'numbers': [
565
+ (r'(\d+\.\d*|\d*\.\d+)([eE][+-]?[0-9]+)?j?', Number.Float),
566
+ (r'\d+[eE][+-]?[0-9]+j?', Number.Float),
567
+ (r'0[0-7]+j?', Number.Oct),
568
+ (r'0[bB][01]+', Number.Bin),
569
+ (r'0[xX][a-fA-F0-9]+', Number.Hex),
570
+ (r'\d+L', Number.Integer.Long),
571
+ (r'\d+j?', Number.Integer)
572
+ ],
573
+ 'backtick': [
574
+ ('`.*?`', String.Backtick),
575
+ ],
576
+ 'name': [
577
+ (r'@[\w.]+', Name.Decorator),
578
+ (r'[a-zA-Z_]\w*', Name),
579
+ ],
580
+ 'funcname': [
581
+ include('magicfuncs'),
582
+ (r'[a-zA-Z_]\w*', Name.Function, '#pop'),
583
+ default('#pop'),
584
+ ],
585
+ 'classname': [
586
+ (r'[a-zA-Z_]\w*', Name.Class, '#pop')
587
+ ],
588
+ 'import': [
589
+ (r'(?:[ \t]|\\\n)+', Text),
590
+ (r'as\b', Keyword.Namespace),
591
+ (r',', Operator),
592
+ (r'[a-zA-Z_][\w.]*', Name.Namespace),
593
+ default('#pop') # all else: go back
594
+ ],
595
+ 'fromimport': [
596
+ (r'(?:[ \t]|\\\n)+', Text),
597
+ (r'import\b', Keyword.Namespace, '#pop'),
598
+ # if None occurs here, it's "raise x from None", since None can
599
+ # never be a module name
600
+ (r'None\b', Name.Builtin.Pseudo, '#pop'),
601
+ # sadly, in "raise x from y" y will be highlighted as namespace too
602
+ (r'[a-zA-Z_.][\w.]*', Name.Namespace),
603
+ # anything else here also means "raise x from y" and is therefore
604
+ # not an error
605
+ default('#pop'),
606
+ ],
607
+ 'stringescape': [
608
+ (r'\\([\\abfnrtv"\']|\n|N\{.*?\}|u[a-fA-F0-9]{4}|'
609
+ r'U[a-fA-F0-9]{8}|x[a-fA-F0-9]{2}|[0-7]{1,3})', String.Escape)
610
+ ],
611
+ 'strings-single': innerstring_rules(String.Single),
612
+ 'strings-double': innerstring_rules(String.Double),
613
+ 'dqs': [
614
+ (r'"', String.Double, '#pop'),
615
+ (r'\\\\|\\"|\\\n', String.Escape), # included here for raw strings
616
+ include('strings-double')
617
+ ],
618
+ 'sqs': [
619
+ (r"'", String.Single, '#pop'),
620
+ (r"\\\\|\\'|\\\n", String.Escape), # included here for raw strings
621
+ include('strings-single')
622
+ ],
623
+ 'tdqs': [
624
+ (r'"""', String.Double, '#pop'),
625
+ include('strings-double'),
626
+ (r'\n', String.Double)
627
+ ],
628
+ 'tsqs': [
629
+ (r"'''", String.Single, '#pop'),
630
+ include('strings-single'),
631
+ (r'\n', String.Single)
632
+ ],
633
+ }
634
+
635
+ def analyse_text(text):
636
+ return shebang_matches(text, r'pythonw?2(\.\d)?')
637
+
638
+ class _PythonConsoleLexerBase(RegexLexer):
639
+ name = 'Python console session'
640
+ aliases = ['pycon', 'python-console']
641
+ mimetypes = ['text/x-python-doctest']
642
+
643
+ """Auxiliary lexer for `PythonConsoleLexer`.
644
+
645
+ Code tokens are output as ``Token.Other.Code``, traceback tokens as
646
+ ``Token.Other.Traceback``.
647
+ """
648
+ tokens = {
649
+ 'root': [
650
+ (r'(>>> )(.*\n)', bygroups(Generic.Prompt, Other.Code), 'continuations'),
651
+ # This happens, e.g., when tracebacks are embedded in documentation;
652
+ # trailing whitespaces are often stripped in such contexts.
653
+ (r'(>>>)(\n)', bygroups(Generic.Prompt, Whitespace)),
654
+ (r'(\^C)?Traceback \(most recent call last\):\n', Other.Traceback, 'traceback'),
655
+ # SyntaxError starts with this
656
+ (r' File "[^"]+", line \d+', Other.Traceback, 'traceback'),
657
+ (r'.*\n', Generic.Output),
658
+ ],
659
+ 'continuations': [
660
+ (r'(\.\.\. )(.*\n)', bygroups(Generic.Prompt, Other.Code)),
661
+ # See above.
662
+ (r'(\.\.\.)(\n)', bygroups(Generic.Prompt, Whitespace)),
663
+ default('#pop'),
664
+ ],
665
+ 'traceback': [
666
+ # As soon as we see a traceback, consume everything until the next
667
+ # >>> prompt.
668
+ (r'(?=>>>( |$))', Text, '#pop'),
669
+ (r'(KeyboardInterrupt)(\n)', bygroups(Name.Class, Whitespace)),
670
+ (r'.*\n', Other.Traceback),
671
+ ],
672
+ }
673
+
674
+ class PythonConsoleLexer(DelegatingLexer):
675
+ """
676
+ For Python console output or doctests, such as:
677
+
678
+ .. sourcecode:: pycon
679
+
680
+ >>> a = 'foo'
681
+ >>> print(a)
682
+ foo
683
+ >>> 1 / 0
684
+ Traceback (most recent call last):
685
+ File "<stdin>", line 1, in <module>
686
+ ZeroDivisionError: integer division or modulo by zero
687
+
688
+ Additional options:
689
+
690
+ `python3`
691
+ Use Python 3 lexer for code. Default is ``True``.
692
+
693
+ .. versionadded:: 1.0
694
+ .. versionchanged:: 2.5
695
+ Now defaults to ``True``.
696
+ """
697
+
698
+ name = 'Python console session'
699
+ aliases = ['pycon', 'python-console']
700
+ mimetypes = ['text/x-python-doctest']
701
+ url = 'https://python.org'
702
+ version_added = ''
703
+
704
+ def __init__(self, **options):
705
+ python3 = get_bool_opt(options, 'python3', True)
706
+ if python3:
707
+ pylexer = PythonLexer
708
+ tblexer = PythonTracebackLexer
709
+ else:
710
+ pylexer = Python2Lexer
711
+ tblexer = Python2TracebackLexer
712
+ # We have two auxiliary lexers. Use DelegatingLexer twice with
713
+ # different tokens. TODO: DelegatingLexer should support this
714
+ # directly, by accepting a tuplet of auxiliary lexers and a tuple of
715
+ # distinguishing tokens. Then we wouldn't need this intermediary
716
+ # class.
717
+ class _ReplaceInnerCode(DelegatingLexer):
718
+ def __init__(self, **options):
719
+ super().__init__(pylexer, _PythonConsoleLexerBase, Other.Code, **options)
720
+ super().__init__(tblexer, _ReplaceInnerCode, Other.Traceback, **options)
721
+
722
+ class PythonTracebackLexer(RegexLexer):
723
+ """
724
+ For Python 3.x tracebacks, with support for chained exceptions.
725
+
726
+ .. versionchanged:: 2.5
727
+ This is now the default ``PythonTracebackLexer``. It is still available
728
+ as the alias ``Python3TracebackLexer``.
729
+ """
730
+
731
+ name = 'Python Traceback'
732
+ aliases = ['pytb', 'py3tb']
733
+ filenames = ['*.pytb', '*.py3tb']
734
+ mimetypes = ['text/x-python-traceback', 'text/x-python3-traceback']
735
+ url = 'https://python.org'
736
+ version_added = '1.0'
737
+
738
+ tokens = {
739
+ 'root': [
740
+ (r'\n', Whitespace),
741
+ (r'^(\^C)?Traceback \(most recent call last\):\n', Generic.Traceback, 'intb'),
742
+ (r'^During handling of the above exception, another '
743
+ r'exception occurred:\n\n', Generic.Traceback),
744
+ (r'^The above exception was the direct cause of the '
745
+ r'following exception:\n\n', Generic.Traceback),
746
+ (r'^(?= File "[^"]+", line \d+)', Generic.Traceback, 'intb'),
747
+ (r'^.*\n', Other),
748
+ ],
749
+ 'intb': [
750
+ (r'^( File )("[^"]+")(, line )(\d+)(, in )(.+)(\n)',
751
+ bygroups(Text, Name.Builtin, Text, Number, Text, Name, Whitespace)),
752
+ (r'^( File )("[^"]+")(, line )(\d+)(\n)',
753
+ bygroups(Text, Name.Builtin, Text, Number, Whitespace)),
754
+ (r'^( )(.+)(\n)',
755
+ bygroups(Whitespace, using(PythonLexer), Whitespace), 'markers'),
756
+ (r'^([ \t]*)(\.\.\.)(\n)',
757
+ bygroups(Whitespace, Comment, Whitespace)), # for doctests...
758
+ (r'^([^:]+)(: )(.+)(\n)',
759
+ bygroups(Generic.Error, Text, Name, Whitespace), '#pop'),
760
+ (r'^([a-zA-Z_][\w.]*)(:?\n)',
761
+ bygroups(Generic.Error, Whitespace), '#pop'),
762
+ default('#pop'),
763
+ ],
764
+ 'markers': [
765
+ # Either `PEP 657 <https://www.python.org/dev/peps/pep-0657/>`
766
+ # error locations in Python 3.11+, or single-caret markers
767
+ # for syntax errors before that.
768
+ (r'^( {4,})([~^]+)(\n)',
769
+ bygroups(Whitespace, Punctuation.Marker, Whitespace),
770
+ '#pop'),
771
+ default('#pop'),
772
+ ],
773
+ }
774
+
775
+
776
+ Python3TracebackLexer = PythonTracebackLexer
777
+
778
+
779
+ class Python2TracebackLexer(RegexLexer):
780
+ """
781
+ For Python tracebacks.
782
+
783
+ .. versionchanged:: 2.5
784
+ This class has been renamed from ``PythonTracebackLexer``.
785
+ ``PythonTracebackLexer`` now refers to the Python 3 variant.
786
+ """
787
+
788
+ name = 'Python 2.x Traceback'
789
+ aliases = ['py2tb']
790
+ filenames = ['*.py2tb']
791
+ mimetypes = ['text/x-python2-traceback']
792
+ url = 'https://python.org'
793
+ version_added = '0.7'
794
+
795
+ tokens = {
796
+ 'root': [
797
+ # Cover both (most recent call last) and (innermost last)
798
+ # The optional ^C allows us to catch keyboard interrupt signals.
799
+ (r'^(\^C)?(Traceback.*\n)',
800
+ bygroups(Text, Generic.Traceback), 'intb'),
801
+ # SyntaxError starts with this.
802
+ (r'^(?= File "[^"]+", line \d+)', Generic.Traceback, 'intb'),
803
+ (r'^.*\n', Other),
804
+ ],
805
+ 'intb': [
806
+ (r'^( File )("[^"]+")(, line )(\d+)(, in )(.+)(\n)',
807
+ bygroups(Text, Name.Builtin, Text, Number, Text, Name, Whitespace)),
808
+ (r'^( File )("[^"]+")(, line )(\d+)(\n)',
809
+ bygroups(Text, Name.Builtin, Text, Number, Whitespace)),
810
+ (r'^( )(.+)(\n)',
811
+ bygroups(Text, using(Python2Lexer), Whitespace), 'marker'),
812
+ (r'^([ \t]*)(\.\.\.)(\n)',
813
+ bygroups(Text, Comment, Whitespace)), # for doctests...
814
+ (r'^([^:]+)(: )(.+)(\n)',
815
+ bygroups(Generic.Error, Text, Name, Whitespace), '#pop'),
816
+ (r'^([a-zA-Z_]\w*)(:?\n)',
817
+ bygroups(Generic.Error, Whitespace), '#pop')
818
+ ],
819
+ 'marker': [
820
+ # For syntax errors.
821
+ (r'( {4,})(\^)', bygroups(Text, Punctuation.Marker), '#pop'),
822
+ default('#pop'),
823
+ ],
824
+ }
825
+
826
+
827
+ class CythonLexer(RegexLexer):
828
+ """
829
+ For Pyrex and Cython source code.
830
+ """
831
+
832
+ name = 'Cython'
833
+ url = 'https://cython.org'
834
+ aliases = ['cython', 'pyx', 'pyrex']
835
+ filenames = ['*.pyx', '*.pxd', '*.pxi']
836
+ mimetypes = ['text/x-cython', 'application/x-cython']
837
+ version_added = '1.1'
838
+
839
+ tokens = {
840
+ 'root': [
841
+ (r'\n', Whitespace),
842
+ (r'^(\s*)("""(?:.|\n)*?""")', bygroups(Whitespace, String.Doc)),
843
+ (r"^(\s*)('''(?:.|\n)*?''')", bygroups(Whitespace, String.Doc)),
844
+ (r'[^\S\n]+', Text),
845
+ (r'#.*$', Comment),
846
+ (r'[]{}:(),;[]', Punctuation),
847
+ (r'\\\n', Whitespace),
848
+ (r'\\', Text),
849
+ (r'(in|is|and|or|not)\b', Operator.Word),
850
+ (r'(<)([a-zA-Z0-9.?]+)(>)',
851
+ bygroups(Punctuation, Keyword.Type, Punctuation)),
852
+ (r'!=|==|<<|>>|[-~+/*%=<>&^|.?]', Operator),
853
+ (r'(from)(\d+)(<=)(\s+)(<)(\d+)(:)',
854
+ bygroups(Keyword, Number.Integer, Operator, Name, Operator,
855
+ Name, Punctuation)),
856
+ include('keywords'),
857
+ (r'(def|property)(\s+)', bygroups(Keyword, Text), 'funcname'),
858
+ (r'(cp?def)(\s+)', bygroups(Keyword, Text), 'cdef'),
859
+ # (should actually start a block with only cdefs)
860
+ (r'(cdef)(:)', bygroups(Keyword, Punctuation)),
861
+ (r'(class|struct)(\s+)', bygroups(Keyword, Text), 'classname'),
862
+ (r'(from)(\s+)', bygroups(Keyword, Text), 'fromimport'),
863
+ (r'(c?import)(\s+)', bygroups(Keyword, Text), 'import'),
864
+ include('builtins'),
865
+ include('backtick'),
866
+ ('(?:[rR]|[uU][rR]|[rR][uU])"""', String, 'tdqs'),
867
+ ("(?:[rR]|[uU][rR]|[rR][uU])'''", String, 'tsqs'),
868
+ ('(?:[rR]|[uU][rR]|[rR][uU])"', String, 'dqs'),
869
+ ("(?:[rR]|[uU][rR]|[rR][uU])'", String, 'sqs'),
870
+ ('[uU]?"""', String, combined('stringescape', 'tdqs')),
871
+ ("[uU]?'''", String, combined('stringescape', 'tsqs')),
872
+ ('[uU]?"', String, combined('stringescape', 'dqs')),
873
+ ("[uU]?'", String, combined('stringescape', 'sqs')),
874
+ include('name'),
875
+ include('numbers'),
876
+ ],
877
+ 'keywords': [
878
+ (words((
879
+ 'assert', 'async', 'await', 'break', 'by', 'continue', 'ctypedef', 'del', 'elif',
880
+ 'else', 'except', 'except?', 'exec', 'finally', 'for', 'fused', 'gil',
881
+ 'global', 'if', 'include', 'lambda', 'nogil', 'pass', 'print',
882
+ 'raise', 'return', 'try', 'while', 'yield', 'as', 'with'), suffix=r'\b'),
883
+ Keyword),
884
+ (r'(DEF|IF|ELIF|ELSE)\b', Comment.Preproc),
885
+ ],
886
+ 'builtins': [
887
+ (words((
888
+ '__import__', 'abs', 'all', 'any', 'apply', 'basestring', 'bin', 'bint',
889
+ 'bool', 'buffer', 'bytearray', 'bytes', 'callable', 'chr',
890
+ 'classmethod', 'cmp', 'coerce', 'compile', 'complex', 'delattr',
891
+ 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'execfile', 'exit',
892
+ 'file', 'filter', 'float', 'frozenset', 'getattr', 'globals',
893
+ 'hasattr', 'hash', 'hex', 'id', 'input', 'int', 'intern', 'isinstance',
894
+ 'issubclass', 'iter', 'len', 'list', 'locals', 'long', 'map', 'max',
895
+ 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'property', 'Py_ssize_t',
896
+ 'range', 'raw_input', 'reduce', 'reload', 'repr', 'reversed',
897
+ 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod',
898
+ 'str', 'sum', 'super', 'tuple', 'type', 'unichr', 'unicode', 'unsigned',
899
+ 'vars', 'xrange', 'zip'), prefix=r'(?<!\.)', suffix=r'\b'),
900
+ Name.Builtin),
901
+ (r'(?<!\.)(self|None|Ellipsis|NotImplemented|False|True|NULL'
902
+ r')\b', Name.Builtin.Pseudo),
903
+ (words((
904
+ 'ArithmeticError', 'AssertionError', 'AttributeError',
905
+ 'BaseException', 'DeprecationWarning', 'EOFError', 'EnvironmentError',
906
+ 'Exception', 'FloatingPointError', 'FutureWarning', 'GeneratorExit',
907
+ 'IOError', 'ImportError', 'ImportWarning', 'IndentationError',
908
+ 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError',
909
+ 'MemoryError', 'NameError', 'NotImplemented', 'NotImplementedError',
910
+ 'OSError', 'OverflowError', 'OverflowWarning',
911
+ 'PendingDeprecationWarning', 'ReferenceError', 'RuntimeError',
912
+ 'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError',
913
+ 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError',
914
+ 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError',
915
+ 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError',
916
+ 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning',
917
+ 'ZeroDivisionError'), prefix=r'(?<!\.)', suffix=r'\b'),
918
+ Name.Exception),
919
+ ],
920
+ 'numbers': [
921
+ (r'(\d+\.?\d*|\d*\.\d+)([eE][+-]?[0-9]+)?', Number.Float),
922
+ (r'0\d+', Number.Oct),
923
+ (r'0[xX][a-fA-F0-9]+', Number.Hex),
924
+ (r'\d+L', Number.Integer.Long),
925
+ (r'\d+', Number.Integer)
926
+ ],
927
+ 'backtick': [
928
+ ('`.*?`', String.Backtick),
929
+ ],
930
+ 'name': [
931
+ (r'@\w+', Name.Decorator),
932
+ (r'[a-zA-Z_]\w*', Name),
933
+ ],
934
+ 'funcname': [
935
+ (r'[a-zA-Z_]\w*', Name.Function, '#pop')
936
+ ],
937
+ 'cdef': [
938
+ (r'(public|readonly|extern|api|inline)\b', Keyword.Reserved),
939
+ (r'(struct|enum|union|class)\b', Keyword),
940
+ (r'([a-zA-Z_]\w*)(\s*)(?=[(:#=]|$)',
941
+ bygroups(Name.Function, Text), '#pop'),
942
+ (r'([a-zA-Z_]\w*)(\s*)(,)',
943
+ bygroups(Name.Function, Text, Punctuation)),
944
+ (r'from\b', Keyword, '#pop'),
945
+ (r'as\b', Keyword),
946
+ (r':', Punctuation, '#pop'),
947
+ (r'(?=["\'])', Text, '#pop'),
948
+ (r'[a-zA-Z_]\w*', Keyword.Type),
949
+ (r'.', Text),
950
+ ],
951
+ 'classname': [
952
+ (r'[a-zA-Z_]\w*', Name.Class, '#pop')
953
+ ],
954
+ 'import': [
955
+ (r'(\s+)(as)(\s+)', bygroups(Text, Keyword, Text)),
956
+ (r'[a-zA-Z_][\w.]*', Name.Namespace),
957
+ (r'(\s*)(,)(\s*)', bygroups(Text, Operator, Text)),
958
+ default('#pop') # all else: go back
959
+ ],
960
+ 'fromimport': [
961
+ (r'(\s+)(c?import)\b', bygroups(Text, Keyword), '#pop'),
962
+ (r'[a-zA-Z_.][\w.]*', Name.Namespace),
963
+ # ``cdef foo from "header"``, or ``for foo from 0 < i < 10``
964
+ default('#pop'),
965
+ ],
966
+ 'stringescape': [
967
+ (r'\\([\\abfnrtv"\']|\n|N\{.*?\}|u[a-fA-F0-9]{4}|'
968
+ r'U[a-fA-F0-9]{8}|x[a-fA-F0-9]{2}|[0-7]{1,3})', String.Escape)
969
+ ],
970
+ 'strings': [
971
+ (r'%(\([a-zA-Z0-9]+\))?[-#0 +]*([0-9]+|[*])?(\.([0-9]+|[*]))?'
972
+ '[hlL]?[E-GXc-giorsux%]', String.Interpol),
973
+ (r'[^\\\'"%\n]+', String),
974
+ # quotes, percents and backslashes must be parsed one at a time
975
+ (r'[\'"\\]', String),
976
+ # unhandled string formatting sign
977
+ (r'%', String)
978
+ # newlines are an error (use "nl" state)
979
+ ],
980
+ 'nl': [
981
+ (r'\n', String)
982
+ ],
983
+ 'dqs': [
984
+ (r'"', String, '#pop'),
985
+ (r'\\\\|\\"|\\\n', String.Escape), # included here again for raw strings
986
+ include('strings')
987
+ ],
988
+ 'sqs': [
989
+ (r"'", String, '#pop'),
990
+ (r"\\\\|\\'|\\\n", String.Escape), # included here again for raw strings
991
+ include('strings')
992
+ ],
993
+ 'tdqs': [
994
+ (r'"""', String, '#pop'),
995
+ include('strings'),
996
+ include('nl')
997
+ ],
998
+ 'tsqs': [
999
+ (r"'''", String, '#pop'),
1000
+ include('strings'),
1001
+ include('nl')
1002
+ ],
1003
+ }
1004
+
1005
+
1006
+ class DgLexer(RegexLexer):
1007
+ """
1008
+ Lexer for dg,
1009
+ a functional and object-oriented programming language
1010
+ running on the CPython 3 VM.
1011
+ """
1012
+ name = 'dg'
1013
+ aliases = ['dg']
1014
+ filenames = ['*.dg']
1015
+ mimetypes = ['text/x-dg']
1016
+ url = 'http://pyos.github.io/dg'
1017
+ version_added = '1.6'
1018
+
1019
+ tokens = {
1020
+ 'root': [
1021
+ (r'\s+', Text),
1022
+ (r'#.*?$', Comment.Single),
1023
+
1024
+ (r'(?i)0b[01]+', Number.Bin),
1025
+ (r'(?i)0o[0-7]+', Number.Oct),
1026
+ (r'(?i)0x[0-9a-f]+', Number.Hex),
1027
+ (r'(?i)[+-]?[0-9]+\.[0-9]+(e[+-]?[0-9]+)?j?', Number.Float),
1028
+ (r'(?i)[+-]?[0-9]+e[+-]?\d+j?', Number.Float),
1029
+ (r'(?i)[+-]?[0-9]+j?', Number.Integer),
1030
+
1031
+ (r"(?i)(br|r?b?)'''", String, combined('stringescape', 'tsqs', 'string')),
1032
+ (r'(?i)(br|r?b?)"""', String, combined('stringescape', 'tdqs', 'string')),
1033
+ (r"(?i)(br|r?b?)'", String, combined('stringescape', 'sqs', 'string')),
1034
+ (r'(?i)(br|r?b?)"', String, combined('stringescape', 'dqs', 'string')),
1035
+
1036
+ (r"`\w+'*`", Operator),
1037
+ (r'\b(and|in|is|or|where)\b', Operator.Word),
1038
+ (r'[!$%&*+\-./:<-@\\^|~;,]+', Operator),
1039
+
1040
+ (words((
1041
+ 'bool', 'bytearray', 'bytes', 'classmethod', 'complex', 'dict', 'dict\'',
1042
+ 'float', 'frozenset', 'int', 'list', 'list\'', 'memoryview', 'object',
1043
+ 'property', 'range', 'set', 'set\'', 'slice', 'staticmethod', 'str',
1044
+ 'super', 'tuple', 'tuple\'', 'type'),
1045
+ prefix=r'(?<!\.)', suffix=r'(?![\'\w])'),
1046
+ Name.Builtin),
1047
+ (words((
1048
+ '__import__', 'abs', 'all', 'any', 'bin', 'bind', 'chr', 'cmp', 'compile',
1049
+ 'complex', 'delattr', 'dir', 'divmod', 'drop', 'dropwhile', 'enumerate',
1050
+ 'eval', 'exhaust', 'filter', 'flip', 'foldl1?', 'format', 'fst',
1051
+ 'getattr', 'globals', 'hasattr', 'hash', 'head', 'hex', 'id', 'init',
1052
+ 'input', 'isinstance', 'issubclass', 'iter', 'iterate', 'last', 'len',
1053
+ 'locals', 'map', 'max', 'min', 'next', 'oct', 'open', 'ord', 'pow',
1054
+ 'print', 'repr', 'reversed', 'round', 'setattr', 'scanl1?', 'snd',
1055
+ 'sorted', 'sum', 'tail', 'take', 'takewhile', 'vars', 'zip'),
1056
+ prefix=r'(?<!\.)', suffix=r'(?![\'\w])'),
1057
+ Name.Builtin),
1058
+ (r"(?<!\.)(self|Ellipsis|NotImplemented|None|True|False)(?!['\w])",
1059
+ Name.Builtin.Pseudo),
1060
+
1061
+ (r"(?<!\.)[A-Z]\w*(Error|Exception|Warning)'*(?!['\w])",
1062
+ Name.Exception),
1063
+ (r"(?<!\.)(Exception|GeneratorExit|KeyboardInterrupt|StopIteration|"
1064
+ r"SystemExit)(?!['\w])", Name.Exception),
1065
+
1066
+ (r"(?<![\w.])(except|finally|for|if|import|not|otherwise|raise|"
1067
+ r"subclass|while|with|yield)(?!['\w])", Keyword.Reserved),
1068
+
1069
+ (r"[A-Z_]+'*(?!['\w])", Name),
1070
+ (r"[A-Z]\w+'*(?!['\w])", Keyword.Type),
1071
+ (r"\w+'*", Name),
1072
+
1073
+ (r'[()]', Punctuation),
1074
+ (r'.', Error),
1075
+ ],
1076
+ 'stringescape': [
1077
+ (r'\\([\\abfnrtv"\']|\n|N\{.*?\}|u[a-fA-F0-9]{4}|'
1078
+ r'U[a-fA-F0-9]{8}|x[a-fA-F0-9]{2}|[0-7]{1,3})', String.Escape)
1079
+ ],
1080
+ 'string': [
1081
+ (r'%(\(\w+\))?[-#0 +]*([0-9]+|[*])?(\.([0-9]+|[*]))?'
1082
+ '[hlL]?[E-GXc-giorsux%]', String.Interpol),
1083
+ (r'[^\\\'"%\n]+', String),
1084
+ # quotes, percents and backslashes must be parsed one at a time
1085
+ (r'[\'"\\]', String),
1086
+ # unhandled string formatting sign
1087
+ (r'%', String),
1088
+ (r'\n', String)
1089
+ ],
1090
+ 'dqs': [
1091
+ (r'"', String, '#pop')
1092
+ ],
1093
+ 'sqs': [
1094
+ (r"'", String, '#pop')
1095
+ ],
1096
+ 'tdqs': [
1097
+ (r'"""', String, '#pop')
1098
+ ],
1099
+ 'tsqs': [
1100
+ (r"'''", String, '#pop')
1101
+ ],
1102
+ }
1103
+
1104
+
1105
+ class NumPyLexer(PythonLexer):
1106
+ """
1107
+ A Python lexer recognizing Numerical Python builtins.
1108
+ """
1109
+
1110
+ name = 'NumPy'
1111
+ url = 'https://numpy.org/'
1112
+ aliases = ['numpy']
1113
+ version_added = '0.10'
1114
+
1115
+ # override the mimetypes to not inherit them from python
1116
+ mimetypes = []
1117
+ filenames = []
1118
+
1119
+ EXTRA_KEYWORDS = {
1120
+ 'abs', 'absolute', 'accumulate', 'add', 'alen', 'all', 'allclose',
1121
+ 'alltrue', 'alterdot', 'amax', 'amin', 'angle', 'any', 'append',
1122
+ 'apply_along_axis', 'apply_over_axes', 'arange', 'arccos', 'arccosh',
1123
+ 'arcsin', 'arcsinh', 'arctan', 'arctan2', 'arctanh', 'argmax', 'argmin',
1124
+ 'argsort', 'argwhere', 'around', 'array', 'array2string', 'array_equal',
1125
+ 'array_equiv', 'array_repr', 'array_split', 'array_str', 'arrayrange',
1126
+ 'asanyarray', 'asarray', 'asarray_chkfinite', 'ascontiguousarray',
1127
+ 'asfarray', 'asfortranarray', 'asmatrix', 'asscalar', 'astype',
1128
+ 'atleast_1d', 'atleast_2d', 'atleast_3d', 'average', 'bartlett',
1129
+ 'base_repr', 'beta', 'binary_repr', 'bincount', 'binomial',
1130
+ 'bitwise_and', 'bitwise_not', 'bitwise_or', 'bitwise_xor', 'blackman',
1131
+ 'bmat', 'broadcast', 'byte_bounds', 'bytes', 'byteswap', 'c_',
1132
+ 'can_cast', 'ceil', 'choose', 'clip', 'column_stack', 'common_type',
1133
+ 'compare_chararrays', 'compress', 'concatenate', 'conj', 'conjugate',
1134
+ 'convolve', 'copy', 'corrcoef', 'correlate', 'cos', 'cosh', 'cov',
1135
+ 'cross', 'cumprod', 'cumproduct', 'cumsum', 'delete', 'deprecate',
1136
+ 'diag', 'diagflat', 'diagonal', 'diff', 'digitize', 'disp', 'divide',
1137
+ 'dot', 'dsplit', 'dstack', 'dtype', 'dump', 'dumps', 'ediff1d', 'empty',
1138
+ 'empty_like', 'equal', 'exp', 'expand_dims', 'expm1', 'extract', 'eye',
1139
+ 'fabs', 'fastCopyAndTranspose', 'fft', 'fftfreq', 'fftshift', 'fill',
1140
+ 'finfo', 'fix', 'flat', 'flatnonzero', 'flatten', 'fliplr', 'flipud',
1141
+ 'floor', 'floor_divide', 'fmod', 'frexp', 'fromarrays', 'frombuffer',
1142
+ 'fromfile', 'fromfunction', 'fromiter', 'frompyfunc', 'fromstring',
1143
+ 'generic', 'get_array_wrap', 'get_include', 'get_numarray_include',
1144
+ 'get_numpy_include', 'get_printoptions', 'getbuffer', 'getbufsize',
1145
+ 'geterr', 'geterrcall', 'geterrobj', 'getfield', 'gradient', 'greater',
1146
+ 'greater_equal', 'gumbel', 'hamming', 'hanning', 'histogram',
1147
+ 'histogram2d', 'histogramdd', 'hsplit', 'hstack', 'hypot', 'i0',
1148
+ 'identity', 'ifft', 'imag', 'index_exp', 'indices', 'inf', 'info',
1149
+ 'inner', 'insert', 'int_asbuffer', 'interp', 'intersect1d',
1150
+ 'intersect1d_nu', 'inv', 'invert', 'iscomplex', 'iscomplexobj',
1151
+ 'isfinite', 'isfortran', 'isinf', 'isnan', 'isneginf', 'isposinf',
1152
+ 'isreal', 'isrealobj', 'isscalar', 'issctype', 'issubclass_',
1153
+ 'issubdtype', 'issubsctype', 'item', 'itemset', 'iterable', 'ix_',
1154
+ 'kaiser', 'kron', 'ldexp', 'left_shift', 'less', 'less_equal', 'lexsort',
1155
+ 'linspace', 'load', 'loads', 'loadtxt', 'log', 'log10', 'log1p', 'log2',
1156
+ 'logical_and', 'logical_not', 'logical_or', 'logical_xor', 'logspace',
1157
+ 'lstsq', 'mat', 'matrix', 'max', 'maximum', 'maximum_sctype',
1158
+ 'may_share_memory', 'mean', 'median', 'meshgrid', 'mgrid', 'min',
1159
+ 'minimum', 'mintypecode', 'mod', 'modf', 'msort', 'multiply', 'nan',
1160
+ 'nan_to_num', 'nanargmax', 'nanargmin', 'nanmax', 'nanmin', 'nansum',
1161
+ 'ndenumerate', 'ndim', 'ndindex', 'negative', 'newaxis', 'newbuffer',
1162
+ 'newbyteorder', 'nonzero', 'not_equal', 'obj2sctype', 'ogrid', 'ones',
1163
+ 'ones_like', 'outer', 'permutation', 'piecewise', 'pinv', 'pkgload',
1164
+ 'place', 'poisson', 'poly', 'poly1d', 'polyadd', 'polyder', 'polydiv',
1165
+ 'polyfit', 'polyint', 'polymul', 'polysub', 'polyval', 'power', 'prod',
1166
+ 'product', 'ptp', 'put', 'putmask', 'r_', 'randint', 'random_integers',
1167
+ 'random_sample', 'ranf', 'rank', 'ravel', 'real', 'real_if_close',
1168
+ 'recarray', 'reciprocal', 'reduce', 'remainder', 'repeat', 'require',
1169
+ 'reshape', 'resize', 'restoredot', 'right_shift', 'rint', 'roll',
1170
+ 'rollaxis', 'roots', 'rot90', 'round', 'round_', 'row_stack', 's_',
1171
+ 'sample', 'savetxt', 'sctype2char', 'searchsorted', 'seed', 'select',
1172
+ 'set_numeric_ops', 'set_printoptions', 'set_string_function',
1173
+ 'setbufsize', 'setdiff1d', 'seterr', 'seterrcall', 'seterrobj',
1174
+ 'setfield', 'setflags', 'setmember1d', 'setxor1d', 'shape',
1175
+ 'show_config', 'shuffle', 'sign', 'signbit', 'sin', 'sinc', 'sinh',
1176
+ 'size', 'slice', 'solve', 'sometrue', 'sort', 'sort_complex', 'source',
1177
+ 'split', 'sqrt', 'square', 'squeeze', 'standard_normal', 'std',
1178
+ 'subtract', 'sum', 'svd', 'swapaxes', 'take', 'tan', 'tanh', 'tensordot',
1179
+ 'test', 'tile', 'tofile', 'tolist', 'tostring', 'trace', 'transpose',
1180
+ 'trapz', 'tri', 'tril', 'trim_zeros', 'triu', 'true_divide', 'typeDict',
1181
+ 'typename', 'uniform', 'union1d', 'unique', 'unique1d', 'unravel_index',
1182
+ 'unwrap', 'vander', 'var', 'vdot', 'vectorize', 'view', 'vonmises',
1183
+ 'vsplit', 'vstack', 'weibull', 'where', 'who', 'zeros', 'zeros_like'
1184
+ }
1185
+
1186
+ def get_tokens_unprocessed(self, text):
1187
+ for index, token, value in \
1188
+ PythonLexer.get_tokens_unprocessed(self, text):
1189
+ if token is Name and value in self.EXTRA_KEYWORDS:
1190
+ yield index, Keyword.Pseudo, value
1191
+ else:
1192
+ yield index, token, value
1193
+
1194
+ def analyse_text(text):
1195
+ ltext = text[:1000]
1196
+ return (shebang_matches(text, r'pythonw?(3(\.\d)?)?') or
1197
+ 'import ' in ltext) \
1198
+ and ('import numpy' in ltext or 'from numpy import' in ltext)
lib/python3.12/site-packages/pip/_vendor/pygments/modeline.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ pygments.modeline
3
+ ~~~~~~~~~~~~~~~~~
4
+
5
+ A simple modeline parser (based on pymodeline).
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
+
13
+ __all__ = ['get_filetype_from_buffer']
14
+
15
+
16
+ modeline_re = re.compile(r'''
17
+ (?: vi | vim | ex ) (?: [<=>]? \d* )? :
18
+ .* (?: ft | filetype | syn | syntax ) = ( [^:\s]+ )
19
+ ''', re.VERBOSE)
20
+
21
+
22
+ def get_filetype_from_line(l): # noqa: E741
23
+ m = modeline_re.search(l)
24
+ if m:
25
+ return m.group(1)
26
+
27
+
28
+ def get_filetype_from_buffer(buf, max_lines=5):
29
+ """
30
+ Scan the buffer for modelines and return filetype if one is found.
31
+ """
32
+ lines = buf.splitlines()
33
+ for line in lines[-1:-max_lines-1:-1]:
34
+ ret = get_filetype_from_line(line)
35
+ if ret:
36
+ return ret
37
+ for i in range(max_lines, -1, -1):
38
+ if i < len(lines):
39
+ ret = get_filetype_from_line(lines[i])
40
+ if ret:
41
+ return ret
42
+
43
+ return None