Skip to content

Add colorless menu #6

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

Merged
merged 3 commits into from
Jun 19, 2025
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
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,11 +309,13 @@ export _GIT_BRANCH="master"

### Color Themes

You can change to the legacy color scheme by toggling the variable
`_MENU_THEME` between `default` and `legacy`
You can change to the legacy color scheme by toggling the variable `_MENU_THEME` between `default` and `legacy`.
You can completely disable the color theme by setting the `_MENU_THEME` variable to `none`.

```bash
export _MENU_THEME="legacy"
# or
export _MENU_THEME="none"
```

## Contributing
Expand Down
2 changes: 2 additions & 0 deletions git_py_stats/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ def get_config() -> Dict[str, Union[str, int]]:
menu_theme: Optional[str] = os.environ.get("_MENU_THEME")
if menu_theme == "legacy":
config["menu_theme"] = "legacy"
elif menu_theme == "none":
config["menu_theme"] = "none"
else:
config["menu_theme"] = ""

Expand Down
9 changes: 8 additions & 1 deletion git_py_stats/menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def interactive_menu(config: Dict[str, Union[str, int]]) -> str:
WHITE = "\033[37m"
CYAN = "\033[36m"

# Handle default and legacy menu
# Handle default, legacy, and colorless menu
theme = config.get("menu_theme", "")

if theme == "legacy":
Expand All @@ -33,6 +33,13 @@ def interactive_menu(config: Dict[str, Union[str, int]]) -> str:
NUMS = f"{BOLD}{YELLOW}"
HELP_TXT = f"{NORMAL}{YELLOW}"
EXIT_TXT = f"{BOLD}{RED}"
elif theme == "none":
TITLES = BOLD
TEXT = ""
NUMS = BOLD
HELP_TXT = ""
EXIT_TXT = BOLD
NORMAL = ""
else:
TITLES = f"{BOLD}{CYAN}"
TEXT = f"{NORMAL}{WHITE}"
Expand Down
55 changes: 55 additions & 0 deletions git_py_stats/tests/test_menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ def setUp(self):
# Mock configurations for testing
self.config_default = {} # Default theme
self.config_legacy = {"menu_theme": "legacy"} # Legacy theme
self.config_none = {"menu_theme": "none"} # Alternate colorless theme alias

# Test cases for default theme
@patch("builtins.input", return_value="1")
@patch("sys.stdout", new_callable=StringIO)
def test_default_theme_option_1(self, mock_stdout, mock_input):
Expand Down Expand Up @@ -76,6 +78,57 @@ def test_default_theme_invalid_input(self, mock_stdout, mock_input):
output = strip_ansi_codes(mock_stdout.getvalue())
self.assertIn("Generate:", output)

@patch("builtins.input", return_value="3")
@patch("sys.stdout", new_callable=StringIO)
def test_none_theme_option_3(self, mock_stdout, mock_input):
"""
Test the interactive_menu with 'none' theme and user selects option '3'.
"""
choice = interactive_menu(self.config_none)
self.assertEqual(choice, "3")
output = mock_stdout.getvalue()
self.assertNotIn("\033[31m", output)
self.assertNotIn("\033[33m", output)
self.assertNotIn("\033[36m", output)
self.assertIn("\033[1m", output)

@patch("builtins.input", return_value="1")
@patch("sys.stdout", new_callable=StringIO)
def test_none_theme_option_1(self, mock_stdout, mock_input):
"""
Test the interactive_menu with 'none' theme and user selects option '1'.
"""
choice = interactive_menu(self.config_none)
self.assertEqual(choice, "1")
output = mock_stdout.getvalue()
self.assertNotIn("\033[31m", output)
self.assertNotIn("\033[33m", output)
self.assertNotIn("\033[36m", output)
self.assertIn("\033[1m", output)

@patch("builtins.input", return_value="")
@patch("sys.stdout", new_callable=StringIO)
def test_none_theme_exit(self, mock_stdout, mock_input):
"""
Test the interactive_menu with none theme and user presses Enter to exit.
"""
choice = interactive_menu(self.config_none)
self.assertEqual(choice, "")
output = strip_ansi_codes(mock_stdout.getvalue())
self.assertIn("press Enter to exit", output)

@patch("builtins.input", return_value="invalid")
@patch("sys.stdout", new_callable=StringIO)
def test_none_theme_invalid_input(self, mock_stdout, mock_input):
"""
Test the interactive_menu with none theme and user enters an invalid option.
"""
choice = interactive_menu(self.config_none)
self.assertEqual(choice, "invalid")
output = strip_ansi_codes(mock_stdout.getvalue())
self.assertIn("Generate:", output)

# Test cases for legacy theme
@patch("builtins.input", return_value="1")
@patch("sys.stdout", new_callable=StringIO)
def test_legacy_theme_option_1(self, mock_stdout, mock_input):
Expand Down Expand Up @@ -121,6 +174,7 @@ def test_legacy_theme_invalid_input(self, mock_stdout, mock_input):
output = strip_ansi_codes(mock_stdout.getvalue())
self.assertIn("Generate:", output)

# Test cases for handling multiple inputs and edge cases
@patch("builtins.input", side_effect=["1", ""])
@patch("sys.stdout", new_callable=StringIO)
def test_multiple_inputs(self, mock_stdout, mock_input):
Expand All @@ -145,6 +199,7 @@ def test_input_with_whitespace(self, mock_stdout, mock_input):
output = strip_ansi_codes(mock_stdout.getvalue())
self.assertIn("5) My daily status", output)

# Test cases for handling exit commands
@patch("builtins.input", return_value="QUIT")
@patch("sys.stdout", new_callable=StringIO)
def test_input_quit(self, mock_stdout, mock_input):
Expand Down