mirror of
https://github.com/trustedsec/hate_crack.git
synced 2026-07-28 14:47:22 -07:00
- Remove review-artifact "Defect 2" comment in main.py:2076; replace with accurate description of the invalid-cap fallback. Same label removed from the test section header in test_ollama_wordlist_sampling.py. - Lift nested _usable helper to module-level _usable_plaintext in main.py, placed alongside the other private wordlist helpers after _wordlist_path. Add six direct unit tests covering blank, whitespace, plain, hash:pw, multi-colon, and empty-plaintext cases. - Move spinner line-clear before thread.join() in progress.py so the terminal is cleaned up even if join() is interrupted by a DoubleInterrupt/second Ctrl-C. Add test_tty_clears_line_even_if_join_raises to cover this path. - Replace time.sleep-based elapsed-counter test with a deterministic version that patches time.monotonic; assert on actual rendered format with r"\d+s". Drop the misleading 0.05s sleep in test_tty_clears_line_on_exit (shorter than one tick, so no frame was ever guaranteed). - Add missing ollama* keys to hate_crack/config.json.example (package-data copy); root config.json.example already had them. Co-Authored-By: Claude <noreply@anthropic.com>
71 lines
2.4 KiB
Python
71 lines
2.4 KiB
Python
"""Generic terminal progress utilities for hate_crack.
|
|
|
|
Provides a context manager that shows a live spinner with elapsed-seconds
|
|
counter while a blocking operation runs. Safe to use in non-TTY environments:
|
|
if stdout is not a TTY the message is printed once and no background thread is
|
|
started.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
import threading
|
|
import time
|
|
from collections.abc import Generator
|
|
from contextlib import contextmanager
|
|
|
|
_SPINNER_FRAMES = ["|", "/", "-", "\\"]
|
|
_TICK_INTERVAL = 0.12 # seconds between repaints (~120 ms)
|
|
|
|
|
|
@contextmanager
|
|
def spinner(message: str) -> "Generator[None, None, None]":
|
|
"""Context manager that shows *message* plus a live elapsed-seconds counter.
|
|
|
|
While the body executes a daemon thread repaints a single terminal line
|
|
roughly every 120 ms showing:
|
|
|
|
| Generating password candidates via Ollama (model)... 3s
|
|
|
|
On exit (normal or exceptional) the spinner line is erased so subsequent
|
|
output starts on a clean line.
|
|
|
|
TTY guard: if ``sys.stdout.isatty()`` is False the message is printed once
|
|
via ``print()`` and no thread is started, keeping piped output and the test
|
|
suite clean.
|
|
"""
|
|
if not sys.stdout.isatty():
|
|
print(message)
|
|
yield
|
|
return
|
|
|
|
stop_event = threading.Event()
|
|
start_time = time.monotonic()
|
|
|
|
def _run() -> None:
|
|
frame_idx = 0
|
|
while not stop_event.is_set():
|
|
elapsed = int(time.monotonic() - start_time)
|
|
frame = _SPINNER_FRAMES[frame_idx % len(_SPINNER_FRAMES)]
|
|
line = f"\r{frame} {message} {elapsed}s"
|
|
sys.stdout.write(line)
|
|
sys.stdout.flush()
|
|
frame_idx += 1
|
|
stop_event.wait(_TICK_INTERVAL)
|
|
|
|
thread = threading.Thread(target=_run, daemon=True)
|
|
thread.start()
|
|
try:
|
|
yield
|
|
finally:
|
|
stop_event.set()
|
|
# Clear the spinner line *before* joining so the terminal is left clean
|
|
# even if join() is interrupted by a second Ctrl-C (DoubleInterrupt).
|
|
# hate_crack's _sigint_handler raises DoubleInterrupt on a second SIGINT
|
|
# within 2 s, and join() can block up to _TICK_INTERVAL (0.12 s) — a
|
|
# narrow but real window. Writing the escape sequence first ensures the
|
|
# line is always erased regardless of what happens to the join.
|
|
sys.stdout.write("\033[2K\r")
|
|
sys.stdout.flush()
|
|
thread.join()
|