@@ -93,6 +93,7 @@ class EarlyArgs(NamedTuple):
9393 version : bool # True if -V was given
9494 zephyr_base : str | None # -z argument value
9595 verbosity : int # 0 if not given, otherwise counts
96+ color : str | None # --color argument value ('always', 'never', 'auto')
9697 command_name : str | None
9798
9899 # Other arguments are appended here.
@@ -106,16 +107,18 @@ def parse_early_args(argv: list[str]) -> EarlyArgs:
106107 version = False
107108 zephyr_base = None
108109 verbosity = 0
110+ color = None
109111 command_name = None
110112 unexpected_arguments = []
111113
112114 expecting_zephyr_base = False
115+ expecting_color = False
113116
114117 def consume_more_args (rest ):
115118 # Handle the 'Vv' portion of 'west -hVv'.
116119
117- nonlocal help , version , zephyr_base , verbosity
118- nonlocal expecting_zephyr_base
120+ nonlocal help , version , zephyr_base , verbosity , color
121+ nonlocal expecting_zephyr_base , expecting_color
119122
120123 if not rest :
121124 return
@@ -145,6 +148,13 @@ def consume_more_args(rest):
145148 for arg in argv :
146149 if expecting_zephyr_base :
147150 zephyr_base = arg
151+ expecting_zephyr_base = False
152+ elif expecting_color :
153+ if arg in ('always' , 'never' , 'auto' ):
154+ color = arg
155+ else :
156+ unexpected_arguments .append (f'--color={ arg } ' )
157+ expecting_color = False
148158 elif arg .startswith ('-h' ):
149159 help = True
150160 consume_more_args (arg [2 :])
@@ -170,13 +180,23 @@ def consume_more_args(rest):
170180 zephyr_base = arg [3 :]
171181 else :
172182 zephyr_base = arg [2 :]
183+ elif arg == '--color' :
184+ expecting_color = True
185+ elif arg .startswith ('--color=' ):
186+ color_val = arg [8 :]
187+ if color_val in ('always' , 'never' , 'auto' ):
188+ color = color_val
189+ else :
190+ unexpected_arguments .append (arg )
173191 elif arg .startswith ('-' ):
174192 unexpected_arguments .append (arg )
175193 else :
176194 command_name = arg
177195 break
178196
179- return EarlyArgs (help , version , zephyr_base , verbosity , command_name , unexpected_arguments )
197+ return EarlyArgs (
198+ help , version , zephyr_base , verbosity , color , command_name , unexpected_arguments
199+ )
180200
181201
182202class LogFormatter (logging .Formatter ):
@@ -222,6 +242,7 @@ def __init__(self):
222242 self .subparser_gen = None # an add_subparsers() return value
223243 self .cmd = None # west.commands.WestCommand, eventually
224244 self .queued_io = [] # I/O hooks we want self.cmd to do
245+ self .color_mode = None # 'always', 'never', 'auto', or None
225246
226247 for group , classes in BUILTIN_COMMAND_GROUPS .items ():
227248 lst = [cls () for cls in classes ]
@@ -253,9 +274,8 @@ def run(self, argv):
253274 # Use verbosity to determine west API log levels
254275 self .setup_west_logging (early_args .verbosity )
255276
256- # Makes ANSI color escapes work on Windows, and strips them when
257- # stdout/stderr isn't a terminal
258- colorama .init ()
277+ # Store color mode for later use
278+ self .color_mode = early_args .color
259279
260280 # See if we're in a workspace. It's fine if we're not.
261281 # Note that this falls back on searching from ZEPHYR_BASE
@@ -273,6 +293,9 @@ def run(self, argv):
273293 # backwards compatibility.
274294 self .config ._copy_to_configparser (west .configuration .config )
275295
296+ # Initialize colorama after config is loaded so we can read color.ui setting
297+ self .init_colorama ()
298+
276299 # Set self.manifest and self.extensions.
277300 self .load_manifest ()
278301 self .load_extension_specs ()
@@ -576,6 +599,13 @@ def make_parsers(self):
576599 help = 'print the program version and exit' ,
577600 )
578601
602+ parser .add_argument (
603+ '--color' ,
604+ choices = ['always' , 'never' , 'auto' ],
605+ default = None ,
606+ help = 'when to colorize output (always, never, auto)' ,
607+ )
608+
579609 subparser_gen = parser .add_subparsers (metavar = '<command>' , dest = 'command' )
580610
581611 return parser , subparser_gen
@@ -739,12 +769,45 @@ def setup_west_logging(self, verbosity):
739769
740770 logger .addHandler (LogHandler ())
741771
772+ def init_colorama (self ):
773+ color_mode = self .color_mode
774+
775+ if color_mode is None and self .config :
776+ config_value = self .config .get ('color.ui' , 'auto' )
777+ # Handle both string values (always/never/auto) and
778+ # boolean values (true/false) for backward compatibility
779+ if config_value in ('always' , 'never' , 'auto' ):
780+ color_mode = config_value
781+ elif config_value in ('true' , 'True' , '1' ):
782+ color_mode = 'always'
783+ elif config_value in ('false' , 'False' , '0' ):
784+ color_mode = 'never'
785+ else :
786+ color_mode = 'auto'
787+
788+ if color_mode == 'always' :
789+ colorama .init (strip = False )
790+ elif color_mode == 'never' :
791+ colorama .init (strip = True )
792+ else :
793+ # 'auto' or None: use colorama's default behavior
794+ colorama .init ()
795+
796+ def apply_color_override (self ):
797+ if self .color_mode == 'always' :
798+ self .cmd ._color_override = True
799+ elif self .color_mode == 'never' :
800+ self .cmd ._color_override = False
801+ # For 'auto' or None, don't set override (use config)
802+
742803 def run_builtin (self , args , unknown ):
743804 self .queued_io .append (
744805 lambda cmd : cmd .dbg ('args namespace:' , args , level = Verbosity .DBG_EXTREME )
745806 )
746807 self .cmd = self .builtins .get (args .command , self .builtins ['help' ])
747808 adjust_command_verbosity (self .cmd , args )
809+ self .apply_color_override ()
810+
748811 if self .mle :
749812 self .handle_builtin_manifest_load_err (args )
750813 for io_hook in self .queued_io :
@@ -771,6 +834,8 @@ def run_extension(self, name, argv):
771834 args , unknown = west_parser .parse_known_args (argv )
772835
773836 adjust_command_verbosity (self .cmd , args )
837+ self .apply_color_override ()
838+
774839 self .queued_io .append (
775840 lambda cmd : cmd .dbg ('args namespace:' , args , level = Verbosity .DBG_EXTREME )
776841 )
0 commit comments