1
+ """Terminal color support detection and fallback options."""
2
+
3
+ import os
4
+ import sys
5
+ from typing import Dict , Optional , Set , Tuple
6
+
7
+ # ANSI color codes
8
+ ANSI_COLORS = {
9
+ 'black' : 30 ,
10
+ 'red' : 31 ,
11
+ 'green' : 32 ,
12
+ 'yellow' : 33 ,
13
+ 'blue' : 34 ,
14
+ 'magenta' : 35 ,
15
+ 'cyan' : 36 ,
16
+ 'white' : 37 ,
17
+ 'bright_black' : 90 ,
18
+ 'bright_red' : 91 ,
19
+ 'bright_green' : 92 ,
20
+ 'bright_yellow' : 93 ,
21
+ 'bright_blue' : 94 ,
22
+ 'bright_magenta' : 95 ,
23
+ 'bright_cyan' : 96 ,
24
+ 'bright_white' : 97 ,
25
+ }
26
+
27
+ class TerminalColorSupport :
28
+ """Detect and manage terminal color support."""
29
+
30
+ def __init__ (self ) -> None :
31
+ self ._color_support : Optional [bool ] = None
32
+ self ._supported_colors : Set [str ] = set ()
33
+ self ._fallback_colors : Dict [str , str ] = {}
34
+
35
+ def detect_color_support (self ) -> bool :
36
+ """Detect if the terminal supports colors."""
37
+ if self ._color_support is not None :
38
+ return self ._color_support
39
+
40
+ # Check environment variables
41
+ if 'NO_COLOR' in os .environ :
42
+ self ._color_support = False
43
+ return False
44
+
45
+ if 'FORCE_COLOR' in os .environ :
46
+ self ._color_support = True
47
+ return True
48
+
49
+ # Check if we're in a terminal
50
+ if not sys .stdout .isatty ():
51
+ self ._color_support = False
52
+ return False
53
+
54
+ # Check terminal type
55
+ term = os .environ .get ('TERM' , '' ).lower ()
56
+ if term in ('dumb' , 'unknown' ):
57
+ self ._color_support = False
58
+ return False
59
+
60
+ # Windows specific checks
61
+ if sys .platform == 'win32' :
62
+ try :
63
+ import ctypes
64
+ kernel32 = ctypes .windll .kernel32
65
+ if kernel32 .GetConsoleMode (kernel32 .GetStdHandle (- 11 ), None ):
66
+ self ._color_support = True
67
+ return True
68
+ except Exception :
69
+ pass
70
+
71
+ # Default to True for modern terminals
72
+ self ._color_support = True
73
+ return True
74
+
75
+ def get_supported_colors (self ) -> Set [str ]:
76
+ """Get the set of supported colors."""
77
+ if not self ._supported_colors :
78
+ if self .detect_color_support ():
79
+ # Test each color
80
+ for color in ANSI_COLORS :
81
+ if self ._test_color (color ):
82
+ self ._supported_colors .add (color )
83
+
84
+ return self ._supported_colors
85
+
86
+ def _test_color (self , color : str ) -> bool :
87
+ """Test if a specific color is supported."""
88
+ # Implementation would test actual color support
89
+ # For now, return True for all colors if color support is enabled
90
+ return self .detect_color_support ()
91
+
92
+ def get_fallback_color (self , color : str ) -> str :
93
+ """Get a fallback color if the requested color is not supported."""
94
+ if color in self ._fallback_colors :
95
+ return self ._fallback_colors [color ]
96
+
97
+ # Define fallback mappings
98
+ fallbacks = {
99
+ 'bright_black' : 'black' ,
100
+ 'bright_red' : 'red' ,
101
+ 'bright_green' : 'green' ,
102
+ 'bright_yellow' : 'yellow' ,
103
+ 'bright_blue' : 'blue' ,
104
+ 'bright_magenta' : 'magenta' ,
105
+ 'bright_cyan' : 'cyan' ,
106
+ 'bright_white' : 'white' ,
107
+ }
108
+
109
+ fallback = fallbacks .get (color , 'white' )
110
+ self ._fallback_colors [color ] = fallback
111
+ return fallback
112
+
113
+ def get_color_code (self , color : str ) -> Tuple [int , bool ]:
114
+ """Get the ANSI color code and whether it's supported."""
115
+ if not self .detect_color_support ():
116
+ return (0 , False )
117
+
118
+ if color not in self .get_supported_colors ():
119
+ color = self .get_fallback_color (color )
120
+
121
+ return (ANSI_COLORS [color ], True )
122
+
123
+ # Global instance
124
+ terminal_color = TerminalColorSupport ()
0 commit comments