|
9 | 9 | import re |
10 | 10 | from rich.console import Console |
11 | 11 | from rich.markdown import Markdown |
| 12 | +from rich.panel import Panel |
| 13 | +from rich.text import Text |
| 14 | +from rich.align import Align |
12 | 15 |
|
13 | 16 |
|
14 | 17 | class DisplayManager: |
@@ -69,30 +72,36 @@ def _format_number(self, number_str: str) -> str: |
69 | 72 | except (ValueError, AttributeError): |
70 | 73 | return number_str or "0" |
71 | 74 |
|
72 | | - def show_repositories(self, repos: List[Dict[str, str]], date_range: str): |
73 | | - """Display a list of trending repositories.""" |
| 75 | + def show_repositories(self, repos: List[Dict[str, str]], date_range: str, callback=None): |
| 76 | + """Display a list of trending repositories with Rich formatting.""" |
74 | 77 | if not repos: |
75 | | - print("No repositories found.") |
| 78 | + self.console.print("[red]No repositories found.[/red]") |
76 | 79 | return |
77 | 80 |
|
78 | 81 | # Header with enhanced styling |
79 | 82 | range_emoji = {"daily": "📅", "weekly": "📊", "monthly": "📈"}.get(date_range, "📋") |
80 | 83 | range_text = date_range.title() if date_range != "current" else "Current List" |
81 | 84 |
|
82 | | - # Create a beautiful header |
83 | | - header_line = "═" * self.terminal_width |
84 | | - print(f"\n{header_line}") |
85 | | - print(f"🚀 GitHub Trending Repositories - {range_text} {range_emoji}".center(self.terminal_width)) |
86 | | - print(f"🌟 Discover the hottest projects on GitHub".center(self.terminal_width)) |
87 | | - print(f"{header_line}") |
| 85 | + # Simple header without panels |
| 86 | + header_text = Text() |
| 87 | + header_text.append("🚀 GitHub Trending Repositories", style="bold") |
| 88 | + header_text.append(f" - {range_text} {range_emoji}", style="dim") |
88 | 89 |
|
89 | | - # Paginate repository list |
90 | | - self._paginate_repositories(repos) |
| 90 | + self.console.print() |
| 91 | + self.console.print(header_text) |
| 92 | + self.console.print() |
91 | 93 |
|
92 | | - # Footer with stats |
93 | | - print(f"\n{header_line}") |
94 | | - print(f"📊 Found {len(repos)} trending repositories • Happy coding! 🎉".center(self.terminal_width)) |
95 | | - print(f"{header_line}") |
| 94 | + # Paginate repository list with Rich |
| 95 | + self._paginate_repositories_rich(repos, callback) |
| 96 | + |
| 97 | + # Simple footer without panels |
| 98 | + footer_text = Text() |
| 99 | + footer_text.append("Found ", style="dim") |
| 100 | + footer_text.append(str(len(repos)), style="bold") |
| 101 | + footer_text.append(" trending repositories", style="dim") |
| 102 | + |
| 103 | + self.console.print() |
| 104 | + self.console.print(footer_text) |
96 | 105 |
|
97 | 106 | def _print_repository_summary(self, index: int, repo: Dict[str, str]): |
98 | 107 | """Print a single repository summary with enhanced formatting.""" |
@@ -250,38 +259,181 @@ def _paginate_content(self, lines: List[str]): |
250 | 259 | print("\n📖 README reading interrupted.") |
251 | 260 | break |
252 | 261 |
|
253 | | - def _paginate_repositories(self, repos: List[Dict[str, str]]): |
254 | | - """Display repositories with pagination.""" |
| 262 | + def _paginate_repositories_rich(self, repos: List[Dict[str, str]], callback=None): |
| 263 | + """Display repositories with scrolling pagination and interactive selection.""" |
255 | 264 | repos_per_page = 5 # Show 5 repositories per page |
256 | 265 | current_repo = 0 |
| 266 | + displayed_repos = [] # Keep track of all displayed repos |
257 | 267 |
|
258 | | - while current_repo < len(repos): |
| 268 | + while True: # Infinite loop - only exit when user presses 'q' |
| 269 | + # Clear screen for scroll effect (but keep header visible) |
| 270 | + if current_repo > 0: |
| 271 | + # Clear previous content but keep some context |
| 272 | + print("\033[H\033[2J", end="") # Clear screen |
| 273 | + # Re-print header |
| 274 | + header_text = Text() |
| 275 | + header_text.append("🚀 GitHub Trending Repositories", style="bold") |
| 276 | + header_text.append(" - Daily 📅", style="dim") |
| 277 | + self.console.print(header_text) |
| 278 | + self.console.print() |
| 279 | + |
259 | 280 | # Display current page of repositories |
260 | 281 | end_repo = min(current_repo + repos_per_page, len(repos)) |
261 | 282 |
|
| 283 | + # Add current page repos to displayed list |
262 | 284 | for i in range(current_repo, end_repo): |
263 | | - repo = repos[i] |
264 | | - self._print_repository_summary(i + 1, repo) |
265 | | - # Add separator between repos (except for the last one on the page) |
266 | | - if i < end_repo - 1: |
267 | | - print("─" * min(60, self.terminal_width - 10)) |
| 285 | + if i >= len(displayed_repos): |
| 286 | + displayed_repos.append(repos[i]) |
268 | 287 |
|
269 | | - current_repo = end_repo |
| 288 | + # Show all repositories up to current point |
| 289 | + for i, repo in enumerate(displayed_repos): |
| 290 | + name = repo.get('name', 'Unknown').strip() |
| 291 | + language = repo.get('language', 'Unknown').strip() |
| 292 | + stars = self._format_number(repo.get('stars', '0')) |
| 293 | + stars_today = self._format_number(repo.get('stars_today', '0')) |
| 294 | + description = repo.get('description', 'No description').strip() |
| 295 | + |
| 296 | + # Clean up repository name |
| 297 | + name = re.sub(r'\s+', ' ', name) |
| 298 | + |
| 299 | + # Get language emoji and color |
| 300 | + lang_emoji = self.LANGUAGE_COLORS.get(language, self.LANGUAGE_COLORS['Unknown']) |
| 301 | + |
| 302 | + # Color coding for stars today with trending indicators |
| 303 | + stars_today_num = int(repo.get('stars_today', '0').replace(',', '') or '0') |
| 304 | + if stars_today_num > 100: |
| 305 | + stars_today_style = "red" |
| 306 | + trending_indicator = "🔥" |
| 307 | + elif stars_today_num > 50: |
| 308 | + stars_today_style = "yellow" |
| 309 | + trending_indicator = "🚀" |
| 310 | + elif stars_today_num > 10: |
| 311 | + stars_today_style = "green" |
| 312 | + trending_indicator = "📈" |
| 313 | + else: |
| 314 | + stars_today_style = "dim" |
| 315 | + trending_indicator = "" |
| 316 | + |
| 317 | + # Create the main line with repository info |
| 318 | + line = Text() |
| 319 | + line.append(f"{i + 1:2}. ", style="dim") |
| 320 | + line.append(f"{name}", style="bold") |
| 321 | + line.append(f" {lang_emoji}", style="dim") |
| 322 | + line.append(f" ⭐{stars}", style="dim") |
| 323 | + line.append(f" {trending_indicator}", style=stars_today_style) |
| 324 | + line.append(f"+{stars_today}", style=stars_today_style) |
| 325 | + |
| 326 | + self.console.print(line) |
| 327 | + self.console.print(f" [dim]{description}[/dim]") |
| 328 | + self.console.print() |
270 | 329 |
|
271 | | - # Check if there are more repositories |
| 330 | + # Only advance current_repo if there are more repositories to show |
| 331 | + if current_repo < len(repos): |
| 332 | + current_repo = end_repo |
| 333 | + |
| 334 | + # Interactive prompt |
272 | 335 | if current_repo < len(repos): |
273 | 336 | remaining_repos = len(repos) - current_repo |
274 | | - print(f"\n--- More repositories available ({remaining_repos} remaining) ---") |
| 337 | + self.console.print(f"[dim]({remaining_repos} more repositories)[/dim]") |
| 338 | + prompt_text = f"Enter repo number (1-{len(displayed_repos)}), Enter for more, 'q' to quit: " |
| 339 | + else: |
| 340 | + prompt_text = f"Enter repo number (1-{len(displayed_repos)}) or 'q' to quit: " |
| 341 | + |
| 342 | + try: |
| 343 | + user_input = input(prompt_text).strip().lower() |
275 | 344 |
|
276 | | - try: |
277 | | - user_input = input("📋 Press Enter to see more repositories, 'q' to stop browsing: ").strip().lower() |
278 | | - if user_input == 'q': |
279 | | - print("📋 Repository browsing stopped.") |
280 | | - break |
281 | | - print() # Add spacing before next page |
282 | | - except KeyboardInterrupt: |
283 | | - print("\n📋 Repository browsing interrupted.") |
| 345 | + if user_input == 'q': |
284 | 346 | break |
| 347 | + elif user_input == '': |
| 348 | + if current_repo >= len(repos): |
| 349 | + # No more repos, stay in selection mode and redisplay current state |
| 350 | + print("\033[H\033[2J", end="") # Clear screen |
| 351 | + # Re-print header |
| 352 | + header_text = Text() |
| 353 | + header_text.append("🚀 GitHub Trending Repositories", style="bold") |
| 354 | + header_text.append(" - Daily 📅", style="dim") |
| 355 | + self.console.print(header_text) |
| 356 | + self.console.print() |
| 357 | + |
| 358 | + # Re-display all repos |
| 359 | + for i, repo in enumerate(displayed_repos): |
| 360 | + name = repo.get('name', 'Unknown').strip() |
| 361 | + language = repo.get('language', 'Unknown').strip() |
| 362 | + stars = self._format_number(repo.get('stars', '0')) |
| 363 | + stars_today = self._format_number(repo.get('stars_today', '0')) |
| 364 | + description = repo.get('description', 'No description').strip() |
| 365 | + |
| 366 | + # Clean up repository name |
| 367 | + name = re.sub(r'\s+', ' ', name) |
| 368 | + |
| 369 | + # Get language emoji and color |
| 370 | + lang_emoji = self.LANGUAGE_COLORS.get(language, self.LANGUAGE_COLORS['Unknown']) |
| 371 | + |
| 372 | + # Color coding for stars today with trending indicators |
| 373 | + stars_today_num = int(repo.get('stars_today', '0').replace(',', '') or '0') |
| 374 | + if stars_today_num > 100: |
| 375 | + stars_today_style = "red" |
| 376 | + trending_indicator = "🔥" |
| 377 | + elif stars_today_num > 50: |
| 378 | + stars_today_style = "yellow" |
| 379 | + trending_indicator = "🚀" |
| 380 | + elif stars_today_num > 10: |
| 381 | + stars_today_style = "green" |
| 382 | + trending_indicator = "📈" |
| 383 | + else: |
| 384 | + stars_today_style = "dim" |
| 385 | + trending_indicator = "" |
| 386 | + |
| 387 | + # Create the main line with repository info |
| 388 | + line = Text() |
| 389 | + line.append(f"{i + 1:2}. ", style="dim") |
| 390 | + line.append(f"{name}", style="bold") |
| 391 | + line.append(f" {lang_emoji}", style="dim") |
| 392 | + line.append(f" ⭐{stars}", style="dim") |
| 393 | + line.append(f" {trending_indicator}", style=stars_today_style) |
| 394 | + line.append(f"+{stars_today}", style=stars_today_style) |
| 395 | + |
| 396 | + self.console.print(line) |
| 397 | + self.console.print(f" [dim]{description}[/dim]") |
| 398 | + self.console.print() |
| 399 | + |
| 400 | + continue |
| 401 | + # Continue to next page (only if there are more repos) |
| 402 | + continue |
| 403 | + else: |
| 404 | + # Try to parse as repository number |
| 405 | + try: |
| 406 | + repo_num = int(user_input) |
| 407 | + if 1 <= repo_num <= len(displayed_repos): |
| 408 | + # User selected a repository |
| 409 | + selected_repo = displayed_repos[repo_num - 1] |
| 410 | + if callback: |
| 411 | + # Call the callback function (e.g., show repository details) |
| 412 | + callback(repo_num - 1, displayed_repos) |
| 413 | + # After viewing details, clear screen and redisplay current state |
| 414 | + print("\033[H\033[2J", end="") # Clear screen |
| 415 | + # Re-print header |
| 416 | + header_text = Text() |
| 417 | + header_text.append("🚀 GitHub Trending Repositories", style="bold") |
| 418 | + header_text.append(" - Daily 📅", style="dim") |
| 419 | + self.console.print(header_text) |
| 420 | + self.console.print() |
| 421 | + # Re-display all repos up to current point |
| 422 | + continue |
| 423 | + else: |
| 424 | + self.console.print(f"\n[green]Selected: {selected_repo.get('name', 'Unknown')}[/green]") |
| 425 | + input("Press Enter to continue browsing...") |
| 426 | + continue |
| 427 | + else: |
| 428 | + self.console.print(f"[red]Please enter a number between 1 and {len(displayed_repos)}[/red]") |
| 429 | + continue |
| 430 | + except ValueError: |
| 431 | + self.console.print("[red]Please enter a valid number, Enter, or 'q'[/red]") |
| 432 | + continue |
| 433 | + |
| 434 | + except KeyboardInterrupt: |
| 435 | + print("\nInterrupted.") |
| 436 | + break |
285 | 437 |
|
286 | 438 | def show_error(self, message: str): |
287 | 439 | """Display an error message.""" |
|
0 commit comments