{ "cells": [ { "cell_type": "code", "execution_count": null, "id": "d0c84aa5", "metadata": {}, "outputs": [], "source": [ "#|default_exp style" ] }, { "cell_type": "markdown", "id": "61bf8203", "metadata": {}, "source": [ "# Style\n", "\n", "> Fast styling for friendly CLIs." ] }, { "cell_type": "markdown", "id": "5437d2fc", "metadata": {}, "source": [ "::: {.callout-note}\n", "\n", "Styled outputs don't show in Quarto documentation. Please use a notebook editor to correctly view this page.\n", "\n", ":::" ] }, { "cell_type": "code", "execution_count": null, "id": "059709e3", "metadata": {}, "outputs": [], "source": [ "#|export\n", "# Source: https://misc.flogisoft.com/bash/tip_colors_and_formatting\n", "_base = 'red green yellow blue magenta cyan'\n", "_regular = f'black {_base} light_gray'\n", "_intense = 'dark_gray ' + ' '.join('light_'+o for o in _base.split()) + ' white'\n", "_fmt = 'bold dim italic underline blink invert hidden strikethrough'" ] }, { "cell_type": "code", "execution_count": null, "id": "12d1cb35", "metadata": {}, "outputs": [], "source": [ "#|export\n", "class StyleCode:\n", " \"An escape sequence for styling terminal text.\"\n", " def __init__(self, name, code, typ): self.name,self.code,self.typ = name,code,typ\n", " def __str__(self): return f'\\033[{self.code}m'" ] }, { "cell_type": "markdown", "id": "84cac46c", "metadata": {}, "source": [ "The primary building block of the `S` API." ] }, { "cell_type": "code", "execution_count": null, "id": "33671ebf", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[34mhello\u001b[39m world\n" ] } ], "source": [ "print(str(StyleCode('blue', 34, 'fg')) + 'hello' + str(StyleCode('default', 39, 'fg')) + ' world')" ] }, { "cell_type": "code", "execution_count": null, "id": "8a35abf7", "metadata": {}, "outputs": [], "source": [ "#|export\n", "def _mk_codes(s, start, typ, fmt=None, **kwargs):\n", " d = {k:i for i,k in enumerate(s.split())} if isinstance(s, str) else s\n", " res = {k if fmt is None else fmt.format(k):start+v for k,v in d.items()}\n", " res.update(kwargs)\n", " return {k:StyleCode(k,v,typ) for k,v in res.items()}" ] }, { "cell_type": "code", "execution_count": null, "id": "ff754020", "metadata": {}, "outputs": [], "source": [ "#|export\n", "# Hardcode `reset_bold=22` since 21 is not always supported\n", "# See: https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797\n", "style_codes = {**_mk_codes(_regular, 30, 'fg', default=39),\n", " **_mk_codes(_intense, 90, 'fg'),\n", " **_mk_codes(_regular, 40, 'bg', '{}_bg', default_bg=49),\n", " **_mk_codes(_intense, 100, 'bg', '{}_bg'),\n", " **_mk_codes(_fmt, 1, 'fmt'),\n", " **_mk_codes(_fmt, 21, 'reset', 'reset_{}', reset=0, reset_bold=22)}\n", "style_codes = {k:v for k,v in style_codes.items() if '' not in k}" ] }, { "cell_type": "code", "execution_count": null, "id": "34a19083", "metadata": {}, "outputs": [], "source": [ "#|export\n", "def _reset_code(s):\n", " if s.typ == 'fg': return style_codes['default']\n", " if s.typ == 'bg': return style_codes['default_bg']\n", " if s.typ == 'fmt': return style_codes['reset_'+s.name]" ] }, { "cell_type": "code", "execution_count": null, "id": "eb561c7e", "metadata": {}, "outputs": [], "source": [ "#|export\n", "class Style:\n", " \"A minimal terminal text styler.\"\n", " def __init__(self, codes=None): self.codes = [] if codes is None else codes\n", " def __dir__(self): return style_codes.keys()\n", " def __getattr__(self, k):\n", " try: return Style(self.codes+[style_codes[k]])\n", " except KeyError: return super().__getattr__(k)\n", " def __call__(self, obj):\n", " set_ = ''.join(str(o) for o in self.codes)\n", " reset = ''.join(sorted('' if o is None else str(o) for o in set(_reset_code(o) for o in self.codes)))\n", " return set_ + str(obj) + reset\n", " def __repr__(self):\n", " nm = type(self).__name__\n", " res = f'<{nm}: '\n", " res += ' '.join(o.name for o in self.codes) if self.codes else 'none'\n", " return res+'>'" ] }, { "cell_type": "markdown", "id": "56848b63", "metadata": {}, "source": [ "The main way to use it is via the exported `S` object." ] }, { "cell_type": "code", "execution_count": null, "id": "73c4d89c", "metadata": {}, "outputs": [], "source": [ "#|exports\n", "S = Style()" ] }, { "cell_type": "markdown", "id": "5bc1c8bf", "metadata": {}, "source": [ "We start with an empty style:" ] }, { "cell_type": "code", "execution_count": null, "id": "2eb8de72", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "S" ] }, { "cell_type": "markdown", "id": "de2fd3a8", "metadata": {}, "source": [ "Define a new style by chaining attributes:" ] }, { "cell_type": "code", "execution_count": null, "id": "d3add7ec", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "s = S.blue.bold.underline\n", "s" ] }, { "cell_type": "markdown", "id": "45578ec4", "metadata": {}, "source": [ "You can see a full list of available styles with auto-complete by typing S . Tab." ] }, { "cell_type": "markdown", "id": "ed24db31", "metadata": {}, "source": [ "Apply a style by calling it with a string:" ] }, { "cell_type": "code", "execution_count": null, "id": "59e5ab51", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'\\x1b[34m\\x1b[1m\\x1b[4mhello world\\x1b[22m\\x1b[24m\\x1b[39m'" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "s('hello world')" ] }, { "cell_type": "markdown", "id": "4fa4b809", "metadata": {}, "source": [ "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:" ] }, { "cell_type": "code", "execution_count": null, "id": "4c5a8a96", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[34m\u001b[1m\u001b[4mhello world\u001b[22m\u001b[24m\u001b[39m\n" ] } ], "source": [ "print(s('hello world'))" ] }, { "cell_type": "markdown", "id": "9919519b", "metadata": {}, "source": [ "You can also nest styles:" ] }, { "cell_type": "code", "execution_count": null, "id": "653d867f", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[1m\u001b[34mkey\u001b[39m = value \u001b[22m\u001b[37m \u001b[4m# With a comment\u001b[24m\u001b[39m and unstyled text\n" ] } ], "source": [ "print(S.bold(S.blue('key') + ' = value ') + S.light_gray(' ' + S.underline('# With a comment')) + ' and unstyled text')" ] }, { "cell_type": "code", "execution_count": null, "id": "ce59dc19", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[34mthis \u001b[1mis\u001b[22m a test\u001b[39m\n" ] } ], "source": [ "print(S.blue('this '+S.bold('is')+' a test'))" ] }, { "cell_type": "code", "execution_count": null, "id": "7c016111", "metadata": {}, "outputs": [], "source": [ "#|export\n", "def _demo(name, code):\n", " s = getattr(S,name)\n", " print(s(f'{code.code:>3} {name:16}'))" ] }, { "cell_type": "code", "execution_count": null, "id": "cf21a9bd", "metadata": {}, "outputs": [], "source": [ "#|export\n", "def demo():\n", " \"Demonstrate all available styles and their codes.\"\n", " for k,v in style_codes.items(): _demo(k,v)" ] }, { "cell_type": "code", "execution_count": null, "id": "fe52f7cb", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[30m 30 black \u001b[39m\n", "\u001b[31m 31 red \u001b[39m\n", "\u001b[32m 32 green \u001b[39m\n", "\u001b[33m 33 yellow \u001b[39m\n", "\u001b[34m 34 blue \u001b[39m\n", "\u001b[35m 35 magenta \u001b[39m\n", "\u001b[36m 36 cyan \u001b[39m\n", "\u001b[37m 37 light_gray \u001b[39m\n", "\u001b[39m 39 default \u001b[39m\n", "\u001b[90m 90 dark_gray \u001b[39m\n", "\u001b[91m 91 light_red \u001b[39m\n", "\u001b[92m 92 light_green \u001b[39m\n", "\u001b[93m 93 light_yellow \u001b[39m\n", "\u001b[94m 94 light_blue \u001b[39m\n", "\u001b[95m 95 light_magenta \u001b[39m\n", "\u001b[96m 96 light_cyan \u001b[39m\n", "\u001b[97m 97 white \u001b[39m\n", "\u001b[40m 40 black_bg \u001b[49m\n", "\u001b[41m 41 red_bg \u001b[49m\n", "\u001b[42m 42 green_bg \u001b[49m\n", "\u001b[43m 43 yellow_bg \u001b[49m\n", "\u001b[44m 44 blue_bg \u001b[49m\n", "\u001b[45m 45 magenta_bg \u001b[49m\n", "\u001b[46m 46 cyan_bg \u001b[49m\n", "\u001b[47m 47 light_gray_bg \u001b[49m\n", "\u001b[49m 49 default_bg \u001b[49m\n", "\u001b[100m100 dark_gray_bg \u001b[49m\n", "\u001b[101m101 light_red_bg \u001b[49m\n", "\u001b[102m102 light_green_bg \u001b[49m\n", "\u001b[103m103 light_yellow_bg \u001b[49m\n", "\u001b[104m104 light_blue_bg \u001b[49m\n", "\u001b[105m105 light_magenta_bg\u001b[49m\n", "\u001b[106m106 light_cyan_bg \u001b[49m\n", "\u001b[107m107 white_bg \u001b[49m\n", "\u001b[1m 1 bold \u001b[22m\n", "\u001b[2m 2 dim \u001b[22m\n", "\u001b[3m 3 italic \u001b[23m\n", "\u001b[4m 4 underline \u001b[24m\n", "\u001b[5m 5 blink \u001b[25m\n", "\u001b[7m 7 invert \u001b[27m\n", "\u001b[8m 8 hidden \u001b[28m\n", "\u001b[9m 9 strikethrough \u001b[29m\n", "\u001b[22m 22 reset_bold \n", "\u001b[22m 22 reset_dim \n", "\u001b[23m 23 reset_italic \n", "\u001b[24m 24 reset_underline \n", "\u001b[25m 25 reset_blink \n", "\u001b[27m 27 reset_invert \n", "\u001b[28m 28 reset_hidden \n", "\u001b[29m 29 reset_strikethrough\n", "\u001b[0m 0 reset \n" ] } ], "source": [ "demo()" ] }, { "cell_type": "markdown", "id": "df973d4e", "metadata": {}, "source": [ "# Export -" ] }, { "cell_type": "code", "execution_count": null, "id": "ad32b076", "metadata": {}, "outputs": [], "source": [ "#|hide\n", "import nbdev; nbdev.nbdev_export()" ] }, { "cell_type": "code", "execution_count": null, "id": "84fe290c", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "python3", "language": "python", "name": "python3" } }, "nbformat": 4, "nbformat_minor": 5 }