1111from collections .abc import Sequence
1212from gettext import gettext
1313from io import TextIOWrapper
14- from typing import IO , TYPE_CHECKING , Any , Final , NoReturn , TextIO
14+ from typing import IO , TYPE_CHECKING , Any , Final , Literal , NoReturn , TextIO , cast
1515
1616from mypy import build , defaults , state , util
1717from mypy .config_parser import (
@@ -90,8 +90,19 @@ def main(
9090 if clean_exit :
9191 options .fast_exit = False
9292
93+ # Type annotation needed for mypy (Pyright understands this)
94+ use_color : bool | Literal ["auto" ] = (
95+ True
96+ if util .should_force_color ()
97+ else ("auto" if options .color_output == "auto" else cast (bool , options .color_output ))
98+ )
99+
93100 formatter = util .FancyFormatter (
94- stdout , stderr , options .hide_error_codes , hide_success = bool (options .output )
101+ stdout ,
102+ stderr ,
103+ options .hide_error_codes ,
104+ hide_success = bool (options .output ),
105+ color_request = use_color ,
95106 )
96107
97108 if options .allow_redefinition_new and not options .local_partial_types :
@@ -124,7 +135,7 @@ def main(
124135 install_types (formatter , options , non_interactive = options .non_interactive )
125136 return
126137
127- res , messages , blockers = run_build (sources , options , fscache , t0 , stdout , stderr )
138+ res , messages , blockers = run_build (sources , options , fscache , t0 , stdout , stderr , use_color )
128139
129140 if options .non_interactive :
130141 missing_pkgs = read_types_packages_to_install (options .cache_dir , after_run = True )
@@ -133,8 +144,10 @@ def main(
133144 install_types (formatter , options , after_run = True , non_interactive = True )
134145 fscache .flush ()
135146 print ()
136- res , messages , blockers = run_build (sources , options , fscache , t0 , stdout , stderr )
137- show_messages (messages , stderr , formatter , options )
147+ res , messages , blockers = run_build (
148+ sources , options , fscache , t0 , stdout , stderr , use_color
149+ )
150+ show_messages (messages , stderr , formatter , options , use_color )
138151
139152 if MEM_PROFILE :
140153 from mypy .memprofile import print_memory_profile
@@ -148,12 +161,12 @@ def main(
148161 if options .error_summary :
149162 if n_errors :
150163 summary = formatter .format_error (
151- n_errors , n_files , len (sources ), blockers = blockers , use_color = options . color_output
164+ n_errors , n_files , len (sources ), blockers = blockers , use_color = use_color
152165 )
153166 stdout .write (summary + "\n " )
154167 # Only notes should also output success
155168 elif not messages or n_notes == len (messages ):
156- stdout .write (formatter .format_success (len (sources ), options . color_output ) + "\n " )
169+ stdout .write (formatter .format_success (len (sources ), use_color ) + "\n " )
157170 stdout .flush ()
158171
159172 if options .install_types and not options .non_interactive :
@@ -182,9 +195,14 @@ def run_build(
182195 t0 : float ,
183196 stdout : TextIO ,
184197 stderr : TextIO ,
198+ use_color : bool | Literal ["auto" ],
185199) -> tuple [build .BuildResult | None , list [str ], bool ]:
186200 formatter = util .FancyFormatter (
187- stdout , stderr , options .hide_error_codes , hide_success = bool (options .output )
201+ stdout ,
202+ stderr ,
203+ options .hide_error_codes ,
204+ hide_success = bool (options .output ),
205+ color_request = use_color ,
188206 )
189207
190208 messages = []
@@ -200,7 +218,7 @@ def flush_errors(filename: str | None, new_messages: list[str], serious: bool) -
200218 # Collect messages and possibly show them later.
201219 return
202220 f = stderr if serious else stdout
203- show_messages (new_messages , f , formatter , options )
221+ show_messages (new_messages , f , formatter , options , use_color )
204222
205223 serious = False
206224 blockers = False
@@ -238,10 +256,16 @@ def flush_errors(filename: str | None, new_messages: list[str], serious: bool) -
238256
239257
240258def show_messages (
241- messages : list [str ], f : TextIO , formatter : util .FancyFormatter , options : Options
259+ messages : list [str ],
260+ f : TextIO ,
261+ formatter : util .FancyFormatter ,
262+ options : Options ,
263+ use_color : bool | Literal ["auto" ],
242264) -> None :
265+ if use_color == "auto" :
266+ use_color = formatter .default_colored
243267 for msg in messages :
244- if options . color_output :
268+ if use_color :
245269 msg = formatter .colorize (msg )
246270 f .write (msg + "\n " )
247271 f .flush ()
@@ -462,6 +486,19 @@ def __call__(
462486 parser .exit ()
463487
464488
489+ # Coupled with the usage in define_options
490+ class ColorOutputAction (argparse .Action ):
491+ def __call__ (
492+ self ,
493+ parser : argparse .ArgumentParser ,
494+ namespace : argparse .Namespace ,
495+ values : str | Sequence [Any ] | None ,
496+ option_string : str | None = None ,
497+ ) -> None :
498+ assert values in ("auto" , None )
499+ setattr (namespace , self .dest , True if values is None else "auto" )
500+
501+
465502def define_options (
466503 program : str = "mypy" ,
467504 header : str = HEADER ,
@@ -993,13 +1030,22 @@ def add_invertible_flag(
9931030 " and show error location markers" ,
9941031 group = error_group ,
9951032 )
996- add_invertible_flag (
997- "--no-color-output" ,
1033+ # XXX Setting default doesn't seem to work unless I change
1034+ # the attribute in options.Options
1035+ error_group .add_argument (
1036+ "--color-output" ,
9981037 dest = "color_output" ,
999- default = True ,
1000- help = "Do not colorize error messages" ,
1001- group = error_group ,
1038+ action = ColorOutputAction ,
1039+ nargs = "?" ,
1040+ choices = ["auto" ],
1041+ help = "Colorize error messages (inverse: --no-color-output). "
1042+ "Detects if to use color when option is omitted and --no-color-output "
1043+ "is not given, or when --color-output=auto" ,
1044+ )
1045+ error_group .add_argument (
1046+ "--no-color-output" , dest = "color_output" , action = "store_false" , help = argparse .SUPPRESS
10021047 )
1048+ # error_group.set_defaults(color_output="auto")
10031049 add_invertible_flag (
10041050 "--no-error-summary" ,
10051051 dest = "error_summary" ,
@@ -1530,7 +1576,8 @@ def set_strict_flags() -> None:
15301576 reason = cache .find_module (p )
15311577 if reason is ModuleNotFoundReason .FOUND_WITHOUT_TYPE_HINTS :
15321578 fail (
1533- f"Package '{ p } ' cannot be type checked due to missing py.typed marker. See https://mypy.readthedocs.io/en/stable/installed_packages.html for more details" ,
1579+ f"Package '{ p } ' cannot be type checked due to missing py.typed marker. "
1580+ "See https://mypy.readthedocs.io/en/stable/installed_packages.html for more details" ,
15341581 stderr ,
15351582 options ,
15361583 )
0 commit comments