pax_global_header00006660000000000000000000000064144315250540014515gustar00rootroot0000000000000052 comment=2ef0410763eff53f0af736c2f08ebd16fa4abb83 pygments-ansi-color-0.3.0/000077500000000000000000000000001443152505400154275ustar00rootroot00000000000000pygments-ansi-color-0.3.0/.activate.sh000077700000000000000000000000001443152505400231052venv/bin/activateustar00rootroot00000000000000pygments-ansi-color-0.3.0/.coveragerc000066400000000000000000000010071443152505400175460ustar00rootroot00000000000000[run] branch = True source = . omit = .tox/* /usr/* setup.py [report] show_missing = True exclude_lines = # Have to re-enable the standard pragma \#\s*pragma: no cover # Don't complain if tests don't hit defensive assertion code: ^\s*raise AssertionError\b ^\s*raise NotImplementedError\b ^\s*return NotImplemented\b ^\s*raise$ # Don't complain if non-runnable code isn't run: ^if __name__ == ['"]__main__['"]:$ [html] directory = coverage-html # vim:ft=dosini pygments-ansi-color-0.3.0/.deactivate.sh000066400000000000000000000000131443152505400201440ustar00rootroot00000000000000deactivate pygments-ansi-color-0.3.0/.gitignore000066400000000000000000000000571443152505400174210ustar00rootroot00000000000000*.py[co] /build /dist /.cache /.coverage /.tox pygments-ansi-color-0.3.0/.pre-commit-config.yaml000066400000000000000000000021261443152505400217110ustar00rootroot00000000000000repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-docstring-first - id: check-merge-conflict - id: check-yaml - id: debug-statements - id: double-quote-string-fixer - id: name-tests-test - id: check-added-large-files - id: check-byte-order-marker - repo: https://github.com/pre-commit/mirrors-autopep8 rev: v2.0.2 hooks: - id: autopep8 - repo: https://github.com/PyCQA/flake8 rev: 6.0.0 hooks: - id: flake8 - repo: https://github.com/asottile/reorder-python-imports rev: v3.9.0 hooks: - id: reorder-python-imports args: [ '--py37-plus', '--add-import', 'from __future__ import annotations', ] - repo: https://github.com/asottile/pyupgrade rev: v3.4.0 hooks: - id: pyupgrade args: ['--py37-plus'] - repo: https://github.com/asottile/add-trailing-comma rev: v2.4.0 hooks: - id: add-trailing-comma args: ['--py36-plus'] pygments-ansi-color-0.3.0/.travis.yml000066400000000000000000000003331443152505400175370ustar00rootroot00000000000000language: python dist: trusty sudo: false install: pip install tox coveralls script: make test python: '3.6' after_success: - coveralls cache: directories: - $HOME/.cache/pip - $HOME/.pre-commit pygments-ansi-color-0.3.0/LICENSE000066400000000000000000000010501443152505400164300ustar00rootroot00000000000000Copyright 2017 Chris Kuehl Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. pygments-ansi-color-0.3.0/Makefile000066400000000000000000000003061443152505400170660ustar00rootroot00000000000000.PHONY: minimal minimal: venv .PHONY: venv venv: tox -e venv .PHONY: test test: tox .PHONY: clean clean: find -name '*.pyc' -delete find -name '__pycache__' -delete rm -rf .tox rm -rf venv pygments-ansi-color-0.3.0/README.md000066400000000000000000000136351443152505400167160ustar00rootroot00000000000000pygments-ansi-color ------------------- [![Build Status](https://travis-ci.org/chriskuehl/pygments-ansi-color.svg?branch=master)](https://travis-ci.org/chriskuehl/pygments-ansi-color) [![Coverage Status](https://coveralls.io/repos/github/chriskuehl/pygments-ansi-color/badge.svg?branch=master)](https://coveralls.io/github/chriskuehl/pygments-ansi-color?branch=master) [![PyPI version](https://badge.fury.io/py/pygments-ansi-color.svg)](https://pypi.python.org/pypi/pygments-ansi-color) An ANSI color-code highlighting lexer for Pygments. ![](https://i.fluffy.cc/nHPkL3gfBtj5Kt4H3RR51T9TJLh6rtv2.png) ### Basic usage 1. Install `pygments-ansi-color`: ```shell-session $ pip install pygments-ansi-color ``` 2. `pygments-ansi-color` is not magic (yet?), so you need to [choose an exising Pygments style](https://pygments.org/styles/), which will be used as a base for your own style. For example, let's choose `pygments.styles.xcode.XcodeStyle`, which looks great to use. And then we will augment this reference style with `pygments-ansi-color`'s color tokens thanks to the `color_tokens` function, to make our final `MyStyle` custom style. Here is how the code looks like: ```python from pygments_ansi_color import color_tokens class MyStyle(pygments.styles.xcode.XcodeStyle): styles = dict(pygments.styles.xcode.XcodeStyle.styles) styles.update(color_tokens()) ``` That's all the custom code you need to integrate with `pygments-ansi-color`. 3. Now you can highlight your content with the dedicated ANSI lexer and your custom style, with the Pygments regular API: ```python import pygments import pygments.formatters import pygments.lexers lexer = pygments.lexers.get_lexer_by_name('ansi-color') formatter = pygments.formatters.HtmlFormatter(style=MyStyle) print(pygments.highlight('your text', lexer, formatter)) ``` ### Design We had to configure above a custom Pygments style with the appropriate color tokens. That's because existing Pygments lexers are built around contextual tokens (think `Comment` or `Punctuation`) rather than actual colors. In the case of ANSI escape sequences, colors have no context beyond the color themselves; we'd always want a `red` rendered as `red`, regardless of your particular theme. ### Custom theme By default, `pygments-ansi-color` maps ANSI codes to its own set of colors. They have been carefully crafted for readability, and are [loosely based on the color scheme used by iTerm2 ](https://github.com/chriskuehl/pygments-ansi-color/pull/27#discussion_r1113790011). Default colors are hard-coded by the `pygments_ansi_color.DEFAULT_STYLE` constant as such: - `Black`: `#000000` - `Red`: `#ef2929` - `Green`: `#8ae234` - `Yellow`: `#fce94f` - `Blue`: `#3465a4` - `Magenta`: `#c509c5` - `Cyan`: `#34e2e2` - `White`: `#f5f5f5` - `BrightBlack`: `#676767` - `BrightRed`: `#ff6d67` - `BrightGreen`: `#5ff967` - `BrightYellow`: `#fefb67` - `BrightBlue`: `#6871ff` - `BrightMagenta`: `#ff76ff` - `BrightCyan`: `#5ffdff` - `BrightWhite`: `#feffff` Still, you may want to use your own colors, to tweak the rendering to your background color, or to match your own theme. For that you can override each color individually, by passing them as arguments to the `color_tokens` function: ```python from pygments_ansi_color import color_tokens class MyStyle(pygments.styles.xcode.XcodeStyle): styles = dict(pygments.styles.xcode.XcodeStyle.styles) styles.update(color_tokens( fg_colors={'Cyan': '#00ffff', 'BrightCyan': '#00ffff'}, bg_colors={'BrightWhite': '#000000'}, )) ``` ### Used by You can see an example [on fluffy][fluffy-example], the project that this lexer was originally developed for. The colors are defined as part of your Pygments style and can be changed. ### Optional: Enable "256 color" support This library supports rendering terminal output using [256 color (8-bit)][256-color] ANSI color codes. However, because of limitations in Pygments tokens, this is an opt-in feature which requires patching the formatter you're using. The reason this requires patching the Pygments formatter is that Pygments does not support multiple tokens on a single piece of text, requiring us to "compose" a single state (which is a tuple of `(bold enabled, fg color, bg color)`) into a single token like `Color.Bold.FGColor.BGColor`. We then need to output the styles for this token in the CSS. In the default mode where we only support the standard 8 colors (plus 1 for no color), we need 2 × 9 × 9 - 1 = 161 tokens, which is reasonable to contain in one CSS file. With 256 colors (plus the standard 8, plus 1 for no color), though, we'd need 2 × 265 × 265 - 1 = 140,449 tokens defined in CSS. This makes the CSS too large to be practical. To make 256-color support realistic, we patch Pygments' HTML formatter so that it places a class for each part of the state tuple independently. This means you need only 1 + 265 + 265 = 531 CSS classes to support all possibilities. If you'd like to enable 256-color support, you'll need to do two things: 1. When calling `color_tokens`, pass `enable_256color=True`: ```python styles.update(color_tokens(enable_256color=True)) ``` This change is what causes your CSS to have the appropriate classes in it. 2. When constructing your formatter, use the `ExtendedColorHtmlFormatterMixin` mixin, like this: ```python from pygments.formatters import HtmlFormatter from pygments_ansi_color import ExtendedColorHtmlFormatterMixin ... class MyFormatter(ExtendedColorHtmlFormatterMixin, HtmlFormatter): pass ... formatter = pygments.formatter.HtmlFormatter(style=MyStyle) ``` This change is what causes the rendered HTML to have the right class names. Once these two changes have been made, you can use pygments-ansi-color as normal. [fluffy-example]: https://i.fluffy.cc/3Gq7Fg86mv3dX30Qx9LHMWcKMqsQLCtd.html [256-color]: https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit pygments-ansi-color-0.3.0/mypy.ini000066400000000000000000000002621443152505400171260ustar00rootroot00000000000000[mypy] check_untyped_defs = true disallow_any_generics = true disallow_incomplete_defs = true disallow_untyped_defs = true warn_redundant_casts = true warn_unused_ignores = true pygments-ansi-color-0.3.0/pygments_ansi_color/000077500000000000000000000000001443152505400215055ustar00rootroot00000000000000pygments-ansi-color-0.3.0/pygments_ansi_color/__init__.py000066400000000000000000000260451443152505400236250ustar00rootroot00000000000000"""Pygments lexer for text containing ANSI color codes.""" from __future__ import annotations import itertools import re import typing import pygments.lexer import pygments.token C = pygments.token.Token.C Color = pygments.token.Token.Color _ansi_code_to_color = { 0: 'Black', 1: 'Red', 2: 'Green', 3: 'Yellow', 4: 'Blue', 5: 'Magenta', 6: 'Cyan', 7: 'White', 60: 'BrightBlack', 61: 'BrightRed', 62: 'BrightGreen', 63: 'BrightYellow', 64: 'BrightBlue', 65: 'BrightMagenta', 66: 'BrightCyan', 67: 'BrightWhite', } _256_colors = { 0: '#000000', 1: '#800000', 2: '#008000', 3: '#808000', 4: '#000080', 5: '#800080', 6: '#008080', 7: '#c0c0c0', 8: '#808080', 9: '#ff0000', 10: '#00ff00', 11: '#ffff00', 12: '#0000ff', 13: '#ff00ff', 14: '#00ffff', 15: '#ffffff', } _vals = (0, 95, 135, 175, 215, 255) _256_colors.update({ 16 + i: '#{:02x}{:02x}{:02x}'.format(*rgb) for i, rgb in enumerate(itertools.product(_vals, _vals, _vals)) }) _256_colors.update({ 232 + i: '#{0:02x}{0:02x}{0:02x}'.format(10 * i + 8) for i in range(24) }) def _token_from_lexer_state( bold: bool, faint: bool, fg_color: str | None, bg_color: str | None, ) -> pygments.token._TokenType: """Construct a token given the current lexer state. We can only emit one token even though we have a multiple-tuple state. To do work around this, we construct tokens like "Bold.Red". """ components: tuple[str, ...] = () if bold: components += ('Bold',) if faint: components += ('Faint',) if fg_color: components += (fg_color,) if bg_color: components += ('BG' + bg_color,) if len(components) == 0: return pygments.token.Text else: token = Color for component in components: token = getattr(token, component) return token DEFAULT_STYLE = { 'Black': '#000000', 'Red': '#ef2929', 'Green': '#8ae234', 'Yellow': '#fce94f', 'Blue': '#3465a4', 'Magenta': '#c509c5', 'Cyan': '#34e2e2', 'White': '#f5f5f5', 'BrightBlack': '#676767', 'BrightRed': '#ff6d67', 'BrightGreen': '#5ff967', 'BrightYellow': '#fefb67', 'BrightBlue': '#6871ff', 'BrightMagenta': '#ff76ff', 'BrightCyan': '#5ffdff', 'BrightWhite': '#feffff', } def color_tokens( fg_colors: dict[str, str] = DEFAULT_STYLE, bg_colors: dict[str, str] = DEFAULT_STYLE, enable_256color: bool = False, ) -> dict[pygments.token._TokenType, str]: """Return color tokens for a given set of colors. Pygments doesn't have a generic "color" token; instead everything is contextual (e.g. "comment" or "variable"). That doesn't make sense for us, where the colors actually *are* what we care about. This function will register combinations of tokens (things like "Red" or "Bold.Red.BGGreen") based on the colors passed in. You can also define the tokens yourself, but note that the token names are *not* currently guaranteed to be stable between releases as I'm not really happy with this approach. Optionally, you can enable 256-color support by passing `enable_256color=True`. This will (very slightly) increase the CSS size, but enable the use of 256-color in text. The reason this is optional and non-default is that it requires patching the Pygments formatter you're using, using the ExtendedColorHtmlFormatterMixin provided by this file. For more details on why and how, see the README. Usage: .. code-block:: python from pygments_ansi_color import color_tokens class MyStyle(pygments.styles.SomeStyle): styles = dict(pygments.styles.SomeStyle.styles) styles.update(color_tokens()) """ styles: dict[pygments.token._TokenType, str] = {} # Validates custom color IDs. if not set(fg_colors).issubset(DEFAULT_STYLE): # pragma: no cover (trivial) raise ValueError( f'Unrecognized {set(fg_colors).difference(DEFAULT_STYLE)}' ' foreground color', ) if not set(bg_colors).issubset(DEFAULT_STYLE): # pragma: no cover (trivial) raise ValueError( f'Unrecognized {set(bg_colors).difference(DEFAULT_STYLE)}' ' background color', ) # Merge the default colors with the user-provided colors. fg_colors = {**DEFAULT_STYLE, **fg_colors} bg_colors = {**DEFAULT_STYLE, **bg_colors} if enable_256color: styles[pygments.token.Token.C.Bold] = 'bold' styles[pygments.token.Token.C.Faint] = '' for i, color in _256_colors.items(): styles[getattr(pygments.token.Token.C, f'C{i}')] = color styles[getattr(pygments.token.Token.C, f'BGC{i}')] = f'bg:{color}' for color, color_value in fg_colors.items(): styles[getattr(C, color)] = color_value for color, color_value in bg_colors.items(): styles[getattr(C, f'BG{color}')] = f'bg:{color_value}' else: for bold, faint, fg_color, bg_color in itertools.product( (False, True), (False, True), {None} | set(fg_colors), {None} | set(bg_colors), ): token = _token_from_lexer_state(bold, faint, fg_color, bg_color) if token is not pygments.token.Text: value: list[str] = [] if bold: value.append('bold') if fg_color: value.append(fg_colors[fg_color]) if bg_color: value.append('bg:' + bg_colors[bg_color]) styles[token] = ' '.join(value) return styles class AnsiColorLexer(pygments.lexer.RegexLexer): name = 'ANSI Color' aliases = ('ansi-color', 'ansi', 'ansi-terminal') flags = re.DOTALL | re.MULTILINE bold: bool fant: bool fg_color: str | None bg_color: str | None def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None: super().__init__(*args, **kwargs) self.reset_state() def reset_state(self) -> None: self.bold = False self.faint = False self.fg_color = None self.bg_color = None @property def current_token(self) -> pygments.token._TokenType: return _token_from_lexer_state( self.bold, self.faint, self.fg_color, self.bg_color, ) def process( self, match: re.Match[str], ) -> typing.Generator[ tuple[int, pygments.token._TokenType, str], None, None, ]: """Produce the next token and bit of text. Interprets the ANSI code (which may be a color code or some other code), changing the lexer state and producing a new token. If it's not a color code, we just strip it out and move on. Some useful reference for ANSI codes: * http://ascii-table.com/ansi-escape-sequences.php """ # "after_escape" contains everything after the start of the escape # sequence, up to the next escape sequence. We still need to separate # the content from the end of the escape sequence. after_escape = match.group(1) # TODO: this doesn't handle the case where the values are non-numeric. # This is rare but can happen for keyboard remapping, e.g. # '\x1b[0;59;"A"p' parsed = re.match( r'([0-9;=]*?)?([a-zA-Z])(.*)$', after_escape, re.DOTALL | re.MULTILINE, ) if parsed is None: # This shouldn't ever happen if we're given valid text + ANSI, but # people can provide us with utter junk, and we should tolerate it. text = after_escape else: value, code, text = parsed.groups() if code == 'm': # "m" is "Set Graphics Mode" # Special case \x1b[m is a reset code if value == '': self.reset_state() else: try: values = [int(v) for v in value.split(';')] except ValueError: # Shouldn't ever happen, but could with invalid ANSI. values = [] while len(values) > 0: value = values.pop(0) fg_color = _ansi_code_to_color.get(value - 30) bg_color = _ansi_code_to_color.get(value - 40) if fg_color: self.fg_color = fg_color elif bg_color: self.bg_color = bg_color elif value == 1: self.bold = True elif value == 2: self.faint = True elif value == 22: self.bold = False self.faint = False elif value == 39: self.fg_color = None elif value == 49: self.bg_color = None elif value == 0: self.reset_state() elif value in (38, 48): try: five = values.pop(0) color = values.pop(0) except IndexError: continue else: if five != 5: continue if 0 <= color <= 255: if value == 38: self.fg_color = f'C{color}' else: self.bg_color = f'C{color}' yield match.start(), self.current_token, text def ignore_unknown_escape(self, match: re.Match[str]) -> typing.Generator[ tuple[int, pygments.token._TokenType, str], None, None, ]: after = match.group(1) # mypy prints these out because it uses curses to determine colors # http://ascii-table.com/ansi-escape-sequences-vt-100.php if re.match(r'\([AB012]', after): yield match.start(), self.current_token, after[2:] else: yield match.start(), self.current_token, after tokens = { # states have to be native strings 'root': [ (r'\x1b\[([^\x1b]*)', process), (r'\x1b([^\x1b]*)', ignore_unknown_escape), (r'[^\x1b]+', pygments.token.Text), ], } class ExtendedColorHtmlFormatterMixin: def _get_css_classes(self, token: pygments.token._TokenType) -> str: classes = super()._get_css_classes(token) # type: ignore if token[0] == 'Color': classes += ' ' + ' '.join( self._get_css_class(getattr(C, part)) # type: ignore for part in token[1:] ) return classes pygments-ansi-color-0.3.0/pygments_ansi_color/py.typed000066400000000000000000000000001443152505400231720ustar00rootroot00000000000000pygments-ansi-color-0.3.0/requirements-dev.txt000066400000000000000000000000661443152505400214710ustar00rootroot00000000000000coverage mypy pre-commit>=1.0.0 pytest types-pygments pygments-ansi-color-0.3.0/setup.py000066400000000000000000000014631443152505400171450ustar00rootroot00000000000000from __future__ import annotations from setuptools import setup setup( name='pygments-ansi-color', version='0.3.0', classifiers=[ 'License :: OSI Approved :: Apache Software License', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', ], python_requires='>=3.7', install_requires=['pygments!=2.7.3'], packages=['pygments_ansi_color'], package_data={ 'pygments_ansi_color': ['py.typed'], }, entry_points={ 'pygments.lexers': [ 'ansi_color = pygments_ansi_color:AnsiColorLexer', ], }, ) pygments-ansi-color-0.3.0/tests/000077500000000000000000000000001443152505400165715ustar00rootroot00000000000000pygments-ansi-color-0.3.0/tests/__init__.py000066400000000000000000000000001443152505400206700ustar00rootroot00000000000000pygments-ansi-color-0.3.0/tests/pygments_ansi_color_test.py000066400000000000000000000176521443152505400242730ustar00rootroot00000000000000from __future__ import annotations import itertools import pytest from pygments.formatters import HtmlFormatter from pygments.token import Text from pygments.token import Token import pygments_ansi_color as main from pygments_ansi_color import C from pygments_ansi_color import Color @pytest.mark.parametrize( ('bold', 'faint', 'fg_color', 'bg_color', 'expected'), ( (False, False, False, False, Text), (True, False, False, False, Color.Bold), (True, False, 'Red', False, Color.Bold.Red), (True, False, 'Red', 'Blue', Color.Bold.Red.BGBlue), (True, True, 'Red', 'Blue', Color.Bold.Faint.Red.BGBlue), ), ) def test_token_from_lexer_state(bold, faint, fg_color, bg_color, expected): ret = main._token_from_lexer_state(bold, faint, fg_color, bg_color) assert ret == expected @pytest.fixture def default_color_tokens(): return dict( itertools.chain.from_iterable( ( (getattr(C, name), value), (getattr(C, f'BG{name}'), f'bg:{value}'), ) for name, value in main.DEFAULT_STYLE.items() ), ) def test_color_tokens(default_color_tokens): fg_colors = {'Red': '#ff0000'} bg_colors = {'Green': '#00ff00'} ret = main.color_tokens(fg_colors, bg_colors) for key, value in { Color.BGGreen: 'bg:#00ff00', Color.Bold: 'bold', Color.Bold.BGGreen: 'bold bg:#00ff00', Color.Bold.Red: 'bold #ff0000', Color.Bold.Red.BGGreen: 'bold #ff0000 bg:#00ff00', Color.Bold.Faint: 'bold', Color.Bold.Faint.BGGreen: 'bold bg:#00ff00', Color.Bold.Faint.Red: 'bold #ff0000', Color.Bold.Faint.Red.BGGreen: 'bold #ff0000 bg:#00ff00', Color.Red: '#ff0000', Color.Red.BGGreen: '#ff0000 bg:#00ff00', Color.Faint: '', Color.Faint.BGGreen: 'bg:#00ff00', Color.Faint.Red: '#ff0000', Color.Faint.Red.BGGreen: '#ff0000 bg:#00ff00', }.items(): assert ret[key] == value def test_color_tokens_256color(default_color_tokens): fg_colors = {'Red': '#ff0000'} bg_colors = {'Green': '#00ff00'} expected = dict(default_color_tokens) expected.update( dict( itertools.chain.from_iterable( ( (getattr(C, f'C{i}'), value), (getattr(C, f'BGC{i}'), f'bg:{value}'), ) for i, value in main._256_colors.items() ), ), ) expected.update({ C.Red: '#ff0000', C.BGGreen: 'bg:#00ff00', C.Bold: 'bold', C.Faint: '', }) assert main.color_tokens( fg_colors, bg_colors, enable_256color=True, ) == expected def _highlight(text): return tuple(main.AnsiColorLexer().get_tokens(text)) def test_plain_text(): assert _highlight('hello world\n') == ( (Text, 'hello world\n'), ) def test_simple_colors(): assert _highlight( 'plain text\n' '\x1b[31mred text\n' '\x1b[1;32mbold green text\n' '\x1b[39mfg color turned off\n' '\x1b[0mplain text after reset\n' '\x1b[1mbold text\n' '\x1b[43mbold from previous line with yellow bg\n' '\x1b[49mbg color turned off\n' '\x1b[92mfg bright green\n' '\x1b[101mbg bright red\n' '\x1b[39;49mcolors turned off\n' '\x1b[2mfaint turned on\n' '\x1b[22mbold turned off\n', ) == ( (Text, 'plain text\n'), (Color.Red, 'red text\n'), (Color.Bold.Green, 'bold green text\n'), (Color.Bold, 'fg color turned off\n'), (Text, 'plain text after reset\n'), (Color.Bold, 'bold text\n'), (Color.Bold.BGYellow, 'bold from previous line with yellow bg\n'), (Color.Bold, 'bg color turned off\n'), (Color.Bold.BrightGreen, 'fg bright green\n'), (Color.Bold.BrightGreen.BGBrightRed, 'bg bright red\n'), (Color.Bold, 'colors turned off\n'), (Color.Bold.Faint, 'faint turned on\n'), (Text, 'bold turned off\n'), ) def test_256_colors(): assert _highlight( 'plain text\n' '\x1b[38;5;15mcolor 15\n' '\x1b[1mbold color 15\n' '\x1b[48;5;8mbold color 15 with color 8 bg\n' '\x1b[38;5;11;22mnot bold color 11 with color 8 bg\n' '\x1b[0mplain text after reset\n', ) == ( (Text, 'plain text\n'), (Color.C15, 'color 15\n'), (Color.Bold.C15, 'bold color 15\n'), (Color.Bold.C15.BGC8, 'bold color 15 with color 8 bg\n'), (Color.C11.BGC8, 'not bold color 11 with color 8 bg\n'), (Text, 'plain text after reset\n'), ) def test_256_colors_invalid_escapes(): assert _highlight( 'plain text\n' # second value is "4" instead of expected "5" '\x1b[38;4;15mA\n' # too few values '\x1b[38;15mB\n' # invalid values (not integers) '\x1b[38;4=;15mC\n' # invalid values (color larger than 255) '\x1b[38;5;937mD\n', ) == ( (Text, 'plain text\n'), (Text, 'A\n'), (Text, 'B\n'), (Text, 'C\n'), (Text, 'D\n'), ) def test_highlight_empty_end_specifier(): ret = _highlight('plain\x1b[31mred\x1b[mplain\n') assert ret == ((Text, 'plain'), (Color.Red, 'red'), (Text, 'plain\n')) @pytest.mark.parametrize( 's', ( pytest.param('\x1b[99m' 'plain text\n', id='unknown int code'), pytest.param('\x1b[=m' 'plain text\n', id='invalid non-int code'), pytest.param('\x1b(B' 'plain text\n', id='other unknown vt100 code'), pytest.param('\x1b' 'plain text\n', id='stray ESC'), ), ) def test_ignores_unrecognized_ansi_color_codes(s): """It should just strip and ignore any unrecognized color ANSI codes.""" assert _highlight(s) == ((Text, 'plain text\n'),) def test_ignores_valid_ansi_non_color_codes(): """It should just strip and ignore any non-color ANSI codes. These include things like moving the cursor position, erasing lines, etc. """ assert _highlight( # restore cursor position '\x1b[u' 'plain ' # move cursor backwards 55 steps '\x1b[55C' 'text\n', ) == ( # Ideally these would be just one token, but our regex isn't smart # enough yet. (Text, 'plain '), (Text, 'text\n'), ) def test_ignores_completely_invalid_escapes(): """It should strip and ignore invalid escapes. This shouldn't happen in valid ANSI text, but we could have an escape followed by garbage. """ assert _highlight( 'plain \x1b[%text\n', ) == ( (Text, 'plain '), (Text, '%text\n'), ) @pytest.fixture def test_formatter(): class TestFormatter(main.ExtendedColorHtmlFormatterMixin, HtmlFormatter): pass return TestFormatter() @pytest.mark.parametrize( ('token', 'expected_classes'), ( # Standard Pygments tokens shouldn't be changed. (Text, ''), (Token.Comment, 'c'), (Token.Comment.Multi, 'c c-Multi'), (Token.Operator, 'o'), # Non-standard (but non-Color) Pygments tokens also shouldn't be changed. (Token.Foo, ' -Foo'), (Token.Foo.Bar.Baz, ' -Foo -Foo-Bar -Foo-Bar-Baz'), # Color tokens should be split out into multiple, non-nested classes prefixed with "C". (Token.Color.Bold, ' -Color -Color-Bold -C-Bold'), (Token.Color.Red, ' -Color -Color-Red -C-Red'), (Token.Color.Bold.Red, ' -Color -Color-Bold -Color-Bold-Red -C-Bold -C-Red'), ( Token.Color.Bold.Red.BGGreen, ' -Color -Color-Bold -Color-Bold-Red -Color-Bold-Red-BGGreen -C-Bold -C-Red -C-BGGreen', ), (Token.Color.C5, ' -Color -Color-C5 -C-C5'), (Token.Color.C5.BGC18, ' -Color -Color-C5 -Color-C5-BGC18 -C-C5 -C-BGC18'), ), ) def test_formatter_mixin_get_css_classes(test_formatter, token, expected_classes): assert test_formatter._get_css_classes(token) == expected_classes pygments-ansi-color-0.3.0/tox.ini000066400000000000000000000010711443152505400167410ustar00rootroot00000000000000[tox] envlist = py tox_pip_extensions_ext_venv_update = true tox_pip_extensions_ext_pip_custom_platform = true [testenv] deps = -rrequirements-dev.txt commands = coverage erase coverage run -m pytest {posargs:tests} coverage report --fail-under 100 mypy pygments_ansi_color pre-commit install -f --install-hooks pre-commit run --all-files [testenv:venv] basepython = /usr/bin/python3.9 envdir = venv commands = [flake8] max-line-length = 119 [pep8] # autopep8 will rewrite lines to be shorter, even though we raised the length ignore = E501