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('&'): '&',
    ord('<'): '<',
    ord('>'): '>',
    ord('"'): '"',
    ord("'"): ''',
}
def escape_html(text, table=_escape_html_table):
    
    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-2023 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-2023 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):
    
    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:
                        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):
        
        ttypeclass = _get_ttype_class(ttype)
        if ttypeclass:
            return self.classprefix + ttypeclass
        return ''
    def _get_css_classes(self, ttype):
        
        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):
        
        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: %s; ' % 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: %s; ' % webify(ndef['bgcolor'])
            if ndef['border']:
                style += 'border: 1px solid %s; ' % webify(ndef['border'])
            if style:
                t2c[ttype] = name
                                                c2s[name] = (style[:-2], ttype, len(ttype))
    def get_style_defs(self, arg=None):
        
        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 = [
            '%s { %s } /* %s */' % (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, '%s{ background: %s;%s }' % (
                    prefix(''), bg_color, text_style
                )
            )
        if hl_color is not None:
            lines.insert(
                0, '%s { background-color: %s }' % (prefix('hll'), hl_color)
            )
        return lines
    def get_linenos_style_defs(self):
        lines = [
            'pre { %s }' % self._pre_style,
            'td.linenos .normal { %s }' % self._linenos_style,
            'span.linenos { %s }' % self._linenos_style,
            'td.linenos .special { %s }' % self._linenos_special_style,
            'span.linenos.special { %s }' % 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):
        return 'color: %s; background-color: %s; padding-left: 5px; padding-right: 5px;' % (
            self.style.line_number_color,
            self.style.line_number_background_color
        )
    @property
    def _linenos_special_style(self):
        return 'color: %s; background-color: %s; padding-left: 5px; padding-right: 5px;' % (
            self.style.line_number_special_color,
            self.style.line_number_special_background_color
        )
    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):
                                cssfilename = self.cssfile
            else:
                try:
                    filename = outfile.name
                    if not filename or filename[0] == '<':
                                                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
                        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 = ' style="%s"' % self._linenos_special_style
                else:
                    style = ' style="%s"' % self._linenos_style
            else:
                if special_line:
                    style = ' class="special"'
                else:
                    style = ' class="normal"'
            if style:
                line = '<span%s>%s</span>' % (style, line)
            lines.append(line)
        ls = '\n'.join(lines)
                        filename_tr = ""
        if self.filename:
            filename_tr = (
                '<tr><th colspan="2" class="filename">'
                '<span class="filename">' + self.filename + '</span>'
                '</th></tr>')
                                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):
                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 = ' style="%s"' % self._linenos_special_style
                else:
                    style = ' style="%s"' % self._linenos_style
            else:
                if special_line:
                    style = ' class="linenos special"'
                else:
                    style = ' class="linenos"'
            if style:
                linenos = '<span%s>%s</span>' % (style, line)
            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
                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('background: %s' % (self.style.background_color,))
        if self.cssstyles:
            style.append(self.cssstyles)
        style = '; '.join(style)
        yield 0, ('<div' + (self.cssclass and ' class="%s"' % self.cssclass) +
                  (style and (' style="%s"' % 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>')
                        yield 0, ('<pre' + (style and ' style="%s"' % 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):
        
        return value.translate(_escape_html_table).split('\n')
    def _format_lines(self, tokensource):
        
        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="%s"' % '.'.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 = '<span style="%s"%s>' % (css_style, title)
                    else:
                        cspan = ''
                else:
                    css_class = self._get_css_classes(ttype)
                    if css_class:
                        cspan = '<span class="%s"%s>' % (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 part in parts[:-1]:
                if line:
                                                            if lspan != cspan and part:
                        line.extend(((lspan and '</span>'), cspan, part,
                                     (cspan and '</span>'), lsep))
                    else:                          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
                        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
            
        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'], entry['lineNumber']
        else:
            return None, None
    def _highlight_lines(self, tokensource):
        
        hls = self.hl_lines
        for i, (t, value) in enumerate(tokensource):
            if t != 1:
                yield t, value
            if i + 1 in hls:                  if self.noclasses:
                    style = ''
                    if self.style.highlight_color is not None:
                        style = (' style="background-color: %s"' %
                                 (self.style.highlight_color,))
                    yield 1, '<span%s>%s</span>' % (style, value)
                else:
                    yield 1, '<span class="hll">%s</span>' % value
            else:
                yield 1, value
    def wrap(self, source):
        
        output = source
        if self.wrapcode:
            output = self._wrap_code(output)
        output = self._wrap_pre(output)
        return output
    def format_unencoded(self, tokensource, outfile):
        
        source = self._format_lines(tokensource)
                        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)