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.
[31m[1mhello[0m
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:
[38;2;241;250;140msample[0m
[38;2;170;187;204msample[0m
[38;2;241;250;140msample[0m
[93msample[0m
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)
[38;2;241;250;140m[1m[3msample[0m
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 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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()producesxcolor-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
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 hidesNoneand falsy attributes and renders RGB tuples as#rrggbbhex.Hex-string color shorthand:
Style(fg="#f1fa8c")works alongsideStyle(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 | bthat merges two styles, with the right operand winning on conflicts.A
Style.cascade()method that fills the style’sNonefields 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 aStyleinstance.Style.contrast_ratio()returning the WCAG contrast ratio between two foreground colors. Useful for theme designers checking accessibility.split_ansi()andrender_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()andansi_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_kwargscache, 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_valueapplied to every kept value. Use to convert RGB tuples to#rrggbbstrings, nested dataclasses to dicts, etc.keep (
Callable[[Any,Any],bool]) – callable(field, value) -> booldeciding whether the field is emitted. Default keeps everything that differs from the field’s declared default.
- Return type:
- 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). RaisesTypeErrorlisting every unknown key, so callers can build a constructor call without an extra pre-validation pass.- Parameters:
- Return type:
- 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-bit38;2;r;g;bsequence and quantizing it to the nearest38;5;n256-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:
COLORTERMoftruecolor/24bit(see_TRUECOLOR_COLORTERMS) keeps 24-bit.Any other non-empty
COLORTERMquantizes: an explicit lower advertisement.A
TERMending in-16colorquantizes: an unambiguous sub-256 terminal.*-256coloris deliberately not treated as a downgrade, since truecolor terminals routinely report it while advertising their 24-bit support throughCOLORTERMinstead. Honoring it would strip truecolor from the very terminals this optimistic default protects.Otherwise keeps 24-bit.
A
dumb/unknownTERMnever reaches this decision for CLI output: it has already disabled color upstream throughresolve_color_env().- Return type:
- 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:
Stylecloup.Stylewith 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 tocloup.Style.- fg: str | tuple[int, int, int] | int | None = None¶
Foreground color: named ANSI string,
#rrggbbhex, RGB tuple, or palette index.
- bg: str | tuple[int, int, int] | int | None = None¶
Background color: named ANSI string,
#rrggbbhex, RGB tuple, or palette index.
- cascade(base)[source]¶
Return a copy with
Nonefields filled in from base.The instance’s own non-
Nonevalues always win:cascadeonly fills gaps. Useful for theme inheritance:derived.cascade(parent)keepsderived’s overrides and inherits the rest fromparent.- Return type:
- to_dict()[source]¶
Serialize to a plain dict with only the set fields.
RGB tuples are emitted as
#rrggbbstrings so the result round-trips through TOML/JSON/YAML untouched. Pair withfrom_dict()to rebuild aStyle.
- classmethod from_dict(data)[source]¶
Build a
Stylefrom the plain dict produced byto_dict().Validates that every key in data names a known
Stylefield and raisesTypeErrorotherwise. Pair withto_dict()to round-trip through TOML/JSON/YAML.- Return type:
- 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 inlinestyle="..."attributes on HTML spans.- Return type:
- 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;n256-color extension, and the38;2;r;g;b/48;2;r;g;b24-bit extension. Reset codes (the full0reset, its parameter-less\x1b[mform included, and selective resets like22,39or49) 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 singleStyle.To tokenize a string mixing text and escapes, with resets honored, see
split_ansi().- Return type:
- 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:
- 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 itsStyle. Unstyled text is yielded with an emptyStyle. 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().
- 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:
- click_extra.styling.ansi_to_html(text)[source]¶
Translate ANSI styling in text to inline-styled HTML
<span>tags.\x1b[34mSummer\x1b[0mbecomes<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:
- click_extra.styling.ansi_to_jira(text)[source]¶
Translate ANSI styling in text to Jira wiki markup.
\x1b[34;1mSummer\x1b[0mbecomes{color:blue}*Summer*{color}.- Return type: