py-wait-until 0.0.4__tar.gz → 0.0.6__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: py-wait-until
3
- Version: 0.0.4
3
+ Version: 0.0.6
4
4
  Summary: Print a loading spinner while another command is running.
5
5
  Author: billwallis
6
6
  License-File: LICENSE
@@ -16,6 +16,7 @@ Description-Content-Type: text/markdown
16
16
 
17
17
  [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/billwallis/py-wait-until/main.svg)](https://results.pre-commit.ci/latest/github/billwallis/py-wait-until/main)
18
18
  [![GitHub last commit](https://img.shields.io/github/last-commit/billwallis/py-wait-until)](https://shields.io/badges/git-hub-last-commit)
19
+ [![PyPI Downloads](https://img.shields.io/pypi/dm/py-wait-until)](https://shields.io/badges/py-pi-downloads)
19
20
 
20
21
  </span>
21
22
 
@@ -61,3 +62,13 @@ Install [uv](https://docs.astral.sh/uv/getting-started/installation/) and then i
61
62
  ```shell
62
63
  uvx --from poethepoet poe install
63
64
  ```
65
+
66
+ ### Choose a spinner
67
+
68
+ There are a selection of spinner animations available which can be specified with the `--spinner` argument; check the CLI help for the latest set of options:
69
+
70
+ ```shell
71
+ py-wait-until --help
72
+ ```
73
+
74
+ By default, a spinner will be chosen randomly.
@@ -7,6 +7,7 @@
7
7
 
8
8
  [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/billwallis/py-wait-until/main.svg)](https://results.pre-commit.ci/latest/github/billwallis/py-wait-until/main)
9
9
  [![GitHub last commit](https://img.shields.io/github/last-commit/billwallis/py-wait-until)](https://shields.io/badges/git-hub-last-commit)
10
+ [![PyPI Downloads](https://img.shields.io/pypi/dm/py-wait-until)](https://shields.io/badges/py-pi-downloads)
10
11
 
11
12
  </span>
12
13
 
@@ -52,3 +53,13 @@ Install [uv](https://docs.astral.sh/uv/getting-started/installation/) and then i
52
53
  ```shell
53
54
  uvx --from poethepoet poe install
54
55
  ```
56
+
57
+ ### Choose a spinner
58
+
59
+ There are a selection of spinner animations available which can be specified with the `--spinner` argument; check the CLI help for the latest set of options:
60
+
61
+ ```shell
62
+ py-wait-until --help
63
+ ```
64
+
65
+ By default, a spinner will be chosen randomly.
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "py-wait-until"
7
- version = "0.0.4"
7
+ version = "0.0.6"
8
8
  description = "Print a loading spinner while another command is running."
9
9
  readme = "README.md"
10
10
  license-files = ["LICENSE"]
@@ -5,7 +5,7 @@ build-backend = "uv_build"
5
5
 
6
6
  [project]
7
7
  name = "py-wait-until"
8
- version = "0.0.4"
8
+ version = "0.0.6"
9
9
  description = "Print a loading spinner while another command is running."
10
10
  authors = [{name = "billwallis"}]
11
11
  readme = "README.md"
@@ -0,0 +1,15 @@
1
+ https://en.wikipedia.org/wiki/Braille_ASCII#Braille_ASCII_values
2
+
3
+ |⠁|⠂|⠄|⠠|⠐|⠈| |
4
+
5
+ |⠃|⠆|⠘|⠰|⠉|⠒|⠤|
6
+ |⠅|⠡|⠑|⠨|⠌|⠊|⠢|⠔|
7
+
8
+ |⠇|⠸|⠋|⠙|⠦|⠴|
9
+ |⠩|⠍|⠥|⠬|⠕|⠪|
10
+ |⠓|⠚|⠲|⠖|⠱|⠎|⠣|⠜|
11
+
12
+ |⠏|⠧|⠼|⠹|⠶|⠛|⠭|
13
+ |⠗|⠺|⠞|⠳|⠫|⠝|⠵|⠮|
14
+
15
+ |⠾|⠽|⠻|⠟|⠯|⠷|⠿|
@@ -1,4 +1,5 @@
1
1
  import argparse
2
+ import contextlib
2
3
  import enum
3
4
  import random
4
5
  import subprocess
@@ -17,11 +18,16 @@ class Spinner(enum.StrEnum):
17
18
  LINE = "line"
18
19
  DOTS = "dots"
19
20
  SAND = "sand"
21
+ CLIMB = "climb"
22
+ EIGHT_WIDTH_2 = "eight-2"
23
+ EIGHT_WIDTH_3 = "eight-3"
24
+ EIGHT_WIDTH_4 = "eight-4"
20
25
 
21
26
  def chars(self) -> Sequence[str]:
22
27
  return {
23
28
  # Basic/simple option
24
29
  self.LINE: ["|", "/", "-", "\\"],
30
+ # https://en.wikipedia.org/wiki/Braille_ASCII#Braille_ASCII_values
25
31
  # Inspired by Docker and dbt Fusion's loading indicator
26
32
  self.DOTS: [
27
33
  *["⠄", "⠆", "⠖", "⠶", "⠲", "⠰"],
@@ -29,6 +35,21 @@ class Spinner(enum.StrEnum):
29
35
  *["⠐", "⠰", "⠴", "⠶", "⠦", "⠆"],
30
36
  *["⠂", "⠒", "⠲", "⠶", "⠴", "⠤"],
31
37
  ],
38
+ self.CLIMB: [
39
+ *["⠄", "⠤", "⠴", "⠶", "⠷", "⠿"],
40
+ *["⠾", "⠶", "⠦", "⠤", "⠠", " "],
41
+ ],
42
+ self.EIGHT_WIDTH_2: [
43
+ *["⠤", "⠰", "⠒", "⠃"],
44
+ *["⠉", "⠘", "⠒", "⠆"],
45
+ ],
46
+ self.EIGHT_WIDTH_3: [
47
+ *["⠦", "⠴", "⠲", "⠓", "⠋", "⠙", "⠚", "⠖"],
48
+ ],
49
+ self.EIGHT_WIDTH_4: [
50
+ *["⠛", "⠏", "⠭", "⠼"],
51
+ *["⠶", "⠧", "⠭", "⠹"],
52
+ ],
32
53
  # https://stackoverflow.com/a/12305221/8213085
33
54
  # https://raw.githubusercontent.com/sindresorhus/cli-spinners/master/spinners.json
34
55
  self.SAND: [
@@ -61,19 +82,23 @@ def _spinner(chars: Sequence[str]) -> Generator[str]:
61
82
  yield chars[index % len(chars)]
62
83
 
63
84
 
85
+ @contextlib.contextmanager
86
+ def hide_cursor(is_tty: bool) -> Generator[None]:
87
+ if is_tty:
88
+ _write_line(sys.stdout, ANSI_HIDE_CURSOR)
89
+
90
+ yield
91
+
92
+ if is_tty:
93
+ _write_line(sys.stdout, ANSI_SHOW_CURSOR)
94
+
95
+
64
96
  def _wait_for_process(
65
97
  cmd: list[str],
66
98
  message: str,
67
99
  spinner: Spinner,
100
+ is_tty: bool,
68
101
  ) -> int:
69
- """
70
- Execute a command and display a waiting message until it completes.
71
- """
72
-
73
- # Only show spinner if stdout is a TTY (interactive terminal)
74
- if is_tty := sys.stdout.isatty():
75
- _write_line(sys.stdout, ANSI_HIDE_CURSOR)
76
-
77
102
  process = subprocess.Popen( # noqa: S603
78
103
  cmd,
79
104
  stdout=subprocess.PIPE,
@@ -92,7 +117,6 @@ def _wait_for_process(
92
117
 
93
118
  if is_tty:
94
119
  _write_line(sys.stdout, f"\r{ANSI_CLEAR_LINE}")
95
- _write_line(sys.stdout, ANSI_SHOW_CURSOR)
96
120
 
97
121
  stdout, stderr = process.communicate()
98
122
  if stdout:
@@ -103,6 +127,32 @@ def _wait_for_process(
103
127
  return process.returncode
104
128
 
105
129
 
130
+ def _wait_until(
131
+ cmd: list[str],
132
+ message: str,
133
+ spinner: Spinner,
134
+ ) -> int:
135
+ """
136
+ Execute a command and display a waiting message until it completes.
137
+ """
138
+
139
+ # TODO: If _not_ a TTY, then just run the process; else run `_wait_for_process`?
140
+ # Only show spinner if stdout is a TTY (interactive terminal)
141
+ is_tty = sys.stdout.isatty()
142
+ with hide_cursor(is_tty=is_tty):
143
+ try:
144
+ return _wait_for_process(
145
+ cmd=cmd,
146
+ message=message,
147
+ spinner=spinner,
148
+ is_tty=is_tty,
149
+ )
150
+ # We purposefully want to catch _everything_, including e.g. `KeyboardInterrupt`
151
+ except BaseException as err:
152
+ _write_line(sys.stderr, str(err))
153
+ return 1
154
+
155
+
106
156
  def main(argv: Sequence[str] | None = None) -> int:
107
157
  """
108
158
  Print a loading spinner while another process is running.
@@ -138,7 +188,7 @@ def main(argv: Sequence[str] | None = None) -> int:
138
188
  parser.print_help()
139
189
  return 1
140
190
 
141
- return _wait_for_process(
191
+ return _wait_until(
142
192
  cmd=args.command,
143
193
  message=args.message,
144
194
  spinner=args.spinner,
File without changes