light-client 2.0.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.
- light_client-2.0.0/.gitignore +10 -0
- light_client-2.0.0/.python-version +1 -0
- light_client-2.0.0/PKG-INFO +97 -0
- light_client-2.0.0/README.md +90 -0
- light_client-2.0.0/pyproject.toml +14 -0
- light_client-2.0.0/src/light_client/__init__.py +17 -0
- light_client-2.0.0/src/light_client/ascii_art.py +101 -0
- light_client-2.0.0/src/light_client/progress.py +168 -0
- light_client-2.0.0/src/light_client/quotes.py +35 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: light-client
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: A totally legitimate light client implementation
|
|
5
|
+
Requires-Python: >=3.8
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
|
|
8
|
+
# light-client
|
|
9
|
+
|
|
10
|
+
A Python library for frog-based progress bars and frog wisdom.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install light-client
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Progress bar
|
|
19
|
+
|
|
20
|
+
`FrogBar` is a drop-in replacement for `tqdm`. Instead of a solid block bar, a frog πΈ hops across lily pads π.
|
|
21
|
+
|
|
22
|
+
**Wrap an iterable:**
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from light_client import FrogBar
|
|
26
|
+
import time
|
|
27
|
+
|
|
28
|
+
for item in FrogBar(range(100), desc="Processing"):
|
|
29
|
+
time.sleep(0.05)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
Processing: [ππππππππΈππππππππππππ] 35.0% 1.8s [35/100 it] *ribbit*
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Context manager with manual updates:**
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
with FrogBar(total=50, desc="Catching flies", unit="fly") as bar:
|
|
40
|
+
for fly in flies:
|
|
41
|
+
catch(fly)
|
|
42
|
+
bar.update(1)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Parameters
|
|
46
|
+
|
|
47
|
+
| Parameter | Default | Description |
|
|
48
|
+
|-----------|---------|-------------|
|
|
49
|
+
| `iterable` | `None` | Iterable to wrap |
|
|
50
|
+
| `total` | inferred | Total number of steps |
|
|
51
|
+
| `desc` | `"Hopping"` | Label shown before the bar |
|
|
52
|
+
| `bar_width` | `20` | Number of lily pad cells |
|
|
53
|
+
| `unit` | `"it"` | Unit label for the count |
|
|
54
|
+
| `leave` | `True` | Keep the bar on screen when done |
|
|
55
|
+
| `file` | `stderr` | Output stream |
|
|
56
|
+
|
|
57
|
+
## Quotes
|
|
58
|
+
|
|
59
|
+
Timeless wisdom from history's greatest frog thinkers.
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from light_client import get_random_quote, print_quote
|
|
63
|
+
|
|
64
|
+
quote, author = get_random_quote()
|
|
65
|
+
print(f'"{quote}" β {author}')
|
|
66
|
+
# "Leap before you look." β Frog Thoreau
|
|
67
|
+
|
|
68
|
+
print_quote() # prints a random quote
|
|
69
|
+
print_quote(3) # prints quote at index 3
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## ASCII art
|
|
73
|
+
|
|
74
|
+
Eight frogs included: `classic`, `tiny`, `zen`, `lily`, `detective`, `kawaii`, `sage`, `startup`.
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from light_client import get_frog, get_random_frog, list_frogs, print_frog
|
|
78
|
+
|
|
79
|
+
print_frog("zen") # print a named frog
|
|
80
|
+
print_frog() # print a random frog
|
|
81
|
+
|
|
82
|
+
name, art = get_random_frog()
|
|
83
|
+
frogs = list_frogs() # ['classic', 'tiny', 'zen', ...]
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
___
|
|
88
|
+
.-' `'.
|
|
89
|
+
/ \
|
|
90
|
+
| O O |
|
|
91
|
+
| ^^ |
|
|
92
|
+
| \____/ |
|
|
93
|
+
\ /
|
|
94
|
+
'-.___.-'
|
|
95
|
+
| |
|
|
96
|
+
frog is one
|
|
97
|
+
```
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# light-client
|
|
2
|
+
|
|
3
|
+
A Python library for frog-based progress bars and frog wisdom.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install light-client
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Progress bar
|
|
12
|
+
|
|
13
|
+
`FrogBar` is a drop-in replacement for `tqdm`. Instead of a solid block bar, a frog πΈ hops across lily pads π.
|
|
14
|
+
|
|
15
|
+
**Wrap an iterable:**
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from light_client import FrogBar
|
|
19
|
+
import time
|
|
20
|
+
|
|
21
|
+
for item in FrogBar(range(100), desc="Processing"):
|
|
22
|
+
time.sleep(0.05)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
Processing: [ππππππππΈππππππππππππ] 35.0% 1.8s [35/100 it] *ribbit*
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Context manager with manual updates:**
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
with FrogBar(total=50, desc="Catching flies", unit="fly") as bar:
|
|
33
|
+
for fly in flies:
|
|
34
|
+
catch(fly)
|
|
35
|
+
bar.update(1)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Parameters
|
|
39
|
+
|
|
40
|
+
| Parameter | Default | Description |
|
|
41
|
+
|-----------|---------|-------------|
|
|
42
|
+
| `iterable` | `None` | Iterable to wrap |
|
|
43
|
+
| `total` | inferred | Total number of steps |
|
|
44
|
+
| `desc` | `"Hopping"` | Label shown before the bar |
|
|
45
|
+
| `bar_width` | `20` | Number of lily pad cells |
|
|
46
|
+
| `unit` | `"it"` | Unit label for the count |
|
|
47
|
+
| `leave` | `True` | Keep the bar on screen when done |
|
|
48
|
+
| `file` | `stderr` | Output stream |
|
|
49
|
+
|
|
50
|
+
## Quotes
|
|
51
|
+
|
|
52
|
+
Timeless wisdom from history's greatest frog thinkers.
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from light_client import get_random_quote, print_quote
|
|
56
|
+
|
|
57
|
+
quote, author = get_random_quote()
|
|
58
|
+
print(f'"{quote}" β {author}')
|
|
59
|
+
# "Leap before you look." β Frog Thoreau
|
|
60
|
+
|
|
61
|
+
print_quote() # prints a random quote
|
|
62
|
+
print_quote(3) # prints quote at index 3
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## ASCII art
|
|
66
|
+
|
|
67
|
+
Eight frogs included: `classic`, `tiny`, `zen`, `lily`, `detective`, `kawaii`, `sage`, `startup`.
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from light_client import get_frog, get_random_frog, list_frogs, print_frog
|
|
71
|
+
|
|
72
|
+
print_frog("zen") # print a named frog
|
|
73
|
+
print_frog() # print a random frog
|
|
74
|
+
|
|
75
|
+
name, art = get_random_frog()
|
|
76
|
+
frogs = list_frogs() # ['classic', 'tiny', 'zen', ...]
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
___
|
|
81
|
+
.-' `'.
|
|
82
|
+
/ \
|
|
83
|
+
| O O |
|
|
84
|
+
| ^^ |
|
|
85
|
+
| \____/ |
|
|
86
|
+
\ /
|
|
87
|
+
'-.___.-'
|
|
88
|
+
| |
|
|
89
|
+
frog is one
|
|
90
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "light-client"
|
|
7
|
+
version = "2.0.0"
|
|
8
|
+
description = "A totally legitimate light client implementation"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
dependencies = []
|
|
12
|
+
|
|
13
|
+
[tool.hatch.build.targets.wheel]
|
|
14
|
+
packages = ["src/light_client"]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
print("It's froggen time")
|
|
2
|
+
|
|
3
|
+
from .quotes import get_quote, get_random_quote, print_quote
|
|
4
|
+
from .ascii_art import get_frog, get_random_frog, list_frogs, print_frog
|
|
5
|
+
from .progress import FrogBar
|
|
6
|
+
|
|
7
|
+
__version__ = "2.0.0"
|
|
8
|
+
__all__ = [
|
|
9
|
+
"get_quote",
|
|
10
|
+
"get_random_quote",
|
|
11
|
+
"print_quote",
|
|
12
|
+
"get_frog",
|
|
13
|
+
"get_random_frog",
|
|
14
|
+
"list_frogs",
|
|
15
|
+
"print_frog",
|
|
16
|
+
"FrogBar",
|
|
17
|
+
]
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import random
|
|
2
|
+
|
|
3
|
+
_FROGS = {
|
|
4
|
+
"classic": r"""
|
|
5
|
+
@..@
|
|
6
|
+
(----)
|
|
7
|
+
( >__< )
|
|
8
|
+
^^ ~~ ^^
|
|
9
|
+
""",
|
|
10
|
+
"tiny": r"""
|
|
11
|
+
(o^..^o)
|
|
12
|
+
( uu )
|
|
13
|
+
""",
|
|
14
|
+
"zen": r"""
|
|
15
|
+
___
|
|
16
|
+
.-' `'.
|
|
17
|
+
/ \
|
|
18
|
+
| O O |
|
|
19
|
+
| ^^ |
|
|
20
|
+
| \____/ |
|
|
21
|
+
\ /
|
|
22
|
+
'-.___.-'
|
|
23
|
+
| |
|
|
24
|
+
frog is one
|
|
25
|
+
""",
|
|
26
|
+
"lily": r"""
|
|
27
|
+
___
|
|
28
|
+
__/ o \__
|
|
29
|
+
/ ( ) \
|
|
30
|
+
| = \_/ = |
|
|
31
|
+
\__________/
|
|
32
|
+
/ \
|
|
33
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
34
|
+
""",
|
|
35
|
+
"detective": r"""
|
|
36
|
+
___
|
|
37
|
+
.-' `'-.
|
|
38
|
+
/ o o\
|
|
39
|
+
| __ |
|
|
40
|
+
| / \ |
|
|
41
|
+
\ \__/ /
|
|
42
|
+
'-.____.-'
|
|
43
|
+
[trenchcoat]
|
|
44
|
+
The name's Frog.
|
|
45
|
+
James Frog.
|
|
46
|
+
""",
|
|
47
|
+
"kawaii": r"""
|
|
48
|
+
οΌοΏ£οΌΌ
|
|
49
|
+
οΌβ Ο βοΌ
|
|
50
|
+
οΌΌοΌΏοΌ
|
|
51
|
+
οΌ οΌΌ
|
|
52
|
+
ribbit~
|
|
53
|
+
""",
|
|
54
|
+
"sage": r"""
|
|
55
|
+
.---.
|
|
56
|
+
( o o )
|
|
57
|
+
| (_) | "The pond
|
|
58
|
+
| | runs deep."
|
|
59
|
+
'-----'
|
|
60
|
+
|
|
|
61
|
+
~~~|~~~
|
|
62
|
+
""",
|
|
63
|
+
"startup": r"""
|
|
64
|
+
___
|
|
65
|
+
.-' `'-.
|
|
66
|
+
/ o o\
|
|
67
|
+
| DISRUPT |
|
|
68
|
+
| POND |
|
|
69
|
+
\ ____ /
|
|
70
|
+
'-' __ '-'
|
|
71
|
+
LEAP FAST,
|
|
72
|
+
CROAK LATER
|
|
73
|
+
""",
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def get_frog(name: str) -> str:
|
|
78
|
+
"""Return a named frog ASCII art string."""
|
|
79
|
+
if name not in _FROGS:
|
|
80
|
+
raise KeyError(f"Unknown frog '{name}'. Available: {list(_FROGS)}")
|
|
81
|
+
return _FROGS[name]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def get_random_frog() -> tuple[str, str]:
|
|
85
|
+
"""Return a (name, art) tuple for a randomly chosen frog."""
|
|
86
|
+
name = random.choice(list(_FROGS))
|
|
87
|
+
return name, _FROGS[name]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def list_frogs() -> list[str]:
|
|
91
|
+
"""Return the names of all available frogs."""
|
|
92
|
+
return list(_FROGS.keys())
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def print_frog(name: str | None = None) -> None:
|
|
96
|
+
"""Print a frog, random if no name given."""
|
|
97
|
+
if name is None:
|
|
98
|
+
chosen_name, art = get_random_frog()
|
|
99
|
+
print(f"[{chosen_name}]{art}")
|
|
100
|
+
else:
|
|
101
|
+
print(f"[{name}]{get_frog(name)}")
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
_FROG = "πΈ"
|
|
5
|
+
_LILY = "π"
|
|
6
|
+
_WATER = "~"
|
|
7
|
+
|
|
8
|
+
_RIBBITS = [
|
|
9
|
+
"*ribbit*", "*croak*", "*brrp*", "*gribbit*", "*kerchunk*",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
_DONE_MSGS = [
|
|
13
|
+
"All pads hopped!",
|
|
14
|
+
"Pond crossed!",
|
|
15
|
+
"Mission ribbited!",
|
|
16
|
+
"The frog has landed.",
|
|
17
|
+
"Leap complete!",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class FrogBar:
|
|
22
|
+
"""A frog-themed progress bar. A frog hops across lily pads instead of filling a block bar.
|
|
23
|
+
|
|
24
|
+
Usage β as an iterator wrapper::
|
|
25
|
+
|
|
26
|
+
for item in FrogBar(my_list, desc="Processing"):
|
|
27
|
+
process(item)
|
|
28
|
+
|
|
29
|
+
Usage β as a context manager with manual updates::
|
|
30
|
+
|
|
31
|
+
with FrogBar(total=100, desc="Loading") as bar:
|
|
32
|
+
for i in range(100):
|
|
33
|
+
do_work()
|
|
34
|
+
bar.update(1)
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
iterable=None,
|
|
40
|
+
total=None,
|
|
41
|
+
desc: str = "Hopping",
|
|
42
|
+
file=None,
|
|
43
|
+
leave: bool = True,
|
|
44
|
+
unit: str = "it",
|
|
45
|
+
bar_width: int = 20,
|
|
46
|
+
):
|
|
47
|
+
self.iterable = iterable
|
|
48
|
+
if total is not None:
|
|
49
|
+
self.total = total
|
|
50
|
+
elif iterable is not None and hasattr(iterable, "__len__"):
|
|
51
|
+
self.total = len(iterable)
|
|
52
|
+
else:
|
|
53
|
+
self.total = None
|
|
54
|
+
|
|
55
|
+
self.desc = desc
|
|
56
|
+
self.bar_width = bar_width
|
|
57
|
+
self.file = file or sys.stderr
|
|
58
|
+
self.leave = leave
|
|
59
|
+
self.unit = unit
|
|
60
|
+
self.n = 0
|
|
61
|
+
self.start_time: float | None = None
|
|
62
|
+
self._rendered_len = 0
|
|
63
|
+
self._done = False
|
|
64
|
+
self._ribbit_index = 0
|
|
65
|
+
|
|
66
|
+
# ------------------------------------------------------------------
|
|
67
|
+
# Context manager / iterator protocol
|
|
68
|
+
# ------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
def __enter__(self) -> "FrogBar":
|
|
71
|
+
self.start_time = time.time()
|
|
72
|
+
self._render()
|
|
73
|
+
return self
|
|
74
|
+
|
|
75
|
+
def __exit__(self, *_) -> None:
|
|
76
|
+
self._finish()
|
|
77
|
+
|
|
78
|
+
def __iter__(self):
|
|
79
|
+
self.start_time = time.time()
|
|
80
|
+
self._render()
|
|
81
|
+
try:
|
|
82
|
+
for item in self.iterable:
|
|
83
|
+
yield item
|
|
84
|
+
self.update(1)
|
|
85
|
+
finally:
|
|
86
|
+
self._finish()
|
|
87
|
+
|
|
88
|
+
# ------------------------------------------------------------------
|
|
89
|
+
# Public API
|
|
90
|
+
# ------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
def update(self, n: int = 1) -> None:
|
|
93
|
+
"""Advance the bar by *n* steps."""
|
|
94
|
+
self.n += n
|
|
95
|
+
self._render()
|
|
96
|
+
|
|
97
|
+
# ------------------------------------------------------------------
|
|
98
|
+
# Internal rendering
|
|
99
|
+
# ------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
def _finish(self) -> None:
|
|
102
|
+
if self._done:
|
|
103
|
+
return
|
|
104
|
+
self._done = True
|
|
105
|
+
self.n = self.total if self.total is not None else self.n
|
|
106
|
+
self._render(final=True)
|
|
107
|
+
if self.leave:
|
|
108
|
+
self.file.write("\n")
|
|
109
|
+
self.file.flush()
|
|
110
|
+
else:
|
|
111
|
+
self._clear()
|
|
112
|
+
|
|
113
|
+
def _clear(self) -> None:
|
|
114
|
+
self.file.write(f"\r{' ' * self._rendered_len}\r")
|
|
115
|
+
self.file.flush()
|
|
116
|
+
|
|
117
|
+
def _render(self, final: bool = False) -> None:
|
|
118
|
+
elapsed = time.time() - (self.start_time or time.time())
|
|
119
|
+
bar_str = self._build_bar()
|
|
120
|
+
pct_str = self._pct_str()
|
|
121
|
+
time_str = _fmt_time(elapsed)
|
|
122
|
+
count_str = f"{self.n}" + (f"/{self.total}" if self.total is not None else "")
|
|
123
|
+
|
|
124
|
+
if final and self.total and self.n >= self.total:
|
|
125
|
+
import random
|
|
126
|
+
suffix = f" {random.choice(_DONE_MSGS)}"
|
|
127
|
+
else:
|
|
128
|
+
suffix = f" {_RIBBITS[self._ribbit_index % len(_RIBBITS)]}"
|
|
129
|
+
self._ribbit_index += 1
|
|
130
|
+
|
|
131
|
+
line = f"\r{self.desc}: [{bar_str}] {pct_str} {time_str} [{count_str} {self.unit}]{suffix}"
|
|
132
|
+
self.file.write(line)
|
|
133
|
+
self.file.flush()
|
|
134
|
+
# track printable length (strip \r for width calc)
|
|
135
|
+
self._rendered_len = len(line) - 1
|
|
136
|
+
|
|
137
|
+
def _build_bar(self) -> str:
|
|
138
|
+
w = self.bar_width
|
|
139
|
+
if self.total and self.total > 0:
|
|
140
|
+
frac = min(self.n / self.total, 1.0)
|
|
141
|
+
pos = int(round(frac * (w - 1)))
|
|
142
|
+
else:
|
|
143
|
+
# bounce when total is unknown
|
|
144
|
+
cycle = (self.n % (w * 2)) if self.n else 0
|
|
145
|
+
pos = cycle if cycle < w else (w * 2 - 1 - cycle)
|
|
146
|
+
|
|
147
|
+
# Each cell is one character: lily pad or frog.
|
|
148
|
+
# Note: emoji are 2 columns wide in most terminals, so the visual
|
|
149
|
+
# width of the bar is ~2Γ the logical character count β but it
|
|
150
|
+
# still hops left to right correctly.
|
|
151
|
+
cells = [_LILY] * w
|
|
152
|
+
cells[pos] = _FROG
|
|
153
|
+
return "".join(cells)
|
|
154
|
+
|
|
155
|
+
def _pct_str(self) -> str:
|
|
156
|
+
if self.total:
|
|
157
|
+
return f"{min(self.n / self.total, 1.0) * 100:5.1f}%"
|
|
158
|
+
return " ? %"
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _fmt_time(seconds: float) -> str:
|
|
162
|
+
if seconds < 60:
|
|
163
|
+
return f"{seconds:4.1f}s"
|
|
164
|
+
m, s = divmod(int(seconds), 60)
|
|
165
|
+
if m < 60:
|
|
166
|
+
return f"{m:02d}:{s:02d}"
|
|
167
|
+
h, m = divmod(m, 60)
|
|
168
|
+
return f"{h:02d}:{m:02d}:{s:02d}"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import random
|
|
2
|
+
|
|
3
|
+
_QUOTES = [
|
|
4
|
+
("Leap before you look.", "Frog Thoreau"),
|
|
5
|
+
("The pond is half full.", "Kermit the Optimist"),
|
|
6
|
+
("I think, therefore I ribbit.", "RenΓ© Desfrogs"),
|
|
7
|
+
("To croak or not to croak, that is the question.", "William Shakespduck"),
|
|
8
|
+
("The greatest glory in living lies not in never falling, but in hopping back up every time we fall.", "Nelson Froggela"),
|
|
9
|
+
("In the middle of every difficulty lies a lily pad.", "Albert Einswamp"),
|
|
10
|
+
("It does not matter how slowly you hop, so long as you do not stop.", "Confrocius"),
|
|
11
|
+
("Be the frog you wish to see in the pond.", "Mahatma Ribbiti"),
|
|
12
|
+
("Tongue out and leapβthat is the whole of the frog.", "Swamp Hemingway"),
|
|
13
|
+
("The pond you seek is already beneath your webbed feet.", "Zen Bullfrog"),
|
|
14
|
+
("Not all those who wander are lost. Some are just looking for flies.", "J.R.R. Toadkien"),
|
|
15
|
+
("Ask not what your swamp can do for you; ask what you can do for your swamp.", "John Fitz-Frog Kennedy"),
|
|
16
|
+
("Float like a lily, sting like a tongue.", "Muhammud Pond-Ali"),
|
|
17
|
+
("Give a frog a fly and you feed him for a day. Teach a frog to catch flies and you feed him for a lifetime.", "Froze Tzu"),
|
|
18
|
+
("With great webbed feet comes great leaping ability.", "Uncle Tadpole"),
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_quote(index: int) -> tuple[str, str]:
|
|
23
|
+
"""Return the quote at the given index as (quote, author)."""
|
|
24
|
+
return _QUOTES[index % len(_QUOTES)]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_random_quote() -> tuple[str, str]:
|
|
28
|
+
"""Return a random frog quote as (quote, author)."""
|
|
29
|
+
return random.choice(_QUOTES)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def print_quote(index: int | None = None) -> None:
|
|
33
|
+
"""Print a frog quote, random if no index given."""
|
|
34
|
+
quote, author = get_random_quote() if index is None else get_quote(index)
|
|
35
|
+
print(f'"{quote}"\n β {author}')
|