Spinnerยถ

An indeterminate progress spinner for blocking work whose duration is unknown: a subprocess, a network call, a long query. Where click.progressbar needs a known length or an iterable to advance through, Spinner simply signals that something is happening.

It animates on a background thread, so the calling thread stays free to block on the work itself:

from time import sleep

from click_extra import Spinner

with Spinner("Brewing tea"):
    sleep(5)

The spinner draws to stderr and is a no-op whenever that stream is not a terminal (a pipe, a file, a CI log), so redirected output and machine-readable formats stay clean. Reassign its label while it runs to reflect the current step, and set a delay so it only appears once an operation is genuinely slow.

Use as a decoratorยถ

A Spinner doubles as a decorator, with or without parentheses. @Spinner wraps a function directly; @Spinner("โ€ฆ") configures the spinner first. Either way the function spins for the duration of every call and returns its result untouched.

@Spinner  # Bare form: a default spinner.
def roast(batch):
    sleep(5)
    return batch


@Spinner("Roasting coffee", timer=True)  # Configured form.
def roast_slowly(batch):
    sleep(5)
    return batch

The one instance is shared across calls, which is right for sequential use; give concurrent callers their own spinner.

Spin directionยถ

Pass reverse=True to rotate the other way. It works with the default frames or any custom sequence:

with Spinner("Chilling lemonade", reverse=True):
    sleep(5)

The animation source is just a sequence of strings. click_extra.spinner ships the default Braille SPINNER_FRAMES and a plain ASCII_SPINNER_FRAMES for terminals without Unicode glyphs; pass your own to frames for anything else.

Spinner catalogยถ

SPINNERS is a catalog of around 90 ready-made animations, each a SpinnerPreset bundling the frames and the interval they were tuned for. They are ported from cli-spinners, the de-facto reference collection. Pick one with spinner=:

from click_extra import Spinner, SPINNERS

with Spinner("Brewing tea", spinner=SPINNERS["moon"]):
    sleep(5)

The preset sets both the frames and the interval; an explicit frames= or interval= still overrides it. Because the spinner redraws the whole line instead of backspacing, the multi-character animations (bouncingBar, pong, shark, โ€ฆ) render correctly here, unlike in the upstream renderers that had to drop them.

Full inventoryยถ

Every style is browsable from the CLI. On an interactive terminal click-extra spinner animates a live tour of the selection (--all for the whole catalog, --random N for a sample, or --select name1,name2 for specific ones); --table prints the reference table below instead of animating. The Frames column previews each animation, and the Tour column is the dwell time the live tour spends on each: three full cycles, clamped to two-to-three seconds:

$ click-extra --color spinner --all --table
โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚ Name                โ”‚ Frames                                                           โ”‚ Interval โ”‚  Tour โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ dots                โ”‚ โ ‹ โ ™ โ น โ ธ โ ผ โ ด โ ฆ โ ง โ ‡ โ                                               โ”‚ 0.08s    โ”‚  2.4s โ”‚
โ”‚ dots2               โ”‚ โฃพ โฃฝ โฃป โขฟ โกฟ โฃŸ โฃฏ โฃท                                                  โ”‚ 0.08s    โ”‚  2.0s โ”‚
โ”‚ dots3               โ”‚ โ ‹ โ ™ โ š โ ž โ – โ ฆ โ ด โ ฒ โ ณ โ “                                              โ”‚ 0.08s    โ”‚  2.4s โ”‚
โ”‚ dots4               โ”‚ โ „ โ † โ ‡ โ ‹ โ ™ โ ธ โ ฐ โ   โ ฐ โ ธ โ ™ โ ‹ โ ‡ โ †                                      โ”‚ 0.08s    โ”‚  3.0s โ”‚
โ”‚ dots5               โ”‚ โ ‹ โ ™ โ š โ ’ โ ‚ โ ‚ โ ’ โ ฒ โ ด โ ฆ โ – โ ’ โ  โ  โ ’ โ “ โ ‹                                โ”‚ 0.08s    โ”‚  3.0s โ”‚
โ”‚ dots6               โ”‚ โ  โ ‰ โ ™ โ š โ ’ โ ‚ โ ‚ โ ’ โ ฒ โ ด โ ค โ „ โ „ โ ค โ ด โ ฒ โ ’ โ ‚ โ ‚ โ ’ โ š โ ™ โ ‰ โ                   โ”‚ 0.08s    โ”‚  3.0s โ”‚
โ”‚ dots7               โ”‚ โ ˆ โ ‰ โ ‹ โ “ โ ’ โ  โ  โ ’ โ – โ ฆ โ ค โ   โ   โ ค โ ฆ โ – โ ’ โ  โ  โ ’ โ “ โ ‹ โ ‰ โ ˆ                  โ”‚ 0.08s    โ”‚  3.0s โ”‚
โ”‚ dots8               โ”‚ โ  โ  โ ‰ โ ™ โ š โ ’ โ ‚ โ ‚ โ ’ โ ฒ โ ด โ ค โ „ โ „ โ ค โ   โ   โ ค โ ฆ โ – โ ’ โ  โ  โ ’ โ “ โ ‹ โ ‰ โ ˆ โ€ฆ (+1)   โ”‚ 0.08s    โ”‚  3.0s โ”‚
โ”‚ dots9               โ”‚ โขน โขบ โขผ โฃธ โฃ‡ โกง โก— โก                                                  โ”‚ 0.08s    โ”‚  2.0s โ”‚
โ”‚ dots10              โ”‚ โข„ โข‚ โข โก โกˆ โก โก                                                     โ”‚ 0.08s    โ”‚  2.0s โ”‚
โ”‚ dots11              โ”‚ โ  โ ‚ โ „ โก€ โข€ โ   โ  โ ˆ                                                  โ”‚ 0.1s     โ”‚  2.4s โ”‚
โ”‚ dots12              โ”‚ โข€โ € โก€โ € โ „โ € โข‚โ € โก‚โ € โ …โ € โขƒโ € โกƒโ € โ โ € โข‹โ € โก‹โ € โ โ  โข‹โ  โก‹โ  โ โ ‰ โ ‹โ ‰ โ ‹โ ‰ โ ‰โ ™ โ ‰โ ™ โ€ฆ (+37) โ”‚ 0.08s    โ”‚  4.5s โ”‚
โ”‚ dots13              โ”‚ โฃผ โฃน โขป โ ฟ โกŸ โฃ โฃง โฃถ                                                  โ”‚ 0.08s    โ”‚  2.0s โ”‚
โ”‚ dots14              โ”‚ โ ‰โ ‰ โ ˆโ ™ โ €โ น โ €โขธ โ €โฃฐ โข€โฃ  โฃ€โฃ€ โฃ„โก€ โฃ†โ € โก‡โ € โ โ € โ ‹โ                               โ”‚ 0.08s    โ”‚  2.9s โ”‚
โ”‚ dots8Bit            โ”‚ โ € โ  โ ‚ โ ƒ โ „ โ … โ † โ ‡ โก€ โก โก‚ โกƒ โก„ โก… โก† โก‡ โ ˆ โ ‰ โ Š โ ‹ โ Œ โ  โ Ž โ  โกˆ โก‰ โกŠ โก‹ โ€ฆ (+228) โ”‚ 0.08s    โ”‚ 20.5s โ”‚
โ”‚ dotsCircle          โ”‚ โขŽ  โ Žโ  โ Šโ ‘ โ ˆโ ฑ  โกฑ โข€โกฐ โข„โก  โข†โก€                                          โ”‚ 0.08s    โ”‚  2.0s โ”‚
โ”‚ sand                โ”‚ โ  โ ‚ โ „ โก€ โกˆ โก โก  โฃ€ โฃ โฃ‚ โฃ„ โฃŒ โฃ” โฃค โฃฅ โฃฆ โฃฎ โฃถ โฃท โฃฟ โกฟ โ ฟ โขŸ โ Ÿ โก› โ › โ ซ โข‹ โ€ฆ (+7)   โ”‚ 0.08s    โ”‚  3.0s โ”‚
โ”‚ line                โ”‚ - \ | /                                                          โ”‚ 0.13s    โ”‚  2.0s โ”‚
โ”‚ line2               โ”‚ โ ‚ - โ€“ โ€” โ€“ -                                                      โ”‚ 0.1s     โ”‚  2.0s โ”‚
โ”‚ rollingLine         โ”‚ /    -   \    |   |  \   -  /                                    โ”‚ 0.08s    โ”‚  2.0s โ”‚
โ”‚ pipe                โ”‚ โ”ค โ”˜ โ”ด โ”” โ”œ โ”Œ โ”ฌ โ”                                                  โ”‚ 0.1s     โ”‚  2.4s โ”‚
โ”‚ simpleDots          โ”‚ .   ..  ...                                                      โ”‚ 0.4s     โ”‚  3.0s โ”‚
โ”‚ simpleDotsScrolling โ”‚ .   ..  ...  ..   .                                              โ”‚ 0.2s     โ”‚  3.0s โ”‚
โ”‚ star                โ”‚ โœถ โœธ โœน โœบ โœน โœท                                                      โ”‚ 0.07s    โ”‚  2.0s โ”‚
โ”‚ star2               โ”‚ + x *                                                            โ”‚ 0.08s    โ”‚  2.0s โ”‚
โ”‚ flip                โ”‚ _ _ _ - ` ` ' ยด - _ _ _                                          โ”‚ 0.07s    โ”‚  2.5s โ”‚
โ”‚ hamburger           โ”‚ โ˜ฑ โ˜ฒ โ˜ด                                                         โ”‚ 0.1s     โ”‚  2.0s โ”‚
โ”‚ growVertical        โ”‚ โ– โ–ƒ โ–„ โ–… โ–† โ–‡ โ–† โ–… โ–„ โ–ƒ                                              โ”‚ 0.12s    โ”‚  3.0s โ”‚
โ”‚ growHorizontal      โ”‚ โ– โ–Ž โ– โ–Œ โ–‹ โ–Š โ–‰ โ–Š โ–‹ โ–Œ โ– โ–Ž                                          โ”‚ 0.12s    โ”‚  3.0s โ”‚
โ”‚ balloon             โ”‚ . o O @ *                                                        โ”‚ 0.14s    โ”‚  2.9s โ”‚
โ”‚ balloon2            โ”‚ . o O ยฐ O o .                                                    โ”‚ 0.12s    โ”‚  2.5s โ”‚
โ”‚ noise               โ”‚ โ–“ โ–’ โ–‘                                                            โ”‚ 0.1s     โ”‚  2.0s โ”‚
โ”‚ bounce              โ”‚ โ  โ ‚ โ „ โ ‚                                                          โ”‚ 0.12s    โ”‚  2.0s โ”‚
โ”‚ boxBounce           โ”‚ โ–– โ–˜ โ– โ–—                                                          โ”‚ 0.12s    โ”‚  2.0s โ”‚
โ”‚ boxBounce2          โ”‚ โ–Œ โ–€ โ– โ–„                                                          โ”‚ 0.1s     โ”‚  2.0s โ”‚
โ”‚ triangle            โ”‚ โ—ข โ—ฃ โ—ค โ—ฅ                                                          โ”‚ 0.05s    โ”‚  2.0s โ”‚
โ”‚ binary              โ”‚ 010010 001100 100101 111010 111101 010111 101011 111000 โ€ฆ (+2)   โ”‚ 0.08s    โ”‚  2.4s โ”‚
โ”‚ arc                 โ”‚ โ—œ โ—  โ— โ—ž โ—ก โ—Ÿ                                                      โ”‚ 0.1s     โ”‚  2.0s โ”‚
โ”‚ circle              โ”‚ โ—ก โŠ™ โ—                                                             โ”‚ 0.12s    โ”‚  2.0s โ”‚
โ”‚ squareCorners       โ”‚ โ—ฐ โ—ณ โ—ฒ โ—ฑ                                                          โ”‚ 0.18s    โ”‚  2.2s โ”‚
โ”‚ circleQuarters      โ”‚ โ—ด โ—ท โ—ถ โ—ต                                                          โ”‚ 0.12s    โ”‚  2.0s โ”‚
โ”‚ circleHalves        โ”‚ โ— โ—“ โ—‘ โ—’                                                          โ”‚ 0.05s    โ”‚  2.0s โ”‚
โ”‚ squish              โ”‚ โ•ซ โ•ช                                                              โ”‚ 0.1s     โ”‚  2.0s โ”‚
โ”‚ toggle              โ”‚ โŠถ โŠท                                                              โ”‚ 0.25s    โ”‚  2.0s โ”‚
โ”‚ toggle2             โ”‚ โ–ซ โ–ช                                                              โ”‚ 0.08s    โ”‚  2.0s โ”‚
โ”‚ toggle3             โ”‚ โ–ก โ–                                                               โ”‚ 0.12s    โ”‚  2.0s โ”‚
โ”‚ toggle4             โ”‚ โ–  โ–ก โ–ช โ–ซ                                                          โ”‚ 0.1s     โ”‚  2.0s โ”‚
โ”‚ toggle5             โ”‚ โ–ฎ โ–ฏ                                                              โ”‚ 0.1s     โ”‚  2.0s โ”‚
โ”‚ toggle6             โ”‚ แ€ แ€                                                              โ”‚ 0.3s     โ”‚  2.0s โ”‚
โ”‚ toggle7             โ”‚ โฆพ โฆฟ                                                              โ”‚ 0.08s    โ”‚  2.0s โ”‚
โ”‚ toggle8             โ”‚ โ— โ—Œ                                                              โ”‚ 0.1s     โ”‚  2.0s โ”‚
โ”‚ toggle9             โ”‚ โ—‰ โ—Ž                                                              โ”‚ 0.1s     โ”‚  2.0s โ”‚
โ”‚ toggle10            โ”‚ ใŠ‚ ใŠ€ ใЁ                                                         โ”‚ 0.1s     โ”‚  2.0s โ”‚
โ”‚ toggle11            โ”‚ โง‡ โง†                                                              โ”‚ 0.05s    โ”‚  2.0s โ”‚
โ”‚ toggle12            โ”‚ โ˜— โ˜–                                                              โ”‚ 0.12s    โ”‚  2.0s โ”‚
โ”‚ toggle13            โ”‚ = * -                                                            โ”‚ 0.08s    โ”‚  2.0s โ”‚
โ”‚ arrow               โ”‚ โ† โ†– โ†‘ โ†— โ†’ โ†˜ โ†“ โ†™                                                  โ”‚ 0.1s     โ”‚  2.4s โ”‚
โ”‚ arrow2              โ”‚ โฌ†  โ†—  โžก  โ†˜  โฌ‡  โ†™  โฌ…  โ†–                                           โ”‚ 0.08s    โ”‚  2.0s โ”‚
โ”‚ arrow3              โ”‚ โ–นโ–นโ–นโ–นโ–น โ–ธโ–นโ–นโ–นโ–น โ–นโ–ธโ–นโ–นโ–น โ–นโ–นโ–ธโ–นโ–น โ–นโ–นโ–นโ–ธโ–น โ–นโ–นโ–นโ–นโ–ธ                              โ”‚ 0.12s    โ”‚  2.2s โ”‚
โ”‚ bouncingBar         โ”‚ [    ] [=   ] [==  ] [=== ] [====] [ ===] [  ==] [   =] โ€ฆ (+8)   โ”‚ 0.08s    โ”‚  3.0s โ”‚
โ”‚ bouncingBall        โ”‚ ( โ—    ) (  โ—   ) (   โ—  ) (    โ— ) (     โ—) (    โ— ) โ€ฆ (+4)     โ”‚ 0.08s    โ”‚  2.4s โ”‚
โ”‚ smiley              โ”‚ ๐Ÿ˜„  ๐Ÿ˜                                                           โ”‚ 0.2s     โ”‚  2.0s โ”‚
โ”‚ monkey              โ”‚ ๐Ÿ™ˆ  ๐Ÿ™ˆ  ๐Ÿ™‰  ๐Ÿ™Š                                                   โ”‚ 0.3s     โ”‚  3.0s โ”‚
โ”‚ hearts              โ”‚ ๐Ÿ’›  ๐Ÿ’™  ๐Ÿ’œ  ๐Ÿ’š  ๐Ÿ’—                                               โ”‚ 0.1s     โ”‚  2.0s โ”‚
โ”‚ clock               โ”‚ ๐Ÿ•›  ๐Ÿ•  ๐Ÿ•‘  ๐Ÿ•’  ๐Ÿ•“  ๐Ÿ•”  ๐Ÿ••  ๐Ÿ•–  ๐Ÿ•—  ๐Ÿ•˜  ๐Ÿ•™  ๐Ÿ•š                   โ”‚ 0.1s     โ”‚  3.0s โ”‚
โ”‚ earth               โ”‚ ๐ŸŒ  ๐ŸŒŽ  ๐ŸŒ                                                       โ”‚ 0.18s    โ”‚  2.0s โ”‚
โ”‚ material            โ”‚ โ–ˆโ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ– โ–ˆโ–ˆโ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ– โ€ฆ (+90)                โ”‚ 0.017s   โ”‚  3.0s โ”‚
โ”‚ moon                โ”‚ ๐ŸŒ‘  ๐ŸŒ’  ๐ŸŒ“  ๐ŸŒ”  ๐ŸŒ•  ๐ŸŒ–  ๐ŸŒ—  ๐ŸŒ˜                                   โ”‚ 0.08s    โ”‚  2.0s โ”‚
โ”‚ runner              โ”‚ ๐Ÿšถ  ๐Ÿƒ                                                           โ”‚ 0.14s    โ”‚  2.0s โ”‚
โ”‚ pong                โ”‚ โ–โ ‚       โ–Œ โ–โ ˆ       โ–Œ โ– โ ‚      โ–Œ โ– โ        โ–Œ โ–  โก€     โ–Œ โ€ฆ (+25)   โ”‚ 0.08s    โ”‚  3.0s โ”‚
โ”‚ shark               โ”‚ โ–|\____________โ–Œ โ–_|\___________โ–Œ โ–__|\__________โ–Œ โ€ฆ (+23)       โ”‚ 0.12s    โ”‚  3.1s โ”‚
โ”‚ dqpb                โ”‚ d q p b                                                          โ”‚ 0.1s     โ”‚  2.0s โ”‚
โ”‚ weather             โ”‚ โ˜€  โ˜€  โ˜€  ๐ŸŒค  โ›…  ๐ŸŒฅ  โ˜  ๐ŸŒง  ๐ŸŒจ  ๐ŸŒง  ๐ŸŒจ  ๐ŸŒง  ๐ŸŒจ  โ›ˆ  ๐ŸŒจ  ๐ŸŒง  ๐ŸŒจ  โ˜  โ€ฆ (+5)    โ”‚ 0.1s     โ”‚  3.0s โ”‚
โ”‚ christmas           โ”‚ ๐ŸŒฒ ๐ŸŽ„                                                            โ”‚ 0.4s     โ”‚  2.4s โ”‚
โ”‚ grenade             โ”‚ ุŒ   โ€ฒ    ยด   โ€พ    โธŒ   โธŠ   |   โŽ   โ•  เทด    โ“                      โ”‚ 0.08s    โ”‚  3.0s โ”‚
โ”‚ point               โ”‚ โˆ™โˆ™โˆ™ โ—โˆ™โˆ™ โˆ™โ—โˆ™ โˆ™โˆ™โ— โˆ™โˆ™โˆ™                                              โ”‚ 0.125s   โ”‚  2.0s โ”‚
โ”‚ layer               โ”‚ - = โ‰ก                                                            โ”‚ 0.15s    โ”‚  2.0s โ”‚
โ”‚ betaWave            โ”‚ ฯฮฒฮฒฮฒฮฒฮฒฮฒ ฮฒฯฮฒฮฒฮฒฮฒฮฒ ฮฒฮฒฯฮฒฮฒฮฒฮฒ ฮฒฮฒฮฒฯฮฒฮฒฮฒ ฮฒฮฒฮฒฮฒฯฮฒฮฒ ฮฒฮฒฮฒฮฒฮฒฯฮฒ ฮฒฮฒฮฒฮฒฮฒฮฒฯ          โ”‚ 0.08s    โ”‚  2.0s โ”‚
โ”‚ fingerDance         โ”‚ ๐Ÿค˜  ๐ŸคŸ  ๐Ÿ––  โœ‹  ๐Ÿคš  ๐Ÿ‘†                                           โ”‚ 0.16s    โ”‚  2.9s โ”‚
โ”‚ fistBump            โ”‚ ๐Ÿคœใ€€ใ€€ใ€€ใ€€๐Ÿค›  ๐Ÿคœใ€€ใ€€ใ€€ใ€€๐Ÿค›  ๐Ÿคœใ€€ใ€€ใ€€ใ€€๐Ÿค›  ใ€€๐Ÿคœใ€€ใ€€๐Ÿค›ใ€€  โ€ฆ (+3)   โ”‚ 0.08s    โ”‚  2.0s โ”‚
โ”‚ soccerHeader        โ”‚ ๐Ÿง‘โšฝ       ๐Ÿง‘  ๐Ÿง‘  โšฝ      ๐Ÿง‘  ๐Ÿง‘   โšฝ     ๐Ÿง‘  โ€ฆ (+9)            โ”‚ 0.08s    โ”‚  2.9s โ”‚
โ”‚ mindblown           โ”‚ ๐Ÿ˜  ๐Ÿ˜  ๐Ÿ˜ฎ  ๐Ÿ˜ฎ  ๐Ÿ˜ฆ  ๐Ÿ˜ฆ  ๐Ÿ˜ง  ๐Ÿ˜ง  ๐Ÿคฏ  ๐Ÿ’ฅ  โœจ                       โ”‚ 0.16s    โ”‚  3.0s โ”‚
โ”‚ speaker             โ”‚ ๐Ÿ”ˆ  ๐Ÿ”‰  ๐Ÿ”Š  ๐Ÿ”‰                                                   โ”‚ 0.16s    โ”‚  2.0s โ”‚
โ”‚ orangePulse         โ”‚ ๐Ÿ”ธ  ๐Ÿ”ถ  ๐ŸŸ   ๐ŸŸ   ๐Ÿ”ถ                                               โ”‚ 0.1s     โ”‚  2.0s โ”‚
โ”‚ bluePulse           โ”‚ ๐Ÿ”น  ๐Ÿ”ท  ๐Ÿ”ต  ๐Ÿ”ต  ๐Ÿ”ท                                               โ”‚ 0.1s     โ”‚  2.0s โ”‚
โ”‚ orangeBluePulse     โ”‚ ๐Ÿ”ธ  ๐Ÿ”ถ  ๐ŸŸ   ๐ŸŸ   ๐Ÿ”ถ  ๐Ÿ”น  ๐Ÿ”ท  ๐Ÿ”ต  ๐Ÿ”ต  ๐Ÿ”ท                           โ”‚ 0.1s     โ”‚  3.0s โ”‚
โ”‚ timeTravel          โ”‚ ๐Ÿ•›  ๐Ÿ•š  ๐Ÿ•™  ๐Ÿ•˜  ๐Ÿ•—  ๐Ÿ•–  ๐Ÿ••  ๐Ÿ•”  ๐Ÿ•“  ๐Ÿ•’  ๐Ÿ•‘  ๐Ÿ•                   โ”‚ 0.1s     โ”‚  3.0s โ”‚
โ”‚ aesthetic           โ”‚ โ–ฐโ–ฑโ–ฑโ–ฑโ–ฑโ–ฑโ–ฑ โ–ฐโ–ฐโ–ฑโ–ฑโ–ฑโ–ฑโ–ฑ โ–ฐโ–ฐโ–ฐโ–ฑโ–ฑโ–ฑโ–ฑ โ–ฐโ–ฐโ–ฐโ–ฐโ–ฑโ–ฑโ–ฑ โ–ฐโ–ฐโ–ฐโ–ฐโ–ฐโ–ฑโ–ฑ โ–ฐโ–ฐโ–ฐโ–ฐโ–ฐโ–ฐโ–ฑ โ–ฐโ–ฐโ–ฐโ–ฐโ–ฐโ–ฐโ–ฐ โ€ฆ (+1)   โ”‚ 0.08s    โ”‚  2.0s โ”‚
โ”‚ dwarfFortress       โ”‚ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ   โ˜บโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ   โ˜บโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ   โ˜บโ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ   โ€ฆ (+129)      โ”‚ 0.08s    โ”‚ 10.6s โ”‚
โ”‚ fish                โ”‚ ~~~~~~~~~~~~~~~~~~~~ > ~~~~~~~~~~~~~~~~~~ โ€ฆ (+25)                โ”‚ 0.08s    โ”‚  3.0s โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

Bell on completionยถ

Set beep=True to ring the terminal bell once when the spinner stops, handy for a long task you walk away from. It rings only when the spinner was actually shown, so redirected or non-interactive runs stay quiet:

with Spinner("Baking bread", beep=True):
    sleep(5)

Printing while spinningยถ

Because the spinner draws to stderr, results written to stdout never collide with the animation. To emit a line on the same stream as the spinner, use echo(): it erases the current frame, prints the message above the spinner, and lets the animation carry on underneath. A bare print would instead leave a frame glyph stranded mid-line.

with Spinner("Picking apples") as spinner:
    for basket in range(3):
        sleep(2)
        spinner.echo(f"Filled basket {basket}")

Parallel workยถ

A Spinner drives a single line, so a pool of concurrent tasks does not need one apiece: one spinner can report on them all. The simplest way is to let the main thread update it as the tasks finish, through concurrent.futures.as_completed.

Update the label for a running count:

from concurrent.futures import ThreadPoolExecutor, as_completed
from time import sleep

from click_extra import Spinner

cities = ["Cairo", "Lima", "Oslo", "Paris", "Tokyo"]


def fetch(city):
    sleep(1)  # The blocking call: a download, a query, a subprocess.
    return city


with Spinner(f"Fetching forecasts (0/{len(cities)})") as spinner:
    with ThreadPoolExecutor() as pool:
        futures = [pool.submit(fetch, city) for city in cities]
        for done, _ in enumerate(as_completed(futures), 1):
            spinner.label = f"Fetching forecasts ({done}/{len(cities)})"

Or echo() a line as each task lands, leaving a trail of finished work that scrolls up while the spinner keeps turning below it:

with Spinner("Fetching forecasts") as spinner:
    with ThreadPoolExecutor() as pool:
        futures = {pool.submit(fetch, city): city for city in cities}
        for future in as_completed(futures):
            spinner.echo(f"โœ“ {futures[future]}")

Both label and echo() are safe to touch while the animation runs, so a worker thread can stream its own progress mid-task rather than only reporting on completion. A genuine spinner per task, several rotating at once on their own lines, is a separate capability: it needs a coordinated multi-line region, which Spinner does not attempt.

The operation trailยถ

The two idioms above (a running done/total label, plus one echoed line per finished task) are packaged together as OperationTrail, the batch-reporting companion of the concurrency primitives run_jobs and run_lanes. Each completed operation leaves a persistent โœ“/โœ˜ trail_line() on screen, and finish() closes the batch with a kept summary line:

from click_extra.execution import run_jobs
from click_extra.spinner import OperationTrail

jobs = 4

with OperationTrail(
    label="Fetching", unit="feeds", total=len(feeds), jobs=jobs
) as trail:

    def fetch(feed):
        trail.mark(*pull(feed))  # pull() returns (ok, message).

    list(run_jobs(fetch, feeds, jobs=jobs))
    trail.finish(
        trail.ok_count == len(feeds),
        f"Fetched {trail.ok_count}/{len(feeds)} feeds",
    )

The rendering adapts to the batchโ€™s concurrency. Run concurrently (jobs > 1), one aggregate spinner carries the Fetching 3/5 feeds tally while the trail lines stream above it, and the finisher becomes the spinnerโ€™s kept ok()/fail() line, elapsed time included; outcomes landing before the spinner first draws are buffered so nothing leaks onto a stream the (possibly delayed or disabled) spinner never touches. That spinnerโ€™s animation is picked from the catalog with spinner=SPINNERS["moon"]. Run sequentially (jobs <= 1), there is no aggregate spinner to collide with, so each outcome echoes as a plain line and the finisher appends the elapsed time itself: each operation stays free to keep its own per-call Spinner.

Like the spinner, the trail renders only on an interactive stream unless enabled forces the matter, so pipes and CI logs stay clean, and mark() is safe to call from worker threads. A sequential batch whose real product is another output (a result table on stdout) can silence its trail with echo_sequential=False while keeping the ok_count tally.

Off a terminal the trail is silent, so it cannot render in a captured build unless enabled forces it. Passing enabled=True echoes the trail regardless of the stream, which is how this page shows a live sequential run (a real CLI leaves enabled=None and lets the terminal decide, as the --progress section below covers):

from click_extra import command
from click_extra.spinner import OperationTrail


@command
def roast():
    """Roast a tray of vegetables, tracing each outcome as it lands."""
    vegetables = ["carrots", "fennel", "leeks", "peppers"]
    # enabled=True forces the trail on so this page can render it; a real CLI
    # leaves enabled=None to auto-detect the terminal (see --progress below).
    with OperationTrail(jobs=1, enabled=True) as trail:
        for vegetable in vegetables:
            roasted = vegetable != "leeks"  # The leeks caught the heat.
            trail.mark(
                roasted,
                f"{vegetable} roasted" if roasted else f"{vegetable} scorched",
            )
        trail.finish(
            trail.ok_count == len(vegetables),
            f"Roasted {trail.ok_count}/{len(vegetables)} vegetables",
        )
$ roast
โœ“ carrots roasted
โœ“ fennel roasted
โœ˜ leeks scorched
โœ“ peppers roasted
โœ˜ Roasted 3/4 vegetables

The lines echo in order as each outcome lands, and the run closes on the โœ˜ finisher because one vegetable scorched: the same trail a sequential batch leaves in a real terminal, without the live redraw.

The trailโ€™s timer follows the --time / --no-time flag by default (timer=None), so this untimed run shows no clocks. Under --time the โœ˜ finisher gains the batchโ€™s total, and each operation can report its own elapsed time: wrap the work in an operation() handle, whose mark() times itself, so the lines read โœ“ carrots roasted (2.4s). Force it either way with timer=True or timer=False, or hand timer a lambda seconds: โ€ฆ callable to format the clock.

def roast(vegetable):
    op = trail.operation()  # Start this operation's clock.
    roasted = vegetable != "leeks"
    op.mark(
        roasted,
        f"{vegetable} roasted" if roasted else f"{vegetable} scorched",
    )

While the batch runs, the aggregate indicator counts the elapsed time up from zero by default; pass clock="eta" to count down an estimate of the time remaining instead. A determinate bar reads that estimate from Click natively, and the concurrent spinner borrows the same click.progressbar estimator since the trail knows its total. The per-operation and finisher times stay elapsed either way.

The bundled CLI wraps all three renderings in one command, to watch in a terminal what this page can only echo: click-extra trail roasts a batch behind a concurrent aggregate spinner, --jobs 1 drops to the sequential plain-line trail above, and --progress-bar swaps the spinner for a determinate bar.

$ click-extra trail --help
Usage: click-extra trail [OPTIONS]

  Trace a simulated batch of operations behind an operation trail.

  Roasts a handful of make-believe vegetables (each a short pause, the leeks
  scorching) and reports them as they land. The display follows the batch: with
  --jobs 1 each outcome echoes as a plain line; with two or more jobs a spinner
  carries the running tally while outcomes stream above it; --progress-bar swaps
  that spinner for a determinate progress bar. Add --time to append each
  vegetable's roast time and the batch total; --elapsed and --eta turn that on
  too, counting up from zero or down as an estimate. Honors --progress / --no-
  progress and stays silent off an interactive terminal.

Options:
  --progress-bar             Drive the batch with a determinate progress bar
                             instead of a spinner.
  --eta / --elapsed          For --progress-bar, show the time remaining (--eta)
                             or elapsed (--elapsed); either one turns timing on,
                             like --time. A spinner always shows elapsed time.
  --spinner NAME             Aggregate spinner animation for concurrent runs
                             (see the spinner command for names). Defaults to
                             the built-in spinner; ignored with --progress-bar.
  --jobs [auto|max|INTEGER]  Number of parallel jobs. Accepts an integer, 'auto'
                             (one fewer than the host's logical CPUs) or 'max'
                             (all logical CPUs). 0 runs sequentially.  [default:
                             auto]
  -h, --help                 Show this message and exit.

A progress bar instead of a spinnerยถ

Because the trail knows its total, it can carry a determinate progress bar rather than an indeterminate spinner. Give the roast command above a bar by adding progress_bar=True (mutually exclusive with spinner=, and requiring a positive total), plus the label and unit its tally reads:

with OperationTrail(
    label="Roasting",
    unit="vegetables",
    total=len(vegetables),
    progress_bar=True,
) as trail:
    ...  # mark() each outcome, then finish(), exactly as above.

The aggregate indicator becomes a bar holding the done/total count, the same โœ“/โœ˜ outcomes streaming above it, and a kept summary replaces the bar on finish(). It serves sequential and concurrent batches alike.

Unlike the sequential trail above, the bar is driven by cursor-control codes, so it draws only on an interactive terminal (unless enabled forces it) and this page cannot render it live. Mid-batch, the landed outcomes sit above a bar tracking the tally:

$ roast
โœ“ carrots roasted
โœ“ fennel roasted
โœ˜ leeks scorched
Roasting  [###########################---------]  3/4  vegetables

When the last vegetable lands, finish() replaces the bar with the kept โœ˜ Roasted 3/4 vegetables (0.0s) summary, the same trail a sequential run leaves behind. A log record emitted mid-batch still lands on its own line above the bar, through the same cooperation the spinner uses. To watch a bar drive a live batch, run click-extra trail --progress-bar in a terminal.

Styling and colorยถ

The spinnerโ€™s glyph, label and timer are painted with a Style instance: the very type Click Extraโ€™s theme system is built on. The simplest customization is a foreground color:

from click_extra import Spinner, Style

with Spinner("Counting sheep", style=Style(fg="cyan")):
    sleep(5)

A Style carries far more than a foreground color. Add a background with bg, and text attributes like bold, dim, italic, underline, blink or reverse, and combine them freely:

with Spinner("Counting sheep", style=Style(fg="bright_white", bg="blue", bold=True)):
    sleep(5)

Colors accept any form click.style understands: ANSI names ("red", "bright_magenta"), 256-color indexes, #rrggbb hex strings, or (r, g, b) tuples. A Style carrying an unrenderable color or attribute is rejected with a ValueError at construction, so a typo fails fast instead of silently dying on the animation thread.

Color follows the terminal, not the spinnerยถ

Color is decoupled from the animation: under --no-color or NO_COLOR the spinner keeps spinning, just in plain text (the --progress section below explains the rationale). Inside a Click Extra CLI the color follows the reconciled --color/--no-color flag; standalone it honors FORCE_COLOR, then NO_COLOR, then falls back to whether the terminal is interactive.

The same Style type colors the ok() / fail() finishers: they default to the themeโ€™s success/error style and take a style= override, covered in the Success and failure section below.

Success and failureยถ

Stopping the spinner (or leaving its context) erases it. To leave a result on screen instead, finish with ok() or fail(): each replaces the final frame with a kept line. The marker defaults to the themeโ€™s success/error glyph (โœ“ / โœ˜), painted with the active themeโ€™s success/error Style, so a finished spinner matches the rest of a themed CLI.

with Spinner("Baking bread") as spinner:
    sleep(5)
    spinner.ok()  # โœ“ Baking bread

Pass your own marker (spinner.ok("done")) or override the paint with a Style (spinner.fail(style=Style(fg="bright_red"))). Color is stripped under --no-color/NO_COLOR; off a terminal the line is still written, so the outcome is recorded in logs and pipes.

Because the finisher is written even when the spinner never appeared (a call shorter than the delay, a pipe, a non-terminal), gate it on the shown property when you only want it after a spinner the reader actually saw:

with Spinner("Baking bread") as spinner:
    bake()
    if spinner.shown:
        spinner.ok()

Elapsed timeยถ

Set timer=True to append the running wall-clock time to the spinner, and to any ok()/fail() line:

with Spinner("Simmering stock", timer=True) as spinner:
    sleep(5)
    spinner.ok()  # โœ“ Simmering stock (5.0s)

The default format is compact: 2.3s, then 1:05, then 1:02:03. For anything else, pass a callable instead of True: it receives the elapsed seconds and returns the string to show:

with Spinner("Simmering stock", timer=lambda s: f"{s / 60:.0f} min") as spinner:
    sleep(5)
    spinner.ok()  # โœ“ Simmering stock (0 min)

Read the elapsed time any moment from the elapsed_time property, which freezes once the spinner stops.

The --progress optionยถ

click_extra.command and click_extra.group add a --progress/--no-progress flag to every CLI by default. It resolves to a single boolean at ctx.meta["click_extra.progress"], which a command reads to decide whether to start a Spinner:

from click_extra import Spinner, command, pass_context
from click_extra.context import PROGRESS


@command
@pass_context
def harvest(ctx):
    """Pick apples, showing a spinner when progress is enabled."""
    with Spinner("Picking apples", enabled=None if ctx.meta[PROGRESS] else False):
        sleep(5)

Spinner display is decoupled from color. A spinner is an interactivity concern, not a color one: it is driven by cursor-control codes, which the NO_COLOR standard explicitly does not govern. So --no-color and NO_COLOR strip the spinnerโ€™s color but keep it spinning, the same way cargo, npm, pip, Rich, indicatif and ora gate progress on the terminal rather than on color.

The resolved value is False only for non-interactive output (a pipe, a TERM=dumb terminal, or CI: handled by the widgetโ€™s own check when you pass enabled=None) and for explicit intent (--no-progress or --accessible, the latter so a screen reader is never handed a spinning glyph).

Progress barsยถ

The same --progress/--no-progress flag also gates Clickโ€™s determinate progress bar. click_extra.progressbar is a drop-in for click.progressbar: it reads the resolved flag and hides the bar when progress is off, so a single --no-progress (or --accessible) silences both the indeterminate spinner and the determinate bar.

from click_extra import command, progressbar


@command
def harvest():
    """Pick apples behind a determinate progress bar."""
    with progressbar((1, 2, 3), label="Picking apples") as bar:
        for _ in bar:
            pass
$ harvest
Picking apples
$ harvest --no-progress

The hidden argument stays authoritative: pass an explicit hidden=True or hidden=False to force the bar regardless of the flag, mirroring how an explicit color= overrides ctx.color on click.echo. Color is handled upstream too, since Click renders the bar through click.echo: --no-color and NO_COLOR strip its ANSI without any extra wiring.

The barโ€™s estimated-time display is gated the same way, but on --time rather than --progress: show_eta defaults to None, shown under --time and hidden otherwise, so a bare bar and an operation trail agree on when to surface timing. An explicit show_eta=True or show_eta=False overrides it (Clickโ€™s own default is True).

click_extra.spinner APIยถ

        classDiagram
  ExtraOption <|-- ProgressOption
  Protocol <|-- _AggregateIndicator
  Protocol <|-- _LiveLine
  tuple <|-- SpinnerPreset
    

An indeterminate terminal spinner for long-running, blocking work.

Click ships click.progressbar(), but it is determinate: it needs a known length or an iterable to advance through. Some work has no measurable progress: a blocking subprocess, a network round-trip, a query whose duration is unknown. For those, the only honest feedback is โ€œsomething is happeningโ€.

Spinner fills that gap. It animates a small frame sequence on a daemon thread, so the caller can stay blocked in a single call (communicate(), urlopen(), โ€ฆ) while the spinner keeps turning:

from time import sleep

from click_extra import Spinner

with Spinner("Brewing tea"):
    sleep(5)  # A blocking call with no measurable progress.

Caution

The spinner draws with carriage returns and ANSI control codes, so it is a no-op whenever its output stream is not a TTY (a pipe, a file, a captured test buffer, a CI log), unless enabled is forced. This keeps redirected output and machine-readable formats clean.

Note

On Windows, Spinner.start() enables the consoleโ€™s virtual-terminal processing so the ANSI control codes animate in place rather than print literally (โ ‹โ›[0m โ€ฆ โ›[K). Modern terminals (Windows Terminal, recent conhost) already have it on; this just covers older consoles.

click_extra.spinner.active_spinner(stream=None)[source]

Return the innermost Spinner currently animating, or None.

A spinner-typed view of _active_line(), skipping any progress-bar indicator that may own the line instead. With stream given, only a spinner drawing on that very stream matches.

Return type:

Spinner | None

class click_extra.spinner.Spinner(label='', *, frames=None, spinner=None, reverse=False, interval=None, delay=0.0, style=None, timer=False, stream=None, enabled=None, hide_cursor=True, beep=False)[source]

Bases: object

A thread-animated, indeterminate progress spinner usable as a context manager.

The animation runs on a background daemon thread, leaving the calling thread free to block on the actual work. Entering the context (or calling start()) begins the animation; leaving it (or calling stop()) halts the thread and erases the spinner line so it never lingers above the next output.

Note

A single Spinner instance drives one animation at a time. mpm and similar tools run their subprocesses sequentially, so one shared instance whose label is reassigned between steps is enough; for concurrent work, use one instance per thread.

Configure (but do not start) the spinner.

Parameters:
  • label (str | Callable[..., Any]) โ€“ text shown after the spinner glyph. As a special case, a bare @Spinner decorator passes the wrapped function here instead; it is detected and the label defaults to empty.

  • frames (Sequence[str] | None) โ€“ the animation frames, cycled in order. Defaults to SPINNER_FRAMES, or the spinner presetโ€™s frames when given.

  • spinner (SpinnerPreset | None) โ€“ a SpinnerPreset from the SPINNERS catalog (spinner=SPINNERS["moon"]), supplying both frames and a tuned interval. An explicit frames or interval still overrides it.

  • reverse (bool) โ€“ cycle the frames backwards, spinning the animation the other way. Set it when the rotation runs counter to what you expect; it composes with any custom frames.

  • interval (float | None) โ€“ seconds between two frames. Defaults to 0.1, or the spinner presetโ€™s interval when given.

  • delay (float) โ€“ seconds to wait before drawing the first frame. A non-zero delay keeps the spinner silent for calls that finish quickly, so it only surfaces once an operation is genuinely slow.

  • style (Style | None) โ€“ a Style applied to the spinner glyph, label and timer (Style(fg="cyan", bold=True)). Color is decoupled from animation: --no-color / NO_COLOR strip it while the spinner keeps spinning (see ProgressOption).

  • timer (bool | Callable[[float], str]) โ€“ append the elapsed wall-clock time to the spinner, and to any final ok() / fail() line. True uses format_duration() for the default compact format (2.3s, 1:05, then 1:02:03). Pass a callable (seconds: float) -> str to format the duration yourself, like timer=lambda s: f"{s / 60:.0f}m" for whole minutes.

  • stream (IO[str] | None) โ€“ where to draw; defaults to sys.stderr so the spinner never mixes into stdout data.

  • enabled (bool | None) โ€“ force the spinner on or off. None (the default) auto-detects, animating only when stream is a TTY.

  • hide_cursor (bool) โ€“ hide the text cursor while spinning and restore it on stop.

  • beep (bool) โ€“ ring the terminal bell once when the spinner stops. It fires only when the spinner was active, so a disabled or redirected spinner stays silent.

Raises:

ValueError โ€“ if style carries a color or attribute that cannot be rendered.

label: str

Text drawn after the spinner glyph.

Reassign it at any time while the spinner runs to reflect the current step; the animation thread reads it afresh on every frame.

property elapsed_time: float

Seconds elapsed since start(), frozen once stop() is called.

Returns 0.0 before the spinner has started.

property shown: bool

Whether the spinner has drawn at least one frame to its stream.

True only once an animation frame was actually rendered. It stays False for a disabled spinner (off a TTY, on a TERM=dumb terminal, or with enabled=False) and for a call that finishes within delay, before the first frame. Reset by start().

Use it to gate output that should mirror the spinnerโ€™s visibility. ok() and fail() write their line unconditionally, so an outcome is still recorded in a pipe or log; guard them with shown when you only want the finisher on screen after a spinner the user actually saw:

with Spinner("Baking bread") as spinner:
    bake()
    if spinner.shown:
        spinner.ok()
start()[source]

Begin animating on a background thread, unless the spinner is disabled.

A disabled spinner (non-TTY stream, or enabled=False) returns at once without spawning a thread or emitting anything (but still records the start time, so a later ok() / fail() can report a duration).

Return type:

None

stop()[source]

Halt the animation and erase the spinner line.

Idempotent and safe to call when the spinner never started. Restores the cursor and clears the line only if the animation actually drew to the terminal.

Return type:

None

echo(message='')[source]

Print message on its own line above the running spinner.

Clickโ€™s click.progressbar() and a bare print both fight the animation: a frame drawn between the cursor returns and the text mangles the line. echo() takes the same draw lock as the animation thread, erases the in-progress frame, writes message followed by a newline, and lets the next tick redraw the spinner underneath. It is safe to call from another thread while the spinner runs.

Output goes to the spinnerโ€™s own stream (stderr by default), so results written to stdout never need it. When the spinner is not animating (disabled, or a non-TTY stream), it degrades to a plain write of message with no control codes.

Return type:

None

ok(symbol=None, *, style=None)[source]

Stop the spinner and leave a persistent success line on screen.

Where stop() erases the spinner, ok() replaces the final frame with symbol followed by the current label (and the elapsed time when timer is set), then keeps that line. symbol defaults to the themed success glyph OK_GLYPH (โœ“), painted with the active themeโ€™s success slot unless style overrides it. Color is stripped under --no-color / NO_COLOR; the glyph stays.

Return type:

None

fail(symbol=None, *, style=None)[source]

Stop the spinner and leave a persistent failure line on screen.

The failure counterpart of ok(), defaulting to KO_GLYPH (โœ˜) painted with the active themeโ€™s error slot.

Return type:

None

click_extra.spinner.trail_glyph(ok)[source]

Return the themed โœ“ or โœ˜ glyph for a trail line or finisher.

The success glyph OK_GLYPH painted with the active themeโ€™s success slot, or the failure glyph KO_GLYPH painted with its error slot.

Return type:

str

click_extra.spinner.trail_line(ok, message)[source]

Format one โœ“/โœ˜ trail line: a status glyph followed by message.

Return type:

str

class click_extra.spinner.OperationTrail(*, label='', unit='', total=0, jobs=1, spinner=None, progress_bar=False, timer=None, clock='elapsed', enabled=None, echo_sequential=True, delay=0.0, stream=None)[source]

Bases: object

A โœ“/โœ˜ progress trail and finisher for a batch of operations.

Where Spinner narrates one long-running call, OperationTrail reports a batch of them: each completed operation leaves a persistent trail_line() on screen, a running done/total tally keeps the batchโ€™s pulse visible, and finish() closes with a persistent summary line. The natural reporting companion of the concurrency primitives run_jobs() and run_lanes(), rendered one of three ways:

  • sequential (jobs <= 1): echo each outcome as it lands, with no aggregate indicator (each operation is free to keep its own per-call Spinner). finish() appends the elapsed time.

  • concurrent (jobs > 1): drive one aggregate Spinner (per-call spinners would collide on the shared stream), buffering outcomes until it first draws, then streaming the rest live above it. Pick the animation from the SPINNERS catalog with spinner=.

  • progress bar (progress_bar=True): drive one aggregate determinate bar carrying the {done}/:total:` tally, with outcomes streaming above it. Serves sequential and concurrent batches alike, and needs a known `total.

All render only on an interactive stream unless enabled forces the matter, so pipes, CI logs and captured test buffers stay clean. The running โœ“ tally is kept as outcomes land (ok_count), so a caller computes no counts of its own.

Thread-safe: mark() may be called from worker threads. Use it as a context manager whenever it may run concurrently, to bound the aggregate spinnerโ€™s life; a purely sequential caller may construct it bare.

from click_extra.execution import run_jobs
from click_extra.spinner import OperationTrail

with OperationTrail(label="Fetching", unit="feeds", total=len(feeds),
                    jobs=jobs) as trail:
    def fetch(feed):
        trail.mark(*pull(feed))  # pull() returns (ok, message).

    list(run_jobs(fetch, feeds, jobs=jobs))
    trail.finish(
        trail.ok_count == len(feeds),
        f"Fetched {trail.ok_count}/{len(feeds)} feeds",
    )

Configure (but do not start) the trail.

Parameters:
  • label (str) โ€“ present-tense verb for the running aggregate indicator ("Fetching"), composed into its {label} {done}/{total} {unit} tally.

  • unit (str) โ€“ the noun counted in the tally ("files", "feeds").

  • total (int) โ€“ how many outcomes are expected, for the done/total count.

  • jobs (int) โ€“ the batchโ€™s worker count; > 1 selects the concurrent rendering (one aggregate spinner), <= 1 the sequential one (plain echoed lines).

  • spinner (SpinnerPreset | None) โ€“ a SpinnerPreset from the SPINNERS catalog (spinner=SPINNERS["moon"]) for the concurrent aggregate spinner. Ignored by the sequential and progress-bar renderings, and mutually exclusive with progress_bar.

  • progress_bar (bool) โ€“ render the aggregate indicator as a determinate click.progressbar() instead of a spinner, for a sequential or concurrent batch alike. Requires a positive total (a bar needs a length) and is mutually exclusive with spinner.

  • timer (bool | Callable[[float], str] | None) โ€“ append each operationโ€™s and the batchโ€™s elapsed time to the trail lines and the finisher. None (the default) follows the CLIโ€™s --time / --no-time flag; True forces timing on with format_duration()โ€™s compact clock, a callable (seconds: float) -> str forces it on with a custom format, and False forces it off. Per-operation times come from a seconds argument to mark(), filled in automatically by an operation() handle.

  • clock (Literal['elapsed', 'eta']) โ€“ whether a running aggregate indicator shows elapsed time ("elapsed", the default: a stopwatch counting up, visible from the start) or remaining time ("eta": an estimate from the batchโ€™s rate, appearing only once an outcome lets it be computed). Both the progress bar and the concurrent spinner honor "eta" (the spinner reuses Clickโ€™s progress-bar estimate, since the trail knows its total). Per-operation and finisher times are always elapsed.

  • enabled (bool | None) โ€“ force the trail on or off. None (the default) auto-detects: the sequential echo renders only on an interactive stream, and the aggregate indicator applies its own TTY gate.

  • echo_sequential (bool) โ€“ whether a sequential batch echoes its outcome lines and finisher at all. Turn it off when the batch has another output that is the real product (a result table) and the trail would be noise; an aggregate indicator is unaffected.

  • delay (float) โ€“ seconds before the aggregate indicator first draws: a fast batch then completes without ever flashing one.

  • stream (IO[str] | None) โ€“ where to render; defaults to sys.stderr so the trail never mixes into stdout data.

Raises:

ValueError โ€“ if progress_bar is set without a positive total, or together with spinner, or if clock is neither "elapsed" nor "eta".

property ok_count: int

How many marked outcomes have succeeded so far.

mark(ok, message, seconds=None)[source]

Record one โœ“/โœ˜ outcome: tally it and render its trail line.

Parameters:

seconds (float | None) โ€“ the operationโ€™s own elapsed time. When timer is on it is formatted and appended to message as (2.3s). An operation() handle fills this in from when it was created; pass it yourself when you already hold a duration.

Return type:

None

finish(ok, summary)[source]

Render the persistent โœ“/โœ˜ {summary} finisher.

With an aggregate indicator, it becomes the indicatorโ€™s kept line (a spinnerโ€™s Spinner.ok() / Spinner.fail() line, or the barโ€™s replacement line); sequential without one, a plain echoed line. The batchโ€™s elapsed time since construction is appended when timer is on (the default).

Return type:

None

operation()[source]

Start a timed operation, returning a handle to record its outcome.

The handle captures the current time; call _Operation.mark() when the work finishes to record its โœ“/โœ˜ outcome with the elapsed time appended (when timer is on). This is how a batch reports per-operation timings under concurrency, where the trail itself never sees when an operation began:

def fetch(feed):
    op = trail.operation()
    ok, message = pull(feed)
    op.mark(ok, message)
Return type:

_Operation

class click_extra.spinner.ProgressOption(param_decls=None, is_flag=True, default=True, is_eager=True, expose_value=False, help='Show progress indicators during long operations. Disabled for non-interactive output (pipes, dumb terminals, CI) and by --accessible.', **kwargs)[source]

Bases: ExtraOption

A pre-configured --progress/--no-progress flag gating spinner display.

Resolves to a single boolean published at ctx.meta[click_extra.context.PROGRESS], which a CLI reads to decide whether to start a Spinner. The default is True; --accessible lowers it to False (via default_map) so a screen reader is never handed a spinning glyph.

Note

Spinner display is intentionally decoupled from color, even though both emit ANSI. A spinner is an interactivity concern, not a color one: it is built from cursor-control codes (hide-cursor, carriage return, clear-line), which the NO_COLOR standard explicitly does not govern โ€“ it โ€œonly signals the userโ€™s intention regarding adding ANSI color to text outputโ€. So --no-color / NO_COLOR strip the spinnerโ€™s colors but never hide it.

This matches how the wider ecosystem treats the two axes as orthogonal: cargo, npm, pip, Rich, indicatif and ora all gate progress on the terminal (and a dedicated --progress/--quiet knob), while NO_COLOR only affects color. Rich uses TERM=dumb โ€“ not NO_COLOR โ€“ as the signal to drop cursor-moving features like progress bars.

The spinner is therefore silenced by two things only, neither of them color:

  • non-interactive output โ€“ a pipe, file, CI log, or TERM=dumb terminal that cannot move the cursor (see Spinner._resolve_enabled);

  • explicit intent โ€“ --no-progress or --accessible.

This option is eager. It no longer reads ctx.color, so its position relative to ColorOption is not load-bearing.

set_progress(ctx, param, value)[source]

Publish whether progress spinners may be shown.

Stores the resolved --progress flag at PROGRESS. Deliberately independent of color: see the ProgressOption note for why a spinner is gated on interactivity (TTY / TERM=dumb) and --accessible, never on --no-color / NO_COLOR.

Return type:

None

click_extra.spinner.progressbar(iterable=None, length=None, label=None, hidden=None, show_eta=None, **kwargs)[source]

Drop-in for click.progressbar() honoring --progress and --time.

Clickโ€™s own progress bar is determinate, the counterpart to the indeterminate Spinner. This thin wrapper gates its visibility on the same PROGRESS flag the spinner uses, so a single --no-progress (or --accessible, which lowers the progress default) silences both, and gates its estimated-time display on --time.

Parameters:
  • hidden (bool | None) โ€“ tri-state. Left at its default None, the bar follows the resolved --progress flag: hidden when the user (or --accessible) turned progress off, shown otherwise. An explicit True or False forces the bar regardless, mirroring how an explicit color= argument overrides ctx.color on click.echo(). With no active context (the bar used outside a Click command) it defaults to shown.

  • show_eta (bool | None) โ€“ tri-state, like hidden. Left at its default None, the estimated-time-remaining display follows the --time / --no-time flag: shown under --time, hidden otherwise (its default, or outside a command). An explicit True or False forces it, keeping a bare barโ€™s timing in step with an OperationTrailโ€™s timer. Clickโ€™s own default is True.

Return type:

ProgressBar[TypeVar(V)]

Note

The --progress flag gates visibility and --time the ETA. Color is already handled upstream: Click renders the bar through click.echo(), whose color=None resolves against ctx.color, so --no-color / NO_COLOR strip the barโ€™s ANSI without any work from this wrapper.

        classDiagram
  tuple <|-- SpinnerPreset
    

The bundled catalog of terminal spinner presets.

Ported from cli-spinners, with frame intervals converted from milliseconds to seconds.

click_extra.spinner_presets.ASCII_SPINNER_FRAMES: Final = ('-', '\\', '|', '/')

Plain ASCII animation frames, for terminals or fonts lacking Unicode glyphs.

click_extra.spinner_presets.SPINNER_FRAMES: Final = ('โ ‹', 'โ ™', 'โ น', 'โ ธ', 'โ ผ', 'โ ด', 'โ ฆ', 'โ ง', 'โ ‡', 'โ ')

Default animation frames: the ubiquitous Braille-dots spinner.

Ten frames give a smooth rotation in any UTF-8 terminal. Fall back to ASCII_SPINNER_FRAMES where Braille glyphs are unavailable.

class click_extra.spinner_presets.SpinnerPreset(frames: tuple[str, ...], interval: float)[source]

Bases: NamedTuple

A named spinner animation: its frames and the interval they look best at.

The SPINNERS catalog is ported from cli-spinners, with intervals converted from milliseconds to seconds. Pass one to Spinner via its spinner argument.

Create new instance of SpinnerPreset(frames, interval)

frames: tuple[str, ...]

The animation frames, cycled in order.

interval: float

Seconds between two frames, tuned per spinner upstream.

click_extra.spinner_presets.SPINNERS: Final = {'aesthetic': (('โ–ฐโ–ฑโ–ฑโ–ฑโ–ฑโ–ฑโ–ฑ', 'โ–ฐโ–ฐโ–ฑโ–ฑโ–ฑโ–ฑโ–ฑ', 'โ–ฐโ–ฐโ–ฐโ–ฑโ–ฑโ–ฑโ–ฑ', 'โ–ฐโ–ฐโ–ฐโ–ฐโ–ฑโ–ฑโ–ฑ', 'โ–ฐโ–ฐโ–ฐโ–ฐโ–ฐโ–ฑโ–ฑ', 'โ–ฐโ–ฐโ–ฐโ–ฐโ–ฐโ–ฐโ–ฑ', 'โ–ฐโ–ฐโ–ฐโ–ฐโ–ฐโ–ฐโ–ฐ', 'โ–ฐโ–ฑโ–ฑโ–ฑโ–ฑโ–ฑโ–ฑ'), 0.08), 'arc': (('โ—œ', 'โ— ', 'โ—', 'โ—ž', 'โ—ก', 'โ—Ÿ'), 0.1), 'arrow': (('โ†', 'โ†–', 'โ†‘', 'โ†—', 'โ†’', 'โ†˜', 'โ†“', 'โ†™'), 0.1), 'arrow2': (('โฌ†๏ธ ', 'โ†—๏ธ ', 'โžก๏ธ ', 'โ†˜๏ธ ', 'โฌ‡๏ธ ', 'โ†™๏ธ ', 'โฌ…๏ธ ', 'โ†–๏ธ '), 0.08), 'arrow3': (('โ–นโ–นโ–นโ–นโ–น', 'โ–ธโ–นโ–นโ–นโ–น', 'โ–นโ–ธโ–นโ–นโ–น', 'โ–นโ–นโ–ธโ–นโ–น', 'โ–นโ–นโ–นโ–ธโ–น', 'โ–นโ–นโ–นโ–นโ–ธ'), 0.12), 'balloon': ((' ', '.', 'o', 'O', '@', '*', ' '), 0.14), 'balloon2': (('.', 'o', 'O', 'ยฐ', 'O', 'o', '.'), 0.12), 'betaWave': (('ฯฮฒฮฒฮฒฮฒฮฒฮฒ', 'ฮฒฯฮฒฮฒฮฒฮฒฮฒ', 'ฮฒฮฒฯฮฒฮฒฮฒฮฒ', 'ฮฒฮฒฮฒฯฮฒฮฒฮฒ', 'ฮฒฮฒฮฒฮฒฯฮฒฮฒ', 'ฮฒฮฒฮฒฮฒฮฒฯฮฒ', 'ฮฒฮฒฮฒฮฒฮฒฮฒฯ'), 0.08), 'binary': (('010010', '001100', '100101', '111010', '111101', '010111', '101011', '111000', '110011', '110101'), 0.08), 'bluePulse': (('๐Ÿ”น ', '๐Ÿ”ท ', '๐Ÿ”ต ', '๐Ÿ”ต ', '๐Ÿ”ท '), 0.1), 'bounce': (('โ ', 'โ ‚', 'โ „', 'โ ‚'), 0.12), 'bouncingBall': (('( โ—    )', '(  โ—   )', '(   โ—  )', '(    โ— )', '(     โ—)', '(    โ— )', '(   โ—  )', '(  โ—   )', '( โ—    )', '(โ—     )'), 0.08), 'bouncingBar': (('[    ]', '[=   ]', '[==  ]', '[=== ]', '[====]', '[ ===]', '[  ==]', '[   =]', '[    ]', '[   =]', '[  ==]', '[ ===]', '[====]', '[=== ]', '[==  ]', '[=   ]'), 0.08), 'boxBounce': (('โ––', 'โ–˜', 'โ–', 'โ–—'), 0.12), 'boxBounce2': (('โ–Œ', 'โ–€', 'โ–', 'โ–„'), 0.1), 'christmas': (('๐ŸŒฒ', '๐ŸŽ„'), 0.4), 'circle': (('โ—ก', 'โŠ™', 'โ— '), 0.12), 'circleHalves': (('โ—', 'โ—“', 'โ—‘', 'โ—’'), 0.05), 'circleQuarters': (('โ—ด', 'โ—ท', 'โ—ถ', 'โ—ต'), 0.12), 'clock': (('๐Ÿ•› ', '๐Ÿ• ', '๐Ÿ•‘ ', '๐Ÿ•’ ', '๐Ÿ•“ ', '๐Ÿ•” ', '๐Ÿ•• ', '๐Ÿ•– ', '๐Ÿ•— ', '๐Ÿ•˜ ', '๐Ÿ•™ ', '๐Ÿ•š '), 0.1), 'dots': (('โ ‹', 'โ ™', 'โ น', 'โ ธ', 'โ ผ', 'โ ด', 'โ ฆ', 'โ ง', 'โ ‡', 'โ '), 0.08), 'dots10': (('โข„', 'โข‚', 'โข', 'โก', 'โกˆ', 'โก', 'โก '), 0.08), 'dots11': (('โ ', 'โ ‚', 'โ „', 'โก€', 'โข€', 'โ  ', 'โ ', 'โ ˆ'), 0.1), 'dots12': (('โข€โ €', 'โก€โ €', 'โ „โ €', 'โข‚โ €', 'โก‚โ €', 'โ …โ €', 'โขƒโ €', 'โกƒโ €', 'โ โ €', 'โข‹โ €', 'โก‹โ €', 'โ โ ', 'โข‹โ ', 'โก‹โ ', 'โ โ ‰', 'โ ‹โ ‰', 'โ ‹โ ‰', 'โ ‰โ ™', 'โ ‰โ ™', 'โ ‰โ ฉ', 'โ ˆโข™', 'โ ˆโก™', 'โขˆโ ฉ', 'โก€โข™', 'โ „โก™', 'โข‚โ ฉ', 'โก‚โข˜', 'โ …โก˜', 'โขƒโ จ', 'โกƒโข', 'โ โก', 'โข‹โ  ', 'โก‹โข€', 'โ โก', 'โข‹โ ', 'โก‹โ ', 'โ โ ‰', 'โ ‹โ ‰', 'โ ‹โ ‰', 'โ ‰โ ™', 'โ ‰โ ™', 'โ ‰โ ฉ', 'โ ˆโข™', 'โ ˆโก™', 'โ ˆโ ฉ', 'โ €โข™', 'โ €โก™', 'โ €โ ฉ', 'โ €โข˜', 'โ €โก˜', 'โ €โ จ', 'โ €โข', 'โ €โก', 'โ €โ  ', 'โ €โข€', 'โ €โก€'), 0.08), 'dots13': (('โฃผ', 'โฃน', 'โขป', 'โ ฟ', 'โกŸ', 'โฃ', 'โฃง', 'โฃถ'), 0.08), 'dots14': (('โ ‰โ ‰', 'โ ˆโ ™', 'โ €โ น', 'โ €โขธ', 'โ €โฃฐ', 'โข€โฃ ', 'โฃ€โฃ€', 'โฃ„โก€', 'โฃ†โ €', 'โก‡โ €', 'โ โ €', 'โ ‹โ '), 0.08), 'dots2': (('โฃพ', 'โฃฝ', 'โฃป', 'โขฟ', 'โกฟ', 'โฃŸ', 'โฃฏ', 'โฃท'), 0.08), 'dots3': (('โ ‹', 'โ ™', 'โ š', 'โ ž', 'โ –', 'โ ฆ', 'โ ด', 'โ ฒ', 'โ ณ', 'โ “'), 0.08), 'dots4': (('โ „', 'โ †', 'โ ‡', 'โ ‹', 'โ ™', 'โ ธ', 'โ ฐ', 'โ  ', 'โ ฐ', 'โ ธ', 'โ ™', 'โ ‹', 'โ ‡', 'โ †'), 0.08), 'dots5': (('โ ‹', 'โ ™', 'โ š', 'โ ’', 'โ ‚', 'โ ‚', 'โ ’', 'โ ฒ', 'โ ด', 'โ ฆ', 'โ –', 'โ ’', 'โ ', 'โ ', 'โ ’', 'โ “', 'โ ‹'), 0.08), 'dots6': (('โ ', 'โ ‰', 'โ ™', 'โ š', 'โ ’', 'โ ‚', 'โ ‚', 'โ ’', 'โ ฒ', 'โ ด', 'โ ค', 'โ „', 'โ „', 'โ ค', 'โ ด', 'โ ฒ', 'โ ’', 'โ ‚', 'โ ‚', 'โ ’', 'โ š', 'โ ™', 'โ ‰', 'โ '), 0.08), 'dots7': (('โ ˆ', 'โ ‰', 'โ ‹', 'โ “', 'โ ’', 'โ ', 'โ ', 'โ ’', 'โ –', 'โ ฆ', 'โ ค', 'โ  ', 'โ  ', 'โ ค', 'โ ฆ', 'โ –', 'โ ’', 'โ ', 'โ ', 'โ ’', 'โ “', 'โ ‹', 'โ ‰', 'โ ˆ'), 0.08), 'dots8': (('โ ', 'โ ', 'โ ‰', 'โ ™', 'โ š', 'โ ’', 'โ ‚', 'โ ‚', 'โ ’', 'โ ฒ', 'โ ด', 'โ ค', 'โ „', 'โ „', 'โ ค', 'โ  ', 'โ  ', 'โ ค', 'โ ฆ', 'โ –', 'โ ’', 'โ ', 'โ ', 'โ ’', 'โ “', 'โ ‹', 'โ ‰', 'โ ˆ', 'โ ˆ'), 0.08), 'dots8Bit': (('โ €', 'โ ', 'โ ‚', 'โ ƒ', 'โ „', 'โ …', 'โ †', 'โ ‡', 'โก€', 'โก', 'โก‚', 'โกƒ', 'โก„', 'โก…', 'โก†', 'โก‡', 'โ ˆ', 'โ ‰', 'โ Š', 'โ ‹', 'โ Œ', 'โ ', 'โ Ž', 'โ ', 'โกˆ', 'โก‰', 'โกŠ', 'โก‹', 'โกŒ', 'โก', 'โกŽ', 'โก', 'โ ', 'โ ‘', 'โ ’', 'โ “', 'โ ”', 'โ •', 'โ –', 'โ —', 'โก', 'โก‘', 'โก’', 'โก“', 'โก”', 'โก•', 'โก–', 'โก—', 'โ ˜', 'โ ™', 'โ š', 'โ ›', 'โ œ', 'โ ', 'โ ž', 'โ Ÿ', 'โก˜', 'โก™', 'โกš', 'โก›', 'โกœ', 'โก', 'โกž', 'โกŸ', 'โ  ', 'โ ก', 'โ ข', 'โ ฃ', 'โ ค', 'โ ฅ', 'โ ฆ', 'โ ง', 'โก ', 'โกก', 'โกข', 'โกฃ', 'โกค', 'โกฅ', 'โกฆ', 'โกง', 'โ จ', 'โ ฉ', 'โ ช', 'โ ซ', 'โ ฌ', 'โ ญ', 'โ ฎ', 'โ ฏ', 'โกจ', 'โกฉ', 'โกช', 'โกซ', 'โกฌ', 'โกญ', 'โกฎ', 'โกฏ', 'โ ฐ', 'โ ฑ', 'โ ฒ', 'โ ณ', 'โ ด', 'โ ต', 'โ ถ', 'โ ท', 'โกฐ', 'โกฑ', 'โกฒ', 'โกณ', 'โกด', 'โกต', 'โกถ', 'โกท', 'โ ธ', 'โ น', 'โ บ', 'โ ป', 'โ ผ', 'โ ฝ', 'โ พ', 'โ ฟ', 'โกธ', 'โกน', 'โกบ', 'โกป', 'โกผ', 'โกฝ', 'โกพ', 'โกฟ', 'โข€', 'โข', 'โข‚', 'โขƒ', 'โข„', 'โข…', 'โข†', 'โข‡', 'โฃ€', 'โฃ', 'โฃ‚', 'โฃƒ', 'โฃ„', 'โฃ…', 'โฃ†', 'โฃ‡', 'โขˆ', 'โข‰', 'โขŠ', 'โข‹', 'โขŒ', 'โข', 'โขŽ', 'โข', 'โฃˆ', 'โฃ‰', 'โฃŠ', 'โฃ‹', 'โฃŒ', 'โฃ', 'โฃŽ', 'โฃ', 'โข', 'โข‘', 'โข’', 'โข“', 'โข”', 'โข•', 'โข–', 'โข—', 'โฃ', 'โฃ‘', 'โฃ’', 'โฃ“', 'โฃ”', 'โฃ•', 'โฃ–', 'โฃ—', 'โข˜', 'โข™', 'โขš', 'โข›', 'โขœ', 'โข', 'โขž', 'โขŸ', 'โฃ˜', 'โฃ™', 'โฃš', 'โฃ›', 'โฃœ', 'โฃ', 'โฃž', 'โฃŸ', 'โข ', 'โขก', 'โขข', 'โขฃ', 'โขค', 'โขฅ', 'โขฆ', 'โขง', 'โฃ ', 'โฃก', 'โฃข', 'โฃฃ', 'โฃค', 'โฃฅ', 'โฃฆ', 'โฃง', 'โขจ', 'โขฉ', 'โขช', 'โขซ', 'โขฌ', 'โขญ', 'โขฎ', 'โขฏ', 'โฃจ', 'โฃฉ', 'โฃช', 'โฃซ', 'โฃฌ', 'โฃญ', 'โฃฎ', 'โฃฏ', 'โขฐ', 'โขฑ', 'โขฒ', 'โขณ', 'โขด', 'โขต', 'โขถ', 'โขท', 'โฃฐ', 'โฃฑ', 'โฃฒ', 'โฃณ', 'โฃด', 'โฃต', 'โฃถ', 'โฃท', 'โขธ', 'โขน', 'โขบ', 'โขป', 'โขผ', 'โขฝ', 'โขพ', 'โขฟ', 'โฃธ', 'โฃน', 'โฃบ', 'โฃป', 'โฃผ', 'โฃฝ', 'โฃพ', 'โฃฟ'), 0.08), 'dots9': (('โขน', 'โขบ', 'โขผ', 'โฃธ', 'โฃ‡', 'โกง', 'โก—', 'โก'), 0.08), 'dotsCircle': (('โขŽ ', 'โ Žโ ', 'โ Šโ ‘', 'โ ˆโ ฑ', ' โกฑ', 'โข€โกฐ', 'โข„โก ', 'โข†โก€'), 0.08), 'dqpb': (('d', 'q', 'p', 'b'), 0.1), 'dwarfFortress': ((' โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', 'โ˜บโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', 'โ˜บโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', 'โ˜บโ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', 'โ˜บโ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', 'โ˜บโ–’โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', 'โ˜บโ–’โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', 'โ˜บโ–‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', 'โ˜บโ–‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', 'โ˜บ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', ' โ˜บโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', ' โ˜บโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', ' โ˜บโ–“โ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', ' โ˜บโ–“โ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', ' โ˜บโ–’โ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', ' โ˜บโ–’โ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', ' โ˜บโ–‘โ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', ' โ˜บโ–‘โ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', ' โ˜บ โ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', '  โ˜บโ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', '  โ˜บโ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', '  โ˜บโ–“โ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', '  โ˜บโ–“โ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', '  โ˜บโ–’โ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', '  โ˜บโ–’โ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', '  โ˜บโ–‘โ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', '  โ˜บโ–‘โ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', '  โ˜บ โ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', '   โ˜บโ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', '   โ˜บโ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', '   โ˜บโ–“โ–ˆโ–ˆยฃยฃยฃ  ', '   โ˜บโ–“โ–ˆโ–ˆยฃยฃยฃ  ', '   โ˜บโ–’โ–ˆโ–ˆยฃยฃยฃ  ', '   โ˜บโ–’โ–ˆโ–ˆยฃยฃยฃ  ', '   โ˜บโ–‘โ–ˆโ–ˆยฃยฃยฃ  ', '   โ˜บโ–‘โ–ˆโ–ˆยฃยฃยฃ  ', '   โ˜บ โ–ˆโ–ˆยฃยฃยฃ  ', '    โ˜บโ–ˆโ–ˆยฃยฃยฃ  ', '    โ˜บโ–ˆโ–ˆยฃยฃยฃ  ', '    โ˜บโ–“โ–ˆยฃยฃยฃ  ', '    โ˜บโ–“โ–ˆยฃยฃยฃ  ', '    โ˜บโ–’โ–ˆยฃยฃยฃ  ', '    โ˜บโ–’โ–ˆยฃยฃยฃ  ', '    โ˜บโ–‘โ–ˆยฃยฃยฃ  ', '    โ˜บโ–‘โ–ˆยฃยฃยฃ  ', '    โ˜บ โ–ˆยฃยฃยฃ  ', '     โ˜บโ–ˆยฃยฃยฃ  ', '     โ˜บโ–ˆยฃยฃยฃ  ', '     โ˜บโ–“ยฃยฃยฃ  ', '     โ˜บโ–“ยฃยฃยฃ  ', '     โ˜บโ–’ยฃยฃยฃ  ', '     โ˜บโ–’ยฃยฃยฃ  ', '     โ˜บโ–‘ยฃยฃยฃ  ', '     โ˜บโ–‘ยฃยฃยฃ  ', '     โ˜บ ยฃยฃยฃ  ', '      โ˜บยฃยฃยฃ  ', '      โ˜บยฃยฃยฃ  ', '      โ˜บโ–“ยฃยฃ  ', '      โ˜บโ–“ยฃยฃ  ', '      โ˜บโ–’ยฃยฃ  ', '      โ˜บโ–’ยฃยฃ  ', '      โ˜บโ–‘ยฃยฃ  ', '      โ˜บโ–‘ยฃยฃ  ', '      โ˜บ ยฃยฃ  ', '       โ˜บยฃยฃ  ', '       โ˜บยฃยฃ  ', '       โ˜บโ–“ยฃ  ', '       โ˜บโ–“ยฃ  ', '       โ˜บโ–’ยฃ  ', '       โ˜บโ–’ยฃ  ', '       โ˜บโ–‘ยฃ  ', '       โ˜บโ–‘ยฃ  ', '       โ˜บ ยฃ  ', '        โ˜บยฃ  ', '        โ˜บยฃ  ', '        โ˜บโ–“  ', '        โ˜บโ–“  ', '        โ˜บโ–’  ', '        โ˜บโ–’  ', '        โ˜บโ–‘  ', '        โ˜บโ–‘  ', '        โ˜บ   ', '        โ˜บ  &', '        โ˜บ โ˜ผ&', '       โ˜บ โ˜ผ &', '       โ˜บโ˜ผ  &', '      โ˜บโ˜ผ  & ', '      โ€ผ   & ', '     โ˜บ   &  ', '    โ€ผ    &  ', '   โ˜บ    &   ', '  โ€ผ     &   ', ' โ˜บ     &    ', 'โ€ผ      &    ', '      &     ', '      &     ', '     &   โ–‘  ', '     &   โ–’  ', '    &    โ–“  ', '    &    ยฃ  ', '   &    โ–‘ยฃ  ', '   &    โ–’ยฃ  ', '  &     โ–“ยฃ  ', '  &     ยฃยฃ  ', ' &     โ–‘ยฃยฃ  ', ' &     โ–’ยฃยฃ  ', '&      โ–“ยฃยฃ  ', '&      ยฃยฃยฃ  ', '      โ–‘ยฃยฃยฃ  ', '      โ–’ยฃยฃยฃ  ', '      โ–“ยฃยฃยฃ  ', '      โ–ˆยฃยฃยฃ  ', '     โ–‘โ–ˆยฃยฃยฃ  ', '     โ–’โ–ˆยฃยฃยฃ  ', '     โ–“โ–ˆยฃยฃยฃ  ', '     โ–ˆโ–ˆยฃยฃยฃ  ', '    โ–‘โ–ˆโ–ˆยฃยฃยฃ  ', '    โ–’โ–ˆโ–ˆยฃยฃยฃ  ', '    โ–“โ–ˆโ–ˆยฃยฃยฃ  ', '    โ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', '   โ–‘โ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', '   โ–’โ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', '   โ–“โ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', '   โ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', '  โ–‘โ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', '  โ–’โ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', '  โ–“โ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', '  โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', ' โ–‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', ' โ–’โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', ' โ–“โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', ' โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ  ', ' โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆยฃยฃยฃ  '), 0.08), 'earth': (('๐ŸŒ ', '๐ŸŒŽ ', '๐ŸŒ '), 0.18), 'fingerDance': (('๐Ÿค˜ ', '๐ŸคŸ ', '๐Ÿ–– ', 'โœ‹ ', '๐Ÿคš ', '๐Ÿ‘† '), 0.16), 'fish': (('~~~~~~~~~~~~~~~~~~~~', '> ~~~~~~~~~~~~~~~~~~', 'ยบ> ~~~~~~~~~~~~~~~~~', '(ยบ> ~~~~~~~~~~~~~~~~', '((ยบ> ~~~~~~~~~~~~~~~', '<((ยบ> ~~~~~~~~~~~~~~', '><((ยบ> ~~~~~~~~~~~~~', ' ><((ยบ> ~~~~~~~~~~~~', '~ ><((ยบ> ~~~~~~~~~~~', '~~ <>((ยบ> ~~~~~~~~~~', '~~~ ><((ยบ> ~~~~~~~~~', '~~~~ <>((ยบ> ~~~~~~~~', '~~~~~ ><((ยบ> ~~~~~~~', '~~~~~~ <>((ยบ> ~~~~~~', '~~~~~~~ ><((ยบ> ~~~~~', '~~~~~~~~ <>((ยบ> ~~~~', '~~~~~~~~~ ><((ยบ> ~~~', '~~~~~~~~~~ <>((ยบ> ~~', '~~~~~~~~~~~ ><((ยบ> ~', '~~~~~~~~~~~~ <>((ยบ> ', '~~~~~~~~~~~~~ ><((ยบ>', '~~~~~~~~~~~~~~ <>((ยบ', '~~~~~~~~~~~~~~~ ><((', '~~~~~~~~~~~~~~~~ <>(', '~~~~~~~~~~~~~~~~~ ><', '~~~~~~~~~~~~~~~~~~ <', '~~~~~~~~~~~~~~~~~~~~'), 0.08), 'fistBump': (('๐Ÿคœ\u3000\u3000\u3000\u3000๐Ÿค› ', '๐Ÿคœ\u3000\u3000\u3000\u3000๐Ÿค› ', '๐Ÿคœ\u3000\u3000\u3000\u3000๐Ÿค› ', '\u3000๐Ÿคœ\u3000\u3000๐Ÿค›\u3000 ', '\u3000\u3000๐Ÿคœ๐Ÿค›\u3000\u3000 ', '\u3000๐Ÿคœโœจ๐Ÿค›\u3000\u3000 ', '๐Ÿคœ\u3000โœจ\u3000๐Ÿค›\u3000 '), 0.08), 'flip': (('_', '_', '_', '-', '`', '`', "'", 'ยด', '-', '_', '_', '_'), 0.07), 'grenade': (('ุŒ  ', 'โ€ฒ  ', ' ยด ', ' โ€พ ', '  โธŒ', '  โธŠ', '  |', '  โŽ', '  โ•', ' เทด ', '  โ“', '   ', '   ', '   '), 0.08), 'growHorizontal': (('โ–', 'โ–Ž', 'โ–', 'โ–Œ', 'โ–‹', 'โ–Š', 'โ–‰', 'โ–Š', 'โ–‹', 'โ–Œ', 'โ–', 'โ–Ž'), 0.12), 'growVertical': (('โ–', 'โ–ƒ', 'โ–„', 'โ–…', 'โ–†', 'โ–‡', 'โ–†', 'โ–…', 'โ–„', 'โ–ƒ'), 0.12), 'hamburger': (('โ˜ฑ', 'โ˜ฒ', 'โ˜ด'), 0.1), 'hearts': (('๐Ÿ’› ', '๐Ÿ’™ ', '๐Ÿ’œ ', '๐Ÿ’š ', '๐Ÿ’— '), 0.1), 'layer': (('-', '=', 'โ‰ก'), 0.15), 'line': (('-', '\\', '|', '/'), 0.13), 'line2': (('โ ‚', '-', 'โ€“', 'โ€”', 'โ€“', '-'), 0.1), 'material': (('โ–ˆโ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–', 'โ–ˆโ–ˆโ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–', 'โ–ˆโ–ˆโ–ˆโ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–', 'โ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–', 'โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–', 'โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–', 'โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–', 'โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–', 'โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–', 'โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–', 'โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–โ–โ–โ–โ–โ–โ–โ–', 'โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–โ–โ–โ–โ–โ–โ–', 'โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–โ–โ–โ–โ–', 'โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–โ–โ–โ–', 'โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–โ–โ–โ–', 'โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–โ–โ–', 'โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–โ–โ–', 'โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–โ–โ–', 'โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–โ–', 'โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–', 'โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–', 'โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–', 'โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–', 'โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–', 'โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–', 'โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–', 'โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ', 'โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ', 'โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ', 'โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ', 'โ–ˆโ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆ', 'โ–ˆโ–ˆโ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆ', 'โ–ˆโ–ˆโ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆ', 'โ–ˆโ–ˆโ–ˆโ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆ', 'โ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆ', 'โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆ', 'โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆ', 'โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆ', 'โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–', 'โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–', 'โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–', 'โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–', 'โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–', 'โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–โ–โ–โ–โ–โ–โ–', 'โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–โ–โ–โ–โ–โ–', 'โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–โ–โ–โ–โ–โ–', 'โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–โ–โ–โ–', 'โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–โ–โ–โ–', 'โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–โ–โ–', 'โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–โ–โ–', 'โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–โ–', 'โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–', 'โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–', 'โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–โ–', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–โ–', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆ', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆ', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆโ–ˆ', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆ', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆโ–ˆ', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆ', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆ', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆโ–ˆ', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆ', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆ', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–ˆ', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–', 'โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–โ–'), 0.017), 'mindblown': (('๐Ÿ˜ ', '๐Ÿ˜ ', '๐Ÿ˜ฎ ', '๐Ÿ˜ฎ ', '๐Ÿ˜ฆ ', '๐Ÿ˜ฆ ', '๐Ÿ˜ง ', '๐Ÿ˜ง ', '๐Ÿคฏ ', '๐Ÿ’ฅ ', 'โœจ ', '\u3000 ', '\u3000 ', '\u3000 '), 0.16), 'monkey': (('๐Ÿ™ˆ ', '๐Ÿ™ˆ ', '๐Ÿ™‰ ', '๐Ÿ™Š '), 0.3), 'moon': (('๐ŸŒ‘ ', '๐ŸŒ’ ', '๐ŸŒ“ ', '๐ŸŒ” ', '๐ŸŒ• ', '๐ŸŒ– ', '๐ŸŒ— ', '๐ŸŒ˜ '), 0.08), 'noise': (('โ–“', 'โ–’', 'โ–‘'), 0.1), 'orangeBluePulse': (('๐Ÿ”ธ ', '๐Ÿ”ถ ', '๐ŸŸ  ', '๐ŸŸ  ', '๐Ÿ”ถ ', '๐Ÿ”น ', '๐Ÿ”ท ', '๐Ÿ”ต ', '๐Ÿ”ต ', '๐Ÿ”ท '), 0.1), 'orangePulse': (('๐Ÿ”ธ ', '๐Ÿ”ถ ', '๐ŸŸ  ', '๐ŸŸ  ', '๐Ÿ”ถ '), 0.1), 'pipe': (('โ”ค', 'โ”˜', 'โ”ด', 'โ””', 'โ”œ', 'โ”Œ', 'โ”ฌ', 'โ”'), 0.1), 'point': (('โˆ™โˆ™โˆ™', 'โ—โˆ™โˆ™', 'โˆ™โ—โˆ™', 'โˆ™โˆ™โ—', 'โˆ™โˆ™โˆ™'), 0.125), 'pong': (('โ–โ ‚       โ–Œ', 'โ–โ ˆ       โ–Œ', 'โ– โ ‚      โ–Œ', 'โ– โ        โ–Œ', 'โ–  โก€     โ–Œ', 'โ–  โ       โ–Œ', 'โ–   โ ‚    โ–Œ', 'โ–   โ ˆ    โ–Œ', 'โ–    โ ‚   โ–Œ', 'โ–    โ     โ–Œ', 'โ–     โก€  โ–Œ', 'โ–     โ    โ–Œ', 'โ–      โ ‚ โ–Œ', 'โ–      โ ˆ โ–Œ', 'โ–       โ ‚โ–Œ', 'โ–       โ  โ–Œ', 'โ–       โก€โ–Œ', 'โ–      โ   โ–Œ', 'โ–      โ ‚ โ–Œ', 'โ–     โ ˆ  โ–Œ', 'โ–     โ ‚  โ–Œ', 'โ–    โ     โ–Œ', 'โ–    โก€   โ–Œ', 'โ–   โ      โ–Œ', 'โ–   โ ‚    โ–Œ', 'โ–  โ ˆ     โ–Œ', 'โ–  โ ‚     โ–Œ', 'โ– โ        โ–Œ', 'โ– โก€      โ–Œ', 'โ–โ         โ–Œ'), 0.08), 'rollingLine': (('/  ', ' - ', ' \\ ', '  |', '  |', ' \\ ', ' - ', '/  '), 0.08), 'runner': (('๐Ÿšถ ', '๐Ÿƒ '), 0.14), 'sand': (('โ ', 'โ ‚', 'โ „', 'โก€', 'โกˆ', 'โก', 'โก ', 'โฃ€', 'โฃ', 'โฃ‚', 'โฃ„', 'โฃŒ', 'โฃ”', 'โฃค', 'โฃฅ', 'โฃฆ', 'โฃฎ', 'โฃถ', 'โฃท', 'โฃฟ', 'โกฟ', 'โ ฟ', 'โขŸ', 'โ Ÿ', 'โก›', 'โ ›', 'โ ซ', 'โข‹', 'โ ‹', 'โ ', 'โก‰', 'โ ‰', 'โ ‘', 'โ ก', 'โข'), 0.08), 'shark': (('โ–|\\____________โ–Œ', 'โ–_|\\___________โ–Œ', 'โ–__|\\__________โ–Œ', 'โ–___|\\_________โ–Œ', 'โ–____|\\________โ–Œ', 'โ–_____|\\_______โ–Œ', 'โ–______|\\______โ–Œ', 'โ–_______|\\_____โ–Œ', 'โ–________|\\____โ–Œ', 'โ–_________|\\___โ–Œ', 'โ–__________|\\__โ–Œ', 'โ–___________|\\_โ–Œ', 'โ–____________|\\โ–Œ', 'โ–____________/|โ–Œ', 'โ–___________/|_โ–Œ', 'โ–__________/|__โ–Œ', 'โ–_________/|___โ–Œ', 'โ–________/|____โ–Œ', 'โ–_______/|_____โ–Œ', 'โ–______/|______โ–Œ', 'โ–_____/|_______โ–Œ', 'โ–____/|________โ–Œ', 'โ–___/|_________โ–Œ', 'โ–__/|__________โ–Œ', 'โ–_/|___________โ–Œ', 'โ–/|____________โ–Œ'), 0.12), 'simpleDots': (('.  ', '.. ', '...', '   '), 0.4), 'simpleDotsScrolling': (('.  ', '.. ', '...', ' ..', '  .', '   '), 0.2), 'smiley': (('๐Ÿ˜„ ', '๐Ÿ˜ '), 0.2), 'soccerHeader': ((' ๐Ÿง‘โšฝ๏ธ       ๐Ÿง‘ ', '๐Ÿง‘  โšฝ๏ธ      ๐Ÿง‘ ', '๐Ÿง‘   โšฝ๏ธ     ๐Ÿง‘ ', '๐Ÿง‘    โšฝ๏ธ    ๐Ÿง‘ ', '๐Ÿง‘     โšฝ๏ธ   ๐Ÿง‘ ', '๐Ÿง‘      โšฝ๏ธ  ๐Ÿง‘ ', '๐Ÿง‘       โšฝ๏ธ๐Ÿง‘  ', '๐Ÿง‘      โšฝ๏ธ  ๐Ÿง‘ ', '๐Ÿง‘     โšฝ๏ธ   ๐Ÿง‘ ', '๐Ÿง‘    โšฝ๏ธ    ๐Ÿง‘ ', '๐Ÿง‘   โšฝ๏ธ     ๐Ÿง‘ ', '๐Ÿง‘  โšฝ๏ธ      ๐Ÿง‘ '), 0.08), 'speaker': (('๐Ÿ”ˆ ', '๐Ÿ”‰ ', '๐Ÿ”Š ', '๐Ÿ”‰ '), 0.16), 'squareCorners': (('โ—ฐ', 'โ—ณ', 'โ—ฒ', 'โ—ฑ'), 0.18), 'squish': (('โ•ซ', 'โ•ช'), 0.1), 'star': (('โœถ', 'โœธ', 'โœน', 'โœบ', 'โœน', 'โœท'), 0.07), 'star2': (('+', 'x', '*'), 0.08), 'timeTravel': (('๐Ÿ•› ', '๐Ÿ•š ', '๐Ÿ•™ ', '๐Ÿ•˜ ', '๐Ÿ•— ', '๐Ÿ•– ', '๐Ÿ•• ', '๐Ÿ•” ', '๐Ÿ•“ ', '๐Ÿ•’ ', '๐Ÿ•‘ ', '๐Ÿ• '), 0.1), 'toggle': (('โŠถ', 'โŠท'), 0.25), 'toggle10': (('ใŠ‚', 'ใŠ€', 'ใЁ'), 0.1), 'toggle11': (('โง‡', 'โง†'), 0.05), 'toggle12': (('โ˜—', 'โ˜–'), 0.12), 'toggle13': (('=', '*', '-'), 0.08), 'toggle2': (('โ–ซ', 'โ–ช'), 0.08), 'toggle3': (('โ–ก', 'โ– '), 0.12), 'toggle4': (('โ– ', 'โ–ก', 'โ–ช', 'โ–ซ'), 0.1), 'toggle5': (('โ–ฎ', 'โ–ฏ'), 0.1), 'toggle6': (('แ€', 'แ€'), 0.3), 'toggle7': (('โฆพ', 'โฆฟ'), 0.08), 'toggle8': (('โ—', 'โ—Œ'), 0.1), 'toggle9': (('โ—‰', 'โ—Ž'), 0.1), 'triangle': (('โ—ข', 'โ—ฃ', 'โ—ค', 'โ—ฅ'), 0.05), 'weather': (('โ˜€๏ธ ', 'โ˜€๏ธ ', 'โ˜€๏ธ ', '๐ŸŒค ', 'โ›…๏ธ ', '๐ŸŒฅ ', 'โ˜๏ธ ', '๐ŸŒง ', '๐ŸŒจ ', '๐ŸŒง ', '๐ŸŒจ ', '๐ŸŒง ', '๐ŸŒจ ', 'โ›ˆ ', '๐ŸŒจ ', '๐ŸŒง ', '๐ŸŒจ ', 'โ˜๏ธ ', '๐ŸŒฅ ', 'โ›…๏ธ ', '๐ŸŒค ', 'โ˜€๏ธ ', 'โ˜€๏ธ '), 0.1)}

Named spinner animations ported from cli-spinners, keyed by name.

Each value is a SpinnerPreset bundling frames and a tuned interval. Select one with Spinnerโ€™s spinner argument:

from click_extra import Spinner, SPINNERS

with Spinner("Brewing tea", spinner=SPINNERS["moon"]):
    ...

Unlike the upstream \b-based renderers, Spinner redraws the whole line, so the multi-character animations (bouncingBar, pong, shark, โ€ฆ) render correctly here.