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

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