123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967 |
- """
- pygments.formatters.html
- ~~~~~~~~~~~~~~~~~~~~~~~~
- Formatter for HTML output.
- :copyright: Copyright 2006-2021 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 pygments.formatter import Formatter
- from pygments.token import Token, Text, STANDARD_TYPES
- from 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):
- """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-2021 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-2021 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 within a ``<pre>`` tag, wrapped
- in a ``<div>`` tag. 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).
- Wrapping can be disabled using the `nowrap` option.
- 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 wrap the tokens at all, not even inside a ``<pre>``
- tag. 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).
- .. 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
- **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, outfile):
- 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 = {}
- 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: %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
- # 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 = [
- '%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):
- # 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") 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
- la = self.lineanchors
- 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>' % (la, 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)
- # 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"><div class="highlight">'
- '<span class="filename">' + self.filename + '</span></div>'
- '</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, (
- '<table class="%stable">' % self.cssclass + filename_tr +
- '<tr><td class="linenos"><div class="linenodiv"><pre>' +
- ls + '</pre></div></td><td class="code">'
- )
- yield 0, dummyoutfile.getvalue()
- 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))
- la = self.lineanchors
- 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>' % (la, 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
- yield 1, '<a id="%s-%d" name="%s-%d"></a>' % (s, i, s, i) + 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>')
- # the empty span here is to keep leading empty lines from being
- # ignored by HTML parsers
- 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):
- """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:
- if nocls:
- css_style = self._get_css_inline_styles(ttype)
- cspan = css_style and '<span style="%s">' % self.class2style[css_style][0] or ''
- else:
- css_class = self._get_css_classes(ttype)
- cspan = css_class and '<span class="%s">' % css_class or ''
- 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:
- if lspan != cspan:
- line.extend(((lspan and '</span>'), cspan, part,
- (cspan and '</span>'), lsep))
- else: # both are the same
- 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'], 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 = (' 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, outfile):
- """
- Wrap the ``source``, which is a generator yielding
- individual lines, in custom generators. See docstring
- for `format`. Can be overridden.
- """
- if self.wrapcode:
- return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
- else:
- return self._wrap_div(self._wrap_pre(source))
- 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, outfile)
- if self.linenos == 1:
- source = self._wrap_tablelinenos(source)
- if self.full:
- source = self._wrap_full(source, outfile)
- for t, piece in source:
- outfile.write(piece)
|