├── Demo ├── Demo.pro ├── parser │ ├── parser.pro │ └── main.cpp └── generator │ ├── generator.pro │ └── main.cpp ├── qcligenerator_meta.h ├── .gitignore ├── qcliparser.pri ├── qcligenerator.cpp ├── qcliparser.h ├── LICENSE ├── qcligenerator.h ├── qclinode.h ├── README.md ├── qclievaluator.h ├── qclinode.cpp ├── qcliparser.cpp └── qclievaluator.cpp /Demo/Demo.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS += \ 4 | parser \ 5 | generator 6 | -------------------------------------------------------------------------------- /qcligenerator_meta.h: -------------------------------------------------------------------------------- 1 | #ifndef QCLIGENERATOR_META_H 2 | #define QCLIGENERATOR_META_H 3 | 4 | #include 5 | 6 | #define Q_CLI_ORCHESTRATOR_OBJECT() Q_CLASSINFO("__q_cli_orchestrator", "") 7 | #define Q_CLI_CONTEXT_OBJECT(path) Q_CLASSINFO("__q_cli_context", #path) 8 | #define Q_CLI_PREFIX(prefix), Q_CLASSINFO("__q_cli_prefix", #prefix "_") // defaults to "qCli_" 9 | 10 | #endif // QCLIGENERATOR_META_H 11 | -------------------------------------------------------------------------------- /Demo/parser/parser.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT += core 4 | QT -= gui 5 | 6 | CONFIG += c++17 warning_clean exceptions console 7 | CONFIG -= app_bundle 8 | DEFINES += QT_DEPRECATED_WARNINGS QT_ASCII_CAST_WARNINGS QT_USE_QSTRINGBUILDER 9 | 10 | TARGET = parser-demo 11 | 12 | include(../../qcliparser.pri) 13 | 14 | SOURCES += main.cpp 15 | 16 | !load(qdep):error("Failed to load qdep feature! Run 'qdep.py prfgen --qmake $$QMAKE_QMAKE' to create it.") 17 | -------------------------------------------------------------------------------- /Demo/generator/generator.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT += core 4 | QT -= gui 5 | 6 | CONFIG += c++17 warning_clean exceptions console 7 | CONFIG -= app_bundle 8 | DEFINES += QT_DEPRECATED_WARNINGS QT_ASCII_CAST_WARNINGS QT_USE_QSTRINGBUILDER 9 | 10 | TARGET = generator-demo 11 | 12 | include(../../qcliparser.pri) 13 | 14 | SOURCES += main.cpp 15 | 16 | !load(qdep):error("Failed to load qdep feature! Run 'qdep.py prfgen --qmake $$QMAKE_QMAKE' to create it.") 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # C++ objects and libs 2 | 3 | *.slo 4 | *.lo 5 | *.o 6 | *.a 7 | *.la 8 | *.lai 9 | *.so 10 | *.dll 11 | *.dylib 12 | 13 | # Qt-es 14 | 15 | /.qmake.cache 16 | /.qmake.stash 17 | *.pro.user 18 | *.pro.user.* 19 | *.qbs.user 20 | *.qbs.user.* 21 | *.moc 22 | moc_*.cpp 23 | moc_*.h 24 | qrc_*.cpp 25 | ui_*.h 26 | Makefile* 27 | *build-* 28 | 29 | # QtCreator 30 | 31 | *.autosave 32 | 33 | # QtCtreator Qml 34 | *.qmlproject.user 35 | *.qmlproject.user.* 36 | 37 | # QtCtreator CMake 38 | CMakeLists.txt.user* 39 | 40 | -------------------------------------------------------------------------------- /qcliparser.pri: -------------------------------------------------------------------------------- 1 | HEADERS += \ 2 | $$PWD/qclievaluator.h \ 3 | $$PWD/qcligenerator.h \ 4 | $$PWD/qcligenerator_meta.h \ 5 | $$PWD/qcliparser.h \ 6 | $$PWD/qclinode.h 7 | 8 | SOURCES += \ 9 | $$PWD/qclievaluator.cpp \ 10 | $$PWD/qcligenerator.cpp \ 11 | $$PWD/qcliparser.cpp \ 12 | $$PWD/qclinode.cpp 13 | 14 | win32: LIBS += -luser32 15 | 16 | INCLUDEPATH += $$PWD 17 | 18 | QDEP_DEPENDS += Skycoder42/QGenericTree 19 | 20 | QDEP_PACKAGE_EXPORTS += Q_CLI_PARSER_EXPORT 21 | !qdep_build: DEFINES += "Q_CLI_PARSER_EXPORT=" 22 | -------------------------------------------------------------------------------- /qcligenerator.cpp: -------------------------------------------------------------------------------- 1 | #include "qcligenerator.h" 2 | 3 | QCliGenerator::QCliGenerator(QObject *parent) : 4 | QObject(parent) 5 | {} 6 | 7 | bool QCliGenerator::setOrchestrator(QCliOrchestrator *orchestrator, bool takeOwnership) 8 | { 9 | 10 | } 11 | 12 | bool QCliGenerator::addContext(const QMetaObject *generatorMetaObject) 13 | { 14 | 15 | } 16 | 17 | bool QCliGenerator::addContext(QObject *generator) 18 | { 19 | 20 | } 21 | 22 | void QCliGenerator::prepareParser(QCliParser &parser) 23 | { 24 | 25 | } 26 | 27 | void QCliGenerator::prepareParser(QCommandLineParser &parser) 28 | { 29 | 30 | } 31 | 32 | int QCliGenerator::exec(QCliParser &parser) 33 | { 34 | 35 | } 36 | 37 | int QCliGenerator::exec(QCommandLineParser &parser) 38 | { 39 | 40 | } 41 | 42 | 43 | 44 | QCliOrchestrator::QCliOrchestrator() = default; 45 | 46 | QCliOrchestrator::~QCliOrchestrator() = default; 47 | 48 | 49 | 50 | QCliMetaOrchestrator::QCliMetaOrchestrator(QObject *parent) : 51 | QObject{parent} 52 | {} 53 | 54 | QList> QCliMetaOrchestrator::contextList(const QStringList &prefix) const 55 | { 56 | 57 | } 58 | 59 | const QObject *QCliMetaOrchestrator::createGenerator(const QStringList &context, QObject *parent) 60 | { 61 | 62 | } 63 | -------------------------------------------------------------------------------- /qcliparser.h: -------------------------------------------------------------------------------- 1 | #ifndef QCLIPARSER_H 2 | #define QCLIPARSER_H 3 | 4 | #include "qclinode.h" 5 | 6 | #include 7 | 8 | class Q_CLI_PARSER_EXPORT QCliParser : public QCommandLineParser, public QCliContext 9 | { 10 | Q_DECLARE_TR_FUNCTIONS(QCliParser) 11 | 12 | public: 13 | QCliParser(); 14 | 15 | using QCliContext::addOption; 16 | using QCliContext::addOptions; 17 | 18 | void process(const QStringList &arguments, bool colored = false); 19 | void process(const QCoreApplication &app, bool colored = false); 20 | bool parse(const QStringList &arguments); 21 | 22 | bool enterContext(const QString &name); 23 | QString currentContext() const; 24 | bool leaveContext(); 25 | 26 | QStringList contextChain() const; 27 | QString errorText() const; 28 | 29 | private: 30 | friend class QCliEvaluator; 31 | 32 | QStringList _contextChain; 33 | QString _errorText; 34 | 35 | int _readContextIndex; 36 | 37 | static void showParserMessage(const QString &message); 38 | 39 | //hide 40 | Q_NORETURN void addPositionalArgument(const QString &name, const QString &description, const QString &syntax = QString()); 41 | Q_NORETURN void clearPositionalArguments(); 42 | 43 | void parseContext(QCliContext *context, QStringList arguments); 44 | void parseLeaf(QCliLeaf *leaf, const QStringList &arguments); 45 | }; 46 | 47 | #endif // QCLIPARSER_H 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, Felix Barz 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /qcligenerator.h: -------------------------------------------------------------------------------- 1 | #ifndef QCLIGENERATOR_H 2 | #define QCLIGENERATOR_H 3 | 4 | #include 5 | #include "qcliparser.h" 6 | 7 | #include "qcligenerator_meta.h" 8 | 9 | class Q_CLI_PARSER_EXPORT QCliOrchestrator 10 | { 11 | Q_DISABLE_COPY(QCliOrchestrator) 12 | public: 13 | QCliOrchestrator(); 14 | virtual ~QCliOrchestrator(); 15 | 16 | virtual QList> contextList(const QStringList &prefix) const = 0; 17 | virtual const QObject *createGenerator(const QStringList &context, QObject *parent) = 0; 18 | }; 19 | 20 | #define QCliOrchestratorIid "de.skycoder42.qcliparser.QCliOrchestrator" 21 | Q_DECLARE_INTERFACE(QCliOrchestrator, QCliOrchestratorIid) 22 | 23 | class Q_CLI_PARSER_EXPORT QCliMetaOrchestrator : public QObject, public QCliOrchestrator 24 | { 25 | Q_OBJECT 26 | Q_INTERFACES(QCliOrchestrator) 27 | 28 | public: 29 | QCliMetaOrchestrator(QObject *parent = nullptr); 30 | 31 | QList > contextList(const QStringList &prefix) const override; 32 | const QObject *createGenerator(const QStringList &context, QObject *parent) override; 33 | 34 | private: 35 | 36 | }; 37 | 38 | class Q_CLI_PARSER_EXPORT QCliGenerator : public QObject 39 | { 40 | Q_OBJECT 41 | 42 | public: 43 | explicit QCliGenerator(QObject *parent = nullptr); 44 | 45 | template 46 | bool setOrchestrator(); 47 | bool setOrchestrator(QCliOrchestrator *orchestrator, bool takeOwnership = false); 48 | 49 | template 50 | bool addContext(const QStringList &path); 51 | bool addContext(const QMetaObject *generatorMetaObject); 52 | bool addContext(QObject *generator); 53 | 54 | void prepareParser(QCliParser &parser); 55 | void prepareParser(QCommandLineParser &parser); 56 | 57 | Q_INVOKABLE int exec(QCliParser &parser); 58 | Q_INVOKABLE int exec(QCommandLineParser &parser); 59 | }; 60 | 61 | #endif // QCLIGENERATOR_H 62 | -------------------------------------------------------------------------------- /qclinode.h: -------------------------------------------------------------------------------- 1 | #ifndef QCLINODE_H 2 | #define QCLINODE_H 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | class Q_CLI_PARSER_EXPORT QCliNode 12 | { 13 | friend class QCliParser; 14 | Q_DISABLE_COPY(QCliNode) 15 | 16 | public: 17 | QCliNode(); 18 | virtual ~QCliNode(); 19 | 20 | bool addOption(const QCommandLineOption &commandLineOption); 21 | bool addOptions(const QList &options); 22 | 23 | void setHidden(bool hidden); 24 | bool isHidden() const; 25 | 26 | private: 27 | QList _options; 28 | QSet _keyCache; 29 | bool _hidden; 30 | }; 31 | 32 | class Q_CLI_PARSER_EXPORT QCliLeaf : public QCliNode 33 | { 34 | friend class QCliParser; 35 | public: 36 | QCliLeaf(); 37 | 38 | void addPositionalArgument(const QString &name, const QString &description, const QString &syntax = {}); 39 | 40 | private: 41 | QList> _arguments; 42 | }; 43 | 44 | class Q_CLI_PARSER_EXPORT QCliContext : public QCliNode 45 | { 46 | friend class QCliParser; 47 | 48 | public: 49 | QCliContext(); 50 | 51 | bool addCliNode(const QString &name, const QString &description, const QSharedPointer &node); 52 | QSharedPointer addContextNode(const QString &name, const QString &description); 53 | QSharedPointer addLeafNode(const QString &name, const QString &description); 54 | void setDefaultNode(const QString &name); 55 | 56 | template 57 | QSharedPointer getNode(const QString &name) const; 58 | 59 | private: 60 | QMap>> _nodes; 61 | QString _defaultNode; 62 | }; 63 | 64 | // ------------- GENERIC IMPLEMENTATION ------------- 65 | 66 | template 67 | QSharedPointer QCliContext::getNode(const QString &name) const 68 | { 69 | return _nodes.value(name).first; 70 | } 71 | 72 | 73 | #endif // QCLINODE_H 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QCliParser 2 | An extension of QCommandlineParser to make context based CLIs easier 3 | 4 | ## Features 5 | - Similar API to QCommandlineParser 6 | - Works by creating command trees 7 | - each tree node can have multiple sub-nodes of any kind 8 | - each tree node can define additional optional arguments 9 | - each tree leaf can define it's own positional arguments 10 | 11 | ## Installation 12 | The package is provided via qdep, as `Skycoder42/QCliParser`. To use it simply: 13 | 14 | 1. Install and enable qdep (See [qdep - Installing](https://github.com/Skycoder42/qdep#installation)) 15 | 2. Add the following to your pro file: 16 | ```qmake 17 | QDEP_DEPENDS += Skycoder42/QCliParser 18 | !load(qdep):error("Failed to load qdep feature! Run 'qdep.py prfgen --qmake $$QMAKE_QMAKE' to create it.") 19 | ``` 20 | 21 | ## Example 22 | Assume the following application: 23 | 24 | ```cpp 25 | QCoreApplication a(argc, argv); 26 | QCliParser parser; 27 | parser.addVersionOption(); 28 | parser.addHelpOption(); 29 | 30 | auto leafNode = parser.addLeafNode("leaf", "a leaf command"); 31 | leafNode->addPositionalArgument("stuff", "some positional args"); 32 | 33 | auto ctxNode = parser.addContextNode("context", "a context command"); 34 | ctxNode->addOption({"option", "some extra option"}); 35 | auto subNode1 = ctxNode->addLeafNode("sub1", "the first sub command"); 36 | auto subNode2 = ctxNode->addLeafNode("sub2", "the second sub command"); 37 | 38 | parser.process(a); 39 | if(parser.enterContext("leaf")) 40 | qDebug() << "leaf was called with args:" << parser.positionalArguments(); 41 | else if(parser.enterContext("context") { 42 | qDebug() << "option set:" << parser.isSet("option"); 43 | if(parser.enterContext("sub1")) 44 | qDebug() << "context sub1"; 45 | else if(parser.enterContext("sub2")) 46 | qDebug() << "context sub2"; 47 | } 48 | ``` 49 | 50 | Some examples of calling this with the corresponding output would be (newlines replaced by `;`): 51 | 52 | - `leaf 1 2 3`: `leaf was called with args: ("1", "2", "3")` 53 | - `context sub1`: `option set: false; context sub1` 54 | - `context --option sub2`: `option set: true; context sub2` 55 | 56 | For a full example, check the demo. 57 | -------------------------------------------------------------------------------- /qclievaluator.h: -------------------------------------------------------------------------------- 1 | #ifndef QCLIEVALUATOR_H 2 | #define QCLIEVALUATOR_H 3 | 4 | #include "qcliparser.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | class Q_CLI_PARSER_EXPORT QCliEvaluator : public QObject 13 | { 14 | Q_OBJECT 15 | 16 | Q_PROPERTY(bool autoResolveObjects READ doesAutoResolveObjects WRITE setAutoResolveObjects NOTIFY autoResolveObjectsChanged) 17 | 18 | public: 19 | explicit QCliEvaluator(QObject *parent = nullptr); 20 | 21 | template 22 | bool registerEvaluator(const QStringList &path); 23 | 24 | bool doesAutoResolveObjects() const; 25 | 26 | Q_INVOKABLE int exec(const QCliParser &parser); 27 | Q_INVOKABLE int exec(const QCommandLineParser &parser); 28 | 29 | public Q_SLOTS: 30 | bool registerEvaluator(const QByteArray &className, const QStringList &path); 31 | bool registerEvaluator(const QMetaObject *metaObject, const QStringList &path); 32 | 33 | void setAutoResolveObjects(bool autoResolveObjects); 34 | 35 | signals: 36 | void autoResolveObjectsChanged(bool autoResolveObjects); 37 | 38 | protected: 39 | virtual QByteArray evaluatorClassName(const QStringList &contextList) const; 40 | virtual QByteArray evaluatorMethodName(const QMetaObject *metaObject, const QStringList &contextList) const; 41 | 42 | private: 43 | struct LogBlocker { 44 | LogBlocker(); 45 | ~LogBlocker(); 46 | }; 47 | 48 | using EvaluatorTree = QUnorderedTree; 49 | 50 | bool _autoResolveObjects = true; 51 | 52 | EvaluatorTree _evaluators; 53 | 54 | static const QMetaObject *metaObjectForName(const QByteArray &className); 55 | 56 | int execImpl(const QCommandLineParser &parser, const QStringList &contextList); 57 | std::optional tryExec(const QMetaObject *metaObject, 58 | const QCommandLineParser &parser, 59 | const QStringList &contextList); 60 | void setOptionProperties(QObject *instance, const QCommandLineParser &parser) const; 61 | int callMetaMethod(QObject *instance, const QMetaMethod &method, QVariantList arguments) const; 62 | }; 63 | 64 | template 65 | bool QCliEvaluator::registerEvaluator(const QStringList &path) 66 | { 67 | static_assert(std::is_base_of_v, "TEvaluator must extend QObject!"); 68 | return registerEvaluator(&TEvaluator::staticMetaObject, path); 69 | } 70 | 71 | #endif // QCLIEVALUATOR_H 72 | -------------------------------------------------------------------------------- /qclinode.cpp: -------------------------------------------------------------------------------- 1 | #include "qclinode.h" 2 | 3 | QCliNode::QCliNode() : 4 | _options(), 5 | _keyCache(), 6 | _hidden(false) 7 | {} 8 | 9 | QCliNode::~QCliNode() = default; 10 | 11 | bool QCliNode::addOption(const QCommandLineOption &commandLineOption) 12 | { 13 | for(const auto &key : commandLineOption.names()) { 14 | if(_keyCache.contains(key)) 15 | return false; 16 | } 17 | 18 | _options.append(commandLineOption); 19 | _keyCache.unite(QSet::fromList(commandLineOption.names())); 20 | return true; 21 | } 22 | 23 | bool QCliNode::addOptions(const QList &options) 24 | { 25 | auto tSet(_keyCache); 26 | for(const auto &option : options) { 27 | for(const auto &key : option.names()) { 28 | if(tSet.contains(key)) 29 | return false; 30 | } 31 | tSet.unite(QSet::fromList(option.names())); 32 | } 33 | 34 | _options.append(options); 35 | _keyCache.unite(tSet); 36 | return true; 37 | } 38 | 39 | void QCliNode::setHidden(bool hidden) 40 | { 41 | _hidden = hidden; 42 | } 43 | 44 | bool QCliNode::isHidden() const 45 | { 46 | return _hidden; 47 | } 48 | 49 | 50 | QCliLeaf::QCliLeaf() : 51 | QCliNode(), 52 | _arguments() 53 | {} 54 | 55 | void QCliLeaf::addPositionalArgument(const QString &name, const QString &description, const QString &syntax) 56 | { 57 | _arguments.append(std::make_tuple( 58 | name, 59 | description, 60 | syntax.isEmpty() ? 61 | QStringLiteral("<%1>").arg(name) : 62 | syntax 63 | )); 64 | } 65 | 66 | QCliContext::QCliContext() : 67 | QCliNode(), 68 | _nodes(), 69 | _defaultNode() 70 | {} 71 | 72 | bool QCliContext::addCliNode(const QString &name, const QString &description, const QSharedPointer &node) 73 | { 74 | if(_nodes.contains(name)) 75 | return false; 76 | else { 77 | _nodes.insert(name, {description, node}); 78 | return true; 79 | } 80 | } 81 | 82 | QSharedPointer QCliContext::addContextNode(const QString &name, const QString &description) 83 | { 84 | auto ptr = QSharedPointer::create(); 85 | if(addCliNode(name, description, ptr)) 86 | return ptr; 87 | else 88 | return {}; 89 | } 90 | 91 | QSharedPointer QCliContext::addLeafNode(const QString &name, const QString &description) 92 | { 93 | auto ptr = QSharedPointer::create(); 94 | if(addCliNode(name, description, ptr)) 95 | return ptr; 96 | else 97 | return {}; 98 | } 99 | 100 | void QCliContext::setDefaultNode(const QString &name) 101 | { 102 | _defaultNode = name; 103 | } 104 | -------------------------------------------------------------------------------- /Demo/generator/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | QCoreApplication a(argc, argv); 8 | QCoreApplication::setApplicationName(QStringLiteral("qcliparser-demo")); 9 | QCoreApplication::setApplicationVersion(QStringLiteral("4.2.0")); 10 | 11 | QCliParser parser; 12 | parser.addVersionOption(); 13 | parser.addHelpOption(); 14 | parser.setApplicationDescription(QStringLiteral("An application to demonstrate the capabilities of QCliParser")); 15 | 16 | parser.addOption({ 17 | {QStringLiteral("e"), QStringLiteral("exit-code")}, 18 | QStringLiteral("The the exit to be used if the program runs successfully. " 19 | "The default code is 0"), 20 | QStringLiteral("code"), 21 | QStringLiteral("0") 22 | }); 23 | 24 | auto printNode = parser.addContextNode(QStringLiteral("print"), QStringLiteral("Print various things to the console")); 25 | printNode->addOption({ 26 | {QStringLiteral("c"), QStringLiteral("colored")}, 27 | QStringLiteral("Prints the graphics colored, instead of black and white") 28 | }); 29 | auto printTreeNode = printNode->addLeafNode(QStringLiteral("tree"), QStringLiteral("print a tree")); 30 | printTreeNode->addOption({ 31 | QStringLiteral("size"), 32 | QStringLiteral("Choose the (in pixels) the tree should be high"), 33 | QStringLiteral("size"), 34 | QStringLiteral("42") 35 | }); 36 | printTreeNode->addOption({ 37 | QStringLiteral("season"), 38 | QStringLiteral("Choose the (spring/summer/fall/winter) to show the tree for"), 39 | QStringLiteral("season"), 40 | QStringLiteral("summer") 41 | }); 42 | auto printNumbersNode = printNode->addLeafNode(QStringLiteral("sum"), QStringLiteral("print the sum of numbers")); 43 | printNumbersNode->addPositionalArgument(QStringLiteral("numbers"), 44 | QStringLiteral("The numbers to be summed up"), 45 | QStringLiteral("number...")); 46 | 47 | auto messageNode = parser.addContextNode(QStringLiteral("message"), QStringLiteral("print some kind of message")); 48 | messageNode->addOption({ 49 | QStringLiteral("scream"), 50 | QStringLiteral("Scream the message, instead of beeing friendly") 51 | }); 52 | messageNode->addOption({ 53 | {QStringLiteral("f"), QStringLiteral("failure")}, 54 | QStringLiteral("Print the message as failure (to stderr) instead of " 55 | "a normal message (stdout)") 56 | }); 57 | 58 | auto messageEchoNode = messageNode->addLeafNode(QStringLiteral("echo"), QStringLiteral("Echo additional arguments")); 59 | messageEchoNode->addPositionalArgument(QStringLiteral("message"), 60 | QStringLiteral("The message to be printed (can contain spaces)"), 61 | QStringLiteral("[message]")); 62 | 63 | messageNode->addLeafNode(QStringLiteral("random"), QStringLiteral("Print some random stuff")); 64 | messageNode->addLeafNode(QStringLiteral("help"), QStringLiteral("print the full help information, not just the selective one")); 65 | messageNode->setDefaultNode(QStringLiteral("random")); 66 | 67 | parser.process(a); 68 | 69 | qInfo() << "Context:" << parser.contextChain(); 70 | qInfo() << "Options:" << parser.optionNames(); 71 | qInfo() << "Arguments:" << parser.positionalArguments(); 72 | return parser.value(QStringLiteral("e")).toInt(); 73 | } 74 | -------------------------------------------------------------------------------- /Demo/parser/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | QCoreApplication a(argc, argv); 8 | QCoreApplication::setApplicationName(QStringLiteral("qcliparser-demo")); 9 | QCoreApplication::setApplicationVersion(QStringLiteral("4.2.0")); 10 | 11 | QCliParser parser; 12 | parser.addVersionOption(); 13 | parser.addHelpOption(); 14 | parser.setApplicationDescription(QStringLiteral("An application to demonstrate the capabilities of QCliParser")); 15 | 16 | parser.addOption({ 17 | {QStringLiteral("e"), QStringLiteral("exit-code")}, 18 | QStringLiteral("The the exit to be used if the program runs successfully. " 19 | "The default code is 0"), 20 | QStringLiteral("code"), 21 | QStringLiteral("0") 22 | }); 23 | 24 | auto printNode = parser.addContextNode(QStringLiteral("print"), QStringLiteral("Print various things to the console")); 25 | printNode->addOption({ 26 | {QStringLiteral("c"), QStringLiteral("colored")}, 27 | QStringLiteral("Prints the graphics colored, instead of black and white") 28 | }); 29 | auto printTreeNode = printNode->addLeafNode(QStringLiteral("tree"), QStringLiteral("print a tree")); 30 | printTreeNode->addOption({ 31 | QStringLiteral("size"), 32 | QStringLiteral("Choose the (in pixels) the tree should be high"), 33 | QStringLiteral("size"), 34 | QStringLiteral("42") 35 | }); 36 | printTreeNode->addOption({ 37 | QStringLiteral("season"), 38 | QStringLiteral("Choose the (spring/summer/fall/winter) to show the tree for"), 39 | QStringLiteral("season"), 40 | QStringLiteral("summer") 41 | }); 42 | auto printNumbersNode = printNode->addLeafNode(QStringLiteral("sum"), QStringLiteral("print the sum of numbers")); 43 | printNumbersNode->addPositionalArgument(QStringLiteral("numbers"), 44 | QStringLiteral("The numbers to be summed up"), 45 | QStringLiteral("number...")); 46 | 47 | auto messageNode = parser.addContextNode(QStringLiteral("message"), QStringLiteral("print some kind of message")); 48 | messageNode->addOption({ 49 | QStringLiteral("scream"), 50 | QStringLiteral("Scream the message, instead of beeing friendly") 51 | }); 52 | messageNode->addOption({ 53 | {QStringLiteral("f"), QStringLiteral("failure")}, 54 | QStringLiteral("Print the message as failure (to stderr) instead of " 55 | "a normal message (stdout)") 56 | }); 57 | 58 | auto messageEchoNode = messageNode->addLeafNode(QStringLiteral("echo"), QStringLiteral("Echo additional arguments")); 59 | messageEchoNode->addPositionalArgument(QStringLiteral("message"), 60 | QStringLiteral("The message to be printed (can contain spaces)"), 61 | QStringLiteral("[message]")); 62 | 63 | messageNode->addLeafNode(QStringLiteral("random"), QStringLiteral("Print some random stuff")); 64 | messageNode->addLeafNode(QStringLiteral("help"), QStringLiteral("print the full help information, not just the selective one")); 65 | messageNode->setDefaultNode(QStringLiteral("random")); 66 | 67 | parser.process(a); 68 | 69 | qInfo() << "Context:" << parser.contextChain(); 70 | qInfo() << "Options:" << parser.optionNames(); 71 | qInfo() << "Arguments:" << parser.positionalArguments(); 72 | return parser.value(QStringLiteral("e")).toInt(); 73 | } 74 | -------------------------------------------------------------------------------- /qcliparser.cpp: -------------------------------------------------------------------------------- 1 | #include "qcliparser.h" 2 | #include 3 | #if defined(Q_OS_WIN) && !defined(QT_BOOTSTRAPPED) && !defined(Q_OS_WINRT) 4 | # include 5 | #endif 6 | 7 | //base on QCommandLineParser code: 8 | 9 | extern void Q_CORE_EXPORT qt_call_post_routines(); 10 | 11 | namespace { 12 | 13 | #if defined(Q_OS_WIN) && !defined(QT_BOOTSTRAPPED) && !defined(Q_OS_WINRT) 14 | // Return whether to use a message box. Use handles if a console can be obtained 15 | // or we are run with redirected handles (for example, by QProcess). 16 | inline bool displayMessageBox() 17 | { 18 | if (GetConsoleWindow()) 19 | return false; 20 | STARTUPINFO startupInfo; 21 | startupInfo.cb = sizeof(STARTUPINFO); 22 | GetStartupInfo(&startupInfo); 23 | return !(startupInfo.dwFlags & STARTF_USESTDHANDLES); 24 | } 25 | #endif // Q_OS_WIN && !QT_BOOTSTRAPPED && !Q_OS_WIN && !Q_OS_WINRT 26 | 27 | void showParserMessage(const QString &message) 28 | { 29 | #if defined(Q_OS_WINRT) 30 | qCritical(qPrintable(message)); 31 | return; 32 | #elif defined(Q_OS_WIN) && !defined(QT_BOOTSTRAPPED) 33 | if (displayMessageBox()) { 34 | const UINT flags = MB_OK | MB_TOPMOST | MB_SETFOREGROUND | MB_ICONERROR; 35 | QString title; 36 | if (QCoreApplication::instance()) 37 | title = QCoreApplication::instance()->property("applicationDisplayName").toString(); 38 | if (title.isEmpty()) 39 | title = QCoreApplication::applicationName(); 40 | MessageBoxW(0, reinterpret_cast(message.utf16()), 41 | reinterpret_cast(title.utf16()), flags); 42 | return; 43 | } 44 | #endif // Q_OS_WIN && !QT_BOOTSTRAPPED 45 | fputs(qPrintable(message), stderr); 46 | } 47 | 48 | } 49 | 50 | 51 | 52 | QCliParser::QCliParser() : 53 | QCommandLineParser(), 54 | QCliContext(), 55 | _contextChain(), 56 | _errorText(), 57 | _readContextIndex(-1) 58 | {} 59 | 60 | void QCliParser::process(const QStringList &arguments, bool colored) 61 | { 62 | if(parse(arguments)) { 63 | if(QCommandLineParser::isSet(QStringLiteral("help"))) 64 | showHelp(EXIT_SUCCESS); 65 | if(QCommandLineParser::isSet(QStringLiteral("version"))) 66 | showVersion(); 67 | } else { 68 | #ifdef Q_OS_WIN 69 | Q_UNUSED(colored) 70 | #else 71 | if(colored) 72 | showParserMessage(QStringLiteral("\033[31m") + errorText() + QStringLiteral("\033[0m\n")); 73 | else 74 | #endif 75 | showParserMessage(errorText() + QLatin1Char('\n')); 76 | qt_call_post_routines(); 77 | ::exit(EXIT_FAILURE); 78 | } 79 | } 80 | 81 | void QCliParser::process(const QCoreApplication &app, bool colored) 82 | { 83 | Q_UNUSED(app) 84 | process(QCoreApplication::arguments(), colored); 85 | } 86 | 87 | bool QCliParser::parse(const QStringList &arguments) 88 | { 89 | #ifndef QT_NO_DEBUG 90 | if(_readContextIndex != -1) { 91 | qWarning() << "Parser is currently in context scope. " 92 | "Parsing different arguments then before can lead to undefined behaviour!"; 93 | } 94 | #endif 95 | _errorText.clear(); 96 | try { 97 | parseContext(this, arguments); 98 | return true; 99 | } catch (QString &string) { 100 | if(_contextChain.isEmpty()) 101 | _errorText = string; 102 | else { 103 | _errorText = tr("%1\nCommand-Context: %2") 104 | .arg(string, _contextChain.join(QStringLiteral(" -> "))); 105 | } 106 | return false; 107 | } 108 | } 109 | 110 | bool QCliParser::enterContext(const QString &name) 111 | { 112 | auto nIndex = _readContextIndex + 1; 113 | if(nIndex < 0 || 114 | nIndex >= _contextChain.size()) 115 | return false; 116 | 117 | if(_contextChain[nIndex] == name) { 118 | _readContextIndex++; 119 | return true; 120 | } else 121 | return false; 122 | } 123 | 124 | QString QCliParser::currentContext() const 125 | { 126 | if(_readContextIndex < 0 || 127 | _readContextIndex >= _contextChain.size()) 128 | return {}; 129 | else 130 | return _contextChain[_readContextIndex]; 131 | } 132 | 133 | bool QCliParser::leaveContext() 134 | { 135 | if(_readContextIndex >= 0) { 136 | _readContextIndex--; 137 | return true; 138 | } else 139 | return false; 140 | } 141 | 142 | QStringList QCliParser::contextChain() const 143 | { 144 | return _contextChain; 145 | } 146 | 147 | QString QCliParser::errorText() const 148 | { 149 | return _errorText; 150 | } 151 | 152 | void QCliParser::showParserMessage(const QString &message) 153 | { 154 | ::showParserMessage(message); 155 | } 156 | 157 | void QCliParser::addPositionalArgument(const QString &name, const QString &description, const QString &syntax) 158 | { 159 | Q_UNREACHABLE(); 160 | Q_UNUSED(name) 161 | Q_UNUSED(description) 162 | Q_UNUSED(syntax) 163 | } 164 | 165 | void QCliParser::clearPositionalArguments() 166 | { 167 | Q_UNREACHABLE(); 168 | } 169 | 170 | void QCliParser::parseContext(QCliContext *context, QStringList arguments) 171 | { 172 | Q_ASSERT_X(!context->_nodes.isEmpty(), 173 | Q_FUNC_INFO, 174 | qPrintable(QStringLiteral("A QCliContext must have at least 1 node. At chain: %1") 175 | .arg(_contextChain.join(QStringLiteral("->"))))); 176 | 177 | // reset args + add options 178 | QCommandLineParser::clearPositionalArguments(); 179 | QCommandLineParser::addOptions(context->_options); 180 | 181 | //create positional args 182 | auto commands = context->_nodes.keys(); 183 | QStringList printArgs; 184 | for(const auto &command : commands) { 185 | if(!context->_nodes.value(command).second->isHidden()) 186 | printArgs.append(command); 187 | } 188 | auto firstName = printArgs.first(); 189 | auto pFirstName = firstName; 190 | if(firstName == context->_defaultNode) 191 | pFirstName = tr("%1 (default)").arg(firstName); 192 | QCommandLineParser::addPositionalArgument(pFirstName, 193 | context->_nodes.value(firstName).first, 194 | (context->_defaultNode.isNull() ? QStringLiteral("%1%2%3") : QStringLiteral("%1%2[%3]")) 195 | .arg(_contextChain.join(QLatin1Char(' ')), 196 | _contextChain.isEmpty() ? QString() : QStringLiteral(" "), 197 | printArgs.join(QLatin1Char('|')))); 198 | for(auto i = 1; i < printArgs.size(); i++) { 199 | auto name = printArgs[i]; 200 | auto pName = name; 201 | if(name == context->_defaultNode) 202 | pName = tr("%1 (default)").arg(name); 203 | QCommandLineParser::addPositionalArgument(pName, context->_nodes.value(name).first, QStringLiteral(" \b")); 204 | } 205 | 206 | // parse . if no errors and version -> done 207 | if(QCommandLineParser::parse(arguments) && 208 | QCommandLineParser::isSet(QStringLiteral("version"))) 209 | return; 210 | //ignore errors, only treated on leafs 211 | 212 | //determine the selected command 213 | auto pArgs = QCommandLineParser::positionalArguments(); 214 | auto cIndex = -1; 215 | if(!pArgs.isEmpty()) { 216 | cIndex = commands.indexOf(pArgs.first()); 217 | if(cIndex == -1) 218 | throw tr("Unknown command \"%1\"").arg(pArgs.first()); 219 | } else { 220 | if(QCommandLineParser::isSet(QStringLiteral("help"))) 221 | return; 222 | cIndex = commands.indexOf(context->_defaultNode); 223 | if(cIndex == -1) 224 | throw tr("A command must be specified"); 225 | } 226 | 227 | // get the next node and it's type 228 | auto nextContext = commands[cIndex]; 229 | _contextChain.append(nextContext); 230 | arguments.removeOne(nextContext); //remove the command from the args list, as it is already processed 231 | auto nextNode = context->_nodes.value(nextContext).second; 232 | 233 | if(auto contextNode = nextNode.dynamicCast()) 234 | parseContext(contextNode.data(), arguments); 235 | else if(auto leafNode = nextNode.dynamicCast()) 236 | parseLeaf(leafNode.data(), arguments); 237 | else 238 | throw tr("Unknown QCliNode type. Must be QCliContext or QCliLeaf"); 239 | } 240 | 241 | void QCliParser::parseLeaf(QCliLeaf *leaf, const QStringList &arguments) 242 | { 243 | // reset args + add options 244 | QCommandLineParser::clearPositionalArguments(); 245 | QCommandLineParser::addOptions(leaf->_options); 246 | 247 | if(leaf->_arguments.isEmpty()) 248 | QCommandLineParser::addPositionalArgument(QStringLiteral(" "), QStringLiteral(" "), _contextChain.join(QLatin1Char(' '))); 249 | else { 250 | auto first = leaf->_arguments.first(); 251 | QCommandLineParser::addPositionalArgument(std::get<0>(first), 252 | std::get<1>(first), 253 | QStringLiteral("%1 %2") 254 | .arg(_contextChain.join(QLatin1Char(' ')), std::get<2>(first))); 255 | for(auto i = 1; i < leaf->_arguments.size(); i++) { 256 | const auto &pArg = leaf->_arguments[i]; 257 | QCommandLineParser::addPositionalArgument(std::get<0>(pArg), std::get<1>(pArg), std::get<2>(pArg)); 258 | } 259 | } 260 | 261 | //parse completly now, must be valid! 262 | if(!QCommandLineParser::parse(arguments)) 263 | throw QCommandLineParser::errorText(); 264 | } 265 | -------------------------------------------------------------------------------- /qclievaluator.cpp: -------------------------------------------------------------------------------- 1 | #include "qclievaluator.h" 2 | #include 3 | #include 4 | #include 5 | 6 | namespace { 7 | 8 | #ifdef QT_NO_DEBUG 9 | constexpr auto LogScope = QtFatalMsg; 10 | #else 11 | constexpr auto LogScope = QtDebugMsg; 12 | #endif 13 | 14 | Q_LOGGING_CATEGORY(cliEval, "QCliEvaluator", LogScope) 15 | 16 | auto logFilterEnabled = false; 17 | QLoggingCategory::CategoryFilter oldCategoryFilter = nullptr; 18 | QLoggingCategory *dCat = nullptr; 19 | 20 | void cliLogFilter(QLoggingCategory *category) 21 | { 22 | oldCategoryFilter(category); 23 | if (qstrcmp(category->categoryName(), "default") == 0) { 24 | dCat = category; 25 | category->setEnabled(QtWarningMsg, !logFilterEnabled); 26 | } 27 | } 28 | 29 | } 30 | 31 | QCliEvaluator::QCliEvaluator(QObject *parent) : 32 | QObject{parent} 33 | { 34 | if (!oldCategoryFilter) 35 | oldCategoryFilter = QLoggingCategory::installFilter(cliLogFilter); 36 | } 37 | 38 | bool QCliEvaluator::doesAutoResolveObjects() const 39 | { 40 | return _autoResolveObjects; 41 | } 42 | 43 | int QCliEvaluator::exec(const QCliParser &parser) 44 | { 45 | return execImpl(parser, parser.contextChain()); 46 | } 47 | 48 | int QCliEvaluator::exec(const QCommandLineParser &parser) 49 | { 50 | return execImpl(parser, {}); 51 | } 52 | 53 | bool QCliEvaluator::registerEvaluator(const QByteArray &className, const QStringList &path) 54 | { 55 | const auto mo = metaObjectForName(className); 56 | if (!mo) 57 | return false; 58 | return registerEvaluator(mo, path); 59 | } 60 | 61 | bool QCliEvaluator::registerEvaluator(const QMetaObject *metaObject, const QStringList &path) 62 | { 63 | if (!metaObject->inherits(&QObject::staticMetaObject)) 64 | return false; 65 | 66 | _evaluators[path] = metaObject; // TODO fails 67 | return true; 68 | } 69 | 70 | void QCliEvaluator::setAutoResolveObjects(bool autoResolveObjects) 71 | { 72 | if (_autoResolveObjects == autoResolveObjects) 73 | return; 74 | 75 | _autoResolveObjects = autoResolveObjects; 76 | emit autoResolveObjectsChanged(_autoResolveObjects); 77 | } 78 | 79 | QByteArray QCliEvaluator::evaluatorClassName(const QStringList &contextList) const 80 | { 81 | return contextList.isEmpty() ? 82 | QByteArrayLiteral("Evaluator*") : 83 | (QByteArrayLiteral("Evaluator_") + contextList.join(QLatin1Char('_')).toUtf8() + '*'); 84 | } 85 | 86 | QByteArray QCliEvaluator::evaluatorMethodName(const QMetaObject *metaObject, const QStringList &contextList) const 87 | { 88 | Q_UNUSED(metaObject) 89 | return contextList.isEmpty() ? 90 | QByteArrayLiteral("exec") : 91 | (QByteArrayLiteral("exec_") + contextList.join(QLatin1Char('_')).toUtf8()); 92 | } 93 | 94 | const QMetaObject *QCliEvaluator::metaObjectForName(const QByteArray &className) 95 | { 96 | const auto typeId = QMetaType::type(className.data()); 97 | if (typeId == QMetaType::UnknownType || 98 | !QMetaType::typeFlags(typeId).testFlag(QMetaType::PointerToQObject)) 99 | return nullptr; 100 | return QMetaType::metaObjectForType(typeId); 101 | } 102 | 103 | int QCliEvaluator::execImpl(const QCommandLineParser &parser, const QStringList &contextList) 104 | { 105 | // find the evaluator node chain 106 | auto pNode = _evaluators[contextList]; 107 | do { 108 | const auto depth = pNode.depth(); 109 | // first: check if explicit evaluator was set 110 | if (pNode.hasValue()) { 111 | const auto res = tryExec(*pNode, parser, contextList.mid(depth)); 112 | if (res) 113 | return res.value(); 114 | } 115 | // second: if allowed, try to auto-resolve 116 | if(_autoResolveObjects) { 117 | const auto evaluatorName = evaluatorClassName(contextList.mid(0, depth)); 118 | const auto metaObj = metaObjectForName(evaluatorName); 119 | if (metaObj) { 120 | const auto res = tryExec(metaObj, parser, contextList.mid(depth)); 121 | if (res) 122 | return res.value(); 123 | } 124 | } 125 | } while((pNode = pNode.parent())); 126 | // no evaluator found... 127 | qCCritical(cliEval) << "Unable to find any evaluators capable of executing" << contextList; 128 | return EXIT_FAILURE; 129 | } 130 | 131 | std::optional QCliEvaluator::tryExec(const QMetaObject *metaObject, const QCommandLineParser &parser, const QStringList &contextList) 132 | { 133 | // find a method that matches the generated name and parameters 134 | const auto methodName = evaluatorMethodName(metaObject, contextList); 135 | const auto &pArgs = parser.positionalArguments(); 136 | const auto argSize = pArgs.size(); 137 | auto pCountMinTotal = std::numeric_limits::max(); 138 | auto pCountMaxTotal = 0; 139 | for (auto mIdx = 0; mIdx < metaObject->methodCount(); ++mIdx) { 140 | const auto method = metaObject->method(mIdx); 141 | // skip non matching methods 142 | if ((method.methodType() != QMetaMethod::Method && 143 | method.methodType() != QMetaMethod::Slot) || // slots are allowed, but still must have the int return type 144 | method.access() != QMetaMethod::Public || 145 | method.returnType() != QMetaType::Int || 146 | method.name() != methodName) 147 | continue; 148 | 149 | // find parameter count with any Args 150 | auto pCount = method.parameterCount(); 151 | auto anyArgs = false; 152 | if (pCount > 0) { 153 | switch (method.parameterType(pCount - 1)) { 154 | case QMetaType::QStringList: 155 | case QMetaType::QByteArrayList: 156 | case QMetaType::QVariantList: 157 | anyArgs = true; 158 | --pCount; 159 | break; 160 | default: 161 | break; 162 | } 163 | } 164 | pCountMinTotal = std::min(pCountMinTotal, pCount); 165 | pCountMaxTotal = std::max(pCountMaxTotal, anyArgs ? std::numeric_limits::max() : pCount); 166 | 167 | // check if pos args can be passed to this method 168 | if (argSize < pCount || 169 | (!anyArgs && argSize > pCount)) 170 | continue; 171 | 172 | // if acceptable -> convert params into variant list 173 | QVariantList varList; 174 | varList.reserve(anyArgs ? pCount + 1 : pCount); 175 | for (auto aIdx = 0; aIdx < pCount; ++aIdx) 176 | varList.append(pArgs[aIdx]); 177 | if (anyArgs && argSize > pCount) { 178 | QVariant listArg; 179 | switch (method.parameterType(pCount)) { 180 | case QMetaType::QVariantList: 181 | case QMetaType::QStringList: 182 | listArg = QVariant::fromValue(pArgs.mid(pCount)); 183 | break; 184 | case QMetaType::QByteArrayList: { 185 | QByteArrayList baList; 186 | baList.reserve(argSize - pCount); 187 | for (const auto &arg : pArgs.mid(pCount)) 188 | baList.append(arg.toUtf8()); 189 | listArg = QVariant::fromValue(baList); 190 | break; 191 | } 192 | default: 193 | Q_UNREACHABLE(); 194 | } 195 | varList.append(listArg); 196 | } 197 | 198 | // create the object and call the method 199 | QScopedPointer instance {metaObject->newInstance(Q_ARG(QObject*, this))}; 200 | if (!instance) { 201 | qCCritical(cliEval) << "Failed to create instance of class" << metaObject->className() 202 | << "- make shure the constructor has the following signature: " 203 | "Q_INVOKABLE constructor(QObject*);"; 204 | return EXIT_FAILURE; 205 | } 206 | // set options 207 | setOptionProperties(instance.data(), parser); 208 | // call method with positional args 209 | return callMetaMethod(instance.data(), method, varList); 210 | } 211 | 212 | // no method found that matches the given name 213 | if (pCountMinTotal == std::numeric_limits::max()) 214 | return std::nullopt; 215 | else { // method was found, but arguments do not match 216 | auto message = tr("Expected "); 217 | if (pCountMinTotal > 0) 218 | message += tr("at least %L1 ").arg(pCountMinTotal); 219 | if (pCountMaxTotal != std::numeric_limits::max()) { 220 | if (pCountMinTotal > 0) 221 | message += tr("and "); 222 | message += tr("at most %L1 ").arg(pCountMaxTotal); 223 | } 224 | message += tr("arguments, but %L1 have been passed!\n").arg(argSize); 225 | QCliParser::showParserMessage(message); 226 | return EXIT_FAILURE; 227 | } 228 | } 229 | 230 | void QCliEvaluator::setOptionProperties(QObject *instance, const QCommandLineParser &parser) const 231 | { 232 | const auto metaObject = instance->metaObject(); 233 | for (auto i = 1; i < metaObject->propertyCount(); ++i) { 234 | const auto property = metaObject->property(i); 235 | if (!property.isWritable()) 236 | continue; 237 | // is set -> update property 238 | const auto pName = QString::fromUtf8(property.name()).replace(QLatin1Char('_'), QLatin1Char('-')); 239 | 240 | auto isSet = false; 241 | { 242 | LogBlocker blocker; 243 | isSet = parser.isSet(pName); 244 | } 245 | if(isSet) { 246 | switch (property.userType()) { 247 | case QMetaType::Bool: 248 | property.write(instance, true); 249 | break; 250 | case QMetaType::QStringList: 251 | case QMetaType::QVariantList: 252 | property.write(instance, parser.values(pName)); 253 | break; 254 | case QMetaType::QByteArrayList: { 255 | const auto pValues = parser.values(pName); 256 | QByteArrayList baList; 257 | baList.reserve(pValues.size()); 258 | for (const auto &arg : pValues) 259 | baList.append(arg.toUtf8()); 260 | property.write(instance, QVariant::fromValue(baList)); 261 | break; 262 | } 263 | default: 264 | property.write(instance, parser.value(pName)); 265 | break; 266 | } 267 | } 268 | } 269 | } 270 | 271 | int QCliEvaluator::callMetaMethod(QObject *instance, const QMetaMethod &method, QVariantList arguments) const 272 | { 273 | Q_ASSERT_X(arguments.size() <= 10, Q_FUNC_INFO, "Trying to invoke methode with more then 10 arguments!"); 274 | 275 | // prepare arguments 276 | std::array args; 277 | for (auto i = 0; i < arguments.size(); ++i) { 278 | auto &val = arguments[i]; 279 | if (!val.convert(method.parameterType(i))) { 280 | QCliParser::showParserMessage(tr("Invalid positional argument at position %L1 " 281 | "- unable to convert input to %2\n") 282 | .arg(i) 283 | .arg(QString::fromUtf8(QMetaType::typeName(method.parameterType(i))))); 284 | return EXIT_FAILURE; 285 | } 286 | args[static_cast(i)] = QGenericArgument{val.typeName(), val.constData()}; 287 | } 288 | 289 | int retVal = EXIT_FAILURE; 290 | if (method.invoke(instance, Qt::DirectConnection, 291 | Q_RETURN_ARG(int, retVal), 292 | args[0], args[1], args[2], args[3], args[4], 293 | args[5], args[6], args[7], args[8], args[9])) 294 | return retVal; 295 | else { 296 | qCCritical(cliEval) << "Failed to call method" << method.methodSignature() 297 | << "on instance" << instance; 298 | return EXIT_FAILURE; 299 | } 300 | } 301 | 302 | 303 | 304 | QCliEvaluator::LogBlocker::LogBlocker() 305 | { 306 | logFilterEnabled = true; 307 | if (dCat) 308 | dCat->setEnabled(QtWarningMsg, false); 309 | } 310 | 311 | QCliEvaluator::LogBlocker::~LogBlocker() 312 | { 313 | logFilterEnabled = false; 314 | if (dCat) 315 | dCat->setEnabled(QtWarningMsg, true); 316 | } 317 | --------------------------------------------------------------------------------