diff options
Diffstat (limited to 'tools/xmlpatterns')
-rw-r--r-- | tools/xmlpatterns/main.cpp | 386 | ||||
-rw-r--r-- | tools/xmlpatterns/main.h | 75 | ||||
-rw-r--r-- | tools/xmlpatterns/qapplicationargument.cpp | 344 | ||||
-rw-r--r-- | tools/xmlpatterns/qapplicationargument_p.h | 100 | ||||
-rw-r--r-- | tools/xmlpatterns/qapplicationargumentparser.cpp | 1028 | ||||
-rw-r--r-- | tools/xmlpatterns/qapplicationargumentparser_p.h | 111 | ||||
-rw-r--r-- | tools/xmlpatterns/qcoloringmessagehandler.cpp | 193 | ||||
-rw-r--r-- | tools/xmlpatterns/qcoloringmessagehandler_p.h | 99 | ||||
-rw-r--r-- | tools/xmlpatterns/qcoloroutput.cpp | 350 | ||||
-rw-r--r-- | tools/xmlpatterns/qcoloroutput_p.h | 134 | ||||
-rw-r--r-- | tools/xmlpatterns/xmlpatterns.pro | 31 |
11 files changed, 2851 insertions, 0 deletions
diff --git a/tools/xmlpatterns/main.cpp b/tools/xmlpatterns/main.cpp new file mode 100644 index 0000000..ceb5f75 --- /dev/null +++ b/tools/xmlpatterns/main.cpp @@ -0,0 +1,386 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Patternist project on Trolltech Labs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/QDir> +#include <QtCore/QtDebug> +#include <QtCore/QFile> +#include <QtCore/QFileInfo> +#include <QtCore/QStringList> +#include <QtCore/QTextCodec> +#include <QtCore/QTextStream> +#include <QtCore/QUrl> +#include <QtCore/QVariant> +#include <QtCore/QVector> + +#include <QtXmlPatterns/QXmlFormatter> +#include <QtXmlPatterns/QXmlItem> +#include <QtXmlPatterns/QXmlQuery> +#include <QtXmlPatterns/QXmlSerializer> + +#include "private/qautoptr_p.h" +#include "qapplicationargument_p.h" +#include "qapplicationargumentparser_p.h" +#include "qcoloringmessagehandler_p.h" + +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) +/* Needed for opening stdout with _fdopen & friends. io.h seems to not be + * needed on MinGW though. */ +#include <io.h> +#include <fcntl.h> +#endif + +#include "main.h" + +QT_USE_NAMESPACE + +/* The two Q_DECLARE_METATYPE macros must appear before the code + * on a couple of HPUX platforms. */ + +/*! + \internal + \since 4.4 + Represents the name and value found in "-param name=value". + */ +typedef QPair<QString, QString> Parameter; +Q_DECLARE_METATYPE(Parameter); + +/*! + \internal + \since 4.4 + For the -output switch. + */ +Q_DECLARE_METATYPE(QIODevice *); + +/*! + \class PatternistApplicationParser + \brief Subclass to handle -param name=value + \internal + \since 4.4 + \reentrant + */ +class PatternistApplicationParser : public QApplicationArgumentParser +{ +public: + inline PatternistApplicationParser(int argc, char **argv, + const QXmlNamePool &np) : QApplicationArgumentParser(argc, argv) + , m_namePool(np) +#ifdef Q_OS_WIN + , m_stdout(0) +#endif + { + } + +#ifdef Q_OS_WIN + virtual ~PatternistApplicationParser() + { + /* QFile::~QFile() nor QFile::close() frees the handle when + * we use QFile::open() so we have to do it manually. + * + * "If stream is NULL, the invalid parameter handler is invoked," so + * lets try to avoid that. */ + if(m_stdout) + fclose(m_stdout); + } +#endif + +protected: + virtual QVariant convertToValue(const QApplicationArgument &arg, + const QString &input) const + { + if(arg.name() == QLatin1String("param")) + { + const int assign = input.indexOf(QLatin1Char('=')); + + if(assign == -1) + { + message(QXmlPatternistCLI::tr("Each binding must contain an equal sign.")); + return QVariant(); + } + + const QString name(input.left(assign)); + const QString value(input.mid(assign + 1)); + + if(!QXmlName::isNCName(name)) + { + message(QXmlPatternistCLI::tr("The variable name must be a valid NCName, which %1 isn't.").arg(name)); + return QVariant(); + } + + /* The value.isNull() check ensures we can bind variables whose value is an empty string. */ + return qVariantFromValue(Parameter(name, value.isNull() ? QString(QLatin1String("")) : value )); + } + else if(arg.name() == QLatin1String("output")) + { + QFile *const f = new QFile(input); + + if(f->open(QIODevice::WriteOnly)) + return qVariantFromValue(static_cast<QIODevice *>(f)); + else + { + message(QXmlPatternistCLI::tr("Failed to open file %1 for writing: %2").arg(f->fileName(), f->errorString())); + return QVariant(); + } + } + else if(arg.name() == QLatin1String("initial-template")) + { + const QXmlName name(QXmlName::fromClarkName(input, m_namePool)); + if(name.isNull()) + { + message(QXmlPatternistCLI::tr("%1 is an invalid Clark Name").arg(input)); + return QVariant(); + } + else + return qVariantFromValue(name); + } + else + return QApplicationArgumentParser::convertToValue(arg, input); + } + + virtual QString typeToName(const QApplicationArgument &argument) const + { + if(argument.name() == QLatin1String("param")) + return QLatin1String("name=value"); + else if(argument.name() == QLatin1String("output")) + return QLatin1String("local file"); + else + return QApplicationArgumentParser::typeToName(argument); + } + + virtual QVariant defaultValue(const QApplicationArgument &argument) const + { + if(argument.name() == QLatin1String("output")) + { + QFile *const out = new QFile(); + +#ifdef Q_OS_WIN + /* If we don't open stdout in "binary" mode on Windows, it will translate + * 0xA into 0xD 0xA. See Trolltech task 173619, for an example. */ + _setmode(_fileno(stdout), _O_BINARY); + m_stdout = QT_WA_INLINE(_wfdopen(_fileno(stdout), L"wb"),_fdopen(_fileno(stdout), "wb")); + out->open(m_stdout, QIODevice::WriteOnly); +#else + out->open(stdout, QIODevice::WriteOnly); +#endif + + return qVariantFromValue(static_cast<QIODevice *>(out)); + } + else + return QApplicationArgumentParser::defaultValue(argument); + } + +private: + QXmlNamePool m_namePool; +#ifdef Q_OS_WIN + mutable FILE * m_stdout; +#endif +}; + +static inline QUrl finalizeURI(const QApplicationArgumentParser &parser, + const QApplicationArgument &isURI, + const QApplicationArgument &arg) +{ + QUrl userURI; + { + const QString stringURI(parser.value(arg).toString()); + + if(parser.has(isURI)) + userURI = QUrl::fromEncoded(stringURI.toLatin1()); + else + userURI = QUrl::fromLocalFile(stringURI); + } + + return QUrl::fromLocalFile(QDir::current().absolutePath() + QLatin1Char('/')).resolved(userURI); +} + +int main(int argc, char **argv) +{ + enum ExitCode + { + /** + * We start from 2, because QApplicationArgumentParser + * uses 1. + */ + QueryFailure = 2, + StdOutFailure + }; + + const QCoreApplication app(argc, argv); + QCoreApplication::setApplicationName(QLatin1String("xmlpatterns")); + + QXmlNamePool namePool; + PatternistApplicationParser parser(argc, argv, namePool); + parser.setApplicationDescription(QLatin1String("A tool for running XQuery queries.")); + parser.setApplicationVersion(QLatin1String("0.1")); + + QApplicationArgument param(QLatin1String("param"), + QXmlPatternistCLI::tr("Binds an external variable. The value is directly available using the variable reference: $name."), + qMetaTypeId<Parameter>()); + param.setMaximumOccurrence(-1); + parser.addArgument(param); + + const QApplicationArgument noformat(QLatin1String("no-format"), + QXmlPatternistCLI::tr("By default output is formatted for readability. When specified, strict serialization is performed.")); + parser.addArgument(noformat); + + const QApplicationArgument isURI(QLatin1String("is-uri"), + QXmlPatternistCLI::tr("If specified, all filenames on the command line are interpreted as URIs instead of a local filenames.")); + parser.addArgument(isURI); + + const QApplicationArgument initialTemplateName(QLatin1String("initial-template"), + QXmlPatternistCLI::tr("The name of the initial template to call as a Clark Name."), + QVariant::String); + parser.addArgument(initialTemplateName); + + /* The temporary object is required to compile with g++ 3.3. */ + QApplicationArgument queryURI = QApplicationArgument(QLatin1String("query/stylesheet"), + QXmlPatternistCLI::tr("A local filename pointing to the query to run. If the name ends with .xsl it's assumed " + "to be an XSL-T stylesheet. If it ends with .xq, it's assumed to be an XQuery query. (In " + "other cases it's also assumed to be an XQuery query, but that interpretation may " + "change in a future release of Qt.)"), + QVariant::String); + queryURI.setMinimumOccurrence(1); + queryURI.setNameless(true); + parser.addArgument(queryURI); + + QApplicationArgument focus = QApplicationArgument(QLatin1String("focus"), + QXmlPatternistCLI::tr("The document to use as focus. Mandatory " + "in case a stylesheet is used. This option is " + "also affected by the is-uris option."), + QVariant::String); + focus.setMinimumOccurrence(0); + focus.setNameless(true); + parser.addArgument(focus); + + QApplicationArgument output(QLatin1String("output"), + QXmlPatternistCLI::tr("A local file to which the output should be written. " + "The file is overwritten, or if not exist, created. " + "If absent, stdout is used."), + qMetaTypeId<QIODevice *>()); + parser.addArgument(output); + + if(!parser.parse()) + return parser.exitCode(); + + /* Get the query URI. */ + const QUrl effectiveURI(finalizeURI(parser, isURI, queryURI)); + + QXmlQuery::QueryLanguage lang; + + if(effectiveURI.toString().endsWith(QLatin1String(".xsl"))) + lang = QXmlQuery::XSLT20; + else + lang = QXmlQuery::XQuery10; + + if(lang == QXmlQuery::XQuery10 && parser.has(initialTemplateName)) + { + parser.message(QXmlPatternistCLI::tr("An initial template name cannot be specified when running an XQuery.")); + return QApplicationArgumentParser::ParseError; + } + + QXmlQuery query(lang, namePool); + + query.setInitialTemplateName(qVariantValue<QXmlName>(parser.value(initialTemplateName))); + + /* Bind external variables. */ + { + const QVariantList parameters(parser.values(param)); + const int len = parameters.count(); + + /* For tracking duplicates. */ + QSet<QString> usedParameters; + + for(int i = 0; i < len; ++i) + { + const Parameter p(qVariantValue<Parameter>(parameters.at(i))); + + if(usedParameters.contains(p.first)) + { + parser.message(QXmlPatternistCLI::tr("Each parameter must be unique, %1 is specified at least twice.").arg(p.first)); + return QApplicationArgumentParser::ParseError; + } + else + { + usedParameters.insert(p.first); + query.bindVariable(p.first, QXmlItem(p.second)); + } + } + } + + if(parser.has(focus)) + { + if(!query.setFocus(finalizeURI(parser, isURI, focus))) + return QueryFailure; + } + else if(lang == QXmlQuery::XSLT20 && !parser.has(initialTemplateName)) + { + parser.message(QXmlPatternistCLI::tr("When a stylesheet is used, a " + "document must be specified as a focus, or an " + "initial template name must be specified, or both.")); + return QApplicationArgumentParser::ParseError; + } + + query.setQuery(effectiveURI); + + const QPatternist::AutoPtr<QIODevice> outDevice(qVariantValue<QIODevice *>(parser.value(output))); + Q_ASSERT(outDevice); + Q_ASSERT(outDevice->isWritable()); + + if(query.isValid()) + { + typedef QPatternist::AutoPtr<QAbstractXmlReceiver> RecPtr; + RecPtr receiver; + + if(parser.has(noformat)) + receiver = RecPtr(new QXmlSerializer(query, outDevice.data())); + else + receiver = RecPtr(new QXmlFormatter(query, outDevice.data())); + + const bool success = query.evaluateTo(receiver.data()); + + if(success) + return parser.exitCode(); + else + return QueryFailure; + } + else + return QueryFailure; +} + diff --git a/tools/xmlpatterns/main.h b/tools/xmlpatterns/main.h new file mode 100644 index 0000000..eaef3bb --- /dev/null +++ b/tools/xmlpatterns/main.h @@ -0,0 +1,75 @@ +/**************************************************************************** + * ** * ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) + * ** + * ** This file is part of the Patternist project on Trolltech Labs. * ** + * ** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ + * ** + * ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * ** + * ****************************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef Patternist_main_h +#define Patternist_main_h + +#include <QCoreApplication> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +class QXmlPatternistCLI +{ +public: + Q_DECLARE_TR_FUNCTIONS(QXmlPatternistCLI) +private: + inline QXmlPatternistCLI(); + Q_DISABLE_COPY(QXmlPatternistCLI) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/tools/xmlpatterns/qapplicationargument.cpp b/tools/xmlpatterns/qapplicationargument.cpp new file mode 100644 index 0000000..e88d5f8 --- /dev/null +++ b/tools/xmlpatterns/qapplicationargument.cpp @@ -0,0 +1,344 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QHash> +#include <QString> + +#include "qapplicationargument_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QApplicationArgument + \brief The QApplicationArgument class is a declared of a command line + argument for QApplicationArgumentParser. + \reentrant + \internal + \since 4.4 + + QApplicationArgument describes a valid command line argument, + by having a set of characteristics: + + \table + \header + \o Characteristic + \o Functions + \row + \o A name. For instance, "backend" + \o setName() and name() + \row + \o A description, for human consumption. + \o setDescription() and description() + \row + \o How many times the argument can occur. For instance, whether the argument is optional or not. + \o setMinimumOccurrence() & minimumOccurrence(), setMaximumOccurrence() & maximumOccurrence() + \row + \o The type of the argument's value, if it has one. For instance, \c int or \c bool. + \o setType() and type() + \row + \o The value that should be used in case the argument isn't used. + \o setDefaultValue() and defaultValue() + \endtable + + \sa QApplicationArgumentParser + */ + +class QApplicationArgumentPrivate +{ +public: + inline QApplicationArgumentPrivate(const QString &newName, + const QString &desc, + const int newType) : name(newName) + , description(desc) + , type(newType) + , minimum(0) + , maximum(1) + , isNameless(false) + { + } + + QString name; + QString description; + int type; + QVariant defaultValue; + int minimum; + int maximum; + bool isNameless; +}; + +/*! + Constructs an invalid QApplicationArgument instance. + */ +QApplicationArgument::QApplicationArgument() : d_ptr(new QApplicationArgumentPrivate(QString(), QString(), QVariant::Invalid)) +{ +} + +/*! + Constructs an QApplicationArgument instance that is a copy of \a other. + */ +QApplicationArgument::QApplicationArgument(const QApplicationArgument &other) : d_ptr(new QApplicationArgumentPrivate(*other.d_ptr)) +{ +} + +/*! + Destructs this QApplicationArgument instance. + */ +QApplicationArgument::~QApplicationArgument() +{ + delete d_ptr; +} + +/*! + Constructs an argument that has the name \a name and is of type + \a aType. + + Calling this constructor is equivalent to calling setName() and setType() + on a default constructed QApplicationArgument instance. + + \sa setName(), setType() + */ +QApplicationArgument::QApplicationArgument(const QString &name, + const QString &description, + int aType) : d_ptr(new QApplicationArgumentPrivate(name, description, aType)) +{ +} + +/*! + Assigns \a other to this QApplicationArgument instance. + */ +QApplicationArgument &QApplicationArgument::operator=(const QApplicationArgument &other) +{ + if(this != &other) + *d_ptr = *other.d_ptr; + + return *this; +} + +// TODO is this really what we want? +/*! + Returns true if this QApplicationArgument instance is equal to \a other. + + Equalness is defined to only consider name(). If for instance the type() differs + but the names are equal, this operator will return \c true. + */ +bool QApplicationArgument::operator==(const QApplicationArgument &other) const +{ + return name() == other.name(); +} + +/*! + \fn qHash(const QApplicationArgument &); + \internal + + Returns a hash index of \a argument. This function is used when QApplicationArgument + is used with QHash. + + The hash index is computed on name(). The other properties are ignored. + + \relates QApplicationArgument + */ + +/*! + Sets this argument's name to \a newName. The name does not + include any dash, or other prefix that is used by the parser. + */ +void QApplicationArgument::setName(const QString &newName) +{ + d_ptr->name = newName; +} + +/*! + Returns the name that this argument has. + + \sa setName() + */ +QString QApplicationArgument::name() const +{ + return d_ptr->name; +} + +/*! + Sets the tupe to \a newType. + + If \a newType is QVariant::Invalid, it signals that this + argument does not accept a value at all. + + \a newType can be a QVariant::type() value, or QVariant::userType(). + + \sa type() + */ +void QApplicationArgument::setType(int newType) +{ + d_ptr->type = newType; +} + +/*! + Returns the type that the value of this argument has. If it + is QVariant::Invalid, it means this argument cannot have a value + and is a switch only. + + The type is by default QVariant::Invalid. +\sa setType() + */ +int QApplicationArgument::type() const +{ + return d_ptr->type; +} + +void QApplicationArgument::setDefaultValue(const QVariant &value) +{ + d_ptr->defaultValue = value; +} + +QVariant QApplicationArgument::defaultValue() const +{ + return d_ptr->defaultValue; +} + +/*! + Sets the minimum amount of times this argument can occur, to \a minimum. + For instance, if \a minimum is 2, the argument must be used at least two times. + + If \a minimum is zero, it means the argument is optional. + + \sa minimumOccurrence(), setMaximumOccurrence() + */ +void QApplicationArgument::setMinimumOccurrence(int minimum) +{ + Q_ASSERT_X(minimum >= 0, Q_FUNC_INFO, + "The minimum cannot be less than zero."); + d_ptr->minimum = minimum; +} + +/*! + Returns the minimum amount of times an an argument must occur. + + The default is 0. + + \sa setMinimumOccurrence(), maximumOccurrence() + */ +int QApplicationArgument::minimumOccurrence() const +{ + return d_ptr->minimum; +} + +/*! + Sets the maximum occurrence to \a maximum. + + If \a maximum is -1, it means the argument can appear an unlimited + amount of times. Setting it to zero or less than -1, yields + undefined behavior. + +\sa maximumOccurrence(), setMinimumOccurrence() + */ +void QApplicationArgument::setMaximumOccurrence(int maximum) +{ + Q_ASSERT_X(maximum == -1 || maximum >= 1, Q_FUNC_INFO, + "The maximum can only be -1 or 1 or larger."); + d_ptr->maximum = maximum; +} + +/*! + Returns the maximum amount of times this argument can occur. For instance, + if the maximum occurrence is 2, it would be an error if 3 were specified + on the command line. + + If the maximum occurrence is -1, it signals the argument can appear an unlimited + amount of times. + + The default is 1. + + \sa setMaximumOccurrence() + */ +int QApplicationArgument::maximumOccurrence() const +{ + return d_ptr->maximum; +} + +/*! + Sets the description to \a newDescription. The description + should describe the argument in a sentence or two. It is used + when displaying a help message, if requested. + + The description should be terminated as if it was a paragraph. This + typically means a period. + + The description should be translated by wrapping the + string literal in a call to tr(). + + */ +void QApplicationArgument::setDescription(const QString &newDescription) +{ + d_ptr->description = newDescription; +} + +/*! + Returns the description of this argument. + + \sa setDescription() + */ +QString QApplicationArgument::description() const +{ + return d_ptr->description; +} + +/*! + \internal + \relates QApplicationArgument + + Computes a hash key on \a argument's name and returns it. + */ +uint qHash(const QApplicationArgument &argument) +{ + return qHash(argument.name()); +} + +void QApplicationArgument::setNameless(bool value) +{ + d_ptr->isNameless = value; +} + +bool QApplicationArgument::isNameless() const +{ + return d_ptr->isNameless; +} + +QT_END_NAMESPACE diff --git a/tools/xmlpatterns/qapplicationargument_p.h b/tools/xmlpatterns/qapplicationargument_p.h new file mode 100644 index 0000000..3232bbc --- /dev/null +++ b/tools/xmlpatterns/qapplicationargument_p.h @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** ** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef QApplicationArgument_H +#define QApplicationArgument_H + +#include <QtCore/QVariant> + +QT_BEGIN_HEADER +QT_BEGIN_NAMESPACE + +class QString; +class QApplicationArgumentPrivate; + +class QApplicationArgument +{ +public: + QApplicationArgument(); + QApplicationArgument(const QApplicationArgument &other); + QApplicationArgument(const QString &name, + const QString &description, + int aType = QVariant::Invalid); + ~QApplicationArgument(); + QApplicationArgument &operator=(const QApplicationArgument &other); + bool operator==(const QApplicationArgument &other) const; + + void setName(const QString &newName); + QString name() const; + void setDescription(const QString &newDescription); + QString description() const; + + int type() const; + void setType(int newType); + void setDefaultValue(const QVariant &value); + QVariant defaultValue() const; + + void setMinimumOccurrence(int minimum); + int minimumOccurrence() const; + void setMaximumOccurrence(int maximum); + int maximumOccurrence() const; + void setNameless(bool value); + bool isNameless() const; + +private: + QApplicationArgumentPrivate *d_ptr; +}; + +uint qHash(const QApplicationArgument &argument); + +QT_END_NAMESPACE +QT_END_HEADER +#endif + diff --git a/tools/xmlpatterns/qapplicationargumentparser.cpp b/tools/xmlpatterns/qapplicationargumentparser.cpp new file mode 100644 index 0000000..7a9b902 --- /dev/null +++ b/tools/xmlpatterns/qapplicationargumentparser.cpp @@ -0,0 +1,1028 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtDebug> +#include <QTextBoundaryFinder> +#include <QCoreApplication> +#include <QHash> +#include <QPair> +#include <QStringList> +#include <QTextStream> +#include <QUrl> + +#include "qapplicationargument_p.h" + +#include "qapplicationargumentparser_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QApplicationArgumentParser + \brief The QApplicationArgumentParser class parses the command + line arguments for an application. + \reentrant + \internal + \since 4.4 + + QApplicationArgumentParser simplifies writing command line applications by taking care of: + + \list + \o Generating help and version arguments + \o Taking care of converting arguments to QVariant types, since each argument + has a type: QApplicationArgument::type() + \o Validates the command line such that the user operates on well-defined input. For instance, + that the argument is a valid integer if that is the case, that an argument does not + occur more times than allowed, and so on. + \o Allows customization through sub-classing. + \endlist + + The user declares what arguments that can be given to the application with QApplicationArgument. Provided + with that information, QApplicationArgumentParser takes care of parsing the actual + command line, appropriately flag errors, generate help messages, and provide + convenient access to the values of the arguments. + + The way to use it is to create a set of QApplicationArgument by ones choosing, call + addArgument() for each, and subsequently call parse(). If parse() returns \c false, + the caller should exit and return exitCode(). + + If parse() returns \c true the command line was successfully parsed, its + values are well-defined, and they can be spectated with count(), + has(), value() and values(). + + \snippet doc/src/snippets/code/tools_patternist_qapplicationargumentparser.cpp 0 + + For arguments without a name(such as filename passed to the \c ls utility on Linux) add a + QApplicationArgument that does not have a name. The minimum and maximum occurrences will be + respected as usual and the type applies too. + + QApplicationArgumentParser always has two options builtin: \c version and \c help. + + \section1 Changing Parsing Convention + + QApplicationArgumentParser by default parses the command line in the style + of Qt's utilities, where arguments are preceded by a single dash, and identified + by a single name. However, in some cases it might be of interest to parse + another style, such as the well-established UNIX \c getopt convention(\c -l + and \c --long). + + This can be achieved by sub-classing QApplicationArgumentParser and reimplementing + parse(). It would do the following: + + \list + \o Call input() to retrieve the strings the user specified on the command line. + \o Call declaredArguments() to retrieve the arguments that the implementor has + decided can be specified. + \o Parse and validate the input. Salt and pepper as per taste. + \o If an error occurred, call setExitCode() and return \c false. + \o Otherwise, call setExitCode(Success), provide access to the + arguments by calling setUsedArguments(), and return \c true. If a + help message was requested, call setExitCode(Success) and return \c false. + \endlist + + \sa QApplicationArgument, QCoreApplication +*/ +class QApplicationArgumentParserPrivate +{ + Q_DECLARE_TR_FUNCTIONS(QApplicationArgumentParserPrivate) +public: + // TODO Isn't it like ten times better with QHash<QApplicationArgument, QList<QVariant> >? + // TODO test QApplicationArgument::nameless() + typedef QList<QPair<QApplicationArgument, QVariant> > UsedList; + + /*! + We initialize exitCode to ParseError such that we consciously flag success. + */ + inline QApplicationArgumentParserPrivate(QApplicationArgumentParser *const master, + const QStringList &aInput) : exitCode(QApplicationArgumentParser::ParseError) + , input(aInput) + , q_ptr(master) + { + Q_ASSERT(!aInput.isEmpty()); + } + + QApplicationArgument nextNamelessArgument() const; + static QStringList argumentsFromLocal(const int argc, const char *const *const argv); + + bool error(const QString &message); + static bool errorMessage(const QString &message); + static inline bool isSwitch(const QApplicationArgument &arg); + static inline QVariant conversionError(const QString &typeName, + const QString &input); + int count(const QApplicationArgument &arg) const; + bool contains(const QApplicationArgument &arg) const; + static inline bool isBuiltinVariant(const int type); + void displayVersion() const; + void displayHelp() const; + void parseNameless(); + bool parseNamelessArguments(const QString &in); + + QApplicationArgumentParser::ExitCode exitCode; + const QStringList input; + + /*! + Since the QString is QApplicationArgument::name() anyway, why + not use a QSet? + */ + QHash<QString, QApplicationArgument> declaredArguments; + + QList<QApplicationArgument> declaredNamelessArguments; + + UsedList usedArguments; + QString applicationDescription; + QString applicationVersion; + +private: + QApplicationArgumentParser *const q_ptr; + Q_DECLARE_PUBLIC(QApplicationArgumentParser) + + static QString lineWrap(const QString &input, + const int leftIndent, + const int width); + static QList<QApplicationArgument> builtinArguments(); +}; + +QApplicationArgument QApplicationArgumentParserPrivate::nextNamelessArgument() const +{ + /* Count how many nameless arguments we have so far. */ + int count = 0; + + for(int i = 0; i < usedArguments.count(); ++i) + { + if(usedArguments.at(i).first.isNameless()) + ++count; + } + + /* TODO this doesn't work for arguments that have more than one + * mandatory value(e.g nameless ones), since several values should + * then only count for one argument. */ + for(int i = 0; i < declaredNamelessArguments.count(); ++i) + { + if(count) + { + /* Skip the ones we already have processed. */ + --count; + continue; + } + + if(declaredNamelessArguments.at(i).isNameless()) + return declaredNamelessArguments.at(i); + } + + return QApplicationArgument(); +} + +int QApplicationArgumentParserPrivate::count(const QApplicationArgument &arg) const +{ + const int len = usedArguments.count(); + int count = 0; + + for(int i = 0; i < len; ++i) + { + if(usedArguments.at(i).first == arg) + ++count; + } + + return count; +} + +/*! + Returns \c true if \a arg has appeared on the command line, not whether it has been declared. + */ +bool QApplicationArgumentParserPrivate::contains(const QApplicationArgument &arg) const +{ + const int len = usedArguments.count(); + + for(int i = 0; i < len; ++i) + { + if(usedArguments.at(i).first == arg) + return true; + } + + return false; +} + +/*! + Returns always \c false. + */ +bool QApplicationArgumentParserPrivate::error(const QString &message) +{ + exitCode = QApplicationArgumentParser::ParseError; + errorMessage(message); + return errorMessage(tr("Pass -help for information about the command line.")); +} + +/*! + Returns always \c false. + */ +bool QApplicationArgumentParserPrivate::errorMessage(const QString &message) +{ + QTextStream out(stderr, QIODevice::WriteOnly); + out << message << endl; + return false; +} + +/*! + \internal + Determines whether \a arg carries a value or is on/off. + */ +bool QApplicationArgumentParserPrivate::isSwitch(const QApplicationArgument &arg) +{ + return arg.type() == QVariant::Invalid; +} + +QVariant QApplicationArgumentParserPrivate::conversionError(const QString &typeName, + const QString &input) +{ + errorMessage(tr("Cannot convert %1 to type %2.").arg(input, typeName)); + return QVariant(); +} + +bool QApplicationArgumentParserPrivate::isBuiltinVariant(const int type) +{ + return type < int(QVariant::UserType); +} + +/*! + TODO Temporary, replace with a function in QCoreApplication. +*/ +QStringList QApplicationArgumentParserPrivate::argumentsFromLocal(const int argc, const char *const *const argv) +{ + Q_ASSERT(argc >= 1); + Q_ASSERT(argv); + QStringList result; + + for(int i = 0; i < argc; ++i) + result.append(QString::fromLocal8Bit(argv[i])); + + return result; +} + +void QApplicationArgumentParserPrivate::displayVersion() const +{ + QTextStream out(stderr); + + out << tr("%1 version %2 using Qt %3").arg(QCoreApplication::applicationName(), applicationVersion, QString::fromAscii(qVersion())) + << endl; +} + +/*! + \internal + \relates QApplicationArgument + + qLess() functor for QApplicationArgument that considers the name. + */ +template<> +class qLess <QApplicationArgument> +{ +public: + inline bool operator()(const QApplicationArgument &o1, + const QApplicationArgument &o2) const + { + return o1.name().compare(o2.name()) < 0; + } +}; + +void QApplicationArgumentParserPrivate::displayHelp() const +{ + enum Constants + { + /** + * When we want to line wrap, 80 minus a couple of characters. This should + * be suitable for vt100 compatible terminals. + */ + LineWrapAt = 78, + + /** + * The initial " -" for each option. + */ + IndentPadding = 3, + + /** + * Pad for the brackets and space we use when we have a type. + */ + ValueArgumentPadding = 4 + }; + + QList<QApplicationArgument> args(declaredArguments.values()); + args += builtinArguments(); + + /* Sort them, such that we get the nameless options at the end, and it + * generally looks tidy. */ + qSort(args); + + /* This is the basic approach: + * Switches: + * -name description + * Value arguments: + * -name <name-of-value-type> description + * + * Nameless arguments + * name <type> description + * + * It all line-wraps at OutputWidth and the description is indented, + * where the highest indent is the length of the name plus length of the name + * of the type. */ + + /* First we find the name with the largest width. */ + int maxWidth = 0; + + QList<QApplicationArgument> nameless(declaredNamelessArguments); + qSort(nameless); + + /* Note, here the nameless arguments appear last, but are sorted + * with themselves. */ + QList<QApplicationArgument> allArgs(args + nameless); + const int allArgsCount = allArgs.count(); + + for(int i = 0; i < allArgsCount; ++i) + { + const QApplicationArgument &at = allArgs.at(i); + const int nameLength = at.name().length(); + const QString typeName(q_ptr->typeToName(at)); + const int typeNameLength = typeName.length(); + const int padding = at.type() == QVariant::Invalid ? 0 : ValueArgumentPadding; + maxWidth = qMax(maxWidth, nameLength + typeNameLength + padding); + } + + QTextStream out(stderr); + out << endl + << QString(IndentPadding, QLatin1Char(' ')) + << QCoreApplication::applicationName() + << QLatin1String(" -- ") + << applicationDescription + << endl; + // TODO synopsis + + /* One extra so we get some space between the overview and the options. */ + out << endl; + + const int indentWidth = maxWidth + 3; + + /* Ok, print them out. */ + for(int i = 0; i < allArgsCount; ++i) + { + const QApplicationArgument &at = allArgs.at(i); + /* " -name ". Indent a bit first, inspired by Qt's moc. */ + const QString &name = at.name(); + QString prolog(QLatin1String(" ")); + + /* We have a special case for the single dash. */ + if(name == QChar::fromLatin1('-')) + prolog.append(name); + else + { + if(!at.isNameless()) + prolog.append(QLatin1Char('-')); + + prolog.append(name + QLatin1Char(' ')); + } + + if(at.type() != QVariant::Invalid) + { + /* It's not a switch, it has a value. */ + + /* Do we have a default value? If so, the argument is optional. */ + const QString typeName(q_ptr->typeToName(at)); + + if(at.defaultValue().isValid()) + prolog.append(QLatin1Char('[') + typeName + QLatin1Char(']')); + else + prolog.append(QLatin1Char('<') + typeName + QLatin1Char('>')); + // TODO Don't we want to display the default value? + + prolog.append(QLatin1Char(' ')); + } + + prolog = prolog.leftJustified(indentWidth); + + out << prolog + << lineWrap(at.description(), indentWidth, LineWrapAt) + << endl; + } +} + +/*! + Line wraps \a input and indents each line with \a leftIndent spaces, such that + the width does not go beyond \a maxWidth. + + The addition of line endings is accounted for by the caller. + + With QTextBoundaryFinder our line wrapping is relatively fancy, since it + does it the Unicode-way. + */ +QString QApplicationArgumentParserPrivate::lineWrap(const QString &input, + const int leftIndent, + const int maxWidth) +{ + const QString indent(QString(leftIndent, QLatin1Char(' '))); + const int len = input.length(); + const int textWidth = maxWidth - leftIndent; + + QString output; + QTextBoundaryFinder wrapFinder(QTextBoundaryFinder::Line, input); + wrapFinder.setPosition(textWidth); + + if(input.length() + leftIndent <= maxWidth) + return input; + + int from = wrapFinder.toPreviousBoundary(); + output.append(input.left(from)); + + while(true) + { + if((len - from) + leftIndent > maxWidth) + { + /* We need to line wrap. */ + wrapFinder.setPosition(from + textWidth); + const int currentWidthPos = wrapFinder.toPreviousBoundary(); + + output.append(QLatin1Char('\n')); + output.append(indent); + output.append(input.mid(from, currentWidthPos - from).trimmed()); + from += (currentWidthPos - from); + } + else + { + /* Append the remains. */ + output.append(QLatin1Char('\n')); + output.append(indent); + output.append(input.mid(from).trimmed()); + break; + } + } + + return output; +} + +/*! + Returns a list with the builtin options that the parser has + */ +QList<QApplicationArgument> QApplicationArgumentParserPrivate::builtinArguments() +{ + QList<QApplicationArgument> result; + + result.append(QApplicationArgument(QLatin1String("help"), + QLatin1String("Displays this help."))); + result.append(QApplicationArgument(QLatin1String("version"), + QLatin1String("Displays version information."))); + + result.append(QApplicationArgument(QLatin1String("-"), + QLatin1String("When appearing, any following options are not interpreted as switches."))); + return result; +} + +/* TODO, I don't think we want this function in a public API. Add it first when there is a demand. */ + +/*! + Creates a QApplicationArgumentParser that will parse the input in \a argc and \a argv. +These arguments should be passed directly from the \c main() function, and the decoding +of the input will be taken care of appropriately, depending on platform. + + It is preferred to use the QStringList overload, in case the input is in the form of QStrings. + */ +QApplicationArgumentParser::QApplicationArgumentParser(int argc, char **argv) : d(new QApplicationArgumentParserPrivate(this, QApplicationArgumentParserPrivate::argumentsFromLocal(argc, argv))) +{ + Q_ASSERT_X(argv, Q_FUNC_INFO, "Argv cannot be null."); + Q_ASSERT_X(argc >= 1, Q_FUNC_INFO, + "argc must at least contain the application name. " + "Use the QStringList overload instead."); +} + +/*! + \overload + + Creates a QApplicationArgumentParser that will parse \a input. That is, instead of passing in \c argc + and \c argv, one can pass in a QStringList. + + The caller guarantees that the first string in \a input is the name of the application. + */ +QApplicationArgumentParser::QApplicationArgumentParser(const QStringList &input) : d(new QApplicationArgumentParserPrivate(this, input)) +{ + Q_ASSERT_X(input.count() >= 1, Q_FUNC_INFO, + "The input must at least contain the application name."); +} + +/*! + This function is only of interest when subclassing. + + Returns the strings that the user specified when starting the application. The first string + in the list is always the application name. + */ +QStringList QApplicationArgumentParser::input() const +{ + Q_ASSERT_X(d->input.count() >= 1, Q_FUNC_INFO, "Internal error, this should always hold true"); + return d->input; +} + +/*! + This function is only of interest when subclassing. + + Sets the arguments that the user actually used on the command line to \a arguments. + The parse() function should call this, such that the result afterwards can be inspected + with for instance has() or count(). + +\sa usedArguments() +*/ +void QApplicationArgumentParser::setUsedArguments(const QList<QPair<QApplicationArgument, QVariant> > &arguments) +{ + d->usedArguments = arguments; +} + +/*! + This function is only of interest when subclassing. + + Returns the arguments that the user used on the command line. + +\sa setUsedArguments() +*/ +QList<QPair<QApplicationArgument, QVariant> > QApplicationArgumentParser::usedArguments() const +{ + return d->usedArguments; +} + +/*! + Destructs this QApplicationArgumentParser instance. + */ +QApplicationArgumentParser::~QApplicationArgumentParser() +{ + delete d; +} + +/*! + Adds \a argument to this parser. + + This function is provided for convenience. It is equivalent to creating a QList + containing \a argument, append the existing arguments, and then call setDeclaredArguments() with the list. + + \sa setDeclaredArguments() + */ +void QApplicationArgumentParser::addArgument(const QApplicationArgument &argument) +{ + if(argument.isNameless()) + d->declaredNamelessArguments.append(argument); + else + d->declaredArguments.insert(argument.name(), argument); +} + +/*! + Makes the parser recognize all arguments in \a arguments. + + Any arguments previously set, are discarded. + + \sa addArgument(), declaredArguments() + */ +void QApplicationArgumentParser::setDeclaredArguments(const QList<QApplicationArgument> &arguments) +{ + // TODO If we have a QHash internally, why not use it in the public API too? + const int len = arguments.count(); + + for(int i = 0; i < len; ++i) + d->declaredArguments.insert(arguments.at(i).name(), arguments.at(i)); +} + +/*! + Returns the arguments that this parser recognizes. + + \sa addArgument(), setDeclaredArguments() + */ +QList<QApplicationArgument> QApplicationArgumentParser::declaredArguments() const +{ + return d->declaredArguments.values(); +} + +bool QApplicationArgumentParserPrivate::parseNamelessArguments(const QString &in) +{ + /* It's a nameless options, such as simply "value". */ + const QApplicationArgument nameless(nextNamelessArgument()); + + const QVariant val(q_ptr->convertToValue(nameless, in)); + if(val.isValid()) + { + usedArguments.append(qMakePair(nameless, val)); + return true; + } + else + return false; // TODO error msg? +} + +/*! + Parses input() together with declaredArguments() and returns \c false if the caller + should exit immediately, which is the case of which an error was encountered or + help or the version was requested. + + In the case of \c true was returned, valid arguments were supplied, and they can + be requested with functions like value(), values(), count() and has(). + + parse() must only be called once per QApplicationArgumentParser instance. The + second time it's called, the effects and return value are undefined. + + \sa convertToValue(), typeToName() + */ +bool QApplicationArgumentParser::parse() +{ + const QChar sep(QLatin1Char('-')); + const int inputCount = d->input.count(); + + /* We skip the first entry, which is the application name. */ + int i = 1; + + for(; i < inputCount; ++i) + { + const QString &in = d->input.at(i); + + /* We have a single '-', signalling that the succeeding are not options. */ + if(in == sep) + { + ++i; + + for(; i < inputCount; ++i) + { + if(!d->parseNamelessArguments(d->input.at(i))) + return false; + /* Process nameless options. Have code for this elsewhere, factor it out. */ + } + + break; + } + + if(in.startsWith(sep)) /* It is "-name". */ + { + const QString name(in.mid(1)); + + if(name == QLatin1String("help")) + { + setExitCode(Success); + d->displayHelp(); + return false; + } + else if(name == QLatin1String("version")) + { + setExitCode(Success); + d->displayVersion(); + return false; + } + + if(!d->declaredArguments.contains(name)) + return d->error(QApplicationArgumentParserPrivate::tr("\"%1\" is an unknown argument.").arg(name)); + + const QApplicationArgument &arg = d->declaredArguments.value(name); + const int argCount = d->count(arg) + 1; + const int max = arg.maximumOccurrence(); + + if(argCount > max && max != -1) + { + /* Let's tailor the message for a common case. */ + if(max == 1) + return d->error(QApplicationArgumentParserPrivate::tr("\"%1\" can only be used once.").arg(name)); + else + return d->error(QApplicationArgumentParserPrivate::tr("\"%1\" can only be used %2 times.").arg(name, QString::number(max))); + } + + if(QApplicationArgumentParserPrivate::isSwitch(arg)) + { + d->usedArguments.append(qMakePair(arg, QVariant())); + continue; + } + else + { + ++i; + + if(i == inputCount) + return d->error(QApplicationArgumentParserPrivate::tr("\"%1\" must be followed by a value.").arg(name)); + + /* Okidoki, got a value, always something. Let's + * see if it validates. */ + const QString &value = d->input.at(i); + + const QVariant val(convertToValue(arg, value)); + if(val.isValid()) + { + d->usedArguments.append(qMakePair(arg, val)); + continue; + } + else + return false; // TODO error msg? + } + } + else + { + if(!d->parseNamelessArguments(in)) + return false; + } + } + + /* Check that all arguments that have been declared as mandatory, are actually + * specified. */ + const QList<QApplicationArgument> declaredArguments(d->declaredArguments.values() + d->declaredNamelessArguments); + const int len = declaredArguments.count(); + for(int i = 0; i < len; ++i) + { + const QApplicationArgument &at = declaredArguments.at(i); + const int min = at.minimumOccurrence(); + const int max = at.maximumOccurrence(); // TODO What about infinite? -1 + if(min == 0) + continue; + else + { + const int usedLen = d->usedArguments.count(); + int useCount = 0; + + for(int u = 0; u < usedLen; ++u) + { + const QPair<QApplicationArgument, QVariant> &used = d->usedArguments.at(u); + if(used.first == at) + ++useCount; + } + + const QString originalName(at.name()); + const QString effectiveName(originalName.isEmpty() ? QLatin1Char('<') + typeToName(at) + QLatin1Char('>') : originalName); + + if(useCount < min) + { + /* For nameless options, we use the type as the name. Looks better. */ + return d->error(QApplicationArgumentParserPrivate::tr("%1 must occur at least %2 times, therefore %3 times is insufficient.", "The number is for %2.", min) + .arg(effectiveName, QString::number(min), QString::number(useCount))); + } + else if(useCount > max) + return d->error(QApplicationArgumentParserPrivate::tr("%1 can occur at most %2 times", "", max).arg(effectiveName, QString::number(max))); + } + } + + d->exitCode = Success; + return true; +} + +/*! + This function is only of interest when subclassing. + + parse() calls this function each time a value, that is \a input, on the command line needs to be + validated and subsequently converted to the type of \a argument. A descriptive error message will + be outputted if \a input cannot be converted to the required type. + + The default implementation uses QVariant::canConvert() and QVariant::convert() for doing conversions. + + QApplicationArgumentParser can be subclassed and this function subsequently overridden, to handle custom types. + + If \a input isn't valid input for \a argument, this function returns a default constructed + QVariant. + + \sa typeToName(), parse() + */ +QVariant QApplicationArgumentParser::convertToValue(const QApplicationArgument &argument, + const QString &input) const +{ + const int type = argument.type(); + + switch(type) + { + case QVariant::Bool: + { + if(input == QLatin1String("true") || input == QChar::fromLatin1('1')) + return QVariant(true); + else if(input == QLatin1String("false") || input == QChar::fromLatin1('0')) + return QVariant(false); + else + return QApplicationArgumentParserPrivate::conversionError(typeToName(argument), input); + } + case QVariant::RegExp: + { + const QRegExp exp(input); + + if(exp.isValid()) + return QVariant(exp); + else + return QApplicationArgumentParserPrivate::conversionError(typeToName(argument), input); + } + case QVariant::Url: + { + const QUrl result(QUrl::fromEncoded(input.toLatin1())); + + if(result.isValid()) + return QVariant(result); + else + return QApplicationArgumentParserPrivate::conversionError(typeToName(argument), input); + } + default: + { + QVariant result(input); + + if(QApplicationArgumentParserPrivate::isBuiltinVariant(type) && + result.convert(QVariant::Type(type))) + return result; + else + return QApplicationArgumentParserPrivate::conversionError(typeToName(argument), input); + } + } +} + +/*! + This function is only of interest when subclassing. + + convertToValue() calls this function when requiring a string for referring to \a type, + when generating user messages. + + The implementation uses QVariant::typeToName() for most types, but special handles + some types, in order to let the message be better tailored for humans. + + \sa convertToValue() + */ +QString QApplicationArgumentParser::typeToName(const QApplicationArgument &argument) const +{ + /* Personally I think nameForType() would be a better name but this is consistent + * with QVariant's function of the same name. */ + const int type = argument.type(); + + switch(type) + { + case QVariant::RegExp: + return QApplicationArgumentParserPrivate::tr("regular expression"); + case QVariant::Url: + return QLatin1String("URI"); + case QVariant::String: + return QLatin1String("string"); + default: + { + if(QApplicationArgumentParserPrivate::isBuiltinVariant(type)) + return QString::fromLatin1(QVariant::typeToName(QVariant::Type(type))); + else + return QLatin1String(QVariant(type, static_cast<void *>(0)).typeName()); + } + } +} + +/*! + Returns the default value for \a argument. The default implementation returns + QApplicationArgument::defaultValue(), if \a argument has been added to this parser. + + Overriding this function can be useful if creating the default value is resource + consuming, such as opening a file. + */ +QVariant QApplicationArgumentParser::defaultValue(const QApplicationArgument &argument) const +{ + return d->declaredArguments.value(argument.name()).defaultValue(); +} + +/*! + Returns the count of how many times \a argument was used on the command line. + + \sa has() + */ +int QApplicationArgumentParser::count(const QApplicationArgument &argument) const +{ + Q_ASSERT_X(d->declaredArguments.contains(argument.name()) || + d->declaredNamelessArguments.contains(argument), Q_FUNC_INFO, + "The argument isn't known to the parser. Has addArgument() been called?"); + return d->count(argument); +} + +/*! + Returns \c true if \a argument has been + specified one or more times on the command line, otherwise \a false. + + \sa count() + */ +bool QApplicationArgumentParser::has(const QApplicationArgument &argument) const +{ + Q_ASSERT_X(d->declaredArguments.contains(argument.name()) || + d->declaredNamelessArguments.contains(argument), Q_FUNC_INFO, + "The argument isn't known to the parser. Has addArgument() been called?"); + return d->contains(argument); +} + +/*! + // TODO docs + + \sa values() + */ +QVariant QApplicationArgumentParser::value(const QApplicationArgument &argument) const +{ + Q_ASSERT_X(d->declaredArguments.contains(argument.name()) || + d->declaredNamelessArguments.contains(argument), Q_FUNC_INFO, + "The argument isn't known to the parser. Has addArgument() been called?"); + + const int len = d->usedArguments.count(); + + for(int i = 0; i < len; ++i) + { + if(d->usedArguments.at(i).first == argument) + return d->usedArguments.at(i).second; + } + + return defaultValue(argument); +} + +/*! + // TODO docs + \sa value() + */ +QVariantList QApplicationArgumentParser::values(const QApplicationArgument &argument) const +{ + Q_ASSERT_X(d->declaredArguments.contains(argument.name()) || + d->declaredNamelessArguments.contains(argument), + Q_FUNC_INFO, + "The argument isn't known to the parser. Has addArgument() been called?"); + + const int len = d->usedArguments.count(); + + QVariantList result; + for(int i = 0; i < len; ++i) + { + if(d->usedArguments.at(i).first == argument) + result.append(d->usedArguments.at(i).second); + } + + // TODO how do we handle default values? + return result; +} + +/*! + After parse() has been called, this function returns a code that can be used to + exit \c main() with. It returns zero upon success or if help was requested, and + otherwise a value signalling failure. + */ +QApplicationArgumentParser::ExitCode QApplicationArgumentParser::exitCode() const +{ + return d->exitCode; +} + +/*! + This function is only of interest when subclassing. + + Makes exitCode() return \a code. + */ +void QApplicationArgumentParser::setExitCode(ExitCode code) +{ + d->exitCode = code; +} + +/*! + Sets the application description to \a description. + + The application description is a sentence or two used for help and version + messages, that briefly describes the application. + + The default is the empty string. + */ +void QApplicationArgumentParser::setApplicationDescription(const QString &description) +{ + d->applicationDescription = description; +} + +/*! + Sets the application version to \a version. + + This string, which is arbitrary but typically is "1.0" or so, is used when + generating a version statement. +*/ +void QApplicationArgumentParser::setApplicationVersion(const QString &version) +{ + d->applicationVersion = version; +} + +/*! + Writes out \a message to \c stderr. + */ +void QApplicationArgumentParser::message(const QString &message) const +{ + d->errorMessage(message); +} + +QT_END_NAMESPACE diff --git a/tools/xmlpatterns/qapplicationargumentparser_p.h b/tools/xmlpatterns/qapplicationargumentparser_p.h new file mode 100644 index 0000000..16bcfe0 --- /dev/null +++ b/tools/xmlpatterns/qapplicationargumentparser_p.h @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** ** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef QApplicationArgumentParser_H +#define QApplicationArgumentParser_H + +#include <QtCore/QVariant> /* Needed, because we can't forward declare QVariantList. */ + +QT_BEGIN_HEADER +QT_BEGIN_NAMESPACE + +class QApplicationArgument; +class QApplicationArgumentParserPrivate; +class QStringList; +template<typename A, typename B> struct QPair; +template<typename T> class QList; +template<typename Value> class QList; + +class QApplicationArgumentParser +{ +public: + enum ExitCode + { + Success = 0, + ParseError = 1 + }; + + QApplicationArgumentParser(int argc, char **argv); + QApplicationArgumentParser(const QStringList &input); + virtual ~QApplicationArgumentParser(); + void addArgument(const QApplicationArgument &argument); + void setDeclaredArguments(const QList<QApplicationArgument> &arguments); + QList<QApplicationArgument> declaredArguments() const; + + int count(const QApplicationArgument &argument) const; + bool has(const QApplicationArgument &argument) const; + + virtual bool parse(); + ExitCode exitCode() const; + QVariant value(const QApplicationArgument &argument) const; + QVariantList values(const QApplicationArgument &argument) const; + void setApplicationDescription(const QString &description); + void setApplicationVersion(const QString &version); + virtual void message(const QString &message) const; + +protected: + void setExitCode(ExitCode code); + void setUsedArguments(const QList<QPair<QApplicationArgument, QVariant> > &arguments); + QList<QPair<QApplicationArgument, QVariant> > usedArguments() const; + QStringList input() const; + virtual QVariant convertToValue(const QApplicationArgument &argument, + const QString &value) const; + virtual QString typeToName(const QApplicationArgument &argument) const; + virtual QVariant defaultValue(const QApplicationArgument &argument) const; + +private: + friend class QApplicationArgumentParserPrivate; + QApplicationArgumentParserPrivate *d; + Q_DISABLE_COPY(QApplicationArgumentParser) +}; + +QT_END_NAMESPACE +QT_END_HEADER +#endif diff --git a/tools/xmlpatterns/qcoloringmessagehandler.cpp b/tools/xmlpatterns/qcoloringmessagehandler.cpp new file mode 100644 index 0000000..dd7e97f --- /dev/null +++ b/tools/xmlpatterns/qcoloringmessagehandler.cpp @@ -0,0 +1,193 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QXmlStreamReader> + +#include "main.h" + +#include "qcoloringmessagehandler_p.h" + +QT_BEGIN_NAMESPACE + +using namespace QPatternist; + +ColoringMessageHandler::ColoringMessageHandler(QObject *parent) : QAbstractMessageHandler(parent) +{ + m_classToColor.insert(QLatin1String("XQuery-data"), Data); + m_classToColor.insert(QLatin1String("XQuery-expression"), Keyword); + m_classToColor.insert(QLatin1String("XQuery-function"), Keyword); + m_classToColor.insert(QLatin1String("XQuery-keyword"), Keyword); + m_classToColor.insert(QLatin1String("XQuery-type"), Keyword); + m_classToColor.insert(QLatin1String("XQuery-uri"), Data); + m_classToColor.insert(QLatin1String("XQuery-filepath"), Data); + + /* If you're tuning the colors, take it easy laddie. Take into account: + * + * - Get over your own taste, there's others too on this planet + * - Make sure it works well on black & white + * - Make sure it works well on white & black + */ + insertMapping(Location, CyanForeground); + insertMapping(ErrorCode, RedForeground); + insertMapping(Keyword, BlueForeground); + insertMapping(Data, BlueForeground); + insertMapping(RunningText, DefaultColor); +} + +void ColoringMessageHandler::handleMessage(QtMsgType type, + const QString &description, + const QUrl &identifier, + const QSourceLocation &sourceLocation) +{ + const bool hasLine = sourceLocation.line() != -1; + + switch(type) + { + case QtWarningMsg: + { + if(hasLine) + { + writeUncolored(QXmlPatternistCLI::tr("Warning in %1, at line %2, column %3: %4").arg(QString::fromLatin1(sourceLocation.uri().toEncoded()), + QString::number(sourceLocation.line()), + QString::number(sourceLocation.column()), + colorifyDescription(description))); + } + else + { + writeUncolored(QXmlPatternistCLI::tr("Warning in %1: %2").arg(QString::fromLatin1(sourceLocation.uri().toEncoded()), + colorifyDescription(description))); + } + + break; + } + case QtFatalMsg: + { + Q_ASSERT(!sourceLocation.isNull()); + const QString errorCode(identifier.fragment()); + Q_ASSERT(!errorCode.isEmpty()); + QUrl uri(identifier); + uri.setFragment(QString()); + + QString errorId; + /* If it's a standard error code, we don't want to output the + * whole URI. */ + if(uri.toString() == QLatin1String("http://www.w3.org/2005/xqt-errors")) + errorId = errorCode; + else + errorId = QString::fromLatin1(identifier.toEncoded()); + + if(hasLine) + { + writeUncolored(QXmlPatternistCLI::tr("Error %1 in %2, at line %3, column %4: %5").arg(colorify(errorId, ErrorCode), + colorify(QString::fromLatin1(sourceLocation.uri().toEncoded()), Location), + colorify(QString::number(sourceLocation.line()), Location), + colorify(QString::number(sourceLocation.column()), Location), + colorifyDescription(description))); + } + else + { + writeUncolored(QXmlPatternistCLI::tr("Error %1 in %2: %3").arg(colorify(errorId, ErrorCode), + colorify(QString::fromLatin1(sourceLocation.uri().toEncoded()), Location), + colorifyDescription(description))); + } + break; + } + case QtCriticalMsg: + /* Fallthrough. */ + case QtDebugMsg: + { + Q_ASSERT_X(false, Q_FUNC_INFO, + "message() is not supposed to receive QtCriticalMsg or QtDebugMsg."); + return; + } + } +} + +QString ColoringMessageHandler::colorifyDescription(const QString &in) const +{ + QXmlStreamReader reader(in); + QString result; + result.reserve(in.size()); + ColorType currentColor = RunningText; + + while(!reader.atEnd()) + { + reader.readNext(); + + switch(reader.tokenType()) + { + case QXmlStreamReader::StartElement: + { + if(reader.name() == QLatin1String("span")) + { + Q_ASSERT(m_classToColor.contains(reader.attributes().value(QLatin1String("class")).toString())); + currentColor = m_classToColor.value(reader.attributes().value(QLatin1String("class")).toString()); + } + + continue; + } + case QXmlStreamReader::Characters: + { + result.append(colorify(reader.text().toString(), currentColor)); + continue; + } + case QXmlStreamReader::EndElement: + { + currentColor = RunningText; + continue; + } + /* Fallthrough, */ + case QXmlStreamReader::StartDocument: + /* Fallthrough, */ + case QXmlStreamReader::EndDocument: + continue; + default: + Q_ASSERT_X(false, Q_FUNC_INFO, + "Unexpected node."); + } + } + + Q_ASSERT_X(!reader.hasError(), Q_FUNC_INFO, + "The output from Patternist must be well-formed."); + return result; +} + +QT_END_NAMESPACE diff --git a/tools/xmlpatterns/qcoloringmessagehandler_p.h b/tools/xmlpatterns/qcoloringmessagehandler_p.h new file mode 100644 index 0000000..69ca5b0 --- /dev/null +++ b/tools/xmlpatterns/qcoloringmessagehandler_p.h @@ -0,0 +1,99 @@ +/**************************************************************************** + * ** * ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) + * ** + * ** This file is part of the Patternist project on Trolltech Labs. * ** + * ** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ + * ** + * ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * ** + * ****************************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef Patternist_ColoringMessageHandler_h +#define Patternist_ColoringMessageHandler_h + +#include <QHash> + +#include "qcoloroutput_p.h" +#include "qabstractmessagehandler.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +namespace QPatternist +{ + class ColoringMessageHandler : public QAbstractMessageHandler + , private ColorOutput + { + public: + ColoringMessageHandler(QObject *parent = 0); + + protected: + virtual void handleMessage(QtMsgType type, + const QString &description, + const QUrl &identifier, + const QSourceLocation &sourceLocation); + + private: + QString colorifyDescription(const QString &in) const; + + enum ColorType + { + RunningText, + Location, + ErrorCode, + Keyword, + Data + }; + + QHash<QString, ColorType> m_classToColor; + }; +} + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/tools/xmlpatterns/qcoloroutput.cpp b/tools/xmlpatterns/qcoloroutput.cpp new file mode 100644 index 0000000..e401c5d --- /dev/null +++ b/tools/xmlpatterns/qcoloroutput.cpp @@ -0,0 +1,350 @@ +/**************************************************************************** + * ** * ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) + * ** + * ** This file is part of the Patternist project on Trolltech Labs. + * ** + * ** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ + * ** + * ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * ** + * ****************************************************************************/ + +#include <QFile> +#include <QHash> +#include <QTextCodec> + +#include "qcoloroutput_p.h" + +// TODO: rename insertMapping() to insertColorMapping() +// TODO: Use a smart pointer for managing ColorOutputPrivate *d; +// TODO: break out the C++ example into a snippet file + +/* This include must appear here, because if it appears at the beginning of the file for + * instance, it breaks build -- "qglobal.h:628: error: template with + * C linkage" -- on Mac OS X 10.4. */ +#ifndef Q_OS_WIN +#include <unistd.h> +#endif + +QT_BEGIN_NAMESPACE + +using namespace QPatternist; + +namespace QPatternist +{ + class ColorOutputPrivate + { + public: + ColorOutputPrivate() : currentColorID(-1) + + { + /* - QIODevice::Unbuffered because we want it to appear when the user actually calls, performance + * is considered of lower priority. + */ + m_out.open(stderr, QIODevice::WriteOnly | QIODevice::Unbuffered); + + coloringEnabled = isColoringPossible(); + } + + ColorOutput::ColorMapping colorMapping; + int currentColorID; + bool coloringEnabled; + + static const char *const foregrounds[]; + static const char *const backgrounds[]; + + inline void write(const QString &msg) + { + m_out.write(msg.toLocal8Bit()); + } + + static QString escapeCode(const QString &in) + { + QString result; + result.append(QChar(0x1B)); + result.append(QLatin1Char('[')); + result.append(in); + result.append(QLatin1Char('m')); + return result; + } + + private: + QFile m_out; + + /*! + Returns true if it's suitable to send colored output to \c stderr. + */ + inline bool isColoringPossible() const + { +# if defined(Q_OS_WIN32) || defined(Q_OS_WINCE) + /* 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(m_out.handle()); +# endif + } + }; +} + +const char *const ColorOutputPrivate::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 ColorOutputPrivate::backgrounds[] = +{ + "0;40", + "0;44", + "0;42", + "0;46", + "0;41", + "0;45", + "0;43" +}; + +/*! + \since 4.4 + \nonreentrant + \brief Outputs colored messages to \c stderr. + \internal + + ColorOutput is a convenience class for outputting messages to \c stderr + using color escape codes, as mandated in ECMA-48. ColorOutput 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. + + ColorOutput 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'`, \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 ColorOutput 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 ColorOutput 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 + }; + + ColorOutput output; + output.insertMapping(Error, ColorOutput::RedForeground); + output.insertMapping(Import, ColorOutput::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. Colours} + {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} + */ + +/*! + \enum ColorOutput::ColorCode + + \value DefaultColor ColorOutput performs no coloring. This typically means black on white + or white on black, depending on the settings of the user's terminal. + */ + +/*! + Sets the color mapping to be \a cMapping. + + Negative values are disallowed. + + \sa colorMapping(), insertMapping() + */ +void ColorOutput::setColorMapping(const ColorMapping &cMapping) +{ + d->colorMapping = cMapping; +} + +/*! + Returns the color mappings in use. + + \sa setColorMapping(), insertMapping() + */ +ColorOutput::ColorMapping ColorOutput::colorMapping() const +{ + return d->colorMapping; +} + +/*! + Constructs a ColorOutput instance, ready for use. + */ +ColorOutput::ColorOutput() : d(new ColorOutputPrivate()) +{ +} + +/*! + Destructs this ColorOutput instance. + */ +ColorOutput::~ColorOutput() +{ + delete d; +} + +/*! + Sends \a message to \c stderr, using the color looked up in colorMapping() using \a colorID. + + If \a color isn't available in colorMapping(), result and behavior is undefined. + + If \a colorID is 0, which is the default value, the previously used coloring is used. ColorOutput + 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 ColorOutput::write(const QString &message, int colorID) +{ + d->write(colorify(message, colorID)); +} + +/*! + 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 ColorOutput for all forms of writing. + */ +void ColorOutput::writeUncolored(const QString &message) +{ + d->write(message + QLatin1Char('\n')); +} + +/*! + 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 ColorOutput::colorify(const QString &message, int colorID) const +{ + Q_ASSERT_X(colorID == -1 || d->colorMapping.contains(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->currentColorID = colorID; + + if(d->coloringEnabled && colorID != -1) + { + const int color(d->colorMapping.value(colorID)); + + /* If DefaultColor is set, we don't want to color it. */ + if(color & DefaultColor) + return message; + + const int foregroundCode = (int(color) & ForegroundMask) >> ForegroundShift; + const int backgroundCode = (int(color) & BackgroundMask) >> BackgroundShift; + QString finalMessage; + bool closureNeeded = false; + + if(foregroundCode) + { + finalMessage.append(ColorOutputPrivate::escapeCode(QLatin1String(ColorOutputPrivate::foregrounds[foregroundCode - 1]))); + closureNeeded = true; + } + + if(backgroundCode) + { + finalMessage.append(ColorOutputPrivate::escapeCode(QLatin1String(ColorOutputPrivate::backgrounds[backgroundCode - 1]))); + closureNeeded = true; + } + + finalMessage.append(message); + + if(closureNeeded) + { + finalMessage.append(QChar(0x1B)); + finalMessage.append(QLatin1String("[0m")); + } + + return finalMessage; + } + else + return message; +} + +/*! + Adds a color mapping from \a colorID to \a colorCode, for this ColorOutput instance. + + This is a convenience function for creating a ColorOutput::ColorMapping instance and + calling setColorMapping(). + + \sa colorMapping(), setColorMapping() + */ +void ColorOutput::insertMapping(int colorID, const ColorCode colorCode) +{ + d->colorMapping.insert(colorID, colorCode); +} + +QT_END_NAMESPACE diff --git a/tools/xmlpatterns/qcoloroutput_p.h b/tools/xmlpatterns/qcoloroutput_p.h new file mode 100644 index 0000000..d9daa14 --- /dev/null +++ b/tools/xmlpatterns/qcoloroutput_p.h @@ -0,0 +1,134 @@ +/**************************************************************************** + * ** * ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) + * ** + * ** This file is part of the Patternist project on Trolltech Labs. * ** + * ** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ + * ** + * ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * ** + * ****************************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef Patternist_ColorOutput_h +#define Patternist_ColorOutput_h + +#include <QtCore/QtGlobal> +#include <QtCore/QHash> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +namespace QPatternist +{ + class ColorOutputPrivate; + + class ColorOutput + { + enum + { + ForegroundShift = 10, + BackgroundShift = 20, + SpecialShift = 20, + ForegroundMask = ((1 << ForegroundShift) - 1) << ForegroundShift, + BackgroundMask = ((1 << BackgroundShift) - 1) << BackgroundShift + }; + + public: + enum ColorCodeComponent + { + BlackForeground = 1 << ForegroundShift, + BlueForeground = 2 << ForegroundShift, + GreenForeground = 3 << ForegroundShift, + CyanForeground = 4 << ForegroundShift, + RedForeground = 5 << ForegroundShift, + PurpleForeground = 6 << ForegroundShift, + BrownForeground = 7 << ForegroundShift, + LightGrayForeground = 8 << ForegroundShift, + DarkGrayForeground = 9 << ForegroundShift, + LightBlueForeground = 10 << ForegroundShift, + LightGreenForeground = 11 << ForegroundShift, + LightCyanForeground = 12 << ForegroundShift, + LightRedForeground = 13 << ForegroundShift, + LightPurpleForeground = 14 << ForegroundShift, + YellowForeground = 15 << ForegroundShift, + WhiteForeground = 16 << ForegroundShift, + + BlackBackground = 1 << BackgroundShift, + BlueBackground = 2 << BackgroundShift, + GreenBackground = 3 << BackgroundShift, + CyanBackground = 4 << BackgroundShift, + RedBackground = 5 << BackgroundShift, + PurpleBackground = 6 << BackgroundShift, + BrownBackground = 7 << BackgroundShift, + DefaultColor = 1 << SpecialShift + }; + + typedef QFlags<ColorCodeComponent> ColorCode; + typedef QHash<int, ColorCode> ColorMapping; + + ColorOutput(); + ~ColorOutput(); + + void setColorMapping(const ColorMapping &cMapping); + ColorMapping colorMapping() const; + void insertMapping(int colorID, const ColorCode colorCode); + + void writeUncolored(const QString &message); + void write(const QString &message, int color = -1); + QString colorify(const QString &message, int color = -1) const; + + private: + ColorOutputPrivate *d; + Q_DISABLE_COPY(ColorOutput) + }; +} + +Q_DECLARE_OPERATORS_FOR_FLAGS(QPatternist::ColorOutput::ColorCode) + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/tools/xmlpatterns/xmlpatterns.pro b/tools/xmlpatterns/xmlpatterns.pro new file mode 100644 index 0000000..9c1aac1 --- /dev/null +++ b/tools/xmlpatterns/xmlpatterns.pro @@ -0,0 +1,31 @@ +TEMPLATE = app +TARGET = xmlpatterns +DESTDIR = ../../bin +QT -= gui +QT += xmlpatterns + +target.path = $$[QT_INSTALL_BINS] +INSTALLS += target + +# This ensures we get stderr and stdout on Windows. +CONFIG += console + +# This ensures that this is a command-line program on OS X and not a GUI application. +CONFIG -= app_bundle + +# Note that qcoloroutput.cpp and qcoloringmessagehandler.cpp are also used internally +# in libQtXmlPatterns. See src/xmlpatterns/api/api.pri. +SOURCES = main.cpp \ + qapplicationargument.cpp \ + qapplicationargumentparser.cpp \ + qcoloringmessagehandler.cpp \ + qcoloroutput.cpp + + +HEADERS = main.h \ + qapplicationargument.cpp \ + qapplicationargumentparser.cpp \ + qcoloringmessagehandler_p.h \ + qcoloroutput_p.h + +include(../src/common.pri) |