Styling

Click Extra ships its own Style class as a drop-in replacement for cloup.Style (which itself wraps click.style). The runtime contract (calling the instance to apply styling, equality, hashing, with_()) is identical to cloup’s; everything below is purely additive ergonomics.

The class lands automatically when you do from click_extra import Style: __init__.py re-exports it after from cloup import * so the click-extra version takes precedence.

hello

Hex-string color shorthand

Style.fg and Style.bg accept "#rrggbb" or "#rgb" shorthand strings alongside Click’s named colors and RGB tuples. The shorthand is converted to an RGB tuple on construction:

sample
sample
sample
sample

REPL-friendly __repr__ and __str__

The compact __repr__ hides None and falsy attributes and renders RGB tuples as #rrggbb. The __str__ returns the styled word "sample" so print(style) and debugger inspectors visualize what the style does, not just its fields:

Style(fg=#f1fa8c, bold, italic)
sample

Composition operator a | b

The | operator merges two styles. The right operand wins on conflicts; None fields on either side don’t override the other side’s set fields. The reflected __ror__ makes mixing with cloup.Style (or any compatible subclass) symmetric:

Style(fg='magenta', bold, italic)
Style(fg='cyan', bold, italic)

cascade(base): fill gaps from a base style

Where | lets the right operand always win, cascade keeps the instance’s set values and only fills its None fields from base. Useful for theme-inheritance patterns where a derived style should keep its overrides and inherit the rest:

Style(fg='magenta', bold, italic)

TOML/JSON round-trip with to_dict / from_dict

Style.to_dict() emits a plain mapping containing only the set fields, with RGB tuples flattened to #rrggbb strings so the output round-trips through TOML/JSON/YAML untouched. Style.from_dict() is the symmetric loader and rejects unknown keys with TypeError so typos surface immediately:

{'fg': '#f1fa8c', 'bold': True}
{"fg": "#f1fa8c", "bold": true}
True
TypeError('Unknown Style field(s): colour')

to_css(): CSS declaration list

Renders the style as a semicolon-separated CSS declaration list, suitable for inline style="..." attributes on HTML spans. Used by AnsiHtmlFormatter and the theme palette swatches:

color: #f1fa8c; font-weight: bold; font-style: italic
color: #ff6d67; text-decoration: underline line-through
color: cyan; opacity: 0.6

The mapping is:

Attribute

CSS declaration

fg

color: <color>

bg

background-color: <color>

bold

font-weight: bold

italic

font-style: italic

underline

text-decoration: underline

overline

text-decoration: overline

strikethrough

text-decoration: line-through

dim

opacity: 0.6

reverse

filter: invert(1)

underline, overline and strikethrough collapse into a single text-decoration declaration when more than one is set.

from_ansi(): parse ANSI SGR escapes

Given one or more consecutive ANSI SGR escapes (the \x1b[...m sequences Click emits), rebuild a Style instance. Supports the standard 8/16-color codes (30–37, 40–47, 90–97, 100–107), the 38;5;n / 48;5;n 256-color extension, and the 38;2;r;g;b / 48;2;r;g;b 24-bit extension. Reset codes (the full 0 reset, its parameter-less \x1b[m form included, and selective resets like 22, 39 or 49) are ignored, so parsing the full output of a style call recovers that style. Multiple back-to-back escapes (as Click emits when combining colors with attributes) are merged into a single Style:

Style(fg='red', bold)
Style(fg='red', bold)
Style(fg=#f1fa8c)
Style(fg=226)

from_ansi is the inverse of calling the style: parsing the output of Style(fg="red", bold=True)("text") recovers the same style.

split_ansi() and render_ansi(): tokenize ANSI streams

While from_ansi() parses bare escapes, split_ansi() tokenizes a whole string mixing text and escapes. It is a stateful SGR stream parser: each escape updates the current style (full and selective resets honored), and every maximal run of text sharing the same style is yielded as a (Style, text) tuple. Non-SGR escapes (cursor movements, OSC hyperlink wrappers) carry no style information and are removed from the yielded text:

Style(fg='blue') -> 'Monday'
Style() -> ' was '
Style(fg='yellow', bold) -> 'sunny'

render_ansi(text, emitter) builds on it to rewrite a string: unstyled runs pass through verbatim, and each styled run is replaced by whatever markup the emitter callable produces. Styled runs are split at newlines, so no markup wrapper ever crosses a line boundary:

a [styled] word

ANSI markup converters

Four ready-made converters translate ANSI styling to markup languages with native styling support. They power the table styles translation, and are just as useful standalone, to export any styled CLI output:

  • ansi_to_html() produces self-contained, inline-CSS <span> tags (also valid in markups accepting embedded HTML, like MediaWiki).

  • ansi_to_jira() produces {color:…} macros and Jira wiki emphasis markers.

  • ansi_to_latex() produces xcolor-based color macros and core LaTeX text macros.

  • ansi_to_textile() produces %{…} spans carrying the style as inline CSS.

<span style="color: blue; font-weight: bold">Summer</span>
{color:blue}*Summer*{color}
\textcolor{blue}{\textbf{Summer}}
%{color: blue; font-weight: bold}Summer%

Each converter maps ANSI attributes to their closest equivalent in the target markup, and silently drops those the target cannot express (like blink in CSS, backgrounds in Jira markup, or dim in LaTeX). Colors named after the 8 base ANSI colors pass through as color keywords; bright variants, 256-color indices and 24-bit values resolve to hex.

contrast_ratio(other): WCAG accessibility check

Returns the WCAG 2.x contrast ratio between this style’s foreground and another style’s foreground. Result is in [1, 21]: 1 = identical colors (no contrast), 21 = maximum contrast (black on white). WCAG AA requires 4.5+ for normal text and 3.0+ for large text; AAA wants 7.0+ and 4.5+ respectively.

19.26
1.47
4.08

Click Extra uses this internally to gate the WCAG legibility floor and AA Large compliance for branded themes.

Equality and hash

Style overrides cloup’s __eq__ and __hash__ to skip the lazily-populated _style_kwargs cache, so two otherwise-identical styles compare equal whether or not either has been called yet:

True
True
True

Shared dataclass round-trip helpers

The serialization machinery Style.to_dict / from_dict relies on three small module-level helpers that codify “walk dataclass fields, skip the cloup _style_kwargs cache, skip default-valued fields, raise on unknown keys”. They’re public so other dataclass-shaped values in click-extra (notably HelpTheme) can reuse them, and so downstream code with similar patterns can build on the same primitives.

fields_to_dict(instance, *, encode=…, keep=…)

Walks every field via dataclasses.fields, applies an optional keep(field, value) -> bool filter (default keeps every non-default field), and passes the surviving values through an encode(field, value) -> encoded_value callback (default identity):

{'r': 255, 'g': 128, 'name': 'orange'}
{'r': 'ff', 'g': '80', 'name': 'orange'}

dict_to_fields(cls, data, *, decode=…)

The symmetric loader. Validates every key in data against cls’s dataclass fields and raises TypeError listing every unknown key, so callers can build a constructor call without an extra pre-validation pass:

Color(r=255, g=0, b=0, name='orange')
TypeError('Unknown Color field(s): red')

click_extra.styling API

        classDiagram
  Style <|-- Style
    

Drop-in replacement for cloup.Style with extra features.

The module name mirrors cloup.styling, the upstream module that hosts the original Style class. Click Extra’s Style is a subclass that keeps cloup’s runtime contract (calling, equality, hashing, with_()) intact and adds:

  • A compact, single-line __repr__ that hides None and falsy attributes and renders RGB tuples as #rrggbb hex.

  • Hex-string color shorthand: Style(fg="#f1fa8c") works alongside Style(fg=(241, 250, 140)).

  • A __str__ that returns the styled word "sample" so REPL prints and debuggers visualize what the style does, not just its fields.

  • A composition operator a | b that merges two styles, with the right operand winning on conflicts.

  • A Style.cascade() method that fills the style’s None fields from a base style without overriding any value already set.

  • Style.to_dict() / Style.from_dict() for round-tripping styles through TOML/JSON/YAML.

  • Style.to_css() for emitting CSS-equivalent declarations: useful for HTML renderings of help screens.

  • Style.from_ansi() for parsing an ANSI SGR escape sequence back into a Style instance.

  • Style.contrast_ratio() returning the WCAG contrast ratio between two foreground colors. Useful for theme designers checking accessibility.

  • split_ansi() and render_ansi() for tokenizing a string mixing text and ANSI escapes into styled runs, and re-rendering those runs through a markup emitter.

  • The ansi_to_html(), ansi_to_jira(), ansi_to_latex() and ansi_to_textile() converters, translating ANSI styling to markup languages with native styling support.

click_extra.styling.fields_to_dict(instance, *, encode=<function <lambda>>, keep=<function <lambda>>)[source]

Serialize a dataclass instance to a dict of set fields.

Walks every field via dataclasses.fields(), skips the internal _style_kwargs cache, applies keep to decide which fields are written (default: every non-default field), and passes the surviving values through encode (default: identity).

Parameters:
  • instance (Any) – the dataclass to serialize.

  • encode (Callable[[Any, Any], Any]) – callable (field, value) -> encoded_value applied to every kept value. Use to convert RGB tuples to #rrggbb strings, nested dataclasses to dicts, etc.

  • keep (Callable[[Any, Any], bool]) – callable (field, value) -> bool deciding whether the field is emitted. Default keeps everything that differs from the field’s declared default.

Return type:

dict[str, Any]

click_extra.styling.dict_to_fields(cls, data, *, decode=<function <lambda>>)[source]

Validate data’s keys against cls’s dataclass fields and decode them.

Returns a kwargs dict ready to splat into cls(**kwargs). Raises TypeError listing every unknown key, so callers can build a constructor call without an extra pre-validation pass.

Parameters:
  • cls (type) – the dataclass type whose fields define the legal keys.

  • data (dict[str, Any]) – mapping from field name to a serialized value.

  • decode (Callable[[Any, Any], Any]) – callable (field, raw) -> decoded_value invoked for every recognized key. Default returns the raw value unchanged.

Return type:

dict[str, Any]

click_extra.styling.supports_truecolor()[source]

Whether the terminal is assumed to render 24-bit (truecolor) ANSI.

Drives Style.__call__’s choice between emitting a 24-bit 38;2;r;g;b sequence and quantizing it to the nearest 38;5;n 256-color index, so a branded theme’s RGB colors degrade gracefully on a terminal that cannot display them.

The policy is optimistic: assume truecolor unless the environment positively says otherwise. Precedence, highest first:

  1. COLORTERM of truecolor / 24bit (see _TRUECOLOR_COLORTERMS) keeps 24-bit.

  2. Any other non-empty COLORTERM quantizes: an explicit lower advertisement.

  3. A TERM ending in -16color quantizes: an unambiguous sub-256 terminal. *-256color is deliberately not treated as a downgrade, since truecolor terminals routinely report it while advertising their 24-bit support through COLORTERM instead. Honoring it would strip truecolor from the very terminals this optimistic default protects.

  4. Otherwise keeps 24-bit.

A dumb / unknown TERM never reaches this decision for CLI output: it has already disabled color upstream through resolve_color_env().

Return type:

bool

class click_extra.styling.Style(fg=None, bg=None, bold=None, dim=None, underline=None, overline=None, italic=None, blink=None, reverse=None, strikethrough=None, text_transform=None)[source]

Bases: Style

cloup.Style with extra ergonomics.

See the module docstring for the full list of additions. The runtime contract (calling the instance to apply styling, equality, hashing, with_()) is otherwise identical to cloup.Style.

fg: str | tuple[int, int, int] | int | None = None

Foreground color: named ANSI string, #rrggbb hex, RGB tuple, or palette index.

bg: str | tuple[int, int, int] | int | None = None

Background color: named ANSI string, #rrggbb hex, RGB tuple, or palette index.

cascade(base)[source]

Return a copy with None fields filled in from base.

The instance’s own non-None values always win: cascade only fills gaps. Useful for theme inheritance: derived.cascade(parent) keeps derived’s overrides and inherits the rest from parent.

Return type:

Style

to_dict()[source]

Serialize to a plain dict with only the set fields.

RGB tuples are emitted as #rrggbb strings so the result round-trips through TOML/JSON/YAML untouched. Pair with from_dict() to rebuild a Style.

Return type:

dict[str, Any]

classmethod from_dict(data)[source]

Build a Style from the plain dict produced by to_dict().

Validates that every key in data names a known Style field and raises TypeError otherwise. Pair with to_dict() to round-trip through TOML/JSON/YAML.

Return type:

Style

to_css()[source]

Render the style as a semicolon-separated CSS declaration list.

Style(fg="#f1fa8c", bold=True).to_css() returns "color: #f1fa8c; font-weight: bold". Suitable for inline style="..." attributes on HTML spans.

Return type:

str

classmethod from_ansi(escape)[source]

Parse one or more consecutive ANSI SGR escapes into a Style.

Supports the standard 8/16-color codes (30–37, 40–47, 90–97, 100–107), the 38;5;n / 48;5;n 256-color extension, and the 38;2;r;g;b / 48;2;r;g;b 24-bit extension. Reset codes (the full 0 reset, its parameter-less \x1b[m form included, and selective resets like 22, 39 or 49) are ignored, so parsing the full output of a style call (trailing reset included) recovers that style. Multiple back-to-back escapes (as click emits when combining colors with attributes: \x1b[31m\x1b[1m) are merged into a single Style.

To tokenize a string mixing text and escapes, with resets honored, see split_ansi().

Return type:

Style

contrast_ratio(other)[source]

Return the WCAG 2.x contrast ratio between this fg and other’s fg.

Result is in [1, 21]: 1 = identical colors (no contrast), 21 = maximum contrast (black on white). WCAG AA requires 4.5+ for normal text, 3.0+ for large text; AAA wants 7.0+ and 4.5+ respectively.

Return type:

float

click_extra.styling.split_ansi(text)[source]

Split text into (style, text) runs at ANSI SGR escape boundaries.

A stateful SGR stream parser: each escape updates the current style state (with full and selective resets honored, unlike Style.from_ansi()), and every maximal run of text sharing the same state is yielded with its Style. Unstyled text is yielded with an empty Style. Consecutive runs with equal styles are merged and empty runs are dropped, so wrapping each yielded run produces minimal markup.

Non-SGR escapes carry no style information and are removed from the yielded text, per _strip_unsupported_ansi().

Return type:

Iterator[tuple[Style, str]]

click_extra.styling.render_ansi(text, emitter)[source]

Rebuild text, replacing each ANSI-styled run by emitter’s markup.

Unstyled runs pass through verbatim, so any markup surrounding the styled runs (table borders, tags produced by another renderer) is preserved byte for byte. Styled runs are handed to emitter one line at a time: runs are split on newlines so no markup wrapper ever crosses a line boundary, which keeps line-oriented markup (LaTeX rows, wiki tables) well-formed even when a style spans multiple lines.

Return type:

str

click_extra.styling.ansi_to_html(text)[source]

Translate ANSI styling in text to inline-styled HTML <span> tags.

\x1b[34mSummer\x1b[0m becomes <span style="color: blue">Summer</span>. The spans are self-contained (no stylesheet needed) and also valid in markups accepting inline HTML, like MediaWiki.

Return type:

str

click_extra.styling.ansi_to_jira(text)[source]

Translate ANSI styling in text to Jira wiki markup.

\x1b[34;1mSummer\x1b[0m becomes {color:blue}*Summer*{color}.

Return type:

str

click_extra.styling.ansi_to_latex(text)[source]

Translate ANSI styling in text to LaTeX macros.

\x1b[34;1mSummer\x1b[0m becomes \textcolor{blue}{\textbf{Summer}}. The colored macros require \usepackage{xcolor} in the document preamble.

Return type:

str

click_extra.styling.ansi_to_textile(text)[source]

Translate ANSI styling in text to Textile spans.

\x1b[34;1mSummer\x1b[0m becomes %{color: blue; font-weight: bold}Summer%.

Return type:

str