Skip to content

Commit

Permalink
theme improvements and docs
Browse files Browse the repository at this point in the history
  • Loading branch information
willmcgugan committed Mar 2, 2020
1 parent c55b357 commit 433ee13
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 19 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Added tab_size to Console and Text
- Added protocol.is_renderable for runtime check
- Added emoji switch to Console
- Added inherit boolean to Theme
- Made Console thread safe, with a thread local buffer

### Changed

Expand Down
29 changes: 22 additions & 7 deletions docs/source/style.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,20 +63,35 @@ It is slightly quicker to construct a Style class like this, since a style defin
You can parse a style definition explicitly with the :meth:`~rich.style.Style.parse` method.


Custom Styles
-------------
Style Themes
------------

While it is possible to explicitly specify styles each time you use them, it is discouraged. Rich allows you to predefine style names which you can use in place on an explicit style. For instance, you may want to define styles for info, warning, danger etc.
If you re-use styles it can be a maintenance headache if you ever want to modify an attribute or color -- you would have to change every line where the style is used. Rich provides a :class:`rich.theme.Theme` class which you can use to pre-define styles, so if you ever need to modify a style you only need change one file.

You can define a mapping of names to styles in the :class:`~rich.console.Console` constructor. These styles will be merged in to the default styles, potentially overwriting them. Here's an example::
Style themes can also make your code more semantic, for instance a style called ``"warning"`` better expresses intent that ``"italic magenta underline"``.

To use a style theme, construct a :class:`rich.theme.Theme` instance and pass it to the :class:`~rich.console.Console` constructor. Here's an example::

from rich.console import Console
styles = {
from rich.theme import Theme
custom_theme = Theme({
"info" : Style.parse("dim cyan"),
"warning": Style.parse("magenta"),
"danger": Style.parse("bold red")
}
console = Console(styles=styles)
})
console = Console(theme=custom_theme)
console.print("This is information", style="info")
console.print("Something terrible happened!", style="danger")

You can also use these custom styles via markup. For example::

console.print("[warning]The pod bay doors are locked[/warning]")

If you prefer you can write your styles in an external config file rather than in Python. Here's an example of the format::

[styles]
info = dim cyan
warning = magenta
danger bold red

You can read these files with the :method:`~rich.theme.Theme.read` method.
21 changes: 15 additions & 6 deletions rich/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import re
import shutil
import sys
import threading
from typing import (
Any,
Callable,
Expand Down Expand Up @@ -286,7 +287,7 @@ def __init__(
else:
self._color_system = COLOR_SYSTEMS[color_system]

self.buffer: List[Segment] = []
self._thread_locals = threading.local()
self._buffer_index = 0
self._record_buffer: List[Segment] = []

Expand All @@ -302,6 +303,14 @@ def __init__(
def __repr__(self) -> str:
return f"<console width={self.width} {str(self._color_system)}>"

@property
def _buffer(self) -> List[Segment]:
"""Get a thread local buffer."""
buffer = getattr(self._thread_locals, "buffer", None)
if buffer is None:
buffer = self._thread_locals.buffer = []
return buffer

def _detect_color_system(self,) -> Optional[ColorSystem]:
"""Detect color system from env vars."""
if not self.is_terminal:
Expand Down Expand Up @@ -423,7 +432,7 @@ def line(self, count: int = 1) -> None:

assert count >= 0, "count must be >= 0"
if count:
self.buffer.append(Segment("\n" * count))
self._buffer.append(Segment("\n" * count))
self._check_buffer()

def _render(
Expand Down Expand Up @@ -771,7 +780,7 @@ def print(
)

render_options = self.options
extend = self.buffer.extend
extend = self._buffer.extend
render = self.render
with self.style(style):
for renderable in renderables:
Expand Down Expand Up @@ -838,7 +847,7 @@ def log(
renderables.append(tabulate_mapping(locals_map, title="Locals"))

with self:
self.buffer.extend(
self._buffer.extend(
self.render(
self._log_render(self, renderables, path=path, line_no=line_no),
self.options,
Expand All @@ -856,10 +865,10 @@ def _render_buffer(self) -> str:
output: List[str] = []
append = output.append
color_system = self._color_system
buffer = self.buffer[:]
buffer = self._buffer[:]
if self.record:
self._record_buffer.extend(buffer)
del self.buffer[:]
del self._buffer[:]
for line in Segment.split_and_crop_lines(buffer, self.width, pad=False):
for text, style in line:
if style:
Expand Down
46 changes: 40 additions & 6 deletions rich/theme.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
import configparser
from typing import Dict, IO

from .default_styles import DEFAULT_STYLES
from .style import Style


class Theme:
def __init__(self, styles: Dict[str, Style] = None):
self.styles = styles or {}
"""An container for style information, used by :class:`~rich.console.Console`.
Args:
styles (Dict[str, Style], optional): A mapping of style names on to styles. Defaults to None for empty styles.
inherit (bool, optional): Switch to inherit default styles. Defaults to True.
"""

def __init__(self, styles: Dict[str, Style] = None, inherit: bool = True):
if inherit:
self.styles = DEFAULT_STYLES
else:
self.styles = {}
if styles is not None:
self.styles.update(styles)

@property
def config(self) -> str:
Expand All @@ -19,14 +32,35 @@ def config(self) -> str:
return config

@classmethod
def from_file(cls, config_file: IO[str], source: str = None) -> "Theme":
def from_file(
cls, config_file: IO[str], source: str = None, inherit: bool = True
) -> "Theme":
"""Load a theme from a text mode file.
Args:
config_file (IO[str]): An open conf file.
source (str, optional): The filename of the open file. Defaults to None.
inherit (bool, optional): Switch to inherit default styles. Defaults to True.
Returns:
Theme: A New theme instance.
"""
config = configparser.ConfigParser()
config.read_file(config_file, source=source)
styles = {name: Style.parse(value) for name, value in config.items("styles")}
theme = Theme(styles)
theme = Theme(styles, inherit=inherit)
return theme

@classmethod
def read(cls, path: str) -> "Theme":
def read(cls, path: str, inherit: bool = True) -> "Theme":
"""Read a theme from a path.
Args:
path (str): Path to a config file readable by Python configparser module.
inherit (bool, optional): Switch to inherit default styles. Defaults to True.
Returns:
Theme: A new theme instance.
"""
with open(path, "rt") as config_file:
return cls.from_file(config_file, source=path)
return cls.from_file(config_file, source=path, inherit=inherit)

0 comments on commit 433ee13

Please sign in to comment.