pytermgui.markup.macros

All PTG-builtin TIM macros.

  1"""All PTG-builtin TIM macros."""
  2
  3from __future__ import annotations
  4
  5from random import shuffle
  6from typing import Any, TypeVar
  7
  8from .parsing import parse
  9
 10DEFAULT_MACROS = {}
 11
 12MarkupLanguage = Any
 13
 14
 15MacroTemplate = TypeVar("MacroTemplate")
 16
 17
 18def export_macro(func: MacroTemplate) -> MacroTemplate:
 19    """A decorator to add a function to `DEFAULT_MACROS`."""
 20
 21    name = "_".join(func.__name__.split("_")[1:])  # type: ignore
 22
 23    DEFAULT_MACROS[f"!{name}"] = func
 24
 25    return func
 26
 27
 28def apply_default_macros(lang: MarkupLanguage) -> None:
 29    """Applies all macros in `DEFAULT_MACROS`.
 30
 31    Args:
 32        lang: The language to apply the macros to.
 33    """
 34
 35    for name, value in DEFAULT_MACROS.items():
 36        lang.define(name, value)
 37
 38
 39@export_macro
 40def macro_upper(text: str) -> str:
 41    """Turns the text into uppercase."""
 42
 43    return text.upper()
 44
 45
 46@export_macro
 47def macro_lower(text: str) -> str:
 48    """Turns the text into lowercase."""
 49
 50    return text.lower()
 51
 52
 53@export_macro
 54def macro_title(text: str) -> str:
 55    """Turns the text into titlecase."""
 56
 57    return text.title()
 58
 59
 60@export_macro
 61def macro_align(width: str, alignment: str, content: str) -> str:
 62    """Aligns given text using fstrings.
 63
 64    Args:
 65        width: The width to align to.
 66        alignment: One of "left", "center", "right".
 67        content: The content to align; implicit argument.
 68    """
 69
 70    aligner = "<" if alignment == "left" else (">" if alignment == "right" else "^")
 71    return f"{content:{aligner}{width}}"
 72
 73
 74@export_macro
 75def macro_expand(lang: MarkupLanguage, tag: str) -> str:
 76    """Expands a tag alias."""
 77
 78    if not tag in lang.user_tags:
 79        return tag
 80
 81    return lang.get_markup(f"\x1b[{lang.user_tags[tag]}m ")[:-1]
 82
 83
 84@export_macro
 85def macro_shuffle(item: str) -> str:
 86    """Shuffles a string using shuffle.shuffle on its list cast."""
 87
 88    shuffled = list(item)
 89    shuffle(shuffled)
 90
 91    return "".join(shuffled)
 92
 93
 94def _apply_colors(colors: list[str] | list[int], item: str) -> str:
 95    """Applies the given list of colors to the item, spread out evenly."""
 96
 97    blocksize = max(round(len(item) / len(colors)), 1)
 98
 99    out = ""
100    current_block = 0
101    for i, char in enumerate(item):
102        if i % blocksize == 0 and current_block < len(colors):
103            out += f"[{colors[current_block]}]"
104            current_block += 1
105
106        out += char
107
108    return parse(out + "[/fg]", append_reset=False) + "\x1b[0m"
109
110
111@export_macro
112def macro_rainbow(item: str) -> str:
113    """Creates rainbow-colored text."""
114
115    colors = ["red", "orange", "yellow", "green", "blue", "indigo", "violet"]
116
117    return _apply_colors(colors, item)
118
119
120@export_macro
121def macro_gradient(base_str: str, item: str) -> str:
122    """Creates an xterm-256 gradient from a base color.
123
124    This exploits the way the colors are arranged in the xterm color table; every
125    36th color is the next item of a single gradient.
126
127    The start of this given gradient is calculated by decreasing the given base by 36 on
128    every iteration as long as the point is a valid gradient start.
129
130    After that, the 6 colors of this gradient are calculated and applied.
131    """
132
133    if not base_str.isdigit():
134        raise ValueError(f"Gradient base has to be a digit, got {base_str}.")
135
136    base = int(base_str)
137    if base < 16 or base > 231:
138        raise ValueError("Gradient base must be between 16 and 232")
139
140    while base > 52:
141        base -= 36
142
143    colors = []
144    for i in range(6):
145        colors.append(base + 36 * i)
146
147    return _apply_colors(colors, item)
def export_macro(func: ~MacroTemplate) -> ~MacroTemplate:
19def export_macro(func: MacroTemplate) -> MacroTemplate:
20    """A decorator to add a function to `DEFAULT_MACROS`."""
21
22    name = "_".join(func.__name__.split("_")[1:])  # type: ignore
23
24    DEFAULT_MACROS[f"!{name}"] = func
25
26    return func

A decorator to add a function to DEFAULT_MACROS.

def apply_default_macros(lang: Any) -> None:
29def apply_default_macros(lang: MarkupLanguage) -> None:
30    """Applies all macros in `DEFAULT_MACROS`.
31
32    Args:
33        lang: The language to apply the macros to.
34    """
35
36    for name, value in DEFAULT_MACROS.items():
37        lang.define(name, value)

Applies all macros in DEFAULT_MACROS.

Args
  • lang: The language to apply the macros to.
@export_macro
def macro_upper(text: str) -> str:
40@export_macro
41def macro_upper(text: str) -> str:
42    """Turns the text into uppercase."""
43
44    return text.upper()

Turns the text into uppercase.

@export_macro
def macro_lower(text: str) -> str:
47@export_macro
48def macro_lower(text: str) -> str:
49    """Turns the text into lowercase."""
50
51    return text.lower()

Turns the text into lowercase.

@export_macro
def macro_title(text: str) -> str:
54@export_macro
55def macro_title(text: str) -> str:
56    """Turns the text into titlecase."""
57
58    return text.title()

Turns the text into titlecase.

@export_macro
def macro_align(width: str, alignment: str, content: str) -> str:
61@export_macro
62def macro_align(width: str, alignment: str, content: str) -> str:
63    """Aligns given text using fstrings.
64
65    Args:
66        width: The width to align to.
67        alignment: One of "left", "center", "right".
68        content: The content to align; implicit argument.
69    """
70
71    aligner = "<" if alignment == "left" else (">" if alignment == "right" else "^")
72    return f"{content:{aligner}{width}}"

Aligns given text using fstrings.

Args
  • width: The width to align to.
  • alignment: One of "left", "center", "right".
  • content: The content to align; implicit argument.
@export_macro
def macro_expand(lang: Any, tag: str) -> str:
75@export_macro
76def macro_expand(lang: MarkupLanguage, tag: str) -> str:
77    """Expands a tag alias."""
78
79    if not tag in lang.user_tags:
80        return tag
81
82    return lang.get_markup(f"\x1b[{lang.user_tags[tag]}m ")[:-1]

Expands a tag alias.

@export_macro
def macro_shuffle(item: str) -> str:
85@export_macro
86def macro_shuffle(item: str) -> str:
87    """Shuffles a string using shuffle.shuffle on its list cast."""
88
89    shuffled = list(item)
90    shuffle(shuffled)
91
92    return "".join(shuffled)

Shuffles a string using shuffle.shuffle on its list cast.

@export_macro
def macro_rainbow(item: str) -> str:
112@export_macro
113def macro_rainbow(item: str) -> str:
114    """Creates rainbow-colored text."""
115
116    colors = ["red", "orange", "yellow", "green", "blue", "indigo", "violet"]
117
118    return _apply_colors(colors, item)

Creates rainbow-colored text.

@export_macro
def macro_gradient(base_str: str, item: str) -> str:
121@export_macro
122def macro_gradient(base_str: str, item: str) -> str:
123    """Creates an xterm-256 gradient from a base color.
124
125    This exploits the way the colors are arranged in the xterm color table; every
126    36th color is the next item of a single gradient.
127
128    The start of this given gradient is calculated by decreasing the given base by 36 on
129    every iteration as long as the point is a valid gradient start.
130
131    After that, the 6 colors of this gradient are calculated and applied.
132    """
133
134    if not base_str.isdigit():
135        raise ValueError(f"Gradient base has to be a digit, got {base_str}.")
136
137    base = int(base_str)
138    if base < 16 or base > 231:
139        raise ValueError("Gradient base must be between 16 and 232")
140
141    while base > 52:
142        base -= 36
143
144    colors = []
145    for i in range(6):
146        colors.append(base + 36 * i)
147
148    return _apply_colors(colors, item)

Creates an xterm-256 gradient from a base color.

This exploits the way the colors are arranged in the xterm color table; every 36th color is the next item of a single gradient.

The start of this given gradient is calculated by decreasing the given base by 36 on every iteration as long as the point is a valid gradient start.

After that, the 6 colors of this gradient are calculated and applied.