unicode-animations 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- unicode_animations-0.1.0/.claude/settings.local.json +14 -0
- unicode_animations-0.1.0/.gitignore +9 -0
- unicode_animations-0.1.0/PKG-INFO +12 -0
- unicode_animations-0.1.0/README.md +48 -0
- unicode_animations-0.1.0/pyproject.toml +19 -0
- unicode_animations-0.1.0/tests/__init__.py +0 -0
- unicode_animations-0.1.0/tests/test_braille.py +125 -0
- unicode_animations-0.1.0/unicode_animations/__init__.py +1 -0
- unicode_animations-0.1.0/unicode_animations/__main__.py +59 -0
- unicode_animations-0.1.0/unicode_animations/braille.py +403 -0
- unicode_animations-0.1.0/uv.lock +153 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"WebFetch(domain:raw.githubusercontent.com)",
|
|
5
|
+
"WebFetch(domain:api.github.com)",
|
|
6
|
+
"Bash(python -m pytest:*)",
|
|
7
|
+
"Bash(python3 -m pytest:*)",
|
|
8
|
+
"Bash(uv run pytest:*)",
|
|
9
|
+
"Bash(uv pip install:*)",
|
|
10
|
+
"Bash(uv run:*)",
|
|
11
|
+
"Bash(cd:*)"
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: unicode-animations
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Pre-built Unicode braille spinner animations as raw frame data
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Keywords: animation,braille,cli,spinner,unicode
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# unicode-animations
|
|
2
|
+
|
|
3
|
+
Python port of [gunnargray-dev/unicode-animations](https://github.com/gunnargray-dev/unicode-animations).
|
|
4
|
+
|
|
5
|
+
18 pre-built Unicode braille spinner animations as raw frame data, plus grid utilities for building custom spinners. Zero dependencies.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
uv add unicode-animations
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from unicode_animations import spinners
|
|
17
|
+
|
|
18
|
+
spinner = spinners["helix"]
|
|
19
|
+
print(spinner.frames) # tuple of animation frames
|
|
20
|
+
print(spinner.interval) # ms between frames
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Custom spinners with the grid API
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from unicode_animations import make_grid, grid_to_braille
|
|
27
|
+
|
|
28
|
+
grid = make_grid(4, 2) # 4 rows × 2 cols (one braille char)
|
|
29
|
+
grid[0][0] = True
|
|
30
|
+
grid[3][1] = True
|
|
31
|
+
print(grid_to_braille(grid)) # ⡁
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### CLI demo
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
uv run python -m unicode_animations # cycle through all spinners
|
|
38
|
+
uv run python -m unicode_animations helix # preview one
|
|
39
|
+
uv run python -m unicode_animations --list # list all names
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Available spinners
|
|
43
|
+
|
|
44
|
+
`braille` · `braillewave` · `dna` · `scan` · `rain` · `scanline` · `pulse` · `snake` · `sparkle` · `cascade` · `columns` · `orbit` · `breathe` · `waverows` · `checkerboard` · `helix` · `fillsweep` · `diagswipe`
|
|
45
|
+
|
|
46
|
+
## License
|
|
47
|
+
|
|
48
|
+
MIT
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "unicode-animations"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Pre-built Unicode braille spinner animations as raw frame data"
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
keywords = ["spinner", "braille", "unicode", "animation", "cli"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"License :: OSI Approved :: MIT License",
|
|
15
|
+
"Operating System :: OS Independent",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.optional-dependencies]
|
|
19
|
+
dev = ["pytest"]
|
|
File without changes
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from unicode_animations.braille import spinners, grid_to_braille, make_grid, BrailleSpinnerName
|
|
4
|
+
|
|
5
|
+
ALL_NAMES: list[str] = [
|
|
6
|
+
"braille", "braillewave", "dna",
|
|
7
|
+
"scan", "rain", "scanline", "pulse", "snake",
|
|
8
|
+
"sparkle", "cascade", "columns", "orbit", "breathe",
|
|
9
|
+
"waverows", "checkerboard", "helix", "fillsweep", "diagswipe",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# ── make_grid ──────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestMakeGrid:
|
|
17
|
+
def test_correct_dimensions(self):
|
|
18
|
+
g = make_grid(4, 8)
|
|
19
|
+
assert len(g) == 4
|
|
20
|
+
assert len(g[0]) == 8
|
|
21
|
+
assert all(cell is False for row in g for cell in row)
|
|
22
|
+
|
|
23
|
+
def test_zero_dimensions(self):
|
|
24
|
+
assert make_grid(0, 5) == []
|
|
25
|
+
assert make_grid(5, 0) == []
|
|
26
|
+
|
|
27
|
+
def test_negative_dimensions(self):
|
|
28
|
+
assert make_grid(-1, 5) == []
|
|
29
|
+
assert make_grid(5, -1) == []
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ── grid_to_braille ───────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TestGridToBraille:
|
|
36
|
+
def test_empty_grid(self):
|
|
37
|
+
assert grid_to_braille([]) == ""
|
|
38
|
+
|
|
39
|
+
def test_blank_braille_char(self):
|
|
40
|
+
g = make_grid(4, 2)
|
|
41
|
+
assert grid_to_braille(g) == "\u2800"
|
|
42
|
+
|
|
43
|
+
def test_full_braille_char(self):
|
|
44
|
+
g = make_grid(4, 2)
|
|
45
|
+
for r in range(4):
|
|
46
|
+
for c in range(2):
|
|
47
|
+
g[r][c] = True
|
|
48
|
+
assert grid_to_braille(g) == "\u28FF"
|
|
49
|
+
|
|
50
|
+
def test_individual_dots(self):
|
|
51
|
+
# dot1 (row0, col0) = 0x01
|
|
52
|
+
g1 = make_grid(4, 2)
|
|
53
|
+
g1[0][0] = True
|
|
54
|
+
assert grid_to_braille(g1) == "\u2801"
|
|
55
|
+
|
|
56
|
+
# dot4 (row0, col1) = 0x08
|
|
57
|
+
g2 = make_grid(4, 2)
|
|
58
|
+
g2[0][1] = True
|
|
59
|
+
assert grid_to_braille(g2) == "\u2808"
|
|
60
|
+
|
|
61
|
+
# dot2 (row1, col0) = 0x02
|
|
62
|
+
g3 = make_grid(4, 2)
|
|
63
|
+
g3[1][0] = True
|
|
64
|
+
assert grid_to_braille(g3) == "\u2802"
|
|
65
|
+
|
|
66
|
+
# dot5 (row1, col1) = 0x10
|
|
67
|
+
g4 = make_grid(4, 2)
|
|
68
|
+
g4[1][1] = True
|
|
69
|
+
assert grid_to_braille(g4) == "\u2810"
|
|
70
|
+
|
|
71
|
+
# dot3 (row2, col0) = 0x04
|
|
72
|
+
g5 = make_grid(4, 2)
|
|
73
|
+
g5[2][0] = True
|
|
74
|
+
assert grid_to_braille(g5) == "\u2804"
|
|
75
|
+
|
|
76
|
+
# dot6 (row2, col1) = 0x20
|
|
77
|
+
g6 = make_grid(4, 2)
|
|
78
|
+
g6[2][1] = True
|
|
79
|
+
assert grid_to_braille(g6) == "\u2820"
|
|
80
|
+
|
|
81
|
+
# dot7 (row3, col0) = 0x40
|
|
82
|
+
g7 = make_grid(4, 2)
|
|
83
|
+
g7[3][0] = True
|
|
84
|
+
assert grid_to_braille(g7) == "\u2840"
|
|
85
|
+
|
|
86
|
+
# dot8 (row3, col1) = 0x80
|
|
87
|
+
g8 = make_grid(4, 2)
|
|
88
|
+
g8[3][1] = True
|
|
89
|
+
assert grid_to_braille(g8) == "\u2880"
|
|
90
|
+
|
|
91
|
+
def test_multiple_characters(self):
|
|
92
|
+
g = make_grid(4, 4)
|
|
93
|
+
g[0][0] = True
|
|
94
|
+
g[0][2] = True
|
|
95
|
+
result = grid_to_braille(g)
|
|
96
|
+
assert len(result) == 2
|
|
97
|
+
assert result == "\u2801\u2801"
|
|
98
|
+
|
|
99
|
+
def test_odd_width(self):
|
|
100
|
+
g = make_grid(4, 3)
|
|
101
|
+
g[0][0] = True
|
|
102
|
+
g[0][2] = True
|
|
103
|
+
result = grid_to_braille(g)
|
|
104
|
+
assert len(result) == 2
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# ── Spinners ──────────────────────────────────────────────────────────
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class TestSpinners:
|
|
111
|
+
def test_exports_all_18(self):
|
|
112
|
+
assert sorted(spinners.keys()) == sorted(ALL_NAMES)
|
|
113
|
+
|
|
114
|
+
@pytest.mark.parametrize("name", ALL_NAMES)
|
|
115
|
+
def test_non_empty_frames(self, name: str):
|
|
116
|
+
assert len(spinners[name].frames) > 0
|
|
117
|
+
|
|
118
|
+
@pytest.mark.parametrize("name", ALL_NAMES)
|
|
119
|
+
def test_positive_interval(self, name: str):
|
|
120
|
+
assert spinners[name].interval > 0
|
|
121
|
+
|
|
122
|
+
@pytest.mark.parametrize("name", ALL_NAMES)
|
|
123
|
+
def test_consistent_frame_widths(self, name: str):
|
|
124
|
+
widths = [len(list(f)) for f in spinners[name].frames]
|
|
125
|
+
assert len(set(widths)) == 1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .braille import spinners, grid_to_braille, make_grid, Spinner, BrailleSpinnerName
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""CLI demo: python -m unicode_animations [name | --list]"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
from .braille import spinners
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _preview(name: str, duration: float = 3.0) -> None:
|
|
12
|
+
spinner = spinners[name] # type: ignore[index]
|
|
13
|
+
frames = spinner.frames
|
|
14
|
+
interval = spinner.interval / 1000
|
|
15
|
+
end = time.monotonic() + duration
|
|
16
|
+
idx = 0
|
|
17
|
+
sys.stdout.write(f"\033[?25l {name}: ")
|
|
18
|
+
sys.stdout.flush()
|
|
19
|
+
try:
|
|
20
|
+
while time.monotonic() < end:
|
|
21
|
+
sys.stdout.write(f"\r {name}: {frames[idx % len(frames)]}")
|
|
22
|
+
sys.stdout.flush()
|
|
23
|
+
time.sleep(interval)
|
|
24
|
+
idx += 1
|
|
25
|
+
finally:
|
|
26
|
+
sys.stdout.write("\033[?25h\n")
|
|
27
|
+
sys.stdout.flush()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def main() -> None:
|
|
31
|
+
args = sys.argv[1:]
|
|
32
|
+
|
|
33
|
+
if "--list" in args or "-l" in args:
|
|
34
|
+
for name in spinners:
|
|
35
|
+
print(name)
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
if args:
|
|
39
|
+
name = args[0]
|
|
40
|
+
if name not in spinners:
|
|
41
|
+
print(f"Unknown spinner: {name}")
|
|
42
|
+
print(f"Available: {', '.join(spinners)}")
|
|
43
|
+
sys.exit(1)
|
|
44
|
+
try:
|
|
45
|
+
_preview(name, duration=5.0)
|
|
46
|
+
except KeyboardInterrupt:
|
|
47
|
+
sys.stdout.write("\033[?25h\n")
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
# Cycle through all spinners
|
|
51
|
+
try:
|
|
52
|
+
for name in spinners:
|
|
53
|
+
_preview(name)
|
|
54
|
+
except KeyboardInterrupt:
|
|
55
|
+
sys.stdout.write("\033[?25h\n")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
if __name__ == "__main__":
|
|
59
|
+
main()
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unicode Braille Spinners
|
|
3
|
+
|
|
4
|
+
A collection of animated unicode spinners built on braille characters (U+2800 block).
|
|
5
|
+
Each braille char is a 2x4 dot grid — these generators compose them into
|
|
6
|
+
multi-character animated frames for use as loading indicators.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import math
|
|
12
|
+
from typing import Literal, NamedTuple
|
|
13
|
+
|
|
14
|
+
# ── Types ──────────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Spinner(NamedTuple):
|
|
18
|
+
frames: tuple[str, ...]
|
|
19
|
+
interval: int
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
BrailleSpinnerName = Literal[
|
|
23
|
+
"braille", "braillewave", "dna",
|
|
24
|
+
"scan", "rain", "scanline", "pulse", "snake",
|
|
25
|
+
"sparkle", "cascade", "columns", "orbit", "breathe",
|
|
26
|
+
"waverows", "checkerboard", "helix", "fillsweep", "diagswipe",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
# ── Braille Grid Utility ──────────────────────────────────────────────
|
|
30
|
+
#
|
|
31
|
+
# Each braille char is a 2-col × 4-row dot grid.
|
|
32
|
+
# Dot numbering & bit values:
|
|
33
|
+
# Row 0: dot1 (0x01) dot4 (0x08)
|
|
34
|
+
# Row 1: dot2 (0x02) dot5 (0x10)
|
|
35
|
+
# Row 2: dot3 (0x04) dot6 (0x20)
|
|
36
|
+
# Row 3: dot7 (0x40) dot8 (0x80)
|
|
37
|
+
#
|
|
38
|
+
# Base codepoint: U+2800
|
|
39
|
+
|
|
40
|
+
BRAILLE_DOT_MAP = [
|
|
41
|
+
[0x01, 0x08], # row 0
|
|
42
|
+
[0x02, 0x10], # row 1
|
|
43
|
+
[0x04, 0x20], # row 2
|
|
44
|
+
[0x40, 0x80], # row 3
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def grid_to_braille(grid: list[list[bool]]) -> str:
|
|
49
|
+
"""Convert a 2D boolean grid into a braille string.
|
|
50
|
+
|
|
51
|
+
grid[row][col] = True means dot is raised.
|
|
52
|
+
Width must be even (2 dot-columns per braille char).
|
|
53
|
+
"""
|
|
54
|
+
rows = len(grid)
|
|
55
|
+
cols = len(grid[0]) if grid else 0
|
|
56
|
+
char_count = math.ceil(cols / 2)
|
|
57
|
+
result: list[str] = []
|
|
58
|
+
for c in range(char_count):
|
|
59
|
+
code = 0x2800
|
|
60
|
+
for r in range(min(4, rows)):
|
|
61
|
+
for d in range(2):
|
|
62
|
+
col = c * 2 + d
|
|
63
|
+
if col < cols and r < len(grid) and grid[r][col]:
|
|
64
|
+
code |= BRAILLE_DOT_MAP[r][d]
|
|
65
|
+
result.append(chr(code))
|
|
66
|
+
return "".join(result)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def make_grid(rows: int, cols: int) -> list[list[bool]]:
|
|
70
|
+
"""Create an empty grid of given dimensions."""
|
|
71
|
+
if rows <= 0 or cols <= 0:
|
|
72
|
+
return []
|
|
73
|
+
return [[False] * cols for _ in range(rows)]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# ── Frame Generators ──────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _gen_scan() -> tuple[str, ...]:
|
|
80
|
+
W, H = 8, 4
|
|
81
|
+
frames: list[str] = []
|
|
82
|
+
for pos in range(-1, W + 1):
|
|
83
|
+
g = make_grid(H, W)
|
|
84
|
+
for r in range(H):
|
|
85
|
+
for c in range(W):
|
|
86
|
+
if c == pos or c == pos - 1:
|
|
87
|
+
g[r][c] = True
|
|
88
|
+
frames.append(grid_to_braille(g))
|
|
89
|
+
return tuple(frames)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _gen_rain() -> tuple[str, ...]:
|
|
93
|
+
W, H, total_frames = 8, 4, 12
|
|
94
|
+
offsets = [0, 3, 1, 5, 2, 7, 4, 6]
|
|
95
|
+
frames: list[str] = []
|
|
96
|
+
for f in range(total_frames):
|
|
97
|
+
g = make_grid(H, W)
|
|
98
|
+
for c in range(W):
|
|
99
|
+
row = (f + offsets[c]) % (H + 2)
|
|
100
|
+
if row < H:
|
|
101
|
+
g[row][c] = True
|
|
102
|
+
frames.append(grid_to_braille(g))
|
|
103
|
+
return tuple(frames)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _gen_scan_line() -> tuple[str, ...]:
|
|
107
|
+
W, H = 6, 4
|
|
108
|
+
positions = [0, 1, 2, 3, 2, 1]
|
|
109
|
+
frames: list[str] = []
|
|
110
|
+
for row in positions:
|
|
111
|
+
g = make_grid(H, W)
|
|
112
|
+
for c in range(W):
|
|
113
|
+
g[row][c] = True
|
|
114
|
+
if row > 0:
|
|
115
|
+
g[row - 1][c] = c % 2 == 0
|
|
116
|
+
frames.append(grid_to_braille(g))
|
|
117
|
+
return tuple(frames)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _gen_pulse() -> tuple[str, ...]:
|
|
121
|
+
W, H = 6, 4
|
|
122
|
+
cx = W / 2 - 0.5
|
|
123
|
+
cy = H / 2 - 0.5
|
|
124
|
+
radii = [0.5, 1.2, 2, 3, 3.5]
|
|
125
|
+
frames: list[str] = []
|
|
126
|
+
for radius in radii:
|
|
127
|
+
g = make_grid(H, W)
|
|
128
|
+
for row in range(H):
|
|
129
|
+
for col in range(W):
|
|
130
|
+
dist = math.sqrt((col - cx) ** 2 + (row - cy) ** 2)
|
|
131
|
+
if abs(dist - radius) < 0.9:
|
|
132
|
+
g[row][col] = True
|
|
133
|
+
frames.append(grid_to_braille(g))
|
|
134
|
+
return tuple(frames)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _gen_snake() -> tuple[str, ...]:
|
|
138
|
+
W, H = 4, 4
|
|
139
|
+
path: list[tuple[int, int]] = []
|
|
140
|
+
for r in range(H):
|
|
141
|
+
if r % 2 == 0:
|
|
142
|
+
for c in range(W):
|
|
143
|
+
path.append((r, c))
|
|
144
|
+
else:
|
|
145
|
+
for c in range(W - 1, -1, -1):
|
|
146
|
+
path.append((r, c))
|
|
147
|
+
frames: list[str] = []
|
|
148
|
+
for i in range(len(path)):
|
|
149
|
+
g = make_grid(H, W)
|
|
150
|
+
for t in range(4):
|
|
151
|
+
idx = (i - t + len(path)) % len(path)
|
|
152
|
+
g[path[idx][0]][path[idx][1]] = True
|
|
153
|
+
frames.append(grid_to_braille(g))
|
|
154
|
+
return tuple(frames)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _gen_sparkle() -> tuple[str, ...]:
|
|
158
|
+
patterns = [
|
|
159
|
+
[1,0,0,1,0,0,1,0, 0,0,1,0,0,1,0,0, 0,1,0,0,1,0,0,1, 1,0,0,0,0,1,0,0],
|
|
160
|
+
[0,1,0,0,1,0,0,1, 1,0,0,1,0,0,0,1, 0,0,0,1,0,1,0,0, 0,0,1,0,1,0,1,0],
|
|
161
|
+
[0,0,1,0,0,1,0,0, 0,1,0,0,0,0,1,0, 1,0,1,0,0,0,0,1, 0,1,0,1,0,0,0,1],
|
|
162
|
+
[1,0,0,0,0,0,1,1, 0,0,1,0,1,0,0,0, 0,0,0,0,1,0,1,0, 1,0,0,1,0,0,1,0],
|
|
163
|
+
[0,0,0,1,1,0,0,0, 0,1,0,0,0,1,0,1, 1,0,0,1,0,0,0,0, 0,1,0,0,0,1,0,1],
|
|
164
|
+
[0,1,1,0,0,0,0,1, 0,0,0,1,0,0,1,0, 0,1,0,0,0,1,0,0, 0,0,1,0,1,0,0,0],
|
|
165
|
+
]
|
|
166
|
+
W, H = 8, 4
|
|
167
|
+
frames: list[str] = []
|
|
168
|
+
for pat in patterns:
|
|
169
|
+
g = make_grid(H, W)
|
|
170
|
+
for r in range(H):
|
|
171
|
+
for c in range(W):
|
|
172
|
+
g[r][c] = bool(pat[r * W + c])
|
|
173
|
+
frames.append(grid_to_braille(g))
|
|
174
|
+
return tuple(frames)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _gen_cascade() -> tuple[str, ...]:
|
|
178
|
+
W, H = 8, 4
|
|
179
|
+
frames: list[str] = []
|
|
180
|
+
for offset in range(-2, W + H):
|
|
181
|
+
g = make_grid(H, W)
|
|
182
|
+
for r in range(H):
|
|
183
|
+
for c in range(W):
|
|
184
|
+
diag = c + r
|
|
185
|
+
if diag == offset or diag == offset - 1:
|
|
186
|
+
g[r][c] = True
|
|
187
|
+
frames.append(grid_to_braille(g))
|
|
188
|
+
return tuple(frames)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _gen_columns() -> tuple[str, ...]:
|
|
192
|
+
W, H = 6, 4
|
|
193
|
+
frames: list[str] = []
|
|
194
|
+
for col in range(W):
|
|
195
|
+
for fill_to in range(H - 1, -1, -1):
|
|
196
|
+
g = make_grid(H, W)
|
|
197
|
+
for pc in range(col):
|
|
198
|
+
for r in range(H):
|
|
199
|
+
g[r][pc] = True
|
|
200
|
+
for r in range(fill_to, H):
|
|
201
|
+
g[r][col] = True
|
|
202
|
+
frames.append(grid_to_braille(g))
|
|
203
|
+
full = make_grid(H, W)
|
|
204
|
+
for r in range(H):
|
|
205
|
+
for c in range(W):
|
|
206
|
+
full[r][c] = True
|
|
207
|
+
frames.append(grid_to_braille(full))
|
|
208
|
+
frames.append(grid_to_braille(make_grid(H, W)))
|
|
209
|
+
return tuple(frames)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _gen_orbit() -> tuple[str, ...]:
|
|
213
|
+
W, H = 2, 4
|
|
214
|
+
path: list[tuple[int, int]] = [
|
|
215
|
+
(0, 0), (0, 1),
|
|
216
|
+
(1, 1), (2, 1), (3, 1),
|
|
217
|
+
(3, 0),
|
|
218
|
+
(2, 0), (1, 0),
|
|
219
|
+
]
|
|
220
|
+
frames: list[str] = []
|
|
221
|
+
for i in range(len(path)):
|
|
222
|
+
g = make_grid(H, W)
|
|
223
|
+
g[path[i][0]][path[i][1]] = True
|
|
224
|
+
t1 = (i - 1 + len(path)) % len(path)
|
|
225
|
+
g[path[t1][0]][path[t1][1]] = True
|
|
226
|
+
frames.append(grid_to_braille(g))
|
|
227
|
+
return tuple(frames)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _gen_breathe() -> tuple[str, ...]:
|
|
231
|
+
stages: list[list[tuple[int, int]]] = [
|
|
232
|
+
[],
|
|
233
|
+
[(1, 0)],
|
|
234
|
+
[(0, 1), (2, 0)],
|
|
235
|
+
[(0, 0), (1, 1), (3, 0)],
|
|
236
|
+
[(0, 0), (1, 1), (2, 0), (3, 1)],
|
|
237
|
+
[(0, 0), (0, 1), (1, 1), (2, 0), (3, 1)],
|
|
238
|
+
[(0, 0), (0, 1), (1, 0), (2, 1), (3, 0), (3, 1)],
|
|
239
|
+
[(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (3, 0), (3, 1)],
|
|
240
|
+
[(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1), (3, 0), (3, 1)],
|
|
241
|
+
]
|
|
242
|
+
sequence = stages + list(reversed(stages))[1:]
|
|
243
|
+
frames: list[str] = []
|
|
244
|
+
for dots in sequence:
|
|
245
|
+
g = make_grid(4, 2)
|
|
246
|
+
for r, c in dots:
|
|
247
|
+
g[r][c] = True
|
|
248
|
+
frames.append(grid_to_braille(g))
|
|
249
|
+
return tuple(frames)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _gen_wave_rows() -> tuple[str, ...]:
|
|
253
|
+
W, H, total_frames = 8, 4, 16
|
|
254
|
+
frames: list[str] = []
|
|
255
|
+
for f in range(total_frames):
|
|
256
|
+
g = make_grid(H, W)
|
|
257
|
+
for c in range(W):
|
|
258
|
+
phase = f - c * 0.5
|
|
259
|
+
row = round((math.sin(phase * 0.8) + 1) / 2 * (H - 1))
|
|
260
|
+
g[row][c] = True
|
|
261
|
+
if row > 0:
|
|
262
|
+
g[row - 1][c] = (f + c) % 3 == 0
|
|
263
|
+
frames.append(grid_to_braille(g))
|
|
264
|
+
return tuple(frames)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _gen_checkerboard() -> tuple[str, ...]:
|
|
268
|
+
W, H = 6, 4
|
|
269
|
+
frames: list[str] = []
|
|
270
|
+
for phase in range(4):
|
|
271
|
+
g = make_grid(H, W)
|
|
272
|
+
for r in range(H):
|
|
273
|
+
for c in range(W):
|
|
274
|
+
if phase < 2:
|
|
275
|
+
g[r][c] = (r + c + phase) % 2 == 0
|
|
276
|
+
else:
|
|
277
|
+
g[r][c] = (r + c + phase) % 3 == 0
|
|
278
|
+
frames.append(grid_to_braille(g))
|
|
279
|
+
return tuple(frames)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _gen_helix() -> tuple[str, ...]:
|
|
283
|
+
W, H, total_frames = 8, 4, 16
|
|
284
|
+
frames: list[str] = []
|
|
285
|
+
for f in range(total_frames):
|
|
286
|
+
g = make_grid(H, W)
|
|
287
|
+
for c in range(W):
|
|
288
|
+
phase = (f + c) * (math.pi / 4)
|
|
289
|
+
y1 = round((math.sin(phase) + 1) / 2 * (H - 1))
|
|
290
|
+
y2 = round((math.sin(phase + math.pi) + 1) / 2 * (H - 1))
|
|
291
|
+
g[y1][c] = True
|
|
292
|
+
g[y2][c] = True
|
|
293
|
+
frames.append(grid_to_braille(g))
|
|
294
|
+
return tuple(frames)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _gen_fill_sweep() -> tuple[str, ...]:
|
|
298
|
+
W, H = 4, 4
|
|
299
|
+
frames: list[str] = []
|
|
300
|
+
for row in range(H - 1, -1, -1):
|
|
301
|
+
g = make_grid(H, W)
|
|
302
|
+
for r in range(row, H):
|
|
303
|
+
for c in range(W):
|
|
304
|
+
g[r][c] = True
|
|
305
|
+
frames.append(grid_to_braille(g))
|
|
306
|
+
full = make_grid(H, W)
|
|
307
|
+
for r in range(H):
|
|
308
|
+
for c in range(W):
|
|
309
|
+
full[r][c] = True
|
|
310
|
+
frames.append(grid_to_braille(full))
|
|
311
|
+
frames.append(grid_to_braille(full))
|
|
312
|
+
for row in range(H):
|
|
313
|
+
g = make_grid(H, W)
|
|
314
|
+
for r in range(row + 1, H):
|
|
315
|
+
for c in range(W):
|
|
316
|
+
g[r][c] = True
|
|
317
|
+
frames.append(grid_to_braille(g))
|
|
318
|
+
frames.append(grid_to_braille(make_grid(H, W)))
|
|
319
|
+
return tuple(frames)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def _gen_diagonal_swipe() -> tuple[str, ...]:
|
|
323
|
+
W, H = 4, 4
|
|
324
|
+
max_diag = W + H - 2
|
|
325
|
+
frames: list[str] = []
|
|
326
|
+
for d in range(max_diag + 1):
|
|
327
|
+
g = make_grid(H, W)
|
|
328
|
+
for r in range(H):
|
|
329
|
+
for c in range(W):
|
|
330
|
+
if r + c <= d:
|
|
331
|
+
g[r][c] = True
|
|
332
|
+
frames.append(grid_to_braille(g))
|
|
333
|
+
full = make_grid(H, W)
|
|
334
|
+
for r in range(H):
|
|
335
|
+
for c in range(W):
|
|
336
|
+
full[r][c] = True
|
|
337
|
+
frames.append(grid_to_braille(full))
|
|
338
|
+
for d in range(max_diag + 1):
|
|
339
|
+
g = make_grid(H, W)
|
|
340
|
+
for r in range(H):
|
|
341
|
+
for c in range(W):
|
|
342
|
+
if r + c > d:
|
|
343
|
+
g[r][c] = True
|
|
344
|
+
frames.append(grid_to_braille(g))
|
|
345
|
+
frames.append(grid_to_braille(make_grid(H, W)))
|
|
346
|
+
return tuple(frames)
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
# ── Spinner Registry ──────────────────────────────────────────────────
|
|
350
|
+
|
|
351
|
+
spinners: dict[BrailleSpinnerName, Spinner] = {
|
|
352
|
+
# Classic braille single-char
|
|
353
|
+
"braille": Spinner(
|
|
354
|
+
frames=("⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"),
|
|
355
|
+
interval=80,
|
|
356
|
+
),
|
|
357
|
+
"braillewave": Spinner(
|
|
358
|
+
frames=(
|
|
359
|
+
"⠁⠂⠄⡀",
|
|
360
|
+
"⠂⠄⡀⢀",
|
|
361
|
+
"⠄⡀⢀⠠",
|
|
362
|
+
"⡀⢀⠠⠐",
|
|
363
|
+
"⢀⠠⠐⠈",
|
|
364
|
+
"⠠⠐⠈⠁",
|
|
365
|
+
"⠐⠈⠁⠂",
|
|
366
|
+
"⠈⠁⠂⠄",
|
|
367
|
+
),
|
|
368
|
+
interval=100,
|
|
369
|
+
),
|
|
370
|
+
"dna": Spinner(
|
|
371
|
+
frames=(
|
|
372
|
+
"⠋⠉⠙⠚",
|
|
373
|
+
"⠉⠙⠚⠒",
|
|
374
|
+
"⠙⠚⠒⠂",
|
|
375
|
+
"⠚⠒⠂⠂",
|
|
376
|
+
"⠒⠂⠂⠒",
|
|
377
|
+
"⠂⠂⠒⠲",
|
|
378
|
+
"⠂⠒⠲⠴",
|
|
379
|
+
"⠒⠲⠴⠤",
|
|
380
|
+
"⠲⠴⠤⠄",
|
|
381
|
+
"⠴⠤⠄⠋",
|
|
382
|
+
"⠤⠄⠋⠉",
|
|
383
|
+
"⠄⠋⠉⠙",
|
|
384
|
+
),
|
|
385
|
+
interval=80,
|
|
386
|
+
),
|
|
387
|
+
# Generated braille grid animations
|
|
388
|
+
"scan": Spinner(frames=_gen_scan(), interval=70),
|
|
389
|
+
"rain": Spinner(frames=_gen_rain(), interval=100),
|
|
390
|
+
"scanline": Spinner(frames=_gen_scan_line(), interval=120),
|
|
391
|
+
"pulse": Spinner(frames=_gen_pulse(), interval=180),
|
|
392
|
+
"snake": Spinner(frames=_gen_snake(), interval=80),
|
|
393
|
+
"sparkle": Spinner(frames=_gen_sparkle(), interval=150),
|
|
394
|
+
"cascade": Spinner(frames=_gen_cascade(), interval=60),
|
|
395
|
+
"columns": Spinner(frames=_gen_columns(), interval=60),
|
|
396
|
+
"orbit": Spinner(frames=_gen_orbit(), interval=100),
|
|
397
|
+
"breathe": Spinner(frames=_gen_breathe(), interval=100),
|
|
398
|
+
"waverows": Spinner(frames=_gen_wave_rows(), interval=90),
|
|
399
|
+
"checkerboard": Spinner(frames=_gen_checkerboard(), interval=250),
|
|
400
|
+
"helix": Spinner(frames=_gen_helix(), interval=80),
|
|
401
|
+
"fillsweep": Spinner(frames=_gen_fill_sweep(), interval=100),
|
|
402
|
+
"diagswipe": Spinner(frames=_gen_diagonal_swipe(), interval=60),
|
|
403
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
version = 1
|
|
2
|
+
requires-python = ">=3.10"
|
|
3
|
+
|
|
4
|
+
[[package]]
|
|
5
|
+
name = "colorama"
|
|
6
|
+
version = "0.4.6"
|
|
7
|
+
source = { registry = "https://pypi.org/simple" }
|
|
8
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
|
|
9
|
+
wheels = [
|
|
10
|
+
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[[package]]
|
|
14
|
+
name = "exceptiongroup"
|
|
15
|
+
version = "1.3.1"
|
|
16
|
+
source = { registry = "https://pypi.org/simple" }
|
|
17
|
+
dependencies = [
|
|
18
|
+
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
|
19
|
+
]
|
|
20
|
+
sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371 }
|
|
21
|
+
wheels = [
|
|
22
|
+
{ url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740 },
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[[package]]
|
|
26
|
+
name = "iniconfig"
|
|
27
|
+
version = "2.3.0"
|
|
28
|
+
source = { registry = "https://pypi.org/simple" }
|
|
29
|
+
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503 }
|
|
30
|
+
wheels = [
|
|
31
|
+
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484 },
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[[package]]
|
|
35
|
+
name = "packaging"
|
|
36
|
+
version = "26.0"
|
|
37
|
+
source = { registry = "https://pypi.org/simple" }
|
|
38
|
+
sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416 }
|
|
39
|
+
wheels = [
|
|
40
|
+
{ url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366 },
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[[package]]
|
|
44
|
+
name = "pluggy"
|
|
45
|
+
version = "1.6.0"
|
|
46
|
+
source = { registry = "https://pypi.org/simple" }
|
|
47
|
+
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 }
|
|
48
|
+
wheels = [
|
|
49
|
+
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 },
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
[[package]]
|
|
53
|
+
name = "pygments"
|
|
54
|
+
version = "2.19.2"
|
|
55
|
+
source = { registry = "https://pypi.org/simple" }
|
|
56
|
+
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631 }
|
|
57
|
+
wheels = [
|
|
58
|
+
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 },
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
[[package]]
|
|
62
|
+
name = "pytest"
|
|
63
|
+
version = "9.0.2"
|
|
64
|
+
source = { registry = "https://pypi.org/simple" }
|
|
65
|
+
dependencies = [
|
|
66
|
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
|
67
|
+
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
|
|
68
|
+
{ name = "iniconfig" },
|
|
69
|
+
{ name = "packaging" },
|
|
70
|
+
{ name = "pluggy" },
|
|
71
|
+
{ name = "pygments" },
|
|
72
|
+
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
|
73
|
+
]
|
|
74
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901 }
|
|
75
|
+
wheels = [
|
|
76
|
+
{ url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801 },
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
[[package]]
|
|
80
|
+
name = "tomli"
|
|
81
|
+
version = "2.4.0"
|
|
82
|
+
source = { registry = "https://pypi.org/simple" }
|
|
83
|
+
sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477 }
|
|
84
|
+
wheels = [
|
|
85
|
+
{ url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663 },
|
|
86
|
+
{ url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469 },
|
|
87
|
+
{ url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039 },
|
|
88
|
+
{ url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007 },
|
|
89
|
+
{ url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875 },
|
|
90
|
+
{ url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271 },
|
|
91
|
+
{ url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770 },
|
|
92
|
+
{ url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626 },
|
|
93
|
+
{ url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842 },
|
|
94
|
+
{ url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894 },
|
|
95
|
+
{ url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053 },
|
|
96
|
+
{ url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481 },
|
|
97
|
+
{ url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720 },
|
|
98
|
+
{ url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014 },
|
|
99
|
+
{ url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820 },
|
|
100
|
+
{ url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712 },
|
|
101
|
+
{ url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296 },
|
|
102
|
+
{ url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553 },
|
|
103
|
+
{ url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915 },
|
|
104
|
+
{ url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038 },
|
|
105
|
+
{ url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245 },
|
|
106
|
+
{ url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335 },
|
|
107
|
+
{ url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962 },
|
|
108
|
+
{ url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396 },
|
|
109
|
+
{ url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530 },
|
|
110
|
+
{ url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227 },
|
|
111
|
+
{ url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748 },
|
|
112
|
+
{ url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725 },
|
|
113
|
+
{ url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901 },
|
|
114
|
+
{ url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375 },
|
|
115
|
+
{ url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639 },
|
|
116
|
+
{ url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897 },
|
|
117
|
+
{ url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697 },
|
|
118
|
+
{ url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567 },
|
|
119
|
+
{ url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556 },
|
|
120
|
+
{ url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014 },
|
|
121
|
+
{ url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339 },
|
|
122
|
+
{ url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490 },
|
|
123
|
+
{ url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398 },
|
|
124
|
+
{ url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515 },
|
|
125
|
+
{ url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806 },
|
|
126
|
+
{ url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340 },
|
|
127
|
+
{ url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106 },
|
|
128
|
+
{ url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504 },
|
|
129
|
+
{ url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561 },
|
|
130
|
+
{ url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477 },
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
[[package]]
|
|
134
|
+
name = "typing-extensions"
|
|
135
|
+
version = "4.15.0"
|
|
136
|
+
source = { registry = "https://pypi.org/simple" }
|
|
137
|
+
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391 }
|
|
138
|
+
wheels = [
|
|
139
|
+
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614 },
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
[[package]]
|
|
143
|
+
name = "unicode-animations"
|
|
144
|
+
version = "0.1.0"
|
|
145
|
+
source = { editable = "." }
|
|
146
|
+
|
|
147
|
+
[package.optional-dependencies]
|
|
148
|
+
dev = [
|
|
149
|
+
{ name = "pytest" },
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
[package.metadata]
|
|
153
|
+
requires-dist = [{ name = "pytest", marker = "extra == 'dev'" }]
|