File: impl.py

package info (click to toggle)
ros-osrf-pycommon 2.1.7-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 360 kB
  • sloc: python: 1,726; makefile: 146; xml: 16
file content (375 lines) | stat: -rw-r--r-- 11,672 bytes parent folder | download | duplicates (3)
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
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
# Copyright 2014 Open Source Robotics Foundation, Inc.
#
# 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.

from __future__ import print_function

import os
import string

_is_windows = (os.name in ['nt'])

if _is_windows:
    from .windows import _print_ansi_color_win32

_ansi = {
    # Escape sequence start
    'escape': '\x1b',

    # Reset
    'reset': '\x1b[0m',
    '|': '\x1b[0m',
    'atbar': '@|',

    # Bold on
    'boldon': '\x1b[1m',
    '!': '\x1b[1m',
    'atexclamation': '@!',

    # Bold off
    'boldoff': '\x1b[22m',

    # Italics on
    'italicson': '\x1b[3m',
    '/': '\x1b[3m',
    'atfwdslash': '@/',

    # Intalics off
    'italicsoff': '\x1b[23m',

    # Underline on
    'ulon': '\x1b[4m',
    '_': '\x1b[4m',
    'atunderscore': '@_',

    # Underline off
    'uloff': '\x1b[24m',

    # Invert foreground/background on/off
    'invon': '\x1b[7m',
    'invoff': '\x1b[27m',

    # Black foreground
    'k': '\x1b[30m',
    'kf': '\x1b[30m',
    'black': '\x1b[30m',
    'blackf': '\x1b[30m',

    # Black background
    'kb': '\x1b[40m',
    'blackb': '\x1b[40m',

    # Blue foreground
    'b': '\x1b[34m',
    'bf': '\x1b[34m',
    'blue': '\x1b[34m',
    'bluef': '\x1b[34m',

    # Blue background
    'bb': '\x1b[44m',
    'blueb': '\x1b[44m',

    # Cyan foreground
    'c': '\x1b[36m',
    'cf': '\x1b[36m',
    'cyan': '\x1b[36m',
    'cyanf': '\x1b[36m',

    # Cyan background
    'cb': '\x1b[46m',
    'cyanb': '\x1b[46m',

    # Green foreground
    'g': '\x1b[32m',
    'gf': '\x1b[32m',
    'green': '\x1b[32m',
    'greenf': '\x1b[32m',

    # Green background
    'gb': '\x1b[42m',
    'greenb': '\x1b[42m',

    # Purple (magenta) foreground
    'p': '\x1b[35m',
    'pf': '\x1b[35m',
    'purple': '\x1b[35m',
    'purplef': '\x1b[35m',

    # Purple (magenta) background
    'pb': '\x1b[45m',
    'purpleb': '\x1b[45m',

    # Red foreground
    'r': '\x1b[31m',
    'rf': '\x1b[31m',
    'red': '\x1b[31m',
    'redf': '\x1b[31m',

    # Red background
    'rb': '\x1b[41m',
    'redb': '\x1b[41m',

    # White foreground
    'w': '\x1b[37m',
    'wf': '\x1b[37m',
    'white': '\x1b[37m',
    'whitef': '\x1b[37m',

    # White background
    'wb': '\x1b[47m',
    'whiteb': '\x1b[47m',

    # Yellow foreground
    'y': '\x1b[33m',
    'yf': '\x1b[33m',
    'yellow': '\x1b[33m',
    'yellowf': '\x1b[33m',

    # Yellow background
    'yb': '\x1b[43m',
    'yellowb': '\x1b[43m',
}
# Set all values to empty string
_null_ansi = dict([(k, '') for k, v in _ansi.items()])
# Except format preservers used by sanitize
_null_ansi.update({
    'atexclamation': '@!',
    'atfwdslash': '@/',
    'atunderscore': '@_',
    'atbar': '@|',
})
# Enable by default
_enabled = True


def ansi(key):
    """Returns the escape sequence for a given ansi color key."""
    global _ansi, _null_ansi, _enabled
    return (_ansi if _enabled else _null_ansi)[key]


def get_ansi_dict():
    """Returns a copy of the dictionary of keys and ansi escape sequences."""
    global _ansi
    return dict(_ansi)


def enable_ansi_color_substitution_globally():
    """
    Causes :py:func:`format_color` to replace color annotations with ansi
    esacpe sequences.

    It also affects :py:func:`ansi`.

    This is the case by default, so there is no need to call this everytime.

    If you have previously caused all substitutions to evaluate to an empty
    string by calling :py:func:`disable_ansi_color_substitution_globally`, then
    you can restore the escape sequences for substitutions by calling this
    function.
    """
    global _enabled
    _enabled = True


def disable_ansi_color_substitution_globally():
    """
    Causes :py:func:`format_color` to replace color annotations with
    empty strings.

    It also affects :py:func:`ansi`.

    This is not the case by default, so if you want to make all substitutions
    given to either function mentioned above return empty strings then call
    this function.

    The default behavior can be restored by calling
    :py:func:`enable_ansi_color_substitution_globally`.
    """
    global _enabled
    _enabled = False


def format_color(msg):
    """
    Replaces color annotations with ansi escape sequences.

    See this module's documentation for the list of available substitutions.

    If :py:func:`disable_ansi_color_substitution_globally` has been called
    then all color annotations will be replaced by empty strings.

    Also, on Windows all color annotations will be replaced with empty strings.
    If you want colorization on Windows, you must pass annotated strings to
    :py:func:`print_color`.

    :param str msg: string message to be colorized
    :returns: colorized string
    :rtype: str
    """
    global _ansi, _null_ansi, _enabled
    ansi_dict = _null_ansi if not _enabled or _is_windows else _ansi
    return _format_color(msg, ansi_dict)


def _format_color(msg, ansi_dict):
    msg = msg.replace('@!', '@{boldon}')
    msg = msg.replace('@/', '@{italicson}')
    msg = msg.replace('@_', '@{ulon}')
    msg = msg.replace('@|', '@{reset}')

    class ColorTemplate(string.Template):
        delimiter = '@'
    return ColorTemplate(msg).substitute(ansi_dict)


def print_ansi_color_win32(*args, **kwargs):
    """
    Prints color string containing ansi escape sequences to console in Windows.

    If called on a non-Windows system, a :py:exc:`NotImplementedError` occurs.

    Does not respect :py:func:`disable_ansi_color_substitution_globally`.

    Does not substitute color annotations like ``@{r}`` or ``@!``, the string
    must already contain the ``\\033[1m`` style ansi escape sequences.

    Works by splitting each argument up by ansi escape sequence, printing
    the text between the sequences, and doing the corresponding win32 action
    for each ansi sequence encountered.
    """
    if not _is_windows:
        raise NotImplementedError(
            "print_ansi_color_win32() is not implemented for this system")
    return _print_ansi_color_win32(*args, **kwargs)


def print_color(*args, **kwargs):
    """
    Colorizes and prints with an implicit ansi reset at the end

    Calls :py:func:`format_color` on each positional argument and then sends
    all positional and keyword arguments to :py:obj:`print`.

    If the ``end`` keyword argument is not present then the default end value
    ``ansi('reset') + '\\n'`` is used and passed to :py:obj:`print`.

    :py:obj:`os.linesep` is used to determine the actual value for ``\\n``.

    Therefore, if you use the ``end`` keyword argument be sure to include
    an ansi reset escape sequence if necessary.

    On Windows the substituted arguments and keyword arguments are passed to
    :py:func:`print_ansi_color_win32` instead of just :py:obj:`print`.
    """
    global _ansi, _null_ansi, _enabled
    # If no end given, use reset + new line
    if 'end' not in kwargs:
        kwargs['end'] = '{0}{1}'.format(ansi('reset'), os.linesep)
    args = [_format_color(a, _ansi if _enabled else _null_ansi) for a in args]
    # If windows, pass to win32 print color function
    if _enabled and _is_windows:
        return print_ansi_color_win32(*args, **kwargs)
    return print(*args, **kwargs)


def sanitize(msg):
    """
    Sanitizes the given string to prevent :py:func:`format_color` from
    substituting content.

    For example, when the string ``'Email: {user}@{org}'`` is passed to
    :py:func:`format_color` the ``@{org}`` will be incorrectly recognized
    as a colorization annotation and it will fail to substitute with a
    :py:exc:`KeyError`: ``org``.

    In order to prevent this, you can first "sanitize" the string, add color
    annotations, and then pass the whole string to :py:func:`format_color`.

    If you give this function the string ``'Email: {user}@{org}'``, then it
    will return ``'Email: {{user}}@@{{org}}'``. Then if you pass that to
    :py:func:`format_color` it will return ``'Email: {user}@{org}'``.
    In this way :py:func:`format_color` is the reverse of this function and
    so it is safe to call this function on any incoming data if it will
    eventually be passed to :py:func:`format_color`.

    In addition to expanding ``{`` => ``{{``, ``}`` => ``}}``, and
    ``@`` => ``@@``, this function will also replace any instances of
    ``@!``, ``@/``, ``@_``, and ``@|`` with ``@{atexclamation}``,
    ``@{atfwdslash}``, ``@{atunderscore}``, and ``@{atbar}`` respectively.
    And then there are corresponding keys in the ansi dict to convert them
    back.

    For example, if you pass the string ``'|@ Notice @|'`` to this function
    it will return ``'|@@ Notice @{atbar}'``.
    And since ``ansi('atbar')`` always returns ``@|``, even when
    :py:func:`disable_ansi_color_substitution_globally` has been called, the
    result of passing that string to :py:func:`format_color` will be
    ``'|@ Notice @|'`` again.

    There are two main strategies for constructing strings which use both the
    Python :py:func:`str.format` function and the colorization annotations.

    One way is to just build each piece and concatenate the result:

    .. code-block:: python

        print_color("@{r}", "{error}".format(error=error_str))
        # Or using print (remember to include an ansi reset)
        print(format_color("@{r}" + "{error}".format(error=error_str) + "@|"))

    Another way is to use this function on the format string, concatenate to
    the annotations, pass the whole string to :py:func:`format_color`, and then
    format the whole thing:

    .. code-block:: python

        print(format_color("@{r}" + sanitize("{error}") + "@|")
              .format(error=error_str))

    However, the most common use for this function is to sanitize incoming
    strings which may have unknown content:

    .. code-block:: python

        def my_func(user_content):
            print_color("@{y}" + sanitize(user_content))

    This function is not intended to be used on strings with color annotations.

    :param str msg: string message to be sanitized
    :returns: sanitized string
    :rtype: str
    """
    msg = msg.replace('@', '@@')
    msg = msg.replace('{', '{{')
    msg = msg.replace('}', '}}')
    # Above line `msg = msg.replace('@', '@@')` will have converted @* to @@*
    msg = msg.replace('@@!', '@{atexclamation}')
    msg = msg.replace('@@/', '@{atfwdslash}')
    msg = msg.replace('@@_', '@{atunderscore}')
    msg = msg.replace('@@|', '@{atbar}')
    return msg


def test_colors(file=None):
    """Prints a color testing block using :py:func:`print_color`"""
    print_color("| Normal     | @!Bold Normal", file=file)
    print_color("| @{kf}Black      @|| @!@{kf}Bold Black", file=file)
    print_color("| @{rf}Red        @|| @!@{rf}Bold Red", file=file)
    print_color("| @{gf}Green      @|| @!@{gf}Bold Green", file=file)
    print_color("| @{yf}Yellow     @|| @!@{yf}Bold Yellow", file=file)
    print_color("| @{bf}Blue       @|| @!@{bf}Bold Blue", file=file)
    print_color("| @{pf}Purple     @|| @!@{pf}Bold Purple", file=file)
    print_color("| @{cf}Cyan       @|| @!@{cf}Bold Cyan", file=file)
    print_color("| @{wf}White      @|| @!@{wf}Bold White", file=file)