|
7 | 7 | import pathlib |
8 | 8 | import shutil |
9 | 9 | import sys |
10 | | -from typing import TYPE_CHECKING, Any |
| 10 | +from typing import TYPE_CHECKING, Any, TextIO |
11 | 11 |
|
12 | 12 | import git |
13 | 13 | from tox.plugin import impl |
|
33 | 33 | "::error title=tox-extra detected git dirty status:: " + WARNING_MSG_GIT_DIRTY |
34 | 34 | ) |
35 | 35 |
|
36 | | -# Change the color of stderr from default red to a dimmed grey |
37 | | -if "TOX_STDERR_COLOR" not in os.environ: |
38 | | - os.environ["TOX_STDERR_COLOR"] = "LIGHTBLACK_EX" |
| 36 | + |
| 37 | +# Based on Ansible implementation |
| 38 | +def to_bool(value: str | bool | None) -> bool: # pragma: no cover # noqa: FBT001 |
| 39 | + """Return a bool for the arg.""" |
| 40 | + if value is None or isinstance(value, bool): |
| 41 | + return bool(value) |
| 42 | + if isinstance(value, str): |
| 43 | + value = value.lower() |
| 44 | + return value in ("yes", "on", "1", "true", 1) |
| 45 | + |
| 46 | + |
| 47 | +def should_do_markup(stream: TextIO = sys.stdout) -> bool: # pragma: no cover |
| 48 | + """Decide about use of ANSI colors.""" |
| 49 | + py_colors = None |
| 50 | + |
| 51 | + # https://xkcd.com/927/ |
| 52 | + for env_var in [ |
| 53 | + "PY_COLORS", |
| 54 | + "CLICOLOR", |
| 55 | + "FORCE_COLOR", |
| 56 | + "TOX_COLORED", |
| 57 | + "GITHUB_ACTIONS", # they support ANSI |
| 58 | + ]: |
| 59 | + value = os.environ.get(env_var, None) |
| 60 | + if value is not None: |
| 61 | + py_colors = to_bool(value) |
| 62 | + break |
| 63 | + |
| 64 | + # If deliberately disabled colors |
| 65 | + if os.environ.get("NO_COLOR", None): |
| 66 | + return False |
| 67 | + |
| 68 | + # User configuration requested colors |
| 69 | + if py_colors is not None: |
| 70 | + return to_bool(py_colors) |
| 71 | + |
| 72 | + term = os.environ.get("TERM", "") |
| 73 | + if "xterm" in term: |
| 74 | + return True |
| 75 | + |
| 76 | + if term == "dumb": |
| 77 | + return False |
| 78 | + |
| 79 | + # Use tty detection logic as last resort because there are numerous |
| 80 | + # factors that can make isatty return a misleading value, including: |
| 81 | + # - stdin.isatty() is the only one returning true, even on a real terminal |
| 82 | + # - stderr returning false if user uses a error stream coloring solution |
| 83 | + return stream.isatty() |
39 | 84 |
|
40 | 85 |
|
41 | 86 | def is_git_dirty(path: str) -> bool: |
@@ -109,3 +154,21 @@ def tox_after_run_commands( |
109 | 154 | if os.environ.get("CI") == "true": |
110 | 155 | raise Fail(ERROR_MSG_GIT_DIRTY) |
111 | 156 | logger.warning(WARNING_MSG_GIT_DIRTY) |
| 157 | + |
| 158 | + |
| 159 | +if should_do_markup(): |
| 160 | + # Workaround for tools that do not naturally detect colors in CI system |
| 161 | + # like Github Actions. Still, when already defined we will not add them. |
| 162 | + overrides = { |
| 163 | + "ANSIBLE_FORCE_COLOR": "1", |
| 164 | + "COLOR": "yes", |
| 165 | + "FORCE_COLOR": "1", |
| 166 | + "MYPY_FORCE_COLOR": "1", |
| 167 | + "PRE_COMMIT_COLOR": "always", |
| 168 | + "PY_COLORS": "1", |
| 169 | + "TOX_COLORED": "yes", |
| 170 | + "TOX_STDERR_COLOR": "LIGHTBLACK_EX", # stderr in grey instead of red |
| 171 | + } |
| 172 | + for k, v in overrides.items(): |
| 173 | + if k not in os.environ: |
| 174 | + os.environ[k] = v |
0 commit comments