Coverage for src / monte_neo / cli / styles.py: 33%

57 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-28 16:27 +0200

1"""CLI styles module. 

2 

3Terminal styling and formatting. 

4""" 

5 

6from __future__ import annotations 

7 

8from questionary import Style 

9from rich.console import Console 

10from rich.panel import Panel 

11from rich.text import Text 

12 

13from monte_neo._version import __version__ 

14 

15console = Console() 

16 

17# Questionary custom style (matches cline/claude code aesthetic) 

18CUSTOM_STYLE = Style( 

19 [ 

20 ("qmark", "fg:cyan bold"), 

21 ("question", "fg:white bold"), 

22 ("answer", "fg:green bold"), 

23 ("pointer", "fg:cyan bold"), 

24 ("highlighted", "fg:cyan bold"), 

25 ("selected", "fg:green"), 

26 ("separator", "fg:white"), 

27 ("instruction", "fg:white dim"), 

28 ("text", "fg:white"), 

29 ("disabled", "fg:white dim"), 

30 ] 

31) 

32 

33 

34def press_any_key() -> None: 

35 """Wait for user to press enter.""" 

36 import questionary 

37 questionary.confirm("Press Enter to continue...", default=True, auto_enter=True, qmark="").ask() 

38 

39def print_banner() -> None: 

40 """Print the application banner.""" 

41 banner = Text() 

42 banner.append( 

43 "╔══════════════════════════════════════════════════════════╗\n", style="cyan" 

44 ) # noqa: E501 

45 banner.append("║", style="cyan") 

46 banner.append( 

47 f" 🎲 Monte-Neo {__version__} ", 

48 style="bold white", 

49 ) # noqa: E501 

50 banner.append("║\n", style="cyan") 

51 banner.append("║", style="cyan") 

52 banner.append( 

53 " Monte Carlo Indicator Generator Framework ", 

54 style="dim white", 

55 ) # noqa: E501 

56 banner.append("║\n", style="cyan") 

57 banner.append( 

58 "╚══════════════════════════════════════════════════════════╝", style="cyan" 

59 ) # noqa: E501 

60 

61 console.print(banner) 

62 console.print() 

63 

64 

65def print_success(message: str) -> None: 

66 """Print success message.""" 

67 console.print(f"[bold green]✓[/] {message}") 

68 

69 

70def print_error(message: str) -> None: 

71 """Print error message.""" 

72 console.print(f"[bold red]✗[/] {message}") 

73 

74 

75def print_warning(message: str) -> None: 

76 """Print warning message.""" 

77 console.print(f"[bold yellow]⚠[/] {message}") 

78 

79 

80def print_info(message: str) -> None: 

81 """Print info message.""" 

82 console.print(f"[bold cyan]ℹ[/] {message}") 

83 

84 

85def print_section(title: str) -> None: 

86 """Print section header.""" 

87 console.print(f"\n[bold cyan]━━━ {title} ━━━[/]\n") 

88 

89 

90def print_metrics_panel(metrics: dict) -> None: 

91 """Print metrics in a styled panel.""" 

92 lines = [] 

93 for key, value in metrics.items(): 

94 if isinstance(value, float): 

95 lines.append(f"[cyan]{key}:[/] [green]{value:.4f}[/]") 

96 else: 

97 lines.append(f"[cyan]{key}:[/] [green]{value}[/]") 

98 

99 panel = Panel( 

100 "\n".join(lines), 

101 title="[bold]Metrics[/]", 

102 border_style="cyan", 

103 ) 

104 console.print(panel) 

105 

106 

107def format_number(value: float, decimals: int = 2) -> str: 

108 """Format number for display.""" 

109 if abs(value) >= 1_000_000: 

110 return f"{value / 1_000_000:.{decimals}f}M" 

111 elif abs(value) >= 1_000: 

112 return f"{value / 1_000:.{decimals}f}K" 

113 else: 

114 return f"{value:.{decimals}f}" 

115 

116 

117def format_percentage(value: float) -> str: 

118 """Format as percentage.""" 

119 return f"{value * 100:.1f}%" 

120 

121 

122def format_duration(seconds: float) -> str: 

123 """Format duration for display.""" 

124 if seconds < 60: 

125 return f"{seconds:.1f}s" 

126 elif seconds < 3600: 

127 minutes = seconds / 60 

128 return f"{minutes:.1f}m" 

129 else: 

130 hours = seconds / 3600 

131 return f"{hours:.1f}h"