Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .Jules/palette.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@
## 2024-05-24 - Fail Fast & Friendly
**Learning:** In CLI tools involving APIs, cascade failures (hundreds of "Failed to X") caused by basic auth issues (401/403) are overwhelming and confusing. A dedicated "Pre-flight Check" that validates credentials *before* attempting the main workload allows for specific, actionable error messages (e.g. "Check your token at [URL]") instead of generic HTTP errors.
**Action:** Implement a `check_api_access()` step at the start of any CLI workflow to validate permissions and provide human-readable guidance on failure.

## 2024-05-25 - Consistent Progress Feedback
**Learning:** Inconsistent progress indicators (some text-based, some bars) degrade the perceived polish of a CLI tool. Centralizing progress logic into a single helper ensures consistent visuals (bars vs counters) and reliable terminal handling (clearing lines properly) across different operations.
**Action:** Implement a reusable `render_progress_bar` function that handles ANSI codes and math safety, rather than re-implementing loops for each task.
36 changes: 20 additions & 16 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,21 @@
return safe


def render_progress_bar(current: int, total: int, label: str, prefix: str = "πŸš€") -> None:

Check warning

Code scanning / Pylint (reported by Codacy)

Missing function docstring Warning

Missing function docstring

Check warning

Code scanning / Pylintpython3 (reported by Codacy)

Missing function or method docstring Warning

Missing function or method docstring
if not USE_COLORS or total == 0:
return

width = 20
progress = min(1.0, current / total)
filled = int(width * progress)
bar = "β–ˆ" * filled + "β–‘" * (width - filled)

Check warning

Code scanning / Pylint (reported by Codacy)

Black listed name "bar" Warning

Black listed name "bar"

Check warning

Code scanning / Pylintpython3 (reported by Codacy)

Black listed name "bar" Warning

Black listed name "bar"
percent = int(progress * 100)

# Use \033[K to clear line residue

Check warning

Code scanning / Prospector (reported by Codacy)

Black listed name "bar" (blacklisted-name) Warning

Black listed name "bar" (blacklisted-name)
sys.stderr.write(f"\r\033[K{Colors.CYAN}{prefix} {label}: [{bar}] {percent}% ({current}/{total}){Colors.ENDC}")

Check warning

Code scanning / Pylint (reported by Codacy)

Line too long (115/100) Warning

Line too long (115/100)

Check warning

Code scanning / Pylintpython3 (reported by Codacy)

Line too long (115/100) Warning

Line too long (115/100)
sys.stderr.flush()


def countdown_timer(seconds: int, message: str = "Waiting") -> None:
"""Shows a countdown timer if strictly in a TTY, otherwise just sleeps."""
if not USE_COLORS:
Expand Down Expand Up @@ -481,16 +496,10 @@
with concurrent.futures.ThreadPoolExecutor() as executor:
futures = {executor.submit(_gh_get, url): url for url in urls_to_fetch}

if USE_COLORS:
sys.stderr.write(f"\r{Colors.CYAN}⏳ Warming up cache: 0/{total}...{Colors.ENDC}")
sys.stderr.flush()
render_progress_bar(0, total, "Warming up cache", prefix="⏳")

for future in concurrent.futures.as_completed(futures):
completed += 1
if USE_COLORS:
sys.stderr.write(f"\r{Colors.CYAN}⏳ Warming up cache: {completed}/{total}...{Colors.ENDC}")
sys.stderr.flush()

try:
future.result()
except Exception as e:
Expand All @@ -501,13 +510,10 @@

log.warning(f"Failed to pre-fetch {sanitize_for_log(futures[future])}: {e}")

if USE_COLORS:
# Restore progress
sys.stderr.write(f"\r{Colors.CYAN}⏳ Warming up cache: {completed}/{total}...{Colors.ENDC}")
sys.stderr.flush()
render_progress_bar(completed, total, "Warming up cache", prefix="⏳")

if USE_COLORS:
sys.stderr.write(f"\r{Colors.GREEN}βœ… Warming up cache: {total}/{total} Done! {Colors.ENDC}\n")
sys.stderr.write(f"\r\033[K{Colors.GREEN}βœ… Warming up cache: Done!{Colors.ENDC}\n")
sys.stderr.flush()

def delete_folder(client: httpx.Client, profile_id: str, name: str, folder_id: str) -> bool:
Expand Down Expand Up @@ -670,13 +676,11 @@
successful_batches += 1
existing_rules.update(result)

if USE_COLORS:
sys.stderr.write(f"\r{Colors.CYAN}πŸš€ Folder {sanitize_for_log(folder_name)}: Pushing batch {successful_batches}/{total_batches}...{Colors.ENDC}")
sys.stderr.flush()
render_progress_bar(successful_batches, total_batches, f"Folder {sanitize_for_log(folder_name)}")

Check warning

Code scanning / Pylint (reported by Codacy)

Line too long (109/100) Warning

Line too long (109/100)

Check warning

Code scanning / Pylintpython3 (reported by Codacy)

Line too long (109/100) Warning

Line too long (109/100)

if successful_batches == total_batches:
if USE_COLORS:
sys.stderr.write(f"\r{Colors.GREEN}βœ… Folder {sanitize_for_log(folder_name)}: Finished ({len(filtered_hostnames)} rules) {Colors.ENDC}\n")
sys.stderr.write(f"\r\033[K{Colors.GREEN}βœ… Folder {sanitize_for_log(folder_name)}: Finished ({len(filtered_hostnames)} rules){Colors.ENDC}\n")

Check warning

Code scanning / Pylint (reported by Codacy)

Line too long (154/100) Warning

Line too long (154/100)

Check warning

Code scanning / Pylintpython3 (reported by Codacy)

Line too long (154/100) Warning

Line too long (154/100)
sys.stderr.flush()
else:
log.info("Folder %s – finished (%d new rules added)", sanitize_for_log(folder_name), len(filtered_hostnames))
Expand Down
Loading