diff --git a/src/gitfetch/cli.py b/src/gitfetch/cli.py index 3480e84..f88d27a 100644 --- a/src/gitfetch/cli.py +++ b/src/gitfetch/cli.py @@ -44,6 +44,18 @@ def parse_args() -> argparse.Namespace: help="GitHub personal access token (optional, increases rate limits)" ) + parser.add_argument( + "--spaced", + action="store_true", + help="Enable spaced layout" + ) + + parser.add_argument( + "--not-spaced", + action="store_true", + help="Disable spaced layout" + ) + parser.add_argument( "--version", action="store_true", @@ -92,7 +104,13 @@ def main() -> int: cache_expiry = config_manager.get_cache_expiry_hours() cache_manager = CacheManager(cache_expiry_hours=cache_expiry) fetcher = GitHubFetcher() # Uses gh CLI, no token needed - formatter = DisplayFormatter() + formatter = DisplayFormatter(config_manager) + if args.spaced: + spaced = True + elif args.not_spaced: + spaced = False + else: + spaced = True # Handle cache clearing if args.clear_cache: @@ -126,8 +144,12 @@ def main() -> int: username) stale_stats = cache_manager.get_stale_cached_stats(username) if stale_user_data is not None and stale_stats is not None: - formatter.display(username, stale_user_data, stale_stats) - print("\nšŸ”„ Refreshing data in background...", file=sys.stderr) + # Display stale cache immediately + formatter.display(username, stale_user_data, stale_stats, spaced=spaced) + print("\nšŸ”„ Refreshing data in background...", + file=sys.stderr) + + # Refresh cache in background (don't wait for it) import threading def refresh_cache(): @@ -148,7 +170,11 @@ def refresh_cache(): user_data = fetcher.fetch_user_data(username) stats = fetcher.fetch_user_stats(username, user_data) cache_manager.cache_user_data(username, user_data, stats) - formatter.display(username, user_data, stats) + # else: fresh cache available, proceed to display + + # Display the results + formatter.display(username, user_data, stats, spaced=spaced) + return 0 except Exception as e: print(f"Error: {e}", file=sys.stderr) diff --git a/src/gitfetch/config.py b/src/gitfetch/config.py index 15db44e..1c3f191 100644 --- a/src/gitfetch/config.py +++ b/src/gitfetch/config.py @@ -25,14 +25,42 @@ def _ensure_config_dir(self) -> None: def _load_config(self) -> None: """Load configuration from file.""" + default_colors = { + 'reset': '\\033[0m', + 'bold': '\\033[1m', + 'dim': '\\033[2m', + 'red': '\\033[91m', + 'green': '\\033[92m', + 'yellow': '\\033[93m', + 'blue': '\\033[94m', + 'magenta': '\\033[95m', + 'cyan': '\\033[96m', + 'white': '\\033[97m', + 'orange': '\\033[38;2;255;165;0m', + 'accent': '\\033[1m', + 'header': '\\033[38;2;118;215;161m', + 'muted': '\\033[2m', + '0': '\\033[48;5;238m', + '1': '\\033[48;5;28m', + '2': '\\033[48;5;34m', + '3': '\\033[48;5;40m', + '4': '\\033[48;5;82m' + } if self.CONFIG_FILE.exists(): self.config.read(self.CONFIG_FILE) + if "COLORS" in self.config: + self.config._sections['COLORS'] = {**default_colors, **self.config._sections['COLORS']} + else: + self.config._sections['COLORS'] = default_colors else: # Create default config self.config['DEFAULT'] = { 'username': '', 'cache_expiry_hours': '24' } + self.config._sections['COLORS'] = default_colors + for k,v in self.config._sections['COLORS'].items(): + self.config._sections['COLORS'][k] = v.encode('utf-8').decode('unicode_escape') def get_default_username(self) -> Optional[str]: """ @@ -44,6 +72,15 @@ def get_default_username(self) -> Optional[str]: username = self.config.get('DEFAULT', 'username', fallback='') return username if username else None + def get_colors(self) -> dict: + """ + Get colors + + Returns: + User defined colors or default colors if not set + """ + return self.config._sections["COLORS"] + def set_default_username(self, username: str) -> None: """ Set the default GitHub username in config. diff --git a/src/gitfetch/display.py b/src/gitfetch/display.py index 6b1b2fd..13c84eb 100644 --- a/src/gitfetch/display.py +++ b/src/gitfetch/display.py @@ -8,18 +8,19 @@ import re import unicodedata from datetime import datetime - +from .config import ConfigManager class DisplayFormatter: """Formats and displays GitHub stats in a neofetch-style layout.""" - def __init__(self): + def __init__(self,config_manager: ConfigManager): """Initialize the display formatter.""" self.terminal_width = shutil.get_terminal_size().columns self.enable_color = sys.stdout.isatty() + self.colors = config_manager.get_colors() def display(self, username: str, user_data: Dict[str, Any], - stats: Dict[str, Any]) -> None: + stats: Dict[str, Any],spaced=True) -> None: """ Display GitHub statistics in neofetch style. @@ -27,19 +28,20 @@ def display(self, username: str, user_data: Dict[str, Any], username: GitHub username user_data: User profile data stats: User statistics data + spaced: Spaced layout """ # Determine layout based on terminal width layout = self._determine_layout() if layout == 'minimal': # Only show contribution graph - self._display_minimal(username, stats) + self._display_minimal(username, stats,spaced) elif layout == 'compact': # Show graph and key info - self._display_compact(username, user_data, stats) + self._display_compact(username, user_data, stats,spaced) else: # Full layout with all sections - self._display_full(username, user_data, stats) + self._display_full(username, user_data, stats,spaced) print() # Empty line at the end @@ -52,20 +54,21 @@ def _determine_layout(self) -> str: else: return 'full' - def _display_minimal(self, username: str, stats: Dict[str, Any]) -> None: + def _display_minimal(self, username: str, stats: Dict[str, Any], spaced=True) -> None: """Display only contribution graph for narrow terminals.""" contrib_graph = stats.get('contribution_graph', []) graph_lines = self._get_contribution_graph_lines( contrib_graph, username, width_constraint=self.terminal_width - 4, - include_sections=False + include_sections=False, + spaced=spaced, ) for line in graph_lines: print(line) def _display_compact(self, username: str, user_data: Dict[str, Any], - stats: Dict[str, Any]) -> None: + stats: Dict[str, Any], spaced= True) -> None: """Display graph and minimal info side-by-side.""" contrib_graph = stats.get('contribution_graph', []) recent_weeks = self._get_recent_weeks(contrib_graph) @@ -74,7 +77,8 @@ def _display_compact(self, username: str, user_data: Dict[str, Any], contrib_graph, username, width_constraint=graph_width, - include_sections=False + include_sections=False, + spaced=spaced, ) info_lines = self._format_user_info_compact(user_data, stats) @@ -97,7 +101,7 @@ def _display_compact(self, username: str, user_data: Dict[str, Any], print(f"{graph_part}{padding} {info_part}") def _display_full(self, username: str, user_data: Dict[str, Any], - stats: Dict[str, Any]) -> None: + stats: Dict[str, Any], spaced=True) -> None: """Display full layout with graph and all info sections.""" contrib_graph = stats.get('contribution_graph', []) graph_width = max(50, (self.terminal_width - 10) // 2) @@ -105,7 +109,8 @@ def _display_full(self, username: str, user_data: Dict[str, Any], contrib_graph, username, width_constraint=graph_width, - include_sections=False + include_sections=False, + spaced=spaced, ) pull_request_lines = self._format_pull_requests(stats) @@ -152,7 +157,8 @@ def _display_full(self, username: str, user_data: Dict[str, Any], def _get_contribution_graph_lines(self, weeks_data: list, username: str, width_constraint: int = None, - include_sections: bool = True) -> list: + include_sections: bool = True, + spaced: bool = True) -> list: """ Get contribution graph as lines for display. @@ -187,7 +193,10 @@ def _get_contribution_graph_lines(self, weeks_data: list, for idx in range(7): day = days[idx] if idx < len(days) else {} count = day.get('contributionCount', 0) - block = self._get_contribution_block_spaced(count) + if spaced: + block = self._get_contribution_block_spaced(count) + else: + block = self._get_contribution_block(count) day_rows[idx].append(block) lines = [*header_lines] @@ -825,22 +834,7 @@ def _colorize(self, text: str, color: str) -> str: if not text: return text - colors = { - 'reset': '\033[0m', - 'bold': '\033[1m', - 'dim': '\033[2m', - 'red': '\033[91m', - 'green': '\033[92m', - 'yellow': '\033[93m', - 'blue': '\033[94m', - 'magenta': '\033[95m', - 'cyan': '\033[96m', - 'white': '\033[97m', - 'orange': '\033[38;2;255;165;0m', - 'accent': '\033[1m', - 'header': '\033[38;2;118;215;161m', - 'muted': '\033[2m' - } + colors = self.colors color_code = colors.get(color.lower()) reset = colors['reset'] @@ -882,15 +876,15 @@ def _get_contribution_block(self, count: int) -> str: reset = '\033[0m' if count == 0: - color = '\033[48;5;238m' + color = self.colors['0'] elif count < 3: - color = '\033[48;5;28m' + color = self.colors['1'] elif count < 7: - color = '\033[48;5;34m' + color = self.colors['2'] elif count < 13: - color = '\033[48;5;40m' + color = self.colors['3'] else: - color = '\033[48;5;82m' + color = self.colors['4'] return f"{color} {reset}"