File: env.py

package info (click to toggle)
progressbar2 4.5.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,216 kB
  • sloc: python: 8,001; makefile: 155
file content (189 lines) | stat: -rw-r--r-- 5,877 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
from __future__ import annotations

import contextlib
import enum
import os
import re
import typing


@typing.overload
def env_flag(name: str, default: bool) -> bool: ...


@typing.overload
def env_flag(name: str, default: bool | None = None) -> bool | None: ...


def env_flag(name: str, default: bool | None = None) -> bool | None:
    """
    Accepts environt variables formatted as y/n, yes/no, 1/0, true/false,
    on/off, and returns it as a boolean.

    If the environment variable is not defined, or has an unknown value,
    returns `default`
    """
    v = os.getenv(name)
    if v and v.lower() in ('y', 'yes', 't', 'true', 'on', '1'):
        return True
    if v and v.lower() in ('n', 'no', 'f', 'false', 'off', '0'):
        return False
    return default


class ColorSupport(enum.IntEnum):
    """Color support for the terminal."""

    NONE = 0
    XTERM = 16
    XTERM_256 = 256
    XTERM_TRUECOLOR = 16777216
    WINDOWS = 8

    @classmethod
    def from_env(cls) -> ColorSupport:
        """Get the color support from the environment.

        If any of the environment variables contain `24bit` or `truecolor`,
        we will enable true color/24 bit support. If they contain `256`, we
        will enable 256 color/8 bit support. If they contain `xterm`, we will
        enable 16 color support. Otherwise, we will assume no color support.

        If `JUPYTER_COLUMNS` or `JUPYTER_LINES` or `JPY_PARENT_PID` is set, we
        will assume true color support.

        Note that the highest available value will be used! Having
        `COLORTERM=truecolor` will override `TERM=xterm-256color`.
        """
        variables = (
            'FORCE_COLOR',
            'PROGRESSBAR_ENABLE_COLORS',
            'COLORTERM',
            'TERM',
        )

        if JUPYTER:
            # Jupyter notebook always supports true color.
            return cls.XTERM_TRUECOLOR
        elif os.name == 'nt':
            # We can't reliably detect true color support on Windows, so we
            # will assume it is supported if the console is configured to
            # support it.
            from .terminal.os_specific import windows

            if (
                windows.get_console_mode()
                & windows.WindowsConsoleModeFlags.ENABLE_PROCESSED_OUTPUT
            ):
                return cls.XTERM_TRUECOLOR
            else:
                return cls.WINDOWS  # pragma: no cover

        support = cls.NONE
        for variable in variables:
            value = os.environ.get(variable)
            if value is None:
                continue
            elif value in {'truecolor', '24bit'}:
                # Truecolor support, we don't need to check anything else.
                support = cls.XTERM_TRUECOLOR
                break
            elif '256' in value:
                support = max(cls.XTERM_256, support)
            elif value == 'xterm':
                support = max(cls.XTERM, support)

        return support


def is_ansi_terminal(
    fd: typing.IO[typing.Any],
    is_terminal: bool | None = None,
) -> bool | None:  # pragma: no cover
    if is_terminal is None:
        # Jupyter Notebooks support progress bars
        if JUPYTER:
            is_terminal = True
        # This works for newer versions of pycharm only. With older versions
        # there is no way to check.
        elif os.environ.get('PYCHARM_HOSTED') == '1' and not os.environ.get(
            'PYTEST_CURRENT_TEST'
        ):
            is_terminal = True

    if is_terminal is None:
        # check if we are writing to a terminal or not. typically a file object
        # is going to return False if the instance has been overridden and
        # isatty has not been defined we have no way of knowing so we will not
        # use ansi.  ansi terminals will typically define one of the 2
        # environment variables.
        with contextlib.suppress(Exception):
            is_tty: bool = fd.isatty()
            # Try and match any of the huge amount of Linux/Unix ANSI consoles
            if is_tty and ANSI_TERM_RE.match(os.environ.get('TERM', '')):
                is_terminal = True
            # ANSICON is a Windows ANSI compatible console
            elif 'ANSICON' in os.environ:
                is_terminal = True
            elif os.name == 'nt':
                from .terminal.os_specific import windows

                return bool(
                    windows.get_console_mode()
                    & windows.WindowsConsoleModeFlags.ENABLE_PROCESSED_OUTPUT,
                )
            else:
                is_terminal = None

    return is_terminal


def is_terminal(
    fd: typing.IO[typing.Any],
    is_terminal: bool | None = None,
) -> bool | None:
    if is_terminal is None:
        # Full ansi support encompasses what we expect from a terminal
        is_terminal = is_ansi_terminal(fd) or None

    if is_terminal is None:
        # Allow a environment variable override
        is_terminal = env_flag('PROGRESSBAR_IS_TERMINAL', None)

    if is_terminal is None:  # pragma: no cover
        # Bare except because a lot can go wrong on different systems. If we do
        # get a TTY we know this is a valid terminal
        try:
            is_terminal = fd.isatty()
        except Exception:
            is_terminal = False

    return is_terminal


# Enable Windows full color mode if possible
if os.name == 'nt':
    pass

    # os_specific.set_console_mode()

JUPYTER = bool(
    os.environ.get('JUPYTER_COLUMNS')
    or os.environ.get('JUPYTER_LINES')
    or os.environ.get('JPY_PARENT_PID')
)
COLOR_SUPPORT = ColorSupport.from_env()
ANSI_TERMS = (
    '([xe]|bv)term',
    '(sco)?ansi',
    'cygwin',
    'konsole',
    'linux',
    'rxvt',
    'screen',
    'tmux',
    'vt(10[02]|220|320)',
)
ANSI_TERM_RE: re.Pattern[str] = re.compile(
    f"^({'|'.join(ANSI_TERMS)})", re.IGNORECASE
)