pytermgui.terminal
This module houses the Terminal
class, and its provided instance.
View Source
"""This module houses the `Terminal` class, and its provided instance.""" # pylint: disable=cyclic-import from __future__ import annotations import os import sys import time import errno import signal from enum import Enum from datetime import datetime from shutil import get_terminal_size from contextlib import contextmanager from typing import Any, Callable, TextIO, Generator from .input import getch_timeout from .regex import strip_ansi, real_length, RE_PIXEL_SIZE, has_open_sequence __all__ = [ "terminal", "set_global_terminal", "get_terminal", "Terminal", "Recorder", "ColorSystem", ] class Recorder: """A class that records & exports terminal content.""" def __init__(self) -> None: """Initializes the Recorder.""" self.recording: list[tuple[str, float]] = [] self._start_stamp = time.time() @property def _content(self) -> str: """Returns the str part of self._recording""" return "".join(data for data, _ in self.recording) def write(self, data: str) -> None: """Writes to the recorder.""" self.recording.append((data, time.time() - self._start_stamp)) def export_text(self) -> str: """Exports current content as plain text.""" return strip_ansi(self._content) def export_html( self, prefix: str | None = None, inline_styles: bool = False ) -> str: """Exports current content as HTML. For help on the arguments, see `pytermgui.html.to_html`. """ from .exporters import to_html # pylint: disable=import-outside-toplevel return to_html(self._content, prefix=prefix, inline_styles=inline_styles) def export_svg( self, prefix: str | None = None, inline_styles: bool = False, title: str = "PyTermGUI", ) -> str: """Exports current content as SVG. For help on the arguments, see `pytermgui.html.to_svg`. """ from .exporters import to_svg # pylint: disable=import-outside-toplevel return to_svg( self._content, prefix=prefix, inline_styles=inline_styles, title=title ) def save_plain(self, filename: str) -> None: """Exports plain text content to the given file. Args: filename: The file to save to. """ with open(filename, "w", encoding="utf-8") as file: file.write(self.export_text()) def save_html( self, filename: str | None = None, prefix: str | None = None, inline_styles: bool = False, ) -> None: """Exports HTML content to the given file. For help on the arguments, see `pytermgui.exporters.to_html`. Args: filename: The file to save to. If the filename does not contain the '.html' extension it will be appended to the end. """ if filename is None: filename = f"PTG_{time.time():%Y-%m-%d %H:%M:%S}.html" if not filename.endswith(".html"): filename += ".html" with open(filename, "w", encoding="utf-8") as file: file.write(self.export_html(prefix=prefix, inline_styles=inline_styles)) def save_svg( self, filename: str | None = None, prefix: str | None = None, inline_styles: bool = False, title: str = "PyTermGUI", ) -> None: """Exports SVG content to the given file. For help on the arguments, see `pytermgui.exporters.to_svg`. Args: filename: The file to save to. If the filename does not contain the '.svg' extension it will be appended to the end. """ if filename is None: timeval = datetime.now() filename = f"PTG_{timeval:%Y-%m-%d_%H:%M:%S}.svg" if not filename.endswith(".svg"): filename += ".svg" with open(filename, "w", encoding="utf-8") as file: file.write( self.export_svg(prefix=prefix, inline_styles=inline_styles, title=title) ) class ColorSystem(Enum): """An enumeration of various terminal-supported colorsystems.""" NO_COLOR = -1 """No-color terminal. See https://no-color.org/.""" STANDARD = 0 """Standard 3-bit colorsystem of the basic 16 colors.""" EIGHT_BIT = 1 """xterm 8-bit colors, 0-256.""" TRUE = 2 """'True' color, a.k.a. 24-bit RGB colors.""" def __ge__(self, other): """Comparison: self >= other.""" if self.__class__ is other.__class__: return self.value >= other.value return NotImplemented def __gt__(self, other): """Comparison: self > other.""" if self.__class__ is other.__class__: return self.value > other.value return NotImplemented def __le__(self, other): """Comparison: self <= other.""" if self.__class__ is other.__class__: return self.value <= other.value return NotImplemented def __lt__(self, other): """Comparison: self < other.""" if self.__class__ is other.__class__: return self.value < other.value return NotImplemented def _get_env_colorsys() -> ColorSystem | None: """Gets a colorsystem if the `PTG_COLORSYS` env var can be linked to one.""" colorsys = os.getenv("PTG_COLORSYS") if colorsys is None: return None try: return ColorSystem[colorsys] except NameError: return None class Terminal: # pylint: disable=too-many-instance-attributes """A class to store & access data about a terminal.""" RESIZE = 0 """Event sent out when the terminal has been resized. Arguments passed: - New size: tuple[int, int] """ margins = [0, 0, 0, 0] """Not quite sure what this does at the moment.""" displayhook_installed: bool = False """This is set to True when `pretty.install` is called.""" origin: tuple[int, int] = (1, 1) """Origin of the internal coordinate system.""" def __init__( self, stream: TextIO | None = None, size: tuple[int, int] | None = None, pixel_size: tuple[int, int] | None = None, ) -> None: """Initialize `_Terminal` class.""" if stream is None: stream = sys.stdout self._size = size self._pixel_size = pixel_size self._stream = stream self._recorder: Recorder | None = None self.size: tuple[int, int] = self._get_size() self.forced_colorsystem: ColorSystem | None = _get_env_colorsys() self.pixel_size: tuple[int, int] = self._get_pixel_size() self._listeners: dict[int, list[Callable[..., Any]]] = {} if hasattr(signal, "SIGWINCH"): signal.signal(signal.SIGWINCH, self._update_size) # TODO: Support SIGWINCH on Windows. self._diff_buffer = [ ["" for _ in range(self.width)] for y in range(self.height) ] def _get_pixel_size(self) -> tuple[int, int]: """Gets the terminal's size, in pixels.""" if self._pixel_size is not None: return self._pixel_size if sys.stdout.isatty(): sys.stdout.write("\x1b[14t") sys.stdout.flush() # Some terminals may not respond to a pixel size query, so we send # a timed-out getch call with a default response of 1280x720. output = getch_timeout(0.01, default="\x1b[4;720;1280t") match = RE_PIXEL_SIZE.match(output) if match is not None: return (int(match[2]), int(match[1])) return (0, 0) def _call_listener(self, event: int, data: Any) -> None: """Calls callbacks for event. Args: event: A terminal event. data: Arbitrary data passed to the callback. """ if event in self._listeners: for callback in self._listeners[event]: callback(data) def _get_size(self) -> tuple[int, int]: """Gets the screen size with origin substracted.""" if self._size is not None: return self.size size = get_terminal_size() return (size[0] - self.origin[0], size[1] - self.origin[1]) def _update_size(self, *_: Any) -> None: """Resize terminal when SIGWINCH occurs, and call listeners.""" self.size = self._get_size() self.pixel_size = self._get_pixel_size() self._call_listener(self.RESIZE, self.size) # Wipe the screen in case anything got messed up self.write("\x1b[2J") @property def width(self) -> int: """Gets the current width of the terminal.""" return self.size[0] @property def height(self) -> int: """Gets the current height of the terminal.""" return self.size[1] @staticmethod def is_interactive() -> bool: """Determines whether shell is interactive. A shell is interactive if it is run from `python3` or `python3 -i`. """ return hasattr(sys, "ps1") @property def forced_colorsystem(self) -> ColorSystem | None: """Forces a color system type on this terminal.""" return self._forced_colorsystem @forced_colorsystem.setter def forced_colorsystem(self, new: ColorSystem | None) -> None: """Sets a colorsystem, clears colorsystem cache.""" self._forced_colorsystem = new @property def colorsystem(self) -> ColorSystem: """Gets the current terminal's supported color system.""" if self.forced_colorsystem is not None: return self.forced_colorsystem if os.getenv("NO_COLOR") is not None: return ColorSystem.NO_COLOR term = os.getenv("TERM", "") color_term = os.getenv("COLORTERM", "").strip().lower() if color_term == "": color_term = term.split("xterm-")[-1] if color_term in ["24bit", "truecolor"]: return ColorSystem.TRUE if color_term == "256color": return ColorSystem.EIGHT_BIT return ColorSystem.STANDARD @contextmanager def record(self) -> Generator[Recorder, None, None]: """Records the terminal's stream.""" if self._recorder is not None: raise RuntimeError(f"{self!r} is already recording.") try: self._recorder = Recorder() yield self._recorder finally: self._recorder = None def replay(self, recorder: Recorder) -> None: """Replays a recording.""" last_time = 0.0 for data, delay in recorder.recording: if last_time > 0.0: time.sleep(delay - last_time) self.write(data, flush=True) last_time = delay def isatty(self) -> bool: """Returns whether self._stream is a tty.""" return self._stream.isatty() def subscribe(self, event: int, callback: Callable[..., Any]) -> None: """Subcribes a callback to be called when event occurs. Args: event: The terminal event that calls callback. callback: The callable to be called. The signature of this callable is dependent on the event. See the documentation of the specific event for more information. """ if not event in self._listeners: self._listeners[event] = [] self._listeners[event].append(callback) def write( self, data: str, pos: tuple[int, int] | None = None, flush: bool = False, slice_too_long: bool = True, ) -> None: """Writes the given data to the terminal's stream. Args: data: The data to write. pos: Terminal-character space position to write the data to, (x, y). flush: If set, `flush` will be called on the stream after reading. slice_too_long: If set, lines that are outside of the terminal will be sliced to fit. Involves a sizable performance hit. """ def _slice(line: str, maximum: int) -> str: length = 0 sliced = "" for char in line: sliced += char if char == "\x1b": continue if ( length > maximum and real_length(sliced) > maximum and not has_open_sequence(sliced) ): break length += 1 return sliced if "\x1b[2J" in data: self.clear_stream() if pos is not None: xpos, ypos = pos if slice_too_long: if not self.height + self.origin[1] + 1 > ypos >= 0: return maximum = self.width - xpos + self.origin[0] if xpos < self.origin[0]: xpos = self.origin[0] sliced = _slice(data, maximum) if len(data) > maximum else data data = f"\x1b[{ypos};{xpos}H{sliced}\x1b[0m" else: data = f"\x1b[{ypos};{xpos}H{data}" self._stream.write(data) if self._recorder is not None: self._recorder.write(data) if flush: self._stream.flush() def clear_stream(self) -> None: """Clears (truncates) the terminal's stream.""" try: self._stream.truncate(0) except OSError as error: if error.errno != errno.EINVAL and os.name != "nt": raise self._stream.write("\x1b[2J") def print( self, *items, pos: tuple[int, int] | None = None, sep: str = " ", end="\n", flush: bool = True, ) -> None: """Prints items to the stream. All arguments not mentioned here are analogous to `print`. Args: pos: Terminal-character space position to write the data to, (x, y). """ self.write(sep.join(map(str, items)) + end, pos=pos, flush=flush) def flush(self) -> None: """Flushes self._stream.""" self._stream.flush() terminal = Terminal() # pylint: disable=invalid-name """Terminal instance that should be used pretty much always.""" def set_global_terminal(new: Terminal) -> None: """Sets the terminal instance to be used by the module.""" globals()["terminal"] = new def get_terminal() -> Terminal: """Gets the default terminal instance used by the module.""" return terminal
Terminal instance that should be used pretty much always.
View Source
def set_global_terminal(new: Terminal) -> None: """Sets the terminal instance to be used by the module.""" globals()["terminal"] = new
Sets the terminal instance to be used by the module.
View Source
def get_terminal() -> Terminal: """Gets the default terminal instance used by the module.""" return terminal
Gets the default terminal instance used by the module.
View Source
class Terminal: # pylint: disable=too-many-instance-attributes """A class to store & access data about a terminal.""" RESIZE = 0 """Event sent out when the terminal has been resized. Arguments passed: - New size: tuple[int, int] """ margins = [0, 0, 0, 0] """Not quite sure what this does at the moment.""" displayhook_installed: bool = False """This is set to True when `pretty.install` is called.""" origin: tuple[int, int] = (1, 1) """Origin of the internal coordinate system.""" def __init__( self, stream: TextIO | None = None, size: tuple[int, int] | None = None, pixel_size: tuple[int, int] | None = None, ) -> None: """Initialize `_Terminal` class.""" if stream is None: stream = sys.stdout self._size = size self._pixel_size = pixel_size self._stream = stream self._recorder: Recorder | None = None self.size: tuple[int, int] = self._get_size() self.forced_colorsystem: ColorSystem | None = _get_env_colorsys() self.pixel_size: tuple[int, int] = self._get_pixel_size() self._listeners: dict[int, list[Callable[..., Any]]] = {} if hasattr(signal, "SIGWINCH"): signal.signal(signal.SIGWINCH, self._update_size) # TODO: Support SIGWINCH on Windows. self._diff_buffer = [ ["" for _ in range(self.width)] for y in range(self.height) ] def _get_pixel_size(self) -> tuple[int, int]: """Gets the terminal's size, in pixels.""" if self._pixel_size is not None: return self._pixel_size if sys.stdout.isatty(): sys.stdout.write("\x1b[14t") sys.stdout.flush() # Some terminals may not respond to a pixel size query, so we send # a timed-out getch call with a default response of 1280x720. output = getch_timeout(0.01, default="\x1b[4;720;1280t") match = RE_PIXEL_SIZE.match(output) if match is not None: return (int(match[2]), int(match[1])) return (0, 0) def _call_listener(self, event: int, data: Any) -> None: """Calls callbacks for event. Args: event: A terminal event. data: Arbitrary data passed to the callback. """ if event in self._listeners: for callback in self._listeners[event]: callback(data) def _get_size(self) -> tuple[int, int]: """Gets the screen size with origin substracted.""" if self._size is not None: return self.size size = get_terminal_size() return (size[0] - self.origin[0], size[1] - self.origin[1]) def _update_size(self, *_: Any) -> None: """Resize terminal when SIGWINCH occurs, and call listeners.""" self.size = self._get_size() self.pixel_size = self._get_pixel_size() self._call_listener(self.RESIZE, self.size) # Wipe the screen in case anything got messed up self.write("\x1b[2J") @property def width(self) -> int: """Gets the current width of the terminal.""" return self.size[0] @property def height(self) -> int: """Gets the current height of the terminal.""" return self.size[1] @staticmethod def is_interactive() -> bool: """Determines whether shell is interactive. A shell is interactive if it is run from `python3` or `python3 -i`. """ return hasattr(sys, "ps1") @property def forced_colorsystem(self) -> ColorSystem | None: """Forces a color system type on this terminal.""" return self._forced_colorsystem @forced_colorsystem.setter def forced_colorsystem(self, new: ColorSystem | None) -> None: """Sets a colorsystem, clears colorsystem cache.""" self._forced_colorsystem = new @property def colorsystem(self) -> ColorSystem: """Gets the current terminal's supported color system.""" if self.forced_colorsystem is not None: return self.forced_colorsystem if os.getenv("NO_COLOR") is not None: return ColorSystem.NO_COLOR term = os.getenv("TERM", "") color_term = os.getenv("COLORTERM", "").strip().lower() if color_term == "": color_term = term.split("xterm-")[-1] if color_term in ["24bit", "truecolor"]: return ColorSystem.TRUE if color_term == "256color": return ColorSystem.EIGHT_BIT return ColorSystem.STANDARD @contextmanager def record(self) -> Generator[Recorder, None, None]: """Records the terminal's stream.""" if self._recorder is not None: raise RuntimeError(f"{self!r} is already recording.") try: self._recorder = Recorder() yield self._recorder finally: self._recorder = None def replay(self, recorder: Recorder) -> None: """Replays a recording.""" last_time = 0.0 for data, delay in recorder.recording: if last_time > 0.0: time.sleep(delay - last_time) self.write(data, flush=True) last_time = delay def isatty(self) -> bool: """Returns whether self._stream is a tty.""" return self._stream.isatty() def subscribe(self, event: int, callback: Callable[..., Any]) -> None: """Subcribes a callback to be called when event occurs. Args: event: The terminal event that calls callback. callback: The callable to be called. The signature of this callable is dependent on the event. See the documentation of the specific event for more information. """ if not event in self._listeners: self._listeners[event] = [] self._listeners[event].append(callback) def write( self, data: str, pos: tuple[int, int] | None = None, flush: bool = False, slice_too_long: bool = True, ) -> None: """Writes the given data to the terminal's stream. Args: data: The data to write. pos: Terminal-character space position to write the data to, (x, y). flush: If set, `flush` will be called on the stream after reading. slice_too_long: If set, lines that are outside of the terminal will be sliced to fit. Involves a sizable performance hit. """ def _slice(line: str, maximum: int) -> str: length = 0 sliced = "" for char in line: sliced += char if char == "\x1b": continue if ( length > maximum and real_length(sliced) > maximum and not has_open_sequence(sliced) ): break length += 1 return sliced if "\x1b[2J" in data: self.clear_stream() if pos is not None: xpos, ypos = pos if slice_too_long: if not self.height + self.origin[1] + 1 > ypos >= 0: return maximum = self.width - xpos + self.origin[0] if xpos < self.origin[0]: xpos = self.origin[0] sliced = _slice(data, maximum) if len(data) > maximum else data data = f"\x1b[{ypos};{xpos}H{sliced}\x1b[0m" else: data = f"\x1b[{ypos};{xpos}H{data}" self._stream.write(data) if self._recorder is not None: self._recorder.write(data) if flush: self._stream.flush() def clear_stream(self) -> None: """Clears (truncates) the terminal's stream.""" try: self._stream.truncate(0) except OSError as error: if error.errno != errno.EINVAL and os.name != "nt": raise self._stream.write("\x1b[2J") def print( self, *items, pos: tuple[int, int] | None = None, sep: str = " ", end="\n", flush: bool = True, ) -> None: """Prints items to the stream. All arguments not mentioned here are analogous to `print`. Args: pos: Terminal-character space position to write the data to, (x, y). """ self.write(sep.join(map(str, items)) + end, pos=pos, flush=flush) def flush(self) -> None: """Flushes self._stream.""" self._stream.flush()
A class to store & access data about a terminal.
View Source
def __init__( self, stream: TextIO | None = None, size: tuple[int, int] | None = None, pixel_size: tuple[int, int] | None = None, ) -> None: """Initialize `_Terminal` class.""" if stream is None: stream = sys.stdout self._size = size self._pixel_size = pixel_size self._stream = stream self._recorder: Recorder | None = None self.size: tuple[int, int] = self._get_size() self.forced_colorsystem: ColorSystem | None = _get_env_colorsys() self.pixel_size: tuple[int, int] = self._get_pixel_size() self._listeners: dict[int, list[Callable[..., Any]]] = {} if hasattr(signal, "SIGWINCH"): signal.signal(signal.SIGWINCH, self._update_size) # TODO: Support SIGWINCH on Windows. self._diff_buffer = [ ["" for _ in range(self.width)] for y in range(self.height) ]
Initialize _Terminal
class.
Event sent out when the terminal has been resized.
Arguments passed:
- New size: tuple[int, int]
Not quite sure what this does at the moment.
This is set to True when pretty.install
is called.
Origin of the internal coordinate system.
Forces a color system type on this terminal.
Gets the current width of the terminal.
Gets the current height of the terminal.
View Source
@staticmethod def is_interactive() -> bool: """Determines whether shell is interactive. A shell is interactive if it is run from `python3` or `python3 -i`. """ return hasattr(sys, "ps1")
Determines whether shell is interactive.
A shell is interactive if it is run from python3
or python3 -i
.
Gets the current terminal's supported color system.
View Source
@contextmanager def record(self) -> Generator[Recorder, None, None]: """Records the terminal's stream.""" if self._recorder is not None: raise RuntimeError(f"{self!r} is already recording.") try: self._recorder = Recorder() yield self._recorder finally: self._recorder = None
Records the terminal's stream.
View Source
def replay(self, recorder: Recorder) -> None: """Replays a recording.""" last_time = 0.0 for data, delay in recorder.recording: if last_time > 0.0: time.sleep(delay - last_time) self.write(data, flush=True) last_time = delay
Replays a recording.
View Source
def isatty(self) -> bool: """Returns whether self._stream is a tty.""" return self._stream.isatty()
Returns whether self._stream is a tty.
View Source
def subscribe(self, event: int, callback: Callable[..., Any]) -> None: """Subcribes a callback to be called when event occurs. Args: event: The terminal event that calls callback. callback: The callable to be called. The signature of this callable is dependent on the event. See the documentation of the specific event for more information. """ if not event in self._listeners: self._listeners[event] = [] self._listeners[event].append(callback)
Subcribes a callback to be called when event occurs.
Args
- event: The terminal event that calls callback.
- callback: The callable to be called. The signature of this callable is dependent on the event. See the documentation of the specific event for more information.
View Source
def write( self, data: str, pos: tuple[int, int] | None = None, flush: bool = False, slice_too_long: bool = True, ) -> None: """Writes the given data to the terminal's stream. Args: data: The data to write. pos: Terminal-character space position to write the data to, (x, y). flush: If set, `flush` will be called on the stream after reading. slice_too_long: If set, lines that are outside of the terminal will be sliced to fit. Involves a sizable performance hit. """ def _slice(line: str, maximum: int) -> str: length = 0 sliced = "" for char in line: sliced += char if char == "\x1b": continue if ( length > maximum and real_length(sliced) > maximum and not has_open_sequence(sliced) ): break length += 1 return sliced if "\x1b[2J" in data: self.clear_stream() if pos is not None: xpos, ypos = pos if slice_too_long: if not self.height + self.origin[1] + 1 > ypos >= 0: return maximum = self.width - xpos + self.origin[0] if xpos < self.origin[0]: xpos = self.origin[0] sliced = _slice(data, maximum) if len(data) > maximum else data data = f"\x1b[{ypos};{xpos}H{sliced}\x1b[0m" else: data = f"\x1b[{ypos};{xpos}H{data}" self._stream.write(data) if self._recorder is not None: self._recorder.write(data) if flush: self._stream.flush()
Writes the given data to the terminal's stream.
Args
- data: The data to write.
- pos: Terminal-character space position to write the data to, (x, y).
- flush: If set,
flush
will be called on the stream after reading. - slice_too_long: If set, lines that are outside of the terminal will be sliced to fit. Involves a sizable performance hit.
View Source
def clear_stream(self) -> None: """Clears (truncates) the terminal's stream.""" try: self._stream.truncate(0) except OSError as error: if error.errno != errno.EINVAL and os.name != "nt": raise self._stream.write("\x1b[2J")
Clears (truncates) the terminal's stream.
View Source
def print( self, *items, pos: tuple[int, int] | None = None, sep: str = " ", end="\n", flush: bool = True, ) -> None: """Prints items to the stream. All arguments not mentioned here are analogous to `print`. Args: pos: Terminal-character space position to write the data to, (x, y). """ self.write(sep.join(map(str, items)) + end, pos=pos, flush=flush)
Prints items to the stream.
All arguments not mentioned here are analogous to print
.
Args
- pos: Terminal-character space position to write the data to, (x, y).
View Source
def flush(self) -> None: """Flushes self._stream.""" self._stream.flush()
Flushes self._stream.
View Source
class Recorder: """A class that records & exports terminal content.""" def __init__(self) -> None: """Initializes the Recorder.""" self.recording: list[tuple[str, float]] = [] self._start_stamp = time.time() @property def _content(self) -> str: """Returns the str part of self._recording""" return "".join(data for data, _ in self.recording) def write(self, data: str) -> None: """Writes to the recorder.""" self.recording.append((data, time.time() - self._start_stamp)) def export_text(self) -> str: """Exports current content as plain text.""" return strip_ansi(self._content) def export_html( self, prefix: str | None = None, inline_styles: bool = False ) -> str: """Exports current content as HTML. For help on the arguments, see `pytermgui.html.to_html`. """ from .exporters import to_html # pylint: disable=import-outside-toplevel return to_html(self._content, prefix=prefix, inline_styles=inline_styles) def export_svg( self, prefix: str | None = None, inline_styles: bool = False, title: str = "PyTermGUI", ) -> str: """Exports current content as SVG. For help on the arguments, see `pytermgui.html.to_svg`. """ from .exporters import to_svg # pylint: disable=import-outside-toplevel return to_svg( self._content, prefix=prefix, inline_styles=inline_styles, title=title ) def save_plain(self, filename: str) -> None: """Exports plain text content to the given file. Args: filename: The file to save to. """ with open(filename, "w", encoding="utf-8") as file: file.write(self.export_text()) def save_html( self, filename: str | None = None, prefix: str | None = None, inline_styles: bool = False, ) -> None: """Exports HTML content to the given file. For help on the arguments, see `pytermgui.exporters.to_html`. Args: filename: The file to save to. If the filename does not contain the '.html' extension it will be appended to the end. """ if filename is None: filename = f"PTG_{time.time():%Y-%m-%d %H:%M:%S}.html" if not filename.endswith(".html"): filename += ".html" with open(filename, "w", encoding="utf-8") as file: file.write(self.export_html(prefix=prefix, inline_styles=inline_styles)) def save_svg( self, filename: str | None = None, prefix: str | None = None, inline_styles: bool = False, title: str = "PyTermGUI", ) -> None: """Exports SVG content to the given file. For help on the arguments, see `pytermgui.exporters.to_svg`. Args: filename: The file to save to. If the filename does not contain the '.svg' extension it will be appended to the end. """ if filename is None: timeval = datetime.now() filename = f"PTG_{timeval:%Y-%m-%d_%H:%M:%S}.svg" if not filename.endswith(".svg"): filename += ".svg" with open(filename, "w", encoding="utf-8") as file: file.write( self.export_svg(prefix=prefix, inline_styles=inline_styles, title=title) )
A class that records & exports terminal content.
View Source
def __init__(self) -> None: """Initializes the Recorder.""" self.recording: list[tuple[str, float]] = [] self._start_stamp = time.time()
Initializes the Recorder.
View Source
def write(self, data: str) -> None: """Writes to the recorder.""" self.recording.append((data, time.time() - self._start_stamp))
Writes to the recorder.
View Source
def export_text(self) -> str: """Exports current content as plain text.""" return strip_ansi(self._content)
Exports current content as plain text.
View Source
def export_html( self, prefix: str | None = None, inline_styles: bool = False ) -> str: """Exports current content as HTML. For help on the arguments, see `pytermgui.html.to_html`. """ from .exporters import to_html # pylint: disable=import-outside-toplevel return to_html(self._content, prefix=prefix, inline_styles=inline_styles)
Exports current content as HTML.
For help on the arguments, see pytermgui.html.to_html
.
View Source
def export_svg( self, prefix: str | None = None, inline_styles: bool = False, title: str = "PyTermGUI", ) -> str: """Exports current content as SVG. For help on the arguments, see `pytermgui.html.to_svg`. """ from .exporters import to_svg # pylint: disable=import-outside-toplevel return to_svg( self._content, prefix=prefix, inline_styles=inline_styles, title=title )
Exports current content as SVG.
For help on the arguments, see pytermgui.html.to_svg
.
View Source
def save_plain(self, filename: str) -> None: """Exports plain text content to the given file. Args: filename: The file to save to. """ with open(filename, "w", encoding="utf-8") as file: file.write(self.export_text())
Exports plain text content to the given file.
Args
- filename: The file to save to.
View Source
def save_html( self, filename: str | None = None, prefix: str | None = None, inline_styles: bool = False, ) -> None: """Exports HTML content to the given file. For help on the arguments, see `pytermgui.exporters.to_html`. Args: filename: The file to save to. If the filename does not contain the '.html' extension it will be appended to the end. """ if filename is None: filename = f"PTG_{time.time():%Y-%m-%d %H:%M:%S}.html" if not filename.endswith(".html"): filename += ".html" with open(filename, "w", encoding="utf-8") as file: file.write(self.export_html(prefix=prefix, inline_styles=inline_styles))
Exports HTML content to the given file.
For help on the arguments, see pytermgui.exporters.to_html
.
Args
- filename: The file to save to. If the filename does not contain the '.html' extension it will be appended to the end.
View Source
def save_svg( self, filename: str | None = None, prefix: str | None = None, inline_styles: bool = False, title: str = "PyTermGUI", ) -> None: """Exports SVG content to the given file. For help on the arguments, see `pytermgui.exporters.to_svg`. Args: filename: The file to save to. If the filename does not contain the '.svg' extension it will be appended to the end. """ if filename is None: timeval = datetime.now() filename = f"PTG_{timeval:%Y-%m-%d_%H:%M:%S}.svg" if not filename.endswith(".svg"): filename += ".svg" with open(filename, "w", encoding="utf-8") as file: file.write( self.export_svg(prefix=prefix, inline_styles=inline_styles, title=title) )
Exports SVG content to the given file.
For help on the arguments, see pytermgui.exporters.to_svg
.
Args
- filename: The file to save to. If the filename does not contain the '.svg' extension it will be appended to the end.
View Source
class ColorSystem(Enum): """An enumeration of various terminal-supported colorsystems.""" NO_COLOR = -1 """No-color terminal. See https://no-color.org/.""" STANDARD = 0 """Standard 3-bit colorsystem of the basic 16 colors.""" EIGHT_BIT = 1 """xterm 8-bit colors, 0-256.""" TRUE = 2 """'True' color, a.k.a. 24-bit RGB colors.""" def __ge__(self, other): """Comparison: self >= other.""" if self.__class__ is other.__class__: return self.value >= other.value return NotImplemented def __gt__(self, other): """Comparison: self > other.""" if self.__class__ is other.__class__: return self.value > other.value return NotImplemented def __le__(self, other): """Comparison: self <= other.""" if self.__class__ is other.__class__: return self.value <= other.value return NotImplemented def __lt__(self, other): """Comparison: self < other.""" if self.__class__ is other.__class__: return self.value < other.value return NotImplemented
An enumeration of various terminal-supported colorsystems.
No-color terminal. See https://no-color.org/.
Standard 3-bit colorsystem of the basic 16 colors.
xterm 8-bit colors, 0-256.
'True' color, a.k.a. 24-bit RGB colors.
Inherited Members
- enum.Enum
- name
- value