[go: up one dir, main page]

File: colored.py

package info (click to toggle)
colored 2.2.4-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 240 kB
  • sloc: python: 2,064; makefile: 4
file content (350 lines) | stat: -rw-r--r-- 10,377 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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import annotations

import os
import sys
import platform
from typing import Any

from .library import Library
from .hexadecimal import Hex
from .utilities import Utilities

TTY_AWARE = True
IS_TTY = sys.stdout.isatty() and sys.stderr.isatty()

_win_vterm_mode = None


class Colored:

    def __init__(self, name: Any):
        """ name can be str or int instead.

        Args:
            name: name of color or number of color
        """
        self._name: str = str(name).lower()
        self._hex_color: str = ''
        self._hex = Hex()
        self._utils = Utilities()

        self._ESC: str = Library.ESC
        self._END: str = Library.END

        self._STYLES: dict = Library.STYLES
        self._FOREGROUND_256: str = Library.FOREGROUND_256
        self._BACKGROUND_256: str = Library.BACKGROUND_256

        self._COLORS: dict = Library.COLORS
        self._HEX_COLORS: dict = Library.HEX_COLORS
        self._UNDERLINE_COLOR: str = Library.UNDERLINE_COLOR

        if self._name.startswith('#'):
            self._hex_color: str = self._hex.find(self._name)

        self.enable_windows_terminal_mode()

    def attribute(self, line_color: str = '') -> str:
        """ Returns stylize text.

        Args:
            line_color: Sets color of the underline.

        Returns:
            str: Style code.
        """
        formatting: str = self._name
        if not self.enabled():
            return ''

        if self._name:
            self._utils.is_style_exist(self._name)

            if self._name in ('underline', '4') and line_color:
                line_color: str = str(line_color).lower()
                self._utils.is_color_exist(line_color)
                if not line_color.isdigit():
                    line_color: str = self._COLORS[line_color]
                return f'{self._UNDERLINE_COLOR}{line_color}{self._END}'

            if not self._name.isdigit():
                formatting: str = self._STYLES[self._name]

        return f'{self._ESC}{formatting}{self._END}'

    def foreground(self) -> str:
        """ Returns a foreground 256 color code. """
        color: str = self._name
        if not self.enabled():
            return ''

        if self._name:
            self._utils.is_color_exist(self._name)

            if self._name.startswith('#'):
                color: str = self._hex_color
            elif not self._name.isdigit():
                color: str = self._COLORS[self._name]

        return f'{self._FOREGROUND_256}{color}{self._END}'

    def background(self) -> str:
        """ Returns a background 256 color code. """
        color: str = self._name
        if not self.enabled():
            return ''

        if self._name:
            self._utils.is_color_exist(self._name)

            if self._name.startswith('#'):
                color: str = self._hex_color
            elif not self._name.isdigit():
                color: str = self._COLORS[self._name]

        return f'{self._BACKGROUND_256}{color}{self._END}'

    @staticmethod
    def enable_windows_terminal_mode() -> Any:
        """ Contribution by: Andreas Fredrik Klasson, Magnus Heskestad,
        Dimitris Papadopoulos.

        Enable virtual terminal processing in Windows terminal. Does
        nothing if not on Windows. This is based on the rejected
        enhancement <https://bugs.python.org/issue29059>.
        """
        global _win_vterm_mode
        if _win_vterm_mode is not None:
            return _win_vterm_mode

        # Note: Cygwin should return something like 'CYGWIN_NT...'
        _win_vterm_mode = platform.system().lower() == 'windows'
        if _win_vterm_mode is False:
            return

        from ctypes import windll, wintypes, byref, c_void_p
        ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
        INVALID_HANDLE_VALUE = c_void_p(-1).value
        STD_OUTPUT_HANDLE = wintypes.DWORD(-11)

        hStdout = windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
        if hStdout == INVALID_HANDLE_VALUE:
            _win_vterm_mode = False
            return

        mode = wintypes.DWORD(0)
        ok = windll.kernel32.GetConsoleMode(wintypes.HANDLE(hStdout), byref(mode))
        if not ok:
            _win_vterm_mode = False
            return

        mode = wintypes.DWORD(mode.value | ENABLE_VIRTUAL_TERMINAL_PROCESSING)
        ok = windll.kernel32.SetConsoleMode(wintypes.HANDLE(hStdout), mode)
        if not ok:
            # Something went wrong, probably a version too old
            # to support the VT100 mode.
            # To be more certain we could check kernel32.GetLastError
            # for STATUS_INVALID_PARAMETER, but since we only enable
            # one flag we can be certain enough.
            _win_vterm_mode = False
            return

    @staticmethod
    def enabled() -> bool:
        """ Contribution by Andreas Motl.
        https://github.com/chalk/supports-color#info
        Use the environment variable FORCE_COLOR=1 (level 1), FORCE_COLOR=2
        (level 2), or FORCE_COLOR=3 (level 3) to forcefully enable color, or
        FORCE_COLOR=0 to forcefully disable. The use of FORCE_COLOR overrides
        all other color support checks.
        """
        if 'FORCE_COLOR' in os.environ:
            if int(os.environ['FORCE_COLOR']) == 0:
                return False
            return True

        # https://no-color.org/
        # Check for the presence of a NO_COLOR environment variable that, when
        # present (regardless of its value), prevents the addition of ANSI
        # color.
        if 'NO_COLOR' in os.environ:
            return False

        # Also disable coloring when not printing to a TTY.
        if TTY_AWARE and not IS_TTY:
            return False

        # In all other cases, enable coloring.
        return True


def style(name: int | str, color: str | int = '') -> str:
    """ Alias for Colored(name).attribute()

    Args:
        name: Sets the name of the color.
        color: Sets the underline color.

    Returns:
        str: Style code.
    """
    return Colored(name).attribute(color)


def fore(name: int | str) -> str:
    """ Combination with text returns color text.

    Args:
        name: Sets the name of the color.

    Returns:
        str: Foreground code.
    """
    return Colored(name).foreground()


def back(name: int | str) -> str:
    """ Combination with text returns color background with text.

    Args:
        name: Sets the name of the color.

    Returns:
        str: Background code.
    """
    return Colored(name).background()


def fore_rgb(r: int | str, g: int | str, b: int | str) -> str:
    """ Combination with text returns color text.

    Args:
        r: Red color.
        g: Green color.
        b: Blue color.

    Returns:
        str: Foreground RGB code.
    """
    utils = Utilities()
    r, g, b = utils.is_percentage((r, g, b))
    return f'{Library.FOREGROUND_RGB}{r};{g};{b}{Library.END}'


def back_rgb(r: int | str, g: int | str, b: int | str) -> str:
    """ Combination with text returns color background with text.

    Args:
        r: Red color.
        g: Green color.
        b: Blue color.

    Returns:
        str: Background RGB code.
    """
    utils = Utilities()
    r, g, b = utils.is_percentage((r, g, b))
    return f'{Library.BACKGROUND_RGB}{r};{g};{b}{Library.END}'


def attr(name: int | str) -> str:
    """ This will be deprecated in the future, do not use with version >= 2.0.0,
    instead please use style() function (See issue #28).

    Args:
        name: Sets the name of the color.

    Returns:
        str: Style code.
    """
    return Colored(name).attribute()


def fg(name: int | str) -> str:
    """ This will be deprecated in the future, do not use with version >= 2.0.0,
    instead please use style() function (See issue #28).

    Args:
        name: Sets the name of the color.

    Returns:
        str: Foreground code.
    """
    return Colored(name).foreground()


def bg(name: int | str) -> str:
    """ This will be deprecated in the future, do not use with version >= 2.0.0,
    instead please use style() function (See issue #28).

    Args:
        name: Sets the name of the color.

    Returns:
        str: Background code.
    """
    return Colored(name).background()


def stylize(text: str, formatting: int | str, reset=True) -> str:
    """ Conveniently styles your text as and resets ANSI codes at its end.

    Args:
        text: String type text.
        formatting: Sets the formatting (color or style) of the text.
        reset: Reset the formatting style at its end, default is True.

    Returns:
        str: Formatting string text.
    """
    terminator: str = style('reset') if reset else ''
    return f'{"".join(formatting)}{text}{terminator}'


def _c0wrap(formatting: str) -> str:
    """ Contribution by brrzap.
    Wrap a set of ANSI styles in C0 control codes for readline safety.

    Args:
        formatting: Sets the formatting (color or style) of the text.
    """
    C0_SOH: str = '\x01'  # mark the beginning of nonprinting characters
    C0_STX: str = '\x02'  # mark the end of nonprinting characters
    return f'{C0_SOH}{"".join(formatting)}{C0_STX}'


def stylize_interactive(text: str, formatting: str, reset=True) -> str:
    """ Contribution by: Jay Deiman, brrzap
    stylize() variant that adds C0 control codes (SOH/STX) for readline
    safety.

    Args:
        text: String type text.
        formatting: Sets the formatting (color or style) of the text.
        reset: Reset the formatting style at its end, default is True.

    Returns:
        str: Formatting string text.
    """
    # problem: readline includes bare ANSI codes in width calculations.
    # solution: wrap nonprinting codes in SOH/STX when necessary.
    # see: https://gitlab.com/dslackw/colored/issues/5
    terminator: str = _c0wrap(style('reset')) if reset else ''
    return f'{_c0wrap(formatting)}{text}{terminator}'


def set_tty_aware(awareness=True) -> None:
    """ Contribution by: Andreas Motl, Jay Deiman

    Makes all interactions here tty aware. This means that if either
    stdout or stderr are directed to something other than a tty,
    colorization will not be added.

    Args:
        awareness: Default is True.
    """
    global TTY_AWARE
    TTY_AWARE = awareness