aboutsummaryrefslogtreecommitdiffstats
path: root/src/qmlcompiler/qcoloroutput.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qmlcompiler/qcoloroutput.cpp')
-rw-r--r--src/qmlcompiler/qcoloroutput.cpp325
1 files changed, 325 insertions, 0 deletions
diff --git a/src/qmlcompiler/qcoloroutput.cpp b/src/qmlcompiler/qcoloroutput.cpp
new file mode 100644
index 0000000000..8e0d57faf5
--- /dev/null
+++ b/src/qmlcompiler/qcoloroutput.cpp
@@ -0,0 +1,325 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "qcoloroutput_p.h"
+
+#include <QtCore/qfile.h>
+#include <QtCore/qhash.h>
+
+#ifndef Q_OS_WIN
+#include <unistd.h>
+#endif
+
+QT_BEGIN_NAMESPACE
+
+class QColorOutputPrivate
+{
+public:
+ QColorOutputPrivate()
+ {
+ m_coloringEnabled = isColoringPossible();
+ }
+
+ static const char *const foregrounds[];
+ static const char *const backgrounds[];
+
+ inline void write(const QString &msg)
+ {
+ const QByteArray encodedMsg = msg.toLocal8Bit();
+ fwrite(encodedMsg.constData(), size_t(1), size_t(encodedMsg.size()), stderr);
+ }
+
+ static QString escapeCode(const QString &in)
+ {
+ const ushort escapeChar = 0x1B;
+ QString result;
+ result.append(QChar(escapeChar));
+ result.append(QLatin1Char('['));
+ result.append(in);
+ result.append(QLatin1Char('m'));
+ return result;
+ }
+
+ void insertColor(int id, QColorOutput::ColorCode code) { m_colorMapping.insert(id, code); }
+ QColorOutput::ColorCode color(int id) const { return m_colorMapping.value(id); }
+ bool containsColor(int id) const { return m_colorMapping.contains(id); }
+
+ void setSilent(bool silent) { m_silent = silent; }
+ bool isSilent() const { return m_silent; }
+
+ void setCurrentColorID(int colorId) { m_currentColorID = colorId; }
+
+ bool coloringEnabled() const { return m_coloringEnabled; }
+
+private:
+ QFile m_out;
+ QColorOutput::ColorMapping m_colorMapping;
+ int m_currentColorID = -1;
+ bool m_coloringEnabled = false;
+ bool m_silent = false;
+
+ /*
+ Returns true if it's suitable to send colored output to \c stderr.
+ */
+ inline bool isColoringPossible() const
+ {
+#if defined(Q_OS_WIN)
+ /* Windows doesn't at all support ANSI escape codes, unless
+ * the user install a "device driver". See the Wikipedia links in the
+ * class documentation for details. */
+ return false;
+#else
+ /* We use QFile::handle() to get the file descriptor. It's a bit unsure
+ * whether it's 2 on all platforms and in all cases, so hopefully this layer
+ * of abstraction helps handle such cases. */
+ return isatty(fileno(stderr));
+#endif
+ }
+};
+
+const char *const QColorOutputPrivate::foregrounds[] =
+{
+ "0;30",
+ "0;34",
+ "0;32",
+ "0;36",
+ "0;31",
+ "0;35",
+ "0;33",
+ "0;37",
+ "1;30",
+ "1;34",
+ "1;32",
+ "1;36",
+ "1;31",
+ "1;35",
+ "1;33",
+ "1;37"
+};
+
+const char *const QColorOutputPrivate::backgrounds[] =
+{
+ "0;40",
+ "0;44",
+ "0;42",
+ "0;46",
+ "0;41",
+ "0;45",
+ "0;43"
+};
+
+/*!
+ \class QColorOutput
+ \nonreentrant
+ \brief Outputs colored messages to \c stderr.
+ \internal
+
+ QColorOutput is a convenience class for outputting messages to \c
+ stderr using color escape codes, as mandated in ECMA-48. QColorOutput
+ will only color output when it is detected to be suitable. For
+ instance, if \c stderr is detected to be attached to a file instead
+ of a TTY, no coloring will be done.
+
+ QColorOutput does its best attempt. but it is generally undefined
+ what coloring or effect the various coloring flags has. It depends
+ strongly on what terminal software that is being used.
+
+ When using `echo -e 'my escape sequence'`, \c{\033} works as an
+ initiator but not when printing from a C++ program, despite having
+ escaped the backslash. That's why we below use characters with
+ value 0x1B.
+
+ It can be convenient to subclass QColorOutput with a private scope,
+ such that the functions are directly available in the class using
+ it.
+
+ \section1 Usage
+
+ To output messages, call write() or writeUncolored(). write() takes
+ as second argument an integer, which QColorOutput uses as a lookup
+ key to find the color it should color the text in. The mapping from
+ keys to colors is done using insertMapping(). Typically this is used
+ by having enums for the various kinds of messages, which
+ subsequently are registered.
+
+ \code
+ enum MyMessage
+ {
+ Error,
+ Important
+ };
+
+ QColorOutput output;
+ output.insertMapping(Error, QColorOutput::RedForeground);
+ output.insertMapping(Import, QColorOutput::BlueForeground);
+
+ output.write("This is important", Important);
+ output.write("Jack, I'm only the selected official!", Error);
+ \endcode
+
+ \sa {http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x329.html}{Bash Prompt HOWTO, 6.1. Colors},
+ {http://linuxgazette.net/issue51/livingston-blade.html}{Linux Gazette, Tweaking Eterm, Edward Livingston-Blade},
+ {http://www.ecma-international.org/publications/standards/Ecma-048.htm}{Standard ECMA-48, Control Functions for Coded Character Sets, ECMA International},
+ {http://en.wikipedia.org/wiki/ANSI_escape_code}{Wikipedia, ANSI escape code},
+ {http://linuxgazette.net/issue65/padala.html}{Linux Gazette, So You Like Color!, Pradeep Padala}
+ */
+
+/*!
+ \internal
+ \enum QColorOutput::ColorCodeComponent
+ \value BlackForeground
+ \value BlueForeground
+ \value GreenForeground
+ \value CyanForeground
+ \value RedForeground
+ \value PurpleForeground
+ \value BrownForeground
+ \value LightGrayForeground
+ \value DarkGrayForeground
+ \value LightBlueForeground
+ \value LightGreenForeground
+ \value LightCyanForeground
+ \value LightRedForeground
+ \value LightPurpleForeground
+ \value YellowForeground
+ \value WhiteForeground
+ \value BlackBackground
+ \value BlueBackground
+ \value GreenBackground
+ \value CyanBackground
+ \value RedBackground
+ \value PurpleBackground
+ \value BrownBackground
+
+ \value DefaultColor QColorOutput performs no coloring. This typically
+ means black on white or white on black, depending
+ on the settings of the user's terminal.
+ */
+
+/*!
+ \internal
+ Constructs a QColorOutput instance, ready for use.
+ */
+QColorOutput::QColorOutput() : d(new QColorOutputPrivate) {}
+
+// must be here so that QScopedPointer has access to the complete type
+QColorOutput::~QColorOutput() = default;
+
+bool QColorOutput::isSilent() const { return d->isSilent(); }
+void QColorOutput::setSilent(bool silent) { d->setSilent(silent); }
+
+/*!
+ \internal
+ Sends \a message to \c stderr, using the color looked up in the color mapping using \a colorID.
+
+ If \a color isn't available in the color mapping, result and behavior is undefined.
+
+ If \a colorID is 0, which is the default value, the previously used coloring is used. QColorOutput
+ is initialized to not color at all.
+
+ If \a message is empty, effects are undefined.
+
+ \a message will be printed as is. For instance, no line endings will be inserted.
+ */
+void QColorOutput::write(QStringView message, int colorID)
+{
+ if (!d->isSilent())
+ d->write(colorify(message, colorID));
+}
+
+void QColorOutput::writePrefixedMessage(const QString &message, QtMsgType type,
+ const QString &prefix)
+{
+ static const QHash<QtMsgType, QString> prefixes = {
+ {QtMsgType::QtCriticalMsg, QStringLiteral("Error")},
+ {QtMsgType::QtWarningMsg, QStringLiteral("Warning")},
+ {QtMsgType::QtInfoMsg, QStringLiteral("Info")},
+ {QtMsgType::QtDebugMsg, QStringLiteral("Hint")}
+ };
+
+ Q_ASSERT(prefixes.contains(type));
+ Q_ASSERT(prefix.isEmpty() || prefix.front().isUpper());
+ write((prefix.isEmpty() ? prefixes[type] : prefix) + QStringLiteral(": "), type);
+ writeUncolored(message);
+}
+
+/*!
+ \internal
+ Writes \a message to \c stderr as if for instance
+ QTextStream would have been used, and adds a line ending at the end.
+
+ This function can be practical to use such that one can use QColorOutput for all forms of writing.
+ */
+void QColorOutput::writeUncolored(const QString &message)
+{
+ if (!d->isSilent())
+ d->write(message + QLatin1Char('\n'));
+}
+
+/*!
+ \internal
+ Treats \a message and \a colorID identically to write(), but instead of writing
+ \a message to \c stderr, it is prepared for being written to \c stderr, but is then
+ returned.
+
+ This is useful when the colored string is inserted into a translated string(dividing
+ the string into several small strings prevents proper translation).
+ */
+QString QColorOutput::colorify(const QStringView message, int colorID) const
+{
+ Q_ASSERT_X(colorID == -1 || d->containsColor(colorID), Q_FUNC_INFO,
+ qPrintable(QString::fromLatin1("There is no color registered by id %1")
+ .arg(colorID)));
+ Q_ASSERT_X(!message.isEmpty(), Q_FUNC_INFO,
+ "It makes no sense to attempt to print an empty string.");
+
+ if (colorID != -1)
+ d->setCurrentColorID(colorID);
+
+ if (d->coloringEnabled() && colorID != -1) {
+ const int color = d->color(colorID);
+
+ /* If DefaultColor is set, we don't want to color it. */
+ if (color & DefaultColor)
+ return message.toString();
+
+ const int foregroundCode = (color & ForegroundMask) >> ForegroundShift;
+ const int backgroundCode = (color & BackgroundMask) >> BackgroundShift;
+ QString finalMessage;
+ bool closureNeeded = false;
+
+ if (foregroundCode > 0) {
+ finalMessage.append(
+ QColorOutputPrivate::escapeCode(
+ QLatin1String(QColorOutputPrivate::foregrounds[foregroundCode - 1])));
+ closureNeeded = true;
+ }
+
+ if (backgroundCode > 0) {
+ finalMessage.append(
+ QColorOutputPrivate::escapeCode(
+ QLatin1String(QColorOutputPrivate::backgrounds[backgroundCode - 1])));
+ closureNeeded = true;
+ }
+
+ finalMessage.append(message);
+
+ if (closureNeeded)
+ finalMessage.append(QColorOutputPrivate::escapeCode(QLatin1String("0")));
+
+ return finalMessage;
+ }
+
+ return message.toString();
+}
+
+/*!
+ \internal
+ Adds a color mapping from \a colorID to \a colorCode, for this QColorOutput instance.
+ */
+void QColorOutput::insertMapping(int colorID, const ColorCode colorCode)
+{
+ d->insertColor(colorID, colorCode);
+}
+
+QT_END_NAMESPACE