MCPcopy Create free account
hub / github.com/Keeper-Security/Commander / Spinner

Class Spinner

keepercommander/display.py:256–327  ·  view source on GitHub ↗

Animated spinner for long-running operations. WARNING: every frame starts with '\\r' and overwrites the current console row with the frame, message and padding spaces. A spinner that is still (or again) ticking while other code prints can therefore erase chunks of large multi-row ou

Source from the content-addressed store, hash-verified

254
255
256class Spinner:
257 """Animated spinner for long-running operations.
258
259 WARNING: every frame starts with '\\r' and overwrites the current console
260 row with the frame, message and padding spaces. A spinner that is still
261 (or again) ticking while other code prints can therefore erase chunks of
262 large multi-row output - especially long single-line blobs like base64
263 KSM config tokens (`pam project import`/`pam gateway new` access_token),
264 silently corrupting what the user copies. Callers MUST guarantee stop()
265 via try/finally, and any change here must keep frames out of stopped
266 spinners and out of redirected/captured output.
267 """
268
269 # Claude-style spinner frames
270 FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
271
272 def __init__(self, message=""):
273 self.message = message
274 self.running = False
275 self.thread = None
276 self._last_visible_len = 0
277
278 def _animate(self):
279 idx = 0
280 while self.running:
281 frame = self.FRAMES[idx % len(self.FRAMES)]
282 message = self.message or ''
283 visible_len = len(message) + 2 # frame + space + message
284 pad = max(0, self._last_visible_len - visible_len)
285 # Re-check right before the write: a stale tick firing after
286 # stop() has returned (join timed out on a blocked console write)
287 # would '\r'-overwrite output printed in the meantime - erasing a
288 # row of large output such as a printed KSM config token.
289 # Skipping costs only one cosmetic frame.
290 if not self.running:
291 break
292 # Frames go to stderr (codebase convention for '\r' progress, see
293 # sox/aram/record_totp): on stdout they land inside redirected or
294 # captured command output, e.g. corrupting the base64 config in
295 # `pam project import ... > out.json`.
296 sys.stderr.write(f'\r{Fore.CYAN}{frame}{Fore.RESET} {message}' + (' ' * pad))
297 sys.stderr.flush()
298 self._last_visible_len = visible_len + pad
299 idx += 1
300 time.sleep(0.08)
301
302 def start(self):
303 # Spin only on a real console; in a redirected/captured stream the
304 # frames cannot animate and would pile up as '\r' noise in the data.
305 try:
306 if not sys.stderr.isatty():
307 return
308 except Exception:
309 return
310 self.running = True
311 self.thread = threading.Thread(target=self._animate, daemon=True)
312 self.thread.start()
313

Callers 1

sync_downFunction · 0.70

Calls

no outgoing calls

Tested by

no test coverage detected