diff options
author | Tomaz Canabrava <tcanabrava@kde.org> | 2020-01-18 19:06:07 +0000 |
---|---|---|
committer | Tomaz Canabrava <tcanabrava@kde.org> | 2020-01-22 14:17:56 +0000 |
commit | 95aee1294e32aca966dd306667386460cd12182d (patch) | |
tree | 8ef6afcf7f8ca76823527182b60ff48e5ee5b1fc /src/kconfig_compiler | |
parent | 68c1cfd0d8dcee761005a9eb08a57c224197fce2 (diff) | |
download | kconfig-95aee1294e32aca966dd306667386460cd12182d.tar.gz kconfig-95aee1294e32aca966dd306667386460cd12182d.tar.bz2 |
Refactor KConfigXT
Summary:
The current KConfigXT compiler is in a sad state:
It's a massive file with loads of global variables that handle state, the generator is done within the main() function and it seems to have grown organically. There are no classes to separate logic / state / generation, what exists is code that generates code from a xml / ini pair, but it's hard to even discover what a bit of code is doing. The code istyle is C++ / Java from the nineties, which is not bad per see but it also uses quite a few things that are going to be deprecated in Qt 6 so I'm also taking the time make the code more streamlined with newer code style (no iterators, lambdas, auto usage, etc).
The code that generates the files simplly pushes strings to a text stream, and it's hard to figure out when something starts or something ends: for instance, the code that generates the Constructor has more than sixty lines of code englobing some nested if - for - if - for constructs.
Currently the code is "done" - there's one bug that I still need to find & fix regarding Translations, but the rest seems sane.
The current testcode generates incorrect *whitespaces* regarding the old code (there's some parts that I feel that it's important to fix before merging, but overall, the whitespace changes are not bad and easier to handle, old code had a hand-counted amount of spaces before each line, new code has a function whitespace() that adds the current-and-correct amount of whitespaces based on indentation level that you start by startScope() and ends with endScope(). rest of the code still needs to be ported to it.
I plan to fix the testcases whitespace by manually adding them, I'v fougth with the code for a while and added a few hacks there but I don't want to make the code hackish again.
New code is not perfect by any means, but is a good step in the right direction.
This code tries to Separate the compiler code into many different files / classes to be more obvious what's happening, and each class also has many helper methods to minimize copypaste.
- CodeGenerator: Has base code for the header and source files that can be shared
- HeaderGenerator: Logic for generating the header file
- SourceGenerator: Logic for generating the source file
- KcfgParser: Logic for parsing the kcfg file and extracting the information from the Xml file
- CommonStructs: a header that contains the structs that are currently used everywhere.
- KConfigParameters: (was CfgConfig - ConfigConfig, wat) - Has information passed via the kcfgc file
- kcfg_compiler - will be renamed to main - start the other classes and generates the files.
This code here currently has the begining of this separation, with the CodeGenerator and the HeaderGenerator in a ~good~ state, but unfinished.
Test Plan:
- Run the test cases,
- Compare the diffs generated by the testcases and fix in the code the errors / differences
- Run and compare real kde source with the new and old generators to look for errors
Reviewers: #frameworks, ervin, bport, dfaure
Reviewed By: dfaure
Subscribers: davidre, bcooksley, cgiboudeaux, kossebau, bport, ngraham, kde-frameworks-devel
Tags: #frameworks
Differential Revision: https://phabricator.kde.org/D26202
Diffstat (limited to 'src/kconfig_compiler')
-rw-r--r-- | src/kconfig_compiler/CMakeLists.txt | 9 | ||||
-rw-r--r-- | src/kconfig_compiler/KConfigCodeGeneratorBase.cpp | 277 | ||||
-rw-r--r-- | src/kconfig_compiler/KConfigCodeGeneratorBase.h | 134 | ||||
-rw-r--r-- | src/kconfig_compiler/KConfigCommonStructs.h | 195 | ||||
-rw-r--r-- | src/kconfig_compiler/KConfigHeaderGenerator.cpp | 614 | ||||
-rw-r--r-- | src/kconfig_compiler/KConfigHeaderGenerator.h | 78 | ||||
-rw-r--r-- | src/kconfig_compiler/KConfigParameters.cpp | 100 | ||||
-rw-r--r-- | src/kconfig_compiler/KConfigParameters.h | 81 | ||||
-rw-r--r-- | src/kconfig_compiler/KConfigSourceGenerator.cpp | 657 | ||||
-rw-r--r-- | src/kconfig_compiler/KConfigSourceGenerator.h | 86 | ||||
-rw-r--r-- | src/kconfig_compiler/KConfigXmlParser.cpp | 564 | ||||
-rw-r--r-- | src/kconfig_compiler/KConfigXmlParser.h | 83 | ||||
-rw-r--r-- | src/kconfig_compiler/kconfig_compiler.cpp | 2221 |
13 files changed, 3010 insertions, 2089 deletions
diff --git a/src/kconfig_compiler/CMakeLists.txt b/src/kconfig_compiler/CMakeLists.txt index dc0a08db..2bd41cec 100644 --- a/src/kconfig_compiler/CMakeLists.txt +++ b/src/kconfig_compiler/CMakeLists.txt @@ -1,6 +1,13 @@ -set(kconfig_compiler_SRCS kconfig_compiler.cpp) +set(kconfig_compiler_SRCS + KConfigParameters.cpp + KConfigCodeGeneratorBase.cpp + KConfigHeaderGenerator.cpp + KConfigSourceGenerator.cpp + KConfigXmlParser.cpp + kconfig_compiler.cpp +) add_executable(kconfig_compiler ${kconfig_compiler_SRCS}) diff --git a/src/kconfig_compiler/KConfigCodeGeneratorBase.cpp b/src/kconfig_compiler/KConfigCodeGeneratorBase.cpp new file mode 100644 index 00000000..574aeff4 --- /dev/null +++ b/src/kconfig_compiler/KConfigCodeGeneratorBase.cpp @@ -0,0 +1,277 @@ +/* m_this file is part of the KDE libraries + Copyright (C) 2020 Tomaz Cananbrava (tcanabrava@kde.org) + Copyright (c) 2003 Cornelius Schumacher <schumacher@kde.org> + Copyright (c) 2003 Waldo Bastian <bastian@kde.org> + Copyright (c) 2003 Zack Rusin <zack@kde.org> + Copyright (c) 2006 Michaël Larouche <michael.larouche@kdemail.net> + Copyright (c) 2008 Allen Winter <winter@kde.org> + Copyright (C) 2020 Tomaz Cananbrava (tcanabrava@kde.org) + + m_this library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + m_this library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "KConfigCodeGeneratorBase.h" +#include "KConfigParameters.h" +#include "KConfigCommonStructs.h" + +#include <QTextStream> +#include <QLatin1Char> +#include <QFileInfo> + +#include <ostream> +#include <QDebug> + +using std::endl; + +namespace +{ +QTextStream cout(stdout); +QTextStream cerr(stderr); +} + +KConfigCodeGeneratorBase::KConfigCodeGeneratorBase( + const QString &inputFile, + const QString &baseDir, + const QString &fileName, + const KConfigParameters ¶meters, + ParseResult &parseResult) + : m_inputFile(inputFile), m_baseDir(baseDir), m_fileName(fileName), m_cfg(parameters), parseResult(parseResult) +{ + m_file.setFileName(m_fileName); + if (!m_file.open(QIODevice::WriteOnly)) { + cerr << "Can not open '" << m_fileName << "for writing." << endl; + exit(1); + } + m_stream.setDevice(&m_file); + m_stream.setCodec("utf-8"); + + if (m_cfg.staticAccessors) { + m_this = QStringLiteral("self()->"); + } else { + m_const = QStringLiteral(" const"); + } +} + +KConfigCodeGeneratorBase::~KConfigCodeGeneratorBase() +{ + save(); +} + +void KConfigCodeGeneratorBase::save() +{ + m_file.close(); +} + +//TODO: Remove this weird logic and adapt the testcases +void KConfigCodeGeneratorBase::indent() +{ + if (m_indentLevel >= 4) { + m_indentLevel += 2; + } else { + m_indentLevel += 4; + } +} + +void KConfigCodeGeneratorBase::unindent() +{ + if (m_indentLevel > 4) { + m_indentLevel -= 2; + } else { + m_indentLevel -= 4; + } +} + +QString KConfigCodeGeneratorBase::whitespace() const +{ + QString spaces; + for (int i = 0; i < m_indentLevel; i++) { + spaces.append(QLatin1Char(' ')); + } + return spaces; +} + +void KConfigCodeGeneratorBase::startScope() +{ + m_stream << whitespace() << QLatin1Char('{'); + m_stream << endl; + indent(); +} + +void KConfigCodeGeneratorBase::endScope(ScopeFinalizer finalizer) +{ + unindent(); + m_stream << whitespace() << QLatin1Char('}'); + if (finalizer == ScopeFinalizer::Semicolon) { + m_stream << ';'; + } + m_stream << endl; +} + +void KConfigCodeGeneratorBase::start() +{ + const QString m_fileName = QFileInfo(m_inputFile).fileName(); + m_stream << "// This file is generated by kconfig_compiler_kf5 from " << m_fileName << ".kcfg" << "." << endl; + m_stream << "// All changes you do to this file will be lost." << endl; +} + +void KConfigCodeGeneratorBase::addHeaders(const QStringList &headerList) +{ + for (auto include : qAsConst(headerList)) { + if (include.startsWith(QLatin1Char('"'))) { + m_stream << "#include " << include << endl; + } else { + m_stream << "#include <" << include << ">" << endl; + } + } +} + +// adds as many 'namespace foo {' lines to p_out as +// there are namespaces in p_ns +void KConfigCodeGeneratorBase::beginNamespaces() +{ + if (!m_cfg.nameSpace.isEmpty()) { + for (const QString &ns : m_cfg.nameSpace.split(QStringLiteral("::"))) { + m_stream << "namespace " << ns << " {" << endl; + } + m_stream << endl; + } +} + +// adds as many '}' lines to p_out as +// there are namespaces in p_ns +void KConfigCodeGeneratorBase::endNamespaces() +{ + if (!m_cfg.nameSpace.isEmpty()) { + m_stream << endl; + const int namespaceCount = m_cfg.nameSpace.count(QStringLiteral("::")) + 1; + for (int i = 0; i < namespaceCount; ++i) { + m_stream << "}" << endl; + } + } +} + +// returns the member accesor implementation +// which should go in the h file if inline +// or the cpp file if not inline +QString KConfigCodeGeneratorBase::memberAccessorBody(const CfgEntry *e, bool globalEnums) const +{ + QString result; + QTextStream out(&result, QIODevice::WriteOnly); + QString n = e->name; + QString t = e->type; + bool useEnumType = m_cfg.useEnumTypes && t == QLatin1String("Enum"); + + out << "return "; + if (useEnumType) { + out << "static_cast<" << enumType(e, globalEnums) << ">("; + } + out << m_this << varPath(n, m_cfg); + if (!e->param.isEmpty()) { + out << "[i]"; + } + if (useEnumType) { + out << ")"; + } + out << ";" << endl; + + return result; +} + +void KConfigCodeGeneratorBase::createIfSetLogic(const CfgEntry *e, const QString &varExpression) +{ + const QString n = e->name; + const QString t = e->type; + const bool hasBody = !e->signalList.empty() || m_cfg.generateProperties; + + m_stream << whitespace() << "if ("; + if (hasBody) { + m_stream << "v != " << varExpression << " && "; + } + m_stream << "!" << m_this << "isImmutable( QStringLiteral( \""; + if (!e->param.isEmpty()) { + QString paramName = e->paramName; + + m_stream << paramName.replace(QStringLiteral("$(") + e->param + QStringLiteral(")"), QLatin1String("%1")) << "\" ).arg( "; + if (e->paramType == QLatin1String("Enum")) { + m_stream << "QLatin1String( "; + + if (m_cfg.globalEnums) { + m_stream << enumName(e->param) << "ToString[i]"; + } else { + m_stream << enumName(e->param) << "::enumToString[i]"; + } + + m_stream << " )"; + } else { + m_stream << "i"; + } + m_stream << " )"; + } else { + m_stream << n << "\" )"; + } + m_stream << " ))"; +} + +void KConfigCodeGeneratorBase::memberMutatorBody(const CfgEntry *e) +{ + QString n = e->name; + QString t = e->type; + + // HACK: Don't open '{' manually, use startScope / endScope to automatically handle whitespace indentation. + if (!e->min.isEmpty()) { + if (e->min != QLatin1String("0") || !isUnsigned(t)) { // skip writing "if uint<0" (#187579) + m_stream << whitespace() << "if (v < " << e->min << ")" << endl; + m_stream << whitespace() << "{" << endl; + m_stream << whitespace(); addDebugMethod(m_stream, m_cfg, n); + m_stream << ": value \" << v << \" is less than the minimum value of " << e->min << "\";" << endl; + m_stream << whitespace() << " v = " << e->min << ";" << endl; + m_stream << whitespace() << "}" << endl; + } + } + + if (!e->max.isEmpty()) { + m_stream << endl; + m_stream << whitespace() << "if (v > " << e->max << ")" << endl; + m_stream << whitespace() << "{" << endl; + m_stream << whitespace(); addDebugMethod(m_stream, m_cfg, n); + m_stream << ": value \" << v << \" is greater than the maximum value of " << e->max << "\";" << endl; + m_stream << whitespace() << " v = " << e->max << ";" << endl; + m_stream << whitespace() << "}" << endl << endl; + } + + const QString varExpression = m_this + varPath(n, m_cfg) + (e->param.isEmpty() ? QString() : QStringLiteral("[i]")); + + // TODO: Remove this `hasBody` logic, always use an '{' for the if. + const bool hasBody = !e->signalList.empty() || m_cfg.generateProperties; + + // m_this call creates an `if (someTest ...) that's just to long to throw over the code. + createIfSetLogic(e, varExpression); + m_stream << (hasBody ? " {" : "") << endl; + m_stream << whitespace() << " " << varExpression << " = v;" << endl; + + const auto listSignal = e->signalList; + for (const Signal &signal : qAsConst(listSignal)) { + if (signal.modify) { + m_stream << whitespace() << " Q_EMIT " << m_this << signal.name << "();" << endl; + } else { + m_stream << whitespace() << " " << m_this << varPath(QStringLiteral("settingsChanged"), m_cfg) + << " |= " << signalEnumName(signal.name) << ";" << endl; + } + } + if (hasBody) { + m_stream << whitespace() << "}" << endl; + } +} diff --git a/src/kconfig_compiler/KConfigCodeGeneratorBase.h b/src/kconfig_compiler/KConfigCodeGeneratorBase.h new file mode 100644 index 00000000..5bbc6b71 --- /dev/null +++ b/src/kconfig_compiler/KConfigCodeGeneratorBase.h @@ -0,0 +1,134 @@ +/* + This file is part of KDE. + + Copyright (C) 2020 Tomaz Cananbrava (tcanabrava@kde.org) + Copyright (c) 2003 Cornelius Schumacher <schumacher@kde.org> + Copyright (c) 2003 Waldo Bastian <bastian@kde.org> + Copyright (c) 2003 Zack Rusin <zack@kde.org> + Copyright (c) 2006 Michaël Larouche <michael.larouche@kdemail.net> + Copyright (c) 2008 Allen Winter <winter@kde.org> + Copyright (C) 2020 Tomaz Cananbrava (tcanabrava@kde.org) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KCONFIGCODEGENERATORBASE_H +#define KCONFIGCODEGENERATORBASE_H + +#include <QString> +#include <QTextStream> +#include <QFile> +#include <QVector> + +#include "KConfigParameters.h" +#include "KConfigCommonStructs.h" + +class CfgEntry; +struct ParseResult; + +/* This class manages the base of writing a C - Based code */ +class KConfigCodeGeneratorBase { +public: + enum ScopeFinalizer {None, Semicolon}; + + KConfigCodeGeneratorBase( + const QString &inputFileName, // The kcfg file + const QString &baseDir, // where we should store the generated file + const QString &fileName, // the name of the generated file + const KConfigParameters ¶meters, // parameters passed to the generator + ParseResult &parseResult // The pre processed configuration entries + ); + virtual ~KConfigCodeGeneratorBase(); + + // iterates over the header list adding an #include directive. + void addHeaders(const QStringList &header); + + // Create all the namespace indentation levels based on the parsed result and parameters */ + void beginNamespaces(); + + // Closes all the namespaces adding lines with single '}' + void endNamespaces(); + + // Add the correct amount of whitespace in the code. + QString whitespace() const; + + // start a block scope `{` and increase indentation level. + void endScope(ScopeFinalizer finalizer = None); + + // end a block scope `}` and decrease indentation level. + void startScope(); + + // start writing to the output file + virtual void start(); + + // save the result on the disk + void save(); + + // Code Implementations + // Implements the `Get` methods for the CfgEntry + // TODO: write to the stream directly without returning a QString. + QString memberAccessorBody(const CfgEntry *e, bool globalEnums) const; + + // Implements the `Set` methods for the CfgEntry + void memberMutatorBody(const CfgEntry *e); + + // This is the code that creates the logic for the Setter / Mutator. + // It *just* creates the if test, no body. The reason is that just + // the if test was more than 20 lines of code and hard to understand + // what was happening in a bigger function. + void createIfSetLogic(const CfgEntry *e, const QString &varExpression); + +protected: + /* advance the number of spaces for the indentation level */ + void indent(); + + /* reduce the number of spaces for the indentation level */ + void unindent(); + + QString inputFile() const { return m_inputFile; } + QString fileName() const { return m_fileName; } + QString baseDir() const { return m_baseDir; } + QString This() const { return m_this; } + QString Const() const { return m_const; } + KConfigParameters cfg() const { return m_cfg; } + + // Can't be const. + QTextStream& stream() { return m_stream; } + + // HACK: This needs to be accesible because the HeaderGenerator actually modifies + // it while running. Considering that this is a the result of the xml Parse, and not + // the result of generating code, I consider this to be quite wrong - but moving the + // changes away from the header generator only created more problems and I have to + // investigate more. + ParseResult &parseResult; // the result of the parsed kcfg file + +private: + QString m_inputFile; // the base file name, input file is based on this. + + QString m_baseDir; // Where we are going to save the file + QString m_fileName; // The file name + + KConfigParameters m_cfg; // The parameters passed via the kcfgc file + QTextStream m_stream; // the stream that operates in the file to write data. + QFile m_file; // The file handler. + + // Special access to `this->` and `const` thru the code. + QString m_this; + QString m_const; + int m_indentLevel = 0; +}; + +#endif diff --git a/src/kconfig_compiler/KConfigCommonStructs.h b/src/kconfig_compiler/KConfigCommonStructs.h new file mode 100644 index 00000000..69b855ce --- /dev/null +++ b/src/kconfig_compiler/KConfigCommonStructs.h @@ -0,0 +1,195 @@ +/* This file is part of the KDE libraries + Copyright (C) 2020 Tomaz Cananbrava (tcanabrava@kde.org) + Copyright (c) 2003 Cornelius Schumacher <schumacher@kde.org> + Copyright (c) 2003 Waldo Bastian <bastian@kde.org> + Copyright (c) 2003 Zack Rusin <zack@kde.org> + Copyright (c) 2006 Michaël Larouche <michael.larouche@kdemail.net> + Copyright (c) 2008 Allen Winter <winter@kde.org> + Copyright (C) 2020 Tomaz Cananbrava (tcanabrava@kde.org) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KCONFIGCOMMONSTRUCTS_H +#define KCONFIGCOMMONSTRUCTS_H + +#include <QString> +#include <QVector> +#include <QList> + +#include "KConfigParameters.h" + +struct Param +{ + QString name; + QString type; +}; + +struct Signal +{ + QString name; + QString label; + QList<Param> arguments; + bool modify = false; +}; + +class CfgEntry +{ +public: + struct Choice { + QString name; + QString context; + QString label; + QString toolTip; + QString whatsThis; + }; + class Choices + { + public: + Choices() {} + Choices(const QList<Choice> &d, const QString &n, const QString &p) + : prefix(p), choices(d), mName(n) + { + int i = n.indexOf(QLatin1String("::")); + if (i >= 0) { + mExternalQual = n.left(i + 2); + } + } + QString prefix; + QList<Choice> choices; + const QString &name() const + { + return mName; + } + const QString &externalQualifier() const + { + return mExternalQual; + } + bool external() const + { + return !mExternalQual.isEmpty(); + } + private: + QString mName; + QString mExternalQual; + }; + +public: + QString group; + QString type; + QString key; + QString name; + QString labelContext; + QString label; + QString toolTipContext; + QString toolTip; + QString whatsThisContext; + QString whatsThis; + QString code; + QString defaultValue; + QString param; + QString paramName; + QString paramType; + Choices choices; + QList<Signal> signalList; + QStringList paramValues; + QStringList paramDefaultValues; + int paramMax; + bool hidden; + QString min; + QString max; +}; + +struct ParseResult { + QString cfgFileName; + bool cfgFileNameArg = false; + QList<Param> parameters; + QList<Signal> signalList; + QStringList includes; + QList<CfgEntry *> entries; + bool hasNonModifySignals = false; +}; + +// TODO: Move those methods +QString enumName(const QString &n); +QString enumName(const QString &n, const CfgEntry::Choices &c); +QString param(const QString &t); +QString cppType(const QString &t); +QString itemType(const QString &type); +QString changeSignalName(const QString &n); + +QString enumType(const CfgEntry *e, bool globalEnums); + +QString getDefaultFunction(const QString &n, const QString &className = QString()); +QString setFunction(const QString &n, const QString &className = QString()); +QString getFunction(const QString &n, const QString &className = QString()); + +QString itemAccessorBody(const CfgEntry *e, const KConfigParameters &cfg); +QString signalEnumName(const QString &signalName); + +bool isUnsigned(const QString &type); + +// returns the name of an member variable +// use itemPath to know the full path +// like using d-> in case of dpointer +QString varName(const QString &n, const KConfigParameters &cfg); + +QString varPath(const QString &n, const KConfigParameters &cfg); + +// returns the name of an item variable +// use itemPath to know the full path +// like using d-> in case of dpointer +QString itemVar(const CfgEntry *e, const KConfigParameters &cfg); + +QString itemPath(const CfgEntry *e, const KConfigParameters &cfg); + +QString filenameOnly(const QString &path); + +QString itemDeclaration(const CfgEntry *e, const KConfigParameters &cfg); + +QString translatedString( + const KConfigParameters &cfg, + const QString &string, + const QString &context = QString(), + const QString ¶m = QString(), + const QString ¶mValue = QString()); + +// TODO: Sanitize those functions. +QString newItem( + const CfgEntry* entry, + const QString &key, + const QString& defaultValue, + const KConfigParameters &cfg, + const QString ¶m = QString()); + +QString userTextsFunctions( + const CfgEntry *e, + const KConfigParameters &cfg, + QString itemVarStr = QString(), + const QString &i = QString()); + +QString paramString(const QString &s, const CfgEntry *e, int i); +QString paramString(const QString &group, const QList<Param> ¶meters); + +QString defaultValue(const QString &t); +QString memberGetDefaultBody(const CfgEntry *e); +QString literalString(const QString &s); +QString enumTypeQualifier(const QString &n, const CfgEntry::Choices &c); + +void addQuotes(QString &s); +void addDebugMethod(QTextStream &out, const KConfigParameters &cfg, const QString &n); + +#endif diff --git a/src/kconfig_compiler/KConfigHeaderGenerator.cpp b/src/kconfig_compiler/KConfigHeaderGenerator.cpp new file mode 100644 index 00000000..5fc16109 --- /dev/null +++ b/src/kconfig_compiler/KConfigHeaderGenerator.cpp @@ -0,0 +1,614 @@ +/* This file is part of the KDE libraries + Copyright (C) 2020 Tomaz Cananbrava (tcanabrava@kde.org) + Copyright (c) 2003 Cornelius Schumacher <schumacher@kde.org> + Copyright (c) 2003 Waldo Bastian <bastian@kde.org> + Copyright (c) 2003 Zack Rusin <zack@kde.org> + Copyright (c) 2006 Michaël Larouche <michael.larouche@kdemail.net> + Copyright (c) 2008 Allen Winter <winter@kde.org> + Copyright (C) 2020 Tomaz Cananbrava (tcanabrava@kde.org) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "KConfigHeaderGenerator.h" +#include "KConfigCommonStructs.h" + +#include <QTextStream> +#include <QDebug> + +namespace +{ +QTextStream cout(stdout); +QTextStream cerr(stderr); +} + +KConfigHeaderGenerator::KConfigHeaderGenerator( + const QString &inputFile, + const QString &baseDir, + const KConfigParameters &cfg, + ParseResult &result) + : KConfigCodeGeneratorBase(inputFile, baseDir, baseDir + cfg.baseName + QLatin1Char('.') + cfg.headerExtension, cfg, result) +{ +} + +void KConfigHeaderGenerator::start() +{ + KConfigCodeGeneratorBase::start(); + startHeaderGuards(); + createHeaders(); + + beginNamespaces(); + + createForwardDeclarations(); + + doClassDefinition(); + + endNamespaces(); + endHeaderGuards(); +} + +void KConfigHeaderGenerator::doClassDefinition() +{ + stream() << "class " << cfg().visibility << cfg().className << " : public " << cfg().inherits << endl; + startScope(); + + // Add Q_OBJECT macro if the config need signals. + if (!parseResult.signalList.isEmpty() || cfg().generateProperties) { + stream() << " Q_OBJECT" << endl; + } + stream() << " public:" << endl; + implementEnums(); + createConstructor(); + createDestructor(); + + for (auto *entry : parseResult.entries) { + const QString n = entry->name; + const QString t = entry->type; + + const QString returnType = (cfg().useEnumTypes && t == QLatin1String("Enum")) + ? enumType(entry, cfg().globalEnums) + : cppType(t); + + createSetters(entry); + createProperties(entry, returnType); + createGetters(entry, returnType); + createDefaultValueMember(entry); + createItemAcessors(entry, returnType); + } + + createSignals(); + stream() << " protected:" << endl; + createSingleton(); + + // TODO: Move those to functions too. + if (parseResult.hasNonModifySignals) { + stream() << whitespace() << "bool usrSave() override;" << endl; + } + + // Member variables + if (!cfg().memberVariables.isEmpty() + && cfg().memberVariables != QLatin1String("private") + && cfg().memberVariables != QLatin1String("dpointer")) { + stream() << " " << cfg().memberVariables << ":" << endl; + } + + // Class Parameters + for (const auto ¶meter : parseResult.parameters) { + stream() << whitespace() << "" << cppType(parameter.type) << " mParam" << parameter.name << ";" << endl; + } + + createNonDPointerHelpers(); + createDPointer(); + + if (cfg().customAddons) { + stream() << whitespace() << "// Include custom additions" << endl; + stream() << whitespace() << "#include \"" << cfg().baseName << "_addons." << cfg().headerExtension << '"' << endl; + } + + endScope(ScopeFinalizer::Semicolon); +} + +void KConfigHeaderGenerator::createHeaders() +{ + addHeaders(cfg().headerIncludes); + if (cfg().headerIncludes.size()) { + stream() << endl; + } + + if (!cfg().singleton && parseResult.parameters.isEmpty()) { + addHeaders({QStringLiteral("qglobal.h")}); + } + + if (cfg().inherits == QLatin1String("KCoreConfigSkeleton")) { + addHeaders({QStringLiteral("kcoreconfigskeleton.h")}); + } else { + addHeaders({QStringLiteral("kconfigskeleton.h")}); + } + + addHeaders({QStringLiteral("QCoreApplication"), QStringLiteral("QDebug")}); + stream() << endl; + + addHeaders(parseResult.includes); + if (parseResult.includes.size()) { + stream() << endl; + } +} + +void KConfigHeaderGenerator::startHeaderGuards() +{ + const bool hasNamespace = !cfg().nameSpace.isEmpty(); + const QString namespaceName = QString(QString(cfg().nameSpace).replace(QLatin1String("::"), QLatin1String("_"))).toUpper(); + const QString namespaceStr = hasNamespace ? namespaceName + QLatin1Char('_') : QStringLiteral(""); + const QString defineName = namespaceStr + cfg().className.toUpper() + QStringLiteral("_H"); + + stream() << "#ifndef " << defineName << endl; + stream() << "#define " << defineName << endl; + stream() << endl; +} + +void KConfigHeaderGenerator::endHeaderGuards() +{ + stream() << endl; + stream() << "#endif"; + stream() << endl; + // HACK: Original files ended with two last newlines, add them. + stream() << endl; +} + +void KConfigHeaderGenerator::implementChoiceEnums(const CfgEntry *entry, const CfgEntry::Choices &choices) +{ + const QList<CfgEntry::Choice> chlist = choices.choices; + + if (chlist.isEmpty()) { + return; + } + + QStringList values; + for (const auto choice : qAsConst(chlist)) { + values.append(choices.prefix + choice.name); + } + + if (choices.name().isEmpty()) { + if (cfg().globalEnums) { + stream() << whitespace() << "enum " << enumName(entry->name, entry->choices) << " { " << values.join(QStringLiteral(", ")) << " };" << endl; + } else { + // Create an automatically named enum + stream() << whitespace() << "class " << enumName(entry->name, entry->choices) << endl; + stream() << whitespace() << "{" << endl; + stream() << whitespace() << " public:" << endl; + stream() << whitespace() << " enum type { " << values.join(QStringLiteral(", ")) << ", COUNT };" << endl; + stream() << whitespace() << "};" << endl; + } + } else if (!choices.external()) { + // Create a named enum + stream() << whitespace() << "enum " << enumName(entry->name, entry->choices) << " { " << values.join(QStringLiteral(", ")) << " };" << endl; + } +} + +void KConfigHeaderGenerator::implementValueEnums(const CfgEntry *entry, const QStringList &values) +{ + if (values.isEmpty()) { + return; + } + + if (cfg().globalEnums) { + // ### FIXME!! + // make the following string table an index-based string search! + // ### + stream() << whitespace() << "enum " << enumName(entry->param) << " { " << values.join(QStringLiteral(", ")) << " };" << endl; + stream() << whitespace() << "static const char* const " << enumName(entry->param) << "ToString[];" << endl; + } else { + stream() << whitespace() << "class " << enumName(entry->param) << endl; + stream() << whitespace() << "{" << endl; + stream() << whitespace() << " public:" << endl; + stream() << whitespace() << " enum type { " << values.join(QStringLiteral(", ")) << ", COUNT };" << endl; + stream() << whitespace() << " static const char* const enumToString[];" << endl; + stream() << whitespace() << "};" << endl; + } +} + +void KConfigHeaderGenerator::implementEnums() +{ + if (!parseResult.entries.size()) { + return; + } + + for (const auto entry : parseResult.entries) { + const CfgEntry::Choices &choices = entry->choices; + const QStringList values = entry->paramValues; + + implementChoiceEnums(entry, choices); + implementValueEnums(entry, values); + } + stream() << endl; +} + +void KConfigHeaderGenerator::createSignals() +{ + // Signal definition. + const bool hasSignals = !parseResult.signalList.isEmpty(); + + unsigned val = 1 << parseResult.signalList.size(); + if (!val) { + cerr << "Too many signals to create unique bit masks" << endl; + exit(1); + } + + if (!hasSignals) { + return; + } + + stream() << "\n enum {" << endl; + val = 1; + + // HACK: Use C-Style for add a comma in all but the last element, + // just to make the source generated code equal to the old one. + // When we are sure, revert this to a range-based-for and just add + // a last comma, as it's valid c++. + for (int i = 0, end = parseResult.signalList.size(); i < end; i++) { + auto signal = parseResult.signalList.at(i); + stream() << whitespace() << " " << signalEnumName(signal.name) << " = 0x" << hex << val; + if (i != end-1) { + stream() << "," << endl; + } + + val <<= 1; + } + stream() << endl; + stream() << whitespace() << "};" << dec << endl << endl; + + stream() << " Q_SIGNALS:"; + for (const Signal &signal : parseResult.signalList) { + stream() << endl; + if (!signal.label.isEmpty()) { + stream() << whitespace() << "/**" << endl; + stream() << whitespace() << " " << signal.label << endl; + stream() << whitespace() << "*/" << endl; + } + stream() << whitespace() << "void " << signal.name << "("; + QList<Param>::ConstIterator it, itEnd = signal.arguments.constEnd(); + for (it = signal.arguments.constBegin(); it != itEnd;) { + Param argument = *it; + QString type = param(argument.type); + if (cfg().useEnumTypes && argument.type == QLatin1String("Enum")) { + for (auto *entry : parseResult.entries) { + if (entry->name == argument.name) { + type = enumType(entry, cfg().globalEnums); + break; + } + } + } + stream() << type << " " << argument.name; + if (++it != itEnd) { + stream() << ", "; + } + } + stream() << ");" << endl; + } + stream() << endl; + + stream() << " private:" << endl; + stream() << whitespace() << "void itemChanged(quint64 flags);" << endl; + stream() << endl; +} + +void KConfigHeaderGenerator::createDPointer() +{ + if (!cfg().dpointer) { + return; + } + + // use a private class for both member variables and items + stream() << " private:" << endl; + for (const auto &entry : parseResult.entries) { + if (cfg().allDefaultGetters || cfg().defaultGetters.contains(entry->name)) { + stream() << whitespace() << ""; + if (cfg().staticAccessors) { + stream() << "static "; + } + stream() << cppType(entry->type) << " " << getDefaultFunction(entry->name) << "_helper("; + if (!entry->param.isEmpty()) { + stream() << " " << cppType(entry->paramType) << " i "; + } + stream() << ")" << Const() << ";" << endl; + } + } + stream() << whitespace() << "" << cfg().className << "Private *d;" << endl; +} + +void KConfigHeaderGenerator::createConstructor() +{ + if (cfg().singleton) { + stream() << whitespace() << "static " << cfg().className << " *self();" << endl; + if (parseResult.cfgFileNameArg) { + stream() << whitespace() << "static void instance(const QString& cfgfilename);" << endl; + stream() << whitespace() << "static void instance(KSharedConfig::Ptr config);" << endl; + } + return; + } + + stream() << whitespace() << "" << cfg().className << "("; + if (parseResult.cfgFileNameArg) { + if (cfg().forceStringFilename) + stream() << " const QString &cfgfilename" << (parseResult.parameters.isEmpty() ? " = QString()" : ", "); + else + stream() << " KSharedConfig::Ptr config" << (parseResult.parameters.isEmpty() ? " = KSharedConfig::openConfig()" : ", "); + } + + bool first = true; + for (const auto parameter : parseResult.parameters) { + if (first) { + first = false; + } else { + stream() << ","; + } + + stream() << " " << param(parameter.type) << " " << parameter.name; + } + + if (cfg().parentInConstructor) { + if (parseResult.cfgFileNameArg || !parseResult.parameters.isEmpty()) { + stream() << ","; + } + stream() << " QObject *parent = nullptr"; + } + stream() << " );" << endl; +} + +void KConfigHeaderGenerator::createDestructor() +{ + stream() << whitespace() << "~" << cfg().className << "();" << endl << endl; +} + +void KConfigHeaderGenerator::createForwardDeclarations() +{ + // Private class declaration + if (cfg().dpointer) { + stream() << "class " << cfg().className << "Private;" << endl << endl; + } +} + +void KConfigHeaderGenerator::createProperties(const CfgEntry *entry, const QString& returnType) +{ + if (!cfg().generateProperties) { + return; + } + stream() << whitespace() << "Q_PROPERTY(" << returnType << ' ' << getFunction(entry->name); + stream() << " READ " << getFunction(entry->name); + + if (cfg().allMutators || cfg().mutators.contains(entry->name)) { + const QString signal = changeSignalName(entry->name); + stream() << " WRITE " << setFunction(entry->name); + stream() << " NOTIFY " << signal; + + //If we have the modified signal, we'll also need + //the changed signal as well + Signal s; + s.name = signal; + s.modify = true; + parseResult.signalList.append(s); + } else { + stream() << " CONSTANT"; + } + stream() << ")" << endl; +} + +void KConfigHeaderGenerator::createSetters(const CfgEntry *entry) +{ + // Manipulator + if (!cfg().allMutators && !cfg().mutators.contains(entry->name)) { + return; + } + + stream() << whitespace() << "/**" << endl; + stream() << whitespace() << " Set " << entry->label << endl; + stream() << whitespace() << "*/" << endl; + + if (cfg().staticAccessors) { + stream() << whitespace() << "static" << endl; + } + + stream() << whitespace() << "void " << setFunction(entry->name) << "( "; + if (!entry->param.isEmpty()) { + stream() <<cppType(entry->paramType) << " i, "; + } + + stream() << (cfg().useEnumTypes && entry->type == QLatin1String("Enum") + ? enumType(entry, cfg().globalEnums) + : param(entry->type)); + + stream() << " v )"; + + // function body inline only if not using dpointer + // for BC mode + if (!cfg().dpointer) { + stream() << endl; + startScope(); + memberMutatorBody(entry); + endScope(); + stream() << endl; + } else { + stream() << ";" << endl << endl; + } +} + +void KConfigHeaderGenerator::createGetters(const CfgEntry *entry, const QString &returnType) +{ + // Accessor + stream() << whitespace() << "/**" << endl; + stream() << whitespace() << " Get " << entry->label << endl; + stream() << whitespace() << "*/" << endl; + if (cfg().staticAccessors) { + stream() << whitespace() << "static" << endl; + } + stream() << whitespace() << ""; + stream() << returnType; + stream() << " " << getFunction(entry->name) << "("; + if (!entry->param.isEmpty()) { + stream() << " " << cppType(entry->paramType) << " i "; + } + stream() << ")" << Const(); + + // function body inline only if not using dpointer + // for BC mode + if (!cfg().dpointer) { + stream() << endl; + startScope(); + stream() << whitespace() << memberAccessorBody(entry, cfg().globalEnums); + endScope(); + stream() << endl; + } else { + stream() << ";" << endl << endl; + } +} + +void KConfigHeaderGenerator::createItemAcessors(const CfgEntry *entry, const QString &returnType) +{ + // Item accessor + if (!cfg().itemAccessors) { + return; + } + stream() << whitespace() << "/**" << endl; + stream() << whitespace() << " Get Item object corresponding to " << entry->name << "()" + << endl; + stream() << whitespace() << "*/" << endl; + stream() << whitespace() << "Item" << itemType(entry->type) << " *" + << getFunction(entry->name) << "Item("; + if (!entry->param.isEmpty()) { + stream() << " " << cppType(entry->paramType) << " i "; + } + stream() << ")"; + if (!cfg().dpointer) { + stream() << endl; + startScope(); + stream() << whitespace() << itemAccessorBody(entry, cfg()); + endScope(); + } else { + stream() << ";" << endl; + } + + stream() <<endl; +} + +void KConfigHeaderGenerator::createDefaultValueMember(const CfgEntry *entry) +{ + // Default value Accessor + if (! ((cfg().allDefaultGetters || cfg().defaultGetters.contains(entry->name)) && !entry->defaultValue.isEmpty())) { + return; + } + stream() << whitespace() << "/**" << endl; + stream() << whitespace() << " Get " << entry->label << " default value" << endl; + stream() << whitespace() << "*/" << endl; + if (cfg().staticAccessors) { + stream() << whitespace() << "static" << endl; + } + stream() << whitespace() << ""; + if (cfg().useEnumTypes && entry->type == QLatin1String("Enum")) { + stream() << enumType(entry, cfg().globalEnums); + } else { + stream() << cppType(entry->type); + } + stream() << " " << getDefaultFunction(entry->name) << "("; + if (!entry->param.isEmpty()) { + stream() << " " << cppType(entry->paramType) << " i "; + } + stream() << ")" << Const() << endl; + stream() << whitespace() << "{" << endl; + stream() << whitespace() << " return "; + if (cfg().useEnumTypes && entry->type == QLatin1String("Enum")) { + stream() << "static_cast<" << enumType(entry, cfg().globalEnums) << ">("; + } + stream() << getDefaultFunction(entry->name) << "_helper("; + if (!entry->param.isEmpty()) { + stream() << " i "; + } + stream() << ")"; + if (cfg().useEnumTypes && entry->type == QLatin1String("Enum")) { + stream() << ")"; + } + stream() << ";" << endl; + stream() << whitespace() << "}" << endl; + stream() << endl; +} + +void KConfigHeaderGenerator::createSingleton() +{ + // Private constructor for singleton + if (!cfg().singleton) { + return; + } + + stream() << whitespace() << "" << cfg().className << "("; + if (parseResult.cfgFileNameArg) { + stream() << "KSharedConfig::Ptr config"; + } + if (cfg().parentInConstructor) { + if (parseResult.cfgFileNameArg) { + stream() << ", "; + } + stream() << "QObject *parent = nullptr"; + } + stream() << ");" << endl; + stream() << whitespace() << "friend class " << cfg().className << "Helper;" << endl << endl; +} + +void KConfigHeaderGenerator::createNonDPointerHelpers() +{ + if (cfg().memberVariables == QLatin1String("dpointer")) { + return; + } + + QString group; + for (auto *entry : parseResult.entries) { + if (entry->group != group) { + group = entry->group; + stream() << endl; + stream() << whitespace() << "// " << group << endl; + } + stream() << whitespace() << "" << cppType(entry->type) << " " << varName(entry->name, cfg()); + if (!entry->param.isEmpty()) { + stream() << QStringLiteral("[%1]").arg(entry->paramMax + 1); + } + stream() << ";" << endl; + + if (cfg().allDefaultGetters || cfg().defaultGetters.contains(entry->name)) { + stream() << whitespace() << ""; + if (cfg().staticAccessors) { + stream() << "static "; + } + stream() << cppType(entry->type) << " " << getDefaultFunction(entry->name) << "_helper("; + if (!entry->param.isEmpty()) { + stream() << " " << cppType(entry->paramType) << " i "; + } + stream() << ")" << Const() << ";" << endl; + } + } + + stream() << endl << " private:" << endl; + if (cfg().itemAccessors) { + for (auto *entry : parseResult.entries) { + stream() << whitespace() << "Item" << itemType(entry->type) << " *" << itemVar(entry, cfg()); + if (!entry->param.isEmpty()) { + stream() << QStringLiteral("[%1]").arg(entry->paramMax + 1); + } + stream() << ";" << endl; + } + } + + if (parseResult.hasNonModifySignals) { + stream() << whitespace() << "uint " << varName(QStringLiteral("settingsChanged"), cfg()) << ";" << endl; + } +} diff --git a/src/kconfig_compiler/KConfigHeaderGenerator.h b/src/kconfig_compiler/KConfigHeaderGenerator.h new file mode 100644 index 00000000..0b85594a --- /dev/null +++ b/src/kconfig_compiler/KConfigHeaderGenerator.h @@ -0,0 +1,78 @@ +/* + This file is part of KDE. + + Copyright (C) 2020 Tomaz Cananbrava (tcanabrava@kde.org) + Copyright (c) 2003 Cornelius Schumacher <schumacher@kde.org> + Copyright (c) 2003 Waldo Bastian <bastian@kde.org> + Copyright (c) 2003 Zack Rusin <zack@kde.org> + Copyright (c) 2006 Michaël Larouche <michael.larouche@kdemail.net> + Copyright (c) 2008 Allen Winter <winter@kde.org> + Copyright (C) 2020 Tomaz Cananbrava (tcanabrava@kde.org) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KCONFIGHEADERGENERATOR_H +#define KCONFIGHEADERGENERATOR_H + +#include "KConfigCodeGeneratorBase.h" +#include "KConfigCommonStructs.h" + +#include <QString> +#include <QList> + +class KConfigParameters; +class CfgEntry; +class QTextStream; +struct ParseResult; + +class KConfigHeaderGenerator : public KConfigCodeGeneratorBase { +public: + KConfigHeaderGenerator( + const QString &inputFile, + const QString &baseDir, + const KConfigParameters ¶meters, + ParseResult &parseResult); + + void start() override; + +private: + void startHeaderGuards(); + void endHeaderGuards(); + + void implementEnums(); + void implementChoiceEnums(const CfgEntry *entry, const CfgEntry::Choices &choices); + void implementValueEnums(const CfgEntry *entry, const QStringList &values); + + void doClassDefinition(); + void createHeaders(); + void createDPointer(); + void createNonDPointerHelpers(); + + void createConstructor(); + void createDestructor(); + void createForwardDeclarations(); + void createSingleton(); + void createSignals(); + + void createSetters(const CfgEntry *entry); + void createItemAcessors(const CfgEntry *entry, const QString &returnType); + void createGetters(const CfgEntry *entry, const QString &returnType); + void createProperties(const CfgEntry *entry, const QString &returnType); + void createDefaultValueMember(const CfgEntry *entry); +}; + +#endif diff --git a/src/kconfig_compiler/KConfigParameters.cpp b/src/kconfig_compiler/KConfigParameters.cpp new file mode 100644 index 00000000..c8046ba6 --- /dev/null +++ b/src/kconfig_compiler/KConfigParameters.cpp @@ -0,0 +1,100 @@ +/* This file is part of the KDE libraries + Copyright (C) 2020 Tomaz Cananbrava (tcanabrava@kde.org) + Copyright (c) 2003 Cornelius Schumacher <schumacher@kde.org> + Copyright (c) 2003 Waldo Bastian <bastian@kde.org> + Copyright (c) 2003 Zack Rusin <zack@kde.org> + Copyright (c) 2006 Michaël Larouche <michael.larouche@kdemail.net> + Copyright (c) 2008 Allen Winter <winter@kde.org> + Copyright (C) 2020 Tomaz Cananbrava (tcanabrava@kde.org) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "KConfigParameters.h" + +// TODO: Remove this. +#undef QT_NO_CAST_FROM_ASCII + +#include <QDebug> +#include <QFileInfo> + +namespace +{ +QTextStream cout(stdout); +QTextStream cerr(stderr); +} + +KConfigParameters::KConfigParameters(const QString &codegenFilename) +{ + if (!codegenFilename.endsWith(QLatin1String(".kcfgc"))) { + cerr << "Codegen options file must have extension .kcfgc" << endl; + exit(1); + } + + baseName = QFileInfo(codegenFilename).fileName(); + baseName = baseName.left(baseName.length() - 6); + + // Configure the compiler with some settings + QSettings codegenConfig(codegenFilename, QSettings::IniFormat); + + nameSpace = codegenConfig.value(QStringLiteral("NameSpace")).toString(); + className = codegenConfig.value(QStringLiteral("ClassName")).toString(); + if (className.isEmpty()) { + cerr << "Class name missing" << endl; + exit(1); + } + inherits = codegenConfig.value(QStringLiteral("Inherits")).toString(); + if (inherits.isEmpty()) { + inherits = QStringLiteral("KConfigSkeleton"); + } + visibility = codegenConfig.value(QStringLiteral("Visibility")).toString(); + if (!visibility.isEmpty()) { + visibility += QLatin1Char(' '); + } + parentInConstructor = codegenConfig.value(QStringLiteral("ParentInConstructor"), false).toBool(); + forceStringFilename = codegenConfig.value(QStringLiteral("ForceStringFilename"), false).toBool(); + singleton = codegenConfig.value(QStringLiteral("Singleton"), false).toBool(); + staticAccessors = singleton; + customAddons = codegenConfig.value(QStringLiteral("CustomAdditions"), false).toBool(); + memberVariables = codegenConfig.value(QStringLiteral("MemberVariables")).toString(); + dpointer = (memberVariables == QLatin1String("dpointer")); + headerIncludes = codegenConfig.value(QStringLiteral("IncludeFiles"), QStringList()).toStringList(); + sourceIncludes = codegenConfig.value(QStringLiteral("SourceIncludeFiles"), QStringList()).toStringList(); + mutators = codegenConfig.value(QStringLiteral("Mutators"), QStringList()).toStringList(); + allMutators = ((mutators.count() == 1) && (mutators.at(0).toLower() == QLatin1String("true"))); + itemAccessors = codegenConfig.value(QStringLiteral("ItemAccessors"), false).toBool(); + setUserTexts = codegenConfig.value(QStringLiteral("SetUserTexts"), false).toBool(); + defaultGetters = codegenConfig.value(QStringLiteral("DefaultValueGetters"), QStringList()).toStringList(); + allDefaultGetters = (defaultGetters.count() == 1) && (defaultGetters.at(0).toLower() == QLatin1String("true")); + notifiers = codegenConfig.value(QStringLiteral("Notifiers"), QStringList()).toStringList(); + allNotifiers = ((notifiers.count() == 1) && (notifiers.at(0).toLower() == QLatin1String("true"))); + globalEnums = codegenConfig.value(QStringLiteral("GlobalEnums"), false).toBool(); + useEnumTypes = codegenConfig.value(QStringLiteral("UseEnumTypes"), false).toBool(); + const QString trString = codegenConfig.value(QStringLiteral("TranslationSystem")).toString().toLower(); + generateProperties = codegenConfig.value(QStringLiteral("GenerateProperties"), false).toBool(); + if (trString == QLatin1String("kde")) { + translationSystem = KdeTranslation; + translationDomain = codegenConfig.value(QStringLiteral("TranslationDomain")).toString(); + } else { + if (!trString.isEmpty() && trString != QLatin1String("qt")) { + cerr << "Unknown translation system, falling back to Qt tr()" << endl; + } + translationSystem = QtTranslation; + } + qCategoryLoggingName = codegenConfig.value(QStringLiteral("CategoryLoggingName"), QString()).toString(); + headerExtension = codegenConfig.value(QStringLiteral("HeaderExtension"), QStringLiteral("h")).toString(); + sourceExtension = codegenConfig.value(QStringLiteral("SourceExtension"), QStringLiteral("cpp")).toString(); +} diff --git a/src/kconfig_compiler/KConfigParameters.h b/src/kconfig_compiler/KConfigParameters.h new file mode 100644 index 00000000..b114dcc9 --- /dev/null +++ b/src/kconfig_compiler/KConfigParameters.h @@ -0,0 +1,81 @@ +/* + This file is part of KDE. + Copyright (C) 2020 Tomaz Cananbrava (tcanabrava@kde.org) + Copyright (c) 2003 Cornelius Schumacher <schumacher@kde.org> + Copyright (c) 2003 Waldo Bastian <bastian@kde.org> + Copyright (c) 2003 Zack Rusin <zack@kde.org> + Copyright (c) 2006 Michaël Larouche <michael.larouche@kdemail.net> + Copyright (c) 2008 Allen Winter <winter@kde.org> + Copyright (C) 2020 Tomaz Cananbrava (tcanabrava@kde.org) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KCONFIGXTPARAMETERS_H +#define KCONFIGXTPARAMETERS_H + +#include <QSettings> +#include <QString> +#include <QStringList> + +/** + Configuration Compiler Configuration +*/ +class KConfigParameters +{ +public: + KConfigParameters(const QString &codegenFilename); + +public: + enum TranslationSystem { + QtTranslation, + KdeTranslation + }; + + // These are read from the .kcfgc configuration file + QString nameSpace; // The namespace for the class to be generated + QString className; // The class name to be generated + QString inherits; // The class the generated class inherits (if empty, from KConfigSkeleton) + QString visibility; + bool parentInConstructor; // The class has the optional parent parameter in its constructor + bool forceStringFilename; + bool singleton; // The class will be a singleton + bool staticAccessors; // provide or not static accessors + bool customAddons; + QString memberVariables; + QStringList headerIncludes; + QStringList sourceIncludes; + QStringList mutators; + QStringList defaultGetters; + QStringList notifiers; + QString qCategoryLoggingName; + QString headerExtension; + QString sourceExtension; + bool allMutators; + bool setUserTexts; + bool allDefaultGetters; + bool dpointer; + bool globalEnums; + bool useEnumTypes; + bool itemAccessors; + bool allNotifiers; + TranslationSystem translationSystem; + QString translationDomain; + bool generateProperties; + QString baseName; +}; + +#endif diff --git a/src/kconfig_compiler/KConfigSourceGenerator.cpp b/src/kconfig_compiler/KConfigSourceGenerator.cpp new file mode 100644 index 00000000..f306693b --- /dev/null +++ b/src/kconfig_compiler/KConfigSourceGenerator.cpp @@ -0,0 +1,657 @@ +/* This file is part of the KDE libraries + Copyright (C) 2020 Tomaz Cananbrava (tcanabrava@kde.org) + Copyright (c) 2003 Cornelius Schumacher <schumacher@kde.org> + Copyright (c) 2003 Waldo Bastian <bastian@kde.org> + Copyright (c) 2003 Zack Rusin <zack@kde.org> + Copyright (c) 2006 Michaël Larouche <michael.larouche@kdemail.net> + Copyright (c) 2008 Allen Winter <winter@kde.org> + Copyright (C) 2020 Tomaz Cananbrava (tcanabrava@kde.org) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "KConfigSourceGenerator.h" +#include "KConfigCommonStructs.h" + + +KConfigSourceGenerator::KConfigSourceGenerator( + const QString &inputFile, + const QString &baseDir, + const KConfigParameters &cfg, + ParseResult &result) + : KConfigCodeGeneratorBase(inputFile, baseDir, baseDir + cfg.baseName + QLatin1Char('.') + cfg.sourceExtension, cfg, result) +{ +} + +void KConfigSourceGenerator::start() +{ + KConfigCodeGeneratorBase::start(); + stream() << endl; + createHeaders(); + + if (!cfg().nameSpace.isEmpty()) { + stream() << "using namespace " << cfg().nameSpace << ";"; + stream() << endl << endl; + } + + createPrivateDPointerImplementation(); + createSingletonImplementation(); + createPreamble(); + doConstructor(); + doGetterSetterDPointerMode(); + createDefaultValueGetterSetter(); + createDestructor(); + createNonModifyingSignalsHelper(); + createSignalFlagsHandler(); + includeMoc(); +} + +void KConfigSourceGenerator::createHeaders() +{ + QString headerName = cfg().baseName + QLatin1Char('.') + cfg().headerExtension; + + // TODO: Make addQuotes return a string instead of replacing it inplace. + addQuotes(headerName); + + addHeaders({ headerName }); + stream() << endl; + + addHeaders(cfg().sourceIncludes); + if (cfg().setUserTexts && cfg().translationSystem == KConfigParameters::KdeTranslation) { + addHeaders({QStringLiteral("klocalizedstring.h")}); + stream() << endl; + } + + // Header required by singleton implementation + if (cfg().singleton) { + addHeaders({QStringLiteral("qglobal.h"), QStringLiteral("QFile")}); + + // HACK: Add single line to fix test. + if (cfg().singleton && parseResult.cfgFileNameArg) { + stream() << endl; + } + } + + if (cfg().singleton && parseResult.cfgFileNameArg) { + addHeaders({QStringLiteral("QDebug")}); + } + + if (cfg().singleton) { + stream() << endl; + } +} + +void KConfigSourceGenerator::createPrivateDPointerImplementation() +{ + // private class implementation + if (!cfg().dpointer) { + return; + } + + QString group; + beginNamespaces(); + stream() << "class " << cfg().className << "Private" << endl; + stream() << "{" << endl; + stream() << " public:" << endl; + + // Create Members + for (auto *entry : parseResult.entries) { + if (entry->group != group) { + group = entry->group; + stream() << endl; + stream() << " // " << group << endl; + } + stream() << " " << cppType(entry->type) << " " << varName(entry->name, cfg()); + if (!entry->param.isEmpty()) { + stream() << QStringLiteral("[%1]").arg(entry->paramMax + 1); + } + stream() << ";" << endl; + } + stream() << endl << " // items" << endl; + + // Create Items. + for (auto *entry : parseResult.entries) { + const QString declType = entry->signalList.isEmpty() + ? QString(cfg().inherits + QStringLiteral("::Item") + itemType(entry->type)) + : QStringLiteral("KConfigCompilerSignallingItem"); + + stream() << " " << declType << " *" << itemVar( entry, cfg() ); + if (!entry->param.isEmpty()) { + stream() << QStringLiteral("[%1]").arg(entry->paramMax + 1); + } + stream() << ";" << endl; + } + + if (parseResult.hasNonModifySignals) { + stream() << " uint " << varName(QStringLiteral("settingsChanged"), cfg()) << ";" << endl; + } + + stream() << "};" << endl << endl; + endNamespaces(); +} + +void KConfigSourceGenerator::createSingletonImplementation() +{ + // Singleton implementation + if (!cfg().singleton) { + return; + } + + beginNamespaces(); + stream() << "class " << cfg().className << "Helper" << endl; + stream() << '{' << endl; + stream() << " public:" << endl; + stream() << " " << cfg().className << "Helper() : q(nullptr) {}" << endl; + stream() << " ~" << cfg().className << "Helper() { delete q; }" << endl; + stream() << " " << cfg().className << "Helper(const " << cfg().className << "Helper&) = delete;" << endl; + stream() << " " << cfg().className << "Helper& operator=(const " << cfg().className << "Helper&) = delete;" << endl; + stream() << " " << cfg().className << " *q;" << endl; + stream() << "};" << endl; + endNamespaces(); + + stream() << "Q_GLOBAL_STATIC(" << cfg().className << "Helper, s_global" << cfg().className << ")" << endl; + + stream() << cfg().className << " *" << cfg().className << "::self()" << endl; + stream() << "{" << endl; + if (parseResult.cfgFileNameArg) { + stream() << " if (!s_global" << cfg().className << "()->q)" << endl; + stream() << " qFatal(\"you need to call " << cfg().className << "::instance before using\");" << endl; + } else { + stream() << " if (!s_global" << cfg().className << "()->q) {" << endl; + stream() << " new " << cfg().className << ';' << endl; + stream() << " s_global" << cfg().className << "()->q->read();" << endl; + stream() << " }" << endl << endl; + } + stream() << " return s_global" << cfg().className << "()->q;" << endl; + stream() << "}" << endl << endl; + + if (parseResult.cfgFileNameArg) { + auto instance = [this] (const QString &type, const QString &arg, bool isString) { + stream() << "void " << cfg().className << "::instance(" << type << " " << arg << ")" << endl; + stream() << "{" << endl; + stream() << " if (s_global" << cfg().className << "()->q) {" << endl; + stream() << " qDebug() << \"" << cfg().className << "::instance called after the first use - ignoring\";" << endl; + stream() << " return;" << endl; + stream() << " }" << endl; + stream() << " new " << cfg().className << "("; + if (isString) { + stream() << "KSharedConfig::openConfig(" << arg << ")"; + } else { + stream() << "std::move(" << arg << ")"; + } + stream() << ");" << endl; + stream() << " s_global" << cfg().className << "()->q->read();" << endl; + stream() << "}" << endl << endl; + }; + instance(QStringLiteral("const QString&"), QStringLiteral("cfgfilename"), true); + instance(QStringLiteral("KSharedConfig::Ptr"), QStringLiteral("config"), false); + } +} + +void KConfigSourceGenerator::createPreamble() +{ + QString cppPreamble; + for (const auto entry : parseResult.entries) { + if (entry->paramValues.isEmpty()) { + continue; + } + + cppPreamble += QStringLiteral("const char* const ") + cfg().className + QStringLiteral("::") + enumName(entry->param); + cppPreamble += cfg().globalEnums + ? QStringLiteral("ToString[] = { \"") + entry->paramValues.join(QStringLiteral("\", \"")) + QStringLiteral("\" };\n") + : QStringLiteral("::enumToString[] = { \"") + entry->paramValues.join(QStringLiteral("\", \"")) + QStringLiteral("\" };\n"); + } + + if (!cppPreamble.isEmpty()) { + stream() << cppPreamble << endl; + } +} + +void KConfigSourceGenerator::createConstructorParameterList() +{ + if (parseResult.cfgFileNameArg) { + if (!cfg().forceStringFilename) { + stream() << " KSharedConfig::Ptr config"; + } else { + stream() << " const QString& config"; + } + stream() << (parseResult.parameters.isEmpty() ? "" : ","); + } + + for (QList<Param>::ConstIterator it = parseResult.parameters.constBegin(); + it != parseResult.parameters.constEnd(); ++it) { + if (it != parseResult.parameters.constBegin()) { + stream() << ","; + } + stream() << " " << param((*it).type) << " " << (*it).name; + } + + if (cfg().parentInConstructor) { + if (parseResult.cfgFileNameArg || !parseResult.parameters.isEmpty()) { + stream() << ","; + } + stream() << " QObject *parent"; + } + +} + +void KConfigSourceGenerator::createParentConstructorCall() +{ + stream() << cfg().inherits << "("; + if (!parseResult.cfgFileName.isEmpty()) { + stream() << " QStringLiteral( \"" << parseResult.cfgFileName << "\" "; + } + if (parseResult.cfgFileNameArg) { + if (! cfg().forceStringFilename) { + stream() << " std::move( config ) "; + } else { + stream() << " config "; + } + } + if (!parseResult.cfgFileName.isEmpty()) { + stream() << ") "; + } + stream() << ")" << endl; +} + +void KConfigSourceGenerator::createInitializerList() +{ + for (const auto ¶meter : parseResult.parameters) { + stream() << " , mParam" << parameter.name << "(" << parameter.name << ")" << endl; + } + + if (parseResult.hasNonModifySignals && !cfg().dpointer) { + stream() << " , " << varName(QStringLiteral("settingsChanged"), cfg()) << "(0)" << endl; + } +} + +void KConfigSourceGenerator::createEnums(const CfgEntry *entry) +{ + if (entry->type != QLatin1String("Enum")) { + return; + } + stream() << " QList<" << cfg().inherits << "::ItemEnum::Choice> values" << entry->name << ";" << endl; + + for (const auto &choice : qAsConst(entry->choices.choices)) { + stream() << " {" << endl; + stream() << " " << cfg().inherits << "::ItemEnum::Choice choice;" << endl; + stream() << " choice.name = QStringLiteral(\"" << choice.name << "\");" << endl; + if (cfg().setUserTexts) { + if (!choice.label.isEmpty()) { + stream() << " choice.label = " + << translatedString(cfg(), choice.label, choice.context) + << ";" << endl; + } + if (!choice.toolTip.isEmpty()) { + stream() << " choice.toolTip = " + << translatedString(cfg(), choice.toolTip, choice.context) + << ";" << endl; + } + if (!choice.whatsThis.isEmpty()) { + stream() << " choice.whatsThis = " + << translatedString(cfg(), choice.whatsThis, choice.context) + << ";" << endl; + } + } + stream() << " values" << entry->name << ".append( choice );" << endl; + stream() << " }" << endl; + } +} + +void KConfigSourceGenerator::createNormalEntry(const CfgEntry *entry, const QString &key) +{ + stream() << " " << itemPath(entry, cfg()) << " = " + << newItem(entry, key, entry->defaultValue, cfg()) << endl; + + if (!entry->min.isEmpty()) { + stream() << " " << itemPath(entry, cfg()) << "->setMinValue(" << entry->min << ");" << endl; + } + if (!entry->max.isEmpty()) { + stream() << " " << itemPath(entry, cfg()) << "->setMaxValue(" << entry->max << ");" << endl; + } + + if (cfg().setUserTexts) { + stream() << userTextsFunctions(entry, cfg()); + } + + if (cfg().allNotifiers || cfg().notifiers.contains(entry->name)) { + stream() << " " << itemPath(entry, cfg()) << "->setWriteFlags(KConfigBase::Notify);" << endl; + } + + stream() << " addItem( " << itemPath(entry, cfg()); + QString quotedName = entry->name; + addQuotes(quotedName); + if (quotedName != key) { + stream() << ", QStringLiteral( \"" << entry->name << "\" )"; + } + stream() << " );" << endl; +} + +void KConfigSourceGenerator::createIndexedEntry(const CfgEntry *entry, const QString &key) +{ + for (int i = 0; i <= entry->paramMax; i++) { + QString itemVarStr(itemPath(entry, cfg()) + QStringLiteral("[%1]").arg(i)); + + QString defaultStr = !entry->paramDefaultValues[i].isEmpty() ? entry->paramDefaultValues[i] + : !entry->defaultValue.isEmpty() ? paramString(entry->defaultValue, entry, i) + : defaultValue(entry->type); + + stream() << " " << itemVarStr << " = " + << newItem(entry, paramString(key, entry, i), defaultStr, cfg(), QStringLiteral("[%1]").arg(i)) << endl; + + if (cfg().setUserTexts) { + stream() << userTextsFunctions(entry, cfg(), itemVarStr, entry->paramName); + } + + // Make mutators for enum parameters work by adding them with $(..) replaced by the + // param name. The check for isImmutable in the set* functions doesn't have the param + // name available, just the corresponding enum value (int), so we need to store the + // param names in a separate static list!. + const bool isEnum = entry->paramType == QLatin1String("Enum"); + const QString arg = isEnum ? entry->paramValues[i] : QString::number(i); + + QString paramName = entry->paramName; + + stream() << " addItem( " << itemVarStr << ", QStringLiteral( \""; + stream() << paramName.replace(QStringLiteral("$(") + entry->param + QLatin1Char(')'), QLatin1String("%1")).arg( arg ); + stream() << "\" ) );" << endl; + } +} + +void KConfigSourceGenerator::handleCurrentGroupChange(const CfgEntry *entry) +{ + if (entry->group == mCurrentGroup) { + return; + } + + // HACK: This fixes one spacing line in the diff. Remove this in the future and adapt the testcases. + static bool first = true; + if (!entry->group.isEmpty()) { + if (!first) { + stream() << endl; + } + first = false; + } + + mCurrentGroup = entry->group; + stream() << " setCurrentGroup( " << paramString(mCurrentGroup, parseResult.parameters) << " );"; + stream() << endl << endl; +} + +void KConfigSourceGenerator::doConstructor() +{ + // Constructor + stream() << cfg().className << "::" << cfg().className << "("; + createConstructorParameterList(); + stream() << " )" << endl; + stream() << " : "; + createParentConstructorCall(); + createInitializerList(); + + stream() << "{" << endl; + + if (cfg().parentInConstructor) { + stream() << " setParent(parent);" << endl; + } + + if (cfg().dpointer) { + stream() << " d = new " << cfg().className << "Private;" << endl; + if (parseResult.hasNonModifySignals) { + stream() << " " << varPath(QStringLiteral("settingsChanged"), cfg()) << " = 0;" << endl; + } + } + + // Needed in case the singleton class is used as baseclass for + // another singleton. + if (cfg().singleton) { + stream() << " Q_ASSERT(!s_global" << cfg().className << "()->q);" << endl; + stream() << " s_global" << cfg().className << "()->q = this;" << endl; + } + + if (!parseResult.signalList.isEmpty()) { + // this cast to base-class pointer-to-member is valid C++ + // https://stackoverflow.com/questions/4272909/is-it-safe-to-upcast-a-method-pointer-and-use-it-with-base-class-pointer/ + stream() << " KConfigCompilerSignallingItem::NotifyFunction notifyFunction =" + << " static_cast<KConfigCompilerSignallingItem::NotifyFunction>(&" + << cfg().className << "::itemChanged);" << endl; + + stream() << endl; + } + + for (auto *entry : parseResult.entries) { + handleCurrentGroupChange(entry); + + const QString key = paramString(entry->key, parseResult.parameters); + if (!entry->code.isEmpty()) { + stream() << entry->code << endl; + } + createEnums(entry); + + if (!cfg().dpointer) { + stream() << itemDeclaration(entry, cfg()); + } + + if (entry->param.isEmpty()) { + createNormalEntry(entry, key); + } else { + createIndexedEntry(entry, key); + } + } + + stream() << "}" << endl << endl; +} + +void KConfigSourceGenerator::createGetterDPointerMode(const CfgEntry *entry) +{ + // Accessor + if (cfg().useEnumTypes && entry->type == QLatin1String("Enum")) { + stream() << enumType(entry, cfg().globalEnums); + } else { + stream() << cppType(entry->type); + } + + stream() << " " << getFunction(entry->name, cfg().className) << "("; + if (!entry->param.isEmpty()) { + stream() << " " << cppType(entry->paramType) << " i "; + } + stream() << ")" << Const() << endl; + + // function body inline only if not using dpointer + // for BC mode + startScope(); + // HACK: Fix memberAccessorBody + stream() << " " << memberAccessorBody(entry, cfg().globalEnums); + endScope(); + stream() << endl; +} + +void KConfigSourceGenerator::createSetterDPointerMode(const CfgEntry *entry) +{ + // Manipulator + if (!(cfg().allMutators || cfg().mutators.contains(entry->name))) { + return; + } + + stream() << "void " << setFunction(entry->name, cfg().className) << "( "; + if (!entry->param.isEmpty()) { + stream() << cppType(entry->paramType) << " i, "; + } + + if (cfg().useEnumTypes && entry->type == QLatin1String("Enum")) { + stream() << enumType(entry, cfg().globalEnums); + } else { + stream() << param(entry->type); + } + stream() << " v )" << endl; + + // function body inline only if not using dpointer + // for BC mode + startScope(); + memberMutatorBody(entry); + endScope(); + stream() << endl; +} + +void KConfigSourceGenerator::createItemGetterDPointerMode(const CfgEntry *entry) +{ + // Item accessor + if (!cfg().itemAccessors) { + return; + } + stream() << endl; + stream() << cfg().inherits << "::Item" << itemType(entry->type) << " *" + << getFunction(entry->name, cfg().className) << "Item("; + if (!entry->param.isEmpty()) { + stream() << " " << cppType(entry->paramType) << " i "; + } + stream() << ")" << endl; + startScope(); + stream() << " " << itemAccessorBody(entry, cfg()); + endScope(); +} + +void KConfigSourceGenerator::doGetterSetterDPointerMode() +{ + if (!cfg().dpointer) { + return; + } + + // setters and getters go in Cpp if in dpointer mode + for (auto *entry : parseResult.entries) { + createSetterDPointerMode(entry); + createGetterDPointerMode(entry); + createItemGetterDPointerMode(entry); + stream() << endl; + } +} + +void KConfigSourceGenerator::createDefaultValueGetterSetter() +{ + // default value getters always go in Cpp + for (auto *entry : parseResult.entries) { + QString n = entry->name; + QString t = entry->type; + + // Default value Accessor, as "helper" function + if ((cfg().allDefaultGetters || cfg().defaultGetters.contains(n)) && !entry->defaultValue.isEmpty()) { + stream() << cppType(t) << " " << getDefaultFunction(n, cfg().className) << "_helper("; + if (!entry->param.isEmpty()) { + stream() << " " << cppType(entry->paramType) << " i "; + } + stream() << ")" << Const() << endl; + startScope(); + stream() << memberGetDefaultBody(entry) << endl; + endScope(); + stream() << endl; + } + } +} + +void KConfigSourceGenerator::createDestructor() +{ + stream() << cfg().className << "::~" << cfg().className << "()" << endl; + startScope(); + if (cfg().dpointer) { + stream() << " delete d;" << endl; + } + if (cfg().singleton) { + stream() << " s_global" << cfg().className << "()->q = nullptr;" << endl; + } + endScope(); + stream() << endl; +} + +void KConfigSourceGenerator::createNonModifyingSignalsHelper() +{ + if (!parseResult.hasNonModifySignals) { + return; + } + stream() << "bool " << cfg().className << "::" << "usrSave()" << endl; + startScope(); + stream() << " const bool res = " << cfg().inherits << "::usrSave();" << endl; + stream() << " if (!res) return false;" << endl << endl; + for (const Signal &signal : parseResult.signalList) { + if (signal.modify) { + continue; + } + + stream() << " if ( " << varPath(QStringLiteral("settingsChanged"), cfg()) << " & " << signalEnumName(signal.name) << " )" << endl; + stream() << " Q_EMIT " << signal.name << "("; + QList<Param>::ConstIterator it, itEnd = signal.arguments.constEnd(); + for (it = signal.arguments.constBegin(); it != itEnd;) { + Param argument = *it; + bool cast = false; + if (cfg().useEnumTypes && argument.type == QLatin1String("Enum")) { + for (int i = 0, end = parseResult.entries.count(); i < end; ++i) { + if (parseResult.entries.at(i)->name == argument.name) { + stream() << "static_cast<" << enumType(parseResult.entries.at(i), cfg().globalEnums) << ">("; + cast = true; + break; + } + } + } + stream() << varPath(argument.name, cfg()); + if (cast) { + stream() << ")"; + } + if (++it != itEnd) { + stream() << ", "; + } + } + + stream() << ");" << endl; + } + + stream() << " " << varPath(QStringLiteral("settingsChanged"), cfg()) << " = 0;" << endl; + stream() << " return true;" << endl; + endScope(); +} + +void KConfigSourceGenerator::createSignalFlagsHandler() +{ + if (parseResult.signalList.isEmpty()) { + return; + } + + stream() << endl; + stream() << "void " << cfg().className << "::" << "itemChanged(quint64 flags) {" << endl; + if (parseResult.hasNonModifySignals) + stream() << " " << varPath(QStringLiteral("settingsChanged"), cfg()) << " |= flags;" << endl; + + if (!parseResult.signalList.isEmpty()) + stream() << endl; + + for (const Signal &signal : parseResult.signalList) { + if (signal.modify) { + stream() << " if ( flags & " << signalEnumName(signal.name) << " ) {" << endl; + stream() << " Q_EMIT " << signal.name << "();" << endl; + stream() << " }" << endl; + } + } + + stream() << "}" << endl; +} + +void KConfigSourceGenerator::includeMoc() { + const QString mocFileName = cfg().baseName + QStringLiteral(".moc"); + + if (parseResult.signalList.count() || cfg().generateProperties) { + // Add includemoc if they are signals defined. + stream() << endl; + stream() << "#include \"" << mocFileName << "\"" << endl; + stream() << endl; + } +} diff --git a/src/kconfig_compiler/KConfigSourceGenerator.h b/src/kconfig_compiler/KConfigSourceGenerator.h new file mode 100644 index 00000000..510d4048 --- /dev/null +++ b/src/kconfig_compiler/KConfigSourceGenerator.h @@ -0,0 +1,86 @@ +/* + This file is part of KDE. + + Copyright (C) 2020 Tomaz Cananbrava (tcanabrava@kde.org) + Copyright (c) 2003 Cornelius Schumacher <schumacher@kde.org> + Copyright (c) 2003 Waldo Bastian <bastian@kde.org> + Copyright (c) 2003 Zack Rusin <zack@kde.org> + Copyright (c) 2006 Michaël Larouche <michael.larouche@kdemail.net> + Copyright (c) 2008 Allen Winter <winter@kde.org> + Copyright (C) 2020 Tomaz Cananbrava (tcanabrava@kde.org) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KCONFIGSOURCEGENERATOR_H +#define KCONFIGSOURCEGENERATOR_H + +#include "KConfigCodeGeneratorBase.h" +#include "KConfigCommonStructs.h" + +#include <QString> +#include <QList> + +class KConfigParameters; +class CfgEntry; +class QTextStream; +struct ParseResult; + +class KConfigSourceGenerator : public KConfigCodeGeneratorBase { +public: + KConfigSourceGenerator( + const QString &inputFile, + const QString &baseDir, + const KConfigParameters ¶meters, + ParseResult &parseResult); + + void start() override; + +private: + // Those are fairly self contained functions. + void createHeaders(); + void createPrivateDPointerImplementation(); + void createSingletonImplementation(); + void createPreamble(); + void createDestructor(); + void createConstructorParameterList(); + void createParentConstructorCall(); + void createInitializerList(); + void createDefaultValueGetterSetter(); + void createNonModifyingSignalsHelper(); + void createSignalFlagsHandler(); + void includeMoc(); + + // Constructor related methods + // the `do` methods have related helper functions that are only related + // to it. So we can break the function into many smaller ones and create + // logic inside of the `do` function. + void doConstructor(); + void createEnums(const CfgEntry *entry); + void createNormalEntry(const CfgEntry *entry, const QString &key); + void createIndexedEntry(const CfgEntry *entry, const QString &key); + void handleCurrentGroupChange(const CfgEntry *entry); + + void doGetterSetterDPointerMode(); + void createGetterDPointerMode(const CfgEntry *entry); + void createSetterDPointerMode(const CfgEntry *entry); + void createItemGetterDPointerMode(const CfgEntry *entry); + +private: + QString mCurrentGroup; +}; + +#endif diff --git a/src/kconfig_compiler/KConfigXmlParser.cpp b/src/kconfig_compiler/KConfigXmlParser.cpp new file mode 100644 index 00000000..11c76abd --- /dev/null +++ b/src/kconfig_compiler/KConfigXmlParser.cpp @@ -0,0 +1,564 @@ +/* This file is part of the KDE libraries + Copyright (c) 2003 Cornelius Schumacher <schumacher@kde.org> + Copyright (c) 2003 Waldo Bastian <bastian@kde.org> + Copyright (c) 2003 Zack Rusin <zack@kde.org> + Copyright (c) 2006 Michaël Larouche <michael.larouche@kdemail.net> + Copyright (c) 2008 Allen Winter <winter@kde.org> + Copyright (C) 2020 Tomaz Cananbrava (tcanabrava@kde.org) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "KConfigXmlParser.h" +#include "KConfigParameters.h" + +#include <QDomAttr> +#include <QDomElement> +#include <QDomNode> +#include <QFile> +#include <QList> +#include <QStringList> +#include <QTextStream> + +namespace +{ +QTextStream cout(stdout); +QTextStream cerr(stderr); +} + +//TODO: Move preprocessDefault to Header / CPP implementation. +// it makes no sense for a parser to process those values and generate code. + +static void preProcessDefault(QString &defaultValue, const QString &name, + const QString &type, + const CfgEntry::Choices &choices, + QString &code, const KConfigParameters &cfg) +{ + if (type == QLatin1String("String") && !defaultValue.isEmpty()) { + defaultValue = literalString(defaultValue); + + } else if (type == QLatin1String("Path") && !defaultValue.isEmpty()) { + defaultValue = literalString(defaultValue); + } else if (type == QLatin1String("Url") && !defaultValue.isEmpty()) { + // Use fromUserInput in order to support absolute paths and absolute urls, like KDE4's KUrl(QString) did. + defaultValue = QLatin1String("QUrl::fromUserInput( ") + literalString(defaultValue) + QLatin1Char(')'); + } else if ((type == QLatin1String("UrlList") || type == QLatin1String("StringList") || type == QLatin1String("PathList")) && !defaultValue.isEmpty()) { + QTextStream cpp(&code, QIODevice::WriteOnly | QIODevice::Append); + if (!code.isEmpty()) { + cpp << endl; + } + + if (type == QLatin1String("UrlList")) { + cpp << " QList<QUrl> default" << name << ";" << endl; + } else { + cpp << " QStringList default" << name << ";" << endl; + } + const QStringList defaults = defaultValue.split(QLatin1Char(',')); + QStringList::ConstIterator it; + for (it = defaults.constBegin(); it != defaults.constEnd(); ++it) { + cpp << " default" << name << ".append( "; + if (type == QLatin1String("UrlList")) { + cpp << "QUrl::fromUserInput("; + } + cpp << "QString::fromUtf8( \"" << *it << "\" ) "; + if (type == QLatin1String("UrlList")) { + cpp << ") "; + } + cpp << ");" << endl; + } + defaultValue = QLatin1String("default") + name; + + } else if (type == QLatin1String("Color") && !defaultValue.isEmpty()) { + const QRegularExpression colorRe(QRegularExpression::anchoredPattern( + QStringLiteral("\\d+,\\s*\\d+,\\s*\\d+(,\\s*\\d+)?"))); + + if (colorRe.match(defaultValue).hasMatch()) { + defaultValue = QLatin1String("QColor( ") + defaultValue + QLatin1String(" )"); + } else { + defaultValue = QLatin1String("QColor( \"") + defaultValue + QLatin1String("\" )"); + } + + } else if (type == QLatin1String("Enum")) { + QList<CfgEntry::Choice>::ConstIterator it; + for (it = choices.choices.constBegin(); it != choices.choices.constEnd(); ++it) { + if ((*it).name == defaultValue) { + if (cfg.globalEnums && choices.name().isEmpty()) { + defaultValue.prepend(choices.prefix); + } else { + defaultValue.prepend(enumTypeQualifier(name, choices) + choices.prefix); + } + break; + } + } + + } else if (type == QLatin1String("IntList")) { + QTextStream cpp(&code, QIODevice::WriteOnly | QIODevice::Append); + if (!code.isEmpty()) { + cpp << endl; + } + + cpp << " QList<int> default" << name << ";" << endl; + if (!defaultValue.isEmpty()) { + const QStringList defaults = defaultValue.split(QLatin1Char(',')); + QStringList::ConstIterator it; + for (it = defaults.constBegin(); it != defaults.constEnd(); ++it) { + cpp << " default" << name << ".append( " << *it << " );" + << endl; + } + } + defaultValue = QLatin1String("default") + name; + } +} + +static QString dumpNode(const QDomNode &node) +{ + QString msg; + QTextStream s(&msg, QIODevice::WriteOnly); + node.save(s, 0); + + msg = msg.simplified(); + if (msg.length() > 40) { + return msg.left(37) + QLatin1String("..."); + } + return msg; +} + +void KConfigXmlParser::readParameterFromEntry(CfgEntry &readEntry, const QDomElement &e) +{ + readEntry.param = e.attribute(QStringLiteral("name")); + readEntry.paramType = e.attribute(QStringLiteral("type")); + + if (readEntry.param.isEmpty()) { + cerr << "Parameter must have a name: " << dumpNode(e) << endl; + exit (1); + } + + if (readEntry.paramType.isEmpty()) { + cerr << "Parameter must have a type: " << dumpNode(e) << endl; + exit(1); + } + + if ((readEntry.paramType == QLatin1String("Int")) || (readEntry.paramType == QLatin1String("UInt"))) { + bool ok; + readEntry.paramMax = e.attribute(QStringLiteral("max")).toInt(&ok); + if (!ok) { + cerr << "Integer parameter must have a maximum (e.g. max=\"0\"): " << dumpNode(e) << endl; + exit(1); + } + } else if (readEntry.paramType == QLatin1String("Enum")) { + for (QDomElement e2 = e.firstChildElement(); !e2.isNull(); e2 = e2.nextSiblingElement()) { + if (e2.tagName() == QLatin1String("values")) { + for (QDomElement e3 = e2.firstChildElement(); !e3.isNull(); e3 = e3.nextSiblingElement()) { + if (e3.tagName() == QLatin1String("value")) { + readEntry.paramValues.append(e3.text()); + } + } + break; + } + } + if (readEntry.paramValues.isEmpty()) { + cerr << "No values specified for parameter '" << readEntry.param << "'." << endl; + exit(1); + } + readEntry.paramMax = readEntry.paramValues.count() - 1; + } else { + cerr << "Parameter '" << readEntry.param << "' has type " << readEntry.paramType + << " but must be of type int, uint or Enum." << endl; + exit(1); + } +} + +bool KConfigXmlParser::hasDefaultCode(CfgEntry &readEntry, const QDomElement &element) +{ + for (QDomElement e = element.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) { + if (e.attribute(QStringLiteral("param")).isEmpty()) { + if (e.attribute(QStringLiteral("code")) == QLatin1String("true")) { + return true; + } + } + } + return false; +} + + +void KConfigXmlParser::readChoicesFromEntry(CfgEntry &readEntry, const QDomElement &e) +{ + QList<CfgEntry::Choice> chlist; + for (QDomElement e2 = e.firstChildElement(); !e2.isNull(); e2 = e2.nextSiblingElement()) { + if (e2.tagName() != QLatin1String("choice")) { + continue; + } + CfgEntry::Choice choice; + choice.name = e2.attribute(QStringLiteral("name")); + if (choice.name.isEmpty()) { + cerr << "Tag <choice> requires attribute 'name'." << endl; + } + for (QDomElement e3 = e2.firstChildElement(); !e3.isNull(); e3 = e3.nextSiblingElement()) { + if (e3.tagName() == QLatin1String("label")) { + choice.label = e3.text(); + choice.context = e3.attribute(QStringLiteral("context")); + } + if (e3.tagName() == QLatin1String("tooltip")) { + choice.toolTip = e3.text(); + choice.context = e3.attribute(QStringLiteral("context")); + } + if (e3.tagName() == QLatin1String("whatsthis")) { + choice.whatsThis = e3.text(); + choice.context = e3.attribute(QStringLiteral("context")); + } + } + chlist.append(choice); + } + + QString name = e.attribute(QStringLiteral("name")); + QString prefix = e.attribute(QStringLiteral("prefix")); + + readEntry.choices = CfgEntry::Choices(chlist, name, prefix); +} + +void KConfigXmlParser::readGroupElements(CfgEntry &readEntry, const QDomElement &element) +{ + for (QDomElement e = element.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) { + QString tag = e.tagName(); + if (tag == QLatin1String("label")) { + readEntry.label = e.text(); + readEntry.labelContext = e.attribute(QStringLiteral("context")); + } else if (tag == QLatin1String("tooltip")) { + readEntry.toolTip = e.text(); + readEntry.toolTipContext = e.attribute(QStringLiteral("context")); + } else if (tag == QLatin1String("whatsthis")) { + readEntry.whatsThis = e.text(); + readEntry.whatsThisContext = e.attribute(QStringLiteral("context")); + } else if (tag == QLatin1String("min")) { + readEntry.min = e.text(); + } else if (tag == QLatin1String("max")) { + readEntry.max = e.text(); + } else if (tag == QLatin1String("code")) { + readEntry.code = e.text(); + } else if (tag == QLatin1String("parameter")) { + readParameterFromEntry(readEntry, e); + } else if (tag == QLatin1String("default")) { + if (e.attribute(QStringLiteral("param")).isEmpty()) { + readEntry.defaultValue = e.text(); + } + } else if (tag == QLatin1String("choices")) { + readChoicesFromEntry(readEntry, e); + } else if (tag == QLatin1String("emit")) { + Signal signal; + signal.name = e.attribute(QStringLiteral("signal")); + readEntry.signalList.append(signal); + } + } +} + +void KConfigXmlParser::createChangedSignal(CfgEntry &readEntry) +{ + if (cfg.generateProperties && (cfg.allMutators || cfg.mutators.contains(readEntry.name))) { + Signal s; + s.name = changeSignalName(readEntry.name); + s.modify = true; + readEntry.signalList.append(s); + } +} + +void KConfigXmlParser::validateNameAndKey(CfgEntry &readEntry, const QDomElement &element) +{ + bool nameIsEmpty = readEntry.name.isEmpty(); + if (nameIsEmpty && readEntry.key.isEmpty()) { + cerr << "Entry must have a name or a key: " << dumpNode(element) << endl; + exit (1); + } + + if (readEntry.key.isEmpty()) { + readEntry.key = readEntry.name; + } + + if (nameIsEmpty) { + readEntry.name = readEntry.key; + readEntry.name.remove(QLatin1Char(' ')); + } else if (readEntry.name.contains(QLatin1Char(' '))) { + cout << "Entry '" << readEntry.name << "' contains spaces! <name> elements can not contain spaces!" << endl; + readEntry.name.remove(QLatin1Char(' ')); + } + + if (readEntry.name.contains(QStringLiteral("$("))) { + if (readEntry.param.isEmpty()) { + cerr << "Name may not be parameterized: " << readEntry.name << endl; + exit (1); + } + } else { + if (!readEntry.param.isEmpty()) { + cerr << "Name must contain '$(" << readEntry.param << ")': " << readEntry.name << endl; + exit (1); + } + } +} + +void KConfigXmlParser::readParamDefaultValues(CfgEntry &readEntry, const QDomElement &element) +{ + if (readEntry.param.isEmpty()) { + return; + } + // Adjust name + readEntry.paramName = readEntry.name; + + readEntry.name.remove(QStringLiteral("$(") + readEntry.param + QLatin1Char(')')); + // Lookup defaults for indexed entries + for (int i = 0; i <= readEntry.paramMax; i++) { + readEntry.paramDefaultValues.append(QString()); + } + + for (QDomElement e = element.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) { + QString tag = e.tagName(); + if (tag != QLatin1String("default")) { + continue; + } + QString index = e.attribute(QStringLiteral("param")); + if (index.isEmpty()) { + continue; + } + + bool ok; + int i = index.toInt(&ok); + if (!ok) { + i = readEntry.paramValues.indexOf(index); + if (i == -1) { + cerr << "Index '" << index << "' for default value is unknown." << endl; + exit (1); + } + } + + if ((i < 0) || (i > readEntry.paramMax)) { + cerr << "Index '" << i << "' for default value is out of range [0, " << readEntry.paramMax << "]." << endl; + exit (1); + } + + QString tmpDefaultValue = e.text(); + + if (e.attribute(QStringLiteral("code")) != QLatin1String("true")) { + preProcessDefault(tmpDefaultValue, readEntry.name, readEntry.type, readEntry.choices, readEntry.code, cfg); + } + + readEntry.paramDefaultValues[i] = tmpDefaultValue; + } +} + +CfgEntry *KConfigXmlParser::parseEntry(const QString &group, const QDomElement &element) +{ + CfgEntry readEntry; + readEntry.type = element.attribute(QStringLiteral("type")); + readEntry.name = element.attribute(QStringLiteral("name")); + readEntry.key = element.attribute(QStringLiteral("key")); + readEntry.hidden = element.attribute(QStringLiteral("hidden")) == QLatin1String("true");; + readEntry.group = group; + + const bool nameIsEmpty = readEntry.name.isEmpty(); + + readGroupElements(readEntry, element); + + createChangedSignal(readEntry); + validateNameAndKey(readEntry, element); + + if (readEntry.label.isEmpty()) { + readEntry.label = readEntry.key; + } + + if (readEntry.type.isEmpty()) { + readEntry.type = QStringLiteral("String"); // XXX : implicit type might be bad + } + + readParamDefaultValues(readEntry, element); + + if (!mValidNameRegexp.match(readEntry.name).hasMatch()) { + if (nameIsEmpty) + cerr << "The key '" << readEntry.key << "' can not be used as name for the entry because " + "it is not a valid name. You need to specify a valid name for this entry." << endl; + else { + cerr << "The name '" << readEntry.name << "' is not a valid name for an entry." << endl; + } + exit (1); + } + + if (mAllNames.contains(readEntry.name)) { + if (nameIsEmpty) + cerr << "The key '" << readEntry.key << "' can not be used as name for the entry because " + "it does not result in a unique name. You need to specify a unique name for this entry." << endl; + else { + cerr << "The name '" << readEntry.name << "' is not unique." << endl; + } + exit (1); + } + + mAllNames.append(readEntry.name); + + if (!hasDefaultCode(readEntry, element)) { + // TODO: Move all the options to CfgEntry. + preProcessDefault(readEntry.defaultValue, readEntry.name, readEntry.type, readEntry.choices, readEntry.code, cfg); + } + + // TODO: Try to Just return the CfgEntry we populated instead of + // creating another one to fill the code. + CfgEntry *result = new CfgEntry(); + result->group = readEntry.group; + result->type = readEntry.type; + result->key = readEntry.key; + result->name = readEntry.name; + result->labelContext = readEntry.labelContext; + result->label = readEntry.label; + result->toolTipContext = readEntry.toolTipContext; + result->toolTip = readEntry.toolTip; + result->whatsThisContext = readEntry.whatsThisContext; + result->whatsThis = readEntry.whatsThis; + result->code = readEntry.code; + result->defaultValue = readEntry.defaultValue; + result->choices = readEntry.choices; + result->signalList = readEntry.signalList; + result->hidden = readEntry.hidden; + + if (!readEntry.param.isEmpty()) { + result->param = readEntry.param; + result->paramName = readEntry.paramName; + result->paramType = readEntry.paramType; + result->paramValues = readEntry.paramValues; + result->paramDefaultValues = readEntry.paramDefaultValues; + result->paramMax = readEntry.paramMax; + } + result->min = readEntry.min; + result->max = readEntry.max; + + return result; +} + +// TODO: Change the name of the config variable. +KConfigXmlParser::KConfigXmlParser(const KConfigParameters &cfg, const QString &inputFileName) + : cfg(cfg), mInputFileName(inputFileName) +{ + mValidNameRegexp.setPattern(QRegularExpression::anchoredPattern(QStringLiteral("[a-zA-Z_][a-zA-Z0-9_]*"))); +} + +void KConfigXmlParser::start() +{ + QFile input(mInputFileName); + QDomDocument doc; + QString errorMsg; + int errorRow; + int errorCol; + if (!doc.setContent(&input, &errorMsg, &errorRow, &errorCol)) { + cerr << "Unable to load document." << endl; + cerr << "Parse error in " << mInputFileName << ", line " << errorRow << ", col " << errorCol << ": " << errorMsg << endl; + exit (1); + } + + QDomElement cfgElement = doc.documentElement(); + if (cfgElement.isNull()) { + cerr << "No document in kcfg file" << endl; + exit (1); + } + + for (QDomElement element = cfgElement.firstChildElement(); !element.isNull(); element = element.nextSiblingElement()) { + QString tag = element.tagName(); + + if (tag == QLatin1String("include")) { + readIncludeTag(element); + } else if (tag == QLatin1String("kcfgfile")) { + readKcfgfileTag(element); + } else if (tag == QLatin1String("group")) { + readGroupTag(element); + } else if (tag == QLatin1String("signal")) { + readSignalTag(element); + } + } +} + +ParseResult KConfigXmlParser::getParseResult() const +{ + return mParseResult; +} + +void KConfigXmlParser::readIncludeTag(const QDomElement &e) +{ + QString includeFile = e.text(); + if (!includeFile.isEmpty()) { + mParseResult.includes.append(includeFile); + } +} + +void KConfigXmlParser::readGroupTag(const QDomElement &e) +{ + QString group = e.attribute(QStringLiteral("name")); + if (group.isEmpty()) { + cerr << "Group without name" << endl; + exit (1); + } + + for (QDomElement e2 = e.firstChildElement(); !e2.isNull(); e2 = e2.nextSiblingElement()) { + if (e2.tagName() != QLatin1String("entry")) { + continue; + } + CfgEntry *entry = parseEntry(group, e2); + if (entry) { + mParseResult.entries.append(entry); + } else { + cerr << "Can not parse entry." << endl; + exit (1); + } + } +} + +void KConfigXmlParser::readKcfgfileTag(const QDomElement &e) +{ + mParseResult.cfgFileName = e.attribute(QStringLiteral("name")); + mParseResult.cfgFileNameArg = e.attribute(QStringLiteral("arg")).toLower() == QLatin1String("true"); + for (QDomElement e2 = e.firstChildElement(); !e2.isNull(); e2 = e2.nextSiblingElement()) { + if (e2.tagName() == QLatin1String("parameter")) { + Param p; + p.name = e2.attribute(QStringLiteral("name")); + p.type = e2.attribute(QStringLiteral("type")); + if (p.type.isEmpty()) { + p.type = QStringLiteral("String"); + } + mParseResult.parameters.append(p); + } + } +} + +void KConfigXmlParser::readSignalTag(const QDomElement &e) +{ + QString signalName = e.attribute(QStringLiteral("name")); + if (signalName.isEmpty()) { + cerr << "Signal without name." << endl; + exit (1); + } + Signal theSignal; + theSignal.name = signalName; + + for (QDomElement e2 = e.firstChildElement(); !e2.isNull(); e2 = e2.nextSiblingElement()) { + if (e2.tagName() == QLatin1String("argument")) { + Param argument; + argument.type = e2.attribute(QStringLiteral("type")); + if (argument.type.isEmpty()) { + cerr << "Signal argument without type." << endl; + exit (1); + } + argument.name= e2.text(); + theSignal.arguments.append(argument); + } else if (e2.tagName() == QLatin1String("label")) { + theSignal.label = e2.text(); + } + } + + mParseResult.signalList.append(theSignal); +} diff --git a/src/kconfig_compiler/KConfigXmlParser.h b/src/kconfig_compiler/KConfigXmlParser.h new file mode 100644 index 00000000..5dbd2745 --- /dev/null +++ b/src/kconfig_compiler/KConfigXmlParser.h @@ -0,0 +1,83 @@ +/* This file is part of the KDE libraries + Copyright (c) 2003 Cornelius Schumacher <schumacher@kde.org> + Copyright (c) 2003 Waldo Bastian <bastian@kde.org> + Copyright (c) 2003 Zack Rusin <zack@kde.org> + Copyright (c) 2006 Michaël Larouche <michael.larouche@kdemail.net> + Copyright (c) 2008 Allen Winter <winter@kde.org> + Copyright (C) 2020 Tomaz Cananbrava (tcanabrava@kde.org) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KCONFIGXMLPARSER_H +#define KCONFIGXMLPARSER_H + +#include <QDomDocument> +#include <QString> +#include <QRegularExpression> + +#include "KConfigCommonStructs.h" +#include "KConfigParameters.h" + +/* This parses the contents of a Xml file into a ParseResult Structure, + * It also fails hard: + * If start() succeeds, you can use the result, + * if start() fails, the program aborts with an error message so there's + * no possibility of generating incorrect code information. + */ +class KConfigXmlParser +{ +public: + KConfigXmlParser(const KConfigParameters &cfg, const QString &inputFileName); + + // Start the parser and reads the contents of the inputFileName into the ParseResult Structure + void start(); + + // Get the result of the parse + ParseResult getParseResult() const; + +private: + // creates a `somethingChanged` signal for every property + void createChangedSignal(CfgEntry &readEntry); + + void validateNameAndKey(CfgEntry &readEntry, const QDomElement &element); + + // TODO: Use std::optional and CfgEntry (without heap allocation) for this function + // *or* fail hard if the parse fails. + CfgEntry *parseEntry(const QString &group, const QDomElement &element); + + // Steps + void readIncludeTag(const QDomElement &element); + void readGroupTag(const QDomElement &element); + void readKcfgfileTag(const QDomElement &element); + void readSignalTag(const QDomElement &element); + + // Those are the Entries in the Xml, that represent a parameter within the <group> </group> tag. + void readParameterFromEntry(CfgEntry &entry, const QDomElement &element); + bool hasDefaultCode(CfgEntry &entry, const QDomElement &element); + void readChoicesFromEntry(CfgEntry &entry, const QDomElement &element); + void readGroupElements(CfgEntry &entry, const QDomElement &element); + void readParamDefaultValues(CfgEntry &entry, const QDomElement &element); + +private: + ParseResult mParseResult; + KConfigParameters cfg; + QString mInputFileName; + QStringList mAllNames; + QRegularExpression mValidNameRegexp; +}; + +#endif diff --git a/src/kconfig_compiler/kconfig_compiler.cpp b/src/kconfig_compiler/kconfig_compiler.cpp index 848595f2..146ff884 100644 --- a/src/kconfig_compiler/kconfig_compiler.cpp +++ b/src/kconfig_compiler/kconfig_compiler.cpp @@ -34,15 +34,19 @@ #include <QDomAttr> #include <QRegularExpression> #include <QStringList> - -#include <qcommandlineparser.h> -#include <qcommandlineoption.h> +#include <QCommandLineParser> +#include <QCommandLineOption> #include <ostream> #include <iostream> #include <stdlib.h> #include "../../kconfig_version.h" +#include "KConfigParameters.h" +#include "KConfigCommonStructs.h" +#include "KConfigHeaderGenerator.h" +#include "KConfigSourceGenerator.h" +#include "KConfigXmlParser.h" namespace { @@ -50,444 +54,7 @@ QTextStream cout(stdout); QTextStream cerr(stderr); } -QStringList allNames; -QRegularExpression *validNameRegexp; -QString This; -QString Const; - -/** - Configuration Compiler Configuration -*/ -class CfgConfig -{ -public: - CfgConfig(const QString &codegenFilename) - { - // Configure the compiler with some settings - QSettings codegenConfig(codegenFilename, QSettings::IniFormat); - - nameSpace = codegenConfig.value(QStringLiteral("NameSpace")).toString(); - className = codegenConfig.value(QStringLiteral("ClassName")).toString(); - if (className.isEmpty()) { - cerr << "Class name missing" << endl; - exit(1); - } - inherits = codegenConfig.value(QStringLiteral("Inherits")).toString(); - if (inherits.isEmpty()) { - inherits = QStringLiteral("KConfigSkeleton"); - } - visibility = codegenConfig.value(QStringLiteral("Visibility")).toString(); - if (!visibility.isEmpty()) { - visibility += ' '; - } - parentInConstructor = codegenConfig.value(QStringLiteral("ParentInConstructor"), false).toBool(); - forceStringFilename = codegenConfig.value(QStringLiteral("ForceStringFilename"), false).toBool(); - singleton = codegenConfig.value(QStringLiteral("Singleton"), false).toBool(); - staticAccessors = singleton; - customAddons = codegenConfig.value(QStringLiteral("CustomAdditions"), false).toBool(); - memberVariables = codegenConfig.value(QStringLiteral("MemberVariables")).toString(); - dpointer = (memberVariables == QLatin1String("dpointer")); - headerIncludes = codegenConfig.value(QStringLiteral("IncludeFiles"), QStringList()).toStringList(); - sourceIncludes = codegenConfig.value(QStringLiteral("SourceIncludeFiles"), QStringList()).toStringList(); - mutators = codegenConfig.value(QStringLiteral("Mutators"), QStringList()).toStringList(); - allMutators = ((mutators.count() == 1) && (mutators.at(0).toLower() == QLatin1String("true"))); - itemAccessors = codegenConfig.value(QStringLiteral("ItemAccessors"), false).toBool(); - setUserTexts = codegenConfig.value(QStringLiteral("SetUserTexts"), false).toBool(); - defaultGetters = codegenConfig.value(QStringLiteral("DefaultValueGetters"), QStringList()).toStringList(); - allDefaultGetters = (defaultGetters.count() == 1) && (defaultGetters.at(0).toLower() == QLatin1String("true")); - notifiers = codegenConfig.value(QStringLiteral("Notifiers"), QStringList()).toStringList(); - allNotifiers = ((notifiers.count() == 1) && (notifiers.at(0).toLower() == QLatin1String("true"))); - globalEnums = codegenConfig.value(QStringLiteral("GlobalEnums"), false).toBool(); - useEnumTypes = codegenConfig.value(QStringLiteral("UseEnumTypes"), false).toBool(); - const QString trString = codegenConfig.value(QStringLiteral("TranslationSystem")).toString().toLower(); - generateProperties = codegenConfig.value(QStringLiteral("GenerateProperties"), false).toBool(); - if (trString == QLatin1String("kde")) { - translationSystem = KdeTranslation; - translationDomain = codegenConfig.value(QStringLiteral("TranslationDomain")).toString(); - } else { - if (!trString.isEmpty() && trString != QLatin1String("qt")) { - cerr << "Unknown translation system, falling back to Qt tr()" << endl; - } - translationSystem = QtTranslation; - } - qCategoryLoggingName = codegenConfig.value(QStringLiteral("CategoryLoggingName"), QString()).toString(); - headerExtension = codegenConfig.value(QStringLiteral("HeaderExtension"), QStringLiteral("h")).toString(); - sourceExtension = codegenConfig.value(QStringLiteral("SourceExtension"), QStringLiteral("cpp")).toString(); - } - -public: - enum TranslationSystem { - QtTranslation, - KdeTranslation - }; - - // These are read from the .kcfgc configuration file - QString nameSpace; // The namespace for the class to be generated - QString className; // The class name to be generated - QString inherits; // The class the generated class inherits (if empty, from KConfigSkeleton) - QString visibility; - bool parentInConstructor; // The class has the optional parent parameter in its constructor - bool forceStringFilename; - bool singleton; // The class will be a singleton - bool staticAccessors; // provide or not static accessors - bool customAddons; - QString memberVariables; - QStringList headerIncludes; - QStringList sourceIncludes; - QStringList mutators; - QStringList defaultGetters; - QStringList notifiers; - QString qCategoryLoggingName; - QString headerExtension; - QString sourceExtension; - bool allMutators; - bool setUserTexts; - bool allDefaultGetters; - bool dpointer; - bool globalEnums; - bool useEnumTypes; - bool itemAccessors; - bool allNotifiers; - TranslationSystem translationSystem; - QString translationDomain; - bool generateProperties; -}; - -struct SignalArguments { - QString type; - QString variableName; -}; - -class Signal -{ -public: - Signal() : modify(false) {} - - QString name; - QString label; - QList<SignalArguments> arguments; - bool modify; -}; - -class CfgEntry -{ -public: - struct Choice { - QString name; - QString context; - QString label; - QString toolTip; - QString whatsThis; - }; - class Choices - { - public: - Choices() {} - Choices(const QList<Choice> &d, const QString &n, const QString &p) - : prefix(p), choices(d), mName(n) - { - int i = n.indexOf(QLatin1String("::")); - if (i >= 0) { - mExternalQual = n.left(i + 2); - } - } - QString prefix; - QList<Choice> choices; - const QString &name() const - { - return mName; - } - const QString &externalQualifier() const - { - return mExternalQual; - } - bool external() const - { - return !mExternalQual.isEmpty(); - } - private: - QString mName; - QString mExternalQual; - }; - - CfgEntry(const QString &group, const QString &type, const QString &key, - const QString &name, const QString &labelContext, const QString &label, - const QString &toolTipContext, const QString &toolTip, const QString &whatsThisContext, const QString &whatsThis, const QString &code, - const QString &defaultValue, const Choices &choices, const QList<Signal> &signalList, - bool hidden) - : mGroup(group), mType(type), mKey(key), mName(name), - mLabelContext(labelContext), mLabel(label), mToolTipContext(toolTipContext), mToolTip(toolTip), - mWhatsThisContext(whatsThisContext), mWhatsThis(whatsThis), - mCode(code), mDefaultValue(defaultValue), mChoices(choices), - mSignalList(signalList), mParamMax(0), mHidden(hidden) - { - } - - void setGroup(const QString &group) - { - mGroup = group; - } - QString group() const - { - return mGroup; - } - - void setType(const QString &type) - { - mType = type; - } - QString type() const - { - return mType; - } - - void setKey(const QString &key) - { - mKey = key; - } - QString key() const - { - return mKey; - } - - void setName(const QString &name) - { - mName = name; - } - QString name() const - { - return mName; - } - - void setLabelContext(const QString &labelContext) - { - mLabelContext = labelContext; - } - QString labelContext() const - { - return mLabelContext; - } - - void setLabel(const QString &label) - { - mLabel = label; - } - QString label() const - { - return mLabel; - } - - void setToolTipContext(const QString &toolTipContext) - { - mToolTipContext = toolTipContext; - } - QString toolTipContext() const - { - return mToolTipContext; - } - - void setToolTip(const QString &toolTip) - { - mToolTip = toolTip; - } - QString toolTip() const - { - return mToolTip; - } - - void setWhatsThisContext(const QString &whatsThisContext) - { - mWhatsThisContext = whatsThisContext; - } - QString whatsThisContext() const - { - return mWhatsThisContext; - } - - void setWhatsThis(const QString &whatsThis) - { - mWhatsThis = whatsThis; - } - QString whatsThis() const - { - return mWhatsThis; - } - - void setDefaultValue(const QString &d) - { - mDefaultValue = d; - } - QString defaultValue() const - { - return mDefaultValue; - } - - void setCode(const QString &d) - { - mCode = d; - } - QString code() const - { - return mCode; - } - - void setMinValue(const QString &d) - { - mMin = d; - } - QString minValue() const - { - return mMin; - } - - void setMaxValue(const QString &d) - { - mMax = d; - } - QString maxValue() const - { - return mMax; - } - - void setParam(const QString &d) - { - mParam = d; - } - QString param() const - { - return mParam; - } - - void setParamName(const QString &d) - { - mParamName = d; - } - QString paramName() const - { - return mParamName; - } - - void setParamType(const QString &d) - { - mParamType = d; - } - QString paramType() const - { - return mParamType; - } - - void setChoices(const QList<Choice> &d, const QString &n, const QString &p) - { - mChoices = Choices(d, n, p); - } - Choices choices() const - { - return mChoices; - } - - void setParamValues(const QStringList &d) - { - mParamValues = d; - } - QStringList paramValues() const - { - return mParamValues; - } - - void setParamDefaultValues(const QStringList &d) - { - mParamDefaultValues = d; - } - QString paramDefaultValue(int i) const - { - return mParamDefaultValues[i]; - } - - void setParamMax(int d) - { - mParamMax = d; - } - int paramMax() const - { - return mParamMax; - } - - void setSignalList(const QList<Signal> &value) - { - mSignalList = value; - } - QList<Signal> signalList() const - { - return mSignalList; - } - - bool hidden() const - { - return mHidden; - } - - void dump() const - { - cerr << "<entry>" << endl; - cerr << " group: " << mGroup << endl; - cerr << " type: " << mType << endl; - cerr << " key: " << mKey << endl; - cerr << " name: " << mName << endl; - cerr << " label context: " << mLabelContext << endl; - cerr << " label: " << mLabel << endl; -// whatsthis - cerr << " code: " << mCode << endl; -// cerr << " values: " << mValues.join(":") << endl; - - if (!param().isEmpty()) { - cerr << " param name: " << mParamName << endl; - cerr << " param type: " << mParamType << endl; - cerr << " paramvalues: " << mParamValues.join(QChar::fromLatin1(':')) << endl; - } - cerr << " default: " << mDefaultValue << endl; - cerr << " hidden: " << mHidden << endl; - cerr << " min: " << mMin << endl; - cerr << " max: " << mMax << endl; - cerr << "</entry>" << endl; - } - -private: - QString mGroup; - QString mType; - QString mKey; - QString mName; - QString mLabelContext; - QString mLabel; - QString mToolTipContext; - QString mToolTip; - QString mWhatsThisContext; - QString mWhatsThis; - QString mCode; - QString mDefaultValue; - QString mParam; - QString mParamName; - QString mParamType; - Choices mChoices; - QList<Signal> mSignalList; - QStringList mParamValues; - QStringList mParamDefaultValues; - int mParamMax; - bool mHidden; - QString mMin; - QString mMax; -}; - -class Param -{ -public: - QString name; - QString type; -}; - -// returns the name of an member variable -// use itemPath to know the full path -// like using d-> in case of dpointer -static QString varName(const QString &n, const CfgConfig &cfg) +QString varName(const QString &n, const KConfigParameters &cfg) { QString result; if (!cfg.dpointer) { @@ -500,7 +67,7 @@ static QString varName(const QString &n, const CfgConfig &cfg) return result; } -static QString varPath(const QString &n, const CfgConfig &cfg) +QString varPath(const QString &n, const KConfigParameters &cfg) { QString result; if (cfg.dpointer) { @@ -511,14 +78,14 @@ static QString varPath(const QString &n, const CfgConfig &cfg) return result; } -static QString enumName(const QString &n) +QString enumName(const QString &n) { QString result = QLatin1String("Enum") + n; result[4] = result[4].toUpper(); return result; } -static QString enumName(const QString &n, const CfgEntry::Choices &c) +QString enumName(const QString &n, const CfgEntry::Choices &c) { QString result = c.name(); if (result.isEmpty()) { @@ -528,11 +95,11 @@ static QString enumName(const QString &n, const CfgEntry::Choices &c) return result; } -static QString enumType(const CfgEntry *e, bool globalEnums) +QString enumType(const CfgEntry *e, bool globalEnums) { - QString result = e->choices().name(); + QString result = e->choices.name(); if (result.isEmpty()) { - result = QLatin1String("Enum") + e->name(); + result = QLatin1String("Enum") + e->name; if (!globalEnums) { result += QLatin1String("::type"); } @@ -541,7 +108,7 @@ static QString enumType(const CfgEntry *e, bool globalEnums) return result; } -static QString enumTypeQualifier(const QString &n, const CfgEntry::Choices &c) +QString enumTypeQualifier(const QString &n, const CfgEntry::Choices &c) { QString result = c.name(); if (result.isEmpty()) { @@ -555,7 +122,7 @@ static QString enumTypeQualifier(const QString &n, const CfgEntry::Choices &c) return result; } -static QString setFunction(const QString &n, const QString &className = QString()) +QString setFunction(const QString &n, const QString &className) { QString result = QLatin1String("set") + n; result[3] = result[3].toUpper(); @@ -566,12 +133,12 @@ static QString setFunction(const QString &n, const QString &className = QString( return result; } -static QString changeSignalName(const QString &n) +QString changeSignalName(const QString &n) { return n+QStringLiteral("Changed"); } -static QString getDefaultFunction(const QString &n, const QString &className = QString()) +QString getDefaultFunction(const QString &n, const QString &className) { QString result = QLatin1String("default") + n + QLatin1String("Value"); result[7] = result[7].toUpper(); @@ -582,7 +149,7 @@ static QString getDefaultFunction(const QString &n, const QString &className = Q return result; } -static QString getFunction(const QString &n, const QString &className = QString()) +QString getFunction(const QString &n, const QString &className) { QString result = n; result[0] = result[0].toLower(); @@ -593,7 +160,7 @@ static QString getFunction(const QString &n, const QString &className = QString( return result; } -static void addQuotes(QString &s) +void addQuotes(QString &s) { if (!s.startsWith(QLatin1Char('"'))) { s.prepend(QLatin1Char('"')); @@ -613,7 +180,7 @@ static QString quoteString(const QString &s) return QLatin1Char('\"') + r + QLatin1Char('\"'); } -static QString literalString(const QString &s) +QString literalString(const QString &s) { bool isAscii = true; for (int i = s.length(); i--;) @@ -628,20 +195,8 @@ static QString literalString(const QString &s) } } -static QString dumpNode(const QDomNode &node) -{ - QString msg; - QTextStream s(&msg, QIODevice::WriteOnly); - node.save(s, 0); - - msg = msg.simplified(); - if (msg.length() > 40) { - return msg.left(37) + QLatin1String("..."); - } - return msg; -} -static QString signalEnumName(const QString &signalName) +QString signalEnumName(const QString &signalName) { QString result; result = QLatin1String("signal") + signalName; @@ -650,345 +205,8 @@ static QString signalEnumName(const QString &signalName) return result; } -static void preProcessDefault(QString &defaultValue, const QString &name, - const QString &type, - const CfgEntry::Choices &choices, - QString &code, const CfgConfig &cfg) -{ - if (type == QLatin1String("String") && !defaultValue.isEmpty()) { - defaultValue = literalString(defaultValue); - - } else if (type == QLatin1String("Path") && !defaultValue.isEmpty()) { - defaultValue = literalString(defaultValue); - } else if (type == QLatin1String("Url") && !defaultValue.isEmpty()) { - // Use fromUserInput in order to support absolute paths and absolute urls, like KDE4's KUrl(QString) did. - defaultValue = QLatin1String("QUrl::fromUserInput( ") + literalString(defaultValue) + QLatin1Char(')'); - } else if ((type == QLatin1String("UrlList") || type == QLatin1String("StringList") || type == QLatin1String("PathList")) && !defaultValue.isEmpty()) { - QTextStream cpp(&code, QIODevice::WriteOnly | QIODevice::Append); - if (!code.isEmpty()) { - cpp << endl; - } - - if (type == QLatin1String("UrlList")) { - cpp << " QList<QUrl> default" << name << ";" << endl; - } else { - cpp << " QStringList default" << name << ";" << endl; - } - const QStringList defaults = defaultValue.split(QLatin1Char(',')); - QStringList::ConstIterator it; - for (it = defaults.constBegin(); it != defaults.constEnd(); ++it) { - cpp << " default" << name << ".append( "; - if (type == QLatin1String("UrlList")) { - cpp << "QUrl::fromUserInput("; - } - cpp << "QString::fromUtf8( \"" << *it << "\" ) "; - if (type == QLatin1String("UrlList")) { - cpp << ") "; - } - cpp << ");" << endl; - } - defaultValue = QLatin1String("default") + name; - - } else if (type == QLatin1String("Color") && !defaultValue.isEmpty()) { - const QRegularExpression colorRe(QRegularExpression::anchoredPattern( - QStringLiteral("\\d+,\\s*\\d+,\\s*\\d+(,\\s*\\d+)?"))); - - if (colorRe.match(defaultValue).hasMatch()) { - defaultValue = QLatin1String("QColor( ") + defaultValue + QLatin1String(" )"); - } else { - defaultValue = QLatin1String("QColor( \"") + defaultValue + QLatin1String("\" )"); - } - - } else if (type == QLatin1String("Enum")) { - QList<CfgEntry::Choice>::ConstIterator it; - for (it = choices.choices.constBegin(); it != choices.choices.constEnd(); ++it) { - if ((*it).name == defaultValue) { - if (cfg.globalEnums && choices.name().isEmpty()) { - defaultValue.prepend(choices.prefix); - } else { - defaultValue.prepend(enumTypeQualifier(name, choices) + choices.prefix); - } - break; - } - } - - } else if (type == QLatin1String("IntList")) { - QTextStream cpp(&code, QIODevice::WriteOnly | QIODevice::Append); - if (!code.isEmpty()) { - cpp << endl; - } - - cpp << " QList<int> default" << name << ";" << endl; - if (!defaultValue.isEmpty()) { - const QStringList defaults = defaultValue.split(QLatin1Char(',')); - QStringList::ConstIterator it; - for (it = defaults.constBegin(); it != defaults.constEnd(); ++it) { - cpp << " default" << name << ".append( " << *it << " );" - << endl; - } - } - defaultValue = QLatin1String("default") + name; - } -} - -CfgEntry *parseEntry(const QString &group, const QDomElement &element, const CfgConfig &cfg) -{ - bool defaultCode = false; - QString type = element.attribute(QStringLiteral("type")); - QString name = element.attribute(QStringLiteral("name")); - QString key = element.attribute(QStringLiteral("key")); - QString hidden = element.attribute(QStringLiteral("hidden")); - QString labelContext; - QString label; - QString toolTipContext; - QString toolTip; - QString whatsThisContext; - QString whatsThis; - QString defaultValue; - QString code; - QString param; - QString paramName; - QString paramType; - CfgEntry::Choices choices; - QList<Signal> signalList; - QStringList paramValues; - QStringList paramDefaultValues; - QString minValue; - QString maxValue; - int paramMax = 0; - - for (QDomElement e = element.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) { - QString tag = e.tagName(); - if (tag == QLatin1String("label")) { - label = e.text(); - labelContext = e.attribute(QStringLiteral("context")); - } else if (tag == QLatin1String("tooltip")) { - toolTip = e.text(); - toolTipContext = e.attribute(QStringLiteral("context")); - } else if (tag == QLatin1String("whatsthis")) { - whatsThis = e.text(); - whatsThisContext = e.attribute(QStringLiteral("context")); - } else if (tag == QLatin1String("min")) { - minValue = e.text(); - } else if (tag == QLatin1String("max")) { - maxValue = e.text(); - } else if (tag == QLatin1String("code")) { - code = e.text(); - } else if (tag == QLatin1String("parameter")) { - param = e.attribute(QStringLiteral("name")); - paramType = e.attribute(QStringLiteral("type")); - if (param.isEmpty()) { - cerr << "Parameter must have a name: " << dumpNode(e) << endl; - return nullptr; - } - if (paramType.isEmpty()) { - cerr << "Parameter must have a type: " << dumpNode(e) << endl; - return nullptr; - } - if ((paramType == QLatin1String("Int")) || (paramType == QLatin1String("UInt"))) { - bool ok; - paramMax = e.attribute(QStringLiteral("max")).toInt(&ok); - if (!ok) { - cerr << "Integer parameter must have a maximum (e.g. max=\"0\"): " - << dumpNode(e) << endl; - return nullptr; - } - } else if (paramType == QLatin1String("Enum")) { - for (QDomElement e2 = e.firstChildElement(); !e2.isNull(); e2 = e2.nextSiblingElement()) { - if (e2.tagName() == QLatin1String("values")) { - for (QDomElement e3 = e2.firstChildElement(); !e3.isNull(); e3 = e3.nextSiblingElement()) { - if (e3.tagName() == QLatin1String("value")) { - paramValues.append(e3.text()); - } - } - break; - } - } - if (paramValues.isEmpty()) { - cerr << "No values specified for parameter '" << param - << "'." << endl; - return nullptr; - } - paramMax = paramValues.count() - 1; - } else { - cerr << "Parameter '" << param << "' has type " << paramType - << " but must be of type int, uint or Enum." << endl; - return nullptr; - } - } else if (tag == QLatin1String("default")) { - if (e.attribute(QStringLiteral("param")).isEmpty()) { - defaultValue = e.text(); - if (e.attribute(QStringLiteral("code")) == QLatin1String("true")) { - defaultCode = true; - } - } - } else if (tag == QLatin1String("choices")) { - QString name = e.attribute(QStringLiteral("name")); - QString prefix = e.attribute(QStringLiteral("prefix")); - QList<CfgEntry::Choice> chlist; - for (QDomElement e2 = e.firstChildElement(); !e2.isNull(); e2 = e2.nextSiblingElement()) { - if (e2.tagName() == QLatin1String("choice")) { - CfgEntry::Choice choice; - choice.name = e2.attribute(QStringLiteral("name")); - if (choice.name.isEmpty()) { - cerr << "Tag <choice> requires attribute 'name'." << endl; - } - for (QDomElement e3 = e2.firstChildElement(); !e3.isNull(); e3 = e3.nextSiblingElement()) { - if (e3.tagName() == QLatin1String("label")) { - choice.label = e3.text(); - choice.context = e3.attribute(QStringLiteral("context")); - } - if (e3.tagName() == QLatin1String("tooltip")) { - choice.toolTip = e3.text(); - choice.context = e3.attribute(QStringLiteral("context")); - } - if (e3.tagName() == QLatin1String("whatsthis")) { - choice.whatsThis = e3.text(); - choice.context = e3.attribute(QStringLiteral("context")); - } - } - chlist.append(choice); - } - } - choices = CfgEntry::Choices(chlist, name, prefix); - } else if (tag == QLatin1String("emit")) { - Signal signal; - signal.name = e.attribute(QStringLiteral("signal")); - signalList.append(signal); - } - } - - if (cfg.generateProperties && (cfg.allMutators || cfg.mutators.contains(name))) { - Signal s; - s.name = changeSignalName(name); - s.modify = true; - signalList.append(s); - } - - bool nameIsEmpty = name.isEmpty(); - if (nameIsEmpty && key.isEmpty()) { - cerr << "Entry must have a name or a key: " << dumpNode(element) << endl; - return nullptr; - } - - if (key.isEmpty()) { - key = name; - } - - if (nameIsEmpty) { - name = key; - name.remove(' '); - } else if (name.contains(' ')) { - cout << "Entry '" << name << "' contains spaces! <name> elements can not contain spaces!" << endl; - name.remove(' '); - } - - if (name.contains(QStringLiteral("$("))) { - if (param.isEmpty()) { - cerr << "Name may not be parameterized: " << name << endl; - return nullptr; - } - } else { - if (!param.isEmpty()) { - cerr << "Name must contain '$(" << param << ")': " << name << endl; - return nullptr; - } - } - - if (label.isEmpty()) { - label = key; - } - - if (type.isEmpty()) { - type = QStringLiteral("String"); // XXX : implicit type might be bad - } - if (!param.isEmpty()) { - // Adjust name - paramName = name; - name.remove("$(" + param + ')'); - // Lookup defaults for indexed entries - for (int i = 0; i <= paramMax; i++) { - paramDefaultValues.append(QString()); - } - - for (QDomElement e = element.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) { - QString tag = e.tagName(); - if (tag == QLatin1String("default")) { - QString index = e.attribute(QStringLiteral("param")); - if (index.isEmpty()) { - continue; - } - - bool ok; - int i = index.toInt(&ok); - if (!ok) { - i = paramValues.indexOf(index); - if (i == -1) { - cerr << "Index '" << index << "' for default value is unknown." << endl; - return nullptr; - } - } - - if ((i < 0) || (i > paramMax)) { - cerr << "Index '" << i << "' for default value is out of range [0, " << paramMax << "]." << endl; - return nullptr; - } - - QString tmpDefaultValue = e.text(); - - if (e.attribute(QStringLiteral("code")) != QLatin1String("true")) { - preProcessDefault(tmpDefaultValue, name, type, choices, code, cfg); - } - - paramDefaultValues[i] = tmpDefaultValue; - } - } - } - - if (!validNameRegexp->match(name).hasMatch()) { - if (nameIsEmpty) - cerr << "The key '" << key << "' can not be used as name for the entry because " - "it is not a valid name. You need to specify a valid name for this entry." << endl; - else { - cerr << "The name '" << name << "' is not a valid name for an entry." << endl; - } - return nullptr; - } - - if (allNames.contains(name)) { - if (nameIsEmpty) - cerr << "The key '" << key << "' can not be used as name for the entry because " - "it does not result in a unique name. You need to specify a unique name for this entry." << endl; - else { - cerr << "The name '" << name << "' is not unique." << endl; - } - return nullptr; - } - allNames.append(name); - - if (!defaultCode) { - preProcessDefault(defaultValue, name, type, choices, code, cfg); - } - - CfgEntry *result = new CfgEntry(group, type, key, name, labelContext, label, toolTipContext, toolTip, whatsThisContext, whatsThis, - code, defaultValue, choices, signalList, - hidden == QLatin1String("true")); - if (!param.isEmpty()) { - result->setParam(param); - result->setParamName(paramName); - result->setParamType(paramType); - result->setParamValues(paramValues); - result->setParamDefaultValues(paramDefaultValues); - result->setParamMax(paramMax); - } - result->setMinValue(minValue); - result->setMaxValue(maxValue); - - return result; -} - -static bool isUnsigned(const QString &type) +bool isUnsigned(const QString &type) { if (type == QLatin1String("UInt")) { return true; @@ -1168,47 +386,47 @@ QString itemType(const QString &type) return t; } -static QString itemDeclaration(const CfgEntry *e, const CfgConfig &cfg) +QString itemDeclaration(const CfgEntry *e, const KConfigParameters &cfg) { if (cfg.itemAccessors) { return QString(); } QString type; - if (!e->signalList().isEmpty()) { + if (!e->signalList.isEmpty()) { type = QStringLiteral("KConfigCompilerSignallingItem"); } else { - type = cfg.inherits + "::Item" + itemType(e->type()); + type = cfg.inherits + "::Item" + itemType(e->type); } - QString fCap = e->name(); + QString fCap = e->name; fCap[0] = fCap[0].toUpper(); return " " + type + " *item" + fCap + - ( (!e->param().isEmpty())?(QStringLiteral("[%1]").arg(e->paramMax()+1)) : QString()) + ";\n"; + ( (!e->param.isEmpty())?(QStringLiteral("[%1]").arg(e->paramMax+1)) : QString()) + ";\n"; } // returns the name of an item variable // use itemPath to know the full path // like using d-> in case of dpointer -static QString itemVar(const CfgEntry *e, const CfgConfig &cfg) +QString itemVar(const CfgEntry *e, const KConfigParameters &cfg) { QString result; if (cfg.itemAccessors) { if (!cfg.dpointer) { - result = 'm' + e->name() + "Item"; + result = 'm' + e->name + "Item"; result[1] = result[1].toUpper(); } else { - result = e->name() + "Item"; + result = e->name + "Item"; result[0] = result[0].toLower(); } } else { - result = "item" + e->name(); + result = "item" + e->name; result[4] = result[4].toUpper(); } return result; } -static QString itemPath(const CfgEntry *e, const CfgConfig &cfg) +QString itemPath(const CfgEntry *e, const KConfigParameters &cfg) { QString result; if (cfg.dpointer) { @@ -1220,18 +438,18 @@ static QString itemPath(const CfgEntry *e, const CfgConfig &cfg) } QString newItem(const CfgEntry* entry, const QString &key, const QString& defaultValue, - const CfgConfig &cfg, const QString ¶m = QString()) { + const KConfigParameters &cfg, const QString ¶m) { - QList<Signal> sigs = entry->signalList(); + QList<Signal> sigs = entry->signalList; QString t; if (!sigs.isEmpty()) { t += QLatin1String("new KConfigCompilerSignallingItem("); } - t += "new "+ cfg.inherits + "::Item" + itemType(entry->type()) + "( currentGroup(), " - + key + ", " + varPath( entry->name(), cfg ) + param; + t += "new "+ cfg.inherits + "::Item" + itemType(entry->type) + "( currentGroup(), " + + key + ", " + varPath( entry->name, cfg ) + param; - if (entry->type() == QLatin1String("Enum")) { - t += ", values" + entry->name(); + if (entry->type == QLatin1String("Enum")) { + t += ", values" + entry->name; } if (!defaultValue.isEmpty()) { t += QLatin1String(", ") + defaultValue; @@ -1255,11 +473,11 @@ QString newItem(const CfgEntry* entry, const QString &key, const QString& defaul QString paramString(const QString &s, const CfgEntry *e, int i) { QString result = s; - QString needle = "$(" + e->param() + ')'; + QString needle = "$(" + e->param + ')'; if (result.contains(needle)) { QString tmp; - if (e->paramType() == QLatin1String("Enum")) { - tmp = e->paramValues().at(i); + if (e->paramType == QLatin1String("Enum")) { + tmp = e->paramValues.at(i); } else { tmp = QString::number(i); } @@ -1289,12 +507,12 @@ QString paramString(const QString &group, const QList<Param> ¶meters) return "QStringLiteral( \"" + paramString + "\" )" + arguments; } -QString translatedString(const CfgConfig &cfg, const QString &string, const QString &context = QString(), const QString ¶m = QString(), const QString ¶mValue = QString()) +QString translatedString(const KConfigParameters &cfg, const QString &string, const QString &context, const QString ¶m, const QString ¶mValue) { QString result; switch (cfg.translationSystem) { - case CfgConfig::QtTranslation: + case KConfigParameters::QtTranslation: if (!context.isEmpty()) { result += "/*: " + context + " */ QCoreApplication::translate(\""; } else { @@ -1303,7 +521,7 @@ QString translatedString(const CfgConfig &cfg, const QString &string, const QStr result += cfg.className + "\", "; break; - case CfgConfig::KdeTranslation: + case KConfigParameters::KdeTranslation: if (!cfg.translationDomain.isEmpty() && !context.isEmpty()) { result += "i18ndc(" + quoteString(cfg.translationDomain) + ", " + quoteString(context) + ", "; } else if (!cfg.translationDomain.isEmpty()) { @@ -1330,62 +548,36 @@ QString translatedString(const CfgConfig &cfg, const QString &string, const QStr } /* int i is the value of the parameter */ -QString userTextsFunctions(CfgEntry *e, const CfgConfig &cfg, QString itemVarStr = QString(), const QString &i = QString()) +QString userTextsFunctions(const CfgEntry *e, const KConfigParameters &cfg, QString itemVarStr, const QString &i) { QString txt; if (itemVarStr.isNull()) { itemVarStr = itemPath(e, cfg); } - if (!e->label().isEmpty()) { + if (!e->label.isEmpty()) { txt += " " + itemVarStr + "->setLabel( "; - txt += translatedString(cfg, e->label(), e->labelContext(), e->param(), i); + txt += translatedString(cfg, e->label, e->labelContext, e->param, i); txt += QLatin1String(" );\n"); } - if (!e->toolTip().isEmpty()) { + if (!e->toolTip.isEmpty()) { txt += " " + itemVarStr + "->setToolTip( "; - txt += translatedString(cfg, e->toolTip(), e->toolTipContext(), e->param(), i); + txt += translatedString(cfg, e->toolTip, e->toolTipContext, e->param, i); txt += QLatin1String(" );\n"); } - if (!e->whatsThis().isEmpty()) { + if (!e->whatsThis.isEmpty()) { txt += " " + itemVarStr + "->setWhatsThis( "; - txt += translatedString(cfg, e->whatsThis(), e->whatsThisContext(), e->param(), i); + txt += translatedString(cfg, e->whatsThis, e->whatsThisContext, e->param, i); txt += QLatin1String(" );\n"); } return txt; } -// returns the member accesor implementation -// which should go in the h file if inline -// or the cpp file if not inline -QString memberAccessorBody(CfgEntry *e, bool globalEnums, const CfgConfig &cfg) -{ - QString result; - QTextStream out(&result, QIODevice::WriteOnly); - QString n = e->name(); - QString t = e->type(); - bool useEnumType = cfg.useEnumTypes && t == QLatin1String("Enum"); - - out << "return "; - if (useEnumType) { - out << "static_cast<" << enumType(e, globalEnums) << ">("; - } - out << This << varPath(n, cfg); - if (!e->param().isEmpty()) { - out << "[i]"; - } - if (useEnumType) { - out << ")"; - } - out << ";" << endl; - - return result; -} // returns the member mutator implementation // which should go in the h file if inline // or the cpp file if not inline - -void addDebugMethod(QTextStream &out, const CfgConfig &cfg, const QString &n) +//TODO: Fix add Debug Method, it should also take the debug string. +void addDebugMethod(QTextStream &out, const KConfigParameters &cfg, const QString &n) { if (cfg.qCategoryLoggingName.isEmpty()) { out << " qDebug() << \"" << setFunction(n); @@ -1394,101 +586,30 @@ void addDebugMethod(QTextStream &out, const CfgConfig &cfg, const QString &n) } } -QString memberMutatorBody(CfgEntry *e, const CfgConfig &cfg) -{ - QString result; - QTextStream out(&result, QIODevice::WriteOnly); - QString n = e->name(); - QString t = e->type(); - - if (!e->minValue().isEmpty()) { - if (e->minValue() != QLatin1String("0") || !isUnsigned(t)) { // skip writing "if uint<0" (#187579) - out << "if (v < " << e->minValue() << ")" << endl; - out << "{" << endl; - addDebugMethod(out, cfg, n); - out << ": value \" << v << \" is less than the minimum value of "; - out << e->minValue() << "\";" << endl; - out << " v = " << e->minValue() << ";" << endl; - out << "}" << endl; - } - } - - if (!e->maxValue().isEmpty()) { - out << endl << "if (v > " << e->maxValue() << ")" << endl; - out << "{" << endl; - addDebugMethod(out, cfg, n); - out << ": value \" << v << \" is greater than the maximum value of "; - out << e->maxValue() << "\";" << endl; - out << " v = " << e->maxValue() << ";" << endl; - out << "}" << endl << endl; - } - - const QString varExpression = This + varPath(n, cfg) + (e->param().isEmpty() ? QString() : QStringLiteral("[i]")); - - const bool hasBody = !e->signalList().empty() || cfg.generateProperties; - out << "if ("; - if (hasBody) { - out << "v != " << varExpression << " && "; - } - out << "!" << This << "isImmutable( QStringLiteral( \""; - if (!e->param().isEmpty()) { - out << e->paramName().replace("$(" + e->param() + ")", QLatin1String("%1")) << "\" ).arg( "; - if (e->paramType() == QLatin1String("Enum")) { - out << "QLatin1String( "; - - if (cfg.globalEnums) { - out << enumName(e->param()) << "ToString[i]"; - } else { - out << enumName(e->param()) << "::enumToString[i]"; - } - - out << " )"; - } else { - out << "i"; - } - out << " )"; - } else { - out << n << "\" )"; - } - out << " ))" << (hasBody ? " {" : "") << endl; - out << " " << varExpression << " = v;" << endl; - - const auto listSignal = e->signalList(); - for (const Signal &signal : listSignal) { - if (signal.modify) { - out << " Q_EMIT " << This << signal.name << "();" << endl; - } else { - out << " " << This << varPath(QStringLiteral("settingsChanged"), cfg) << " |= " << signalEnumName(signal.name) << ";" << endl; - } - } - if (hasBody) { - out << "}" << endl; - } - - return result; -} // returns the member get default implementation // which should go in the h file if inline // or the cpp file if not inline -QString memberGetDefaultBody(CfgEntry *e) +QString memberGetDefaultBody(const CfgEntry *e) { - QString result = e->code(); + QString result = e->code; QTextStream out(&result, QIODevice::WriteOnly); out << endl; - if (!e->param().isEmpty()) { + if (!e->param.isEmpty()) { out << " switch (i) {" << endl; - for (int i = 0; i <= e->paramMax(); ++i) { - if (!e->paramDefaultValue(i).isEmpty()) { - out << " case " << i << ": return " << e->paramDefaultValue(i) << ';' << endl; + for (int i = 0; i <= e->paramMax; ++i) { + if (!e->paramDefaultValues[i].isEmpty()) { + out << " case " << i << ": return " << e->paramDefaultValues[i] << ';' << endl; } } + QString defaultValue = e->defaultValue; + out << " default:" << endl; - out << " return " << e->defaultValue().replace("$(" + e->param() + ')', QLatin1String("i")) << ';' << endl; + out << " return " << defaultValue.replace("$(" + e->param + ')', QLatin1String("i")) << ';' << endl; out << " }" << endl; } else { - out << " return " << e->defaultValue() << ';'; + out << " return " << e->defaultValue << ';'; } return result; @@ -1497,13 +618,13 @@ QString memberGetDefaultBody(CfgEntry *e) // returns the item accesor implementation // which should go in the h file if inline // or the cpp file if not inline -QString itemAccessorBody(CfgEntry *e, const CfgConfig &cfg) +QString itemAccessorBody(const CfgEntry *e, const KConfigParameters &cfg) { QString result; QTextStream out(&result, QIODevice::WriteOnly); out << "return " << itemPath(e, cfg); - if (!e->param().isEmpty()) { + if (!e->param.isEmpty()) { out << "[i]"; } out << ";" << endl; @@ -1529,30 +650,42 @@ QString indent(QString text, int spaces) return result; } -// adds as many 'namespace foo {' lines to p_out as -// there are namespaces in p_ns -void beginNamespaces(const QString &p_ns, QTextStream &p_out) +bool hasErrors(KConfigXmlParser &parser, const ParseResult& parseResult, const KConfigParameters &cfg) { - if (!p_ns.isEmpty()) { - const QStringList nameSpaces = p_ns.split(QStringLiteral("::")); - for (const QString &ns : nameSpaces) { - p_out << "namespace " << ns << " {" << endl; - } - p_out << endl; + if (cfg.className.isEmpty()) { + cerr << "Class name missing" << endl; + return true; } -} -// adds as many '}' lines to p_out as -// there are namespaces in p_ns -void endNamespaces(const QString &p_ns, QTextStream &p_out) -{ - if (!p_ns.isEmpty()) { - const int namespaceCount = p_ns.count(QStringLiteral("::")) + 1; - for (int i = 0; i < namespaceCount; ++i) { - p_out << "}" << endl; - } - p_out << endl; + if (cfg.singleton && !parseResult.parameters.isEmpty()) { + cerr << "Singleton class can not have parameters" << endl; + return true; } + + if (!parseResult.cfgFileName.isEmpty() && parseResult.cfgFileNameArg) { + cerr << "Having both a fixed filename and a filename as argument is not possible." << endl; + return true; + } + + /* TODO: For some reason some configuration files prefer to have *no* entries + * at all in it, and the generated code is mostly bogus as KConfigXT will not + * handle save / load / properties, etc, nothing. + * + * The first of those files that I came across are qmakebuilderconfig.kcfg from the KDevelop + * project. + * I think we should remove the possibility of creating configuration classes from configuration + * files that don't really have configuration in it. but I'm changing this right now to allow + * kdevelop files to pass. + * + * Remove for KDE 6 + * (to make things more interesting, it failed in a code that's never used within KDevelop... ) + */ + if (parseResult.entries.isEmpty()) { + cerr << "No entries." << endl; + return false; + } + + return false; } int main(int argc, char **argv) @@ -1561,22 +694,22 @@ int main(int argc, char **argv) app.setApplicationName(QStringLiteral("kconfig_compiler")); app.setApplicationVersion(QStringLiteral(KCONFIG_VERSION_STRING)); - validNameRegexp = new QRegularExpression(QRegularExpression::anchoredPattern( - QStringLiteral("[a-zA-Z_][a-zA-Z0-9_]*"))); - QString inputFilename, codegenFilename; + QCommandLineOption targetDirectoryOption(QStringList { QStringLiteral("d"), QStringLiteral("directory") }, + QCoreApplication::translate("main", "Directory to generate files in [.]"), + QCoreApplication::translate("main", "directory"), QStringLiteral(".")); + + QCommandLineOption licenseOption ( + QStringList { QStringLiteral("l"), QStringLiteral("license") }, + QCoreApplication::translate("main", "Display software license.")); + QCommandLineParser parser; parser.addPositionalArgument(QStringLiteral("file.kcfg"), QStringLiteral("Input kcfg XML file")); parser.addPositionalArgument(QStringLiteral("file.kcfgc"), QStringLiteral("Code generation options file")); - QCommandLineOption targetDirectoryOption(QStringList { QStringLiteral("d"), QStringLiteral("directory") }, - QCoreApplication::translate("main", "Directory to generate files in [.]"), - QCoreApplication::translate("main", "directory"), QStringLiteral(".")); parser.addOption(targetDirectoryOption); - - QCommandLineOption licenseOption (QStringList { QStringLiteral("l"), QStringLiteral("license") }, QCoreApplication::translate("main", "Display software license.")); parser.addOption (licenseOption); parser.addVersionOption(); @@ -1584,7 +717,7 @@ int main(int argc, char **argv) parser.process(app); if (parser.isSet(licenseOption)) { - cout << "Copyright 2003 Cornelius Schumacher, Waldo Bastian, Zack Rusin," << endl; + cout << "Copyright 2003 Cornelius Schumacher, Waldo Bastian, Zack Rusin," << endl; cout << " Reinhold Kainhofer, Duncan Mac-Vicar P., Harald Fernengel" << endl; cout << "This program comes with ABSOLUTELY NO WARRANTY." << endl; cout << "You may redistribute copies of this program" << endl; @@ -1595,16 +728,18 @@ int main(int argc, char **argv) const QStringList args = parser.positionalArguments(); if (args.count() < 2) { - cerr << "Too few arguments." << endl; - return 1; + cerr << "Too few arguments." << endl; + return 1; } + if (args.count() > 2) { - cerr << "Too many arguments." << endl; - return 1; + cerr << "Too many arguments." << endl; + return 1; } inputFilename = args.at(0); codegenFilename = args.at(1); + // TODO: Transform baseDir into a helper. QString baseDir = parser.value(targetDirectoryOption); #ifdef Q_OS_WIN @@ -1614,1124 +749,34 @@ int main(int argc, char **argv) #endif baseDir.append("/"); - if (!codegenFilename.endsWith(QLatin1String(".kcfgc"))) { - cerr << "Codegen options file must have extension .kcfgc" << endl; - return 1; - } - QString baseName = QFileInfo(codegenFilename).fileName(); - baseName = baseName.left(baseName.length() - 6); - - CfgConfig cfg = CfgConfig(codegenFilename); - - QFile input(inputFilename); - - QDomDocument doc; - QString errorMsg; - int errorRow; - int errorCol; - if (!doc.setContent(&input, &errorMsg, &errorRow, &errorCol)) { - cerr << "Unable to load document." << endl; - cerr << "Parse error in " << inputFilename << ", line " << errorRow << ", col " << errorCol << ": " << errorMsg << endl; - return 1; - } - - QDomElement cfgElement = doc.documentElement(); - - if (cfgElement.isNull()) { - cerr << "No document in kcfg file" << endl; - return 1; - } - - QString cfgFileName; - bool cfgFileNameArg = false; - QList<Param> parameters; - QList<Signal> signalList; - QStringList includes; - - QList<CfgEntry *> entries; - - for (QDomElement e = cfgElement.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) { - QString tag = e.tagName(); - - if (tag == QLatin1String("include")) { - QString includeFile = e.text(); - if (!includeFile.isEmpty()) { - includes.append(includeFile); - } - - } else if (tag == QLatin1String("kcfgfile")) { - cfgFileName = e.attribute(QStringLiteral("name")); - cfgFileNameArg = e.attribute(QStringLiteral("arg")).toLower() == QLatin1String("true"); - for (QDomElement e2 = e.firstChildElement(); !e2.isNull(); e2 = e2.nextSiblingElement()) { - if (e2.tagName() == QLatin1String("parameter")) { - Param p; - p.name = e2.attribute(QStringLiteral("name")); - p.type = e2.attribute(QStringLiteral("type")); - if (p.type.isEmpty()) { - p.type = QStringLiteral("String"); - } - parameters.append(p); - } - } - - } else if (tag == QLatin1String("group")) { - QString group = e.attribute(QStringLiteral("name")); - if (group.isEmpty()) { - cerr << "Group without name" << endl; - return 1; - } - for (QDomElement e2 = e.firstChildElement(); !e2.isNull(); e2 = e2.nextSiblingElement()) { - if (e2.tagName() != QLatin1String("entry")) { - continue; - } - CfgEntry *entry = parseEntry(group, e2, cfg); - if (entry) { - entries.append(entry); - } else { - cerr << "Can not parse entry." << endl; - return 1; - } - } - } else if (tag == QLatin1String("signal")) { - QString signalName = e.attribute(QStringLiteral("name")); - if (signalName.isEmpty()) { - cerr << "Signal without name." << endl; - return 1; - } - Signal theSignal; - theSignal.name = signalName; - - for (QDomElement e2 = e.firstChildElement(); !e2.isNull(); e2 = e2.nextSiblingElement()) { - if (e2.tagName() == QLatin1String("argument")) { - SignalArguments argument; - argument.type = e2.attribute(QStringLiteral("type")); - if (argument.type.isEmpty()) { - cerr << "Signal argument without type." << endl; - return 1; - } - argument.variableName = e2.text(); - theSignal.arguments.append(argument); - } else if (e2.tagName() == QLatin1String("label")) { - theSignal.label = e2.text(); - } - } - signalList.append(theSignal); - } - } - - if (cfg.className.isEmpty()) { - cerr << "Class name missing" << endl; - return 1; - } - - if (cfg.singleton && !parameters.isEmpty()) { - cerr << "Singleton class can not have parameters" << endl; - return 1; - } - - if (!cfgFileName.isEmpty() && cfgFileNameArg) { - cerr << "Having both a fixed filename and a filename as argument is not possible." << endl; - return 1; - } - - if (entries.isEmpty()) { - cerr << "No entries." << endl; - } - -#if 0 - CfgEntry *cfg; - for (cfg = entries.first(); cfg; cfg = entries.next()) { - cfg->dump(); - } -#endif - - QString headerFileName = baseName + '.' + cfg.headerExtension; - QString implementationFileName = baseName + '.' + cfg.sourceExtension; - QString mocFileName = baseName + ".moc"; - QString cppPreamble; // code to be inserted at the beginnin of the cpp file, e.g. initialization of static values - - QFile header(baseDir + headerFileName); - if (!header.open(QIODevice::WriteOnly)) { - cerr << "Can not open '" << baseDir << headerFileName << "for writing." << endl; - return 1; - } - - QTextStream h(&header); - - h.setCodec("utf-8"); - - h << "// This file is generated by kconfig_compiler_kf5 from " << QFileInfo(inputFilename).fileName() << "." << endl; - h << "// All changes you do to this file will be lost." << endl; - - h << "#ifndef " << (!cfg.nameSpace.isEmpty() ? QString(QString(cfg.nameSpace).replace(QLatin1String("::"), QLatin1String("_")).toUpper() + '_') : QLatin1String("")) - << cfg.className.toUpper() << "_H" << endl; - h << "#define " << (!cfg.nameSpace.isEmpty() ? QString(QString(cfg.nameSpace).replace(QLatin1String("::"), QLatin1String("_")).toUpper() + '_') : QLatin1String("")) - << cfg.className.toUpper() << "_H" << endl << endl; - - // Includes - QStringList::ConstIterator it; - for (it = cfg.headerIncludes.constBegin(); it != cfg.headerIncludes.constEnd(); ++it) { - if ((*it).startsWith('"')) { - h << "#include " << *it << endl; - } else { - h << "#include <" << *it << ">" << endl; - } - } - - if (!cfg.headerIncludes.isEmpty()) { - h << endl; - } - - if (!cfg.singleton && parameters.isEmpty()) { - h << "#include <qglobal.h>" << endl; - } - - if (cfg.inherits == QLatin1String("KCoreConfigSkeleton")) { - h << "#include <kcoreconfigskeleton.h>" << endl; - } else { - h << "#include <kconfigskeleton.h>" << endl; - } - - h << "#include <QCoreApplication>" << endl; - h << "#include <QDebug>" << endl << endl; - - // Includes - for (it = includes.constBegin(); it != includes.constEnd(); ++it) { - if ((*it).startsWith('"')) { - h << "#include " << *it << endl; - } else { - h << "#include <" << *it << ">" << endl; - } - } - - beginNamespaces(cfg.nameSpace, h); - - // Private class declaration - if (cfg.dpointer) { - h << "class " << cfg.className << "Private;" << endl << endl; - } - - // Class declaration header - h << "class " << cfg.visibility << cfg.className << " : public " << cfg.inherits << endl; - - h << "{" << endl; - // Add Q_OBJECT macro if the config need signals. - if (!signalList.isEmpty() || cfg.generateProperties) { - h << " Q_OBJECT" << endl; - } - h << " public:" << endl; - - // enums - QList<CfgEntry *>::ConstIterator itEntry; - for (itEntry = entries.constBegin(); itEntry != entries.constEnd(); ++itEntry) { - const CfgEntry::Choices &choices = (*itEntry)->choices(); - const QList<CfgEntry::Choice> chlist = choices.choices; - if (!chlist.isEmpty()) { - QStringList values; - QList<CfgEntry::Choice>::ConstIterator itChoice; - for (itChoice = chlist.constBegin(); itChoice != chlist.constEnd(); ++itChoice) { - values.append(choices.prefix + (*itChoice).name); - } - if (choices.name().isEmpty()) { - if (cfg.globalEnums) { - h << " enum " << enumName((*itEntry)->name(), (*itEntry)->choices()) << " { " << values.join(QStringLiteral(", ")) << " };" << endl; - } else { - // Create an automatically named enum - h << " class " << enumName((*itEntry)->name(), (*itEntry)->choices()) << endl; - h << " {" << endl; - h << " public:" << endl; - h << " enum type { " << values.join(QStringLiteral(", ")) << ", COUNT };" << endl; - h << " };" << endl; - } - } else if (!choices.external()) { - // Create a named enum - h << " enum " << enumName((*itEntry)->name(), (*itEntry)->choices()) << " { " << values.join(QStringLiteral(", ")) << " };" << endl; - } - } - const QStringList values = (*itEntry)->paramValues(); - if (!values.isEmpty()) { - if (cfg.globalEnums) { - // ### FIXME!! - // make the following string table an index-based string search! - // ### - h << " enum " << enumName((*itEntry)->param()) << " { " << values.join(QStringLiteral(", ")) << " };" << endl; - h << " static const char* const " << enumName((*itEntry)->param()) << "ToString[];" << endl; - cppPreamble += "const char* const " + cfg.className + "::" + enumName((*itEntry)->param()) + - "ToString[] = { \"" + values.join(QStringLiteral("\", \"")) + "\" };\n"; - } else { - h << " class " << enumName((*itEntry)->param()) << endl; - h << " {" << endl; - h << " public:" << endl; - h << " enum type { " << values.join(QStringLiteral(", ")) << ", COUNT };" << endl; - h << " static const char* const enumToString[];" << endl; - h << " };" << endl; - cppPreamble += "const char* const " + cfg.className + "::" + enumName((*itEntry)->param()) + - "::enumToString[] = { \"" + values.join(QStringLiteral("\", \"")) + "\" };\n"; - } - } - } - h << endl; - - - // Constructor or singleton accessor - if (!cfg.singleton) { - h << " " << cfg.className << "("; - if (cfgFileNameArg) { - if (cfg.forceStringFilename) - h << " const QString &cfgfilename" - << (parameters.isEmpty() ? " = QString()" : ", "); - else - h << " KSharedConfig::Ptr config" - << (parameters.isEmpty() ? " = KSharedConfig::openConfig()" : ", "); - } - for (QList<Param>::ConstIterator it = parameters.constBegin(); - it != parameters.constEnd(); ++it) { - if (it != parameters.constBegin()) { - h << ","; - } - h << " " << param((*it).type) << " " << (*it).name; - } - if (cfg.parentInConstructor) { - if (cfgFileNameArg || !parameters.isEmpty()) { - h << ","; - } - h << " QObject *parent = nullptr"; - } - h << " );" << endl; - } else { - h << " static " << cfg.className << " *self();" << endl; - if (cfgFileNameArg) { - h << " static void instance(const QString& cfgfilename);" << endl; - h << " static void instance(KSharedConfig::Ptr config);" << endl; - } - } - - // Destructor - h << " ~" << cfg.className << "();" << endl << endl; - - // global variables - if (cfg.staticAccessors) { - This = QStringLiteral("self()->"); - } else { - Const = QStringLiteral(" const"); - } - - for (itEntry = entries.constBegin(); itEntry != entries.constEnd(); ++itEntry) { - QString n = (*itEntry)->name(); - QString t = (*itEntry)->type(); - - // Manipulator - if (cfg.allMutators || cfg.mutators.contains(n)) { - h << " /**" << endl; - h << " Set " << (*itEntry)->label() << endl; - h << " */" << endl; - if (cfg.staticAccessors) { - h << " static" << endl; - } - h << " void " << setFunction(n) << "( "; - if (!(*itEntry)->param().isEmpty()) { - h << cppType((*itEntry)->paramType()) << " i, "; - } - if (cfg.useEnumTypes && t == QLatin1String("Enum")) { - h << enumType(*itEntry, cfg.globalEnums); - } else { - h << param(t); - } - h << " v )"; - // function body inline only if not using dpointer - // for BC mode - if (!cfg.dpointer) { - h << endl << " {" << endl; - h << indent(memberMutatorBody(*itEntry, cfg), 6); - h << " }" << endl; - } else { - h << ";" << endl; - } - } - h << endl; - - QString returnType; - if (cfg.useEnumTypes && t == QLatin1String("Enum")) { - returnType = enumType(*itEntry, cfg.globalEnums); - } else { - returnType = cppType(t); - } - - if (cfg.generateProperties) { - h << " Q_PROPERTY(" << returnType << ' ' << getFunction(n); - h << " READ " << getFunction(n); - if (cfg.allMutators || cfg.mutators.contains(n)) { - const QString signal = changeSignalName(n); - h << " WRITE " << setFunction(n); - h << " NOTIFY " << signal; - - //If we have the modified signal, we'll also need - //the changed signal as well - Signal s; - s.name = signal; - s.modify = true; - signalList.append(s); - } else { - h << " CONSTANT"; - } - h << ")" << endl; - } - // Accessor - h << " /**" << endl; - h << " Get " << (*itEntry)->label() << endl; - h << " */" << endl; - if (cfg.staticAccessors) { - h << " static" << endl; - } - h << " "; - h << returnType; - h << " " << getFunction(n) << "("; - if (!(*itEntry)->param().isEmpty()) { - h << " " << cppType((*itEntry)->paramType()) << " i "; - } - h << ")" << Const; - // function body inline only if not using dpointer - // for BC mode - if (!cfg.dpointer) { - h << endl << " {" << endl; - h << indent(memberAccessorBody(*itEntry, cfg.globalEnums, cfg), 6); - h << " }" << endl; - } else { - h << ";" << endl; - } - - // Default value Accessor - if ((cfg.allDefaultGetters || cfg.defaultGetters.contains(n)) && !(*itEntry)->defaultValue().isEmpty()) { - h << endl; - h << " /**" << endl; - h << " Get " << (*itEntry)->label() << " default value" << endl; - h << " */" << endl; - if (cfg.staticAccessors) { - h << " static" << endl; - } - h << " "; - if (cfg.useEnumTypes && t == QLatin1String("Enum")) { - h << enumType(*itEntry, cfg.globalEnums); - } else { - h << cppType(t); - } - h << " " << getDefaultFunction(n) << "("; - if (!(*itEntry)->param().isEmpty()) { - h << " " << cppType((*itEntry)->paramType()) << " i "; - } - h << ")" << Const << endl; - h << " {" << endl; - h << " return "; - if (cfg.useEnumTypes && t == QLatin1String("Enum")) { - h << "static_cast<" << enumType(*itEntry, cfg.globalEnums) << ">("; - } - h << getDefaultFunction(n) << "_helper("; - if (!(*itEntry)->param().isEmpty()) { - h << " i "; - } - h << ")"; - if (cfg.useEnumTypes && t == QLatin1String("Enum")) { - h << ")"; - } - h << ";" << endl; - h << " }" << endl; - } - - // Item accessor - if (cfg.itemAccessors) { - h << endl; - h << " /**" << endl; - h << " Get Item object corresponding to " << n << "()" - << endl; - h << " */" << endl; - h << " Item" << itemType((*itEntry)->type()) << " *" - << getFunction(n) << "Item("; - if (!(*itEntry)->param().isEmpty()) { - h << " " << cppType((*itEntry)->paramType()) << " i "; - } - h << ")"; - if (!cfg.dpointer) { - h << endl << " {" << endl; - h << indent(itemAccessorBody((*itEntry), cfg), 6); - h << " }" << endl; - } else { - h << ";" << endl; - } - } - - h << endl; - } - - // Signal definition. - const bool hasSignals = !signalList.isEmpty(); - bool hasNonModifySignals = false; - if (hasSignals) { - h << "\n enum {" << endl; - unsigned val = 1; - QList<Signal>::ConstIterator it, itEnd = signalList.constEnd(); - for (it = signalList.constBegin(); it != itEnd; val <<= 1) { - hasNonModifySignals |= !it->modify; - if (!val) { - cerr << "Too many signals to create unique bit masks" << endl; - exit(1); - } - Signal signal = *it; - h << " " << signalEnumName(signal.name) << " = 0x" << - #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) - hex - #else - Qt::hex - #endif - << val; - if (++it != itEnd) { - h << ","; - } - h << endl; - } - h << " };" << - #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) - dec - #else - Qt::dec - #endif - << endl << endl; - - h << " Q_SIGNALS:"; - for (const Signal &signal : qAsConst(signalList)) { - h << endl; - if (!signal.label.isEmpty()) { - h << " /**" << endl; - h << " " << signal.label << endl; - h << " */" << endl; - } - h << " void " << signal.name << "("; - QList<SignalArguments>::ConstIterator it, itEnd = signal.arguments.constEnd(); - for (it = signal.arguments.constBegin(); it != itEnd;) { - SignalArguments argument = *it; - QString type = param(argument.type); - if (cfg.useEnumTypes && argument.type == QLatin1String("Enum")) { - for (int i = 0, end = entries.count(); i < end; ++i) { - if (entries[i]->name() == argument.variableName) { - type = enumType(entries[i], cfg.globalEnums); - break; - } - } - } - h << type << " " << argument.variableName; - if (++it != itEnd) { - h << ", "; - } - } - h << ");" << endl; - } - h << endl; - - h << " private:" << endl; - h << " void itemChanged(quint64 flags);" << endl; - h << endl; - } - - h << " protected:" << endl; - - // Private constructor for singleton - if (cfg.singleton) { - h << " " << cfg.className << "("; - if (cfgFileNameArg) { - h << "KSharedConfig::Ptr config"; - } - if (cfg.parentInConstructor) { - if (cfgFileNameArg) { - h << ", "; - } - h << "QObject *parent = nullptr"; - } - h << ");" << endl; - h << " friend class " << cfg.className << "Helper;" << endl << endl; - } - - if (hasNonModifySignals) { - h << " bool usrSave() override;" << endl; - } - - // Member variables - if (!cfg.memberVariables.isEmpty() && cfg.memberVariables != QLatin1String("private") && cfg.memberVariables != QLatin1String("dpointer")) { - h << " " << cfg.memberVariables << ":" << endl; - } - - // Class Parameters - for (QList<Param>::ConstIterator it = parameters.constBegin(); - it != parameters.constEnd(); ++it) { - h << " " << cppType((*it).type) << " mParam" << (*it).name << ";" << endl; - } - - if (cfg.memberVariables != QLatin1String("dpointer")) { - QString group; - for (itEntry = entries.constBegin(); itEntry != entries.constEnd(); ++itEntry) { - if ((*itEntry)->group() != group) { - group = (*itEntry)->group(); - h << endl; - h << " // " << group << endl; - } - h << " " << cppType((*itEntry)->type()) << " " << varName((*itEntry)->name(), cfg); - if (!(*itEntry)->param().isEmpty()) { - h << QStringLiteral("[%1]").arg((*itEntry)->paramMax() + 1); - } - h << ";" << endl; - - if (cfg.allDefaultGetters || cfg.defaultGetters.contains((*itEntry)->name())) { - h << " "; - if (cfg.staticAccessors) { - h << "static "; - } - h << cppType((*itEntry)->type()) << " " << getDefaultFunction((*itEntry)->name()) << "_helper("; - if (!(*itEntry)->param().isEmpty()) { - h << " " << cppType((*itEntry)->paramType()) << " i "; - } - h << ")" << Const << ";" << endl; - } - } - - h << endl << " private:" << endl; - if (cfg.itemAccessors) { - for (itEntry = entries.constBegin(); itEntry != entries.constEnd(); ++itEntry) { - h << " Item" << itemType((*itEntry)->type()) << " *" << itemVar(*itEntry, cfg); - if (!(*itEntry)->param().isEmpty()) { - h << QStringLiteral("[%1]").arg((*itEntry)->paramMax() + 1); - } - h << ";" << endl; - } - } - if (hasNonModifySignals) { - h << " uint " << varName(QStringLiteral("settingsChanged"), cfg) << ";" << endl; - } - - } else { - // use a private class for both member variables and items - h << " private:" << endl; - for (itEntry = entries.constBegin(); itEntry != entries.constEnd(); ++itEntry) { - if (cfg.allDefaultGetters || cfg.defaultGetters.contains((*itEntry)->name())) { - h << " "; - if (cfg.staticAccessors) { - h << "static "; - } - h << cppType((*itEntry)->type()) << " " << getDefaultFunction((*itEntry)->name()) << "_helper("; - if (!(*itEntry)->param().isEmpty()) { - h << " " << cppType((*itEntry)->paramType()) << " i "; - } - h << ")" << Const << ";" << endl; - } - } - h << " " + cfg.className + "Private *d;" << endl; - } + KConfigParameters cfg(codegenFilename); - if (cfg.customAddons) { - h << " // Include custom additions" << endl; - h << " #include \"" << baseName << "_addons." << cfg.headerExtension << '"' << endl; - } + KConfigXmlParser xmlParser(cfg, inputFilename); + + // The Xml Parser aborts in the case of an error, so if we get + // to parseResult, we have a working Xml file. + xmlParser.start(); - h << "};" << endl << endl; + ParseResult parseResult = xmlParser.getParseResult(); - endNamespaces(cfg.nameSpace, h); - - h << "#endif" << endl << endl; - - header.close(); - - QFile implementation(baseDir + implementationFileName); - if (!implementation.open(QIODevice::WriteOnly)) { - cerr << "Can not open '" << implementationFileName << "for writing." - << endl; + if (hasErrors(xmlParser, parseResult, cfg)) { return 1; } - QTextStream cpp(&implementation); - - cpp.setCodec("utf-8"); - - cpp << "// This file is generated by kconfig_compiler_kf5 from " << QFileInfo(inputFilename).fileName() << "." << endl; - cpp << "// All changes you do to this file will be lost." << endl << endl; - - cpp << "#include \"" << headerFileName << "\"" << endl << endl; - - for (it = cfg.sourceIncludes.constBegin(); it != cfg.sourceIncludes.constEnd(); ++it) { - if ((*it).startsWith('"')) { - cpp << "#include " << *it << endl; - } else { - cpp << "#include <" << *it << ">" << endl; - } - } - - if (!cfg.sourceIncludes.isEmpty()) { - cpp << endl; - } - - if (cfg.setUserTexts && cfg.translationSystem == CfgConfig::KdeTranslation) { - cpp << "#include <klocalizedstring.h>" << endl << endl; - } - - // Header required by singleton implementation - if (cfg.singleton) { - cpp << "#include <qglobal.h>" << endl << "#include <QFile>" << endl << endl; - } - if (cfg.singleton && cfgFileNameArg) { - cpp << "#include <QDebug>" << endl << endl; - } - - if (!cfg.nameSpace.isEmpty()) { - cpp << "using namespace " << cfg.nameSpace << ";" << endl << endl; - } - - QString group; - - // private class implementation - if (cfg.dpointer) { - beginNamespaces(cfg.nameSpace, cpp); - cpp << "class " << cfg.className << "Private" << endl; - cpp << "{" << endl; - cpp << " public:" << endl; - for (itEntry = entries.constBegin(); itEntry != entries.constEnd(); ++itEntry) { - if ((*itEntry)->group() != group) { - group = (*itEntry)->group(); - cpp << endl; - cpp << " // " << group << endl; - } - cpp << " " << cppType((*itEntry)->type()) << " " << varName((*itEntry)->name(), cfg); - if (!(*itEntry)->param().isEmpty()) { - cpp << QStringLiteral("[%1]").arg((*itEntry)->paramMax() + 1); - } - cpp << ";" << endl; - } - cpp << endl << " // items" << endl; - for (itEntry = entries.constBegin(); itEntry != entries.constEnd(); ++itEntry) { - const QString declType = (*itEntry)->signalList().isEmpty() - ? QString(cfg.inherits + "::Item" + itemType((*itEntry)->type())) - : QStringLiteral("KConfigCompilerSignallingItem"); - cpp << " " << declType << " *" << itemVar( *itEntry, cfg ); - if (!(*itEntry)->param().isEmpty()) { - cpp << QStringLiteral("[%1]").arg((*itEntry)->paramMax() + 1); - } - cpp << ";" << endl; - } - if (hasNonModifySignals) { - cpp << " uint " << varName(QStringLiteral("settingsChanged"), cfg) << ";" << endl; - } - - cpp << "};" << endl << endl; - endNamespaces(cfg.nameSpace, cpp); - } - - // Singleton implementation - if (cfg.singleton) { - beginNamespaces(cfg.nameSpace, cpp); - cpp << "class " << cfg.className << "Helper" << endl; - cpp << '{' << endl; - cpp << " public:" << endl; - cpp << " " << cfg.className << "Helper() : q(nullptr) {}" << endl; - cpp << " ~" << cfg.className << "Helper() { delete q; }" << endl; - cpp << " " << cfg.className << "Helper(const " << cfg.className << "Helper&) = delete;" << endl; - cpp << " " << cfg.className << "Helper& operator=(const " << cfg.className << "Helper&) = delete;" << endl; - cpp << " " << cfg.className << " *q;" << endl; - cpp << "};" << endl; - endNamespaces(cfg.nameSpace, cpp); - cpp << "Q_GLOBAL_STATIC(" << cfg.className << "Helper, s_global" << cfg.className << ")" << endl; - - cpp << cfg.className << " *" << cfg.className << "::self()" << endl; - cpp << "{" << endl; - if (cfgFileNameArg) { - cpp << " if (!s_global" << cfg.className << "()->q)" << endl; - cpp << " qFatal(\"you need to call " << cfg.className << "::instance before using\");" << endl; - } else { - cpp << " if (!s_global" << cfg.className << "()->q) {" << endl; - cpp << " new " << cfg.className << ';' << endl; - cpp << " s_global" << cfg.className << "()->q->read();" << endl; - cpp << " }" << endl << endl; - } - cpp << " return s_global" << cfg.className << "()->q;" << endl; - cpp << "}" << endl << endl; - - if (cfgFileNameArg) { - auto instance = [&cfg, &cpp] (const QString &type, const QString &arg, bool isString) { - cpp << "void " << cfg.className << "::instance(" << type << " " << arg << ")" << endl; - cpp << "{" << endl; - cpp << " if (s_global" << cfg.className << "()->q) {" << endl; - cpp << " qDebug() << \"" << cfg.className << "::instance called after the first use - ignoring\";" << endl; - cpp << " return;" << endl; - cpp << " }" << endl; - cpp << " new " << cfg.className << "("; - if (isString) { - cpp << "KSharedConfig::openConfig(" << arg << ")"; - } else { - cpp << "std::move(" << arg << ")"; - } - cpp << ");" << endl; - cpp << " s_global" << cfg.className << "()->q->read();" << endl; - cpp << "}" << endl << endl; - }; - instance(QStringLiteral("const QString&"), QStringLiteral("cfgfilename"), true); - instance(QStringLiteral("KSharedConfig::Ptr"), QStringLiteral("config"), false); - } - } - - if (!cppPreamble.isEmpty()) { - cpp << cppPreamble << endl; - } - - // Constructor - cpp << cfg.className << "::" << cfg.className << "("; - if (cfgFileNameArg) { - if (! cfg.forceStringFilename) { - cpp << " KSharedConfig::Ptr config"; - } else { - cpp << " const QString& config"; - } - cpp << (parameters.isEmpty() ? "" : ","); - } - - for (QList<Param>::ConstIterator it = parameters.constBegin(); - it != parameters.constEnd(); ++it) { - if (it != parameters.constBegin()) { - cpp << ","; - } - cpp << " " << param((*it).type) << " " << (*it).name; - } - - if (cfg.parentInConstructor) { - if (cfgFileNameArg || !parameters.isEmpty()) { - cpp << ","; - } - cpp << " QObject *parent"; - } - cpp << " )" << endl; - - cpp << " : " << cfg.inherits << "("; - if (!cfgFileName.isEmpty()) { - cpp << " QStringLiteral( \"" << cfgFileName << "\" "; - } - if (cfgFileNameArg) { - if (! cfg.forceStringFilename) { - cpp << " std::move( config ) "; - } else { - cpp << " config "; - } - } - if (!cfgFileName.isEmpty()) { - cpp << ") "; - } - cpp << ")" << endl; - - // Store parameters - for (QList<Param>::ConstIterator it = parameters.constBegin(); - it != parameters.constEnd(); ++it) { - cpp << " , mParam" << (*it).name << "(" << (*it).name << ")" << endl; - } - - if (hasNonModifySignals && !cfg.dpointer) { - cpp << " , " << varName(QStringLiteral("settingsChanged"), cfg) << "(0)" << endl; - } - - cpp << "{" << endl; - - if (cfg.parentInConstructor) { - cpp << " setParent(parent);" << endl; - } - - if (cfg.dpointer) { - cpp << " d = new " + cfg.className + "Private;" << endl; - if (hasNonModifySignals) { - cpp << " " << varPath(QStringLiteral("settingsChanged"), cfg) << " = 0;" << endl; - } - } - // Needed in case the singleton class is used as baseclass for - // another singleton. - if (cfg.singleton) { - cpp << " Q_ASSERT(!s_global" << cfg.className << "()->q);" << endl; - cpp << " s_global" << cfg.className << "()->q = this;" << endl; - } - - group.clear(); - - if (hasSignals) { - // this cast to base-class pointer-to-member is valid C++ - // https://stackoverflow.com/questions/4272909/is-it-safe-to-upcast-a-method-pointer-and-use-it-with-base-class-pointer/ - cpp << " KConfigCompilerSignallingItem::NotifyFunction notifyFunction =" - << " static_cast<KConfigCompilerSignallingItem::NotifyFunction>(&" - << cfg.className << "::itemChanged);" << endl << endl; - } - - for (itEntry = entries.constBegin(); itEntry != entries.constEnd(); ++itEntry) { - if ((*itEntry)->group() != group) { - if (!group.isEmpty()) { - cpp << endl; - } - group = (*itEntry)->group(); - cpp << " setCurrentGroup( " << paramString(group, parameters) << " );" << endl << endl; - } - - QString key = paramString((*itEntry)->key(), parameters); - if (!(*itEntry)->code().isEmpty()) { - cpp << (*itEntry)->code() << endl; - } - if ((*itEntry)->type() == QLatin1String("Enum")) { - cpp << " QList<" + cfg.inherits + "::ItemEnum::Choice> values" - << (*itEntry)->name() << ";" << endl; - const QList<CfgEntry::Choice> choices = (*itEntry)->choices().choices; - QList<CfgEntry::Choice>::ConstIterator it; - for (it = choices.constBegin(); it != choices.constEnd(); ++it) { - cpp << " {" << endl; - cpp << " " + cfg.inherits + "::ItemEnum::Choice choice;" << endl; - cpp << " choice.name = QStringLiteral(\"" << (*it).name << "\");" << endl; - if (cfg.setUserTexts) { - if (!(*it).label.isEmpty()) { - cpp << " choice.label = " - << translatedString(cfg, (*it).label, (*it).context) - << ";" << endl; - } - if (!(*it).toolTip.isEmpty()) { - cpp << " choice.toolTip = " - << translatedString(cfg, (*it).toolTip, (*it).context) - << ";" << endl; - } - if (!(*it).whatsThis.isEmpty()) { - cpp << " choice.whatsThis = " - << translatedString(cfg, (*it).whatsThis, (*it).context) - << ";" << endl; - } - } - cpp << " values" << (*itEntry)->name() << ".append( choice );" << endl; - cpp << " }" << endl; - } - } - - if (!cfg.dpointer) { - cpp << itemDeclaration(*itEntry, cfg); - } - - if ((*itEntry)->param().isEmpty()) { - // Normal case - cpp << " " << itemPath(*itEntry, cfg) << " = " - << newItem((*itEntry), key, (*itEntry)->defaultValue(), cfg) << endl; - - if (!(*itEntry)->minValue().isEmpty()) { - cpp << " " << itemPath(*itEntry, cfg) << "->setMinValue(" << (*itEntry)->minValue() << ");" << endl; - } - if (!(*itEntry)->maxValue().isEmpty()) { - cpp << " " << itemPath(*itEntry, cfg) << "->setMaxValue(" << (*itEntry)->maxValue() << ");" << endl; - } - - if (cfg.setUserTexts) { - cpp << userTextsFunctions((*itEntry), cfg); - } - - if (cfg.allNotifiers || cfg.notifiers.contains((*itEntry)->name())) { - cpp << " " << itemPath(*itEntry, cfg) << "->setWriteFlags(KConfigBase::Notify);" << endl; - } - - cpp << " addItem( " << itemPath(*itEntry, cfg); - QString quotedName = (*itEntry)->name(); - addQuotes(quotedName); - if (quotedName != key) { - cpp << ", QStringLiteral( \"" << (*itEntry)->name() << "\" )"; - } - cpp << " );" << endl; - } else { - // Indexed - for (int i = 0; i <= (*itEntry)->paramMax(); i++) { - QString defaultStr; - QString itemVarStr(itemPath(*itEntry, cfg) + QStringLiteral("[%1]").arg(i)); - - if (!(*itEntry)->paramDefaultValue(i).isEmpty()) { - defaultStr = (*itEntry)->paramDefaultValue(i); - } else if (!(*itEntry)->defaultValue().isEmpty()) { - defaultStr = paramString((*itEntry)->defaultValue(), (*itEntry), i); - } else { - defaultStr = defaultValue((*itEntry)->type()); - } - - cpp << " " << itemVarStr << " = " - << newItem((*itEntry), paramString(key, *itEntry, i), defaultStr, cfg, QStringLiteral("[%1]").arg(i)) << endl; - - if (cfg.setUserTexts) { - cpp << userTextsFunctions(*itEntry, cfg, itemVarStr, (*itEntry)->paramName()); - } - - // Make mutators for enum parameters work by adding them with $(..) replaced by the - // param name. The check for isImmutable in the set* functions doesn't have the param - // name available, just the corresponding enum value (int), so we need to store the - // param names in a separate static list!. - cpp << " addItem( " << itemVarStr << ", QStringLiteral( \""; - if ((*itEntry)->paramType() == QLatin1String("Enum")) { - cpp << (*itEntry)->paramName().replace("$(" + (*itEntry)->param() + ')', QLatin1String("%1")).arg((*itEntry)->paramValues()[i]); - } else { - cpp << (*itEntry)->paramName().replace("$(" + (*itEntry)->param() + ')', QLatin1String("%1")).arg(i); - } - cpp << "\" ) );" << endl; - } - } + // TODO: Move this to somewhere saner. + for (const auto &signal : qAsConst(parseResult.signalList)) { + parseResult.hasNonModifySignals |= !signal.modify; } - cpp << "}" << endl << endl; - - if (cfg.dpointer) { - // setters and getters go in Cpp if in dpointer mode - for (itEntry = entries.constBegin(); itEntry != entries.constEnd(); ++itEntry) { - QString n = (*itEntry)->name(); - QString t = (*itEntry)->type(); - - // Manipulator - if (cfg.allMutators || cfg.mutators.contains(n)) { - cpp << "void " << setFunction(n, cfg.className) << "( "; - if (!(*itEntry)->param().isEmpty()) { - cpp << cppType((*itEntry)->paramType()) << " i, "; - } - if (cfg.useEnumTypes && t == QLatin1String("Enum")) { - cpp << enumType(*itEntry, cfg.globalEnums); - } else { - cpp << param(t); - } - cpp << " v )" << endl; - // function body inline only if not using dpointer - // for BC mode - cpp << "{" << endl; - cpp << indent(memberMutatorBody(*itEntry, cfg), 6); - cpp << "}" << endl << endl; - } - - // Accessor - if (cfg.useEnumTypes && t == QLatin1String("Enum")) { - cpp << enumType(*itEntry, cfg.globalEnums); - } else { - cpp << cppType(t); - } - cpp << " " << getFunction(n, cfg.className) << "("; - if (!(*itEntry)->param().isEmpty()) { - cpp << " " << cppType((*itEntry)->paramType()) << " i "; - } - cpp << ")" << Const << endl; - // function body inline only if not using dpointer - // for BC mode - cpp << "{" << endl; - cpp << indent(memberAccessorBody(*itEntry, cfg.globalEnums, cfg), 2); - cpp << "}" << endl << endl; - - // Default value Accessor -- written by the loop below - - // Item accessor - if (cfg.itemAccessors) { - cpp << endl; - cpp << cfg.inherits + "::Item" << itemType((*itEntry)->type()) << " *" - << getFunction(n, cfg.className) << "Item("; - if (!(*itEntry)->param().isEmpty()) { - cpp << " " << cppType((*itEntry)->paramType()) << " i "; - } - cpp << ")" << endl; - cpp << "{" << endl; - cpp << indent(itemAccessorBody(*itEntry, cfg), 2); - cpp << "}" << endl; - } - - cpp << endl; - } - } - - // default value getters always go in Cpp - for (itEntry = entries.constBegin(); itEntry != entries.constEnd(); ++itEntry) { - QString n = (*itEntry)->name(); - QString t = (*itEntry)->type(); - - // Default value Accessor, as "helper" function - if ((cfg.allDefaultGetters || cfg.defaultGetters.contains(n)) && !(*itEntry)->defaultValue().isEmpty()) { - cpp << cppType(t) << " " << getDefaultFunction(n, cfg.className) << "_helper("; - if (!(*itEntry)->param().isEmpty()) { - cpp << " " << cppType((*itEntry)->paramType()) << " i "; - } - cpp << ")" << Const << endl; - cpp << "{" << endl; - cpp << memberGetDefaultBody(*itEntry) << endl; - cpp << "}" << endl << endl; - } - } - - // Destructor - cpp << cfg.className << "::~" << cfg.className << "()" << endl; - cpp << "{" << endl; - if (cfg.dpointer) { - cpp << " delete d;" << endl; - } - if (cfg.singleton) { - cpp << " s_global" << cfg.className << "()->q = nullptr;" << endl; - } - cpp << "}" << endl << endl; - - if (hasNonModifySignals) { - cpp << "bool " << cfg.className << "::" << "usrSave()" << endl; - cpp << "{" << endl; - cpp << " const bool res = " << cfg.inherits << "::usrSave();" << endl; - cpp << " if (!res) return false;" << endl << endl; - for (const Signal &signal : qAsConst(signalList)) { - if (signal.modify) { - continue; - } - - cpp << " if ( " << varPath(QStringLiteral("settingsChanged"), cfg) << " & " << signalEnumName(signal.name) << " )" << endl; - cpp << " Q_EMIT " << signal.name << "("; - QList<SignalArguments>::ConstIterator it, itEnd = signal.arguments.constEnd(); - for (it = signal.arguments.constBegin(); it != itEnd;) { - SignalArguments argument = *it; - bool cast = false; - if (cfg.useEnumTypes && argument.type == QLatin1String("Enum")) { - for (int i = 0, end = entries.count(); i < end; ++i) { - if (entries[i]->name() == argument.variableName) { - cpp << "static_cast<" << enumType(entries[i], cfg.globalEnums) << ">("; - cast = true; - break; - } - } - } - cpp << varPath(argument.variableName, cfg); - if (cast) { - cpp << ")"; - } - if (++it != itEnd) { - cpp << ", "; - } - } - cpp << ");" << endl; - } - - cpp << " " << varPath(QStringLiteral("settingsChanged"), cfg) << " = 0;" << endl; - cpp << " return true;" << endl; - cpp << "}" << endl; - } - - if (hasSignals) { - cpp << endl; - cpp << "void " << cfg.className << "::" << "itemChanged(quint64 flags) {" << endl; - if (hasNonModifySignals) - cpp << " " << varPath(QStringLiteral("settingsChanged"), cfg) << " |= flags;" << endl; - - if (!signalList.isEmpty()) - cpp << endl; - - for (const Signal &signal : qAsConst(signalList)) { - if (signal.modify) { - cpp << " if ( flags & " << signalEnumName(signal.name) << " ) {" << endl; - cpp << " Q_EMIT " << signal.name << "();" << endl; - cpp << " }" << endl; - } - } - - cpp << "}" << endl; - } - - if (hasSignals || cfg.generateProperties) { - // Add includemoc if they are signals defined. - cpp << endl; - cpp << "#include \"" << mocFileName << "\"" << endl; - cpp << endl; - } + // remove '.kcfg' from the name. + const QString baseName = inputFilename.mid(0, inputFilename.size()-5); + KConfigHeaderGenerator headerGenerator(baseName, baseDir, cfg, parseResult); + headerGenerator.start(); + headerGenerator.save(); - // clear entries list - qDeleteAll(entries); + KConfigSourceGenerator sourceGenerator(baseName, baseDir, cfg, parseResult); + sourceGenerator.start(); + sourceGenerator.save(); - implementation.close(); + qDeleteAll(parseResult.entries); } |