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

source code of qtdeclarative/tools/qmllint/qcoloroutput.cpp