From bf892e04d7a89a7c5e8a528d657d18d418a307f4 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 31 Jan 2026 22:53:59 +0000 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Add=20detailed=20?= =?UTF-8?q?dry-run=20plan=20output?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added `print_dry_run_plan` to display a user-friendly summary of changes during a dry run. - Updated `sync_profile` to call `print_dry_run_plan`. - Removed a duplicate definition of `render_progress_bar` (kept the one with docstring). - Added unit tests for the new output function. Co-authored-by: abhimehro <84992105+abhimehro@users.noreply.github.com> --- main.py | 52 ++++++++++++++--------- tests/test_palette_ux.py | 89 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 19 deletions(-) create mode 100644 tests/test_palette_ux.py diff --git a/main.py b/main.py index 86792da4..9b2e82de 100644 --- a/main.py +++ b/main.py @@ -159,25 +159,6 @@ def sanitize_for_log(text: Any) -> str: return safe -def render_progress_bar( - current: int, total: int, label: str, prefix: str = "šŸš€" -) -> None: - if not USE_COLORS or total == 0: - return - - width = 20 - 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.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: @@ -1033,6 +1014,38 @@ def _process_single_folder( return folder_success +def print_dry_run_plan(plan_entry: Dict[str, Any]) -> None: + profile = plan_entry["profile"] + folders = plan_entry["folders"] + + if USE_COLORS: + print( + f"\n{Colors.HEADER}šŸ“ Dry Run Plan for Profile: {Colors.BOLD}{profile}{Colors.ENDC}" + ) + print( + f"{Colors.CYAN} The following folders will be created/replaced:{Colors.ENDC}" + ) + else: + print(f"\nšŸ“ Dry Run Plan for Profile: {profile}") + print(" The following folders will be created/replaced:") + + if not folders: + print( + f" {Colors.WARNING}(No folders found to sync){Colors.ENDC}" + if USE_COLORS + else " (No folders found to sync)" + ) + + for folder in folders: + name = folder["name"] + rules = folder["rules"] + if USE_COLORS: + print(f" • {Colors.BOLD}{name}{Colors.ENDC}: {rules} rules") + else: + print(f" - {name}: {rules} rules") + print("") + + # --------------------------------------------------------------------------- # # 4. Main workflow # --------------------------------------------------------------------------- # @@ -1129,6 +1142,7 @@ def _fetch_if_valid(url: str): plan_accumulator.append(plan_entry) if dry_run: + print_dry_run_plan(plan_entry) log.info("Dry-run complete: no API calls were made.") return True diff --git a/tests/test_palette_ux.py b/tests/test_palette_ux.py new file mode 100644 index 00000000..16987fa6 --- /dev/null +++ b/tests/test_palette_ux.py @@ -0,0 +1,89 @@ +import sys +from unittest.mock import MagicMock +import main + +def test_print_dry_run_plan_with_colors(monkeypatch): + """Verify print_dry_run_plan output with colors enabled.""" + # Force colors on + monkeypatch.setattr(main, "USE_COLORS", True) + + # Since Colors class is evaluated at import time, we need to manually set color codes + # if the module was imported in a non-TTY environment + for attr, code in { + "HEADER": "\033[95m", + "BLUE": "\033[94m", + "CYAN": "\033[96m", + "GREEN": "\033[92m", + "WARNING": "\033[93m", + "FAIL": "\033[91m", + "ENDC": "\033[0m", + "BOLD": "\033[1m", + "UNDERLINE": "\033[4m", + }.items(): + monkeypatch.setattr(main.Colors, attr, code) + + # Mock stdout to capture print output + mock_stdout = MagicMock() + monkeypatch.setattr(sys, "stdout", mock_stdout) + + plan = { + "profile": "test_profile", + "folders": [ + {"name": "Test Folder 1", "rules": 10}, + {"name": "Test Folder 2", "rules": 20}, + ] + } + + main.print_dry_run_plan(plan) + + # Aggregate all writes + # print() typically calls write(string) and write('\n') + combined_output = "".join([str(args[0]) for args, _ in mock_stdout.write.call_args_list]) + + assert "šŸ“ Dry Run Plan for Profile:" in combined_output + assert "test_profile" in combined_output + assert "Test Folder 1" in combined_output + assert "10 rules" in combined_output + # ANSI codes should be present (main.Colors.HEADER starts with \033[95m) + assert "\033[" in combined_output + +def test_print_dry_run_plan_no_colors(monkeypatch): + """Verify print_dry_run_plan output without colors.""" + monkeypatch.setattr(main, "USE_COLORS", False) + + mock_stdout = MagicMock() + monkeypatch.setattr(sys, "stdout", mock_stdout) + + plan = { + "profile": "test_profile", + "folders": [ + {"name": "Test Folder 1", "rules": 10}, + ] + } + + main.print_dry_run_plan(plan) + + combined_output = "".join([str(args[0]) for args, _ in mock_stdout.write.call_args_list]) + + assert "šŸ“ Dry Run Plan for Profile:" in combined_output + assert "test_profile" in combined_output + assert "Test Folder 1" in combined_output + assert "10 rules" in combined_output + # No ANSI codes + assert "\033[" not in combined_output + +def test_print_dry_run_plan_empty_folders(monkeypatch): + """Verify output when no folders are present.""" + monkeypatch.setattr(main, "USE_COLORS", False) + mock_stdout = MagicMock() + monkeypatch.setattr(sys, "stdout", mock_stdout) + + plan = { + "profile": "test_profile", + "folders": [] + } + + main.print_dry_run_plan(plan) + + combined_output = "".join([str(args[0]) for args, _ in mock_stdout.write.call_args_list]) + assert "(No folders found to sync)" in combined_output From 19a419b254a6a17ed990b72740bb4e41fd749300 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 31 Jan 2026 23:00:16 +0000 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Add=20detailed=20?= =?UTF-8?q?dry-run=20plan=20output=20and=20fix=20security=20alert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added `print_dry_run_plan` to display a user-friendly summary of changes during a dry run. - Updated `sync_profile` to call `print_dry_run_plan`. - Used `sanitize_for_log` on printed user inputs to satisfy CodeQL security checks. - Removed a duplicate definition of `render_progress_bar`. - Added unit tests for the new output function. Co-authored-by: abhimehro <84992105+abhimehro@users.noreply.github.com> --- main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index 9b2e82de..6e21297c 100644 --- a/main.py +++ b/main.py @@ -1015,7 +1015,7 @@ def _process_single_folder( def print_dry_run_plan(plan_entry: Dict[str, Any]) -> None: - profile = plan_entry["profile"] + profile = sanitize_for_log(plan_entry["profile"]) folders = plan_entry["folders"] if USE_COLORS: @@ -1037,7 +1037,7 @@ def print_dry_run_plan(plan_entry: Dict[str, Any]) -> None: ) for folder in folders: - name = folder["name"] + name = sanitize_for_log(folder["name"]) rules = folder["rules"] if USE_COLORS: print(f" • {Colors.BOLD}{name}{Colors.ENDC}: {rules} rules") From 6f2f5cd405e199630fc35b8cfa0343ddfe087162 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 31 Jan 2026 23:03:32 +0000 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Add=20detailed=20?= =?UTF-8?q?dry-run=20plan=20output=20and=20fix=20security=20alert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added `print_dry_run_plan` to display a user-friendly summary of changes during a dry run. - Updated `sync_profile` to call `print_dry_run_plan`. - Used `sanitize_for_log` on printed user inputs to satisfy CodeQL security checks. - Removed a duplicate definition of `render_progress_bar`. - Added unit tests for the new output function. Co-authored-by: abhimehro <84992105+abhimehro@users.noreply.github.com> --- main.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index 6e21297c..7ababd63 100644 --- a/main.py +++ b/main.py @@ -1037,12 +1037,14 @@ def print_dry_run_plan(plan_entry: Dict[str, Any]) -> None: ) for folder in folders: - name = sanitize_for_log(folder["name"]) - rules = folder["rules"] + safe_name = sanitize_for_log(folder["name"]) + safe_rules = sanitize_for_log(folder["rules"]) if USE_COLORS: - print(f" • {Colors.BOLD}{name}{Colors.ENDC}: {rules} rules") + print( + f" • {Colors.BOLD}{safe_name}{Colors.ENDC}: {safe_rules} rules" + ) else: - print(f" - {name}: {rules} rules") + print(f" - {safe_name}: {safe_rules} rules") print("")