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

source code of qtxmlpatterns/src/xmlpatterns/api/qcoloroutput.cpp