1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "qcoloroutput_p.h"
5
6#include <QtCore/qfile.h>
7#include <QtCore/qhash.h>
8
9#ifndef Q_OS_WIN
10#include <unistd.h>
11#endif
12
13QT_BEGIN_NAMESPACE
14
15class QColorOutputPrivate
16{
17public:
18 QColorOutputPrivate()
19 {
20 m_coloringEnabled = isColoringPossible();
21 }
22
23 ~QColorOutputPrivate() { fflush(stderr); }
24
25 static const char *const foregrounds[];
26 static const char *const backgrounds[];
27
28 inline void write(const QString &msg)
29 {
30 m_buffer.append(a: msg.toLocal8Bit());
31 }
32
33 static QString escapeCode(const QString &in)
34 {
35 const ushort escapeChar = 0x1B;
36 QString result;
37 result.append(c: QChar(escapeChar));
38 result.append(c: QLatin1Char('['));
39 result.append(s: in);
40 result.append(c: QLatin1Char('m'));
41 return result;
42 }
43
44 void insertColor(int id, QColorOutput::ColorCode code) { m_colorMapping.insert(key: id, value: code); }
45 QColorOutput::ColorCode color(int id) const { return m_colorMapping.value(key: id); }
46 bool containsColor(int id) const { return m_colorMapping.contains(key: id); }
47
48 void setSilent(bool silent) { m_silent = silent; }
49 bool isSilent() const { return m_silent; }
50
51 void setCurrentColorID(int colorId) { m_currentColorID = colorId; }
52
53 bool coloringEnabled() const { return m_coloringEnabled; }
54
55 void flushBuffer()
56 {
57 fwrite(ptr: m_buffer.constData(), size: size_t(1), n: size_t(m_buffer.size()), stderr);
58 m_buffer.clear();
59 }
60
61 void discardBuffer()
62 {
63 m_buffer.clear();
64 }
65
66private:
67 QByteArray m_buffer;
68 QColorOutput::ColorMapping m_colorMapping;
69 int m_currentColorID = -1;
70 bool m_coloringEnabled = false;
71 bool m_silent = false;
72
73 /*
74 Returns true if it's suitable to send colored output to \c stderr.
75 */
76 inline bool isColoringPossible() const
77 {
78#if defined(Q_OS_WIN)
79 /* Windows doesn't at all support ANSI escape codes, unless
80 * the user install a "device driver". See the Wikipedia links in the
81 * class documentation for details. */
82 return false;
83#else
84 /* We use QFile::handle() to get the file descriptor. It's a bit unsure
85 * whether it's 2 on all platforms and in all cases, so hopefully this layer
86 * of abstraction helps handle such cases. */
87 return isatty(fd: fileno(stderr));
88#endif
89 }
90};
91
92const char *const QColorOutputPrivate::foregrounds[] =
93{
94 "0;30",
95 "0;34",
96 "0;32",
97 "0;36",
98 "0;31",
99 "0;35",
100 "0;33",
101 "0;37",
102 "1;30",
103 "1;34",
104 "1;32",
105 "1;36",
106 "1;31",
107 "1;35",
108 "1;33",
109 "1;37"
110};
111
112const char *const QColorOutputPrivate::backgrounds[] =
113{
114 "0;40",
115 "0;44",
116 "0;42",
117 "0;46",
118 "0;41",
119 "0;45",
120 "0;43"
121};
122
123/*!
124 \class QColorOutput
125 \nonreentrant
126 \brief Outputs colored messages to \c stderr.
127 \internal
128
129 QColorOutput is a convenience class for outputting messages to \c
130 stderr using color escape codes, as mandated in ECMA-48. QColorOutput
131 will only color output when it is detected to be suitable. For
132 instance, if \c stderr is detected to be attached to a file instead
133 of a TTY, no coloring will be done.
134
135 QColorOutput does its best attempt. but it is generally undefined
136 what coloring or effect the various coloring flags has. It depends
137 strongly on what terminal software that is being used.
138
139 When using `echo -e 'my escape sequence'`, \c{\033} works as an
140 initiator but not when printing from a C++ program, despite having
141 escaped the backslash. That's why we below use characters with
142 value 0x1B.
143
144 It can be convenient to subclass QColorOutput with a private scope,
145 such that the functions are directly available in the class using
146 it.
147
148 \section1 Usage
149
150 To output messages, call write() or writeUncolored(). write() takes
151 as second argument an integer, which QColorOutput uses as a lookup
152 key to find the color it should color the text in. The mapping from
153 keys to colors is done using insertMapping(). Typically this is used
154 by having enums for the various kinds of messages, which
155 subsequently are registered.
156
157 \code
158 enum MyMessage
159 {
160 Error,
161 Important
162 };
163
164 QColorOutput output;
165 output.insertMapping(Error, QColorOutput::RedForeground);
166 output.insertMapping(Import, QColorOutput::BlueForeground);
167
168 output.write("This is important", Important);
169 output.write("Jack, I'm only the selected official!", Error);
170 \endcode
171
172 \sa {http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x329.html}{Bash Prompt HOWTO, 6.1. Colors},
173 {http://linuxgazette.net/issue51/livingston-blade.html}{Linux Gazette, Tweaking Eterm, Edward Livingston-Blade},
174 {http://www.ecma-international.org/publications/standards/Ecma-048.htm}{Standard ECMA-48, Control Functions for Coded Character Sets, ECMA International},
175 {http://en.wikipedia.org/wiki/ANSI_escape_code}{Wikipedia, ANSI escape code},
176 {http://linuxgazette.net/issue65/padala.html}{Linux Gazette, So You Like Color!, Pradeep Padala}
177 */
178
179/*!
180 \internal
181 \enum QColorOutput::ColorCodeComponent
182 \value BlackForeground
183 \value BlueForeground
184 \value GreenForeground
185 \value CyanForeground
186 \value RedForeground
187 \value PurpleForeground
188 \value BrownForeground
189 \value LightGrayForeground
190 \value DarkGrayForeground
191 \value LightBlueForeground
192 \value LightGreenForeground
193 \value LightCyanForeground
194 \value LightRedForeground
195 \value LightPurpleForeground
196 \value YellowForeground
197 \value WhiteForeground
198 \value BlackBackground
199 \value BlueBackground
200 \value GreenBackground
201 \value CyanBackground
202 \value RedBackground
203 \value PurpleBackground
204 \value BrownBackground
205
206 \value DefaultColor QColorOutput performs no coloring. This typically
207 means black on white or white on black, depending
208 on the settings of the user's terminal.
209 */
210
211/*!
212 \internal
213 Constructs a QColorOutput instance, ready for use.
214 */
215QColorOutput::QColorOutput() : d(new QColorOutputPrivate) {}
216
217// must be here so that QScopedPointer has access to the complete type
218QColorOutput::~QColorOutput() = default;
219
220bool QColorOutput::isSilent() const { return d->isSilent(); }
221void QColorOutput::setSilent(bool silent) { d->setSilent(silent); }
222
223/*!
224 \internal
225 Sends \a message to \c stderr, using the color looked up in the color mapping using \a colorID.
226
227 If \a color isn't available in the color mapping, result and behavior is undefined.
228
229 If \a colorID is 0, which is the default value, the previously used coloring is used. QColorOutput
230 is initialized to not color at all.
231
232 If \a message is empty, effects are undefined.
233
234 \a message will be printed as is. For instance, no line endings will be inserted.
235 */
236void QColorOutput::write(QStringView message, int colorID)
237{
238 if (!d->isSilent())
239 d->write(msg: colorify(message, color: colorID));
240}
241
242void QColorOutput::writePrefixedMessage(const QString &message, QtMsgType type,
243 const QString &prefix)
244{
245 static const QHash<QtMsgType, QString> prefixes = {
246 {QtMsgType::QtCriticalMsg, QStringLiteral("Error")},
247 {QtMsgType::QtWarningMsg, QStringLiteral("Warning")},
248 {QtMsgType::QtInfoMsg, QStringLiteral("Info")},
249 {QtMsgType::QtDebugMsg, QStringLiteral("Hint")}
250 };
251
252 Q_ASSERT(prefixes.contains(type));
253 Q_ASSERT(prefix.isEmpty() || prefix.front().isUpper());
254 write(message: (prefix.isEmpty() ? prefixes[type] : prefix) + QStringLiteral(": "), color: type);
255 writeUncolored(message);
256}
257
258/*!
259 \internal
260 Writes \a message to \c stderr as if for instance
261 QTextStream would have been used, and adds a line ending at the end.
262
263 This function can be practical to use such that one can use QColorOutput for all forms of writing.
264 */
265void QColorOutput::writeUncolored(const QString &message)
266{
267 if (!d->isSilent())
268 d->write(msg: message + QLatin1Char('\n'));
269}
270
271/*!
272 \internal
273 Treats \a message and \a colorID identically to write(), but instead of writing
274 \a message to \c stderr, it is prepared for being written to \c stderr, but is then
275 returned.
276
277 This is useful when the colored string is inserted into a translated string(dividing
278 the string into several small strings prevents proper translation).
279 */
280QString QColorOutput::colorify(const QStringView message, int colorID) const
281{
282 Q_ASSERT_X(colorID == -1 || d->containsColor(colorID), Q_FUNC_INFO,
283 qPrintable(QString::fromLatin1("There is no color registered by id %1")
284 .arg(colorID)));
285 Q_ASSERT_X(!message.isEmpty(), Q_FUNC_INFO,
286 "It makes no sense to attempt to print an empty string.");
287
288 if (colorID != -1)
289 d->setCurrentColorID(colorID);
290
291 if (d->coloringEnabled() && colorID != -1) {
292 const int color = d->color(id: colorID);
293
294 /* If DefaultColor is set, we don't want to color it. */
295 if (color & DefaultColor)
296 return message.toString();
297
298 const int foregroundCode = (color & ForegroundMask) >> ForegroundShift;
299 const int backgroundCode = (color & BackgroundMask) >> BackgroundShift;
300 QString finalMessage;
301 bool closureNeeded = false;
302
303 if (foregroundCode > 0) {
304 finalMessage.append(
305 s: QColorOutputPrivate::escapeCode(
306 in: QLatin1String(QColorOutputPrivate::foregrounds[foregroundCode - 1])));
307 closureNeeded = true;
308 }
309
310 if (backgroundCode > 0) {
311 finalMessage.append(
312 s: QColorOutputPrivate::escapeCode(
313 in: QLatin1String(QColorOutputPrivate::backgrounds[backgroundCode - 1])));
314 closureNeeded = true;
315 }
316
317 finalMessage.append(v: message);
318
319 if (closureNeeded)
320 finalMessage.append(s: QColorOutputPrivate::escapeCode(in: QLatin1String("0")));
321
322 return finalMessage;
323 }
324
325 return message.toString();
326}
327
328void QColorOutput::flushBuffer()
329{
330 d->flushBuffer();
331}
332
333void QColorOutput::discardBuffer()
334{
335 d->discardBuffer();
336}
337
338/*!
339 \internal
340 Adds a color mapping from \a colorID to \a colorCode, for this QColorOutput instance.
341 */
342void QColorOutput::insertMapping(int colorID, const ColorCode colorCode)
343{
344 d->insertColor(id: colorID, code: colorCode);
345}
346
347QT_END_NAMESPACE
348

source code of qtdeclarative/src/qmlcompiler/qcoloroutput.cpp