Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/misc/utility/color.py
9903 views
1
from __future__ import annotations
2
3
import os
4
import re
5
import sys
6
from enum import Enum
7
from typing import Final
8
9
# Colors are disabled in non-TTY environments such as pipes. This means if output is redirected
10
# to a file, it won't contain color codes. Colors are enabled by default on continuous integration.
11
12
IS_CI: Final[bool] = bool(os.environ.get("CI"))
13
NO_COLOR: Final[bool] = bool(os.environ.get("NO_COLOR"))
14
CLICOLOR_FORCE: Final[bool] = bool(os.environ.get("CLICOLOR_FORCE"))
15
STDOUT_TTY: Final[bool] = bool(sys.stdout.isatty())
16
STDERR_TTY: Final[bool] = bool(sys.stderr.isatty())
17
18
19
_STDOUT_ORIGINAL: Final[bool] = False if NO_COLOR else CLICOLOR_FORCE or IS_CI or STDOUT_TTY
20
_STDERR_ORIGINAL: Final[bool] = False if NO_COLOR else CLICOLOR_FORCE or IS_CI or STDERR_TTY
21
_stdout_override: bool = _STDOUT_ORIGINAL
22
_stderr_override: bool = _STDERR_ORIGINAL
23
24
25
def is_stdout_color() -> bool:
26
return _stdout_override
27
28
29
def is_stderr_color() -> bool:
30
return _stderr_override
31
32
33
def force_stdout_color(value: bool) -> None:
34
"""
35
Explicitly set `stdout` support for ANSI escape codes.
36
If environment overrides exist, does nothing.
37
"""
38
if not NO_COLOR or not CLICOLOR_FORCE:
39
global _stdout_override
40
_stdout_override = value
41
42
43
def force_stderr_color(value: bool) -> None:
44
"""
45
Explicitly set `stderr` support for ANSI escape codes.
46
If environment overrides exist, does nothing.
47
"""
48
if not NO_COLOR or not CLICOLOR_FORCE:
49
global _stderr_override
50
_stderr_override = value
51
52
53
class Ansi(Enum):
54
"""
55
Enum class for adding ANSI codepoints directly into strings. Automatically converts values to
56
strings representing their internal value.
57
"""
58
59
RESET = "\x1b[0m"
60
61
BOLD = "\x1b[1m"
62
DIM = "\x1b[2m"
63
ITALIC = "\x1b[3m"
64
UNDERLINE = "\x1b[4m"
65
STRIKETHROUGH = "\x1b[9m"
66
REGULAR = "\x1b[22;23;24;29m"
67
68
BLACK = "\x1b[30m"
69
RED = "\x1b[31m"
70
GREEN = "\x1b[32m"
71
YELLOW = "\x1b[33m"
72
BLUE = "\x1b[34m"
73
MAGENTA = "\x1b[35m"
74
CYAN = "\x1b[36m"
75
WHITE = "\x1b[37m"
76
GRAY = "\x1b[90m"
77
78
def __str__(self) -> str:
79
return self.value
80
81
82
RE_ANSI = re.compile(r"\x1b\[[=\?]?[;\d]+[a-zA-Z]")
83
84
85
def color_print(*values: object, sep: str | None = " ", end: str | None = "\n", flush: bool = False) -> None:
86
"""Prints a colored message to `stdout`. If disabled, ANSI codes are automatically stripped."""
87
if is_stdout_color():
88
print(*values, sep=sep, end=f"{Ansi.RESET}{end}", flush=flush)
89
else:
90
print(RE_ANSI.sub("", (sep or " ").join(map(str, values))), sep="", end=end, flush=flush)
91
92
93
def color_printerr(*values: object, sep: str | None = " ", end: str | None = "\n", flush: bool = False) -> None:
94
"""Prints a colored message to `stderr`. If disabled, ANSI codes are automatically stripped."""
95
if is_stderr_color():
96
print(*values, sep=sep, end=f"{Ansi.RESET}{end}", flush=flush, file=sys.stderr)
97
else:
98
print(RE_ANSI.sub("", (sep or " ").join(map(str, values))), sep="", end=end, flush=flush, file=sys.stderr)
99
100
101
def print_info(*values: object) -> None:
102
"""Prints a informational message with formatting."""
103
color_print(f"{Ansi.GRAY}{Ansi.BOLD}INFO:{Ansi.REGULAR}", *values)
104
105
106
def print_warning(*values: object) -> None:
107
"""Prints a warning message with formatting."""
108
color_printerr(f"{Ansi.YELLOW}{Ansi.BOLD}WARNING:{Ansi.REGULAR}", *values)
109
110
111
def print_error(*values: object) -> None:
112
"""Prints an error message with formatting."""
113
color_printerr(f"{Ansi.RED}{Ansi.BOLD}ERROR:{Ansi.REGULAR}", *values)
114
115
116
if sys.platform == "win32":
117
118
def _win_color_fix():
119
"""Attempts to enable ANSI escape code support on Windows 10 and later."""
120
from ctypes import POINTER, WINFUNCTYPE, WinError, windll
121
from ctypes.wintypes import BOOL, DWORD, HANDLE
122
123
STDOUT_HANDLE = -11
124
STDERR_HANDLE = -12
125
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
126
127
def err_handler(result, func, args):
128
if not result:
129
raise WinError()
130
return args
131
132
GetStdHandle = WINFUNCTYPE(HANDLE, DWORD)(("GetStdHandle", windll.kernel32), ((1, "nStdHandle"),))
133
GetConsoleMode = WINFUNCTYPE(BOOL, HANDLE, POINTER(DWORD))(
134
("GetConsoleMode", windll.kernel32),
135
((1, "hConsoleHandle"), (2, "lpMode")),
136
)
137
GetConsoleMode.errcheck = err_handler
138
SetConsoleMode = WINFUNCTYPE(BOOL, HANDLE, DWORD)(
139
("SetConsoleMode", windll.kernel32),
140
((1, "hConsoleHandle"), (1, "dwMode")),
141
)
142
SetConsoleMode.errcheck = err_handler
143
144
for handle_id in [STDOUT_HANDLE, STDERR_HANDLE]:
145
try:
146
handle = GetStdHandle(handle_id)
147
flags = GetConsoleMode(handle)
148
SetConsoleMode(handle, flags | ENABLE_VIRTUAL_TERMINAL_PROCESSING)
149
except OSError:
150
pass
151
152
_win_color_fix()
153
154