aboutsummaryrefslogtreecommitdiffstats
path: root/pylog/onaplogging/colorFormatter.py
blob: 5de0d604beed28da9e9890e3fe20ff020d02df7b (plain)
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
# Copyright 2018 ke liang <lokyse@163.com>.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os

from logging import Formatter, LogRecord
from deprecated import deprecated
from warnings import warn
from typing import Optional, Union, Dict

from onaplogging.utils.system import is_above_python_2_7, is_above_python_3_2
from onaplogging.utils.styles import (
    ATTRIBUTES,
    HIGHLIGHTS,
    COLORS,

    ATTRIBUTE_TAG,
    HIGHLIGHT_TAG,
    COLOR_TAG,

    RESET,
    FMT_STR
)


class BaseColorFormatter(Formatter):
    """Text color formatter class.

    Wraps the logging. Uses Git shell coloring codes.  Doesn't support Windows
    CMD yet. If `fmt` is not suppied, the `style` is used. Eventually converts
    a LogRecord object to "colored" text.

    TODO:
        Support for Windows CMD.
    Extends:
        logging.Formatter
    Properties:
        style       : '%', '{' or '$' formatting.
        datefrmt    : ISO8601-like (or RFC 3339-like) format.
    Args:
        fmt         : human-readable format.                  Defaults to None.
        datefmt     : ISO8601-like (or RFC 3339-like) format. Defaults to None.
        colorfmt    : Color schemas for logging levels.       Defaults to None.
        style       : '%', '{' or '$' formatting.             Defaults to '%'.
    Methods:
        format      : formats a LogRecord record.
        _parseColor : selects colors based on a logging levels.
    """

    @property
    def style(self):
        # type: () -> str
        return self.__style  # name mangling with __ to avoid accidents

    @property
    def colorfmt(self):
        # type: () -> str
        return self.__colorfmt

    @style.setter
    def style(self, value):
        # type: (str) -> None
        """Assign new style."""
        self.__style = value

    @colorfmt.setter
    def colorfmt(self, value):
        # type: (str) -> None
        """Assign new color format."""
        self.__colorfmt = value

    def __init__(self,
                 fmt=None,          # type: Optional[str]
                 datefmt=None,      # type: Optional[str]
                 colorfmt=None,     # type: Optional[Dict]
                 style="%"):        # type: Optional[str]

        if is_above_python_3_2():
            super(BaseColorFormatter, self). \
            __init__(fmt=fmt,  # noqa: E122
                    datefmt=datefmt,
                    style=style)

        elif is_above_python_2_7():
            super(BaseColorFormatter, self). \
            __init__(fmt, datefmt)  # noqa: E122

        else:
            Formatter. \
            __init__(self, fmt, datefmt)  # noqa: E122
        self.style = style
        self.colorfmt = colorfmt

    def format(self, record):
        """Text formatter.

        Connects 2 methods. First it extract a level and a colors
        assigned to  this level in  the BaseColorFormatter  class.
        Second it applied the colors to the text.

        Args:
            record   : an instance of a logged event.
        Returns:
            str      : "colored" text (formatted text).
        """

        if is_above_python_2_7():
            s = super(BaseColorFormatter, self). \
                format(record)

        else:
            s = Formatter. \
                format(self, record)

        color, highlight, attribute = self._parse_color(record)

        return apply_color(s, color, highlight, attrs=attribute)

    def _parse_color(self, record):
        # type: (LogRecord) -> (Optional[str], Optional[str], Optional[str])
        """Color formatter based on the logging level.

        This method formats the  record according to  its  level
        and a color format  set for that level.  If the level is
        not found, then this method will eventually return None.

        Args:
            record  : an instance of a logged event.
        Returns:
            str     : Colors.
            str     : Hightlight tag.
            str     : Attribute tag.
        """
        if  self.colorfmt and \
            isinstance(self.colorfmt, dict):

            level = record.levelname
            colors = self.colorfmt.get(level, None)

            if  colors is not None and \
                isinstance(colors, dict):
                return (colors.get(COLOR_TAG, None),        # noqa: E201
                        colors.get(HIGHLIGHT_TAG, None),
                        colors.get(ATTRIBUTE_TAG, None))    # noqa: E202
        return None, None, None

    @deprecated(reason="Will be removed. Use _parse_color(record) instead.")
    def _parseColor(self, record):
        """
        Color based on logging level.
        See method _parse_color(record).
        """
        return self._parse_color(record)


def apply_color(text,           # type: str
                color=None,     # type: Optional[str]
                on_color=None,  # type: Optional[str]
                attrs=None):    # type: Optional[Union[str, list]]
    # type: (...) -> str
    """Applies color codes to the text.

    Args:
        text      : text to be "colored" (formatted).
        color     : Color in human-readable format. Defaults to None.
        highlight : Hightlight color in human-readable format.
                         Previously called "on_color". Defaults to None.
        attrs     : Colors for attribute(s). Defaults to None.
    Returns:
        str       : "colored" text (formatted text).
    """
    warn("`on_color` will be replaced with `highlight`.", DeprecationWarning)
    highlight = on_color  # replace the parameter and remove

    if os.name in ('nt', 'ce'):
        return text

    if isinstance(attrs, str):
        attrs = [attrs]

    ansi_disabled = os.getenv('ANSI_COLORS_DISABLED', None)

    if ansi_disabled is None:

        if  color is not None and \
            isinstance(color, str):
            text = FMT_STR % (COLORS.get(color, 0), text)

        if  highlight is not None and \
            isinstance(highlight, str):
            text = FMT_STR % (HIGHLIGHTS.get(highlight, 0), text)

        if attrs is not None:
            for attr in attrs:
                text = FMT_STR % (ATTRIBUTES.get(attr, 0), text)

        text += RESET  # keep origin color for tail spaces

    return text


@deprecated(reason="Will be removed. Call apply_color(...) instead.")
def colored(text, color=None, on_color=None, attrs=None):
    """
    Format text with color codes.
    See method apply_color(text, color, on_color, attrs).
    """
    return apply_color(text, color, on_color, attrs)