light-client 2.0.0__py3-none-any.whl

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.
@@ -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}"
light_client/quotes.py ADDED
@@ -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}')
@@ -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,7 @@
1
+ light_client/__init__.py,sha256=T3C6yzFIS50thUREA8E5Ul5ArqR6o5lE73gJb41Nbfo,379
2
+ light_client/ascii_art.py,sha256=MbJh5sruN4cZhSRqoRsQOIcjR6xk0EbkOTQDtXoe9gU,1678
3
+ light_client/progress.py,sha256=PRWPcn_vaCml3rRQuF4fYr5Dz46KF_OTj67cXqphuHE,4945
4
+ light_client/quotes.py,sha256=u6i0L6ndUkhwrsd1T0Yf7Eo5tHf5iRZqeasXzcsak6A,1795
5
+ light_client-2.0.0.dist-info/METADATA,sha256=TEdRjad47XvrBh3HpSdPBKhCkl3vyLrrYeCvUrIQn0g,2202
6
+ light_client-2.0.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
7
+ light_client-2.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any