mirror of https://github.com/pypa/pip
Merge pull request #10462 from pradyunsg/rich
This commit is contained in:
commit
a800ae2a7d
|
@ -0,0 +1 @@
|
|||
Tree-trim unused portions of vendored pygments, to reduce the distribution size.
|
|
@ -0,0 +1 @@
|
|||
Add pygments 2.10.0 as a vendored dependency.
|
|
@ -0,0 +1 @@
|
|||
Add rich 10.14.0 as a vendored dependency.
|
|
@ -0,0 +1 @@
|
|||
Add typing_extensions 3.10.0.2 as a vendored dependency.
|
|
@ -39,6 +39,7 @@ substitute = [
|
|||
# pkg_resource's vendored packages are directly vendored in pip.
|
||||
{ match='pkg_resources\.extern', replace="pip._vendor" },
|
||||
{ match='from \.extern', replace="from pip._vendor" },
|
||||
{ match='''\('pygments\.lexers\.''', replace="('pip._vendor.pygments.lexer." },
|
||||
]
|
||||
drop = [
|
||||
# contains unnecessary scripts
|
||||
|
@ -50,6 +51,11 @@ drop = [
|
|||
"setuptools",
|
||||
"pkg_resources/_vendor/",
|
||||
"pkg_resources/extern/",
|
||||
# trim vendored pygments styles and lexers
|
||||
"pygments/styles/[!_]*.py",
|
||||
'^pygments/lexers/(?!python|__init__|_mapping).*\.py$',
|
||||
# trim rich's markdown support
|
||||
"rich/markdown.py",
|
||||
]
|
||||
|
||||
[tool.vendoring.typing-stubs]
|
||||
|
|
|
@ -1,10 +1,23 @@
|
|||
import functools
|
||||
import itertools
|
||||
import sys
|
||||
from signal import SIGINT, default_int_handler, signal
|
||||
from typing import Any
|
||||
from typing import Any, Callable, Iterator, Optional, Tuple
|
||||
|
||||
from pip._vendor.progress.bar import Bar, FillingCirclesBar, IncrementalBar
|
||||
from pip._vendor.progress.spinner import Spinner
|
||||
from pip._vendor.rich.progress import (
|
||||
BarColumn,
|
||||
DownloadColumn,
|
||||
FileSizeColumn,
|
||||
Progress,
|
||||
ProgressColumn,
|
||||
SpinnerColumn,
|
||||
TextColumn,
|
||||
TimeElapsedColumn,
|
||||
TimeRemainingColumn,
|
||||
TransferSpeedColumn,
|
||||
)
|
||||
|
||||
from pip._internal.utils.compat import WINDOWS
|
||||
from pip._internal.utils.logging import get_indentation
|
||||
|
@ -17,6 +30,8 @@ try:
|
|||
except Exception:
|
||||
colorama = None
|
||||
|
||||
DownloadProgressRenderer = Callable[[Iterator[bytes]], Iterator[bytes]]
|
||||
|
||||
|
||||
def _select_progress_class(preferred: Bar, fallback: Bar) -> Bar:
|
||||
encoding = getattr(preferred.file, "encoding", None)
|
||||
|
@ -243,8 +258,64 @@ BAR_TYPES = {
|
|||
}
|
||||
|
||||
|
||||
def DownloadProgressProvider(progress_bar, max=None): # type: ignore
|
||||
def _legacy_progress_bar(
|
||||
progress_bar: str, max: Optional[int]
|
||||
) -> DownloadProgressRenderer:
|
||||
if max is None or max == 0:
|
||||
return BAR_TYPES[progress_bar][1]().iter
|
||||
return BAR_TYPES[progress_bar][1]().iter # type: ignore
|
||||
else:
|
||||
return BAR_TYPES[progress_bar][0](max=max).iter
|
||||
|
||||
|
||||
#
|
||||
# Modern replacement, for our legacy progress bars.
|
||||
#
|
||||
def _rich_progress_bar(
|
||||
iterable: Iterator[bytes],
|
||||
*,
|
||||
bar_type: str,
|
||||
size: int,
|
||||
) -> Iterator[bytes]:
|
||||
assert bar_type == "on", "This should only be used in the default mode."
|
||||
|
||||
if not size:
|
||||
total = float("inf")
|
||||
columns: Tuple[ProgressColumn, ...] = (
|
||||
TextColumn("[progress.description]{task.description}"),
|
||||
SpinnerColumn("line", speed=1.5),
|
||||
FileSizeColumn(),
|
||||
TransferSpeedColumn(),
|
||||
TimeElapsedColumn(),
|
||||
)
|
||||
else:
|
||||
total = size
|
||||
columns = (
|
||||
TextColumn("[progress.description]{task.description}"),
|
||||
BarColumn(),
|
||||
DownloadColumn(),
|
||||
TransferSpeedColumn(),
|
||||
TextColumn("eta"),
|
||||
TimeRemainingColumn(),
|
||||
)
|
||||
|
||||
progress = Progress(*columns, refresh_per_second=30)
|
||||
task_id = progress.add_task(" " * (get_indentation() + 2), total=total)
|
||||
with progress:
|
||||
for chunk in iterable:
|
||||
yield chunk
|
||||
progress.update(task_id, advance=len(chunk))
|
||||
|
||||
|
||||
def get_download_progress_renderer(
|
||||
*, bar_type: str, size: Optional[int] = None
|
||||
) -> DownloadProgressRenderer:
|
||||
"""Get an object that can be used to render the download progress.
|
||||
|
||||
Returns a callable, that takes an iterable to "wrap".
|
||||
"""
|
||||
if bar_type == "on":
|
||||
return functools.partial(_rich_progress_bar, bar_type=bar_type, size=size)
|
||||
elif bar_type == "off":
|
||||
return iter # no-op, when passed an iterator
|
||||
else:
|
||||
return _legacy_progress_bar(bar_type, size)
|
||||
|
|
|
@ -276,6 +276,13 @@ class RequirementCommand(IndexGroupCommand):
|
|||
gone_in="22.1",
|
||||
)
|
||||
|
||||
if options.progress_bar not in {"on", "off"}:
|
||||
deprecated(
|
||||
reason="Custom progress bar styles are deprecated",
|
||||
replacement="to use the default progress bar style.",
|
||||
gone_in="22.1",
|
||||
)
|
||||
|
||||
return RequirementPreparer(
|
||||
build_dir=temp_build_dir_path,
|
||||
src_dir=options.src_dir,
|
||||
|
|
|
@ -8,7 +8,7 @@ from typing import Iterable, Optional, Tuple
|
|||
|
||||
from pip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response
|
||||
|
||||
from pip._internal.cli.progress_bars import DownloadProgressProvider
|
||||
from pip._internal.cli.progress_bars import get_download_progress_renderer
|
||||
from pip._internal.exceptions import NetworkConnectionError
|
||||
from pip._internal.models.index import PyPI
|
||||
from pip._internal.models.link import Link
|
||||
|
@ -65,7 +65,8 @@ def _prepare_download(
|
|||
if not show_progress:
|
||||
return chunks
|
||||
|
||||
return DownloadProgressProvider(progress_bar, max=total_length)(chunks)
|
||||
renderer = get_download_progress_renderer(bar_type=progress_bar, size=total_length)
|
||||
return renderer(chunks)
|
||||
|
||||
|
||||
def sanitize_content_filename(filename: str) -> str:
|
||||
|
|
|
@ -9,7 +9,7 @@ from pathlib import Path
|
|||
from typing import TYPE_CHECKING, Optional, Type, Union
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Literal # pragma: no cover
|
||||
from pip._vendor.typing_extensions import Literal # pragma: no cover
|
||||
|
||||
from .api import PlatformDirsABC
|
||||
from .version import __version__, __version_info__
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
from pygments import *
|
|
@ -0,0 +1,25 @@
|
|||
Copyright (c) 2006-2021 by the respective authors (see AUTHORS file).
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,84 @@
|
|||
"""
|
||||
Pygments
|
||||
~~~~~~~~
|
||||
|
||||
Pygments is a syntax highlighting package written in Python.
|
||||
|
||||
It is a generic syntax highlighter for general use in all kinds of software
|
||||
such as forum systems, wikis or other applications that need to prettify
|
||||
source code. Highlights are:
|
||||
|
||||
* a wide range of common languages and markup formats is supported
|
||||
* special attention is paid to details, increasing quality by a fair amount
|
||||
* support for new languages and formats are added easily
|
||||
* a number of output formats, presently HTML, LaTeX, RTF, SVG, all image
|
||||
formats that PIL supports, and ANSI sequences
|
||||
* it is usable as a command-line tool and as a library
|
||||
* ... and it highlights even Brainfuck!
|
||||
|
||||
The `Pygments master branch`_ is installable with ``easy_install Pygments==dev``.
|
||||
|
||||
.. _Pygments master branch:
|
||||
https://github.com/pygments/pygments/archive/master.zip#egg=Pygments-dev
|
||||
|
||||
:copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
import sys
|
||||
from io import StringIO, BytesIO
|
||||
|
||||
__version__ = '2.10.0'
|
||||
__docformat__ = 'restructuredtext'
|
||||
|
||||
__all__ = ['lex', 'format', 'highlight']
|
||||
|
||||
|
||||
def lex(code, lexer):
|
||||
"""
|
||||
Lex ``code`` with ``lexer`` and return an iterable of tokens.
|
||||
"""
|
||||
try:
|
||||
return lexer.get_tokens(code)
|
||||
except TypeError as err:
|
||||
if (isinstance(err.args[0], str) and
|
||||
('unbound method get_tokens' in err.args[0] or
|
||||
'missing 1 required positional argument' in err.args[0])):
|
||||
raise TypeError('lex() argument must be a lexer instance, '
|
||||
'not a class')
|
||||
raise
|
||||
|
||||
|
||||
def format(tokens, formatter, outfile=None): # pylint: disable=redefined-builtin
|
||||
"""
|
||||
Format a tokenlist ``tokens`` with the formatter ``formatter``.
|
||||
|
||||
If ``outfile`` is given and a valid file object (an object
|
||||
with a ``write`` method), the result will be written to it, otherwise
|
||||
it is returned as a string.
|
||||
"""
|
||||
try:
|
||||
if not outfile:
|
||||
realoutfile = getattr(formatter, 'encoding', None) and BytesIO() or StringIO()
|
||||
formatter.format(tokens, realoutfile)
|
||||
return realoutfile.getvalue()
|
||||
else:
|
||||
formatter.format(tokens, outfile)
|
||||
except TypeError as err:
|
||||
if (isinstance(err.args[0], str) and
|
||||
('unbound method format' in err.args[0] or
|
||||
'missing 1 required positional argument' in err.args[0])):
|
||||
raise TypeError('format() argument must be a formatter instance, '
|
||||
'not a class')
|
||||
raise
|
||||
|
||||
|
||||
def highlight(code, lexer, formatter, outfile=None):
|
||||
"""
|
||||
Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
|
||||
|
||||
If ``outfile`` is given and a valid file object (an object
|
||||
with a ``write`` method), the result will be written to it, otherwise
|
||||
it is returned as a string.
|
||||
"""
|
||||
return format(lex(code, lexer), formatter, outfile)
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
"""
|
||||
pygments.__main__
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Main entry point for ``python -m pygments``.
|
||||
|
||||
:copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pip._vendor.pygments.cmdline import main
|
||||
|
||||
try:
|
||||
sys.exit(main(sys.argv))
|
||||
except KeyboardInterrupt:
|
||||
sys.exit(1)
|
|
@ -0,0 +1,602 @@
|
|||
"""
|
||||
pygments.cmdline
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Command line interface.
|
||||
|
||||
:copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import argparse
|
||||
from textwrap import dedent
|
||||
|
||||
from pip._vendor.pygments import __version__, highlight
|
||||
from pip._vendor.pygments.util import ClassNotFound, OptionError, docstring_headline, \
|
||||
guess_decode, guess_decode_from_terminal, terminal_encoding, \
|
||||
UnclosingTextIOWrapper
|
||||
from pip._vendor.pygments.lexers import get_all_lexers, get_lexer_by_name, guess_lexer, \
|
||||
load_lexer_from_file, get_lexer_for_filename, find_lexer_class_for_filename
|
||||
from pip._vendor.pygments.lexers.special import TextLexer
|
||||
from pip._vendor.pygments.formatters.latex import LatexEmbeddedLexer, LatexFormatter
|
||||
from pip._vendor.pygments.formatters import get_all_formatters, get_formatter_by_name, \
|
||||
load_formatter_from_file, get_formatter_for_filename, find_formatter_class
|
||||
from pip._vendor.pygments.formatters.terminal import TerminalFormatter
|
||||
from pip._vendor.pygments.formatters.terminal256 import Terminal256Formatter
|
||||
from pip._vendor.pygments.filters import get_all_filters, find_filter_class
|
||||
from pip._vendor.pygments.styles import get_all_styles, get_style_by_name
|
||||
|
||||
|
||||
def _parse_options(o_strs):
|
||||
opts = {}
|
||||
if not o_strs:
|
||||
return opts
|
||||
for o_str in o_strs:
|
||||
if not o_str.strip():
|
||||
continue
|
||||
o_args = o_str.split(',')
|
||||
for o_arg in o_args:
|
||||
o_arg = o_arg.strip()
|
||||
try:
|
||||
o_key, o_val = o_arg.split('=', 1)
|
||||
o_key = o_key.strip()
|
||||
o_val = o_val.strip()
|
||||
except ValueError:
|
||||
opts[o_arg] = True
|
||||
else:
|
||||
opts[o_key] = o_val
|
||||
return opts
|
||||
|
||||
|
||||
def _parse_filters(f_strs):
|
||||
filters = []
|
||||
if not f_strs:
|
||||
return filters
|
||||
for f_str in f_strs:
|
||||
if ':' in f_str:
|
||||
fname, fopts = f_str.split(':', 1)
|
||||
filters.append((fname, _parse_options([fopts])))
|
||||
else:
|
||||
filters.append((f_str, {}))
|
||||
return filters
|
||||
|
||||
|
||||
def _print_help(what, name):
|
||||
try:
|
||||
if what == 'lexer':
|
||||
cls = get_lexer_by_name(name)
|
||||
print("Help on the %s lexer:" % cls.name)
|
||||
print(dedent(cls.__doc__))
|
||||
elif what == 'formatter':
|
||||
cls = find_formatter_class(name)
|
||||
print("Help on the %s formatter:" % cls.name)
|
||||
print(dedent(cls.__doc__))
|
||||
elif what == 'filter':
|
||||
cls = find_filter_class(name)
|
||||
print("Help on the %s filter:" % name)
|
||||
print(dedent(cls.__doc__))
|
||||
return 0
|
||||
except (AttributeError, ValueError):
|
||||
print("%s not found!" % what, file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
def _print_list(what):
|
||||
if what == 'lexer':
|
||||
print()
|
||||
print("Lexers:")
|
||||
print("~~~~~~~")
|
||||
|
||||
info = []
|
||||
for fullname, names, exts, _ in get_all_lexers():
|
||||
tup = (', '.join(names)+':', fullname,
|
||||
exts and '(filenames ' + ', '.join(exts) + ')' or '')
|
||||
info.append(tup)
|
||||
info.sort()
|
||||
for i in info:
|
||||
print(('* %s\n %s %s') % i)
|
||||
|
||||
elif what == 'formatter':
|
||||
print()
|
||||
print("Formatters:")
|
||||
print("~~~~~~~~~~~")
|
||||
|
||||
info = []
|
||||
for cls in get_all_formatters():
|
||||
doc = docstring_headline(cls)
|
||||
tup = (', '.join(cls.aliases) + ':', doc, cls.filenames and
|
||||
'(filenames ' + ', '.join(cls.filenames) + ')' or '')
|
||||
info.append(tup)
|
||||
info.sort()
|
||||
for i in info:
|
||||
print(('* %s\n %s %s') % i)
|
||||
|
||||
elif what == 'filter':
|
||||
print()
|
||||
print("Filters:")
|
||||
print("~~~~~~~~")
|
||||
|
||||
for name in get_all_filters():
|
||||
cls = find_filter_class(name)
|
||||
print("* " + name + ':')
|
||||
print(" %s" % docstring_headline(cls))
|
||||
|
||||
elif what == 'style':
|
||||
print()
|
||||
print("Styles:")
|
||||
print("~~~~~~~")
|
||||
|
||||
for name in get_all_styles():
|
||||
cls = get_style_by_name(name)
|
||||
print("* " + name + ':')
|
||||
print(" %s" % docstring_headline(cls))
|
||||
|
||||
|
||||
def main_inner(parser, argns):
|
||||
if argns.help:
|
||||
parser.print_help()
|
||||
return 0
|
||||
|
||||
if argns.V:
|
||||
print('Pygments version %s, (c) 2006-2021 by Georg Brandl, Matthäus '
|
||||
'Chajdas and contributors.' % __version__)
|
||||
return 0
|
||||
|
||||
def is_only_option(opt):
|
||||
return not any(v for (k, v) in vars(argns).items() if k != opt)
|
||||
|
||||
# handle ``pygmentize -L``
|
||||
if argns.L is not None:
|
||||
if not is_only_option('L'):
|
||||
parser.print_help(sys.stderr)
|
||||
return 2
|
||||
# print version
|
||||
main(['', '-V'])
|
||||
allowed_types = {'lexer', 'formatter', 'filter', 'style'}
|
||||
largs = [arg.rstrip('s') for arg in argns.L]
|
||||
if any(arg not in allowed_types for arg in largs):
|
||||
parser.print_help(sys.stderr)
|
||||
return 0
|
||||
if not largs:
|
||||
largs = allowed_types
|
||||
for arg in largs:
|
||||
_print_list(arg)
|
||||
return 0
|
||||
|
||||
# handle ``pygmentize -H``
|
||||
if argns.H:
|
||||
if not is_only_option('H'):
|
||||
parser.print_help(sys.stderr)
|
||||
return 2
|
||||
what, name = argns.H
|
||||
if what not in ('lexer', 'formatter', 'filter'):
|
||||
parser.print_help(sys.stderr)
|
||||
return 2
|
||||
return _print_help(what, name)
|
||||
|
||||
# parse -O options
|
||||
parsed_opts = _parse_options(argns.O or [])
|
||||
|
||||
# parse -P options
|
||||
for p_opt in argns.P or []:
|
||||
try:
|
||||
name, value = p_opt.split('=', 1)
|
||||
except ValueError:
|
||||
parsed_opts[p_opt] = True
|
||||
else:
|
||||
parsed_opts[name] = value
|
||||
|
||||
# encodings
|
||||
inencoding = parsed_opts.get('inencoding', parsed_opts.get('encoding'))
|
||||
outencoding = parsed_opts.get('outencoding', parsed_opts.get('encoding'))
|
||||
|
||||
# handle ``pygmentize -N``
|
||||
if argns.N:
|
||||
lexer = find_lexer_class_for_filename(argns.N)
|
||||
if lexer is None:
|
||||
lexer = TextLexer
|
||||
|
||||
print(lexer.aliases[0])
|
||||
return 0
|
||||
|
||||
# handle ``pygmentize -C``
|
||||
if argns.C:
|
||||
inp = sys.stdin.buffer.read()
|
||||
try:
|
||||
lexer = guess_lexer(inp, inencoding=inencoding)
|
||||
except ClassNotFound:
|
||||
lexer = TextLexer
|
||||
|
||||
print(lexer.aliases[0])
|
||||
return 0
|
||||
|
||||
# handle ``pygmentize -S``
|
||||
S_opt = argns.S
|
||||
a_opt = argns.a
|
||||
if S_opt is not None:
|
||||
f_opt = argns.f
|
||||
if not f_opt:
|
||||
parser.print_help(sys.stderr)
|
||||
return 2
|
||||
if argns.l or argns.INPUTFILE:
|
||||
parser.print_help(sys.stderr)
|
||||
return 2
|
||||
|
||||
try:
|
||||
parsed_opts['style'] = S_opt
|
||||
fmter = get_formatter_by_name(f_opt, **parsed_opts)
|
||||
except ClassNotFound as err:
|
||||
print(err, file=sys.stderr)
|
||||
return 1
|
||||
|
||||
print(fmter.get_style_defs(a_opt or ''))
|
||||
return 0
|
||||
|
||||
# if no -S is given, -a is not allowed
|
||||
if argns.a is not None:
|
||||
parser.print_help(sys.stderr)
|
||||
return 2
|
||||
|
||||
# parse -F options
|
||||
F_opts = _parse_filters(argns.F or [])
|
||||
|
||||
# -x: allow custom (eXternal) lexers and formatters
|
||||
allow_custom_lexer_formatter = bool(argns.x)
|
||||
|
||||
# select lexer
|
||||
lexer = None
|
||||
|
||||
# given by name?
|
||||
lexername = argns.l
|
||||
if lexername:
|
||||
# custom lexer, located relative to user's cwd
|
||||
if allow_custom_lexer_formatter and '.py' in lexername:
|
||||
try:
|
||||
filename = None
|
||||
name = None
|
||||
if ':' in lexername:
|
||||
filename, name = lexername.rsplit(':', 1)
|
||||
|
||||
if '.py' in name:
|
||||
# This can happen on Windows: If the lexername is
|
||||
# C:\lexer.py -- return to normal load path in that case
|
||||
name = None
|
||||
|
||||
if filename and name:
|
||||
lexer = load_lexer_from_file(filename, name,
|
||||
**parsed_opts)
|
||||
else:
|
||||
lexer = load_lexer_from_file(lexername, **parsed_opts)
|
||||
except ClassNotFound as err:
|
||||
print('Error:', err, file=sys.stderr)
|
||||
return 1
|
||||
else:
|
||||
try:
|
||||
lexer = get_lexer_by_name(lexername, **parsed_opts)
|
||||
except (OptionError, ClassNotFound) as err:
|
||||
print('Error:', err, file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# read input code
|
||||
code = None
|
||||
|
||||
if argns.INPUTFILE:
|
||||
if argns.s:
|
||||
print('Error: -s option not usable when input file specified',
|
||||
file=sys.stderr)
|
||||
return 2
|
||||
|
||||
infn = argns.INPUTFILE
|
||||
try:
|
||||
with open(infn, 'rb') as infp:
|
||||
code = infp.read()
|
||||
except Exception as err:
|
||||
print('Error: cannot read infile:', err, file=sys.stderr)
|
||||
return 1
|
||||
if not inencoding:
|
||||
code, inencoding = guess_decode(code)
|
||||
|
||||
# do we have to guess the lexer?
|
||||
if not lexer:
|
||||
try:
|
||||
lexer = get_lexer_for_filename(infn, code, **parsed_opts)
|
||||
except ClassNotFound as err:
|
||||
if argns.g:
|
||||
try:
|
||||
lexer = guess_lexer(code, **parsed_opts)
|
||||
except ClassNotFound:
|
||||
lexer = TextLexer(**parsed_opts)
|
||||
else:
|
||||
print('Error:', err, file=sys.stderr)
|
||||
return 1
|
||||
except OptionError as err:
|
||||
print('Error:', err, file=sys.stderr)
|
||||
return 1
|
||||
|
||||
elif not argns.s: # treat stdin as full file (-s support is later)
|
||||
# read code from terminal, always in binary mode since we want to
|
||||
# decode ourselves and be tolerant with it
|
||||
code = sys.stdin.buffer.read() # use .buffer to get a binary stream
|
||||
if not inencoding:
|
||||
code, inencoding = guess_decode_from_terminal(code, sys.stdin)
|
||||
# else the lexer will do the decoding
|
||||
if not lexer:
|
||||
try:
|
||||
lexer = guess_lexer(code, **parsed_opts)
|
||||
except ClassNotFound:
|
||||
lexer = TextLexer(**parsed_opts)
|
||||
|
||||
else: # -s option needs a lexer with -l
|
||||
if not lexer:
|
||||
print('Error: when using -s a lexer has to be selected with -l',
|
||||
file=sys.stderr)
|
||||
return 2
|
||||
|
||||
# process filters
|
||||
for fname, fopts in F_opts:
|
||||
try:
|
||||
lexer.add_filter(fname, **fopts)
|
||||
except ClassNotFound as err:
|
||||
print('Error:', err, file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# select formatter
|
||||
outfn = argns.o
|
||||
fmter = argns.f
|
||||
if fmter:
|
||||
# custom formatter, located relative to user's cwd
|
||||
if allow_custom_lexer_formatter and '.py' in fmter:
|
||||
try:
|
||||
filename = None
|
||||
name = None
|
||||
if ':' in fmter:
|
||||
# Same logic as above for custom lexer
|
||||
filename, name = fmter.rsplit(':', 1)
|
||||
|
||||
if '.py' in name:
|
||||
name = None
|
||||
|
||||
if filename and name:
|
||||
fmter = load_formatter_from_file(filename, name,
|
||||
**parsed_opts)
|
||||
else:
|
||||
fmter = load_formatter_from_file(fmter, **parsed_opts)
|
||||
except ClassNotFound as err:
|
||||
print('Error:', err, file=sys.stderr)
|
||||
return 1
|
||||
else:
|
||||
try:
|
||||
fmter = get_formatter_by_name(fmter, **parsed_opts)
|
||||
except (OptionError, ClassNotFound) as err:
|
||||
print('Error:', err, file=sys.stderr)
|
||||
return 1
|
||||
|
||||
if outfn:
|
||||
if not fmter:
|
||||
try:
|
||||
fmter = get_formatter_for_filename(outfn, **parsed_opts)
|
||||
except (OptionError, ClassNotFound) as err:
|
||||
print('Error:', err, file=sys.stderr)
|
||||
return 1
|
||||
try:
|
||||
outfile = open(outfn, 'wb')
|
||||
except Exception as err:
|
||||
print('Error: cannot open outfile:', err, file=sys.stderr)
|
||||
return 1
|
||||
else:
|
||||
if not fmter:
|
||||
if '256' in os.environ.get('TERM', ''):
|
||||
fmter = Terminal256Formatter(**parsed_opts)
|
||||
else:
|
||||
fmter = TerminalFormatter(**parsed_opts)
|
||||
outfile = sys.stdout.buffer
|
||||
|
||||
# determine output encoding if not explicitly selected
|
||||
if not outencoding:
|
||||
if outfn:
|
||||
# output file? use lexer encoding for now (can still be None)
|
||||
fmter.encoding = inencoding
|
||||
else:
|
||||
# else use terminal encoding
|
||||
fmter.encoding = terminal_encoding(sys.stdout)
|
||||
|
||||
# provide coloring under Windows, if possible
|
||||
if not outfn and sys.platform in ('win32', 'cygwin') and \
|
||||
fmter.name in ('Terminal', 'Terminal256'): # pragma: no cover
|
||||
# unfortunately colorama doesn't support binary streams on Py3
|
||||
outfile = UnclosingTextIOWrapper(outfile, encoding=fmter.encoding)
|
||||
fmter.encoding = None
|
||||
try:
|
||||
import pip._vendor.colorama.initialise as colorama_initialise
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
outfile = colorama_initialise.wrap_stream(
|
||||
outfile, convert=None, strip=None, autoreset=False, wrap=True)
|
||||
|
||||
# When using the LaTeX formatter and the option `escapeinside` is
|
||||
# specified, we need a special lexer which collects escaped text
|
||||
# before running the chosen language lexer.
|
||||
escapeinside = parsed_opts.get('escapeinside', '')
|
||||
if len(escapeinside) == 2 and isinstance(fmter, LatexFormatter):
|
||||
left = escapeinside[0]
|
||||
right = escapeinside[1]
|
||||
lexer = LatexEmbeddedLexer(left, right, lexer)
|
||||
|
||||
# ... and do it!
|
||||
if not argns.s:
|
||||
# process whole input as per normal...
|
||||
try:
|
||||
highlight(code, lexer, fmter, outfile)
|
||||
finally:
|
||||
if outfn:
|
||||
outfile.close()
|
||||
return 0
|
||||
else:
|
||||
# line by line processing of stdin (eg: for 'tail -f')...
|
||||
try:
|
||||
while 1:
|
||||
line = sys.stdin.buffer.readline()
|
||||
if not line:
|
||||
break
|
||||
if not inencoding:
|
||||
line = guess_decode_from_terminal(line, sys.stdin)[0]
|
||||
highlight(line, lexer, fmter, outfile)
|
||||
if hasattr(outfile, 'flush'):
|
||||
outfile.flush()
|
||||
return 0
|
||||
except KeyboardInterrupt: # pragma: no cover
|
||||
return 0
|
||||
finally:
|
||||
if outfn:
|
||||
outfile.close()
|
||||
|
||||
|
||||
class HelpFormatter(argparse.HelpFormatter):
|
||||
def __init__(self, prog, indent_increment=2, max_help_position=16, width=None):
|
||||
if width is None:
|
||||
try:
|
||||
width = shutil.get_terminal_size().columns - 2
|
||||
except Exception:
|
||||
pass
|
||||
argparse.HelpFormatter.__init__(self, prog, indent_increment,
|
||||
max_help_position, width)
|
||||
|
||||
|
||||
def main(args=sys.argv):
|
||||
"""
|
||||
Main command line entry point.
|
||||
"""
|
||||
desc = "Highlight an input file and write the result to an output file."
|
||||
parser = argparse.ArgumentParser(description=desc, add_help=False,
|
||||
formatter_class=HelpFormatter)
|
||||
|
||||
operation = parser.add_argument_group('Main operation')
|
||||
lexersel = operation.add_mutually_exclusive_group()
|
||||
lexersel.add_argument(
|
||||
'-l', metavar='LEXER',
|
||||
help='Specify the lexer to use. (Query names with -L.) If not '
|
||||
'given and -g is not present, the lexer is guessed from the filename.')
|
||||
lexersel.add_argument(
|
||||
'-g', action='store_true',
|
||||
help='Guess the lexer from the file contents, or pass through '
|
||||
'as plain text if nothing can be guessed.')
|
||||
operation.add_argument(
|
||||
'-F', metavar='FILTER[:options]', action='append',
|
||||
help='Add a filter to the token stream. (Query names with -L.) '
|
||||
'Filter options are given after a colon if necessary.')
|
||||
operation.add_argument(
|
||||
'-f', metavar='FORMATTER',
|
||||
help='Specify the formatter to use. (Query names with -L.) '
|
||||
'If not given, the formatter is guessed from the output filename, '
|
||||
'and defaults to the terminal formatter if the output is to the '
|
||||
'terminal or an unknown file extension.')
|
||||
operation.add_argument(
|
||||
'-O', metavar='OPTION=value[,OPTION=value,...]', action='append',
|
||||
help='Give options to the lexer and formatter as a comma-separated '
|
||||
'list of key-value pairs. '
|
||||
'Example: `-O bg=light,python=cool`.')
|
||||
operation.add_argument(
|
||||
'-P', metavar='OPTION=value', action='append',
|
||||
help='Give a single option to the lexer and formatter - with this '
|
||||
'you can pass options whose value contains commas and equal signs. '
|
||||
'Example: `-P "heading=Pygments, the Python highlighter"`.')
|
||||
operation.add_argument(
|
||||
'-o', metavar='OUTPUTFILE',
|
||||
help='Where to write the output. Defaults to standard output.')
|
||||
|
||||
operation.add_argument(
|
||||
'INPUTFILE', nargs='?',
|
||||
help='Where to read the input. Defaults to standard input.')
|
||||
|
||||
flags = parser.add_argument_group('Operation flags')
|
||||
flags.add_argument(
|
||||
'-v', action='store_true',
|
||||
help='Print a detailed traceback on unhandled exceptions, which '
|
||||
'is useful for debugging and bug reports.')
|
||||
flags.add_argument(
|
||||
'-s', action='store_true',
|
||||
help='Process lines one at a time until EOF, rather than waiting to '
|
||||
'process the entire file. This only works for stdin, only for lexers '
|
||||
'with no line-spanning constructs, and is intended for streaming '
|
||||
'input such as you get from `tail -f`. '
|
||||
'Example usage: `tail -f sql.log | pygmentize -s -l sql`.')
|
||||
flags.add_argument(
|
||||
'-x', action='store_true',
|
||||
help='Allow custom lexers and formatters to be loaded from a .py file '
|
||||
'relative to the current working directory. For example, '
|
||||
'`-l ./customlexer.py -x`. By default, this option expects a file '
|
||||
'with a class named CustomLexer or CustomFormatter; you can also '
|
||||
'specify your own class name with a colon (`-l ./lexer.py:MyLexer`). '
|
||||
'Users should be very careful not to use this option with untrusted '
|
||||
'files, because it will import and run them.')
|
||||
|
||||
special_modes_group = parser.add_argument_group(
|
||||
'Special modes - do not do any highlighting')
|
||||
special_modes = special_modes_group.add_mutually_exclusive_group()
|
||||
special_modes.add_argument(
|
||||
'-S', metavar='STYLE -f formatter',
|
||||
help='Print style definitions for STYLE for a formatter '
|
||||
'given with -f. The argument given by -a is formatter '
|
||||
'dependent.')
|
||||
special_modes.add_argument(
|
||||
'-L', nargs='*', metavar='WHAT',
|
||||
help='List lexers, formatters, styles or filters -- '
|
||||
'give additional arguments for the thing(s) you want to list '
|
||||
'(e.g. "styles"), or omit them to list everything.')
|
||||
special_modes.add_argument(
|
||||
'-N', metavar='FILENAME',
|
||||
help='Guess and print out a lexer name based solely on the given '
|
||||
'filename. Does not take input or highlight anything. If no specific '
|
||||
'lexer can be determined, "text" is printed.')
|
||||
special_modes.add_argument(
|
||||
'-C', action='store_true',
|
||||
help='Like -N, but print out a lexer name based solely on '
|
||||
'a given content from standard input.')
|
||||
special_modes.add_argument(
|
||||
'-H', action='store', nargs=2, metavar=('NAME', 'TYPE'),
|
||||
help='Print detailed help for the object <name> of type <type>, '
|
||||
'where <type> is one of "lexer", "formatter" or "filter".')
|
||||
special_modes.add_argument(
|
||||
'-V', action='store_true',
|
||||
help='Print the package version.')
|
||||
special_modes.add_argument(
|
||||
'-h', '--help', action='store_true',
|
||||
help='Print this help.')
|
||||
special_modes_group.add_argument(
|
||||
'-a', metavar='ARG',
|
||||
help='Formatter-specific additional argument for the -S (print '
|
||||
'style sheet) mode.')
|
||||
|
||||
argns = parser.parse_args(args[1:])
|
||||
|
||||
try:
|
||||
return main_inner(parser, argns)
|
||||
except Exception:
|
||||
if argns.v:
|
||||
print(file=sys.stderr)
|
||||
print('*' * 65, file=sys.stderr)
|
||||
print('An unhandled exception occurred while highlighting.',
|
||||
file=sys.stderr)
|
||||
print('Please report the whole traceback to the issue tracker at',
|
||||
file=sys.stderr)
|
||||
print('<https://github.com/pygments/pygments/issues>.',
|
||||
file=sys.stderr)
|
||||
print('*' * 65, file=sys.stderr)
|
||||
print(file=sys.stderr)
|
||||
raise
|
||||
import traceback
|
||||
info = traceback.format_exception(*sys.exc_info())
|
||||
msg = info[-1].strip()
|
||||
if len(info) >= 3:
|
||||
# extract relevant file and position info
|
||||
msg += '\n (f%s)' % info[-2].split('\n')[0].strip()[1:]
|
||||
print(file=sys.stderr)
|
||||
print('*** Error while highlighting:', file=sys.stderr)
|
||||
print(msg, file=sys.stderr)
|
||||
print('*** If this is a bug you want to report, please rerun with -v.',
|
||||
file=sys.stderr)
|
||||
return 1
|
|
@ -0,0 +1,70 @@
|
|||
"""
|
||||
pygments.console
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Format colored console output.
|
||||
|
||||
:copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
esc = "\x1b["
|
||||
|
||||
codes = {}
|
||||
codes[""] = ""
|
||||
codes["reset"] = esc + "39;49;00m"
|
||||
|
||||
codes["bold"] = esc + "01m"
|
||||
codes["faint"] = esc + "02m"
|
||||
codes["standout"] = esc + "03m"
|
||||
codes["underline"] = esc + "04m"
|
||||
codes["blink"] = esc + "05m"
|
||||
codes["overline"] = esc + "06m"
|
||||
|
||||
dark_colors = ["black", "red", "green", "yellow", "blue",
|
||||
"magenta", "cyan", "gray"]
|
||||
light_colors = ["brightblack", "brightred", "brightgreen", "brightyellow", "brightblue",
|
||||
"brightmagenta", "brightcyan", "white"]
|
||||
|
||||
x = 30
|
||||
for d, l in zip(dark_colors, light_colors):
|
||||
codes[d] = esc + "%im" % x
|
||||
codes[l] = esc + "%im" % (60 + x)
|
||||
x += 1
|
||||
|
||||
del d, l, x
|
||||
|
||||
codes["white"] = codes["bold"]
|
||||
|
||||
|
||||
def reset_color():
|
||||
return codes["reset"]
|
||||
|
||||
|
||||
def colorize(color_key, text):
|
||||
return codes[color_key] + text + codes["reset"]
|
||||
|
||||
|
||||
def ansiformat(attr, text):
|
||||
"""
|
||||
Format ``text`` with a color and/or some attributes::
|
||||
|
||||
color normal color
|
||||
*color* bold color
|
||||
_color_ underlined color
|
||||
+color+ blinking color
|
||||
"""
|
||||
result = []
|
||||
if attr[:1] == attr[-1:] == '+':
|
||||
result.append(codes['blink'])
|
||||
attr = attr[1:-1]
|
||||
if attr[:1] == attr[-1:] == '*':
|
||||
result.append(codes['bold'])
|
||||
attr = attr[1:-1]
|
||||
if attr[:1] == attr[-1:] == '_':
|
||||
result.append(codes['underline'])
|
||||
attr = attr[1:-1]
|
||||
result.append(codes[attr])
|
||||
result.append(text)
|
||||
result.append(codes['reset'])
|
||||
return ''.join(result)
|
|
@ -0,0 +1,71 @@
|
|||
"""
|
||||
pygments.filter
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Module that implements the default filter.
|
||||
|
||||
:copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
|
||||
def apply_filters(stream, filters, lexer=None):
|
||||
"""
|
||||
Use this method to apply an iterable of filters to
|
||||
a stream. If lexer is given it's forwarded to the
|
||||
filter, otherwise the filter receives `None`.
|
||||
"""
|
||||
def _apply(filter_, stream):
|
||||
yield from filter_.filter(lexer, stream)
|
||||
for filter_ in filters:
|
||||
stream = _apply(filter_, stream)
|
||||
return stream
|
||||
|
||||
|
||||
def simplefilter(f):
|
||||
"""
|
||||
Decorator that converts a function into a filter::
|
||||
|
||||
@simplefilter
|
||||
def lowercase(self, lexer, stream, options):
|
||||
for ttype, value in stream:
|
||||
yield ttype, value.lower()
|
||||
"""
|
||||
return type(f.__name__, (FunctionFilter,), {
|
||||
'__module__': getattr(f, '__module__'),
|
||||
'__doc__': f.__doc__,
|
||||
'function': f,
|
||||
})
|
||||
|
||||
|
||||
class Filter:
|
||||
"""
|
||||
Default filter. Subclass this class or use the `simplefilter`
|
||||
decorator to create own filters.
|
||||
"""
|
||||
|
||||
def __init__(self, **options):
|
||||
self.options = options
|
||||
|
||||
def filter(self, lexer, stream):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class FunctionFilter(Filter):
|
||||
"""
|
||||
Abstract class used by `simplefilter` to create simple
|
||||
function filters on the fly. The `simplefilter` decorator
|
||||
automatically creates subclasses of this class for
|
||||
functions passed to it.
|
||||
"""
|
||||
function = None
|
||||
|
||||
def __init__(self, **options):
|
||||
if not hasattr(self, 'function'):
|
||||
raise TypeError('%r used without bound function' %
|
||||
self.__class__.__name__)
|
||||
Filter.__init__(self, **options)
|
||||
|
||||
def filter(self, lexer, stream):
|
||||
# pylint: disable=not-callable
|
||||
yield from self.function(lexer, stream, self.options)
|
|
@ -0,0 +1,937 @@
|
|||
"""
|
||||
pygments.filters
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Module containing filter lookup functions and default
|
||||
filters.
|
||||
|
||||
:copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from pip._vendor.pygments.token import String, Comment, Keyword, Name, Error, Whitespace, \
|
||||
string_to_tokentype
|
||||
from pip._vendor.pygments.filter import Filter
|
||||
from pip._vendor.pygments.util import get_list_opt, get_int_opt, get_bool_opt, \
|
||||
get_choice_opt, ClassNotFound, OptionError
|
||||
from pip._vendor.pygments.plugin import find_plugin_filters
|
||||
|
||||
|
||||
def find_filter_class(filtername):
|
||||
"""Lookup a filter by name. Return None if not found."""
|
||||
if filtername in FILTERS:
|
||||
return FILTERS[filtername]
|
||||
for name, cls in find_plugin_filters():
|
||||
if name == filtername:
|
||||
return cls
|
||||
return None
|
||||
|
||||
|
||||
def get_filter_by_name(filtername, **options):
|
||||
"""Return an instantiated filter.
|
||||
|
||||
Options are passed to the filter initializer if wanted.
|
||||
Raise a ClassNotFound if not found.
|
||||
"""
|
||||
cls = find_filter_class(filtername)
|
||||
if cls:
|
||||
return cls(**options)
|
||||
else:
|
||||
raise ClassNotFound('filter %r not found' % filtername)
|
||||
|
||||
|
||||
def get_all_filters():
|
||||
"""Return a generator of all filter names."""
|
||||
yield from FILTERS
|
||||
for name, _ in find_plugin_filters():
|
||||
yield name
|
||||
|
||||
|
||||
def _replace_special(ttype, value, regex, specialttype,
|
||||
replacefunc=lambda x: x):
|
||||
last = 0
|
||||
for match in regex.finditer(value):
|
||||
start, end = match.start(), match.end()
|
||||
if start != last:
|
||||
yield ttype, value[last:start]
|
||||
yield specialttype, replacefunc(value[start:end])
|
||||
last = end
|
||||
if last != len(value):
|
||||
yield ttype, value[last:]
|
||||
|
||||
|
||||
class CodeTagFilter(Filter):
|
||||
"""Highlight special code tags in comments and docstrings.
|
||||
|
||||
Options accepted:
|
||||
|
||||
`codetags` : list of strings
|
||||
A list of strings that are flagged as code tags. The default is to
|
||||
highlight ``XXX``, ``TODO``, ``BUG`` and ``NOTE``.
|
||||
"""
|
||||
|
||||
def __init__(self, **options):
|
||||
Filter.__init__(self, **options)
|
||||
tags = get_list_opt(options, 'codetags',
|
||||
['XXX', 'TODO', 'BUG', 'NOTE'])
|
||||
self.tag_re = re.compile(r'\b(%s)\b' % '|'.join([
|
||||
re.escape(tag) for tag in tags if tag
|
||||
]))
|
||||
|
||||
def filter(self, lexer, stream):
|
||||
regex = self.tag_re
|
||||
for ttype, value in stream:
|
||||
if ttype in String.Doc or \
|
||||
ttype in Comment and \
|
||||
ttype not in Comment.Preproc:
|
||||
yield from _replace_special(ttype, value, regex, Comment.Special)
|
||||
else:
|
||||
yield ttype, value
|
||||
|
||||
|
||||
class SymbolFilter(Filter):
|
||||
"""Convert mathematical symbols such as \\<longrightarrow> in Isabelle
|
||||
or \\longrightarrow in LaTeX into Unicode characters.
|
||||
|
||||
This is mostly useful for HTML or console output when you want to
|
||||
approximate the source rendering you'd see in an IDE.
|
||||
|
||||
Options accepted:
|
||||
|
||||
`lang` : string
|
||||
The symbol language. Must be one of ``'isabelle'`` or
|
||||
``'latex'``. The default is ``'isabelle'``.
|
||||
"""
|
||||
|
||||
latex_symbols = {
|
||||
'\\alpha' : '\U000003b1',
|
||||
'\\beta' : '\U000003b2',
|
||||
'\\gamma' : '\U000003b3',
|
||||
'\\delta' : '\U000003b4',
|
||||
'\\varepsilon' : '\U000003b5',
|
||||
'\\zeta' : '\U000003b6',
|
||||
'\\eta' : '\U000003b7',
|
||||
'\\vartheta' : '\U000003b8',
|
||||
'\\iota' : '\U000003b9',
|
||||
'\\kappa' : '\U000003ba',
|
||||
'\\lambda' : '\U000003bb',
|
||||
'\\mu' : '\U000003bc',
|
||||
'\\nu' : '\U000003bd',
|
||||
'\\xi' : '\U000003be',
|
||||
'\\pi' : '\U000003c0',
|
||||
'\\varrho' : '\U000003c1',
|
||||
'\\sigma' : '\U000003c3',
|
||||
'\\tau' : '\U000003c4',
|
||||
'\\upsilon' : '\U000003c5',
|
||||
'\\varphi' : '\U000003c6',
|
||||
'\\chi' : '\U000003c7',
|
||||
'\\psi' : '\U000003c8',
|
||||
'\\omega' : '\U000003c9',
|
||||
'\\Gamma' : '\U00000393',
|
||||
'\\Delta' : '\U00000394',
|
||||
'\\Theta' : '\U00000398',
|
||||
'\\Lambda' : '\U0000039b',
|
||||
'\\Xi' : '\U0000039e',
|
||||
'\\Pi' : '\U000003a0',
|
||||
'\\Sigma' : '\U000003a3',
|
||||
'\\Upsilon' : '\U000003a5',
|
||||
'\\Phi' : '\U000003a6',
|
||||
'\\Psi' : '\U000003a8',
|
||||
'\\Omega' : '\U000003a9',
|
||||
'\\leftarrow' : '\U00002190',
|
||||
'\\longleftarrow' : '\U000027f5',
|
||||
'\\rightarrow' : '\U00002192',
|
||||
'\\longrightarrow' : '\U000027f6',
|
||||
'\\Leftarrow' : '\U000021d0',
|
||||
'\\Longleftarrow' : '\U000027f8',
|
||||
'\\Rightarrow' : '\U000021d2',
|
||||
'\\Longrightarrow' : '\U000027f9',
|
||||
'\\leftrightarrow' : '\U00002194',
|
||||
'\\longleftrightarrow' : '\U000027f7',
|
||||
'\\Leftrightarrow' : '\U000021d4',
|
||||
'\\Longleftrightarrow' : '\U000027fa',
|
||||
'\\mapsto' : '\U000021a6',
|
||||
'\\longmapsto' : '\U000027fc',
|
||||
'\\relbar' : '\U00002500',
|
||||
'\\Relbar' : '\U00002550',
|
||||
'\\hookleftarrow' : '\U000021a9',
|
||||
'\\hookrightarrow' : '\U000021aa',
|
||||
'\\leftharpoondown' : '\U000021bd',
|
||||
'\\rightharpoondown' : '\U000021c1',
|
||||
'\\leftharpoonup' : '\U000021bc',
|
||||
'\\rightharpoonup' : '\U000021c0',
|
||||
'\\rightleftharpoons' : '\U000021cc',
|
||||
'\\leadsto' : '\U0000219d',
|
||||
'\\downharpoonleft' : '\U000021c3',
|
||||
'\\downharpoonright' : '\U000021c2',
|
||||
'\\upharpoonleft' : '\U000021bf',
|
||||
'\\upharpoonright' : '\U000021be',
|
||||
'\\restriction' : '\U000021be',
|
||||
'\\uparrow' : '\U00002191',
|
||||
'\\Uparrow' : '\U000021d1',
|
||||
'\\downarrow' : '\U00002193',
|
||||
'\\Downarrow' : '\U000021d3',
|
||||
'\\updownarrow' : '\U00002195',
|
||||
'\\Updownarrow' : '\U000021d5',
|
||||
'\\langle' : '\U000027e8',
|
||||
'\\rangle' : '\U000027e9',
|
||||
'\\lceil' : '\U00002308',
|
||||
'\\rceil' : '\U00002309',
|
||||
'\\lfloor' : '\U0000230a',
|
||||
'\\rfloor' : '\U0000230b',
|
||||
'\\flqq' : '\U000000ab',
|
||||
'\\frqq' : '\U000000bb',
|
||||
'\\bot' : '\U000022a5',
|
||||
'\\top' : '\U000022a4',
|
||||
'\\wedge' : '\U00002227',
|
||||
'\\bigwedge' : '\U000022c0',
|
||||
'\\vee' : '\U00002228',
|
||||
'\\bigvee' : '\U000022c1',
|
||||
'\\forall' : '\U00002200',
|
||||
'\\exists' : '\U00002203',
|
||||
'\\nexists' : '\U00002204',
|
||||
'\\neg' : '\U000000ac',
|
||||
'\\Box' : '\U000025a1',
|
||||
'\\Diamond' : '\U000025c7',
|
||||
'\\vdash' : '\U000022a2',
|
||||
'\\models' : '\U000022a8',
|
||||
'\\dashv' : '\U000022a3',
|
||||
'\\surd' : '\U0000221a',
|
||||
'\\le' : '\U00002264',
|
||||
'\\ge' : '\U00002265',
|
||||
'\\ll' : '\U0000226a',
|
||||
'\\gg' : '\U0000226b',
|
||||
'\\lesssim' : '\U00002272',
|
||||
'\\gtrsim' : '\U00002273',
|
||||
'\\lessapprox' : '\U00002a85',
|
||||
'\\gtrapprox' : '\U00002a86',
|
||||
'\\in' : '\U00002208',
|
||||
'\\notin' : '\U00002209',
|
||||
'\\subset' : '\U00002282',
|
||||
'\\supset' : '\U00002283',
|
||||
'\\subseteq' : '\U00002286',
|
||||
'\\supseteq' : '\U00002287',
|
||||
'\\sqsubset' : '\U0000228f',
|
||||
'\\sqsupset' : '\U00002290',
|
||||
'\\sqsubseteq' : '\U00002291',
|
||||
'\\sqsupseteq' : '\U00002292',
|
||||
'\\cap' : '\U00002229',
|
||||
'\\bigcap' : '\U000022c2',
|
||||
'\\cup' : '\U0000222a',
|
||||
'\\bigcup' : '\U000022c3',
|
||||
'\\sqcup' : '\U00002294',
|
||||
'\\bigsqcup' : '\U00002a06',
|
||||
'\\sqcap' : '\U00002293',
|
||||
'\\Bigsqcap' : '\U00002a05',
|
||||
'\\setminus' : '\U00002216',
|
||||
'\\propto' : '\U0000221d',
|
||||
'\\uplus' : '\U0000228e',
|
||||
'\\bigplus' : '\U00002a04',
|
||||
'\\sim' : '\U0000223c',
|
||||
'\\doteq' : '\U00002250',
|
||||
'\\simeq' : '\U00002243',
|
||||
'\\approx' : '\U00002248',
|
||||
'\\asymp' : '\U0000224d',
|
||||
'\\cong' : '\U00002245',
|
||||
'\\equiv' : '\U00002261',
|
||||
'\\Join' : '\U000022c8',
|
||||
'\\bowtie' : '\U00002a1d',
|
||||
'\\prec' : '\U0000227a',
|
||||
'\\succ' : '\U0000227b',
|
||||
'\\preceq' : '\U0000227c',
|
||||
'\\succeq' : '\U0000227d',
|
||||
'\\parallel' : '\U00002225',
|
||||
'\\mid' : '\U000000a6',
|
||||
'\\pm' : '\U000000b1',
|
||||
'\\mp' : '\U00002213',
|
||||
'\\times' : '\U000000d7',
|
||||
'\\div' : '\U000000f7',
|
||||
'\\cdot' : '\U000022c5',
|
||||
'\\star' : '\U000022c6',
|
||||
'\\circ' : '\U00002218',
|
||||
'\\dagger' : '\U00002020',
|
||||
'\\ddagger' : '\U00002021',
|
||||
'\\lhd' : '\U000022b2',
|
||||
'\\rhd' : '\U000022b3',
|
||||
'\\unlhd' : '\U000022b4',
|
||||
'\\unrhd' : '\U000022b5',
|
||||
'\\triangleleft' : '\U000025c3',
|
||||
'\\triangleright' : '\U000025b9',
|
||||
'\\triangle' : '\U000025b3',
|
||||
'\\triangleq' : '\U0000225c',
|
||||
'\\oplus' : '\U00002295',
|
||||
'\\bigoplus' : '\U00002a01',
|
||||
'\\otimes' : '\U00002297',
|
||||
'\\bigotimes' : '\U00002a02',
|
||||
'\\odot' : '\U00002299',
|
||||
'\\bigodot' : '\U00002a00',
|
||||
'\\ominus' : '\U00002296',
|
||||
'\\oslash' : '\U00002298',
|
||||
'\\dots' : '\U00002026',
|
||||
'\\cdots' : '\U000022ef',
|
||||
'\\sum' : '\U00002211',
|
||||
'\\prod' : '\U0000220f',
|
||||
'\\coprod' : '\U00002210',
|
||||
'\\infty' : '\U0000221e',
|
||||
'\\int' : '\U0000222b',
|
||||
'\\oint' : '\U0000222e',
|
||||
'\\clubsuit' : '\U00002663',
|
||||
'\\diamondsuit' : '\U00002662',
|
||||
'\\heartsuit' : '\U00002661',
|
||||
'\\spadesuit' : '\U00002660',
|
||||
'\\aleph' : '\U00002135',
|
||||
'\\emptyset' : '\U00002205',
|
||||
'\\nabla' : '\U00002207',
|
||||
'\\partial' : '\U00002202',
|
||||
'\\flat' : '\U0000266d',
|
||||
'\\natural' : '\U0000266e',
|
||||
'\\sharp' : '\U0000266f',
|
||||
'\\angle' : '\U00002220',
|
||||
'\\copyright' : '\U000000a9',
|
||||
'\\textregistered' : '\U000000ae',
|
||||
'\\textonequarter' : '\U000000bc',
|
||||
'\\textonehalf' : '\U000000bd',
|
||||
'\\textthreequarters' : '\U000000be',
|
||||
'\\textordfeminine' : '\U000000aa',
|
||||
'\\textordmasculine' : '\U000000ba',
|
||||
'\\euro' : '\U000020ac',
|
||||
'\\pounds' : '\U000000a3',
|
||||
'\\yen' : '\U000000a5',
|
||||
'\\textcent' : '\U000000a2',
|
||||
'\\textcurrency' : '\U000000a4',
|
||||
'\\textdegree' : '\U000000b0',
|
||||
}
|
||||
|
||||
isabelle_symbols = {
|
||||
'\\<zero>' : '\U0001d7ec',
|
||||
'\\<one>' : '\U0001d7ed',
|
||||
'\\<two>' : '\U0001d7ee',
|
||||
'\\<three>' : '\U0001d7ef',
|
||||
'\\<four>' : '\U0001d7f0',
|
||||
'\\<five>' : '\U0001d7f1',
|
||||
'\\<six>' : '\U0001d7f2',
|
||||
'\\<seven>' : '\U0001d7f3',
|
||||
'\\<eight>' : '\U0001d7f4',
|
||||
'\\<nine>' : '\U0001d7f5',
|
||||
'\\<A>' : '\U0001d49c',
|
||||
'\\<B>' : '\U0000212c',
|
||||
'\\<C>' : '\U0001d49e',
|
||||
'\\<D>' : '\U0001d49f',
|
||||
'\\<E>' : '\U00002130',
|
||||
'\\<F>' : '\U00002131',
|
||||
'\\<G>' : '\U0001d4a2',
|
||||
'\\<H>' : '\U0000210b',
|
||||
'\\<I>' : '\U00002110',
|
||||
'\\<J>' : '\U0001d4a5',
|
||||
'\\<K>' : '\U0001d4a6',
|
||||
'\\<L>' : '\U00002112',
|
||||
'\\<M>' : '\U00002133',
|
||||
'\\<N>' : '\U0001d4a9',
|
||||
'\\<O>' : '\U0001d4aa',
|
||||
'\\<P>' : '\U0001d4ab',
|
||||
'\\<Q>' : '\U0001d4ac',
|
||||
'\\<R>' : '\U0000211b',
|
||||
'\\<S>' : '\U0001d4ae',
|
||||
'\\<T>' : '\U0001d4af',
|
||||
'\\<U>' : '\U0001d4b0',
|
||||
'\\<V>' : '\U0001d4b1',
|
||||
'\\<W>' : '\U0001d4b2',
|
||||
'\\<X>' : '\U0001d4b3',
|
||||
'\\<Y>' : '\U0001d4b4',
|
||||
'\\<Z>' : '\U0001d4b5',
|
||||
'\\<a>' : '\U0001d5ba',
|
||||
'\\<b>' : '\U0001d5bb',
|
||||
'\\<c>' : '\U0001d5bc',
|
||||
'\\<d>' : '\U0001d5bd',
|
||||
'\\<e>' : '\U0001d5be',
|
||||
'\\<f>' : '\U0001d5bf',
|
||||
'\\<g>' : '\U0001d5c0',
|
||||
'\\<h>' : '\U0001d5c1',
|
||||
'\\<i>' : '\U0001d5c2',
|
||||
'\\<j>' : '\U0001d5c3',
|
||||
'\\<k>' : '\U0001d5c4',
|
||||
'\\<l>' : '\U0001d5c5',
|
||||
'\\<m>' : '\U0001d5c6',
|
||||
'\\<n>' : '\U0001d5c7',
|
||||
'\\<o>' : '\U0001d5c8',
|
||||
'\\<p>' : '\U0001d5c9',
|
||||
'\\<q>' : '\U0001d5ca',
|
||||
'\\<r>' : '\U0001d5cb',
|
||||
'\\<s>' : '\U0001d5cc',
|
||||
'\\<t>' : '\U0001d5cd',
|
||||
'\\<u>' : '\U0001d5ce',
|
||||
'\\<v>' : '\U0001d5cf',
|
||||
'\\<w>' : '\U0001d5d0',
|
||||
'\\<x>' : '\U0001d5d1',
|
||||
'\\<y>' : '\U0001d5d2',
|
||||
'\\<z>' : '\U0001d5d3',
|
||||
'\\<AA>' : '\U0001d504',
|
||||
'\\<BB>' : '\U0001d505',
|
||||
'\\<CC>' : '\U0000212d',
|
||||
'\\<DD>' : '\U0001d507',
|
||||
'\\<EE>' : '\U0001d508',
|
||||
'\\<FF>' : '\U0001d509',
|
||||
'\\<GG>' : '\U0001d50a',
|
||||
'\\<HH>' : '\U0000210c',
|
||||
'\\<II>' : '\U00002111',
|
||||
'\\<JJ>' : '\U0001d50d',
|
||||
'\\<KK>' : '\U0001d50e',
|
||||
'\\<LL>' : '\U0001d50f',
|
||||
'\\<MM>' : '\U0001d510',
|
||||
'\\<NN>' : '\U0001d511',
|
||||
'\\<OO>' : '\U0001d512',
|
||||
'\\<PP>' : '\U0001d513',
|
||||
'\\<QQ>' : '\U0001d514',
|
||||
'\\<RR>' : '\U0000211c',
|
||||
'\\<SS>' : '\U0001d516',
|
||||
'\\<TT>' : '\U0001d517',
|
||||
'\\<UU>' : '\U0001d518',
|
||||
'\\<VV>' : '\U0001d519',
|
||||
'\\<WW>' : '\U0001d51a',
|
||||
'\\<XX>' : '\U0001d51b',
|
||||
'\\<YY>' : '\U0001d51c',
|
||||
'\\<ZZ>' : '\U00002128',
|
||||
'\\<aa>' : '\U0001d51e',
|
||||
'\\<bb>' : '\U0001d51f',
|
||||
'\\<cc>' : '\U0001d520',
|
||||
'\\<dd>' : '\U0001d521',
|
||||
'\\<ee>' : '\U0001d522',
|
||||
'\\<ff>' : '\U0001d523',
|
||||
'\\<gg>' : '\U0001d524',
|
||||
'\\<hh>' : '\U0001d525',
|
||||
'\\<ii>' : '\U0001d526',
|
||||
'\\<jj>' : '\U0001d527',
|
||||
'\\<kk>' : '\U0001d528',
|
||||
'\\<ll>' : '\U0001d529',
|
||||
'\\<mm>' : '\U0001d52a',
|
||||
'\\<nn>' : '\U0001d52b',
|
||||
'\\<oo>' : '\U0001d52c',
|
||||
'\\<pp>' : '\U0001d52d',
|
||||
'\\<qq>' : '\U0001d52e',
|
||||
'\\<rr>' : '\U0001d52f',
|
||||
'\\<ss>' : '\U0001d530',
|
||||
'\\<tt>' : '\U0001d531',
|
||||
'\\<uu>' : '\U0001d532',
|
||||
'\\<vv>' : '\U0001d533',
|
||||
'\\<ww>' : '\U0001d534',
|
||||
'\\<xx>' : '\U0001d535',
|
||||
'\\<yy>' : '\U0001d536',
|
||||
'\\<zz>' : '\U0001d537',
|
||||
'\\<alpha>' : '\U000003b1',
|
||||
'\\<beta>' : '\U000003b2',
|
||||
'\\<gamma>' : '\U000003b3',
|
||||
'\\<delta>' : '\U000003b4',
|
||||
'\\<epsilon>' : '\U000003b5',
|
||||
'\\<zeta>' : '\U000003b6',
|
||||
'\\<eta>' : '\U000003b7',
|
||||
'\\<theta>' : '\U000003b8',
|
||||
'\\<iota>' : '\U000003b9',
|
||||
'\\<kappa>' : '\U000003ba',
|
||||
'\\<lambda>' : '\U000003bb',
|
||||
'\\<mu>' : '\U000003bc',
|
||||
'\\<nu>' : '\U000003bd',
|
||||
'\\<xi>' : '\U000003be',
|
||||
'\\<pi>' : '\U000003c0',
|
||||
'\\<rho>' : '\U000003c1',
|
||||
'\\<sigma>' : '\U000003c3',
|
||||
'\\<tau>' : '\U000003c4',
|
||||
'\\<upsilon>' : '\U000003c5',
|
||||
'\\<phi>' : '\U000003c6',
|
||||
'\\<chi>' : '\U000003c7',
|
||||
'\\<psi>' : '\U000003c8',
|
||||
'\\<omega>' : '\U000003c9',
|
||||
'\\<Gamma>' : '\U00000393',
|
||||
'\\<Delta>' : '\U00000394',
|
||||
'\\<Theta>' : '\U00000398',
|
||||
'\\<Lambda>' : '\U0000039b',
|
||||
'\\<Xi>' : '\U0000039e',
|
||||
'\\<Pi>' : '\U000003a0',
|
||||
'\\<Sigma>' : '\U000003a3',
|
||||
'\\<Upsilon>' : '\U000003a5',
|
||||
'\\<Phi>' : '\U000003a6',
|
||||
'\\<Psi>' : '\U000003a8',
|
||||
'\\<Omega>' : '\U000003a9',
|
||||
'\\<bool>' : '\U0001d539',
|
||||
'\\<complex>' : '\U00002102',
|
||||
'\\<nat>' : '\U00002115',
|
||||
'\\<rat>' : '\U0000211a',
|
||||
'\\<real>' : '\U0000211d',
|
||||
'\\<int>' : '\U00002124',
|
||||
'\\<leftarrow>' : '\U00002190',
|
||||
'\\<longleftarrow>' : '\U000027f5',
|
||||
'\\<rightarrow>' : '\U00002192',
|
||||
'\\<longrightarrow>' : '\U000027f6',
|
||||
'\\<Leftarrow>' : '\U000021d0',
|
||||
'\\<Longleftarrow>' : '\U000027f8',
|
||||
'\\<Rightarrow>' : '\U000021d2',
|
||||
'\\<Longrightarrow>' : '\U000027f9',
|
||||
'\\<leftrightarrow>' : '\U00002194',
|
||||
'\\<longleftrightarrow>' : '\U000027f7',
|
||||
'\\<Leftrightarrow>' : '\U000021d4',
|
||||
'\\<Longleftrightarrow>' : '\U000027fa',
|
||||
'\\<mapsto>' : '\U000021a6',
|
||||
'\\<longmapsto>' : '\U000027fc',
|
||||
'\\<midarrow>' : '\U00002500',
|
||||
'\\<Midarrow>' : '\U00002550',
|
||||
'\\<hookleftarrow>' : '\U000021a9',
|
||||
'\\<hookrightarrow>' : '\U000021aa',
|
||||
'\\<leftharpoondown>' : '\U000021bd',
|
||||
'\\<rightharpoondown>' : '\U000021c1',
|
||||
'\\<leftharpoonup>' : '\U000021bc',
|
||||
'\\<rightharpoonup>' : '\U000021c0',
|
||||
'\\<rightleftharpoons>' : '\U000021cc',
|
||||
'\\<leadsto>' : '\U0000219d',
|
||||
'\\<downharpoonleft>' : '\U000021c3',
|
||||
'\\<downharpoonright>' : '\U000021c2',
|
||||
'\\<upharpoonleft>' : '\U000021bf',
|
||||
'\\<upharpoonright>' : '\U000021be',
|
||||
'\\<restriction>' : '\U000021be',
|
||||
'\\<Colon>' : '\U00002237',
|
||||
'\\<up>' : '\U00002191',
|
||||
'\\<Up>' : '\U000021d1',
|
||||
'\\<down>' : '\U00002193',
|
||||
'\\<Down>' : '\U000021d3',
|
||||
'\\<updown>' : '\U00002195',
|
||||
'\\<Updown>' : '\U000021d5',
|
||||
'\\<langle>' : '\U000027e8',
|
||||
'\\<rangle>' : '\U000027e9',
|
||||
'\\<lceil>' : '\U00002308',
|
||||
'\\<rceil>' : '\U00002309',
|
||||
'\\<lfloor>' : '\U0000230a',
|
||||
'\\<rfloor>' : '\U0000230b',
|
||||
'\\<lparr>' : '\U00002987',
|
||||
'\\<rparr>' : '\U00002988',
|
||||
'\\<lbrakk>' : '\U000027e6',
|
||||
'\\<rbrakk>' : '\U000027e7',
|
||||
'\\<lbrace>' : '\U00002983',
|
||||
'\\<rbrace>' : '\U00002984',
|
||||
'\\<guillemotleft>' : '\U000000ab',
|
||||
'\\<guillemotright>' : '\U000000bb',
|
||||
'\\<bottom>' : '\U000022a5',
|
||||
'\\<top>' : '\U000022a4',
|
||||
'\\<and>' : '\U00002227',
|
||||
'\\<And>' : '\U000022c0',
|
||||
'\\<or>' : '\U00002228',
|
||||
'\\<Or>' : '\U000022c1',
|
||||
'\\<forall>' : '\U00002200',
|
||||
'\\<exists>' : '\U00002203',
|
||||
'\\<nexists>' : '\U00002204',
|
||||
'\\<not>' : '\U000000ac',
|
||||
'\\<box>' : '\U000025a1',
|
||||
'\\<diamond>' : '\U000025c7',
|
||||
'\\<turnstile>' : '\U000022a2',
|
||||
'\\<Turnstile>' : '\U000022a8',
|
||||
'\\<tturnstile>' : '\U000022a9',
|
||||
'\\<TTurnstile>' : '\U000022ab',
|
||||
'\\<stileturn>' : '\U000022a3',
|
||||
'\\<surd>' : '\U0000221a',
|
||||
'\\<le>' : '\U00002264',
|
||||
'\\<ge>' : '\U00002265',
|
||||
'\\<lless>' : '\U0000226a',
|
||||
'\\<ggreater>' : '\U0000226b',
|
||||
'\\<lesssim>' : '\U00002272',
|
||||
'\\<greatersim>' : '\U00002273',
|
||||
'\\<lessapprox>' : '\U00002a85',
|
||||
'\\<greaterapprox>' : '\U00002a86',
|
||||
'\\<in>' : '\U00002208',
|
||||
'\\<notin>' : '\U00002209',
|
||||
'\\<subset>' : '\U00002282',
|
||||
'\\<supset>' : '\U00002283',
|
||||
'\\<subseteq>' : '\U00002286',
|
||||
'\\<supseteq>' : '\U00002287',
|
||||
'\\<sqsubset>' : '\U0000228f',
|
||||
'\\<sqsupset>' : '\U00002290',
|
||||
'\\<sqsubseteq>' : '\U00002291',
|
||||
'\\<sqsupseteq>' : '\U00002292',
|
||||
'\\<inter>' : '\U00002229',
|
||||
'\\<Inter>' : '\U000022c2',
|
||||
'\\<union>' : '\U0000222a',
|
||||
'\\<Union>' : '\U000022c3',
|
||||
'\\<squnion>' : '\U00002294',
|
||||
'\\<Squnion>' : '\U00002a06',
|
||||
'\\<sqinter>' : '\U00002293',
|
||||
'\\<Sqinter>' : '\U00002a05',
|
||||
'\\<setminus>' : '\U00002216',
|
||||
'\\<propto>' : '\U0000221d',
|
||||
'\\<uplus>' : '\U0000228e',
|
||||
'\\<Uplus>' : '\U00002a04',
|
||||
'\\<noteq>' : '\U00002260',
|
||||
'\\<sim>' : '\U0000223c',
|
||||
'\\<doteq>' : '\U00002250',
|
||||
'\\<simeq>' : '\U00002243',
|
||||
'\\<approx>' : '\U00002248',
|
||||
'\\<asymp>' : '\U0000224d',
|
||||
'\\<cong>' : '\U00002245',
|
||||
'\\<smile>' : '\U00002323',
|
||||
'\\<equiv>' : '\U00002261',
|
||||
'\\<frown>' : '\U00002322',
|
||||
'\\<Join>' : '\U000022c8',
|
||||
'\\<bowtie>' : '\U00002a1d',
|
||||
'\\<prec>' : '\U0000227a',
|
||||
'\\<succ>' : '\U0000227b',
|
||||
'\\<preceq>' : '\U0000227c',
|
||||
'\\<succeq>' : '\U0000227d',
|
||||
'\\<parallel>' : '\U00002225',
|
||||
'\\<bar>' : '\U000000a6',
|
||||
'\\<plusminus>' : '\U000000b1',
|
||||
'\\<minusplus>' : '\U00002213',
|
||||
'\\<times>' : '\U000000d7',
|
||||
'\\<div>' : '\U000000f7',
|
||||
'\\<cdot>' : '\U000022c5',
|
||||
'\\<star>' : '\U000022c6',
|
||||
'\\<bullet>' : '\U00002219',
|
||||
'\\<circ>' : '\U00002218',
|
||||
'\\<dagger>' : '\U00002020',
|
||||
'\\<ddagger>' : '\U00002021',
|
||||
'\\<lhd>' : '\U000022b2',
|
||||
'\\<rhd>' : '\U000022b3',
|
||||
'\\<unlhd>' : '\U000022b4',
|
||||
'\\<unrhd>' : '\U000022b5',
|
||||
'\\<triangleleft>' : '\U000025c3',
|
||||
'\\<triangleright>' : '\U000025b9',
|
||||
'\\<triangle>' : '\U000025b3',
|
||||
'\\<triangleq>' : '\U0000225c',
|
||||
'\\<oplus>' : '\U00002295',
|
||||
'\\<Oplus>' : '\U00002a01',
|
||||
'\\<otimes>' : '\U00002297',
|
||||
'\\<Otimes>' : '\U00002a02',
|
||||
'\\<odot>' : '\U00002299',
|
||||
'\\<Odot>' : '\U00002a00',
|
||||
'\\<ominus>' : '\U00002296',
|
||||
'\\<oslash>' : '\U00002298',
|
||||
'\\<dots>' : '\U00002026',
|
||||
'\\<cdots>' : '\U000022ef',
|
||||
'\\<Sum>' : '\U00002211',
|
||||
'\\<Prod>' : '\U0000220f',
|
||||
'\\<Coprod>' : '\U00002210',
|
||||
'\\<infinity>' : '\U0000221e',
|
||||
'\\<integral>' : '\U0000222b',
|
||||
'\\<ointegral>' : '\U0000222e',
|
||||
'\\<clubsuit>' : '\U00002663',
|
||||
'\\<diamondsuit>' : '\U00002662',
|
||||
'\\<heartsuit>' : '\U00002661',
|
||||
'\\<spadesuit>' : '\U00002660',
|
||||
'\\<aleph>' : '\U00002135',
|
||||
'\\<emptyset>' : '\U00002205',
|
||||
'\\<nabla>' : '\U00002207',
|
||||
'\\<partial>' : '\U00002202',
|
||||
'\\<flat>' : '\U0000266d',
|
||||
'\\<natural>' : '\U0000266e',
|
||||
'\\<sharp>' : '\U0000266f',
|
||||
'\\<angle>' : '\U00002220',
|
||||
'\\<copyright>' : '\U000000a9',
|
||||
'\\<registered>' : '\U000000ae',
|
||||
'\\<hyphen>' : '\U000000ad',
|
||||
'\\<inverse>' : '\U000000af',
|
||||
'\\<onequarter>' : '\U000000bc',
|
||||
'\\<onehalf>' : '\U000000bd',
|
||||
'\\<threequarters>' : '\U000000be',
|
||||
'\\<ordfeminine>' : '\U000000aa',
|
||||
'\\<ordmasculine>' : '\U000000ba',
|
||||
'\\<section>' : '\U000000a7',
|
||||
'\\<paragraph>' : '\U000000b6',
|
||||
'\\<exclamdown>' : '\U000000a1',
|
||||
'\\<questiondown>' : '\U000000bf',
|
||||
'\\<euro>' : '\U000020ac',
|
||||
'\\<pounds>' : '\U000000a3',
|
||||
'\\<yen>' : '\U000000a5',
|
||||
'\\<cent>' : '\U000000a2',
|
||||
'\\<currency>' : '\U000000a4',
|
||||
'\\<degree>' : '\U000000b0',
|
||||
'\\<amalg>' : '\U00002a3f',
|
||||
'\\<mho>' : '\U00002127',
|
||||
'\\<lozenge>' : '\U000025ca',
|
||||
'\\<wp>' : '\U00002118',
|
||||
'\\<wrong>' : '\U00002240',
|
||||
'\\<struct>' : '\U000022c4',
|
||||
'\\<acute>' : '\U000000b4',
|
||||
'\\<index>' : '\U00000131',
|
||||
'\\<dieresis>' : '\U000000a8',
|
||||
'\\<cedilla>' : '\U000000b8',
|
||||
'\\<hungarumlaut>' : '\U000002dd',
|
||||
'\\<some>' : '\U000003f5',
|
||||
'\\<newline>' : '\U000023ce',
|
||||
'\\<open>' : '\U00002039',
|
||||
'\\<close>' : '\U0000203a',
|
||||
'\\<here>' : '\U00002302',
|
||||
'\\<^sub>' : '\U000021e9',
|
||||
'\\<^sup>' : '\U000021e7',
|
||||
'\\<^bold>' : '\U00002759',
|
||||
'\\<^bsub>' : '\U000021d8',
|
||||
'\\<^esub>' : '\U000021d9',
|
||||
'\\<^bsup>' : '\U000021d7',
|
||||
'\\<^esup>' : '\U000021d6',
|
||||
}
|
||||
|
||||
lang_map = {'isabelle' : isabelle_symbols, 'latex' : latex_symbols}
|
||||
|
||||
def __init__(self, **options):
|
||||
Filter.__init__(self, **options)
|
||||
lang = get_choice_opt(options, 'lang',
|
||||
['isabelle', 'latex'], 'isabelle')
|
||||
self.symbols = self.lang_map[lang]
|
||||
|
||||
def filter(self, lexer, stream):
|
||||
for ttype, value in stream:
|
||||
if value in self.symbols:
|
||||
yield ttype, self.symbols[value]
|
||||
else:
|
||||
yield ttype, value
|
||||
|
||||
|
||||
class KeywordCaseFilter(Filter):
|
||||
"""Convert keywords to lowercase or uppercase or capitalize them, which
|
||||
means first letter uppercase, rest lowercase.
|
||||
|
||||
This can be useful e.g. if you highlight Pascal code and want to adapt the
|
||||
code to your styleguide.
|
||||
|
||||
Options accepted:
|
||||
|
||||
`case` : string
|
||||
The casing to convert keywords to. Must be one of ``'lower'``,
|
||||
``'upper'`` or ``'capitalize'``. The default is ``'lower'``.
|
||||
"""
|
||||
|
||||
def __init__(self, **options):
|
||||
Filter.__init__(self, **options)
|
||||
case = get_choice_opt(options, 'case',
|
||||
['lower', 'upper', 'capitalize'], 'lower')
|
||||
self.convert = getattr(str, case)
|
||||
|
||||
def filter(self, lexer, stream):
|
||||
for ttype, value in stream:
|
||||
if ttype in Keyword:
|
||||
yield ttype, self.convert(value)
|
||||
else:
|
||||
yield ttype, value
|
||||
|
||||
|
||||
class NameHighlightFilter(Filter):
|
||||
"""Highlight a normal Name (and Name.*) token with a different token type.
|
||||
|
||||
Example::
|
||||
|
||||
filter = NameHighlightFilter(
|
||||
names=['foo', 'bar', 'baz'],
|
||||
tokentype=Name.Function,
|
||||
)
|
||||
|
||||
This would highlight the names "foo", "bar" and "baz"
|
||||
as functions. `Name.Function` is the default token type.
|
||||
|
||||
Options accepted:
|
||||
|
||||
`names` : list of strings
|
||||
A list of names that should be given the different token type.
|
||||
There is no default.
|
||||
`tokentype` : TokenType or string
|
||||
A token type or a string containing a token type name that is
|
||||
used for highlighting the strings in `names`. The default is
|
||||
`Name.Function`.
|
||||
"""
|
||||
|
||||
def __init__(self, **options):
|
||||
Filter.__init__(self, **options)
|
||||
self.names = set(get_list_opt(options, 'names', []))
|
||||
tokentype = options.get('tokentype')
|
||||
if tokentype:
|
||||
self.tokentype = string_to_tokentype(tokentype)
|
||||
else:
|
||||
self.tokentype = Name.Function
|
||||
|
||||
def filter(self, lexer, stream):
|
||||
for ttype, value in stream:
|
||||
if ttype in Name and value in self.names:
|
||||
yield self.tokentype, value
|
||||
else:
|
||||
yield ttype, value
|
||||
|
||||
|
||||
class ErrorToken(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class RaiseOnErrorTokenFilter(Filter):
|
||||
"""Raise an exception when the lexer generates an error token.
|
||||
|
||||
Options accepted:
|
||||
|
||||
`excclass` : Exception class
|
||||
The exception class to raise.
|
||||
The default is `pygments.filters.ErrorToken`.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
"""
|
||||
|
||||
def __init__(self, **options):
|
||||
Filter.__init__(self, **options)
|
||||
self.exception = options.get('excclass', ErrorToken)
|
||||
try:
|
||||
# issubclass() will raise TypeError if first argument is not a class
|
||||
if not issubclass(self.exception, Exception):
|
||||
raise TypeError
|
||||
except TypeError:
|
||||
raise OptionError('excclass option is not an exception class')
|
||||
|
||||
def filter(self, lexer, stream):
|
||||
for ttype, value in stream:
|
||||
if ttype is Error:
|
||||
raise self.exception(value)
|
||||
yield ttype, value
|
||||
|
||||
|
||||
class VisibleWhitespaceFilter(Filter):
|
||||
"""Convert tabs, newlines and/or spaces to visible characters.
|
||||
|
||||
Options accepted:
|
||||
|
||||
`spaces` : string or bool
|
||||
If this is a one-character string, spaces will be replaces by this string.
|
||||
If it is another true value, spaces will be replaced by ``·`` (unicode
|
||||
MIDDLE DOT). If it is a false value, spaces will not be replaced. The
|
||||
default is ``False``.
|
||||
`tabs` : string or bool
|
||||
The same as for `spaces`, but the default replacement character is ``»``
|
||||
(unicode RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK). The default value
|
||||
is ``False``. Note: this will not work if the `tabsize` option for the
|
||||
lexer is nonzero, as tabs will already have been expanded then.
|
||||
`tabsize` : int
|
||||
If tabs are to be replaced by this filter (see the `tabs` option), this
|
||||
is the total number of characters that a tab should be expanded to.
|
||||
The default is ``8``.
|
||||
`newlines` : string or bool
|
||||
The same as for `spaces`, but the default replacement character is ``¶``
|
||||
(unicode PILCROW SIGN). The default value is ``False``.
|
||||
`wstokentype` : bool
|
||||
If true, give whitespace the special `Whitespace` token type. This allows
|
||||
styling the visible whitespace differently (e.g. greyed out), but it can
|
||||
disrupt background colors. The default is ``True``.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
"""
|
||||
|
||||
def __init__(self, **options):
|
||||
Filter.__init__(self, **options)
|
||||
for name, default in [('spaces', '·'),
|
||||
('tabs', '»'),
|
||||
('newlines', '¶')]:
|
||||
opt = options.get(name, False)
|
||||
if isinstance(opt, str) and len(opt) == 1:
|
||||
setattr(self, name, opt)
|
||||
else:
|
||||
setattr(self, name, (opt and default or ''))
|
||||
tabsize = get_int_opt(options, 'tabsize', 8)
|
||||
if self.tabs:
|
||||
self.tabs += ' ' * (tabsize - 1)
|
||||
if self.newlines:
|
||||
self.newlines += '\n'
|
||||
self.wstt = get_bool_opt(options, 'wstokentype', True)
|
||||
|
||||
def filter(self, lexer, stream):
|
||||
if self.wstt:
|
||||
spaces = self.spaces or ' '
|
||||
tabs = self.tabs or '\t'
|
||||
newlines = self.newlines or '\n'
|
||||
regex = re.compile(r'\s')
|
||||
|
||||
def replacefunc(wschar):
|
||||
if wschar == ' ':
|
||||
return spaces
|
||||
elif wschar == '\t':
|
||||
return tabs
|
||||
elif wschar == '\n':
|
||||
return newlines
|
||||
return wschar
|
||||
|
||||
for ttype, value in stream:
|
||||
yield from _replace_special(ttype, value, regex, Whitespace,
|
||||
replacefunc)
|
||||
else:
|
||||
spaces, tabs, newlines = self.spaces, self.tabs, self.newlines
|
||||
# simpler processing
|
||||
for ttype, value in stream:
|
||||
if spaces:
|
||||
value = value.replace(' ', spaces)
|
||||
if tabs:
|
||||
value = value.replace('\t', tabs)
|
||||
if newlines:
|
||||
value = value.replace('\n', newlines)
|
||||
yield ttype, value
|
||||
|
||||
|
||||
class GobbleFilter(Filter):
|
||||
"""Gobbles source code lines (eats initial characters).
|
||||
|
||||
This filter drops the first ``n`` characters off every line of code. This
|
||||
may be useful when the source code fed to the lexer is indented by a fixed
|
||||
amount of space that isn't desired in the output.
|
||||
|
||||
Options accepted:
|
||||
|
||||
`n` : int
|
||||
The number of characters to gobble.
|
||||
|
||||
.. versionadded:: 1.2
|
||||
"""
|
||||
def __init__(self, **options):
|
||||
Filter.__init__(self, **options)
|
||||
self.n = get_int_opt(options, 'n', 0)
|
||||
|
||||
def gobble(self, value, left):
|
||||
if left < len(value):
|
||||
return value[left:], 0
|
||||
else:
|
||||
return '', left - len(value)
|
||||
|
||||
def filter(self, lexer, stream):
|
||||
n = self.n
|
||||
left = n # How many characters left to gobble.
|
||||
for ttype, value in stream:
|
||||
# Remove ``left`` tokens from first line, ``n`` from all others.
|
||||
parts = value.split('\n')
|
||||
(parts[0], left) = self.gobble(parts[0], left)
|
||||
for i in range(1, len(parts)):
|
||||
(parts[i], left) = self.gobble(parts[i], n)
|
||||
value = '\n'.join(parts)
|
||||
|
||||
if value != '':
|
||||
yield ttype, value
|
||||
|
||||
|
||||
class TokenMergeFilter(Filter):
|
||||
"""Merges consecutive tokens with the same token type in the output
|
||||
stream of a lexer.
|
||||
|
||||
.. versionadded:: 1.2
|
||||
"""
|
||||
def __init__(self, **options):
|
||||
Filter.__init__(self, **options)
|
||||
|
||||
def filter(self, lexer, stream):
|
||||
current_type = None
|
||||
current_value = None
|
||||
for ttype, value in stream:
|
||||
if ttype is current_type:
|
||||
current_value += value
|
||||
else:
|
||||
if current_type is not None:
|
||||
yield current_type, current_value
|
||||
current_type = ttype
|
||||
current_value = value
|
||||
if current_type is not None:
|
||||
yield current_type, current_value
|
||||
|
||||
|
||||
FILTERS = {
|
||||
'codetagify': CodeTagFilter,
|
||||
'keywordcase': KeywordCaseFilter,
|
||||
'highlight': NameHighlightFilter,
|
||||
'raiseonerror': RaiseOnErrorTokenFilter,
|
||||
'whitespace': VisibleWhitespaceFilter,
|
||||
'gobble': GobbleFilter,
|
||||
'tokenmerge': TokenMergeFilter,
|
||||
'symbols': SymbolFilter,
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
"""
|
||||
pygments.formatter
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Base formatter class.
|
||||
|
||||
:copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import codecs
|
||||
|
||||
from pip._vendor.pygments.util import get_bool_opt
|
||||
from pip._vendor.pygments.styles import get_style_by_name
|
||||
|
||||
__all__ = ['Formatter']
|
||||
|
||||
|
||||
def _lookup_style(style):
|
||||
if isinstance(style, str):
|
||||
return get_style_by_name(style)
|
||||
return style
|
||||
|
||||
|
||||
class Formatter:
|
||||
"""
|
||||
Converts a token stream to text.
|
||||
|
||||
Options accepted:
|
||||
|
||||
``style``
|
||||
The style to use, can be a string or a Style subclass
|
||||
(default: "default"). Not used by e.g. the
|
||||
TerminalFormatter.
|
||||
``full``
|
||||
Tells the formatter to output a "full" document, i.e.
|
||||
a complete self-contained document. This doesn't have
|
||||
any effect for some formatters (default: false).
|
||||
``title``
|
||||
If ``full`` is true, the title that should be used to
|
||||
caption the document (default: '').
|
||||
``encoding``
|
||||
If given, must be an encoding name. This will be used to
|
||||
convert the Unicode token strings to byte strings in the
|
||||
output. If it is "" or None, Unicode strings will be written
|
||||
to the output file, which most file-like objects do not
|
||||
support (default: None).
|
||||
``outencoding``
|
||||
Overrides ``encoding`` if given.
|
||||
"""
|
||||
|
||||
#: Name of the formatter
|
||||
name = None
|
||||
|
||||
#: Shortcuts for the formatter
|
||||
aliases = []
|
||||
|
||||
#: fn match rules
|
||||
filenames = []
|
||||
|
||||
#: If True, this formatter outputs Unicode strings when no encoding
|
||||
#: option is given.
|
||||
unicodeoutput = True
|
||||
|
||||
def __init__(self, **options):
|
||||
self.style = _lookup_style(options.get('style', 'default'))
|
||||
self.full = get_bool_opt(options, 'full', False)
|
||||
self.title = options.get('title', '')
|
||||
self.encoding = options.get('encoding', None) or None
|
||||
if self.encoding in ('guess', 'chardet'):
|
||||
# can happen for e.g. pygmentize -O encoding=guess
|
||||
self.encoding = 'utf-8'
|
||||
self.encoding = options.get('outencoding') or self.encoding
|
||||
self.options = options
|
||||
|
||||
def get_style_defs(self, arg=''):
|
||||
"""
|
||||
Return the style definitions for the current style as a string.
|
||||
|
||||
``arg`` is an additional argument whose meaning depends on the
|
||||
formatter used. Note that ``arg`` can also be a list or tuple
|
||||
for some formatters like the html formatter.
|
||||
"""
|
||||
return ''
|
||||
|
||||
def format(self, tokensource, outfile):
|
||||
"""
|
||||
Format ``tokensource``, an iterable of ``(tokentype, tokenstring)``
|
||||
tuples and write it into ``outfile``.
|
||||
"""
|
||||
if self.encoding:
|
||||
# wrap the outfile in a StreamWriter
|
||||
outfile = codecs.lookup(self.encoding)[3](outfile)
|
||||
return self.format_unencoded(tokensource, outfile)
|
|
@ -0,0 +1,153 @@
|
|||
"""
|
||||
pygments.formatters
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Pygments formatters.
|
||||
|
||||
:copyright: Copyright 2006-2021 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):
|
||||
"""Lookup and instantiate a formatter by alias.
|
||||
|
||||
Raises ClassNotFound if not found.
|
||||
"""
|
||||
cls = find_formatter_class(_alias)
|
||||
if cls is None:
|
||||
raise ClassNotFound("no formatter found for name %r" % _alias)
|
||||
return cls(**options)
|
||||
|
||||
|
||||
def load_formatter_from_file(filename, formattername="CustomFormatter",
|
||||
**options):
|
||||
"""Load a formatter from a file.
|
||||
|
||||
This method expects a file located relative to the current working
|
||||
directory, which contains a class named CustomFormatter. By default,
|
||||
it expects the Formatter to be named CustomFormatter; you can specify
|
||||
your own class name as the second argument to this function.
|
||||
|
||||
Users should be very careful with the input, because this method
|
||||
is equivalent to running eval on the input file.
|
||||
|
||||
Raises ClassNotFound if there are any problems importing 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('no valid %s class found in %s' %
|
||||
(formattername, filename))
|
||||
formatter_class = custom_namespace[formattername]
|
||||
# And finally instantiate it with the options
|
||||
return formatter_class(**options)
|
||||
except OSError as err:
|
||||
raise ClassNotFound('cannot read %s: %s' % (filename, err))
|
||||
except ClassNotFound:
|
||||
raise
|
||||
except Exception as err:
|
||||
raise ClassNotFound('error when loading custom formatter: %s' % err)
|
||||
|
||||
|
||||
def get_formatter_for_filename(fn, **options):
|
||||
"""Lookup and instantiate a formatter by filename pattern.
|
||||
|
||||
Raises ClassNotFound if not 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 cls in find_plugin_formatters():
|
||||
for filename in cls.filenames:
|
||||
if _fn_matches(fn, filename):
|
||||
return cls(**options)
|
||||
raise ClassNotFound("no formatter found for file name %r" % fn)
|
||||
|
||||
|
||||
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
|
|
@ -0,0 +1,83 @@
|
|||
"""
|
||||
pygments.formatters._mapping
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Formatter mapping definitions. This file is generated by itself. Everytime
|
||||
you change something on a builtin formatter definition, run this script from
|
||||
the formatters folder to update it.
|
||||
|
||||
Do not alter the FORMATTERS dictionary by hand.
|
||||
|
||||
:copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
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.'),
|
||||
'HtmlFormatter': ('pygments.formatters.html', 'HTML', ('html',), ('*.html', '*.htm'), "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."),
|
||||
'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.')
|
||||
}
|
||||
|
||||
if __name__ == '__main__': # pragma: no cover
|
||||
import sys
|
||||
import os
|
||||
|
||||
# lookup formatters
|
||||
found_formatters = []
|
||||
imports = []
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
from pip._vendor.pygments.util import docstring_headline
|
||||
|
||||
for root, dirs, files in os.walk('.'):
|
||||
for filename in files:
|
||||
if filename.endswith('.py') and not filename.startswith('_'):
|
||||
module_name = 'pygments.formatters%s.%s' % (
|
||||
root[1:].replace('/', '.'), filename[:-3])
|
||||
print(module_name)
|
||||
module = __import__(module_name, None, None, [''])
|
||||
for formatter_name in module.__all__:
|
||||
formatter = getattr(module, formatter_name)
|
||||
found_formatters.append(
|
||||
'%r: %r' % (formatter_name,
|
||||
(module_name,
|
||||
formatter.name,
|
||||
tuple(formatter.aliases),
|
||||
tuple(formatter.filenames),
|
||||
docstring_headline(formatter))))
|
||||
# sort them to make the diff minimal
|
||||
found_formatters.sort()
|
||||
|
||||
# extract useful sourcecode from this file
|
||||
with open(__file__) as fp:
|
||||
content = fp.read()
|
||||
# replace crnl to nl for Windows.
|
||||
#
|
||||
# Note that, originally, contributers should keep nl of master
|
||||
# repository, for example by using some kind of automatic
|
||||
# management EOL, like `EolExtension
|
||||
# <https://www.mercurial-scm.org/wiki/EolExtension>`.
|
||||
content = content.replace("\r\n", "\n")
|
||||
header = content[:content.find('FORMATTERS = {')]
|
||||
footer = content[content.find("if __name__ == '__main__':"):]
|
||||
|
||||
# write new file
|
||||
with open(__file__, 'w') as fp:
|
||||
fp.write(header)
|
||||
fp.write('FORMATTERS = {\n %s\n}\n\n' % ',\n '.join(found_formatters))
|
||||
fp.write(footer)
|
||||
|
||||
print ('=== %d formatters processed.' % len(found_formatters))
|
|
@ -0,0 +1,108 @@
|
|||
"""
|
||||
pygments.formatters.bbcode
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
BBcode formatter.
|
||||
|
||||
:copyright: Copyright 2006-2021 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=#%s]' % 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')
|
|
@ -0,0 +1,967 @@
|
|||
"""
|
||||
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 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):
|
||||
"""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)
|
|
@ -0,0 +1,641 @@
|
|||
"""
|
||||
pygments.formatters.img
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Formatter for Pixmap output.
|
||||
|
||||
:copyright: Copyright 2006-2021 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
|
||||
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', "%s:style=%s" % (name, 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('No usable fonts named: "%s"' %
|
||||
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('No usable fonts named: "%s"' %
|
||||
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 = '%s%s%s' % (basename, style and ' '+style, suffix)
|
||||
val, _ = _winreg.QueryValueEx(key, valname)
|
||||
return val
|
||||
except OSError:
|
||||
continue
|
||||
else:
|
||||
if fail:
|
||||
raise FontNotFound('Font %s (%s) not found in registry' %
|
||||
(basename, styles[0]))
|
||||
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.fonts['NORMAL'].getsize('M')
|
||||
|
||||
def get_text_size(self, text):
|
||||
"""
|
||||
Get the text size(width, height).
|
||||
"""
|
||||
return self.fonts['NORMAL'].getsize(text)
|
||||
|
||||
def get_font(self, bold, oblique):
|
||||
"""
|
||||
Get the font based on bold and italic flags.
|
||||
"""
|
||||
if bold and oblique:
|
||||
return self.fonts['BOLDITALIC']
|
||||
elif bold:
|
||||
return self.fonts['BOLD']
|
||||
elif oblique:
|
||||
return self.fonts['ITALIC']
|
||||
else:
|
||||
return self.fonts['NORMAL']
|
||||
|
||||
|
||||
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.
|
||||
|
||||
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, temp_hight = 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:
|
||||
text_size = draw.textsize(text=value, font=font)
|
||||
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'
|
|
@ -0,0 +1,181 @@
|
|||
"""
|
||||
pygments.formatters.irc
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Formatter for IRC output
|
||||
|
||||
:copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
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):
|
||||
self._lineno += 1
|
||||
outfile.write("\n%04d: " % self._lineno)
|
||||
|
||||
def _format_unencoded_with_lineno(self, tokensource, outfile):
|
||||
self._write_lineno(outfile)
|
||||
|
||||
for ttype, value in tokensource:
|
||||
if value.endswith("\n"):
|
||||
self._write_lineno(outfile)
|
||||
value = value[:-1]
|
||||
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]:
|
||||
self._write_lineno(outfile)
|
||||
if line:
|
||||
outfile.write(ircformat(color, line[:-1]))
|
||||
if spl[-1]:
|
||||
outfile.write(ircformat(color, spl[-1]))
|
||||
else:
|
||||
outfile.write(value)
|
||||
|
||||
outfile.write("\n")
|
||||
|
||||
def format_unencoded(self, tokensource, outfile):
|
||||
if self.linenos:
|
||||
self._format_unencoded_with_lineno(tokensource, outfile)
|
||||
return
|
||||
|
||||
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')
|
||||
if spl[-1]:
|
||||
outfile.write(ircformat(color, spl[-1]))
|
||||
else:
|
||||
outfile.write(value)
|
|
@ -0,0 +1,511 @@
|
|||
"""
|
||||
pygments.formatters.latex
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Formatter for LaTeX fancyvrb output.
|
||||
|
||||
:copyright: Copyright 2006-2021 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', r'\%sZbs{}' % commandprefix). \
|
||||
replace('\x01', r'\%sZob{}' % commandprefix). \
|
||||
replace('\x02', r'\%sZcb{}' % commandprefix). \
|
||||
replace('^', r'\%sZca{}' % commandprefix). \
|
||||
replace('_', r'\%sZus{}' % commandprefix). \
|
||||
replace('&', r'\%sZam{}' % commandprefix). \
|
||||
replace('<', r'\%sZlt{}' % commandprefix). \
|
||||
replace('>', r'\%sZgt{}' % commandprefix). \
|
||||
replace('#', r'\%sZsh{}' % commandprefix). \
|
||||
replace('%', r'\%sZpc{}' % commandprefix). \
|
||||
replace('$', r'\%sZdl{}' % commandprefix). \
|
||||
replace('-', r'\%sZhy{}' % commandprefix). \
|
||||
replace("'", r'\%sZsq{}' % commandprefix). \
|
||||
replace('"', r'\%sZdq{}' % commandprefix). \
|
||||
replace('~', r'\%sZti{}' % commandprefix)
|
||||
|
||||
|
||||
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}
|
||||
|
||||
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:
|
||||
|
||||
`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.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]{%s}{##1}}' %
|
||||
rgbcolor(ndef['color']))
|
||||
if ndef['border']:
|
||||
cmndef += (r'\def\$$@bc##1{{\setlength{\fboxsep}{\string -\fboxrule}'
|
||||
r'\fcolorbox[rgb]{%s}{%s}{\strut ##1}}}' %
|
||||
(rgbcolor(ndef['border']),
|
||||
rgbcolor(ndef['bgcolor'])))
|
||||
elif ndef['bgcolor']:
|
||||
cmndef += (r'\def\$$@bc##1{{\setlength{\fboxsep}{0pt}'
|
||||
r'\colorbox[rgb]{%s}{\strut ##1}}}' %
|
||||
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(r'\@namedef{%s@tok@%s}{%s}' % (cp, 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()
|
||||
|
||||
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("\\%s{%s}{%s}" % (cp, styleval, line))
|
||||
outfile.write('\n')
|
||||
if spl[-1]:
|
||||
outfile.write("\\%s{%s}{%s}" % (cp, styleval, spl[-1]))
|
||||
else:
|
||||
outfile.write(value)
|
||||
|
||||
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
|
|
@ -0,0 +1,161 @@
|
|||
"""
|
||||
pygments.formatters.other
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Other formatters: NullFormatter, RawTokenFormatter.
|
||||
|
||||
:copyright: Copyright 2006-2021 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("Invalid color %r specified" %
|
||||
self.error_color)
|
||||
|
||||
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('%s(%s, %r),\n' % (indentation, ttype, value))
|
||||
|
||||
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()
|
|
@ -0,0 +1,83 @@
|
|||
"""
|
||||
pygments.formatters.pangomarkup
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Formatter for Pango markup output.
|
||||
|
||||
:copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
from pip._vendor.pygments.formatter import Formatter
|
||||
|
||||
|
||||
__all__ = ['PangoMarkupFormatter']
|
||||
|
||||
|
||||
_escape_table = {
|
||||
ord('&'): '&',
|
||||
ord('<'): '<',
|
||||
}
|
||||
|
||||
|
||||
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="#%s">' % 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>')
|
|
@ -0,0 +1,146 @@
|
|||
"""
|
||||
pygments.formatters.rtf
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A formatter that generates RTF files.
|
||||
|
||||
:copyright: Copyright 2006-2021 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_int_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
|
||||
"""
|
||||
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)
|
||||
|
||||
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\n')
|
||||
|
||||
def format_unencoded(self, tokensource, outfile):
|
||||
# rtf 1.8 header
|
||||
outfile.write('{\\rtf1\\ansi\\uc0\\deff0'
|
||||
'{\\fonttbl{\\f0\\fmodern\\fprq1\\fcharset0%s;}}'
|
||||
'{\\colortbl;' % (self.fontface and
|
||||
' ' + self._escape(self.fontface) or
|
||||
''))
|
||||
|
||||
# convert colors and save them in a mapping to access them later.
|
||||
color_mapping = {}
|
||||
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
|
||||
outfile.write('\\red%d\\green%d\\blue%d;' % (
|
||||
int(color[0:2], 16),
|
||||
int(color[2:4], 16),
|
||||
int(color[4:6], 16)
|
||||
))
|
||||
offset += 1
|
||||
outfile.write('}\\f0 ')
|
||||
if self.fontsize:
|
||||
outfile.write('\\fs%d' % self.fontsize)
|
||||
|
||||
# highlight stream
|
||||
for ttype, value in tokensource:
|
||||
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' % color_mapping[style['bgcolor']])
|
||||
if style['color']:
|
||||
buf.append('\\cf%d' % 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' %
|
||||
color_mapping[style['border']])
|
||||
start = ''.join(buf)
|
||||
if start:
|
||||
outfile.write('{%s ' % start)
|
||||
outfile.write(self._escape_text(value))
|
||||
if start:
|
||||
outfile.write('}')
|
||||
|
||||
outfile.write('}')
|
|
@ -0,0 +1,188 @@
|
|||
"""
|
||||
pygments.formatters.svg
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Formatter for SVG output.
|
||||
|
||||
:copyright: Copyright 2006-2021 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('&', '&'). \
|
||||
replace('<', '<'). \
|
||||
replace('>', '>'). \
|
||||
replace('"', '"'). \
|
||||
replace("'", ''')
|
||||
|
||||
|
||||
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 `` ``, 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:
|
||||
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('<?xml version="1.0" encoding="%s"?>\n' %
|
||||
self.encoding)
|
||||
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('<g font-family="%s" font-size="%s">\n' %
|
||||
(self.fontfamily, self.fontsize))
|
||||
|
||||
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('<text x="%s" y="%s" %s text-anchor="end">%s</text>' %
|
||||
(x+self.linenowidth,y,counter_style,counter))
|
||||
line_x += self.linenowidth + self.ystep
|
||||
counter += 1
|
||||
|
||||
outfile.write('<text x="%s" y="%s" xml:space="preserve">' % (line_x, y))
|
||||
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(' ', ' ')
|
||||
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('<text x="%s" y="%s" text-anchor="end" %s>%s</text>' %
|
||||
(x+self.linenowidth,y,counter_style,counter))
|
||||
|
||||
counter += 1
|
||||
outfile.write('<text x="%s" y="%s" ' 'xml:space="preserve">' % (line_x,y))
|
||||
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
|
|
@ -0,0 +1,129 @@
|
|||
"""
|
||||
pygments.formatters.terminal
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Formatter for terminal output with ANSI sequences.
|
||||
|
||||
:copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
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")
|
|
@ -0,0 +1,340 @@
|
|||
"""
|
||||
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-2021 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...
|
||||
|
||||
import sys
|
||||
|
||||
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[:-1]
|
||||
# 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())
|
|
@ -0,0 +1,874 @@
|
|||
"""
|
||||
pygments.lexer
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Base lexer classes.
|
||||
|
||||
:copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
|
||||
from pip._vendor.pygments.filter import apply_filters, Filter
|
||||
from pip._vendor.pygments.filters import get_filter_by_name
|
||||
from pip._vendor.pygments.token import Error, Text, Other, _TokenType
|
||||
from pip._vendor.pygments.util import get_bool_opt, get_int_opt, get_list_opt, \
|
||||
make_analysator, Future, guess_decode
|
||||
from pip._vendor.pygments.regexopt import regex_opt
|
||||
|
||||
__all__ = ['Lexer', 'RegexLexer', 'ExtendedRegexLexer', 'DelegatingLexer',
|
||||
'LexerContext', 'include', 'inherit', 'bygroups', 'using', 'this',
|
||||
'default', 'words']
|
||||
|
||||
|
||||
_encoding_map = [(b'\xef\xbb\xbf', 'utf-8'),
|
||||
(b'\xff\xfe\0\0', 'utf-32'),
|
||||
(b'\0\0\xfe\xff', 'utf-32be'),
|
||||
(b'\xff\xfe', 'utf-16'),
|
||||
(b'\xfe\xff', 'utf-16be')]
|
||||
|
||||
_default_analyse = staticmethod(lambda x: 0.0)
|
||||
|
||||
|
||||
class LexerMeta(type):
|
||||
"""
|
||||
This metaclass automagically converts ``analyse_text`` methods into
|
||||
static methods which always return float values.
|
||||
"""
|
||||
|
||||
def __new__(mcs, name, bases, d):
|
||||
if 'analyse_text' in d:
|
||||
d['analyse_text'] = make_analysator(d['analyse_text'])
|
||||
return type.__new__(mcs, name, bases, d)
|
||||
|
||||
|
||||
class Lexer(metaclass=LexerMeta):
|
||||
"""
|
||||
Lexer for a specific language.
|
||||
|
||||
Basic options recognized:
|
||||
``stripnl``
|
||||
Strip leading and trailing newlines from the input (default: True).
|
||||
``stripall``
|
||||
Strip all leading and trailing whitespace from the input
|
||||
(default: False).
|
||||
``ensurenl``
|
||||
Make sure that the input ends with a newline (default: True). This
|
||||
is required for some lexers that consume input linewise.
|
||||
|
||||
.. versionadded:: 1.3
|
||||
|
||||
``tabsize``
|
||||
If given and greater than 0, expand tabs in the input (default: 0).
|
||||
``encoding``
|
||||
If given, must be an encoding name. This encoding will be used to
|
||||
convert the input string to Unicode, if it is not already a Unicode
|
||||
string (default: ``'guess'``, which uses a simple UTF-8 / Locale /
|
||||
Latin1 detection. Can also be ``'chardet'`` to use the chardet
|
||||
library, if it is installed.
|
||||
``inencoding``
|
||||
Overrides the ``encoding`` if given.
|
||||
"""
|
||||
|
||||
#: Name of the lexer
|
||||
name = None
|
||||
|
||||
#: Shortcuts for the lexer
|
||||
aliases = []
|
||||
|
||||
#: File name globs
|
||||
filenames = []
|
||||
|
||||
#: Secondary file name globs
|
||||
alias_filenames = []
|
||||
|
||||
#: MIME types
|
||||
mimetypes = []
|
||||
|
||||
#: Priority, should multiple lexers match and no content is provided
|
||||
priority = 0
|
||||
|
||||
def __init__(self, **options):
|
||||
self.options = options
|
||||
self.stripnl = get_bool_opt(options, 'stripnl', True)
|
||||
self.stripall = get_bool_opt(options, 'stripall', False)
|
||||
self.ensurenl = get_bool_opt(options, 'ensurenl', True)
|
||||
self.tabsize = get_int_opt(options, 'tabsize', 0)
|
||||
self.encoding = options.get('encoding', 'guess')
|
||||
self.encoding = options.get('inencoding') or self.encoding
|
||||
self.filters = []
|
||||
for filter_ in get_list_opt(options, 'filters', ()):
|
||||
self.add_filter(filter_)
|
||||
|
||||
def __repr__(self):
|
||||
if self.options:
|
||||
return '<pygments.lexers.%s with %r>' % (self.__class__.__name__,
|
||||
self.options)
|
||||
else:
|
||||
return '<pygments.lexers.%s>' % self.__class__.__name__
|
||||
|
||||
def add_filter(self, filter_, **options):
|
||||
"""
|
||||
Add a new stream filter to this lexer.
|
||||
"""
|
||||
if not isinstance(filter_, Filter):
|
||||
filter_ = get_filter_by_name(filter_, **options)
|
||||
self.filters.append(filter_)
|
||||
|
||||
def analyse_text(text):
|
||||
"""
|
||||
Has to return a float between ``0`` and ``1`` that indicates
|
||||
if a lexer wants to highlight this text. Used by ``guess_lexer``.
|
||||
If this method returns ``0`` it won't highlight it in any case, if
|
||||
it returns ``1`` highlighting with this lexer is guaranteed.
|
||||
|
||||
The `LexerMeta` metaclass automatically wraps this function so
|
||||
that it works like a static method (no ``self`` or ``cls``
|
||||
parameter) and the return value is automatically converted to
|
||||
`float`. If the return value is an object that is boolean `False`
|
||||
it's the same as if the return values was ``0.0``.
|
||||
"""
|
||||
|
||||
def get_tokens(self, text, unfiltered=False):
|
||||
"""
|
||||
Return an iterable of (tokentype, value) pairs generated from
|
||||
`text`. If `unfiltered` is set to `True`, the filtering mechanism
|
||||
is bypassed even if filters are defined.
|
||||
|
||||
Also preprocess the text, i.e. expand tabs and strip it if
|
||||
wanted and applies registered filters.
|
||||
"""
|
||||
if not isinstance(text, str):
|
||||
if self.encoding == 'guess':
|
||||
text, _ = guess_decode(text)
|
||||
elif self.encoding == 'chardet':
|
||||
try:
|
||||
from pip._vendor import chardet
|
||||
except ImportError as e:
|
||||
raise ImportError('To enable chardet encoding guessing, '
|
||||
'please install the chardet library '
|
||||
'from http://chardet.feedparser.org/') from e
|
||||
# check for BOM first
|
||||
decoded = None
|
||||
for bom, encoding in _encoding_map:
|
||||
if text.startswith(bom):
|
||||
decoded = text[len(bom):].decode(encoding, 'replace')
|
||||
break
|
||||
# no BOM found, so use chardet
|
||||
if decoded is None:
|
||||
enc = chardet.detect(text[:1024]) # Guess using first 1KB
|
||||
decoded = text.decode(enc.get('encoding') or 'utf-8',
|
||||
'replace')
|
||||
text = decoded
|
||||
else:
|
||||
text = text.decode(self.encoding)
|
||||
if text.startswith('\ufeff'):
|
||||
text = text[len('\ufeff'):]
|
||||
else:
|
||||
if text.startswith('\ufeff'):
|
||||
text = text[len('\ufeff'):]
|
||||
|
||||
# text now *is* a unicode string
|
||||
text = text.replace('\r\n', '\n')
|
||||
text = text.replace('\r', '\n')
|
||||
if self.stripall:
|
||||
text = text.strip()
|
||||
elif self.stripnl:
|
||||
text = text.strip('\n')
|
||||
if self.tabsize > 0:
|
||||
text = text.expandtabs(self.tabsize)
|
||||
if self.ensurenl and not text.endswith('\n'):
|
||||
text += '\n'
|
||||
|
||||
def streamer():
|
||||
for _, t, v in self.get_tokens_unprocessed(text):
|
||||
yield t, v
|
||||
stream = streamer()
|
||||
if not unfiltered:
|
||||
stream = apply_filters(stream, self.filters, self)
|
||||
return stream
|
||||
|
||||
def get_tokens_unprocessed(self, text):
|
||||
"""
|
||||
Return an iterable of (index, tokentype, value) pairs where "index"
|
||||
is the starting position of the token within the input text.
|
||||
|
||||
In subclasses, implement this method as a generator to
|
||||
maximize effectiveness.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class DelegatingLexer(Lexer):
|
||||
"""
|
||||
This lexer takes two lexer as arguments. A root lexer and
|
||||
a language lexer. First everything is scanned using the language
|
||||
lexer, afterwards all ``Other`` tokens are lexed using the root
|
||||
lexer.
|
||||
|
||||
The lexers from the ``template`` lexer package use this base lexer.
|
||||
"""
|
||||
|
||||
def __init__(self, _root_lexer, _language_lexer, _needle=Other, **options):
|
||||
self.root_lexer = _root_lexer(**options)
|
||||
self.language_lexer = _language_lexer(**options)
|
||||
self.needle = _needle
|
||||
Lexer.__init__(self, **options)
|
||||
|
||||
def get_tokens_unprocessed(self, text):
|
||||
buffered = ''
|
||||
insertions = []
|
||||
lng_buffer = []
|
||||
for i, t, v in self.language_lexer.get_tokens_unprocessed(text):
|
||||
if t is self.needle:
|
||||
if lng_buffer:
|
||||
insertions.append((len(buffered), lng_buffer))
|
||||
lng_buffer = []
|
||||
buffered += v
|
||||
else:
|
||||
lng_buffer.append((i, t, v))
|
||||
if lng_buffer:
|
||||
insertions.append((len(buffered), lng_buffer))
|
||||
return do_insertions(insertions,
|
||||
self.root_lexer.get_tokens_unprocessed(buffered))
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# RegexLexer and ExtendedRegexLexer
|
||||
#
|
||||
|
||||
|
||||
class include(str): # pylint: disable=invalid-name
|
||||
"""
|
||||
Indicates that a state should include rules from another state.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class _inherit:
|
||||
"""
|
||||
Indicates the a state should inherit from its superclass.
|
||||
"""
|
||||
def __repr__(self):
|
||||
return 'inherit'
|
||||
|
||||
inherit = _inherit() # pylint: disable=invalid-name
|
||||
|
||||
|
||||
class combined(tuple): # pylint: disable=invalid-name
|
||||
"""
|
||||
Indicates a state combined from multiple states.
|
||||
"""
|
||||
|
||||
def __new__(cls, *args):
|
||||
return tuple.__new__(cls, args)
|
||||
|
||||
def __init__(self, *args):
|
||||
# tuple.__init__ doesn't do anything
|
||||
pass
|
||||
|
||||
|
||||
class _PseudoMatch:
|
||||
"""
|
||||
A pseudo match object constructed from a string.
|
||||
"""
|
||||
|
||||
def __init__(self, start, text):
|
||||
self._text = text
|
||||
self._start = start
|
||||
|
||||
def start(self, arg=None):
|
||||
return self._start
|
||||
|
||||
def end(self, arg=None):
|
||||
return self._start + len(self._text)
|
||||
|
||||
def group(self, arg=None):
|
||||
if arg:
|
||||
raise IndexError('No such group')
|
||||
return self._text
|
||||
|
||||
def groups(self):
|
||||
return (self._text,)
|
||||
|
||||
def groupdict(self):
|
||||
return {}
|
||||
|
||||
|
||||
def bygroups(*args):
|
||||
"""
|
||||
Callback that yields multiple actions for each group in the match.
|
||||
"""
|
||||
def callback(lexer, match, ctx=None):
|
||||
for i, action in enumerate(args):
|
||||
if action is None:
|
||||
continue
|
||||
elif type(action) is _TokenType:
|
||||
data = match.group(i + 1)
|
||||
if data:
|
||||
yield match.start(i + 1), action, data
|
||||
else:
|
||||
data = match.group(i + 1)
|
||||
if data is not None:
|
||||
if ctx:
|
||||
ctx.pos = match.start(i + 1)
|
||||
for item in action(lexer,
|
||||
_PseudoMatch(match.start(i + 1), data), ctx):
|
||||
if item:
|
||||
yield item
|
||||
if ctx:
|
||||
ctx.pos = match.end()
|
||||
return callback
|
||||
|
||||
|
||||
class _This:
|
||||
"""
|
||||
Special singleton used for indicating the caller class.
|
||||
Used by ``using``.
|
||||
"""
|
||||
|
||||
this = _This()
|
||||
|
||||
|
||||
def using(_other, **kwargs):
|
||||
"""
|
||||
Callback that processes the match with a different lexer.
|
||||
|
||||
The keyword arguments are forwarded to the lexer, except `state` which
|
||||
is handled separately.
|
||||
|
||||
`state` specifies the state that the new lexer will start in, and can
|
||||
be an enumerable such as ('root', 'inline', 'string') or a simple
|
||||
string which is assumed to be on top of the root state.
|
||||
|
||||
Note: For that to work, `_other` must not be an `ExtendedRegexLexer`.
|
||||
"""
|
||||
gt_kwargs = {}
|
||||
if 'state' in kwargs:
|
||||
s = kwargs.pop('state')
|
||||
if isinstance(s, (list, tuple)):
|
||||
gt_kwargs['stack'] = s
|
||||
else:
|
||||
gt_kwargs['stack'] = ('root', s)
|
||||
|
||||
if _other is this:
|
||||
def callback(lexer, match, ctx=None):
|
||||
# if keyword arguments are given the callback
|
||||
# function has to create a new lexer instance
|
||||
if kwargs:
|
||||
# XXX: cache that somehow
|
||||
kwargs.update(lexer.options)
|
||||
lx = lexer.__class__(**kwargs)
|
||||
else:
|
||||
lx = lexer
|
||||
s = match.start()
|
||||
for i, t, v in lx.get_tokens_unprocessed(match.group(), **gt_kwargs):
|
||||
yield i + s, t, v
|
||||
if ctx:
|
||||
ctx.pos = match.end()
|
||||
else:
|
||||
def callback(lexer, match, ctx=None):
|
||||
# XXX: cache that somehow
|
||||
kwargs.update(lexer.options)
|
||||
lx = _other(**kwargs)
|
||||
|
||||
s = match.start()
|
||||
for i, t, v in lx.get_tokens_unprocessed(match.group(), **gt_kwargs):
|
||||
yield i + s, t, v
|
||||
if ctx:
|
||||
ctx.pos = match.end()
|
||||
return callback
|
||||
|
||||
|
||||
class default:
|
||||
"""
|
||||
Indicates a state or state action (e.g. #pop) to apply.
|
||||
For example default('#pop') is equivalent to ('', Token, '#pop')
|
||||
Note that state tuples may be used as well.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
def __init__(self, state):
|
||||
self.state = state
|
||||
|
||||
|
||||
class words(Future):
|
||||
"""
|
||||
Indicates a list of literal words that is transformed into an optimized
|
||||
regex that matches any of the words.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
def __init__(self, words, prefix='', suffix=''):
|
||||
self.words = words
|
||||
self.prefix = prefix
|
||||
self.suffix = suffix
|
||||
|
||||
def get(self):
|
||||
return regex_opt(self.words, prefix=self.prefix, suffix=self.suffix)
|
||||
|
||||
|
||||
class RegexLexerMeta(LexerMeta):
|
||||
"""
|
||||
Metaclass for RegexLexer, creates the self._tokens attribute from
|
||||
self.tokens on the first instantiation.
|
||||
"""
|
||||
|
||||
def _process_regex(cls, regex, rflags, state):
|
||||
"""Preprocess the regular expression component of a token definition."""
|
||||
if isinstance(regex, Future):
|
||||
regex = regex.get()
|
||||
return re.compile(regex, rflags).match
|
||||
|
||||
def _process_token(cls, token):
|
||||
"""Preprocess the token component of a token definition."""
|
||||
assert type(token) is _TokenType or callable(token), \
|
||||
'token type must be simple type or callable, not %r' % (token,)
|
||||
return token
|
||||
|
||||
def _process_new_state(cls, new_state, unprocessed, processed):
|
||||
"""Preprocess the state transition action of a token definition."""
|
||||
if isinstance(new_state, str):
|
||||
# an existing state
|
||||
if new_state == '#pop':
|
||||
return -1
|
||||
elif new_state in unprocessed:
|
||||
return (new_state,)
|
||||
elif new_state == '#push':
|
||||
return new_state
|
||||
elif new_state[:5] == '#pop:':
|
||||
return -int(new_state[5:])
|
||||
else:
|
||||
assert False, 'unknown new state %r' % new_state
|
||||
elif isinstance(new_state, combined):
|
||||
# combine a new state from existing ones
|
||||
tmp_state = '_tmp_%d' % cls._tmpname
|
||||
cls._tmpname += 1
|
||||
itokens = []
|
||||
for istate in new_state:
|
||||
assert istate != new_state, 'circular state ref %r' % istate
|
||||
itokens.extend(cls._process_state(unprocessed,
|
||||
processed, istate))
|
||||
processed[tmp_state] = itokens
|
||||
return (tmp_state,)
|
||||
elif isinstance(new_state, tuple):
|
||||
# push more than one state
|
||||
for istate in new_state:
|
||||
assert (istate in unprocessed or
|
||||
istate in ('#pop', '#push')), \
|
||||
'unknown new state ' + istate
|
||||
return new_state
|
||||
else:
|
||||
assert False, 'unknown new state def %r' % new_state
|
||||
|
||||
def _process_state(cls, unprocessed, processed, state):
|
||||
"""Preprocess a single state definition."""
|
||||
assert type(state) is str, "wrong state name %r" % state
|
||||
assert state[0] != '#', "invalid state name %r" % state
|
||||
if state in processed:
|
||||
return processed[state]
|
||||
tokens = processed[state] = []
|
||||
rflags = cls.flags
|
||||
for tdef in unprocessed[state]:
|
||||
if isinstance(tdef, include):
|
||||
# it's a state reference
|
||||
assert tdef != state, "circular state reference %r" % state
|
||||
tokens.extend(cls._process_state(unprocessed, processed,
|
||||
str(tdef)))
|
||||
continue
|
||||
if isinstance(tdef, _inherit):
|
||||
# should be processed already, but may not in the case of:
|
||||
# 1. the state has no counterpart in any parent
|
||||
# 2. the state includes more than one 'inherit'
|
||||
continue
|
||||
if isinstance(tdef, default):
|
||||
new_state = cls._process_new_state(tdef.state, unprocessed, processed)
|
||||
tokens.append((re.compile('').match, None, new_state))
|
||||
continue
|
||||
|
||||
assert type(tdef) is tuple, "wrong rule def %r" % tdef
|
||||
|
||||
try:
|
||||
rex = cls._process_regex(tdef[0], rflags, state)
|
||||
except Exception as err:
|
||||
raise ValueError("uncompilable regex %r in state %r of %r: %s" %
|
||||
(tdef[0], state, cls, err)) from err
|
||||
|
||||
token = cls._process_token(tdef[1])
|
||||
|
||||
if len(tdef) == 2:
|
||||
new_state = None
|
||||
else:
|
||||
new_state = cls._process_new_state(tdef[2],
|
||||
unprocessed, processed)
|
||||
|
||||
tokens.append((rex, token, new_state))
|
||||
return tokens
|
||||
|
||||
def process_tokendef(cls, name, tokendefs=None):
|
||||
"""Preprocess a dictionary of token definitions."""
|
||||
processed = cls._all_tokens[name] = {}
|
||||
tokendefs = tokendefs or cls.tokens[name]
|
||||
for state in list(tokendefs):
|
||||
cls._process_state(tokendefs, processed, state)
|
||||
return processed
|
||||
|
||||
def get_tokendefs(cls):
|
||||
"""
|
||||
Merge tokens from superclasses in MRO order, returning a single tokendef
|
||||
dictionary.
|
||||
|
||||
Any state that is not defined by a subclass will be inherited
|
||||
automatically. States that *are* defined by subclasses will, by
|
||||
default, override that state in the superclass. If a subclass wishes to
|
||||
inherit definitions from a superclass, it can use the special value
|
||||
"inherit", which will cause the superclass' state definition to be
|
||||
included at that point in the state.
|
||||
"""
|
||||
tokens = {}
|
||||
inheritable = {}
|
||||
for c in cls.__mro__:
|
||||
toks = c.__dict__.get('tokens', {})
|
||||
|
||||
for state, items in toks.items():
|
||||
curitems = tokens.get(state)
|
||||
if curitems is None:
|
||||
# N.b. because this is assigned by reference, sufficiently
|
||||
# deep hierarchies are processed incrementally (e.g. for
|
||||
# A(B), B(C), C(RegexLexer), B will be premodified so X(B)
|
||||
# will not see any inherits in B).
|
||||
tokens[state] = items
|
||||
try:
|
||||
inherit_ndx = items.index(inherit)
|
||||
except ValueError:
|
||||
continue
|
||||
inheritable[state] = inherit_ndx
|
||||
continue
|
||||
|
||||
inherit_ndx = inheritable.pop(state, None)
|
||||
if inherit_ndx is None:
|
||||
continue
|
||||
|
||||
# Replace the "inherit" value with the items
|
||||
curitems[inherit_ndx:inherit_ndx+1] = items
|
||||
try:
|
||||
# N.b. this is the index in items (that is, the superclass
|
||||
# copy), so offset required when storing below.
|
||||
new_inh_ndx = items.index(inherit)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
inheritable[state] = inherit_ndx + new_inh_ndx
|
||||
|
||||
return tokens
|
||||
|
||||
def __call__(cls, *args, **kwds):
|
||||
"""Instantiate cls after preprocessing its token definitions."""
|
||||
if '_tokens' not in cls.__dict__:
|
||||
cls._all_tokens = {}
|
||||
cls._tmpname = 0
|
||||
if hasattr(cls, 'token_variants') and cls.token_variants:
|
||||
# don't process yet
|
||||
pass
|
||||
else:
|
||||
cls._tokens = cls.process_tokendef('', cls.get_tokendefs())
|
||||
|
||||
return type.__call__(cls, *args, **kwds)
|
||||
|
||||
|
||||
class RegexLexer(Lexer, metaclass=RegexLexerMeta):
|
||||
"""
|
||||
Base for simple stateful regular expression-based lexers.
|
||||
Simplifies the lexing process so that you need only
|
||||
provide a list of states and regular expressions.
|
||||
"""
|
||||
|
||||
#: Flags for compiling the regular expressions.
|
||||
#: Defaults to MULTILINE.
|
||||
flags = re.MULTILINE
|
||||
|
||||
#: Dict of ``{'state': [(regex, tokentype, new_state), ...], ...}``
|
||||
#:
|
||||
#: The initial state is 'root'.
|
||||
#: ``new_state`` can be omitted to signify no state transition.
|
||||
#: If it is a string, the state is pushed on the stack and changed.
|
||||
#: If it is a tuple of strings, all states are pushed on the stack and
|
||||
#: the current state will be the topmost.
|
||||
#: It can also be ``combined('state1', 'state2', ...)``
|
||||
#: to signify a new, anonymous state combined from the rules of two
|
||||
#: or more existing ones.
|
||||
#: Furthermore, it can be '#pop' to signify going back one step in
|
||||
#: the state stack, or '#push' to push the current state on the stack
|
||||
#: again.
|
||||
#:
|
||||
#: The tuple can also be replaced with ``include('state')``, in which
|
||||
#: case the rules from the state named by the string are included in the
|
||||
#: current one.
|
||||
tokens = {}
|
||||
|
||||
def get_tokens_unprocessed(self, text, stack=('root',)):
|
||||
"""
|
||||
Split ``text`` into (tokentype, text) pairs.
|
||||
|
||||
``stack`` is the inital stack (default: ``['root']``)
|
||||
"""
|
||||
pos = 0
|
||||
tokendefs = self._tokens
|
||||
statestack = list(stack)
|
||||
statetokens = tokendefs[statestack[-1]]
|
||||
while 1:
|
||||
for rexmatch, action, new_state in statetokens:
|
||||
m = rexmatch(text, pos)
|
||||
if m:
|
||||
if action is not None:
|
||||
if type(action) is _TokenType:
|
||||
yield pos, action, m.group()
|
||||
else:
|
||||
yield from action(self, m)
|
||||
pos = m.end()
|
||||
if new_state is not None:
|
||||
# state transition
|
||||
if isinstance(new_state, tuple):
|
||||
for state in new_state:
|
||||
if state == '#pop':
|
||||
if len(statestack) > 1:
|
||||
statestack.pop()
|
||||
elif state == '#push':
|
||||
statestack.append(statestack[-1])
|
||||
else:
|
||||
statestack.append(state)
|
||||
elif isinstance(new_state, int):
|
||||
# pop, but keep at least one state on the stack
|
||||
# (random code leading to unexpected pops should
|
||||
# not allow exceptions)
|
||||
if abs(new_state) >= len(statestack):
|
||||
del statestack[1:]
|
||||
else:
|
||||
del statestack[new_state:]
|
||||
elif new_state == '#push':
|
||||
statestack.append(statestack[-1])
|
||||
else:
|
||||
assert False, "wrong state def: %r" % new_state
|
||||
statetokens = tokendefs[statestack[-1]]
|
||||
break
|
||||
else:
|
||||
# We are here only if all state tokens have been considered
|
||||
# and there was not a match on any of them.
|
||||
try:
|
||||
if text[pos] == '\n':
|
||||
# at EOL, reset state to "root"
|
||||
statestack = ['root']
|
||||
statetokens = tokendefs['root']
|
||||
yield pos, Text, '\n'
|
||||
pos += 1
|
||||
continue
|
||||
yield pos, Error, text[pos]
|
||||
pos += 1
|
||||
except IndexError:
|
||||
break
|
||||
|
||||
|
||||
class LexerContext:
|
||||
"""
|
||||
A helper object that holds lexer position data.
|
||||
"""
|
||||
|
||||
def __init__(self, text, pos, stack=None, end=None):
|
||||
self.text = text
|
||||
self.pos = pos
|
||||
self.end = end or len(text) # end=0 not supported ;-)
|
||||
self.stack = stack or ['root']
|
||||
|
||||
def __repr__(self):
|
||||
return 'LexerContext(%r, %r, %r)' % (
|
||||
self.text, self.pos, self.stack)
|
||||
|
||||
|
||||
class ExtendedRegexLexer(RegexLexer):
|
||||
"""
|
||||
A RegexLexer that uses a context object to store its state.
|
||||
"""
|
||||
|
||||
def get_tokens_unprocessed(self, text=None, context=None):
|
||||
"""
|
||||
Split ``text`` into (tokentype, text) pairs.
|
||||
If ``context`` is given, use this lexer context instead.
|
||||
"""
|
||||
tokendefs = self._tokens
|
||||
if not context:
|
||||
ctx = LexerContext(text, 0)
|
||||
statetokens = tokendefs['root']
|
||||
else:
|
||||
ctx = context
|
||||
statetokens = tokendefs[ctx.stack[-1]]
|
||||
text = ctx.text
|
||||
while 1:
|
||||
for rexmatch, action, new_state in statetokens:
|
||||
m = rexmatch(text, ctx.pos, ctx.end)
|
||||
if m:
|
||||
if action is not None:
|
||||
if type(action) is _TokenType:
|
||||
yield ctx.pos, action, m.group()
|
||||
ctx.pos = m.end()
|
||||
else:
|
||||
yield from action(self, m, ctx)
|
||||
if not new_state:
|
||||
# altered the state stack?
|
||||
statetokens = tokendefs[ctx.stack[-1]]
|
||||
# CAUTION: callback must set ctx.pos!
|
||||
if new_state is not None:
|
||||
# state transition
|
||||
if isinstance(new_state, tuple):
|
||||
for state in new_state:
|
||||
if state == '#pop':
|
||||
if len(ctx.stack) > 1:
|
||||
ctx.stack.pop()
|
||||
elif state == '#push':
|
||||
ctx.stack.append(ctx.stack[-1])
|
||||
else:
|
||||
ctx.stack.append(state)
|
||||
elif isinstance(new_state, int):
|
||||
# see RegexLexer for why this check is made
|
||||
if abs(new_state) >= len(ctx.stack):
|
||||
del ctx.state[1:]
|
||||
else:
|
||||
del ctx.stack[new_state:]
|
||||
elif new_state == '#push':
|
||||
ctx.stack.append(ctx.stack[-1])
|
||||
else:
|
||||
assert False, "wrong state def: %r" % new_state
|
||||
statetokens = tokendefs[ctx.stack[-1]]
|
||||
break
|
||||
else:
|
||||
try:
|
||||
if ctx.pos >= ctx.end:
|
||||
break
|
||||
if text[ctx.pos] == '\n':
|
||||
# at EOL, reset state to "root"
|
||||
ctx.stack = ['root']
|
||||
statetokens = tokendefs['root']
|
||||
yield ctx.pos, Text, '\n'
|
||||
ctx.pos += 1
|
||||
continue
|
||||
yield ctx.pos, Error, text[ctx.pos]
|
||||
ctx.pos += 1
|
||||
except IndexError:
|
||||
break
|
||||
|
||||
|
||||
def do_insertions(insertions, tokens):
|
||||
"""
|
||||
Helper for lexers which must combine the results of several
|
||||
sublexers.
|
||||
|
||||
``insertions`` is a list of ``(index, itokens)`` pairs.
|
||||
Each ``itokens`` iterable should be inserted at position
|
||||
``index`` into the token stream given by the ``tokens``
|
||||
argument.
|
||||
|
||||
The result is a combined token stream.
|
||||
|
||||
TODO: clean up the code here.
|
||||
"""
|
||||
insertions = iter(insertions)
|
||||
try:
|
||||
index, itokens = next(insertions)
|
||||
except StopIteration:
|
||||
# no insertions
|
||||
yield from tokens
|
||||
return
|
||||
|
||||
realpos = None
|
||||
insleft = True
|
||||
|
||||
# iterate over the token stream where we want to insert
|
||||
# the tokens from the insertion list.
|
||||
for i, t, v in tokens:
|
||||
# first iteration. store the postition of first item
|
||||
if realpos is None:
|
||||
realpos = i
|
||||
oldi = 0
|
||||
while insleft and i + len(v) >= index:
|
||||
tmpval = v[oldi:index - i]
|
||||
if tmpval:
|
||||
yield realpos, t, tmpval
|
||||
realpos += len(tmpval)
|
||||
for it_index, it_token, it_value in itokens:
|
||||
yield realpos, it_token, it_value
|
||||
realpos += len(it_value)
|
||||
oldi = index - i
|
||||
try:
|
||||
index, itokens = next(insertions)
|
||||
except StopIteration:
|
||||
insleft = False
|
||||
break # not strictly necessary
|
||||
if oldi < len(v):
|
||||
yield realpos, t, v[oldi:]
|
||||
realpos += len(v) - oldi
|
||||
|
||||
# leftover tokens
|
||||
while insleft:
|
||||
# no normal tokens, set realpos to zero
|
||||
realpos = realpos or 0
|
||||
for p, t, v in itokens:
|
||||
yield realpos, t, v
|
||||
realpos += len(v)
|
||||
try:
|
||||
index, itokens = next(insertions)
|
||||
except StopIteration:
|
||||
insleft = False
|
||||
break # not strictly necessary
|
||||
|
||||
|
||||
class ProfilingRegexLexerMeta(RegexLexerMeta):
|
||||
"""Metaclass for ProfilingRegexLexer, collects regex timing info."""
|
||||
|
||||
def _process_regex(cls, regex, rflags, state):
|
||||
if isinstance(regex, words):
|
||||
rex = regex_opt(regex.words, prefix=regex.prefix,
|
||||
suffix=regex.suffix)
|
||||
else:
|
||||
rex = regex
|
||||
compiled = re.compile(rex, rflags)
|
||||
|
||||
def match_func(text, pos, endpos=sys.maxsize):
|
||||
info = cls._prof_data[-1].setdefault((state, rex), [0, 0.0])
|
||||
t0 = time.time()
|
||||
res = compiled.match(text, pos, endpos)
|
||||
t1 = time.time()
|
||||
info[0] += 1
|
||||
info[1] += t1 - t0
|
||||
return res
|
||||
return match_func
|
||||
|
||||
|
||||
class ProfilingRegexLexer(RegexLexer, metaclass=ProfilingRegexLexerMeta):
|
||||
"""Drop-in replacement for RegexLexer that does profiling of its regexes."""
|
||||
|
||||
_prof_data = []
|
||||
_prof_sort_index = 4 # defaults to time per call
|
||||
|
||||
def get_tokens_unprocessed(self, text, stack=('root',)):
|
||||
# this needs to be a stack, since using(this) will produce nested calls
|
||||
self.__class__._prof_data.append({})
|
||||
yield from RegexLexer.get_tokens_unprocessed(self, text, stack)
|
||||
rawdata = self.__class__._prof_data.pop()
|
||||
data = sorted(((s, repr(r).strip('u\'').replace('\\\\', '\\')[:65],
|
||||
n, 1000 * t, 1000 * t / n)
|
||||
for ((s, r), (n, t)) in rawdata.items()),
|
||||
key=lambda x: x[self._prof_sort_index],
|
||||
reverse=True)
|
||||
sum_total = sum(x[3] for x in data)
|
||||
|
||||
print()
|
||||
print('Profiling result for %s lexing %d chars in %.3f ms' %
|
||||
(self.__class__.__name__, len(text), sum_total))
|
||||
print('=' * 110)
|
||||
print('%-20s %-64s ncalls tottime percall' % ('state', 'regex'))
|
||||
print('-' * 110)
|
||||
for d in data:
|
||||
print('%-20s %-65s %5d %8.4f %8.4f' % d)
|
||||
print('=' * 110)
|
|
@ -0,0 +1,341 @@
|
|||
"""
|
||||
pygments.lexers
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Pygments lexers.
|
||||
|
||||
:copyright: Copyright 2006-2021 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.lexers._mapping import LEXERS
|
||||
from pip._vendor.pygments.modeline import get_filetype_from_buffer
|
||||
from pip._vendor.pygments.plugin import find_plugin_lexers
|
||||
from pip._vendor.pygments.util import ClassNotFound, guess_decode
|
||||
|
||||
COMPAT = {
|
||||
'Python3Lexer': 'PythonLexer',
|
||||
'Python3TracebackLexer': 'PythonTracebackLexer',
|
||||
}
|
||||
|
||||
__all__ = ['get_lexer_by_name', 'get_lexer_for_filename', 'find_lexer_class',
|
||||
'guess_lexer', 'load_lexer_from_file'] + list(LEXERS) + list(COMPAT)
|
||||
|
||||
_lexer_cache = {}
|
||||
_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_lexers(module_name):
|
||||
"""Load a lexer (and all others in the module too)."""
|
||||
mod = __import__(module_name, None, None, ['__all__'])
|
||||
for lexer_name in mod.__all__:
|
||||
cls = getattr(mod, lexer_name)
|
||||
_lexer_cache[cls.name] = cls
|
||||
|
||||
|
||||
def get_all_lexers():
|
||||
"""Return a generator of tuples in the form ``(name, aliases,
|
||||
filenames, mimetypes)`` of all know lexers.
|
||||
"""
|
||||
for item in LEXERS.values():
|
||||
yield item[1:]
|
||||
for lexer in find_plugin_lexers():
|
||||
yield lexer.name, lexer.aliases, lexer.filenames, lexer.mimetypes
|
||||
|
||||
|
||||
def find_lexer_class(name):
|
||||
"""Lookup a lexer class by name.
|
||||
|
||||
Return None if not found.
|
||||
"""
|
||||
if name in _lexer_cache:
|
||||
return _lexer_cache[name]
|
||||
# lookup builtin lexers
|
||||
for module_name, lname, aliases, _, _ in LEXERS.values():
|
||||
if name == lname:
|
||||
_load_lexers(module_name)
|
||||
return _lexer_cache[name]
|
||||
# continue with lexers from setuptools entrypoints
|
||||
for cls in find_plugin_lexers():
|
||||
if cls.name == name:
|
||||
return cls
|
||||
|
||||
|
||||
def find_lexer_class_by_name(_alias):
|
||||
"""Lookup a lexer class by alias.
|
||||
|
||||
Like `get_lexer_by_name`, but does not instantiate the class.
|
||||
|
||||
.. versionadded:: 2.2
|
||||
"""
|
||||
if not _alias:
|
||||
raise ClassNotFound('no lexer for alias %r found' % _alias)
|
||||
# lookup builtin lexers
|
||||
for module_name, name, aliases, _, _ in LEXERS.values():
|
||||
if _alias.lower() in aliases:
|
||||
if name not in _lexer_cache:
|
||||
_load_lexers(module_name)
|
||||
return _lexer_cache[name]
|
||||
# continue with lexers from setuptools entrypoints
|
||||
for cls in find_plugin_lexers():
|
||||
if _alias.lower() in cls.aliases:
|
||||
return cls
|
||||
raise ClassNotFound('no lexer for alias %r found' % _alias)
|
||||
|
||||
|
||||
def get_lexer_by_name(_alias, **options):
|
||||
"""Get a lexer by an alias.
|
||||
|
||||
Raises ClassNotFound if not found.
|
||||
"""
|
||||
if not _alias:
|
||||
raise ClassNotFound('no lexer for alias %r found' % _alias)
|
||||
|
||||
# lookup builtin lexers
|
||||
for module_name, name, aliases, _, _ in LEXERS.values():
|
||||
if _alias.lower() in aliases:
|
||||
if name not in _lexer_cache:
|
||||
_load_lexers(module_name)
|
||||
return _lexer_cache[name](**options)
|
||||
# continue with lexers from setuptools entrypoints
|
||||
for cls in find_plugin_lexers():
|
||||
if _alias.lower() in cls.aliases:
|
||||
return cls(**options)
|
||||
raise ClassNotFound('no lexer for alias %r found' % _alias)
|
||||
|
||||
|
||||
def load_lexer_from_file(filename, lexername="CustomLexer", **options):
|
||||
"""Load a lexer from a file.
|
||||
|
||||
This method expects a file located relative to the current working
|
||||
directory, which contains a Lexer class. By default, it expects the
|
||||
Lexer to be name CustomLexer; you can specify your own class name
|
||||
as the second argument to this function.
|
||||
|
||||
Users should be very careful with the input, because this method
|
||||
is equivalent to running eval on the input file.
|
||||
|
||||
Raises ClassNotFound if there are any problems importing the Lexer.
|
||||
|
||||
.. 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 `lexername` from that namespace
|
||||
if lexername not in custom_namespace:
|
||||
raise ClassNotFound('no valid %s class found in %s' %
|
||||
(lexername, filename))
|
||||
lexer_class = custom_namespace[lexername]
|
||||
# And finally instantiate it with the options
|
||||
return lexer_class(**options)
|
||||
except OSError as err:
|
||||
raise ClassNotFound('cannot read %s: %s' % (filename, err))
|
||||
except ClassNotFound:
|
||||
raise
|
||||
except Exception as err:
|
||||
raise ClassNotFound('error when loading custom lexer: %s' % err)
|
||||
|
||||
|
||||
def find_lexer_class_for_filename(_fn, code=None):
|
||||
"""Get a lexer for a filename.
|
||||
|
||||
If multiple lexers match the filename pattern, use ``analyse_text()`` to
|
||||
figure out which one is more appropriate.
|
||||
|
||||
Returns None if not found.
|
||||
"""
|
||||
matches = []
|
||||
fn = basename(_fn)
|
||||
for modname, name, _, filenames, _ in LEXERS.values():
|
||||
for filename in filenames:
|
||||
if _fn_matches(fn, filename):
|
||||
if name not in _lexer_cache:
|
||||
_load_lexers(modname)
|
||||
matches.append((_lexer_cache[name], filename))
|
||||
for cls in find_plugin_lexers():
|
||||
for filename in cls.filenames:
|
||||
if _fn_matches(fn, filename):
|
||||
matches.append((cls, filename))
|
||||
|
||||
if isinstance(code, bytes):
|
||||
# decode it, since all analyse_text functions expect unicode
|
||||
code = guess_decode(code)
|
||||
|
||||
def get_rating(info):
|
||||
cls, filename = info
|
||||
# explicit patterns get a bonus
|
||||
bonus = '*' not in filename and 0.5 or 0
|
||||
# The class _always_ defines analyse_text because it's included in
|
||||
# the Lexer class. The default implementation returns None which
|
||||
# gets turned into 0.0. Run scripts/detect_missing_analyse_text.py
|
||||
# to find lexers which need it overridden.
|
||||
if code:
|
||||
return cls.analyse_text(code) + bonus, cls.__name__
|
||||
return cls.priority + bonus, cls.__name__
|
||||
|
||||
if matches:
|
||||
matches.sort(key=get_rating)
|
||||
# print "Possible lexers, after sort:", matches
|
||||
return matches[-1][0]
|
||||
|
||||
|
||||
def get_lexer_for_filename(_fn, code=None, **options):
|
||||
"""Get a lexer for a filename.
|
||||
|
||||
If multiple lexers match the filename pattern, use ``analyse_text()`` to
|
||||
figure out which one is more appropriate.
|
||||
|
||||
Raises ClassNotFound if not found.
|
||||
"""
|
||||
res = find_lexer_class_for_filename(_fn, code)
|
||||
if not res:
|
||||
raise ClassNotFound('no lexer for filename %r found' % _fn)
|
||||
return res(**options)
|
||||
|
||||
|
||||
def get_lexer_for_mimetype(_mime, **options):
|
||||
"""Get a lexer for a mimetype.
|
||||
|
||||
Raises ClassNotFound if not found.
|
||||
"""
|
||||
for modname, name, _, _, mimetypes in LEXERS.values():
|
||||
if _mime in mimetypes:
|
||||
if name not in _lexer_cache:
|
||||
_load_lexers(modname)
|
||||
return _lexer_cache[name](**options)
|
||||
for cls in find_plugin_lexers():
|
||||
if _mime in cls.mimetypes:
|
||||
return cls(**options)
|
||||
raise ClassNotFound('no lexer for mimetype %r found' % _mime)
|
||||
|
||||
|
||||
def _iter_lexerclasses(plugins=True):
|
||||
"""Return an iterator over all lexer classes."""
|
||||
for key in sorted(LEXERS):
|
||||
module_name, name = LEXERS[key][:2]
|
||||
if name not in _lexer_cache:
|
||||
_load_lexers(module_name)
|
||||
yield _lexer_cache[name]
|
||||
if plugins:
|
||||
yield from find_plugin_lexers()
|
||||
|
||||
|
||||
def guess_lexer_for_filename(_fn, _text, **options):
|
||||
"""
|
||||
Lookup all lexers that handle those filenames primary (``filenames``)
|
||||
or secondary (``alias_filenames``). Then run a text analysis for those
|
||||
lexers and choose the best result.
|
||||
|
||||
usage::
|
||||
|
||||
>>> from pygments.lexers import guess_lexer_for_filename
|
||||
>>> guess_lexer_for_filename('hello.html', '<%= @foo %>')
|
||||
<pygments.lexers.templates.RhtmlLexer object at 0xb7d2f32c>
|
||||
>>> guess_lexer_for_filename('hello.html', '<h1>{{ title|e }}</h1>')
|
||||
<pygments.lexers.templates.HtmlDjangoLexer object at 0xb7d2f2ac>
|
||||
>>> guess_lexer_for_filename('style.css', 'a { color: <?= $link ?> }')
|
||||
<pygments.lexers.templates.CssPhpLexer object at 0xb7ba518c>
|
||||
"""
|
||||
fn = basename(_fn)
|
||||
primary = {}
|
||||
matching_lexers = set()
|
||||
for lexer in _iter_lexerclasses():
|
||||
for filename in lexer.filenames:
|
||||
if _fn_matches(fn, filename):
|
||||
matching_lexers.add(lexer)
|
||||
primary[lexer] = True
|
||||
for filename in lexer.alias_filenames:
|
||||
if _fn_matches(fn, filename):
|
||||
matching_lexers.add(lexer)
|
||||
primary[lexer] = False
|
||||
if not matching_lexers:
|
||||
raise ClassNotFound('no lexer for filename %r found' % fn)
|
||||
if len(matching_lexers) == 1:
|
||||
return matching_lexers.pop()(**options)
|
||||
result = []
|
||||
for lexer in matching_lexers:
|
||||
rv = lexer.analyse_text(_text)
|
||||
if rv == 1.0:
|
||||
return lexer(**options)
|
||||
result.append((rv, lexer))
|
||||
|
||||
def type_sort(t):
|
||||
# sort by:
|
||||
# - analyse score
|
||||
# - is primary filename pattern?
|
||||
# - priority
|
||||
# - last resort: class name
|
||||
return (t[0], primary[t[1]], t[1].priority, t[1].__name__)
|
||||
result.sort(key=type_sort)
|
||||
|
||||
return result[-1][1](**options)
|
||||
|
||||
|
||||
def guess_lexer(_text, **options):
|
||||
"""Guess a lexer by strong distinctions in the text (eg, shebang)."""
|
||||
|
||||
if not isinstance(_text, str):
|
||||
inencoding = options.get('inencoding', options.get('encoding'))
|
||||
if inencoding:
|
||||
_text = _text.decode(inencoding or 'utf8')
|
||||
else:
|
||||
_text, _ = guess_decode(_text)
|
||||
|
||||
# try to get a vim modeline first
|
||||
ft = get_filetype_from_buffer(_text)
|
||||
|
||||
if ft is not None:
|
||||
try:
|
||||
return get_lexer_by_name(ft, **options)
|
||||
except ClassNotFound:
|
||||
pass
|
||||
|
||||
best_lexer = [0.0, None]
|
||||
for lexer in _iter_lexerclasses():
|
||||
rv = lexer.analyse_text(_text)
|
||||
if rv == 1.0:
|
||||
return lexer(**options)
|
||||
if rv > best_lexer[0]:
|
||||
best_lexer[:] = (rv, lexer)
|
||||
if not best_lexer[0] or best_lexer[1] is None:
|
||||
raise ClassNotFound('no lexer matching the text found')
|
||||
return best_lexer[1](**options)
|
||||
|
||||
|
||||
class _automodule(types.ModuleType):
|
||||
"""Automatically import lexers."""
|
||||
|
||||
def __getattr__(self, name):
|
||||
info = LEXERS.get(name)
|
||||
if info:
|
||||
_load_lexers(info[0])
|
||||
cls = _lexer_cache[info[1]]
|
||||
setattr(self, name, cls)
|
||||
return cls
|
||||
if name in COMPAT:
|
||||
return getattr(self, COMPAT[name])
|
||||
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
|
|
@ -0,0 +1,570 @@
|
|||
"""
|
||||
pygments.lexers._mapping
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Lexer mapping definitions. This file is generated by itself. Everytime
|
||||
you change something on a builtin lexer definition, run this script from
|
||||
the lexers folder to update it.
|
||||
|
||||
Do not alter the LEXERS dictionary by hand.
|
||||
|
||||
:copyright: Copyright 2006-2014, 2016 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
LEXERS = {
|
||||
'ABAPLexer': ('pip._vendor.pygments.lexer.business', 'ABAP', ('abap',), ('*.abap', '*.ABAP'), ('text/x-abap',)),
|
||||
'AMDGPULexer': ('pip._vendor.pygments.lexer.amdgpu', 'AMDGPU', ('amdgpu',), ('*.isa',), ()),
|
||||
'APLLexer': ('pip._vendor.pygments.lexer.apl', 'APL', ('apl',), ('*.apl', '*.aplf', '*.aplo', '*.apln', '*.aplc', '*.apli', '*.dyalog'), ()),
|
||||
'AbnfLexer': ('pip._vendor.pygments.lexer.grammar_notation', 'ABNF', ('abnf',), ('*.abnf',), ('text/x-abnf',)),
|
||||
'ActionScript3Lexer': ('pip._vendor.pygments.lexer.actionscript', 'ActionScript 3', ('actionscript3', 'as3'), ('*.as',), ('application/x-actionscript3', 'text/x-actionscript3', 'text/actionscript3')),
|
||||
'ActionScriptLexer': ('pip._vendor.pygments.lexer.actionscript', 'ActionScript', ('actionscript', 'as'), ('*.as',), ('application/x-actionscript', 'text/x-actionscript', 'text/actionscript')),
|
||||
'AdaLexer': ('pip._vendor.pygments.lexer.pascal', 'Ada', ('ada', 'ada95', 'ada2005'), ('*.adb', '*.ads', '*.ada'), ('text/x-ada',)),
|
||||
'AdlLexer': ('pip._vendor.pygments.lexer.archetype', 'ADL', ('adl',), ('*.adl', '*.adls', '*.adlf', '*.adlx'), ()),
|
||||
'AgdaLexer': ('pip._vendor.pygments.lexer.haskell', 'Agda', ('agda',), ('*.agda',), ('text/x-agda',)),
|
||||
'AheuiLexer': ('pip._vendor.pygments.lexer.esoteric', 'Aheui', ('aheui',), ('*.aheui',), ()),
|
||||
'AlloyLexer': ('pip._vendor.pygments.lexer.dsls', 'Alloy', ('alloy',), ('*.als',), ('text/x-alloy',)),
|
||||
'AmbientTalkLexer': ('pip._vendor.pygments.lexer.ambient', 'AmbientTalk', ('ambienttalk', 'ambienttalk/2', 'at'), ('*.at',), ('text/x-ambienttalk',)),
|
||||
'AmplLexer': ('pip._vendor.pygments.lexer.ampl', 'Ampl', ('ampl',), ('*.run',), ()),
|
||||
'Angular2HtmlLexer': ('pip._vendor.pygments.lexer.templates', 'HTML + Angular2', ('html+ng2',), ('*.ng2',), ()),
|
||||
'Angular2Lexer': ('pip._vendor.pygments.lexer.templates', 'Angular2', ('ng2',), (), ()),
|
||||
'AntlrActionScriptLexer': ('pip._vendor.pygments.lexer.parsers', 'ANTLR With ActionScript Target', ('antlr-actionscript', 'antlr-as'), ('*.G', '*.g'), ()),
|
||||
'AntlrCSharpLexer': ('pip._vendor.pygments.lexer.parsers', 'ANTLR With C# Target', ('antlr-csharp', 'antlr-c#'), ('*.G', '*.g'), ()),
|
||||
'AntlrCppLexer': ('pip._vendor.pygments.lexer.parsers', 'ANTLR With CPP Target', ('antlr-cpp',), ('*.G', '*.g'), ()),
|
||||
'AntlrJavaLexer': ('pip._vendor.pygments.lexer.parsers', 'ANTLR With Java Target', ('antlr-java',), ('*.G', '*.g'), ()),
|
||||
'AntlrLexer': ('pip._vendor.pygments.lexer.parsers', 'ANTLR', ('antlr',), (), ()),
|
||||
'AntlrObjectiveCLexer': ('pip._vendor.pygments.lexer.parsers', 'ANTLR With ObjectiveC Target', ('antlr-objc',), ('*.G', '*.g'), ()),
|
||||
'AntlrPerlLexer': ('pip._vendor.pygments.lexer.parsers', 'ANTLR With Perl Target', ('antlr-perl',), ('*.G', '*.g'), ()),
|
||||
'AntlrPythonLexer': ('pip._vendor.pygments.lexer.parsers', 'ANTLR With Python Target', ('antlr-python',), ('*.G', '*.g'), ()),
|
||||
'AntlrRubyLexer': ('pip._vendor.pygments.lexer.parsers', 'ANTLR With Ruby Target', ('antlr-ruby', 'antlr-rb'), ('*.G', '*.g'), ()),
|
||||
'ApacheConfLexer': ('pip._vendor.pygments.lexer.configs', 'ApacheConf', ('apacheconf', 'aconf', 'apache'), ('.htaccess', 'apache.conf', 'apache2.conf'), ('text/x-apacheconf',)),
|
||||
'AppleScriptLexer': ('pip._vendor.pygments.lexer.scripting', 'AppleScript', ('applescript',), ('*.applescript',), ()),
|
||||
'ArduinoLexer': ('pip._vendor.pygments.lexer.c_like', 'Arduino', ('arduino',), ('*.ino',), ('text/x-arduino',)),
|
||||
'ArrowLexer': ('pip._vendor.pygments.lexer.arrow', 'Arrow', ('arrow',), ('*.arw',), ()),
|
||||
'AscLexer': ('pip._vendor.pygments.lexer.asc', 'ASCII armored', ('asc', 'pem'), ('*.asc', '*.pem', 'id_dsa', 'id_ecdsa', 'id_ecdsa_sk', 'id_ed25519', 'id_ed25519_sk', 'id_rsa'), ('application/pgp-keys', 'application/pgp-encrypted', 'application/pgp-signature')),
|
||||
'AspectJLexer': ('pip._vendor.pygments.lexer.jvm', 'AspectJ', ('aspectj',), ('*.aj',), ('text/x-aspectj',)),
|
||||
'AsymptoteLexer': ('pip._vendor.pygments.lexer.graphics', 'Asymptote', ('asymptote', 'asy'), ('*.asy',), ('text/x-asymptote',)),
|
||||
'AugeasLexer': ('pip._vendor.pygments.lexer.configs', 'Augeas', ('augeas',), ('*.aug',), ()),
|
||||
'AutoItLexer': ('pip._vendor.pygments.lexer.automation', 'AutoIt', ('autoit',), ('*.au3',), ('text/x-autoit',)),
|
||||
'AutohotkeyLexer': ('pip._vendor.pygments.lexer.automation', 'autohotkey', ('autohotkey', 'ahk'), ('*.ahk', '*.ahkl'), ('text/x-autohotkey',)),
|
||||
'AwkLexer': ('pip._vendor.pygments.lexer.textedit', 'Awk', ('awk', 'gawk', 'mawk', 'nawk'), ('*.awk',), ('application/x-awk',)),
|
||||
'BBCBasicLexer': ('pip._vendor.pygments.lexer.basic', 'BBC Basic', ('bbcbasic',), ('*.bbc',), ()),
|
||||
'BBCodeLexer': ('pip._vendor.pygments.lexer.markup', 'BBCode', ('bbcode',), (), ('text/x-bbcode',)),
|
||||
'BCLexer': ('pip._vendor.pygments.lexer.algebra', 'BC', ('bc',), ('*.bc',), ()),
|
||||
'BSTLexer': ('pip._vendor.pygments.lexer.bibtex', 'BST', ('bst', 'bst-pybtex'), ('*.bst',), ()),
|
||||
'BareLexer': ('pip._vendor.pygments.lexer.bare', 'BARE', ('bare',), ('*.bare',), ()),
|
||||
'BaseMakefileLexer': ('pip._vendor.pygments.lexer.make', 'Base Makefile', ('basemake',), (), ()),
|
||||
'BashLexer': ('pip._vendor.pygments.lexer.shell', 'Bash', ('bash', 'sh', 'ksh', 'zsh', 'shell'), ('*.sh', '*.ksh', '*.bash', '*.ebuild', '*.eclass', '*.exheres-0', '*.exlib', '*.zsh', '.bashrc', 'bashrc', '.bash_*', 'bash_*', 'zshrc', '.zshrc', 'PKGBUILD'), ('application/x-sh', 'application/x-shellscript', 'text/x-shellscript')),
|
||||
'BashSessionLexer': ('pip._vendor.pygments.lexer.shell', 'Bash Session', ('console', 'shell-session'), ('*.sh-session', '*.shell-session'), ('application/x-shell-session', 'application/x-sh-session')),
|
||||
'BatchLexer': ('pip._vendor.pygments.lexer.shell', 'Batchfile', ('batch', 'bat', 'dosbatch', 'winbatch'), ('*.bat', '*.cmd'), ('application/x-dos-batch',)),
|
||||
'BefungeLexer': ('pip._vendor.pygments.lexer.esoteric', 'Befunge', ('befunge',), ('*.befunge',), ('application/x-befunge',)),
|
||||
'BibTeXLexer': ('pip._vendor.pygments.lexer.bibtex', 'BibTeX', ('bibtex', 'bib'), ('*.bib',), ('text/x-bibtex',)),
|
||||
'BlitzBasicLexer': ('pip._vendor.pygments.lexer.basic', 'BlitzBasic', ('blitzbasic', 'b3d', 'bplus'), ('*.bb', '*.decls'), ('text/x-bb',)),
|
||||
'BlitzMaxLexer': ('pip._vendor.pygments.lexer.basic', 'BlitzMax', ('blitzmax', 'bmax'), ('*.bmx',), ('text/x-bmx',)),
|
||||
'BnfLexer': ('pip._vendor.pygments.lexer.grammar_notation', 'BNF', ('bnf',), ('*.bnf',), ('text/x-bnf',)),
|
||||
'BoaLexer': ('pip._vendor.pygments.lexer.boa', 'Boa', ('boa',), ('*.boa',), ()),
|
||||
'BooLexer': ('pip._vendor.pygments.lexer.dotnet', 'Boo', ('boo',), ('*.boo',), ('text/x-boo',)),
|
||||
'BoogieLexer': ('pip._vendor.pygments.lexer.verification', 'Boogie', ('boogie',), ('*.bpl',), ()),
|
||||
'BrainfuckLexer': ('pip._vendor.pygments.lexer.esoteric', 'Brainfuck', ('brainfuck', 'bf'), ('*.bf', '*.b'), ('application/x-brainfuck',)),
|
||||
'BugsLexer': ('pip._vendor.pygments.lexer.modeling', 'BUGS', ('bugs', 'winbugs', 'openbugs'), ('*.bug',), ()),
|
||||
'CAmkESLexer': ('pip._vendor.pygments.lexer.esoteric', 'CAmkES', ('camkes', 'idl4'), ('*.camkes', '*.idl4'), ()),
|
||||
'CLexer': ('pip._vendor.pygments.lexer.c_cpp', 'C', ('c',), ('*.c', '*.h', '*.idc'), ('text/x-chdr', 'text/x-csrc')),
|
||||
'CMakeLexer': ('pip._vendor.pygments.lexer.make', 'CMake', ('cmake',), ('*.cmake', 'CMakeLists.txt'), ('text/x-cmake',)),
|
||||
'CObjdumpLexer': ('pip._vendor.pygments.lexer.asm', 'c-objdump', ('c-objdump',), ('*.c-objdump',), ('text/x-c-objdump',)),
|
||||
'CPSALexer': ('pip._vendor.pygments.lexer.lisp', 'CPSA', ('cpsa',), ('*.cpsa',), ()),
|
||||
'CSharpAspxLexer': ('pip._vendor.pygments.lexer.dotnet', 'aspx-cs', ('aspx-cs',), ('*.aspx', '*.asax', '*.ascx', '*.ashx', '*.asmx', '*.axd'), ()),
|
||||
'CSharpLexer': ('pip._vendor.pygments.lexer.dotnet', 'C#', ('csharp', 'c#'), ('*.cs',), ('text/x-csharp',)),
|
||||
'Ca65Lexer': ('pip._vendor.pygments.lexer.asm', 'ca65 assembler', ('ca65',), ('*.s',), ()),
|
||||
'CadlLexer': ('pip._vendor.pygments.lexer.archetype', 'cADL', ('cadl',), ('*.cadl',), ()),
|
||||
'CapDLLexer': ('pip._vendor.pygments.lexer.esoteric', 'CapDL', ('capdl',), ('*.cdl',), ()),
|
||||
'CapnProtoLexer': ('pip._vendor.pygments.lexer.capnproto', "Cap'n Proto", ('capnp',), ('*.capnp',), ()),
|
||||
'CbmBasicV2Lexer': ('pip._vendor.pygments.lexer.basic', 'CBM BASIC V2', ('cbmbas',), ('*.bas',), ()),
|
||||
'CddlLexer': ('pip._vendor.pygments.lexer.cddl', 'CDDL', ('cddl',), ('*.cddl',), ('text/x-cddl',)),
|
||||
'CeylonLexer': ('pip._vendor.pygments.lexer.jvm', 'Ceylon', ('ceylon',), ('*.ceylon',), ('text/x-ceylon',)),
|
||||
'Cfengine3Lexer': ('pip._vendor.pygments.lexer.configs', 'CFEngine3', ('cfengine3', 'cf3'), ('*.cf',), ()),
|
||||
'ChaiscriptLexer': ('pip._vendor.pygments.lexer.scripting', 'ChaiScript', ('chaiscript', 'chai'), ('*.chai',), ('text/x-chaiscript', 'application/x-chaiscript')),
|
||||
'ChapelLexer': ('pip._vendor.pygments.lexer.chapel', 'Chapel', ('chapel', 'chpl'), ('*.chpl',), ()),
|
||||
'CharmciLexer': ('pip._vendor.pygments.lexer.c_like', 'Charmci', ('charmci',), ('*.ci',), ()),
|
||||
'CheetahHtmlLexer': ('pip._vendor.pygments.lexer.templates', 'HTML+Cheetah', ('html+cheetah', 'html+spitfire', 'htmlcheetah'), (), ('text/html+cheetah', 'text/html+spitfire')),
|
||||
'CheetahJavascriptLexer': ('pip._vendor.pygments.lexer.templates', 'JavaScript+Cheetah', ('javascript+cheetah', 'js+cheetah', 'javascript+spitfire', 'js+spitfire'), (), ('application/x-javascript+cheetah', 'text/x-javascript+cheetah', 'text/javascript+cheetah', 'application/x-javascript+spitfire', 'text/x-javascript+spitfire', 'text/javascript+spitfire')),
|
||||
'CheetahLexer': ('pip._vendor.pygments.lexer.templates', 'Cheetah', ('cheetah', 'spitfire'), ('*.tmpl', '*.spt'), ('application/x-cheetah', 'application/x-spitfire')),
|
||||
'CheetahXmlLexer': ('pip._vendor.pygments.lexer.templates', 'XML+Cheetah', ('xml+cheetah', 'xml+spitfire'), (), ('application/xml+cheetah', 'application/xml+spitfire')),
|
||||
'CirruLexer': ('pip._vendor.pygments.lexer.webmisc', 'Cirru', ('cirru',), ('*.cirru',), ('text/x-cirru',)),
|
||||
'ClayLexer': ('pip._vendor.pygments.lexer.c_like', 'Clay', ('clay',), ('*.clay',), ('text/x-clay',)),
|
||||
'CleanLexer': ('pip._vendor.pygments.lexer.clean', 'Clean', ('clean',), ('*.icl', '*.dcl'), ()),
|
||||
'ClojureLexer': ('pip._vendor.pygments.lexer.jvm', 'Clojure', ('clojure', 'clj'), ('*.clj',), ('text/x-clojure', 'application/x-clojure')),
|
||||
'ClojureScriptLexer': ('pip._vendor.pygments.lexer.jvm', 'ClojureScript', ('clojurescript', 'cljs'), ('*.cljs',), ('text/x-clojurescript', 'application/x-clojurescript')),
|
||||
'CobolFreeformatLexer': ('pip._vendor.pygments.lexer.business', 'COBOLFree', ('cobolfree',), ('*.cbl', '*.CBL'), ()),
|
||||
'CobolLexer': ('pip._vendor.pygments.lexer.business', 'COBOL', ('cobol',), ('*.cob', '*.COB', '*.cpy', '*.CPY'), ('text/x-cobol',)),
|
||||
'CoffeeScriptLexer': ('pip._vendor.pygments.lexer.javascript', 'CoffeeScript', ('coffeescript', 'coffee-script', 'coffee'), ('*.coffee',), ('text/coffeescript',)),
|
||||
'ColdfusionCFCLexer': ('pip._vendor.pygments.lexer.templates', 'Coldfusion CFC', ('cfc',), ('*.cfc',), ()),
|
||||
'ColdfusionHtmlLexer': ('pip._vendor.pygments.lexer.templates', 'Coldfusion HTML', ('cfm',), ('*.cfm', '*.cfml'), ('application/x-coldfusion',)),
|
||||
'ColdfusionLexer': ('pip._vendor.pygments.lexer.templates', 'cfstatement', ('cfs',), (), ()),
|
||||
'CommonLispLexer': ('pip._vendor.pygments.lexer.lisp', 'Common Lisp', ('common-lisp', 'cl', 'lisp'), ('*.cl', '*.lisp'), ('text/x-common-lisp',)),
|
||||
'ComponentPascalLexer': ('pip._vendor.pygments.lexer.oberon', 'Component Pascal', ('componentpascal', 'cp'), ('*.cp', '*.cps'), ('text/x-component-pascal',)),
|
||||
'CoqLexer': ('pip._vendor.pygments.lexer.theorem', 'Coq', ('coq',), ('*.v',), ('text/x-coq',)),
|
||||
'CppLexer': ('pip._vendor.pygments.lexer.c_cpp', 'C++', ('cpp', 'c++'), ('*.cpp', '*.hpp', '*.c++', '*.h++', '*.cc', '*.hh', '*.cxx', '*.hxx', '*.C', '*.H', '*.cp', '*.CPP'), ('text/x-c++hdr', 'text/x-c++src')),
|
||||
'CppObjdumpLexer': ('pip._vendor.pygments.lexer.asm', 'cpp-objdump', ('cpp-objdump', 'c++-objdumb', 'cxx-objdump'), ('*.cpp-objdump', '*.c++-objdump', '*.cxx-objdump'), ('text/x-cpp-objdump',)),
|
||||
'CrmshLexer': ('pip._vendor.pygments.lexer.dsls', 'Crmsh', ('crmsh', 'pcmk'), ('*.crmsh', '*.pcmk'), ()),
|
||||
'CrocLexer': ('pip._vendor.pygments.lexer.d', 'Croc', ('croc',), ('*.croc',), ('text/x-crocsrc',)),
|
||||
'CryptolLexer': ('pip._vendor.pygments.lexer.haskell', 'Cryptol', ('cryptol', 'cry'), ('*.cry',), ('text/x-cryptol',)),
|
||||
'CrystalLexer': ('pip._vendor.pygments.lexer.crystal', 'Crystal', ('cr', 'crystal'), ('*.cr',), ('text/x-crystal',)),
|
||||
'CsoundDocumentLexer': ('pip._vendor.pygments.lexer.csound', 'Csound Document', ('csound-document', 'csound-csd'), ('*.csd',), ()),
|
||||
'CsoundOrchestraLexer': ('pip._vendor.pygments.lexer.csound', 'Csound Orchestra', ('csound', 'csound-orc'), ('*.orc', '*.udo'), ()),
|
||||
'CsoundScoreLexer': ('pip._vendor.pygments.lexer.csound', 'Csound Score', ('csound-score', 'csound-sco'), ('*.sco',), ()),
|
||||
'CssDjangoLexer': ('pip._vendor.pygments.lexer.templates', 'CSS+Django/Jinja', ('css+django', 'css+jinja'), (), ('text/css+django', 'text/css+jinja')),
|
||||
'CssErbLexer': ('pip._vendor.pygments.lexer.templates', 'CSS+Ruby', ('css+ruby', 'css+erb'), (), ('text/css+ruby',)),
|
||||
'CssGenshiLexer': ('pip._vendor.pygments.lexer.templates', 'CSS+Genshi Text', ('css+genshitext', 'css+genshi'), (), ('text/css+genshi',)),
|
||||
'CssLexer': ('pip._vendor.pygments.lexer.css', 'CSS', ('css',), ('*.css',), ('text/css',)),
|
||||
'CssPhpLexer': ('pip._vendor.pygments.lexer.templates', 'CSS+PHP', ('css+php',), (), ('text/css+php',)),
|
||||
'CssSmartyLexer': ('pip._vendor.pygments.lexer.templates', 'CSS+Smarty', ('css+smarty',), (), ('text/css+smarty',)),
|
||||
'CudaLexer': ('pip._vendor.pygments.lexer.c_like', 'CUDA', ('cuda', 'cu'), ('*.cu', '*.cuh'), ('text/x-cuda',)),
|
||||
'CypherLexer': ('pip._vendor.pygments.lexer.graph', 'Cypher', ('cypher',), ('*.cyp', '*.cypher'), ()),
|
||||
'CythonLexer': ('pip._vendor.pygments.lexer.python', 'Cython', ('cython', 'pyx', 'pyrex'), ('*.pyx', '*.pxd', '*.pxi'), ('text/x-cython', 'application/x-cython')),
|
||||
'DLexer': ('pip._vendor.pygments.lexer.d', 'D', ('d',), ('*.d', '*.di'), ('text/x-dsrc',)),
|
||||
'DObjdumpLexer': ('pip._vendor.pygments.lexer.asm', 'd-objdump', ('d-objdump',), ('*.d-objdump',), ('text/x-d-objdump',)),
|
||||
'DarcsPatchLexer': ('pip._vendor.pygments.lexer.diff', 'Darcs Patch', ('dpatch',), ('*.dpatch', '*.darcspatch'), ()),
|
||||
'DartLexer': ('pip._vendor.pygments.lexer.javascript', 'Dart', ('dart',), ('*.dart',), ('text/x-dart',)),
|
||||
'Dasm16Lexer': ('pip._vendor.pygments.lexer.asm', 'DASM16', ('dasm16',), ('*.dasm16', '*.dasm'), ('text/x-dasm16',)),
|
||||
'DebianControlLexer': ('pip._vendor.pygments.lexer.installers', 'Debian Control file', ('debcontrol', 'control'), ('control',), ()),
|
||||
'DelphiLexer': ('pip._vendor.pygments.lexer.pascal', 'Delphi', ('delphi', 'pas', 'pascal', 'objectpascal'), ('*.pas', '*.dpr'), ('text/x-pascal',)),
|
||||
'DevicetreeLexer': ('pip._vendor.pygments.lexer.devicetree', 'Devicetree', ('devicetree', 'dts'), ('*.dts', '*.dtsi'), ('text/x-c',)),
|
||||
'DgLexer': ('pip._vendor.pygments.lexer.python', 'dg', ('dg',), ('*.dg',), ('text/x-dg',)),
|
||||
'DiffLexer': ('pip._vendor.pygments.lexer.diff', 'Diff', ('diff', 'udiff'), ('*.diff', '*.patch'), ('text/x-diff', 'text/x-patch')),
|
||||
'DjangoLexer': ('pip._vendor.pygments.lexer.templates', 'Django/Jinja', ('django', 'jinja'), (), ('application/x-django-templating', 'application/x-jinja')),
|
||||
'DockerLexer': ('pip._vendor.pygments.lexer.configs', 'Docker', ('docker', 'dockerfile'), ('Dockerfile', '*.docker'), ('text/x-dockerfile-config',)),
|
||||
'DtdLexer': ('pip._vendor.pygments.lexer.html', 'DTD', ('dtd',), ('*.dtd',), ('application/xml-dtd',)),
|
||||
'DuelLexer': ('pip._vendor.pygments.lexer.webmisc', 'Duel', ('duel', 'jbst', 'jsonml+bst'), ('*.duel', '*.jbst'), ('text/x-duel', 'text/x-jbst')),
|
||||
'DylanConsoleLexer': ('pip._vendor.pygments.lexer.dylan', 'Dylan session', ('dylan-console', 'dylan-repl'), ('*.dylan-console',), ('text/x-dylan-console',)),
|
||||
'DylanLexer': ('pip._vendor.pygments.lexer.dylan', 'Dylan', ('dylan',), ('*.dylan', '*.dyl', '*.intr'), ('text/x-dylan',)),
|
||||
'DylanLidLexer': ('pip._vendor.pygments.lexer.dylan', 'DylanLID', ('dylan-lid', 'lid'), ('*.lid', '*.hdp'), ('text/x-dylan-lid',)),
|
||||
'ECLLexer': ('pip._vendor.pygments.lexer.ecl', 'ECL', ('ecl',), ('*.ecl',), ('application/x-ecl',)),
|
||||
'ECLexer': ('pip._vendor.pygments.lexer.c_like', 'eC', ('ec',), ('*.ec', '*.eh'), ('text/x-echdr', 'text/x-ecsrc')),
|
||||
'EarlGreyLexer': ('pip._vendor.pygments.lexer.javascript', 'Earl Grey', ('earl-grey', 'earlgrey', 'eg'), ('*.eg',), ('text/x-earl-grey',)),
|
||||
'EasytrieveLexer': ('pip._vendor.pygments.lexer.scripting', 'Easytrieve', ('easytrieve',), ('*.ezt', '*.mac'), ('text/x-easytrieve',)),
|
||||
'EbnfLexer': ('pip._vendor.pygments.lexer.parsers', 'EBNF', ('ebnf',), ('*.ebnf',), ('text/x-ebnf',)),
|
||||
'EiffelLexer': ('pip._vendor.pygments.lexer.eiffel', 'Eiffel', ('eiffel',), ('*.e',), ('text/x-eiffel',)),
|
||||
'ElixirConsoleLexer': ('pip._vendor.pygments.lexer.erlang', 'Elixir iex session', ('iex',), (), ('text/x-elixir-shellsession',)),
|
||||
'ElixirLexer': ('pip._vendor.pygments.lexer.erlang', 'Elixir', ('elixir', 'ex', 'exs'), ('*.ex', '*.eex', '*.exs', '*.leex'), ('text/x-elixir',)),
|
||||
'ElmLexer': ('pip._vendor.pygments.lexer.elm', 'Elm', ('elm',), ('*.elm',), ('text/x-elm',)),
|
||||
'EmacsLispLexer': ('pip._vendor.pygments.lexer.lisp', 'EmacsLisp', ('emacs-lisp', 'elisp', 'emacs'), ('*.el',), ('text/x-elisp', 'application/x-elisp')),
|
||||
'EmailLexer': ('pip._vendor.pygments.lexer.email', 'E-mail', ('email', 'eml'), ('*.eml',), ('message/rfc822',)),
|
||||
'ErbLexer': ('pip._vendor.pygments.lexer.templates', 'ERB', ('erb',), (), ('application/x-ruby-templating',)),
|
||||
'ErlangLexer': ('pip._vendor.pygments.lexer.erlang', 'Erlang', ('erlang',), ('*.erl', '*.hrl', '*.es', '*.escript'), ('text/x-erlang',)),
|
||||
'ErlangShellLexer': ('pip._vendor.pygments.lexer.erlang', 'Erlang erl session', ('erl',), ('*.erl-sh',), ('text/x-erl-shellsession',)),
|
||||
'EvoqueHtmlLexer': ('pip._vendor.pygments.lexer.templates', 'HTML+Evoque', ('html+evoque',), ('*.html',), ('text/html+evoque',)),
|
||||
'EvoqueLexer': ('pip._vendor.pygments.lexer.templates', 'Evoque', ('evoque',), ('*.evoque',), ('application/x-evoque',)),
|
||||
'EvoqueXmlLexer': ('pip._vendor.pygments.lexer.templates', 'XML+Evoque', ('xml+evoque',), ('*.xml',), ('application/xml+evoque',)),
|
||||
'ExeclineLexer': ('pip._vendor.pygments.lexer.shell', 'execline', ('execline',), ('*.exec',), ()),
|
||||
'EzhilLexer': ('pip._vendor.pygments.lexer.ezhil', 'Ezhil', ('ezhil',), ('*.n',), ('text/x-ezhil',)),
|
||||
'FSharpLexer': ('pip._vendor.pygments.lexer.dotnet', 'F#', ('fsharp', 'f#'), ('*.fs', '*.fsi'), ('text/x-fsharp',)),
|
||||
'FStarLexer': ('pip._vendor.pygments.lexer.ml', 'FStar', ('fstar',), ('*.fst', '*.fsti'), ('text/x-fstar',)),
|
||||
'FactorLexer': ('pip._vendor.pygments.lexer.factor', 'Factor', ('factor',), ('*.factor',), ('text/x-factor',)),
|
||||
'FancyLexer': ('pip._vendor.pygments.lexer.ruby', 'Fancy', ('fancy', 'fy'), ('*.fy', '*.fancypack'), ('text/x-fancysrc',)),
|
||||
'FantomLexer': ('pip._vendor.pygments.lexer.fantom', 'Fantom', ('fan',), ('*.fan',), ('application/x-fantom',)),
|
||||
'FelixLexer': ('pip._vendor.pygments.lexer.felix', 'Felix', ('felix', 'flx'), ('*.flx', '*.flxh'), ('text/x-felix',)),
|
||||
'FennelLexer': ('pip._vendor.pygments.lexer.lisp', 'Fennel', ('fennel', 'fnl'), ('*.fnl',), ()),
|
||||
'FishShellLexer': ('pip._vendor.pygments.lexer.shell', 'Fish', ('fish', 'fishshell'), ('*.fish', '*.load'), ('application/x-fish',)),
|
||||
'FlatlineLexer': ('pip._vendor.pygments.lexer.dsls', 'Flatline', ('flatline',), (), ('text/x-flatline',)),
|
||||
'FloScriptLexer': ('pip._vendor.pygments.lexer.floscript', 'FloScript', ('floscript', 'flo'), ('*.flo',), ()),
|
||||
'ForthLexer': ('pip._vendor.pygments.lexer.forth', 'Forth', ('forth',), ('*.frt', '*.fs'), ('application/x-forth',)),
|
||||
'FortranFixedLexer': ('pip._vendor.pygments.lexer.fortran', 'FortranFixed', ('fortranfixed',), ('*.f', '*.F'), ()),
|
||||
'FortranLexer': ('pip._vendor.pygments.lexer.fortran', 'Fortran', ('fortran',), ('*.f03', '*.f90', '*.F03', '*.F90'), ('text/x-fortran',)),
|
||||
'FoxProLexer': ('pip._vendor.pygments.lexer.foxpro', 'FoxPro', ('foxpro', 'vfp', 'clipper', 'xbase'), ('*.PRG', '*.prg'), ()),
|
||||
'FreeFemLexer': ('pip._vendor.pygments.lexer.freefem', 'Freefem', ('freefem',), ('*.edp',), ('text/x-freefem',)),
|
||||
'FutharkLexer': ('pip._vendor.pygments.lexer.futhark', 'Futhark', ('futhark',), ('*.fut',), ('text/x-futhark',)),
|
||||
'GAPLexer': ('pip._vendor.pygments.lexer.algebra', 'GAP', ('gap',), ('*.g', '*.gd', '*.gi', '*.gap'), ()),
|
||||
'GDScriptLexer': ('pip._vendor.pygments.lexer.gdscript', 'GDScript', ('gdscript', 'gd'), ('*.gd',), ('text/x-gdscript', 'application/x-gdscript')),
|
||||
'GLShaderLexer': ('pip._vendor.pygments.lexer.graphics', 'GLSL', ('glsl',), ('*.vert', '*.frag', '*.geo'), ('text/x-glslsrc',)),
|
||||
'GSQLLexer': ('pip._vendor.pygments.lexer.gsql', 'GSQL', ('gsql',), ('*.gsql',), ()),
|
||||
'GasLexer': ('pip._vendor.pygments.lexer.asm', 'GAS', ('gas', 'asm'), ('*.s', '*.S'), ('text/x-gas',)),
|
||||
'GcodeLexer': ('pip._vendor.pygments.lexer.gcodelexer', 'g-code', ('gcode',), ('*.gcode',), ()),
|
||||
'GenshiLexer': ('pip._vendor.pygments.lexer.templates', 'Genshi', ('genshi', 'kid', 'xml+genshi', 'xml+kid'), ('*.kid',), ('application/x-genshi', 'application/x-kid')),
|
||||
'GenshiTextLexer': ('pip._vendor.pygments.lexer.templates', 'Genshi Text', ('genshitext',), (), ('application/x-genshi-text', 'text/x-genshi')),
|
||||
'GettextLexer': ('pip._vendor.pygments.lexer.textfmts', 'Gettext Catalog', ('pot', 'po'), ('*.pot', '*.po'), ('application/x-gettext', 'text/x-gettext', 'text/gettext')),
|
||||
'GherkinLexer': ('pip._vendor.pygments.lexer.testing', 'Gherkin', ('gherkin', 'cucumber'), ('*.feature',), ('text/x-gherkin',)),
|
||||
'GnuplotLexer': ('pip._vendor.pygments.lexer.graphics', 'Gnuplot', ('gnuplot',), ('*.plot', '*.plt'), ('text/x-gnuplot',)),
|
||||
'GoLexer': ('pip._vendor.pygments.lexer.go', 'Go', ('go', 'golang'), ('*.go',), ('text/x-gosrc',)),
|
||||
'GoloLexer': ('pip._vendor.pygments.lexer.jvm', 'Golo', ('golo',), ('*.golo',), ()),
|
||||
'GoodDataCLLexer': ('pip._vendor.pygments.lexer.business', 'GoodData-CL', ('gooddata-cl',), ('*.gdc',), ('text/x-gooddata-cl',)),
|
||||
'GosuLexer': ('pip._vendor.pygments.lexer.jvm', 'Gosu', ('gosu',), ('*.gs', '*.gsx', '*.gsp', '*.vark'), ('text/x-gosu',)),
|
||||
'GosuTemplateLexer': ('pip._vendor.pygments.lexer.jvm', 'Gosu Template', ('gst',), ('*.gst',), ('text/x-gosu-template',)),
|
||||
'GraphvizLexer': ('pip._vendor.pygments.lexer.graphviz', 'Graphviz', ('graphviz', 'dot'), ('*.gv', '*.dot'), ('text/x-graphviz', 'text/vnd.graphviz')),
|
||||
'GroffLexer': ('pip._vendor.pygments.lexer.markup', 'Groff', ('groff', 'nroff', 'man'), ('*.[1234567]', '*.man'), ('application/x-troff', 'text/troff')),
|
||||
'GroovyLexer': ('pip._vendor.pygments.lexer.jvm', 'Groovy', ('groovy',), ('*.groovy', '*.gradle'), ('text/x-groovy',)),
|
||||
'HLSLShaderLexer': ('pip._vendor.pygments.lexer.graphics', 'HLSL', ('hlsl',), ('*.hlsl', '*.hlsli'), ('text/x-hlsl',)),
|
||||
'HamlLexer': ('pip._vendor.pygments.lexer.html', 'Haml', ('haml',), ('*.haml',), ('text/x-haml',)),
|
||||
'HandlebarsHtmlLexer': ('pip._vendor.pygments.lexer.templates', 'HTML+Handlebars', ('html+handlebars',), ('*.handlebars', '*.hbs'), ('text/html+handlebars', 'text/x-handlebars-template')),
|
||||
'HandlebarsLexer': ('pip._vendor.pygments.lexer.templates', 'Handlebars', ('handlebars',), (), ()),
|
||||
'HaskellLexer': ('pip._vendor.pygments.lexer.haskell', 'Haskell', ('haskell', 'hs'), ('*.hs',), ('text/x-haskell',)),
|
||||
'HaxeLexer': ('pip._vendor.pygments.lexer.haxe', 'Haxe', ('haxe', 'hxsl', 'hx'), ('*.hx', '*.hxsl'), ('text/haxe', 'text/x-haxe', 'text/x-hx')),
|
||||
'HexdumpLexer': ('pip._vendor.pygments.lexer.hexdump', 'Hexdump', ('hexdump',), (), ()),
|
||||
'HsailLexer': ('pip._vendor.pygments.lexer.asm', 'HSAIL', ('hsail', 'hsa'), ('*.hsail',), ('text/x-hsail',)),
|
||||
'HspecLexer': ('pip._vendor.pygments.lexer.haskell', 'Hspec', ('hspec',), (), ()),
|
||||
'HtmlDjangoLexer': ('pip._vendor.pygments.lexer.templates', 'HTML+Django/Jinja', ('html+django', 'html+jinja', 'htmldjango'), (), ('text/html+django', 'text/html+jinja')),
|
||||
'HtmlGenshiLexer': ('pip._vendor.pygments.lexer.templates', 'HTML+Genshi', ('html+genshi', 'html+kid'), (), ('text/html+genshi',)),
|
||||
'HtmlLexer': ('pip._vendor.pygments.lexer.html', 'HTML', ('html',), ('*.html', '*.htm', '*.xhtml', '*.xslt'), ('text/html', 'application/xhtml+xml')),
|
||||
'HtmlPhpLexer': ('pip._vendor.pygments.lexer.templates', 'HTML+PHP', ('html+php',), ('*.phtml',), ('application/x-php', 'application/x-httpd-php', 'application/x-httpd-php3', 'application/x-httpd-php4', 'application/x-httpd-php5')),
|
||||
'HtmlSmartyLexer': ('pip._vendor.pygments.lexer.templates', 'HTML+Smarty', ('html+smarty',), (), ('text/html+smarty',)),
|
||||
'HttpLexer': ('pip._vendor.pygments.lexer.textfmts', 'HTTP', ('http',), (), ()),
|
||||
'HxmlLexer': ('pip._vendor.pygments.lexer.haxe', 'Hxml', ('haxeml', 'hxml'), ('*.hxml',), ()),
|
||||
'HyLexer': ('pip._vendor.pygments.lexer.lisp', 'Hy', ('hylang',), ('*.hy',), ('text/x-hy', 'application/x-hy')),
|
||||
'HybrisLexer': ('pip._vendor.pygments.lexer.scripting', 'Hybris', ('hybris', 'hy'), ('*.hy', '*.hyb'), ('text/x-hybris', 'application/x-hybris')),
|
||||
'IDLLexer': ('pip._vendor.pygments.lexer.idl', 'IDL', ('idl',), ('*.pro',), ('text/idl',)),
|
||||
'IconLexer': ('pip._vendor.pygments.lexer.unicon', 'Icon', ('icon',), ('*.icon', '*.ICON'), ()),
|
||||
'IdrisLexer': ('pip._vendor.pygments.lexer.haskell', 'Idris', ('idris', 'idr'), ('*.idr',), ('text/x-idris',)),
|
||||
'IgorLexer': ('pip._vendor.pygments.lexer.igor', 'Igor', ('igor', 'igorpro'), ('*.ipf',), ('text/ipf',)),
|
||||
'Inform6Lexer': ('pip._vendor.pygments.lexer.int_fiction', 'Inform 6', ('inform6', 'i6'), ('*.inf',), ()),
|
||||
'Inform6TemplateLexer': ('pip._vendor.pygments.lexer.int_fiction', 'Inform 6 template', ('i6t',), ('*.i6t',), ()),
|
||||
'Inform7Lexer': ('pip._vendor.pygments.lexer.int_fiction', 'Inform 7', ('inform7', 'i7'), ('*.ni', '*.i7x'), ()),
|
||||
'IniLexer': ('pip._vendor.pygments.lexer.configs', 'INI', ('ini', 'cfg', 'dosini'), ('*.ini', '*.cfg', '*.inf', '*.service', '*.socket', '*.device', '*.mount', '*.automount', '*.swap', '*.target', '*.path', '*.timer', '*.slice', '*.scope'), ('text/x-ini', 'text/inf')),
|
||||
'IoLexer': ('pip._vendor.pygments.lexer.iolang', 'Io', ('io',), ('*.io',), ('text/x-iosrc',)),
|
||||
'IokeLexer': ('pip._vendor.pygments.lexer.jvm', 'Ioke', ('ioke', 'ik'), ('*.ik',), ('text/x-iokesrc',)),
|
||||
'IrcLogsLexer': ('pip._vendor.pygments.lexer.textfmts', 'IRC logs', ('irc',), ('*.weechatlog',), ('text/x-irclog',)),
|
||||
'IsabelleLexer': ('pip._vendor.pygments.lexer.theorem', 'Isabelle', ('isabelle',), ('*.thy',), ('text/x-isabelle',)),
|
||||
'JLexer': ('pip._vendor.pygments.lexer.j', 'J', ('j',), ('*.ijs',), ('text/x-j',)),
|
||||
'JSLTLexer': ('pip._vendor.pygments.lexer.jslt', 'JSLT', ('jslt',), ('*.jslt',), ('text/x-jslt',)),
|
||||
'JagsLexer': ('pip._vendor.pygments.lexer.modeling', 'JAGS', ('jags',), ('*.jag', '*.bug'), ()),
|
||||
'JasminLexer': ('pip._vendor.pygments.lexer.jvm', 'Jasmin', ('jasmin', 'jasminxt'), ('*.j',), ()),
|
||||
'JavaLexer': ('pip._vendor.pygments.lexer.jvm', 'Java', ('java',), ('*.java',), ('text/x-java',)),
|
||||
'JavascriptDjangoLexer': ('pip._vendor.pygments.lexer.templates', 'JavaScript+Django/Jinja', ('javascript+django', 'js+django', 'javascript+jinja', 'js+jinja'), (), ('application/x-javascript+django', 'application/x-javascript+jinja', 'text/x-javascript+django', 'text/x-javascript+jinja', 'text/javascript+django', 'text/javascript+jinja')),
|
||||
'JavascriptErbLexer': ('pip._vendor.pygments.lexer.templates', 'JavaScript+Ruby', ('javascript+ruby', 'js+ruby', 'javascript+erb', 'js+erb'), (), ('application/x-javascript+ruby', 'text/x-javascript+ruby', 'text/javascript+ruby')),
|
||||
'JavascriptGenshiLexer': ('pip._vendor.pygments.lexer.templates', 'JavaScript+Genshi Text', ('js+genshitext', 'js+genshi', 'javascript+genshitext', 'javascript+genshi'), (), ('application/x-javascript+genshi', 'text/x-javascript+genshi', 'text/javascript+genshi')),
|
||||
'JavascriptLexer': ('pip._vendor.pygments.lexer.javascript', 'JavaScript', ('javascript', 'js'), ('*.js', '*.jsm', '*.mjs', '*.cjs'), ('application/javascript', 'application/x-javascript', 'text/x-javascript', 'text/javascript')),
|
||||
'JavascriptPhpLexer': ('pip._vendor.pygments.lexer.templates', 'JavaScript+PHP', ('javascript+php', 'js+php'), (), ('application/x-javascript+php', 'text/x-javascript+php', 'text/javascript+php')),
|
||||
'JavascriptSmartyLexer': ('pip._vendor.pygments.lexer.templates', 'JavaScript+Smarty', ('javascript+smarty', 'js+smarty'), (), ('application/x-javascript+smarty', 'text/x-javascript+smarty', 'text/javascript+smarty')),
|
||||
'JclLexer': ('pip._vendor.pygments.lexer.scripting', 'JCL', ('jcl',), ('*.jcl',), ('text/x-jcl',)),
|
||||
'JsgfLexer': ('pip._vendor.pygments.lexer.grammar_notation', 'JSGF', ('jsgf',), ('*.jsgf',), ('application/jsgf', 'application/x-jsgf', 'text/jsgf')),
|
||||
'JsonBareObjectLexer': ('pip._vendor.pygments.lexer.data', 'JSONBareObject', (), (), ()),
|
||||
'JsonLdLexer': ('pip._vendor.pygments.lexer.data', 'JSON-LD', ('jsonld', 'json-ld'), ('*.jsonld',), ('application/ld+json',)),
|
||||
'JsonLexer': ('pip._vendor.pygments.lexer.data', 'JSON', ('json', 'json-object'), ('*.json', 'Pipfile.lock'), ('application/json', 'application/json-object')),
|
||||
'JspLexer': ('pip._vendor.pygments.lexer.templates', 'Java Server Page', ('jsp',), ('*.jsp',), ('application/x-jsp',)),
|
||||
'JuliaConsoleLexer': ('pip._vendor.pygments.lexer.julia', 'Julia console', ('jlcon', 'julia-repl'), (), ()),
|
||||
'JuliaLexer': ('pip._vendor.pygments.lexer.julia', 'Julia', ('julia', 'jl'), ('*.jl',), ('text/x-julia', 'application/x-julia')),
|
||||
'JuttleLexer': ('pip._vendor.pygments.lexer.javascript', 'Juttle', ('juttle',), ('*.juttle',), ('application/juttle', 'application/x-juttle', 'text/x-juttle', 'text/juttle')),
|
||||
'KalLexer': ('pip._vendor.pygments.lexer.javascript', 'Kal', ('kal',), ('*.kal',), ('text/kal', 'application/kal')),
|
||||
'KconfigLexer': ('pip._vendor.pygments.lexer.configs', 'Kconfig', ('kconfig', 'menuconfig', 'linux-config', 'kernel-config'), ('Kconfig*', '*Config.in*', 'external.in*', 'standard-modules.in'), ('text/x-kconfig',)),
|
||||
'KernelLogLexer': ('pip._vendor.pygments.lexer.textfmts', 'Kernel log', ('kmsg', 'dmesg'), ('*.kmsg', '*.dmesg'), ()),
|
||||
'KokaLexer': ('pip._vendor.pygments.lexer.haskell', 'Koka', ('koka',), ('*.kk', '*.kki'), ('text/x-koka',)),
|
||||
'KotlinLexer': ('pip._vendor.pygments.lexer.jvm', 'Kotlin', ('kotlin',), ('*.kt', '*.kts'), ('text/x-kotlin',)),
|
||||
'KuinLexer': ('pip._vendor.pygments.lexer.kuin', 'Kuin', ('kuin',), ('*.kn',), ()),
|
||||
'LSLLexer': ('pip._vendor.pygments.lexer.scripting', 'LSL', ('lsl',), ('*.lsl',), ('text/x-lsl',)),
|
||||
'LassoCssLexer': ('pip._vendor.pygments.lexer.templates', 'CSS+Lasso', ('css+lasso',), (), ('text/css+lasso',)),
|
||||
'LassoHtmlLexer': ('pip._vendor.pygments.lexer.templates', 'HTML+Lasso', ('html+lasso',), (), ('text/html+lasso', 'application/x-httpd-lasso', 'application/x-httpd-lasso[89]')),
|
||||
'LassoJavascriptLexer': ('pip._vendor.pygments.lexer.templates', 'JavaScript+Lasso', ('javascript+lasso', 'js+lasso'), (), ('application/x-javascript+lasso', 'text/x-javascript+lasso', 'text/javascript+lasso')),
|
||||
'LassoLexer': ('pip._vendor.pygments.lexer.javascript', 'Lasso', ('lasso', 'lassoscript'), ('*.lasso', '*.lasso[89]'), ('text/x-lasso',)),
|
||||
'LassoXmlLexer': ('pip._vendor.pygments.lexer.templates', 'XML+Lasso', ('xml+lasso',), (), ('application/xml+lasso',)),
|
||||
'LeanLexer': ('pip._vendor.pygments.lexer.theorem', 'Lean', ('lean',), ('*.lean',), ('text/x-lean',)),
|
||||
'LessCssLexer': ('pip._vendor.pygments.lexer.css', 'LessCss', ('less',), ('*.less',), ('text/x-less-css',)),
|
||||
'LighttpdConfLexer': ('pip._vendor.pygments.lexer.configs', 'Lighttpd configuration file', ('lighttpd', 'lighty'), ('lighttpd.conf',), ('text/x-lighttpd-conf',)),
|
||||
'LimboLexer': ('pip._vendor.pygments.lexer.inferno', 'Limbo', ('limbo',), ('*.b',), ('text/limbo',)),
|
||||
'LiquidLexer': ('pip._vendor.pygments.lexer.templates', 'liquid', ('liquid',), ('*.liquid',), ()),
|
||||
'LiterateAgdaLexer': ('pip._vendor.pygments.lexer.haskell', 'Literate Agda', ('literate-agda', 'lagda'), ('*.lagda',), ('text/x-literate-agda',)),
|
||||
'LiterateCryptolLexer': ('pip._vendor.pygments.lexer.haskell', 'Literate Cryptol', ('literate-cryptol', 'lcryptol', 'lcry'), ('*.lcry',), ('text/x-literate-cryptol',)),
|
||||
'LiterateHaskellLexer': ('pip._vendor.pygments.lexer.haskell', 'Literate Haskell', ('literate-haskell', 'lhaskell', 'lhs'), ('*.lhs',), ('text/x-literate-haskell',)),
|
||||
'LiterateIdrisLexer': ('pip._vendor.pygments.lexer.haskell', 'Literate Idris', ('literate-idris', 'lidris', 'lidr'), ('*.lidr',), ('text/x-literate-idris',)),
|
||||
'LiveScriptLexer': ('pip._vendor.pygments.lexer.javascript', 'LiveScript', ('livescript', 'live-script'), ('*.ls',), ('text/livescript',)),
|
||||
'LlvmLexer': ('pip._vendor.pygments.lexer.asm', 'LLVM', ('llvm',), ('*.ll',), ('text/x-llvm',)),
|
||||
'LlvmMirBodyLexer': ('pip._vendor.pygments.lexer.asm', 'LLVM-MIR Body', ('llvm-mir-body',), (), ()),
|
||||
'LlvmMirLexer': ('pip._vendor.pygments.lexer.asm', 'LLVM-MIR', ('llvm-mir',), ('*.mir',), ()),
|
||||
'LogosLexer': ('pip._vendor.pygments.lexer.objective', 'Logos', ('logos',), ('*.x', '*.xi', '*.xm', '*.xmi'), ('text/x-logos',)),
|
||||
'LogtalkLexer': ('pip._vendor.pygments.lexer.prolog', 'Logtalk', ('logtalk',), ('*.lgt', '*.logtalk'), ('text/x-logtalk',)),
|
||||
'LuaLexer': ('pip._vendor.pygments.lexer.scripting', 'Lua', ('lua',), ('*.lua', '*.wlua'), ('text/x-lua', 'application/x-lua')),
|
||||
'MIMELexer': ('pip._vendor.pygments.lexer.mime', 'MIME', ('mime',), (), ('multipart/mixed', 'multipart/related', 'multipart/alternative')),
|
||||
'MOOCodeLexer': ('pip._vendor.pygments.lexer.scripting', 'MOOCode', ('moocode', 'moo'), ('*.moo',), ('text/x-moocode',)),
|
||||
'MSDOSSessionLexer': ('pip._vendor.pygments.lexer.shell', 'MSDOS Session', ('doscon',), (), ()),
|
||||
'MakefileLexer': ('pip._vendor.pygments.lexer.make', 'Makefile', ('make', 'makefile', 'mf', 'bsdmake'), ('*.mak', '*.mk', 'Makefile', 'makefile', 'Makefile.*', 'GNUmakefile'), ('text/x-makefile',)),
|
||||
'MakoCssLexer': ('pip._vendor.pygments.lexer.templates', 'CSS+Mako', ('css+mako',), (), ('text/css+mako',)),
|
||||
'MakoHtmlLexer': ('pip._vendor.pygments.lexer.templates', 'HTML+Mako', ('html+mako',), (), ('text/html+mako',)),
|
||||
'MakoJavascriptLexer': ('pip._vendor.pygments.lexer.templates', 'JavaScript+Mako', ('javascript+mako', 'js+mako'), (), ('application/x-javascript+mako', 'text/x-javascript+mako', 'text/javascript+mako')),
|
||||
'MakoLexer': ('pip._vendor.pygments.lexer.templates', 'Mako', ('mako',), ('*.mao',), ('application/x-mako',)),
|
||||
'MakoXmlLexer': ('pip._vendor.pygments.lexer.templates', 'XML+Mako', ('xml+mako',), (), ('application/xml+mako',)),
|
||||
'MaqlLexer': ('pip._vendor.pygments.lexer.business', 'MAQL', ('maql',), ('*.maql',), ('text/x-gooddata-maql', 'application/x-gooddata-maql')),
|
||||
'MarkdownLexer': ('pip._vendor.pygments.lexer.markup', 'Markdown', ('markdown', 'md'), ('*.md', '*.markdown'), ('text/x-markdown',)),
|
||||
'MaskLexer': ('pip._vendor.pygments.lexer.javascript', 'Mask', ('mask',), ('*.mask',), ('text/x-mask',)),
|
||||
'MasonLexer': ('pip._vendor.pygments.lexer.templates', 'Mason', ('mason',), ('*.m', '*.mhtml', '*.mc', '*.mi', 'autohandler', 'dhandler'), ('application/x-mason',)),
|
||||
'MathematicaLexer': ('pip._vendor.pygments.lexer.algebra', 'Mathematica', ('mathematica', 'mma', 'nb'), ('*.nb', '*.cdf', '*.nbp', '*.ma'), ('application/mathematica', 'application/vnd.wolfram.mathematica', 'application/vnd.wolfram.mathematica.package', 'application/vnd.wolfram.cdf')),
|
||||
'MatlabLexer': ('pip._vendor.pygments.lexer.matlab', 'Matlab', ('matlab',), ('*.m',), ('text/matlab',)),
|
||||
'MatlabSessionLexer': ('pip._vendor.pygments.lexer.matlab', 'Matlab session', ('matlabsession',), (), ()),
|
||||
'MesonLexer': ('pip._vendor.pygments.lexer.meson', 'Meson', ('meson', 'meson.build'), ('meson.build', 'meson_options.txt'), ('text/x-meson',)),
|
||||
'MiniDLexer': ('pip._vendor.pygments.lexer.d', 'MiniD', ('minid',), (), ('text/x-minidsrc',)),
|
||||
'MiniScriptLexer': ('pip._vendor.pygments.lexer.scripting', 'MiniScript', ('miniscript', 'ms'), ('*.ms',), ('text/x-minicript', 'application/x-miniscript')),
|
||||
'ModelicaLexer': ('pip._vendor.pygments.lexer.modeling', 'Modelica', ('modelica',), ('*.mo',), ('text/x-modelica',)),
|
||||
'Modula2Lexer': ('pip._vendor.pygments.lexer.modula2', 'Modula-2', ('modula2', 'm2'), ('*.def', '*.mod'), ('text/x-modula2',)),
|
||||
'MoinWikiLexer': ('pip._vendor.pygments.lexer.markup', 'MoinMoin/Trac Wiki markup', ('trac-wiki', 'moin'), (), ('text/x-trac-wiki',)),
|
||||
'MonkeyLexer': ('pip._vendor.pygments.lexer.basic', 'Monkey', ('monkey',), ('*.monkey',), ('text/x-monkey',)),
|
||||
'MonteLexer': ('pip._vendor.pygments.lexer.monte', 'Monte', ('monte',), ('*.mt',), ()),
|
||||
'MoonScriptLexer': ('pip._vendor.pygments.lexer.scripting', 'MoonScript', ('moonscript', 'moon'), ('*.moon',), ('text/x-moonscript', 'application/x-moonscript')),
|
||||
'MoselLexer': ('pip._vendor.pygments.lexer.mosel', 'Mosel', ('mosel',), ('*.mos',), ()),
|
||||
'MozPreprocCssLexer': ('pip._vendor.pygments.lexer.markup', 'CSS+mozpreproc', ('css+mozpreproc',), ('*.css.in',), ()),
|
||||
'MozPreprocHashLexer': ('pip._vendor.pygments.lexer.markup', 'mozhashpreproc', ('mozhashpreproc',), (), ()),
|
||||
'MozPreprocJavascriptLexer': ('pip._vendor.pygments.lexer.markup', 'Javascript+mozpreproc', ('javascript+mozpreproc',), ('*.js.in',), ()),
|
||||
'MozPreprocPercentLexer': ('pip._vendor.pygments.lexer.markup', 'mozpercentpreproc', ('mozpercentpreproc',), (), ()),
|
||||
'MozPreprocXulLexer': ('pip._vendor.pygments.lexer.markup', 'XUL+mozpreproc', ('xul+mozpreproc',), ('*.xul.in',), ()),
|
||||
'MqlLexer': ('pip._vendor.pygments.lexer.c_like', 'MQL', ('mql', 'mq4', 'mq5', 'mql4', 'mql5'), ('*.mq4', '*.mq5', '*.mqh'), ('text/x-mql',)),
|
||||
'MscgenLexer': ('pip._vendor.pygments.lexer.dsls', 'Mscgen', ('mscgen', 'msc'), ('*.msc',), ()),
|
||||
'MuPADLexer': ('pip._vendor.pygments.lexer.algebra', 'MuPAD', ('mupad',), ('*.mu',), ()),
|
||||
'MxmlLexer': ('pip._vendor.pygments.lexer.actionscript', 'MXML', ('mxml',), ('*.mxml',), ()),
|
||||
'MySqlLexer': ('pip._vendor.pygments.lexer.sql', 'MySQL', ('mysql',), (), ('text/x-mysql',)),
|
||||
'MyghtyCssLexer': ('pip._vendor.pygments.lexer.templates', 'CSS+Myghty', ('css+myghty',), (), ('text/css+myghty',)),
|
||||
'MyghtyHtmlLexer': ('pip._vendor.pygments.lexer.templates', 'HTML+Myghty', ('html+myghty',), (), ('text/html+myghty',)),
|
||||
'MyghtyJavascriptLexer': ('pip._vendor.pygments.lexer.templates', 'JavaScript+Myghty', ('javascript+myghty', 'js+myghty'), (), ('application/x-javascript+myghty', 'text/x-javascript+myghty', 'text/javascript+mygthy')),
|
||||
'MyghtyLexer': ('pip._vendor.pygments.lexer.templates', 'Myghty', ('myghty',), ('*.myt', 'autodelegate'), ('application/x-myghty',)),
|
||||
'MyghtyXmlLexer': ('pip._vendor.pygments.lexer.templates', 'XML+Myghty', ('xml+myghty',), (), ('application/xml+myghty',)),
|
||||
'NCLLexer': ('pip._vendor.pygments.lexer.ncl', 'NCL', ('ncl',), ('*.ncl',), ('text/ncl',)),
|
||||
'NSISLexer': ('pip._vendor.pygments.lexer.installers', 'NSIS', ('nsis', 'nsi', 'nsh'), ('*.nsi', '*.nsh'), ('text/x-nsis',)),
|
||||
'NasmLexer': ('pip._vendor.pygments.lexer.asm', 'NASM', ('nasm',), ('*.asm', '*.ASM'), ('text/x-nasm',)),
|
||||
'NasmObjdumpLexer': ('pip._vendor.pygments.lexer.asm', 'objdump-nasm', ('objdump-nasm',), ('*.objdump-intel',), ('text/x-nasm-objdump',)),
|
||||
'NemerleLexer': ('pip._vendor.pygments.lexer.dotnet', 'Nemerle', ('nemerle',), ('*.n',), ('text/x-nemerle',)),
|
||||
'NesCLexer': ('pip._vendor.pygments.lexer.c_like', 'nesC', ('nesc',), ('*.nc',), ('text/x-nescsrc',)),
|
||||
'NestedTextLexer': ('pip._vendor.pygments.lexer.configs', 'NestedText', ('nestedtext', 'nt'), ('*.nt',), ()),
|
||||
'NewLispLexer': ('pip._vendor.pygments.lexer.lisp', 'NewLisp', ('newlisp',), ('*.lsp', '*.nl', '*.kif'), ('text/x-newlisp', 'application/x-newlisp')),
|
||||
'NewspeakLexer': ('pip._vendor.pygments.lexer.smalltalk', 'Newspeak', ('newspeak',), ('*.ns2',), ('text/x-newspeak',)),
|
||||
'NginxConfLexer': ('pip._vendor.pygments.lexer.configs', 'Nginx configuration file', ('nginx',), ('nginx.conf',), ('text/x-nginx-conf',)),
|
||||
'NimrodLexer': ('pip._vendor.pygments.lexer.nimrod', 'Nimrod', ('nimrod', 'nim'), ('*.nim', '*.nimrod'), ('text/x-nim',)),
|
||||
'NitLexer': ('pip._vendor.pygments.lexer.nit', 'Nit', ('nit',), ('*.nit',), ()),
|
||||
'NixLexer': ('pip._vendor.pygments.lexer.nix', 'Nix', ('nixos', 'nix'), ('*.nix',), ('text/x-nix',)),
|
||||
'NodeConsoleLexer': ('pip._vendor.pygments.lexer.javascript', 'Node.js REPL console session', ('nodejsrepl',), (), ('text/x-nodejsrepl',)),
|
||||
'NotmuchLexer': ('pip._vendor.pygments.lexer.textfmts', 'Notmuch', ('notmuch',), (), ()),
|
||||
'NuSMVLexer': ('pip._vendor.pygments.lexer.smv', 'NuSMV', ('nusmv',), ('*.smv',), ()),
|
||||
'NumPyLexer': ('pip._vendor.pygments.lexer.python', 'NumPy', ('numpy',), (), ()),
|
||||
'ObjdumpLexer': ('pip._vendor.pygments.lexer.asm', 'objdump', ('objdump',), ('*.objdump',), ('text/x-objdump',)),
|
||||
'ObjectiveCLexer': ('pip._vendor.pygments.lexer.objective', 'Objective-C', ('objective-c', 'objectivec', 'obj-c', 'objc'), ('*.m', '*.h'), ('text/x-objective-c',)),
|
||||
'ObjectiveCppLexer': ('pip._vendor.pygments.lexer.objective', 'Objective-C++', ('objective-c++', 'objectivec++', 'obj-c++', 'objc++'), ('*.mm', '*.hh'), ('text/x-objective-c++',)),
|
||||
'ObjectiveJLexer': ('pip._vendor.pygments.lexer.javascript', 'Objective-J', ('objective-j', 'objectivej', 'obj-j', 'objj'), ('*.j',), ('text/x-objective-j',)),
|
||||
'OcamlLexer': ('pip._vendor.pygments.lexer.ml', 'OCaml', ('ocaml',), ('*.ml', '*.mli', '*.mll', '*.mly'), ('text/x-ocaml',)),
|
||||
'OctaveLexer': ('pip._vendor.pygments.lexer.matlab', 'Octave', ('octave',), ('*.m',), ('text/octave',)),
|
||||
'OdinLexer': ('pip._vendor.pygments.lexer.archetype', 'ODIN', ('odin',), ('*.odin',), ('text/odin',)),
|
||||
'OmgIdlLexer': ('pip._vendor.pygments.lexer.c_like', 'OMG Interface Definition Language', ('omg-idl',), ('*.idl', '*.pidl'), ()),
|
||||
'OocLexer': ('pip._vendor.pygments.lexer.ooc', 'Ooc', ('ooc',), ('*.ooc',), ('text/x-ooc',)),
|
||||
'OpaLexer': ('pip._vendor.pygments.lexer.ml', 'Opa', ('opa',), ('*.opa',), ('text/x-opa',)),
|
||||
'OpenEdgeLexer': ('pip._vendor.pygments.lexer.business', 'OpenEdge ABL', ('openedge', 'abl', 'progress'), ('*.p', '*.cls'), ('text/x-openedge', 'application/x-openedge')),
|
||||
'OutputLexer': ('pip._vendor.pygments.lexer.special', 'Text output', ('output',), (), ()),
|
||||
'PacmanConfLexer': ('pip._vendor.pygments.lexer.configs', 'PacmanConf', ('pacmanconf',), ('pacman.conf',), ()),
|
||||
'PanLexer': ('pip._vendor.pygments.lexer.dsls', 'Pan', ('pan',), ('*.pan',), ()),
|
||||
'ParaSailLexer': ('pip._vendor.pygments.lexer.parasail', 'ParaSail', ('parasail',), ('*.psi', '*.psl'), ('text/x-parasail',)),
|
||||
'PawnLexer': ('pip._vendor.pygments.lexer.pawn', 'Pawn', ('pawn',), ('*.p', '*.pwn', '*.inc'), ('text/x-pawn',)),
|
||||
'PegLexer': ('pip._vendor.pygments.lexer.grammar_notation', 'PEG', ('peg',), ('*.peg',), ('text/x-peg',)),
|
||||
'Perl6Lexer': ('pip._vendor.pygments.lexer.perl', 'Perl6', ('perl6', 'pl6', 'raku'), ('*.pl', '*.pm', '*.nqp', '*.p6', '*.6pl', '*.p6l', '*.pl6', '*.6pm', '*.p6m', '*.pm6', '*.t', '*.raku', '*.rakumod', '*.rakutest', '*.rakudoc'), ('text/x-perl6', 'application/x-perl6')),
|
||||
'PerlLexer': ('pip._vendor.pygments.lexer.perl', 'Perl', ('perl', 'pl'), ('*.pl', '*.pm', '*.t', '*.perl'), ('text/x-perl', 'application/x-perl')),
|
||||
'PhpLexer': ('pip._vendor.pygments.lexer.php', 'PHP', ('php', 'php3', 'php4', 'php5'), ('*.php', '*.php[345]', '*.inc'), ('text/x-php',)),
|
||||
'PigLexer': ('pip._vendor.pygments.lexer.jvm', 'Pig', ('pig',), ('*.pig',), ('text/x-pig',)),
|
||||
'PikeLexer': ('pip._vendor.pygments.lexer.c_like', 'Pike', ('pike',), ('*.pike', '*.pmod'), ('text/x-pike',)),
|
||||
'PkgConfigLexer': ('pip._vendor.pygments.lexer.configs', 'PkgConfig', ('pkgconfig',), ('*.pc',), ()),
|
||||
'PlPgsqlLexer': ('pip._vendor.pygments.lexer.sql', 'PL/pgSQL', ('plpgsql',), (), ('text/x-plpgsql',)),
|
||||
'PointlessLexer': ('pip._vendor.pygments.lexer.pointless', 'Pointless', ('pointless',), ('*.ptls',), ()),
|
||||
'PonyLexer': ('pip._vendor.pygments.lexer.pony', 'Pony', ('pony',), ('*.pony',), ()),
|
||||
'PostScriptLexer': ('pip._vendor.pygments.lexer.graphics', 'PostScript', ('postscript', 'postscr'), ('*.ps', '*.eps'), ('application/postscript',)),
|
||||
'PostgresConsoleLexer': ('pip._vendor.pygments.lexer.sql', 'PostgreSQL console (psql)', ('psql', 'postgresql-console', 'postgres-console'), (), ('text/x-postgresql-psql',)),
|
||||
'PostgresLexer': ('pip._vendor.pygments.lexer.sql', 'PostgreSQL SQL dialect', ('postgresql', 'postgres'), (), ('text/x-postgresql',)),
|
||||
'PovrayLexer': ('pip._vendor.pygments.lexer.graphics', 'POVRay', ('pov',), ('*.pov', '*.inc'), ('text/x-povray',)),
|
||||
'PowerShellLexer': ('pip._vendor.pygments.lexer.shell', 'PowerShell', ('powershell', 'pwsh', 'posh', 'ps1', 'psm1'), ('*.ps1', '*.psm1'), ('text/x-powershell',)),
|
||||
'PowerShellSessionLexer': ('pip._vendor.pygments.lexer.shell', 'PowerShell Session', ('pwsh-session', 'ps1con'), (), ()),
|
||||
'PraatLexer': ('pip._vendor.pygments.lexer.praat', 'Praat', ('praat',), ('*.praat', '*.proc', '*.psc'), ()),
|
||||
'ProcfileLexer': ('pip._vendor.pygments.lexer.procfile', 'Procfile', ('procfile',), ('Procfile',), ()),
|
||||
'PrologLexer': ('pip._vendor.pygments.lexer.prolog', 'Prolog', ('prolog',), ('*.ecl', '*.prolog', '*.pro', '*.pl'), ('text/x-prolog',)),
|
||||
'PromQLLexer': ('pip._vendor.pygments.lexer.promql', 'PromQL', ('promql',), ('*.promql',), ()),
|
||||
'PropertiesLexer': ('pip._vendor.pygments.lexer.configs', 'Properties', ('properties', 'jproperties'), ('*.properties',), ('text/x-java-properties',)),
|
||||
'ProtoBufLexer': ('pip._vendor.pygments.lexer.dsls', 'Protocol Buffer', ('protobuf', 'proto'), ('*.proto',), ()),
|
||||
'PsyshConsoleLexer': ('pip._vendor.pygments.lexer.php', 'PsySH console session for PHP', ('psysh',), (), ()),
|
||||
'PugLexer': ('pip._vendor.pygments.lexer.html', 'Pug', ('pug', 'jade'), ('*.pug', '*.jade'), ('text/x-pug', 'text/x-jade')),
|
||||
'PuppetLexer': ('pip._vendor.pygments.lexer.dsls', 'Puppet', ('puppet',), ('*.pp',), ()),
|
||||
'PyPyLogLexer': ('pip._vendor.pygments.lexer.console', 'PyPy Log', ('pypylog', 'pypy'), ('*.pypylog',), ('application/x-pypylog',)),
|
||||
'Python2Lexer': ('pip._vendor.pygments.lexer.python', 'Python 2.x', ('python2', 'py2'), (), ('text/x-python2', 'application/x-python2')),
|
||||
'Python2TracebackLexer': ('pip._vendor.pygments.lexer.python', 'Python 2.x Traceback', ('py2tb',), ('*.py2tb',), ('text/x-python2-traceback',)),
|
||||
'PythonConsoleLexer': ('pip._vendor.pygments.lexer.python', 'Python console session', ('pycon',), (), ('text/x-python-doctest',)),
|
||||
'PythonLexer': ('pip._vendor.pygments.lexer.python', 'Python', ('python', 'py', 'sage', 'python3', 'py3'), ('*.py', '*.pyw', '*.jy', '*.sage', '*.sc', 'SConstruct', 'SConscript', '*.bzl', 'BUCK', 'BUILD', 'BUILD.bazel', 'WORKSPACE', '*.tac'), ('text/x-python', 'application/x-python', 'text/x-python3', 'application/x-python3')),
|
||||
'PythonTracebackLexer': ('pip._vendor.pygments.lexer.python', 'Python Traceback', ('pytb', 'py3tb'), ('*.pytb', '*.py3tb'), ('text/x-python-traceback', 'text/x-python3-traceback')),
|
||||
'QBasicLexer': ('pip._vendor.pygments.lexer.basic', 'QBasic', ('qbasic', 'basic'), ('*.BAS', '*.bas'), ('text/basic',)),
|
||||
'QVToLexer': ('pip._vendor.pygments.lexer.qvt', 'QVTO', ('qvto', 'qvt'), ('*.qvto',), ()),
|
||||
'QmlLexer': ('pip._vendor.pygments.lexer.webmisc', 'QML', ('qml', 'qbs'), ('*.qml', '*.qbs'), ('application/x-qml', 'application/x-qt.qbs+qml')),
|
||||
'RConsoleLexer': ('pip._vendor.pygments.lexer.r', 'RConsole', ('rconsole', 'rout'), ('*.Rout',), ()),
|
||||
'RNCCompactLexer': ('pip._vendor.pygments.lexer.rnc', 'Relax-NG Compact', ('rng-compact', 'rnc'), ('*.rnc',), ()),
|
||||
'RPMSpecLexer': ('pip._vendor.pygments.lexer.installers', 'RPMSpec', ('spec',), ('*.spec',), ('text/x-rpm-spec',)),
|
||||
'RacketLexer': ('pip._vendor.pygments.lexer.lisp', 'Racket', ('racket', 'rkt'), ('*.rkt', '*.rktd', '*.rktl'), ('text/x-racket', 'application/x-racket')),
|
||||
'RagelCLexer': ('pip._vendor.pygments.lexer.parsers', 'Ragel in C Host', ('ragel-c',), ('*.rl',), ()),
|
||||
'RagelCppLexer': ('pip._vendor.pygments.lexer.parsers', 'Ragel in CPP Host', ('ragel-cpp',), ('*.rl',), ()),
|
||||
'RagelDLexer': ('pip._vendor.pygments.lexer.parsers', 'Ragel in D Host', ('ragel-d',), ('*.rl',), ()),
|
||||
'RagelEmbeddedLexer': ('pip._vendor.pygments.lexer.parsers', 'Embedded Ragel', ('ragel-em',), ('*.rl',), ()),
|
||||
'RagelJavaLexer': ('pip._vendor.pygments.lexer.parsers', 'Ragel in Java Host', ('ragel-java',), ('*.rl',), ()),
|
||||
'RagelLexer': ('pip._vendor.pygments.lexer.parsers', 'Ragel', ('ragel',), (), ()),
|
||||
'RagelObjectiveCLexer': ('pip._vendor.pygments.lexer.parsers', 'Ragel in Objective C Host', ('ragel-objc',), ('*.rl',), ()),
|
||||
'RagelRubyLexer': ('pip._vendor.pygments.lexer.parsers', 'Ragel in Ruby Host', ('ragel-ruby', 'ragel-rb'), ('*.rl',), ()),
|
||||
'RawTokenLexer': ('pip._vendor.pygments.lexer.special', 'Raw token data', (), (), ('application/x-pygments-tokens',)),
|
||||
'RdLexer': ('pip._vendor.pygments.lexer.r', 'Rd', ('rd',), ('*.Rd',), ('text/x-r-doc',)),
|
||||
'ReasonLexer': ('pip._vendor.pygments.lexer.ml', 'ReasonML', ('reasonml', 'reason'), ('*.re', '*.rei'), ('text/x-reasonml',)),
|
||||
'RebolLexer': ('pip._vendor.pygments.lexer.rebol', 'REBOL', ('rebol',), ('*.r', '*.r3', '*.reb'), ('text/x-rebol',)),
|
||||
'RedLexer': ('pip._vendor.pygments.lexer.rebol', 'Red', ('red', 'red/system'), ('*.red', '*.reds'), ('text/x-red', 'text/x-red-system')),
|
||||
'RedcodeLexer': ('pip._vendor.pygments.lexer.esoteric', 'Redcode', ('redcode',), ('*.cw',), ()),
|
||||
'RegeditLexer': ('pip._vendor.pygments.lexer.configs', 'reg', ('registry',), ('*.reg',), ('text/x-windows-registry',)),
|
||||
'ResourceLexer': ('pip._vendor.pygments.lexer.resource', 'ResourceBundle', ('resourcebundle', 'resource'), (), ()),
|
||||
'RexxLexer': ('pip._vendor.pygments.lexer.scripting', 'Rexx', ('rexx', 'arexx'), ('*.rexx', '*.rex', '*.rx', '*.arexx'), ('text/x-rexx',)),
|
||||
'RhtmlLexer': ('pip._vendor.pygments.lexer.templates', 'RHTML', ('rhtml', 'html+erb', 'html+ruby'), ('*.rhtml',), ('text/html+ruby',)),
|
||||
'RideLexer': ('pip._vendor.pygments.lexer.ride', 'Ride', ('ride',), ('*.ride',), ('text/x-ride',)),
|
||||
'RoboconfGraphLexer': ('pip._vendor.pygments.lexer.roboconf', 'Roboconf Graph', ('roboconf-graph',), ('*.graph',), ()),
|
||||
'RoboconfInstancesLexer': ('pip._vendor.pygments.lexer.roboconf', 'Roboconf Instances', ('roboconf-instances',), ('*.instances',), ()),
|
||||
'RobotFrameworkLexer': ('pip._vendor.pygments.lexer.robotframework', 'RobotFramework', ('robotframework',), ('*.robot',), ('text/x-robotframework',)),
|
||||
'RqlLexer': ('pip._vendor.pygments.lexer.sql', 'RQL', ('rql',), ('*.rql',), ('text/x-rql',)),
|
||||
'RslLexer': ('pip._vendor.pygments.lexer.dsls', 'RSL', ('rsl',), ('*.rsl',), ('text/rsl',)),
|
||||
'RstLexer': ('pip._vendor.pygments.lexer.markup', 'reStructuredText', ('restructuredtext', 'rst', 'rest'), ('*.rst', '*.rest'), ('text/x-rst', 'text/prs.fallenstein.rst')),
|
||||
'RtsLexer': ('pip._vendor.pygments.lexer.trafficscript', 'TrafficScript', ('trafficscript', 'rts'), ('*.rts',), ()),
|
||||
'RubyConsoleLexer': ('pip._vendor.pygments.lexer.ruby', 'Ruby irb session', ('rbcon', 'irb'), (), ('text/x-ruby-shellsession',)),
|
||||
'RubyLexer': ('pip._vendor.pygments.lexer.ruby', 'Ruby', ('ruby', 'rb', 'duby'), ('*.rb', '*.rbw', 'Rakefile', '*.rake', '*.gemspec', '*.rbx', '*.duby', 'Gemfile'), ('text/x-ruby', 'application/x-ruby')),
|
||||
'RustLexer': ('pip._vendor.pygments.lexer.rust', 'Rust', ('rust', 'rs'), ('*.rs', '*.rs.in'), ('text/rust', 'text/x-rust')),
|
||||
'SASLexer': ('pip._vendor.pygments.lexer.sas', 'SAS', ('sas',), ('*.SAS', '*.sas'), ('text/x-sas', 'text/sas', 'application/x-sas')),
|
||||
'SLexer': ('pip._vendor.pygments.lexer.r', 'S', ('splus', 's', 'r'), ('*.S', '*.R', '.Rhistory', '.Rprofile', '.Renviron'), ('text/S-plus', 'text/S', 'text/x-r-source', 'text/x-r', 'text/x-R', 'text/x-r-history', 'text/x-r-profile')),
|
||||
'SMLLexer': ('pip._vendor.pygments.lexer.ml', 'Standard ML', ('sml',), ('*.sml', '*.sig', '*.fun'), ('text/x-standardml', 'application/x-standardml')),
|
||||
'SarlLexer': ('pip._vendor.pygments.lexer.jvm', 'SARL', ('sarl',), ('*.sarl',), ('text/x-sarl',)),
|
||||
'SassLexer': ('pip._vendor.pygments.lexer.css', 'Sass', ('sass',), ('*.sass',), ('text/x-sass',)),
|
||||
'ScalaLexer': ('pip._vendor.pygments.lexer.jvm', 'Scala', ('scala',), ('*.scala',), ('text/x-scala',)),
|
||||
'ScamlLexer': ('pip._vendor.pygments.lexer.html', 'Scaml', ('scaml',), ('*.scaml',), ('text/x-scaml',)),
|
||||
'ScdocLexer': ('pip._vendor.pygments.lexer.scdoc', 'scdoc', ('scdoc', 'scd'), ('*.scd', '*.scdoc'), ()),
|
||||
'SchemeLexer': ('pip._vendor.pygments.lexer.lisp', 'Scheme', ('scheme', 'scm'), ('*.scm', '*.ss'), ('text/x-scheme', 'application/x-scheme')),
|
||||
'ScilabLexer': ('pip._vendor.pygments.lexer.matlab', 'Scilab', ('scilab',), ('*.sci', '*.sce', '*.tst'), ('text/scilab',)),
|
||||
'ScssLexer': ('pip._vendor.pygments.lexer.css', 'SCSS', ('scss',), ('*.scss',), ('text/x-scss',)),
|
||||
'ShExCLexer': ('pip._vendor.pygments.lexer.rdf', 'ShExC', ('shexc', 'shex'), ('*.shex',), ('text/shex',)),
|
||||
'ShenLexer': ('pip._vendor.pygments.lexer.lisp', 'Shen', ('shen',), ('*.shen',), ('text/x-shen', 'application/x-shen')),
|
||||
'SieveLexer': ('pip._vendor.pygments.lexer.sieve', 'Sieve', ('sieve',), ('*.siv', '*.sieve'), ()),
|
||||
'SilverLexer': ('pip._vendor.pygments.lexer.verification', 'Silver', ('silver',), ('*.sil', '*.vpr'), ()),
|
||||
'SingularityLexer': ('pip._vendor.pygments.lexer.configs', 'Singularity', ('singularity',), ('*.def', 'Singularity'), ()),
|
||||
'SlashLexer': ('pip._vendor.pygments.lexer.slash', 'Slash', ('slash',), ('*.sla',), ()),
|
||||
'SlimLexer': ('pip._vendor.pygments.lexer.webmisc', 'Slim', ('slim',), ('*.slim',), ('text/x-slim',)),
|
||||
'SlurmBashLexer': ('pip._vendor.pygments.lexer.shell', 'Slurm', ('slurm', 'sbatch'), ('*.sl',), ()),
|
||||
'SmaliLexer': ('pip._vendor.pygments.lexer.dalvik', 'Smali', ('smali',), ('*.smali',), ('text/smali',)),
|
||||
'SmalltalkLexer': ('pip._vendor.pygments.lexer.smalltalk', 'Smalltalk', ('smalltalk', 'squeak', 'st'), ('*.st',), ('text/x-smalltalk',)),
|
||||
'SmartGameFormatLexer': ('pip._vendor.pygments.lexer.sgf', 'SmartGameFormat', ('sgf',), ('*.sgf',), ()),
|
||||
'SmartyLexer': ('pip._vendor.pygments.lexer.templates', 'Smarty', ('smarty',), ('*.tpl',), ('application/x-smarty',)),
|
||||
'SmithyLexer': ('pip._vendor.pygments.lexer.smithy', 'Smithy', ('smithy',), ('*.smithy',), ()),
|
||||
'SnobolLexer': ('pip._vendor.pygments.lexer.snobol', 'Snobol', ('snobol',), ('*.snobol',), ('text/x-snobol',)),
|
||||
'SnowballLexer': ('pip._vendor.pygments.lexer.dsls', 'Snowball', ('snowball',), ('*.sbl',), ()),
|
||||
'SolidityLexer': ('pip._vendor.pygments.lexer.solidity', 'Solidity', ('solidity',), ('*.sol',), ()),
|
||||
'SourcePawnLexer': ('pip._vendor.pygments.lexer.pawn', 'SourcePawn', ('sp',), ('*.sp',), ('text/x-sourcepawn',)),
|
||||
'SourcesListLexer': ('pip._vendor.pygments.lexer.installers', 'Debian Sourcelist', ('debsources', 'sourceslist', 'sources.list'), ('sources.list',), ()),
|
||||
'SparqlLexer': ('pip._vendor.pygments.lexer.rdf', 'SPARQL', ('sparql',), ('*.rq', '*.sparql'), ('application/sparql-query',)),
|
||||
'SqlLexer': ('pip._vendor.pygments.lexer.sql', 'SQL', ('sql',), ('*.sql',), ('text/x-sql',)),
|
||||
'SqliteConsoleLexer': ('pip._vendor.pygments.lexer.sql', 'sqlite3con', ('sqlite3',), ('*.sqlite3-console',), ('text/x-sqlite3-console',)),
|
||||
'SquidConfLexer': ('pip._vendor.pygments.lexer.configs', 'SquidConf', ('squidconf', 'squid.conf', 'squid'), ('squid.conf',), ('text/x-squidconf',)),
|
||||
'SspLexer': ('pip._vendor.pygments.lexer.templates', 'Scalate Server Page', ('ssp',), ('*.ssp',), ('application/x-ssp',)),
|
||||
'StanLexer': ('pip._vendor.pygments.lexer.modeling', 'Stan', ('stan',), ('*.stan',), ()),
|
||||
'StataLexer': ('pip._vendor.pygments.lexer.stata', 'Stata', ('stata', 'do'), ('*.do', '*.ado'), ('text/x-stata', 'text/stata', 'application/x-stata')),
|
||||
'SuperColliderLexer': ('pip._vendor.pygments.lexer.supercollider', 'SuperCollider', ('supercollider', 'sc'), ('*.sc', '*.scd'), ('application/supercollider', 'text/supercollider')),
|
||||
'SwiftLexer': ('pip._vendor.pygments.lexer.objective', 'Swift', ('swift',), ('*.swift',), ('text/x-swift',)),
|
||||
'SwigLexer': ('pip._vendor.pygments.lexer.c_like', 'SWIG', ('swig',), ('*.swg', '*.i'), ('text/swig',)),
|
||||
'SystemVerilogLexer': ('pip._vendor.pygments.lexer.hdl', 'systemverilog', ('systemverilog', 'sv'), ('*.sv', '*.svh'), ('text/x-systemverilog',)),
|
||||
'TAPLexer': ('pip._vendor.pygments.lexer.testing', 'TAP', ('tap',), ('*.tap',), ()),
|
||||
'TNTLexer': ('pip._vendor.pygments.lexer.tnt', 'Typographic Number Theory', ('tnt',), ('*.tnt',), ()),
|
||||
'TOMLLexer': ('pip._vendor.pygments.lexer.configs', 'TOML', ('toml',), ('*.toml', 'Pipfile', 'poetry.lock'), ()),
|
||||
'Tads3Lexer': ('pip._vendor.pygments.lexer.int_fiction', 'TADS 3', ('tads3',), ('*.t',), ()),
|
||||
'TasmLexer': ('pip._vendor.pygments.lexer.asm', 'TASM', ('tasm',), ('*.asm', '*.ASM', '*.tasm'), ('text/x-tasm',)),
|
||||
'TclLexer': ('pip._vendor.pygments.lexer.tcl', 'Tcl', ('tcl',), ('*.tcl', '*.rvt'), ('text/x-tcl', 'text/x-script.tcl', 'application/x-tcl')),
|
||||
'TcshLexer': ('pip._vendor.pygments.lexer.shell', 'Tcsh', ('tcsh', 'csh'), ('*.tcsh', '*.csh'), ('application/x-csh',)),
|
||||
'TcshSessionLexer': ('pip._vendor.pygments.lexer.shell', 'Tcsh Session', ('tcshcon',), (), ()),
|
||||
'TeaTemplateLexer': ('pip._vendor.pygments.lexer.templates', 'Tea', ('tea',), ('*.tea',), ('text/x-tea',)),
|
||||
'TealLexer': ('pip._vendor.pygments.lexer.teal', 'teal', ('teal',), ('*.teal',), ()),
|
||||
'TeraTermLexer': ('pip._vendor.pygments.lexer.teraterm', 'Tera Term macro', ('teratermmacro', 'teraterm', 'ttl'), ('*.ttl',), ('text/x-teratermmacro',)),
|
||||
'TermcapLexer': ('pip._vendor.pygments.lexer.configs', 'Termcap', ('termcap',), ('termcap', 'termcap.src'), ()),
|
||||
'TerminfoLexer': ('pip._vendor.pygments.lexer.configs', 'Terminfo', ('terminfo',), ('terminfo', 'terminfo.src'), ()),
|
||||
'TerraformLexer': ('pip._vendor.pygments.lexer.configs', 'Terraform', ('terraform', 'tf'), ('*.tf',), ('application/x-tf', 'application/x-terraform')),
|
||||
'TexLexer': ('pip._vendor.pygments.lexer.markup', 'TeX', ('tex', 'latex'), ('*.tex', '*.aux', '*.toc'), ('text/x-tex', 'text/x-latex')),
|
||||
'TextLexer': ('pip._vendor.pygments.lexer.special', 'Text only', ('text',), ('*.txt',), ('text/plain',)),
|
||||
'ThingsDBLexer': ('pip._vendor.pygments.lexer.thingsdb', 'ThingsDB', ('ti', 'thingsdb'), ('*.ti',), ()),
|
||||
'ThriftLexer': ('pip._vendor.pygments.lexer.dsls', 'Thrift', ('thrift',), ('*.thrift',), ('application/x-thrift',)),
|
||||
'TiddlyWiki5Lexer': ('pip._vendor.pygments.lexer.markup', 'tiddler', ('tid',), ('*.tid',), ('text/vnd.tiddlywiki',)),
|
||||
'TodotxtLexer': ('pip._vendor.pygments.lexer.textfmts', 'Todotxt', ('todotxt',), ('todo.txt', '*.todotxt'), ('text/x-todo',)),
|
||||
'TransactSqlLexer': ('pip._vendor.pygments.lexer.sql', 'Transact-SQL', ('tsql', 't-sql'), ('*.sql',), ('text/x-tsql',)),
|
||||
'TreetopLexer': ('pip._vendor.pygments.lexer.parsers', 'Treetop', ('treetop',), ('*.treetop', '*.tt'), ()),
|
||||
'TurtleLexer': ('pip._vendor.pygments.lexer.rdf', 'Turtle', ('turtle',), ('*.ttl',), ('text/turtle', 'application/x-turtle')),
|
||||
'TwigHtmlLexer': ('pip._vendor.pygments.lexer.templates', 'HTML+Twig', ('html+twig',), ('*.twig',), ('text/html+twig',)),
|
||||
'TwigLexer': ('pip._vendor.pygments.lexer.templates', 'Twig', ('twig',), (), ('application/x-twig',)),
|
||||
'TypeScriptLexer': ('pip._vendor.pygments.lexer.javascript', 'TypeScript', ('typescript', 'ts'), ('*.ts',), ('application/x-typescript', 'text/x-typescript')),
|
||||
'TypoScriptCssDataLexer': ('pip._vendor.pygments.lexer.typoscript', 'TypoScriptCssData', ('typoscriptcssdata',), (), ()),
|
||||
'TypoScriptHtmlDataLexer': ('pip._vendor.pygments.lexer.typoscript', 'TypoScriptHtmlData', ('typoscripthtmldata',), (), ()),
|
||||
'TypoScriptLexer': ('pip._vendor.pygments.lexer.typoscript', 'TypoScript', ('typoscript',), ('*.typoscript',), ('text/x-typoscript',)),
|
||||
'UcodeLexer': ('pip._vendor.pygments.lexer.unicon', 'ucode', ('ucode',), ('*.u', '*.u1', '*.u2'), ()),
|
||||
'UniconLexer': ('pip._vendor.pygments.lexer.unicon', 'Unicon', ('unicon',), ('*.icn',), ('text/unicon',)),
|
||||
'UrbiscriptLexer': ('pip._vendor.pygments.lexer.urbi', 'UrbiScript', ('urbiscript',), ('*.u',), ('application/x-urbiscript',)),
|
||||
'UsdLexer': ('pip._vendor.pygments.lexer.usd', 'USD', ('usd', 'usda'), ('*.usd', '*.usda'), ()),
|
||||
'VBScriptLexer': ('pip._vendor.pygments.lexer.basic', 'VBScript', ('vbscript',), ('*.vbs', '*.VBS'), ()),
|
||||
'VCLLexer': ('pip._vendor.pygments.lexer.varnish', 'VCL', ('vcl',), ('*.vcl',), ('text/x-vclsrc',)),
|
||||
'VCLSnippetLexer': ('pip._vendor.pygments.lexer.varnish', 'VCLSnippets', ('vclsnippets', 'vclsnippet'), (), ('text/x-vclsnippet',)),
|
||||
'VCTreeStatusLexer': ('pip._vendor.pygments.lexer.console', 'VCTreeStatus', ('vctreestatus',), (), ()),
|
||||
'VGLLexer': ('pip._vendor.pygments.lexer.dsls', 'VGL', ('vgl',), ('*.rpf',), ()),
|
||||
'ValaLexer': ('pip._vendor.pygments.lexer.c_like', 'Vala', ('vala', 'vapi'), ('*.vala', '*.vapi'), ('text/x-vala',)),
|
||||
'VbNetAspxLexer': ('pip._vendor.pygments.lexer.dotnet', 'aspx-vb', ('aspx-vb',), ('*.aspx', '*.asax', '*.ascx', '*.ashx', '*.asmx', '*.axd'), ()),
|
||||
'VbNetLexer': ('pip._vendor.pygments.lexer.dotnet', 'VB.net', ('vb.net', 'vbnet'), ('*.vb', '*.bas'), ('text/x-vbnet', 'text/x-vba')),
|
||||
'VelocityHtmlLexer': ('pip._vendor.pygments.lexer.templates', 'HTML+Velocity', ('html+velocity',), (), ('text/html+velocity',)),
|
||||
'VelocityLexer': ('pip._vendor.pygments.lexer.templates', 'Velocity', ('velocity',), ('*.vm', '*.fhtml'), ()),
|
||||
'VelocityXmlLexer': ('pip._vendor.pygments.lexer.templates', 'XML+Velocity', ('xml+velocity',), (), ('application/xml+velocity',)),
|
||||
'VerilogLexer': ('pip._vendor.pygments.lexer.hdl', 'verilog', ('verilog', 'v'), ('*.v',), ('text/x-verilog',)),
|
||||
'VhdlLexer': ('pip._vendor.pygments.lexer.hdl', 'vhdl', ('vhdl',), ('*.vhdl', '*.vhd'), ('text/x-vhdl',)),
|
||||
'VimLexer': ('pip._vendor.pygments.lexer.textedit', 'VimL', ('vim',), ('*.vim', '.vimrc', '.exrc', '.gvimrc', '_vimrc', '_exrc', '_gvimrc', 'vimrc', 'gvimrc'), ('text/x-vim',)),
|
||||
'WDiffLexer': ('pip._vendor.pygments.lexer.diff', 'WDiff', ('wdiff',), ('*.wdiff',), ()),
|
||||
'WatLexer': ('pip._vendor.pygments.lexer.webassembly', 'WebAssembly', ('wast', 'wat'), ('*.wat', '*.wast'), ()),
|
||||
'WebIDLLexer': ('pip._vendor.pygments.lexer.webidl', 'Web IDL', ('webidl',), ('*.webidl',), ()),
|
||||
'WhileyLexer': ('pip._vendor.pygments.lexer.whiley', 'Whiley', ('whiley',), ('*.whiley',), ('text/x-whiley',)),
|
||||
'X10Lexer': ('pip._vendor.pygments.lexer.x10', 'X10', ('x10', 'xten'), ('*.x10',), ('text/x-x10',)),
|
||||
'XQueryLexer': ('pip._vendor.pygments.lexer.webmisc', 'XQuery', ('xquery', 'xqy', 'xq', 'xql', 'xqm'), ('*.xqy', '*.xquery', '*.xq', '*.xql', '*.xqm'), ('text/xquery', 'application/xquery')),
|
||||
'XmlDjangoLexer': ('pip._vendor.pygments.lexer.templates', 'XML+Django/Jinja', ('xml+django', 'xml+jinja'), (), ('application/xml+django', 'application/xml+jinja')),
|
||||
'XmlErbLexer': ('pip._vendor.pygments.lexer.templates', 'XML+Ruby', ('xml+ruby', 'xml+erb'), (), ('application/xml+ruby',)),
|
||||
'XmlLexer': ('pip._vendor.pygments.lexer.html', 'XML', ('xml',), ('*.xml', '*.xsl', '*.rss', '*.xslt', '*.xsd', '*.wsdl', '*.wsf'), ('text/xml', 'application/xml', 'image/svg+xml', 'application/rss+xml', 'application/atom+xml')),
|
||||
'XmlPhpLexer': ('pip._vendor.pygments.lexer.templates', 'XML+PHP', ('xml+php',), (), ('application/xml+php',)),
|
||||
'XmlSmartyLexer': ('pip._vendor.pygments.lexer.templates', 'XML+Smarty', ('xml+smarty',), (), ('application/xml+smarty',)),
|
||||
'XorgLexer': ('pip._vendor.pygments.lexer.xorg', 'Xorg', ('xorg.conf',), ('xorg.conf',), ()),
|
||||
'XsltLexer': ('pip._vendor.pygments.lexer.html', 'XSLT', ('xslt',), ('*.xsl', '*.xslt', '*.xpl'), ('application/xsl+xml', 'application/xslt+xml')),
|
||||
'XtendLexer': ('pip._vendor.pygments.lexer.jvm', 'Xtend', ('xtend',), ('*.xtend',), ('text/x-xtend',)),
|
||||
'XtlangLexer': ('pip._vendor.pygments.lexer.lisp', 'xtlang', ('extempore',), ('*.xtm',), ()),
|
||||
'YamlJinjaLexer': ('pip._vendor.pygments.lexer.templates', 'YAML+Jinja', ('yaml+jinja', 'salt', 'sls'), ('*.sls',), ('text/x-yaml+jinja', 'text/x-sls')),
|
||||
'YamlLexer': ('pip._vendor.pygments.lexer.data', 'YAML', ('yaml',), ('*.yaml', '*.yml'), ('text/x-yaml',)),
|
||||
'YangLexer': ('pip._vendor.pygments.lexer.yang', 'YANG', ('yang',), ('*.yang',), ('application/yang',)),
|
||||
'ZeekLexer': ('pip._vendor.pygments.lexer.dsls', 'Zeek', ('zeek', 'bro'), ('*.zeek', '*.bro'), ()),
|
||||
'ZephirLexer': ('pip._vendor.pygments.lexer.php', 'Zephir', ('zephir',), ('*.zep',), ()),
|
||||
'ZigLexer': ('pip._vendor.pygments.lexer.zig', 'Zig', ('zig',), ('*.zig',), ('text/zig',)),
|
||||
'apdlexer': ('pip._vendor.pygments.lexer.apdlexer', 'ANSYS parametric design language', ('ansys', 'apdl'), ('*.ans',), ()),
|
||||
}
|
||||
|
||||
if __name__ == '__main__': # pragma: no cover
|
||||
import sys
|
||||
import os
|
||||
|
||||
# lookup lexers
|
||||
found_lexers = []
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
for root, dirs, files in os.walk('.'):
|
||||
for filename in files:
|
||||
if filename.endswith('.py') and not filename.startswith('_'):
|
||||
module_name = 'pygments.lexers%s.%s' % (
|
||||
root[1:].replace('/', '.'), filename[:-3])
|
||||
print(module_name)
|
||||
module = __import__(module_name, None, None, [''])
|
||||
for lexer_name in module.__all__:
|
||||
lexer = getattr(module, lexer_name)
|
||||
found_lexers.append(
|
||||
'%r: %r' % (lexer_name,
|
||||
(module_name,
|
||||
lexer.name,
|
||||
tuple(lexer.aliases),
|
||||
tuple(lexer.filenames),
|
||||
tuple(lexer.mimetypes))))
|
||||
# sort them to make the diff minimal
|
||||
found_lexers.sort()
|
||||
|
||||
# extract useful sourcecode from this file
|
||||
with open(__file__) as fp:
|
||||
content = fp.read()
|
||||
# replace crnl to nl for Windows.
|
||||
#
|
||||
# Note that, originally, contributers should keep nl of master
|
||||
# repository, for example by using some kind of automatic
|
||||
# management EOL, like `EolExtension
|
||||
# <https://www.mercurial-scm.org/wiki/EolExtension>`.
|
||||
content = content.replace("\r\n", "\n")
|
||||
header = content[:content.find('LEXERS = {')]
|
||||
footer = content[content.find("if __name__ == '__main__':"):]
|
||||
|
||||
# write new file
|
||||
with open(__file__, 'w') as fp:
|
||||
fp.write(header)
|
||||
fp.write('LEXERS = {\n %s,\n}\n\n' % ',\n '.join(found_lexers))
|
||||
fp.write(footer)
|
||||
|
||||
print ('=== %d lexers processed.' % len(found_lexers))
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,43 @@
|
|||
"""
|
||||
pygments.modeline
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
A simple modeline parser (based on pymodeline).
|
||||
|
||||
:copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
__all__ = ['get_filetype_from_buffer']
|
||||
|
||||
|
||||
modeline_re = re.compile(r'''
|
||||
(?: vi | vim | ex ) (?: [<=>]? \d* )? :
|
||||
.* (?: ft | filetype | syn | syntax ) = ( [^:\s]+ )
|
||||
''', re.VERBOSE)
|
||||
|
||||
|
||||
def get_filetype_from_line(l):
|
||||
m = modeline_re.search(l)
|
||||
if m:
|
||||
return m.group(1)
|
||||
|
||||
|
||||
def get_filetype_from_buffer(buf, max_lines=5):
|
||||
"""
|
||||
Scan the buffer for modelines and return filetype if one is found.
|
||||
"""
|
||||
lines = buf.splitlines()
|
||||
for l in lines[-1:-max_lines-1:-1]:
|
||||
ret = get_filetype_from_line(l)
|
||||
if ret:
|
||||
return ret
|
||||
for i in range(max_lines, -1, -1):
|
||||
if i < len(lines):
|
||||
ret = get_filetype_from_line(lines[i])
|
||||
if ret:
|
||||
return ret
|
||||
|
||||
return None
|
|
@ -0,0 +1,69 @@
|
|||
"""
|
||||
pygments.plugin
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Pygments setuptools plugin interface. The methods defined
|
||||
here also work if setuptools isn't installed but they just
|
||||
return nothing.
|
||||
|
||||
lexer plugins::
|
||||
|
||||
[pygments.lexers]
|
||||
yourlexer = yourmodule:YourLexer
|
||||
|
||||
formatter plugins::
|
||||
|
||||
[pygments.formatters]
|
||||
yourformatter = yourformatter:YourFormatter
|
||||
/.ext = yourformatter:YourFormatter
|
||||
|
||||
As you can see, you can define extensions for the formatter
|
||||
with a leading slash.
|
||||
|
||||
syntax plugins::
|
||||
|
||||
[pygments.styles]
|
||||
yourstyle = yourstyle:YourStyle
|
||||
|
||||
filter plugin::
|
||||
|
||||
[pygments.filter]
|
||||
yourfilter = yourfilter:YourFilter
|
||||
|
||||
|
||||
:copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
LEXER_ENTRY_POINT = 'pygments.lexers'
|
||||
FORMATTER_ENTRY_POINT = 'pygments.formatters'
|
||||
STYLE_ENTRY_POINT = 'pygments.styles'
|
||||
FILTER_ENTRY_POINT = 'pygments.filters'
|
||||
|
||||
|
||||
def iter_entry_points(group_name):
|
||||
try:
|
||||
from pip._vendor import pkg_resources
|
||||
except (ImportError, OSError):
|
||||
return []
|
||||
|
||||
return pkg_resources.iter_entry_points(group_name)
|
||||
|
||||
|
||||
def find_plugin_lexers():
|
||||
for entrypoint in iter_entry_points(LEXER_ENTRY_POINT):
|
||||
yield entrypoint.load()
|
||||
|
||||
|
||||
def find_plugin_formatters():
|
||||
for entrypoint in iter_entry_points(FORMATTER_ENTRY_POINT):
|
||||
yield entrypoint.name, entrypoint.load()
|
||||
|
||||
|
||||
def find_plugin_styles():
|
||||
for entrypoint in iter_entry_points(STYLE_ENTRY_POINT):
|
||||
yield entrypoint.name, entrypoint.load()
|
||||
|
||||
|
||||
def find_plugin_filters():
|
||||
for entrypoint in iter_entry_points(FILTER_ENTRY_POINT):
|
||||
yield entrypoint.name, entrypoint.load()
|
|
@ -0,0 +1,91 @@
|
|||
"""
|
||||
pygments.regexopt
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
An algorithm that generates optimized regexes for matching long lists of
|
||||
literal strings.
|
||||
|
||||
:copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import re
|
||||
from re import escape
|
||||
from os.path import commonprefix
|
||||
from itertools import groupby
|
||||
from operator import itemgetter
|
||||
|
||||
CS_ESCAPE = re.compile(r'[\[\^\\\-\]]')
|
||||
FIRST_ELEMENT = itemgetter(0)
|
||||
|
||||
|
||||
def make_charset(letters):
|
||||
return '[' + CS_ESCAPE.sub(lambda m: '\\' + m.group(), ''.join(letters)) + ']'
|
||||
|
||||
|
||||
def regex_opt_inner(strings, open_paren):
|
||||
"""Return a regex that matches any string in the sorted list of strings."""
|
||||
close_paren = open_paren and ')' or ''
|
||||
# print strings, repr(open_paren)
|
||||
if not strings:
|
||||
# print '-> nothing left'
|
||||
return ''
|
||||
first = strings[0]
|
||||
if len(strings) == 1:
|
||||
# print '-> only 1 string'
|
||||
return open_paren + escape(first) + close_paren
|
||||
if not first:
|
||||
# print '-> first string empty'
|
||||
return open_paren + regex_opt_inner(strings[1:], '(?:') \
|
||||
+ '?' + close_paren
|
||||
if len(first) == 1:
|
||||
# multiple one-char strings? make a charset
|
||||
oneletter = []
|
||||
rest = []
|
||||
for s in strings:
|
||||
if len(s) == 1:
|
||||
oneletter.append(s)
|
||||
else:
|
||||
rest.append(s)
|
||||
if len(oneletter) > 1: # do we have more than one oneletter string?
|
||||
if rest:
|
||||
# print '-> 1-character + rest'
|
||||
return open_paren + regex_opt_inner(rest, '') + '|' \
|
||||
+ make_charset(oneletter) + close_paren
|
||||
# print '-> only 1-character'
|
||||
return open_paren + make_charset(oneletter) + close_paren
|
||||
prefix = commonprefix(strings)
|
||||
if prefix:
|
||||
plen = len(prefix)
|
||||
# we have a prefix for all strings
|
||||
# print '-> prefix:', prefix
|
||||
return open_paren + escape(prefix) \
|
||||
+ regex_opt_inner([s[plen:] for s in strings], '(?:') \
|
||||
+ close_paren
|
||||
# is there a suffix?
|
||||
strings_rev = [s[::-1] for s in strings]
|
||||
suffix = commonprefix(strings_rev)
|
||||
if suffix:
|
||||
slen = len(suffix)
|
||||
# print '-> suffix:', suffix[::-1]
|
||||
return open_paren \
|
||||
+ regex_opt_inner(sorted(s[:-slen] for s in strings), '(?:') \
|
||||
+ escape(suffix[::-1]) + close_paren
|
||||
# recurse on common 1-string prefixes
|
||||
# print '-> last resort'
|
||||
return open_paren + \
|
||||
'|'.join(regex_opt_inner(list(group[1]), '')
|
||||
for group in groupby(strings, lambda s: s[0] == first[0])) \
|
||||
+ close_paren
|
||||
|
||||
|
||||
def regex_opt(strings, prefix='', suffix=''):
|
||||
"""Return a compiled regex that matches any string in the given list.
|
||||
|
||||
The strings to match must be literal strings, not regexes. They will be
|
||||
regex-escaped.
|
||||
|
||||
*prefix* and *suffix* are pre- and appended to the final regex.
|
||||
"""
|
||||
strings = sorted(strings)
|
||||
return prefix + regex_opt_inner(strings, '(') + suffix
|
|
@ -0,0 +1,104 @@
|
|||
"""
|
||||
pygments.scanner
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
This library implements a regex based scanner. Some languages
|
||||
like Pascal are easy to parse but have some keywords that
|
||||
depend on the context. Because of this it's impossible to lex
|
||||
that just by using a regular expression lexer like the
|
||||
`RegexLexer`.
|
||||
|
||||
Have a look at the `DelphiLexer` to get an idea of how to use
|
||||
this scanner.
|
||||
|
||||
:copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
import re
|
||||
|
||||
|
||||
class EndOfText(RuntimeError):
|
||||
"""
|
||||
Raise if end of text is reached and the user
|
||||
tried to call a match function.
|
||||
"""
|
||||
|
||||
|
||||
class Scanner:
|
||||
"""
|
||||
Simple scanner
|
||||
|
||||
All method patterns are regular expression strings (not
|
||||
compiled expressions!)
|
||||
"""
|
||||
|
||||
def __init__(self, text, flags=0):
|
||||
"""
|
||||
:param text: The text which should be scanned
|
||||
:param flags: default regular expression flags
|
||||
"""
|
||||
self.data = text
|
||||
self.data_length = len(text)
|
||||
self.start_pos = 0
|
||||
self.pos = 0
|
||||
self.flags = flags
|
||||
self.last = None
|
||||
self.match = None
|
||||
self._re_cache = {}
|
||||
|
||||
def eos(self):
|
||||
"""`True` if the scanner reached the end of text."""
|
||||
return self.pos >= self.data_length
|
||||
eos = property(eos, eos.__doc__)
|
||||
|
||||
def check(self, pattern):
|
||||
"""
|
||||
Apply `pattern` on the current position and return
|
||||
the match object. (Doesn't touch pos). Use this for
|
||||
lookahead.
|
||||
"""
|
||||
if self.eos:
|
||||
raise EndOfText()
|
||||
if pattern not in self._re_cache:
|
||||
self._re_cache[pattern] = re.compile(pattern, self.flags)
|
||||
return self._re_cache[pattern].match(self.data, self.pos)
|
||||
|
||||
def test(self, pattern):
|
||||
"""Apply a pattern on the current position and check
|
||||
if it patches. Doesn't touch pos.
|
||||
"""
|
||||
return self.check(pattern) is not None
|
||||
|
||||
def scan(self, pattern):
|
||||
"""
|
||||
Scan the text for the given pattern and update pos/match
|
||||
and related fields. The return value is a boolen that
|
||||
indicates if the pattern matched. The matched value is
|
||||
stored on the instance as ``match``, the last value is
|
||||
stored as ``last``. ``start_pos`` is the position of the
|
||||
pointer before the pattern was matched, ``pos`` is the
|
||||
end position.
|
||||
"""
|
||||
if self.eos:
|
||||
raise EndOfText()
|
||||
if pattern not in self._re_cache:
|
||||
self._re_cache[pattern] = re.compile(pattern, self.flags)
|
||||
self.last = self.match
|
||||
m = self._re_cache[pattern].match(self.data, self.pos)
|
||||
if m is None:
|
||||
return False
|
||||
self.start_pos = m.start()
|
||||
self.pos = m.end()
|
||||
self.match = m.group()
|
||||
return True
|
||||
|
||||
def get_char(self):
|
||||
"""Scan exactly one char."""
|
||||
self.scan('.')
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s %d/%d>' % (
|
||||
self.__class__.__name__,
|
||||
self.pos,
|
||||
self.data_length
|
||||
)
|
|
@ -0,0 +1,155 @@
|
|||
"""
|
||||
pygments.sphinxext
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Sphinx extension to generate automatic documentation of lexers,
|
||||
formatters and filters.
|
||||
|
||||
:copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.statemachine import ViewList
|
||||
from docutils.parsers.rst import Directive
|
||||
from sphinx.util.nodes import nested_parse_with_titles
|
||||
|
||||
|
||||
MODULEDOC = '''
|
||||
.. module:: %s
|
||||
|
||||
%s
|
||||
%s
|
||||
'''
|
||||
|
||||
LEXERDOC = '''
|
||||
.. class:: %s
|
||||
|
||||
:Short names: %s
|
||||
:Filenames: %s
|
||||
:MIME types: %s
|
||||
|
||||
%s
|
||||
|
||||
'''
|
||||
|
||||
FMTERDOC = '''
|
||||
.. class:: %s
|
||||
|
||||
:Short names: %s
|
||||
:Filenames: %s
|
||||
|
||||
%s
|
||||
|
||||
'''
|
||||
|
||||
FILTERDOC = '''
|
||||
.. class:: %s
|
||||
|
||||
:Name: %s
|
||||
|
||||
%s
|
||||
|
||||
'''
|
||||
|
||||
|
||||
class PygmentsDoc(Directive):
|
||||
"""
|
||||
A directive to collect all lexers/formatters/filters and generate
|
||||
autoclass directives for them.
|
||||
"""
|
||||
has_content = False
|
||||
required_arguments = 1
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = False
|
||||
option_spec = {}
|
||||
|
||||
def run(self):
|
||||
self.filenames = set()
|
||||
if self.arguments[0] == 'lexers':
|
||||
out = self.document_lexers()
|
||||
elif self.arguments[0] == 'formatters':
|
||||
out = self.document_formatters()
|
||||
elif self.arguments[0] == 'filters':
|
||||
out = self.document_filters()
|
||||
else:
|
||||
raise Exception('invalid argument for "pygmentsdoc" directive')
|
||||
node = nodes.compound()
|
||||
vl = ViewList(out.split('\n'), source='')
|
||||
nested_parse_with_titles(self.state, vl, node)
|
||||
for fn in self.filenames:
|
||||
self.state.document.settings.record_dependencies.add(fn)
|
||||
return node.children
|
||||
|
||||
def document_lexers(self):
|
||||
from pip._vendor.pygments.lexers._mapping import LEXERS
|
||||
out = []
|
||||
modules = {}
|
||||
moduledocstrings = {}
|
||||
for classname, data in sorted(LEXERS.items(), key=lambda x: x[0]):
|
||||
module = data[0]
|
||||
mod = __import__(module, None, None, [classname])
|
||||
self.filenames.add(mod.__file__)
|
||||
cls = getattr(mod, classname)
|
||||
if not cls.__doc__:
|
||||
print("Warning: %s does not have a docstring." % classname)
|
||||
docstring = cls.__doc__
|
||||
if isinstance(docstring, bytes):
|
||||
docstring = docstring.decode('utf8')
|
||||
modules.setdefault(module, []).append((
|
||||
classname,
|
||||
', '.join(data[2]) or 'None',
|
||||
', '.join(data[3]).replace('*', '\\*').replace('_', '\\') or 'None',
|
||||
', '.join(data[4]) or 'None',
|
||||
docstring))
|
||||
if module not in moduledocstrings:
|
||||
moddoc = mod.__doc__
|
||||
if isinstance(moddoc, bytes):
|
||||
moddoc = moddoc.decode('utf8')
|
||||
moduledocstrings[module] = moddoc
|
||||
|
||||
for module, lexers in sorted(modules.items(), key=lambda x: x[0]):
|
||||
if moduledocstrings[module] is None:
|
||||
raise Exception("Missing docstring for %s" % (module,))
|
||||
heading = moduledocstrings[module].splitlines()[4].strip().rstrip('.')
|
||||
out.append(MODULEDOC % (module, heading, '-'*len(heading)))
|
||||
for data in lexers:
|
||||
out.append(LEXERDOC % data)
|
||||
|
||||
return ''.join(out)
|
||||
|
||||
def document_formatters(self):
|
||||
from pip._vendor.pygments.formatters import FORMATTERS
|
||||
|
||||
out = []
|
||||
for classname, data in sorted(FORMATTERS.items(), key=lambda x: x[0]):
|
||||
module = data[0]
|
||||
mod = __import__(module, None, None, [classname])
|
||||
self.filenames.add(mod.__file__)
|
||||
cls = getattr(mod, classname)
|
||||
docstring = cls.__doc__
|
||||
if isinstance(docstring, bytes):
|
||||
docstring = docstring.decode('utf8')
|
||||
heading = cls.__name__
|
||||
out.append(FMTERDOC % (heading, ', '.join(data[2]) or 'None',
|
||||
', '.join(data[3]).replace('*', '\\*') or 'None',
|
||||
docstring))
|
||||
return ''.join(out)
|
||||
|
||||
def document_filters(self):
|
||||
from pip._vendor.pygments.filters import FILTERS
|
||||
|
||||
out = []
|
||||
for name, cls in FILTERS.items():
|
||||
self.filenames.add(sys.modules[cls.__module__].__file__)
|
||||
docstring = cls.__doc__
|
||||
if isinstance(docstring, bytes):
|
||||
docstring = docstring.decode('utf8')
|
||||
out.append(FILTERDOC % (cls.__name__, name, docstring))
|
||||
return ''.join(out)
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.add_directive('pygmentsdoc', PygmentsDoc)
|
|
@ -0,0 +1,191 @@
|
|||
"""
|
||||
pygments.style
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Basic style object.
|
||||
|
||||
:copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
from pip._vendor.pygments.token import Token, STANDARD_TYPES
|
||||
|
||||
# Default mapping of ansixxx to RGB colors.
|
||||
_ansimap = {
|
||||
# dark
|
||||
'ansiblack': '000000',
|
||||
'ansired': '7f0000',
|
||||
'ansigreen': '007f00',
|
||||
'ansiyellow': '7f7fe0',
|
||||
'ansiblue': '00007f',
|
||||
'ansimagenta': '7f007f',
|
||||
'ansicyan': '007f7f',
|
||||
'ansigray': 'e5e5e5',
|
||||
# normal
|
||||
'ansibrightblack': '555555',
|
||||
'ansibrightred': 'ff0000',
|
||||
'ansibrightgreen': '00ff00',
|
||||
'ansibrightyellow': 'ffff00',
|
||||
'ansibrightblue': '0000ff',
|
||||
'ansibrightmagenta': 'ff00ff',
|
||||
'ansibrightcyan': '00ffff',
|
||||
'ansiwhite': 'ffffff',
|
||||
}
|
||||
# mapping of deprecated #ansixxx colors to new color names
|
||||
_deprecated_ansicolors = {
|
||||
# dark
|
||||
'#ansiblack': 'ansiblack',
|
||||
'#ansidarkred': 'ansired',
|
||||
'#ansidarkgreen': 'ansigreen',
|
||||
'#ansibrown': 'ansiyellow',
|
||||
'#ansidarkblue': 'ansiblue',
|
||||
'#ansipurple': 'ansimagenta',
|
||||
'#ansiteal': 'ansicyan',
|
||||
'#ansilightgray': 'ansigray',
|
||||
# normal
|
||||
'#ansidarkgray': 'ansibrightblack',
|
||||
'#ansired': 'ansibrightred',
|
||||
'#ansigreen': 'ansibrightgreen',
|
||||
'#ansiyellow': 'ansibrightyellow',
|
||||
'#ansiblue': 'ansibrightblue',
|
||||
'#ansifuchsia': 'ansibrightmagenta',
|
||||
'#ansiturquoise': 'ansibrightcyan',
|
||||
'#ansiwhite': 'ansiwhite',
|
||||
}
|
||||
ansicolors = set(_ansimap)
|
||||
|
||||
|
||||
class StyleMeta(type):
|
||||
|
||||
def __new__(mcs, name, bases, dct):
|
||||
obj = type.__new__(mcs, name, bases, dct)
|
||||
for token in STANDARD_TYPES:
|
||||
if token not in obj.styles:
|
||||
obj.styles[token] = ''
|
||||
|
||||
def colorformat(text):
|
||||
if text in ansicolors:
|
||||
return text
|
||||
if text[0:1] == '#':
|
||||
col = text[1:]
|
||||
if len(col) == 6:
|
||||
return col
|
||||
elif len(col) == 3:
|
||||
return col[0] * 2 + col[1] * 2 + col[2] * 2
|
||||
elif text == '':
|
||||
return ''
|
||||
elif text.startswith('var') or text.startswith('calc'):
|
||||
return text
|
||||
assert False, "wrong color format %r" % text
|
||||
|
||||
_styles = obj._styles = {}
|
||||
|
||||
for ttype in obj.styles:
|
||||
for token in ttype.split():
|
||||
if token in _styles:
|
||||
continue
|
||||
ndef = _styles.get(token.parent, None)
|
||||
styledefs = obj.styles.get(token, '').split()
|
||||
if not ndef or token is None:
|
||||
ndef = ['', 0, 0, 0, '', '', 0, 0, 0]
|
||||
elif 'noinherit' in styledefs and token is not Token:
|
||||
ndef = _styles[Token][:]
|
||||
else:
|
||||
ndef = ndef[:]
|
||||
_styles[token] = ndef
|
||||
for styledef in obj.styles.get(token, '').split():
|
||||
if styledef == 'noinherit':
|
||||
pass
|
||||
elif styledef == 'bold':
|
||||
ndef[1] = 1
|
||||
elif styledef == 'nobold':
|
||||
ndef[1] = 0
|
||||
elif styledef == 'italic':
|
||||
ndef[2] = 1
|
||||
elif styledef == 'noitalic':
|
||||
ndef[2] = 0
|
||||
elif styledef == 'underline':
|
||||
ndef[3] = 1
|
||||
elif styledef == 'nounderline':
|
||||
ndef[3] = 0
|
||||
elif styledef[:3] == 'bg:':
|
||||
ndef[4] = colorformat(styledef[3:])
|
||||
elif styledef[:7] == 'border:':
|
||||
ndef[5] = colorformat(styledef[7:])
|
||||
elif styledef == 'roman':
|
||||
ndef[6] = 1
|
||||
elif styledef == 'sans':
|
||||
ndef[7] = 1
|
||||
elif styledef == 'mono':
|
||||
ndef[8] = 1
|
||||
else:
|
||||
ndef[0] = colorformat(styledef)
|
||||
|
||||
return obj
|
||||
|
||||
def style_for_token(cls, token):
|
||||
t = cls._styles[token]
|
||||
ansicolor = bgansicolor = None
|
||||
color = t[0]
|
||||
if color in _deprecated_ansicolors:
|
||||
color = _deprecated_ansicolors[color]
|
||||
if color in ansicolors:
|
||||
ansicolor = color
|
||||
color = _ansimap[color]
|
||||
bgcolor = t[4]
|
||||
if bgcolor in _deprecated_ansicolors:
|
||||
bgcolor = _deprecated_ansicolors[bgcolor]
|
||||
if bgcolor in ansicolors:
|
||||
bgansicolor = bgcolor
|
||||
bgcolor = _ansimap[bgcolor]
|
||||
|
||||
return {
|
||||
'color': color or None,
|
||||
'bold': bool(t[1]),
|
||||
'italic': bool(t[2]),
|
||||
'underline': bool(t[3]),
|
||||
'bgcolor': bgcolor or None,
|
||||
'border': t[5] or None,
|
||||
'roman': bool(t[6]) or None,
|
||||
'sans': bool(t[7]) or None,
|
||||
'mono': bool(t[8]) or None,
|
||||
'ansicolor': ansicolor,
|
||||
'bgansicolor': bgansicolor,
|
||||
}
|
||||
|
||||
def list_styles(cls):
|
||||
return list(cls)
|
||||
|
||||
def styles_token(cls, ttype):
|
||||
return ttype in cls._styles
|
||||
|
||||
def __iter__(cls):
|
||||
for token in cls._styles:
|
||||
yield token, cls.style_for_token(token)
|
||||
|
||||
def __len__(cls):
|
||||
return len(cls._styles)
|
||||
|
||||
|
||||
class Style(metaclass=StyleMeta):
|
||||
|
||||
#: overall background color (``None`` means transparent)
|
||||
background_color = '#ffffff'
|
||||
|
||||
#: highlight background color
|
||||
highlight_color = '#ffffcc'
|
||||
|
||||
#: line number font color
|
||||
line_number_color = 'inherit'
|
||||
|
||||
#: line number background color
|
||||
line_number_background_color = 'transparent'
|
||||
|
||||
#: special line number font color
|
||||
line_number_special_color = '#000000'
|
||||
|
||||
#: special line number background color
|
||||
line_number_special_background_color = '#ffffc0'
|
||||
|
||||
#: Style definitions for individual token types.
|
||||
styles = {}
|
|
@ -0,0 +1,89 @@
|
|||
"""
|
||||
pygments.styles
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Contains built-in styles.
|
||||
|
||||
:copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
from pip._vendor.pygments.plugin import find_plugin_styles
|
||||
from pip._vendor.pygments.util import ClassNotFound
|
||||
|
||||
|
||||
#: Maps style names to 'submodule::classname'.
|
||||
STYLE_MAP = {
|
||||
'default': 'default::DefaultStyle',
|
||||
'emacs': 'emacs::EmacsStyle',
|
||||
'friendly': 'friendly::FriendlyStyle',
|
||||
'colorful': 'colorful::ColorfulStyle',
|
||||
'autumn': 'autumn::AutumnStyle',
|
||||
'murphy': 'murphy::MurphyStyle',
|
||||
'manni': 'manni::ManniStyle',
|
||||
'material': 'material::MaterialStyle',
|
||||
'monokai': 'monokai::MonokaiStyle',
|
||||
'perldoc': 'perldoc::PerldocStyle',
|
||||
'pastie': 'pastie::PastieStyle',
|
||||
'borland': 'borland::BorlandStyle',
|
||||
'trac': 'trac::TracStyle',
|
||||
'native': 'native::NativeStyle',
|
||||
'fruity': 'fruity::FruityStyle',
|
||||
'bw': 'bw::BlackWhiteStyle',
|
||||
'vim': 'vim::VimStyle',
|
||||
'vs': 'vs::VisualStudioStyle',
|
||||
'tango': 'tango::TangoStyle',
|
||||
'rrt': 'rrt::RrtStyle',
|
||||
'xcode': 'xcode::XcodeStyle',
|
||||
'igor': 'igor::IgorStyle',
|
||||
'paraiso-light': 'paraiso_light::ParaisoLightStyle',
|
||||
'paraiso-dark': 'paraiso_dark::ParaisoDarkStyle',
|
||||
'lovelace': 'lovelace::LovelaceStyle',
|
||||
'algol': 'algol::AlgolStyle',
|
||||
'algol_nu': 'algol_nu::Algol_NuStyle',
|
||||
'arduino': 'arduino::ArduinoStyle',
|
||||
'rainbow_dash': 'rainbow_dash::RainbowDashStyle',
|
||||
'abap': 'abap::AbapStyle',
|
||||
'solarized-dark': 'solarized::SolarizedDarkStyle',
|
||||
'solarized-light': 'solarized::SolarizedLightStyle',
|
||||
'sas': 'sas::SasStyle',
|
||||
'stata': 'stata_light::StataLightStyle',
|
||||
'stata-light': 'stata_light::StataLightStyle',
|
||||
'stata-dark': 'stata_dark::StataDarkStyle',
|
||||
'inkpot': 'inkpot::InkPotStyle',
|
||||
'zenburn': 'zenburn::ZenburnStyle',
|
||||
'gruvbox-dark': 'gruvbox::GruvboxDarkStyle',
|
||||
'gruvbox-light': 'gruvbox::GruvboxLightStyle',
|
||||
}
|
||||
|
||||
|
||||
def get_style_by_name(name):
|
||||
if name in STYLE_MAP:
|
||||
mod, cls = STYLE_MAP[name].split('::')
|
||||
builtin = "yes"
|
||||
else:
|
||||
for found_name, style in find_plugin_styles():
|
||||
if name == found_name:
|
||||
return style
|
||||
# perhaps it got dropped into our styles package
|
||||
builtin = ""
|
||||
mod = name
|
||||
cls = name.title() + "Style"
|
||||
|
||||
try:
|
||||
mod = __import__('pygments.styles.' + mod, None, None, [cls])
|
||||
except ImportError:
|
||||
raise ClassNotFound("Could not find style module %r" % mod +
|
||||
(builtin and ", though it should be builtin") + ".")
|
||||
try:
|
||||
return getattr(mod, cls)
|
||||
except AttributeError:
|
||||
raise ClassNotFound("Could not find style class %r in style module." % cls)
|
||||
|
||||
|
||||
def get_all_styles():
|
||||
"""Return a generator for all styles by name,
|
||||
both builtin and plugin."""
|
||||
yield from STYLE_MAP
|
||||
for name, _ in find_plugin_styles():
|
||||
yield name
|
|
@ -0,0 +1,212 @@
|
|||
"""
|
||||
pygments.token
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Basic token types and the standard tokens.
|
||||
|
||||
:copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
|
||||
class _TokenType(tuple):
|
||||
parent = None
|
||||
|
||||
def split(self):
|
||||
buf = []
|
||||
node = self
|
||||
while node is not None:
|
||||
buf.append(node)
|
||||
node = node.parent
|
||||
buf.reverse()
|
||||
return buf
|
||||
|
||||
def __init__(self, *args):
|
||||
# no need to call super.__init__
|
||||
self.subtypes = set()
|
||||
|
||||
def __contains__(self, val):
|
||||
return self is val or (
|
||||
type(val) is self.__class__ and
|
||||
val[:len(self)] == self
|
||||
)
|
||||
|
||||
def __getattr__(self, val):
|
||||
if not val or not val[0].isupper():
|
||||
return tuple.__getattribute__(self, val)
|
||||
new = _TokenType(self + (val,))
|
||||
setattr(self, val, new)
|
||||
self.subtypes.add(new)
|
||||
new.parent = self
|
||||
return new
|
||||
|
||||
def __repr__(self):
|
||||
return 'Token' + (self and '.' or '') + '.'.join(self)
|
||||
|
||||
def __copy__(self):
|
||||
# These instances are supposed to be singletons
|
||||
return self
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
# These instances are supposed to be singletons
|
||||
return self
|
||||
|
||||
|
||||
Token = _TokenType()
|
||||
|
||||
# Special token types
|
||||
Text = Token.Text
|
||||
Whitespace = Text.Whitespace
|
||||
Escape = Token.Escape
|
||||
Error = Token.Error
|
||||
# Text that doesn't belong to this lexer (e.g. HTML in PHP)
|
||||
Other = Token.Other
|
||||
|
||||
# Common token types for source code
|
||||
Keyword = Token.Keyword
|
||||
Name = Token.Name
|
||||
Literal = Token.Literal
|
||||
String = Literal.String
|
||||
Number = Literal.Number
|
||||
Punctuation = Token.Punctuation
|
||||
Operator = Token.Operator
|
||||
Comment = Token.Comment
|
||||
|
||||
# Generic types for non-source code
|
||||
Generic = Token.Generic
|
||||
|
||||
# String and some others are not direct children of Token.
|
||||
# alias them:
|
||||
Token.Token = Token
|
||||
Token.String = String
|
||||
Token.Number = Number
|
||||
|
||||
|
||||
def is_token_subtype(ttype, other):
|
||||
"""
|
||||
Return True if ``ttype`` is a subtype of ``other``.
|
||||
|
||||
exists for backwards compatibility. use ``ttype in other`` now.
|
||||
"""
|
||||
return ttype in other
|
||||
|
||||
|
||||
def string_to_tokentype(s):
|
||||
"""
|
||||
Convert a string into a token type::
|
||||
|
||||
>>> string_to_token('String.Double')
|
||||
Token.Literal.String.Double
|
||||
>>> string_to_token('Token.Literal.Number')
|
||||
Token.Literal.Number
|
||||
>>> string_to_token('')
|
||||
Token
|
||||
|
||||
Tokens that are already tokens are returned unchanged:
|
||||
|
||||
>>> string_to_token(String)
|
||||
Token.Literal.String
|
||||
"""
|
||||
if isinstance(s, _TokenType):
|
||||
return s
|
||||
if not s:
|
||||
return Token
|
||||
node = Token
|
||||
for item in s.split('.'):
|
||||
node = getattr(node, item)
|
||||
return node
|
||||
|
||||
|
||||
# Map standard token types to short names, used in CSS class naming.
|
||||
# If you add a new item, please be sure to run this file to perform
|
||||
# a consistency check for duplicate values.
|
||||
STANDARD_TYPES = {
|
||||
Token: '',
|
||||
|
||||
Text: '',
|
||||
Whitespace: 'w',
|
||||
Escape: 'esc',
|
||||
Error: 'err',
|
||||
Other: 'x',
|
||||
|
||||
Keyword: 'k',
|
||||
Keyword.Constant: 'kc',
|
||||
Keyword.Declaration: 'kd',
|
||||
Keyword.Namespace: 'kn',
|
||||
Keyword.Pseudo: 'kp',
|
||||
Keyword.Reserved: 'kr',
|
||||
Keyword.Type: 'kt',
|
||||
|
||||
Name: 'n',
|
||||
Name.Attribute: 'na',
|
||||
Name.Builtin: 'nb',
|
||||
Name.Builtin.Pseudo: 'bp',
|
||||
Name.Class: 'nc',
|
||||
Name.Constant: 'no',
|
||||
Name.Decorator: 'nd',
|
||||
Name.Entity: 'ni',
|
||||
Name.Exception: 'ne',
|
||||
Name.Function: 'nf',
|
||||
Name.Function.Magic: 'fm',
|
||||
Name.Property: 'py',
|
||||
Name.Label: 'nl',
|
||||
Name.Namespace: 'nn',
|
||||
Name.Other: 'nx',
|
||||
Name.Tag: 'nt',
|
||||
Name.Variable: 'nv',
|
||||
Name.Variable.Class: 'vc',
|
||||
Name.Variable.Global: 'vg',
|
||||
Name.Variable.Instance: 'vi',
|
||||
Name.Variable.Magic: 'vm',
|
||||
|
||||
Literal: 'l',
|
||||
Literal.Date: 'ld',
|
||||
|
||||
String: 's',
|
||||
String.Affix: 'sa',
|
||||
String.Backtick: 'sb',
|
||||
String.Char: 'sc',
|
||||
String.Delimiter: 'dl',
|
||||
String.Doc: 'sd',
|
||||
String.Double: 's2',
|
||||
String.Escape: 'se',
|
||||
String.Heredoc: 'sh',
|
||||
String.Interpol: 'si',
|
||||
String.Other: 'sx',
|
||||
String.Regex: 'sr',
|
||||
String.Single: 's1',
|
||||
String.Symbol: 'ss',
|
||||
|
||||
Number: 'm',
|
||||
Number.Bin: 'mb',
|
||||
Number.Float: 'mf',
|
||||
Number.Hex: 'mh',
|
||||
Number.Integer: 'mi',
|
||||
Number.Integer.Long: 'il',
|
||||
Number.Oct: 'mo',
|
||||
|
||||
Operator: 'o',
|
||||
Operator.Word: 'ow',
|
||||
|
||||
Punctuation: 'p',
|
||||
|
||||
Comment: 'c',
|
||||
Comment.Hashbang: 'ch',
|
||||
Comment.Multiline: 'cm',
|
||||
Comment.Preproc: 'cp',
|
||||
Comment.PreprocFile: 'cpf',
|
||||
Comment.Single: 'c1',
|
||||
Comment.Special: 'cs',
|
||||
|
||||
Generic: 'g',
|
||||
Generic.Deleted: 'gd',
|
||||
Generic.Emph: 'ge',
|
||||
Generic.Error: 'gr',
|
||||
Generic.Heading: 'gh',
|
||||
Generic.Inserted: 'gi',
|
||||
Generic.Output: 'go',
|
||||
Generic.Prompt: 'gp',
|
||||
Generic.Strong: 'gs',
|
||||
Generic.Subheading: 'gu',
|
||||
Generic.Traceback: 'gt',
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,308 @@
|
|||
"""
|
||||
pygments.util
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Utility functions.
|
||||
|
||||
:copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import re
|
||||
from io import TextIOWrapper
|
||||
|
||||
|
||||
split_path_re = re.compile(r'[/\\ ]')
|
||||
doctype_lookup_re = re.compile(r'''
|
||||
<!DOCTYPE\s+(
|
||||
[a-zA-Z_][a-zA-Z0-9]*
|
||||
(?: \s+ # optional in HTML5
|
||||
[a-zA-Z_][a-zA-Z0-9]*\s+
|
||||
"[^"]*")?
|
||||
)
|
||||
[^>]*>
|
||||
''', re.DOTALL | re.MULTILINE | re.VERBOSE)
|
||||
tag_re = re.compile(r'<(.+?)(\s.*?)?>.*?</.+?>',
|
||||
re.UNICODE | re.IGNORECASE | re.DOTALL | re.MULTILINE)
|
||||
xml_decl_re = re.compile(r'\s*<\?xml[^>]*\?>', re.I)
|
||||
|
||||
|
||||
class ClassNotFound(ValueError):
|
||||
"""Raised if one of the lookup functions didn't find a matching class."""
|
||||
|
||||
|
||||
class OptionError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def get_choice_opt(options, optname, allowed, default=None, normcase=False):
|
||||
string = options.get(optname, default)
|
||||
if normcase:
|
||||
string = string.lower()
|
||||
if string not in allowed:
|
||||
raise OptionError('Value for option %s must be one of %s' %
|
||||
(optname, ', '.join(map(str, allowed))))
|
||||
return string
|
||||
|
||||
|
||||
def get_bool_opt(options, optname, default=None):
|
||||
string = options.get(optname, default)
|
||||
if isinstance(string, bool):
|
||||
return string
|
||||
elif isinstance(string, int):
|
||||
return bool(string)
|
||||
elif not isinstance(string, str):
|
||||
raise OptionError('Invalid type %r for option %s; use '
|
||||
'1/0, yes/no, true/false, on/off' % (
|
||||
string, optname))
|
||||
elif string.lower() in ('1', 'yes', 'true', 'on'):
|
||||
return True
|
||||
elif string.lower() in ('0', 'no', 'false', 'off'):
|
||||
return False
|
||||
else:
|
||||
raise OptionError('Invalid value %r for option %s; use '
|
||||
'1/0, yes/no, true/false, on/off' % (
|
||||
string, optname))
|
||||
|
||||
|
||||
def get_int_opt(options, optname, default=None):
|
||||
string = options.get(optname, default)
|
||||
try:
|
||||
return int(string)
|
||||
except TypeError:
|
||||
raise OptionError('Invalid type %r for option %s; you '
|
||||
'must give an integer value' % (
|
||||
string, optname))
|
||||
except ValueError:
|
||||
raise OptionError('Invalid value %r for option %s; you '
|
||||
'must give an integer value' % (
|
||||
string, optname))
|
||||
|
||||
|
||||
def get_list_opt(options, optname, default=None):
|
||||
val = options.get(optname, default)
|
||||
if isinstance(val, str):
|
||||
return val.split()
|
||||
elif isinstance(val, (list, tuple)):
|
||||
return list(val)
|
||||
else:
|
||||
raise OptionError('Invalid type %r for option %s; you '
|
||||
'must give a list value' % (
|
||||
val, optname))
|
||||
|
||||
|
||||
def docstring_headline(obj):
|
||||
if not obj.__doc__:
|
||||
return ''
|
||||
res = []
|
||||
for line in obj.__doc__.strip().splitlines():
|
||||
if line.strip():
|
||||
res.append(" " + line.strip())
|
||||
else:
|
||||
break
|
||||
return ''.join(res).lstrip()
|
||||
|
||||
|
||||
def make_analysator(f):
|
||||
"""Return a static text analyser function that returns float values."""
|
||||
def text_analyse(text):
|
||||
try:
|
||||
rv = f(text)
|
||||
except Exception:
|
||||
return 0.0
|
||||
if not rv:
|
||||
return 0.0
|
||||
try:
|
||||
return min(1.0, max(0.0, float(rv)))
|
||||
except (ValueError, TypeError):
|
||||
return 0.0
|
||||
text_analyse.__doc__ = f.__doc__
|
||||
return staticmethod(text_analyse)
|
||||
|
||||
|
||||
def shebang_matches(text, regex):
|
||||
r"""Check if the given regular expression matches the last part of the
|
||||
shebang if one exists.
|
||||
|
||||
>>> from pygments.util import shebang_matches
|
||||
>>> shebang_matches('#!/usr/bin/env python', r'python(2\.\d)?')
|
||||
True
|
||||
>>> shebang_matches('#!/usr/bin/python2.4', r'python(2\.\d)?')
|
||||
True
|
||||
>>> shebang_matches('#!/usr/bin/python-ruby', r'python(2\.\d)?')
|
||||
False
|
||||
>>> shebang_matches('#!/usr/bin/python/ruby', r'python(2\.\d)?')
|
||||
False
|
||||
>>> shebang_matches('#!/usr/bin/startsomethingwith python',
|
||||
... r'python(2\.\d)?')
|
||||
True
|
||||
|
||||
It also checks for common windows executable file extensions::
|
||||
|
||||
>>> shebang_matches('#!C:\\Python2.4\\Python.exe', r'python(2\.\d)?')
|
||||
True
|
||||
|
||||
Parameters (``'-f'`` or ``'--foo'`` are ignored so ``'perl'`` does
|
||||
the same as ``'perl -e'``)
|
||||
|
||||
Note that this method automatically searches the whole string (eg:
|
||||
the regular expression is wrapped in ``'^$'``)
|
||||
"""
|
||||
index = text.find('\n')
|
||||
if index >= 0:
|
||||
first_line = text[:index].lower()
|
||||
else:
|
||||
first_line = text.lower()
|
||||
if first_line.startswith('#!'):
|
||||
try:
|
||||
found = [x for x in split_path_re.split(first_line[2:].strip())
|
||||
if x and not x.startswith('-')][-1]
|
||||
except IndexError:
|
||||
return False
|
||||
regex = re.compile(r'^%s(\.(exe|cmd|bat|bin))?$' % regex, re.IGNORECASE)
|
||||
if regex.search(found) is not None:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def doctype_matches(text, regex):
|
||||
"""Check if the doctype matches a regular expression (if present).
|
||||
|
||||
Note that this method only checks the first part of a DOCTYPE.
|
||||
eg: 'html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"'
|
||||
"""
|
||||
m = doctype_lookup_re.search(text)
|
||||
if m is None:
|
||||
return False
|
||||
doctype = m.group(1)
|
||||
return re.compile(regex, re.I).match(doctype.strip()) is not None
|
||||
|
||||
|
||||
def html_doctype_matches(text):
|
||||
"""Check if the file looks like it has a html doctype."""
|
||||
return doctype_matches(text, r'html')
|
||||
|
||||
|
||||
_looks_like_xml_cache = {}
|
||||
|
||||
|
||||
def looks_like_xml(text):
|
||||
"""Check if a doctype exists or if we have some tags."""
|
||||
if xml_decl_re.match(text):
|
||||
return True
|
||||
key = hash(text)
|
||||
try:
|
||||
return _looks_like_xml_cache[key]
|
||||
except KeyError:
|
||||
m = doctype_lookup_re.search(text)
|
||||
if m is not None:
|
||||
return True
|
||||
rv = tag_re.search(text[:1000]) is not None
|
||||
_looks_like_xml_cache[key] = rv
|
||||
return rv
|
||||
|
||||
|
||||
def surrogatepair(c):
|
||||
"""Given a unicode character code with length greater than 16 bits,
|
||||
return the two 16 bit surrogate pair.
|
||||
"""
|
||||
# From example D28 of:
|
||||
# http://www.unicode.org/book/ch03.pdf
|
||||
return (0xd7c0 + (c >> 10), (0xdc00 + (c & 0x3ff)))
|
||||
|
||||
|
||||
def format_lines(var_name, seq, raw=False, indent_level=0):
|
||||
"""Formats a sequence of strings for output."""
|
||||
lines = []
|
||||
base_indent = ' ' * indent_level * 4
|
||||
inner_indent = ' ' * (indent_level + 1) * 4
|
||||
lines.append(base_indent + var_name + ' = (')
|
||||
if raw:
|
||||
# These should be preformatted reprs of, say, tuples.
|
||||
for i in seq:
|
||||
lines.append(inner_indent + i + ',')
|
||||
else:
|
||||
for i in seq:
|
||||
# Force use of single quotes
|
||||
r = repr(i + '"')
|
||||
lines.append(inner_indent + r[:-2] + r[-1] + ',')
|
||||
lines.append(base_indent + ')')
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def duplicates_removed(it, already_seen=()):
|
||||
"""
|
||||
Returns a list with duplicates removed from the iterable `it`.
|
||||
|
||||
Order is preserved.
|
||||
"""
|
||||
lst = []
|
||||
seen = set()
|
||||
for i in it:
|
||||
if i in seen or i in already_seen:
|
||||
continue
|
||||
lst.append(i)
|
||||
seen.add(i)
|
||||
return lst
|
||||
|
||||
|
||||
class Future:
|
||||
"""Generic class to defer some work.
|
||||
|
||||
Handled specially in RegexLexerMeta, to support regex string construction at
|
||||
first use.
|
||||
"""
|
||||
def get(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def guess_decode(text):
|
||||
"""Decode *text* with guessed encoding.
|
||||
|
||||
First try UTF-8; this should fail for non-UTF-8 encodings.
|
||||
Then try the preferred locale encoding.
|
||||
Fall back to latin-1, which always works.
|
||||
"""
|
||||
try:
|
||||
text = text.decode('utf-8')
|
||||
return text, 'utf-8'
|
||||
except UnicodeDecodeError:
|
||||
try:
|
||||
import locale
|
||||
prefencoding = locale.getpreferredencoding()
|
||||
text = text.decode()
|
||||
return text, prefencoding
|
||||
except (UnicodeDecodeError, LookupError):
|
||||
text = text.decode('latin1')
|
||||
return text, 'latin1'
|
||||
|
||||
|
||||
def guess_decode_from_terminal(text, term):
|
||||
"""Decode *text* coming from terminal *term*.
|
||||
|
||||
First try the terminal encoding, if given.
|
||||
Then try UTF-8. Then try the preferred locale encoding.
|
||||
Fall back to latin-1, which always works.
|
||||
"""
|
||||
if getattr(term, 'encoding', None):
|
||||
try:
|
||||
text = text.decode(term.encoding)
|
||||
except UnicodeDecodeError:
|
||||
pass
|
||||
else:
|
||||
return text, term.encoding
|
||||
return guess_decode(text)
|
||||
|
||||
|
||||
def terminal_encoding(term):
|
||||
"""Return our best guess of encoding for the given *term*."""
|
||||
if getattr(term, 'encoding', None):
|
||||
return term.encoding
|
||||
import locale
|
||||
return locale.getpreferredencoding()
|
||||
|
||||
|
||||
class UnclosingTextIOWrapper(TextIOWrapper):
|
||||
# Don't close underlying buffer on destruction.
|
||||
def close(self):
|
||||
self.flush()
|
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2020 Will McGugan
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,172 @@
|
|||
"""Rich text and beautiful formatting in the terminal."""
|
||||
|
||||
import os
|
||||
from typing import Callable, IO, TYPE_CHECKING, Any, Optional
|
||||
|
||||
from ._extension import load_ipython_extension
|
||||
|
||||
__all__ = ["get_console", "reconfigure", "print", "inspect"]
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .console import Console
|
||||
|
||||
# Global console used by alternative print
|
||||
_console: Optional["Console"] = None
|
||||
|
||||
_IMPORT_CWD = os.path.abspath(os.getcwd())
|
||||
|
||||
|
||||
def get_console() -> "Console":
|
||||
"""Get a global :class:`~rich.console.Console` instance. This function is used when Rich requires a Console,
|
||||
and hasn't been explicitly given one.
|
||||
|
||||
Returns:
|
||||
Console: A console instance.
|
||||
"""
|
||||
global _console
|
||||
if _console is None:
|
||||
from .console import Console
|
||||
|
||||
_console = Console()
|
||||
|
||||
return _console
|
||||
|
||||
|
||||
def reconfigure(*args: Any, **kwargs: Any) -> None:
|
||||
"""Reconfigures the global console by replacing it with another.
|
||||
|
||||
Args:
|
||||
console (Console): Replacement console instance.
|
||||
"""
|
||||
from pip._vendor.rich.console import Console
|
||||
|
||||
new_console = Console(*args, **kwargs)
|
||||
_console = get_console()
|
||||
_console.__dict__ = new_console.__dict__
|
||||
|
||||
|
||||
def print(
|
||||
*objects: Any,
|
||||
sep: str = " ",
|
||||
end: str = "\n",
|
||||
file: Optional[IO[str]] = None,
|
||||
flush: bool = False,
|
||||
) -> None:
|
||||
r"""Print object(s) supplied via positional arguments.
|
||||
This function has an identical signature to the built-in print.
|
||||
For more advanced features, see the :class:`~rich.console.Console` class.
|
||||
|
||||
Args:
|
||||
sep (str, optional): Separator between printed objects. Defaults to " ".
|
||||
end (str, optional): Character to write at end of output. Defaults to "\\n".
|
||||
file (IO[str], optional): File to write to, or None for stdout. Defaults to None.
|
||||
flush (bool, optional): Has no effect as Rich always flushes output. Defaults to False.
|
||||
|
||||
"""
|
||||
from .console import Console
|
||||
|
||||
write_console = get_console() if file is None else Console(file=file)
|
||||
return write_console.print(*objects, sep=sep, end=end)
|
||||
|
||||
|
||||
def print_json(
|
||||
json: Optional[str] = None,
|
||||
*,
|
||||
data: Any = None,
|
||||
indent: int = 2,
|
||||
highlight: bool = True,
|
||||
skip_keys: bool = False,
|
||||
ensure_ascii: bool = True,
|
||||
check_circular: bool = True,
|
||||
allow_nan: bool = True,
|
||||
default: Optional[Callable[[Any], Any]] = None,
|
||||
sort_keys: bool = False,
|
||||
) -> None:
|
||||
"""Pretty prints JSON. Output will be valid JSON.
|
||||
|
||||
Args:
|
||||
json (str): A string containing JSON.
|
||||
data (Any): If json is not supplied, then encode this data.
|
||||
indent (int, optional): Number of spaces to indent. Defaults to 2.
|
||||
highlight (bool, optional): Enable highlighting of output: Defaults to True.
|
||||
skip_keys (bool, optional): Skip keys not of a basic type. Defaults to False.
|
||||
ensure_ascii (bool, optional): Escape all non-ascii characters. Defaults to False.
|
||||
check_circular (bool, optional): Check for circular references. Defaults to True.
|
||||
allow_nan (bool, optional): Allow NaN and Infinity values. Defaults to True.
|
||||
default (Callable, optional): A callable that converts values that can not be encoded
|
||||
in to something that can be JSON encoded. Defaults to None.
|
||||
sort_keys (bool, optional): Sort dictionary keys. Defaults to False.
|
||||
"""
|
||||
|
||||
get_console().print_json(
|
||||
json,
|
||||
data=data,
|
||||
indent=indent,
|
||||
highlight=highlight,
|
||||
skip_keys=skip_keys,
|
||||
ensure_ascii=ensure_ascii,
|
||||
check_circular=check_circular,
|
||||
allow_nan=allow_nan,
|
||||
default=default,
|
||||
sort_keys=sort_keys,
|
||||
)
|
||||
|
||||
|
||||
def inspect(
|
||||
obj: Any,
|
||||
*,
|
||||
console: Optional["Console"] = None,
|
||||
title: Optional[str] = None,
|
||||
help: bool = False,
|
||||
methods: bool = False,
|
||||
docs: bool = True,
|
||||
private: bool = False,
|
||||
dunder: bool = False,
|
||||
sort: bool = True,
|
||||
all: bool = False,
|
||||
value: bool = True,
|
||||
) -> None:
|
||||
"""Inspect any Python object.
|
||||
|
||||
* inspect(<OBJECT>) to see summarized info.
|
||||
* inspect(<OBJECT>, methods=True) to see methods.
|
||||
* inspect(<OBJECT>, help=True) to see full (non-abbreviated) help.
|
||||
* inspect(<OBJECT>, private=True) to see private attributes (single underscore).
|
||||
* inspect(<OBJECT>, dunder=True) to see attributes beginning with double underscore.
|
||||
* inspect(<OBJECT>, all=True) to see all attributes.
|
||||
|
||||
Args:
|
||||
obj (Any): An object to inspect.
|
||||
title (str, optional): Title to display over inspect result, or None use type. Defaults to None.
|
||||
help (bool, optional): Show full help text rather than just first paragraph. Defaults to False.
|
||||
methods (bool, optional): Enable inspection of callables. Defaults to False.
|
||||
docs (bool, optional): Also render doc strings. Defaults to True.
|
||||
private (bool, optional): Show private attributes (beginning with underscore). Defaults to False.
|
||||
dunder (bool, optional): Show attributes starting with double underscore. Defaults to False.
|
||||
sort (bool, optional): Sort attributes alphabetically. Defaults to True.
|
||||
all (bool, optional): Show all attributes. Defaults to False.
|
||||
value (bool, optional): Pretty print value. Defaults to True.
|
||||
"""
|
||||
_console = console or get_console()
|
||||
from pip._vendor.rich._inspect import Inspect
|
||||
|
||||
# Special case for inspect(inspect)
|
||||
is_inspect = obj is inspect
|
||||
|
||||
_inspect = Inspect(
|
||||
obj,
|
||||
title=title,
|
||||
help=is_inspect or help,
|
||||
methods=is_inspect or methods,
|
||||
docs=is_inspect or docs,
|
||||
private=private,
|
||||
dunder=dunder,
|
||||
sort=sort,
|
||||
all=all,
|
||||
value=value,
|
||||
)
|
||||
_console.print(_inspect)
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
print("Hello, **World**")
|
|
@ -0,0 +1,285 @@
|
|||
import colorsys
|
||||
import io
|
||||
from time import process_time
|
||||
|
||||
from pip._vendor.rich import box
|
||||
from pip._vendor.rich.color import Color
|
||||
from pip._vendor.rich.console import (
|
||||
Console,
|
||||
ConsoleOptions,
|
||||
Group,
|
||||
RenderResult,
|
||||
RenderableType,
|
||||
)
|
||||
from pip._vendor.rich.markdown import Markdown
|
||||
from pip._vendor.rich.measure import Measurement
|
||||
from pip._vendor.rich.pretty import Pretty
|
||||
from pip._vendor.rich.segment import Segment
|
||||
from pip._vendor.rich.style import Style
|
||||
from pip._vendor.rich.syntax import Syntax
|
||||
from pip._vendor.rich.table import Table
|
||||
from pip._vendor.rich.text import Text
|
||||
|
||||
|
||||
class ColorBox:
|
||||
def __rich_console__(
|
||||
self, console: Console, options: ConsoleOptions
|
||||
) -> RenderResult:
|
||||
for y in range(0, 5):
|
||||
for x in range(options.max_width):
|
||||
h = x / options.max_width
|
||||
l = 0.1 + ((y / 5) * 0.7)
|
||||
r1, g1, b1 = colorsys.hls_to_rgb(h, l, 1.0)
|
||||
r2, g2, b2 = colorsys.hls_to_rgb(h, l + 0.7 / 10, 1.0)
|
||||
bgcolor = Color.from_rgb(r1 * 255, g1 * 255, b1 * 255)
|
||||
color = Color.from_rgb(r2 * 255, g2 * 255, b2 * 255)
|
||||
yield Segment("▄", Style(color=color, bgcolor=bgcolor))
|
||||
yield Segment.line()
|
||||
|
||||
def __rich_measure__(
|
||||
self, console: "Console", options: ConsoleOptions
|
||||
) -> Measurement:
|
||||
return Measurement(1, options.max_width)
|
||||
|
||||
|
||||
def make_test_card() -> Table:
|
||||
"""Get a renderable that demonstrates a number of features."""
|
||||
table = Table.grid(padding=1, pad_edge=True)
|
||||
table.title = "Rich features"
|
||||
table.add_column("Feature", no_wrap=True, justify="center", style="bold red")
|
||||
table.add_column("Demonstration")
|
||||
|
||||
color_table = Table(
|
||||
box=None,
|
||||
expand=False,
|
||||
show_header=False,
|
||||
show_edge=False,
|
||||
pad_edge=False,
|
||||
)
|
||||
color_table.add_row(
|
||||
# "[bold yellow]256[/] colors or [bold green]16.7 million[/] colors [blue](if supported by your terminal)[/].",
|
||||
(
|
||||
"✓ [bold green]4-bit color[/]\n"
|
||||
"✓ [bold blue]8-bit color[/]\n"
|
||||
"✓ [bold magenta]Truecolor (16.7 million)[/]\n"
|
||||
"✓ [bold yellow]Dumb terminals[/]\n"
|
||||
"✓ [bold cyan]Automatic color conversion"
|
||||
),
|
||||
ColorBox(),
|
||||
)
|
||||
|
||||
table.add_row("Colors", color_table)
|
||||
|
||||
table.add_row(
|
||||
"Styles",
|
||||
"All ansi styles: [bold]bold[/], [dim]dim[/], [italic]italic[/italic], [underline]underline[/], [strike]strikethrough[/], [reverse]reverse[/], and even [blink]blink[/].",
|
||||
)
|
||||
|
||||
lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque in metus sed sapien ultricies pretium a at justo. Maecenas luctus velit et auctor maximus."
|
||||
lorem_table = Table.grid(padding=1, collapse_padding=True)
|
||||
lorem_table.pad_edge = False
|
||||
lorem_table.add_row(
|
||||
Text(lorem, justify="left", style="green"),
|
||||
Text(lorem, justify="center", style="yellow"),
|
||||
Text(lorem, justify="right", style="blue"),
|
||||
Text(lorem, justify="full", style="red"),
|
||||
)
|
||||
table.add_row(
|
||||
"Text",
|
||||
Group(
|
||||
Text.from_markup(
|
||||
"""Word wrap text. Justify [green]left[/], [yellow]center[/], [blue]right[/] or [red]full[/].\n"""
|
||||
),
|
||||
lorem_table,
|
||||
),
|
||||
)
|
||||
|
||||
def comparison(renderable1: RenderableType, renderable2: RenderableType) -> Table:
|
||||
table = Table(show_header=False, pad_edge=False, box=None, expand=True)
|
||||
table.add_column("1", ratio=1)
|
||||
table.add_column("2", ratio=1)
|
||||
table.add_row(renderable1, renderable2)
|
||||
return table
|
||||
|
||||
table.add_row(
|
||||
"Asian\nlanguage\nsupport",
|
||||
":flag_for_china: 该库支持中文,日文和韩文文本!\n:flag_for_japan: ライブラリは中国語、日本語、韓国語のテキストをサポートしています\n:flag_for_south_korea: 이 라이브러리는 중국어, 일본어 및 한국어 텍스트를 지원합니다",
|
||||
)
|
||||
|
||||
markup_example = (
|
||||
"[bold magenta]Rich[/] supports a simple [i]bbcode[/i]-like [b]markup[/b] for [yellow]color[/], [underline]style[/], and emoji! "
|
||||
":+1: :apple: :ant: :bear: :baguette_bread: :bus: "
|
||||
)
|
||||
table.add_row("Markup", markup_example)
|
||||
|
||||
example_table = Table(
|
||||
show_edge=False,
|
||||
show_header=True,
|
||||
expand=False,
|
||||
row_styles=["none", "dim"],
|
||||
box=box.SIMPLE,
|
||||
)
|
||||
example_table.add_column("[green]Date", style="green", no_wrap=True)
|
||||
example_table.add_column("[blue]Title", style="blue")
|
||||
example_table.add_column(
|
||||
"[cyan]Production Budget",
|
||||
style="cyan",
|
||||
justify="right",
|
||||
no_wrap=True,
|
||||
)
|
||||
example_table.add_column(
|
||||
"[magenta]Box Office",
|
||||
style="magenta",
|
||||
justify="right",
|
||||
no_wrap=True,
|
||||
)
|
||||
example_table.add_row(
|
||||
"Dec 20, 2019",
|
||||
"Star Wars: The Rise of Skywalker",
|
||||
"$275,000,000",
|
||||
"$375,126,118",
|
||||
)
|
||||
example_table.add_row(
|
||||
"May 25, 2018",
|
||||
"[b]Solo[/]: A Star Wars Story",
|
||||
"$275,000,000",
|
||||
"$393,151,347",
|
||||
)
|
||||
example_table.add_row(
|
||||
"Dec 15, 2017",
|
||||
"Star Wars Ep. VIII: The Last Jedi",
|
||||
"$262,000,000",
|
||||
"[bold]$1,332,539,889[/bold]",
|
||||
)
|
||||
example_table.add_row(
|
||||
"May 19, 1999",
|
||||
"Star Wars Ep. [b]I[/b]: [i]The phantom Menace",
|
||||
"$115,000,000",
|
||||
"$1,027,044,677",
|
||||
)
|
||||
|
||||
table.add_row("Tables", example_table)
|
||||
|
||||
code = '''\
|
||||
def iter_last(values: Iterable[T]) -> Iterable[Tuple[bool, T]]:
|
||||
"""Iterate and generate a tuple with a flag for last value."""
|
||||
iter_values = iter(values)
|
||||
try:
|
||||
previous_value = next(iter_values)
|
||||
except StopIteration:
|
||||
return
|
||||
for value in iter_values:
|
||||
yield False, previous_value
|
||||
previous_value = value
|
||||
yield True, previous_value'''
|
||||
|
||||
pretty_data = {
|
||||
"foo": [
|
||||
3.1427,
|
||||
(
|
||||
"Paul Atreides",
|
||||
"Vladimir Harkonnen",
|
||||
"Thufir Hawat",
|
||||
),
|
||||
],
|
||||
"atomic": (False, True, None),
|
||||
}
|
||||
table.add_row(
|
||||
"Syntax\nhighlighting\n&\npretty\nprinting",
|
||||
comparison(
|
||||
Syntax(code, "python3", line_numbers=True, indent_guides=True),
|
||||
Pretty(pretty_data, indent_guides=True),
|
||||
),
|
||||
)
|
||||
|
||||
markdown_example = """\
|
||||
# Markdown
|
||||
|
||||
Supports much of the *markdown* __syntax__!
|
||||
|
||||
- Headers
|
||||
- Basic formatting: **bold**, *italic*, `code`
|
||||
- Block quotes
|
||||
- Lists, and more...
|
||||
"""
|
||||
table.add_row(
|
||||
"Markdown", comparison("[cyan]" + markdown_example, Markdown(markdown_example))
|
||||
)
|
||||
|
||||
table.add_row(
|
||||
"+more!",
|
||||
"""Progress bars, columns, styled logging handler, tracebacks, etc...""",
|
||||
)
|
||||
return table
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
|
||||
console = Console(
|
||||
file=io.StringIO(),
|
||||
force_terminal=True,
|
||||
)
|
||||
test_card = make_test_card()
|
||||
|
||||
# Print once to warm cache
|
||||
console.print(test_card)
|
||||
console.file = io.StringIO()
|
||||
|
||||
start = process_time()
|
||||
console.print(test_card)
|
||||
taken = round((process_time() - start) * 1000.0, 1)
|
||||
|
||||
text = console.file.getvalue()
|
||||
# https://bugs.python.org/issue37871
|
||||
for line in text.splitlines(True):
|
||||
print(line, end="")
|
||||
|
||||
print(f"rendered in {taken}ms")
|
||||
|
||||
from pip._vendor.rich.panel import Panel
|
||||
|
||||
console = Console()
|
||||
|
||||
sponsor_message = Table.grid(padding=1)
|
||||
sponsor_message.add_column(style="green", justify="right")
|
||||
sponsor_message.add_column(no_wrap=True)
|
||||
sponsor_message.add_row(
|
||||
"Sponsor me",
|
||||
"[u blue link=https://github.com/sponsors/willmcgugan]https://github.com/sponsors/willmcgugan",
|
||||
)
|
||||
sponsor_message.add_row(
|
||||
"Buy me a :coffee:",
|
||||
"[u blue link=https://ko-fi.com/willmcgugan]https://ko-fi.com/willmcgugan",
|
||||
)
|
||||
sponsor_message.add_row(
|
||||
"Twitter",
|
||||
"[u blue link=https://twitter.com/willmcgugan]https://twitter.com/willmcgugan",
|
||||
)
|
||||
sponsor_message.add_row(
|
||||
"Blog", "[u blue link=https://www.willmcgugan.com]https://www.willmcgugan.com"
|
||||
)
|
||||
|
||||
intro_message = Text.from_markup(
|
||||
"""\
|
||||
It takes a lot of time to develop Rich and to provide support.
|
||||
|
||||
Consider supporting my work via Github Sponsors (ask your company / organization), or buy me a coffee to say thanks.
|
||||
|
||||
- Will McGugan"""
|
||||
)
|
||||
|
||||
message = Table.grid(padding=2)
|
||||
message.add_column()
|
||||
message.add_column(no_wrap=True)
|
||||
message.add_row(intro_message, sponsor_message)
|
||||
|
||||
console.print(
|
||||
Panel.fit(
|
||||
message,
|
||||
box=box.ROUNDED,
|
||||
padding=(1, 2),
|
||||
title="[b red]Thanks for trying out Rich!",
|
||||
border_style="bright_blue",
|
||||
),
|
||||
justify="center",
|
||||
)
|
|
@ -0,0 +1,451 @@
|
|||
# Auto generated by make_terminal_widths.py
|
||||
|
||||
CELL_WIDTHS = [
|
||||
(0, 0, 0),
|
||||
(1, 31, -1),
|
||||
(127, 159, -1),
|
||||
(768, 879, 0),
|
||||
(1155, 1161, 0),
|
||||
(1425, 1469, 0),
|
||||
(1471, 1471, 0),
|
||||
(1473, 1474, 0),
|
||||
(1476, 1477, 0),
|
||||
(1479, 1479, 0),
|
||||
(1552, 1562, 0),
|
||||
(1611, 1631, 0),
|
||||
(1648, 1648, 0),
|
||||
(1750, 1756, 0),
|
||||
(1759, 1764, 0),
|
||||
(1767, 1768, 0),
|
||||
(1770, 1773, 0),
|
||||
(1809, 1809, 0),
|
||||
(1840, 1866, 0),
|
||||
(1958, 1968, 0),
|
||||
(2027, 2035, 0),
|
||||
(2045, 2045, 0),
|
||||
(2070, 2073, 0),
|
||||
(2075, 2083, 0),
|
||||
(2085, 2087, 0),
|
||||
(2089, 2093, 0),
|
||||
(2137, 2139, 0),
|
||||
(2259, 2273, 0),
|
||||
(2275, 2306, 0),
|
||||
(2362, 2362, 0),
|
||||
(2364, 2364, 0),
|
||||
(2369, 2376, 0),
|
||||
(2381, 2381, 0),
|
||||
(2385, 2391, 0),
|
||||
(2402, 2403, 0),
|
||||
(2433, 2433, 0),
|
||||
(2492, 2492, 0),
|
||||
(2497, 2500, 0),
|
||||
(2509, 2509, 0),
|
||||
(2530, 2531, 0),
|
||||
(2558, 2558, 0),
|
||||
(2561, 2562, 0),
|
||||
(2620, 2620, 0),
|
||||
(2625, 2626, 0),
|
||||
(2631, 2632, 0),
|
||||
(2635, 2637, 0),
|
||||
(2641, 2641, 0),
|
||||
(2672, 2673, 0),
|
||||
(2677, 2677, 0),
|
||||
(2689, 2690, 0),
|
||||
(2748, 2748, 0),
|
||||
(2753, 2757, 0),
|
||||
(2759, 2760, 0),
|
||||
(2765, 2765, 0),
|
||||
(2786, 2787, 0),
|
||||
(2810, 2815, 0),
|
||||
(2817, 2817, 0),
|
||||
(2876, 2876, 0),
|
||||
(2879, 2879, 0),
|
||||
(2881, 2884, 0),
|
||||
(2893, 2893, 0),
|
||||
(2901, 2902, 0),
|
||||
(2914, 2915, 0),
|
||||
(2946, 2946, 0),
|
||||
(3008, 3008, 0),
|
||||
(3021, 3021, 0),
|
||||
(3072, 3072, 0),
|
||||
(3076, 3076, 0),
|
||||
(3134, 3136, 0),
|
||||
(3142, 3144, 0),
|
||||
(3146, 3149, 0),
|
||||
(3157, 3158, 0),
|
||||
(3170, 3171, 0),
|
||||
(3201, 3201, 0),
|
||||
(3260, 3260, 0),
|
||||
(3263, 3263, 0),
|
||||
(3270, 3270, 0),
|
||||
(3276, 3277, 0),
|
||||
(3298, 3299, 0),
|
||||
(3328, 3329, 0),
|
||||
(3387, 3388, 0),
|
||||
(3393, 3396, 0),
|
||||
(3405, 3405, 0),
|
||||
(3426, 3427, 0),
|
||||
(3457, 3457, 0),
|
||||
(3530, 3530, 0),
|
||||
(3538, 3540, 0),
|
||||
(3542, 3542, 0),
|
||||
(3633, 3633, 0),
|
||||
(3636, 3642, 0),
|
||||
(3655, 3662, 0),
|
||||
(3761, 3761, 0),
|
||||
(3764, 3772, 0),
|
||||
(3784, 3789, 0),
|
||||
(3864, 3865, 0),
|
||||
(3893, 3893, 0),
|
||||
(3895, 3895, 0),
|
||||
(3897, 3897, 0),
|
||||
(3953, 3966, 0),
|
||||
(3968, 3972, 0),
|
||||
(3974, 3975, 0),
|
||||
(3981, 3991, 0),
|
||||
(3993, 4028, 0),
|
||||
(4038, 4038, 0),
|
||||
(4141, 4144, 0),
|
||||
(4146, 4151, 0),
|
||||
(4153, 4154, 0),
|
||||
(4157, 4158, 0),
|
||||
(4184, 4185, 0),
|
||||
(4190, 4192, 0),
|
||||
(4209, 4212, 0),
|
||||
(4226, 4226, 0),
|
||||
(4229, 4230, 0),
|
||||
(4237, 4237, 0),
|
||||
(4253, 4253, 0),
|
||||
(4352, 4447, 2),
|
||||
(4957, 4959, 0),
|
||||
(5906, 5908, 0),
|
||||
(5938, 5940, 0),
|
||||
(5970, 5971, 0),
|
||||
(6002, 6003, 0),
|
||||
(6068, 6069, 0),
|
||||
(6071, 6077, 0),
|
||||
(6086, 6086, 0),
|
||||
(6089, 6099, 0),
|
||||
(6109, 6109, 0),
|
||||
(6155, 6157, 0),
|
||||
(6277, 6278, 0),
|
||||
(6313, 6313, 0),
|
||||
(6432, 6434, 0),
|
||||
(6439, 6440, 0),
|
||||
(6450, 6450, 0),
|
||||
(6457, 6459, 0),
|
||||
(6679, 6680, 0),
|
||||
(6683, 6683, 0),
|
||||
(6742, 6742, 0),
|
||||
(6744, 6750, 0),
|
||||
(6752, 6752, 0),
|
||||
(6754, 6754, 0),
|
||||
(6757, 6764, 0),
|
||||
(6771, 6780, 0),
|
||||
(6783, 6783, 0),
|
||||
(6832, 6848, 0),
|
||||
(6912, 6915, 0),
|
||||
(6964, 6964, 0),
|
||||
(6966, 6970, 0),
|
||||
(6972, 6972, 0),
|
||||
(6978, 6978, 0),
|
||||
(7019, 7027, 0),
|
||||
(7040, 7041, 0),
|
||||
(7074, 7077, 0),
|
||||
(7080, 7081, 0),
|
||||
(7083, 7085, 0),
|
||||
(7142, 7142, 0),
|
||||
(7144, 7145, 0),
|
||||
(7149, 7149, 0),
|
||||
(7151, 7153, 0),
|
||||
(7212, 7219, 0),
|
||||
(7222, 7223, 0),
|
||||
(7376, 7378, 0),
|
||||
(7380, 7392, 0),
|
||||
(7394, 7400, 0),
|
||||
(7405, 7405, 0),
|
||||
(7412, 7412, 0),
|
||||
(7416, 7417, 0),
|
||||
(7616, 7673, 0),
|
||||
(7675, 7679, 0),
|
||||
(8203, 8207, 0),
|
||||
(8232, 8238, 0),
|
||||
(8288, 8291, 0),
|
||||
(8400, 8432, 0),
|
||||
(8986, 8987, 2),
|
||||
(9001, 9002, 2),
|
||||
(9193, 9196, 2),
|
||||
(9200, 9200, 2),
|
||||
(9203, 9203, 2),
|
||||
(9725, 9726, 2),
|
||||
(9748, 9749, 2),
|
||||
(9800, 9811, 2),
|
||||
(9855, 9855, 2),
|
||||
(9875, 9875, 2),
|
||||
(9889, 9889, 2),
|
||||
(9898, 9899, 2),
|
||||
(9917, 9918, 2),
|
||||
(9924, 9925, 2),
|
||||
(9934, 9934, 2),
|
||||
(9940, 9940, 2),
|
||||
(9962, 9962, 2),
|
||||
(9970, 9971, 2),
|
||||
(9973, 9973, 2),
|
||||
(9978, 9978, 2),
|
||||
(9981, 9981, 2),
|
||||
(9989, 9989, 2),
|
||||
(9994, 9995, 2),
|
||||
(10024, 10024, 2),
|
||||
(10060, 10060, 2),
|
||||
(10062, 10062, 2),
|
||||
(10067, 10069, 2),
|
||||
(10071, 10071, 2),
|
||||
(10133, 10135, 2),
|
||||
(10160, 10160, 2),
|
||||
(10175, 10175, 2),
|
||||
(11035, 11036, 2),
|
||||
(11088, 11088, 2),
|
||||
(11093, 11093, 2),
|
||||
(11503, 11505, 0),
|
||||
(11647, 11647, 0),
|
||||
(11744, 11775, 0),
|
||||
(11904, 11929, 2),
|
||||
(11931, 12019, 2),
|
||||
(12032, 12245, 2),
|
||||
(12272, 12283, 2),
|
||||
(12288, 12329, 2),
|
||||
(12330, 12333, 0),
|
||||
(12334, 12350, 2),
|
||||
(12353, 12438, 2),
|
||||
(12441, 12442, 0),
|
||||
(12443, 12543, 2),
|
||||
(12549, 12591, 2),
|
||||
(12593, 12686, 2),
|
||||
(12688, 12771, 2),
|
||||
(12784, 12830, 2),
|
||||
(12832, 12871, 2),
|
||||
(12880, 19903, 2),
|
||||
(19968, 42124, 2),
|
||||
(42128, 42182, 2),
|
||||
(42607, 42610, 0),
|
||||
(42612, 42621, 0),
|
||||
(42654, 42655, 0),
|
||||
(42736, 42737, 0),
|
||||
(43010, 43010, 0),
|
||||
(43014, 43014, 0),
|
||||
(43019, 43019, 0),
|
||||
(43045, 43046, 0),
|
||||
(43052, 43052, 0),
|
||||
(43204, 43205, 0),
|
||||
(43232, 43249, 0),
|
||||
(43263, 43263, 0),
|
||||
(43302, 43309, 0),
|
||||
(43335, 43345, 0),
|
||||
(43360, 43388, 2),
|
||||
(43392, 43394, 0),
|
||||
(43443, 43443, 0),
|
||||
(43446, 43449, 0),
|
||||
(43452, 43453, 0),
|
||||
(43493, 43493, 0),
|
||||
(43561, 43566, 0),
|
||||
(43569, 43570, 0),
|
||||
(43573, 43574, 0),
|
||||
(43587, 43587, 0),
|
||||
(43596, 43596, 0),
|
||||
(43644, 43644, 0),
|
||||
(43696, 43696, 0),
|
||||
(43698, 43700, 0),
|
||||
(43703, 43704, 0),
|
||||
(43710, 43711, 0),
|
||||
(43713, 43713, 0),
|
||||
(43756, 43757, 0),
|
||||
(43766, 43766, 0),
|
||||
(44005, 44005, 0),
|
||||
(44008, 44008, 0),
|
||||
(44013, 44013, 0),
|
||||
(44032, 55203, 2),
|
||||
(63744, 64255, 2),
|
||||
(64286, 64286, 0),
|
||||
(65024, 65039, 0),
|
||||
(65040, 65049, 2),
|
||||
(65056, 65071, 0),
|
||||
(65072, 65106, 2),
|
||||
(65108, 65126, 2),
|
||||
(65128, 65131, 2),
|
||||
(65281, 65376, 2),
|
||||
(65504, 65510, 2),
|
||||
(66045, 66045, 0),
|
||||
(66272, 66272, 0),
|
||||
(66422, 66426, 0),
|
||||
(68097, 68099, 0),
|
||||
(68101, 68102, 0),
|
||||
(68108, 68111, 0),
|
||||
(68152, 68154, 0),
|
||||
(68159, 68159, 0),
|
||||
(68325, 68326, 0),
|
||||
(68900, 68903, 0),
|
||||
(69291, 69292, 0),
|
||||
(69446, 69456, 0),
|
||||
(69633, 69633, 0),
|
||||
(69688, 69702, 0),
|
||||
(69759, 69761, 0),
|
||||
(69811, 69814, 0),
|
||||
(69817, 69818, 0),
|
||||
(69888, 69890, 0),
|
||||
(69927, 69931, 0),
|
||||
(69933, 69940, 0),
|
||||
(70003, 70003, 0),
|
||||
(70016, 70017, 0),
|
||||
(70070, 70078, 0),
|
||||
(70089, 70092, 0),
|
||||
(70095, 70095, 0),
|
||||
(70191, 70193, 0),
|
||||
(70196, 70196, 0),
|
||||
(70198, 70199, 0),
|
||||
(70206, 70206, 0),
|
||||
(70367, 70367, 0),
|
||||
(70371, 70378, 0),
|
||||
(70400, 70401, 0),
|
||||
(70459, 70460, 0),
|
||||
(70464, 70464, 0),
|
||||
(70502, 70508, 0),
|
||||
(70512, 70516, 0),
|
||||
(70712, 70719, 0),
|
||||
(70722, 70724, 0),
|
||||
(70726, 70726, 0),
|
||||
(70750, 70750, 0),
|
||||
(70835, 70840, 0),
|
||||
(70842, 70842, 0),
|
||||
(70847, 70848, 0),
|
||||
(70850, 70851, 0),
|
||||
(71090, 71093, 0),
|
||||
(71100, 71101, 0),
|
||||
(71103, 71104, 0),
|
||||
(71132, 71133, 0),
|
||||
(71219, 71226, 0),
|
||||
(71229, 71229, 0),
|
||||
(71231, 71232, 0),
|
||||
(71339, 71339, 0),
|
||||
(71341, 71341, 0),
|
||||
(71344, 71349, 0),
|
||||
(71351, 71351, 0),
|
||||
(71453, 71455, 0),
|
||||
(71458, 71461, 0),
|
||||
(71463, 71467, 0),
|
||||
(71727, 71735, 0),
|
||||
(71737, 71738, 0),
|
||||
(71995, 71996, 0),
|
||||
(71998, 71998, 0),
|
||||
(72003, 72003, 0),
|
||||
(72148, 72151, 0),
|
||||
(72154, 72155, 0),
|
||||
(72160, 72160, 0),
|
||||
(72193, 72202, 0),
|
||||
(72243, 72248, 0),
|
||||
(72251, 72254, 0),
|
||||
(72263, 72263, 0),
|
||||
(72273, 72278, 0),
|
||||
(72281, 72283, 0),
|
||||
(72330, 72342, 0),
|
||||
(72344, 72345, 0),
|
||||
(72752, 72758, 0),
|
||||
(72760, 72765, 0),
|
||||
(72767, 72767, 0),
|
||||
(72850, 72871, 0),
|
||||
(72874, 72880, 0),
|
||||
(72882, 72883, 0),
|
||||
(72885, 72886, 0),
|
||||
(73009, 73014, 0),
|
||||
(73018, 73018, 0),
|
||||
(73020, 73021, 0),
|
||||
(73023, 73029, 0),
|
||||
(73031, 73031, 0),
|
||||
(73104, 73105, 0),
|
||||
(73109, 73109, 0),
|
||||
(73111, 73111, 0),
|
||||
(73459, 73460, 0),
|
||||
(92912, 92916, 0),
|
||||
(92976, 92982, 0),
|
||||
(94031, 94031, 0),
|
||||
(94095, 94098, 0),
|
||||
(94176, 94179, 2),
|
||||
(94180, 94180, 0),
|
||||
(94192, 94193, 2),
|
||||
(94208, 100343, 2),
|
||||
(100352, 101589, 2),
|
||||
(101632, 101640, 2),
|
||||
(110592, 110878, 2),
|
||||
(110928, 110930, 2),
|
||||
(110948, 110951, 2),
|
||||
(110960, 111355, 2),
|
||||
(113821, 113822, 0),
|
||||
(119143, 119145, 0),
|
||||
(119163, 119170, 0),
|
||||
(119173, 119179, 0),
|
||||
(119210, 119213, 0),
|
||||
(119362, 119364, 0),
|
||||
(121344, 121398, 0),
|
||||
(121403, 121452, 0),
|
||||
(121461, 121461, 0),
|
||||
(121476, 121476, 0),
|
||||
(121499, 121503, 0),
|
||||
(121505, 121519, 0),
|
||||
(122880, 122886, 0),
|
||||
(122888, 122904, 0),
|
||||
(122907, 122913, 0),
|
||||
(122915, 122916, 0),
|
||||
(122918, 122922, 0),
|
||||
(123184, 123190, 0),
|
||||
(123628, 123631, 0),
|
||||
(125136, 125142, 0),
|
||||
(125252, 125258, 0),
|
||||
(126980, 126980, 2),
|
||||
(127183, 127183, 2),
|
||||
(127374, 127374, 2),
|
||||
(127377, 127386, 2),
|
||||
(127488, 127490, 2),
|
||||
(127504, 127547, 2),
|
||||
(127552, 127560, 2),
|
||||
(127568, 127569, 2),
|
||||
(127584, 127589, 2),
|
||||
(127744, 127776, 2),
|
||||
(127789, 127797, 2),
|
||||
(127799, 127868, 2),
|
||||
(127870, 127891, 2),
|
||||
(127904, 127946, 2),
|
||||
(127951, 127955, 2),
|
||||
(127968, 127984, 2),
|
||||
(127988, 127988, 2),
|
||||
(127992, 128062, 2),
|
||||
(128064, 128064, 2),
|
||||
(128066, 128252, 2),
|
||||
(128255, 128317, 2),
|
||||
(128331, 128334, 2),
|
||||
(128336, 128359, 2),
|
||||
(128378, 128378, 2),
|
||||
(128405, 128406, 2),
|
||||
(128420, 128420, 2),
|
||||
(128507, 128591, 2),
|
||||
(128640, 128709, 2),
|
||||
(128716, 128716, 2),
|
||||
(128720, 128722, 2),
|
||||
(128725, 128727, 2),
|
||||
(128747, 128748, 2),
|
||||
(128756, 128764, 2),
|
||||
(128992, 129003, 2),
|
||||
(129292, 129338, 2),
|
||||
(129340, 129349, 2),
|
||||
(129351, 129400, 2),
|
||||
(129402, 129483, 2),
|
||||
(129485, 129535, 2),
|
||||
(129648, 129652, 2),
|
||||
(129656, 129658, 2),
|
||||
(129664, 129670, 2),
|
||||
(129680, 129704, 2),
|
||||
(129712, 129718, 2),
|
||||
(129728, 129730, 2),
|
||||
(129744, 129750, 2),
|
||||
(131072, 196605, 2),
|
||||
(196608, 262141, 2),
|
||||
(917760, 917999, 0),
|
||||
]
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,32 @@
|
|||
from typing import Callable, Match, Optional
|
||||
import re
|
||||
|
||||
from ._emoji_codes import EMOJI
|
||||
|
||||
|
||||
_ReStringMatch = Match[str] # regex match object
|
||||
_ReSubCallable = Callable[[_ReStringMatch], str] # Callable invoked by re.sub
|
||||
_EmojiSubMethod = Callable[[_ReSubCallable, str], str] # Sub method of a compiled re
|
||||
|
||||
|
||||
def _emoji_replace(
|
||||
text: str,
|
||||
default_variant: Optional[str] = None,
|
||||
_emoji_sub: _EmojiSubMethod = re.compile(r"(:(\S*?)(?:(?:\-)(emoji|text))?:)").sub,
|
||||
) -> str:
|
||||
"""Replace emoji code in text."""
|
||||
get_emoji = EMOJI.__getitem__
|
||||
variants = {"text": "\uFE0E", "emoji": "\uFE0F"}
|
||||
get_variant = variants.get
|
||||
default_variant_code = variants.get(default_variant, "") if default_variant else ""
|
||||
|
||||
def do_replace(match: Match[str]) -> str:
|
||||
emoji_code, emoji_name, variant = match.groups()
|
||||
try:
|
||||
return get_emoji(emoji_name.lower()) + get_variant(
|
||||
variant, default_variant_code
|
||||
)
|
||||
except KeyError:
|
||||
return emoji_code
|
||||
|
||||
return _emoji_sub(do_replace, text)
|
|
@ -0,0 +1,10 @@
|
|||
from typing import Any
|
||||
|
||||
|
||||
def load_ipython_extension(ip: Any) -> None: # pragma: no cover
|
||||
# prevent circular import
|
||||
from pip._vendor.rich.pretty import install
|
||||
from pip._vendor.rich.traceback import install as tr_install
|
||||
|
||||
install()
|
||||
tr_install()
|
|
@ -0,0 +1,210 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from inspect import cleandoc, getdoc, getfile, isclass, ismodule, signature
|
||||
from typing import Any, Iterable, Optional, Tuple
|
||||
|
||||
from .console import RenderableType, Group
|
||||
from .highlighter import ReprHighlighter
|
||||
from .jupyter import JupyterMixin
|
||||
from .panel import Panel
|
||||
from .pretty import Pretty
|
||||
from .table import Table
|
||||
from .text import Text, TextType
|
||||
|
||||
|
||||
def _first_paragraph(doc: str) -> str:
|
||||
"""Get the first paragraph from a docstring."""
|
||||
paragraph, _, _ = doc.partition("\n\n")
|
||||
return paragraph
|
||||
|
||||
|
||||
def _reformat_doc(doc: str) -> str:
|
||||
"""Reformat docstring."""
|
||||
doc = cleandoc(doc).strip()
|
||||
return doc
|
||||
|
||||
|
||||
class Inspect(JupyterMixin):
|
||||
"""A renderable to inspect any Python Object.
|
||||
|
||||
Args:
|
||||
obj (Any): An object to inspect.
|
||||
title (str, optional): Title to display over inspect result, or None use type. Defaults to None.
|
||||
help (bool, optional): Show full help text rather than just first paragraph. Defaults to False.
|
||||
methods (bool, optional): Enable inspection of callables. Defaults to False.
|
||||
docs (bool, optional): Also render doc strings. Defaults to True.
|
||||
private (bool, optional): Show private attributes (beginning with underscore). Defaults to False.
|
||||
dunder (bool, optional): Show attributes starting with double underscore. Defaults to False.
|
||||
sort (bool, optional): Sort attributes alphabetically. Defaults to True.
|
||||
all (bool, optional): Show all attributes. Defaults to False.
|
||||
value (bool, optional): Pretty print value of object. Defaults to True.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
obj: Any,
|
||||
*,
|
||||
title: Optional[TextType] = None,
|
||||
help: bool = False,
|
||||
methods: bool = False,
|
||||
docs: bool = True,
|
||||
private: bool = False,
|
||||
dunder: bool = False,
|
||||
sort: bool = True,
|
||||
all: bool = True,
|
||||
value: bool = True,
|
||||
) -> None:
|
||||
self.highlighter = ReprHighlighter()
|
||||
self.obj = obj
|
||||
self.title = title or self._make_title(obj)
|
||||
if all:
|
||||
methods = private = dunder = True
|
||||
self.help = help
|
||||
self.methods = methods
|
||||
self.docs = docs or help
|
||||
self.private = private or dunder
|
||||
self.dunder = dunder
|
||||
self.sort = sort
|
||||
self.value = value
|
||||
|
||||
def _make_title(self, obj: Any) -> Text:
|
||||
"""Make a default title."""
|
||||
title_str = (
|
||||
str(obj)
|
||||
if (isclass(obj) or callable(obj) or ismodule(obj))
|
||||
else str(type(obj))
|
||||
)
|
||||
title_text = self.highlighter(title_str)
|
||||
return title_text
|
||||
|
||||
def __rich__(self) -> Panel:
|
||||
return Panel.fit(
|
||||
Group(*self._render()),
|
||||
title=self.title,
|
||||
border_style="scope.border",
|
||||
padding=(0, 1),
|
||||
)
|
||||
|
||||
def _get_signature(self, name: str, obj: Any) -> Optional[Text]:
|
||||
"""Get a signature for a callable."""
|
||||
try:
|
||||
_signature = str(signature(obj)) + ":"
|
||||
except ValueError:
|
||||
_signature = "(...)"
|
||||
except TypeError:
|
||||
return None
|
||||
|
||||
source_filename: Optional[str] = None
|
||||
try:
|
||||
source_filename = getfile(obj)
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
callable_name = Text(name, style="inspect.callable")
|
||||
if source_filename:
|
||||
callable_name.stylize(f"link file://{source_filename}")
|
||||
signature_text = self.highlighter(_signature)
|
||||
|
||||
qualname = name or getattr(obj, "__qualname__", name)
|
||||
qual_signature = Text.assemble(
|
||||
("def ", "inspect.def"), (qualname, "inspect.callable"), signature_text
|
||||
)
|
||||
|
||||
return qual_signature
|
||||
|
||||
def _render(self) -> Iterable[RenderableType]:
|
||||
"""Render object."""
|
||||
|
||||
def sort_items(item: Tuple[str, Any]) -> Tuple[bool, str]:
|
||||
key, (_error, value) = item
|
||||
return (callable(value), key.strip("_").lower())
|
||||
|
||||
def safe_getattr(attr_name: str) -> Tuple[Any, Any]:
|
||||
"""Get attribute or any exception."""
|
||||
try:
|
||||
return (None, getattr(obj, attr_name))
|
||||
except Exception as error:
|
||||
return (error, None)
|
||||
|
||||
obj = self.obj
|
||||
keys = dir(obj)
|
||||
total_items = len(keys)
|
||||
if not self.dunder:
|
||||
keys = [key for key in keys if not key.startswith("__")]
|
||||
if not self.private:
|
||||
keys = [key for key in keys if not key.startswith("_")]
|
||||
not_shown_count = total_items - len(keys)
|
||||
items = [(key, safe_getattr(key)) for key in keys]
|
||||
if self.sort:
|
||||
items.sort(key=sort_items)
|
||||
|
||||
items_table = Table.grid(padding=(0, 1), expand=False)
|
||||
items_table.add_column(justify="right")
|
||||
add_row = items_table.add_row
|
||||
highlighter = self.highlighter
|
||||
|
||||
if callable(obj):
|
||||
signature = self._get_signature("", obj)
|
||||
if signature is not None:
|
||||
yield signature
|
||||
yield ""
|
||||
|
||||
if self.docs:
|
||||
_doc = getdoc(obj)
|
||||
if _doc is not None:
|
||||
if not self.help:
|
||||
_doc = _first_paragraph(_doc)
|
||||
doc_text = Text(_reformat_doc(_doc), style="inspect.help")
|
||||
doc_text = highlighter(doc_text)
|
||||
yield doc_text
|
||||
yield ""
|
||||
|
||||
if self.value and not (isclass(obj) or callable(obj) or ismodule(obj)):
|
||||
yield Panel(
|
||||
Pretty(obj, indent_guides=True, max_length=10, max_string=60),
|
||||
border_style="inspect.value.border",
|
||||
)
|
||||
yield ""
|
||||
|
||||
for key, (error, value) in items:
|
||||
key_text = Text.assemble(
|
||||
(
|
||||
key,
|
||||
"inspect.attr.dunder" if key.startswith("__") else "inspect.attr",
|
||||
),
|
||||
(" =", "inspect.equals"),
|
||||
)
|
||||
if error is not None:
|
||||
warning = key_text.copy()
|
||||
warning.stylize("inspect.error")
|
||||
add_row(warning, highlighter(repr(error)))
|
||||
continue
|
||||
|
||||
if callable(value):
|
||||
if not self.methods:
|
||||
continue
|
||||
|
||||
_signature_text = self._get_signature(key, value)
|
||||
if _signature_text is None:
|
||||
add_row(key_text, Pretty(value, highlighter=highlighter))
|
||||
else:
|
||||
if self.docs:
|
||||
docs = getdoc(value)
|
||||
if docs is not None:
|
||||
_doc = _reformat_doc(str(docs))
|
||||
if not self.help:
|
||||
_doc = _first_paragraph(_doc)
|
||||
_signature_text.append("\n" if "\n" in _doc else " ")
|
||||
doc = highlighter(_doc)
|
||||
doc.stylize("inspect.doc")
|
||||
_signature_text.append(doc)
|
||||
|
||||
add_row(key_text, _signature_text)
|
||||
else:
|
||||
add_row(key_text, Pretty(value, highlighter=highlighter))
|
||||
if items_table.row_count:
|
||||
yield items_table
|
||||
else:
|
||||
yield Text.from_markup(
|
||||
f"[b cyan]{not_shown_count}[/][i] attribute(s) not shown.[/i] Run [b][magenta]inspect[/]([not b]inspect[/])[/b] for options."
|
||||
)
|
|
@ -0,0 +1,94 @@
|
|||
from datetime import datetime
|
||||
from typing import Iterable, List, Optional, TYPE_CHECKING, Union, Callable
|
||||
|
||||
|
||||
from .text import Text, TextType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .console import Console, ConsoleRenderable, RenderableType
|
||||
from .table import Table
|
||||
|
||||
FormatTimeCallable = Callable[[datetime], Text]
|
||||
|
||||
|
||||
class LogRender:
|
||||
def __init__(
|
||||
self,
|
||||
show_time: bool = True,
|
||||
show_level: bool = False,
|
||||
show_path: bool = True,
|
||||
time_format: Union[str, FormatTimeCallable] = "[%x %X]",
|
||||
omit_repeated_times: bool = True,
|
||||
level_width: Optional[int] = 8,
|
||||
) -> None:
|
||||
self.show_time = show_time
|
||||
self.show_level = show_level
|
||||
self.show_path = show_path
|
||||
self.time_format = time_format
|
||||
self.omit_repeated_times = omit_repeated_times
|
||||
self.level_width = level_width
|
||||
self._last_time: Optional[Text] = None
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
console: "Console",
|
||||
renderables: Iterable["ConsoleRenderable"],
|
||||
log_time: Optional[datetime] = None,
|
||||
time_format: Optional[Union[str, FormatTimeCallable]] = None,
|
||||
level: TextType = "",
|
||||
path: Optional[str] = None,
|
||||
line_no: Optional[int] = None,
|
||||
link_path: Optional[str] = None,
|
||||
) -> "Table":
|
||||
from .containers import Renderables
|
||||
from .table import Table
|
||||
|
||||
output = Table.grid(padding=(0, 1))
|
||||
output.expand = True
|
||||
if self.show_time:
|
||||
output.add_column(style="log.time")
|
||||
if self.show_level:
|
||||
output.add_column(style="log.level", width=self.level_width)
|
||||
output.add_column(ratio=1, style="log.message", overflow="fold")
|
||||
if self.show_path and path:
|
||||
output.add_column(style="log.path")
|
||||
row: List["RenderableType"] = []
|
||||
if self.show_time:
|
||||
log_time = log_time or console.get_datetime()
|
||||
time_format = time_format or self.time_format
|
||||
if callable(time_format):
|
||||
log_time_display = time_format(log_time)
|
||||
else:
|
||||
log_time_display = Text(log_time.strftime(time_format))
|
||||
if log_time_display == self._last_time and self.omit_repeated_times:
|
||||
row.append(Text(" " * len(log_time_display)))
|
||||
else:
|
||||
row.append(log_time_display)
|
||||
self._last_time = log_time_display
|
||||
if self.show_level:
|
||||
row.append(level)
|
||||
|
||||
row.append(Renderables(renderables))
|
||||
if self.show_path and path:
|
||||
path_text = Text()
|
||||
path_text.append(
|
||||
path, style=f"link file://{link_path}" if link_path else ""
|
||||
)
|
||||
if line_no:
|
||||
path_text.append(":")
|
||||
path_text.append(
|
||||
f"{line_no}",
|
||||
style=f"link file://{link_path}#{line_no}" if link_path else "",
|
||||
)
|
||||
row.append(path_text)
|
||||
|
||||
output.add_row(*row)
|
||||
return output
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
from pip._vendor.rich.console import Console
|
||||
|
||||
c = Console()
|
||||
c.print("[on blue]Hello", justify="right")
|
||||
c.log("[on blue]hello", justify="right")
|
|
@ -0,0 +1,43 @@
|
|||
from typing import Iterable, Tuple, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def loop_first(values: Iterable[T]) -> Iterable[Tuple[bool, T]]:
|
||||
"""Iterate and generate a tuple with a flag for first value."""
|
||||
iter_values = iter(values)
|
||||
try:
|
||||
value = next(iter_values)
|
||||
except StopIteration:
|
||||
return
|
||||
yield True, value
|
||||
for value in iter_values:
|
||||
yield False, value
|
||||
|
||||
|
||||
def loop_last(values: Iterable[T]) -> Iterable[Tuple[bool, T]]:
|
||||
"""Iterate and generate a tuple with a flag for last value."""
|
||||
iter_values = iter(values)
|
||||
try:
|
||||
previous_value = next(iter_values)
|
||||
except StopIteration:
|
||||
return
|
||||
for value in iter_values:
|
||||
yield False, previous_value
|
||||
previous_value = value
|
||||
yield True, previous_value
|
||||
|
||||
|
||||
def loop_first_last(values: Iterable[T]) -> Iterable[Tuple[bool, bool, T]]:
|
||||
"""Iterate and generate a tuple with a flag for first and last value."""
|
||||
iter_values = iter(values)
|
||||
try:
|
||||
previous_value = next(iter_values)
|
||||
except StopIteration:
|
||||
return
|
||||
first = True
|
||||
for value in iter_values:
|
||||
yield first, False, previous_value
|
||||
first = False
|
||||
previous_value = value
|
||||
yield first, True, previous_value
|
|
@ -0,0 +1,34 @@
|
|||
from collections import OrderedDict
|
||||
from typing import Dict, Generic, TypeVar
|
||||
|
||||
|
||||
CacheKey = TypeVar("CacheKey")
|
||||
CacheValue = TypeVar("CacheValue")
|
||||
|
||||
|
||||
class LRUCache(Generic[CacheKey, CacheValue], OrderedDict): # type: ignore # https://github.com/python/mypy/issues/6904
|
||||
"""
|
||||
A dictionary-like container that stores a given maximum items.
|
||||
|
||||
If an additional item is added when the LRUCache is full, the least
|
||||
recently used key is discarded to make room for the new item.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, cache_size: int) -> None:
|
||||
self.cache_size = cache_size
|
||||
super(LRUCache, self).__init__()
|
||||
|
||||
def __setitem__(self, key: CacheKey, value: CacheValue) -> None:
|
||||
"""Store a new views, potentially discarding an old value."""
|
||||
if key not in self:
|
||||
if len(self) >= self.cache_size:
|
||||
self.popitem(last=False)
|
||||
OrderedDict.__setitem__(self, key, value)
|
||||
|
||||
def __getitem__(self: Dict[CacheKey, CacheValue], key: CacheKey) -> CacheValue:
|
||||
"""Gets the item, but also makes it most recent."""
|
||||
value: CacheValue = OrderedDict.__getitem__(self, key)
|
||||
OrderedDict.__delitem__(self, key)
|
||||
OrderedDict.__setitem__(self, key, value)
|
||||
return value
|
|
@ -0,0 +1,309 @@
|
|||
from .palette import Palette
|
||||
|
||||
|
||||
# Taken from https://en.wikipedia.org/wiki/ANSI_escape_code (Windows 10 column)
|
||||
WINDOWS_PALETTE = Palette(
|
||||
[
|
||||
(12, 12, 12),
|
||||
(197, 15, 31),
|
||||
(19, 161, 14),
|
||||
(193, 156, 0),
|
||||
(0, 55, 218),
|
||||
(136, 23, 152),
|
||||
(58, 150, 221),
|
||||
(204, 204, 204),
|
||||
(118, 118, 118),
|
||||
(231, 72, 86),
|
||||
(22, 198, 12),
|
||||
(249, 241, 165),
|
||||
(59, 120, 255),
|
||||
(180, 0, 158),
|
||||
(97, 214, 214),
|
||||
(242, 242, 242),
|
||||
]
|
||||
)
|
||||
|
||||
# # The standard ansi colors (including bright variants)
|
||||
STANDARD_PALETTE = Palette(
|
||||
[
|
||||
(0, 0, 0),
|
||||
(170, 0, 0),
|
||||
(0, 170, 0),
|
||||
(170, 85, 0),
|
||||
(0, 0, 170),
|
||||
(170, 0, 170),
|
||||
(0, 170, 170),
|
||||
(170, 170, 170),
|
||||
(85, 85, 85),
|
||||
(255, 85, 85),
|
||||
(85, 255, 85),
|
||||
(255, 255, 85),
|
||||
(85, 85, 255),
|
||||
(255, 85, 255),
|
||||
(85, 255, 255),
|
||||
(255, 255, 255),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
# The 256 color palette
|
||||
EIGHT_BIT_PALETTE = Palette(
|
||||
[
|
||||
(0, 0, 0),
|
||||
(128, 0, 0),
|
||||
(0, 128, 0),
|
||||
(128, 128, 0),
|
||||
(0, 0, 128),
|
||||
(128, 0, 128),
|
||||
(0, 128, 128),
|
||||
(192, 192, 192),
|
||||
(128, 128, 128),
|
||||
(255, 0, 0),
|
||||
(0, 255, 0),
|
||||
(255, 255, 0),
|
||||
(0, 0, 255),
|
||||
(255, 0, 255),
|
||||
(0, 255, 255),
|
||||
(255, 255, 255),
|
||||
(0, 0, 0),
|
||||
(0, 0, 95),
|
||||
(0, 0, 135),
|
||||
(0, 0, 175),
|
||||
(0, 0, 215),
|
||||
(0, 0, 255),
|
||||
(0, 95, 0),
|
||||
(0, 95, 95),
|
||||
(0, 95, 135),
|
||||
(0, 95, 175),
|
||||
(0, 95, 215),
|
||||
(0, 95, 255),
|
||||
(0, 135, 0),
|
||||
(0, 135, 95),
|
||||
(0, 135, 135),
|
||||
(0, 135, 175),
|
||||
(0, 135, 215),
|
||||
(0, 135, 255),
|
||||
(0, 175, 0),
|
||||
(0, 175, 95),
|
||||
(0, 175, 135),
|
||||
(0, 175, 175),
|
||||
(0, 175, 215),
|
||||
(0, 175, 255),
|
||||
(0, 215, 0),
|
||||
(0, 215, 95),
|
||||
(0, 215, 135),
|
||||
(0, 215, 175),
|
||||
(0, 215, 215),
|
||||
(0, 215, 255),
|
||||
(0, 255, 0),
|
||||
(0, 255, 95),
|
||||
(0, 255, 135),
|
||||
(0, 255, 175),
|
||||
(0, 255, 215),
|
||||
(0, 255, 255),
|
||||
(95, 0, 0),
|
||||
(95, 0, 95),
|
||||
(95, 0, 135),
|
||||
(95, 0, 175),
|
||||
(95, 0, 215),
|
||||
(95, 0, 255),
|
||||
(95, 95, 0),
|
||||
(95, 95, 95),
|
||||
(95, 95, 135),
|
||||
(95, 95, 175),
|
||||
(95, 95, 215),
|
||||
(95, 95, 255),
|
||||
(95, 135, 0),
|
||||
(95, 135, 95),
|
||||
(95, 135, 135),
|
||||
(95, 135, 175),
|
||||
(95, 135, 215),
|
||||
(95, 135, 255),
|
||||
(95, 175, 0),
|
||||
(95, 175, 95),
|
||||
(95, 175, 135),
|
||||
(95, 175, 175),
|
||||
(95, 175, 215),
|
||||
(95, 175, 255),
|
||||
(95, 215, 0),
|
||||
(95, 215, 95),
|
||||
(95, 215, 135),
|
||||
(95, 215, 175),
|
||||
(95, 215, 215),
|
||||
(95, 215, 255),
|
||||
(95, 255, 0),
|
||||
(95, 255, 95),
|
||||
(95, 255, 135),
|
||||
(95, 255, 175),
|
||||
(95, 255, 215),
|
||||
(95, 255, 255),
|
||||
(135, 0, 0),
|
||||
(135, 0, 95),
|
||||
(135, 0, 135),
|
||||
(135, 0, 175),
|
||||
(135, 0, 215),
|
||||
(135, 0, 255),
|
||||
(135, 95, 0),
|
||||
(135, 95, 95),
|
||||
(135, 95, 135),
|
||||
(135, 95, 175),
|
||||
(135, 95, 215),
|
||||
(135, 95, 255),
|
||||
(135, 135, 0),
|
||||
(135, 135, 95),
|
||||
(135, 135, 135),
|
||||
(135, 135, 175),
|
||||
(135, 135, 215),
|
||||
(135, 135, 255),
|
||||
(135, 175, 0),
|
||||
(135, 175, 95),
|
||||
(135, 175, 135),
|
||||
(135, 175, 175),
|
||||
(135, 175, 215),
|
||||
(135, 175, 255),
|
||||
(135, 215, 0),
|
||||
(135, 215, 95),
|
||||
(135, 215, 135),
|
||||
(135, 215, 175),
|
||||
(135, 215, 215),
|
||||
(135, 215, 255),
|
||||
(135, 255, 0),
|
||||
(135, 255, 95),
|
||||
(135, 255, 135),
|
||||
(135, 255, 175),
|
||||
(135, 255, 215),
|
||||
(135, 255, 255),
|
||||
(175, 0, 0),
|
||||
(175, 0, 95),
|
||||
(175, 0, 135),
|
||||
(175, 0, 175),
|
||||
(175, 0, 215),
|
||||
(175, 0, 255),
|
||||
(175, 95, 0),
|
||||
(175, 95, 95),
|
||||
(175, 95, 135),
|
||||
(175, 95, 175),
|
||||
(175, 95, 215),
|
||||
(175, 95, 255),
|
||||
(175, 135, 0),
|
||||
(175, 135, 95),
|
||||
(175, 135, 135),
|
||||
(175, 135, 175),
|
||||
(175, 135, 215),
|
||||
(175, 135, 255),
|
||||
(175, 175, 0),
|
||||
(175, 175, 95),
|
||||
(175, 175, 135),
|
||||
(175, 175, 175),
|
||||
(175, 175, 215),
|
||||
(175, 175, 255),
|
||||
(175, 215, 0),
|
||||
(175, 215, 95),
|
||||
(175, 215, 135),
|
||||
(175, 215, 175),
|
||||
(175, 215, 215),
|
||||
(175, 215, 255),
|
||||
(175, 255, 0),
|
||||
(175, 255, 95),
|
||||
(175, 255, 135),
|
||||
(175, 255, 175),
|
||||
(175, 255, 215),
|
||||
(175, 255, 255),
|
||||
(215, 0, 0),
|
||||
(215, 0, 95),
|
||||
(215, 0, 135),
|
||||
(215, 0, 175),
|
||||
(215, 0, 215),
|
||||
(215, 0, 255),
|
||||
(215, 95, 0),
|
||||
(215, 95, 95),
|
||||
(215, 95, 135),
|
||||
(215, 95, 175),
|
||||
(215, 95, 215),
|
||||
(215, 95, 255),
|
||||
(215, 135, 0),
|
||||
(215, 135, 95),
|
||||
(215, 135, 135),
|
||||
(215, 135, 175),
|
||||
(215, 135, 215),
|
||||
(215, 135, 255),
|
||||
(215, 175, 0),
|
||||
(215, 175, 95),
|
||||
(215, 175, 135),
|
||||
(215, 175, 175),
|
||||
(215, 175, 215),
|
||||
(215, 175, 255),
|
||||
(215, 215, 0),
|
||||
(215, 215, 95),
|
||||
(215, 215, 135),
|
||||
(215, 215, 175),
|
||||
(215, 215, 215),
|
||||
(215, 215, 255),
|
||||
(215, 255, 0),
|
||||
(215, 255, 95),
|
||||
(215, 255, 135),
|
||||
(215, 255, 175),
|
||||
(215, 255, 215),
|
||||
(215, 255, 255),
|
||||
(255, 0, 0),
|
||||
(255, 0, 95),
|
||||
(255, 0, 135),
|
||||
(255, 0, 175),
|
||||
(255, 0, 215),
|
||||
(255, 0, 255),
|
||||
(255, 95, 0),
|
||||
(255, 95, 95),
|
||||
(255, 95, 135),
|
||||
(255, 95, 175),
|
||||
(255, 95, 215),
|
||||
(255, 95, 255),
|
||||
(255, 135, 0),
|
||||
(255, 135, 95),
|
||||
(255, 135, 135),
|
||||
(255, 135, 175),
|
||||
(255, 135, 215),
|
||||
(255, 135, 255),
|
||||
(255, 175, 0),
|
||||
(255, 175, 95),
|
||||
(255, 175, 135),
|
||||
(255, 175, 175),
|
||||
(255, 175, 215),
|
||||
(255, 175, 255),
|
||||
(255, 215, 0),
|
||||
(255, 215, 95),
|
||||
(255, 215, 135),
|
||||
(255, 215, 175),
|
||||
(255, 215, 215),
|
||||
(255, 215, 255),
|
||||
(255, 255, 0),
|
||||
(255, 255, 95),
|
||||
(255, 255, 135),
|
||||
(255, 255, 175),
|
||||
(255, 255, 215),
|
||||
(255, 255, 255),
|
||||
(8, 8, 8),
|
||||
(18, 18, 18),
|
||||
(28, 28, 28),
|
||||
(38, 38, 38),
|
||||
(48, 48, 48),
|
||||
(58, 58, 58),
|
||||
(68, 68, 68),
|
||||
(78, 78, 78),
|
||||
(88, 88, 88),
|
||||
(98, 98, 98),
|
||||
(108, 108, 108),
|
||||
(118, 118, 118),
|
||||
(128, 128, 128),
|
||||
(138, 138, 138),
|
||||
(148, 148, 148),
|
||||
(158, 158, 158),
|
||||
(168, 168, 168),
|
||||
(178, 178, 178),
|
||||
(188, 188, 188),
|
||||
(198, 198, 198),
|
||||
(208, 208, 208),
|
||||
(218, 218, 218),
|
||||
(228, 228, 228),
|
||||
(238, 238, 238),
|
||||
]
|
||||
)
|
|
@ -0,0 +1,17 @@
|
|||
from typing import Optional
|
||||
|
||||
|
||||
def pick_bool(*values: Optional[bool]) -> bool:
|
||||
"""Pick the first non-none bool or return the last value.
|
||||
|
||||
Args:
|
||||
*values (bool): Any number of boolean or None values.
|
||||
|
||||
Returns:
|
||||
bool: First non-none boolean.
|
||||
"""
|
||||
assert values, "1 or more values required"
|
||||
for value in values:
|
||||
if value is not None:
|
||||
return value
|
||||
return bool(value)
|
|
@ -0,0 +1,160 @@
|
|||
import sys
|
||||
from fractions import Fraction
|
||||
from math import ceil
|
||||
from typing import cast, List, Optional, Sequence
|
||||
|
||||
if sys.version_info >= (3, 8):
|
||||
from typing import Protocol
|
||||
else:
|
||||
from pip._vendor.typing_extensions import Protocol # pragma: no cover
|
||||
|
||||
|
||||
class Edge(Protocol):
|
||||
"""Any object that defines an edge (such as Layout)."""
|
||||
|
||||
size: Optional[int] = None
|
||||
ratio: int = 1
|
||||
minimum_size: int = 1
|
||||
|
||||
|
||||
def ratio_resolve(total: int, edges: Sequence[Edge]) -> List[int]:
|
||||
"""Divide total space to satisfy size, ratio, and minimum_size, constraints.
|
||||
|
||||
The returned list of integers should add up to total in most cases, unless it is
|
||||
impossible to satisfy all the constraints. For instance, if there are two edges
|
||||
with a minimum size of 20 each and `total` is 30 then the returned list will be
|
||||
greater than total. In practice, this would mean that a Layout object would
|
||||
clip the rows that would overflow the screen height.
|
||||
|
||||
Args:
|
||||
total (int): Total number of characters.
|
||||
edges (List[Edge]): Edges within total space.
|
||||
|
||||
Returns:
|
||||
List[int]: Number of characters for each edge.
|
||||
"""
|
||||
# Size of edge or None for yet to be determined
|
||||
sizes = [(edge.size or None) for edge in edges]
|
||||
|
||||
_Fraction = Fraction
|
||||
|
||||
# While any edges haven't been calculated
|
||||
while None in sizes:
|
||||
# Get flexible edges and index to map these back on to sizes list
|
||||
flexible_edges = [
|
||||
(index, edge)
|
||||
for index, (size, edge) in enumerate(zip(sizes, edges))
|
||||
if size is None
|
||||
]
|
||||
# Remaining space in total
|
||||
remaining = total - sum(size or 0 for size in sizes)
|
||||
if remaining <= 0:
|
||||
# No room for flexible edges
|
||||
return [
|
||||
((edge.minimum_size or 1) if size is None else size)
|
||||
for size, edge in zip(sizes, edges)
|
||||
]
|
||||
# Calculate number of characters in a ratio portion
|
||||
portion = _Fraction(
|
||||
remaining, sum((edge.ratio or 1) for _, edge in flexible_edges)
|
||||
)
|
||||
|
||||
# If any edges will be less than their minimum, replace size with the minimum
|
||||
for index, edge in flexible_edges:
|
||||
if portion * edge.ratio <= edge.minimum_size:
|
||||
sizes[index] = edge.minimum_size
|
||||
# New fixed size will invalidate calculations, so we need to repeat the process
|
||||
break
|
||||
else:
|
||||
# Distribute flexible space and compensate for rounding error
|
||||
# Since edge sizes can only be integers we need to add the remainder
|
||||
# to the following line
|
||||
remainder = _Fraction(0)
|
||||
for index, edge in flexible_edges:
|
||||
size, remainder = divmod(portion * edge.ratio + remainder, 1)
|
||||
sizes[index] = size
|
||||
break
|
||||
# Sizes now contains integers only
|
||||
return cast(List[int], sizes)
|
||||
|
||||
|
||||
def ratio_reduce(
|
||||
total: int, ratios: List[int], maximums: List[int], values: List[int]
|
||||
) -> List[int]:
|
||||
"""Divide an integer total in to parts based on ratios.
|
||||
|
||||
Args:
|
||||
total (int): The total to divide.
|
||||
ratios (List[int]): A list of integer ratios.
|
||||
maximums (List[int]): List of maximums values for each slot.
|
||||
values (List[int]): List of values
|
||||
|
||||
Returns:
|
||||
List[int]: A list of integers guaranteed to sum to total.
|
||||
"""
|
||||
ratios = [ratio if _max else 0 for ratio, _max in zip(ratios, maximums)]
|
||||
total_ratio = sum(ratios)
|
||||
if not total_ratio:
|
||||
return values[:]
|
||||
total_remaining = total
|
||||
result: List[int] = []
|
||||
append = result.append
|
||||
for ratio, maximum, value in zip(ratios, maximums, values):
|
||||
if ratio and total_ratio > 0:
|
||||
distributed = min(maximum, round(ratio * total_remaining / total_ratio))
|
||||
append(value - distributed)
|
||||
total_remaining -= distributed
|
||||
total_ratio -= ratio
|
||||
else:
|
||||
append(value)
|
||||
return result
|
||||
|
||||
|
||||
def ratio_distribute(
|
||||
total: int, ratios: List[int], minimums: Optional[List[int]] = None
|
||||
) -> List[int]:
|
||||
"""Distribute an integer total in to parts based on ratios.
|
||||
|
||||
Args:
|
||||
total (int): The total to divide.
|
||||
ratios (List[int]): A list of integer ratios.
|
||||
minimums (List[int]): List of minimum values for each slot.
|
||||
|
||||
Returns:
|
||||
List[int]: A list of integers guaranteed to sum to total.
|
||||
"""
|
||||
if minimums:
|
||||
ratios = [ratio if _min else 0 for ratio, _min in zip(ratios, minimums)]
|
||||
total_ratio = sum(ratios)
|
||||
assert total_ratio > 0, "Sum of ratios must be > 0"
|
||||
|
||||
total_remaining = total
|
||||
distributed_total: List[int] = []
|
||||
append = distributed_total.append
|
||||
if minimums is None:
|
||||
_minimums = [0] * len(ratios)
|
||||
else:
|
||||
_minimums = minimums
|
||||
for ratio, minimum in zip(ratios, _minimums):
|
||||
if total_ratio > 0:
|
||||
distributed = max(minimum, ceil(ratio * total_remaining / total_ratio))
|
||||
else:
|
||||
distributed = total_remaining
|
||||
append(distributed)
|
||||
total_ratio -= ratio
|
||||
total_remaining -= distributed
|
||||
return distributed_total
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class E:
|
||||
|
||||
size: Optional[int] = None
|
||||
ratio: int = 1
|
||||
minimum_size: int = 1
|
||||
|
||||
resolved = ratio_resolve(110, [E(None, 1, 1), E(None, 1, 1), E(None, 1, 1)])
|
||||
print(sum(resolved))
|
|
@ -0,0 +1,848 @@
|
|||
"""
|
||||
Spinners are from:
|
||||
* cli-spinners:
|
||||
MIT License
|
||||
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN THE SOFTWARE.
|
||||
"""
|
||||
|
||||
SPINNERS = {
|
||||
"dots": {
|
||||
"interval": 80,
|
||||
"frames": ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
|
||||
},
|
||||
"dots2": {"interval": 80, "frames": ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"]},
|
||||
"dots3": {
|
||||
"interval": 80,
|
||||
"frames": ["⠋", "⠙", "⠚", "⠞", "⠖", "⠦", "⠴", "⠲", "⠳", "⠓"],
|
||||
},
|
||||
"dots4": {
|
||||
"interval": 80,
|
||||
"frames": [
|
||||
"⠄",
|
||||
"⠆",
|
||||
"⠇",
|
||||
"⠋",
|
||||
"⠙",
|
||||
"⠸",
|
||||
"⠰",
|
||||
"⠠",
|
||||
"⠰",
|
||||
"⠸",
|
||||
"⠙",
|
||||
"⠋",
|
||||
"⠇",
|
||||
"⠆",
|
||||
],
|
||||
},
|
||||
"dots5": {
|
||||
"interval": 80,
|
||||
"frames": [
|
||||
"⠋",
|
||||
"⠙",
|
||||
"⠚",
|
||||
"⠒",
|
||||
"⠂",
|
||||
"⠂",
|
||||
"⠒",
|
||||
"⠲",
|
||||
"⠴",
|
||||
"⠦",
|
||||
"⠖",
|
||||
"⠒",
|
||||
"⠐",
|
||||
"⠐",
|
||||
"⠒",
|
||||
"⠓",
|
||||
"⠋",
|
||||
],
|
||||
},
|
||||
"dots6": {
|
||||
"interval": 80,
|
||||
"frames": [
|
||||
"⠁",
|
||||
"⠉",
|
||||
"⠙",
|
||||
"⠚",
|
||||
"⠒",
|
||||
"⠂",
|
||||
"⠂",
|
||||
"⠒",
|
||||
"⠲",
|
||||
"⠴",
|
||||
"⠤",
|
||||
"⠄",
|
||||
"⠄",
|
||||
"⠤",
|
||||
"⠴",
|
||||
"⠲",
|
||||
"⠒",
|
||||
"⠂",
|
||||
"⠂",
|
||||
"⠒",
|
||||
"⠚",
|
||||
"⠙",
|
||||
"⠉",
|
||||
"⠁",
|
||||
],
|
||||
},
|
||||
"dots7": {
|
||||
"interval": 80,
|
||||
"frames": [
|
||||
"⠈",
|
||||
"⠉",
|
||||
"⠋",
|
||||
"⠓",
|
||||
"⠒",
|
||||
"⠐",
|
||||
"⠐",
|
||||
"⠒",
|
||||
"⠖",
|
||||
"⠦",
|
||||
"⠤",
|
||||
"⠠",
|
||||
"⠠",
|
||||
"⠤",
|
||||
"⠦",
|
||||
"⠖",
|
||||
"⠒",
|
||||
"⠐",
|
||||
"⠐",
|
||||
"⠒",
|
||||
"⠓",
|
||||
"⠋",
|
||||
"⠉",
|
||||
"⠈",
|
||||
],
|
||||
},
|
||||
"dots8": {
|
||||
"interval": 80,
|
||||
"frames": [
|
||||
"⠁",
|
||||
"⠁",
|
||||
"⠉",
|
||||
"⠙",
|
||||
"⠚",
|
||||
"⠒",
|
||||
"⠂",
|
||||
"⠂",
|
||||
"⠒",
|
||||
"⠲",
|
||||
"⠴",
|
||||
"⠤",
|
||||
"⠄",
|
||||
"⠄",
|
||||
"⠤",
|
||||
"⠠",
|
||||
"⠠",
|
||||
"⠤",
|
||||
"⠦",
|
||||
"⠖",
|
||||
"⠒",
|
||||
"⠐",
|
||||
"⠐",
|
||||
"⠒",
|
||||
"⠓",
|
||||
"⠋",
|
||||
"⠉",
|
||||
"⠈",
|
||||
"⠈",
|
||||
],
|
||||
},
|
||||
"dots9": {"interval": 80, "frames": ["⢹", "⢺", "⢼", "⣸", "⣇", "⡧", "⡗", "⡏"]},
|
||||
"dots10": {"interval": 80, "frames": ["⢄", "⢂", "⢁", "⡁", "⡈", "⡐", "⡠"]},
|
||||
"dots11": {"interval": 100, "frames": ["⠁", "⠂", "⠄", "⡀", "⢀", "⠠", "⠐", "⠈"]},
|
||||
"dots12": {
|
||||
"interval": 80,
|
||||
"frames": [
|
||||
"⢀⠀",
|
||||
"⡀⠀",
|
||||
"⠄⠀",
|
||||
"⢂⠀",
|
||||
"⡂⠀",
|
||||
"⠅⠀",
|
||||
"⢃⠀",
|
||||
"⡃⠀",
|
||||
"⠍⠀",
|
||||
"⢋⠀",
|
||||
"⡋⠀",
|
||||
"⠍⠁",
|
||||
"⢋⠁",
|
||||
"⡋⠁",
|
||||
"⠍⠉",
|
||||
"⠋⠉",
|
||||
"⠋⠉",
|
||||
"⠉⠙",
|
||||
"⠉⠙",
|
||||
"⠉⠩",
|
||||
"⠈⢙",
|
||||
"⠈⡙",
|
||||
"⢈⠩",
|
||||
"⡀⢙",
|
||||
"⠄⡙",
|
||||
"⢂⠩",
|
||||
"⡂⢘",
|
||||
"⠅⡘",
|
||||
"⢃⠨",
|
||||
"⡃⢐",
|
||||
"⠍⡐",
|
||||
"⢋⠠",
|
||||
"⡋⢀",
|
||||
"⠍⡁",
|
||||
"⢋⠁",
|
||||
"⡋⠁",
|
||||
"⠍⠉",
|
||||
"⠋⠉",
|
||||
"⠋⠉",
|
||||
"⠉⠙",
|
||||
"⠉⠙",
|
||||
"⠉⠩",
|
||||
"⠈⢙",
|
||||
"⠈⡙",
|
||||
"⠈⠩",
|
||||
"⠀⢙",
|
||||
"⠀⡙",
|
||||
"⠀⠩",
|
||||
"⠀⢘",
|
||||
"⠀⡘",
|
||||
"⠀⠨",
|
||||
"⠀⢐",
|
||||
"⠀⡐",
|
||||
"⠀⠠",
|
||||
"⠀⢀",
|
||||
"⠀⡀",
|
||||
],
|
||||
},
|
||||
"dots8Bit": {
|
||||
"interval": 80,
|
||||
"frames": [
|
||||
"⠀",
|
||||
"⠁",
|
||||
"⠂",
|
||||
"⠃",
|
||||
"⠄",
|
||||
"⠅",
|
||||
"⠆",
|
||||
"⠇",
|
||||
"⡀",
|
||||
"⡁",
|
||||
"⡂",
|
||||
"⡃",
|
||||
"⡄",
|
||||
"⡅",
|
||||
"⡆",
|
||||
"⡇",
|
||||
"⠈",
|
||||
"⠉",
|
||||
"⠊",
|
||||
"⠋",
|
||||
"⠌",
|
||||
"⠍",
|
||||
"⠎",
|
||||
"⠏",
|
||||
"⡈",
|
||||
"⡉",
|
||||
"⡊",
|
||||
"⡋",
|
||||
"⡌",
|
||||
"⡍",
|
||||
"⡎",
|
||||
"⡏",
|
||||
"⠐",
|
||||
"⠑",
|
||||
"⠒",
|
||||
"⠓",
|
||||
"⠔",
|
||||
"⠕",
|
||||
"⠖",
|
||||
"⠗",
|
||||
"⡐",
|
||||
"⡑",
|
||||
"⡒",
|
||||
"⡓",
|
||||
"⡔",
|
||||
"⡕",
|
||||
"⡖",
|
||||
"⡗",
|
||||
"⠘",
|
||||
"⠙",
|
||||
"⠚",
|
||||
"⠛",
|
||||
"⠜",
|
||||
"⠝",
|
||||
"⠞",
|
||||
"⠟",
|
||||
"⡘",
|
||||
"⡙",
|
||||
"⡚",
|
||||
"⡛",
|
||||
"⡜",
|
||||
"⡝",
|
||||
"⡞",
|
||||
"⡟",
|
||||
"⠠",
|
||||
"⠡",
|
||||
"⠢",
|
||||
"⠣",
|
||||
"⠤",
|
||||
"⠥",
|
||||
"⠦",
|
||||
"⠧",
|
||||
"⡠",
|
||||
"⡡",
|
||||
"⡢",
|
||||
"⡣",
|
||||
"⡤",
|
||||
"⡥",
|
||||
"⡦",
|
||||
"⡧",
|
||||
"⠨",
|
||||
"⠩",
|
||||
"⠪",
|
||||
"⠫",
|
||||
"⠬",
|
||||
"⠭",
|
||||
"⠮",
|
||||
"⠯",
|
||||
"⡨",
|
||||
"⡩",
|
||||
"⡪",
|
||||
"⡫",
|
||||
"⡬",
|
||||
"⡭",
|
||||
"⡮",
|
||||
"⡯",
|
||||
"⠰",
|
||||
"⠱",
|
||||
"⠲",
|
||||
"⠳",
|
||||
"⠴",
|
||||
"⠵",
|
||||
"⠶",
|
||||
"⠷",
|
||||
"⡰",
|
||||
"⡱",
|
||||
"⡲",
|
||||
"⡳",
|
||||
"⡴",
|
||||
"⡵",
|
||||
"⡶",
|
||||
"⡷",
|
||||
"⠸",
|
||||
"⠹",
|
||||
"⠺",
|
||||
"⠻",
|
||||
"⠼",
|
||||
"⠽",
|
||||
"⠾",
|
||||
"⠿",
|
||||
"⡸",
|
||||
"⡹",
|
||||
"⡺",
|
||||
"⡻",
|
||||
"⡼",
|
||||
"⡽",
|
||||
"⡾",
|
||||
"⡿",
|
||||
"⢀",
|
||||
"⢁",
|
||||
"⢂",
|
||||
"⢃",
|
||||
"⢄",
|
||||
"⢅",
|
||||
"⢆",
|
||||
"⢇",
|
||||
"⣀",
|
||||
"⣁",
|
||||
"⣂",
|
||||
"⣃",
|
||||
"⣄",
|
||||
"⣅",
|
||||
"⣆",
|
||||
"⣇",
|
||||
"⢈",
|
||||
"⢉",
|
||||
"⢊",
|
||||
"⢋",
|
||||
"⢌",
|
||||
"⢍",
|
||||
"⢎",
|
||||
"⢏",
|
||||
"⣈",
|
||||
"⣉",
|
||||
"⣊",
|
||||
"⣋",
|
||||
"⣌",
|
||||
"⣍",
|
||||
"⣎",
|
||||
"⣏",
|
||||
"⢐",
|
||||
"⢑",
|
||||
"⢒",
|
||||
"⢓",
|
||||
"⢔",
|
||||
"⢕",
|
||||
"⢖",
|
||||
"⢗",
|
||||
"⣐",
|
||||
"⣑",
|
||||
"⣒",
|
||||
"⣓",
|
||||
"⣔",
|
||||
"⣕",
|
||||
"⣖",
|
||||
"⣗",
|
||||
"⢘",
|
||||
"⢙",
|
||||
"⢚",
|
||||
"⢛",
|
||||
"⢜",
|
||||
"⢝",
|
||||
"⢞",
|
||||
"⢟",
|
||||
"⣘",
|
||||
"⣙",
|
||||
"⣚",
|
||||
"⣛",
|
||||
"⣜",
|
||||
"⣝",
|
||||
"⣞",
|
||||
"⣟",
|
||||
"⢠",
|
||||
"⢡",
|
||||
"⢢",
|
||||
"⢣",
|
||||
"⢤",
|
||||
"⢥",
|
||||
"⢦",
|
||||
"⢧",
|
||||
"⣠",
|
||||
"⣡",
|
||||
"⣢",
|
||||
"⣣",
|
||||
"⣤",
|
||||
"⣥",
|
||||
"⣦",
|
||||
"⣧",
|
||||
"⢨",
|
||||
"⢩",
|
||||
"⢪",
|
||||
"⢫",
|
||||
"⢬",
|
||||
"⢭",
|
||||
"⢮",
|
||||
"⢯",
|
||||
"⣨",
|
||||
"⣩",
|
||||
"⣪",
|
||||
"⣫",
|
||||
"⣬",
|
||||
"⣭",
|
||||
"⣮",
|
||||
"⣯",
|
||||
"⢰",
|
||||
"⢱",
|
||||
"⢲",
|
||||
"⢳",
|
||||
"⢴",
|
||||
"⢵",
|
||||
"⢶",
|
||||
"⢷",
|
||||
"⣰",
|
||||
"⣱",
|
||||
"⣲",
|
||||
"⣳",
|
||||
"⣴",
|
||||
"⣵",
|
||||
"⣶",
|
||||
"⣷",
|
||||
"⢸",
|
||||
"⢹",
|
||||
"⢺",
|
||||
"⢻",
|
||||
"⢼",
|
||||
"⢽",
|
||||
"⢾",
|
||||
"⢿",
|
||||
"⣸",
|
||||
"⣹",
|
||||
"⣺",
|
||||
"⣻",
|
||||
"⣼",
|
||||
"⣽",
|
||||
"⣾",
|
||||
"⣿",
|
||||
],
|
||||
},
|
||||
"line": {"interval": 130, "frames": ["-", "\\", "|", "/"]},
|
||||
"line2": {"interval": 100, "frames": ["⠂", "-", "–", "—", "–", "-"]},
|
||||
"pipe": {"interval": 100, "frames": ["┤", "┘", "┴", "└", "├", "┌", "┬", "┐"]},
|
||||
"simpleDots": {"interval": 400, "frames": [". ", ".. ", "...", " "]},
|
||||
"simpleDotsScrolling": {
|
||||
"interval": 200,
|
||||
"frames": [". ", ".. ", "...", " ..", " .", " "],
|
||||
},
|
||||
"star": {"interval": 70, "frames": ["✶", "✸", "✹", "✺", "✹", "✷"]},
|
||||
"star2": {"interval": 80, "frames": ["+", "x", "*"]},
|
||||
"flip": {
|
||||
"interval": 70,
|
||||
"frames": ["_", "_", "_", "-", "`", "`", "'", "´", "-", "_", "_", "_"],
|
||||
},
|
||||
"hamburger": {"interval": 100, "frames": ["☱", "☲", "☴"]},
|
||||
"growVertical": {
|
||||
"interval": 120,
|
||||
"frames": ["▁", "▃", "▄", "▅", "▆", "▇", "▆", "▅", "▄", "▃"],
|
||||
},
|
||||
"growHorizontal": {
|
||||
"interval": 120,
|
||||
"frames": ["▏", "▎", "▍", "▌", "▋", "▊", "▉", "▊", "▋", "▌", "▍", "▎"],
|
||||
},
|
||||
"balloon": {"interval": 140, "frames": [" ", ".", "o", "O", "@", "*", " "]},
|
||||
"balloon2": {"interval": 120, "frames": [".", "o", "O", "°", "O", "o", "."]},
|
||||
"noise": {"interval": 100, "frames": ["▓", "▒", "░"]},
|
||||
"bounce": {"interval": 120, "frames": ["⠁", "⠂", "⠄", "⠂"]},
|
||||
"boxBounce": {"interval": 120, "frames": ["▖", "▘", "▝", "▗"]},
|
||||
"boxBounce2": {"interval": 100, "frames": ["▌", "▀", "▐", "▄"]},
|
||||
"triangle": {"interval": 50, "frames": ["◢", "◣", "◤", "◥"]},
|
||||
"arc": {"interval": 100, "frames": ["◜", "◠", "◝", "◞", "◡", "◟"]},
|
||||
"circle": {"interval": 120, "frames": ["◡", "⊙", "◠"]},
|
||||
"squareCorners": {"interval": 180, "frames": ["◰", "◳", "◲", "◱"]},
|
||||
"circleQuarters": {"interval": 120, "frames": ["◴", "◷", "◶", "◵"]},
|
||||
"circleHalves": {"interval": 50, "frames": ["◐", "◓", "◑", "◒"]},
|
||||
"squish": {"interval": 100, "frames": ["╫", "╪"]},
|
||||
"toggle": {"interval": 250, "frames": ["⊶", "⊷"]},
|
||||
"toggle2": {"interval": 80, "frames": ["▫", "▪"]},
|
||||
"toggle3": {"interval": 120, "frames": ["□", "■"]},
|
||||
"toggle4": {"interval": 100, "frames": ["■", "□", "▪", "▫"]},
|
||||
"toggle5": {"interval": 100, "frames": ["▮", "▯"]},
|
||||
"toggle6": {"interval": 300, "frames": ["ဝ", "၀"]},
|
||||
"toggle7": {"interval": 80, "frames": ["⦾", "⦿"]},
|
||||
"toggle8": {"interval": 100, "frames": ["◍", "◌"]},
|
||||
"toggle9": {"interval": 100, "frames": ["◉", "◎"]},
|
||||
"toggle10": {"interval": 100, "frames": ["㊂", "㊀", "㊁"]},
|
||||
"toggle11": {"interval": 50, "frames": ["⧇", "⧆"]},
|
||||
"toggle12": {"interval": 120, "frames": ["☗", "☖"]},
|
||||
"toggle13": {"interval": 80, "frames": ["=", "*", "-"]},
|
||||
"arrow": {"interval": 100, "frames": ["←", "↖", "↑", "↗", "→", "↘", "↓", "↙"]},
|
||||
"arrow2": {
|
||||
"interval": 80,
|
||||
"frames": ["⬆️ ", "↗️ ", "➡️ ", "↘️ ", "⬇️ ", "↙️ ", "⬅️ ", "↖️ "],
|
||||
},
|
||||
"arrow3": {
|
||||
"interval": 120,
|
||||
"frames": ["▹▹▹▹▹", "▸▹▹▹▹", "▹▸▹▹▹", "▹▹▸▹▹", "▹▹▹▸▹", "▹▹▹▹▸"],
|
||||
},
|
||||
"bouncingBar": {
|
||||
"interval": 80,
|
||||
"frames": [
|
||||
"[ ]",
|
||||
"[= ]",
|
||||
"[== ]",
|
||||
"[=== ]",
|
||||
"[ ===]",
|
||||
"[ ==]",
|
||||
"[ =]",
|
||||
"[ ]",
|
||||
"[ =]",
|
||||
"[ ==]",
|
||||
"[ ===]",
|
||||
"[====]",
|
||||
"[=== ]",
|
||||
"[== ]",
|
||||
"[= ]",
|
||||
],
|
||||
},
|
||||
"bouncingBall": {
|
||||
"interval": 80,
|
||||
"frames": [
|
||||
"( ● )",
|
||||
"( ● )",
|
||||
"( ● )",
|
||||
"( ● )",
|
||||
"( ●)",
|
||||
"( ● )",
|
||||
"( ● )",
|
||||
"( ● )",
|
||||
"( ● )",
|
||||
"(● )",
|
||||
],
|
||||
},
|
||||
"smiley": {"interval": 200, "frames": ["😄 ", "😝 "]},
|
||||
"monkey": {"interval": 300, "frames": ["🙈 ", "🙈 ", "🙉 ", "🙊 "]},
|
||||
"hearts": {"interval": 100, "frames": ["💛 ", "💙 ", "💜 ", "💚 ", "❤️ "]},
|
||||
"clock": {
|
||||
"interval": 100,
|
||||
"frames": [
|
||||
"🕛 ",
|
||||
"🕐 ",
|
||||
"🕑 ",
|
||||
"🕒 ",
|
||||
"🕓 ",
|
||||
"🕔 ",
|
||||
"🕕 ",
|
||||
"🕖 ",
|
||||
"🕗 ",
|
||||
"🕘 ",
|
||||
"🕙 ",
|
||||
"🕚 ",
|
||||
],
|
||||
},
|
||||
"earth": {"interval": 180, "frames": ["🌍 ", "🌎 ", "🌏 "]},
|
||||
"material": {
|
||||
"interval": 17,
|
||||
"frames": [
|
||||
"█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
|
||||
"██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
|
||||
"███▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
|
||||
"████▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
|
||||
"██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
|
||||
"██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
|
||||
"███████▁▁▁▁▁▁▁▁▁▁▁▁▁",
|
||||
"████████▁▁▁▁▁▁▁▁▁▁▁▁",
|
||||
"█████████▁▁▁▁▁▁▁▁▁▁▁",
|
||||
"█████████▁▁▁▁▁▁▁▁▁▁▁",
|
||||
"██████████▁▁▁▁▁▁▁▁▁▁",
|
||||
"███████████▁▁▁▁▁▁▁▁▁",
|
||||
"█████████████▁▁▁▁▁▁▁",
|
||||
"██████████████▁▁▁▁▁▁",
|
||||
"██████████████▁▁▁▁▁▁",
|
||||
"▁██████████████▁▁▁▁▁",
|
||||
"▁██████████████▁▁▁▁▁",
|
||||
"▁██████████████▁▁▁▁▁",
|
||||
"▁▁██████████████▁▁▁▁",
|
||||
"▁▁▁██████████████▁▁▁",
|
||||
"▁▁▁▁█████████████▁▁▁",
|
||||
"▁▁▁▁██████████████▁▁",
|
||||
"▁▁▁▁██████████████▁▁",
|
||||
"▁▁▁▁▁██████████████▁",
|
||||
"▁▁▁▁▁██████████████▁",
|
||||
"▁▁▁▁▁██████████████▁",
|
||||
"▁▁▁▁▁▁██████████████",
|
||||
"▁▁▁▁▁▁██████████████",
|
||||
"▁▁▁▁▁▁▁█████████████",
|
||||
"▁▁▁▁▁▁▁█████████████",
|
||||
"▁▁▁▁▁▁▁▁████████████",
|
||||
"▁▁▁▁▁▁▁▁████████████",
|
||||
"▁▁▁▁▁▁▁▁▁███████████",
|
||||
"▁▁▁▁▁▁▁▁▁███████████",
|
||||
"▁▁▁▁▁▁▁▁▁▁██████████",
|
||||
"▁▁▁▁▁▁▁▁▁▁██████████",
|
||||
"▁▁▁▁▁▁▁▁▁▁▁▁████████",
|
||||
"▁▁▁▁▁▁▁▁▁▁▁▁▁███████",
|
||||
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁██████",
|
||||
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████",
|
||||
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████",
|
||||
"█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████",
|
||||
"██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███",
|
||||
"██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███",
|
||||
"███▁▁▁▁▁▁▁▁▁▁▁▁▁▁███",
|
||||
"████▁▁▁▁▁▁▁▁▁▁▁▁▁▁██",
|
||||
"█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█",
|
||||
"█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█",
|
||||
"██████▁▁▁▁▁▁▁▁▁▁▁▁▁█",
|
||||
"████████▁▁▁▁▁▁▁▁▁▁▁▁",
|
||||
"█████████▁▁▁▁▁▁▁▁▁▁▁",
|
||||
"█████████▁▁▁▁▁▁▁▁▁▁▁",
|
||||
"█████████▁▁▁▁▁▁▁▁▁▁▁",
|
||||
"█████████▁▁▁▁▁▁▁▁▁▁▁",
|
||||
"███████████▁▁▁▁▁▁▁▁▁",
|
||||
"████████████▁▁▁▁▁▁▁▁",
|
||||
"████████████▁▁▁▁▁▁▁▁",
|
||||
"██████████████▁▁▁▁▁▁",
|
||||
"██████████████▁▁▁▁▁▁",
|
||||
"▁██████████████▁▁▁▁▁",
|
||||
"▁██████████████▁▁▁▁▁",
|
||||
"▁▁▁█████████████▁▁▁▁",
|
||||
"▁▁▁▁▁████████████▁▁▁",
|
||||
"▁▁▁▁▁████████████▁▁▁",
|
||||
"▁▁▁▁▁▁███████████▁▁▁",
|
||||
"▁▁▁▁▁▁▁▁█████████▁▁▁",
|
||||
"▁▁▁▁▁▁▁▁█████████▁▁▁",
|
||||
"▁▁▁▁▁▁▁▁▁█████████▁▁",
|
||||
"▁▁▁▁▁▁▁▁▁█████████▁▁",
|
||||
"▁▁▁▁▁▁▁▁▁▁█████████▁",
|
||||
"▁▁▁▁▁▁▁▁▁▁▁████████▁",
|
||||
"▁▁▁▁▁▁▁▁▁▁▁████████▁",
|
||||
"▁▁▁▁▁▁▁▁▁▁▁▁███████▁",
|
||||
"▁▁▁▁▁▁▁▁▁▁▁▁███████▁",
|
||||
"▁▁▁▁▁▁▁▁▁▁▁▁▁███████",
|
||||
"▁▁▁▁▁▁▁▁▁▁▁▁▁███████",
|
||||
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████",
|
||||
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████",
|
||||
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████",
|
||||
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████",
|
||||
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███",
|
||||
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███",
|
||||
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██",
|
||||
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██",
|
||||
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██",
|
||||
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█",
|
||||
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█",
|
||||
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█",
|
||||
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
|
||||
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
|
||||
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
|
||||
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
|
||||
],
|
||||
},
|
||||
"moon": {
|
||||
"interval": 80,
|
||||
"frames": ["🌑 ", "🌒 ", "🌓 ", "🌔 ", "🌕 ", "🌖 ", "🌗 ", "🌘 "],
|
||||
},
|
||||
"runner": {"interval": 140, "frames": ["🚶 ", "🏃 "]},
|
||||
"pong": {
|
||||
"interval": 80,
|
||||
"frames": [
|
||||
"▐⠂ ▌",
|
||||
"▐⠈ ▌",
|
||||
"▐ ⠂ ▌",
|
||||
"▐ ⠠ ▌",
|
||||
"▐ ⡀ ▌",
|
||||
"▐ ⠠ ▌",
|
||||
"▐ ⠂ ▌",
|
||||
"▐ ⠈ ▌",
|
||||
"▐ ⠂ ▌",
|
||||
"▐ ⠠ ▌",
|
||||
"▐ ⡀ ▌",
|
||||
"▐ ⠠ ▌",
|
||||
"▐ ⠂ ▌",
|
||||
"▐ ⠈ ▌",
|
||||
"▐ ⠂▌",
|
||||
"▐ ⠠▌",
|
||||
"▐ ⡀▌",
|
||||
"▐ ⠠ ▌",
|
||||
"▐ ⠂ ▌",
|
||||
"▐ ⠈ ▌",
|
||||
"▐ ⠂ ▌",
|
||||
"▐ ⠠ ▌",
|
||||
"▐ ⡀ ▌",
|
||||
"▐ ⠠ ▌",
|
||||
"▐ ⠂ ▌",
|
||||
"▐ ⠈ ▌",
|
||||
"▐ ⠂ ▌",
|
||||
"▐ ⠠ ▌",
|
||||
"▐ ⡀ ▌",
|
||||
"▐⠠ ▌",
|
||||
],
|
||||
},
|
||||
"shark": {
|
||||
"interval": 120,
|
||||
"frames": [
|
||||
"▐|\\____________▌",
|
||||
"▐_|\\___________▌",
|
||||
"▐__|\\__________▌",
|
||||
"▐___|\\_________▌",
|
||||
"▐____|\\________▌",
|
||||
"▐_____|\\_______▌",
|
||||
"▐______|\\______▌",
|
||||
"▐_______|\\_____▌",
|
||||
"▐________|\\____▌",
|
||||
"▐_________|\\___▌",
|
||||
"▐__________|\\__▌",
|
||||
"▐___________|\\_▌",
|
||||
"▐____________|\\▌",
|
||||
"▐____________/|▌",
|
||||
"▐___________/|_▌",
|
||||
"▐__________/|__▌",
|
||||
"▐_________/|___▌",
|
||||
"▐________/|____▌",
|
||||
"▐_______/|_____▌",
|
||||
"▐______/|______▌",
|
||||
"▐_____/|_______▌",
|
||||
"▐____/|________▌",
|
||||
"▐___/|_________▌",
|
||||
"▐__/|__________▌",
|
||||
"▐_/|___________▌",
|
||||
"▐/|____________▌",
|
||||
],
|
||||
},
|
||||
"dqpb": {"interval": 100, "frames": ["d", "q", "p", "b"]},
|
||||
"weather": {
|
||||
"interval": 100,
|
||||
"frames": [
|
||||
"☀️ ",
|
||||
"☀️ ",
|
||||
"☀️ ",
|
||||
"🌤 ",
|
||||
"⛅️ ",
|
||||
"🌥 ",
|
||||
"☁️ ",
|
||||
"🌧 ",
|
||||
"🌨 ",
|
||||
"🌧 ",
|
||||
"🌨 ",
|
||||
"🌧 ",
|
||||
"🌨 ",
|
||||
"⛈ ",
|
||||
"🌨 ",
|
||||
"🌧 ",
|
||||
"🌨 ",
|
||||
"☁️ ",
|
||||
"🌥 ",
|
||||
"⛅️ ",
|
||||
"🌤 ",
|
||||
"☀️ ",
|
||||
"☀️ ",
|
||||
],
|
||||
},
|
||||
"christmas": {"interval": 400, "frames": ["🌲", "🎄"]},
|
||||
"grenade": {
|
||||
"interval": 80,
|
||||
"frames": [
|
||||
"، ",
|
||||
"′ ",
|
||||
" ´ ",
|
||||
" ‾ ",
|
||||
" ⸌",
|
||||
" ⸊",
|
||||
" |",
|
||||
" ⁎",
|
||||
" ⁕",
|
||||
" ෴ ",
|
||||
" ⁓",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
],
|
||||
},
|
||||
"point": {"interval": 125, "frames": ["∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"]},
|
||||
"layer": {"interval": 150, "frames": ["-", "=", "≡"]},
|
||||
"betaWave": {
|
||||
"interval": 80,
|
||||
"frames": [
|
||||
"ρββββββ",
|
||||
"βρβββββ",
|
||||
"ββρββββ",
|
||||
"βββρβββ",
|
||||
"ββββρββ",
|
||||
"βββββρβ",
|
||||
"ββββββρ",
|
||||
],
|
||||
},
|
||||
"aesthetic": {
|
||||
"interval": 80,
|
||||
"frames": [
|
||||
"▰▱▱▱▱▱▱",
|
||||
"▰▰▱▱▱▱▱",
|
||||
"▰▰▰▱▱▱▱",
|
||||
"▰▰▰▰▱▱▱",
|
||||
"▰▰▰▰▰▱▱",
|
||||
"▰▰▰▰▰▰▱",
|
||||
"▰▰▰▰▰▰▰",
|
||||
"▰▱▱▱▱▱▱",
|
||||
],
|
||||
},
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
from typing import List, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class Stack(List[T]):
|
||||
"""A small shim over builtin list."""
|
||||
|
||||
@property
|
||||
def top(self) -> T:
|
||||
"""Get top of stack."""
|
||||
return self[-1]
|
||||
|
||||
def push(self, item: T) -> None:
|
||||
"""Push an item on to the stack (append in stack nomenclature)."""
|
||||
self.append(item)
|
|
@ -0,0 +1,19 @@
|
|||
"""
|
||||
Timer context manager, only used in debug.
|
||||
|
||||
"""
|
||||
|
||||
from time import time
|
||||
|
||||
import contextlib
|
||||
from typing import Generator
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def timer(subject: str = "time") -> Generator[None, None, None]:
|
||||
"""print the elapsed time. (only used in debugging)"""
|
||||
start = time()
|
||||
yield
|
||||
elapsed = time() - start
|
||||
elapsed_ms = elapsed * 1000
|
||||
print(f"{subject} elapsed {elapsed_ms:.1f}ms")
|
|
@ -0,0 +1,75 @@
|
|||
import sys
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class WindowsConsoleFeatures:
|
||||
"""Windows features available."""
|
||||
|
||||
vt: bool = False
|
||||
"""The console supports VT codes."""
|
||||
truecolor: bool = False
|
||||
"""The console supports truecolor."""
|
||||
|
||||
|
||||
try:
|
||||
import ctypes
|
||||
from ctypes import wintypes
|
||||
from ctypes import LibraryLoader
|
||||
|
||||
if sys.platform == "win32":
|
||||
windll = LibraryLoader(ctypes.WinDLL)
|
||||
else:
|
||||
windll = None
|
||||
raise ImportError("Not windows")
|
||||
except (AttributeError, ImportError, ValueError):
|
||||
|
||||
# Fallback if we can't load the Windows DLL
|
||||
def get_windows_console_features() -> WindowsConsoleFeatures:
|
||||
features = WindowsConsoleFeatures()
|
||||
return features
|
||||
|
||||
|
||||
else:
|
||||
|
||||
STDOUT = -11
|
||||
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
|
||||
_GetConsoleMode = windll.kernel32.GetConsoleMode
|
||||
_GetConsoleMode.argtypes = [wintypes.HANDLE, wintypes.LPDWORD]
|
||||
_GetConsoleMode.restype = wintypes.BOOL
|
||||
|
||||
_GetStdHandle = windll.kernel32.GetStdHandle
|
||||
_GetStdHandle.argtypes = [
|
||||
wintypes.DWORD,
|
||||
]
|
||||
_GetStdHandle.restype = wintypes.HANDLE
|
||||
|
||||
def get_windows_console_features() -> WindowsConsoleFeatures:
|
||||
"""Get windows console features.
|
||||
|
||||
Returns:
|
||||
WindowsConsoleFeatures: An instance of WindowsConsoleFeatures.
|
||||
"""
|
||||
handle = _GetStdHandle(STDOUT)
|
||||
console_mode = wintypes.DWORD()
|
||||
result = _GetConsoleMode(handle, console_mode)
|
||||
vt = bool(result and console_mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING)
|
||||
truecolor = False
|
||||
if vt:
|
||||
win_version = sys.getwindowsversion()
|
||||
truecolor = win_version.major > 10 or (
|
||||
win_version.major == 10 and win_version.build >= 15063
|
||||
)
|
||||
features = WindowsConsoleFeatures(vt=vt, truecolor=truecolor)
|
||||
return features
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import platform
|
||||
|
||||
features = get_windows_console_features()
|
||||
from pip._vendor.rich import print
|
||||
|
||||
print(f'platform="{platform.system()}"')
|
||||
print(repr(features))
|
|
@ -0,0 +1,55 @@
|
|||
import re
|
||||
from typing import Iterable, List, Tuple
|
||||
|
||||
from .cells import cell_len, chop_cells
|
||||
from ._loop import loop_last
|
||||
|
||||
re_word = re.compile(r"\s*\S+\s*")
|
||||
|
||||
|
||||
def words(text: str) -> Iterable[Tuple[int, int, str]]:
|
||||
position = 0
|
||||
word_match = re_word.match(text, position)
|
||||
while word_match is not None:
|
||||
start, end = word_match.span()
|
||||
word = word_match.group(0)
|
||||
yield start, end, word
|
||||
word_match = re_word.match(text, end)
|
||||
|
||||
|
||||
def divide_line(text: str, width: int, fold: bool = True) -> List[int]:
|
||||
divides: List[int] = []
|
||||
append = divides.append
|
||||
line_position = 0
|
||||
_cell_len = cell_len
|
||||
for start, _end, word in words(text):
|
||||
word_length = _cell_len(word.rstrip())
|
||||
if line_position + word_length > width:
|
||||
if word_length > width:
|
||||
if fold:
|
||||
for last, line in loop_last(
|
||||
chop_cells(word, width, position=line_position)
|
||||
):
|
||||
if last:
|
||||
line_position = _cell_len(line)
|
||||
else:
|
||||
start += len(line)
|
||||
append(start)
|
||||
else:
|
||||
if start:
|
||||
append(start)
|
||||
line_position = _cell_len(word)
|
||||
elif line_position and start:
|
||||
append(start)
|
||||
line_position = _cell_len(word)
|
||||
else:
|
||||
line_position += _cell_len(word)
|
||||
return divides
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
from .console import Console
|
||||
|
||||
console = Console(width=10)
|
||||
console.print("12345 abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ 12345")
|
||||
print(chop_cells("abcdefghijklmnopqrstuvwxyz", 10, position=2))
|
|
@ -0,0 +1,33 @@
|
|||
from abc import ABC
|
||||
|
||||
|
||||
class RichRenderable(ABC):
|
||||
"""An abstract base class for Rich renderables.
|
||||
|
||||
Note that there is no need to extend this class, the intended use is to check if an
|
||||
object supports the Rich renderable protocol. For example::
|
||||
|
||||
if isinstance(my_object, RichRenderable):
|
||||
console.print(my_object)
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def __subclasshook__(cls, other: type) -> bool:
|
||||
"""Check if this class supports the rich render protocol."""
|
||||
return hasattr(other, "__rich_console__") or hasattr(other, "__rich__")
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
from pip._vendor.rich.text import Text
|
||||
|
||||
t = Text()
|
||||
print(isinstance(Text, RichRenderable))
|
||||
print(isinstance(t, RichRenderable))
|
||||
|
||||
class Foo:
|
||||
pass
|
||||
|
||||
f = Foo()
|
||||
print(isinstance(f, RichRenderable))
|
||||
print(isinstance("", RichRenderable))
|
|
@ -0,0 +1,312 @@
|
|||
import sys
|
||||
from itertools import chain
|
||||
from typing import TYPE_CHECKING, Iterable, Optional
|
||||
|
||||
if sys.version_info >= (3, 8):
|
||||
from typing import Literal
|
||||
else:
|
||||
from pip._vendor.typing_extensions import Literal # pragma: no cover
|
||||
|
||||
from .constrain import Constrain
|
||||
from .jupyter import JupyterMixin
|
||||
from .measure import Measurement
|
||||
from .segment import Segment
|
||||
from .style import StyleType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .console import Console, ConsoleOptions, RenderableType, RenderResult
|
||||
|
||||
AlignMethod = Literal["left", "center", "right"]
|
||||
VerticalAlignMethod = Literal["top", "middle", "bottom"]
|
||||
AlignValues = AlignMethod # TODO: deprecate AlignValues
|
||||
|
||||
|
||||
class Align(JupyterMixin):
|
||||
"""Align a renderable by adding spaces if necessary.
|
||||
|
||||
Args:
|
||||
renderable (RenderableType): A console renderable.
|
||||
align (AlignMethod): One of "left", "center", or "right""
|
||||
style (StyleType, optional): An optional style to apply to the background.
|
||||
vertical (Optional[VerticalAlginMethod], optional): Optional vertical align, one of "top", "middle", or "bottom". Defaults to None.
|
||||
pad (bool, optional): Pad the right with spaces. Defaults to True.
|
||||
width (int, optional): Restrict contents to given width, or None to use default width. Defaults to None.
|
||||
height (int, optional): Set height of align renderable, or None to fit to contents. Defaults to None.
|
||||
|
||||
Raises:
|
||||
ValueError: if ``align`` is not one of the expected values.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
renderable: "RenderableType",
|
||||
align: AlignMethod = "left",
|
||||
style: Optional[StyleType] = None,
|
||||
*,
|
||||
vertical: Optional[VerticalAlignMethod] = None,
|
||||
pad: bool = True,
|
||||
width: Optional[int] = None,
|
||||
height: Optional[int] = None,
|
||||
) -> None:
|
||||
if align not in ("left", "center", "right"):
|
||||
raise ValueError(
|
||||
f'invalid value for align, expected "left", "center", or "right" (not {align!r})'
|
||||
)
|
||||
if vertical is not None and vertical not in ("top", "middle", "bottom"):
|
||||
raise ValueError(
|
||||
f'invalid value for vertical, expected "top", "middle", or "bottom" (not {vertical!r})'
|
||||
)
|
||||
self.renderable = renderable
|
||||
self.align = align
|
||||
self.style = style
|
||||
self.vertical = vertical
|
||||
self.pad = pad
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Align({self.renderable!r}, {self.align!r})"
|
||||
|
||||
@classmethod
|
||||
def left(
|
||||
cls,
|
||||
renderable: "RenderableType",
|
||||
style: Optional[StyleType] = None,
|
||||
*,
|
||||
vertical: Optional[VerticalAlignMethod] = None,
|
||||
pad: bool = True,
|
||||
width: Optional[int] = None,
|
||||
height: Optional[int] = None,
|
||||
) -> "Align":
|
||||
"""Align a renderable to the left."""
|
||||
return cls(
|
||||
renderable,
|
||||
"left",
|
||||
style=style,
|
||||
vertical=vertical,
|
||||
pad=pad,
|
||||
width=width,
|
||||
height=height,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def center(
|
||||
cls,
|
||||
renderable: "RenderableType",
|
||||
style: Optional[StyleType] = None,
|
||||
*,
|
||||
vertical: Optional[VerticalAlignMethod] = None,
|
||||
pad: bool = True,
|
||||
width: Optional[int] = None,
|
||||
height: Optional[int] = None,
|
||||
) -> "Align":
|
||||
"""Align a renderable to the center."""
|
||||
return cls(
|
||||
renderable,
|
||||
"center",
|
||||
style=style,
|
||||
vertical=vertical,
|
||||
pad=pad,
|
||||
width=width,
|
||||
height=height,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def right(
|
||||
cls,
|
||||
renderable: "RenderableType",
|
||||
style: Optional[StyleType] = None,
|
||||
*,
|
||||
vertical: Optional[VerticalAlignMethod] = None,
|
||||
pad: bool = True,
|
||||
width: Optional[int] = None,
|
||||
height: Optional[int] = None,
|
||||
) -> "Align":
|
||||
"""Align a renderable to the right."""
|
||||
return cls(
|
||||
renderable,
|
||||
"right",
|
||||
style=style,
|
||||
vertical=vertical,
|
||||
pad=pad,
|
||||
width=width,
|
||||
height=height,
|
||||
)
|
||||
|
||||
def __rich_console__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> "RenderResult":
|
||||
align = self.align
|
||||
width = console.measure(self.renderable, options=options).maximum
|
||||
rendered = console.render(
|
||||
Constrain(
|
||||
self.renderable, width if self.width is None else min(width, self.width)
|
||||
),
|
||||
options.update(height=None),
|
||||
)
|
||||
lines = list(Segment.split_lines(rendered))
|
||||
width, height = Segment.get_shape(lines)
|
||||
lines = Segment.set_shape(lines, width, height)
|
||||
new_line = Segment.line()
|
||||
excess_space = options.max_width - width
|
||||
style = console.get_style(self.style) if self.style is not None else None
|
||||
|
||||
def generate_segments() -> Iterable[Segment]:
|
||||
if excess_space <= 0:
|
||||
# Exact fit
|
||||
for line in lines:
|
||||
yield from line
|
||||
yield new_line
|
||||
|
||||
elif align == "left":
|
||||
# Pad on the right
|
||||
pad = Segment(" " * excess_space, style) if self.pad else None
|
||||
for line in lines:
|
||||
yield from line
|
||||
if pad:
|
||||
yield pad
|
||||
yield new_line
|
||||
|
||||
elif align == "center":
|
||||
# Pad left and right
|
||||
left = excess_space // 2
|
||||
pad = Segment(" " * left, style)
|
||||
pad_right = (
|
||||
Segment(" " * (excess_space - left), style) if self.pad else None
|
||||
)
|
||||
for line in lines:
|
||||
if left:
|
||||
yield pad
|
||||
yield from line
|
||||
if pad_right:
|
||||
yield pad_right
|
||||
yield new_line
|
||||
|
||||
elif align == "right":
|
||||
# Padding on left
|
||||
pad = Segment(" " * excess_space, style)
|
||||
for line in lines:
|
||||
yield pad
|
||||
yield from line
|
||||
yield new_line
|
||||
|
||||
blank_line = (
|
||||
Segment(f"{' ' * (self.width or options.max_width)}\n", style)
|
||||
if self.pad
|
||||
else Segment("\n")
|
||||
)
|
||||
|
||||
def blank_lines(count: int) -> Iterable[Segment]:
|
||||
if count > 0:
|
||||
for _ in range(count):
|
||||
yield blank_line
|
||||
|
||||
vertical_height = self.height or options.height
|
||||
iter_segments: Iterable[Segment]
|
||||
if self.vertical and vertical_height is not None:
|
||||
if self.vertical == "top":
|
||||
bottom_space = vertical_height - height
|
||||
iter_segments = chain(generate_segments(), blank_lines(bottom_space))
|
||||
elif self.vertical == "middle":
|
||||
top_space = (vertical_height - height) // 2
|
||||
bottom_space = vertical_height - top_space - height
|
||||
iter_segments = chain(
|
||||
blank_lines(top_space),
|
||||
generate_segments(),
|
||||
blank_lines(bottom_space),
|
||||
)
|
||||
else: # self.vertical == "bottom":
|
||||
top_space = vertical_height - height
|
||||
iter_segments = chain(blank_lines(top_space), generate_segments())
|
||||
else:
|
||||
iter_segments = generate_segments()
|
||||
if self.style:
|
||||
style = console.get_style(self.style)
|
||||
iter_segments = Segment.apply_style(iter_segments, style)
|
||||
yield from iter_segments
|
||||
|
||||
def __rich_measure__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> Measurement:
|
||||
measurement = Measurement.get(console, options, self.renderable)
|
||||
return measurement
|
||||
|
||||
|
||||
class VerticalCenter(JupyterMixin):
|
||||
"""Vertically aligns a renderable.
|
||||
|
||||
Warn:
|
||||
This class is deprecated and may be removed in a future version. Use Align class with
|
||||
`vertical="middle"`.
|
||||
|
||||
Args:
|
||||
renderable (RenderableType): A renderable object.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
renderable: "RenderableType",
|
||||
style: Optional[StyleType] = None,
|
||||
) -> None:
|
||||
self.renderable = renderable
|
||||
self.style = style
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"VerticalCenter({self.renderable!r})"
|
||||
|
||||
def __rich_console__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> "RenderResult":
|
||||
style = console.get_style(self.style) if self.style is not None else None
|
||||
lines = console.render_lines(
|
||||
self.renderable, options.update(height=None), pad=False
|
||||
)
|
||||
width, _height = Segment.get_shape(lines)
|
||||
new_line = Segment.line()
|
||||
height = options.height or options.size.height
|
||||
top_space = (height - len(lines)) // 2
|
||||
bottom_space = height - top_space - len(lines)
|
||||
blank_line = Segment(f"{' ' * width}", style)
|
||||
|
||||
def blank_lines(count: int) -> Iterable[Segment]:
|
||||
for _ in range(count):
|
||||
yield blank_line
|
||||
yield new_line
|
||||
|
||||
if top_space > 0:
|
||||
yield from blank_lines(top_space)
|
||||
for line in lines:
|
||||
yield from line
|
||||
yield new_line
|
||||
if bottom_space > 0:
|
||||
yield from blank_lines(bottom_space)
|
||||
|
||||
def __rich_measure__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> Measurement:
|
||||
measurement = Measurement.get(console, options, self.renderable)
|
||||
return measurement
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
from pip._vendor.rich.console import Console, Group
|
||||
from pip._vendor.rich.highlighter import ReprHighlighter
|
||||
from pip._vendor.rich.panel import Panel
|
||||
|
||||
highlighter = ReprHighlighter()
|
||||
console = Console()
|
||||
|
||||
panel = Panel(
|
||||
Group(
|
||||
Align.left(highlighter("align='left'")),
|
||||
Align.center(highlighter("align='center'")),
|
||||
Align.right(highlighter("align='right'")),
|
||||
),
|
||||
width=60,
|
||||
style="on dark_blue",
|
||||
title="Algin",
|
||||
)
|
||||
|
||||
console.print(
|
||||
Align.center(panel, vertical="middle", style="on red", height=console.height)
|
||||
)
|
|
@ -0,0 +1,228 @@
|
|||
from contextlib import suppress
|
||||
import re
|
||||
from typing import Iterable, NamedTuple
|
||||
|
||||
from .color import Color
|
||||
from .style import Style
|
||||
from .text import Text
|
||||
|
||||
re_ansi = re.compile(r"(?:\x1b\[(.*?)m)|(?:\x1b\](.*?)\x1b\\)")
|
||||
re_csi = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
|
||||
|
||||
|
||||
class _AnsiToken(NamedTuple):
|
||||
"""Result of ansi tokenized string."""
|
||||
|
||||
plain: str = ""
|
||||
sgr: str = ""
|
||||
osc: str = ""
|
||||
|
||||
|
||||
def _ansi_tokenize(ansi_text: str) -> Iterable[_AnsiToken]:
|
||||
"""Tokenize a string in to plain text and ANSI codes.
|
||||
|
||||
Args:
|
||||
ansi_text (str): A String containing ANSI codes.
|
||||
|
||||
Yields:
|
||||
AnsiToken: A named tuple of (plain, sgr, osc)
|
||||
"""
|
||||
|
||||
def remove_csi(ansi_text: str) -> str:
|
||||
"""Remove unknown CSI sequences."""
|
||||
return re_csi.sub("", ansi_text)
|
||||
|
||||
position = 0
|
||||
for match in re_ansi.finditer(ansi_text):
|
||||
start, end = match.span(0)
|
||||
sgr, osc = match.groups()
|
||||
if start > position:
|
||||
yield _AnsiToken(remove_csi(ansi_text[position:start]))
|
||||
yield _AnsiToken("", sgr, osc)
|
||||
position = end
|
||||
if position < len(ansi_text):
|
||||
yield _AnsiToken(remove_csi(ansi_text[position:]))
|
||||
|
||||
|
||||
SGR_STYLE_MAP = {
|
||||
1: "bold",
|
||||
2: "dim",
|
||||
3: "italic",
|
||||
4: "underline",
|
||||
5: "blink",
|
||||
6: "blink2",
|
||||
7: "reverse",
|
||||
8: "conceal",
|
||||
9: "strike",
|
||||
21: "underline2",
|
||||
22: "not dim not bold",
|
||||
23: "not italic",
|
||||
24: "not underline",
|
||||
25: "not blink",
|
||||
26: "not blink2",
|
||||
27: "not reverse",
|
||||
28: "not conceal",
|
||||
29: "not strike",
|
||||
30: "color(0)",
|
||||
31: "color(1)",
|
||||
32: "color(2)",
|
||||
33: "color(3)",
|
||||
34: "color(4)",
|
||||
35: "color(5)",
|
||||
36: "color(6)",
|
||||
37: "color(7)",
|
||||
39: "default",
|
||||
40: "on color(0)",
|
||||
41: "on color(1)",
|
||||
42: "on color(2)",
|
||||
43: "on color(3)",
|
||||
44: "on color(4)",
|
||||
45: "on color(5)",
|
||||
46: "on color(6)",
|
||||
47: "on color(7)",
|
||||
49: "on default",
|
||||
51: "frame",
|
||||
52: "encircle",
|
||||
53: "overline",
|
||||
54: "not frame not encircle",
|
||||
55: "not overline",
|
||||
90: "color(8)",
|
||||
91: "color(9)",
|
||||
92: "color(10)",
|
||||
93: "color(11)",
|
||||
94: "color(12)",
|
||||
95: "color(13)",
|
||||
96: "color(14)",
|
||||
97: "color(15)",
|
||||
100: "on color(8)",
|
||||
101: "on color(9)",
|
||||
102: "on color(10)",
|
||||
103: "on color(11)",
|
||||
104: "on color(12)",
|
||||
105: "on color(13)",
|
||||
106: "on color(14)",
|
||||
107: "on color(15)",
|
||||
}
|
||||
|
||||
|
||||
class AnsiDecoder:
|
||||
"""Translate ANSI code in to styled Text."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.style = Style.null()
|
||||
|
||||
def decode(self, terminal_text: str) -> Iterable[Text]:
|
||||
"""Decode ANSI codes in an interable of lines.
|
||||
|
||||
Args:
|
||||
lines (Iterable[str]): An iterable of lines of terminal output.
|
||||
|
||||
Yields:
|
||||
Text: Marked up Text.
|
||||
"""
|
||||
for line in terminal_text.splitlines():
|
||||
yield self.decode_line(line)
|
||||
|
||||
def decode_line(self, line: str) -> Text:
|
||||
"""Decode a line containing ansi codes.
|
||||
|
||||
Args:
|
||||
line (str): A line of terminal output.
|
||||
|
||||
Returns:
|
||||
Text: A Text instance marked up according to ansi codes.
|
||||
"""
|
||||
from_ansi = Color.from_ansi
|
||||
from_rgb = Color.from_rgb
|
||||
_Style = Style
|
||||
text = Text()
|
||||
append = text.append
|
||||
line = line.rsplit("\r", 1)[-1]
|
||||
for token in _ansi_tokenize(line):
|
||||
plain_text, sgr, osc = token
|
||||
if plain_text:
|
||||
append(plain_text, self.style or None)
|
||||
elif osc:
|
||||
if osc.startswith("8;"):
|
||||
_params, semicolon, link = osc[2:].partition(";")
|
||||
if semicolon:
|
||||
self.style = self.style.update_link(link or None)
|
||||
elif sgr:
|
||||
# Translate in to semi-colon separated codes
|
||||
# Ignore invalid codes, because we want to be lenient
|
||||
codes = [
|
||||
min(255, int(_code)) for _code in sgr.split(";") if _code.isdigit()
|
||||
]
|
||||
iter_codes = iter(codes)
|
||||
for code in iter_codes:
|
||||
if code == 0:
|
||||
# reset
|
||||
self.style = _Style.null()
|
||||
elif code in SGR_STYLE_MAP:
|
||||
# styles
|
||||
self.style += _Style.parse(SGR_STYLE_MAP[code])
|
||||
elif code == 38:
|
||||
# Foreground
|
||||
with suppress(StopIteration):
|
||||
color_type = next(iter_codes)
|
||||
if color_type == 5:
|
||||
self.style += _Style.from_color(
|
||||
from_ansi(next(iter_codes))
|
||||
)
|
||||
elif color_type == 2:
|
||||
self.style += _Style.from_color(
|
||||
from_rgb(
|
||||
next(iter_codes),
|
||||
next(iter_codes),
|
||||
next(iter_codes),
|
||||
)
|
||||
)
|
||||
elif code == 48:
|
||||
# Background
|
||||
with suppress(StopIteration):
|
||||
color_type = next(iter_codes)
|
||||
if color_type == 5:
|
||||
self.style += _Style.from_color(
|
||||
None, from_ansi(next(iter_codes))
|
||||
)
|
||||
elif color_type == 2:
|
||||
self.style += _Style.from_color(
|
||||
None,
|
||||
from_rgb(
|
||||
next(iter_codes),
|
||||
next(iter_codes),
|
||||
next(iter_codes),
|
||||
),
|
||||
)
|
||||
|
||||
return text
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
import pty
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
|
||||
decoder = AnsiDecoder()
|
||||
|
||||
stdout = io.BytesIO()
|
||||
|
||||
def read(fd: int) -> bytes:
|
||||
data = os.read(fd, 1024)
|
||||
stdout.write(data)
|
||||
return data
|
||||
|
||||
pty.spawn(sys.argv[1:], read)
|
||||
|
||||
from .console import Console
|
||||
|
||||
console = Console(record=True)
|
||||
|
||||
stdout_result = stdout.getvalue().decode("utf-8")
|
||||
print(stdout_result)
|
||||
|
||||
for line in decoder.decode(stdout_result):
|
||||
console.print(line)
|
||||
|
||||
console.save_html("stdout.html")
|
|
@ -0,0 +1,94 @@
|
|||
from typing import Optional, Union
|
||||
|
||||
from .color import Color
|
||||
from .console import Console, ConsoleOptions, RenderResult
|
||||
from .jupyter import JupyterMixin
|
||||
from .measure import Measurement
|
||||
from .segment import Segment
|
||||
from .style import Style
|
||||
|
||||
# There are left-aligned characters for 1/8 to 7/8, but
|
||||
# the right-aligned characters exist only for 1/8 and 4/8.
|
||||
BEGIN_BLOCK_ELEMENTS = ["█", "█", "█", "▐", "▐", "▐", "▕", "▕"]
|
||||
END_BLOCK_ELEMENTS = [" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉"]
|
||||
FULL_BLOCK = "█"
|
||||
|
||||
|
||||
class Bar(JupyterMixin):
|
||||
"""Renders a solid block bar.
|
||||
|
||||
Args:
|
||||
size (float): Value for the end of the bar.
|
||||
begin (float): Begin point (between 0 and size, inclusive).
|
||||
end (float): End point (between 0 and size, inclusive).
|
||||
width (int, optional): Width of the bar, or ``None`` for maximum width. Defaults to None.
|
||||
color (Union[Color, str], optional): Color of the bar. Defaults to "default".
|
||||
bgcolor (Union[Color, str], optional): Color of bar background. Defaults to "default".
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
size: float,
|
||||
begin: float,
|
||||
end: float,
|
||||
*,
|
||||
width: Optional[int] = None,
|
||||
color: Union[Color, str] = "default",
|
||||
bgcolor: Union[Color, str] = "default",
|
||||
):
|
||||
self.size = size
|
||||
self.begin = max(begin, 0)
|
||||
self.end = min(end, size)
|
||||
self.width = width
|
||||
self.style = Style(color=color, bgcolor=bgcolor)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Bar({self.size}, {self.begin}, {self.end})"
|
||||
|
||||
def __rich_console__(
|
||||
self, console: Console, options: ConsoleOptions
|
||||
) -> RenderResult:
|
||||
|
||||
width = min(
|
||||
self.width if self.width is not None else options.max_width,
|
||||
options.max_width,
|
||||
)
|
||||
|
||||
if self.begin >= self.end:
|
||||
yield Segment(" " * width, self.style)
|
||||
yield Segment.line()
|
||||
return
|
||||
|
||||
prefix_complete_eights = int(width * 8 * self.begin / self.size)
|
||||
prefix_bar_count = prefix_complete_eights // 8
|
||||
prefix_eights_count = prefix_complete_eights % 8
|
||||
|
||||
body_complete_eights = int(width * 8 * self.end / self.size)
|
||||
body_bar_count = body_complete_eights // 8
|
||||
body_eights_count = body_complete_eights % 8
|
||||
|
||||
# When start and end fall into the same cell, we ideally should render
|
||||
# a symbol that's "center-aligned", but there is no good symbol in Unicode.
|
||||
# In this case, we fall back to right-aligned block symbol for simplicity.
|
||||
|
||||
prefix = " " * prefix_bar_count
|
||||
if prefix_eights_count:
|
||||
prefix += BEGIN_BLOCK_ELEMENTS[prefix_eights_count]
|
||||
|
||||
body = FULL_BLOCK * body_bar_count
|
||||
if body_eights_count:
|
||||
body += END_BLOCK_ELEMENTS[body_eights_count]
|
||||
|
||||
suffix = " " * (width - len(body))
|
||||
|
||||
yield Segment(prefix + body[len(prefix) :] + suffix, self.style)
|
||||
yield Segment.line()
|
||||
|
||||
def __rich_measure__(
|
||||
self, console: Console, options: ConsoleOptions
|
||||
) -> Measurement:
|
||||
return (
|
||||
Measurement(self.width, self.width)
|
||||
if self.width is not None
|
||||
else Measurement(4, options.max_width)
|
||||
)
|
|
@ -0,0 +1,483 @@
|
|||
import sys
|
||||
from typing import TYPE_CHECKING, Iterable, List
|
||||
|
||||
if sys.version_info >= (3, 8):
|
||||
from typing import Literal
|
||||
else:
|
||||
from pip._vendor.typing_extensions import Literal # pragma: no cover
|
||||
|
||||
|
||||
from ._loop import loop_last
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pip._vendor.rich.console import ConsoleOptions
|
||||
|
||||
|
||||
class Box:
|
||||
"""Defines characters to render boxes.
|
||||
|
||||
┌─┬┐ top
|
||||
│ ││ head
|
||||
├─┼┤ head_row
|
||||
│ ││ mid
|
||||
├─┼┤ row
|
||||
├─┼┤ foot_row
|
||||
│ ││ foot
|
||||
└─┴┘ bottom
|
||||
|
||||
Args:
|
||||
box (str): Characters making up box.
|
||||
ascii (bool, optional): True if this box uses ascii characters only. Default is False.
|
||||
"""
|
||||
|
||||
def __init__(self, box: str, *, ascii: bool = False) -> None:
|
||||
self._box = box
|
||||
self.ascii = ascii
|
||||
line1, line2, line3, line4, line5, line6, line7, line8 = box.splitlines()
|
||||
# top
|
||||
self.top_left, self.top, self.top_divider, self.top_right = iter(line1)
|
||||
# head
|
||||
self.head_left, _, self.head_vertical, self.head_right = iter(line2)
|
||||
# head_row
|
||||
(
|
||||
self.head_row_left,
|
||||
self.head_row_horizontal,
|
||||
self.head_row_cross,
|
||||
self.head_row_right,
|
||||
) = iter(line3)
|
||||
|
||||
# mid
|
||||
self.mid_left, _, self.mid_vertical, self.mid_right = iter(line4)
|
||||
# row
|
||||
self.row_left, self.row_horizontal, self.row_cross, self.row_right = iter(line5)
|
||||
# foot_row
|
||||
(
|
||||
self.foot_row_left,
|
||||
self.foot_row_horizontal,
|
||||
self.foot_row_cross,
|
||||
self.foot_row_right,
|
||||
) = iter(line6)
|
||||
# foot
|
||||
self.foot_left, _, self.foot_vertical, self.foot_right = iter(line7)
|
||||
# bottom
|
||||
self.bottom_left, self.bottom, self.bottom_divider, self.bottom_right = iter(
|
||||
line8
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "Box(...)"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self._box
|
||||
|
||||
def substitute(self, options: "ConsoleOptions", safe: bool = True) -> "Box":
|
||||
"""Substitute this box for another if it won't render due to platform issues.
|
||||
|
||||
Args:
|
||||
options (ConsoleOptions): Console options used in rendering.
|
||||
safe (bool, optional): Substitute this for another Box if there are known problems
|
||||
displaying on the platform (currently only relevant on Windows). Default is True.
|
||||
|
||||
Returns:
|
||||
Box: A different Box or the same Box.
|
||||
"""
|
||||
box = self
|
||||
if options.legacy_windows and safe:
|
||||
box = LEGACY_WINDOWS_SUBSTITUTIONS.get(box, box)
|
||||
if options.ascii_only and not box.ascii:
|
||||
box = ASCII
|
||||
return box
|
||||
|
||||
def get_top(self, widths: Iterable[int]) -> str:
|
||||
"""Get the top of a simple box.
|
||||
|
||||
Args:
|
||||
widths (List[int]): Widths of columns.
|
||||
|
||||
Returns:
|
||||
str: A string of box characters.
|
||||
"""
|
||||
|
||||
parts: List[str] = []
|
||||
append = parts.append
|
||||
append(self.top_left)
|
||||
for last, width in loop_last(widths):
|
||||
append(self.top * width)
|
||||
if not last:
|
||||
append(self.top_divider)
|
||||
append(self.top_right)
|
||||
return "".join(parts)
|
||||
|
||||
def get_row(
|
||||
self,
|
||||
widths: Iterable[int],
|
||||
level: Literal["head", "row", "foot", "mid"] = "row",
|
||||
edge: bool = True,
|
||||
) -> str:
|
||||
"""Get the top of a simple box.
|
||||
|
||||
Args:
|
||||
width (List[int]): Widths of columns.
|
||||
|
||||
Returns:
|
||||
str: A string of box characters.
|
||||
"""
|
||||
if level == "head":
|
||||
left = self.head_row_left
|
||||
horizontal = self.head_row_horizontal
|
||||
cross = self.head_row_cross
|
||||
right = self.head_row_right
|
||||
elif level == "row":
|
||||
left = self.row_left
|
||||
horizontal = self.row_horizontal
|
||||
cross = self.row_cross
|
||||
right = self.row_right
|
||||
elif level == "mid":
|
||||
left = self.mid_left
|
||||
horizontal = " "
|
||||
cross = self.mid_vertical
|
||||
right = self.mid_right
|
||||
elif level == "foot":
|
||||
left = self.foot_row_left
|
||||
horizontal = self.foot_row_horizontal
|
||||
cross = self.foot_row_cross
|
||||
right = self.foot_row_right
|
||||
else:
|
||||
raise ValueError("level must be 'head', 'row' or 'foot'")
|
||||
|
||||
parts: List[str] = []
|
||||
append = parts.append
|
||||
if edge:
|
||||
append(left)
|
||||
for last, width in loop_last(widths):
|
||||
append(horizontal * width)
|
||||
if not last:
|
||||
append(cross)
|
||||
if edge:
|
||||
append(right)
|
||||
return "".join(parts)
|
||||
|
||||
def get_bottom(self, widths: Iterable[int]) -> str:
|
||||
"""Get the bottom of a simple box.
|
||||
|
||||
Args:
|
||||
widths (List[int]): Widths of columns.
|
||||
|
||||
Returns:
|
||||
str: A string of box characters.
|
||||
"""
|
||||
|
||||
parts: List[str] = []
|
||||
append = parts.append
|
||||
append(self.bottom_left)
|
||||
for last, width in loop_last(widths):
|
||||
append(self.bottom * width)
|
||||
if not last:
|
||||
append(self.bottom_divider)
|
||||
append(self.bottom_right)
|
||||
return "".join(parts)
|
||||
|
||||
|
||||
ASCII: Box = Box(
|
||||
"""\
|
||||
+--+
|
||||
| ||
|
||||
|-+|
|
||||
| ||
|
||||
|-+|
|
||||
|-+|
|
||||
| ||
|
||||
+--+
|
||||
""",
|
||||
ascii=True,
|
||||
)
|
||||
|
||||
ASCII2: Box = Box(
|
||||
"""\
|
||||
+-++
|
||||
| ||
|
||||
+-++
|
||||
| ||
|
||||
+-++
|
||||
+-++
|
||||
| ||
|
||||
+-++
|
||||
""",
|
||||
ascii=True,
|
||||
)
|
||||
|
||||
ASCII_DOUBLE_HEAD: Box = Box(
|
||||
"""\
|
||||
+-++
|
||||
| ||
|
||||
+=++
|
||||
| ||
|
||||
+-++
|
||||
+-++
|
||||
| ||
|
||||
+-++
|
||||
""",
|
||||
ascii=True,
|
||||
)
|
||||
|
||||
SQUARE: Box = Box(
|
||||
"""\
|
||||
┌─┬┐
|
||||
│ ││
|
||||
├─┼┤
|
||||
│ ││
|
||||
├─┼┤
|
||||
├─┼┤
|
||||
│ ││
|
||||
└─┴┘
|
||||
"""
|
||||
)
|
||||
|
||||
SQUARE_DOUBLE_HEAD: Box = Box(
|
||||
"""\
|
||||
┌─┬┐
|
||||
│ ││
|
||||
╞═╪╡
|
||||
│ ││
|
||||
├─┼┤
|
||||
├─┼┤
|
||||
│ ││
|
||||
└─┴┘
|
||||
"""
|
||||
)
|
||||
|
||||
MINIMAL: Box = Box(
|
||||
"""\
|
||||
╷
|
||||
│
|
||||
╶─┼╴
|
||||
│
|
||||
╶─┼╴
|
||||
╶─┼╴
|
||||
│
|
||||
╵
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
MINIMAL_HEAVY_HEAD: Box = Box(
|
||||
"""\
|
||||
╷
|
||||
│
|
||||
╺━┿╸
|
||||
│
|
||||
╶─┼╴
|
||||
╶─┼╴
|
||||
│
|
||||
╵
|
||||
"""
|
||||
)
|
||||
|
||||
MINIMAL_DOUBLE_HEAD: Box = Box(
|
||||
"""\
|
||||
╷
|
||||
│
|
||||
═╪
|
||||
│
|
||||
─┼
|
||||
─┼
|
||||
│
|
||||
╵
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
SIMPLE: Box = Box(
|
||||
"""\
|
||||
|
||||
|
||||
──
|
||||
|
||||
|
||||
──
|
||||
|
||||
|
||||
"""
|
||||
)
|
||||
|
||||
SIMPLE_HEAD: Box = Box(
|
||||
"""\
|
||||
|
||||
|
||||
──
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
SIMPLE_HEAVY: Box = Box(
|
||||
"""\
|
||||
|
||||
|
||||
━━
|
||||
|
||||
|
||||
━━
|
||||
|
||||
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
HORIZONTALS: Box = Box(
|
||||
"""\
|
||||
──
|
||||
|
||||
──
|
||||
|
||||
──
|
||||
──
|
||||
|
||||
──
|
||||
"""
|
||||
)
|
||||
|
||||
ROUNDED: Box = Box(
|
||||
"""\
|
||||
╭─┬╮
|
||||
│ ││
|
||||
├─┼┤
|
||||
│ ││
|
||||
├─┼┤
|
||||
├─┼┤
|
||||
│ ││
|
||||
╰─┴╯
|
||||
"""
|
||||
)
|
||||
|
||||
HEAVY: Box = Box(
|
||||
"""\
|
||||
┏━┳┓
|
||||
┃ ┃┃
|
||||
┣━╋┫
|
||||
┃ ┃┃
|
||||
┣━╋┫
|
||||
┣━╋┫
|
||||
┃ ┃┃
|
||||
┗━┻┛
|
||||
"""
|
||||
)
|
||||
|
||||
HEAVY_EDGE: Box = Box(
|
||||
"""\
|
||||
┏━┯┓
|
||||
┃ │┃
|
||||
┠─┼┨
|
||||
┃ │┃
|
||||
┠─┼┨
|
||||
┠─┼┨
|
||||
┃ │┃
|
||||
┗━┷┛
|
||||
"""
|
||||
)
|
||||
|
||||
HEAVY_HEAD: Box = Box(
|
||||
"""\
|
||||
┏━┳┓
|
||||
┃ ┃┃
|
||||
┡━╇┩
|
||||
│ ││
|
||||
├─┼┤
|
||||
├─┼┤
|
||||
│ ││
|
||||
└─┴┘
|
||||
"""
|
||||
)
|
||||
|
||||
DOUBLE: Box = Box(
|
||||
"""\
|
||||
╔═╦╗
|
||||
║ ║║
|
||||
╠═╬╣
|
||||
║ ║║
|
||||
╠═╬╣
|
||||
╠═╬╣
|
||||
║ ║║
|
||||
╚═╩╝
|
||||
"""
|
||||
)
|
||||
|
||||
DOUBLE_EDGE: Box = Box(
|
||||
"""\
|
||||
╔═╤╗
|
||||
║ │║
|
||||
╟─┼╢
|
||||
║ │║
|
||||
╟─┼╢
|
||||
╟─┼╢
|
||||
║ │║
|
||||
╚═╧╝
|
||||
"""
|
||||
)
|
||||
|
||||
# Map Boxes that don't render with raster fonts on to equivalent that do
|
||||
LEGACY_WINDOWS_SUBSTITUTIONS = {
|
||||
ROUNDED: SQUARE,
|
||||
MINIMAL_HEAVY_HEAD: MINIMAL,
|
||||
SIMPLE_HEAVY: SIMPLE,
|
||||
HEAVY: SQUARE,
|
||||
HEAVY_EDGE: SQUARE,
|
||||
HEAVY_HEAD: SQUARE,
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
|
||||
from pip._vendor.rich.columns import Columns
|
||||
from pip._vendor.rich.panel import Panel
|
||||
|
||||
from . import box as box
|
||||
from .console import Console
|
||||
from .table import Table
|
||||
from .text import Text
|
||||
|
||||
console = Console(record=True)
|
||||
|
||||
BOXES = [
|
||||
"ASCII",
|
||||
"ASCII2",
|
||||
"ASCII_DOUBLE_HEAD",
|
||||
"SQUARE",
|
||||
"SQUARE_DOUBLE_HEAD",
|
||||
"MINIMAL",
|
||||
"MINIMAL_HEAVY_HEAD",
|
||||
"MINIMAL_DOUBLE_HEAD",
|
||||
"SIMPLE",
|
||||
"SIMPLE_HEAD",
|
||||
"SIMPLE_HEAVY",
|
||||
"HORIZONTALS",
|
||||
"ROUNDED",
|
||||
"HEAVY",
|
||||
"HEAVY_EDGE",
|
||||
"HEAVY_HEAD",
|
||||
"DOUBLE",
|
||||
"DOUBLE_EDGE",
|
||||
]
|
||||
|
||||
console.print(Panel("[bold green]Box Constants", style="green"), justify="center")
|
||||
console.print()
|
||||
|
||||
columns = Columns(expand=True, padding=2)
|
||||
for box_name in sorted(BOXES):
|
||||
table = Table(
|
||||
show_footer=True, style="dim", border_style="not dim", expand=True
|
||||
)
|
||||
table.add_column("Header 1", "Footer 1")
|
||||
table.add_column("Header 2", "Footer 2")
|
||||
table.add_row("Cell", "Cell")
|
||||
table.add_row("Cell", "Cell")
|
||||
table.box = getattr(box, box_name)
|
||||
table.title = Text(f"box.{box_name}", style="magenta")
|
||||
columns.add_renderable(table)
|
||||
console.print(columns)
|
||||
|
||||
# console.save_html("box.html", inline_styles=True)
|
|
@ -0,0 +1,135 @@
|
|||
from functools import lru_cache
|
||||
from typing import Dict, List
|
||||
|
||||
from ._cell_widths import CELL_WIDTHS
|
||||
from ._lru_cache import LRUCache
|
||||
|
||||
|
||||
def cell_len(text: str, _cache: Dict[str, int] = LRUCache(1024 * 4)) -> int:
|
||||
"""Get the number of cells required to display text.
|
||||
|
||||
Args:
|
||||
text (str): Text to display.
|
||||
|
||||
Returns:
|
||||
int: Number of cells required to display the text.
|
||||
"""
|
||||
cached_result = _cache.get(text, None)
|
||||
if cached_result is not None:
|
||||
return cached_result
|
||||
|
||||
_get_size = get_character_cell_size
|
||||
total_size = sum(_get_size(character) for character in text)
|
||||
if len(text) <= 64:
|
||||
_cache[text] = total_size
|
||||
return total_size
|
||||
|
||||
|
||||
@lru_cache(maxsize=4096)
|
||||
def get_character_cell_size(character: str) -> int:
|
||||
"""Get the cell size of a character.
|
||||
|
||||
Args:
|
||||
character (str): A single character.
|
||||
|
||||
Returns:
|
||||
int: Number of cells (0, 1 or 2) occupied by that character.
|
||||
"""
|
||||
|
||||
codepoint = ord(character)
|
||||
if 127 > codepoint > 31:
|
||||
# Shortcut for ascii
|
||||
return 1
|
||||
return _get_codepoint_cell_size(codepoint)
|
||||
|
||||
|
||||
@lru_cache(maxsize=4096)
|
||||
def _get_codepoint_cell_size(codepoint: int) -> int:
|
||||
"""Get the cell size of a character.
|
||||
|
||||
Args:
|
||||
character (str): A single character.
|
||||
|
||||
Returns:
|
||||
int: Number of cells (0, 1 or 2) occupied by that character.
|
||||
"""
|
||||
|
||||
_table = CELL_WIDTHS
|
||||
lower_bound = 0
|
||||
upper_bound = len(_table) - 1
|
||||
index = (lower_bound + upper_bound) // 2
|
||||
while True:
|
||||
start, end, width = _table[index]
|
||||
if codepoint < start:
|
||||
upper_bound = index - 1
|
||||
elif codepoint > end:
|
||||
lower_bound = index + 1
|
||||
else:
|
||||
return 0 if width == -1 else width
|
||||
if upper_bound < lower_bound:
|
||||
break
|
||||
index = (lower_bound + upper_bound) // 2
|
||||
return 1
|
||||
|
||||
|
||||
def set_cell_size(text: str, total: int) -> str:
|
||||
"""Set the length of a string to fit within given number of cells."""
|
||||
if not total:
|
||||
return ""
|
||||
cell_size = cell_len(text)
|
||||
if cell_size == total:
|
||||
return text
|
||||
if cell_size < total:
|
||||
return text + " " * (total - cell_size)
|
||||
|
||||
start = 0
|
||||
end = len(text)
|
||||
|
||||
# Binary search until we find the right size
|
||||
while True:
|
||||
pos = (start + end) // 2
|
||||
before = text[: pos + 1]
|
||||
before_len = cell_len(before)
|
||||
if before_len == total + 1 and cell_len(before[-1]) == 2:
|
||||
return before[:-1] + " "
|
||||
if before_len == total:
|
||||
return before
|
||||
if before_len > total:
|
||||
end = pos
|
||||
else:
|
||||
start = pos
|
||||
|
||||
|
||||
# TODO: This is inefficient
|
||||
# TODO: This might not work with CWJ type characters
|
||||
def chop_cells(text: str, max_size: int, position: int = 0) -> List[str]:
|
||||
"""Break text in to equal (cell) length strings."""
|
||||
_get_character_cell_size = get_character_cell_size
|
||||
characters = [
|
||||
(character, _get_character_cell_size(character)) for character in text
|
||||
][::-1]
|
||||
total_size = position
|
||||
lines: List[List[str]] = [[]]
|
||||
append = lines[-1].append
|
||||
|
||||
pop = characters.pop
|
||||
while characters:
|
||||
character, size = pop()
|
||||
if total_size + size > max_size:
|
||||
lines.append([character])
|
||||
append = lines[-1].append
|
||||
total_size = size
|
||||
else:
|
||||
total_size += size
|
||||
append(character)
|
||||
return ["".join(line) for line in lines]
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
|
||||
print(get_character_cell_size("😽"))
|
||||
for line in chop_cells("""这是对亚洲语言支持的测试。面对模棱两可的想法,拒绝猜测的诱惑。""", 8):
|
||||
print(line)
|
||||
for n in range(80, 1, -1):
|
||||
print(set_cell_size("""这是对亚洲语言支持的测试。面对模棱两可的想法,拒绝猜测的诱惑。""", n) + "|")
|
||||
print("x" * n)
|
|
@ -0,0 +1,581 @@
|
|||
import platform
|
||||
import re
|
||||
from colorsys import rgb_to_hls
|
||||
from enum import IntEnum
|
||||
from functools import lru_cache
|
||||
from typing import TYPE_CHECKING, NamedTuple, Optional, Tuple
|
||||
|
||||
from ._palettes import EIGHT_BIT_PALETTE, STANDARD_PALETTE, WINDOWS_PALETTE
|
||||
from .color_triplet import ColorTriplet
|
||||
from .repr import rich_repr, Result
|
||||
from .terminal_theme import DEFAULT_TERMINAL_THEME
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from .terminal_theme import TerminalTheme
|
||||
from .text import Text
|
||||
|
||||
|
||||
WINDOWS = platform.system() == "Windows"
|
||||
|
||||
|
||||
class ColorSystem(IntEnum):
|
||||
"""One of the 3 color system supported by terminals."""
|
||||
|
||||
STANDARD = 1
|
||||
EIGHT_BIT = 2
|
||||
TRUECOLOR = 3
|
||||
WINDOWS = 4
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"ColorSystem.{self.name}"
|
||||
|
||||
|
||||
class ColorType(IntEnum):
|
||||
"""Type of color stored in Color class."""
|
||||
|
||||
DEFAULT = 0
|
||||
STANDARD = 1
|
||||
EIGHT_BIT = 2
|
||||
TRUECOLOR = 3
|
||||
WINDOWS = 4
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"ColorType.{self.name}"
|
||||
|
||||
|
||||
ANSI_COLOR_NAMES = {
|
||||
"black": 0,
|
||||
"red": 1,
|
||||
"green": 2,
|
||||
"yellow": 3,
|
||||
"blue": 4,
|
||||
"magenta": 5,
|
||||
"cyan": 6,
|
||||
"white": 7,
|
||||
"bright_black": 8,
|
||||
"bright_red": 9,
|
||||
"bright_green": 10,
|
||||
"bright_yellow": 11,
|
||||
"bright_blue": 12,
|
||||
"bright_magenta": 13,
|
||||
"bright_cyan": 14,
|
||||
"bright_white": 15,
|
||||
"grey0": 16,
|
||||
"navy_blue": 17,
|
||||
"dark_blue": 18,
|
||||
"blue3": 20,
|
||||
"blue1": 21,
|
||||
"dark_green": 22,
|
||||
"deep_sky_blue4": 25,
|
||||
"dodger_blue3": 26,
|
||||
"dodger_blue2": 27,
|
||||
"green4": 28,
|
||||
"spring_green4": 29,
|
||||
"turquoise4": 30,
|
||||
"deep_sky_blue3": 32,
|
||||
"dodger_blue1": 33,
|
||||
"green3": 40,
|
||||
"spring_green3": 41,
|
||||
"dark_cyan": 36,
|
||||
"light_sea_green": 37,
|
||||
"deep_sky_blue2": 38,
|
||||
"deep_sky_blue1": 39,
|
||||
"spring_green2": 47,
|
||||
"cyan3": 43,
|
||||
"dark_turquoise": 44,
|
||||
"turquoise2": 45,
|
||||
"green1": 46,
|
||||
"spring_green1": 48,
|
||||
"medium_spring_green": 49,
|
||||
"cyan2": 50,
|
||||
"cyan1": 51,
|
||||
"dark_red": 88,
|
||||
"deep_pink4": 125,
|
||||
"purple4": 55,
|
||||
"purple3": 56,
|
||||
"blue_violet": 57,
|
||||
"orange4": 94,
|
||||
"grey37": 59,
|
||||
"medium_purple4": 60,
|
||||
"slate_blue3": 62,
|
||||
"royal_blue1": 63,
|
||||
"chartreuse4": 64,
|
||||
"dark_sea_green4": 71,
|
||||
"pale_turquoise4": 66,
|
||||
"steel_blue": 67,
|
||||
"steel_blue3": 68,
|
||||
"cornflower_blue": 69,
|
||||
"chartreuse3": 76,
|
||||
"cadet_blue": 73,
|
||||
"sky_blue3": 74,
|
||||
"steel_blue1": 81,
|
||||
"pale_green3": 114,
|
||||
"sea_green3": 78,
|
||||
"aquamarine3": 79,
|
||||
"medium_turquoise": 80,
|
||||
"chartreuse2": 112,
|
||||
"sea_green2": 83,
|
||||
"sea_green1": 85,
|
||||
"aquamarine1": 122,
|
||||
"dark_slate_gray2": 87,
|
||||
"dark_magenta": 91,
|
||||
"dark_violet": 128,
|
||||
"purple": 129,
|
||||
"light_pink4": 95,
|
||||
"plum4": 96,
|
||||
"medium_purple3": 98,
|
||||
"slate_blue1": 99,
|
||||
"yellow4": 106,
|
||||
"wheat4": 101,
|
||||
"grey53": 102,
|
||||
"light_slate_grey": 103,
|
||||
"medium_purple": 104,
|
||||
"light_slate_blue": 105,
|
||||
"dark_olive_green3": 149,
|
||||
"dark_sea_green": 108,
|
||||
"light_sky_blue3": 110,
|
||||
"sky_blue2": 111,
|
||||
"dark_sea_green3": 150,
|
||||
"dark_slate_gray3": 116,
|
||||
"sky_blue1": 117,
|
||||
"chartreuse1": 118,
|
||||
"light_green": 120,
|
||||
"pale_green1": 156,
|
||||
"dark_slate_gray1": 123,
|
||||
"red3": 160,
|
||||
"medium_violet_red": 126,
|
||||
"magenta3": 164,
|
||||
"dark_orange3": 166,
|
||||
"indian_red": 167,
|
||||
"hot_pink3": 168,
|
||||
"medium_orchid3": 133,
|
||||
"medium_orchid": 134,
|
||||
"medium_purple2": 140,
|
||||
"dark_goldenrod": 136,
|
||||
"light_salmon3": 173,
|
||||
"rosy_brown": 138,
|
||||
"grey63": 139,
|
||||
"medium_purple1": 141,
|
||||
"gold3": 178,
|
||||
"dark_khaki": 143,
|
||||
"navajo_white3": 144,
|
||||
"grey69": 145,
|
||||
"light_steel_blue3": 146,
|
||||
"light_steel_blue": 147,
|
||||
"yellow3": 184,
|
||||
"dark_sea_green2": 157,
|
||||
"light_cyan3": 152,
|
||||
"light_sky_blue1": 153,
|
||||
"green_yellow": 154,
|
||||
"dark_olive_green2": 155,
|
||||
"dark_sea_green1": 193,
|
||||
"pale_turquoise1": 159,
|
||||
"deep_pink3": 162,
|
||||
"magenta2": 200,
|
||||
"hot_pink2": 169,
|
||||
"orchid": 170,
|
||||
"medium_orchid1": 207,
|
||||
"orange3": 172,
|
||||
"light_pink3": 174,
|
||||
"pink3": 175,
|
||||
"plum3": 176,
|
||||
"violet": 177,
|
||||
"light_goldenrod3": 179,
|
||||
"tan": 180,
|
||||
"misty_rose3": 181,
|
||||
"thistle3": 182,
|
||||
"plum2": 183,
|
||||
"khaki3": 185,
|
||||
"light_goldenrod2": 222,
|
||||
"light_yellow3": 187,
|
||||
"grey84": 188,
|
||||
"light_steel_blue1": 189,
|
||||
"yellow2": 190,
|
||||
"dark_olive_green1": 192,
|
||||
"honeydew2": 194,
|
||||
"light_cyan1": 195,
|
||||
"red1": 196,
|
||||
"deep_pink2": 197,
|
||||
"deep_pink1": 199,
|
||||
"magenta1": 201,
|
||||
"orange_red1": 202,
|
||||
"indian_red1": 204,
|
||||
"hot_pink": 206,
|
||||
"dark_orange": 208,
|
||||
"salmon1": 209,
|
||||
"light_coral": 210,
|
||||
"pale_violet_red1": 211,
|
||||
"orchid2": 212,
|
||||
"orchid1": 213,
|
||||
"orange1": 214,
|
||||
"sandy_brown": 215,
|
||||
"light_salmon1": 216,
|
||||
"light_pink1": 217,
|
||||
"pink1": 218,
|
||||
"plum1": 219,
|
||||
"gold1": 220,
|
||||
"navajo_white1": 223,
|
||||
"misty_rose1": 224,
|
||||
"thistle1": 225,
|
||||
"yellow1": 226,
|
||||
"light_goldenrod1": 227,
|
||||
"khaki1": 228,
|
||||
"wheat1": 229,
|
||||
"cornsilk1": 230,
|
||||
"grey100": 231,
|
||||
"grey3": 232,
|
||||
"grey7": 233,
|
||||
"grey11": 234,
|
||||
"grey15": 235,
|
||||
"grey19": 236,
|
||||
"grey23": 237,
|
||||
"grey27": 238,
|
||||
"grey30": 239,
|
||||
"grey35": 240,
|
||||
"grey39": 241,
|
||||
"grey42": 242,
|
||||
"grey46": 243,
|
||||
"grey50": 244,
|
||||
"grey54": 245,
|
||||
"grey58": 246,
|
||||
"grey62": 247,
|
||||
"grey66": 248,
|
||||
"grey70": 249,
|
||||
"grey74": 250,
|
||||
"grey78": 251,
|
||||
"grey82": 252,
|
||||
"grey85": 253,
|
||||
"grey89": 254,
|
||||
"grey93": 255,
|
||||
}
|
||||
|
||||
|
||||
class ColorParseError(Exception):
|
||||
"""The color could not be parsed."""
|
||||
|
||||
|
||||
RE_COLOR = re.compile(
|
||||
r"""^
|
||||
\#([0-9a-f]{6})$|
|
||||
color\(([0-9]{1,3})\)$|
|
||||
rgb\(([\d\s,]+)\)$
|
||||
""",
|
||||
re.VERBOSE,
|
||||
)
|
||||
|
||||
|
||||
@rich_repr
|
||||
class Color(NamedTuple):
|
||||
"""Terminal color definition."""
|
||||
|
||||
name: str
|
||||
"""The name of the color (typically the input to Color.parse)."""
|
||||
type: ColorType
|
||||
"""The type of the color."""
|
||||
number: Optional[int] = None
|
||||
"""The color number, if a standard color, or None."""
|
||||
triplet: Optional[ColorTriplet] = None
|
||||
"""A triplet of color components, if an RGB color."""
|
||||
|
||||
def __rich__(self) -> "Text":
|
||||
"""Dispays the actual color if Rich printed."""
|
||||
from .text import Text
|
||||
from .style import Style
|
||||
|
||||
return Text.assemble(
|
||||
f"<color {self.name!r} ({self.type.name.lower()})",
|
||||
("⬤", Style(color=self)),
|
||||
" >",
|
||||
)
|
||||
|
||||
def __rich_repr__(self) -> Result:
|
||||
yield self.name
|
||||
yield self.type
|
||||
yield "number", self.number, None
|
||||
yield "triplet", self.triplet, None
|
||||
|
||||
@property
|
||||
def system(self) -> ColorSystem:
|
||||
"""Get the native color system for this color."""
|
||||
if self.type == ColorType.DEFAULT:
|
||||
return ColorSystem.STANDARD
|
||||
return ColorSystem(int(self.type))
|
||||
|
||||
@property
|
||||
def is_system_defined(self) -> bool:
|
||||
"""Check if the color is ultimately defined by the system."""
|
||||
return self.system not in (ColorSystem.EIGHT_BIT, ColorSystem.TRUECOLOR)
|
||||
|
||||
@property
|
||||
def is_default(self) -> bool:
|
||||
"""Check if the color is a default color."""
|
||||
return self.type == ColorType.DEFAULT
|
||||
|
||||
def get_truecolor(
|
||||
self, theme: Optional["TerminalTheme"] = None, foreground: bool = True
|
||||
) -> ColorTriplet:
|
||||
"""Get an equivalent color triplet for this color.
|
||||
|
||||
Args:
|
||||
theme (TerminalTheme, optional): Optional terminal theme, or None to use default. Defaults to None.
|
||||
foreground (bool, optional): True for a foreground color, or False for background. Defaults to True.
|
||||
|
||||
Returns:
|
||||
ColorTriplet: A color triplet containing RGB components.
|
||||
"""
|
||||
|
||||
if theme is None:
|
||||
theme = DEFAULT_TERMINAL_THEME
|
||||
if self.type == ColorType.TRUECOLOR:
|
||||
assert self.triplet is not None
|
||||
return self.triplet
|
||||
elif self.type == ColorType.EIGHT_BIT:
|
||||
assert self.number is not None
|
||||
return EIGHT_BIT_PALETTE[self.number]
|
||||
elif self.type == ColorType.STANDARD:
|
||||
assert self.number is not None
|
||||
return theme.ansi_colors[self.number]
|
||||
elif self.type == ColorType.WINDOWS:
|
||||
assert self.number is not None
|
||||
return WINDOWS_PALETTE[self.number]
|
||||
else: # self.type == ColorType.DEFAULT:
|
||||
assert self.number is None
|
||||
return theme.foreground_color if foreground else theme.background_color
|
||||
|
||||
@classmethod
|
||||
def from_ansi(cls, number: int) -> "Color":
|
||||
"""Create a Color number from it's 8-bit ansi number.
|
||||
|
||||
Args:
|
||||
number (int): A number between 0-255 inclusive.
|
||||
|
||||
Returns:
|
||||
Color: A new Color instance.
|
||||
"""
|
||||
return cls(
|
||||
name=f"color({number})",
|
||||
type=(ColorType.STANDARD if number < 16 else ColorType.EIGHT_BIT),
|
||||
number=number,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_triplet(cls, triplet: "ColorTriplet") -> "Color":
|
||||
"""Create a truecolor RGB color from a triplet of values.
|
||||
|
||||
Args:
|
||||
triplet (ColorTriplet): A color triplet containing red, green and blue components.
|
||||
|
||||
Returns:
|
||||
Color: A new color object.
|
||||
"""
|
||||
return cls(name=triplet.hex, type=ColorType.TRUECOLOR, triplet=triplet)
|
||||
|
||||
@classmethod
|
||||
def from_rgb(cls, red: float, green: float, blue: float) -> "Color":
|
||||
"""Create a truecolor from three color components in the range(0->255).
|
||||
|
||||
Args:
|
||||
red (float): Red component in range 0-255.
|
||||
green (float): Green component in range 0-255.
|
||||
blue (float): Blue component in range 0-255.
|
||||
|
||||
Returns:
|
||||
Color: A new color object.
|
||||
"""
|
||||
return cls.from_triplet(ColorTriplet(int(red), int(green), int(blue)))
|
||||
|
||||
@classmethod
|
||||
def default(cls) -> "Color":
|
||||
"""Get a Color instance representing the default color.
|
||||
|
||||
Returns:
|
||||
Color: Default color.
|
||||
"""
|
||||
return cls(name="default", type=ColorType.DEFAULT)
|
||||
|
||||
@classmethod
|
||||
@lru_cache(maxsize=1024)
|
||||
def parse(cls, color: str) -> "Color":
|
||||
"""Parse a color definition."""
|
||||
original_color = color
|
||||
color = color.lower().strip()
|
||||
|
||||
if color == "default":
|
||||
return cls(color, type=ColorType.DEFAULT)
|
||||
|
||||
color_number = ANSI_COLOR_NAMES.get(color)
|
||||
if color_number is not None:
|
||||
return cls(
|
||||
color,
|
||||
type=(ColorType.STANDARD if color_number < 16 else ColorType.EIGHT_BIT),
|
||||
number=color_number,
|
||||
)
|
||||
|
||||
color_match = RE_COLOR.match(color)
|
||||
if color_match is None:
|
||||
raise ColorParseError(f"{original_color!r} is not a valid color")
|
||||
|
||||
color_24, color_8, color_rgb = color_match.groups()
|
||||
if color_24:
|
||||
triplet = ColorTriplet(
|
||||
int(color_24[0:2], 16), int(color_24[2:4], 16), int(color_24[4:6], 16)
|
||||
)
|
||||
return cls(color, ColorType.TRUECOLOR, triplet=triplet)
|
||||
|
||||
elif color_8:
|
||||
number = int(color_8)
|
||||
if number > 255:
|
||||
raise ColorParseError(f"color number must be <= 255 in {color!r}")
|
||||
return cls(
|
||||
color,
|
||||
type=(ColorType.STANDARD if number < 16 else ColorType.EIGHT_BIT),
|
||||
number=number,
|
||||
)
|
||||
|
||||
else: # color_rgb:
|
||||
components = color_rgb.split(",")
|
||||
if len(components) != 3:
|
||||
raise ColorParseError(
|
||||
f"expected three components in {original_color!r}"
|
||||
)
|
||||
red, green, blue = components
|
||||
triplet = ColorTriplet(int(red), int(green), int(blue))
|
||||
if not all(component <= 255 for component in triplet):
|
||||
raise ColorParseError(
|
||||
f"color components must be <= 255 in {original_color!r}"
|
||||
)
|
||||
return cls(color, ColorType.TRUECOLOR, triplet=triplet)
|
||||
|
||||
@lru_cache(maxsize=1024)
|
||||
def get_ansi_codes(self, foreground: bool = True) -> Tuple[str, ...]:
|
||||
"""Get the ANSI escape codes for this color."""
|
||||
_type = self.type
|
||||
if _type == ColorType.DEFAULT:
|
||||
return ("39" if foreground else "49",)
|
||||
|
||||
elif _type == ColorType.WINDOWS:
|
||||
number = self.number
|
||||
assert number is not None
|
||||
fore, back = (30, 40) if number < 8 else (82, 92)
|
||||
return (str(fore + number if foreground else back + number),)
|
||||
|
||||
elif _type == ColorType.STANDARD:
|
||||
number = self.number
|
||||
assert number is not None
|
||||
fore, back = (30, 40) if number < 8 else (82, 92)
|
||||
return (str(fore + number if foreground else back + number),)
|
||||
|
||||
elif _type == ColorType.EIGHT_BIT:
|
||||
assert self.number is not None
|
||||
return ("38" if foreground else "48", "5", str(self.number))
|
||||
|
||||
else: # self.standard == ColorStandard.TRUECOLOR:
|
||||
assert self.triplet is not None
|
||||
red, green, blue = self.triplet
|
||||
return ("38" if foreground else "48", "2", str(red), str(green), str(blue))
|
||||
|
||||
@lru_cache(maxsize=1024)
|
||||
def downgrade(self, system: ColorSystem) -> "Color":
|
||||
"""Downgrade a color system to a system with fewer colors."""
|
||||
|
||||
if self.type in [ColorType.DEFAULT, system]:
|
||||
return self
|
||||
# Convert to 8-bit color from truecolor color
|
||||
if system == ColorSystem.EIGHT_BIT and self.system == ColorSystem.TRUECOLOR:
|
||||
assert self.triplet is not None
|
||||
red, green, blue = self.triplet.normalized
|
||||
_h, l, s = rgb_to_hls(red, green, blue)
|
||||
# If saturation is under 10% assume it is grayscale
|
||||
if s < 0.1:
|
||||
gray = round(l * 25.0)
|
||||
if gray == 0:
|
||||
color_number = 16
|
||||
elif gray == 25:
|
||||
color_number = 231
|
||||
else:
|
||||
color_number = 231 + gray
|
||||
return Color(self.name, ColorType.EIGHT_BIT, number=color_number)
|
||||
|
||||
color_number = (
|
||||
16 + 36 * round(red * 5.0) + 6 * round(green * 5.0) + round(blue * 5.0)
|
||||
)
|
||||
return Color(self.name, ColorType.EIGHT_BIT, number=color_number)
|
||||
|
||||
# Convert to standard from truecolor or 8-bit
|
||||
elif system == ColorSystem.STANDARD:
|
||||
if self.system == ColorSystem.TRUECOLOR:
|
||||
assert self.triplet is not None
|
||||
triplet = self.triplet
|
||||
else: # self.system == ColorSystem.EIGHT_BIT
|
||||
assert self.number is not None
|
||||
triplet = ColorTriplet(*EIGHT_BIT_PALETTE[self.number])
|
||||
|
||||
color_number = STANDARD_PALETTE.match(triplet)
|
||||
return Color(self.name, ColorType.STANDARD, number=color_number)
|
||||
|
||||
elif system == ColorSystem.WINDOWS:
|
||||
if self.system == ColorSystem.TRUECOLOR:
|
||||
assert self.triplet is not None
|
||||
triplet = self.triplet
|
||||
else: # self.system == ColorSystem.EIGHT_BIT
|
||||
assert self.number is not None
|
||||
if self.number < 16:
|
||||
return Color(self.name, ColorType.WINDOWS, number=self.number)
|
||||
triplet = ColorTriplet(*EIGHT_BIT_PALETTE[self.number])
|
||||
|
||||
color_number = WINDOWS_PALETTE.match(triplet)
|
||||
return Color(self.name, ColorType.WINDOWS, number=color_number)
|
||||
|
||||
return self
|
||||
|
||||
|
||||
def parse_rgb_hex(hex_color: str) -> ColorTriplet:
|
||||
"""Parse six hex characters in to RGB triplet."""
|
||||
assert len(hex_color) == 6, "must be 6 characters"
|
||||
color = ColorTriplet(
|
||||
int(hex_color[0:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16)
|
||||
)
|
||||
return color
|
||||
|
||||
|
||||
def blend_rgb(
|
||||
color1: ColorTriplet, color2: ColorTriplet, cross_fade: float = 0.5
|
||||
) -> ColorTriplet:
|
||||
"""Blend one RGB color in to another."""
|
||||
r1, g1, b1 = color1
|
||||
r2, g2, b2 = color2
|
||||
new_color = ColorTriplet(
|
||||
int(r1 + (r2 - r1) * cross_fade),
|
||||
int(g1 + (g2 - g1) * cross_fade),
|
||||
int(b1 + (b2 - b1) * cross_fade),
|
||||
)
|
||||
return new_color
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
|
||||
from .console import Console
|
||||
from .table import Table
|
||||
from .text import Text
|
||||
|
||||
console = Console()
|
||||
|
||||
table = Table(show_footer=False, show_edge=True)
|
||||
table.add_column("Color", width=10, overflow="ellipsis")
|
||||
table.add_column("Number", justify="right", style="yellow")
|
||||
table.add_column("Name", style="green")
|
||||
table.add_column("Hex", style="blue")
|
||||
table.add_column("RGB", style="magenta")
|
||||
|
||||
colors = sorted((v, k) for k, v in ANSI_COLOR_NAMES.items())
|
||||
for color_number, name in colors:
|
||||
color_cell = Text(" " * 10, style=f"on {name}")
|
||||
if color_number < 16:
|
||||
table.add_row(color_cell, f"{color_number}", Text(f'"{name}"'))
|
||||
else:
|
||||
color = EIGHT_BIT_PALETTE[color_number] # type: ignore
|
||||
table.add_row(
|
||||
color_cell, str(color_number), Text(f'"{name}"'), color.hex, color.rgb
|
||||
)
|
||||
|
||||
console.print(table)
|
|
@ -0,0 +1,38 @@
|
|||
from typing import NamedTuple, Tuple
|
||||
|
||||
|
||||
class ColorTriplet(NamedTuple):
|
||||
"""The red, green, and blue components of a color."""
|
||||
|
||||
red: int
|
||||
"""Red component in 0 to 255 range."""
|
||||
green: int
|
||||
"""Green component in 0 to 255 range."""
|
||||
blue: int
|
||||
"""Blue component in 0 to 255 range."""
|
||||
|
||||
@property
|
||||
def hex(self) -> str:
|
||||
"""get the color triplet in CSS style."""
|
||||
red, green, blue = self
|
||||
return f"#{red:02x}{green:02x}{blue:02x}"
|
||||
|
||||
@property
|
||||
def rgb(self) -> str:
|
||||
"""The color in RGB format.
|
||||
|
||||
Returns:
|
||||
str: An rgb color, e.g. ``"rgb(100,23,255)"``.
|
||||
"""
|
||||
red, green, blue = self
|
||||
return f"rgb({red},{green},{blue})"
|
||||
|
||||
@property
|
||||
def normalized(self) -> Tuple[float, float, float]:
|
||||
"""Convert components into floats between 0 and 1.
|
||||
|
||||
Returns:
|
||||
Tuple[float, float, float]: A tuple of three normalized colour components.
|
||||
"""
|
||||
red, green, blue = self
|
||||
return red / 255.0, green / 255.0, blue / 255.0
|
|
@ -0,0 +1,187 @@
|
|||
from collections import defaultdict
|
||||
from itertools import chain
|
||||
from operator import itemgetter
|
||||
from typing import Dict, Iterable, List, Optional, Tuple
|
||||
|
||||
from .align import Align, AlignMethod
|
||||
from .console import Console, ConsoleOptions, RenderableType, RenderResult
|
||||
from .constrain import Constrain
|
||||
from .measure import Measurement
|
||||
from .padding import Padding, PaddingDimensions
|
||||
from .table import Table
|
||||
from .text import TextType
|
||||
from .jupyter import JupyterMixin
|
||||
|
||||
|
||||
class Columns(JupyterMixin):
|
||||
"""Display renderables in neat columns.
|
||||
|
||||
Args:
|
||||
renderables (Iterable[RenderableType]): Any number of Rich renderables (including str).
|
||||
width (int, optional): The desired width of the columns, or None to auto detect. Defaults to None.
|
||||
padding (PaddingDimensions, optional): Optional padding around cells. Defaults to (0, 1).
|
||||
expand (bool, optional): Expand columns to full width. Defaults to False.
|
||||
equal (bool, optional): Arrange in to equal sized columns. Defaults to False.
|
||||
column_first (bool, optional): Align items from top to bottom (rather than left to right). Defaults to False.
|
||||
right_to_left (bool, optional): Start column from right hand side. Defaults to False.
|
||||
align (str, optional): Align value ("left", "right", or "center") or None for default. Defaults to None.
|
||||
title (TextType, optional): Optional title for Columns.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
renderables: Optional[Iterable[RenderableType]] = None,
|
||||
padding: PaddingDimensions = (0, 1),
|
||||
*,
|
||||
width: Optional[int] = None,
|
||||
expand: bool = False,
|
||||
equal: bool = False,
|
||||
column_first: bool = False,
|
||||
right_to_left: bool = False,
|
||||
align: Optional[AlignMethod] = None,
|
||||
title: Optional[TextType] = None,
|
||||
) -> None:
|
||||
self.renderables = list(renderables or [])
|
||||
self.width = width
|
||||
self.padding = padding
|
||||
self.expand = expand
|
||||
self.equal = equal
|
||||
self.column_first = column_first
|
||||
self.right_to_left = right_to_left
|
||||
self.align: Optional[AlignMethod] = align
|
||||
self.title = title
|
||||
|
||||
def add_renderable(self, renderable: RenderableType) -> None:
|
||||
"""Add a renderable to the columns.
|
||||
|
||||
Args:
|
||||
renderable (RenderableType): Any renderable object.
|
||||
"""
|
||||
self.renderables.append(renderable)
|
||||
|
||||
def __rich_console__(
|
||||
self, console: Console, options: ConsoleOptions
|
||||
) -> RenderResult:
|
||||
render_str = console.render_str
|
||||
renderables = [
|
||||
render_str(renderable) if isinstance(renderable, str) else renderable
|
||||
for renderable in self.renderables
|
||||
]
|
||||
if not renderables:
|
||||
return
|
||||
_top, right, _bottom, left = Padding.unpack(self.padding)
|
||||
width_padding = max(left, right)
|
||||
max_width = options.max_width
|
||||
widths: Dict[int, int] = defaultdict(int)
|
||||
column_count = len(renderables)
|
||||
|
||||
get_measurement = Measurement.get
|
||||
renderable_widths = [
|
||||
get_measurement(console, options, renderable).maximum
|
||||
for renderable in renderables
|
||||
]
|
||||
if self.equal:
|
||||
renderable_widths = [max(renderable_widths)] * len(renderable_widths)
|
||||
|
||||
def iter_renderables(
|
||||
column_count: int,
|
||||
) -> Iterable[Tuple[int, Optional[RenderableType]]]:
|
||||
item_count = len(renderables)
|
||||
if self.column_first:
|
||||
width_renderables = list(zip(renderable_widths, renderables))
|
||||
|
||||
column_lengths: List[int] = [item_count // column_count] * column_count
|
||||
for col_no in range(item_count % column_count):
|
||||
column_lengths[col_no] += 1
|
||||
|
||||
row_count = (item_count + column_count - 1) // column_count
|
||||
cells = [[-1] * column_count for _ in range(row_count)]
|
||||
row = col = 0
|
||||
for index in range(item_count):
|
||||
cells[row][col] = index
|
||||
column_lengths[col] -= 1
|
||||
if column_lengths[col]:
|
||||
row += 1
|
||||
else:
|
||||
col += 1
|
||||
row = 0
|
||||
for index in chain.from_iterable(cells):
|
||||
if index == -1:
|
||||
break
|
||||
yield width_renderables[index]
|
||||
else:
|
||||
yield from zip(renderable_widths, renderables)
|
||||
# Pad odd elements with spaces
|
||||
if item_count % column_count:
|
||||
for _ in range(column_count - (item_count % column_count)):
|
||||
yield 0, None
|
||||
|
||||
table = Table.grid(padding=self.padding, collapse_padding=True, pad_edge=False)
|
||||
table.expand = self.expand
|
||||
table.title = self.title
|
||||
|
||||
if self.width is not None:
|
||||
column_count = (max_width) // (self.width + width_padding)
|
||||
for _ in range(column_count):
|
||||
table.add_column(width=self.width)
|
||||
else:
|
||||
while column_count > 1:
|
||||
widths.clear()
|
||||
column_no = 0
|
||||
for renderable_width, _ in iter_renderables(column_count):
|
||||
widths[column_no] = max(widths[column_no], renderable_width)
|
||||
total_width = sum(widths.values()) + width_padding * (
|
||||
len(widths) - 1
|
||||
)
|
||||
if total_width > max_width:
|
||||
column_count = len(widths) - 1
|
||||
break
|
||||
else:
|
||||
column_no = (column_no + 1) % column_count
|
||||
else:
|
||||
break
|
||||
|
||||
get_renderable = itemgetter(1)
|
||||
_renderables = [
|
||||
get_renderable(_renderable)
|
||||
for _renderable in iter_renderables(column_count)
|
||||
]
|
||||
if self.equal:
|
||||
_renderables = [
|
||||
None
|
||||
if renderable is None
|
||||
else Constrain(renderable, renderable_widths[0])
|
||||
for renderable in _renderables
|
||||
]
|
||||
if self.align:
|
||||
align = self.align
|
||||
_Align = Align
|
||||
_renderables = [
|
||||
None if renderable is None else _Align(renderable, align)
|
||||
for renderable in _renderables
|
||||
]
|
||||
|
||||
right_to_left = self.right_to_left
|
||||
add_row = table.add_row
|
||||
for start in range(0, len(_renderables), column_count):
|
||||
row = _renderables[start : start + column_count]
|
||||
if right_to_left:
|
||||
row = row[::-1]
|
||||
add_row(*row)
|
||||
yield table
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
import os
|
||||
|
||||
console = Console()
|
||||
|
||||
files = [f"{i} {s}" for i, s in enumerate(sorted(os.listdir()))]
|
||||
columns = Columns(files, padding=(0, 1), expand=False, equal=False)
|
||||
console.print(columns)
|
||||
console.rule()
|
||||
columns.column_first = True
|
||||
console.print(columns)
|
||||
columns.right_to_left = True
|
||||
console.rule()
|
||||
console.print(columns)
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,37 @@
|
|||
from typing import Optional, TYPE_CHECKING
|
||||
|
||||
from .jupyter import JupyterMixin
|
||||
from .measure import Measurement
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .console import Console, ConsoleOptions, RenderableType, RenderResult
|
||||
|
||||
|
||||
class Constrain(JupyterMixin):
|
||||
"""Constrain the width of a renderable to a given number of characters.
|
||||
|
||||
Args:
|
||||
renderable (RenderableType): A renderable object.
|
||||
width (int, optional): The maximum width (in characters) to render. Defaults to 80.
|
||||
"""
|
||||
|
||||
def __init__(self, renderable: "RenderableType", width: Optional[int] = 80) -> None:
|
||||
self.renderable = renderable
|
||||
self.width = width
|
||||
|
||||
def __rich_console__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> "RenderResult":
|
||||
if self.width is None:
|
||||
yield self.renderable
|
||||
else:
|
||||
child_options = options.update_width(min(self.width, options.max_width))
|
||||
yield from console.render(self.renderable, child_options)
|
||||
|
||||
def __rich_measure__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> "Measurement":
|
||||
if self.width is not None:
|
||||
options = options.update_width(self.width)
|
||||
measurement = Measurement.get(console, options, self.renderable)
|
||||
return measurement
|
|
@ -0,0 +1,167 @@
|
|||
from itertools import zip_longest
|
||||
from typing import (
|
||||
Iterator,
|
||||
Iterable,
|
||||
List,
|
||||
Optional,
|
||||
Union,
|
||||
overload,
|
||||
TypeVar,
|
||||
TYPE_CHECKING,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .console import (
|
||||
Console,
|
||||
ConsoleOptions,
|
||||
JustifyMethod,
|
||||
OverflowMethod,
|
||||
RenderResult,
|
||||
RenderableType,
|
||||
)
|
||||
from .text import Text
|
||||
|
||||
from .cells import cell_len
|
||||
from .measure import Measurement
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class Renderables:
|
||||
"""A list subclass which renders its contents to the console."""
|
||||
|
||||
def __init__(
|
||||
self, renderables: Optional[Iterable["RenderableType"]] = None
|
||||
) -> None:
|
||||
self._renderables: List["RenderableType"] = (
|
||||
list(renderables) if renderables is not None else []
|
||||
)
|
||||
|
||||
def __rich_console__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> "RenderResult":
|
||||
"""Console render method to insert line-breaks."""
|
||||
yield from self._renderables
|
||||
|
||||
def __rich_measure__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> "Measurement":
|
||||
dimensions = [
|
||||
Measurement.get(console, options, renderable)
|
||||
for renderable in self._renderables
|
||||
]
|
||||
if not dimensions:
|
||||
return Measurement(1, 1)
|
||||
_min = max(dimension.minimum for dimension in dimensions)
|
||||
_max = max(dimension.maximum for dimension in dimensions)
|
||||
return Measurement(_min, _max)
|
||||
|
||||
def append(self, renderable: "RenderableType") -> None:
|
||||
self._renderables.append(renderable)
|
||||
|
||||
def __iter__(self) -> Iterable["RenderableType"]:
|
||||
return iter(self._renderables)
|
||||
|
||||
|
||||
class Lines:
|
||||
"""A list subclass which can render to the console."""
|
||||
|
||||
def __init__(self, lines: Iterable["Text"] = ()) -> None:
|
||||
self._lines: List["Text"] = list(lines)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Lines({self._lines!r})"
|
||||
|
||||
def __iter__(self) -> Iterator["Text"]:
|
||||
return iter(self._lines)
|
||||
|
||||
@overload
|
||||
def __getitem__(self, index: int) -> "Text":
|
||||
...
|
||||
|
||||
@overload
|
||||
def __getitem__(self, index: slice) -> List["Text"]:
|
||||
...
|
||||
|
||||
def __getitem__(self, index: Union[slice, int]) -> Union["Text", List["Text"]]:
|
||||
return self._lines[index]
|
||||
|
||||
def __setitem__(self, index: int, value: "Text") -> "Lines":
|
||||
self._lines[index] = value
|
||||
return self
|
||||
|
||||
def __len__(self) -> int:
|
||||
return self._lines.__len__()
|
||||
|
||||
def __rich_console__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> "RenderResult":
|
||||
"""Console render method to insert line-breaks."""
|
||||
yield from self._lines
|
||||
|
||||
def append(self, line: "Text") -> None:
|
||||
self._lines.append(line)
|
||||
|
||||
def extend(self, lines: Iterable["Text"]) -> None:
|
||||
self._lines.extend(lines)
|
||||
|
||||
def pop(self, index: int = -1) -> "Text":
|
||||
return self._lines.pop(index)
|
||||
|
||||
def justify(
|
||||
self,
|
||||
console: "Console",
|
||||
width: int,
|
||||
justify: "JustifyMethod" = "left",
|
||||
overflow: "OverflowMethod" = "fold",
|
||||
) -> None:
|
||||
"""Justify and overflow text to a given width.
|
||||
|
||||
Args:
|
||||
console (Console): Console instance.
|
||||
width (int): Number of characters per line.
|
||||
justify (str, optional): Default justify method for text: "left", "center", "full" or "right". Defaults to "left".
|
||||
overflow (str, optional): Default overflow for text: "crop", "fold", or "ellipsis". Defaults to "fold".
|
||||
|
||||
"""
|
||||
from .text import Text
|
||||
|
||||
if justify == "left":
|
||||
for line in self._lines:
|
||||
line.truncate(width, overflow=overflow, pad=True)
|
||||
elif justify == "center":
|
||||
for line in self._lines:
|
||||
line.rstrip()
|
||||
line.truncate(width, overflow=overflow)
|
||||
line.pad_left((width - cell_len(line.plain)) // 2)
|
||||
line.pad_right(width - cell_len(line.plain))
|
||||
elif justify == "right":
|
||||
for line in self._lines:
|
||||
line.rstrip()
|
||||
line.truncate(width, overflow=overflow)
|
||||
line.pad_left(width - cell_len(line.plain))
|
||||
elif justify == "full":
|
||||
for line_index, line in enumerate(self._lines):
|
||||
if line_index == len(self._lines) - 1:
|
||||
break
|
||||
words = line.split(" ")
|
||||
words_size = sum(cell_len(word.plain) for word in words)
|
||||
num_spaces = len(words) - 1
|
||||
spaces = [1 for _ in range(num_spaces)]
|
||||
index = 0
|
||||
if spaces:
|
||||
while words_size + num_spaces < width:
|
||||
spaces[len(spaces) - index - 1] += 1
|
||||
num_spaces += 1
|
||||
index = (index + 1) % len(spaces)
|
||||
tokens: List[Text] = []
|
||||
for index, (word, next_word) in enumerate(
|
||||
zip_longest(words, words[1:])
|
||||
):
|
||||
tokens.append(word)
|
||||
if index < len(spaces):
|
||||
style = word.get_style_at_offset(console, -1)
|
||||
next_style = next_word.get_style_at_offset(console, 0)
|
||||
space_style = style if style == next_style else line.style
|
||||
tokens.append(Text(" " * spaces[index], style=space_style))
|
||||
self[line_index] = Text("").join(tokens)
|
|
@ -0,0 +1,175 @@
|
|||
from typing import Any, Callable, Dict, Iterable, List, TYPE_CHECKING, Union
|
||||
|
||||
from .segment import ControlCode, ControlType, Segment
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .console import Console, ConsoleOptions, RenderResult
|
||||
|
||||
STRIP_CONTROL_CODES = [
|
||||
8, # Backspace
|
||||
11, # Vertical tab
|
||||
12, # Form feed
|
||||
13, # Carriage return
|
||||
]
|
||||
_CONTROL_TRANSLATE = {_codepoint: None for _codepoint in STRIP_CONTROL_CODES}
|
||||
|
||||
|
||||
CONTROL_CODES_FORMAT: Dict[int, Callable[..., str]] = {
|
||||
ControlType.BELL: lambda: "\x07",
|
||||
ControlType.CARRIAGE_RETURN: lambda: "\r",
|
||||
ControlType.HOME: lambda: "\x1b[H",
|
||||
ControlType.CLEAR: lambda: "\x1b[2J",
|
||||
ControlType.ENABLE_ALT_SCREEN: lambda: "\x1b[?1049h",
|
||||
ControlType.DISABLE_ALT_SCREEN: lambda: "\x1b[?1049l",
|
||||
ControlType.SHOW_CURSOR: lambda: "\x1b[?25h",
|
||||
ControlType.HIDE_CURSOR: lambda: "\x1b[?25l",
|
||||
ControlType.CURSOR_UP: lambda param: f"\x1b[{param}A",
|
||||
ControlType.CURSOR_DOWN: lambda param: f"\x1b[{param}B",
|
||||
ControlType.CURSOR_FORWARD: lambda param: f"\x1b[{param}C",
|
||||
ControlType.CURSOR_BACKWARD: lambda param: f"\x1b[{param}D",
|
||||
ControlType.CURSOR_MOVE_TO_COLUMN: lambda param: f"\x1b[{param+1}G",
|
||||
ControlType.ERASE_IN_LINE: lambda param: f"\x1b[{param}K",
|
||||
ControlType.CURSOR_MOVE_TO: lambda x, y: f"\x1b[{y+1};{x+1}H",
|
||||
}
|
||||
|
||||
|
||||
class Control:
|
||||
"""A renderable that inserts a control code (non printable but may move cursor).
|
||||
|
||||
Args:
|
||||
*codes (str): Positional arguments are either a :class:`~rich.segment.ControlType` enum or a
|
||||
tuple of ControlType and an integer parameter
|
||||
"""
|
||||
|
||||
__slots__ = ["segment"]
|
||||
|
||||
def __init__(self, *codes: Union[ControlType, ControlCode]) -> None:
|
||||
control_codes: List[ControlCode] = [
|
||||
(code,) if isinstance(code, ControlType) else code for code in codes
|
||||
]
|
||||
_format_map = CONTROL_CODES_FORMAT
|
||||
rendered_codes = "".join(
|
||||
_format_map[code](*parameters) for code, *parameters in control_codes
|
||||
)
|
||||
self.segment = Segment(rendered_codes, None, control_codes)
|
||||
|
||||
@classmethod
|
||||
def bell(cls) -> "Control":
|
||||
"""Ring the 'bell'."""
|
||||
return cls(ControlType.BELL)
|
||||
|
||||
@classmethod
|
||||
def home(cls) -> "Control":
|
||||
"""Move cursor to 'home' position."""
|
||||
return cls(ControlType.HOME)
|
||||
|
||||
@classmethod
|
||||
def move(cls, x: int = 0, y: int = 0) -> "Control":
|
||||
"""Move cursor relative to current position.
|
||||
|
||||
Args:
|
||||
x (int): X offset.
|
||||
y (int): Y offset.
|
||||
|
||||
Returns:
|
||||
~Control: Control object.
|
||||
|
||||
"""
|
||||
|
||||
def get_codes() -> Iterable[ControlCode]:
|
||||
control = ControlType
|
||||
if x:
|
||||
yield (
|
||||
control.CURSOR_FORWARD if x > 0 else control.CURSOR_BACKWARD,
|
||||
abs(x),
|
||||
)
|
||||
if y:
|
||||
yield (
|
||||
control.CURSOR_DOWN if y > 0 else control.CURSOR_UP,
|
||||
abs(y),
|
||||
)
|
||||
|
||||
control = cls(*get_codes())
|
||||
return control
|
||||
|
||||
@classmethod
|
||||
def move_to_column(cls, x: int, y: int = 0) -> "Control":
|
||||
"""Move to the given column, optionally add offset to row.
|
||||
|
||||
Returns:
|
||||
x (int): absolute x (column)
|
||||
y (int): optional y offset (row)
|
||||
|
||||
Returns:
|
||||
~Control: Control object.
|
||||
"""
|
||||
|
||||
return (
|
||||
cls(
|
||||
(ControlType.CURSOR_MOVE_TO_COLUMN, x),
|
||||
(
|
||||
ControlType.CURSOR_DOWN if y > 0 else ControlType.CURSOR_UP,
|
||||
abs(y),
|
||||
),
|
||||
)
|
||||
if y
|
||||
else cls((ControlType.CURSOR_MOVE_TO_COLUMN, x))
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def move_to(cls, x: int, y: int) -> "Control":
|
||||
"""Move cursor to absolute position.
|
||||
|
||||
Args:
|
||||
x (int): x offset (column)
|
||||
y (int): y offset (row)
|
||||
|
||||
Returns:
|
||||
~Control: Control object.
|
||||
"""
|
||||
return cls((ControlType.CURSOR_MOVE_TO, x, y))
|
||||
|
||||
@classmethod
|
||||
def clear(cls) -> "Control":
|
||||
"""Clear the screen."""
|
||||
return cls(ControlType.CLEAR)
|
||||
|
||||
@classmethod
|
||||
def show_cursor(cls, show: bool) -> "Control":
|
||||
"""Show or hide the cursor."""
|
||||
return cls(ControlType.SHOW_CURSOR if show else ControlType.HIDE_CURSOR)
|
||||
|
||||
@classmethod
|
||||
def alt_screen(cls, enable: bool) -> "Control":
|
||||
"""Enable or disable alt screen."""
|
||||
if enable:
|
||||
return cls(ControlType.ENABLE_ALT_SCREEN, ControlType.HOME)
|
||||
else:
|
||||
return cls(ControlType.DISABLE_ALT_SCREEN)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.segment.text
|
||||
|
||||
def __rich_console__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> "RenderResult":
|
||||
if self.segment.text:
|
||||
yield self.segment
|
||||
|
||||
|
||||
def strip_control_codes(
|
||||
text: str, _translate_table: Dict[int, None] = _CONTROL_TRANSLATE
|
||||
) -> str:
|
||||
"""Remove control codes from text.
|
||||
|
||||
Args:
|
||||
text (str): A string possibly contain control codes.
|
||||
|
||||
Returns:
|
||||
str: String with control codes removed.
|
||||
"""
|
||||
return text.translate(_translate_table)
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
print(strip_control_codes("hello\rWorld"))
|
|
@ -0,0 +1,159 @@
|
|||
from typing import Dict
|
||||
|
||||
from .style import Style
|
||||
|
||||
|
||||
DEFAULT_STYLES: Dict[str, Style] = {
|
||||
"none": Style.null(),
|
||||
"reset": Style(
|
||||
color="default",
|
||||
bgcolor="default",
|
||||
dim=False,
|
||||
bold=False,
|
||||
italic=False,
|
||||
underline=False,
|
||||
blink=False,
|
||||
blink2=False,
|
||||
reverse=False,
|
||||
conceal=False,
|
||||
strike=False,
|
||||
),
|
||||
"dim": Style(dim=True),
|
||||
"bright": Style(dim=False),
|
||||
"bold": Style(bold=True),
|
||||
"strong": Style(bold=True),
|
||||
"code": Style(reverse=True, bold=True),
|
||||
"italic": Style(italic=True),
|
||||
"emphasize": Style(italic=True),
|
||||
"underline": Style(underline=True),
|
||||
"blink": Style(blink=True),
|
||||
"blink2": Style(blink2=True),
|
||||
"reverse": Style(reverse=True),
|
||||
"strike": Style(strike=True),
|
||||
"black": Style(color="black"),
|
||||
"red": Style(color="red"),
|
||||
"green": Style(color="green"),
|
||||
"yellow": Style(color="yellow"),
|
||||
"magenta": Style(color="magenta"),
|
||||
"cyan": Style(color="cyan"),
|
||||
"white": Style(color="white"),
|
||||
"inspect.attr": Style(color="yellow", italic=True),
|
||||
"inspect.attr.dunder": Style(color="yellow", italic=True, dim=True),
|
||||
"inspect.callable": Style(bold=True, color="red"),
|
||||
"inspect.def": Style(italic=True, color="bright_cyan"),
|
||||
"inspect.error": Style(bold=True, color="red"),
|
||||
"inspect.equals": Style(),
|
||||
"inspect.help": Style(color="cyan"),
|
||||
"inspect.doc": Style(dim=True),
|
||||
"inspect.value.border": Style(color="green"),
|
||||
"live.ellipsis": Style(bold=True, color="red"),
|
||||
"layout.tree.row": Style(dim=False, color="red"),
|
||||
"layout.tree.column": Style(dim=False, color="blue"),
|
||||
"logging.keyword": Style(bold=True, color="yellow"),
|
||||
"logging.level.notset": Style(dim=True),
|
||||
"logging.level.debug": Style(color="green"),
|
||||
"logging.level.info": Style(color="blue"),
|
||||
"logging.level.warning": Style(color="red"),
|
||||
"logging.level.error": Style(color="red", bold=True),
|
||||
"logging.level.critical": Style(color="red", bold=True, reverse=True),
|
||||
"log.level": Style.null(),
|
||||
"log.time": Style(color="cyan", dim=True),
|
||||
"log.message": Style.null(),
|
||||
"log.path": Style(dim=True),
|
||||
"repr.ellipsis": Style(color="yellow"),
|
||||
"repr.indent": Style(color="green", dim=True),
|
||||
"repr.error": Style(color="red", bold=True),
|
||||
"repr.str": Style(color="green", italic=False, bold=False),
|
||||
"repr.brace": Style(bold=True),
|
||||
"repr.comma": Style(bold=True),
|
||||
"repr.ipv4": Style(bold=True, color="bright_green"),
|
||||
"repr.ipv6": Style(bold=True, color="bright_green"),
|
||||
"repr.eui48": Style(bold=True, color="bright_green"),
|
||||
"repr.eui64": Style(bold=True, color="bright_green"),
|
||||
"repr.tag_start": Style(bold=True),
|
||||
"repr.tag_name": Style(color="bright_magenta", bold=True),
|
||||
"repr.tag_contents": Style(color="default"),
|
||||
"repr.tag_end": Style(bold=True),
|
||||
"repr.attrib_name": Style(color="yellow", italic=False),
|
||||
"repr.attrib_equal": Style(bold=True),
|
||||
"repr.attrib_value": Style(color="magenta", italic=False),
|
||||
"repr.number": Style(color="cyan", bold=True, italic=False),
|
||||
"repr.bool_true": Style(color="bright_green", italic=True),
|
||||
"repr.bool_false": Style(color="bright_red", italic=True),
|
||||
"repr.none": Style(color="magenta", italic=True),
|
||||
"repr.url": Style(underline=True, color="bright_blue", italic=False, bold=False),
|
||||
"repr.uuid": Style(color="bright_yellow", bold=False),
|
||||
"repr.call": Style(color="magenta", bold=True),
|
||||
"repr.path": Style(color="magenta"),
|
||||
"repr.filename": Style(color="bright_magenta"),
|
||||
"rule.line": Style(color="bright_green"),
|
||||
"rule.text": Style.null(),
|
||||
"json.brace": Style(bold=True),
|
||||
"json.bool_true": Style(color="bright_green", italic=True),
|
||||
"json.bool_false": Style(color="bright_red", italic=True),
|
||||
"json.null": Style(color="magenta", italic=True),
|
||||
"json.number": Style(color="cyan", bold=True, italic=False),
|
||||
"json.str": Style(color="green", italic=False, bold=False),
|
||||
"json.key": Style(color="blue", bold=True),
|
||||
"prompt": Style.null(),
|
||||
"prompt.choices": Style(color="magenta", bold=True),
|
||||
"prompt.default": Style(color="cyan", bold=True),
|
||||
"prompt.invalid": Style(color="red"),
|
||||
"prompt.invalid.choice": Style(color="red"),
|
||||
"pretty": Style.null(),
|
||||
"scope.border": Style(color="blue"),
|
||||
"scope.key": Style(color="yellow", italic=True),
|
||||
"scope.key.special": Style(color="yellow", italic=True, dim=True),
|
||||
"scope.equals": Style(color="red"),
|
||||
"table.header": Style(bold=True),
|
||||
"table.footer": Style(bold=True),
|
||||
"table.cell": Style.null(),
|
||||
"table.title": Style(italic=True),
|
||||
"table.caption": Style(italic=True, dim=True),
|
||||
"traceback.error": Style(color="red", italic=True),
|
||||
"traceback.border.syntax_error": Style(color="bright_red"),
|
||||
"traceback.border": Style(color="red"),
|
||||
"traceback.text": Style.null(),
|
||||
"traceback.title": Style(color="red", bold=True),
|
||||
"traceback.exc_type": Style(color="bright_red", bold=True),
|
||||
"traceback.exc_value": Style.null(),
|
||||
"traceback.offset": Style(color="bright_red", bold=True),
|
||||
"bar.back": Style(color="grey23"),
|
||||
"bar.complete": Style(color="rgb(249,38,114)"),
|
||||
"bar.finished": Style(color="rgb(114,156,31)"),
|
||||
"bar.pulse": Style(color="rgb(249,38,114)"),
|
||||
"progress.description": Style.null(),
|
||||
"progress.filesize": Style(color="green"),
|
||||
"progress.filesize.total": Style(color="green"),
|
||||
"progress.download": Style(color="green"),
|
||||
"progress.elapsed": Style(color="yellow"),
|
||||
"progress.percentage": Style(color="magenta"),
|
||||
"progress.remaining": Style(color="cyan"),
|
||||
"progress.data.speed": Style(color="red"),
|
||||
"progress.spinner": Style(color="green"),
|
||||
"status.spinner": Style(color="green"),
|
||||
"tree": Style(),
|
||||
"tree.line": Style(),
|
||||
"markdown.paragraph": Style(),
|
||||
"markdown.text": Style(),
|
||||
"markdown.emph": Style(italic=True),
|
||||
"markdown.strong": Style(bold=True),
|
||||
"markdown.code": Style(bgcolor="black", color="bright_white"),
|
||||
"markdown.code_block": Style(dim=True, color="cyan", bgcolor="black"),
|
||||
"markdown.block_quote": Style(color="magenta"),
|
||||
"markdown.list": Style(color="cyan"),
|
||||
"markdown.item": Style(),
|
||||
"markdown.item.bullet": Style(color="yellow", bold=True),
|
||||
"markdown.item.number": Style(color="yellow", bold=True),
|
||||
"markdown.hr": Style(color="yellow"),
|
||||
"markdown.h1.border": Style(),
|
||||
"markdown.h1": Style(bold=True),
|
||||
"markdown.h2": Style(bold=True, underline=True),
|
||||
"markdown.h3": Style(bold=True),
|
||||
"markdown.h4": Style(bold=True, dim=True),
|
||||
"markdown.h5": Style(underline=True),
|
||||
"markdown.h6": Style(italic=True),
|
||||
"markdown.h7": Style(italic=True, dim=True),
|
||||
"markdown.link": Style(color="bright_blue"),
|
||||
"markdown.link_url": Style(color="blue"),
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
if __name__ == "__main__": # pragma: no cover
|
||||
from pip._vendor.rich.console import Console
|
||||
from pip._vendor.rich import inspect
|
||||
|
||||
console = Console()
|
||||
inspect(console)
|
|
@ -0,0 +1,96 @@
|
|||
import sys
|
||||
from typing import TYPE_CHECKING, Optional, Union
|
||||
|
||||
from .jupyter import JupyterMixin
|
||||
from .segment import Segment
|
||||
from .style import Style
|
||||
from ._emoji_codes import EMOJI
|
||||
from ._emoji_replace import _emoji_replace
|
||||
|
||||
if sys.version_info >= (3, 8):
|
||||
from typing import Literal
|
||||
else:
|
||||
from pip._vendor.typing_extensions import Literal # pragma: no cover
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .console import Console, ConsoleOptions, RenderResult
|
||||
|
||||
|
||||
EmojiVariant = Literal["emoji", "text"]
|
||||
|
||||
|
||||
class NoEmoji(Exception):
|
||||
"""No emoji by that name."""
|
||||
|
||||
|
||||
class Emoji(JupyterMixin):
|
||||
__slots__ = ["name", "style", "_char", "variant"]
|
||||
|
||||
VARIANTS = {"text": "\uFE0E", "emoji": "\uFE0F"}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
style: Union[str, Style] = "none",
|
||||
variant: Optional[EmojiVariant] = None,
|
||||
) -> None:
|
||||
"""A single emoji character.
|
||||
|
||||
Args:
|
||||
name (str): Name of emoji.
|
||||
style (Union[str, Style], optional): Optional style. Defaults to None.
|
||||
|
||||
Raises:
|
||||
NoEmoji: If the emoji doesn't exist.
|
||||
"""
|
||||
self.name = name
|
||||
self.style = style
|
||||
self.variant = variant
|
||||
try:
|
||||
self._char = EMOJI[name]
|
||||
except KeyError:
|
||||
raise NoEmoji(f"No emoji called {name!r}")
|
||||
if variant is not None:
|
||||
self._char += self.VARIANTS.get(variant, "")
|
||||
|
||||
@classmethod
|
||||
def replace(cls, text: str) -> str:
|
||||
"""Replace emoji markup with corresponding unicode characters.
|
||||
|
||||
Args:
|
||||
text (str): A string with emojis codes, e.g. "Hello :smiley:!"
|
||||
|
||||
Returns:
|
||||
str: A string with emoji codes replaces with actual emoji.
|
||||
"""
|
||||
return _emoji_replace(text)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<emoji {self.name!r}>"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self._char
|
||||
|
||||
def __rich_console__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> "RenderResult":
|
||||
yield Segment(self._char, console.get_style(self.style))
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
import sys
|
||||
|
||||
from pip._vendor.rich.columns import Columns
|
||||
from pip._vendor.rich.console import Console
|
||||
|
||||
console = Console(record=True)
|
||||
|
||||
columns = Columns(
|
||||
(f":{name}: {name}" for name in sorted(EMOJI.keys()) if "\u200D" not in name),
|
||||
column_first=True,
|
||||
)
|
||||
|
||||
console.print(columns)
|
||||
if len(sys.argv) > 1:
|
||||
console.save_html(sys.argv[1])
|
|
@ -0,0 +1,34 @@
|
|||
class ConsoleError(Exception):
|
||||
"""An error in console operation."""
|
||||
|
||||
|
||||
class StyleError(Exception):
|
||||
"""An error in styles."""
|
||||
|
||||
|
||||
class StyleSyntaxError(ConsoleError):
|
||||
"""Style was badly formatted."""
|
||||
|
||||
|
||||
class MissingStyle(StyleError):
|
||||
"""No such style."""
|
||||
|
||||
|
||||
class StyleStackError(ConsoleError):
|
||||
"""Style stack is invalid."""
|
||||
|
||||
|
||||
class NotRenderableError(ConsoleError):
|
||||
"""Object is not renderable."""
|
||||
|
||||
|
||||
class MarkupError(ConsoleError):
|
||||
"""Markup was badly formatted."""
|
||||
|
||||
|
||||
class LiveError(ConsoleError):
|
||||
"""Error related to Live display."""
|
||||
|
||||
|
||||
class NoAltScreen(ConsoleError):
|
||||
"""Alt screen mode was required."""
|
|
@ -0,0 +1,54 @@
|
|||
import io
|
||||
from typing import List, Any, IO, TYPE_CHECKING
|
||||
|
||||
from .ansi import AnsiDecoder
|
||||
from .text import Text
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .console import Console
|
||||
|
||||
|
||||
class FileProxy(io.TextIOBase):
|
||||
"""Wraps a file (e.g. sys.stdout) and redirects writes to a console."""
|
||||
|
||||
def __init__(self, console: "Console", file: IO[str]) -> None:
|
||||
self.__console = console
|
||||
self.__file = file
|
||||
self.__buffer: List[str] = []
|
||||
self.__ansi_decoder = AnsiDecoder()
|
||||
|
||||
@property
|
||||
def rich_proxied_file(self) -> IO[str]:
|
||||
"""Get proxied file."""
|
||||
return self.__file
|
||||
|
||||
def __getattr__(self, name: str) -> Any:
|
||||
return getattr(self.__file, name)
|
||||
|
||||
def write(self, text: str) -> int:
|
||||
if not isinstance(text, str):
|
||||
raise TypeError(f"write() argument must be str, not {type(text).__name__}")
|
||||
buffer = self.__buffer
|
||||
lines: List[str] = []
|
||||
while text:
|
||||
line, new_line, text = text.partition("\n")
|
||||
if new_line:
|
||||
lines.append("".join(buffer) + line)
|
||||
del buffer[:]
|
||||
else:
|
||||
buffer.append(line)
|
||||
break
|
||||
if lines:
|
||||
console = self.__console
|
||||
with console:
|
||||
output = Text("\n").join(
|
||||
self.__ansi_decoder.decode_line(line) for line in lines
|
||||
)
|
||||
console.print(output, markup=False, emoji=False, highlight=False)
|
||||
return len(text)
|
||||
|
||||
def flush(self) -> None:
|
||||
buffer = self.__buffer
|
||||
if buffer:
|
||||
self.__console.print("".join(buffer))
|
||||
del buffer[:]
|
|
@ -0,0 +1,89 @@
|
|||
# coding: utf-8
|
||||
"""Functions for reporting filesizes. Borrowed from https://github.com/PyFilesystem/pyfilesystem2
|
||||
|
||||
The functions declared in this module should cover the different
|
||||
usecases needed to generate a string representation of a file size
|
||||
using several different units. Since there are many standards regarding
|
||||
file size units, three different functions have been implemented.
|
||||
|
||||
See Also:
|
||||
* `Wikipedia: Binary prefix <https://en.wikipedia.org/wiki/Binary_prefix>`_
|
||||
|
||||
"""
|
||||
|
||||
__all__ = ["decimal"]
|
||||
|
||||
from typing import Iterable, List, Tuple, Optional
|
||||
|
||||
|
||||
def _to_str(
|
||||
size: int,
|
||||
suffixes: Iterable[str],
|
||||
base: int,
|
||||
*,
|
||||
precision: Optional[int] = 1,
|
||||
separator: Optional[str] = " ",
|
||||
) -> str:
|
||||
if size == 1:
|
||||
return "1 byte"
|
||||
elif size < base:
|
||||
return "{:,} bytes".format(size)
|
||||
|
||||
for i, suffix in enumerate(suffixes, 2): # noqa: B007
|
||||
unit = base ** i
|
||||
if size < unit:
|
||||
break
|
||||
return "{:,.{precision}f}{separator}{}".format(
|
||||
(base * size / unit),
|
||||
suffix,
|
||||
precision=precision,
|
||||
separator=separator,
|
||||
)
|
||||
|
||||
|
||||
def pick_unit_and_suffix(size: int, suffixes: List[str], base: int) -> Tuple[int, str]:
|
||||
"""Pick a suffix and base for the given size."""
|
||||
for i, suffix in enumerate(suffixes):
|
||||
unit = base ** i
|
||||
if size < unit * base:
|
||||
break
|
||||
return unit, suffix
|
||||
|
||||
|
||||
def decimal(
|
||||
size: int,
|
||||
*,
|
||||
precision: Optional[int] = 1,
|
||||
separator: Optional[str] = " ",
|
||||
) -> str:
|
||||
"""Convert a filesize in to a string (powers of 1000, SI prefixes).
|
||||
|
||||
In this convention, ``1000 B = 1 kB``.
|
||||
|
||||
This is typically the format used to advertise the storage
|
||||
capacity of USB flash drives and the like (*256 MB* meaning
|
||||
actually a storage capacity of more than *256 000 000 B*),
|
||||
or used by **Mac OS X** since v10.6 to report file sizes.
|
||||
|
||||
Arguments:
|
||||
int (size): A file size.
|
||||
int (precision): The number of decimal places to include (default = 1).
|
||||
str (separator): The string to separate the value from the units (default = " ").
|
||||
|
||||
Returns:
|
||||
`str`: A string containing a abbreviated file size and units.
|
||||
|
||||
Example:
|
||||
>>> filesize.decimal(30000)
|
||||
'30.0 kB'
|
||||
>>> filesize.decimal(30000, precision=2, separator="")
|
||||
'30.00kB'
|
||||
|
||||
"""
|
||||
return _to_str(
|
||||
size,
|
||||
("kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"),
|
||||
1000,
|
||||
precision=precision,
|
||||
separator=separator,
|
||||
)
|
|
@ -0,0 +1,147 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from typing import List, Union
|
||||
|
||||
from .text import Text
|
||||
|
||||
|
||||
def _combine_regex(*regexes: str) -> str:
|
||||
"""Combine a number of regexes in to a single regex.
|
||||
|
||||
Returns:
|
||||
str: New regex with all regexes ORed together.
|
||||
"""
|
||||
return "|".join(regexes)
|
||||
|
||||
|
||||
class Highlighter(ABC):
|
||||
"""Abstract base class for highlighters."""
|
||||
|
||||
def __call__(self, text: Union[str, Text]) -> Text:
|
||||
"""Highlight a str or Text instance.
|
||||
|
||||
Args:
|
||||
text (Union[str, ~Text]): Text to highlight.
|
||||
|
||||
Raises:
|
||||
TypeError: If not called with text or str.
|
||||
|
||||
Returns:
|
||||
Text: A test instance with highlighting applied.
|
||||
"""
|
||||
if isinstance(text, str):
|
||||
highlight_text = Text(text)
|
||||
elif isinstance(text, Text):
|
||||
highlight_text = text.copy()
|
||||
else:
|
||||
raise TypeError(f"str or Text instance required, not {text!r}")
|
||||
self.highlight(highlight_text)
|
||||
return highlight_text
|
||||
|
||||
@abstractmethod
|
||||
def highlight(self, text: Text) -> None:
|
||||
"""Apply highlighting in place to text.
|
||||
|
||||
Args:
|
||||
text (~Text): A text object highlight.
|
||||
"""
|
||||
|
||||
|
||||
class NullHighlighter(Highlighter):
|
||||
"""A highlighter object that doesn't highlight.
|
||||
|
||||
May be used to disable highlighting entirely.
|
||||
|
||||
"""
|
||||
|
||||
def highlight(self, text: Text) -> None:
|
||||
"""Nothing to do"""
|
||||
|
||||
|
||||
class RegexHighlighter(Highlighter):
|
||||
"""Applies highlighting from a list of regular expressions."""
|
||||
|
||||
highlights: List[str] = []
|
||||
base_style: str = ""
|
||||
|
||||
def highlight(self, text: Text) -> None:
|
||||
"""Highlight :class:`rich.text.Text` using regular expressions.
|
||||
|
||||
Args:
|
||||
text (~Text): Text to highlighted.
|
||||
|
||||
"""
|
||||
|
||||
highlight_regex = text.highlight_regex
|
||||
for re_highlight in self.highlights:
|
||||
highlight_regex(re_highlight, style_prefix=self.base_style)
|
||||
|
||||
|
||||
class ReprHighlighter(RegexHighlighter):
|
||||
"""Highlights the text typically produced from ``__repr__`` methods."""
|
||||
|
||||
base_style = "repr."
|
||||
highlights = [
|
||||
r"(?P<tag_start>\<)(?P<tag_name>[\w\-\.\:]*)(?P<tag_contents>[\w\W]*?)(?P<tag_end>\>)",
|
||||
r"(?P<attrib_name>[\w_]{1,50})=(?P<attrib_value>\"?[\w_]+\"?)?",
|
||||
r"(?P<brace>[\{\[\(\)\]\}])",
|
||||
_combine_regex(
|
||||
r"(?P<ipv4>[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})",
|
||||
r"(?P<ipv6>([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})",
|
||||
r"(?P<eui64>(?:[0-9A-Fa-f]{1,2}-){7}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{1,2}:){7}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{4}\.){3}[0-9A-Fa-f]{4})",
|
||||
r"(?P<eui48>(?:[0-9A-Fa-f]{1,2}-){5}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{1,2}:){5}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{4}\.){2}[0-9A-Fa-f]{4})",
|
||||
r"(?P<call>[\w\.]*?)\(",
|
||||
r"\b(?P<bool_true>True)\b|\b(?P<bool_false>False)\b|\b(?P<none>None)\b",
|
||||
r"(?P<ellipsis>\.\.\.)",
|
||||
r"(?P<number>(?<!\w)\-?[0-9]+\.?[0-9]*(e[\-\+]?\d+?)?\b|0x[0-9a-fA-F]*)",
|
||||
r"(?P<path>\B(\/[\w\.\-\_\+]+)*\/)(?P<filename>[\w\.\-\_\+]*)?",
|
||||
r"(?<![\\\w])(?P<str>b?\'\'\'.*?(?<!\\)\'\'\'|b?\'.*?(?<!\\)\'|b?\"\"\".*?(?<!\\)\"\"\"|b?\".*?(?<!\\)\")",
|
||||
r"(?P<uuid>[a-fA-F0-9]{8}\-[a-fA-F0-9]{4}\-[a-fA-F0-9]{4}\-[a-fA-F0-9]{4}\-[a-fA-F0-9]{12})",
|
||||
r"(?P<url>(file|https|http|ws|wss):\/\/[0-9a-zA-Z\$\-\_\+\!`\(\)\,\.\?\/\;\:\&\=\%\#]*)",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
class JSONHighlighter(RegexHighlighter):
|
||||
"""Highlights JSON"""
|
||||
|
||||
base_style = "json."
|
||||
highlights = [
|
||||
_combine_regex(
|
||||
r"(?P<brace>[\{\[\(\)\]\}])",
|
||||
r"\b(?P<bool_true>true)\b|\b(?P<bool_false>false)\b|\b(?P<null>null)\b",
|
||||
r"(?P<number>(?<!\w)\-?[0-9]+\.?[0-9]*(e[\-\+]?\d+?)?\b|0x[0-9a-fA-F]*)",
|
||||
r"(?<![\\\w])(?P<str>b?\".*?(?<!\\)\")",
|
||||
),
|
||||
r"(?<![\\\w])(?P<key>b?\".*?(?<!\\)\")\:",
|
||||
]
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
from .console import Console
|
||||
|
||||
console = Console()
|
||||
console.print("[bold green]hello world![/bold green]")
|
||||
console.print("'[bold green]hello world![/bold green]'")
|
||||
|
||||
console.print(" /foo")
|
||||
console.print("/foo/")
|
||||
console.print("/foo/bar")
|
||||
console.print("foo/bar/baz")
|
||||
|
||||
console.print("/foo/bar/baz?foo=bar+egg&egg=baz")
|
||||
console.print("/foo/bar/baz/")
|
||||
console.print("/foo/bar/baz/egg")
|
||||
console.print("/foo/bar/baz/egg.py")
|
||||
console.print("/foo/bar/baz/egg.py word")
|
||||
console.print(" /foo/bar/baz/egg.py word")
|
||||
console.print("foo /foo/bar/baz/egg.py word")
|
||||
console.print("foo /foo/bar/ba._++z/egg+.py word")
|
||||
console.print("https://example.org?foo=bar#header")
|
||||
|
||||
console.print(1234567.34)
|
||||
console.print(1 / 2)
|
||||
console.print(-1 / 123123123123)
|
||||
|
||||
console.print(
|
||||
"127.0.1.1 bar 192.168.1.4 2001:0db8:85a3:0000:0000:8a2e:0370:7334 foo"
|
||||
)
|
|
@ -0,0 +1,140 @@
|
|||
from json import loads, dumps
|
||||
from typing import Any, Callable, Optional, Union
|
||||
|
||||
from .text import Text
|
||||
from .highlighter import JSONHighlighter, NullHighlighter
|
||||
|
||||
|
||||
class JSON:
|
||||
"""A renderable which pretty prints JSON.
|
||||
|
||||
Args:
|
||||
json (str): JSON encoded data.
|
||||
indent (Union[None, int, str], optional): Number of characters to indent by. Defaults to 2.
|
||||
highlight (bool, optional): Enable highlighting. Defaults to True.
|
||||
skip_keys (bool, optional): Skip keys not of a basic type. Defaults to False.
|
||||
ensure_ascii (bool, optional): Escape all non-ascii characters. Defaults to False.
|
||||
check_circular (bool, optional): Check for circular references. Defaults to True.
|
||||
allow_nan (bool, optional): Allow NaN and Infinity values. Defaults to True.
|
||||
default (Callable, optional): A callable that converts values that can not be encoded
|
||||
in to something that can be JSON encoded. Defaults to None.
|
||||
sort_keys (bool, optional): Sort dictionary keys. Defaults to False.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
json: str,
|
||||
indent: Union[None, int, str] = 2,
|
||||
highlight: bool = True,
|
||||
skip_keys: bool = False,
|
||||
ensure_ascii: bool = True,
|
||||
check_circular: bool = True,
|
||||
allow_nan: bool = True,
|
||||
default: Optional[Callable[[Any], Any]] = None,
|
||||
sort_keys: bool = False,
|
||||
) -> None:
|
||||
data = loads(json)
|
||||
json = dumps(
|
||||
data,
|
||||
indent=indent,
|
||||
skipkeys=skip_keys,
|
||||
ensure_ascii=ensure_ascii,
|
||||
check_circular=check_circular,
|
||||
allow_nan=allow_nan,
|
||||
default=default,
|
||||
sort_keys=sort_keys,
|
||||
)
|
||||
highlighter = JSONHighlighter() if highlight else NullHighlighter()
|
||||
self.text = highlighter(json)
|
||||
self.text.no_wrap = True
|
||||
self.text.overflow = None
|
||||
|
||||
@classmethod
|
||||
def from_data(
|
||||
cls,
|
||||
data: Any,
|
||||
indent: Union[None, int, str] = 2,
|
||||
highlight: bool = True,
|
||||
skip_keys: bool = False,
|
||||
ensure_ascii: bool = True,
|
||||
check_circular: bool = True,
|
||||
allow_nan: bool = True,
|
||||
default: Optional[Callable[[Any], Any]] = None,
|
||||
sort_keys: bool = False,
|
||||
) -> "JSON":
|
||||
"""Encodes a JSON object from arbitrary data.
|
||||
|
||||
Args:
|
||||
data (Any): An object that may be encoded in to JSON
|
||||
indent (Union[None, int, str], optional): Number of characters to indent by. Defaults to 2.
|
||||
highlight (bool, optional): Enable highlighting. Defaults to True.
|
||||
default (Callable, optional): Optional callable which will be called for objects that cannot be serialized. Defaults to None.
|
||||
skip_keys (bool, optional): Skip keys not of a basic type. Defaults to False.
|
||||
ensure_ascii (bool, optional): Escape all non-ascii characters. Defaults to False.
|
||||
check_circular (bool, optional): Check for circular references. Defaults to True.
|
||||
allow_nan (bool, optional): Allow NaN and Infinity values. Defaults to True.
|
||||
default (Callable, optional): A callable that converts values that can not be encoded
|
||||
in to something that can be JSON encoded. Defaults to None.
|
||||
sort_keys (bool, optional): Sort dictionary keys. Defaults to False.
|
||||
|
||||
Returns:
|
||||
JSON: New JSON object from the given data.
|
||||
"""
|
||||
json_instance: "JSON" = cls.__new__(cls)
|
||||
json = dumps(
|
||||
data,
|
||||
indent=indent,
|
||||
skipkeys=skip_keys,
|
||||
ensure_ascii=ensure_ascii,
|
||||
check_circular=check_circular,
|
||||
allow_nan=allow_nan,
|
||||
default=default,
|
||||
sort_keys=sort_keys,
|
||||
)
|
||||
highlighter = JSONHighlighter() if highlight else NullHighlighter()
|
||||
json_instance.text = highlighter(json)
|
||||
json_instance.text.no_wrap = True
|
||||
json_instance.text.overflow = None
|
||||
return json_instance
|
||||
|
||||
def __rich__(self) -> Text:
|
||||
return self.text
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
parser = argparse.ArgumentParser(description="Pretty print json")
|
||||
parser.add_argument(
|
||||
"path",
|
||||
metavar="PATH",
|
||||
help="path to file, or - for stdin",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-i",
|
||||
"--indent",
|
||||
metavar="SPACES",
|
||||
type=int,
|
||||
help="Number of spaces in an indent",
|
||||
default=2,
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
from pip._vendor.rich.console import Console
|
||||
|
||||
console = Console()
|
||||
error_console = Console(stderr=True)
|
||||
|
||||
try:
|
||||
if args.path == "-":
|
||||
json_data = sys.stdin.read()
|
||||
else:
|
||||
with open(args.path, "rt") as json_file:
|
||||
json_data = json_file.read()
|
||||
except Exception as error:
|
||||
error_console.print(f"Unable to read {args.path!r}; {error}")
|
||||
sys.exit(-1)
|
||||
|
||||
console.print(JSON(json_data, indent=args.indent), soft_wrap=True)
|
|
@ -0,0 +1,88 @@
|
|||
from typing import Any, Dict, Iterable, List
|
||||
|
||||
from . import get_console
|
||||
from .segment import Segment
|
||||
from .terminal_theme import DEFAULT_TERMINAL_THEME
|
||||
|
||||
|
||||
JUPYTER_HTML_FORMAT = """\
|
||||
<pre style="white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace">{code}</pre>
|
||||
"""
|
||||
|
||||
|
||||
class JupyterRenderable:
|
||||
"""A shim to write html to Jupyter notebook."""
|
||||
|
||||
def __init__(self, html: str, text: str) -> None:
|
||||
self.html = html
|
||||
self.text = text
|
||||
|
||||
def _repr_mimebundle_(
|
||||
self, include: Iterable[str], exclude: Iterable[str], **kwargs: Any
|
||||
) -> Dict[str, str]:
|
||||
data = {"text/plain": self.text, "text/html": self.html}
|
||||
if include:
|
||||
data = {k: v for (k, v) in data.items() if k in include}
|
||||
if exclude:
|
||||
data = {k: v for (k, v) in data.items() if k not in exclude}
|
||||
return data
|
||||
|
||||
|
||||
class JupyterMixin:
|
||||
"""Add to an Rich renderable to make it render in Jupyter notebook."""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def _repr_mimebundle_(
|
||||
self, include: Iterable[str], exclude: Iterable[str], **kwargs: Any
|
||||
) -> Dict[str, str]:
|
||||
console = get_console()
|
||||
segments = list(console.render(self, console.options)) # type: ignore
|
||||
html = _render_segments(segments)
|
||||
text = console._render_buffer(segments)
|
||||
data = {"text/plain": text, "text/html": html}
|
||||
if include:
|
||||
data = {k: v for (k, v) in data.items() if k in include}
|
||||
if exclude:
|
||||
data = {k: v for (k, v) in data.items() if k not in exclude}
|
||||
return data
|
||||
|
||||
|
||||
def _render_segments(segments: Iterable[Segment]) -> str:
|
||||
def escape(text: str) -> str:
|
||||
"""Escape html."""
|
||||
return text.replace("&", "&").replace("<", "<").replace(">", ">")
|
||||
|
||||
fragments: List[str] = []
|
||||
append_fragment = fragments.append
|
||||
theme = DEFAULT_TERMINAL_THEME
|
||||
for text, style, control in Segment.simplify(segments):
|
||||
if control:
|
||||
continue
|
||||
text = escape(text)
|
||||
if style:
|
||||
rule = style.get_html_style(theme)
|
||||
text = f'<span style="{rule}">{text}</span>' if rule else text
|
||||
if style.link:
|
||||
text = f'<a href="{style.link}">{text}</a>'
|
||||
append_fragment(text)
|
||||
|
||||
code = "".join(fragments)
|
||||
html = JUPYTER_HTML_FORMAT.format(code=code)
|
||||
|
||||
return html
|
||||
|
||||
|
||||
def display(segments: Iterable[Segment], text: str) -> None:
|
||||
"""Render segments to Jupyter."""
|
||||
from IPython.display import display as ipython_display
|
||||
|
||||
html = _render_segments(segments)
|
||||
jupyter_renderable = JupyterRenderable(html, text)
|
||||
ipython_display(jupyter_renderable)
|
||||
|
||||
|
||||
def print(*args: Any, **kwargs: Any) -> None:
|
||||
"""Proxy for Console print."""
|
||||
console = get_console()
|
||||
return console.print(*args, **kwargs)
|
|
@ -0,0 +1,444 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from itertools import islice
|
||||
from operator import itemgetter
|
||||
from threading import RLock
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Dict,
|
||||
Iterable,
|
||||
List,
|
||||
NamedTuple,
|
||||
Optional,
|
||||
Sequence,
|
||||
Tuple,
|
||||
Union,
|
||||
)
|
||||
|
||||
from ._ratio import ratio_resolve
|
||||
from .align import Align
|
||||
from .console import Console, ConsoleOptions, RenderableType, RenderResult
|
||||
from .highlighter import ReprHighlighter
|
||||
from .panel import Panel
|
||||
from .pretty import Pretty
|
||||
from .repr import rich_repr, Result
|
||||
from .region import Region
|
||||
from .segment import Segment
|
||||
from .style import StyleType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pip._vendor.rich.tree import Tree
|
||||
|
||||
|
||||
class LayoutRender(NamedTuple):
|
||||
"""An individual layout render."""
|
||||
|
||||
region: Region
|
||||
render: List[List[Segment]]
|
||||
|
||||
|
||||
RegionMap = Dict["Layout", Region]
|
||||
RenderMap = Dict["Layout", LayoutRender]
|
||||
|
||||
|
||||
class LayoutError(Exception):
|
||||
"""Layout related error."""
|
||||
|
||||
|
||||
class NoSplitter(LayoutError):
|
||||
"""Requested splitter does not exist."""
|
||||
|
||||
|
||||
class _Placeholder:
|
||||
"""An internal renderable used as a Layout placeholder."""
|
||||
|
||||
highlighter = ReprHighlighter()
|
||||
|
||||
def __init__(self, layout: "Layout", style: StyleType = "") -> None:
|
||||
self.layout = layout
|
||||
self.style = style
|
||||
|
||||
def __rich_console__(
|
||||
self, console: Console, options: ConsoleOptions
|
||||
) -> RenderResult:
|
||||
width = options.max_width
|
||||
height = options.height or options.size.height
|
||||
layout = self.layout
|
||||
title = (
|
||||
f"{layout.name!r} ({width} x {height})"
|
||||
if layout.name
|
||||
else f"({width} x {height})"
|
||||
)
|
||||
yield Panel(
|
||||
Align.center(Pretty(layout), vertical="middle"),
|
||||
style=self.style,
|
||||
title=self.highlighter(title),
|
||||
border_style="blue",
|
||||
)
|
||||
|
||||
|
||||
class Splitter(ABC):
|
||||
"""Base class for a splitter."""
|
||||
|
||||
name: str = ""
|
||||
|
||||
@abstractmethod
|
||||
def get_tree_icon(self) -> str:
|
||||
"""Get the icon (emoji) used in layout.tree"""
|
||||
|
||||
@abstractmethod
|
||||
def divide(
|
||||
self, children: Sequence["Layout"], region: Region
|
||||
) -> Iterable[Tuple["Layout", Region]]:
|
||||
"""Divide a region amongst several child layouts.
|
||||
|
||||
Args:
|
||||
children (Sequence(Layout)): A number of child layouts.
|
||||
region (Region): A rectangular region to divide.
|
||||
"""
|
||||
|
||||
|
||||
class RowSplitter(Splitter):
|
||||
"""Split a layout region in to rows."""
|
||||
|
||||
name = "row"
|
||||
|
||||
def get_tree_icon(self) -> str:
|
||||
return "[layout.tree.row]⬌"
|
||||
|
||||
def divide(
|
||||
self, children: Sequence["Layout"], region: Region
|
||||
) -> Iterable[Tuple["Layout", Region]]:
|
||||
x, y, width, height = region
|
||||
render_widths = ratio_resolve(width, children)
|
||||
offset = 0
|
||||
_Region = Region
|
||||
for child, child_width in zip(children, render_widths):
|
||||
yield child, _Region(x + offset, y, child_width, height)
|
||||
offset += child_width
|
||||
|
||||
|
||||
class ColumnSplitter(Splitter):
|
||||
"""Split a layout region in to columns."""
|
||||
|
||||
name = "column"
|
||||
|
||||
def get_tree_icon(self) -> str:
|
||||
return "[layout.tree.column]⬍"
|
||||
|
||||
def divide(
|
||||
self, children: Sequence["Layout"], region: Region
|
||||
) -> Iterable[Tuple["Layout", Region]]:
|
||||
x, y, width, height = region
|
||||
render_heights = ratio_resolve(height, children)
|
||||
offset = 0
|
||||
_Region = Region
|
||||
for child, child_height in zip(children, render_heights):
|
||||
yield child, _Region(x, y + offset, width, child_height)
|
||||
offset += child_height
|
||||
|
||||
|
||||
@rich_repr
|
||||
class Layout:
|
||||
"""A renderable to divide a fixed height in to rows or columns.
|
||||
|
||||
Args:
|
||||
renderable (RenderableType, optional): Renderable content, or None for placeholder. Defaults to None.
|
||||
name (str, optional): Optional identifier for Layout. Defaults to None.
|
||||
size (int, optional): Optional fixed size of layout. Defaults to None.
|
||||
minimum_size (int, optional): Minimum size of layout. Defaults to 1.
|
||||
ratio (int, optional): Optional ratio for flexible layout. Defaults to 1.
|
||||
visible (bool, optional): Visibility of layout. Defaults to True.
|
||||
"""
|
||||
|
||||
splitters = {"row": RowSplitter, "column": ColumnSplitter}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
renderable: Optional[RenderableType] = None,
|
||||
*,
|
||||
name: Optional[str] = None,
|
||||
size: Optional[int] = None,
|
||||
minimum_size: int = 1,
|
||||
ratio: int = 1,
|
||||
visible: bool = True,
|
||||
height: Optional[int] = None,
|
||||
) -> None:
|
||||
self._renderable = renderable or _Placeholder(self)
|
||||
self.size = size
|
||||
self.minimum_size = minimum_size
|
||||
self.ratio = ratio
|
||||
self.name = name
|
||||
self.visible = visible
|
||||
self.height = height
|
||||
self.splitter: Splitter = self.splitters["column"]()
|
||||
self._children: List[Layout] = []
|
||||
self._render_map: RenderMap = {}
|
||||
self._lock = RLock()
|
||||
|
||||
def __rich_repr__(self) -> Result:
|
||||
yield "name", self.name, None
|
||||
yield "size", self.size, None
|
||||
yield "minimum_size", self.minimum_size, 1
|
||||
yield "ratio", self.ratio, 1
|
||||
|
||||
@property
|
||||
def renderable(self) -> RenderableType:
|
||||
"""Layout renderable."""
|
||||
return self if self._children else self._renderable
|
||||
|
||||
@property
|
||||
def children(self) -> List["Layout"]:
|
||||
"""Gets (visible) layout children."""
|
||||
return [child for child in self._children if child.visible]
|
||||
|
||||
@property
|
||||
def map(self) -> RenderMap:
|
||||
"""Get a map of the last render."""
|
||||
return self._render_map
|
||||
|
||||
def get(self, name: str) -> Optional["Layout"]:
|
||||
"""Get a named layout, or None if it doesn't exist.
|
||||
|
||||
Args:
|
||||
name (str): Name of layout.
|
||||
|
||||
Returns:
|
||||
Optional[Layout]: Layout instance or None if no layout was found.
|
||||
"""
|
||||
if self.name == name:
|
||||
return self
|
||||
else:
|
||||
for child in self._children:
|
||||
named_layout = child.get(name)
|
||||
if named_layout is not None:
|
||||
return named_layout
|
||||
return None
|
||||
|
||||
def __getitem__(self, name: str) -> "Layout":
|
||||
layout = self.get(name)
|
||||
if layout is None:
|
||||
raise KeyError(f"No layout with name {name!r}")
|
||||
return layout
|
||||
|
||||
@property
|
||||
def tree(self) -> "Tree":
|
||||
"""Get a tree renderable to show layout structure."""
|
||||
from pip._vendor.rich.styled import Styled
|
||||
from pip._vendor.rich.table import Table
|
||||
from pip._vendor.rich.tree import Tree
|
||||
|
||||
def summary(layout: "Layout") -> Table:
|
||||
|
||||
icon = layout.splitter.get_tree_icon()
|
||||
|
||||
table = Table.grid(padding=(0, 1, 0, 0))
|
||||
|
||||
text: RenderableType = (
|
||||
Pretty(layout) if layout.visible else Styled(Pretty(layout), "dim")
|
||||
)
|
||||
table.add_row(icon, text)
|
||||
_summary = table
|
||||
return _summary
|
||||
|
||||
layout = self
|
||||
tree = Tree(
|
||||
summary(layout),
|
||||
guide_style=f"layout.tree.{layout.splitter.name}",
|
||||
highlight=True,
|
||||
)
|
||||
|
||||
def recurse(tree: "Tree", layout: "Layout") -> None:
|
||||
for child in layout._children:
|
||||
recurse(
|
||||
tree.add(
|
||||
summary(child),
|
||||
guide_style=f"layout.tree.{child.splitter.name}",
|
||||
),
|
||||
child,
|
||||
)
|
||||
|
||||
recurse(tree, self)
|
||||
return tree
|
||||
|
||||
def split(
|
||||
self,
|
||||
*layouts: Union["Layout", RenderableType],
|
||||
splitter: Union[Splitter, str] = "column",
|
||||
) -> None:
|
||||
"""Split the layout in to multiple sub-layouts.
|
||||
|
||||
Args:
|
||||
*layouts (Layout): Positional arguments should be (sub) Layout instances.
|
||||
splitter (Union[Splitter, str]): Splitter instance or name of splitter.
|
||||
"""
|
||||
_layouts = [
|
||||
layout if isinstance(layout, Layout) else Layout(layout)
|
||||
for layout in layouts
|
||||
]
|
||||
try:
|
||||
self.splitter = (
|
||||
splitter
|
||||
if isinstance(splitter, Splitter)
|
||||
else self.splitters[splitter]()
|
||||
)
|
||||
except KeyError:
|
||||
raise NoSplitter(f"No splitter called {splitter!r}")
|
||||
self._children[:] = _layouts
|
||||
|
||||
def add_split(self, *layouts: Union["Layout", RenderableType]) -> None:
|
||||
"""Add a new layout(s) to existing split.
|
||||
|
||||
Args:
|
||||
*layouts (Union[Layout, RenderableType]): Positional arguments should be renderables or (sub) Layout instances.
|
||||
|
||||
"""
|
||||
_layouts = (
|
||||
layout if isinstance(layout, Layout) else Layout(layout)
|
||||
for layout in layouts
|
||||
)
|
||||
self._children.extend(_layouts)
|
||||
|
||||
def split_row(self, *layouts: Union["Layout", RenderableType]) -> None:
|
||||
"""Split the layout in tow a row (Layouts side by side).
|
||||
|
||||
Args:
|
||||
*layouts (Layout): Positional arguments should be (sub) Layout instances.
|
||||
"""
|
||||
self.split(*layouts, splitter="row")
|
||||
|
||||
def split_column(self, *layouts: Union["Layout", RenderableType]) -> None:
|
||||
"""Split the layout in to a column (layouts stacked on top of each other).
|
||||
|
||||
Args:
|
||||
*layouts (Layout): Positional arguments should be (sub) Layout instances.
|
||||
"""
|
||||
self.split(*layouts, splitter="column")
|
||||
|
||||
def unsplit(self) -> None:
|
||||
"""Reset splits to initial state."""
|
||||
del self._children[:]
|
||||
|
||||
def update(self, renderable: RenderableType) -> None:
|
||||
"""Update renderable.
|
||||
|
||||
Args:
|
||||
renderable (RenderableType): New renderable object.
|
||||
"""
|
||||
with self._lock:
|
||||
self._renderable = renderable
|
||||
|
||||
def refresh_screen(self, console: "Console", layout_name: str) -> None:
|
||||
"""Refresh a sub-layout.
|
||||
|
||||
Args:
|
||||
console (Console): Console instance where Layout is to be rendered.
|
||||
layout_name (str): Name of layout.
|
||||
"""
|
||||
with self._lock:
|
||||
layout = self[layout_name]
|
||||
region, _lines = self._render_map[layout]
|
||||
(x, y, width, height) = region
|
||||
lines = console.render_lines(
|
||||
layout, console.options.update_dimensions(width, height)
|
||||
)
|
||||
self._render_map[layout] = LayoutRender(region, lines)
|
||||
console.update_screen_lines(lines, x, y)
|
||||
|
||||
def _make_region_map(self, width: int, height: int) -> RegionMap:
|
||||
"""Create a dict that maps layout on to Region."""
|
||||
stack: List[Tuple[Layout, Region]] = [(self, Region(0, 0, width, height))]
|
||||
push = stack.append
|
||||
pop = stack.pop
|
||||
layout_regions: List[Tuple[Layout, Region]] = []
|
||||
append_layout_region = layout_regions.append
|
||||
while stack:
|
||||
append_layout_region(pop())
|
||||
layout, region = layout_regions[-1]
|
||||
children = layout.children
|
||||
if children:
|
||||
for child_and_region in layout.splitter.divide(children, region):
|
||||
push(child_and_region)
|
||||
|
||||
region_map = {
|
||||
layout: region
|
||||
for layout, region in sorted(layout_regions, key=itemgetter(1))
|
||||
}
|
||||
return region_map
|
||||
|
||||
def render(self, console: Console, options: ConsoleOptions) -> RenderMap:
|
||||
"""Render the sub_layouts.
|
||||
|
||||
Args:
|
||||
console (Console): Console instance.
|
||||
options (ConsoleOptions): Console options.
|
||||
|
||||
Returns:
|
||||
RenderMap: A dict that maps Layout on to a tuple of Region, lines
|
||||
"""
|
||||
render_width = options.max_width
|
||||
render_height = options.height or console.height
|
||||
region_map = self._make_region_map(render_width, render_height)
|
||||
layout_regions = [
|
||||
(layout, region)
|
||||
for layout, region in region_map.items()
|
||||
if not layout.children
|
||||
]
|
||||
render_map: Dict["Layout", "LayoutRender"] = {}
|
||||
render_lines = console.render_lines
|
||||
update_dimensions = options.update_dimensions
|
||||
|
||||
for layout, region in layout_regions:
|
||||
lines = render_lines(
|
||||
layout.renderable, update_dimensions(region.width, region.height)
|
||||
)
|
||||
render_map[layout] = LayoutRender(region, lines)
|
||||
return render_map
|
||||
|
||||
def __rich_console__(
|
||||
self, console: Console, options: ConsoleOptions
|
||||
) -> RenderResult:
|
||||
with self._lock:
|
||||
width = options.max_width or console.width
|
||||
height = options.height or console.height
|
||||
render_map = self.render(console, options.update_dimensions(width, height))
|
||||
self._render_map = render_map
|
||||
layout_lines: List[List[Segment]] = [[] for _ in range(height)]
|
||||
_islice = islice
|
||||
for (region, lines) in render_map.values():
|
||||
_x, y, _layout_width, layout_height = region
|
||||
for row, line in zip(
|
||||
_islice(layout_lines, y, y + layout_height), lines
|
||||
):
|
||||
row.extend(line)
|
||||
|
||||
new_line = Segment.line()
|
||||
for layout_row in layout_lines:
|
||||
yield from layout_row
|
||||
yield new_line
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from pip._vendor.rich.console import Console
|
||||
|
||||
console = Console()
|
||||
layout = Layout()
|
||||
|
||||
layout.split_column(
|
||||
Layout(name="header", size=3),
|
||||
Layout(ratio=1, name="main"),
|
||||
Layout(size=10, name="footer"),
|
||||
)
|
||||
|
||||
layout["main"].split_row(Layout(name="side"), Layout(name="body", ratio=2))
|
||||
|
||||
layout["body"].split_row(Layout(name="content", ratio=2), Layout(name="s2"))
|
||||
|
||||
layout["s2"].split_column(
|
||||
Layout(name="top"), Layout(name="middle"), Layout(name="bottom")
|
||||
)
|
||||
|
||||
layout["side"].split_column(Layout(layout.tree, name="left1"), Layout(name="left2"))
|
||||
|
||||
layout["content"].update("foo")
|
||||
|
||||
console.print(layout)
|
|
@ -0,0 +1,375 @@
|
|||
import sys
|
||||
from threading import Event, RLock, Thread
|
||||
from types import TracebackType
|
||||
from typing import IO, Any, Callable, List, Optional, TextIO, Type, cast
|
||||
|
||||
from . import get_console
|
||||
from .console import Console, ConsoleRenderable, RenderableType, RenderHook
|
||||
from .control import Control
|
||||
from .file_proxy import FileProxy
|
||||
from .jupyter import JupyterMixin
|
||||
from .live_render import LiveRender, VerticalOverflowMethod
|
||||
from .screen import Screen
|
||||
from .text import Text
|
||||
|
||||
|
||||
class _RefreshThread(Thread):
|
||||
"""A thread that calls refresh() at regular intervals."""
|
||||
|
||||
def __init__(self, live: "Live", refresh_per_second: float) -> None:
|
||||
self.live = live
|
||||
self.refresh_per_second = refresh_per_second
|
||||
self.done = Event()
|
||||
super().__init__(daemon=True)
|
||||
|
||||
def stop(self) -> None:
|
||||
self.done.set()
|
||||
|
||||
def run(self) -> None:
|
||||
while not self.done.wait(1 / self.refresh_per_second):
|
||||
with self.live._lock:
|
||||
if not self.done.is_set():
|
||||
self.live.refresh()
|
||||
|
||||
|
||||
class Live(JupyterMixin, RenderHook):
|
||||
"""Renders an auto-updating live display of any given renderable.
|
||||
|
||||
Args:
|
||||
renderable (RenderableType, optional): The renderable to live display. Defaults to displaying nothing.
|
||||
console (Console, optional): Optional Console instance. Default will an internal Console instance writing to stdout.
|
||||
screen (bool, optional): Enable alternate screen mode. Defaults to False.
|
||||
auto_refresh (bool, optional): Enable auto refresh. If disabled, you will need to call `refresh()` or `update()` with refresh flag. Defaults to True
|
||||
refresh_per_second (float, optional): Number of times per second to refresh the live display. Defaults to 4.
|
||||
transient (bool, optional): Clear the renderable on exit (has no effect when screen=True). Defaults to False.
|
||||
redirect_stdout (bool, optional): Enable redirection of stdout, so ``print`` may be used. Defaults to True.
|
||||
redirect_stderr (bool, optional): Enable redirection of stderr. Defaults to True.
|
||||
vertical_overflow (VerticalOverflowMethod, optional): How to handle renderable when it is too tall for the console. Defaults to "ellipsis".
|
||||
get_renderable (Callable[[], RenderableType], optional): Optional callable to get renderable. Defaults to None.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
renderable: Optional[RenderableType] = None,
|
||||
*,
|
||||
console: Optional[Console] = None,
|
||||
screen: bool = False,
|
||||
auto_refresh: bool = True,
|
||||
refresh_per_second: float = 4,
|
||||
transient: bool = False,
|
||||
redirect_stdout: bool = True,
|
||||
redirect_stderr: bool = True,
|
||||
vertical_overflow: VerticalOverflowMethod = "ellipsis",
|
||||
get_renderable: Optional[Callable[[], RenderableType]] = None,
|
||||
) -> None:
|
||||
assert refresh_per_second > 0, "refresh_per_second must be > 0"
|
||||
self._renderable = renderable
|
||||
self.console = console if console is not None else get_console()
|
||||
self._screen = screen
|
||||
self._alt_screen = False
|
||||
|
||||
self._redirect_stdout = redirect_stdout
|
||||
self._redirect_stderr = redirect_stderr
|
||||
self._restore_stdout: Optional[IO[str]] = None
|
||||
self._restore_stderr: Optional[IO[str]] = None
|
||||
|
||||
self._lock = RLock()
|
||||
self.ipy_widget: Optional[Any] = None
|
||||
self.auto_refresh = auto_refresh
|
||||
self._started: bool = False
|
||||
self.transient = True if screen else transient
|
||||
|
||||
self._refresh_thread: Optional[_RefreshThread] = None
|
||||
self.refresh_per_second = refresh_per_second
|
||||
|
||||
self.vertical_overflow = vertical_overflow
|
||||
self._get_renderable = get_renderable
|
||||
self._live_render = LiveRender(
|
||||
self.get_renderable(), vertical_overflow=vertical_overflow
|
||||
)
|
||||
|
||||
@property
|
||||
def is_started(self) -> bool:
|
||||
"""Check if live display has been started."""
|
||||
return self._started
|
||||
|
||||
def get_renderable(self) -> RenderableType:
|
||||
renderable = (
|
||||
self._get_renderable()
|
||||
if self._get_renderable is not None
|
||||
else self._renderable
|
||||
)
|
||||
return renderable or ""
|
||||
|
||||
def start(self, refresh: bool = False) -> None:
|
||||
"""Start live rendering display.
|
||||
|
||||
Args:
|
||||
refresh (bool, optional): Also refresh. Defaults to False.
|
||||
"""
|
||||
with self._lock:
|
||||
if self._started:
|
||||
return
|
||||
self.console.set_live(self)
|
||||
self._started = True
|
||||
if self._screen:
|
||||
self._alt_screen = self.console.set_alt_screen(True)
|
||||
self.console.show_cursor(False)
|
||||
self._enable_redirect_io()
|
||||
self.console.push_render_hook(self)
|
||||
if refresh:
|
||||
self.refresh()
|
||||
if self.auto_refresh:
|
||||
self._refresh_thread = _RefreshThread(self, self.refresh_per_second)
|
||||
self._refresh_thread.start()
|
||||
|
||||
def stop(self) -> None:
|
||||
"""Stop live rendering display."""
|
||||
with self._lock:
|
||||
if not self._started:
|
||||
return
|
||||
self.console.clear_live()
|
||||
self._started = False
|
||||
try:
|
||||
if self.auto_refresh and self._refresh_thread is not None:
|
||||
self._refresh_thread.stop()
|
||||
# allow it to fully render on the last even if overflow
|
||||
self.vertical_overflow = "visible"
|
||||
if not self._alt_screen and not self.console.is_jupyter:
|
||||
self.refresh()
|
||||
|
||||
finally:
|
||||
self._disable_redirect_io()
|
||||
self.console.pop_render_hook()
|
||||
if not self._alt_screen and self.console.is_terminal:
|
||||
self.console.line()
|
||||
self.console.show_cursor(True)
|
||||
if self._alt_screen:
|
||||
self.console.set_alt_screen(False)
|
||||
|
||||
if self._refresh_thread is not None:
|
||||
self._refresh_thread.join()
|
||||
self._refresh_thread = None
|
||||
if self.transient and not self._alt_screen:
|
||||
self.console.control(self._live_render.restore_cursor())
|
||||
if self.ipy_widget is not None: # pragma: no cover
|
||||
if self.transient:
|
||||
self.ipy_widget.close()
|
||||
else:
|
||||
# jupyter last refresh must occur after console pop render hook
|
||||
# i am not sure why this is needed
|
||||
self.refresh()
|
||||
|
||||
def __enter__(self) -> "Live":
|
||||
self.start(refresh=self._renderable is not None)
|
||||
return self
|
||||
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: Optional[Type[BaseException]],
|
||||
exc_val: Optional[BaseException],
|
||||
exc_tb: Optional[TracebackType],
|
||||
) -> None:
|
||||
self.stop()
|
||||
|
||||
def _enable_redirect_io(self) -> None:
|
||||
"""Enable redirecting of stdout / stderr."""
|
||||
if self.console.is_terminal:
|
||||
if self._redirect_stdout and not isinstance(sys.stdout, FileProxy):
|
||||
self._restore_stdout = sys.stdout
|
||||
sys.stdout = cast("TextIO", FileProxy(self.console, sys.stdout))
|
||||
if self._redirect_stderr and not isinstance(sys.stderr, FileProxy):
|
||||
self._restore_stderr = sys.stderr
|
||||
sys.stderr = cast("TextIO", FileProxy(self.console, sys.stderr))
|
||||
|
||||
def _disable_redirect_io(self) -> None:
|
||||
"""Disable redirecting of stdout / stderr."""
|
||||
if self._restore_stdout:
|
||||
sys.stdout = cast("TextIO", self._restore_stdout)
|
||||
self._restore_stdout = None
|
||||
if self._restore_stderr:
|
||||
sys.stderr = cast("TextIO", self._restore_stderr)
|
||||
self._restore_stderr = None
|
||||
|
||||
@property
|
||||
def renderable(self) -> RenderableType:
|
||||
"""Get the renderable that is being displayed
|
||||
|
||||
Returns:
|
||||
RenderableType: Displayed renderable.
|
||||
"""
|
||||
renderable = self.get_renderable()
|
||||
return Screen(renderable) if self._alt_screen else renderable
|
||||
|
||||
def update(self, renderable: RenderableType, *, refresh: bool = False) -> None:
|
||||
"""Update the renderable that is being displayed
|
||||
|
||||
Args:
|
||||
renderable (RenderableType): New renderable to use.
|
||||
refresh (bool, optional): Refresh the display. Defaults to False.
|
||||
"""
|
||||
with self._lock:
|
||||
self._renderable = renderable
|
||||
if refresh:
|
||||
self.refresh()
|
||||
|
||||
def refresh(self) -> None:
|
||||
"""Update the display of the Live Render."""
|
||||
with self._lock:
|
||||
self._live_render.set_renderable(self.renderable)
|
||||
if self.console.is_jupyter: # pragma: no cover
|
||||
try:
|
||||
from IPython.display import display
|
||||
from ipywidgets import Output
|
||||
except ImportError:
|
||||
import warnings
|
||||
|
||||
warnings.warn('install "ipywidgets" for Jupyter support')
|
||||
else:
|
||||
if self.ipy_widget is None:
|
||||
self.ipy_widget = Output()
|
||||
display(self.ipy_widget)
|
||||
|
||||
with self.ipy_widget:
|
||||
self.ipy_widget.clear_output(wait=True)
|
||||
self.console.print(self._live_render.renderable)
|
||||
elif self.console.is_terminal and not self.console.is_dumb_terminal:
|
||||
with self.console:
|
||||
self.console.print(Control())
|
||||
elif (
|
||||
not self._started and not self.transient
|
||||
): # if it is finished allow files or dumb-terminals to see final result
|
||||
with self.console:
|
||||
self.console.print(Control())
|
||||
|
||||
def process_renderables(
|
||||
self, renderables: List[ConsoleRenderable]
|
||||
) -> List[ConsoleRenderable]:
|
||||
"""Process renderables to restore cursor and display progress."""
|
||||
self._live_render.vertical_overflow = self.vertical_overflow
|
||||
if self.console.is_interactive:
|
||||
# lock needs acquiring as user can modify live_render renderable at any time unlike in Progress.
|
||||
with self._lock:
|
||||
reset = (
|
||||
Control.home()
|
||||
if self._alt_screen
|
||||
else self._live_render.position_cursor()
|
||||
)
|
||||
renderables = [
|
||||
reset,
|
||||
*renderables,
|
||||
self._live_render,
|
||||
]
|
||||
elif (
|
||||
not self._started and not self.transient
|
||||
): # if it is finished render the final output for files or dumb_terminals
|
||||
renderables = [*renderables, self._live_render]
|
||||
|
||||
return renderables
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
import random
|
||||
import time
|
||||
from itertools import cycle
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
from .align import Align
|
||||
from .console import Console
|
||||
from .live import Live as Live
|
||||
from .panel import Panel
|
||||
from .rule import Rule
|
||||
from .syntax import Syntax
|
||||
from .table import Table
|
||||
|
||||
console = Console()
|
||||
|
||||
syntax = Syntax(
|
||||
'''def loop_last(values: Iterable[T]) -> Iterable[Tuple[bool, T]]:
|
||||
"""Iterate and generate a tuple with a flag for last value."""
|
||||
iter_values = iter(values)
|
||||
try:
|
||||
previous_value = next(iter_values)
|
||||
except StopIteration:
|
||||
return
|
||||
for value in iter_values:
|
||||
yield False, previous_value
|
||||
previous_value = value
|
||||
yield True, previous_value''',
|
||||
"python",
|
||||
line_numbers=True,
|
||||
)
|
||||
|
||||
table = Table("foo", "bar", "baz")
|
||||
table.add_row("1", "2", "3")
|
||||
|
||||
progress_renderables = [
|
||||
"You can make the terminal shorter and taller to see the live table hide"
|
||||
"Text may be printed while the progress bars are rendering.",
|
||||
Panel("In fact, [i]any[/i] renderable will work"),
|
||||
"Such as [magenta]tables[/]...",
|
||||
table,
|
||||
"Pretty printed structures...",
|
||||
{"type": "example", "text": "Pretty printed"},
|
||||
"Syntax...",
|
||||
syntax,
|
||||
Rule("Give it a try!"),
|
||||
]
|
||||
|
||||
examples = cycle(progress_renderables)
|
||||
|
||||
exchanges = [
|
||||
"SGD",
|
||||
"MYR",
|
||||
"EUR",
|
||||
"USD",
|
||||
"AUD",
|
||||
"JPY",
|
||||
"CNH",
|
||||
"HKD",
|
||||
"CAD",
|
||||
"INR",
|
||||
"DKK",
|
||||
"GBP",
|
||||
"RUB",
|
||||
"NZD",
|
||||
"MXN",
|
||||
"IDR",
|
||||
"TWD",
|
||||
"THB",
|
||||
"VND",
|
||||
]
|
||||
with Live(console=console) as live_table:
|
||||
exchange_rate_dict: Dict[Tuple[str, str], float] = {}
|
||||
|
||||
for index in range(100):
|
||||
select_exchange = exchanges[index % len(exchanges)]
|
||||
|
||||
for exchange in exchanges:
|
||||
if exchange == select_exchange:
|
||||
continue
|
||||
time.sleep(0.4)
|
||||
if random.randint(0, 10) < 1:
|
||||
console.log(next(examples))
|
||||
exchange_rate_dict[(select_exchange, exchange)] = 200 / (
|
||||
(random.random() * 320) + 1
|
||||
)
|
||||
if len(exchange_rate_dict) > len(exchanges) - 1:
|
||||
exchange_rate_dict.pop(list(exchange_rate_dict.keys())[0])
|
||||
table = Table(title="Exchange Rates")
|
||||
|
||||
table.add_column("Source Currency")
|
||||
table.add_column("Destination Currency")
|
||||
table.add_column("Exchange Rate")
|
||||
|
||||
for ((source, dest), exchange_rate) in exchange_rate_dict.items():
|
||||
table.add_row(
|
||||
source,
|
||||
dest,
|
||||
Text(
|
||||
f"{exchange_rate:.4f}",
|
||||
style="red" if exchange_rate < 1.0 else "green",
|
||||
),
|
||||
)
|
||||
|
||||
live_table.update(Align.center(table))
|
|
@ -0,0 +1,113 @@
|
|||
import sys
|
||||
from typing import Optional, Tuple
|
||||
|
||||
if sys.version_info >= (3, 8):
|
||||
from typing import Literal
|
||||
else:
|
||||
from pip._vendor.typing_extensions import Literal # pragma: no cover
|
||||
|
||||
|
||||
from ._loop import loop_last
|
||||
from .console import Console, ConsoleOptions, RenderableType, RenderResult
|
||||
from .control import Control
|
||||
from .segment import ControlType, Segment
|
||||
from .style import StyleType
|
||||
from .text import Text
|
||||
|
||||
VerticalOverflowMethod = Literal["crop", "ellipsis", "visible"]
|
||||
|
||||
|
||||
class LiveRender:
|
||||
"""Creates a renderable that may be updated.
|
||||
|
||||
Args:
|
||||
renderable (RenderableType): Any renderable object.
|
||||
style (StyleType, optional): An optional style to apply to the renderable. Defaults to "".
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
renderable: RenderableType,
|
||||
style: StyleType = "",
|
||||
vertical_overflow: VerticalOverflowMethod = "ellipsis",
|
||||
) -> None:
|
||||
self.renderable = renderable
|
||||
self.style = style
|
||||
self.vertical_overflow = vertical_overflow
|
||||
self._shape: Optional[Tuple[int, int]] = None
|
||||
|
||||
def set_renderable(self, renderable: RenderableType) -> None:
|
||||
"""Set a new renderable.
|
||||
|
||||
Args:
|
||||
renderable (RenderableType): Any renderable object, including str.
|
||||
"""
|
||||
self.renderable = renderable
|
||||
|
||||
def position_cursor(self) -> Control:
|
||||
"""Get control codes to move cursor to beginning of live render.
|
||||
|
||||
Returns:
|
||||
Control: A control instance that may be printed.
|
||||
"""
|
||||
if self._shape is not None:
|
||||
_, height = self._shape
|
||||
return Control(
|
||||
ControlType.CARRIAGE_RETURN,
|
||||
(ControlType.ERASE_IN_LINE, 2),
|
||||
*(
|
||||
(
|
||||
(ControlType.CURSOR_UP, 1),
|
||||
(ControlType.ERASE_IN_LINE, 2),
|
||||
)
|
||||
* (height - 1)
|
||||
)
|
||||
)
|
||||
return Control()
|
||||
|
||||
def restore_cursor(self) -> Control:
|
||||
"""Get control codes to clear the render and restore the cursor to its previous position.
|
||||
|
||||
Returns:
|
||||
Control: A Control instance that may be printed.
|
||||
"""
|
||||
if self._shape is not None:
|
||||
_, height = self._shape
|
||||
return Control(
|
||||
ControlType.CARRIAGE_RETURN,
|
||||
*((ControlType.CURSOR_UP, 1), (ControlType.ERASE_IN_LINE, 2)) * height
|
||||
)
|
||||
return Control()
|
||||
|
||||
def __rich_console__(
|
||||
self, console: Console, options: ConsoleOptions
|
||||
) -> RenderResult:
|
||||
|
||||
renderable = self.renderable
|
||||
_Segment = Segment
|
||||
style = console.get_style(self.style)
|
||||
lines = console.render_lines(renderable, options, style=style, pad=False)
|
||||
shape = _Segment.get_shape(lines)
|
||||
|
||||
_, height = shape
|
||||
if height > options.size.height:
|
||||
if self.vertical_overflow == "crop":
|
||||
lines = lines[: options.size.height]
|
||||
shape = _Segment.get_shape(lines)
|
||||
elif self.vertical_overflow == "ellipsis":
|
||||
lines = lines[: (options.size.height - 1)]
|
||||
overflow_text = Text(
|
||||
"...",
|
||||
overflow="crop",
|
||||
justify="center",
|
||||
end="",
|
||||
style="live.ellipsis",
|
||||
)
|
||||
lines.append(list(console.render(overflow_text)))
|
||||
shape = _Segment.get_shape(lines)
|
||||
self._shape = shape
|
||||
|
||||
for last, line in loop_last(lines):
|
||||
yield from line
|
||||
if not last:
|
||||
yield _Segment.line()
|
|
@ -0,0 +1,267 @@
|
|||
import logging
|
||||
from datetime import datetime
|
||||
from logging import Handler, LogRecord
|
||||
from pathlib import Path
|
||||
from typing import ClassVar, List, Optional, Type, Union
|
||||
|
||||
from . import get_console
|
||||
from ._log_render import LogRender, FormatTimeCallable
|
||||
from .console import Console, ConsoleRenderable
|
||||
from .highlighter import Highlighter, ReprHighlighter
|
||||
from .text import Text
|
||||
from .traceback import Traceback
|
||||
|
||||
|
||||
class RichHandler(Handler):
|
||||
"""A logging handler that renders output with Rich. The time / level / message and file are displayed in columns.
|
||||
The level is color coded, and the message is syntax highlighted.
|
||||
|
||||
Note:
|
||||
Be careful when enabling console markup in log messages if you have configured logging for libraries not
|
||||
under your control. If a dependency writes messages containing square brackets, it may not produce the intended output.
|
||||
|
||||
Args:
|
||||
level (Union[int, str], optional): Log level. Defaults to logging.NOTSET.
|
||||
console (:class:`~rich.console.Console`, optional): Optional console instance to write logs.
|
||||
Default will use a global console instance writing to stdout.
|
||||
show_time (bool, optional): Show a column for the time. Defaults to True.
|
||||
omit_repeated_times (bool, optional): Omit repetition of the same time. Defaults to True.
|
||||
show_level (bool, optional): Show a column for the level. Defaults to True.
|
||||
show_path (bool, optional): Show the path to the original log call. Defaults to True.
|
||||
enable_link_path (bool, optional): Enable terminal link of path column to file. Defaults to True.
|
||||
highlighter (Highlighter, optional): Highlighter to style log messages, or None to use ReprHighlighter. Defaults to None.
|
||||
markup (bool, optional): Enable console markup in log messages. Defaults to False.
|
||||
rich_tracebacks (bool, optional): Enable rich tracebacks with syntax highlighting and formatting. Defaults to False.
|
||||
tracebacks_width (Optional[int], optional): Number of characters used to render tracebacks, or None for full width. Defaults to None.
|
||||
tracebacks_extra_lines (int, optional): Additional lines of code to render tracebacks, or None for full width. Defaults to None.
|
||||
tracebacks_theme (str, optional): Override pygments theme used in traceback.
|
||||
tracebacks_word_wrap (bool, optional): Enable word wrapping of long tracebacks lines. Defaults to True.
|
||||
tracebacks_show_locals (bool, optional): Enable display of locals in tracebacks. Defaults to False.
|
||||
locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
|
||||
Defaults to 10.
|
||||
locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80.
|
||||
log_time_format (Union[str, TimeFormatterCallable], optional): If ``log_time`` is enabled, either string for strftime or callable that formats the time. Defaults to "[%x %X] ".
|
||||
"""
|
||||
|
||||
KEYWORDS: ClassVar[Optional[List[str]]] = [
|
||||
"GET",
|
||||
"POST",
|
||||
"HEAD",
|
||||
"PUT",
|
||||
"DELETE",
|
||||
"OPTIONS",
|
||||
"TRACE",
|
||||
"PATCH",
|
||||
]
|
||||
HIGHLIGHTER_CLASS: ClassVar[Type[Highlighter]] = ReprHighlighter
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
level: Union[int, str] = logging.NOTSET,
|
||||
console: Optional[Console] = None,
|
||||
*,
|
||||
show_time: bool = True,
|
||||
omit_repeated_times: bool = True,
|
||||
show_level: bool = True,
|
||||
show_path: bool = True,
|
||||
enable_link_path: bool = True,
|
||||
highlighter: Optional[Highlighter] = None,
|
||||
markup: bool = False,
|
||||
rich_tracebacks: bool = False,
|
||||
tracebacks_width: Optional[int] = None,
|
||||
tracebacks_extra_lines: int = 3,
|
||||
tracebacks_theme: Optional[str] = None,
|
||||
tracebacks_word_wrap: bool = True,
|
||||
tracebacks_show_locals: bool = False,
|
||||
locals_max_length: int = 10,
|
||||
locals_max_string: int = 80,
|
||||
log_time_format: Union[str, FormatTimeCallable] = "[%x %X]",
|
||||
) -> None:
|
||||
super().__init__(level=level)
|
||||
self.console = console or get_console()
|
||||
self.highlighter = highlighter or self.HIGHLIGHTER_CLASS()
|
||||
self._log_render = LogRender(
|
||||
show_time=show_time,
|
||||
show_level=show_level,
|
||||
show_path=show_path,
|
||||
time_format=log_time_format,
|
||||
omit_repeated_times=omit_repeated_times,
|
||||
level_width=None,
|
||||
)
|
||||
self.enable_link_path = enable_link_path
|
||||
self.markup = markup
|
||||
self.rich_tracebacks = rich_tracebacks
|
||||
self.tracebacks_width = tracebacks_width
|
||||
self.tracebacks_extra_lines = tracebacks_extra_lines
|
||||
self.tracebacks_theme = tracebacks_theme
|
||||
self.tracebacks_word_wrap = tracebacks_word_wrap
|
||||
self.tracebacks_show_locals = tracebacks_show_locals
|
||||
self.locals_max_length = locals_max_length
|
||||
self.locals_max_string = locals_max_string
|
||||
|
||||
def get_level_text(self, record: LogRecord) -> Text:
|
||||
"""Get the level name from the record.
|
||||
|
||||
Args:
|
||||
record (LogRecord): LogRecord instance.
|
||||
|
||||
Returns:
|
||||
Text: A tuple of the style and level name.
|
||||
"""
|
||||
level_name = record.levelname
|
||||
level_text = Text.styled(
|
||||
level_name.ljust(8), f"logging.level.{level_name.lower()}"
|
||||
)
|
||||
return level_text
|
||||
|
||||
def emit(self, record: LogRecord) -> None:
|
||||
"""Invoked by logging."""
|
||||
message = self.format(record)
|
||||
traceback = None
|
||||
if (
|
||||
self.rich_tracebacks
|
||||
and record.exc_info
|
||||
and record.exc_info != (None, None, None)
|
||||
):
|
||||
exc_type, exc_value, exc_traceback = record.exc_info
|
||||
assert exc_type is not None
|
||||
assert exc_value is not None
|
||||
traceback = Traceback.from_exception(
|
||||
exc_type,
|
||||
exc_value,
|
||||
exc_traceback,
|
||||
width=self.tracebacks_width,
|
||||
extra_lines=self.tracebacks_extra_lines,
|
||||
theme=self.tracebacks_theme,
|
||||
word_wrap=self.tracebacks_word_wrap,
|
||||
show_locals=self.tracebacks_show_locals,
|
||||
locals_max_length=self.locals_max_length,
|
||||
locals_max_string=self.locals_max_string,
|
||||
)
|
||||
message = record.getMessage()
|
||||
if self.formatter:
|
||||
record.message = record.getMessage()
|
||||
formatter = self.formatter
|
||||
if hasattr(formatter, "usesTime") and formatter.usesTime():
|
||||
record.asctime = formatter.formatTime(record, formatter.datefmt)
|
||||
message = formatter.formatMessage(record)
|
||||
|
||||
message_renderable = self.render_message(record, message)
|
||||
log_renderable = self.render(
|
||||
record=record, traceback=traceback, message_renderable=message_renderable
|
||||
)
|
||||
try:
|
||||
self.console.print(log_renderable)
|
||||
except Exception:
|
||||
self.handleError(record)
|
||||
|
||||
def render_message(self, record: LogRecord, message: str) -> "ConsoleRenderable":
|
||||
"""Render message text in to Text.
|
||||
|
||||
record (LogRecord): logging Record.
|
||||
message (str): String containing log message.
|
||||
|
||||
Returns:
|
||||
ConsoleRenderable: Renderable to display log message.
|
||||
"""
|
||||
use_markup = (
|
||||
getattr(record, "markup") if hasattr(record, "markup") else self.markup
|
||||
)
|
||||
message_text = Text.from_markup(message) if use_markup else Text(message)
|
||||
if self.highlighter:
|
||||
message_text = self.highlighter(message_text)
|
||||
if self.KEYWORDS:
|
||||
message_text.highlight_words(self.KEYWORDS, "logging.keyword")
|
||||
return message_text
|
||||
|
||||
def render(
|
||||
self,
|
||||
*,
|
||||
record: LogRecord,
|
||||
traceback: Optional[Traceback],
|
||||
message_renderable: "ConsoleRenderable",
|
||||
) -> "ConsoleRenderable":
|
||||
"""Render log for display.
|
||||
|
||||
Args:
|
||||
record (LogRecord): logging Record.
|
||||
traceback (Optional[Traceback]): Traceback instance or None for no Traceback.
|
||||
message_renderable (ConsoleRenderable): Renderable (typically Text) containing log message contents.
|
||||
|
||||
Returns:
|
||||
ConsoleRenderable: Renderable to display log.
|
||||
"""
|
||||
path = Path(record.pathname).name
|
||||
level = self.get_level_text(record)
|
||||
time_format = None if self.formatter is None else self.formatter.datefmt
|
||||
log_time = datetime.fromtimestamp(record.created)
|
||||
|
||||
log_renderable = self._log_render(
|
||||
self.console,
|
||||
[message_renderable] if not traceback else [message_renderable, traceback],
|
||||
log_time=log_time,
|
||||
time_format=time_format,
|
||||
level=level,
|
||||
path=path,
|
||||
line_no=record.lineno,
|
||||
link_path=record.pathname if self.enable_link_path else None,
|
||||
)
|
||||
return log_renderable
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
from time import sleep
|
||||
|
||||
FORMAT = "%(message)s"
|
||||
# FORMAT = "%(asctime)-15s - %(levelname)s - %(message)s"
|
||||
logging.basicConfig(
|
||||
level="NOTSET",
|
||||
format=FORMAT,
|
||||
datefmt="[%X]",
|
||||
handlers=[RichHandler(rich_tracebacks=True, tracebacks_show_locals=True)],
|
||||
)
|
||||
log = logging.getLogger("rich")
|
||||
|
||||
log.info("Server starting...")
|
||||
log.info("Listening on http://127.0.0.1:8080")
|
||||
sleep(1)
|
||||
|
||||
log.info("GET /index.html 200 1298")
|
||||
log.info("GET /imgs/backgrounds/back1.jpg 200 54386")
|
||||
log.info("GET /css/styles.css 200 54386")
|
||||
log.warning("GET /favicon.ico 404 242")
|
||||
sleep(1)
|
||||
|
||||
log.debug(
|
||||
"JSONRPC request\n--> %r\n<-- %r",
|
||||
{
|
||||
"version": "1.1",
|
||||
"method": "confirmFruitPurchase",
|
||||
"params": [["apple", "orange", "mangoes", "pomelo"], 1.123],
|
||||
"id": "194521489",
|
||||
},
|
||||
{"version": "1.1", "result": True, "error": None, "id": "194521489"},
|
||||
)
|
||||
log.debug(
|
||||
"Loading configuration file /adasd/asdasd/qeqwe/qwrqwrqwr/sdgsdgsdg/werwerwer/dfgerert/ertertert/ertetert/werwerwer"
|
||||
)
|
||||
log.error("Unable to find 'pomelo' in database!")
|
||||
log.info("POST /jsonrpc/ 200 65532")
|
||||
log.info("POST /admin/ 401 42234")
|
||||
log.warning("password was rejected for admin site.")
|
||||
|
||||
def divide() -> None:
|
||||
number = 1
|
||||
divisor = 0
|
||||
foos = ["foo"] * 100
|
||||
log.debug("in divide")
|
||||
try:
|
||||
number / divisor
|
||||
except:
|
||||
log.exception("An error of some kind occurred!")
|
||||
|
||||
divide()
|
||||
sleep(1)
|
||||
log.critical("Out of memory!")
|
||||
log.info("Server exited with code=-1")
|
||||
log.info("[bold]EXITING...[/bold]", extra=dict(markup=True))
|
|
@ -0,0 +1,244 @@
|
|||
from ast import literal_eval
|
||||
from operator import attrgetter
|
||||
import re
|
||||
from typing import Callable, Iterable, List, Match, NamedTuple, Optional, Tuple, Union
|
||||
|
||||
from .errors import MarkupError
|
||||
from .style import Style
|
||||
from .text import Span, Text
|
||||
from .emoji import EmojiVariant
|
||||
from ._emoji_replace import _emoji_replace
|
||||
|
||||
|
||||
RE_TAGS = re.compile(
|
||||
r"""((\\*)\[([a-z#\/@].*?)\])""",
|
||||
re.VERBOSE,
|
||||
)
|
||||
|
||||
RE_HANDLER = re.compile(r"^([\w\.]*?)(\(.*?\))?$")
|
||||
|
||||
|
||||
class Tag(NamedTuple):
|
||||
"""A tag in console markup."""
|
||||
|
||||
name: str
|
||||
"""The tag name. e.g. 'bold'."""
|
||||
parameters: Optional[str]
|
||||
"""Any additional parameters after the name."""
|
||||
|
||||
def __str__(self) -> str:
|
||||
return (
|
||||
self.name if self.parameters is None else f"{self.name} {self.parameters}"
|
||||
)
|
||||
|
||||
@property
|
||||
def markup(self) -> str:
|
||||
"""Get the string representation of this tag."""
|
||||
return (
|
||||
f"[{self.name}]"
|
||||
if self.parameters is None
|
||||
else f"[{self.name}={self.parameters}]"
|
||||
)
|
||||
|
||||
|
||||
_ReStringMatch = Match[str] # regex match object
|
||||
_ReSubCallable = Callable[[_ReStringMatch], str] # Callable invoked by re.sub
|
||||
_EscapeSubMethod = Callable[[_ReSubCallable, str], str] # Sub method of a compiled re
|
||||
|
||||
|
||||
def escape(
|
||||
markup: str, _escape: _EscapeSubMethod = re.compile(r"(\\*)(\[[a-z#\/].*?\])").sub
|
||||
) -> str:
|
||||
"""Escapes text so that it won't be interpreted as markup.
|
||||
|
||||
Args:
|
||||
markup (str): Content to be inserted in to markup.
|
||||
|
||||
Returns:
|
||||
str: Markup with square brackets escaped.
|
||||
"""
|
||||
|
||||
def escape_backslashes(match: Match[str]) -> str:
|
||||
"""Called by re.sub replace matches."""
|
||||
backslashes, text = match.groups()
|
||||
return f"{backslashes}{backslashes}\\{text}"
|
||||
|
||||
markup = _escape(escape_backslashes, markup)
|
||||
return markup
|
||||
|
||||
|
||||
def _parse(markup: str) -> Iterable[Tuple[int, Optional[str], Optional[Tag]]]:
|
||||
"""Parse markup in to an iterable of tuples of (position, text, tag).
|
||||
|
||||
Args:
|
||||
markup (str): A string containing console markup
|
||||
|
||||
"""
|
||||
position = 0
|
||||
_divmod = divmod
|
||||
_Tag = Tag
|
||||
for match in RE_TAGS.finditer(markup):
|
||||
full_text, escapes, tag_text = match.groups()
|
||||
start, end = match.span()
|
||||
if start > position:
|
||||
yield start, markup[position:start], None
|
||||
if escapes:
|
||||
backslashes, escaped = _divmod(len(escapes), 2)
|
||||
if backslashes:
|
||||
# Literal backslashes
|
||||
yield start, "\\" * backslashes, None
|
||||
start += backslashes * 2
|
||||
if escaped:
|
||||
# Escape of tag
|
||||
yield start, full_text[len(escapes) :], None
|
||||
position = end
|
||||
continue
|
||||
text, equals, parameters = tag_text.partition("=")
|
||||
yield start, None, _Tag(text, parameters if equals else None)
|
||||
position = end
|
||||
if position < len(markup):
|
||||
yield position, markup[position:], None
|
||||
|
||||
|
||||
def render(
|
||||
markup: str,
|
||||
style: Union[str, Style] = "",
|
||||
emoji: bool = True,
|
||||
emoji_variant: Optional[EmojiVariant] = None,
|
||||
) -> Text:
|
||||
"""Render console markup in to a Text instance.
|
||||
|
||||
Args:
|
||||
markup (str): A string containing console markup.
|
||||
emoji (bool, optional): Also render emoji code. Defaults to True.
|
||||
|
||||
Raises:
|
||||
MarkupError: If there is a syntax error in the markup.
|
||||
|
||||
Returns:
|
||||
Text: A test instance.
|
||||
"""
|
||||
emoji_replace = _emoji_replace
|
||||
if "[" not in markup:
|
||||
return Text(
|
||||
emoji_replace(markup, default_variant=emoji_variant) if emoji else markup,
|
||||
style=style,
|
||||
)
|
||||
text = Text(style=style)
|
||||
append = text.append
|
||||
normalize = Style.normalize
|
||||
|
||||
style_stack: List[Tuple[int, Tag]] = []
|
||||
pop = style_stack.pop
|
||||
|
||||
spans: List[Span] = []
|
||||
append_span = spans.append
|
||||
|
||||
_Span = Span
|
||||
_Tag = Tag
|
||||
|
||||
def pop_style(style_name: str) -> Tuple[int, Tag]:
|
||||
"""Pop tag matching given style name."""
|
||||
for index, (_, tag) in enumerate(reversed(style_stack), 1):
|
||||
if tag.name == style_name:
|
||||
return pop(-index)
|
||||
raise KeyError(style_name)
|
||||
|
||||
for position, plain_text, tag in _parse(markup):
|
||||
if plain_text is not None:
|
||||
append(emoji_replace(plain_text) if emoji else plain_text)
|
||||
elif tag is not None:
|
||||
if tag.name.startswith("/"): # Closing tag
|
||||
style_name = tag.name[1:].strip()
|
||||
|
||||
if style_name: # explicit close
|
||||
style_name = normalize(style_name)
|
||||
try:
|
||||
start, open_tag = pop_style(style_name)
|
||||
except KeyError:
|
||||
raise MarkupError(
|
||||
f"closing tag '{tag.markup}' at position {position} doesn't match any open tag"
|
||||
) from None
|
||||
else: # implicit close
|
||||
try:
|
||||
start, open_tag = pop()
|
||||
except IndexError:
|
||||
raise MarkupError(
|
||||
f"closing tag '[/]' at position {position} has nothing to close"
|
||||
) from None
|
||||
|
||||
if open_tag.name.startswith("@"):
|
||||
if open_tag.parameters:
|
||||
handler_name = ""
|
||||
parameters = open_tag.parameters.strip()
|
||||
handler_match = RE_HANDLER.match(parameters)
|
||||
if handler_match is not None:
|
||||
handler_name, match_parameters = handler_match.groups()
|
||||
parameters = (
|
||||
"()" if match_parameters is None else match_parameters
|
||||
)
|
||||
|
||||
try:
|
||||
meta_params = literal_eval(parameters)
|
||||
except SyntaxError as error:
|
||||
raise MarkupError(
|
||||
f"error parsing {parameters!r} in {open_tag.parameters!r}; {error.msg}"
|
||||
)
|
||||
except Exception as error:
|
||||
raise MarkupError(
|
||||
f"error parsing {open_tag.parameters!r}; {error}"
|
||||
) from None
|
||||
|
||||
if handler_name:
|
||||
meta_params = (
|
||||
handler_name,
|
||||
meta_params
|
||||
if isinstance(meta_params, tuple)
|
||||
else (meta_params,),
|
||||
)
|
||||
|
||||
else:
|
||||
meta_params = ()
|
||||
|
||||
append_span(
|
||||
_Span(
|
||||
start, len(text), Style(meta={open_tag.name: meta_params})
|
||||
)
|
||||
)
|
||||
else:
|
||||
append_span(_Span(start, len(text), str(open_tag)))
|
||||
|
||||
else: # Opening tag
|
||||
normalized_tag = _Tag(normalize(tag.name), tag.parameters)
|
||||
style_stack.append((len(text), normalized_tag))
|
||||
|
||||
text_length = len(text)
|
||||
while style_stack:
|
||||
start, tag = style_stack.pop()
|
||||
style = str(tag)
|
||||
if style:
|
||||
append_span(_Span(start, text_length, style))
|
||||
|
||||
text.spans = sorted(spans[::-1], key=attrgetter("start"))
|
||||
return text
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
|
||||
MARKUP = [
|
||||
"[red]Hello World[/red]",
|
||||
"[magenta]Hello [b]World[/b]",
|
||||
"[bold]Bold[italic] bold and italic [/bold]italic[/italic]",
|
||||
"Click [link=https://www.willmcgugan.com]here[/link] to visit my Blog",
|
||||
":warning-emoji: [bold red blink] DANGER![/]",
|
||||
]
|
||||
|
||||
from pip._vendor.rich.table import Table
|
||||
from pip._vendor.rich import print
|
||||
|
||||
grid = Table("Markup", "Result", padding=(0, 1))
|
||||
|
||||
for markup in MARKUP:
|
||||
grid.add_row(Text(markup), markup)
|
||||
|
||||
print(grid)
|
|
@ -0,0 +1,149 @@
|
|||
from operator import itemgetter
|
||||
from typing import Callable, Iterable, NamedTuple, Optional, TYPE_CHECKING
|
||||
|
||||
from . import errors
|
||||
from .protocol import is_renderable, rich_cast
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .console import Console, ConsoleOptions, RenderableType
|
||||
|
||||
|
||||
class Measurement(NamedTuple):
|
||||
"""Stores the minimum and maximum widths (in characters) required to render an object."""
|
||||
|
||||
minimum: int
|
||||
"""Minimum number of cells required to render."""
|
||||
maximum: int
|
||||
"""Maximum number of cells required to render."""
|
||||
|
||||
@property
|
||||
def span(self) -> int:
|
||||
"""Get difference between maximum and minimum."""
|
||||
return self.maximum - self.minimum
|
||||
|
||||
def normalize(self) -> "Measurement":
|
||||
"""Get measurement that ensures that minimum <= maximum and minimum >= 0
|
||||
|
||||
Returns:
|
||||
Measurement: A normalized measurement.
|
||||
"""
|
||||
minimum, maximum = self
|
||||
minimum = min(max(0, minimum), maximum)
|
||||
return Measurement(max(0, minimum), max(0, max(minimum, maximum)))
|
||||
|
||||
def with_maximum(self, width: int) -> "Measurement":
|
||||
"""Get a RenderableWith where the widths are <= width.
|
||||
|
||||
Args:
|
||||
width (int): Maximum desired width.
|
||||
|
||||
Returns:
|
||||
Measurement: New Measurement object.
|
||||
"""
|
||||
minimum, maximum = self
|
||||
return Measurement(min(minimum, width), min(maximum, width))
|
||||
|
||||
def with_minimum(self, width: int) -> "Measurement":
|
||||
"""Get a RenderableWith where the widths are >= width.
|
||||
|
||||
Args:
|
||||
width (int): Minimum desired width.
|
||||
|
||||
Returns:
|
||||
Measurement: New Measurement object.
|
||||
"""
|
||||
minimum, maximum = self
|
||||
width = max(0, width)
|
||||
return Measurement(max(minimum, width), max(maximum, width))
|
||||
|
||||
def clamp(
|
||||
self, min_width: Optional[int] = None, max_width: Optional[int] = None
|
||||
) -> "Measurement":
|
||||
"""Clamp a measurement within the specified range.
|
||||
|
||||
Args:
|
||||
min_width (int): Minimum desired width, or ``None`` for no minimum. Defaults to None.
|
||||
max_width (int): Maximum desired width, or ``None`` for no maximum. Defaults to None.
|
||||
|
||||
Returns:
|
||||
Measurement: New Measurement object.
|
||||
"""
|
||||
measurement = self
|
||||
if min_width is not None:
|
||||
measurement = measurement.with_minimum(min_width)
|
||||
if max_width is not None:
|
||||
measurement = measurement.with_maximum(max_width)
|
||||
return measurement
|
||||
|
||||
@classmethod
|
||||
def get(
|
||||
cls, console: "Console", options: "ConsoleOptions", renderable: "RenderableType"
|
||||
) -> "Measurement":
|
||||
"""Get a measurement for a renderable.
|
||||
|
||||
Args:
|
||||
console (~rich.console.Console): Console instance.
|
||||
options (~rich.console.ConsoleOptions): Console options.
|
||||
renderable (RenderableType): An object that may be rendered with Rich.
|
||||
|
||||
Raises:
|
||||
errors.NotRenderableError: If the object is not renderable.
|
||||
|
||||
Returns:
|
||||
Measurement: Measurement object containing range of character widths required to render the object.
|
||||
"""
|
||||
_max_width = options.max_width
|
||||
if _max_width < 1:
|
||||
return Measurement(0, 0)
|
||||
if isinstance(renderable, str):
|
||||
renderable = console.render_str(renderable, markup=options.markup)
|
||||
renderable = rich_cast(renderable)
|
||||
if is_renderable(renderable):
|
||||
get_console_width: Optional[
|
||||
Callable[["Console", "ConsoleOptions"], "Measurement"]
|
||||
] = getattr(renderable, "__rich_measure__", None)
|
||||
if get_console_width is not None:
|
||||
render_width = (
|
||||
get_console_width(console, options)
|
||||
.normalize()
|
||||
.with_maximum(_max_width)
|
||||
)
|
||||
if render_width.maximum < 1:
|
||||
return Measurement(0, 0)
|
||||
return render_width.normalize()
|
||||
else:
|
||||
return Measurement(0, _max_width)
|
||||
else:
|
||||
raise errors.NotRenderableError(
|
||||
f"Unable to get render width for {renderable!r}; "
|
||||
"a str, Segment, or object with __rich_console__ method is required"
|
||||
)
|
||||
|
||||
|
||||
def measure_renderables(
|
||||
console: "Console",
|
||||
options: "ConsoleOptions",
|
||||
renderables: Iterable["RenderableType"],
|
||||
) -> "Measurement":
|
||||
"""Get a measurement that would fit a number of renderables.
|
||||
|
||||
Args:
|
||||
console (~rich.console.Console): Console instance.
|
||||
options (~rich.console.ConsoleOptions): Console options.
|
||||
renderables (Iterable[RenderableType]): One or more renderable objects.
|
||||
|
||||
Returns:
|
||||
Measurement: Measurement object containing range of character widths required to
|
||||
contain all given renderables.
|
||||
"""
|
||||
if not renderables:
|
||||
return Measurement(0, 0)
|
||||
get_measurement = Measurement.get
|
||||
measurements = [
|
||||
get_measurement(console, options, renderable) for renderable in renderables
|
||||
]
|
||||
measured_width = Measurement(
|
||||
max(measurements, key=itemgetter(0)).minimum,
|
||||
max(measurements, key=itemgetter(1)).maximum,
|
||||
)
|
||||
return measured_width
|
|
@ -0,0 +1,139 @@
|
|||
from typing import cast, List, Optional, Tuple, TYPE_CHECKING, Union
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .console import (
|
||||
Console,
|
||||
ConsoleOptions,
|
||||
RenderableType,
|
||||
RenderResult,
|
||||
)
|
||||
from .jupyter import JupyterMixin
|
||||
from .measure import Measurement
|
||||
from .style import Style
|
||||
from .segment import Segment
|
||||
|
||||
|
||||
PaddingDimensions = Union[int, Tuple[int], Tuple[int, int], Tuple[int, int, int, int]]
|
||||
|
||||
|
||||
class Padding(JupyterMixin):
|
||||
"""Draw space around content.
|
||||
|
||||
Example:
|
||||
>>> print(Padding("Hello", (2, 4), style="on blue"))
|
||||
|
||||
Args:
|
||||
renderable (RenderableType): String or other renderable.
|
||||
pad (Union[int, Tuple[int]]): Padding for top, right, bottom, and left borders.
|
||||
May be specified with 1, 2, or 4 integers (CSS style).
|
||||
style (Union[str, Style], optional): Style for padding characters. Defaults to "none".
|
||||
expand (bool, optional): Expand padding to fit available width. Defaults to True.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
renderable: "RenderableType",
|
||||
pad: "PaddingDimensions" = (0, 0, 0, 0),
|
||||
*,
|
||||
style: Union[str, Style] = "none",
|
||||
expand: bool = True,
|
||||
):
|
||||
self.renderable = renderable
|
||||
self.top, self.right, self.bottom, self.left = self.unpack(pad)
|
||||
self.style = style
|
||||
self.expand = expand
|
||||
|
||||
@classmethod
|
||||
def indent(cls, renderable: "RenderableType", level: int) -> "Padding":
|
||||
"""Make padding instance to render an indent.
|
||||
|
||||
Args:
|
||||
renderable (RenderableType): String or other renderable.
|
||||
level (int): Number of characters to indent.
|
||||
|
||||
Returns:
|
||||
Padding: A Padding instance.
|
||||
"""
|
||||
|
||||
return Padding(renderable, pad=(0, 0, 0, level), expand=False)
|
||||
|
||||
@staticmethod
|
||||
def unpack(pad: "PaddingDimensions") -> Tuple[int, int, int, int]:
|
||||
"""Unpack padding specified in CSS style."""
|
||||
if isinstance(pad, int):
|
||||
return (pad, pad, pad, pad)
|
||||
if len(pad) == 1:
|
||||
_pad = pad[0]
|
||||
return (_pad, _pad, _pad, _pad)
|
||||
if len(pad) == 2:
|
||||
pad_top, pad_right = cast(Tuple[int, int], pad)
|
||||
return (pad_top, pad_right, pad_top, pad_right)
|
||||
if len(pad) == 4:
|
||||
top, right, bottom, left = cast(Tuple[int, int, int, int], pad)
|
||||
return (top, right, bottom, left)
|
||||
raise ValueError(f"1, 2 or 4 integers required for padding; {len(pad)} given")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Padding({self.renderable!r}, ({self.top},{self.right},{self.bottom},{self.left}))"
|
||||
|
||||
def __rich_console__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> "RenderResult":
|
||||
style = console.get_style(self.style)
|
||||
if self.expand:
|
||||
width = options.max_width
|
||||
else:
|
||||
width = min(
|
||||
Measurement.get(console, options, self.renderable).maximum
|
||||
+ self.left
|
||||
+ self.right,
|
||||
options.max_width,
|
||||
)
|
||||
lines = console.render_lines(
|
||||
self.renderable,
|
||||
options.update_width(width - self.left - self.right),
|
||||
style=style,
|
||||
pad=True,
|
||||
)
|
||||
_Segment = Segment
|
||||
|
||||
left = _Segment(" " * self.left, style) if self.left else None
|
||||
right = (
|
||||
[_Segment(f'{" " * self.right}', style), _Segment.line()]
|
||||
if self.right
|
||||
else [_Segment.line()]
|
||||
)
|
||||
blank_line: Optional[List[Segment]] = None
|
||||
if self.top:
|
||||
blank_line = [_Segment(f'{" " * width}\n', style)]
|
||||
yield from blank_line * self.top
|
||||
if left:
|
||||
for line in lines:
|
||||
yield left
|
||||
yield from line
|
||||
yield from right
|
||||
else:
|
||||
for line in lines:
|
||||
yield from line
|
||||
yield from right
|
||||
if self.bottom:
|
||||
blank_line = blank_line or [_Segment(f'{" " * width}\n', style)]
|
||||
yield from blank_line * self.bottom
|
||||
|
||||
def __rich_measure__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> "Measurement":
|
||||
max_width = options.max_width
|
||||
extra_width = self.left + self.right
|
||||
if max_width - extra_width < 1:
|
||||
return Measurement(max_width, max_width)
|
||||
measure_min, measure_max = Measurement.get(console, options, self.renderable)
|
||||
measurement = Measurement(measure_min + extra_width, measure_max + extra_width)
|
||||
measurement = measurement.with_maximum(max_width)
|
||||
return measurement
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
from pip._vendor.rich import print
|
||||
|
||||
print(Padding("Hello, World", (2, 4), style="on blue"))
|
|
@ -0,0 +1,34 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from typing import Any, Callable
|
||||
|
||||
|
||||
class Pager(ABC):
|
||||
"""Base class for a pager."""
|
||||
|
||||
@abstractmethod
|
||||
def show(self, content: str) -> None:
|
||||
"""Show content in pager.
|
||||
|
||||
Args:
|
||||
content (str): Content to be displayed.
|
||||
"""
|
||||
|
||||
|
||||
class SystemPager(Pager):
|
||||
"""Uses the pager installed on the system."""
|
||||
|
||||
def _pager(self, content: str) -> Any:
|
||||
return __import__("pydoc").pager(content)
|
||||
|
||||
def show(self, content: str) -> None:
|
||||
"""Use the same pager used by pydoc."""
|
||||
self._pager(content)
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
from .__main__ import make_test_card
|
||||
from .console import Console
|
||||
|
||||
console = Console()
|
||||
with console.pager(styles=True):
|
||||
console.print(make_test_card())
|
|
@ -0,0 +1,100 @@
|
|||
from math import sqrt
|
||||
from functools import lru_cache
|
||||
from typing import Sequence, Tuple, TYPE_CHECKING
|
||||
|
||||
from .color_triplet import ColorTriplet
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pip._vendor.rich.table import Table
|
||||
|
||||
|
||||
class Palette:
|
||||
"""A palette of available colors."""
|
||||
|
||||
def __init__(self, colors: Sequence[Tuple[int, int, int]]):
|
||||
self._colors = colors
|
||||
|
||||
def __getitem__(self, number: int) -> ColorTriplet:
|
||||
return ColorTriplet(*self._colors[number])
|
||||
|
||||
def __rich__(self) -> "Table":
|
||||
from pip._vendor.rich.color import Color
|
||||
from pip._vendor.rich.style import Style
|
||||
from pip._vendor.rich.text import Text
|
||||
from pip._vendor.rich.table import Table
|
||||
|
||||
table = Table(
|
||||
"index",
|
||||
"RGB",
|
||||
"Color",
|
||||
title="Palette",
|
||||
caption=f"{len(self._colors)} colors",
|
||||
highlight=True,
|
||||
caption_justify="right",
|
||||
)
|
||||
for index, color in enumerate(self._colors):
|
||||
table.add_row(
|
||||
str(index),
|
||||
repr(color),
|
||||
Text(" " * 16, style=Style(bgcolor=Color.from_rgb(*color))),
|
||||
)
|
||||
return table
|
||||
|
||||
# This is somewhat inefficient and needs caching
|
||||
@lru_cache(maxsize=1024)
|
||||
def match(self, color: Tuple[int, int, int]) -> int:
|
||||
"""Find a color from a palette that most closely matches a given color.
|
||||
|
||||
Args:
|
||||
color (Tuple[int, int, int]): RGB components in range 0 > 255.
|
||||
|
||||
Returns:
|
||||
int: Index of closes matching color.
|
||||
"""
|
||||
red1, green1, blue1 = color
|
||||
_sqrt = sqrt
|
||||
get_color = self._colors.__getitem__
|
||||
|
||||
def get_color_distance(index: int) -> float:
|
||||
"""Get the distance to a color."""
|
||||
red2, green2, blue2 = get_color(index)
|
||||
red_mean = (red1 + red2) // 2
|
||||
red = red1 - red2
|
||||
green = green1 - green2
|
||||
blue = blue1 - blue2
|
||||
return _sqrt(
|
||||
(((512 + red_mean) * red * red) >> 8)
|
||||
+ 4 * green * green
|
||||
+ (((767 - red_mean) * blue * blue) >> 8)
|
||||
)
|
||||
|
||||
min_index = min(range(len(self._colors)), key=get_color_distance)
|
||||
return min_index
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
import colorsys
|
||||
from typing import Iterable
|
||||
from pip._vendor.rich.color import Color
|
||||
from pip._vendor.rich.console import Console, ConsoleOptions
|
||||
from pip._vendor.rich.segment import Segment
|
||||
from pip._vendor.rich.style import Style
|
||||
|
||||
class ColorBox:
|
||||
def __rich_console__(
|
||||
self, console: Console, options: ConsoleOptions
|
||||
) -> Iterable[Segment]:
|
||||
height = console.size.height - 3
|
||||
for y in range(0, height):
|
||||
for x in range(options.max_width):
|
||||
h = x / options.max_width
|
||||
l = y / (height + 1)
|
||||
r1, g1, b1 = colorsys.hls_to_rgb(h, l, 1.0)
|
||||
r2, g2, b2 = colorsys.hls_to_rgb(h, l + (1 / height / 2), 1.0)
|
||||
bgcolor = Color.from_rgb(r1 * 255, g1 * 255, b1 * 255)
|
||||
color = Color.from_rgb(r2 * 255, g2 * 255, b2 * 255)
|
||||
yield Segment("▄", Style(color=color, bgcolor=bgcolor))
|
||||
yield Segment.line()
|
||||
|
||||
console = Console()
|
||||
console.print(ColorBox())
|
|
@ -0,0 +1,250 @@
|
|||
from typing import Optional, TYPE_CHECKING
|
||||
|
||||
from .box import Box, ROUNDED
|
||||
|
||||
from .align import AlignMethod
|
||||
from .jupyter import JupyterMixin
|
||||
from .measure import Measurement, measure_renderables
|
||||
from .padding import Padding, PaddingDimensions
|
||||
from .style import StyleType
|
||||
from .text import Text, TextType
|
||||
from .segment import Segment
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .console import Console, ConsoleOptions, RenderableType, RenderResult
|
||||
|
||||
|
||||
class Panel(JupyterMixin):
|
||||
"""A console renderable that draws a border around its contents.
|
||||
|
||||
Example:
|
||||
>>> console.print(Panel("Hello, World!"))
|
||||
|
||||
Args:
|
||||
renderable (RenderableType): A console renderable object.
|
||||
box (Box, optional): A Box instance that defines the look of the border (see :ref:`appendix_box`.
|
||||
Defaults to box.ROUNDED.
|
||||
safe_box (bool, optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True.
|
||||
expand (bool, optional): If True the panel will stretch to fill the console
|
||||
width, otherwise it will be sized to fit the contents. Defaults to True.
|
||||
style (str, optional): The style of the panel (border and contents). Defaults to "none".
|
||||
border_style (str, optional): The style of the border. Defaults to "none".
|
||||
width (Optional[int], optional): Optional width of panel. Defaults to None to auto-detect.
|
||||
height (Optional[int], optional): Optional height of panel. Defaults to None to auto-detect.
|
||||
padding (Optional[PaddingDimensions]): Optional padding around renderable. Defaults to 0.
|
||||
highlight (bool, optional): Enable automatic highlighting of panel title (if str). Defaults to False.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
renderable: "RenderableType",
|
||||
box: Box = ROUNDED,
|
||||
*,
|
||||
title: Optional[TextType] = None,
|
||||
title_align: AlignMethod = "center",
|
||||
subtitle: Optional[TextType] = None,
|
||||
subtitle_align: AlignMethod = "center",
|
||||
safe_box: Optional[bool] = None,
|
||||
expand: bool = True,
|
||||
style: StyleType = "none",
|
||||
border_style: StyleType = "none",
|
||||
width: Optional[int] = None,
|
||||
height: Optional[int] = None,
|
||||
padding: PaddingDimensions = (0, 1),
|
||||
highlight: bool = False,
|
||||
) -> None:
|
||||
self.renderable = renderable
|
||||
self.box = box
|
||||
self.title = title
|
||||
self.title_align: AlignMethod = title_align
|
||||
self.subtitle = subtitle
|
||||
self.subtitle_align = subtitle_align
|
||||
self.safe_box = safe_box
|
||||
self.expand = expand
|
||||
self.style = style
|
||||
self.border_style = border_style
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.padding = padding
|
||||
self.highlight = highlight
|
||||
|
||||
@classmethod
|
||||
def fit(
|
||||
cls,
|
||||
renderable: "RenderableType",
|
||||
box: Box = ROUNDED,
|
||||
*,
|
||||
title: Optional[TextType] = None,
|
||||
title_align: AlignMethod = "center",
|
||||
subtitle: Optional[TextType] = None,
|
||||
subtitle_align: AlignMethod = "center",
|
||||
safe_box: Optional[bool] = None,
|
||||
style: StyleType = "none",
|
||||
border_style: StyleType = "none",
|
||||
width: Optional[int] = None,
|
||||
padding: PaddingDimensions = (0, 1),
|
||||
) -> "Panel":
|
||||
"""An alternative constructor that sets expand=False."""
|
||||
return cls(
|
||||
renderable,
|
||||
box,
|
||||
title=title,
|
||||
title_align=title_align,
|
||||
subtitle=subtitle,
|
||||
subtitle_align=subtitle_align,
|
||||
safe_box=safe_box,
|
||||
style=style,
|
||||
border_style=border_style,
|
||||
width=width,
|
||||
padding=padding,
|
||||
expand=False,
|
||||
)
|
||||
|
||||
@property
|
||||
def _title(self) -> Optional[Text]:
|
||||
if self.title:
|
||||
title_text = (
|
||||
Text.from_markup(self.title)
|
||||
if isinstance(self.title, str)
|
||||
else self.title.copy()
|
||||
)
|
||||
title_text.end = ""
|
||||
title_text.plain = title_text.plain.replace("\n", " ")
|
||||
title_text.no_wrap = True
|
||||
title_text.expand_tabs()
|
||||
title_text.pad(1)
|
||||
return title_text
|
||||
return None
|
||||
|
||||
@property
|
||||
def _subtitle(self) -> Optional[Text]:
|
||||
if self.subtitle:
|
||||
subtitle_text = (
|
||||
Text.from_markup(self.subtitle)
|
||||
if isinstance(self.subtitle, str)
|
||||
else self.subtitle.copy()
|
||||
)
|
||||
subtitle_text.end = ""
|
||||
subtitle_text.plain = subtitle_text.plain.replace("\n", " ")
|
||||
subtitle_text.no_wrap = True
|
||||
subtitle_text.expand_tabs()
|
||||
subtitle_text.pad(1)
|
||||
return subtitle_text
|
||||
return None
|
||||
|
||||
def __rich_console__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> "RenderResult":
|
||||
_padding = Padding.unpack(self.padding)
|
||||
renderable = (
|
||||
Padding(self.renderable, _padding) if any(_padding) else self.renderable
|
||||
)
|
||||
style = console.get_style(self.style)
|
||||
border_style = style + console.get_style(self.border_style)
|
||||
width = (
|
||||
options.max_width
|
||||
if self.width is None
|
||||
else min(options.max_width, self.width)
|
||||
)
|
||||
|
||||
safe_box: bool = console.safe_box if self.safe_box is None else self.safe_box
|
||||
box = self.box.substitute(options, safe=safe_box)
|
||||
|
||||
title_text = self._title
|
||||
if title_text is not None:
|
||||
title_text.style = border_style
|
||||
|
||||
child_width = (
|
||||
width - 2
|
||||
if self.expand
|
||||
else console.measure(
|
||||
renderable, options=options.update_width(width - 2)
|
||||
).maximum
|
||||
)
|
||||
child_height = self.height or options.height or None
|
||||
if child_height:
|
||||
child_height -= 2
|
||||
if title_text is not None:
|
||||
child_width = min(
|
||||
options.max_width - 2, max(child_width, title_text.cell_len + 2)
|
||||
)
|
||||
|
||||
width = child_width + 2
|
||||
child_options = options.update(
|
||||
width=child_width, height=child_height, highlight=self.highlight
|
||||
)
|
||||
lines = console.render_lines(renderable, child_options, style=style)
|
||||
|
||||
line_start = Segment(box.mid_left, border_style)
|
||||
line_end = Segment(f"{box.mid_right}", border_style)
|
||||
new_line = Segment.line()
|
||||
if title_text is None or width <= 4:
|
||||
yield Segment(box.get_top([width - 2]), border_style)
|
||||
else:
|
||||
title_text.align(self.title_align, width - 4, character=box.top)
|
||||
yield Segment(box.top_left + box.top, border_style)
|
||||
yield from console.render(title_text)
|
||||
yield Segment(box.top + box.top_right, border_style)
|
||||
|
||||
yield new_line
|
||||
for line in lines:
|
||||
yield line_start
|
||||
yield from line
|
||||
yield line_end
|
||||
yield new_line
|
||||
|
||||
subtitle_text = self._subtitle
|
||||
if subtitle_text is not None:
|
||||
subtitle_text.style = border_style
|
||||
|
||||
if subtitle_text is None or width <= 4:
|
||||
yield Segment(box.get_bottom([width - 2]), border_style)
|
||||
else:
|
||||
subtitle_text.align(self.subtitle_align, width - 4, character=box.bottom)
|
||||
yield Segment(box.bottom_left + box.bottom, border_style)
|
||||
yield from console.render(subtitle_text)
|
||||
yield Segment(box.bottom + box.bottom_right, border_style)
|
||||
|
||||
yield new_line
|
||||
|
||||
def __rich_measure__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> "Measurement":
|
||||
_title = self._title
|
||||
_, right, _, left = Padding.unpack(self.padding)
|
||||
padding = left + right
|
||||
renderables = [self.renderable, _title] if _title else [self.renderable]
|
||||
|
||||
if self.width is None:
|
||||
width = (
|
||||
measure_renderables(
|
||||
console,
|
||||
options.update_width(options.max_width - padding - 2),
|
||||
renderables,
|
||||
).maximum
|
||||
+ padding
|
||||
+ 2
|
||||
)
|
||||
else:
|
||||
width = self.width
|
||||
return Measurement(width, width)
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
from .console import Console
|
||||
|
||||
c = Console()
|
||||
|
||||
from .padding import Padding
|
||||
from .box import ROUNDED, DOUBLE
|
||||
|
||||
p = Panel(
|
||||
"Hello, World!",
|
||||
title="rich.Panel",
|
||||
style="white on blue",
|
||||
box=DOUBLE,
|
||||
padding=1,
|
||||
)
|
||||
|
||||
c.print()
|
||||
c.print(p)
|
|
@ -0,0 +1,833 @@
|
|||
import builtins
|
||||
import os
|
||||
from pip._vendor.rich.repr import RichReprResult
|
||||
import sys
|
||||
from array import array
|
||||
from collections import Counter, defaultdict, deque, UserDict, UserList
|
||||
import dataclasses
|
||||
from dataclasses import dataclass, fields, is_dataclass
|
||||
from inspect import isclass
|
||||
from itertools import islice
|
||||
import re
|
||||
from typing import (
|
||||
DefaultDict,
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
Iterable,
|
||||
List,
|
||||
Optional,
|
||||
Set,
|
||||
Union,
|
||||
Tuple,
|
||||
)
|
||||
from types import MappingProxyType
|
||||
|
||||
try:
|
||||
import attr as _attr_module
|
||||
except ImportError: # pragma: no cover
|
||||
_attr_module = None # type: ignore
|
||||
|
||||
|
||||
from .highlighter import ReprHighlighter
|
||||
from . import get_console
|
||||
from ._loop import loop_last
|
||||
from ._pick import pick_bool
|
||||
from .abc import RichRenderable
|
||||
from .cells import cell_len
|
||||
from .highlighter import ReprHighlighter
|
||||
from .jupyter import JupyterMixin, JupyterRenderable
|
||||
from .measure import Measurement
|
||||
from .text import Text
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .console import (
|
||||
Console,
|
||||
ConsoleOptions,
|
||||
HighlighterType,
|
||||
JustifyMethod,
|
||||
OverflowMethod,
|
||||
RenderResult,
|
||||
)
|
||||
|
||||
# Matches Jupyter's special methods
|
||||
_re_jupyter_repr = re.compile(f"^_repr_.+_$")
|
||||
|
||||
|
||||
def _is_attr_object(obj: Any) -> bool:
|
||||
"""Check if an object was created with attrs module."""
|
||||
return _attr_module is not None and _attr_module.has(type(obj))
|
||||
|
||||
|
||||
def _get_attr_fields(obj: Any) -> Iterable["_attr_module.Attribute[Any]"]:
|
||||
"""Get fields for an attrs object."""
|
||||
return _attr_module.fields(type(obj)) if _attr_module is not None else []
|
||||
|
||||
|
||||
def _is_dataclass_repr(obj: object) -> bool:
|
||||
"""Check if an instance of a dataclass contains the default repr.
|
||||
|
||||
Args:
|
||||
obj (object): A dataclass instance.
|
||||
|
||||
Returns:
|
||||
bool: True if the default repr is used, False if there is a custom repr.
|
||||
"""
|
||||
# Digging in to a lot of internals here
|
||||
# Catching all exceptions in case something is missing on a non CPython implementation
|
||||
try:
|
||||
return obj.__repr__.__code__.co_filename == dataclasses.__file__
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def install(
|
||||
console: Optional["Console"] = None,
|
||||
overflow: "OverflowMethod" = "ignore",
|
||||
crop: bool = False,
|
||||
indent_guides: bool = False,
|
||||
max_length: Optional[int] = None,
|
||||
max_string: Optional[int] = None,
|
||||
expand_all: bool = False,
|
||||
) -> None:
|
||||
"""Install automatic pretty printing in the Python REPL.
|
||||
|
||||
Args:
|
||||
console (Console, optional): Console instance or ``None`` to use global console. Defaults to None.
|
||||
overflow (Optional[OverflowMethod], optional): Overflow method. Defaults to "ignore".
|
||||
crop (Optional[bool], optional): Enable cropping of long lines. Defaults to False.
|
||||
indent_guides (bool, optional): Enable indentation guides. Defaults to False.
|
||||
max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
|
||||
Defaults to None.
|
||||
max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to None.
|
||||
expand_all (bool, optional): Expand all containers. Defaults to False.
|
||||
max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100.
|
||||
"""
|
||||
from pip._vendor.rich import get_console
|
||||
|
||||
from .console import ConsoleRenderable # needed here to prevent circular import
|
||||
|
||||
console = console or get_console()
|
||||
assert console is not None
|
||||
|
||||
def display_hook(value: Any) -> None:
|
||||
"""Replacement sys.displayhook which prettifies objects with Rich."""
|
||||
if value is not None:
|
||||
assert console is not None
|
||||
builtins._ = None # type: ignore
|
||||
console.print(
|
||||
value
|
||||
if isinstance(value, RichRenderable)
|
||||
else Pretty(
|
||||
value,
|
||||
overflow=overflow,
|
||||
indent_guides=indent_guides,
|
||||
max_length=max_length,
|
||||
max_string=max_string,
|
||||
expand_all=expand_all,
|
||||
),
|
||||
crop=crop,
|
||||
)
|
||||
builtins._ = value # type: ignore
|
||||
|
||||
def ipy_display_hook(value: Any) -> None: # pragma: no cover
|
||||
assert console is not None
|
||||
# always skip rich generated jupyter renderables or None values
|
||||
if isinstance(value, JupyterRenderable) or value is None:
|
||||
return
|
||||
# on jupyter rich display, if using one of the special representations don't use rich
|
||||
if console.is_jupyter and any(
|
||||
_re_jupyter_repr.match(attr) for attr in dir(value)
|
||||
):
|
||||
return
|
||||
|
||||
# certain renderables should start on a new line
|
||||
if isinstance(value, ConsoleRenderable):
|
||||
console.line()
|
||||
|
||||
console.print(
|
||||
value
|
||||
if isinstance(value, RichRenderable)
|
||||
else Pretty(
|
||||
value,
|
||||
overflow=overflow,
|
||||
indent_guides=indent_guides,
|
||||
max_length=max_length,
|
||||
max_string=max_string,
|
||||
expand_all=expand_all,
|
||||
margin=12,
|
||||
),
|
||||
crop=crop,
|
||||
new_line_start=True,
|
||||
)
|
||||
|
||||
try: # pragma: no cover
|
||||
ip = get_ipython() # type: ignore
|
||||
from IPython.core.formatters import BaseFormatter
|
||||
|
||||
class RichFormatter(BaseFormatter): # type: ignore
|
||||
pprint: bool = True
|
||||
|
||||
def __call__(self, value: Any) -> Any:
|
||||
if self.pprint:
|
||||
return ipy_display_hook(value)
|
||||
else:
|
||||
return repr(value)
|
||||
|
||||
# replace plain text formatter with rich formatter
|
||||
rich_formatter = RichFormatter()
|
||||
ip.display_formatter.formatters["text/plain"] = rich_formatter
|
||||
except Exception:
|
||||
sys.displayhook = display_hook
|
||||
|
||||
|
||||
class Pretty(JupyterMixin):
|
||||
"""A rich renderable that pretty prints an object.
|
||||
|
||||
Args:
|
||||
_object (Any): An object to pretty print.
|
||||
highlighter (HighlighterType, optional): Highlighter object to apply to result, or None for ReprHighlighter. Defaults to None.
|
||||
indent_size (int, optional): Number of spaces in indent. Defaults to 4.
|
||||
justify (JustifyMethod, optional): Justify method, or None for default. Defaults to None.
|
||||
overflow (OverflowMethod, optional): Overflow method, or None for default. Defaults to None.
|
||||
no_wrap (Optional[bool], optional): Disable word wrapping. Defaults to False.
|
||||
indent_guides (bool, optional): Enable indentation guides. Defaults to False.
|
||||
max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
|
||||
Defaults to None.
|
||||
max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to None.
|
||||
expand_all (bool, optional): Expand all containers. Defaults to False.
|
||||
margin (int, optional): Subtrace a margin from width to force containers to expand earlier. Defaults to 0.
|
||||
insert_line (bool, optional): Insert a new line if the output has multiple new lines. Defaults to False.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
_object: Any,
|
||||
highlighter: Optional["HighlighterType"] = None,
|
||||
*,
|
||||
indent_size: int = 4,
|
||||
justify: Optional["JustifyMethod"] = None,
|
||||
overflow: Optional["OverflowMethod"] = None,
|
||||
no_wrap: Optional[bool] = False,
|
||||
indent_guides: bool = False,
|
||||
max_length: Optional[int] = None,
|
||||
max_string: Optional[int] = None,
|
||||
expand_all: bool = False,
|
||||
margin: int = 0,
|
||||
insert_line: bool = False,
|
||||
) -> None:
|
||||
self._object = _object
|
||||
self.highlighter = highlighter or ReprHighlighter()
|
||||
self.indent_size = indent_size
|
||||
self.justify: Optional["JustifyMethod"] = justify
|
||||
self.overflow: Optional["OverflowMethod"] = overflow
|
||||
self.no_wrap = no_wrap
|
||||
self.indent_guides = indent_guides
|
||||
self.max_length = max_length
|
||||
self.max_string = max_string
|
||||
self.expand_all = expand_all
|
||||
self.margin = margin
|
||||
self.insert_line = insert_line
|
||||
|
||||
def __rich_console__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> "RenderResult":
|
||||
pretty_str = pretty_repr(
|
||||
self._object,
|
||||
max_width=options.max_width - self.margin,
|
||||
indent_size=self.indent_size,
|
||||
max_length=self.max_length,
|
||||
max_string=self.max_string,
|
||||
expand_all=self.expand_all,
|
||||
)
|
||||
pretty_text = Text(
|
||||
pretty_str,
|
||||
justify=self.justify or options.justify,
|
||||
overflow=self.overflow or options.overflow,
|
||||
no_wrap=pick_bool(self.no_wrap, options.no_wrap),
|
||||
style="pretty",
|
||||
)
|
||||
pretty_text = (
|
||||
self.highlighter(pretty_text)
|
||||
if pretty_text
|
||||
else Text(
|
||||
f"{type(self._object)}.__repr__ returned empty string",
|
||||
style="dim italic",
|
||||
)
|
||||
)
|
||||
if self.indent_guides and not options.ascii_only:
|
||||
pretty_text = pretty_text.with_indent_guides(
|
||||
self.indent_size, style="repr.indent"
|
||||
)
|
||||
if self.insert_line and "\n" in pretty_text:
|
||||
yield ""
|
||||
yield pretty_text
|
||||
|
||||
def __rich_measure__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> "Measurement":
|
||||
pretty_str = pretty_repr(
|
||||
self._object,
|
||||
max_width=options.max_width,
|
||||
indent_size=self.indent_size,
|
||||
max_length=self.max_length,
|
||||
max_string=self.max_string,
|
||||
)
|
||||
text_width = (
|
||||
max(cell_len(line) for line in pretty_str.splitlines()) if pretty_str else 0
|
||||
)
|
||||
return Measurement(text_width, text_width)
|
||||
|
||||
|
||||
def _get_braces_for_defaultdict(_object: DefaultDict[Any, Any]) -> Tuple[str, str, str]:
|
||||
return (
|
||||
f"defaultdict({_object.default_factory!r}, {{",
|
||||
"})",
|
||||
f"defaultdict({_object.default_factory!r}, {{}})",
|
||||
)
|
||||
|
||||
|
||||
def _get_braces_for_array(_object: "array[Any]") -> Tuple[str, str, str]:
|
||||
return (f"array({_object.typecode!r}, [", "])", "array({_object.typecode!r})")
|
||||
|
||||
|
||||
_BRACES: Dict[type, Callable[[Any], Tuple[str, str, str]]] = {
|
||||
os._Environ: lambda _object: ("environ({", "})", "environ({})"),
|
||||
array: _get_braces_for_array,
|
||||
defaultdict: _get_braces_for_defaultdict,
|
||||
Counter: lambda _object: ("Counter({", "})", "Counter()"),
|
||||
deque: lambda _object: ("deque([", "])", "deque()"),
|
||||
dict: lambda _object: ("{", "}", "{}"),
|
||||
UserDict: lambda _object: ("{", "}", "{}"),
|
||||
frozenset: lambda _object: ("frozenset({", "})", "frozenset()"),
|
||||
list: lambda _object: ("[", "]", "[]"),
|
||||
UserList: lambda _object: ("[", "]", "[]"),
|
||||
set: lambda _object: ("{", "}", "set()"),
|
||||
tuple: lambda _object: ("(", ")", "()"),
|
||||
MappingProxyType: lambda _object: ("mappingproxy({", "})", "mappingproxy({})"),
|
||||
}
|
||||
_CONTAINERS = tuple(_BRACES.keys())
|
||||
_MAPPING_CONTAINERS = (dict, os._Environ, MappingProxyType, UserDict)
|
||||
|
||||
|
||||
def is_expandable(obj: Any) -> bool:
|
||||
"""Check if an object may be expanded by pretty print."""
|
||||
return (
|
||||
isinstance(obj, _CONTAINERS)
|
||||
or (is_dataclass(obj))
|
||||
or (hasattr(obj, "__rich_repr__"))
|
||||
or _is_attr_object(obj)
|
||||
) and not isclass(obj)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Node:
|
||||
"""A node in a repr tree. May be atomic or a container."""
|
||||
|
||||
key_repr: str = ""
|
||||
value_repr: str = ""
|
||||
open_brace: str = ""
|
||||
close_brace: str = ""
|
||||
empty: str = ""
|
||||
last: bool = False
|
||||
is_tuple: bool = False
|
||||
children: Optional[List["Node"]] = None
|
||||
key_separator = ": "
|
||||
separator: str = ", "
|
||||
|
||||
def iter_tokens(self) -> Iterable[str]:
|
||||
"""Generate tokens for this node."""
|
||||
if self.key_repr:
|
||||
yield self.key_repr
|
||||
yield self.key_separator
|
||||
if self.value_repr:
|
||||
yield self.value_repr
|
||||
elif self.children is not None:
|
||||
if self.children:
|
||||
yield self.open_brace
|
||||
if self.is_tuple and len(self.children) == 1:
|
||||
yield from self.children[0].iter_tokens()
|
||||
yield ","
|
||||
else:
|
||||
for child in self.children:
|
||||
yield from child.iter_tokens()
|
||||
if not child.last:
|
||||
yield self.separator
|
||||
yield self.close_brace
|
||||
else:
|
||||
yield self.empty
|
||||
|
||||
def check_length(self, start_length: int, max_length: int) -> bool:
|
||||
"""Check the length fits within a limit.
|
||||
|
||||
Args:
|
||||
start_length (int): Starting length of the line (indent, prefix, suffix).
|
||||
max_length (int): Maximum length.
|
||||
|
||||
Returns:
|
||||
bool: True if the node can be rendered within max length, otherwise False.
|
||||
"""
|
||||
total_length = start_length
|
||||
for token in self.iter_tokens():
|
||||
total_length += cell_len(token)
|
||||
if total_length > max_length:
|
||||
return False
|
||||
return True
|
||||
|
||||
def __str__(self) -> str:
|
||||
repr_text = "".join(self.iter_tokens())
|
||||
return repr_text
|
||||
|
||||
def render(
|
||||
self, max_width: int = 80, indent_size: int = 4, expand_all: bool = False
|
||||
) -> str:
|
||||
"""Render the node to a pretty repr.
|
||||
|
||||
Args:
|
||||
max_width (int, optional): Maximum width of the repr. Defaults to 80.
|
||||
indent_size (int, optional): Size of indents. Defaults to 4.
|
||||
expand_all (bool, optional): Expand all levels. Defaults to False.
|
||||
|
||||
Returns:
|
||||
str: A repr string of the original object.
|
||||
"""
|
||||
lines = [_Line(node=self, is_root=True)]
|
||||
line_no = 0
|
||||
while line_no < len(lines):
|
||||
line = lines[line_no]
|
||||
if line.expandable and not line.expanded:
|
||||
if expand_all or not line.check_length(max_width):
|
||||
lines[line_no : line_no + 1] = line.expand(indent_size)
|
||||
line_no += 1
|
||||
|
||||
repr_str = "\n".join(str(line) for line in lines)
|
||||
return repr_str
|
||||
|
||||
|
||||
@dataclass
|
||||
class _Line:
|
||||
"""A line in repr output."""
|
||||
|
||||
parent: Optional["_Line"] = None
|
||||
is_root: bool = False
|
||||
node: Optional[Node] = None
|
||||
text: str = ""
|
||||
suffix: str = ""
|
||||
whitespace: str = ""
|
||||
expanded: bool = False
|
||||
last: bool = False
|
||||
|
||||
@property
|
||||
def expandable(self) -> bool:
|
||||
"""Check if the line may be expanded."""
|
||||
return bool(self.node is not None and self.node.children)
|
||||
|
||||
def check_length(self, max_length: int) -> bool:
|
||||
"""Check this line fits within a given number of cells."""
|
||||
start_length = (
|
||||
len(self.whitespace) + cell_len(self.text) + cell_len(self.suffix)
|
||||
)
|
||||
assert self.node is not None
|
||||
return self.node.check_length(start_length, max_length)
|
||||
|
||||
def expand(self, indent_size: int) -> Iterable["_Line"]:
|
||||
"""Expand this line by adding children on their own line."""
|
||||
node = self.node
|
||||
assert node is not None
|
||||
whitespace = self.whitespace
|
||||
assert node.children
|
||||
if node.key_repr:
|
||||
new_line = yield _Line(
|
||||
text=f"{node.key_repr}{node.key_separator}{node.open_brace}",
|
||||
whitespace=whitespace,
|
||||
)
|
||||
else:
|
||||
new_line = yield _Line(text=node.open_brace, whitespace=whitespace)
|
||||
child_whitespace = self.whitespace + " " * indent_size
|
||||
tuple_of_one = node.is_tuple and len(node.children) == 1
|
||||
for last, child in loop_last(node.children):
|
||||
separator = "," if tuple_of_one else node.separator
|
||||
line = _Line(
|
||||
parent=new_line,
|
||||
node=child,
|
||||
whitespace=child_whitespace,
|
||||
suffix=separator,
|
||||
last=last and not tuple_of_one,
|
||||
)
|
||||
yield line
|
||||
|
||||
yield _Line(
|
||||
text=node.close_brace,
|
||||
whitespace=whitespace,
|
||||
suffix=self.suffix,
|
||||
last=self.last,
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self.last:
|
||||
return f"{self.whitespace}{self.text}{self.node or ''}"
|
||||
else:
|
||||
return (
|
||||
f"{self.whitespace}{self.text}{self.node or ''}{self.suffix.rstrip()}"
|
||||
)
|
||||
|
||||
|
||||
def traverse(
|
||||
_object: Any, max_length: Optional[int] = None, max_string: Optional[int] = None
|
||||
) -> Node:
|
||||
"""Traverse object and generate a tree.
|
||||
|
||||
Args:
|
||||
_object (Any): Object to be traversed.
|
||||
max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
|
||||
Defaults to None.
|
||||
max_string (int, optional): Maximum length of string before truncating, or None to disable truncating.
|
||||
Defaults to None.
|
||||
|
||||
Returns:
|
||||
Node: The root of a tree structure which can be used to render a pretty repr.
|
||||
"""
|
||||
|
||||
def to_repr(obj: Any) -> str:
|
||||
"""Get repr string for an object, but catch errors."""
|
||||
if (
|
||||
max_string is not None
|
||||
and isinstance(obj, (bytes, str))
|
||||
and len(obj) > max_string
|
||||
):
|
||||
truncated = len(obj) - max_string
|
||||
obj_repr = f"{obj[:max_string]!r}+{truncated}"
|
||||
else:
|
||||
try:
|
||||
obj_repr = repr(obj)
|
||||
except Exception as error:
|
||||
obj_repr = f"<repr-error {str(error)!r}>"
|
||||
return obj_repr
|
||||
|
||||
visited_ids: Set[int] = set()
|
||||
push_visited = visited_ids.add
|
||||
pop_visited = visited_ids.remove
|
||||
|
||||
def _traverse(obj: Any, root: bool = False) -> Node:
|
||||
"""Walk the object depth first."""
|
||||
obj_type = type(obj)
|
||||
py_version = (sys.version_info.major, sys.version_info.minor)
|
||||
children: List[Node]
|
||||
|
||||
def iter_rich_args(rich_args: Any) -> Iterable[Union[Any, Tuple[str, Any]]]:
|
||||
for arg in rich_args:
|
||||
if isinstance(arg, tuple):
|
||||
if len(arg) == 3:
|
||||
key, child, default = arg
|
||||
if default == child:
|
||||
continue
|
||||
yield key, child
|
||||
elif len(arg) == 2:
|
||||
key, child = arg
|
||||
yield key, child
|
||||
elif len(arg) == 1:
|
||||
yield arg[0]
|
||||
else:
|
||||
yield arg
|
||||
|
||||
try:
|
||||
fake_attributes = hasattr(
|
||||
obj, "awehoi234_wdfjwljet234_234wdfoijsdfmmnxpi492"
|
||||
)
|
||||
except Exception:
|
||||
fake_attributes = False
|
||||
|
||||
rich_repr_result: Optional[RichReprResult] = None
|
||||
if not fake_attributes:
|
||||
try:
|
||||
if hasattr(obj, "__rich_repr__") and not isclass(obj):
|
||||
rich_repr_result = obj.__rich_repr__()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if rich_repr_result is not None:
|
||||
angular = getattr(obj.__rich_repr__, "angular", False)
|
||||
args = list(iter_rich_args(rich_repr_result))
|
||||
class_name = obj.__class__.__name__
|
||||
|
||||
if args:
|
||||
children = []
|
||||
append = children.append
|
||||
if angular:
|
||||
node = Node(
|
||||
open_brace=f"<{class_name} ",
|
||||
close_brace=">",
|
||||
children=children,
|
||||
last=root,
|
||||
separator=" ",
|
||||
)
|
||||
else:
|
||||
node = Node(
|
||||
open_brace=f"{class_name}(",
|
||||
close_brace=")",
|
||||
children=children,
|
||||
last=root,
|
||||
)
|
||||
for last, arg in loop_last(args):
|
||||
if isinstance(arg, tuple):
|
||||
key, child = arg
|
||||
child_node = _traverse(child)
|
||||
child_node.last = last
|
||||
child_node.key_repr = key
|
||||
child_node.key_separator = "="
|
||||
append(child_node)
|
||||
else:
|
||||
child_node = _traverse(arg)
|
||||
child_node.last = last
|
||||
append(child_node)
|
||||
else:
|
||||
node = Node(
|
||||
value_repr=f"<{class_name}>" if angular else f"{class_name}()",
|
||||
children=[],
|
||||
last=root,
|
||||
)
|
||||
elif _is_attr_object(obj) and not fake_attributes:
|
||||
children = []
|
||||
append = children.append
|
||||
|
||||
attr_fields = _get_attr_fields(obj)
|
||||
if attr_fields:
|
||||
node = Node(
|
||||
open_brace=f"{obj.__class__.__name__}(",
|
||||
close_brace=")",
|
||||
children=children,
|
||||
last=root,
|
||||
)
|
||||
|
||||
def iter_attrs() -> Iterable[
|
||||
Tuple[str, Any, Optional[Callable[[Any], str]]]
|
||||
]:
|
||||
"""Iterate over attr fields and values."""
|
||||
for attr in attr_fields:
|
||||
if attr.repr:
|
||||
try:
|
||||
value = getattr(obj, attr.name)
|
||||
except Exception as error:
|
||||
# Can happen, albeit rarely
|
||||
yield (attr.name, error, None)
|
||||
else:
|
||||
yield (
|
||||
attr.name,
|
||||
value,
|
||||
attr.repr if callable(attr.repr) else None,
|
||||
)
|
||||
|
||||
for last, (name, value, repr_callable) in loop_last(iter_attrs()):
|
||||
if repr_callable:
|
||||
child_node = Node(value_repr=str(repr_callable(value)))
|
||||
else:
|
||||
child_node = _traverse(value)
|
||||
child_node.last = last
|
||||
child_node.key_repr = name
|
||||
child_node.key_separator = "="
|
||||
append(child_node)
|
||||
else:
|
||||
node = Node(
|
||||
value_repr=f"{obj.__class__.__name__}()", children=[], last=root
|
||||
)
|
||||
|
||||
elif (
|
||||
is_dataclass(obj)
|
||||
and not isinstance(obj, type)
|
||||
and not fake_attributes
|
||||
and (_is_dataclass_repr(obj) or py_version == (3, 6))
|
||||
):
|
||||
obj_id = id(obj)
|
||||
if obj_id in visited_ids:
|
||||
# Recursion detected
|
||||
return Node(value_repr="...")
|
||||
push_visited(obj_id)
|
||||
|
||||
children = []
|
||||
append = children.append
|
||||
node = Node(
|
||||
open_brace=f"{obj.__class__.__name__}(",
|
||||
close_brace=")",
|
||||
children=children,
|
||||
last=root,
|
||||
)
|
||||
|
||||
for last, field in loop_last(field for field in fields(obj) if field.repr):
|
||||
child_node = _traverse(getattr(obj, field.name))
|
||||
child_node.key_repr = field.name
|
||||
child_node.last = last
|
||||
child_node.key_separator = "="
|
||||
append(child_node)
|
||||
|
||||
pop_visited(obj_id)
|
||||
|
||||
elif isinstance(obj, _CONTAINERS):
|
||||
for container_type in _CONTAINERS:
|
||||
if isinstance(obj, container_type):
|
||||
obj_type = container_type
|
||||
break
|
||||
|
||||
obj_id = id(obj)
|
||||
if obj_id in visited_ids:
|
||||
# Recursion detected
|
||||
return Node(value_repr="...")
|
||||
push_visited(obj_id)
|
||||
|
||||
open_brace, close_brace, empty = _BRACES[obj_type](obj)
|
||||
|
||||
if obj_type.__repr__ != type(obj).__repr__:
|
||||
node = Node(value_repr=to_repr(obj), last=root)
|
||||
elif obj:
|
||||
children = []
|
||||
node = Node(
|
||||
open_brace=open_brace,
|
||||
close_brace=close_brace,
|
||||
children=children,
|
||||
last=root,
|
||||
)
|
||||
append = children.append
|
||||
num_items = len(obj)
|
||||
last_item_index = num_items - 1
|
||||
|
||||
if isinstance(obj, _MAPPING_CONTAINERS):
|
||||
iter_items = iter(obj.items())
|
||||
if max_length is not None:
|
||||
iter_items = islice(iter_items, max_length)
|
||||
for index, (key, child) in enumerate(iter_items):
|
||||
child_node = _traverse(child)
|
||||
child_node.key_repr = to_repr(key)
|
||||
child_node.last = index == last_item_index
|
||||
append(child_node)
|
||||
else:
|
||||
iter_values = iter(obj)
|
||||
if max_length is not None:
|
||||
iter_values = islice(iter_values, max_length)
|
||||
for index, child in enumerate(iter_values):
|
||||
child_node = _traverse(child)
|
||||
child_node.last = index == last_item_index
|
||||
append(child_node)
|
||||
if max_length is not None and num_items > max_length:
|
||||
append(Node(value_repr=f"... +{num_items-max_length}", last=True))
|
||||
else:
|
||||
node = Node(empty=empty, children=[], last=root)
|
||||
|
||||
pop_visited(obj_id)
|
||||
else:
|
||||
node = Node(value_repr=to_repr(obj), last=root)
|
||||
node.is_tuple = isinstance(obj, tuple)
|
||||
return node
|
||||
|
||||
node = _traverse(_object, root=True)
|
||||
return node
|
||||
|
||||
|
||||
def pretty_repr(
|
||||
_object: Any,
|
||||
*,
|
||||
max_width: int = 80,
|
||||
indent_size: int = 4,
|
||||
max_length: Optional[int] = None,
|
||||
max_string: Optional[int] = None,
|
||||
expand_all: bool = False,
|
||||
) -> str:
|
||||
"""Prettify repr string by expanding on to new lines to fit within a given width.
|
||||
|
||||
Args:
|
||||
_object (Any): Object to repr.
|
||||
max_width (int, optional): Desired maximum width of repr string. Defaults to 80.
|
||||
indent_size (int, optional): Number of spaces to indent. Defaults to 4.
|
||||
max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
|
||||
Defaults to None.
|
||||
max_string (int, optional): Maximum length of string before truncating, or None to disable truncating.
|
||||
Defaults to None.
|
||||
expand_all (bool, optional): Expand all containers regardless of available width. Defaults to False.
|
||||
|
||||
Returns:
|
||||
str: A possibly multi-line representation of the object.
|
||||
"""
|
||||
|
||||
if isinstance(_object, Node):
|
||||
node = _object
|
||||
else:
|
||||
node = traverse(_object, max_length=max_length, max_string=max_string)
|
||||
repr_str = node.render(
|
||||
max_width=max_width, indent_size=indent_size, expand_all=expand_all
|
||||
)
|
||||
return repr_str
|
||||
|
||||
|
||||
def pprint(
|
||||
_object: Any,
|
||||
*,
|
||||
console: Optional["Console"] = None,
|
||||
indent_guides: bool = True,
|
||||
max_length: Optional[int] = None,
|
||||
max_string: Optional[int] = None,
|
||||
expand_all: bool = False,
|
||||
) -> None:
|
||||
"""A convenience function for pretty printing.
|
||||
|
||||
Args:
|
||||
_object (Any): Object to pretty print.
|
||||
console (Console, optional): Console instance, or None to use default. Defaults to None.
|
||||
max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
|
||||
Defaults to None.
|
||||
max_string (int, optional): Maximum length of strings before truncating, or None to disable. Defaults to None.
|
||||
indent_guides (bool, optional): Enable indentation guides. Defaults to True.
|
||||
expand_all (bool, optional): Expand all containers. Defaults to False.
|
||||
"""
|
||||
_console = get_console() if console is None else console
|
||||
_console.print(
|
||||
Pretty(
|
||||
_object,
|
||||
max_length=max_length,
|
||||
max_string=max_string,
|
||||
indent_guides=indent_guides,
|
||||
expand_all=expand_all,
|
||||
overflow="ignore",
|
||||
),
|
||||
soft_wrap=True,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
|
||||
class BrokenRepr:
|
||||
def __repr__(self) -> str:
|
||||
1 / 0
|
||||
return "this will fail"
|
||||
|
||||
d = defaultdict(int)
|
||||
d["foo"] = 5
|
||||
data = {
|
||||
"foo": [
|
||||
1,
|
||||
"Hello World!",
|
||||
100.123,
|
||||
323.232,
|
||||
432324.0,
|
||||
{5, 6, 7, (1, 2, 3, 4), 8},
|
||||
],
|
||||
"bar": frozenset({1, 2, 3}),
|
||||
"defaultdict": defaultdict(
|
||||
list, {"crumble": ["apple", "rhubarb", "butter", "sugar", "flour"]}
|
||||
),
|
||||
"counter": Counter(
|
||||
[
|
||||
"apple",
|
||||
"orange",
|
||||
"pear",
|
||||
"kumquat",
|
||||
"kumquat",
|
||||
"durian" * 100,
|
||||
]
|
||||
),
|
||||
"atomic": (False, True, None),
|
||||
"Broken": BrokenRepr(),
|
||||
}
|
||||
data["foo"].append(data) # type: ignore
|
||||
|
||||
from pip._vendor.rich import print
|
||||
|
||||
print(Pretty(data, indent_guides=True, max_string=20))
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,216 @@
|
|||
import math
|
||||
from functools import lru_cache
|
||||
from time import monotonic
|
||||
from typing import Iterable, List, Optional
|
||||
|
||||
from .color import Color, blend_rgb
|
||||
from .color_triplet import ColorTriplet
|
||||
from .console import Console, ConsoleOptions, RenderResult
|
||||
from .jupyter import JupyterMixin
|
||||
from .measure import Measurement
|
||||
from .segment import Segment
|
||||
from .style import Style, StyleType
|
||||
|
||||
# Number of characters before 'pulse' animation repeats
|
||||
PULSE_SIZE = 20
|
||||
|
||||
|
||||
class ProgressBar(JupyterMixin):
|
||||
"""Renders a (progress) bar. Used by rich.progress.
|
||||
|
||||
Args:
|
||||
total (float, optional): Number of steps in the bar. Defaults to 100.
|
||||
completed (float, optional): Number of steps completed. Defaults to 0.
|
||||
width (int, optional): Width of the bar, or ``None`` for maximum width. Defaults to None.
|
||||
pulse (bool, optional): Enable pulse effect. Defaults to False.
|
||||
style (StyleType, optional): Style for the bar background. Defaults to "bar.back".
|
||||
complete_style (StyleType, optional): Style for the completed bar. Defaults to "bar.complete".
|
||||
finished_style (StyleType, optional): Style for a finished bar. Defaults to "bar.done".
|
||||
pulse_style (StyleType, optional): Style for pulsing bars. Defaults to "bar.pulse".
|
||||
animation_time (Optional[float], optional): Time in seconds to use for animation, or None to use system time.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
total: float = 100.0,
|
||||
completed: float = 0,
|
||||
width: Optional[int] = None,
|
||||
pulse: bool = False,
|
||||
style: StyleType = "bar.back",
|
||||
complete_style: StyleType = "bar.complete",
|
||||
finished_style: StyleType = "bar.finished",
|
||||
pulse_style: StyleType = "bar.pulse",
|
||||
animation_time: Optional[float] = None,
|
||||
):
|
||||
self.total = total
|
||||
self.completed = completed
|
||||
self.width = width
|
||||
self.pulse = pulse
|
||||
self.style = style
|
||||
self.complete_style = complete_style
|
||||
self.finished_style = finished_style
|
||||
self.pulse_style = pulse_style
|
||||
self.animation_time = animation_time
|
||||
|
||||
self._pulse_segments: Optional[List[Segment]] = None
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Bar {self.completed!r} of {self.total!r}>"
|
||||
|
||||
@property
|
||||
def percentage_completed(self) -> float:
|
||||
"""Calculate percentage complete."""
|
||||
completed = (self.completed / self.total) * 100.0
|
||||
completed = min(100, max(0.0, completed))
|
||||
return completed
|
||||
|
||||
@lru_cache(maxsize=16)
|
||||
def _get_pulse_segments(
|
||||
self,
|
||||
fore_style: Style,
|
||||
back_style: Style,
|
||||
color_system: str,
|
||||
no_color: bool,
|
||||
ascii: bool = False,
|
||||
) -> List[Segment]:
|
||||
"""Get a list of segments to render a pulse animation.
|
||||
|
||||
Returns:
|
||||
List[Segment]: A list of segments, one segment per character.
|
||||
"""
|
||||
bar = "-" if ascii else "━"
|
||||
segments: List[Segment] = []
|
||||
if color_system not in ("standard", "eight_bit", "truecolor") or no_color:
|
||||
segments += [Segment(bar, fore_style)] * (PULSE_SIZE // 2)
|
||||
segments += [Segment(" " if no_color else bar, back_style)] * (
|
||||
PULSE_SIZE - (PULSE_SIZE // 2)
|
||||
)
|
||||
return segments
|
||||
|
||||
append = segments.append
|
||||
fore_color = (
|
||||
fore_style.color.get_truecolor()
|
||||
if fore_style.color
|
||||
else ColorTriplet(255, 0, 255)
|
||||
)
|
||||
back_color = (
|
||||
back_style.color.get_truecolor()
|
||||
if back_style.color
|
||||
else ColorTriplet(0, 0, 0)
|
||||
)
|
||||
cos = math.cos
|
||||
pi = math.pi
|
||||
_Segment = Segment
|
||||
_Style = Style
|
||||
from_triplet = Color.from_triplet
|
||||
|
||||
for index in range(PULSE_SIZE):
|
||||
position = index / PULSE_SIZE
|
||||
fade = 0.5 + cos((position * pi * 2)) / 2.0
|
||||
color = blend_rgb(fore_color, back_color, cross_fade=fade)
|
||||
append(_Segment(bar, _Style(color=from_triplet(color))))
|
||||
return segments
|
||||
|
||||
def update(self, completed: float, total: Optional[float] = None) -> None:
|
||||
"""Update progress with new values.
|
||||
|
||||
Args:
|
||||
completed (float): Number of steps completed.
|
||||
total (float, optional): Total number of steps, or ``None`` to not change. Defaults to None.
|
||||
"""
|
||||
self.completed = completed
|
||||
self.total = total if total is not None else self.total
|
||||
|
||||
def _render_pulse(
|
||||
self, console: Console, width: int, ascii: bool = False
|
||||
) -> Iterable[Segment]:
|
||||
"""Renders the pulse animation.
|
||||
|
||||
Args:
|
||||
console (Console): Console instance.
|
||||
width (int): Width in characters of pulse animation.
|
||||
|
||||
Returns:
|
||||
RenderResult: [description]
|
||||
|
||||
Yields:
|
||||
Iterator[Segment]: Segments to render pulse
|
||||
"""
|
||||
fore_style = console.get_style(self.pulse_style, default="white")
|
||||
back_style = console.get_style(self.style, default="black")
|
||||
|
||||
pulse_segments = self._get_pulse_segments(
|
||||
fore_style, back_style, console.color_system, console.no_color, ascii=ascii
|
||||
)
|
||||
segment_count = len(pulse_segments)
|
||||
current_time = (
|
||||
monotonic() if self.animation_time is None else self.animation_time
|
||||
)
|
||||
segments = pulse_segments * (int(width / segment_count) + 2)
|
||||
offset = int(-current_time * 15) % segment_count
|
||||
segments = segments[offset : offset + width]
|
||||
yield from segments
|
||||
|
||||
def __rich_console__(
|
||||
self, console: Console, options: ConsoleOptions
|
||||
) -> RenderResult:
|
||||
|
||||
width = min(self.width or options.max_width, options.max_width)
|
||||
ascii = options.legacy_windows or options.ascii_only
|
||||
if self.pulse:
|
||||
yield from self._render_pulse(console, width, ascii=ascii)
|
||||
return
|
||||
|
||||
completed = min(self.total, max(0, self.completed))
|
||||
|
||||
bar = "-" if ascii else "━"
|
||||
half_bar_right = " " if ascii else "╸"
|
||||
half_bar_left = " " if ascii else "╺"
|
||||
complete_halves = (
|
||||
int(width * 2 * completed / self.total) if self.total else width * 2
|
||||
)
|
||||
bar_count = complete_halves // 2
|
||||
half_bar_count = complete_halves % 2
|
||||
style = console.get_style(self.style)
|
||||
complete_style = console.get_style(
|
||||
self.complete_style if self.completed < self.total else self.finished_style
|
||||
)
|
||||
_Segment = Segment
|
||||
if bar_count:
|
||||
yield _Segment(bar * bar_count, complete_style)
|
||||
if half_bar_count:
|
||||
yield _Segment(half_bar_right * half_bar_count, complete_style)
|
||||
|
||||
if not console.no_color:
|
||||
remaining_bars = width - bar_count - half_bar_count
|
||||
if remaining_bars and console.color_system is not None:
|
||||
if not half_bar_count and bar_count:
|
||||
yield _Segment(half_bar_left, style)
|
||||
remaining_bars -= 1
|
||||
if remaining_bars:
|
||||
yield _Segment(bar * remaining_bars, style)
|
||||
|
||||
def __rich_measure__(
|
||||
self, console: Console, options: ConsoleOptions
|
||||
) -> Measurement:
|
||||
return (
|
||||
Measurement(self.width, self.width)
|
||||
if self.width is not None
|
||||
else Measurement(4, options.max_width)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
console = Console()
|
||||
bar = ProgressBar(width=50, total=100)
|
||||
|
||||
import time
|
||||
|
||||
console.show_cursor(False)
|
||||
for n in range(0, 101, 1):
|
||||
bar.update(n)
|
||||
console.print(bar)
|
||||
console.file.write("\r")
|
||||
time.sleep(0.05)
|
||||
console.show_cursor(True)
|
||||
console.print()
|
|
@ -0,0 +1,376 @@
|
|||
from typing import Any, Generic, List, Optional, TextIO, TypeVar, Union, overload
|
||||
|
||||
from . import get_console
|
||||
from .console import Console
|
||||
from .text import Text, TextType
|
||||
|
||||
PromptType = TypeVar("PromptType")
|
||||
DefaultType = TypeVar("DefaultType")
|
||||
|
||||
|
||||
class PromptError(Exception):
|
||||
"""Exception base class for prompt related errors."""
|
||||
|
||||
|
||||
class InvalidResponse(PromptError):
|
||||
"""Exception to indicate a response was invalid. Raise this within process_response() to indicate an error
|
||||
and provide an error message.
|
||||
|
||||
Args:
|
||||
message (Union[str, Text]): Error message.
|
||||
"""
|
||||
|
||||
def __init__(self, message: TextType) -> None:
|
||||
self.message = message
|
||||
|
||||
def __rich__(self) -> TextType:
|
||||
return self.message
|
||||
|
||||
|
||||
class PromptBase(Generic[PromptType]):
|
||||
"""Ask the user for input until a valid response is received. This is the base class, see one of
|
||||
the concrete classes for examples.
|
||||
|
||||
Args:
|
||||
prompt (TextType, optional): Prompt text. Defaults to "".
|
||||
console (Console, optional): A Console instance or None to use global console. Defaults to None.
|
||||
password (bool, optional): Enable password input. Defaults to False.
|
||||
choices (List[str], optional): A list of valid choices. Defaults to None.
|
||||
show_default (bool, optional): Show default in prompt. Defaults to True.
|
||||
show_choices (bool, optional): Show choices in prompt. Defaults to True.
|
||||
"""
|
||||
|
||||
response_type: type = str
|
||||
|
||||
validate_error_message = "[prompt.invalid]Please enter a valid value"
|
||||
illegal_choice_message = (
|
||||
"[prompt.invalid.choice]Please select one of the available options"
|
||||
)
|
||||
prompt_suffix = ": "
|
||||
|
||||
choices: Optional[List[str]] = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
prompt: TextType = "",
|
||||
*,
|
||||
console: Optional[Console] = None,
|
||||
password: bool = False,
|
||||
choices: Optional[List[str]] = None,
|
||||
show_default: bool = True,
|
||||
show_choices: bool = True,
|
||||
) -> None:
|
||||
self.console = console or get_console()
|
||||
self.prompt = (
|
||||
Text.from_markup(prompt, style="prompt")
|
||||
if isinstance(prompt, str)
|
||||
else prompt
|
||||
)
|
||||
self.password = password
|
||||
if choices is not None:
|
||||
self.choices = choices
|
||||
self.show_default = show_default
|
||||
self.show_choices = show_choices
|
||||
|
||||
@classmethod
|
||||
@overload
|
||||
def ask(
|
||||
cls,
|
||||
prompt: TextType = "",
|
||||
*,
|
||||
console: Optional[Console] = None,
|
||||
password: bool = False,
|
||||
choices: Optional[List[str]] = None,
|
||||
show_default: bool = True,
|
||||
show_choices: bool = True,
|
||||
default: DefaultType,
|
||||
stream: Optional[TextIO] = None,
|
||||
) -> Union[DefaultType, PromptType]:
|
||||
...
|
||||
|
||||
@classmethod
|
||||
@overload
|
||||
def ask(
|
||||
cls,
|
||||
prompt: TextType = "",
|
||||
*,
|
||||
console: Optional[Console] = None,
|
||||
password: bool = False,
|
||||
choices: Optional[List[str]] = None,
|
||||
show_default: bool = True,
|
||||
show_choices: bool = True,
|
||||
stream: Optional[TextIO] = None,
|
||||
) -> PromptType:
|
||||
...
|
||||
|
||||
@classmethod
|
||||
def ask(
|
||||
cls,
|
||||
prompt: TextType = "",
|
||||
*,
|
||||
console: Optional[Console] = None,
|
||||
password: bool = False,
|
||||
choices: Optional[List[str]] = None,
|
||||
show_default: bool = True,
|
||||
show_choices: bool = True,
|
||||
default: Any = ...,
|
||||
stream: Optional[TextIO] = None,
|
||||
) -> Any:
|
||||
"""Shortcut to construct and run a prompt loop and return the result.
|
||||
|
||||
Example:
|
||||
>>> filename = Prompt.ask("Enter a filename")
|
||||
|
||||
Args:
|
||||
prompt (TextType, optional): Prompt text. Defaults to "".
|
||||
console (Console, optional): A Console instance or None to use global console. Defaults to None.
|
||||
password (bool, optional): Enable password input. Defaults to False.
|
||||
choices (List[str], optional): A list of valid choices. Defaults to None.
|
||||
show_default (bool, optional): Show default in prompt. Defaults to True.
|
||||
show_choices (bool, optional): Show choices in prompt. Defaults to True.
|
||||
stream (TextIO, optional): Optional text file open for reading to get input. Defaults to None.
|
||||
"""
|
||||
_prompt = cls(
|
||||
prompt,
|
||||
console=console,
|
||||
password=password,
|
||||
choices=choices,
|
||||
show_default=show_default,
|
||||
show_choices=show_choices,
|
||||
)
|
||||
return _prompt(default=default, stream=stream)
|
||||
|
||||
def render_default(self, default: DefaultType) -> Text:
|
||||
"""Turn the supplied default in to a Text instance.
|
||||
|
||||
Args:
|
||||
default (DefaultType): Default value.
|
||||
|
||||
Returns:
|
||||
Text: Text containing rendering of default value.
|
||||
"""
|
||||
return Text(f"({default})", "prompt.default")
|
||||
|
||||
def make_prompt(self, default: DefaultType) -> Text:
|
||||
"""Make prompt text.
|
||||
|
||||
Args:
|
||||
default (DefaultType): Default value.
|
||||
|
||||
Returns:
|
||||
Text: Text to display in prompt.
|
||||
"""
|
||||
prompt = self.prompt.copy()
|
||||
prompt.end = ""
|
||||
|
||||
if self.show_choices and self.choices:
|
||||
_choices = "/".join(self.choices)
|
||||
choices = f"[{_choices}]"
|
||||
prompt.append(" ")
|
||||
prompt.append(choices, "prompt.choices")
|
||||
|
||||
if (
|
||||
default != ...
|
||||
and self.show_default
|
||||
and isinstance(default, (str, self.response_type))
|
||||
):
|
||||
prompt.append(" ")
|
||||
_default = self.render_default(default)
|
||||
prompt.append(_default)
|
||||
|
||||
prompt.append(self.prompt_suffix)
|
||||
|
||||
return prompt
|
||||
|
||||
@classmethod
|
||||
def get_input(
|
||||
cls,
|
||||
console: Console,
|
||||
prompt: TextType,
|
||||
password: bool,
|
||||
stream: Optional[TextIO] = None,
|
||||
) -> str:
|
||||
"""Get input from user.
|
||||
|
||||
Args:
|
||||
console (Console): Console instance.
|
||||
prompt (TextType): Prompt text.
|
||||
password (bool): Enable password entry.
|
||||
|
||||
Returns:
|
||||
str: String from user.
|
||||
"""
|
||||
return console.input(prompt, password=password, stream=stream)
|
||||
|
||||
def check_choice(self, value: str) -> bool:
|
||||
"""Check value is in the list of valid choices.
|
||||
|
||||
Args:
|
||||
value (str): Value entered by user.
|
||||
|
||||
Returns:
|
||||
bool: True if choice was valid, otherwise False.
|
||||
"""
|
||||
assert self.choices is not None
|
||||
return value.strip() in self.choices
|
||||
|
||||
def process_response(self, value: str) -> PromptType:
|
||||
"""Process response from user, convert to prompt type.
|
||||
|
||||
Args:
|
||||
value (str): String typed by user.
|
||||
|
||||
Raises:
|
||||
InvalidResponse: If ``value`` is invalid.
|
||||
|
||||
Returns:
|
||||
PromptType: The value to be returned from ask method.
|
||||
"""
|
||||
value = value.strip()
|
||||
try:
|
||||
return_value = self.response_type(value)
|
||||
except ValueError:
|
||||
raise InvalidResponse(self.validate_error_message)
|
||||
|
||||
if self.choices is not None and not self.check_choice(value):
|
||||
raise InvalidResponse(self.illegal_choice_message)
|
||||
|
||||
return return_value # type: ignore
|
||||
|
||||
def on_validate_error(self, value: str, error: InvalidResponse) -> None:
|
||||
"""Called to handle validation error.
|
||||
|
||||
Args:
|
||||
value (str): String entered by user.
|
||||
error (InvalidResponse): Exception instance the initiated the error.
|
||||
"""
|
||||
self.console.print(error)
|
||||
|
||||
def pre_prompt(self) -> None:
|
||||
"""Hook to display something before the prompt."""
|
||||
|
||||
@overload
|
||||
def __call__(self, *, stream: Optional[TextIO] = None) -> PromptType:
|
||||
...
|
||||
|
||||
@overload
|
||||
def __call__(
|
||||
self, *, default: DefaultType, stream: Optional[TextIO] = None
|
||||
) -> Union[PromptType, DefaultType]:
|
||||
...
|
||||
|
||||
def __call__(self, *, default: Any = ..., stream: Optional[TextIO] = None) -> Any:
|
||||
"""Run the prompt loop.
|
||||
|
||||
Args:
|
||||
default (Any, optional): Optional default value.
|
||||
|
||||
Returns:
|
||||
PromptType: Processed value.
|
||||
"""
|
||||
while True:
|
||||
self.pre_prompt()
|
||||
prompt = self.make_prompt(default)
|
||||
value = self.get_input(self.console, prompt, self.password, stream=stream)
|
||||
if value == "" and default != ...:
|
||||
return default
|
||||
try:
|
||||
return_value = self.process_response(value)
|
||||
except InvalidResponse as error:
|
||||
self.on_validate_error(value, error)
|
||||
continue
|
||||
else:
|
||||
return return_value
|
||||
|
||||
|
||||
class Prompt(PromptBase[str]):
|
||||
"""A prompt that returns a str.
|
||||
|
||||
Example:
|
||||
>>> name = Prompt.ask("Enter your name")
|
||||
|
||||
|
||||
"""
|
||||
|
||||
response_type = str
|
||||
|
||||
|
||||
class IntPrompt(PromptBase[int]):
|
||||
"""A prompt that returns an integer.
|
||||
|
||||
Example:
|
||||
>>> burrito_count = IntPrompt.ask("How many burritos do you want to order")
|
||||
|
||||
"""
|
||||
|
||||
response_type = int
|
||||
validate_error_message = "[prompt.invalid]Please enter a valid integer number"
|
||||
|
||||
|
||||
class FloatPrompt(PromptBase[int]):
|
||||
"""A prompt that returns a float.
|
||||
|
||||
Example:
|
||||
>>> temperature = FloatPrompt.ask("Enter desired temperature")
|
||||
|
||||
"""
|
||||
|
||||
response_type = float
|
||||
validate_error_message = "[prompt.invalid]Please enter a number"
|
||||
|
||||
|
||||
class Confirm(PromptBase[bool]):
|
||||
"""A yes / no confirmation prompt.
|
||||
|
||||
Example:
|
||||
>>> if Confirm.ask("Continue"):
|
||||
run_job()
|
||||
|
||||
"""
|
||||
|
||||
response_type = bool
|
||||
validate_error_message = "[prompt.invalid]Please enter Y or N"
|
||||
choices: List[str] = ["y", "n"]
|
||||
|
||||
def render_default(self, default: DefaultType) -> Text:
|
||||
"""Render the default as (y) or (n) rather than True/False."""
|
||||
yes, no = self.choices
|
||||
return Text(f"({yes})" if default else f"({no})", style="prompt.default")
|
||||
|
||||
def process_response(self, value: str) -> bool:
|
||||
"""Convert choices to a bool."""
|
||||
value = value.strip().lower()
|
||||
if value not in self.choices:
|
||||
raise InvalidResponse(self.validate_error_message)
|
||||
return value == self.choices[0]
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
|
||||
from pip._vendor.rich import print
|
||||
|
||||
if Confirm.ask("Run [i]prompt[/i] tests?", default=True):
|
||||
while True:
|
||||
result = IntPrompt.ask(
|
||||
":rocket: Enter a number between [b]1[/b] and [b]10[/b]", default=5
|
||||
)
|
||||
if result >= 1 and result <= 10:
|
||||
break
|
||||
print(":pile_of_poo: [prompt.invalid]Number must be between 1 and 10")
|
||||
print(f"number={result}")
|
||||
|
||||
while True:
|
||||
password = Prompt.ask(
|
||||
"Please enter a password [cyan](must be at least 5 characters)",
|
||||
password=True,
|
||||
)
|
||||
if len(password) >= 5:
|
||||
break
|
||||
print("[prompt.invalid]password too short")
|
||||
print(f"password={password!r}")
|
||||
|
||||
fruit = Prompt.ask("Enter a fruit", choices=["apple", "orange", "pear"])
|
||||
print(f"fruit={fruit!r}")
|
||||
|
||||
else:
|
||||
print("[b]OK :loudly_crying_face:")
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue