Skip to content

Commit

Permalink
Merge pull request #216 from dwreeves/enhance-docs
Browse files Browse the repository at this point in the history
[Draft] Enhance documentation with accessibility and comparison pages
  • Loading branch information
ewels authored Dec 20, 2024
2 parents 964019e + 3abaaa2 commit b3f4d86
Show file tree
Hide file tree
Showing 15 changed files with 461 additions and 63 deletions.
8 changes: 8 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,11 @@ repos:
types: [python]
exclude: ^examples|^docs/
require_serial: true

- repo: https://github.com/codespell-project/codespell
rev: v2.2.4
hooks:
- id: codespell
files: ^docs/.*\.md$
additional_dependencies:
- tomli
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
- Rich is a _"Python library for rich text and beautiful formatting in the terminal"_.

The intention of `rich-click` is to provide attractive help output from
Click, formatted with Rich, with minimal customisation required.
Click, formatted with Rich, with minimal customization required.

## Features

Expand All @@ -39,7 +39,7 @@ Click, formatted with Rich, with minimal customisation required.
- 🎁 Group commands and options into named panels
- ❌ Well formatted error messages
- 🔢 Easily give custom sort order for options and commands
- 🎨 Extensive customisation of styling and behaviour possible
- 🎨 Extensive customization of styling and behaviour possible

## Installation

Expand Down
2 changes: 1 addition & 1 deletion docs/blog/posts/pycon-sweden-2024.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ You can find my slides below:
## Slides


[Open in new tab   :octicons-link-external-16:](/rich-click/images/blog/pycon-sweden-2024/Ewels-PyCon-Sweden-2024.pdf){ .md-button }
[Open in new tab   :octicons-link-external-16:](../../images/blog/pycon-sweden-2024/Ewels-PyCon-Sweden-2024.pdf){ .md-button }


<iframe style="width: 100%; border: 1px solid #333; aspect-ratio: 16 / 10;" src="/rich-click/images/blog/pycon-sweden-2024/Ewels-PyCon-Sweden-2024.pdf" title="PyCon Sweden 2024 Slides - Phil Ewels"></iframe>
Expand Down
26 changes: 26 additions & 0 deletions docs/code_snippets/accessibility/colors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# /// script
# dependencies = [
# "rich",
# ]
from rich.console import Console
from rich.box import SIMPLE
from rich.table import Table

console = Console(color_system="truecolor")
table = Table(title="ANSI Colors", box=SIMPLE)
colors = ["black", "red", "green", "yellow", "blue", "magenta", "cyan", "white"]
table.add_column("Color", style="bold")

for variant in ["Normal", "Dim", "Bright", "Dim +\nBright"]:
table.add_column(variant, style="bold")

for color in colors:
table.add_row(
color,
*[
f"[{style}{color}]██████[/{style}{color}]"
for style in ["", "dim ", "bright_", "dim bright_"]
]
)

console.print(table)
114 changes: 114 additions & 0 deletions docs/documentation/accessibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Accessibility

This page goes over information relevant to both developers and users relating to accessibility considerations.

Accessibility is very important to consider when developing applications.
Colorblindness impacts roughly 4% of the general population, meaning that if your application gets even a small user base, there is a high probability that it is being used by someone with colorblindness.

Fortunately, there are ways as both developers and users to address these accessibility concerns, as detailed on this page.

## For users

If you are a user of a **rich-click** CLI, there are a few options you have to improve accessibility for yourself.

### 1. Use the `NO_COLOR` environment variable

Rich uses the `NO_COLOR` standard ([more information here](https://no-color.org/)), giving rich-click built-in capability to allow the user to suppress color.

So, to run any rich-click CLI program without colour, you can do:

```shell
export NO_COLOR=1 # Set environment variable in shell
python cli.py # Run CLI tool

# ... Or run as a single line:
NO_COLOR=1 python cli.py
```

In order to set this environment variable automatically every time you use the terminal, you can add it to your `~/.bashrc` (if using bash) or `~/.zshrc` (if using zsh):

=== "bash"
```shell
echo "export NO_COLOR=1" >> ~/.bashrc
```

=== "zsh"
```shell
echo "export NO_COLOR=1" >> ~/.zshrc
```

!!!tip
Note that other programs may also respect `NO_COLOR`, so it could have other effects!

### 2. Configure your terminal's 4-bit ANSI colors

The 4-bit ANSI color system is a set of 16 different colors implemented in every terminal. This is the most common way to set colors.
These colors are **not** deterministic; different terminals use slightly different hex values for the ANSI colors. [Wikipedia has a full breakdown of all the variations in these colors](https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit)

Most modern terminals have the ability to customize all of these colors in the terminals' settings.
If you are having difficulty distinguishing colors, it is recommended that you adjust these settings.

!!! note
This will only work for CLIs that utilize the 4-bit ANSI color system.
CLIs that utilize hex values or other color systems will not be impacted by your terminal's ANSI color settings.

## For developers

If you would like to make your CLI more accessible for others, there are a few rules of thumb you can follow:

### 1. Use Rich features over Click features

There are some Click features that rich-click doesn't override such as print statements and interactive prompts (see [Comparison of Click and rich-click](comparison_of_click_and_rich_click.md#click-features-that-rich-click-does-not-override)).

In these cases, we recommend using native Rich functionality so that your end users can benefit from `NO_COLOR`, which Click does not support.

So, for example:

- `#!python Confirm.ask("[red]Are you sure?[/]")` is more accessible because it works with `NO_COLOR`.
- `#!python click.confirm(click.echo("Are you sure?", fg="red"))` is less accessible because it cannot be overridden by `NO_COLOR`.

### 2. Use 4-bit ANSI colors

The 4-bit ANSI color system is a set of 16 different colors implemented in effectively every terminal, and they are the most common way to set colors.
These colors are **not** deterministic; different terminals use slightly different hex values for the ANSI colors. [Wikipedia has a full breakdown of all the variations in these colors](https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit)

!!! note
**rich-click**'s logo references the ANSI colors! 😁

There are 16 total ANSI colors: 8 base ANSI colors, with each one having a "bright" variant:

- `black`, `bright_black`
- `red`, `bright_red`
- `green`, `bright_green`
- `yellow`, `bright_yellow`
- `blue`, `bright_blue`
- `magenta`, `bright_magenta`
- `cyan`, `bright_cyan`
- `white`, `bright_white`

Additionally, each one of these can be modified with `dim`, which in modern terminals just applies a change to the opacity of the color, giving developers a total of 32 different colors that can be shown.

Below is a quick script that renders all of these colors:

```python
{!code_snippets/accessibility/colors.py!}
```

<!-- RICH-CODEX
working_dir: docs/code_snippets/accessibility
hide_command: true
terminal_width: 48
-->
![`python colors.py`](../images/code_snippets/accessibility/colors.svg){.screenshot}

(The colors you see when running this locally will differ from the colors in the image.)

The fact that the colors are not deterministic is a _benefit_ for accessibility; it means, for example, a user can customize their terminal so that the ANSI "red" is more suitable for them.
Nearly every modern terminal allows for this sort of customization.

This means that developers looking to create a more accessible experience should prefer ANSI colors.

So, for example:

- `#!python RichHelpConfiguration(style_option="red")` is more accessible because users can configure the hex value of this red.
- `#!python RichHelpConfiguration(style_option="#FF0000")` is less accessible because it is not configurable by the end user.
111 changes: 111 additions & 0 deletions docs/documentation/comparison_of_click_and_rich_click.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Comparison of Click and rich-click

**rich-click** is a [shim](https://en.wikipedia.org/wiki/Shim_(computing)) around Click,
meaning its API is designed to mirror the Click API and intercept some of the calls to slightly different functions.

Everything available via `#!python import click` is also available via `#!python import rich_click as click`.

**rich-click** is designed to keep the additional API surface introduced on top of Click as lightweight as possible.

## Click features that rich-click overrides

The only things that **rich-click** explicitly overrides in the high-level API are the decorators:

- `click.command()`
- `click.group()`

The only change to these decorators is that by default, their `cls=` parameters point to the **rich-click** implementations of `Command` (i.e. `RichCommand`) and `Group` (i.e. `RichGroup`).

!!! note
There is also a thin wrapper around `pass_context()` to cast the `click.Context` type in the function signature to `click.RichContext` to assist with static type-checking with MyPy. Aside from different typing, there are no substantive changes to the `pass_context()` decorator.

## Click features that rich-click does _not_ override

### Base Click command classes

You can still access the base Click classes by their original names:

```python
from rich_click import Command, Group, Context
```

The above are the same as importing from `click`.

**rich-click**'s subclasses all have the word Rich in front of them!

```python
from rich_click import RichCommand, RichGroup, RichContext
```

### Echo and interactive elements

**rich-click** deliberately does _not_ enrich certain Click features:

```python
click.echo()
click.echo_via_pager()
click.confirm()
click.prompt()
```

You are free to use these functions and they are available via `#!python import rich_click as click`,
but Rich's markup will not work with these functions because these functions are just the base Click implementations,
without any changes.

This is a deliberate decision that we are unlikely to change in the future.
We do not want to maintain a more spread-out API surface, and we encourage users to become comfortable using Rich directly; it's a great library and it's worth learning a little bit about it!
If you'd like Rich markup for your echos and interactive elements, then you can:

| Click Function | Rich Replacement | Rich Documentation |
|---------------|------------------|---------------|
| `click.echo()` | `rich.print()` | [Quick start](https://rich.readthedocs.io/en/stable/introduction.html#quick-start) |
| `click.echo_via_pager()` | `rich.Console().pager()` | [Console](https://rich.readthedocs.io/en/stable/console.html#paging) |
| `click.confirm()` | `rich.prompt.Confirm.ask()` | [Prompt](https://rich.readthedocs.io/en/stable/prompt.html) |
| `click.prompt()` | `rich.prompt.Prompt.ask()` | [Prompt](https://rich.readthedocs.io/en/stable/prompt.html) |

Below is a side-by-side comparison of Click and Rich implementations of echos and interactive elements in **rich-click**:

=== "Click"

```python
import rich_click as click

@click.command("greet")
def greet():
name = click.prompt(click.style("What is your name?", fg="blue"))

if not click.confirm(click.style("Are you sure?", fg="blue")):
click.echo(click.style("Aborting", fg="red"))
return

click.echo(click.style(f"Hello, {name}!", fg="green"))

if __name__ == "__main__":
greet()
```

=== "Rich"

```python
import rich_click as click
import rich
from rich.prompt import Confirm, Prompt

@click.command("greet")
def greet():
name = Prompt.ask("[blue]What is your name?[/]")

if not Confirm.ask("[blue]Are you sure?[/]"):
rich.print("[red]Aborting[/]")
return

rich.print(f"[green]Hello, {name}![/]")

if __name__ == "__main__":
greet()
```

## Additional rich-click features

- **rich-click** has a configuration object, `RichHelpConfiguration()`, that allows for control over how **rich-click** help text renders, so you are not just locked into the defaults. More information about this is described in [the **Configuration** docs](configuration.md).
- **rich-click** comes with a CLI tool that allows you to convert regular Click CLIs into **rich-click** CLIs, and also lets you render your **rich-click** CLI help text as HTML and SVG. More information about this is described in [the **rich-click CLI** docs](rich_click_cli.md).
2 changes: 1 addition & 1 deletion docs/documentation/configuration.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Configuration

There are two methods to configure rich-click:
There are two methods to configure **rich-click**:

- Decorator: Use the `@rich_config()` decorator (and `RichHelpConfiguration()`).
- Globals: Set the global variables in the `rich_config.rich_config` module.
Expand Down
12 changes: 6 additions & 6 deletions docs/documentation/formatting_and_styles.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

## Formatting

There are a large number of customisation options in rich-click.
There are a large number of customization options in rich-click.
These can be modified by changing variables in the `click.rich_click` namespace.

Note that most normal click options should still work, such as `show_default=True`, `required=True` and `hidden=True`.
Expand Down Expand Up @@ -158,7 +158,7 @@ working_dir: .
-->
![`python examples/01_simple.py --hep || true`](../images/error.svg "Error message"){.screenshot}

You can customise the _Try 'command --help' for help._ message with `ERRORS_SUGGESTION`
You can customize the _Try 'command --help' for help._ message with `ERRORS_SUGGESTION`
using rich-click though, and add some text after the error with `ERRORS_EPILOGUE`.

For example, from [`examples/07_custom_errors.py`](https://github.com/ewels/rich-click/blob/main/examples/07_custom_errors.py):
Expand Down Expand Up @@ -211,9 +211,9 @@ Setting `MAX_WIDTH` overrides the effect of `WIDTH`
!!! success
Check out the [**Live Style Editor**](../editor.md) to easily get started building a custom **rich-click** style!

Most aspects of rich-click formatting can be customised, from colours to alignment.
Most aspects of rich-click formatting can be customized, from color to alignment.

For example, to print the option flags in a different colour, you can use:
For example, to print the option flags in a different color, you can use:

```python
click.rich_click.STYLE_OPTION = "magenta"
Expand All @@ -226,7 +226,7 @@ click.rich_click.STYLE_OPTIONS_TABLE_LEADING = 1
click.rich_click.STYLE_OPTIONS_TABLE_BOX = "SIMPLE"
```

You can make some really ~horrible~ _colourful_ solutions using these styles if you wish:
You can make some really ~horrible~ _colorful_ solutions using these styles if you wish:

<!-- RICH-CODEX
working_dir: .
Expand All @@ -238,5 +238,5 @@ extra_env:

> See [`examples/10_table_styles.py`](https://github.com/ewels/rich-click/blob/main/examples/10_table_styles.py) for an example.
See the [_Configuration options_](#configuration-options) section below for the full list of available options.
See the [_Configuration options_](configuration.md#configuration-options) section below for the full list of available options.

Loading

0 comments on commit b3f4d86

Please sign in to comment.