about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters')
-rw-r--r--.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__init__.py157
-rw-r--r--.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/_mapping.py23
-rw-r--r--.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/bbcode.py108
-rw-r--r--.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/groff.py170
-rw-r--r--.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/html.py987
-rw-r--r--.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/img.py685
-rw-r--r--.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/irc.py154
-rw-r--r--.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/latex.py518
-rw-r--r--.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/other.py160
-rw-r--r--.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/pangomarkup.py83
-rw-r--r--.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/rtf.py349
-rw-r--r--.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/svg.py185
-rw-r--r--.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/terminal.py127
-rw-r--r--.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/terminal256.py338
14 files changed, 4044 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__init__.py b/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__init__.py
new file mode 100644
index 00000000..f19e9931
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__init__.py
@@ -0,0 +1,157 @@
+"""
+    pygments.formatters
+    ~~~~~~~~~~~~~~~~~~~
+
+    Pygments formatters.
+
+    :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
+    :license: BSD, see LICENSE for details.
+"""
+
+import re
+import sys
+import types
+import fnmatch
+from os.path import basename
+
+from pip._vendor.pygments.formatters._mapping import FORMATTERS
+from pip._vendor.pygments.plugin import find_plugin_formatters
+from pip._vendor.pygments.util import ClassNotFound
+
+__all__ = ['get_formatter_by_name', 'get_formatter_for_filename',
+           'get_all_formatters', 'load_formatter_from_file'] + list(FORMATTERS)
+
+_formatter_cache = {}  # classes by name
+_pattern_cache = {}
+
+
+def _fn_matches(fn, glob):
+    """Return whether the supplied file name fn matches pattern filename."""
+    if glob not in _pattern_cache:
+        pattern = _pattern_cache[glob] = re.compile(fnmatch.translate(glob))
+        return pattern.match(fn)
+    return _pattern_cache[glob].match(fn)
+
+
+def _load_formatters(module_name):
+    """Load a formatter (and all others in the module too)."""
+    mod = __import__(module_name, None, None, ['__all__'])
+    for formatter_name in mod.__all__:
+        cls = getattr(mod, formatter_name)
+        _formatter_cache[cls.name] = cls
+
+
+def get_all_formatters():
+    """Return a generator for all formatter classes."""
+    # NB: this returns formatter classes, not info like get_all_lexers().
+    for info in FORMATTERS.values():
+        if info[1] not in _formatter_cache:
+            _load_formatters(info[0])
+        yield _formatter_cache[info[1]]
+    for _, formatter in find_plugin_formatters():
+        yield formatter
+
+
+def find_formatter_class(alias):
+    """Lookup a formatter by alias.
+
+    Returns None if not found.
+    """
+    for module_name, name, aliases, _, _ in FORMATTERS.values():
+        if alias in aliases:
+            if name not in _formatter_cache:
+                _load_formatters(module_name)
+            return _formatter_cache[name]
+    for _, cls in find_plugin_formatters():
+        if alias in cls.aliases:
+            return cls
+
+
+def get_formatter_by_name(_alias, **options):
+    """
+    Return an instance of a :class:`.Formatter` subclass that has `alias` in its
+    aliases list. The formatter is given the `options` at its instantiation.
+
+    Will raise :exc:`pygments.util.ClassNotFound` if no formatter with that
+    alias is found.
+    """
+    cls = find_formatter_class(_alias)
+    if cls is None:
+        raise ClassNotFound(f"no formatter found for name {_alias!r}")
+    return cls(**options)
+
+
+def load_formatter_from_file(filename, formattername="CustomFormatter", **options):
+    """
+    Return a `Formatter` subclass instance loaded from the provided file, relative
+    to the current directory.
+
+    The file is expected to contain a Formatter class named ``formattername``
+    (by default, CustomFormatter). Users should be very careful with the input, because
+    this method is equivalent to running ``eval()`` on the input file. The formatter is
+    given the `options` at its instantiation.
+
+    :exc:`pygments.util.ClassNotFound` is raised if there are any errors loading
+    the formatter.
+
+    .. versionadded:: 2.2
+    """
+    try:
+        # This empty dict will contain the namespace for the exec'd file
+        custom_namespace = {}
+        with open(filename, 'rb') as f:
+            exec(f.read(), custom_namespace)
+        # Retrieve the class `formattername` from that namespace
+        if formattername not in custom_namespace:
+            raise ClassNotFound(f'no valid {formattername} class found in {filename}')
+        formatter_class = custom_namespace[formattername]
+        # And finally instantiate it with the options
+        return formatter_class(**options)
+    except OSError as err:
+        raise ClassNotFound(f'cannot read {filename}: {err}')
+    except ClassNotFound:
+        raise
+    except Exception as err:
+        raise ClassNotFound(f'error when loading custom formatter: {err}')
+
+
+def get_formatter_for_filename(fn, **options):
+    """
+    Return a :class:`.Formatter` subclass instance that has a filename pattern
+    matching `fn`. The formatter is given the `options` at its instantiation.
+
+    Will raise :exc:`pygments.util.ClassNotFound` if no formatter for that filename
+    is found.
+    """
+    fn = basename(fn)
+    for modname, name, _, filenames, _ in FORMATTERS.values():
+        for filename in filenames:
+            if _fn_matches(fn, filename):
+                if name not in _formatter_cache:
+                    _load_formatters(modname)
+                return _formatter_cache[name](**options)
+    for _name, cls in find_plugin_formatters():
+        for filename in cls.filenames:
+            if _fn_matches(fn, filename):
+                return cls(**options)
+    raise ClassNotFound(f"no formatter found for file name {fn!r}")
+
+
+class _automodule(types.ModuleType):
+    """Automatically import formatters."""
+
+    def __getattr__(self, name):
+        info = FORMATTERS.get(name)
+        if info:
+            _load_formatters(info[0])
+            cls = _formatter_cache[info[1]]
+            setattr(self, name, cls)
+            return cls
+        raise AttributeError(name)
+
+
+oldmod = sys.modules[__name__]
+newmod = _automodule(__name__)
+newmod.__dict__.update(oldmod.__dict__)
+sys.modules[__name__] = newmod
+del newmod.newmod, newmod.oldmod, newmod.sys, newmod.types
diff --git a/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/_mapping.py b/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/_mapping.py
new file mode 100644
index 00000000..72ca8404
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/_mapping.py
@@ -0,0 +1,23 @@
+# Automatically generated by scripts/gen_mapfiles.py.
+# DO NOT EDIT BY HAND; run `tox -e mapfiles` instead.
+
+FORMATTERS = {
+    'BBCodeFormatter': ('pygments.formatters.bbcode', 'BBCode', ('bbcode', 'bb'), (), 'Format tokens with BBcodes. These formatting codes are used by many bulletin boards, so you can highlight your sourcecode with pygments before posting it there.'),
+    'BmpImageFormatter': ('pygments.formatters.img', 'img_bmp', ('bmp', 'bitmap'), ('*.bmp',), 'Create a bitmap image from source code. This uses the Python Imaging Library to generate a pixmap from the source code.'),
+    'GifImageFormatter': ('pygments.formatters.img', 'img_gif', ('gif',), ('*.gif',), 'Create a GIF image from source code. This uses the Python Imaging Library to generate a pixmap from the source code.'),
+    'GroffFormatter': ('pygments.formatters.groff', 'groff', ('groff', 'troff', 'roff'), (), 'Format tokens with groff escapes to change their color and font style.'),
+    'HtmlFormatter': ('pygments.formatters.html', 'HTML', ('html',), ('*.html', '*.htm'), "Format tokens as HTML 4 ``<span>`` tags. By default, the content is enclosed in a ``<pre>`` tag, itself wrapped in a ``<div>`` tag (but see the `nowrap` option). The ``<div>``'s CSS class can be set by the `cssclass` option."),
+    'IRCFormatter': ('pygments.formatters.irc', 'IRC', ('irc', 'IRC'), (), 'Format tokens with IRC color sequences'),
+    'ImageFormatter': ('pygments.formatters.img', 'img', ('img', 'IMG', 'png'), ('*.png',), 'Create a PNG image from source code. This uses the Python Imaging Library to generate a pixmap from the source code.'),
+    'JpgImageFormatter': ('pygments.formatters.img', 'img_jpg', ('jpg', 'jpeg'), ('*.jpg',), 'Create a JPEG image from source code. This uses the Python Imaging Library to generate a pixmap from the source code.'),
+    'LatexFormatter': ('pygments.formatters.latex', 'LaTeX', ('latex', 'tex'), ('*.tex',), 'Format tokens as LaTeX code. This needs the `fancyvrb` and `color` standard packages.'),
+    'NullFormatter': ('pygments.formatters.other', 'Text only', ('text', 'null'), ('*.txt',), 'Output the text unchanged without any formatting.'),
+    'PangoMarkupFormatter': ('pygments.formatters.pangomarkup', 'Pango Markup', ('pango', 'pangomarkup'), (), 'Format tokens as Pango Markup code. It can then be rendered to an SVG.'),
+    'RawTokenFormatter': ('pygments.formatters.other', 'Raw tokens', ('raw', 'tokens'), ('*.raw',), 'Format tokens as a raw representation for storing token streams.'),
+    'RtfFormatter': ('pygments.formatters.rtf', 'RTF', ('rtf',), ('*.rtf',), 'Format tokens as RTF markup. This formatter automatically outputs full RTF documents with color information and other useful stuff. Perfect for Copy and Paste into Microsoft(R) Word(R) documents.'),
+    'SvgFormatter': ('pygments.formatters.svg', 'SVG', ('svg',), ('*.svg',), 'Format tokens as an SVG graphics file.  This formatter is still experimental. Each line of code is a ``<text>`` element with explicit ``x`` and ``y`` coordinates containing ``<tspan>`` elements with the individual token styles.'),
+    'Terminal256Formatter': ('pygments.formatters.terminal256', 'Terminal256', ('terminal256', 'console256', '256'), (), 'Format tokens with ANSI color sequences, for output in a 256-color terminal or console.  Like in `TerminalFormatter` color sequences are terminated at newlines, so that paging the output works correctly.'),
+    'TerminalFormatter': ('pygments.formatters.terminal', 'Terminal', ('terminal', 'console'), (), 'Format tokens with ANSI color sequences, for output in a text console. Color sequences are terminated at newlines, so that paging the output works correctly.'),
+    'TerminalTrueColorFormatter': ('pygments.formatters.terminal256', 'TerminalTrueColor', ('terminal16m', 'console16m', '16m'), (), 'Format tokens with ANSI color sequences, for output in a true-color terminal or console.  Like in `TerminalFormatter` color sequences are terminated at newlines, so that paging the output works correctly.'),
+    'TestcaseFormatter': ('pygments.formatters.other', 'Testcase', ('testcase',), (), 'Format tokens as appropriate for a new testcase.'),
+}
diff --git a/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/bbcode.py b/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/bbcode.py
new file mode 100644
index 00000000..5a05bd96
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/bbcode.py
@@ -0,0 +1,108 @@
+"""
+    pygments.formatters.bbcode
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    BBcode formatter.
+
+    :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
+    :license: BSD, see LICENSE for details.
+"""
+
+
+from pip._vendor.pygments.formatter import Formatter
+from pip._vendor.pygments.util import get_bool_opt
+
+__all__ = ['BBCodeFormatter']
+
+
+class BBCodeFormatter(Formatter):
+    """
+    Format tokens with BBcodes. These formatting codes are used by many
+    bulletin boards, so you can highlight your sourcecode with pygments before
+    posting it there.
+
+    This formatter has no support for background colors and borders, as there
+    are no common BBcode tags for that.
+
+    Some board systems (e.g. phpBB) don't support colors in their [code] tag,
+    so you can't use the highlighting together with that tag.
+    Text in a [code] tag usually is shown with a monospace font (which this
+    formatter can do with the ``monofont`` option) and no spaces (which you
+    need for indentation) are removed.
+
+    Additional options accepted:
+
+    `style`
+        The style to use, can be a string or a Style subclass (default:
+        ``'default'``).
+
+    `codetag`
+        If set to true, put the output into ``[code]`` tags (default:
+        ``false``)
+
+    `monofont`
+        If set to true, add a tag to show the code with a monospace font
+        (default: ``false``).
+    """
+    name = 'BBCode'
+    aliases = ['bbcode', 'bb']
+    filenames = []
+
+    def __init__(self, **options):
+        Formatter.__init__(self, **options)
+        self._code = get_bool_opt(options, 'codetag', False)
+        self._mono = get_bool_opt(options, 'monofont', False)
+
+        self.styles = {}
+        self._make_styles()
+
+    def _make_styles(self):
+        for ttype, ndef in self.style:
+            start = end = ''
+            if ndef['color']:
+                start += '[color=#{}]'.format(ndef['color'])
+                end = '[/color]' + end
+            if ndef['bold']:
+                start += '[b]'
+                end = '[/b]' + end
+            if ndef['italic']:
+                start += '[i]'
+                end = '[/i]' + end
+            if ndef['underline']:
+                start += '[u]'
+                end = '[/u]' + end
+            # there are no common BBcodes for background-color and border
+
+            self.styles[ttype] = start, end
+
+    def format_unencoded(self, tokensource, outfile):
+        if self._code:
+            outfile.write('[code]')
+        if self._mono:
+            outfile.write('[font=monospace]')
+
+        lastval = ''
+        lasttype = None
+
+        for ttype, value in tokensource:
+            while ttype not in self.styles:
+                ttype = ttype.parent
+            if ttype == lasttype:
+                lastval += value
+            else:
+                if lastval:
+                    start, end = self.styles[lasttype]
+                    outfile.write(''.join((start, lastval, end)))
+                lastval = value
+                lasttype = ttype
+
+        if lastval:
+            start, end = self.styles[lasttype]
+            outfile.write(''.join((start, lastval, end)))
+
+        if self._mono:
+            outfile.write('[/font]')
+        if self._code:
+            outfile.write('[/code]')
+        if self._code or self._mono:
+            outfile.write('\n')
diff --git a/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/groff.py b/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/groff.py
new file mode 100644
index 00000000..5c8a958f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/groff.py
@@ -0,0 +1,170 @@
+"""
+    pygments.formatters.groff
+    ~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Formatter for groff output.
+
+    :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
+    :license: BSD, see LICENSE for details.
+"""
+
+import math
+from pip._vendor.pygments.formatter import Formatter
+from pip._vendor.pygments.util import get_bool_opt, get_int_opt
+
+__all__ = ['GroffFormatter']
+
+
+class GroffFormatter(Formatter):
+    """
+    Format tokens with groff escapes to change their color and font style.
+
+    .. versionadded:: 2.11
+
+    Additional options accepted:
+
+    `style`
+        The style to use, can be a string or a Style subclass (default:
+        ``'default'``).
+
+    `monospaced`
+        If set to true, monospace font will be used (default: ``true``).
+
+    `linenos`
+        If set to true, print the line numbers (default: ``false``).
+
+    `wrap`
+        Wrap lines to the specified number of characters. Disabled if set to 0
+        (default: ``0``).
+    """
+
+    name = 'groff'
+    aliases = ['groff','troff','roff']
+    filenames = []
+
+    def __init__(self, **options):
+        Formatter.__init__(self, **options)
+
+        self.monospaced = get_bool_opt(options, 'monospaced', True)
+        self.linenos = get_bool_opt(options, 'linenos', False)
+        self._lineno = 0
+        self.wrap = get_int_opt(options, 'wrap', 0)
+        self._linelen = 0
+
+        self.styles = {}
+        self._make_styles()
+
+
+    def _make_styles(self):
+        regular = '\\f[CR]' if self.monospaced else '\\f[R]'
+        bold = '\\f[CB]' if self.monospaced else '\\f[B]'
+        italic = '\\f[CI]' if self.monospaced else '\\f[I]'
+
+        for ttype, ndef in self.style:
+            start = end = ''
+            if ndef['color']:
+                start += '\\m[{}]'.format(ndef['color'])
+                end = '\\m[]' + end
+            if ndef['bold']:
+                start += bold
+                end = regular + end
+            if ndef['italic']:
+                start += italic
+                end = regular + end
+            if ndef['bgcolor']:
+                start += '\\M[{}]'.format(ndef['bgcolor'])
+                end = '\\M[]' + end
+
+            self.styles[ttype] = start, end
+
+
+    def _define_colors(self, outfile):
+        colors = set()
+        for _, ndef in self.style:
+            if ndef['color'] is not None:
+                colors.add(ndef['color'])
+
+        for color in sorted(colors):
+            outfile.write('.defcolor ' + color + ' rgb #' + color + '\n')
+
+
+    def _write_lineno(self, outfile):
+        self._lineno += 1
+        outfile.write("%s% 4d " % (self._lineno != 1 and '\n' or '', self._lineno))
+
+
+    def _wrap_line(self, line):
+        length = len(line.rstrip('\n'))
+        space = '     ' if self.linenos else ''
+        newline = ''
+
+        if length > self.wrap:
+            for i in range(0, math.floor(length / self.wrap)):
+                chunk = line[i*self.wrap:i*self.wrap+self.wrap]
+                newline += (chunk + '\n' + space)
+            remainder = length % self.wrap
+            if remainder > 0:
+                newline += line[-remainder-1:]
+                self._linelen = remainder
+        elif self._linelen + length > self.wrap:
+            newline = ('\n' + space) + line
+            self._linelen = length
+        else:
+            newline = line
+            self._linelen += length
+
+        return newline
+
+
+    def _escape_chars(self, text):
+        text = text.replace('\\', '\\[u005C]'). \
+                    replace('.', '\\[char46]'). \
+                    replace('\'', '\\[u0027]'). \
+                    replace('`', '\\[u0060]'). \
+                    replace('~', '\\[u007E]')
+        copy = text
+
+        for char in copy:
+            if len(char) != len(char.encode()):
+                uni = char.encode('unicode_escape') \
+                    .decode()[1:] \
+                    .replace('x', 'u00') \
+                    .upper()
+                text = text.replace(char, '\\[u' + uni[1:] + ']')
+
+        return text
+
+
+    def format_unencoded(self, tokensource, outfile):
+        self._define_colors(outfile)
+
+        outfile.write('.nf\n\\f[CR]\n')
+
+        if self.linenos:
+            self._write_lineno(outfile)
+
+        for ttype, value in tokensource:
+            while ttype not in self.styles:
+                ttype = ttype.parent
+            start, end = self.styles[ttype]
+
+            for line in value.splitlines(True):
+                if self.wrap > 0:
+                    line = self._wrap_line(line)
+
+                if start and end:
+                    text = self._escape_chars(line.rstrip('\n'))
+                    if text != '':
+                        outfile.write(''.join((start, text, end)))
+                else:
+                    outfile.write(self._escape_chars(line.rstrip('\n')))
+
+                if line.endswith('\n'):
+                    if self.linenos:
+                        self._write_lineno(outfile)
+                        self._linelen = 0
+                    else:
+                        outfile.write('\n')
+                        self._linelen = 0
+
+        outfile.write('\n.fi')
diff --git a/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/html.py b/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/html.py
new file mode 100644
index 00000000..7aa938f5
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/html.py
@@ -0,0 +1,987 @@
+"""
+    pygments.formatters.html
+    ~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Formatter for HTML output.
+
+    :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
+    :license: BSD, see LICENSE for details.
+"""
+
+import functools
+import os
+import sys
+import os.path
+from io import StringIO
+
+from pip._vendor.pygments.formatter import Formatter
+from pip._vendor.pygments.token import Token, Text, STANDARD_TYPES
+from pip._vendor.pygments.util import get_bool_opt, get_int_opt, get_list_opt
+
+try:
+    import ctags
+except ImportError:
+    ctags = None
+
+__all__ = ['HtmlFormatter']
+
+
+_escape_html_table = {
+    ord('&'): '&amp;',
+    ord('<'): '&lt;',
+    ord('>'): '&gt;',
+    ord('"'): '&quot;',
+    ord("'"): '&#39;',
+}
+
+
+def escape_html(text, table=_escape_html_table):
+    """Escape &, <, > as well as single and double quotes for HTML."""
+    return text.translate(table)
+
+
+def webify(color):
+    if color.startswith('calc') or color.startswith('var'):
+        return color
+    else:
+        return '#' + color
+
+
+def _get_ttype_class(ttype):
+    fname = STANDARD_TYPES.get(ttype)
+    if fname:
+        return fname
+    aname = ''
+    while fname is None:
+        aname = '-' + ttype[-1] + aname
+        ttype = ttype.parent
+        fname = STANDARD_TYPES.get(ttype)
+    return fname + aname
+
+
+CSSFILE_TEMPLATE = '''\
+/*
+generated by Pygments <https://pygments.org/>
+Copyright 2006-2024 by the Pygments team.
+Licensed under the BSD license, see LICENSE for details.
+*/
+%(styledefs)s
+'''
+
+DOC_HEADER = '''\
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
+   "http://www.w3.org/TR/html4/strict.dtd">
+<!--
+generated by Pygments <https://pygments.org/>
+Copyright 2006-2024 by the Pygments team.
+Licensed under the BSD license, see LICENSE for details.
+-->
+<html>
+<head>
+  <title>%(title)s</title>
+  <meta http-equiv="content-type" content="text/html; charset=%(encoding)s">
+  <style type="text/css">
+''' + CSSFILE_TEMPLATE + '''
+  </style>
+</head>
+<body>
+<h2>%(title)s</h2>
+
+'''
+
+DOC_HEADER_EXTERNALCSS = '''\
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
+   "http://www.w3.org/TR/html4/strict.dtd">
+
+<html>
+<head>
+  <title>%(title)s</title>
+  <meta http-equiv="content-type" content="text/html; charset=%(encoding)s">
+  <link rel="stylesheet" href="%(cssfile)s" type="text/css">
+</head>
+<body>
+<h2>%(title)s</h2>
+
+'''
+
+DOC_FOOTER = '''\
+</body>
+</html>
+'''
+
+
+class HtmlFormatter(Formatter):
+    r"""
+    Format tokens as HTML 4 ``<span>`` tags. By default, the content is enclosed
+    in a ``<pre>`` tag, itself wrapped in a ``<div>`` tag (but see the `nowrap` option).
+    The ``<div>``'s CSS class can be set by the `cssclass` option.
+
+    If the `linenos` option is set to ``"table"``, the ``<pre>`` is
+    additionally wrapped inside a ``<table>`` which has one row and two
+    cells: one containing the line numbers and one containing the code.
+    Example:
+
+    .. sourcecode:: html
+
+        <div class="highlight" >
+        <table><tr>
+          <td class="linenos" title="click to toggle"
+            onclick="with (this.firstChild.style)
+                     { display = (display == '') ? 'none' : '' }">
+            <pre>1
+            2</pre>
+          </td>
+          <td class="code">
+            <pre><span class="Ke">def </span><span class="NaFu">foo</span>(bar):
+              <span class="Ke">pass</span>
+            </pre>
+          </td>
+        </tr></table></div>
+
+    (whitespace added to improve clarity).
+
+    A list of lines can be specified using the `hl_lines` option to make these
+    lines highlighted (as of Pygments 0.11).
+
+    With the `full` option, a complete HTML 4 document is output, including
+    the style definitions inside a ``<style>`` tag, or in a separate file if
+    the `cssfile` option is given.
+
+    When `tagsfile` is set to the path of a ctags index file, it is used to
+    generate hyperlinks from names to their definition.  You must enable
+    `lineanchors` and run ctags with the `-n` option for this to work.  The
+    `python-ctags` module from PyPI must be installed to use this feature;
+    otherwise a `RuntimeError` will be raised.
+
+    The `get_style_defs(arg='')` method of a `HtmlFormatter` returns a string
+    containing CSS rules for the CSS classes used by the formatter. The
+    argument `arg` can be used to specify additional CSS selectors that
+    are prepended to the classes. A call `fmter.get_style_defs('td .code')`
+    would result in the following CSS classes:
+
+    .. sourcecode:: css
+
+        td .code .kw { font-weight: bold; color: #00FF00 }
+        td .code .cm { color: #999999 }
+        ...
+
+    If you have Pygments 0.6 or higher, you can also pass a list or tuple to the
+    `get_style_defs()` method to request multiple prefixes for the tokens:
+
+    .. sourcecode:: python
+
+        formatter.get_style_defs(['div.syntax pre', 'pre.syntax'])
+
+    The output would then look like this:
+
+    .. sourcecode:: css
+
+        div.syntax pre .kw,
+        pre.syntax .kw { font-weight: bold; color: #00FF00 }
+        div.syntax pre .cm,
+        pre.syntax .cm { color: #999999 }
+        ...
+
+    Additional options accepted:
+
+    `nowrap`
+        If set to ``True``, don't add a ``<pre>`` and a ``<div>`` tag
+        around the tokens. This disables most other options (default: ``False``).
+
+    `full`
+        Tells the formatter to output a "full" document, i.e. a complete
+        self-contained document (default: ``False``).
+
+    `title`
+        If `full` is true, the title that should be used to caption the
+        document (default: ``''``).
+
+    `style`
+        The style to use, can be a string or a Style subclass (default:
+        ``'default'``). This option has no effect if the `cssfile`
+        and `noclobber_cssfile` option are given and the file specified in
+        `cssfile` exists.
+
+    `noclasses`
+        If set to true, token ``<span>`` tags (as well as line number elements)
+        will not use CSS classes, but inline styles. This is not recommended
+        for larger pieces of code since it increases output size by quite a bit
+        (default: ``False``).
+
+    `classprefix`
+        Since the token types use relatively short class names, they may clash
+        with some of your own class names. In this case you can use the
+        `classprefix` option to give a string to prepend to all Pygments-generated
+        CSS class names for token types.
+        Note that this option also affects the output of `get_style_defs()`.
+
+    `cssclass`
+        CSS class for the wrapping ``<div>`` tag (default: ``'highlight'``).
+        If you set this option, the default selector for `get_style_defs()`
+        will be this class.
+
+        .. versionadded:: 0.9
+           If you select the ``'table'`` line numbers, the wrapping table will
+           have a CSS class of this string plus ``'table'``, the default is
+           accordingly ``'highlighttable'``.
+
+    `cssstyles`
+        Inline CSS styles for the wrapping ``<div>`` tag (default: ``''``).
+
+    `prestyles`
+        Inline CSS styles for the ``<pre>`` tag (default: ``''``).
+
+        .. versionadded:: 0.11
+
+    `cssfile`
+        If the `full` option is true and this option is given, it must be the
+        name of an external file. If the filename does not include an absolute
+        path, the file's path will be assumed to be relative to the main output
+        file's path, if the latter can be found. The stylesheet is then written
+        to this file instead of the HTML file.
+
+        .. versionadded:: 0.6
+
+    `noclobber_cssfile`
+        If `cssfile` is given and the specified file exists, the css file will
+        not be overwritten. This allows the use of the `full` option in
+        combination with a user specified css file. Default is ``False``.
+
+        .. versionadded:: 1.1
+
+    `linenos`
+        If set to ``'table'``, output line numbers as a table with two cells,
+        one containing the line numbers, the other the whole code.  This is
+        copy-and-paste-friendly, but may cause alignment problems with some
+        browsers or fonts.  If set to ``'inline'``, the line numbers will be
+        integrated in the ``<pre>`` tag that contains the code (that setting
+        is *new in Pygments 0.8*).
+
+        For compatibility with Pygments 0.7 and earlier, every true value
+        except ``'inline'`` means the same as ``'table'`` (in particular, that
+        means also ``True``).
+
+        The default value is ``False``, which means no line numbers at all.
+
+        **Note:** with the default ("table") line number mechanism, the line
+        numbers and code can have different line heights in Internet Explorer
+        unless you give the enclosing ``<pre>`` tags an explicit ``line-height``
+        CSS property (you get the default line spacing with ``line-height:
+        125%``).
+
+    `hl_lines`
+        Specify a list of lines to be highlighted. The line numbers are always
+        relative to the input (i.e. the first line is line 1) and are
+        independent of `linenostart`.
+
+        .. versionadded:: 0.11
+
+    `linenostart`
+        The line number for the first line (default: ``1``).
+
+    `linenostep`
+        If set to a number n > 1, only every nth line number is printed.
+
+    `linenospecial`
+        If set to a number n > 0, every nth line number is given the CSS
+        class ``"special"`` (default: ``0``).
+
+    `nobackground`
+        If set to ``True``, the formatter won't output the background color
+        for the wrapping element (this automatically defaults to ``False``
+        when there is no wrapping element [eg: no argument for the
+        `get_syntax_defs` method given]) (default: ``False``).
+
+        .. versionadded:: 0.6
+
+    `lineseparator`
+        This string is output between lines of code. It defaults to ``"\n"``,
+        which is enough to break a line inside ``<pre>`` tags, but you can
+        e.g. set it to ``"<br>"`` to get HTML line breaks.
+
+        .. versionadded:: 0.7
+
+    `lineanchors`
+        If set to a nonempty string, e.g. ``foo``, the formatter will wrap each
+        output line in an anchor tag with an ``id`` (and `name`) of ``foo-linenumber``.
+        This allows easy linking to certain lines.
+
+        .. versionadded:: 0.9
+
+    `linespans`
+        If set to a nonempty string, e.g. ``foo``, the formatter will wrap each
+        output line in a span tag with an ``id`` of ``foo-linenumber``.
+        This allows easy access to lines via javascript.
+
+        .. versionadded:: 1.6
+
+    `anchorlinenos`
+        If set to `True`, will wrap line numbers in <a> tags. Used in
+        combination with `linenos` and `lineanchors`.
+
+    `tagsfile`
+        If set to the path of a ctags file, wrap names in anchor tags that
+        link to their definitions. `lineanchors` should be used, and the
+        tags file should specify line numbers (see the `-n` option to ctags).
+        The tags file is assumed to be encoded in UTF-8.
+
+        .. versionadded:: 1.6
+
+    `tagurlformat`
+        A string formatting pattern used to generate links to ctags definitions.
+        Available variables are `%(path)s`, `%(fname)s` and `%(fext)s`.
+        Defaults to an empty string, resulting in just `#prefix-number` links.
+
+        .. versionadded:: 1.6
+
+    `filename`
+        A string used to generate a filename when rendering ``<pre>`` blocks,
+        for example if displaying source code. If `linenos` is set to
+        ``'table'`` then the filename will be rendered in an initial row
+        containing a single `<th>` which spans both columns.
+
+        .. versionadded:: 2.1
+
+    `wrapcode`
+        Wrap the code inside ``<pre>`` blocks using ``<code>``, as recommended
+        by the HTML5 specification.
+
+        .. versionadded:: 2.4
+
+    `debug_token_types`
+        Add ``title`` attributes to all token ``<span>`` tags that show the
+        name of the token.
+
+        .. versionadded:: 2.10
+
+
+    **Subclassing the HTML formatter**
+
+    .. versionadded:: 0.7
+
+    The HTML formatter is now built in a way that allows easy subclassing, thus
+    customizing the output HTML code. The `format()` method calls
+    `self._format_lines()` which returns a generator that yields tuples of ``(1,
+    line)``, where the ``1`` indicates that the ``line`` is a line of the
+    formatted source code.
+
+    If the `nowrap` option is set, the generator is the iterated over and the
+    resulting HTML is output.
+
+    Otherwise, `format()` calls `self.wrap()`, which wraps the generator with
+    other generators. These may add some HTML code to the one generated by
+    `_format_lines()`, either by modifying the lines generated by the latter,
+    then yielding them again with ``(1, line)``, and/or by yielding other HTML
+    code before or after the lines, with ``(0, html)``. The distinction between
+    source lines and other code makes it possible to wrap the generator multiple
+    times.
+
+    The default `wrap()` implementation adds a ``<div>`` and a ``<pre>`` tag.
+
+    A custom `HtmlFormatter` subclass could look like this:
+
+    .. sourcecode:: python
+
+        class CodeHtmlFormatter(HtmlFormatter):
+
+            def wrap(self, source, *, include_div):
+                return self._wrap_code(source)
+
+            def _wrap_code(self, source):
+                yield 0, '<code>'
+                for i, t in source:
+                    if i == 1:
+                        # it's a line of formatted code
+                        t += '<br>'
+                    yield i, t
+                yield 0, '</code>'
+
+    This results in wrapping the formatted lines with a ``<code>`` tag, where the
+    source lines are broken using ``<br>`` tags.
+
+    After calling `wrap()`, the `format()` method also adds the "line numbers"
+    and/or "full document" wrappers if the respective options are set. Then, all
+    HTML yielded by the wrapped generator is output.
+    """
+
+    name = 'HTML'
+    aliases = ['html']
+    filenames = ['*.html', '*.htm']
+
+    def __init__(self, **options):
+        Formatter.__init__(self, **options)
+        self.title = self._decodeifneeded(self.title)
+        self.nowrap = get_bool_opt(options, 'nowrap', False)
+        self.noclasses = get_bool_opt(options, 'noclasses', False)
+        self.classprefix = options.get('classprefix', '')
+        self.cssclass = self._decodeifneeded(options.get('cssclass', 'highlight'))
+        self.cssstyles = self._decodeifneeded(options.get('cssstyles', ''))
+        self.prestyles = self._decodeifneeded(options.get('prestyles', ''))
+        self.cssfile = self._decodeifneeded(options.get('cssfile', ''))
+        self.noclobber_cssfile = get_bool_opt(options, 'noclobber_cssfile', False)
+        self.tagsfile = self._decodeifneeded(options.get('tagsfile', ''))
+        self.tagurlformat = self._decodeifneeded(options.get('tagurlformat', ''))
+        self.filename = self._decodeifneeded(options.get('filename', ''))
+        self.wrapcode = get_bool_opt(options, 'wrapcode', False)
+        self.span_element_openers = {}
+        self.debug_token_types = get_bool_opt(options, 'debug_token_types', False)
+
+        if self.tagsfile:
+            if not ctags:
+                raise RuntimeError('The "ctags" package must to be installed '
+                                   'to be able to use the "tagsfile" feature.')
+            self._ctags = ctags.CTags(self.tagsfile)
+
+        linenos = options.get('linenos', False)
+        if linenos == 'inline':
+            self.linenos = 2
+        elif linenos:
+            # compatibility with <= 0.7
+            self.linenos = 1
+        else:
+            self.linenos = 0
+        self.linenostart = abs(get_int_opt(options, 'linenostart', 1))
+        self.linenostep = abs(get_int_opt(options, 'linenostep', 1))
+        self.linenospecial = abs(get_int_opt(options, 'linenospecial', 0))
+        self.nobackground = get_bool_opt(options, 'nobackground', False)
+        self.lineseparator = options.get('lineseparator', '\n')
+        self.lineanchors = options.get('lineanchors', '')
+        self.linespans = options.get('linespans', '')
+        self.anchorlinenos = get_bool_opt(options, 'anchorlinenos', False)
+        self.hl_lines = set()
+        for lineno in get_list_opt(options, 'hl_lines', []):
+            try:
+                self.hl_lines.add(int(lineno))
+            except ValueError:
+                pass
+
+        self._create_stylesheet()
+
+    def _get_css_class(self, ttype):
+        """Return the css class of this token type prefixed with
+        the classprefix option."""
+        ttypeclass = _get_ttype_class(ttype)
+        if ttypeclass:
+            return self.classprefix + ttypeclass
+        return ''
+
+    def _get_css_classes(self, ttype):
+        """Return the CSS classes of this token type prefixed with the classprefix option."""
+        cls = self._get_css_class(ttype)
+        while ttype not in STANDARD_TYPES:
+            ttype = ttype.parent
+            cls = self._get_css_class(ttype) + ' ' + cls
+        return cls or ''
+
+    def _get_css_inline_styles(self, ttype):
+        """Return the inline CSS styles for this token type."""
+        cclass = self.ttype2class.get(ttype)
+        while cclass is None:
+            ttype = ttype.parent
+            cclass = self.ttype2class.get(ttype)
+        return cclass or ''
+
+    def _create_stylesheet(self):
+        t2c = self.ttype2class = {Token: ''}
+        c2s = self.class2style = {}
+        for ttype, ndef in self.style:
+            name = self._get_css_class(ttype)
+            style = ''
+            if ndef['color']:
+                style += 'color: {}; '.format(webify(ndef['color']))
+            if ndef['bold']:
+                style += 'font-weight: bold; '
+            if ndef['italic']:
+                style += 'font-style: italic; '
+            if ndef['underline']:
+                style += 'text-decoration: underline; '
+            if ndef['bgcolor']:
+                style += 'background-color: {}; '.format(webify(ndef['bgcolor']))
+            if ndef['border']:
+                style += 'border: 1px solid {}; '.format(webify(ndef['border']))
+            if style:
+                t2c[ttype] = name
+                # save len(ttype) to enable ordering the styles by
+                # hierarchy (necessary for CSS cascading rules!)
+                c2s[name] = (style[:-2], ttype, len(ttype))
+
+    def get_style_defs(self, arg=None):
+        """
+        Return CSS style definitions for the classes produced by the current
+        highlighting style. ``arg`` can be a string or list of selectors to
+        insert before the token type classes.
+        """
+        style_lines = []
+
+        style_lines.extend(self.get_linenos_style_defs())
+        style_lines.extend(self.get_background_style_defs(arg))
+        style_lines.extend(self.get_token_style_defs(arg))
+
+        return '\n'.join(style_lines)
+
+    def get_token_style_defs(self, arg=None):
+        prefix = self.get_css_prefix(arg)
+
+        styles = [
+            (level, ttype, cls, style)
+            for cls, (style, ttype, level) in self.class2style.items()
+            if cls and style
+        ]
+        styles.sort()
+
+        lines = [
+            f'{prefix(cls)} {{ {style} }} /* {repr(ttype)[6:]} */'
+            for (level, ttype, cls, style) in styles
+        ]
+
+        return lines
+
+    def get_background_style_defs(self, arg=None):
+        prefix = self.get_css_prefix(arg)
+        bg_color = self.style.background_color
+        hl_color = self.style.highlight_color
+
+        lines = []
+
+        if arg and not self.nobackground and bg_color is not None:
+            text_style = ''
+            if Text in self.ttype2class:
+                text_style = ' ' + self.class2style[self.ttype2class[Text]][0]
+            lines.insert(
+                0, '{}{{ background: {};{} }}'.format(
+                    prefix(''), bg_color, text_style
+                )
+            )
+        if hl_color is not None:
+            lines.insert(
+                0, '{} {{ background-color: {} }}'.format(prefix('hll'), hl_color)
+            )
+
+        return lines
+
+    def get_linenos_style_defs(self):
+        lines = [
+            f'pre {{ {self._pre_style} }}',
+            f'td.linenos .normal {{ {self._linenos_style} }}',
+            f'span.linenos {{ {self._linenos_style} }}',
+            f'td.linenos .special {{ {self._linenos_special_style} }}',
+            f'span.linenos.special {{ {self._linenos_special_style} }}',
+        ]
+
+        return lines
+
+    def get_css_prefix(self, arg):
+        if arg is None:
+            arg = ('cssclass' in self.options and '.'+self.cssclass or '')
+        if isinstance(arg, str):
+            args = [arg]
+        else:
+            args = list(arg)
+
+        def prefix(cls):
+            if cls:
+                cls = '.' + cls
+            tmp = []
+            for arg in args:
+                tmp.append((arg and arg + ' ' or '') + cls)
+            return ', '.join(tmp)
+
+        return prefix
+
+    @property
+    def _pre_style(self):
+        return 'line-height: 125%;'
+
+    @property
+    def _linenos_style(self):
+        color = self.style.line_number_color
+        background_color = self.style.line_number_background_color
+        return f'color: {color}; background-color: {background_color}; padding-left: 5px; padding-right: 5px;'
+
+    @property
+    def _linenos_special_style(self):
+        color = self.style.line_number_special_color
+        background_color = self.style.line_number_special_background_color
+        return f'color: {color}; background-color: {background_color}; padding-left: 5px; padding-right: 5px;'
+
+    def _decodeifneeded(self, value):
+        if isinstance(value, bytes):
+            if self.encoding:
+                return value.decode(self.encoding)
+            return value.decode()
+        return value
+
+    def _wrap_full(self, inner, outfile):
+        if self.cssfile:
+            if os.path.isabs(self.cssfile):
+                # it's an absolute filename
+                cssfilename = self.cssfile
+            else:
+                try:
+                    filename = outfile.name
+                    if not filename or filename[0] == '<':
+                        # pseudo files, e.g. name == '<fdopen>'
+                        raise AttributeError
+                    cssfilename = os.path.join(os.path.dirname(filename),
+                                               self.cssfile)
+                except AttributeError:
+                    print('Note: Cannot determine output file name, '
+                          'using current directory as base for the CSS file name',
+                          file=sys.stderr)
+                    cssfilename = self.cssfile
+            # write CSS file only if noclobber_cssfile isn't given as an option.
+            try:
+                if not os.path.exists(cssfilename) or not self.noclobber_cssfile:
+                    with open(cssfilename, "w", encoding="utf-8") as cf:
+                        cf.write(CSSFILE_TEMPLATE %
+                                 {'styledefs': self.get_style_defs('body')})
+            except OSError as err:
+                err.strerror = 'Error writing CSS file: ' + err.strerror
+                raise
+
+            yield 0, (DOC_HEADER_EXTERNALCSS %
+                      dict(title=self.title,
+                           cssfile=self.cssfile,
+                           encoding=self.encoding))
+        else:
+            yield 0, (DOC_HEADER %
+                      dict(title=self.title,
+                           styledefs=self.get_style_defs('body'),
+                           encoding=self.encoding))
+
+        yield from inner
+        yield 0, DOC_FOOTER
+
+    def _wrap_tablelinenos(self, inner):
+        dummyoutfile = StringIO()
+        lncount = 0
+        for t, line in inner:
+            if t:
+                lncount += 1
+            dummyoutfile.write(line)
+
+        fl = self.linenostart
+        mw = len(str(lncount + fl - 1))
+        sp = self.linenospecial
+        st = self.linenostep
+        anchor_name = self.lineanchors or self.linespans
+        aln = self.anchorlinenos
+        nocls = self.noclasses
+
+        lines = []
+
+        for i in range(fl, fl+lncount):
+            print_line = i % st == 0
+            special_line = sp and i % sp == 0
+
+            if print_line:
+                line = '%*d' % (mw, i)
+                if aln:
+                    line = '<a href="#%s-%d">%s</a>' % (anchor_name, i, line)
+            else:
+                line = ' ' * mw
+
+            if nocls:
+                if special_line:
+                    style = f' style="{self._linenos_special_style}"'
+                else:
+                    style = f' style="{self._linenos_style}"'
+            else:
+                if special_line:
+                    style = ' class="special"'
+                else:
+                    style = ' class="normal"'
+
+            if style:
+                line = f'<span{style}>{line}</span>'
+
+            lines.append(line)
+
+        ls = '\n'.join(lines)
+
+        # If a filename was specified, we can't put it into the code table as it
+        # would misalign the line numbers. Hence we emit a separate row for it.
+        filename_tr = ""
+        if self.filename:
+            filename_tr = (
+                '<tr><th colspan="2" class="filename">'
+                '<span class="filename">' + self.filename + '</span>'
+                '</th></tr>')
+
+        # in case you wonder about the seemingly redundant <div> here: since the
+        # content in the other cell also is wrapped in a div, some browsers in
+        # some configurations seem to mess up the formatting...
+        yield 0, (f'<table class="{self.cssclass}table">' + filename_tr +
+            '<tr><td class="linenos"><div class="linenodiv"><pre>' +
+            ls + '</pre></div></td><td class="code">')
+        yield 0, '<div>'
+        yield 0, dummyoutfile.getvalue()
+        yield 0, '</div>'
+        yield 0, '</td></tr></table>'
+
+
+    def _wrap_inlinelinenos(self, inner):
+        # need a list of lines since we need the width of a single number :(
+        inner_lines = list(inner)
+        sp = self.linenospecial
+        st = self.linenostep
+        num = self.linenostart
+        mw = len(str(len(inner_lines) + num - 1))
+        anchor_name = self.lineanchors or self.linespans
+        aln = self.anchorlinenos
+        nocls = self.noclasses
+
+        for _, inner_line in inner_lines:
+            print_line = num % st == 0
+            special_line = sp and num % sp == 0
+
+            if print_line:
+                line = '%*d' % (mw, num)
+            else:
+                line = ' ' * mw
+
+            if nocls:
+                if special_line:
+                    style = f' style="{self._linenos_special_style}"'
+                else:
+                    style = f' style="{self._linenos_style}"'
+            else:
+                if special_line:
+                    style = ' class="linenos special"'
+                else:
+                    style = ' class="linenos"'
+
+            if style:
+                linenos = f'<span{style}>{line}</span>'
+            else:
+                linenos = line
+
+            if aln:
+                yield 1, ('<a href="#%s-%d">%s</a>' % (anchor_name, num, linenos) +
+                          inner_line)
+            else:
+                yield 1, linenos + inner_line
+            num += 1
+
+    def _wrap_lineanchors(self, inner):
+        s = self.lineanchors
+        # subtract 1 since we have to increment i *before* yielding
+        i = self.linenostart - 1
+        for t, line in inner:
+            if t:
+                i += 1
+                href = "" if self.linenos else ' href="#%s-%d"' % (s, i)
+                yield 1, '<a id="%s-%d" name="%s-%d"%s></a>' % (s, i, s, i, href) + line
+            else:
+                yield 0, line
+
+    def _wrap_linespans(self, inner):
+        s = self.linespans
+        i = self.linenostart - 1
+        for t, line in inner:
+            if t:
+                i += 1
+                yield 1, '<span id="%s-%d">%s</span>' % (s, i, line)
+            else:
+                yield 0, line
+
+    def _wrap_div(self, inner):
+        style = []
+        if (self.noclasses and not self.nobackground and
+                self.style.background_color is not None):
+            style.append(f'background: {self.style.background_color}')
+        if self.cssstyles:
+            style.append(self.cssstyles)
+        style = '; '.join(style)
+
+        yield 0, ('<div' + (self.cssclass and f' class="{self.cssclass}"') +
+                  (style and (f' style="{style}"')) + '>')
+        yield from inner
+        yield 0, '</div>\n'
+
+    def _wrap_pre(self, inner):
+        style = []
+        if self.prestyles:
+            style.append(self.prestyles)
+        if self.noclasses:
+            style.append(self._pre_style)
+        style = '; '.join(style)
+
+        if self.filename and self.linenos != 1:
+            yield 0, ('<span class="filename">' + self.filename + '</span>')
+
+        # the empty span here is to keep leading empty lines from being
+        # ignored by HTML parsers
+        yield 0, ('<pre' + (style and f' style="{style}"') + '><span></span>')
+        yield from inner
+        yield 0, '</pre>'
+
+    def _wrap_code(self, inner):
+        yield 0, '<code>'
+        yield from inner
+        yield 0, '</code>'
+
+    @functools.lru_cache(maxsize=100)
+    def _translate_parts(self, value):
+        """HTML-escape a value and split it by newlines."""
+        return value.translate(_escape_html_table).split('\n')
+
+    def _format_lines(self, tokensource):
+        """
+        Just format the tokens, without any wrapping tags.
+        Yield individual lines.
+        """
+        nocls = self.noclasses
+        lsep = self.lineseparator
+        tagsfile = self.tagsfile
+
+        lspan = ''
+        line = []
+        for ttype, value in tokensource:
+            try:
+                cspan = self.span_element_openers[ttype]
+            except KeyError:
+                title = ' title="{}"'.format('.'.join(ttype)) if self.debug_token_types else ''
+                if nocls:
+                    css_style = self._get_css_inline_styles(ttype)
+                    if css_style:
+                        css_style = self.class2style[css_style][0]
+                        cspan = f'<span style="{css_style}"{title}>'
+                    else:
+                        cspan = ''
+                else:
+                    css_class = self._get_css_classes(ttype)
+                    if css_class:
+                        cspan = f'<span class="{css_class}"{title}>'
+                    else:
+                        cspan = ''
+                self.span_element_openers[ttype] = cspan
+
+            parts = self._translate_parts(value)
+
+            if tagsfile and ttype in Token.Name:
+                filename, linenumber = self._lookup_ctag(value)
+                if linenumber:
+                    base, filename = os.path.split(filename)
+                    if base:
+                        base += '/'
+                    filename, extension = os.path.splitext(filename)
+                    url = self.tagurlformat % {'path': base, 'fname': filename,
+                                               'fext': extension}
+                    parts[0] = "<a href=\"%s#%s-%d\">%s" % \
+                        (url, self.lineanchors, linenumber, parts[0])
+                    parts[-1] = parts[-1] + "</a>"
+
+            # for all but the last line
+            for part in parts[:-1]:
+                if line:
+                    # Also check for part being non-empty, so we avoid creating
+                    # empty <span> tags
+                    if lspan != cspan and part:
+                        line.extend(((lspan and '</span>'), cspan, part,
+                                     (cspan and '</span>'), lsep))
+                    else:  # both are the same, or the current part was empty
+                        line.extend((part, (lspan and '</span>'), lsep))
+                    yield 1, ''.join(line)
+                    line = []
+                elif part:
+                    yield 1, ''.join((cspan, part, (cspan and '</span>'), lsep))
+                else:
+                    yield 1, lsep
+            # for the last line
+            if line and parts[-1]:
+                if lspan != cspan:
+                    line.extend(((lspan and '</span>'), cspan, parts[-1]))
+                    lspan = cspan
+                else:
+                    line.append(parts[-1])
+            elif parts[-1]:
+                line = [cspan, parts[-1]]
+                lspan = cspan
+            # else we neither have to open a new span nor set lspan
+
+        if line:
+            line.extend(((lspan and '</span>'), lsep))
+            yield 1, ''.join(line)
+
+    def _lookup_ctag(self, token):
+        entry = ctags.TagEntry()
+        if self._ctags.find(entry, token.encode(), 0):
+            return entry['file'].decode(), entry['lineNumber']
+        else:
+            return None, None
+
+    def _highlight_lines(self, tokensource):
+        """
+        Highlighted the lines specified in the `hl_lines` option by
+        post-processing the token stream coming from `_format_lines`.
+        """
+        hls = self.hl_lines
+
+        for i, (t, value) in enumerate(tokensource):
+            if t != 1:
+                yield t, value
+            if i + 1 in hls:  # i + 1 because Python indexes start at 0
+                if self.noclasses:
+                    style = ''
+                    if self.style.highlight_color is not None:
+                        style = (f' style="background-color: {self.style.highlight_color}"')
+                    yield 1, f'<span{style}>{value}</span>'
+                else:
+                    yield 1, f'<span class="hll">{value}</span>'
+            else:
+                yield 1, value
+
+    def wrap(self, source):
+        """
+        Wrap the ``source``, which is a generator yielding
+        individual lines, in custom generators. See docstring
+        for `format`. Can be overridden.
+        """
+
+        output = source
+        if self.wrapcode:
+            output = self._wrap_code(output)
+
+        output = self._wrap_pre(output)
+
+        return output
+
+    def format_unencoded(self, tokensource, outfile):
+        """
+        The formatting process uses several nested generators; which of
+        them are used is determined by the user's options.
+
+        Each generator should take at least one argument, ``inner``,
+        and wrap the pieces of text generated by this.
+
+        Always yield 2-tuples: (code, text). If "code" is 1, the text
+        is part of the original tokensource being highlighted, if it's
+        0, the text is some piece of wrapping. This makes it possible to
+        use several different wrappers that process the original source
+        linewise, e.g. line number generators.
+        """
+        source = self._format_lines(tokensource)
+
+        # As a special case, we wrap line numbers before line highlighting
+        # so the line numbers get wrapped in the highlighting tag.
+        if not self.nowrap and self.linenos == 2:
+            source = self._wrap_inlinelinenos(source)
+
+        if self.hl_lines:
+            source = self._highlight_lines(source)
+
+        if not self.nowrap:
+            if self.lineanchors:
+                source = self._wrap_lineanchors(source)
+            if self.linespans:
+                source = self._wrap_linespans(source)
+            source = self.wrap(source)
+            if self.linenos == 1:
+                source = self._wrap_tablelinenos(source)
+            source = self._wrap_div(source)
+            if self.full:
+                source = self._wrap_full(source, outfile)
+
+        for t, piece in source:
+            outfile.write(piece)
diff --git a/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/img.py b/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/img.py
new file mode 100644
index 00000000..7542cfad
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/img.py
@@ -0,0 +1,685 @@
+"""
+    pygments.formatters.img
+    ~~~~~~~~~~~~~~~~~~~~~~~
+
+    Formatter for Pixmap output.
+
+    :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
+    :license: BSD, see LICENSE for details.
+"""
+import os
+import sys
+
+from pip._vendor.pygments.formatter import Formatter
+from pip._vendor.pygments.util import get_bool_opt, get_int_opt, get_list_opt, \
+    get_choice_opt
+
+import subprocess
+
+# Import this carefully
+try:
+    from PIL import Image, ImageDraw, ImageFont
+    pil_available = True
+except ImportError:
+    pil_available = False
+
+try:
+    import _winreg
+except ImportError:
+    try:
+        import winreg as _winreg
+    except ImportError:
+        _winreg = None
+
+__all__ = ['ImageFormatter', 'GifImageFormatter', 'JpgImageFormatter',
+           'BmpImageFormatter']
+
+
+# For some unknown reason every font calls it something different
+STYLES = {
+    'NORMAL':     ['', 'Roman', 'Book', 'Normal', 'Regular', 'Medium'],
+    'ITALIC':     ['Oblique', 'Italic'],
+    'BOLD':       ['Bold'],
+    'BOLDITALIC': ['Bold Oblique', 'Bold Italic'],
+}
+
+# A sane default for modern systems
+DEFAULT_FONT_NAME_NIX = 'DejaVu Sans Mono'
+DEFAULT_FONT_NAME_WIN = 'Courier New'
+DEFAULT_FONT_NAME_MAC = 'Menlo'
+
+
+class PilNotAvailable(ImportError):
+    """When Python imaging library is not available"""
+
+
+class FontNotFound(Exception):
+    """When there are no usable fonts specified"""
+
+
+class FontManager:
+    """
+    Manages a set of fonts: normal, italic, bold, etc...
+    """
+
+    def __init__(self, font_name, font_size=14):
+        self.font_name = font_name
+        self.font_size = font_size
+        self.fonts = {}
+        self.encoding = None
+        self.variable = False
+        if hasattr(font_name, 'read') or os.path.isfile(font_name):
+            font = ImageFont.truetype(font_name, self.font_size)
+            self.variable = True
+            for style in STYLES:
+                self.fonts[style] = font
+
+            return
+
+        if sys.platform.startswith('win'):
+            if not font_name:
+                self.font_name = DEFAULT_FONT_NAME_WIN
+            self._create_win()
+        elif sys.platform.startswith('darwin'):
+            if not font_name:
+                self.font_name = DEFAULT_FONT_NAME_MAC
+            self._create_mac()
+        else:
+            if not font_name:
+                self.font_name = DEFAULT_FONT_NAME_NIX
+            self._create_nix()
+
+    def _get_nix_font_path(self, name, style):
+        proc = subprocess.Popen(['fc-list', f"{name}:style={style}", 'file'],
+                                stdout=subprocess.PIPE, stderr=None)
+        stdout, _ = proc.communicate()
+        if proc.returncode == 0:
+            lines = stdout.splitlines()
+            for line in lines:
+                if line.startswith(b'Fontconfig warning:'):
+                    continue
+                path = line.decode().strip().strip(':')
+                if path:
+                    return path
+            return None
+
+    def _create_nix(self):
+        for name in STYLES['NORMAL']:
+            path = self._get_nix_font_path(self.font_name, name)
+            if path is not None:
+                self.fonts['NORMAL'] = ImageFont.truetype(path, self.font_size)
+                break
+        else:
+            raise FontNotFound(f'No usable fonts named: "{self.font_name}"')
+        for style in ('ITALIC', 'BOLD', 'BOLDITALIC'):
+            for stylename in STYLES[style]:
+                path = self._get_nix_font_path(self.font_name, stylename)
+                if path is not None:
+                    self.fonts[style] = ImageFont.truetype(path, self.font_size)
+                    break
+            else:
+                if style == 'BOLDITALIC':
+                    self.fonts[style] = self.fonts['BOLD']
+                else:
+                    self.fonts[style] = self.fonts['NORMAL']
+
+    def _get_mac_font_path(self, font_map, name, style):
+        return font_map.get((name + ' ' + style).strip().lower())
+
+    def _create_mac(self):
+        font_map = {}
+        for font_dir in (os.path.join(os.getenv("HOME"), 'Library/Fonts/'),
+                         '/Library/Fonts/', '/System/Library/Fonts/'):
+            font_map.update(
+                (os.path.splitext(f)[0].lower(), os.path.join(font_dir, f))
+                for f in os.listdir(font_dir)
+                if f.lower().endswith(('ttf', 'ttc')))
+
+        for name in STYLES['NORMAL']:
+            path = self._get_mac_font_path(font_map, self.font_name, name)
+            if path is not None:
+                self.fonts['NORMAL'] = ImageFont.truetype(path, self.font_size)
+                break
+        else:
+            raise FontNotFound(f'No usable fonts named: "{self.font_name}"')
+        for style in ('ITALIC', 'BOLD', 'BOLDITALIC'):
+            for stylename in STYLES[style]:
+                path = self._get_mac_font_path(font_map, self.font_name, stylename)
+                if path is not None:
+                    self.fonts[style] = ImageFont.truetype(path, self.font_size)
+                    break
+            else:
+                if style == 'BOLDITALIC':
+                    self.fonts[style] = self.fonts['BOLD']
+                else:
+                    self.fonts[style] = self.fonts['NORMAL']
+
+    def _lookup_win(self, key, basename, styles, fail=False):
+        for suffix in ('', ' (TrueType)'):
+            for style in styles:
+                try:
+                    valname = '{}{}{}'.format(basename, style and ' '+style, suffix)
+                    val, _ = _winreg.QueryValueEx(key, valname)
+                    return val
+                except OSError:
+                    continue
+        else:
+            if fail:
+                raise FontNotFound(f'Font {basename} ({styles[0]}) not found in registry')
+            return None
+
+    def _create_win(self):
+        lookuperror = None
+        keynames = [ (_winreg.HKEY_CURRENT_USER, r'Software\Microsoft\Windows NT\CurrentVersion\Fonts'),
+                     (_winreg.HKEY_CURRENT_USER, r'Software\Microsoft\Windows\CurrentVersion\Fonts'),
+                     (_winreg.HKEY_LOCAL_MACHINE, r'Software\Microsoft\Windows NT\CurrentVersion\Fonts'),
+                     (_winreg.HKEY_LOCAL_MACHINE, r'Software\Microsoft\Windows\CurrentVersion\Fonts') ]
+        for keyname in keynames:
+            try:
+                key = _winreg.OpenKey(*keyname)
+                try:
+                    path = self._lookup_win(key, self.font_name, STYLES['NORMAL'], True)
+                    self.fonts['NORMAL'] = ImageFont.truetype(path, self.font_size)
+                    for style in ('ITALIC', 'BOLD', 'BOLDITALIC'):
+                        path = self._lookup_win(key, self.font_name, STYLES[style])
+                        if path:
+                            self.fonts[style] = ImageFont.truetype(path, self.font_size)
+                        else:
+                            if style == 'BOLDITALIC':
+                                self.fonts[style] = self.fonts['BOLD']
+                            else:
+                                self.fonts[style] = self.fonts['NORMAL']
+                    return
+                except FontNotFound as err:
+                    lookuperror = err
+                finally:
+                    _winreg.CloseKey(key)
+            except OSError:
+                pass
+        else:
+            # If we get here, we checked all registry keys and had no luck
+            # We can be in one of two situations now:
+            # * All key lookups failed. In this case lookuperror is None and we
+            #   will raise a generic error
+            # * At least one lookup failed with a FontNotFound error. In this
+            #   case, we will raise that as a more specific error
+            if lookuperror:
+                raise lookuperror
+            raise FontNotFound('Can\'t open Windows font registry key')
+
+    def get_char_size(self):
+        """
+        Get the character size.
+        """
+        return self.get_text_size('M')
+
+    def get_text_size(self, text):
+        """
+        Get the text size (width, height).
+        """
+        font = self.fonts['NORMAL']
+        if hasattr(font, 'getbbox'):  # Pillow >= 9.2.0
+            return font.getbbox(text)[2:4]
+        else:
+            return font.getsize(text)
+
+    def get_font(self, bold, oblique):
+        """
+        Get the font based on bold and italic flags.
+        """
+        if bold and oblique:
+            if self.variable:
+                return self.get_style('BOLDITALIC')
+
+            return self.fonts['BOLDITALIC']
+        elif bold:
+            if self.variable:
+                return self.get_style('BOLD')
+
+            return self.fonts['BOLD']
+        elif oblique:
+            if self.variable:
+                return self.get_style('ITALIC')
+
+            return self.fonts['ITALIC']
+        else:
+            if self.variable:
+                return self.get_style('NORMAL')
+
+            return self.fonts['NORMAL']
+
+    def get_style(self, style):
+        """
+        Get the specified style of the font if it is a variable font.
+        If not found, return the normal font.
+        """
+        font = self.fonts[style]
+        for style_name in STYLES[style]:
+            try:
+                font.set_variation_by_name(style_name)
+                return font
+            except ValueError:
+                pass
+            except OSError:
+                return font
+
+        return font
+
+
+class ImageFormatter(Formatter):
+    """
+    Create a PNG image from source code. This uses the Python Imaging Library to
+    generate a pixmap from the source code.
+
+    .. versionadded:: 0.10
+
+    Additional options accepted:
+
+    `image_format`
+        An image format to output to that is recognised by PIL, these include:
+
+        * "PNG" (default)
+        * "JPEG"
+        * "BMP"
+        * "GIF"
+
+    `line_pad`
+        The extra spacing (in pixels) between each line of text.
+
+        Default: 2
+
+    `font_name`
+        The font name to be used as the base font from which others, such as
+        bold and italic fonts will be generated.  This really should be a
+        monospace font to look sane.
+        If a filename or a file-like object is specified, the user must
+        provide different styles of the font.
+
+        Default: "Courier New" on Windows, "Menlo" on Mac OS, and
+                 "DejaVu Sans Mono" on \\*nix
+
+    `font_size`
+        The font size in points to be used.
+
+        Default: 14
+
+    `image_pad`
+        The padding, in pixels to be used at each edge of the resulting image.
+
+        Default: 10
+
+    `line_numbers`
+        Whether line numbers should be shown: True/False
+
+        Default: True
+
+    `line_number_start`
+        The line number of the first line.
+
+        Default: 1
+
+    `line_number_step`
+        The step used when printing line numbers.
+
+        Default: 1
+
+    `line_number_bg`
+        The background colour (in "#123456" format) of the line number bar, or
+        None to use the style background color.
+
+        Default: "#eed"
+
+    `line_number_fg`
+        The text color of the line numbers (in "#123456"-like format).
+
+        Default: "#886"
+
+    `line_number_chars`
+        The number of columns of line numbers allowable in the line number
+        margin.
+
+        Default: 2
+
+    `line_number_bold`
+        Whether line numbers will be bold: True/False
+
+        Default: False
+
+    `line_number_italic`
+        Whether line numbers will be italicized: True/False
+
+        Default: False
+
+    `line_number_separator`
+        Whether a line will be drawn between the line number area and the
+        source code area: True/False
+
+        Default: True
+
+    `line_number_pad`
+        The horizontal padding (in pixels) between the line number margin, and
+        the source code area.
+
+        Default: 6
+
+    `hl_lines`
+        Specify a list of lines to be highlighted.
+
+        .. versionadded:: 1.2
+
+        Default: empty list
+
+    `hl_color`
+        Specify the color for highlighting lines.
+
+        .. versionadded:: 1.2
+
+        Default: highlight color of the selected style
+    """
+
+    # Required by the pygments mapper
+    name = 'img'
+    aliases = ['img', 'IMG', 'png']
+    filenames = ['*.png']
+
+    unicodeoutput = False
+
+    default_image_format = 'png'
+
+    def __init__(self, **options):
+        """
+        See the class docstring for explanation of options.
+        """
+        if not pil_available:
+            raise PilNotAvailable(
+                'Python Imaging Library is required for this formatter')
+        Formatter.__init__(self, **options)
+        self.encoding = 'latin1'  # let pygments.format() do the right thing
+        # Read the style
+        self.styles = dict(self.style)
+        if self.style.background_color is None:
+            self.background_color = '#fff'
+        else:
+            self.background_color = self.style.background_color
+        # Image options
+        self.image_format = get_choice_opt(
+            options, 'image_format', ['png', 'jpeg', 'gif', 'bmp'],
+            self.default_image_format, normcase=True)
+        self.image_pad = get_int_opt(options, 'image_pad', 10)
+        self.line_pad = get_int_opt(options, 'line_pad', 2)
+        # The fonts
+        fontsize = get_int_opt(options, 'font_size', 14)
+        self.fonts = FontManager(options.get('font_name', ''), fontsize)
+        self.fontw, self.fonth = self.fonts.get_char_size()
+        # Line number options
+        self.line_number_fg = options.get('line_number_fg', '#886')
+        self.line_number_bg = options.get('line_number_bg', '#eed')
+        self.line_number_chars = get_int_opt(options,
+                                             'line_number_chars', 2)
+        self.line_number_bold = get_bool_opt(options,
+                                             'line_number_bold', False)
+        self.line_number_italic = get_bool_opt(options,
+                                               'line_number_italic', False)
+        self.line_number_pad = get_int_opt(options, 'line_number_pad', 6)
+        self.line_numbers = get_bool_opt(options, 'line_numbers', True)
+        self.line_number_separator = get_bool_opt(options,
+                                                  'line_number_separator', True)
+        self.line_number_step = get_int_opt(options, 'line_number_step', 1)
+        self.line_number_start = get_int_opt(options, 'line_number_start', 1)
+        if self.line_numbers:
+            self.line_number_width = (self.fontw * self.line_number_chars +
+                                      self.line_number_pad * 2)
+        else:
+            self.line_number_width = 0
+        self.hl_lines = []
+        hl_lines_str = get_list_opt(options, 'hl_lines', [])
+        for line in hl_lines_str:
+            try:
+                self.hl_lines.append(int(line))
+            except ValueError:
+                pass
+        self.hl_color = options.get('hl_color',
+                                    self.style.highlight_color) or '#f90'
+        self.drawables = []
+
+    def get_style_defs(self, arg=''):
+        raise NotImplementedError('The -S option is meaningless for the image '
+                                  'formatter. Use -O style=<stylename> instead.')
+
+    def _get_line_height(self):
+        """
+        Get the height of a line.
+        """
+        return self.fonth + self.line_pad
+
+    def _get_line_y(self, lineno):
+        """
+        Get the Y coordinate of a line number.
+        """
+        return lineno * self._get_line_height() + self.image_pad
+
+    def _get_char_width(self):
+        """
+        Get the width of a character.
+        """
+        return self.fontw
+
+    def _get_char_x(self, linelength):
+        """
+        Get the X coordinate of a character position.
+        """
+        return linelength + self.image_pad + self.line_number_width
+
+    def _get_text_pos(self, linelength, lineno):
+        """
+        Get the actual position for a character and line position.
+        """
+        return self._get_char_x(linelength), self._get_line_y(lineno)
+
+    def _get_linenumber_pos(self, lineno):
+        """
+        Get the actual position for the start of a line number.
+        """
+        return (self.image_pad, self._get_line_y(lineno))
+
+    def _get_text_color(self, style):
+        """
+        Get the correct color for the token from the style.
+        """
+        if style['color'] is not None:
+            fill = '#' + style['color']
+        else:
+            fill = '#000'
+        return fill
+
+    def _get_text_bg_color(self, style):
+        """
+        Get the correct background color for the token from the style.
+        """
+        if style['bgcolor'] is not None:
+            bg_color = '#' + style['bgcolor']
+        else:
+            bg_color = None
+        return bg_color
+
+    def _get_style_font(self, style):
+        """
+        Get the correct font for the style.
+        """
+        return self.fonts.get_font(style['bold'], style['italic'])
+
+    def _get_image_size(self, maxlinelength, maxlineno):
+        """
+        Get the required image size.
+        """
+        return (self._get_char_x(maxlinelength) + self.image_pad,
+                self._get_line_y(maxlineno + 0) + self.image_pad)
+
+    def _draw_linenumber(self, posno, lineno):
+        """
+        Remember a line number drawable to paint later.
+        """
+        self._draw_text(
+            self._get_linenumber_pos(posno),
+            str(lineno).rjust(self.line_number_chars),
+            font=self.fonts.get_font(self.line_number_bold,
+                                     self.line_number_italic),
+            text_fg=self.line_number_fg,
+            text_bg=None,
+        )
+
+    def _draw_text(self, pos, text, font, text_fg, text_bg):
+        """
+        Remember a single drawable tuple to paint later.
+        """
+        self.drawables.append((pos, text, font, text_fg, text_bg))
+
+    def _create_drawables(self, tokensource):
+        """
+        Create drawables for the token content.
+        """
+        lineno = charno = maxcharno = 0
+        maxlinelength = linelength = 0
+        for ttype, value in tokensource:
+            while ttype not in self.styles:
+                ttype = ttype.parent
+            style = self.styles[ttype]
+            # TODO: make sure tab expansion happens earlier in the chain.  It
+            # really ought to be done on the input, as to do it right here is
+            # quite complex.
+            value = value.expandtabs(4)
+            lines = value.splitlines(True)
+            # print lines
+            for i, line in enumerate(lines):
+                temp = line.rstrip('\n')
+                if temp:
+                    self._draw_text(
+                        self._get_text_pos(linelength, lineno),
+                        temp,
+                        font = self._get_style_font(style),
+                        text_fg = self._get_text_color(style),
+                        text_bg = self._get_text_bg_color(style),
+                    )
+                    temp_width, _ = self.fonts.get_text_size(temp)
+                    linelength += temp_width
+                    maxlinelength = max(maxlinelength, linelength)
+                    charno += len(temp)
+                    maxcharno = max(maxcharno, charno)
+                if line.endswith('\n'):
+                    # add a line for each extra line in the value
+                    linelength = 0
+                    charno = 0
+                    lineno += 1
+        self.maxlinelength = maxlinelength
+        self.maxcharno = maxcharno
+        self.maxlineno = lineno
+
+    def _draw_line_numbers(self):
+        """
+        Create drawables for the line numbers.
+        """
+        if not self.line_numbers:
+            return
+        for p in range(self.maxlineno):
+            n = p + self.line_number_start
+            if (n % self.line_number_step) == 0:
+                self._draw_linenumber(p, n)
+
+    def _paint_line_number_bg(self, im):
+        """
+        Paint the line number background on the image.
+        """
+        if not self.line_numbers:
+            return
+        if self.line_number_fg is None:
+            return
+        draw = ImageDraw.Draw(im)
+        recth = im.size[-1]
+        rectw = self.image_pad + self.line_number_width - self.line_number_pad
+        draw.rectangle([(0, 0), (rectw, recth)],
+                       fill=self.line_number_bg)
+        if self.line_number_separator:
+            draw.line([(rectw, 0), (rectw, recth)], fill=self.line_number_fg)
+        del draw
+
+    def format(self, tokensource, outfile):
+        """
+        Format ``tokensource``, an iterable of ``(tokentype, tokenstring)``
+        tuples and write it into ``outfile``.
+
+        This implementation calculates where it should draw each token on the
+        pixmap, then calculates the required pixmap size and draws the items.
+        """
+        self._create_drawables(tokensource)
+        self._draw_line_numbers()
+        im = Image.new(
+            'RGB',
+            self._get_image_size(self.maxlinelength, self.maxlineno),
+            self.background_color
+        )
+        self._paint_line_number_bg(im)
+        draw = ImageDraw.Draw(im)
+        # Highlight
+        if self.hl_lines:
+            x = self.image_pad + self.line_number_width - self.line_number_pad + 1
+            recth = self._get_line_height()
+            rectw = im.size[0] - x
+            for linenumber in self.hl_lines:
+                y = self._get_line_y(linenumber - 1)
+                draw.rectangle([(x, y), (x + rectw, y + recth)],
+                               fill=self.hl_color)
+        for pos, value, font, text_fg, text_bg in self.drawables:
+            if text_bg:
+                # see deprecations https://pillow.readthedocs.io/en/stable/releasenotes/9.2.0.html#font-size-and-offset-methods
+                if hasattr(draw, 'textsize'):
+                    text_size = draw.textsize(text=value, font=font)
+                else:
+                    text_size = font.getbbox(value)[2:]
+                draw.rectangle([pos[0], pos[1], pos[0] + text_size[0], pos[1] + text_size[1]], fill=text_bg)
+            draw.text(pos, value, font=font, fill=text_fg)
+        im.save(outfile, self.image_format.upper())
+
+
+# Add one formatter per format, so that the "-f gif" option gives the correct result
+# when used in pygmentize.
+
+class GifImageFormatter(ImageFormatter):
+    """
+    Create a GIF image from source code. This uses the Python Imaging Library to
+    generate a pixmap from the source code.
+
+    .. versionadded:: 1.0
+    """
+
+    name = 'img_gif'
+    aliases = ['gif']
+    filenames = ['*.gif']
+    default_image_format = 'gif'
+
+
+class JpgImageFormatter(ImageFormatter):
+    """
+    Create a JPEG image from source code. This uses the Python Imaging Library to
+    generate a pixmap from the source code.
+
+    .. versionadded:: 1.0
+    """
+
+    name = 'img_jpg'
+    aliases = ['jpg', 'jpeg']
+    filenames = ['*.jpg']
+    default_image_format = 'jpeg'
+
+
+class BmpImageFormatter(ImageFormatter):
+    """
+    Create a bitmap image from source code. This uses the Python Imaging Library to
+    generate a pixmap from the source code.
+
+    .. versionadded:: 1.0
+    """
+
+    name = 'img_bmp'
+    aliases = ['bmp', 'bitmap']
+    filenames = ['*.bmp']
+    default_image_format = 'bmp'
diff --git a/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/irc.py b/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/irc.py
new file mode 100644
index 00000000..468c2876
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/irc.py
@@ -0,0 +1,154 @@
+"""
+    pygments.formatters.irc
+    ~~~~~~~~~~~~~~~~~~~~~~~
+
+    Formatter for IRC output
+
+    :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
+    :license: BSD, see LICENSE for details.
+"""
+
+from pip._vendor.pygments.formatter import Formatter
+from pip._vendor.pygments.token import Keyword, Name, Comment, String, Error, \
+    Number, Operator, Generic, Token, Whitespace
+from pip._vendor.pygments.util import get_choice_opt
+
+
+__all__ = ['IRCFormatter']
+
+
+#: Map token types to a tuple of color values for light and dark
+#: backgrounds.
+IRC_COLORS = {
+    Token:              ('',            ''),
+
+    Whitespace:         ('gray',   'brightblack'),
+    Comment:            ('gray',   'brightblack'),
+    Comment.Preproc:    ('cyan',        'brightcyan'),
+    Keyword:            ('blue',    'brightblue'),
+    Keyword.Type:       ('cyan',        'brightcyan'),
+    Operator.Word:      ('magenta',      'brightcyan'),
+    Name.Builtin:       ('cyan',        'brightcyan'),
+    Name.Function:      ('green',   'brightgreen'),
+    Name.Namespace:     ('_cyan_',      '_brightcyan_'),
+    Name.Class:         ('_green_', '_brightgreen_'),
+    Name.Exception:     ('cyan',        'brightcyan'),
+    Name.Decorator:     ('brightblack',    'gray'),
+    Name.Variable:      ('red',     'brightred'),
+    Name.Constant:      ('red',     'brightred'),
+    Name.Attribute:     ('cyan',        'brightcyan'),
+    Name.Tag:           ('brightblue',        'brightblue'),
+    String:             ('yellow',       'yellow'),
+    Number:             ('blue',    'brightblue'),
+
+    Generic.Deleted:    ('brightred',        'brightred'),
+    Generic.Inserted:   ('green',  'brightgreen'),
+    Generic.Heading:    ('**',         '**'),
+    Generic.Subheading: ('*magenta*',   '*brightmagenta*'),
+    Generic.Error:      ('brightred',        'brightred'),
+
+    Error:              ('_brightred_',      '_brightred_'),
+}
+
+
+IRC_COLOR_MAP = {
+    'white': 0,
+    'black': 1,
+    'blue': 2,
+    'brightgreen': 3,
+    'brightred': 4,
+    'yellow': 5,
+    'magenta': 6,
+    'orange': 7,
+    'green': 7, #compat w/ ansi
+    'brightyellow': 8,
+    'lightgreen': 9,
+    'brightcyan': 9, # compat w/ ansi
+    'cyan': 10,
+    'lightblue': 11,
+    'red': 11, # compat w/ ansi
+    'brightblue': 12,
+    'brightmagenta': 13,
+    'brightblack': 14,
+    'gray': 15,
+}
+
+def ircformat(color, text):
+    if len(color) < 1:
+        return text
+    add = sub = ''
+    if '_' in color: # italic
+        add += '\x1D'
+        sub = '\x1D' + sub
+        color = color.strip('_')
+    if '*' in color: # bold
+        add += '\x02'
+        sub = '\x02' + sub
+        color = color.strip('*')
+    # underline (\x1F) not supported
+    # backgrounds (\x03FF,BB) not supported
+    if len(color) > 0: # actual color - may have issues with ircformat("red", "blah")+"10" type stuff
+        add += '\x03' + str(IRC_COLOR_MAP[color]).zfill(2)
+        sub = '\x03' + sub
+    return add + text + sub
+    return '<'+add+'>'+text+'</'+sub+'>'
+
+
+class IRCFormatter(Formatter):
+    r"""
+    Format tokens with IRC color sequences
+
+    The `get_style_defs()` method doesn't do anything special since there is
+    no support for common styles.
+
+    Options accepted:
+
+    `bg`
+        Set to ``"light"`` or ``"dark"`` depending on the terminal's background
+        (default: ``"light"``).
+
+    `colorscheme`
+        A dictionary mapping token types to (lightbg, darkbg) color names or
+        ``None`` (default: ``None`` = use builtin colorscheme).
+
+    `linenos`
+        Set to ``True`` to have line numbers in the output as well
+        (default: ``False`` = no line numbers).
+    """
+    name = 'IRC'
+    aliases = ['irc', 'IRC']
+    filenames = []
+
+    def __init__(self, **options):
+        Formatter.__init__(self, **options)
+        self.darkbg = get_choice_opt(options, 'bg',
+                                     ['light', 'dark'], 'light') == 'dark'
+        self.colorscheme = options.get('colorscheme', None) or IRC_COLORS
+        self.linenos = options.get('linenos', False)
+        self._lineno = 0
+
+    def _write_lineno(self, outfile):
+        if self.linenos:
+            self._lineno += 1
+            outfile.write("%04d: " % self._lineno)
+
+    def format_unencoded(self, tokensource, outfile):
+        self._write_lineno(outfile)
+
+        for ttype, value in tokensource:
+            color = self.colorscheme.get(ttype)
+            while color is None:
+                ttype = ttype[:-1]
+                color = self.colorscheme.get(ttype)
+            if color:
+                color = color[self.darkbg]
+                spl = value.split('\n')
+                for line in spl[:-1]:
+                    if line:
+                        outfile.write(ircformat(color, line))
+                    outfile.write('\n')
+                    self._write_lineno(outfile)
+                if spl[-1]:
+                    outfile.write(ircformat(color, spl[-1]))
+            else:
+                outfile.write(value)
diff --git a/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/latex.py b/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/latex.py
new file mode 100644
index 00000000..0ec9089b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/latex.py
@@ -0,0 +1,518 @@
+"""
+    pygments.formatters.latex
+    ~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Formatter for LaTeX fancyvrb output.
+
+    :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
+    :license: BSD, see LICENSE for details.
+"""
+
+from io import StringIO
+
+from pip._vendor.pygments.formatter import Formatter
+from pip._vendor.pygments.lexer import Lexer, do_insertions
+from pip._vendor.pygments.token import Token, STANDARD_TYPES
+from pip._vendor.pygments.util import get_bool_opt, get_int_opt
+
+
+__all__ = ['LatexFormatter']
+
+
+def escape_tex(text, commandprefix):
+    return text.replace('\\', '\x00'). \
+                replace('{', '\x01'). \
+                replace('}', '\x02'). \
+                replace('\x00', rf'\{commandprefix}Zbs{{}}'). \
+                replace('\x01', rf'\{commandprefix}Zob{{}}'). \
+                replace('\x02', rf'\{commandprefix}Zcb{{}}'). \
+                replace('^', rf'\{commandprefix}Zca{{}}'). \
+                replace('_', rf'\{commandprefix}Zus{{}}'). \
+                replace('&', rf'\{commandprefix}Zam{{}}'). \
+                replace('<', rf'\{commandprefix}Zlt{{}}'). \
+                replace('>', rf'\{commandprefix}Zgt{{}}'). \
+                replace('#', rf'\{commandprefix}Zsh{{}}'). \
+                replace('%', rf'\{commandprefix}Zpc{{}}'). \
+                replace('$', rf'\{commandprefix}Zdl{{}}'). \
+                replace('-', rf'\{commandprefix}Zhy{{}}'). \
+                replace("'", rf'\{commandprefix}Zsq{{}}'). \
+                replace('"', rf'\{commandprefix}Zdq{{}}'). \
+                replace('~', rf'\{commandprefix}Zti{{}}')
+
+
+DOC_TEMPLATE = r'''
+\documentclass{%(docclass)s}
+\usepackage{fancyvrb}
+\usepackage{color}
+\usepackage[%(encoding)s]{inputenc}
+%(preamble)s
+
+%(styledefs)s
+
+\begin{document}
+
+\section*{%(title)s}
+
+%(code)s
+\end{document}
+'''
+
+## Small explanation of the mess below :)
+#
+# The previous version of the LaTeX formatter just assigned a command to
+# each token type defined in the current style.  That obviously is
+# problematic if the highlighted code is produced for a different style
+# than the style commands themselves.
+#
+# This version works much like the HTML formatter which assigns multiple
+# CSS classes to each <span> tag, from the most specific to the least
+# specific token type, thus falling back to the parent token type if one
+# is not defined.  Here, the classes are there too and use the same short
+# forms given in token.STANDARD_TYPES.
+#
+# Highlighted code now only uses one custom command, which by default is
+# \PY and selectable by the commandprefix option (and in addition the
+# escapes \PYZat, \PYZlb and \PYZrb which haven't been renamed for
+# backwards compatibility purposes).
+#
+# \PY has two arguments: the classes, separated by +, and the text to
+# render in that style.  The classes are resolved into the respective
+# style commands by magic, which serves to ignore unknown classes.
+#
+# The magic macros are:
+# * \PY@it, \PY@bf, etc. are unconditionally wrapped around the text
+#   to render in \PY@do.  Their definition determines the style.
+# * \PY@reset resets \PY@it etc. to do nothing.
+# * \PY@toks parses the list of classes, using magic inspired by the
+#   keyval package (but modified to use plusses instead of commas
+#   because fancyvrb redefines commas inside its environments).
+# * \PY@tok processes one class, calling the \PY@tok@classname command
+#   if it exists.
+# * \PY@tok@classname sets the \PY@it etc. to reflect the chosen style
+#   for its class.
+# * \PY resets the style, parses the classnames and then calls \PY@do.
+#
+# Tip: to read this code, print it out in substituted form using e.g.
+# >>> print STYLE_TEMPLATE % {'cp': 'PY'}
+
+STYLE_TEMPLATE = r'''
+\makeatletter
+\def\%(cp)s@reset{\let\%(cp)s@it=\relax \let\%(cp)s@bf=\relax%%
+    \let\%(cp)s@ul=\relax \let\%(cp)s@tc=\relax%%
+    \let\%(cp)s@bc=\relax \let\%(cp)s@ff=\relax}
+\def\%(cp)s@tok#1{\csname %(cp)s@tok@#1\endcsname}
+\def\%(cp)s@toks#1+{\ifx\relax#1\empty\else%%
+    \%(cp)s@tok{#1}\expandafter\%(cp)s@toks\fi}
+\def\%(cp)s@do#1{\%(cp)s@bc{\%(cp)s@tc{\%(cp)s@ul{%%
+    \%(cp)s@it{\%(cp)s@bf{\%(cp)s@ff{#1}}}}}}}
+\def\%(cp)s#1#2{\%(cp)s@reset\%(cp)s@toks#1+\relax+\%(cp)s@do{#2}}
+
+%(styles)s
+
+\def\%(cp)sZbs{\char`\\}
+\def\%(cp)sZus{\char`\_}
+\def\%(cp)sZob{\char`\{}
+\def\%(cp)sZcb{\char`\}}
+\def\%(cp)sZca{\char`\^}
+\def\%(cp)sZam{\char`\&}
+\def\%(cp)sZlt{\char`\<}
+\def\%(cp)sZgt{\char`\>}
+\def\%(cp)sZsh{\char`\#}
+\def\%(cp)sZpc{\char`\%%}
+\def\%(cp)sZdl{\char`\$}
+\def\%(cp)sZhy{\char`\-}
+\def\%(cp)sZsq{\char`\'}
+\def\%(cp)sZdq{\char`\"}
+\def\%(cp)sZti{\char`\~}
+%% for compatibility with earlier versions
+\def\%(cp)sZat{@}
+\def\%(cp)sZlb{[}
+\def\%(cp)sZrb{]}
+\makeatother
+'''
+
+
+def _get_ttype_name(ttype):
+    fname = STANDARD_TYPES.get(ttype)
+    if fname:
+        return fname
+    aname = ''
+    while fname is None:
+        aname = ttype[-1] + aname
+        ttype = ttype.parent
+        fname = STANDARD_TYPES.get(ttype)
+    return fname + aname
+
+
+class LatexFormatter(Formatter):
+    r"""
+    Format tokens as LaTeX code. This needs the `fancyvrb` and `color`
+    standard packages.
+
+    Without the `full` option, code is formatted as one ``Verbatim``
+    environment, like this:
+
+    .. sourcecode:: latex
+
+        \begin{Verbatim}[commandchars=\\\{\}]
+        \PY{k}{def }\PY{n+nf}{foo}(\PY{n}{bar}):
+            \PY{k}{pass}
+        \end{Verbatim}
+
+    Wrapping can be disabled using the `nowrap` option.
+
+    The special command used here (``\PY``) and all the other macros it needs
+    are output by the `get_style_defs` method.
+
+    With the `full` option, a complete LaTeX document is output, including
+    the command definitions in the preamble.
+
+    The `get_style_defs()` method of a `LatexFormatter` returns a string
+    containing ``\def`` commands defining the macros needed inside the
+    ``Verbatim`` environments.
+
+    Additional options accepted:
+
+    `nowrap`
+        If set to ``True``, don't wrap the tokens at all, not even inside a
+        ``\begin{Verbatim}`` environment. This disables most other options
+        (default: ``False``).
+
+    `style`
+        The style to use, can be a string or a Style subclass (default:
+        ``'default'``).
+
+    `full`
+        Tells the formatter to output a "full" document, i.e. a complete
+        self-contained document (default: ``False``).
+
+    `title`
+        If `full` is true, the title that should be used to caption the
+        document (default: ``''``).
+
+    `docclass`
+        If the `full` option is enabled, this is the document class to use
+        (default: ``'article'``).
+
+    `preamble`
+        If the `full` option is enabled, this can be further preamble commands,
+        e.g. ``\usepackage`` (default: ``''``).
+
+    `linenos`
+        If set to ``True``, output line numbers (default: ``False``).
+
+    `linenostart`
+        The line number for the first line (default: ``1``).
+
+    `linenostep`
+        If set to a number n > 1, only every nth line number is printed.
+
+    `verboptions`
+        Additional options given to the Verbatim environment (see the *fancyvrb*
+        docs for possible values) (default: ``''``).
+
+    `commandprefix`
+        The LaTeX commands used to produce colored output are constructed
+        using this prefix and some letters (default: ``'PY'``).
+
+        .. versionadded:: 0.7
+        .. versionchanged:: 0.10
+           The default is now ``'PY'`` instead of ``'C'``.
+
+    `texcomments`
+        If set to ``True``, enables LaTeX comment lines.  That is, LaTex markup
+        in comment tokens is not escaped so that LaTeX can render it (default:
+        ``False``).
+
+        .. versionadded:: 1.2
+
+    `mathescape`
+        If set to ``True``, enables LaTeX math mode escape in comments. That
+        is, ``'$...$'`` inside a comment will trigger math mode (default:
+        ``False``).
+
+        .. versionadded:: 1.2
+
+    `escapeinside`
+        If set to a string of length 2, enables escaping to LaTeX. Text
+        delimited by these 2 characters is read as LaTeX code and
+        typeset accordingly. It has no effect in string literals. It has
+        no effect in comments if `texcomments` or `mathescape` is
+        set. (default: ``''``).
+
+        .. versionadded:: 2.0
+
+    `envname`
+        Allows you to pick an alternative environment name replacing Verbatim.
+        The alternate environment still has to support Verbatim's option syntax.
+        (default: ``'Verbatim'``).
+
+        .. versionadded:: 2.0
+    """
+    name = 'LaTeX'
+    aliases = ['latex', 'tex']
+    filenames = ['*.tex']
+
+    def __init__(self, **options):
+        Formatter.__init__(self, **options)
+        self.nowrap = get_bool_opt(options, 'nowrap', False)
+        self.docclass = options.get('docclass', 'article')
+        self.preamble = options.get('preamble', '')
+        self.linenos = get_bool_opt(options, 'linenos', False)
+        self.linenostart = abs(get_int_opt(options, 'linenostart', 1))
+        self.linenostep = abs(get_int_opt(options, 'linenostep', 1))
+        self.verboptions = options.get('verboptions', '')
+        self.nobackground = get_bool_opt(options, 'nobackground', False)
+        self.commandprefix = options.get('commandprefix', 'PY')
+        self.texcomments = get_bool_opt(options, 'texcomments', False)
+        self.mathescape = get_bool_opt(options, 'mathescape', False)
+        self.escapeinside = options.get('escapeinside', '')
+        if len(self.escapeinside) == 2:
+            self.left = self.escapeinside[0]
+            self.right = self.escapeinside[1]
+        else:
+            self.escapeinside = ''
+        self.envname = options.get('envname', 'Verbatim')
+
+        self._create_stylesheet()
+
+    def _create_stylesheet(self):
+        t2n = self.ttype2name = {Token: ''}
+        c2d = self.cmd2def = {}
+        cp = self.commandprefix
+
+        def rgbcolor(col):
+            if col:
+                return ','.join(['%.2f' % (int(col[i] + col[i + 1], 16) / 255.0)
+                                 for i in (0, 2, 4)])
+            else:
+                return '1,1,1'
+
+        for ttype, ndef in self.style:
+            name = _get_ttype_name(ttype)
+            cmndef = ''
+            if ndef['bold']:
+                cmndef += r'\let\$$@bf=\textbf'
+            if ndef['italic']:
+                cmndef += r'\let\$$@it=\textit'
+            if ndef['underline']:
+                cmndef += r'\let\$$@ul=\underline'
+            if ndef['roman']:
+                cmndef += r'\let\$$@ff=\textrm'
+            if ndef['sans']:
+                cmndef += r'\let\$$@ff=\textsf'
+            if ndef['mono']:
+                cmndef += r'\let\$$@ff=\textsf'
+            if ndef['color']:
+                cmndef += (r'\def\$$@tc##1{{\textcolor[rgb]{{{}}}{{##1}}}}'.format(rgbcolor(ndef['color'])))
+            if ndef['border']:
+                cmndef += (r'\def\$$@bc##1{{{{\setlength{{\fboxsep}}{{\string -\fboxrule}}'
+                           r'\fcolorbox[rgb]{{{}}}{{{}}}{{\strut ##1}}}}}}'.format(rgbcolor(ndef['border']),
+                            rgbcolor(ndef['bgcolor'])))
+            elif ndef['bgcolor']:
+                cmndef += (r'\def\$$@bc##1{{{{\setlength{{\fboxsep}}{{0pt}}'
+                           r'\colorbox[rgb]{{{}}}{{\strut ##1}}}}}}'.format(rgbcolor(ndef['bgcolor'])))
+            if cmndef == '':
+                continue
+            cmndef = cmndef.replace('$$', cp)
+            t2n[ttype] = name
+            c2d[name] = cmndef
+
+    def get_style_defs(self, arg=''):
+        """
+        Return the command sequences needed to define the commands
+        used to format text in the verbatim environment. ``arg`` is ignored.
+        """
+        cp = self.commandprefix
+        styles = []
+        for name, definition in self.cmd2def.items():
+            styles.append(rf'\@namedef{{{cp}@tok@{name}}}{{{definition}}}')
+        return STYLE_TEMPLATE % {'cp': self.commandprefix,
+                                 'styles': '\n'.join(styles)}
+
+    def format_unencoded(self, tokensource, outfile):
+        # TODO: add support for background colors
+        t2n = self.ttype2name
+        cp = self.commandprefix
+
+        if self.full:
+            realoutfile = outfile
+            outfile = StringIO()
+
+        if not self.nowrap:
+            outfile.write('\\begin{' + self.envname + '}[commandchars=\\\\\\{\\}')
+            if self.linenos:
+                start, step = self.linenostart, self.linenostep
+                outfile.write(',numbers=left' +
+                              (start and ',firstnumber=%d' % start or '') +
+                              (step and ',stepnumber=%d' % step or ''))
+            if self.mathescape or self.texcomments or self.escapeinside:
+                outfile.write(',codes={\\catcode`\\$=3\\catcode`\\^=7'
+                              '\\catcode`\\_=8\\relax}')
+            if self.verboptions:
+                outfile.write(',' + self.verboptions)
+            outfile.write(']\n')
+
+        for ttype, value in tokensource:
+            if ttype in Token.Comment:
+                if self.texcomments:
+                    # Try to guess comment starting lexeme and escape it ...
+                    start = value[0:1]
+                    for i in range(1, len(value)):
+                        if start[0] != value[i]:
+                            break
+                        start += value[i]
+
+                    value = value[len(start):]
+                    start = escape_tex(start, cp)
+
+                    # ... but do not escape inside comment.
+                    value = start + value
+                elif self.mathescape:
+                    # Only escape parts not inside a math environment.
+                    parts = value.split('$')
+                    in_math = False
+                    for i, part in enumerate(parts):
+                        if not in_math:
+                            parts[i] = escape_tex(part, cp)
+                        in_math = not in_math
+                    value = '$'.join(parts)
+                elif self.escapeinside:
+                    text = value
+                    value = ''
+                    while text:
+                        a, sep1, text = text.partition(self.left)
+                        if sep1:
+                            b, sep2, text = text.partition(self.right)
+                            if sep2:
+                                value += escape_tex(a, cp) + b
+                            else:
+                                value += escape_tex(a + sep1 + b, cp)
+                        else:
+                            value += escape_tex(a, cp)
+                else:
+                    value = escape_tex(value, cp)
+            elif ttype not in Token.Escape:
+                value = escape_tex(value, cp)
+            styles = []
+            while ttype is not Token:
+                try:
+                    styles.append(t2n[ttype])
+                except KeyError:
+                    # not in current style
+                    styles.append(_get_ttype_name(ttype))
+                ttype = ttype.parent
+            styleval = '+'.join(reversed(styles))
+            if styleval:
+                spl = value.split('\n')
+                for line in spl[:-1]:
+                    if line:
+                        outfile.write(f"\\{cp}{{{styleval}}}{{{line}}}")
+                    outfile.write('\n')
+                if spl[-1]:
+                    outfile.write(f"\\{cp}{{{styleval}}}{{{spl[-1]}}}")
+            else:
+                outfile.write(value)
+
+        if not self.nowrap:
+            outfile.write('\\end{' + self.envname + '}\n')
+
+        if self.full:
+            encoding = self.encoding or 'utf8'
+            # map known existings encodings from LaTeX distribution
+            encoding = {
+                'utf_8': 'utf8',
+                'latin_1': 'latin1',
+                'iso_8859_1': 'latin1',
+            }.get(encoding.replace('-', '_'), encoding)
+            realoutfile.write(DOC_TEMPLATE %
+                dict(docclass  = self.docclass,
+                     preamble  = self.preamble,
+                     title     = self.title,
+                     encoding  = encoding,
+                     styledefs = self.get_style_defs(),
+                     code      = outfile.getvalue()))
+
+
+class LatexEmbeddedLexer(Lexer):
+    """
+    This lexer takes one lexer as argument, the lexer for the language
+    being formatted, and the left and right delimiters for escaped text.
+
+    First everything is scanned using the language lexer to obtain
+    strings and comments. All other consecutive tokens are merged and
+    the resulting text is scanned for escaped segments, which are given
+    the Token.Escape type. Finally text that is not escaped is scanned
+    again with the language lexer.
+    """
+    def __init__(self, left, right, lang, **options):
+        self.left = left
+        self.right = right
+        self.lang = lang
+        Lexer.__init__(self, **options)
+
+    def get_tokens_unprocessed(self, text):
+        # find and remove all the escape tokens (replace with an empty string)
+        # this is very similar to DelegatingLexer.get_tokens_unprocessed.
+        buffered = ''
+        insertions = []
+        insertion_buf = []
+        for i, t, v in self._find_safe_escape_tokens(text):
+            if t is None:
+                if insertion_buf:
+                    insertions.append((len(buffered), insertion_buf))
+                    insertion_buf = []
+                buffered += v
+            else:
+                insertion_buf.append((i, t, v))
+        if insertion_buf:
+            insertions.append((len(buffered), insertion_buf))
+        return do_insertions(insertions,
+                             self.lang.get_tokens_unprocessed(buffered))
+
+    def _find_safe_escape_tokens(self, text):
+        """ find escape tokens that are not in strings or comments """
+        for i, t, v in self._filter_to(
+            self.lang.get_tokens_unprocessed(text),
+            lambda t: t in Token.Comment or t in Token.String
+        ):
+            if t is None:
+                for i2, t2, v2 in self._find_escape_tokens(v):
+                    yield i + i2, t2, v2
+            else:
+                yield i, None, v
+
+    def _filter_to(self, it, pred):
+        """ Keep only the tokens that match `pred`, merge the others together """
+        buf = ''
+        idx = 0
+        for i, t, v in it:
+            if pred(t):
+                if buf:
+                    yield idx, None, buf
+                    buf = ''
+                yield i, t, v
+            else:
+                if not buf:
+                    idx = i
+                buf += v
+        if buf:
+            yield idx, None, buf
+
+    def _find_escape_tokens(self, text):
+        """ Find escape tokens within text, give token=None otherwise """
+        index = 0
+        while text:
+            a, sep1, text = text.partition(self.left)
+            if a:
+                yield index, None, a
+                index += len(a)
+            if sep1:
+                b, sep2, text = text.partition(self.right)
+                if sep2:
+                    yield index + len(sep1), Token.Escape, b
+                    index += len(sep1) + len(b) + len(sep2)
+                else:
+                    yield index, Token.Error, sep1
+                    index += len(sep1)
+                    text = b
diff --git a/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/other.py b/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/other.py
new file mode 100644
index 00000000..de8d9dcf
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/other.py
@@ -0,0 +1,160 @@
+"""
+    pygments.formatters.other
+    ~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Other formatters: NullFormatter, RawTokenFormatter.
+
+    :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
+    :license: BSD, see LICENSE for details.
+"""
+
+from pip._vendor.pygments.formatter import Formatter
+from pip._vendor.pygments.util import get_choice_opt
+from pip._vendor.pygments.token import Token
+from pip._vendor.pygments.console import colorize
+
+__all__ = ['NullFormatter', 'RawTokenFormatter', 'TestcaseFormatter']
+
+
+class NullFormatter(Formatter):
+    """
+    Output the text unchanged without any formatting.
+    """
+    name = 'Text only'
+    aliases = ['text', 'null']
+    filenames = ['*.txt']
+
+    def format(self, tokensource, outfile):
+        enc = self.encoding
+        for ttype, value in tokensource:
+            if enc:
+                outfile.write(value.encode(enc))
+            else:
+                outfile.write(value)
+
+
+class RawTokenFormatter(Formatter):
+    r"""
+    Format tokens as a raw representation for storing token streams.
+
+    The format is ``tokentype<TAB>repr(tokenstring)\n``. The output can later
+    be converted to a token stream with the `RawTokenLexer`, described in the
+    :doc:`lexer list <lexers>`.
+
+    Only two options are accepted:
+
+    `compress`
+        If set to ``'gz'`` or ``'bz2'``, compress the output with the given
+        compression algorithm after encoding (default: ``''``).
+    `error_color`
+        If set to a color name, highlight error tokens using that color.  If
+        set but with no value, defaults to ``'red'``.
+
+        .. versionadded:: 0.11
+
+    """
+    name = 'Raw tokens'
+    aliases = ['raw', 'tokens']
+    filenames = ['*.raw']
+
+    unicodeoutput = False
+
+    def __init__(self, **options):
+        Formatter.__init__(self, **options)
+        # We ignore self.encoding if it is set, since it gets set for lexer
+        # and formatter if given with -Oencoding on the command line.
+        # The RawTokenFormatter outputs only ASCII. Override here.
+        self.encoding = 'ascii'  # let pygments.format() do the right thing
+        self.compress = get_choice_opt(options, 'compress',
+                                       ['', 'none', 'gz', 'bz2'], '')
+        self.error_color = options.get('error_color', None)
+        if self.error_color is True:
+            self.error_color = 'red'
+        if self.error_color is not None:
+            try:
+                colorize(self.error_color, '')
+            except KeyError:
+                raise ValueError(f"Invalid color {self.error_color!r} specified")
+
+    def format(self, tokensource, outfile):
+        try:
+            outfile.write(b'')
+        except TypeError:
+            raise TypeError('The raw tokens formatter needs a binary '
+                            'output file')
+        if self.compress == 'gz':
+            import gzip
+            outfile = gzip.GzipFile('', 'wb', 9, outfile)
+
+            write = outfile.write
+            flush = outfile.close
+        elif self.compress == 'bz2':
+            import bz2
+            compressor = bz2.BZ2Compressor(9)
+
+            def write(text):
+                outfile.write(compressor.compress(text))
+
+            def flush():
+                outfile.write(compressor.flush())
+                outfile.flush()
+        else:
+            write = outfile.write
+            flush = outfile.flush
+
+        if self.error_color:
+            for ttype, value in tokensource:
+                line = b"%r\t%r\n" % (ttype, value)
+                if ttype is Token.Error:
+                    write(colorize(self.error_color, line))
+                else:
+                    write(line)
+        else:
+            for ttype, value in tokensource:
+                write(b"%r\t%r\n" % (ttype, value))
+        flush()
+
+
+TESTCASE_BEFORE = '''\
+    def testNeedsName(lexer):
+        fragment = %r
+        tokens = [
+'''
+TESTCASE_AFTER = '''\
+        ]
+        assert list(lexer.get_tokens(fragment)) == tokens
+'''
+
+
+class TestcaseFormatter(Formatter):
+    """
+    Format tokens as appropriate for a new testcase.
+
+    .. versionadded:: 2.0
+    """
+    name = 'Testcase'
+    aliases = ['testcase']
+
+    def __init__(self, **options):
+        Formatter.__init__(self, **options)
+        if self.encoding is not None and self.encoding != 'utf-8':
+            raise ValueError("Only None and utf-8 are allowed encodings.")
+
+    def format(self, tokensource, outfile):
+        indentation = ' ' * 12
+        rawbuf = []
+        outbuf = []
+        for ttype, value in tokensource:
+            rawbuf.append(value)
+            outbuf.append(f'{indentation}({ttype}, {value!r}),\n')
+
+        before = TESTCASE_BEFORE % (''.join(rawbuf),)
+        during = ''.join(outbuf)
+        after = TESTCASE_AFTER
+        if self.encoding is None:
+            outfile.write(before + during + after)
+        else:
+            outfile.write(before.encode('utf-8'))
+            outfile.write(during.encode('utf-8'))
+            outfile.write(after.encode('utf-8'))
+        outfile.flush()
diff --git a/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/pangomarkup.py b/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/pangomarkup.py
new file mode 100644
index 00000000..dfed53ab
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/pangomarkup.py
@@ -0,0 +1,83 @@
+"""
+    pygments.formatters.pangomarkup
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Formatter for Pango markup output.
+
+    :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
+    :license: BSD, see LICENSE for details.
+"""
+
+from pip._vendor.pygments.formatter import Formatter
+
+
+__all__ = ['PangoMarkupFormatter']
+
+
+_escape_table = {
+    ord('&'): '&amp;',
+    ord('<'): '&lt;',
+}
+
+
+def escape_special_chars(text, table=_escape_table):
+    """Escape & and < for Pango Markup."""
+    return text.translate(table)
+
+
+class PangoMarkupFormatter(Formatter):
+    """
+    Format tokens as Pango Markup code. It can then be rendered to an SVG.
+
+    .. versionadded:: 2.9
+    """
+
+    name = 'Pango Markup'
+    aliases = ['pango', 'pangomarkup']
+    filenames = []
+
+    def __init__(self, **options):
+        Formatter.__init__(self, **options)
+
+        self.styles = {}
+
+        for token, style in self.style:
+            start = ''
+            end = ''
+            if style['color']:
+                start += '<span fgcolor="#{}">'.format(style['color'])
+                end = '</span>' + end
+            if style['bold']:
+                start += '<b>'
+                end = '</b>' + end
+            if style['italic']:
+                start += '<i>'
+                end = '</i>' + end
+            if style['underline']:
+                start += '<u>'
+                end = '</u>' + end
+            self.styles[token] = (start, end)
+
+    def format_unencoded(self, tokensource, outfile):
+        lastval = ''
+        lasttype = None
+
+        outfile.write('<tt>')
+
+        for ttype, value in tokensource:
+            while ttype not in self.styles:
+                ttype = ttype.parent
+            if ttype == lasttype:
+                lastval += escape_special_chars(value)
+            else:
+                if lastval:
+                    stylebegin, styleend = self.styles[lasttype]
+                    outfile.write(stylebegin + lastval + styleend)
+                lastval = escape_special_chars(value)
+                lasttype = ttype
+
+        if lastval:
+            stylebegin, styleend = self.styles[lasttype]
+            outfile.write(stylebegin + lastval + styleend)
+
+        outfile.write('</tt>')
diff --git a/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/rtf.py b/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/rtf.py
new file mode 100644
index 00000000..eca2a41a
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/rtf.py
@@ -0,0 +1,349 @@
+"""
+    pygments.formatters.rtf
+    ~~~~~~~~~~~~~~~~~~~~~~~
+
+    A formatter that generates RTF files.
+
+    :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
+    :license: BSD, see LICENSE for details.
+"""
+
+from collections import OrderedDict
+from pip._vendor.pygments.formatter import Formatter
+from pip._vendor.pygments.style import _ansimap
+from pip._vendor.pygments.util import get_bool_opt, get_int_opt, get_list_opt, surrogatepair
+
+
+__all__ = ['RtfFormatter']
+
+
+class RtfFormatter(Formatter):
+    """
+    Format tokens as RTF markup. This formatter automatically outputs full RTF
+    documents with color information and other useful stuff. Perfect for Copy and
+    Paste into Microsoft(R) Word(R) documents.
+
+    Please note that ``encoding`` and ``outencoding`` options are ignored.
+    The RTF format is ASCII natively, but handles unicode characters correctly
+    thanks to escape sequences.
+
+    .. versionadded:: 0.6
+
+    Additional options accepted:
+
+    `style`
+        The style to use, can be a string or a Style subclass (default:
+        ``'default'``).
+
+    `fontface`
+        The used font family, for example ``Bitstream Vera Sans``. Defaults to
+        some generic font which is supposed to have fixed width.
+
+    `fontsize`
+        Size of the font used. Size is specified in half points. The
+        default is 24 half-points, giving a size 12 font.
+
+        .. versionadded:: 2.0
+
+    `linenos`
+        Turn on line numbering (default: ``False``).
+
+        .. versionadded:: 2.18
+
+    `lineno_fontsize`
+        Font size for line numbers. Size is specified in half points
+        (default: `fontsize`). 
+
+        .. versionadded:: 2.18
+
+    `lineno_padding`
+        Number of spaces between the (inline) line numbers and the
+        source code (default: ``2``).
+
+        .. versionadded:: 2.18
+
+    `linenostart`
+        The line number for the first line (default: ``1``).
+
+        .. versionadded:: 2.18
+
+    `linenostep`
+        If set to a number n > 1, only every nth line number is printed.
+
+        .. versionadded:: 2.18
+
+    `lineno_color`
+        Color for line numbers specified as a hex triplet, e.g. ``'5e5e5e'``. 
+        Defaults to the style's line number color if it is a hex triplet, 
+        otherwise ansi bright black.
+
+        .. versionadded:: 2.18
+
+    `hl_lines`
+        Specify a list of lines to be highlighted, as line numbers separated by
+        spaces, e.g. ``'3 7 8'``. The line numbers are relative to the input 
+        (i.e. the first line is line 1) unless `hl_linenostart` is set.
+
+        .. versionadded:: 2.18
+
+    `hl_color`
+        Color for highlighting the lines specified in `hl_lines`, specified as 
+        a hex triplet (default: style's `highlight_color`).
+
+        .. versionadded:: 2.18
+
+    `hl_linenostart`
+        If set to ``True`` line numbers in `hl_lines` are specified
+        relative to `linenostart` (default ``False``).
+
+        .. versionadded:: 2.18
+    """
+    name = 'RTF'
+    aliases = ['rtf']
+    filenames = ['*.rtf']
+
+    def __init__(self, **options):
+        r"""
+        Additional options accepted:
+
+        ``fontface``
+            Name of the font used. Could for example be ``'Courier New'``
+            to further specify the default which is ``'\fmodern'``. The RTF
+            specification claims that ``\fmodern`` are "Fixed-pitch serif
+            and sans serif fonts". Hope every RTF implementation thinks
+            the same about modern...
+
+        """
+        Formatter.__init__(self, **options)
+        self.fontface = options.get('fontface') or ''
+        self.fontsize = get_int_opt(options, 'fontsize', 0)
+        self.linenos = get_bool_opt(options, 'linenos', False)
+        self.lineno_fontsize = get_int_opt(options, 'lineno_fontsize',
+                                           self.fontsize)
+        self.lineno_padding = get_int_opt(options, 'lineno_padding', 2)
+        self.linenostart = abs(get_int_opt(options, 'linenostart', 1))
+        self.linenostep = abs(get_int_opt(options, 'linenostep', 1))
+        self.hl_linenostart = get_bool_opt(options, 'hl_linenostart', False)
+
+        self.hl_color = options.get('hl_color', '')
+        if not self.hl_color:
+            self.hl_color = self.style.highlight_color
+
+        self.hl_lines = []
+        for lineno in get_list_opt(options, 'hl_lines', []):
+            try:
+                lineno = int(lineno)
+                if self.hl_linenostart:
+                    lineno = lineno - self.linenostart + 1
+                self.hl_lines.append(lineno)
+            except ValueError:
+                pass
+
+        self.lineno_color = options.get('lineno_color', '')
+        if not self.lineno_color:
+            if  self.style.line_number_color == 'inherit':
+                # style color is the css value 'inherit'
+                # default to ansi bright-black
+                self.lineno_color = _ansimap['ansibrightblack']
+            else:
+                # style color is assumed to be a hex triplet as other
+                # colors in pygments/style.py
+                self.lineno_color = self.style.line_number_color
+
+        self.color_mapping = self._create_color_mapping()
+
+    def _escape(self, text):
+        return text.replace('\\', '\\\\') \
+                   .replace('{', '\\{') \
+                   .replace('}', '\\}')
+
+    def _escape_text(self, text):
+        # empty strings, should give a small performance improvement
+        if not text:
+            return ''
+
+        # escape text
+        text = self._escape(text)
+
+        buf = []
+        for c in text:
+            cn = ord(c)
+            if cn < (2**7):
+                # ASCII character
+                buf.append(str(c))
+            elif (2**7) <= cn < (2**16):
+                # single unicode escape sequence
+                buf.append('{\\u%d}' % cn)
+            elif (2**16) <= cn:
+                # RTF limits unicode to 16 bits.
+                # Force surrogate pairs
+                buf.append('{\\u%d}{\\u%d}' % surrogatepair(cn))
+
+        return ''.join(buf).replace('\n', '\\par')
+
+    @staticmethod
+    def hex_to_rtf_color(hex_color):
+        if hex_color[0] == "#":
+            hex_color = hex_color[1:]
+
+        return '\\red%d\\green%d\\blue%d;' % (
+                        int(hex_color[0:2], 16),
+                        int(hex_color[2:4], 16),
+                        int(hex_color[4:6], 16)
+                    )
+
+    def _split_tokens_on_newlines(self, tokensource):
+        """
+        Split tokens containing newline characters into multiple token
+        each representing a line of the input file. Needed for numbering
+        lines of e.g. multiline comments.
+        """
+        for ttype, value in tokensource:
+            if value == '\n':
+                yield (ttype, value)
+            elif "\n" in value:
+                lines = value.split("\n")
+                for line in lines[:-1]:
+                    yield (ttype, line+"\n")
+                if lines[-1]:
+                    yield (ttype, lines[-1])
+            else:
+                yield (ttype, value)
+
+    def _create_color_mapping(self):
+        """
+        Create a mapping of style hex colors to index/offset in
+        the RTF color table.
+        """
+        color_mapping = OrderedDict()
+        offset = 1
+
+        if self.linenos:
+            color_mapping[self.lineno_color] = offset
+            offset += 1
+
+        if self.hl_lines:
+            color_mapping[self.hl_color] = offset
+            offset += 1
+
+        for _, style in self.style:
+            for color in style['color'], style['bgcolor'], style['border']:
+                if color and color not in color_mapping:
+                    color_mapping[color] = offset
+                    offset += 1
+
+        return color_mapping
+
+    @property
+    def _lineno_template(self):
+        if self.lineno_fontsize != self.fontsize:
+            return '{{\\fs{} \\cf{} %s{}}}'.format(self.lineno_fontsize,
+                          self.color_mapping[self.lineno_color],
+                          " " * self.lineno_padding)
+
+        return '{{\\cf{} %s{}}}'.format(self.color_mapping[self.lineno_color],
+                      " " * self.lineno_padding)
+
+    @property
+    def _hl_open_str(self):
+        return rf'{{\highlight{self.color_mapping[self.hl_color]} '
+
+    @property
+    def _rtf_header(self):
+        lines = []
+        # rtf 1.8 header
+        lines.append('{\\rtf1\\ansi\\uc0\\deff0'
+                     '{\\fonttbl{\\f0\\fmodern\\fprq1\\fcharset0%s;}}'
+                     % (self.fontface and ' '
+                        + self._escape(self.fontface) or ''))
+
+        # color table
+        lines.append('{\\colortbl;')
+        for color, _ in self.color_mapping.items():
+            lines.append(self.hex_to_rtf_color(color))
+        lines.append('}')
+
+        # font and fontsize
+        lines.append('\\f0\\sa0')
+        if self.fontsize:
+            lines.append('\\fs%d' % self.fontsize)
+
+        # ensure Libre Office Writer imports and renders consecutive
+        # space characters the same width, needed for line numbering.
+        # https://bugs.documentfoundation.org/show_bug.cgi?id=144050
+        lines.append('\\dntblnsbdb')
+
+        return lines
+
+    def format_unencoded(self, tokensource, outfile):
+        for line in self._rtf_header:
+            outfile.write(line + "\n")
+
+        tokensource = self._split_tokens_on_newlines(tokensource)
+
+        # first pass of tokens to count lines, needed for line numbering
+        if self.linenos:
+            line_count = 0
+            tokens = [] # for copying the token source generator
+            for ttype, value in tokensource:
+                tokens.append((ttype, value))
+                if value.endswith("\n"):
+                    line_count += 1
+
+            # width of line number strings (for padding with spaces)
+            linenos_width = len(str(line_count+self.linenostart-1))
+
+            tokensource = tokens
+
+        # highlight stream
+        lineno = 1
+        start_new_line = True
+        for ttype, value in tokensource:
+            if start_new_line and lineno in self.hl_lines:
+                outfile.write(self._hl_open_str)
+
+            if start_new_line and self.linenos:
+                if (lineno-self.linenostart+1)%self.linenostep == 0:
+                    current_lineno = lineno + self.linenostart - 1
+                    lineno_str = str(current_lineno).rjust(linenos_width)
+                else:
+                    lineno_str = "".rjust(linenos_width)
+                outfile.write(self._lineno_template % lineno_str)
+
+            while not self.style.styles_token(ttype) and ttype.parent:
+                ttype = ttype.parent
+            style = self.style.style_for_token(ttype)
+            buf = []
+            if style['bgcolor']:
+                buf.append('\\cb%d' % self.color_mapping[style['bgcolor']])
+            if style['color']:
+                buf.append('\\cf%d' % self.color_mapping[style['color']])
+            if style['bold']:
+                buf.append('\\b')
+            if style['italic']:
+                buf.append('\\i')
+            if style['underline']:
+                buf.append('\\ul')
+            if style['border']:
+                buf.append('\\chbrdr\\chcfpat%d' %
+                           self.color_mapping[style['border']])
+            start = ''.join(buf)
+            if start:
+                outfile.write(f'{{{start} ')
+            outfile.write(self._escape_text(value))
+            if start:
+                outfile.write('}')
+            start_new_line = False
+
+            # complete line of input
+            if value.endswith("\n"):
+                # close line highlighting
+                if lineno in self.hl_lines:
+                    outfile.write('}')
+                # newline in RTF file after closing }
+                outfile.write("\n")
+
+                start_new_line = True
+                lineno += 1
+
+        outfile.write('}\n')
diff --git a/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/svg.py b/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/svg.py
new file mode 100644
index 00000000..d3e018ff
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/svg.py
@@ -0,0 +1,185 @@
+"""
+    pygments.formatters.svg
+    ~~~~~~~~~~~~~~~~~~~~~~~
+
+    Formatter for SVG output.
+
+    :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
+    :license: BSD, see LICENSE for details.
+"""
+
+from pip._vendor.pygments.formatter import Formatter
+from pip._vendor.pygments.token import Comment
+from pip._vendor.pygments.util import get_bool_opt, get_int_opt
+
+__all__ = ['SvgFormatter']
+
+
+def escape_html(text):
+    """Escape &, <, > as well as single and double quotes for HTML."""
+    return text.replace('&', '&amp;').  \
+                replace('<', '&lt;').   \
+                replace('>', '&gt;').   \
+                replace('"', '&quot;'). \
+                replace("'", '&#39;')
+
+
+class2style = {}
+
+class SvgFormatter(Formatter):
+    """
+    Format tokens as an SVG graphics file.  This formatter is still experimental.
+    Each line of code is a ``<text>`` element with explicit ``x`` and ``y``
+    coordinates containing ``<tspan>`` elements with the individual token styles.
+
+    By default, this formatter outputs a full SVG document including doctype
+    declaration and the ``<svg>`` root element.
+
+    .. versionadded:: 0.9
+
+    Additional options accepted:
+
+    `nowrap`
+        Don't wrap the SVG ``<text>`` elements in ``<svg><g>`` elements and
+        don't add a XML declaration and a doctype.  If true, the `fontfamily`
+        and `fontsize` options are ignored.  Defaults to ``False``.
+
+    `fontfamily`
+        The value to give the wrapping ``<g>`` element's ``font-family``
+        attribute, defaults to ``"monospace"``.
+
+    `fontsize`
+        The value to give the wrapping ``<g>`` element's ``font-size``
+        attribute, defaults to ``"14px"``.
+
+    `linenos`
+        If ``True``, add line numbers (default: ``False``).
+
+    `linenostart`
+        The line number for the first line (default: ``1``).
+
+    `linenostep`
+        If set to a number n > 1, only every nth line number is printed.
+
+    `linenowidth`
+        Maximum width devoted to line numbers (default: ``3*ystep``, sufficient
+        for up to 4-digit line numbers. Increase width for longer code blocks).
+
+    `xoffset`
+        Starting offset in X direction, defaults to ``0``.
+
+    `yoffset`
+        Starting offset in Y direction, defaults to the font size if it is given
+        in pixels, or ``20`` else.  (This is necessary since text coordinates
+        refer to the text baseline, not the top edge.)
+
+    `ystep`
+        Offset to add to the Y coordinate for each subsequent line.  This should
+        roughly be the text size plus 5.  It defaults to that value if the text
+        size is given in pixels, or ``25`` else.
+
+    `spacehack`
+        Convert spaces in the source to ``&#160;``, which are non-breaking
+        spaces.  SVG provides the ``xml:space`` attribute to control how
+        whitespace inside tags is handled, in theory, the ``preserve`` value
+        could be used to keep all whitespace as-is.  However, many current SVG
+        viewers don't obey that rule, so this option is provided as a workaround
+        and defaults to ``True``.
+    """
+    name = 'SVG'
+    aliases = ['svg']
+    filenames = ['*.svg']
+
+    def __init__(self, **options):
+        Formatter.__init__(self, **options)
+        self.nowrap = get_bool_opt(options, 'nowrap', False)
+        self.fontfamily = options.get('fontfamily', 'monospace')
+        self.fontsize = options.get('fontsize', '14px')
+        self.xoffset = get_int_opt(options, 'xoffset', 0)
+        fs = self.fontsize.strip()
+        if fs.endswith('px'):
+            fs = fs[:-2].strip()
+        try:
+            int_fs = int(fs)
+        except ValueError:
+            int_fs = 20
+        self.yoffset = get_int_opt(options, 'yoffset', int_fs)
+        self.ystep = get_int_opt(options, 'ystep', int_fs + 5)
+        self.spacehack = get_bool_opt(options, 'spacehack', True)
+        self.linenos = get_bool_opt(options,'linenos',False)
+        self.linenostart = get_int_opt(options,'linenostart',1)
+        self.linenostep = get_int_opt(options,'linenostep',1)
+        self.linenowidth = get_int_opt(options,'linenowidth', 3*self.ystep)
+        self._stylecache = {}
+
+    def format_unencoded(self, tokensource, outfile):
+        """
+        Format ``tokensource``, an iterable of ``(tokentype, tokenstring)``
+        tuples and write it into ``outfile``.
+
+        For our implementation we put all lines in their own 'line group'.
+        """
+        x = self.xoffset
+        y = self.yoffset
+        if not self.nowrap:
+            if self.encoding:
+                outfile.write(f'<?xml version="1.0" encoding="{self.encoding}"?>\n')
+            else:
+                outfile.write('<?xml version="1.0"?>\n')
+            outfile.write('<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" '
+                          '"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/'
+                          'svg10.dtd">\n')
+            outfile.write('<svg xmlns="http://www.w3.org/2000/svg">\n')
+            outfile.write(f'<g font-family="{self.fontfamily}" font-size="{self.fontsize}">\n')
+
+        counter = self.linenostart
+        counter_step = self.linenostep
+        counter_style = self._get_style(Comment)
+        line_x = x
+
+        if self.linenos:
+            if counter % counter_step == 0:
+                outfile.write(f'<text x="{x+self.linenowidth}" y="{y}" {counter_style} text-anchor="end">{counter}</text>')
+            line_x += self.linenowidth + self.ystep
+            counter += 1
+
+        outfile.write(f'<text x="{line_x}" y="{y}" xml:space="preserve">')
+        for ttype, value in tokensource:
+            style = self._get_style(ttype)
+            tspan = style and '<tspan' + style + '>' or ''
+            tspanend = tspan and '</tspan>' or ''
+            value = escape_html(value)
+            if self.spacehack:
+                value = value.expandtabs().replace(' ', '&#160;')
+            parts = value.split('\n')
+            for part in parts[:-1]:
+                outfile.write(tspan + part + tspanend)
+                y += self.ystep
+                outfile.write('</text>\n')
+                if self.linenos and counter % counter_step == 0:
+                    outfile.write(f'<text x="{x+self.linenowidth}" y="{y}" text-anchor="end" {counter_style}>{counter}</text>')
+
+                counter += 1
+                outfile.write(f'<text x="{line_x}" y="{y}" ' 'xml:space="preserve">')
+            outfile.write(tspan + parts[-1] + tspanend)
+        outfile.write('</text>')
+
+        if not self.nowrap:
+            outfile.write('</g></svg>\n')
+
+    def _get_style(self, tokentype):
+        if tokentype in self._stylecache:
+            return self._stylecache[tokentype]
+        otokentype = tokentype
+        while not self.style.styles_token(tokentype):
+            tokentype = tokentype.parent
+        value = self.style.style_for_token(tokentype)
+        result = ''
+        if value['color']:
+            result = ' fill="#' + value['color'] + '"'
+        if value['bold']:
+            result += ' font-weight="bold"'
+        if value['italic']:
+            result += ' font-style="italic"'
+        self._stylecache[otokentype] = result
+        return result
diff --git a/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/terminal.py b/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/terminal.py
new file mode 100644
index 00000000..51b902d3
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/terminal.py
@@ -0,0 +1,127 @@
+"""
+    pygments.formatters.terminal
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Formatter for terminal output with ANSI sequences.
+
+    :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
+    :license: BSD, see LICENSE for details.
+"""
+
+from pip._vendor.pygments.formatter import Formatter
+from pip._vendor.pygments.token import Keyword, Name, Comment, String, Error, \
+    Number, Operator, Generic, Token, Whitespace
+from pip._vendor.pygments.console import ansiformat
+from pip._vendor.pygments.util import get_choice_opt
+
+
+__all__ = ['TerminalFormatter']
+
+
+#: Map token types to a tuple of color values for light and dark
+#: backgrounds.
+TERMINAL_COLORS = {
+    Token:              ('',            ''),
+
+    Whitespace:         ('gray',   'brightblack'),
+    Comment:            ('gray',   'brightblack'),
+    Comment.Preproc:    ('cyan',        'brightcyan'),
+    Keyword:            ('blue',    'brightblue'),
+    Keyword.Type:       ('cyan',        'brightcyan'),
+    Operator.Word:      ('magenta',      'brightmagenta'),
+    Name.Builtin:       ('cyan',        'brightcyan'),
+    Name.Function:      ('green',   'brightgreen'),
+    Name.Namespace:     ('_cyan_',      '_brightcyan_'),
+    Name.Class:         ('_green_', '_brightgreen_'),
+    Name.Exception:     ('cyan',        'brightcyan'),
+    Name.Decorator:     ('brightblack',    'gray'),
+    Name.Variable:      ('red',     'brightred'),
+    Name.Constant:      ('red',     'brightred'),
+    Name.Attribute:     ('cyan',        'brightcyan'),
+    Name.Tag:           ('brightblue',        'brightblue'),
+    String:             ('yellow',       'yellow'),
+    Number:             ('blue',    'brightblue'),
+
+    Generic.Deleted:    ('brightred',        'brightred'),
+    Generic.Inserted:   ('green',  'brightgreen'),
+    Generic.Heading:    ('**',         '**'),
+    Generic.Subheading: ('*magenta*',   '*brightmagenta*'),
+    Generic.Prompt:     ('**',         '**'),
+    Generic.Error:      ('brightred',        'brightred'),
+
+    Error:              ('_brightred_',      '_brightred_'),
+}
+
+
+class TerminalFormatter(Formatter):
+    r"""
+    Format tokens with ANSI color sequences, for output in a text console.
+    Color sequences are terminated at newlines, so that paging the output
+    works correctly.
+
+    The `get_style_defs()` method doesn't do anything special since there is
+    no support for common styles.
+
+    Options accepted:
+
+    `bg`
+        Set to ``"light"`` or ``"dark"`` depending on the terminal's background
+        (default: ``"light"``).
+
+    `colorscheme`
+        A dictionary mapping token types to (lightbg, darkbg) color names or
+        ``None`` (default: ``None`` = use builtin colorscheme).
+
+    `linenos`
+        Set to ``True`` to have line numbers on the terminal output as well
+        (default: ``False`` = no line numbers).
+    """
+    name = 'Terminal'
+    aliases = ['terminal', 'console']
+    filenames = []
+
+    def __init__(self, **options):
+        Formatter.__init__(self, **options)
+        self.darkbg = get_choice_opt(options, 'bg',
+                                     ['light', 'dark'], 'light') == 'dark'
+        self.colorscheme = options.get('colorscheme', None) or TERMINAL_COLORS
+        self.linenos = options.get('linenos', False)
+        self._lineno = 0
+
+    def format(self, tokensource, outfile):
+        return Formatter.format(self, tokensource, outfile)
+
+    def _write_lineno(self, outfile):
+        self._lineno += 1
+        outfile.write("%s%04d: " % (self._lineno != 1 and '\n' or '', self._lineno))
+
+    def _get_color(self, ttype):
+        # self.colorscheme is a dict containing usually generic types, so we
+        # have to walk the tree of dots.  The base Token type must be a key,
+        # even if it's empty string, as in the default above.
+        colors = self.colorscheme.get(ttype)
+        while colors is None:
+            ttype = ttype.parent
+            colors = self.colorscheme.get(ttype)
+        return colors[self.darkbg]
+
+    def format_unencoded(self, tokensource, outfile):
+        if self.linenos:
+            self._write_lineno(outfile)
+
+        for ttype, value in tokensource:
+            color = self._get_color(ttype)
+
+            for line in value.splitlines(True):
+                if color:
+                    outfile.write(ansiformat(color, line.rstrip('\n')))
+                else:
+                    outfile.write(line.rstrip('\n'))
+                if line.endswith('\n'):
+                    if self.linenos:
+                        self._write_lineno(outfile)
+                    else:
+                        outfile.write('\n')
+
+        if self.linenos:
+            outfile.write("\n")
diff --git a/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/terminal256.py b/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/terminal256.py
new file mode 100644
index 00000000..5f254051
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/terminal256.py
@@ -0,0 +1,338 @@
+"""
+    pygments.formatters.terminal256
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Formatter for 256-color terminal output with ANSI sequences.
+
+    RGB-to-XTERM color conversion routines adapted from xterm256-conv
+    tool (http://frexx.de/xterm-256-notes/data/xterm256-conv2.tar.bz2)
+    by Wolfgang Frisch.
+
+    Formatter version 1.
+
+    :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
+    :license: BSD, see LICENSE for details.
+"""
+
+# TODO:
+#  - Options to map style's bold/underline/italic/border attributes
+#    to some ANSI attrbutes (something like 'italic=underline')
+#  - An option to output "style RGB to xterm RGB/index" conversion table
+#  - An option to indicate that we are running in "reverse background"
+#    xterm. This means that default colors are white-on-black, not
+#    black-on-while, so colors like "white background" need to be converted
+#    to "white background, black foreground", etc...
+
+from pip._vendor.pygments.formatter import Formatter
+from pip._vendor.pygments.console import codes
+from pip._vendor.pygments.style import ansicolors
+
+
+__all__ = ['Terminal256Formatter', 'TerminalTrueColorFormatter']
+
+
+class EscapeSequence:
+    def __init__(self, fg=None, bg=None, bold=False, underline=False, italic=False):
+        self.fg = fg
+        self.bg = bg
+        self.bold = bold
+        self.underline = underline
+        self.italic = italic
+
+    def escape(self, attrs):
+        if len(attrs):
+            return "\x1b[" + ";".join(attrs) + "m"
+        return ""
+
+    def color_string(self):
+        attrs = []
+        if self.fg is not None:
+            if self.fg in ansicolors:
+                esc = codes[self.fg.replace('ansi','')]
+                if ';01m' in esc:
+                    self.bold = True
+                # extract fg color code.
+                attrs.append(esc[2:4])
+            else:
+                attrs.extend(("38", "5", "%i" % self.fg))
+        if self.bg is not None:
+            if self.bg in ansicolors:
+                esc = codes[self.bg.replace('ansi','')]
+                # extract fg color code, add 10 for bg.
+                attrs.append(str(int(esc[2:4])+10))
+            else:
+                attrs.extend(("48", "5", "%i" % self.bg))
+        if self.bold:
+            attrs.append("01")
+        if self.underline:
+            attrs.append("04")
+        if self.italic:
+            attrs.append("03")
+        return self.escape(attrs)
+
+    def true_color_string(self):
+        attrs = []
+        if self.fg:
+            attrs.extend(("38", "2", str(self.fg[0]), str(self.fg[1]), str(self.fg[2])))
+        if self.bg:
+            attrs.extend(("48", "2", str(self.bg[0]), str(self.bg[1]), str(self.bg[2])))
+        if self.bold:
+            attrs.append("01")
+        if self.underline:
+            attrs.append("04")
+        if self.italic:
+            attrs.append("03")
+        return self.escape(attrs)
+
+    def reset_string(self):
+        attrs = []
+        if self.fg is not None:
+            attrs.append("39")
+        if self.bg is not None:
+            attrs.append("49")
+        if self.bold or self.underline or self.italic:
+            attrs.append("00")
+        return self.escape(attrs)
+
+
+class Terminal256Formatter(Formatter):
+    """
+    Format tokens with ANSI color sequences, for output in a 256-color
+    terminal or console.  Like in `TerminalFormatter` color sequences
+    are terminated at newlines, so that paging the output works correctly.
+
+    The formatter takes colors from a style defined by the `style` option
+    and converts them to nearest ANSI 256-color escape sequences. Bold and
+    underline attributes from the style are preserved (and displayed).
+
+    .. versionadded:: 0.9
+
+    .. versionchanged:: 2.2
+       If the used style defines foreground colors in the form ``#ansi*``, then
+       `Terminal256Formatter` will map these to non extended foreground color.
+       See :ref:`AnsiTerminalStyle` for more information.
+
+    .. versionchanged:: 2.4
+       The ANSI color names have been updated with names that are easier to
+       understand and align with colornames of other projects and terminals.
+       See :ref:`this table <new-ansi-color-names>` for more information.
+
+
+    Options accepted:
+
+    `style`
+        The style to use, can be a string or a Style subclass (default:
+        ``'default'``).
+
+    `linenos`
+        Set to ``True`` to have line numbers on the terminal output as well
+        (default: ``False`` = no line numbers).
+    """
+    name = 'Terminal256'
+    aliases = ['terminal256', 'console256', '256']
+    filenames = []
+
+    def __init__(self, **options):
+        Formatter.__init__(self, **options)
+
+        self.xterm_colors = []
+        self.best_match = {}
+        self.style_string = {}
+
+        self.usebold = 'nobold' not in options
+        self.useunderline = 'nounderline' not in options
+        self.useitalic = 'noitalic' not in options
+
+        self._build_color_table()  # build an RGB-to-256 color conversion table
+        self._setup_styles()  # convert selected style's colors to term. colors
+
+        self.linenos = options.get('linenos', False)
+        self._lineno = 0
+
+    def _build_color_table(self):
+        # colors 0..15: 16 basic colors
+
+        self.xterm_colors.append((0x00, 0x00, 0x00))  # 0
+        self.xterm_colors.append((0xcd, 0x00, 0x00))  # 1
+        self.xterm_colors.append((0x00, 0xcd, 0x00))  # 2
+        self.xterm_colors.append((0xcd, 0xcd, 0x00))  # 3
+        self.xterm_colors.append((0x00, 0x00, 0xee))  # 4
+        self.xterm_colors.append((0xcd, 0x00, 0xcd))  # 5
+        self.xterm_colors.append((0x00, 0xcd, 0xcd))  # 6
+        self.xterm_colors.append((0xe5, 0xe5, 0xe5))  # 7
+        self.xterm_colors.append((0x7f, 0x7f, 0x7f))  # 8
+        self.xterm_colors.append((0xff, 0x00, 0x00))  # 9
+        self.xterm_colors.append((0x00, 0xff, 0x00))  # 10
+        self.xterm_colors.append((0xff, 0xff, 0x00))  # 11
+        self.xterm_colors.append((0x5c, 0x5c, 0xff))  # 12
+        self.xterm_colors.append((0xff, 0x00, 0xff))  # 13
+        self.xterm_colors.append((0x00, 0xff, 0xff))  # 14
+        self.xterm_colors.append((0xff, 0xff, 0xff))  # 15
+
+        # colors 16..232: the 6x6x6 color cube
+
+        valuerange = (0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff)
+
+        for i in range(217):
+            r = valuerange[(i // 36) % 6]
+            g = valuerange[(i // 6) % 6]
+            b = valuerange[i % 6]
+            self.xterm_colors.append((r, g, b))
+
+        # colors 233..253: grayscale
+
+        for i in range(1, 22):
+            v = 8 + i * 10
+            self.xterm_colors.append((v, v, v))
+
+    def _closest_color(self, r, g, b):
+        distance = 257*257*3  # "infinity" (>distance from #000000 to #ffffff)
+        match = 0
+
+        for i in range(0, 254):
+            values = self.xterm_colors[i]
+
+            rd = r - values[0]
+            gd = g - values[1]
+            bd = b - values[2]
+            d = rd*rd + gd*gd + bd*bd
+
+            if d < distance:
+                match = i
+                distance = d
+        return match
+
+    def _color_index(self, color):
+        index = self.best_match.get(color, None)
+        if color in ansicolors:
+            # strip the `ansi/#ansi` part and look up code
+            index = color
+            self.best_match[color] = index
+        if index is None:
+            try:
+                rgb = int(str(color), 16)
+            except ValueError:
+                rgb = 0
+
+            r = (rgb >> 16) & 0xff
+            g = (rgb >> 8) & 0xff
+            b = rgb & 0xff
+            index = self._closest_color(r, g, b)
+            self.best_match[color] = index
+        return index
+
+    def _setup_styles(self):
+        for ttype, ndef in self.style:
+            escape = EscapeSequence()
+            # get foreground from ansicolor if set
+            if ndef['ansicolor']:
+                escape.fg = self._color_index(ndef['ansicolor'])
+            elif ndef['color']:
+                escape.fg = self._color_index(ndef['color'])
+            if ndef['bgansicolor']:
+                escape.bg = self._color_index(ndef['bgansicolor'])
+            elif ndef['bgcolor']:
+                escape.bg = self._color_index(ndef['bgcolor'])
+            if self.usebold and ndef['bold']:
+                escape.bold = True
+            if self.useunderline and ndef['underline']:
+                escape.underline = True
+            if self.useitalic and ndef['italic']:
+                escape.italic = True
+            self.style_string[str(ttype)] = (escape.color_string(),
+                                             escape.reset_string())
+
+    def _write_lineno(self, outfile):
+        self._lineno += 1
+        outfile.write("%s%04d: " % (self._lineno != 1 and '\n' or '', self._lineno))
+
+    def format(self, tokensource, outfile):
+        return Formatter.format(self, tokensource, outfile)
+
+    def format_unencoded(self, tokensource, outfile):
+        if self.linenos:
+            self._write_lineno(outfile)
+
+        for ttype, value in tokensource:
+            not_found = True
+            while ttype and not_found:
+                try:
+                    # outfile.write( "<" + str(ttype) + ">" )
+                    on, off = self.style_string[str(ttype)]
+
+                    # Like TerminalFormatter, add "reset colors" escape sequence
+                    # on newline.
+                    spl = value.split('\n')
+                    for line in spl[:-1]:
+                        if line:
+                            outfile.write(on + line + off)
+                        if self.linenos:
+                            self._write_lineno(outfile)
+                        else:
+                            outfile.write('\n')
+
+                    if spl[-1]:
+                        outfile.write(on + spl[-1] + off)
+
+                    not_found = False
+                    # outfile.write( '#' + str(ttype) + '#' )
+
+                except KeyError:
+                    # ottype = ttype
+                    ttype = ttype.parent
+                    # outfile.write( '!' + str(ottype) + '->' + str(ttype) + '!' )
+
+            if not_found:
+                outfile.write(value)
+
+        if self.linenos:
+            outfile.write("\n")
+
+
+
+class TerminalTrueColorFormatter(Terminal256Formatter):
+    r"""
+    Format tokens with ANSI color sequences, for output in a true-color
+    terminal or console.  Like in `TerminalFormatter` color sequences
+    are terminated at newlines, so that paging the output works correctly.
+
+    .. versionadded:: 2.1
+
+    Options accepted:
+
+    `style`
+        The style to use, can be a string or a Style subclass (default:
+        ``'default'``).
+    """
+    name = 'TerminalTrueColor'
+    aliases = ['terminal16m', 'console16m', '16m']
+    filenames = []
+
+    def _build_color_table(self):
+        pass
+
+    def _color_tuple(self, color):
+        try:
+            rgb = int(str(color), 16)
+        except ValueError:
+            return None
+        r = (rgb >> 16) & 0xff
+        g = (rgb >> 8) & 0xff
+        b = rgb & 0xff
+        return (r, g, b)
+
+    def _setup_styles(self):
+        for ttype, ndef in self.style:
+            escape = EscapeSequence()
+            if ndef['color']:
+                escape.fg = self._color_tuple(ndef['color'])
+            if ndef['bgcolor']:
+                escape.bg = self._color_tuple(ndef['bgcolor'])
+            if self.usebold and ndef['bold']:
+                escape.bold = True
+            if self.useunderline and ndef['underline']:
+                escape.underline = True
+            if self.useitalic and ndef['italic']:
+                escape.italic = True
+            self.style_string[str(ttype)] = (escape.true_color_string(),
+                                             escape.reset_string())