Uploaded Test files

This commit is contained in:
Batuhan Berk Başoğlu 2020-11-12 11:05:57 -05:00
parent f584ad9d97
commit 2e81cb7d99
16627 changed files with 2065359 additions and 102444 deletions

View file

@ -0,0 +1,64 @@
"""
Styling for prompt_toolkit applications.
"""
from .base import (
ANSI_COLOR_NAMES,
DEFAULT_ATTRS,
Attrs,
BaseStyle,
DummyStyle,
DynamicStyle,
)
from .defaults import default_pygments_style, default_ui_style
from .named_colors import NAMED_COLORS
from .pygments import (
pygments_token_to_classname,
style_from_pygments_cls,
style_from_pygments_dict,
)
from .style import Priority, Style, merge_styles, parse_color
from .style_transformation import (
AdjustBrightnessStyleTransformation,
ConditionalStyleTransformation,
DummyStyleTransformation,
DynamicStyleTransformation,
ReverseStyleTransformation,
SetDefaultColorStyleTransformation,
StyleTransformation,
SwapLightAndDarkStyleTransformation,
merge_style_transformations,
)
__all__ = [
# Base.
"Attrs",
"DEFAULT_ATTRS",
"ANSI_COLOR_NAMES",
"BaseStyle",
"DummyStyle",
"DynamicStyle",
# Defaults.
"default_ui_style",
"default_pygments_style",
# Style.
"Style",
"Priority",
"merge_styles",
"parse_color",
# Style transformation.
"StyleTransformation",
"SwapLightAndDarkStyleTransformation",
"ReverseStyleTransformation",
"SetDefaultColorStyleTransformation",
"AdjustBrightnessStyleTransformation",
"DummyStyleTransformation",
"ConditionalStyleTransformation",
"DynamicStyleTransformation",
"merge_style_transformations",
# Pygments.
"style_from_pygments_cls",
"style_from_pygments_dict",
"pygments_token_to_classname",
# Named colors.
"NAMED_COLORS",
]

View file

@ -0,0 +1,181 @@
"""
The base classes for the styling.
"""
from abc import ABCMeta, abstractmethod, abstractproperty
from typing import Callable, Dict, Hashable, List, NamedTuple, Optional, Tuple
__all__ = [
"Attrs",
"DEFAULT_ATTRS",
"ANSI_COLOR_NAMES",
"ANSI_COLOR_NAMES_ALIASES",
"BaseStyle",
"DummyStyle",
"DynamicStyle",
]
#: Style attributes.
Attrs = NamedTuple(
"Attrs",
[
("color", Optional[str]),
("bgcolor", Optional[str]),
("bold", Optional[bool]),
("underline", Optional[bool]),
("italic", Optional[bool]),
("blink", Optional[bool]),
("reverse", Optional[bool]),
("hidden", Optional[bool]),
],
)
"""
:param color: Hexadecimal string. E.g. '000000' or Ansi color name: e.g. 'ansiblue'
:param bgcolor: Hexadecimal string. E.g. 'ffffff' or Ansi color name: e.g. 'ansired'
:param bold: Boolean
:param underline: Boolean
:param italic: Boolean
:param blink: Boolean
:param reverse: Boolean
:param hidden: Boolean
"""
#: The default `Attrs`.
DEFAULT_ATTRS = Attrs(
color="",
bgcolor="",
bold=False,
underline=False,
italic=False,
blink=False,
reverse=False,
hidden=False,
)
#: ``Attrs.bgcolor/fgcolor`` can be in either 'ffffff' format, or can be any of
#: the following in case we want to take colors from the 8/16 color palette.
#: Usually, in that case, the terminal application allows to configure the RGB
#: values for these names.
#: ISO 6429 colors
ANSI_COLOR_NAMES = [
"ansidefault",
# Low intensity, dark. (One or two components 0x80, the other 0x00.)
"ansiblack",
"ansired",
"ansigreen",
"ansiyellow",
"ansiblue",
"ansimagenta",
"ansicyan",
"ansigray",
# High intensity, bright. (One or two components 0xff, the other 0x00. Not supported everywhere.)
"ansibrightblack",
"ansibrightred",
"ansibrightgreen",
"ansibrightyellow",
"ansibrightblue",
"ansibrightmagenta",
"ansibrightcyan",
"ansiwhite",
]
# People don't use the same ANSI color names everywhere. In prompt_toolkit 1.0
# we used some unconventional names (which were contributed like that to
# Pygments). This is fixed now, but we still support the old names.
# The table below maps the old aliases to the current names.
ANSI_COLOR_NAMES_ALIASES: Dict[str, str] = {
"ansidarkgray": "ansibrightblack",
"ansiteal": "ansicyan",
"ansiturquoise": "ansibrightcyan",
"ansibrown": "ansiyellow",
"ansipurple": "ansimagenta",
"ansifuchsia": "ansibrightmagenta",
"ansilightgray": "ansigray",
"ansidarkred": "ansired",
"ansidarkgreen": "ansigreen",
"ansidarkblue": "ansiblue",
}
assert set(ANSI_COLOR_NAMES_ALIASES.values()).issubset(set(ANSI_COLOR_NAMES))
assert not (set(ANSI_COLOR_NAMES_ALIASES.keys()) & set(ANSI_COLOR_NAMES))
class BaseStyle(metaclass=ABCMeta):
"""
Abstract base class for prompt_toolkit styles.
"""
@abstractmethod
def get_attrs_for_style_str(
self, style_str: str, default: Attrs = DEFAULT_ATTRS
) -> Attrs:
"""
Return :class:`.Attrs` for the given style string.
:param style_str: The style string. This can contain inline styling as
well as classnames (e.g. "class:title").
:param default: `Attrs` to be used if no styling was defined.
"""
@abstractproperty
def style_rules(self) -> List[Tuple[str, str]]:
"""
The list of style rules, used to create this style.
(Required for `DynamicStyle` and `_MergedStyle` to work.)
"""
return []
@abstractmethod
def invalidation_hash(self) -> Hashable:
"""
Invalidation hash for the style. When this changes over time, the
renderer knows that something in the style changed, and that everything
has to be redrawn.
"""
class DummyStyle(BaseStyle):
"""
A style that doesn't style anything.
"""
def get_attrs_for_style_str(
self, style_str: str, default: Attrs = DEFAULT_ATTRS
) -> Attrs:
return default
def invalidation_hash(self) -> Hashable:
return 1 # Always the same value.
@property
def style_rules(self) -> List[Tuple[str, str]]:
return []
class DynamicStyle(BaseStyle):
"""
Style class that can dynamically returns an other Style.
:param get_style: Callable that returns a :class:`.Style` instance.
"""
def __init__(self, get_style: Callable[[], Optional[BaseStyle]]):
self.get_style = get_style
self._dummy = DummyStyle()
def get_attrs_for_style_str(
self, style_str: str, default: Attrs = DEFAULT_ATTRS
) -> Attrs:
style = self.get_style() or self._dummy
return style.get_attrs_for_style_str(style_str, default)
def invalidation_hash(self) -> Hashable:
return (self.get_style() or self._dummy).invalidation_hash()
@property
def style_rules(self) -> List[Tuple[str, str]]:
return (self.get_style() or self._dummy).style_rules

View file

@ -0,0 +1,227 @@
"""
The default styling.
"""
from prompt_toolkit.cache import memoized
from .base import ANSI_COLOR_NAMES
from .named_colors import NAMED_COLORS
from .style import BaseStyle, Style, merge_styles
__all__ = [
"default_ui_style",
"default_pygments_style",
]
#: Default styling. Mapping from classnames to their style definition.
PROMPT_TOOLKIT_STYLE = [
# Highlighting of search matches in document.
("search", "bg:ansibrightyellow ansiblack"),
("search.current", ""),
# Incremental search.
("incsearch", ""),
("incsearch.current", "reverse"),
# Highlighting of select text in document.
("selected", "reverse"),
("cursor-column", "bg:#dddddd"),
("cursor-line", "underline"),
("color-column", "bg:#ccaacc"),
# Highlighting of matching brackets.
("matching-bracket", ""),
("matching-bracket.other", "#000000 bg:#aacccc"),
("matching-bracket.cursor", "#ff8888 bg:#880000"),
# Styling of other cursors, in case of block editing.
("multiple-cursors", "#000000 bg:#ccccaa"),
# Line numbers.
("line-number", "#888888"),
("line-number.current", "bold"),
("tilde", "#8888ff"),
# Default prompt.
("prompt", ""),
("prompt.arg", "noinherit"),
("prompt.arg.text", ""),
("prompt.search", "noinherit"),
("prompt.search.text", ""),
# Search toolbar.
("search-toolbar", "bold"),
("search-toolbar.text", "nobold"),
# System toolbar
("system-toolbar", "bold"),
("system-toolbar.text", "nobold"),
# "arg" toolbar.
("arg-toolbar", "bold"),
("arg-toolbar.text", "nobold"),
# Validation toolbar.
("validation-toolbar", "bg:#550000 #ffffff"),
("window-too-small", "bg:#550000 #ffffff"),
# Completions toolbar.
("completion-toolbar", "bg:#bbbbbb #000000"),
("completion-toolbar.arrow", "bg:#bbbbbb #000000 bold"),
("completion-toolbar.completion", "bg:#bbbbbb #000000"),
("completion-toolbar.completion.current", "bg:#444444 #ffffff"),
# Completions menu.
("completion-menu", "bg:#bbbbbb #000000"),
("completion-menu.completion", ""),
("completion-menu.completion.current", "bg:#888888 #ffffff"),
("completion-menu.meta.completion", "bg:#999999 #000000"),
("completion-menu.meta.completion.current", "bg:#aaaaaa #000000"),
("completion-menu.multi-column-meta", "bg:#aaaaaa #000000"),
# Fuzzy matches in completion menu (for FuzzyCompleter).
("completion-menu.completion fuzzymatch.outside", "fg:#444444"),
("completion-menu.completion fuzzymatch.inside", "bold"),
("completion-menu.completion fuzzymatch.inside.character", "underline"),
("completion-menu.completion.current fuzzymatch.outside", "fg:default"),
("completion-menu.completion.current fuzzymatch.inside", "nobold"),
# Styling of readline-like completions.
("readline-like-completions", ""),
("readline-like-completions.completion", ""),
("readline-like-completions.completion fuzzymatch.outside", "#888888"),
("readline-like-completions.completion fuzzymatch.inside", ""),
("readline-like-completions.completion fuzzymatch.inside.character", "underline"),
# Scrollbars.
("scrollbar.background", "bg:#aaaaaa"),
("scrollbar.button", "bg:#444444"),
("scrollbar.arrow", "noinherit bold"),
# Start/end of scrollbars. Adding 'underline' here provides a nice little
# detail to the progress bar, but it doesn't look good on all terminals.
# ('scrollbar.start', 'underline #ffffff'),
# ('scrollbar.end', 'underline #000000'),
# Auto suggestion text.
("auto-suggestion", "#666666"),
# Trailing whitespace and tabs.
("trailing-whitespace", "#999999"),
("tab", "#999999"),
# When Control-C/D has been pressed. Grayed.
("aborting", "#888888 bg:default noreverse noitalic nounderline noblink"),
("exiting", "#888888 bg:default noreverse noitalic nounderline noblink"),
# Entering a Vi digraph.
("digraph", "#4444ff"),
# Control characters, like ^C, ^X.
("control-character", "ansiblue"),
# Non-breaking space.
("nbsp", "underline ansiyellow"),
# Default styling of HTML elements.
("i", "italic"),
("u", "underline"),
("b", "bold"),
("em", "italic"),
("strong", "bold"),
("hidden", "hidden"),
# It should be possible to use the style names in HTML.
# <reverse>...</reverse> or <noreverse>...</noreverse>.
("italic", "italic"),
("underline", "underline"),
("bold", "bold"),
("reverse", "reverse"),
("noitalic", "noitalic"),
("nounderline", "nounderline"),
("nobold", "nobold"),
("noreverse", "noreverse"),
# Prompt bottom toolbar
("bottom-toolbar", "reverse"),
]
# Style that will turn for instance the class 'red' into 'red'.
COLORS_STYLE = [(name, "fg:" + name) for name in ANSI_COLOR_NAMES] + [
(name.lower(), "fg:" + name) for name in NAMED_COLORS
]
WIDGETS_STYLE = [
# Dialog windows.
("dialog", "bg:#4444ff"),
("dialog.body", "bg:#ffffff #000000"),
("dialog.body text-area", "bg:#cccccc"),
("dialog.body text-area last-line", "underline"),
("dialog frame.label", "#ff0000 bold"),
# Scrollbars in dialogs.
("dialog.body scrollbar.background", ""),
("dialog.body scrollbar.button", "bg:#000000"),
("dialog.body scrollbar.arrow", ""),
("dialog.body scrollbar.start", "nounderline"),
("dialog.body scrollbar.end", "nounderline"),
# Buttons.
("button", ""),
("button.arrow", "bold"),
("button.focused", "bg:#aa0000 #ffffff"),
# Menu bars.
("menu-bar", "bg:#aaaaaa #000000"),
("menu-bar.selected-item", "bg:#ffffff #000000"),
("menu", "bg:#888888 #ffffff"),
("menu.border", "#aaaaaa"),
("menu.border shadow", "#444444"),
# Shadows.
("dialog shadow", "bg:#000088"),
("dialog.body shadow", "bg:#aaaaaa"),
("progress-bar", "bg:#000088"),
("progress-bar.used", "bg:#ff0000"),
]
# The default Pygments style, include this by default in case a Pygments lexer
# is used.
PYGMENTS_DEFAULT_STYLE = {
"pygments.whitespace": "#bbbbbb",
"pygments.comment": "italic #408080",
"pygments.comment.preproc": "noitalic #bc7a00",
"pygments.keyword": "bold #008000",
"pygments.keyword.pseudo": "nobold",
"pygments.keyword.type": "nobold #b00040",
"pygments.operator": "#666666",
"pygments.operator.word": "bold #aa22ff",
"pygments.name.builtin": "#008000",
"pygments.name.function": "#0000ff",
"pygments.name.class": "bold #0000ff",
"pygments.name.namespace": "bold #0000ff",
"pygments.name.exception": "bold #d2413a",
"pygments.name.variable": "#19177c",
"pygments.name.constant": "#880000",
"pygments.name.label": "#a0a000",
"pygments.name.entity": "bold #999999",
"pygments.name.attribute": "#7d9029",
"pygments.name.tag": "bold #008000",
"pygments.name.decorator": "#aa22ff",
# Note: In Pygments, Token.String is an alias for Token.Literal.String,
# and Token.Number as an alias for Token.Literal.Number.
"pygments.literal.string": "#ba2121",
"pygments.literal.string.doc": "italic",
"pygments.literal.string.interpol": "bold #bb6688",
"pygments.literal.string.escape": "bold #bb6622",
"pygments.literal.string.regex": "#bb6688",
"pygments.literal.string.symbol": "#19177c",
"pygments.literal.string.other": "#008000",
"pygments.literal.number": "#666666",
"pygments.generic.heading": "bold #000080",
"pygments.generic.subheading": "bold #800080",
"pygments.generic.deleted": "#a00000",
"pygments.generic.inserted": "#00a000",
"pygments.generic.error": "#ff0000",
"pygments.generic.emph": "italic",
"pygments.generic.strong": "bold",
"pygments.generic.prompt": "bold #000080",
"pygments.generic.output": "#888",
"pygments.generic.traceback": "#04d",
"pygments.error": "border:#ff0000",
}
@memoized()
def default_ui_style() -> BaseStyle:
"""
Create a default `Style` object.
"""
return merge_styles(
[
Style(PROMPT_TOOLKIT_STYLE),
Style(COLORS_STYLE),
Style(WIDGETS_STYLE),
]
)
@memoized()
def default_pygments_style() -> Style:
"""
Create a `Style` object that contains the default Pygments style.
"""
return Style.from_dict(PYGMENTS_DEFAULT_STYLE)

View file

@ -0,0 +1,161 @@
"""
All modern web browsers support these 140 color names.
Taken from: https://www.w3schools.com/colors/colors_names.asp
"""
from typing import Dict
__all__ = [
"NAMED_COLORS",
]
NAMED_COLORS: Dict[str, str] = {
"AliceBlue": "#f0f8ff",
"AntiqueWhite": "#faebd7",
"Aqua": "#00ffff",
"Aquamarine": "#7fffd4",
"Azure": "#f0ffff",
"Beige": "#f5f5dc",
"Bisque": "#ffe4c4",
"Black": "#000000",
"BlanchedAlmond": "#ffebcd",
"Blue": "#0000ff",
"BlueViolet": "#8a2be2",
"Brown": "#a52a2a",
"BurlyWood": "#deb887",
"CadetBlue": "#5f9ea0",
"Chartreuse": "#7fff00",
"Chocolate": "#d2691e",
"Coral": "#ff7f50",
"CornflowerBlue": "#6495ed",
"Cornsilk": "#fff8dc",
"Crimson": "#dc143c",
"Cyan": "#00ffff",
"DarkBlue": "#00008b",
"DarkCyan": "#008b8b",
"DarkGoldenRod": "#b8860b",
"DarkGray": "#a9a9a9",
"DarkGreen": "#006400",
"DarkGrey": "#a9a9a9",
"DarkKhaki": "#bdb76b",
"DarkMagenta": "#8b008b",
"DarkOliveGreen": "#556b2f",
"DarkOrange": "#ff8c00",
"DarkOrchid": "#9932cc",
"DarkRed": "#8b0000",
"DarkSalmon": "#e9967a",
"DarkSeaGreen": "#8fbc8f",
"DarkSlateBlue": "#483d8b",
"DarkSlateGray": "#2f4f4f",
"DarkSlateGrey": "#2f4f4f",
"DarkTurquoise": "#00ced1",
"DarkViolet": "#9400d3",
"DeepPink": "#ff1493",
"DeepSkyBlue": "#00bfff",
"DimGray": "#696969",
"DimGrey": "#696969",
"DodgerBlue": "#1e90ff",
"FireBrick": "#b22222",
"FloralWhite": "#fffaf0",
"ForestGreen": "#228b22",
"Fuchsia": "#ff00ff",
"Gainsboro": "#dcdcdc",
"GhostWhite": "#f8f8ff",
"Gold": "#ffd700",
"GoldenRod": "#daa520",
"Gray": "#808080",
"Green": "#008000",
"GreenYellow": "#adff2f",
"Grey": "#808080",
"HoneyDew": "#f0fff0",
"HotPink": "#ff69b4",
"IndianRed": "#cd5c5c",
"Indigo": "#4b0082",
"Ivory": "#fffff0",
"Khaki": "#f0e68c",
"Lavender": "#e6e6fa",
"LavenderBlush": "#fff0f5",
"LawnGreen": "#7cfc00",
"LemonChiffon": "#fffacd",
"LightBlue": "#add8e6",
"LightCoral": "#f08080",
"LightCyan": "#e0ffff",
"LightGoldenRodYellow": "#fafad2",
"LightGray": "#d3d3d3",
"LightGreen": "#90ee90",
"LightGrey": "#d3d3d3",
"LightPink": "#ffb6c1",
"LightSalmon": "#ffa07a",
"LightSeaGreen": "#20b2aa",
"LightSkyBlue": "#87cefa",
"LightSlateGray": "#778899",
"LightSlateGrey": "#778899",
"LightSteelBlue": "#b0c4de",
"LightYellow": "#ffffe0",
"Lime": "#00ff00",
"LimeGreen": "#32cd32",
"Linen": "#faf0e6",
"Magenta": "#ff00ff",
"Maroon": "#800000",
"MediumAquaMarine": "#66cdaa",
"MediumBlue": "#0000cd",
"MediumOrchid": "#ba55d3",
"MediumPurple": "#9370db",
"MediumSeaGreen": "#3cb371",
"MediumSlateBlue": "#7b68ee",
"MediumSpringGreen": "#00fa9a",
"MediumTurquoise": "#48d1cc",
"MediumVioletRed": "#c71585",
"MidnightBlue": "#191970",
"MintCream": "#f5fffa",
"MistyRose": "#ffe4e1",
"Moccasin": "#ffe4b5",
"NavajoWhite": "#ffdead",
"Navy": "#000080",
"OldLace": "#fdf5e6",
"Olive": "#808000",
"OliveDrab": "#6b8e23",
"Orange": "#ffa500",
"OrangeRed": "#ff4500",
"Orchid": "#da70d6",
"PaleGoldenRod": "#eee8aa",
"PaleGreen": "#98fb98",
"PaleTurquoise": "#afeeee",
"PaleVioletRed": "#db7093",
"PapayaWhip": "#ffefd5",
"PeachPuff": "#ffdab9",
"Peru": "#cd853f",
"Pink": "#ffc0cb",
"Plum": "#dda0dd",
"PowderBlue": "#b0e0e6",
"Purple": "#800080",
"RebeccaPurple": "#663399",
"Red": "#ff0000",
"RosyBrown": "#bc8f8f",
"RoyalBlue": "#4169e1",
"SaddleBrown": "#8b4513",
"Salmon": "#fa8072",
"SandyBrown": "#f4a460",
"SeaGreen": "#2e8b57",
"SeaShell": "#fff5ee",
"Sienna": "#a0522d",
"Silver": "#c0c0c0",
"SkyBlue": "#87ceeb",
"SlateBlue": "#6a5acd",
"SlateGray": "#708090",
"SlateGrey": "#708090",
"Snow": "#fffafa",
"SpringGreen": "#00ff7f",
"SteelBlue": "#4682b4",
"Tan": "#d2b48c",
"Teal": "#008080",
"Thistle": "#d8bfd8",
"Tomato": "#ff6347",
"Turquoise": "#40e0d0",
"Violet": "#ee82ee",
"Wheat": "#f5deb3",
"White": "#ffffff",
"WhiteSmoke": "#f5f5f5",
"Yellow": "#ffff00",
"YellowGreen": "#9acd32",
}

View file

@ -0,0 +1,67 @@
"""
Adaptor for building prompt_toolkit styles, starting from a Pygments style.
Usage::
from pygments.styles.tango import TangoStyle
style = style_from_pygments_cls(pygments_style_cls=TangoStyle)
"""
from typing import TYPE_CHECKING, Dict, Type
from .style import Style
if TYPE_CHECKING:
from pygments.style import Style as PygmentsStyle
from pygments.token import Token
__all__ = [
"style_from_pygments_cls",
"style_from_pygments_dict",
"pygments_token_to_classname",
]
def style_from_pygments_cls(pygments_style_cls: Type["PygmentsStyle"]) -> Style:
"""
Shortcut to create a :class:`.Style` instance from a Pygments style class
and a style dictionary.
Example::
from prompt_toolkit.styles.from_pygments import style_from_pygments_cls
from pygments.styles import get_style_by_name
style = style_from_pygments_cls(get_style_by_name('monokai'))
:param pygments_style_cls: Pygments style class to start from.
"""
# Import inline.
from pygments.style import Style as PygmentsStyle
assert issubclass(pygments_style_cls, PygmentsStyle)
return style_from_pygments_dict(pygments_style_cls.styles)
def style_from_pygments_dict(pygments_dict: Dict["Token", str]) -> Style:
"""
Create a :class:`.Style` instance from a Pygments style dictionary.
(One that maps Token objects to style strings.)
"""
pygments_style = []
for token, style in pygments_dict.items():
pygments_style.append((pygments_token_to_classname(token), style))
return Style(pygments_style)
def pygments_token_to_classname(token: "Token") -> str:
"""
Turn e.g. `Token.Name.Exception` into `'pygments.name.exception'`.
(Our Pygments lexer will also turn the tokens that pygments produces in a
prompt_toolkit list of fragments that match these styling rules.)
"""
parts = ("pygments",) + token
return ".".join(parts).lower()

View file

@ -0,0 +1,398 @@
"""
Tool for creating styles from a dictionary.
"""
import itertools
import re
import sys
from enum import Enum
from typing import Dict, Hashable, List, Set, Tuple, TypeVar
from prompt_toolkit.cache import SimpleCache
from .base import (
ANSI_COLOR_NAMES,
ANSI_COLOR_NAMES_ALIASES,
DEFAULT_ATTRS,
Attrs,
BaseStyle,
)
from .named_colors import NAMED_COLORS
__all__ = [
"Style",
"parse_color",
"Priority",
"merge_styles",
]
_named_colors_lowercase = {k.lower(): v.lstrip("#") for k, v in NAMED_COLORS.items()}
def parse_color(text: str) -> str:
"""
Parse/validate color format.
Like in Pygments, but also support the ANSI color names.
(These will map to the colors of the 16 color palette.)
"""
# ANSI color names.
if text in ANSI_COLOR_NAMES:
return text
if text in ANSI_COLOR_NAMES_ALIASES:
return ANSI_COLOR_NAMES_ALIASES[text]
# 140 named colors.
try:
# Replace by 'hex' value.
return _named_colors_lowercase[text.lower()]
except KeyError:
pass
# Hex codes.
if text[0:1] == "#":
col = text[1:]
# Keep this for backwards-compatibility (Pygments does it).
# I don't like the '#' prefix for named colors.
if col in ANSI_COLOR_NAMES:
return col
elif col in ANSI_COLOR_NAMES_ALIASES:
return ANSI_COLOR_NAMES_ALIASES[col]
# 6 digit hex color.
elif len(col) == 6:
return col
# 3 digit hex color.
elif len(col) == 3:
return col[0] * 2 + col[1] * 2 + col[2] * 2
# Default.
elif text in ("", "default"):
return text
raise ValueError("Wrong color format %r" % text)
# Attributes, when they are not filled in by a style. None means that we take
# the value from the parent.
_EMPTY_ATTRS = Attrs(
color=None,
bgcolor=None,
bold=None,
underline=None,
italic=None,
blink=None,
reverse=None,
hidden=None,
)
def _expand_classname(classname: str) -> List[str]:
"""
Split a single class name at the `.` operator, and build a list of classes.
E.g. 'a.b.c' becomes ['a', 'a.b', 'a.b.c']
"""
result = []
parts = classname.split(".")
for i in range(1, len(parts) + 1):
result.append(".".join(parts[:i]).lower())
return result
def _parse_style_str(style_str: str) -> Attrs:
"""
Take a style string, e.g. 'bg:red #88ff00 class:title'
and return a `Attrs` instance.
"""
# Start from default Attrs.
if "noinherit" in style_str:
attrs = DEFAULT_ATTRS
else:
attrs = _EMPTY_ATTRS
# Now update with the given attributes.
for part in style_str.split():
if part == "noinherit":
pass
elif part == "bold":
attrs = attrs._replace(bold=True)
elif part == "nobold":
attrs = attrs._replace(bold=False)
elif part == "italic":
attrs = attrs._replace(italic=True)
elif part == "noitalic":
attrs = attrs._replace(italic=False)
elif part == "underline":
attrs = attrs._replace(underline=True)
elif part == "nounderline":
attrs = attrs._replace(underline=False)
# prompt_toolkit extensions. Not in Pygments.
elif part == "blink":
attrs = attrs._replace(blink=True)
elif part == "noblink":
attrs = attrs._replace(blink=False)
elif part == "reverse":
attrs = attrs._replace(reverse=True)
elif part == "noreverse":
attrs = attrs._replace(reverse=False)
elif part == "hidden":
attrs = attrs._replace(hidden=True)
elif part == "nohidden":
attrs = attrs._replace(hidden=False)
# Pygments properties that we ignore.
elif part in ("roman", "sans", "mono"):
pass
elif part.startswith("border:"):
pass
# Ignore pieces in between square brackets. This is internal stuff.
# Like '[transparent]' or '[set-cursor-position]'.
elif part.startswith("[") and part.endswith("]"):
pass
# Colors.
elif part.startswith("bg:"):
attrs = attrs._replace(bgcolor=parse_color(part[3:]))
elif part.startswith("fg:"): # The 'fg:' prefix is optional.
attrs = attrs._replace(color=parse_color(part[3:]))
else:
attrs = attrs._replace(color=parse_color(part))
return attrs
CLASS_NAMES_RE = re.compile(r"^[a-z0-9.\s_-]*$") # This one can't contain a comma!
class Priority(Enum):
"""
The priority of the rules, when a style is created from a dictionary.
In a `Style`, rules that are defined later will always override previous
defined rules, however in a dictionary, the key order was arbitrary before
Python 3.6. This means that the style could change at random between rules.
We have two options:
- `DICT_KEY_ORDER`: This means, iterate through the dictionary, and take
the key/value pairs in order as they come. This is a good option if you
have Python >3.6. Rules at the end will override rules at the beginning.
- `MOST_PRECISE`: keys that are defined with most precision will get higher
priority. (More precise means: more elements.)
"""
DICT_KEY_ORDER = "KEY_ORDER"
MOST_PRECISE = "MOST_PRECISE"
# In the latest python verions, we take the dictionary ordering like it is,
# In older versions, we sort by by precision. If you need to write code that
# runs on all Python versions, it's best to sort them manually, with the most
# precise rules at the bottom.
if sys.version_info >= (3, 6):
default_priority = Priority.DICT_KEY_ORDER
else:
default_priority = Priority.MOST_PRECISE
class Style(BaseStyle):
"""
Create a ``Style`` instance from a list of style rules.
The `style_rules` is supposed to be a list of ('classnames', 'style') tuples.
The classnames are a whitespace separated string of class names and the
style string is just like a Pygments style definition, but with a few
additions: it supports 'reverse' and 'blink'.
Later rules always override previous rules.
Usage::
Style([
('title', '#ff0000 bold underline'),
('something-else', 'reverse'),
('class1 class2', 'reverse'),
])
The ``from_dict`` classmethod is similar, but takes a dictionary as input.
"""
def __init__(self, style_rules: List[Tuple[str, str]]) -> None:
class_names_and_attrs = []
# Loop through the rules in the order they were defined.
# Rules that are defined later get priority.
for class_names, style_str in style_rules:
assert CLASS_NAMES_RE.match(class_names), repr(class_names)
# The order of the class names doesn't matter.
# (But the order of rules does matter.)
class_names_set = frozenset(class_names.lower().split())
attrs = _parse_style_str(style_str)
class_names_and_attrs.append((class_names_set, attrs))
self._style_rules = style_rules
self.class_names_and_attrs = class_names_and_attrs
@property
def style_rules(self) -> List[Tuple[str, str]]:
return self._style_rules
@classmethod
def from_dict(
cls, style_dict: Dict[str, str], priority: Priority = default_priority
) -> "Style":
"""
:param style_dict: Style dictionary.
:param priority: `Priority` value.
"""
if priority == Priority.MOST_PRECISE:
def key(item: Tuple[str, str]) -> int:
# Split on '.' and whitespace. Count elements.
return sum(len(i.split(".")) for i in item[0].split())
return cls(sorted(style_dict.items(), key=key))
else:
return cls(list(style_dict.items()))
def get_attrs_for_style_str(
self, style_str: str, default: Attrs = DEFAULT_ATTRS
) -> Attrs:
"""
Get `Attrs` for the given style string.
"""
list_of_attrs = [default]
class_names: Set[str] = set()
# Apply default styling.
for names, attr in self.class_names_and_attrs:
if not names:
list_of_attrs.append(attr)
# Go from left to right through the style string. Things on the right
# take precedence.
for part in style_str.split():
# This part represents a class.
# Do lookup of this class name in the style definition, as well
# as all class combinations that we have so far.
if part.startswith("class:"):
# Expand all class names (comma separated list).
new_class_names = []
for p in part[6:].lower().split(","):
new_class_names.extend(_expand_classname(p))
for new_name in new_class_names:
# Build a set of all possible class combinations to be applied.
combos = set()
combos.add(frozenset([new_name]))
for count in range(1, len(class_names) + 1):
for c2 in itertools.combinations(class_names, count):
combos.add(frozenset(c2 + (new_name,)))
# Apply the styles that match these class names.
for names, attr in self.class_names_and_attrs:
if names in combos:
list_of_attrs.append(attr)
class_names.add(new_name)
# Process inline style.
else:
inline_attrs = _parse_style_str(part)
list_of_attrs.append(inline_attrs)
return _merge_attrs(list_of_attrs)
def invalidation_hash(self) -> Hashable:
return id(self.class_names_and_attrs)
_T = TypeVar("_T")
def _merge_attrs(list_of_attrs: List[Attrs]) -> Attrs:
"""
Take a list of :class:`.Attrs` instances and merge them into one.
Every `Attr` in the list can override the styling of the previous one. So,
the last one has highest priority.
"""
def _or(*values: _T) -> _T:
" Take first not-None value, starting at the end. "
for v in values[::-1]:
if v is not None:
return v
raise ValueError # Should not happen, there's always one non-null value.
return Attrs(
color=_or("", *[a.color for a in list_of_attrs]),
bgcolor=_or("", *[a.bgcolor for a in list_of_attrs]),
bold=_or(False, *[a.bold for a in list_of_attrs]),
underline=_or(False, *[a.underline for a in list_of_attrs]),
italic=_or(False, *[a.italic for a in list_of_attrs]),
blink=_or(False, *[a.blink for a in list_of_attrs]),
reverse=_or(False, *[a.reverse for a in list_of_attrs]),
hidden=_or(False, *[a.hidden for a in list_of_attrs]),
)
def merge_styles(styles: List[BaseStyle]) -> "_MergedStyle":
"""
Merge multiple `Style` objects.
"""
styles = [s for s in styles if s is not None]
return _MergedStyle(styles)
class _MergedStyle(BaseStyle):
"""
Merge multiple `Style` objects into one.
This is supposed to ensure consistency: if any of the given styles changes,
then this style will be updated.
"""
# NOTE: previously, we used an algorithm where we did not generate the
# combined style. Instead this was a proxy that called one style
# after the other, passing the outcome of the previous style as the
# default for the next one. This did not work, because that way, the
# priorities like described in the `Style` class don't work.
# 'class:aborted' was for instance never displayed in gray, because
# the next style specified a default color for any text. (The
# explicit styling of class:aborted should have taken priority,
# because it was more precise.)
def __init__(self, styles: List[BaseStyle]) -> None:
self.styles = styles
self._style: SimpleCache[Hashable, Style] = SimpleCache(maxsize=1)
@property
def _merged_style(self) -> Style:
" The `Style` object that has the other styles merged together. "
def get() -> Style:
return Style(self.style_rules)
return self._style.get(self.invalidation_hash(), get)
@property
def style_rules(self) -> List[Tuple[str, str]]:
style_rules = []
for s in self.styles:
style_rules.extend(s.style_rules)
return style_rules
def get_attrs_for_style_str(
self, style_str: str, default: Attrs = DEFAULT_ATTRS
) -> Attrs:
return self._merged_style.get_attrs_for_style_str(style_str, default)
def invalidation_hash(self) -> Hashable:
return tuple(s.invalidation_hash() for s in self.styles)

View file

@ -0,0 +1,375 @@
"""
Collection of style transformations.
Think of it as a kind of color post processing after the rendering is done.
This could be used for instance to change the contrast/saturation; swap light
and dark colors or even change certain colors for other colors.
When the UI is rendered, these transformations can be applied right after the
style strings are turned into `Attrs` objects that represent the actual
formatting.
"""
from abc import ABCMeta, abstractmethod
from colorsys import hls_to_rgb, rgb_to_hls
from typing import Callable, Hashable, Optional, Sequence, Tuple, Union
from prompt_toolkit.cache import memoized
from prompt_toolkit.filters import FilterOrBool, to_filter
from prompt_toolkit.utils import AnyFloat, to_float, to_str
from .base import ANSI_COLOR_NAMES, Attrs
from .style import parse_color
__all__ = [
"StyleTransformation",
"SwapLightAndDarkStyleTransformation",
"ReverseStyleTransformation",
"SetDefaultColorStyleTransformation",
"AdjustBrightnessStyleTransformation",
"DummyStyleTransformation",
"ConditionalStyleTransformation",
"DynamicStyleTransformation",
"merge_style_transformations",
]
class StyleTransformation(metaclass=ABCMeta):
"""
Base class for any style transformation.
"""
@abstractmethod
def transform_attrs(self, attrs: Attrs) -> Attrs:
"""
Take an `Attrs` object and return a new `Attrs` object.
Remember that the color formats can be either "ansi..." or a 6 digit
lowercase hexadecimal color (without '#' prefix).
"""
def invalidation_hash(self) -> Hashable:
"""
When this changes, the cache should be invalidated.
"""
return "%s-%s" % (self.__class__.__name__, id(self))
class SwapLightAndDarkStyleTransformation(StyleTransformation):
"""
Turn dark colors into light colors and the other way around.
This is meant to make color schemes that work on a dark background usable
on a light background (and the other way around).
Notice that this doesn't swap foreground and background like "reverse"
does. It turns light green into dark green and the other way around.
Foreground and background colors are considered individually.
Also notice that when <reverse> is used somewhere and no colors are given
in particular (like what is the default for the bottom toolbar), then this
doesn't change anything. This is what makes sense, because when the
'default' color is chosen, it's what works best for the terminal, and
reverse works good with that.
"""
def transform_attrs(self, attrs: Attrs) -> Attrs:
"""
Return the `Attrs` used when opposite luminosity should be used.
"""
# Reverse colors.
attrs = attrs._replace(color=get_opposite_color(attrs.color))
attrs = attrs._replace(bgcolor=get_opposite_color(attrs.bgcolor))
return attrs
class ReverseStyleTransformation(StyleTransformation):
"""
Swap the 'reverse' attribute.
(This is still experimental.)
"""
def transform_attrs(self, attrs: Attrs) -> Attrs:
return attrs._replace(reverse=not attrs.reverse)
class SetDefaultColorStyleTransformation(StyleTransformation):
"""
Set default foreground/background color for output that doesn't specify
anything. This is useful for overriding the terminal default colors.
:param fg: Color string or callable that returns a color string for the
foreground.
:param bg: Like `fg`, but for the background.
"""
def __init__(
self, fg: Union[str, Callable[[], str]], bg: Union[str, Callable[[], str]]
) -> None:
self.fg = fg
self.bg = bg
def transform_attrs(self, attrs: Attrs) -> Attrs:
if attrs.bgcolor in ("", "default"):
attrs = attrs._replace(bgcolor=parse_color(to_str(self.bg)))
if attrs.color in ("", "default"):
attrs = attrs._replace(color=parse_color(to_str(self.fg)))
return attrs
def invalidation_hash(self) -> Hashable:
return (
"set-default-color",
to_str(self.fg),
to_str(self.bg),
)
class AdjustBrightnessStyleTransformation(StyleTransformation):
"""
Adjust the brightness to improve the rendering on either dark or light
backgrounds.
For dark backgrounds, it's best to increase `min_brightness`. For light
backgrounds it's best to decrease `max_brightness`. Usually, only one
setting is adjusted.
This will only change the brightness for text that has a foreground color
defined, but no background color. It works best for 256 or true color
output.
.. note:: Notice that there is no universal way to detect whether the
application is running in a light or dark terminal. As a
developer of an command line application, you'll have to make
this configurable for the user.
:param min_brightness: Float between 0.0 and 1.0 or a callable that returns
a float.
:param max_brightness: Float between 0.0 and 1.0 or a callable that returns
a float.
"""
def __init__(
self, min_brightness: AnyFloat = 0.0, max_brightness: AnyFloat = 1.0
) -> None:
self.min_brightness = min_brightness
self.max_brightness = max_brightness
def transform_attrs(self, attrs: Attrs) -> Attrs:
min_brightness = to_float(self.min_brightness)
max_brightness = to_float(self.max_brightness)
assert 0 <= min_brightness <= 1
assert 0 <= max_brightness <= 1
# Don't do anything if the whole brightness range is acceptable.
# This also avoids turning ansi colors into RGB sequences.
if min_brightness == 0.0 and max_brightness == 1.0:
return attrs
# If a foreground color is given without a background color.
no_background = not attrs.bgcolor or attrs.bgcolor == "default"
has_fgcolor = attrs.color and attrs.color != "ansidefault"
if has_fgcolor and no_background:
# Calculate new RGB values.
r, g, b = self._color_to_rgb(attrs.color or "")
hue, brightness, saturation = rgb_to_hls(r, g, b)
brightness = self._interpolate_brightness(
brightness, min_brightness, max_brightness
)
r, g, b = hls_to_rgb(hue, brightness, saturation)
new_color = "%02x%02x%02x" % (int(r * 255), int(g * 255), int(b * 255))
attrs = attrs._replace(color=new_color)
return attrs
def _color_to_rgb(self, color: str) -> Tuple[float, float, float]:
"""
Parse `style.Attrs` color into RGB tuple.
"""
# Do RGB lookup for ANSI colors.
try:
from prompt_toolkit.output.vt100 import ANSI_COLORS_TO_RGB
r, g, b = ANSI_COLORS_TO_RGB[color]
return r / 255.0, g / 255.0, b / 255.0
except KeyError:
pass
# Parse RRGGBB format.
return (
int(color[0:2], 16) / 255.0,
int(color[2:4], 16) / 255.0,
int(color[4:6], 16) / 255.0,
)
# NOTE: we don't have to support named colors here. They are already
# transformed into RGB values in `style.parse_color`.
def _interpolate_brightness(
self, value: float, min_brightness: float, max_brightness: float
) -> float:
"""
Map the brightness to the (min_brightness..max_brightness) range.
"""
return min_brightness + (max_brightness - min_brightness) * value
def invalidation_hash(self) -> Hashable:
return (
"adjust-brightness",
to_float(self.min_brightness),
to_float(self.max_brightness),
)
class DummyStyleTransformation(StyleTransformation):
"""
Don't transform anything at all.
"""
def transform_attrs(self, attrs: Attrs) -> Attrs:
return attrs
def invalidation_hash(self) -> Hashable:
# Always return the same hash for these dummy instances.
return "dummy-style-transformation"
class DynamicStyleTransformation(StyleTransformation):
"""
StyleTransformation class that can dynamically returns any
`StyleTransformation`.
:param get_style_transformation: Callable that returns a
:class:`.StyleTransformation` instance.
"""
def __init__(
self, get_style_transformation: Callable[[], Optional[StyleTransformation]]
) -> None:
self.get_style_transformation = get_style_transformation
def transform_attrs(self, attrs: Attrs) -> Attrs:
style_transformation = (
self.get_style_transformation() or DummyStyleTransformation()
)
return style_transformation.transform_attrs(attrs)
def invalidation_hash(self) -> Hashable:
style_transformation = (
self.get_style_transformation() or DummyStyleTransformation()
)
return style_transformation.invalidation_hash()
class ConditionalStyleTransformation(StyleTransformation):
"""
Apply the style transformation depending on a condition.
"""
def __init__(
self, style_transformation: StyleTransformation, filter: FilterOrBool
) -> None:
self.style_transformation = style_transformation
self.filter = to_filter(filter)
def transform_attrs(self, attrs: Attrs) -> Attrs:
if self.filter():
return self.style_transformation.transform_attrs(attrs)
return attrs
def invalidation_hash(self) -> Hashable:
return (self.filter(), self.style_transformation.invalidation_hash())
class _MergedStyleTransformation(StyleTransformation):
def __init__(self, style_transformations: Sequence[StyleTransformation]) -> None:
self.style_transformations = style_transformations
def transform_attrs(self, attrs: Attrs) -> Attrs:
for transformation in self.style_transformations:
attrs = transformation.transform_attrs(attrs)
return attrs
def invalidation_hash(self) -> Hashable:
return tuple(t.invalidation_hash() for t in self.style_transformations)
def merge_style_transformations(
style_transformations: Sequence[StyleTransformation],
) -> StyleTransformation:
"""
Merge multiple transformations together.
"""
return _MergedStyleTransformation(style_transformations)
# Dictionary that maps ANSI color names to their opposite. This is useful for
# turning color schemes that are optimized for a black background usable for a
# white background.
OPPOSITE_ANSI_COLOR_NAMES = {
"ansidefault": "ansidefault",
"ansiblack": "ansiwhite",
"ansired": "ansibrightred",
"ansigreen": "ansibrightgreen",
"ansiyellow": "ansibrightyellow",
"ansiblue": "ansibrightblue",
"ansimagenta": "ansibrightmagenta",
"ansicyan": "ansibrightcyan",
"ansigray": "ansibrightblack",
"ansiwhite": "ansiblack",
"ansibrightred": "ansired",
"ansibrightgreen": "ansigreen",
"ansibrightyellow": "ansiyellow",
"ansibrightblue": "ansiblue",
"ansibrightmagenta": "ansimagenta",
"ansibrightcyan": "ansicyan",
"ansibrightblack": "ansigray",
}
assert set(OPPOSITE_ANSI_COLOR_NAMES.keys()) == set(ANSI_COLOR_NAMES)
assert set(OPPOSITE_ANSI_COLOR_NAMES.values()) == set(ANSI_COLOR_NAMES)
@memoized()
def get_opposite_color(colorname: Optional[str]) -> Optional[str]:
"""
Take a color name in either 'ansi...' format or 6 digit RGB, return the
color of opposite luminosity (same hue/saturation).
This is used for turning color schemes that work on a light background
usable on a dark background.
"""
if colorname is None: # Because color/bgcolor can be None in `Attrs`.
return None
# Special values.
if colorname in ("", "default"):
return colorname
# Try ANSI color names.
try:
return OPPOSITE_ANSI_COLOR_NAMES[colorname]
except KeyError:
# Try 6 digit RGB colors.
r = int(colorname[:2], 16) / 255.0
g = int(colorname[2:4], 16) / 255.0
b = int(colorname[4:6], 16) / 255.0
h, l, s = rgb_to_hls(r, g, b)
l = 1 - l
r, g, b = hls_to_rgb(h, l, s)
r = int(r * 255)
g = int(g * 255)
b = int(b * 255)
return "%02x%02x%02x" % (r, g, b)