#|default_exp style
Fast styling for friendly CLIs.
::: {.callout-note}
Styled outputs don't show in Quarto documentation. Please use a notebook editor to correctly view this page.
:::
#|export
# Source: https://misc.flogisoft.com/bash/tip_colors_and_formatting
_base = 'red green yellow blue magenta cyan'
_regular = f'black {_base} light_gray'
_intense = 'dark_gray ' + ' '.join('light_'+o for o in _base.split()) + ' white'
_fmt = 'bold dim italic underline blink <na> invert hidden strikethrough'
#|export
class StyleCode:
"An escape sequence for styling terminal text."
def __init__(self, name, code, typ): self.name,self.code,self.typ = name,code,typ
def __str__(self): return f'\033[{self.code}m'
The primary building block of the S
API.
print(str(StyleCode('blue', 34, 'fg')) + 'hello' + str(StyleCode('default', 39, 'fg')) + ' world')
hello world
#|export
def _mk_codes(s, start, typ, fmt=None, **kwargs):
d = {k:i for i,k in enumerate(s.split())} if isinstance(s, str) else s
res = {k if fmt is None else fmt.format(k):start+v for k,v in d.items()}
res.update(kwargs)
return {k:StyleCode(k,v,typ) for k,v in res.items()}
#|export
# Hardcode `reset_bold=22` since 21 is not always supported
# See: https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
style_codes = {**_mk_codes(_regular, 30, 'fg', default=39),
**_mk_codes(_intense, 90, 'fg'),
**_mk_codes(_regular, 40, 'bg', '{}_bg', default_bg=49),
**_mk_codes(_intense, 100, 'bg', '{}_bg'),
**_mk_codes(_fmt, 1, 'fmt'),
**_mk_codes(_fmt, 21, 'reset', 'reset_{}', reset=0, reset_bold=22)}
style_codes = {k:v for k,v in style_codes.items() if '<na>' not in k}
#|export
def _reset_code(s):
if s.typ == 'fg': return style_codes['default']
if s.typ == 'bg': return style_codes['default_bg']
if s.typ == 'fmt': return style_codes['reset_'+s.name]
#|export
class Style:
"A minimal terminal text styler."
def __init__(self, codes=None): self.codes = [] if codes is None else codes
def __dir__(self): return style_codes.keys()
def __getattr__(self, k):
try: return Style(self.codes+[style_codes[k]])
except KeyError: return super().__getattr__(k)
def __call__(self, obj):
set_ = ''.join(str(o) for o in self.codes)
reset = ''.join(sorted('' if o is None else str(o) for o in set(_reset_code(o) for o in self.codes)))
return set_ + str(obj) + reset
def __repr__(self):
nm = type(self).__name__
res = f'<{nm}: '
res += ' '.join(o.name for o in self.codes) if self.codes else 'none'
return res+'>'
The main way to use it is via the exported S
object.
#|exports
S = Style()
We start with an empty style:
S
<Style: none>
Define a new style by chaining attributes:
s = S.blue.bold.underline
s
<Style: blue bold underline>
You can see a full list of available styles with auto-complete by typing S . Tab.
Apply a style by calling it with a string:
s('hello world')
'\x1b[34m\x1b[1m\x1b[4mhello world\x1b[22m\x1b[24m\x1b[39m'
That's a raw string with the underlying escape sequences that tell the terminal how to format text. To see the styled version we have to print it:
print(s('hello world'))
hello world
You can also nest styles:
print(S.bold(S.blue('key') + ' = value ') + S.light_gray(' ' + S.underline('# With a comment')) + ' and unstyled text')
key = value # With a comment and unstyled text
print(S.blue('this '+S.bold('is')+' a test'))
this is a test
#|export
def _demo(name, code):
s = getattr(S,name)
print(s(f'{code.code:>3} {name:16}'))
#|export
def demo():
"Demonstrate all available styles and their codes."
for k,v in style_codes.items(): _demo(k,v)
demo()
30 black 31 red 32 green 33 yellow 34 blue 35 magenta 36 cyan 37 light_gray 39 default 90 dark_gray 91 light_red 92 light_green 93 light_yellow 94 light_blue 95 light_magenta 96 light_cyan 97 white 40 black_bg 41 red_bg 42 green_bg 43 yellow_bg 44 blue_bg 45 magenta_bg 46 cyan_bg 47 light_gray_bg 49 default_bg 100 dark_gray_bg 101 light_red_bg 102 light_green_bg 103 light_yellow_bg 104 light_blue_bg 105 light_magenta_bg 106 light_cyan_bg 107 white_bg 1 bold 2 dim 3 italic 4 underline 5 blink 7 invert 8 hidden 9 strikethrough 22 reset_bold 22 reset_dim 23 reset_italic 24 reset_underline 25 reset_blink 27 reset_invert 28 reset_hidden 29 reset_strikethrough 0 reset
#|hide
import nbdev; nbdev.nbdev_export()