1# SPDX-FileCopyrightText: 2020 GNOME Foundation
2# SPDX-License-Identifier: Apache-2.0 OR GPL-3.0-or-later
3
4import os
5import platform
6import sys
7import time
8
9
10def setup_output():
11    try:
12        if platform.system().lower() == 'windows':
13            return os.isatty(sys.stdout.fileno())
14        return os.isatty(sys.stdout.fileno()) and os.environ.get('TERM') != 'dumb'
15    except Exception:
16        return False
17
18
19def setup_debug():
20    if os.environ.get('GIDOCGEN_DEBUG', '0') == '1':
21        return True
22    return False
23
24
25log_colorize_output = setup_output()
26log_debug = setup_debug()
27log_quiet = False
28log_fatal_warnings = False
29log_warnings_counter = 0
30log_epoch = 0
31
32colors = {
33    'NONE': "[0m",
34    'RED': "[1;31m",
35    'GREEN': "[1;32m",
36    'YELLOW': "[1;33m",
37    'BLUE': "[1;34m",
38    'LIGHT_GREY': "[1;37m",
39    'DARK_GREY': "[1;90m",
40}
41
42modifiers = {
43    'NONE': "[0m",
44    'DEFAULT': "[4;39m",
45    'BOLD_DEFAULT': "[1;39m",
46    'DIM_DEFAULT': "[2;39m",
47}
48
49
50logged_once = set()
51
52
53class AnsiEscape(object):
54    '''
55    A string-like object that contains an ANSI escaped string.
56    '''
57    char = '\033'
58
59    def __init__(self, *args, **kwargs):
60        self.text = kwargs.get('text', '')
61        self.color = kwargs.get('color', 'NONE')
62        self.mods = kwargs.get('mods', 'DEFAULT')
63
64    def __str__(self):
65        if self.mods != 'DEFAULT':
66            return f'{AnsiEscape.char}{modifiers[self.mods]}{self.text}{AnsiEscape.char}{modifiers["NONE"]}'
67        return f'{AnsiEscape.char}{colors[self.color]}{self.text}{AnsiEscape.char}{colors["NONE"]}'
68
69
70def color(text, color_id):
71    return f'\u001b[38;5;{color_id}m{text}\u001b[0m'
72
73
74def red(text):
75    return AnsiEscape(text=text, color='RED')
76
77
78def green(text):
79    return AnsiEscape(text=text, color='GREEN')
80
81
82def yellow(text):
83    return AnsiEscape(text=text, color='YELLOW')
84
85
86def blue(text):
87    return AnsiEscape(text=text, color='BLUE')
88
89
90def bold(text):
91    return AnsiEscape(text=text, mods='BOLD_DEFAULT')
92
93
94def dim(text):
95    return AnsiEscape(text=text, mods='DIM_DEFAULT')
96
97
98class Location(object):
99    '''
100    A location object, pointing to a filename and a line.
101    '''
102    def __init__(self, **kwargs):
103        self.filename = kwargs.get('filename', 'input')
104        self.line = kwargs.get('line', 0)
105
106    def __str__(self):
107        return f'{self.filename}:{self.line}:'
108
109
110def log_once(text, prefix=None, location=None):
111    '''
112    Prints a line of text only once.
113    '''
114    t = tuple(text, prefix, location)
115    if t in logged_once:
116        return
117    log(text, prefix, location)
118    logged_once.add(t)
119
120
121def set_quiet(quiet):
122    global log_quiet
123    log_quiet = quiet
124
125
126def set_fatal_warnings(fatal_warnings):
127    global log_fatal_warnings
128    log_fatal_warnings = fatal_warnings
129
130
131def set_log_epoch(epoch=0):
132    global log_epoch
133    if epoch == 0:
134        log_epoch = time.monotonic()
135    else:
136        log_epoch = epoch
137
138
139def log(text, prefix=None, location=None, out=None):
140    '''
141    Prints a line of text using the given prefix and location.
142
143    @prefix: (optional): a prefix string, or an AnsiEscape object
144    @location: (optional): a location string, or a Location object
145    @out: (optional): a File object
146    '''
147    res = []
148    if prefix:
149        res += [str(prefix), ': ']
150    if location:
151        res += [str(location), ' ']
152    res += [text]
153    print(''.join(res), file=out)
154
155
156def error(text, location=None):
157    '''Prints an error message'''
158    log(text, prefix=red('ERROR'), location=location, out=sys.stderr)
159    sys.exit(1)
160
161
162def warning(text, location=None):
163    '''Prints a warning message'''
164    log(text, prefix=yellow('WARNING'), location=location, out=sys.stderr)
165
166    global log_warnings_counter
167    log_warnings_counter += 1
168
169    if log_fatal_warnings:
170        sys.exit(1)
171
172
173def info(text, location=None):
174    '''Prints an information message'''
175    if not log_quiet:
176        log(text, prefix=green('INFO'), location=location)
177
178
179def debug(text, location=None):
180    '''Prints a debug message'''
181    if log_debug:
182        log(text, prefix=dim('DEBUG'), location=location)
183
184
185def deprecation(text, location=None):
186    '''Prints a deprecation warning'''
187    log(text, prefix=blue('DEPRECATED'), location=location, out=sys.stderr)
188    global log_warnings_counter
189    log_warnings_counter += 1
190
191
192def checkpoint(prefix=None):
193    if log_quiet:
194        return
195    elapsed = (time.monotonic() - log_epoch)
196    msg = f"Elapsed time {elapsed:.3f} seconds"
197    if prefix is None:
198        prefix = green('INFO')
199    log(msg, prefix)
200
201
202def report():
203    if log_quiet:
204        return
205    elapsed = (time.monotonic() - log_epoch)
206    report = [""]
207    report += [f"Elapsed time: {elapsed:.3f} seconds"]
208    report += [f"Total warnings: {log_warnings_counter}"]
209    print("\n".join(report))
210    return log_warnings_counter != 0
211