-
Notifications
You must be signed in to change notification settings - Fork 1
π¨ Palette: Improved CLI Feedback & Visuals #144
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| ## 2026-01-29 - CLI UX Patterns | ||
| **Learning:** CLI tools often suffer from "duplicate feedback" where a validator logs an error AND the input loop prints a generic error. | ||
| **Action:** Silence the generic error if the validator provides specific feedback, or direct the user to the specific errors. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -162,10 +162,11 @@ | |
| def render_progress_bar( | ||
| current: int, total: int, label: str, prefix: str = "π" | ||
| ) -> None: | ||
| """Renders a progress bar to stderr if USE_COLORS is True.""" | ||
| if not USE_COLORS or total == 0: | ||
| return | ||
|
|
||
| width = 20 | ||
| width = 30 | ||
| progress = min(1.0, current / total) | ||
| filled = int(width * progress) | ||
| bar = "β" * filled + "β" * (width - filled) | ||
|
|
@@ -184,7 +185,7 @@ | |
| time.sleep(seconds) | ||
| return | ||
|
|
||
| width = 15 | ||
| width = 30 | ||
| for remaining in range(seconds, 0, -1): | ||
| progress = (seconds - remaining + 1) / seconds | ||
| filled = int(width * progress) | ||
|
|
@@ -195,27 +196,7 @@ | |
| sys.stderr.flush() | ||
| time.sleep(1) | ||
|
|
||
| sys.stderr.write(f"\r\033[K{Colors.GREEN}β {message}: Done!{Colors.ENDC}\n") | ||
| sys.stderr.flush() | ||
|
|
||
|
|
||
| def render_progress_bar( | ||
| current: int, total: int, label: str, prefix: str = "π" | ||
| ) -> None: | ||
| """Renders a progress bar to stderr if USE_COLORS is True.""" | ||
| if not USE_COLORS or total == 0: | ||
| return | ||
|
|
||
| width = 15 | ||
| progress = min(1.0, current / total) | ||
| filled = int(width * progress) | ||
| bar = "β" * filled + "β" * (width - filled) | ||
| percent = int(progress * 100) | ||
|
|
||
| # Use \033[K to clear line residue | ||
| sys.stderr.write( | ||
| f"\r\033[K{Colors.CYAN}{prefix} {label}: [{bar}] {percent}% ({current}/{total}){Colors.ENDC}" | ||
| ) | ||
| sys.stderr.write(f"\r\033[K{Colors.GREEN}β {message}: Complete!{Colors.ENDC}\n") | ||
| sys.stderr.flush() | ||
|
|
||
|
|
||
|
|
@@ -409,9 +390,13 @@ | |
| if not is_valid_profile_id_format(profile_id): | ||
| if log_errors: | ||
| if not re.match(r"^[a-zA-Z0-9_-]+$", profile_id): | ||
| log.error("Invalid profile ID format (contains unsafe characters)") | ||
| log.error( | ||
Check noticeCode scanning / Pylintpython3 (reported by Codacy) Use lazy % formatting in logging functions Note
Use lazy % formatting in logging functions
|
||
| f"Invalid profile ID {sanitize_for_log(profile_id)}: contains unsafe characters (allowed: A-Z, a-z, 0-9, _, -)" | ||
Check warningCode scanning / Pylintpython3 (reported by Codacy) Line too long (131/100) Warning
Line too long (131/100)
Check warningCode scanning / Pylint (reported by Codacy) Line too long (131/100) Warning
Line too long (131/100)
|
||
| ) | ||
| elif len(profile_id) > 64: | ||
| log.error("Invalid profile ID length (max 64 chars)") | ||
| log.error( | ||
|
||
| f"Invalid profile ID {sanitize_for_log(profile_id)}: too long (max 64 chars)" | ||
| ) | ||
| return False | ||
| return True | ||
|
|
||
|
|
@@ -850,7 +835,7 @@ | |
| def push_rules( | ||
| profile_id: str, | ||
| folder_name: str, | ||
| folder_id: str, | ||
Check noticeCode scanning / Pylintpython3 (reported by Codacy) Use lazy % formatting in logging functions Note
Use lazy % formatting in logging functions
|
||
| do: int, | ||
| status: int, | ||
| hostnames: List[str], | ||
|
|
@@ -1092,7 +1077,7 @@ | |
| grp = folder_data["group"] | ||
| name = grp["group"].strip() | ||
|
|
||
| if "rule_groups" in folder_data: | ||
Check noticeCode scanning / Pylintpython3 (reported by Codacy) Use lazy % formatting in logging functions Note
Use lazy % formatting in logging functions
|
||
| # Multi-action format | ||
| total_rules = sum( | ||
| len(rg.get("rules", [])) for rg in folder_data["rule_groups"] | ||
|
|
@@ -1219,7 +1204,7 @@ | |
| "--profiles", help="Comma-separated list of profile IDs", default=None | ||
| ) | ||
| parser.add_argument( | ||
| "--folder-url", action="append", help="Folder JSON URL(s)", default=None | ||
Check warningCode scanning / Prospector (reported by Codacy) Use lazy % formatting in logging functions (logging-fstring-interpolation) Warning
Use lazy % formatting in logging functions (logging-fstring-interpolation)
|
||
| ) | ||
| parser.add_argument("--dry-run", action="store_true", help="Plan only") | ||
| parser.add_argument( | ||
|
|
@@ -1249,13 +1234,13 @@ | |
| def validate_profile_input(value: str) -> bool: | ||
| ids = [extract_profile_id(p) for p in value.split(",") if p.strip()] | ||
| return bool(ids) and all( | ||
|
Comment on lines
1234
to
1236
|
||
| validate_profile_id(pid, log_errors=False) for pid in ids | ||
| validate_profile_id(pid, log_errors=True) for pid in ids | ||
| ) | ||
|
|
||
| p_input = get_validated_input( | ||
| f"{Colors.BOLD}Enter Control D Profile ID:{Colors.ENDC} ", | ||
| validate_profile_input, | ||
| "Invalid ID(s) or URL(s). Must be a valid Profile ID or a Control D Profile URL. Comma-separate for multiple.", | ||
| "Please enter valid Profile ID(s). See errors above.", | ||
| ) | ||
| profile_ids = [ | ||
| extract_profile_id(p) for p in p_input.split(",") if p.strip() | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The width
30is now used here and incountdown_timeron line 188. To improve maintainability and avoid magic numbers, consider defining this as a constant in the constants section (e.g.,PROGRESS_BAR_WIDTH = 30) and using it in both places.