Advance Wayland and KDE package bring-up

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-14 10:51:06 +01:00
parent 51f3c21121
commit cf12defd28
15214 changed files with 20594243 additions and 269 deletions
@@ -0,0 +1,28 @@
add_executable(kconfig_compiler)
if(CMAKE_CROSSCOMPILING)
if(BUILD_TESTING)
message(WARNING "Testing should be disabled on cross-compilation")
endif()
else()
add_executable(KF6::kconfig_compiler ALIAS kconfig_compiler)
endif()
target_sources(kconfig_compiler PRIVATE
KConfigParameters.cpp
KConfigCodeGeneratorBase.cpp
KConfigHeaderGenerator.cpp
KConfigSourceGenerator.cpp
KConfigXmlParser.cpp
kconfig_compiler.cpp
)
set_target_properties(kconfig_compiler PROPERTIES
OUTPUT_NAME "kconfig_compiler_kf6"
)
target_link_libraries(kconfig_compiler Qt6::Xml)
ecm_mark_nongui_executable(kconfig_compiler)
install(TARGETS kconfig_compiler EXPORT KF6ConfigCompilerTargets DESTINATION ${KDE_INSTALL_LIBEXECDIR_KF})
@@ -0,0 +1,265 @@
/*
This file is part of the KDE libraries.
SPDX-FileCopyrightText: 2003 Cornelius Schumacher <schumacher@kde.org>
SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
SPDX-FileCopyrightText: 2003 Zack Rusin <zack@kde.org>
SPDX-FileCopyrightText: 2006 Michaël Larouche <michael.larouche@kdemail.net>
SPDX-FileCopyrightText: 2008 Allen Winter <winter@kde.org>
SPDX-FileCopyrightText: 2020 Tomaz Cananbrava <tcanabrava@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "KConfigCodeGeneratorBase.h"
#include <QFileInfo>
#include <QLatin1Char>
#include <QDebug>
#include <ostream>
#include <iostream>
KConfigCodeGeneratorBase::KConfigCodeGeneratorBase(const QString &inputFile,
const QString &baseDir,
const QString &fileName,
const KConfigParameters &parameters,
ParseResult &parseResult)
: parseResult(parseResult)
, m_inputFile(inputFile)
, m_baseDir(baseDir)
, m_fileName(fileName)
, m_cfg(parameters)
{
m_file.setFileName(m_fileName);
if (!m_file.open(QIODevice::WriteOnly)) {
std::cerr << "Can not open '" << qPrintable(m_fileName) << "for writing." << std::endl;
exit(1);
}
m_stream.setDevice(&m_file);
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 << '\n';
indent();
}
void KConfigCodeGeneratorBase::endScope(ScopeFinalizer finalizer)
{
unindent();
m_stream << whitespace() << QLatin1Char('}');
if (finalizer == ScopeFinalizer::Semicolon) {
m_stream << ';';
}
m_stream << '\n';
}
void KConfigCodeGeneratorBase::start()
{
const QString m_fileName = QFileInfo(m_inputFile).fileName();
m_stream << "// This file is generated by kconfig_compiler_kf6 from " << m_fileName << ".kcfg"
<< ".\n";
m_stream << "// All changes you do to this file will be lost.\n";
}
void KConfigCodeGeneratorBase::addHeaders(const QStringList &headerList)
{
for (const auto &include : headerList) {
if (include.startsWith(QLatin1Char('"'))) {
m_stream << "#include " << include << '\n';
} else {
m_stream << "#include <" << include << ">\n";
}
}
}
// adds as many 'namespace foo {' lines to p_out as
// there are namespaces in p_ns
void KConfigCodeGeneratorBase::beginNamespaces()
{
if (!m_cfg.nameSpace.isEmpty()) {
const auto nameSpaceList = m_cfg.nameSpace.split(QStringLiteral("::"));
for (const QString &ns : nameSpaceList) {
m_stream << "namespace " << ns << " {\n";
}
m_stream << '\n';
}
}
// adds as many '}' lines to p_out as
// there are namespaces in p_ns
void KConfigCodeGeneratorBase::endNamespaces()
{
if (!m_cfg.nameSpace.isEmpty()) {
m_stream << '\n';
const int namespaceCount = m_cfg.nameSpace.count(QStringLiteral("::")) + 1;
for (int i = 0; i < namespaceCount; ++i) {
m_stream << "}\n";
}
}
}
// returns the member accessor 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 << ";\n";
return result;
}
void KConfigCodeGeneratorBase::memberImmutableBody(const CfgEntry *e, bool globalEnums)
{
stream() << whitespace() << "return " << m_this << "isImmutable( QStringLiteral( \"";
if (!e->param.isEmpty()) {
stream() << QString(e->paramName).replace(QLatin1String("$(%1)").arg(e->param), QLatin1String("%1")) << "\" ).arg( ";
if (e->paramType == QLatin1String("Enum")) {
stream() << "QLatin1String( ";
if (globalEnums) {
stream() << enumName(e->param) << "ToString[i]";
} else {
stream() << enumName(e->param) << "::enumToString[i]";
}
stream() << " )";
} else {
stream() << "i";
}
stream() << " )";
} else {
stream() << e->name << "\" )";
}
stream() << " );\n";
}
void KConfigCodeGeneratorBase::createIfSetLogic(const CfgEntry *e, const QString &varExpression)
{
const bool hasBody = !e->signalList.empty() || m_cfg.generateProperties;
m_stream << whitespace() << "if (";
if (hasBody) {
m_stream << "v != " << varExpression << " && ";
}
const auto immutablefunction = immutableFunction(e->name, m_cfg.dpointer ? m_cfg.className : QString{});
m_stream << "!" << m_this << immutablefunction << "(";
if (!e->param.isEmpty()) {
m_stream << " i ";
}
m_stream << "))";
}
void KConfigCodeGeneratorBase::memberMutatorBody(const CfgEntry *e)
{
// HACK: Don't open '{' manually, use startScope / endScope to automatically handle whitespace indentation.
if (!e->min.isEmpty()) {
if (e->min != QLatin1String("0") || !isUnsigned(e->type)) { // skip writing "if uint<0" (#187579)
m_stream << whitespace() << "if (v < " << e->min << ")\n";
m_stream << whitespace() << "{\n";
m_stream << whitespace();
addDebugMethod(m_stream, m_cfg, e->name);
m_stream << ": value \" << v << \" is less than the minimum value of " << e->min << "\";\n";
m_stream << whitespace() << " v = " << e->min << ";\n";
m_stream << whitespace() << "}\n";
}
}
if (!e->max.isEmpty()) {
m_stream << '\n';
m_stream << whitespace() << "if (v > " << e->max << ")\n";
m_stream << whitespace() << "{\n";
m_stream << whitespace();
addDebugMethod(m_stream, m_cfg, e->name);
m_stream << ": value \" << v << \" is greater than the maximum value of " << e->max << "\";\n";
m_stream << whitespace() << " v = " << e->max << ";\n";
m_stream << whitespace() << "}\n\n";
}
const QString varExpression = m_this + varPath(e->name, 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 ? " {" : "") << '\n';
m_stream << whitespace() << " " << varExpression << " = v;\n";
const auto listSignal = e->signalList;
for (const Signal &signal : std::as_const(listSignal)) {
if (signal.modify) {
m_stream << whitespace() << " Q_EMIT " << m_this << signal.name << "();\n";
} else {
m_stream << whitespace() << " " << m_this << varPath(QStringLiteral("settingsChanged"), m_cfg) << ".insert(" << signalEnumName(signal.name) << ");\n";
}
}
if (hasBody) {
m_stream << whitespace() << "}\n";
}
}
@@ -0,0 +1,147 @@
/*
This file is part of KDE.
SPDX-FileCopyrightText: 2003 Cornelius Schumacher <schumacher@kde.org>
SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
SPDX-FileCopyrightText: 2003 Zack Rusin <zack@kde.org>
SPDX-FileCopyrightText: 2006 Michaël Larouche <michael.larouche@kdemail.net>
SPDX-FileCopyrightText: 2008 Allen Winter <winter@kde.org>
SPDX-FileCopyrightText: 2020 Tomaz Cananbrava <tcanabrava@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCONFIGCODEGENERATORBASE_H
#define KCONFIGCODEGENERATORBASE_H
#include <QFile>
#include <QList>
#include <QString>
#include <QTextStream>
#include "KConfigCommonStructs.h"
#include "KConfigParameters.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 &parameters, // 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 is<Param>Immutable for the CfgEntry
void memberImmutableBody(const CfgEntry *e, bool globalEnums);
// 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 accessible 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
@@ -0,0 +1,191 @@
/*
This file is part of the KDE libraries.
SPDX-FileCopyrightText: 2003 Cornelius Schumacher <schumacher@kde.org>
SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
SPDX-FileCopyrightText: 2003 Zack Rusin <zack@kde.org>
SPDX-FileCopyrightText: 2006 Michaël Larouche <michael.larouche@kdemail.net>
SPDX-FileCopyrightText: 2008 Allen Winter <winter@kde.org>
SPDX-FileCopyrightText: 2020 Tomaz Cananbrava <tcanabrava@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCONFIGCOMMONSTRUCTS_H
#define KCONFIGCOMMONSTRUCTS_H
#include <QList>
#include <QString>
#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;
QString val;
QString value() const
{
return !val.isEmpty() ? val : name;
}
};
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 parentGroup;
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;
bool cfgStateConfig = 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 immutableFunction(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);
// returns the name of the local inner item if there is one
// (before wrapping with KConfigCompilerSignallingItem)
// Otherwise return itemVar()
QString innerItemVar(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 &param = QString(),
const QString &paramValue = QString());
// TODO: Sanitize those functions.
QString newItem(const CfgEntry *entry, const QString &key, const QString &defaultValue, const KConfigParameters &cfg, const QString &param = QString());
QString newInnerItem(const CfgEntry *entry, const QString &key, const QString &defaultValue, const KConfigParameters &cfg, const QString &param = 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> &parameters);
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
@@ -0,0 +1,659 @@
/*
This file is part of the KDE libraries.
SPDX-FileCopyrightText: 2003 Cornelius Schumacher <schumacher@kde.org>
SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
SPDX-FileCopyrightText: 2003 Zack Rusin <zack@kde.org>
SPDX-FileCopyrightText: 2006 Michaël Larouche <michael.larouche@kdemail.net>
SPDX-FileCopyrightText: 2008 Allen Winter <winter@kde.org>
SPDX-FileCopyrightText: 2020 Tomaz Cananbrava <tcanabrava@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "KConfigHeaderGenerator.h"
#include <QDebug>
#include <QTextStream>
#include <iostream>
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 << '\n';
startScope();
// Add Q_OBJECT macro if the config need signals.
if (!parseResult.signalList.isEmpty() || cfg().generateProperties) {
stream() << " Q_OBJECT\n";
}
if (cfg().qmlRegistration) {
stream() << " QML_ELEMENT\n";
if (cfg().singleton) {
stream() << " QML_SINGLETON\n";
}
}
stream() << " public:\n";
implementEnums();
createConstructor();
createDestructor();
for (const auto *entry : std::as_const(parseResult.entries)) {
const QString returnType = (cfg().useEnumTypes && entry->type == QLatin1String("Enum")) ? enumType(entry, cfg().globalEnums) : cppType(entry->type);
createSetters(entry);
createProperties(entry, returnType);
createImmutableProperty(entry);
createGetters(entry, returnType);
createImmutableGetters(entry);
createDefaultValueMember(entry);
createItemAcessors(entry, returnType);
}
createSignals();
stream() << " protected:\n";
createSingleton();
// TODO: Move those to functions too.
if (parseResult.hasNonModifySignals) {
stream() << whitespace() << "bool usrSave() override;\n";
}
// Member variables
if (!cfg().memberVariables.isEmpty() //
&& cfg().memberVariables != QLatin1String("private") //
&& cfg().memberVariables != QLatin1String("dpointer")) {
stream() << " " << cfg().memberVariables << ":\n";
}
// Class Parameters
for (const auto &parameter : std::as_const(parseResult.parameters)) {
stream() << whitespace() << "" << cppType(parameter.type) << " mParam" << parameter.name << ";\n";
}
createNonDPointerHelpers();
createDPointer();
if (cfg().customAddons) {
stream() << whitespace() << "// Include custom additions\n";
stream() << whitespace() << "#include \"" << cfg().baseName << "_addons." << cfg().headerExtension << "\"\n";
}
endScope(ScopeFinalizer::Semicolon);
}
void KConfigHeaderGenerator::createHeaders()
{
addHeaders(cfg().headerIncludes);
if (cfg().headerIncludes.size()) {
stream() << '\n';
}
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")});
if (!cfg().dpointer && parseResult.hasNonModifySignals) {
addHeaders({QStringLiteral("QSet")});
}
if (cfg().qmlRegistration) {
addHeaders({QStringLiteral("qqmlintegration.h")});
}
stream() << '\n';
addHeaders(parseResult.includes);
if (parseResult.includes.size()) {
stream() << '\n';
}
}
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('_') : QString{};
const QString defineName = namespaceStr + cfg().className.toUpper() + QStringLiteral("_H");
stream() << "#ifndef " << defineName << '\n';
stream() << "#define " << defineName << '\n';
stream() << '\n';
}
void KConfigHeaderGenerator::endHeaderGuards()
{
stream() << '\n';
stream() << "#endif";
stream() << '\n';
// HACK: Original files ended with two last newlines, add them.
stream() << '\n';
}
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 : std::as_const(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(", ")) << " };\n";
if (cfg().generateProperties) {
stream() << whitespace() << "Q_ENUM(" << enumName(entry->name, entry->choices) << ")\n";
}
} else {
// Create an automatically named enum
stream() << whitespace() << "class " << enumName(entry->name, entry->choices) << '\n';
stream() << whitespace() << "{\n";
stream() << whitespace() << " public:\n";
stream() << whitespace() << " enum type { " << values.join(QStringLiteral(", ")) << ", COUNT };\n";
stream() << whitespace() << "};\n";
}
} else if (!choices.external()) {
// Create a named enum
stream() << whitespace() << "enum " << enumName(entry->name, entry->choices) << " { " << values.join(QStringLiteral(", ")) << " };\n";
}
}
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(", ")) << " };\n";
stream() << whitespace() << "static const char* const " << enumName(entry->param) << "ToString[];\n";
} else {
stream() << whitespace() << "class " << enumName(entry->param) << '\n';
stream() << whitespace() << "{\n";
stream() << whitespace() << " public:\n";
stream() << whitespace() << " enum type { " << values.join(QStringLiteral(", ")) << ", COUNT };\n";
stream() << whitespace() << " static const char* const enumToString[];\n";
stream() << whitespace() << "};\n";
}
}
void KConfigHeaderGenerator::implementEnums()
{
if (!parseResult.entries.size()) {
return;
}
for (const auto *entry : std::as_const(parseResult.entries)) {
const CfgEntry::Choices &choices = entry->choices;
const QStringList values = entry->paramValues;
implementChoiceEnums(entry, choices);
implementValueEnums(entry, values);
}
stream() << '\n';
}
void KConfigHeaderGenerator::createSignals()
{
// Signal definition.
if (parseResult.signalList.isEmpty()) {
return;
}
stream() << "\n enum {\n";
// 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) << " = " << (i + 1);
if (i != end - 1) {
stream() << ",\n";
}
}
stream() << '\n';
stream() << whitespace() << "};\n\n";
stream() << " Q_SIGNALS:";
for (const Signal &signal : std::as_const(parseResult.signalList)) {
stream() << '\n';
if (!signal.label.isEmpty()) {
stream() << whitespace() << "/**\n";
stream() << whitespace() << " " << signal.label << '\n';
stream() << whitespace() << "*/\n";
}
stream() << whitespace() << "void " << signal.name << "(";
auto it = signal.arguments.cbegin();
const auto itEnd = signal.arguments.cend();
while (it != itEnd) {
Param argument = *it;
QString type = param(argument.type);
if (cfg().useEnumTypes && argument.type == QLatin1String("Enum")) {
for (const auto *entry : std::as_const(parseResult.entries)) {
if (entry->name == argument.name) {
type = enumType(entry, cfg().globalEnums);
break;
}
}
}
stream() << type << " " << argument.name;
if (++it != itEnd) {
stream() << ", ";
}
}
stream() << ");\n";
}
stream() << '\n';
stream() << " private:\n";
stream() << whitespace() << "void itemChanged(quint64 signalFlag);\n";
stream() << '\n';
}
void KConfigHeaderGenerator::createDPointer()
{
if (!cfg().dpointer) {
return;
}
// use a private class for both member variables and items
stream() << " private:\n";
for (const auto *entry : std::as_const(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() << ";\n";
}
}
stream() << whitespace() << "" << cfg().className << "Private *d;\n";
}
void KConfigHeaderGenerator::createConstructor()
{
if (cfg().singleton) {
stream() << whitespace() << "static " << cfg().className << " *self();\n";
if (cfg().qmlRegistration) {
stream() << whitespace() << "static " << cfg().className << " *create(QQmlEngine *, QJSEngine *);\n";
}
if (parseResult.cfgFileNameArg) {
stream() << whitespace() << "static void instance(const QString& cfgfilename);\n";
stream() << whitespace() << "static void instance(KSharedConfig::Ptr config);\n";
}
return;
}
stream() << whitespace() << "" << cfg().className << "(";
if (parseResult.cfgFileNameArg) {
if (cfg().forceStringFilename) {
stream() << " const QString &cfgfilename" << (parseResult.parameters.isEmpty() ? " = QString()" : ", ");
} else if (parseResult.cfgStateConfig) {
stream() << " KSharedConfig::Ptr config" << (parseResult.parameters.isEmpty() ? " = KSharedConfig::openStateConfig()" : ", ");
} else {
stream() << " KSharedConfig::Ptr config" << (parseResult.parameters.isEmpty() ? " = KSharedConfig::openConfig()" : ", ");
}
}
if (cfg().forceStringFilename && parseResult.cfgStateConfig) {
std::cerr << "One can not use ForceStringFilename and use the stateConfig attribute, consider "
"removing the ForceStringFilename kcfgc option if you want to use state data"
<< std::endl;
}
bool first = true;
for (const auto &parameter : std::as_const(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() << " );\n";
}
void KConfigHeaderGenerator::createDestructor()
{
stream() << whitespace() << "~" << cfg().className << "() override;\n\n";
}
void KConfigHeaderGenerator::createForwardDeclarations()
{
// Private class declaration
if (cfg().dpointer) {
stream() << "class " << cfg().className << "Private;\n\n";
}
if (cfg().qmlRegistration && cfg().singleton) {
stream() << "class QQmlEngine;\n";
stream() << "class QJSEngine;\n\n";
}
}
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() << ")\n";
}
void KConfigHeaderGenerator::createImmutableProperty(const CfgEntry *entry)
{
if (!cfg().generateProperties) {
return;
}
stream() << whitespace();
stream() << "Q_PROPERTY(bool " << immutableFunction(entry->name);
stream() << " READ " << immutableFunction(entry->name);
stream() << " CONSTANT)\n";
}
void KConfigHeaderGenerator::createSetters(const CfgEntry *entry)
{
// Manipulator
if (!cfg().allMutators && !cfg().mutators.contains(entry->name)) {
return;
}
stream() << whitespace() << "/**\n";
stream() << whitespace() << " Set " << entry->label << '\n';
stream() << whitespace() << "*/\n";
if (cfg().staticAccessors) {
stream() << whitespace() << "static\n";
}
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() << '\n';
startScope();
memberMutatorBody(entry);
endScope();
stream() << '\n';
} else {
stream() << ";\n\n";
}
}
void KConfigHeaderGenerator::createGetters(const CfgEntry *entry, const QString &returnType)
{
// Accessor
stream() << whitespace() << "/**\n";
stream() << whitespace() << " Get " << entry->label << '\n';
stream() << whitespace() << "*/\n";
if (cfg().staticAccessors) {
stream() << whitespace() << "static\n";
}
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() << '\n';
startScope();
stream() << whitespace() << memberAccessorBody(entry, cfg().globalEnums);
endScope();
stream() << '\n';
} else {
stream() << ";\n\n";
}
}
void KConfigHeaderGenerator::createImmutableGetters(const CfgEntry *entry)
{
stream() << whitespace() << "/**\n";
stream() << whitespace() << " Is " << entry->label << " Immutable\n";
stream() << whitespace() << "*/\n";
// Immutable
if (cfg().staticAccessors) {
stream() << whitespace() << "static\n";
}
stream() << whitespace() << "";
stream() << "bool " << immutableFunction(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() << '\n';
startScope();
memberImmutableBody(entry, cfg().globalEnums);
endScope();
stream() << '\n';
} else {
stream() << ";\n\n";
}
}
void KConfigHeaderGenerator::createItemAcessors(const CfgEntry *entry, const QString &returnType)
{
Q_UNUSED(returnType)
// Item accessor
if (!cfg().itemAccessors) {
return;
}
const QString declType = entry->signalList.isEmpty() ? QStringLiteral("Item") + itemType(entry->type) : QStringLiteral("KConfigCompilerSignallingItem");
stream() << whitespace() << "/**\n";
stream() << whitespace() << " Get Item object corresponding to " << entry->name << "()" << '\n';
stream() << whitespace() << "*/\n";
stream() << whitespace() << declType << " *" << getFunction(entry->name) << "Item(";
if (!entry->param.isEmpty()) {
stream() << " " << cppType(entry->paramType) << " i ";
}
stream() << ")";
if (!cfg().dpointer) {
stream() << '\n';
startScope();
stream() << whitespace() << itemAccessorBody(entry, cfg());
endScope();
} else {
stream() << ";\n";
}
stream() << '\n';
}
void KConfigHeaderGenerator::createDefaultValueMember(const CfgEntry *entry)
{
// Default value Accessor
if (!((cfg().allDefaultGetters || cfg().defaultGetters.contains(entry->name)) && !entry->defaultValue.isEmpty())) {
return;
}
stream() << whitespace() << "/**\n";
stream() << whitespace() << " Get " << entry->label << " default value\n";
stream() << whitespace() << "*/\n";
if (cfg().staticAccessors) {
stream() << whitespace() << "static\n";
}
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() << '\n';
stream() << whitespace() << "{\n";
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() << ";\n";
stream() << whitespace() << "}\n";
stream() << '\n';
}
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() << ");\n";
stream() << whitespace() << "friend class " << cfg().className << "Helper;\n\n";
}
void KConfigHeaderGenerator::createNonDPointerHelpers()
{
if (cfg().memberVariables == QLatin1String("dpointer")) {
return;
}
QString group;
for (const auto *entry : std::as_const(parseResult.entries)) {
if (entry->group != group) {
group = entry->group;
stream() << '\n';
stream() << whitespace() << "// " << group << '\n';
}
stream() << whitespace() << "" << cppType(entry->type) << " " << varName(entry->name, cfg());
if (!entry->param.isEmpty()) {
stream() << QStringLiteral("[%1]").arg(entry->paramMax + 1);
}
stream() << ";\n";
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() << ";\n";
}
}
stream() << "\n private:\n";
if (cfg().itemAccessors) {
for (const auto *entry : std::as_const(parseResult.entries)) {
const QString declType =
entry->signalList.isEmpty() ? QStringLiteral("Item") + itemType(entry->type) : QStringLiteral("KConfigCompilerSignallingItem");
stream() << whitespace() << declType << " *" << itemVar(entry, cfg());
if (!entry->param.isEmpty()) {
stream() << QStringLiteral("[%1]").arg(entry->paramMax + 1);
}
stream() << ";\n";
}
}
if (parseResult.hasNonModifySignals) {
stream() << whitespace() << "QSet<quint64> " << varName(QStringLiteral("settingsChanged"), cfg()) << ";\n";
}
}
@@ -0,0 +1,63 @@
/*
This file is part of KDE.
SPDX-FileCopyrightText: 2003 Cornelius Schumacher <schumacher@kde.org>
SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
SPDX-FileCopyrightText: 2003 Zack Rusin <zack@kde.org>
SPDX-FileCopyrightText: 2006 Michaël Larouche <michael.larouche@kdemail.net>
SPDX-FileCopyrightText: 2008 Allen Winter <winter@kde.org>
SPDX-FileCopyrightText: 2020 Tomaz Cananbrava <tcanabrava@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCONFIGHEADERGENERATOR_H
#define KCONFIGHEADERGENERATOR_H
#include "KConfigCodeGeneratorBase.h"
#include "KConfigCommonStructs.h"
#include <QList>
#include <QString>
class KConfigParameters;
class CfgEntry;
class QTextStream;
struct ParseResult;
class KConfigHeaderGenerator : public KConfigCodeGeneratorBase
{
public:
KConfigHeaderGenerator(const QString &inputFile, const QString &baseDir, const KConfigParameters &parameters, 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 createImmutableGetters(const CfgEntry *entry);
void createProperties(const CfgEntry *entry, const QString &returnType);
void createImmutableProperty(const CfgEntry *entry);
void createDefaultValueMember(const CfgEntry *entry);
};
#endif
@@ -0,0 +1,81 @@
/*
This file is part of the KDE libraries.
SPDX-FileCopyrightText: 2003 Cornelius Schumacher <schumacher@kde.org>
SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
SPDX-FileCopyrightText: 2003 Zack Rusin <zack@kde.org>
SPDX-FileCopyrightText: 2006 Michaël Larouche <michael.larouche@kdemail.net>
SPDX-FileCopyrightText: 2008 Allen Winter <winter@kde.org>
SPDX-FileCopyrightText: 2020 Tomaz Cananbrava <tcanabrava@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "KConfigParameters.h"
#include <QDebug>
#include <QFileInfo>
#include <iostream>
KConfigParameters::KConfigParameters(const QString &codegenFilename)
{
if (!codegenFilename.endsWith(QLatin1String(".kcfgc"))) {
std::cerr << "Codegen options file must have extension .kcfgc" << std::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()) {
std::cerr << "Class name missing" << std::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")) {
std::cerr << "Unknown translation system, falling back to Qt tr()" << std::endl;
}
translationSystem = QtTranslation;
}
qCategoryLoggingName = codegenConfig.value(QStringLiteral("CategoryLoggingName"), QString()).toString();
headerExtension = codegenConfig.value(QStringLiteral("HeaderExtension"), QStringLiteral("h")).toString();
qmlRegistration = codegenConfig.value(QStringLiteral("QmlRegistration")).toBool();
}
@@ -0,0 +1,68 @@
/*
This file is part of KDE.
SPDX-FileCopyrightText: 2003 Cornelius Schumacher <schumacher@kde.org>
SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
SPDX-FileCopyrightText: 2003 Zack Rusin <zack@kde.org>
SPDX-FileCopyrightText: 2006 Michaël Larouche <michael.larouche@kdemail.net>
SPDX-FileCopyrightText: 2008 Allen Winter <winter@kde.org>
SPDX-FileCopyrightText: 2020 Tomaz Cananbrava <tcanabrava@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#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;
bool allMutators;
bool setUserTexts;
bool allDefaultGetters;
bool dpointer;
bool globalEnums;
bool useEnumTypes;
bool itemAccessors;
bool allNotifiers;
TranslationSystem translationSystem;
QString translationDomain;
bool generateProperties;
QString baseName;
bool qmlRegistration;
};
#endif
@@ -0,0 +1,718 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2003 Cornelius Schumacher <schumacher@kde.org>
SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
SPDX-FileCopyrightText: 2003 Zack Rusin <zack@kde.org>
SPDX-FileCopyrightText: 2006 Michaël Larouche <michael.larouche@kdemail.net>
SPDX-FileCopyrightText: 2008 Allen Winter <winter@kde.org>
SPDX-FileCopyrightText: 2020 Tomaz Cananbrava <tcanabrava@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "KConfigSourceGenerator.h"
#include <QRegularExpression>
KConfigSourceGenerator::KConfigSourceGenerator(const QString &inputFile, const QString &baseDir, const KConfigParameters &cfg, ParseResult &parseResult)
: KConfigCodeGeneratorBase(inputFile, baseDir, baseDir + cfg.baseName + QLatin1String(".cpp"), cfg, parseResult)
{
}
void KConfigSourceGenerator::start()
{
KConfigCodeGeneratorBase::start();
stream() << '\n';
createHeaders();
if (!cfg().nameSpace.isEmpty()) {
stream() << "using namespace " << cfg().nameSpace << ";";
stream() << "\n\n";
}
createPrivateDPointerImplementation();
createSingletonImplementation();
createPreamble();
doConstructor();
doGetterSetterDPointerMode();
createDefaultValueGetterSetter();
createDestructor();
createNonModifyingSignalsHelper();
createSignalFlagsHandler();
}
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() << '\n';
addHeaders(cfg().sourceIncludes);
if (cfg().setUserTexts && cfg().translationSystem == KConfigParameters::KdeTranslation) {
addHeaders({QStringLiteral("klocalizedstring.h")});
stream() << '\n';
}
// 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() << '\n';
}
}
if (cfg().singleton && parseResult.cfgFileNameArg) {
addHeaders({QStringLiteral("QDebug")});
}
if (cfg().dpointer && parseResult.hasNonModifySignals) {
addHeaders({QStringLiteral("QSet")});
}
if (cfg().qmlRegistration && cfg().singleton) {
addHeaders({QStringLiteral("QQmlEngine")});
}
if (cfg().singleton) {
stream() << '\n';
}
}
void KConfigSourceGenerator::createPrivateDPointerImplementation()
{
// private class implementation
if (!cfg().dpointer) {
return;
}
QString group;
beginNamespaces();
stream() << "class " << cfg().className << "Private\n";
stream() << "{\n";
stream() << " public:\n";
// Create Members
for (const auto *entry : std::as_const(parseResult.entries)) {
if (entry->group != group) {
group = entry->group;
stream() << '\n';
stream() << " // " << group << '\n';
}
stream() << " " << cppType(entry->type) << " " << varName(entry->name, cfg());
if (!entry->param.isEmpty()) {
stream() << QStringLiteral("[%1]").arg(entry->paramMax + 1);
}
stream() << ";\n";
}
stream() << "\n // items\n";
// Create Items.
for (const auto *entry : std::as_const(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() << ";\n";
}
if (parseResult.hasNonModifySignals) {
stream() << " QSet<quint64> " << varName(QStringLiteral("settingsChanged"), cfg()) << ";\n";
}
stream() << "};\n\n";
endNamespaces();
}
void KConfigSourceGenerator::createSingletonImplementation()
{
// Singleton implementation
if (!cfg().singleton) {
return;
}
beginNamespaces();
stream() << "class " << cfg().className << "Helper\n";
stream() << '{' << '\n';
stream() << " public:\n";
stream() << " " << cfg().className << "Helper() : q(nullptr) {}\n";
stream() << " ~" << cfg().className << "Helper() { delete q; q = nullptr; }\n";
stream() << " " << cfg().className << "Helper(const " << cfg().className << "Helper&) = delete;\n";
stream() << " " << cfg().className << "Helper& operator=(const " << cfg().className << "Helper&) = delete;\n";
stream() << " " << cfg().className << " *q;\n";
stream() << "};\n";
endNamespaces();
stream() << "Q_GLOBAL_STATIC(" << cfg().className << "Helper, s_global" << cfg().className << ")\n";
stream() << cfg().className << " *" << cfg().className << "::self()\n";
stream() << "{\n";
if (parseResult.cfgFileNameArg) {
stream() << " if (!s_global" << cfg().className << "()->q)\n";
stream() << " qFatal(\"you need to call " << cfg().className << "::instance before using\");\n";
} else {
stream() << " if (!s_global" << cfg().className << "()->q) {\n";
stream() << " new " << cfg().className << ';' << '\n';
stream() << " s_global" << cfg().className << "()->q->read();\n";
stream() << " }\n\n";
}
stream() << " return s_global" << cfg().className << "()->q;\n";
stream() << "}\n\n";
if (cfg().qmlRegistration && cfg().singleton) {
stream() << cfg().className << " *" << cfg().className << "::create(QQmlEngine *, QJSEngine *)\n";
stream() << "{\n";
stream() << " QQmlEngine::setObjectOwnership(self(), QQmlEngine::CppOwnership);\n";
stream() << " return self();\n";
stream() << "}\n\n";
}
if (parseResult.cfgFileNameArg) {
auto instance = [this](const QString &type, const QString &arg, bool isString) {
stream() << "void " << cfg().className << "::instance(" << type << " " << arg << ")\n";
stream() << "{\n";
stream() << " if (s_global" << cfg().className << "()->q) {\n";
stream() << " qDebug() << \"" << cfg().className << "::instance called after the first use - ignoring\";\n";
stream() << " return;\n";
stream() << " }\n";
stream() << " new " << cfg().className << "(";
if (parseResult.cfgStateConfig) {
stream() << "KSharedConfig::openStateConfig(" << arg << ")";
} else if (isString) {
stream() << "KSharedConfig::openConfig(" << arg << ")";
} else {
stream() << "std::move(" << arg << ")";
}
stream() << ");\n";
stream() << " s_global" << cfg().className << "()->q->read();\n";
stream() << "}\n\n";
};
instance(QStringLiteral("const QString&"), QStringLiteral("cfgfilename"), true);
instance(QStringLiteral("KSharedConfig::Ptr"), QStringLiteral("config"), false);
}
}
void KConfigSourceGenerator::createPreamble()
{
QString cppPreamble;
for (const auto *entry : std::as_const(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 << '\n';
}
}
void KConfigSourceGenerator::createConstructorParameterList()
{
if (parseResult.cfgFileNameArg) {
if (!cfg().forceStringFilename) {
stream() << " KSharedConfig::Ptr config";
} else {
stream() << " const QString& config";
}
stream() << (parseResult.parameters.isEmpty() ? "" : ",");
}
for (auto it = parseResult.parameters.cbegin(); it != parseResult.parameters.cend(); ++it) {
if (it != parseResult.parameters.cbegin()) {
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.cfgStateConfig) {
stream() << " KSharedConfig::openStateConfig(QStringLiteral( \"" << parseResult.cfgFileName << "\") ";
} else 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() << ")\n";
}
void KConfigSourceGenerator::createInitializerList()
{
for (const auto &parameter : std::as_const(parseResult.parameters)) {
stream() << " , mParam" << parameter.name << "(" << parameter.name << ")\n";
}
}
void KConfigSourceGenerator::createEnums(const CfgEntry *entry)
{
if (entry->type != QLatin1String("Enum")) {
return;
}
stream() << " QList<" << cfg().inherits << "::ItemEnum::Choice> values" << entry->name << ";\n";
for (const auto &choice : std::as_const(entry->choices.choices)) {
stream() << " {\n";
stream() << " " << cfg().inherits << "::ItemEnum::Choice choice;\n";
stream() << " choice.name = QStringLiteral(\"" << choice.name << "\");\n";
if (cfg().setUserTexts) {
if (!choice.label.isEmpty()) {
stream() << " choice.label = " << translatedString(cfg(), choice.label, choice.context) << ";\n";
}
if (!choice.toolTip.isEmpty()) {
stream() << " choice.toolTip = " << translatedString(cfg(), choice.toolTip, choice.context) << ";\n";
}
if (!choice.whatsThis.isEmpty()) {
stream() << " choice.whatsThis = " << translatedString(cfg(), choice.whatsThis, choice.context) << ";\n";
}
}
stream() << " values" << entry->name << ".append( choice );\n";
stream() << " }\n";
}
}
void KConfigSourceGenerator::createNormalEntry(const CfgEntry *entry, const QString &key)
{
const QString itemVarStr = itemPath(entry, cfg());
const QString innerItemVarStr = innerItemVar(entry, cfg());
if (!entry->signalList.isEmpty()) {
stream() << " " << innerItemVarStr << " = " << newInnerItem(entry, key, entry->defaultValue, cfg()) << '\n';
}
stream() << " " << itemVarStr << " = " << newItem(entry, key, entry->defaultValue, cfg()) << '\n';
if (!entry->min.isEmpty()) {
stream() << " " << innerItemVarStr << "->setMinValue(" << entry->min << ");\n";
}
if (!entry->max.isEmpty()) {
stream() << " " << innerItemVarStr << "->setMaxValue(" << entry->max << ");\n";
}
if (cfg().setUserTexts) {
stream() << userTextsFunctions(entry, cfg());
}
if (cfg().allNotifiers || cfg().notifiers.contains(entry->name)) {
stream() << " " << itemVarStr << "->setWriteFlags(KConfigBase::Notify);\n";
}
for (const CfgEntry::Choice &choice : std::as_const(entry->choices.choices)) {
if (!choice.val.isEmpty()) {
stream() << " " << innerItemVarStr << "->setValueForChoice(QStringLiteral( \"" << choice.name << "\" ), QStringLiteral( \"" << choice.val
<< "\" ));\n";
}
}
if (!entry->parentGroup.isEmpty()) {
stream() << " " << itemVarStr << "->setGroup(cg" << QString(entry->group).remove(QRegularExpression(QStringLiteral("\\W"))) << ");\n";
}
stream() << " addItem( " << itemVarStr;
QString quotedName = entry->name;
addQuotes(quotedName);
if (quotedName != key) {
stream() << ", QStringLiteral( \"" << entry->name << "\" )";
}
stream() << " );\n";
}
// TODO : Some compiler option won't work or generate bogus settings file.
// * Does not manage properly Notifiers=true kcfgc option for parameterized entries :
// ** KConfigCompilerSignallingItem generated with wrong userData parameter (4th one).
// ** setWriteFlags() is missing.
// * Q_PROPERTY signal won't work
void KConfigSourceGenerator::createIndexedEntry(const CfgEntry *entry, const QString &key)
{
for (int i = 0; i <= entry->paramMax; i++) {
const QString argBracket = QStringLiteral("[%1]").arg(i);
const QString innerItemVarStr = innerItemVar(entry, cfg()) + argBracket;
const QString defaultStr = !entry->paramDefaultValues[i].isEmpty() ? entry->paramDefaultValues[i]
: !entry->defaultValue.isEmpty() ? paramString(entry->defaultValue, entry, i)
: defaultValue(entry->type);
if (!entry->signalList.isEmpty()) {
stream() << " " << innerItemVarStr << " = " << newInnerItem(entry, paramString(key, entry, i), defaultStr, cfg(), argBracket) << '\n';
}
const QString itemVarStr = itemPath(entry, cfg()) + argBracket;
stream() << " " << itemVarStr << " = " << newItem(entry, paramString(key, entry, i), defaultStr, cfg(), argBracket) << '\n';
if (!entry->min.isEmpty()) {
stream() << " " << innerItemVarStr << "->setMinValue(" << entry->min << ");\n";
}
if (!entry->max.isEmpty()) {
stream() << " " << innerItemVarStr << "->setMaxValue(" << entry->max << ");\n";
}
for (const CfgEntry::Choice &choice : std::as_const(entry->choices.choices)) {
if (!choice.val.isEmpty()) {
stream() << " " << innerItemVarStr << "->setValueForChoice(QStringLiteral( \"" << choice.name << "\" ), QStringLiteral( \"" << choice.val
<< "\" ));\n";
}
}
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() << "\" ) );\n";
}
}
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() << '\n';
}
first = false;
}
mCurrentGroup = entry->group;
if (!entry->parentGroup.isEmpty()) {
QString parentGroup = QString(entry->parentGroup).remove(QRegularExpression(QStringLiteral("\\W")));
if (!mConfigGroupList.contains(parentGroup)) {
stream() << " KConfigGroup cg" << parentGroup << "(this->config(), " << paramString(entry->parentGroup, parseResult.parameters) << ");\n";
mConfigGroupList << parentGroup;
}
QString currentGroup = QString(mCurrentGroup).remove(QRegularExpression(QStringLiteral("\\W")));
if (!mConfigGroupList.contains(currentGroup)) {
stream() << " KConfigGroup cg" << currentGroup << " = cg" << QString(entry->parentGroup).remove(QRegularExpression(QStringLiteral("\\W")))
<< ".group(" << paramString(mCurrentGroup, parseResult.parameters) << ");\n";
mConfigGroupList << currentGroup;
}
} else {
stream() << " setCurrentGroup( " << paramString(mCurrentGroup, parseResult.parameters) << " );";
stream() << "\n\n";
}
}
void KConfigSourceGenerator::doConstructor()
{
// Constructor
stream() << cfg().className << "::" << cfg().className << "(";
createConstructorParameterList();
stream() << " )\n";
stream() << " : ";
createParentConstructorCall();
createInitializerList();
stream() << "{\n";
if (cfg().parentInConstructor) {
stream() << " setParent(parent);\n";
}
if (cfg().dpointer) {
stream() << " d = new " << cfg().className << "Private;\n";
}
// Needed in case the singleton class is used as baseclass for
// another singleton.
if (cfg().singleton) {
stream() << " Q_ASSERT(!s_global" << cfg().className << "()->q);\n";
stream() << " s_global" << cfg().className << "()->q = this;\n";
}
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);\n";
stream() << '\n';
}
for (const auto *entry : std::as_const(parseResult.entries)) {
handleCurrentGroupChange(entry);
const QString key = paramString(entry->key, parseResult.parameters);
if (!entry->code.isEmpty()) {
stream() << entry->code << '\n';
}
createEnums(entry);
stream() << itemDeclaration(entry, cfg());
if (entry->param.isEmpty()) {
createNormalEntry(entry, key);
} else {
createIndexedEntry(entry, key);
}
}
stream() << "}\n\n";
}
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() << '\n';
// function body inline only if not using dpointer
// for BC mode
startScope();
// HACK: Fix memberAccessorBody
stream() << " " << memberAccessorBody(entry, cfg().globalEnums);
endScope();
stream() << '\n';
}
void KConfigSourceGenerator::createImmutableGetterDPointerMode(const CfgEntry *entry)
{
stream() << whitespace() << "";
stream() << "bool "
<< " " << immutableFunction(entry->name, cfg().className) << "(";
if (!entry->param.isEmpty()) {
stream() << " " << cppType(entry->paramType) << " i ";
}
stream() << ")" << Const() << '\n';
startScope();
memberImmutableBody(entry, cfg().globalEnums);
endScope();
stream() << '\n';
}
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 )\n";
// function body inline only if not using dpointer
// for BC mode
startScope();
memberMutatorBody(entry);
endScope();
stream() << '\n';
}
void KConfigSourceGenerator::createItemGetterDPointerMode(const CfgEntry *entry)
{
// Item accessor
if (!cfg().itemAccessors) {
return;
}
stream() << '\n';
stream() << cfg().inherits << "::Item" << itemType(entry->type) << " *" << getFunction(entry->name, cfg().className) << "Item(";
if (!entry->param.isEmpty()) {
stream() << " " << cppType(entry->paramType) << " i ";
}
stream() << ")\n";
startScope();
stream() << " " << itemAccessorBody(entry, cfg());
endScope();
}
void KConfigSourceGenerator::doGetterSetterDPointerMode()
{
if (!cfg().dpointer) {
return;
}
// setters and getters go in Cpp if in dpointer mode
for (const auto *entry : std::as_const(parseResult.entries)) {
createSetterDPointerMode(entry);
createGetterDPointerMode(entry);
createImmutableGetterDPointerMode(entry);
createItemGetterDPointerMode(entry);
stream() << '\n';
}
}
void KConfigSourceGenerator::createDefaultValueGetterSetter()
{
// default value getters always go in Cpp
for (const auto *entry : std::as_const(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() << '\n';
startScope();
stream() << memberGetDefaultBody(entry) << '\n';
endScope();
stream() << '\n';
}
}
}
void KConfigSourceGenerator::createDestructor()
{
stream() << cfg().className << "::~" << cfg().className << "()\n";
startScope();
if (cfg().dpointer) {
stream() << " delete d;\n";
}
if (cfg().singleton) {
const QString qgs = QLatin1String("s_global") + cfg().className;
stream() << " if (" << qgs << ".exists() && !" << qgs << ".isDestroyed()) {\n";
stream() << " " << qgs << "()->q = nullptr;\n";
stream() << " }\n";
}
endScope();
stream() << '\n';
}
void KConfigSourceGenerator::createNonModifyingSignalsHelper()
{
if (!parseResult.hasNonModifySignals) {
return;
}
stream() << "bool " << cfg().className << "::"
<< "usrSave()\n";
startScope();
stream() << " const bool res = " << cfg().inherits << "::usrSave();\n";
stream() << " if (!res) return false;\n\n";
for (const Signal &signal : std::as_const(parseResult.signalList)) {
if (signal.modify) {
continue;
}
stream() << " if (" << varPath(QStringLiteral("settingsChanged"), cfg()) << ".contains(" << signalEnumName(signal.name) << "))\n";
stream() << " Q_EMIT " << signal.name << "(";
auto it = signal.arguments.cbegin();
const auto itEnd = signal.arguments.cend();
while (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() << ");\n";
}
stream() << " " << varPath(QStringLiteral("settingsChanged"), cfg()) << ".clear();\n";
stream() << " return true;\n";
endScope();
}
void KConfigSourceGenerator::createSignalFlagsHandler()
{
if (parseResult.signalList.isEmpty()) {
return;
}
stream() << '\n';
stream() << "void " << cfg().className << "::"
<< "itemChanged(quint64 signalFlag) {\n";
if (parseResult.hasNonModifySignals) {
stream() << " " << varPath(QStringLiteral("settingsChanged"), cfg()) << ".insert(signalFlag);\n";
}
if (!parseResult.signalList.isEmpty()) {
stream() << '\n';
}
bool modifySignalsWritten = false;
for (const Signal &signal : std::as_const(parseResult.signalList)) {
if (signal.modify) {
if (!modifySignalsWritten) {
stream() << " switch (signalFlag) {\n";
modifySignalsWritten = true;
}
stream() << " case " << signalEnumName(signal.name) << ":\n";
stream() << " Q_EMIT " << signal.name << "();\n";
stream() << " break;\n";
}
}
if (modifySignalsWritten) {
stream() << " }\n";
}
stream() << "}\n";
}
@@ -0,0 +1,70 @@
/*
This file is part of KDE.
SPDX-FileCopyrightText: 2003 Cornelius Schumacher <schumacher@kde.org>
SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
SPDX-FileCopyrightText: 2003 Zack Rusin <zack@kde.org>
SPDX-FileCopyrightText: 2006 Michaël Larouche <michael.larouche@kdemail.net>
SPDX-FileCopyrightText: 2008 Allen Winter <winter@kde.org>
SPDX-FileCopyrightText: 2020 Tomaz Cananbrava <tcanabrava@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCONFIGSOURCEGENERATOR_H
#define KCONFIGSOURCEGENERATOR_H
#include "KConfigCodeGeneratorBase.h"
#include "KConfigCommonStructs.h"
#include <QList>
#include <QString>
class KConfigParameters;
class CfgEntry;
class QTextStream;
struct ParseResult;
class KConfigSourceGenerator : public KConfigCodeGeneratorBase
{
public:
KConfigSourceGenerator(const QString &inputFile, const QString &baseDir, const KConfigParameters &parameters, 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();
// 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 createImmutableGetterDPointerMode(const CfgEntry *entry);
void createSetterDPointerMode(const CfgEntry *entry);
void createItemGetterDPointerMode(const CfgEntry *entry);
private:
QString mCurrentGroup;
QStringList mConfigGroupList; // keeps track of generated KConfigGroup;
};
#endif
@@ -0,0 +1,564 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2003 Cornelius Schumacher <schumacher@kde.org>
SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
SPDX-FileCopyrightText: 2003 Zack Rusin <zack@kde.org>
SPDX-FileCopyrightText: 2006 Michaël Larouche <michael.larouche@kdemail.net>
SPDX-FileCopyrightText: 2008 Allen Winter <winter@kde.org>
SPDX-FileCopyrightText: 2020 Tomaz Cananbrava <tcanabrava@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "KConfigXmlParser.h"
#include <QDebug>
#include <QDomAttr>
#include <QDomElement>
#include <QDomNode>
#include <QFile>
#include <QList>
#include <QStringList>
#include <QTextStream>
#include <iostream>
// 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 &cfgChoices,
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( %1)").arg(literalString(defaultValue));
} else if ((type == QLatin1String("UrlList") || type == QLatin1String("StringList") || type == QLatin1String("PathList")) && !defaultValue.isEmpty()) {
QTextStream cpp(&code, QIODevice::WriteOnly | QIODevice::Append);
if (!code.isEmpty()) {
cpp << '\n';
}
if (type == QLatin1String("UrlList")) {
cpp << " QList<QUrl> default" << name << ";\n";
} else {
cpp << " QStringList default" << name << ";\n";
}
const QStringList defaults = defaultValue.split(QLatin1Char(','));
for (const auto &val : defaults) {
cpp << " default" << name << ".append( ";
if (type == QLatin1String("UrlList")) {
cpp << "QUrl::fromUserInput(";
}
cpp << "QString::fromUtf8( \"" << val << "\" ) ";
if (type == QLatin1String("UrlList")) {
cpp << ") ";
}
cpp << ");\n";
}
defaultValue = QLatin1String("default") + name;
} else if (type == QLatin1String("Color") && !defaultValue.isEmpty()) {
static const QRegularExpression colorRe(QRegularExpression::anchoredPattern(QStringLiteral("\\d+,\\s*\\d+,\\s*\\d+(,\\s*\\d+)?")));
if (colorRe.match(defaultValue).hasMatch()) {
defaultValue = QLatin1String("QColor( %1 )").arg(defaultValue);
} else {
defaultValue = QLatin1String("QColor( \"%1\" )").arg(defaultValue);
}
} else if (type == QLatin1String("Enum")) {
for (const auto &choice : cfgChoices.choices) {
if (choice.name == defaultValue) {
if (cfg.globalEnums && cfgChoices.name().isEmpty()) {
defaultValue.prepend(cfgChoices.prefix);
} else {
defaultValue.prepend(enumTypeQualifier(name, cfgChoices) + cfgChoices.prefix);
}
break;
}
}
} else if (type == QLatin1String("IntList")) {
QTextStream cpp(&code, QIODevice::WriteOnly | QIODevice::Append);
if (!code.isEmpty()) {
cpp << '\n';
}
cpp << " QList<int> default" << name << ";\n";
if (!defaultValue.isEmpty()) {
const QStringList defaults = defaultValue.split(QLatin1Char(','));
for (const auto &defaultVal : defaults) {
cpp << " default" << name << ".append( " << defaultVal << " );\n";
}
}
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()) {
std::cerr << "Parameter must have a name: " << qPrintable(dumpNode(e)) << std::endl;
exit(1);
}
if (readEntry.paramType.isEmpty()) {
std::cerr << "Parameter must have a type: " << qPrintable(dumpNode(e)) << std::endl;
exit(1);
}
if ((readEntry.paramType == QLatin1String("Int")) || (readEntry.paramType == QLatin1String("UInt"))) {
bool ok;
readEntry.paramMax = e.attribute(QStringLiteral("max")).toInt(&ok);
if (!ok) {
std::cerr << "Integer parameter must have a maximum (e.g. max=\"0\"): " << qPrintable(dumpNode(e)) << std::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()) {
std::cerr << "No values specified for parameter '" << qPrintable(readEntry.param) << "'." << std::endl;
exit(1);
}
readEntry.paramMax = readEntry.paramValues.count() - 1;
} else {
std::cerr << "Parameter '" << qPrintable(readEntry.param) << "' has type " << qPrintable(readEntry.paramType)
<< " but must be of type int, uint or Enum." << std::endl;
exit(1);
}
}
bool KConfigXmlParser::hasDefaultCode(CfgEntry &readEntry, const QDomElement &element)
{
Q_UNUSED(readEntry)
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;
const auto choiceNameRegex = QRegularExpression(QStringLiteral("\\w+"));
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()) {
std::cerr << "Tag <choice> requires attribute 'name'." << std::endl;
} else if (!choiceNameRegex.match(choice.name).hasMatch()) {
std::cerr << "Tag <choice> attribute 'name' must be compatible with Enum naming. name was '" << qPrintable(choice.name)
<< "'. You can use attribute 'value' to pass any string as the choice value." << std::endl;
}
choice.val = e2.attribute(QStringLiteral("value"));
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()) {
std::cerr << "Entry must have a name or a key: " << qPrintable(dumpNode(element)) << std::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(' '))) {
std::cout << "Entry '" << qPrintable(readEntry.name) << "' contains spaces! <name> elements can not contain spaces!" << std::endl;
readEntry.name.remove(QLatin1Char(' '));
}
if (readEntry.name.contains(QStringLiteral("$("))) {
if (readEntry.param.isEmpty()) {
std::cerr << "Name may not be parameterized: " << qPrintable(readEntry.name) << std::endl;
exit(1);
}
} else {
if (!readEntry.param.isEmpty()) {
std::cerr << "Name must contain '$(" << qPrintable(readEntry.param) << ")': " << qPrintable(readEntry.name) << std::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) {
std::cerr << "Index '" << qPrintable(index) << "' for default value is unknown." << std::endl;
exit(1);
}
}
if ((i < 0) || (i > readEntry.paramMax)) {
std::cerr << "Index '" << i << "' for default value is out of range [0, " << readEntry.paramMax << "]." << std::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 QString &parentGroup, 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;
readEntry.parentGroup = parentGroup;
const bool nameIsEmpty = readEntry.name.isEmpty();
readGroupElements(readEntry, element);
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) {
std::cerr << "The key '" << qPrintable(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."
<< std::endl;
} else {
std::cerr << "The name '" << qPrintable(readEntry.name) << "' is not a valid name for an entry." << std::endl;
}
exit(1);
}
if (mAllNames.contains(readEntry.name)) {
if (nameIsEmpty) {
std::cerr << "The key '" << qPrintable(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."
<< std::endl;
} else {
std::cerr << "The name '" << qPrintable(readEntry.name) << "' is not unique." << std::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->parentGroup = readEntry.parentGroup;
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;
createChangedSignal(*result);
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);
if (!input.open(QIODevice::ReadOnly)) {
qFatal("Could not open input file: %s", qUtf8Printable(mInputFileName));
}
QDomDocument doc;
const QDomDocument::ParseResult parseResult = doc.setContent(&input);
if (!parseResult) {
std::cerr << "Unable to load document." << std::endl;
std::cerr << "Parse error in " << qPrintable(mInputFileName) << ", line " << parseResult.errorLine << ", col " << parseResult.errorColumn << ": "
<< qPrintable(parseResult.errorMessage) << std::endl;
exit(1);
}
QDomElement cfgElement = doc.documentElement();
if (cfgElement.isNull()) {
std::cerr << "No document in kcfg file" << std::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()) {
std::cerr << "Group without name" << std::endl;
exit(1);
}
const QString parentGroup = e.attribute(QStringLiteral("parentGroupName"));
for (QDomElement e2 = e.firstChildElement(); !e2.isNull(); e2 = e2.nextSiblingElement()) {
if (e2.tagName() != QLatin1String("entry")) {
continue;
}
CfgEntry *entry = parseEntry(group, parentGroup, e2);
if (entry) {
mParseResult.entries.append(entry);
} else {
std::cerr << "Can not parse entry." << std::endl;
exit(1);
}
}
}
void KConfigXmlParser::readKcfgfileTag(const QDomElement &e)
{
mParseResult.cfgFileName = e.attribute(QStringLiteral("name"));
mParseResult.cfgStateConfig = e.attribute(QStringLiteral("stateConfig")).toLower() == QLatin1String("true");
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()) {
std::cerr << "Signal without name." << std::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()) {
std::cerr << "Signal argument without type." << std::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);
}
@@ -0,0 +1,72 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2003 Cornelius Schumacher <schumacher@kde.org>
SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
SPDX-FileCopyrightText: 2003 Zack Rusin <zack@kde.org>
SPDX-FileCopyrightText: 2006 Michaël Larouche <michael.larouche@kdemail.net>
SPDX-FileCopyrightText: 2008 Allen Winter <winter@kde.org>
SPDX-FileCopyrightText: 2020 Tomaz Cananbrava <tcanabrava@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCONFIGXMLPARSER_H
#define KCONFIGXMLPARSER_H
#include <QDomDocument>
#include <QRegularExpression>
#include <QString>
#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 QString &parentGroup, 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
@@ -0,0 +1,560 @@
/**
\page kconfig_compiler The KDE Configuration Compiler
kconfig_compiler generates C++ source code from an XML file containing
information about configuration options (.kcfg) and a file that provides
the code generation options (.kcfgc) The generated class is based on
KConfigSkeleton and provides an API for the application to access its
configuration data.
The generated C++ source code is output to a .h and a .cpp file, whose base
name is the same as that of the .kcfgc file.
\section kcfg_format The .kcfg XML file format: description of the configuration skeleton
The structure of the .kcfg file is described by its DTD kcfg.xsd.
The \<kcfgfile\> tag may contain either the "name" attribute, which should be the name
of the configuration file described, or the "arg" attribute, which, if set to
"true", will allow you to pass the KSharedConfig::Ptr object to use.
If neither "name" nor "arg" is set, the default configuration file
("\<appname\>rc") will be used.
The \<include\> tags are optional and may contain C++ header files that
are needed to compile the code needed to compute default values. To generate
a \#include "..." statement instead of \#include \<...\>, enclose the header
file name in double quotes (e.g. \<include\>"header.h"\</include\>).
The remaining entries in the XML file are grouped by the tag \<group\>
which describes the corresponding group in the configuration file.
The individual entries must have at least a name or a key. The key is used
as the key in the config file, while the name is used to create accessor and
modifier functions. If \<key\> is given, but not \<name\>, the name is
constructed by removing all spaces from \<key\>. If \<name\> is given, but
not \<key\>, the key is the same as \<name\>.
An entry must also have a type. The list of allowable types is
specified in the DTD and loosely follows the list of types supported
by the QVariant with exception of the clearly binary types
(e.g. Pixmap, Image...) which are not supported. Besides those basic
types the following special types are supported:
<dl>
<dt>Path</dt>
<dd>
This is a string that is specially treated as a file-path.
In particular paths in the home directory are prefixed with $HOME in
when being stored in the configuration file.
</dd>
<dt>Enum</dt>
<dd>
This indicates an enumeration. The possible enum values and optional
enum name should be provided via the \<choices\> tag. Enum values are
accessed as integers by the application but stored as strings in the
configuration file. This makes it possible to add more values at a later
date without breaking compatibility.
</dd>
<dt>IntList</dt>
<dd>
This indicates a list of integers. This information is provided
to the application as QList<int>. Useful for storing QSplitter
geometries.
</dd>
<dt>Color</dt>
<dd>
Isn't a special type but has special input. It is generated as QColor.
Any valid input to QColor(QString) can be used (hex or SVG keyword notation)
as well as a special format r,g,b,a where the a denotes the alpha channel and
may be omitted.
</dd>
</dl>
An entry can optionally have a default value which is used as default when
the value isn't specified in any config file. Default values are interpreted
as literal constant values. If a default value needs to be computed
or if it needs to be obtained from a function call, the \<default\> tag
should contain the code="true" attribute. The contents of the \<default\>
tag is then considered to be a C++ expression. Note that in this case you
might have to add an \<include\> tag as described above, or a
SourceIncludeFiles entry in the .kcfgc file as described below, so that the
code which computes the default value can be compiled.
Additional code for computing default values can be provided outside any
entry definition via the \<code\> tag. The contents of the \<code\> tag is
inserted as-is. A typical use for this is to compute a common default value
which can then be referenced by multiple entries that follow.
\section kcfgc_format The .kcfgc file format: code generation options
The options for generating the C++ sources are read from the file with the
extension .kcfgc. To generate a class add the corresponding kcfgc file to the
SOURCES line in the Makefile.am.
The following options are read from the kcfgc file:
<dl>
<dt>File=\<string\></dt>
<dd>
Default: \<programname\>.kcfg \n
Name of kcfg file containing the options the class is generated for
<!-- what? -->
</dd>
<dt>HeaderExtension=\<string\></dt>
<dd>
Default: h \n
Since KF 5.57 \n
Extension to use for the name of the generated C++ header files.
</dd>
<dt>NameSpace=\<string\></dt>
<dd>
Optional namespace for generated class
</dd>
<dt>ClassName=\<string\></dt>
<dd>
Name of generated class (required)
</dd>
<dt>Inherits=\<string\></dt>
<dd>
Default: KConfigSkeleton \n
Class the generated class inherits from.
This class must inherit KConfigSkeleton and must provide a default
constructor (kcfgfile not specified), a constructor taking a
QString argument (kcfgfile with "name" attribute) and a constructor
taking a KSharedConfig::Ptr as argument (kcfgfile with "arg" attribute).
Please refer to the documentation of KConfigSkeleton.
</dd>
<dt>Visibility=\<string\></dt>
<dd>
Inserts visibility directive (for example KDE_EXPORT) between
"class" keyword and class name in header file
</dd>
<dt>Singleton=\<bool\></dt>
<dd>
Default: false \n
Generated class is a singleton.
</dd>
<dt>CustomAdditions=\<bool\></dt>
<dd>
<!-- what? -->
</dd>
<dt>MemberVariables=\<string\></dt>
<dd>
Values: public, protected, private, dpointer \n
Default: private \n
C++ access modifier used for member variables holding the configuration
values
</dd>
<dt>IncludeFiles=\<string\>, \<string\> ...</dt>
<dd>
Names of files to be included in the header of the generated class.
Enclose a file name in (escaped) double quotes to generate
\#include "..." instead of \#include \<...\>.
</dd>
<dt>SourceIncludeFiles=\<string\>, \<string\> ...</dt>
<dd>
Names of files to be included in the source file of the generated class.
Enclose a file name in (escaped) double quotes to generate
\#include "..." instead of \#include \<...\>.
</dd>
<dt>Mutators=\<value\></dt>
<dd>
Values: true, false or a comma separated list of options \n
Default: false \n
If true, mutator functions for all configuration options are generated.
If false, no mutator functions are generated. If a list is provided,
mutator functions are generated for the options that are listed.
</dd>
<dt>Notifiers=\<value\></dt>
<dd>
Values: true, false or a comma separated list of options \n
Default: false \n
If true, entries will be written with the <b>Notify</b> flag enabled,
so changes can be detected using <b>KConfigWatcher</b>.
If a list is provided, the options that are listed are written with
said flag.
</dd>
<dt>DefaultValueGetters=\<value\></dt>
<dd>
Values: true, false or a comma separated list of options \n
Default: false \n
If true, functions to return the default value of all configuration options
are generated. If false, no default value functions are generated. If a list
is provided, default value functions are generated for the options that are listed.
</dd>
<dt>ItemAccessors=\<bool\></dt>
<dd>
Default: false \n
Generate accessor functions for the KConfigSkeletonItem objects
corresponding to the configuration options. If <b>SetUserTexts</b> is set,
<b>ItemAccessors</b> also has to be set.
</dd>
<dt>SetUserTexts=\<bool\></dt>
<dd>
Default: false \n
Set the label and whatthis texts of the items from the kcfg file.
If <b>SetUserTexts</b> is set, <b>ItemAccessors</b> also has to be set.
</dd>
<dt>GlobalEnums=\<bool\></dt>
<dd>
Default: false \n
If set to true all choices of Enum items will be created in the global
scope of the generated class. If set to false, each Enum item whose enum is not
explicitly named will get its own namespace for its choices.
</dd>
<dt>UseEnumTypes=\<bool\></dt>
<dd>
Default: false \n
If set to true, all Enum items whose enums are named will use enum types for
the return value of accessor functions and for the parameter of mutator
functions. This eliminates the need to cast accessor return values to the enum
type if you want to use the enum type in your own code. If set to false,
accessor return values and mutator parameters will be of type int.
</dd>
<dt>ForceStringFilename=\<bool\></dt>
<dd>
Default: false \n
If set to true, forces the first parameter of the generated class
to be a QString when using an argument for the filename.
This is useful to specify at runtime the filename of the configuration class.
</dd>
<dt>GenerateProperties=\<bool\></dt>
<dd>
Default: false \n
If set to true, a Q_PROPERTY will be generated for each member variable holding a
configuration value and the Q_OBJECT macro will be added to the generated class.
Note that you will also need to pass the GENERATE_MOC option to the
kconfig_add_kcfg_files macro.
</dd>
<dt>ParentInConstructor=\<bool\></dt>
<dd>
Default: false \n
If set to true, the generated constructor will take an additional QObject*
parameter that will be used as the object's parent.
This is useful when working with KQuickAddons::ManagedConfigModule
to set it as the parent of the generated class to allow automatic
settings discovery and handle the deallocation.
Note this parameter is incompatible with <b>Singleton</b> set to true.
</dd>
<dt>TranslationSystem=\<string\></dt>
<dd>
Default: qt \n
Set the translation system for label, tooltip and whatsthis text in generated KConfigSkeleton.
Set the value to <b>kde</b> to use the KDE Framework translation system, see KI18n.
</dd>
<dt>TranslationDomain=\<value\></dt>
<dd>
When <b>TranslationSystem=kde</b> is set, allow to specify the domain in which to look for translations.
</dd>
<dt>QmlRegistration=\<bool></dt>
<dd>
Default: false\n
If set to true the generated code will use the QML_ELEMENT macro to register the type to QML.
If Singleton is also true it will be registered as a QML singleton using QML_SINGLETON
</dd>
</dl>
\section entry_options Advanced entry options
There are several possibilities to parameterize entries.
\subsection parametrized_entries Parameterized entries
An entry can be parameterized using a fixed range parameter specified with
the \<parameter\> tag. Such parameter can either be an Enum or an int. An Enum
parameter should specify the possible enumeration values with the \<choices\>
tag. An int parameter should specify its maximum value. Its minimum value
is always 0.
A parameterized entry is expanded to a number of entries, one for each
value in the parameter range. The name and key should contain a reference
to the parameter in the form of $(parameter-name). When expanding the entries
the $(parameter-name) part is replaced with the value of the parameter.
In the case of an Enum parameter it is replaced with the name of the
enumuration value. In the case of an int parameter it is replaced with
the numeric value of the parameter.
Parameterized entries all share the same default value unless different
default values have been specified for specific parameter values.
This can be done with the param= attribute of the \<default\>. When a
param attribute is specified the default value only applies to that
particular parameter value.
Example 1:
\code{.xml}
<entry name="Color$(ColorIndex)" type="Color" key="color_$(ColorIndex)">
<parameter name="ColorIndex" type="Int" max="3"/>
<default param="0">#ff0000</default>
<default param="1">#00ff00</default>
<default param="2">#0000ff</default>
<default param="3">#ffff00</default>
</entry>
\endcode
The above describes 4 color configuration entries with the following defaults:
\verbatim
color_0=#ff0000
color_1=#00ff00
color_2=#0000ff
color_3=#ffff00
\endverbatim
The configuration options will be accessible to the application via
a QColor color(int ColorIndex) and a
void setColor(int ColorIndex, const QColor &v) function.
Example 2:
\code{.xml}
<entry name="Sound$(SoundEvent)" type="String" key="sound_$(SoundEvent)">
<parameter name="SoundEvent" type="Enum">
<values>
<value>Explosion</value>
<value>Crash</value>
<value>Missile</value>
</values>
</parameter>
<default param="Explosion">boom.wav</default>
<default param="Crash">crash.wav</default>
<default param="Missile">missile.wav</default>
</entry>
\endcode
The above describes 3 string configuration entries with the following defaults:
\verbatim
sound_Explosion=boom.wav
sound_Crash=crash.wav
sound_Missile=missile.wav
\endverbatim
The configuration options will be accessible to the application via
a QString sound(int SoundEvent) and a
void setSound(int SoundEvent, const QString &v) function.
\subsection parametrized_groups Parameterized groups
A group name can be parametrized using a parameter given to the KConfigSkeleton
instance (which means this feature cannot be used with singleton classes).
Example 1:
\code{.xml}
<kcfgfile name="testrc">
<parameter name="groupname"/>
</kcfgfile>
<group name="$(groupname)">
<entry key="Text" type="string">
</entry>
</group>
\endcode
In this case passing "Group2" as the 'groupname' parameter to the generated class
will make it use group "Group2" for the entry "Text".
By setting the stateConfig attribute of kcfgfile to "true", KSharedConfig::openStateConfig is used.
This should be used when one stores volatile data, like window sizes or autocompletion texts.
It is recommended to have at least two separate kcfg files for the different kinds of data.
NOTE: This option is ignored when ForceStringFilename is set.
\subsection enums Enums
By default, if <b>GlobalEnums</b> is set to false, a separate named enum will be generated
for each Enum entry. Since each enum is defined in a little enclosing class of its own,
this allows the same Enum value names to be used in different enums. For example, the
.kcfg entry
\code{.xml}
<entry name="KeepData" type="Enum">
<choices>
<choice name="Do"/>
<choice name="Dont" value="Don't"/>
</choices>
</entry>
\endcode
will generate this public class containing the enum definition, inside the generated class:
\code{.cpp}
class EnumKeepData
{
public:
enum type { Do, Dont, COUNT };
};
\endcode
Since 5.68, if present the <b>value</b> attribute will be used as the choice value written to the backend
instead of the <b>name</b>, allowing to write text incompatible with enum naming.
Alternatively, if <b>GlobalEnums</b> is set to true, all Enum items are defined as
unnamed enums in the global scope of the generated class. In this case, all Enum values
must have different names to avoid clashes. However, you can use a 'prefix' argument
in \<choices\> to prevent duplicate enum member names clashing. Using this, the Enum value
names are prefixed in code with the string you specify. For example, if <b>GlobalEnums</b>
is set to true, the .kcfg entry
\code{.xml}
<entry name="KeepData" type="Enum">
<choices prefix="Keep_">
<choice name="Do"/>
<choice name="Dont" value="Don't"/>
</choices>
</entry>
\endcode
will generate config file entries of "KeepData=Do" and "KeepData=Dont", but the enum
will be declared
\code{.cpp}
enum { Keep_Do, Keep_Dont };
\endcode
It is possible to specify your own name for a generated enum, by including a
'name' parameter in \<choices\>. Just like unnamed enums, this enum will be defined in
the global scope of the generated class (without any enclosing class of its own).
Therefore the names of Enum values must be unique across both unnamed enums (if
<b>GlobalEnums</b> is set to true) and all specifically named enums.
An example of a specifically named enum:
\code{.xml}
<entry name="KeepData" type="Enum">
<choices name="Types">
<choice name="Do"/>
<choice name="Dont"/>
</choices>
</entry>
\endcode
which results in the following enum declaration, inside the generated class:
\code{.cpp}
enum Types { Do, Dont };
\endcode
It is also possible to specify the use of enums external to the generated class, by
including the string "::" in the enum name - just ensure that it is sufficiently
qualified to be unambiguous in use. To specify use of an unnamed enum, append a
trailing "::". For example, to use the enum 'myEnum' defined in class ClassA, use
either of
\code{.xml}
<choices name="ClassA::myEnum">
<choices name="::ClassA::myEnum">
\endcode
To specify an unnamed enum in namespace ProgSpace, use
\code{.xml}
<choices name="ProgSpace::">
\endcode
To specify a top-level unnamed enum, use
\code{.xml}
<choices name="::">
\endcode
To specify the top-level enum 'anotherEnum', use
\code{.xml}
<choices name="::anotherEnum">
\endcode
\subsection notify_signals Notify signals
An entry can emit a signal when it gets changed. First of all, you must
define a list of signals for the configuration class. The signal's name may be
any legal identifier you wish. The \<argument\> tag allows you to specify arguments
for the emitted signal. It supports all types as defined in
the KConfigXT DTD. The argument value must specify the name, without spaces, of one
of the entries defined in the .kcfg file.
A signal definition can also contain a \<label\> tag which will be
the documentation line in the generated file.
\code{.xml}
<signal name="emoticonSettingsChanged" />
<signal name="styleChanged">
<label>Tell when a complete style change.</label>
<argument type="String">stylePath</argument>
<argument type="String">StyleCSSVariant</argument>
</signal>
\endcode
After defining the signals, you must tell which signal to emit for the entry.
A signal can be emitted by multiple entries. Also, you don't need to specify the arguments
for a signal, the signal name will suffice.
\code{.xml}
<entry key="stylePath" type="String">
<label>Absolute path to a directory containing a Adium/Kopete chat window style.</label>
<emit signal="styleChanged" />
</entry>
\endcode
The signal will be emitted when save() is called.
@warning
You must not call save() in direct response to any notify signal.
If you need to call save(),
use a QueuedConnection to listen to the notify signal.
You can also listen to the generic configChanged() signal from KConfigSkeleton to
notify your application about configuration changes.
In that case you may also call save() in direct response.
Note that you will also need to pass the GENERATE_MOC option to the kconfig_add_kcfg_files macro.
\subsection translation_context Translation context
In the kcfg file you can specify the translation's context for \<tooltip\>, \<whatsthis\> and \<label\> element for an entry.
\code{.xml}
<entry type="Bool" key="Auto Save">
<label>Enable automatic saving of calendar</label>
<whatsthis context="@info:whatsthis">Enable automatic saving of calendars to have calendars saved automatically.</whatsthis>
<default>false</default>
</entry>
\endcode
For more information on translation context and how to write good translatable text,
please refer to https://api.kde.org/frameworks/ki18n/html/prg_guide.html
*/
@@ -0,0 +1,85 @@
#!/usr/bin/perl
if ( @ARGV < 1 ) {
print STDERR "Missing arg: filename\n";
exit 1;
}
$file = $ARGV[0];
$file =~ /^(.*)\.[^\.]*$/;
$filebase = $1;
$header_extension = @ARGV > 1 ? $ARGV[1] : "h";
$source_extension = @ARGV > 2 ? $ARGV[2] : "cpp";
$file_h = "$filebase.$header_extension";
$file_cpp = "$filebase.$source_extension";
$kcfgc = $file . "c";
$cmd = "./kconfig_compiler_kf6 $file $kcfgc";
#print "CMD $cmd\n";
if ( system( $cmd ) != 0 ) {
print STDERR "Unable to run kconfig_compiler_kf6\n";
exit 1;
}
checkfile( $file_h );
checkfile( $file_cpp );
exit 0;
sub checkfile()
{
my $file = shift;
$file =~ /\/([^\/]*)$/;
my $filename = $1;
print "Checking '$filename':\n";
my @ref;
if ( !open( REF, "$file.ref" ) ) {
print STDERR "Unable to open $file.ref\n";
exit 1;
}
while( <REF> ) {
push @ref, $_;
}
close REF;
if ( !open( READ, $filename ) ) {
print STDERR "Unable to open $filename\n";
exit 1;
}
$error = 0;
$i = 0;
$line = 1;
while( <READ> ) {
$out = $_;
$ref = @ref[$i++];
if ( $out ne $ref ) {
$error++;
print " Line $line: Expected : $ref";
print " Line $line: Compiler output : $out";
}
$line++;
}
close READ;
if ( $error > 0 ) {
print "\n FAILED: $error errors found.\n";
if ( $error > 5 ) {
system( "diff -u $file.ref $filename" );
}
exit 1;
} else {
print " OK\n";
}
}
@@ -0,0 +1,239 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- kcfg XSD v1.0 -->
<xsd:schema
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://www.kde.org/standards/kcfg/1.0"
xmlns:kcfg="http://www.kde.org/standards/kcfg/1.0"
targetNamespace="http://www.kde.org/standards/kcfg/1.0"
version="1.0"
elementFormDefault="qualified" >
<xsd:annotation>
<xsd:documentation>
SPDX-FileCopyrightText: 2003 Cornelius Schumacher &lt;schumacher@kde.org&gt;
SPDX-FileCopyrightText: 2003 Waldo Bastian &lt;bastian@kde.org&gt;
SPDX-FileCopyrightText: 2003 Zack Rusin &lt;zack@kde.org&gt;
SPDX-FileCopyrightText: 2004 Frans Englich &lt;frans.englich@telia.com&gt;
SPDX-FileCopyrightText: 2006 Michaël Larouche &lt;michael.larouche@kdemail.net&gt;
Permission to use, copy, modify and distribute this DTD
and its accompanying documentation for any purpose and without fee
is hereby granted in perpetuity, provided that the above copyright
notice and this paragraph appear in all copies. The copyright
holders make no representation about the suitability of the DTD for
any purpose. It is provided "as is" without expressed or implied
warranty.
</xsd:documentation>
</xsd:annotation>
<xsd:annotation>
<xsd:documentation>
A Schema for KDE's KConfigXT XML format. It is similar to the DTD
found at:
http://www.kde.org/standards/kcfg/1.0/kcfg.dtd
Documents valid against the Schema version are backwards compatible
to the DTD. Validating against the Schema instead of the DTD is
recommended, since the former provides better validation.
A document instance of this Schema should have a declaration
looking like this:
<![CDATA[
<?xml version="1.0" encoding="UTF-8" ?>
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
<!-- the content -->
</kcfg>
]]>
</xsd:documentation>
</xsd:annotation>
<xsd:element name="kcfg">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="include" minOccurs="0" maxOccurs="unbounded" type="xsd:string"/>
<xsd:element name="kcfgfile" minOccurs="0" maxOccurs="1" >
<xsd:complexType>
<xsd:sequence>
<xsd:element name="parameter" type="kcfg:parameter" minOccurs="0" maxOccurs="unbounded" />
<!-- FIXME: Are really unbounded occurrences of parameter allowed? -->
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="optional"/>
<xsd:attribute name="stateConfig" type="xsd:boolean" use="optional"/>
<xsd:attribute name="arg" type="xsd:boolean" use="optional"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="signal" type="kcfg:signal" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="group" maxOccurs="unbounded" >
<xsd:complexType>
<xsd:sequence>
<xsd:element name="entry" maxOccurs="unbounded">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="parameter" minOccurs="0" type="kcfg:parameter"/>
<xsd:element name="label" minOccurs="0" type="kcfg:translatableString"/>
<xsd:element name="whatsthis" minOccurs="0" type="kcfg:translatableString"/>
<xsd:element name="tooltip" minOccurs="0" type="kcfg:translatableString"/>
<xsd:element name="choices" minOccurs="0">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="choice" maxOccurs="unbounded">
<xsd:complexType>
<xsd:all>
<xsd:element minOccurs="0" name="label" type="kcfg:translatableString"/>
<xsd:element minOccurs="0" name="whatsthis" type="kcfg:translatableString"/>
<xsd:element minOccurs="0" name="tooltip" type="kcfg:translatableString"/>
</xsd:all>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="value" use="optional" type="xsd:string"/>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="name" use="optional" type="xsd:string"/>
<xsd:attribute name="prefix" use="optional" type="xsd:string"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="code" minOccurs="0" type="kcfg:code"/>
<xsd:element name="default" maxOccurs="unbounded" minOccurs="0" >
<xsd:complexType>
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute use="optional" name="code" type="xsd:boolean"/>
<xsd:attribute use="optional" name="param" type="xsd:string"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="min" minOccurs="0" >
<xsd:complexType>
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="code" type="xsd:boolean"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="max" minOccurs="0">
<xsd:complexType>
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="code" type="xsd:boolean"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="emit" minOccurs="0">
<xsd:complexType>
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="signal" use="required" type="xsd:string"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
</xsd:element>
</xsd:choice>
<xsd:attribute name="name" use="optional" type="xsd:string"/>
<xsd:attribute name="key" use="optional" type="xsd:string"/>
<xsd:attribute name="hidden" use="optional" type="xsd:boolean"/>
<xsd:attribute name="type" type="kcfg:datatype"/>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="parentGroupName" use="optional" type="xsd:string"/>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:simpleType name="datatype">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="String"/>
<xsd:enumeration value="StringList"/>
<xsd:enumeration value="Font"/>
<xsd:enumeration value="Rect"/>
<xsd:enumeration value="RectF"/>
<xsd:enumeration value="Size"/>
<xsd:enumeration value="SizeF"/>
<xsd:enumeration value="Color"/>
<xsd:enumeration value="Point"/>
<xsd:enumeration value="PointF"/>
<xsd:enumeration value="Int"/>
<xsd:enumeration value="UInt"/>
<xsd:enumeration value="Bool"/>
<xsd:enumeration value="Double"/>
<xsd:enumeration value="DateTime"/>
<xsd:enumeration value="LongLong"/>
<xsd:enumeration value="ULongLong"/>
<xsd:enumeration value="IntList"/>
<xsd:enumeration value="Enum"/>
<xsd:enumeration value="Path"/>
<xsd:enumeration value="PathList"/>
<xsd:enumeration value="Password"/>
<xsd:enumeration value="Url"/>
<xsd:enumeration value="UrlList"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="parameter">
<xsd:sequence>
<xsd:element minOccurs="0" name="values">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" maxOccurs="unbounded" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" use="optional" type="kcfg:datatype" />
<xsd:attribute name="max" use="optional" type="xsd:positiveInteger"/>
</xsd:complexType>
<xsd:complexType name="code">
<xsd:simpleContent>
<xsd:extension base="xsd:string"/>
</xsd:simpleContent>
</xsd:complexType>
<xsd:complexType name="signal">
<xsd:sequence>
<xsd:element name="label" minOccurs="0" type="xsd:string"/>
<xsd:element name="argument" maxOccurs="unbounded" minOccurs="0" >
<xsd:complexType>
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute use="required" name="type" type="kcfg:datatype"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="translatableString">
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute use="optional" name="context" type="xsd:string"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
</xsd:schema>
@@ -0,0 +1,809 @@
/*
This file is part of KDE.
SPDX-FileCopyrightText: 2003 Cornelius Schumacher <schumacher@kde.org>
SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
SPDX-FileCopyrightText: 2003 Zack Rusin <zack@kde.org>
SPDX-FileCopyrightText: 2006 Michaël Larouche <michael.larouche@kdemail.net>
SPDX-FileCopyrightText: 2008 Allen Winter <winter@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include <QCommandLineOption>
#include <QCommandLineParser>
#include <QCoreApplication>
#include <QDomAttr>
#include <QFile>
#include <QFileInfo>
#include <QRegularExpression>
#include <QSettings>
#include <QStringList>
#include <QTextStream>
#include <algorithm>
#include <iostream>
#include <ostream>
#include <stdlib.h>
#include "../core/kconfig_version.h"
#include "KConfigCommonStructs.h"
#include "KConfigHeaderGenerator.h"
#include "KConfigParameters.h"
#include "KConfigSourceGenerator.h"
#include "KConfigXmlParser.h"
QString varName(const QString &n, const KConfigParameters &cfg)
{
QString result;
if (!cfg.dpointer) {
result = QChar::fromLatin1('m') + n;
result[1] = result.at(1).toUpper();
} else {
result = n;
result[0] = result.at(0).toLower();
}
return result;
}
QString varPath(const QString &n, const KConfigParameters &cfg)
{
QString result;
if (cfg.dpointer) {
result = QLatin1String{"d->"} + varName(n, cfg);
} else {
result = varName(n, cfg);
}
return result;
}
QString enumName(const QString &n)
{
QString result = QLatin1String("Enum") + n;
result[4] = result.at(4).toUpper();
return result;
}
QString enumName(const QString &n, const CfgEntry::Choices &c)
{
QString result = c.name();
if (result.isEmpty()) {
result = QLatin1String("Enum") + n;
result[4] = result.at(4).toUpper();
}
return result;
}
QString enumType(const CfgEntry *e, bool globalEnums)
{
QString result = e->choices.name();
if (result.isEmpty()) {
result = QLatin1String("Enum") + e->name;
if (!globalEnums) {
result += QLatin1String("::type");
}
result[4] = result.at(4).toUpper();
}
return result;
}
QString enumTypeQualifier(const QString &n, const CfgEntry::Choices &c)
{
QString result = c.name();
if (result.isEmpty()) {
result = QLatin1String("Enum") + n + QLatin1String("::");
result[4] = result.at(4).toUpper();
} else if (c.external()) {
result = c.externalQualifier();
} else {
result.clear();
}
return result;
}
QString setFunction(const QString &n, const QString &className)
{
QString result = QLatin1String("set") + n;
result[3] = result.at(3).toUpper();
if (!className.isEmpty()) {
result = className + QLatin1String("::") + result;
}
return result;
}
QString changeSignalName(const QString &n)
{
return n + QLatin1String{"Changed"};
}
QString getDefaultFunction(const QString &n, const QString &className)
{
QString result = QLatin1String("default%1Value").arg(n);
result[7] = result.at(7).toUpper();
if (!className.isEmpty()) {
result.prepend(className + QLatin1String("::"));
}
return result;
}
QString getFunction(const QString &n, const QString &className)
{
QString result = n;
result[0] = result.at(0).toLower();
if (!className.isEmpty()) {
result.prepend(className + QLatin1String("::"));
}
return result;
}
QString immutableFunction(const QString &n, const QString &className)
{
QString result = QLatin1String("is") + n;
result[2] = result.at(2).toUpper();
result += QLatin1String{"Immutable"};
if (!className.isEmpty()) {
result.prepend(className + QLatin1String("::"));
}
return result;
}
void addQuotes(QString &s)
{
if (!s.startsWith(QLatin1Char('"'))) {
s.prepend(QLatin1Char('"'));
}
if (!s.endsWith(QLatin1Char('"'))) {
s.append(QLatin1Char('"'));
}
}
static QString quoteString(const QString &s)
{
QString r = s;
r.replace(QLatin1Char('\\'), QLatin1String("\\\\"));
r.replace(QLatin1Char('\"'), QLatin1String("\\\""));
r.remove(QLatin1Char('\r'));
r.replace(QLatin1Char('\n'), QLatin1String("\\n\"\n\""));
return QLatin1Char('\"') + r + QLatin1Char('\"');
}
QString literalString(const QString &str)
{
const bool isAscii = std::none_of(str.cbegin(), str.cend(), [](const QChar ch) {
return ch.unicode() > 127;
});
if (isAscii) {
return QLatin1String("QStringLiteral( %1 )").arg(quoteString(str));
} else {
return QLatin1String("QString::fromUtf8( %1 )").arg(quoteString(str));
}
}
QString signalEnumName(const QString &signalName)
{
QString result;
result = QLatin1String("signal") + signalName;
result[6] = result.at(6).toUpper();
return result;
}
bool isUnsigned(const QString &type)
{
return type == QLatin1String("UInt") || type == QLatin1String("ULongLong");
}
/**
Return parameter declaration for given type.
*/
QString param(const QString &t)
{
const QString type = t.toLower();
if (type == QLatin1String("string")) {
return QStringLiteral("const QString &");
} else if (type == QLatin1String("stringlist")) {
return QStringLiteral("const QStringList &");
} else if (type == QLatin1String("font")) {
return QStringLiteral("const QFont &");
} else if (type == QLatin1String("rect")) {
return QStringLiteral("const QRect &");
} else if (type == QLatin1String("rectf")) {
return QStringLiteral("const QRectF &");
} else if (type == QLatin1String("size")) {
return QStringLiteral("const QSize &");
} else if (type == QLatin1String("sizef")) {
return QStringLiteral("const QSizeF &");
} else if (type == QLatin1String("color")) {
return QStringLiteral("const QColor &");
} else if (type == QLatin1String("point")) {
return QStringLiteral("const QPoint &");
} else if (type == QLatin1String("pointf")) {
return QStringLiteral("const QPointF &");
} else if (type == QLatin1String("int")) {
return QStringLiteral("int");
} else if (type == QLatin1String("uint")) {
return QStringLiteral("uint");
} else if (type == QLatin1String("bool")) {
return QStringLiteral("bool");
} else if (type == QLatin1String("double")) {
return QStringLiteral("double");
} else if (type == QLatin1String("datetime")) {
return QStringLiteral("const QDateTime &");
} else if (type == QLatin1String("longlong")) {
return QStringLiteral("qint64");
} else if (type == QLatin1String("ulonglong")) {
return QStringLiteral("quint64");
} else if (type == QLatin1String("intlist")) {
return QStringLiteral("const QList<int> &");
} else if (type == QLatin1String("enum")) {
return QStringLiteral("int");
} else if (type == QLatin1String("path")) {
return QStringLiteral("const QString &");
} else if (type == QLatin1String("pathlist")) {
return QStringLiteral("const QStringList &");
} else if (type == QLatin1String("password")) {
return QStringLiteral("const QString &");
} else if (type == QLatin1String("url")) {
return QStringLiteral("const QUrl &");
} else if (type == QLatin1String("urllist")) {
return QStringLiteral("const QList<QUrl> &");
} else {
std::cerr << "kconfig_compiler_kf6 does not support type \"" << qPrintable(type) << "\"" << std::endl;
return QStringLiteral("QString"); // For now, but an assert would be better
}
}
/**
Actual C++ storage type for given type.
*/
QString cppType(const QString &t)
{
const QString type = t.toLower();
if (type == QLatin1String("string")) {
return QStringLiteral("QString");
} else if (type == QLatin1String("stringlist")) {
return QStringLiteral("QStringList");
} else if (type == QLatin1String("font")) {
return QStringLiteral("QFont");
} else if (type == QLatin1String("rect")) {
return QStringLiteral("QRect");
} else if (type == QLatin1String("rectf")) {
return QStringLiteral("QRectF");
} else if (type == QLatin1String("size")) {
return QStringLiteral("QSize");
} else if (type == QLatin1String("sizef")) {
return QStringLiteral("QSizeF");
} else if (type == QLatin1String("color")) {
return QStringLiteral("QColor");
} else if (type == QLatin1String("point")) {
return QStringLiteral("QPoint");
} else if (type == QLatin1String("pointf")) {
return QStringLiteral("QPointF");
} else if (type == QLatin1String("int")) {
return QStringLiteral("int");
} else if (type == QLatin1String("uint")) {
return QStringLiteral("uint");
} else if (type == QLatin1String("bool")) {
return QStringLiteral("bool");
} else if (type == QLatin1String("double")) {
return QStringLiteral("double");
} else if (type == QLatin1String("datetime")) {
return QStringLiteral("QDateTime");
} else if (type == QLatin1String("longlong")) {
return QStringLiteral("qint64");
} else if (type == QLatin1String("ulonglong")) {
return QStringLiteral("quint64");
} else if (type == QLatin1String("intlist")) {
return QStringLiteral("QList<int>");
} else if (type == QLatin1String("enum")) {
return QStringLiteral("int");
} else if (type == QLatin1String("path")) {
return QStringLiteral("QString");
} else if (type == QLatin1String("pathlist")) {
return QStringLiteral("QStringList");
} else if (type == QLatin1String("password")) {
return QStringLiteral("QString");
} else if (type == QLatin1String("url")) {
return QStringLiteral("QUrl");
} else if (type == QLatin1String("urllist")) {
return QStringLiteral("QList<QUrl>");
} else {
std::cerr << "kconfig_compiler_kf6 does not support type \"" << qPrintable(type) << "\"" << std::endl;
return QStringLiteral("QString"); // For now, but an assert would be better
}
}
QString defaultValue(const QString &t)
{
const QString type = t.toLower();
if (type == QLatin1String("string")) {
return QStringLiteral("\"\""); // Use empty string, not null string!
} else if (type == QLatin1String("stringlist")) {
return QStringLiteral("QStringList()");
} else if (type == QLatin1String("font")) {
return QStringLiteral("QFont()");
} else if (type == QLatin1String("rect")) {
return QStringLiteral("QRect()");
} else if (type == QLatin1String("rectf")) {
return QStringLiteral("QRectF()");
} else if (type == QLatin1String("size")) {
return QStringLiteral("QSize()");
} else if (type == QLatin1String("sizef")) {
return QStringLiteral("QSizeF()");
} else if (type == QLatin1String("color")) {
return QStringLiteral("QColor(128, 128, 128)");
} else if (type == QLatin1String("point")) {
return QStringLiteral("QPoint()");
} else if (type == QLatin1String("pointf")) {
return QStringLiteral("QPointF()");
} else if (type == QLatin1String("int")) {
return QStringLiteral("0");
} else if (type == QLatin1String("uint")) {
return QStringLiteral("0");
} else if (type == QLatin1String("bool")) {
return QStringLiteral("false");
} else if (type == QLatin1String("double")) {
return QStringLiteral("0.0");
} else if (type == QLatin1String("datetime")) {
return QStringLiteral("QDateTime()");
} else if (type == QLatin1String("longlong")) {
return QStringLiteral("0");
} else if (type == QLatin1String("ulonglong")) {
return QStringLiteral("0");
} else if (type == QLatin1String("intlist")) {
return QStringLiteral("QList<int>()");
} else if (type == QLatin1String("enum")) {
return QStringLiteral("0");
} else if (type == QLatin1String("path")) {
return QStringLiteral("\"\""); // Use empty string, not null string!
} else if (type == QLatin1String("pathlist")) {
return QStringLiteral("QStringList()");
} else if (type == QLatin1String("password")) {
return QStringLiteral("\"\""); // Use empty string, not null string!
} else if (type == QLatin1String("url")) {
return QStringLiteral("QUrl()");
} else if (type == QLatin1String("urllist")) {
return QStringLiteral("QList<QUrl>()");
} else {
std::cerr << "Error, kconfig_compiler_kf6 does not support the \"" << qPrintable(type) << "\" type!" << std::endl;
return QStringLiteral("QString"); // For now, but an assert would be better
}
}
QString itemType(const QString &type)
{
if (type.isEmpty()) {
return QString{};
}
QString str = type;
str[0] = str.at(0).toUpper();
return str;
}
QString itemDeclaration(const CfgEntry *e, const KConfigParameters &cfg)
{
if (e->name.isEmpty()) {
return QString{};
}
const QString type = cfg.inherits + QLatin1String{"::Item"} + itemType(e->type);
QString fCap = e->name;
fCap[0] = fCap.at(0).toUpper();
const QString argSuffix = (!e->param.isEmpty()) ? (QStringLiteral("[%1]").arg(e->paramMax + 1)) : QString();
QString result;
if (!cfg.itemAccessors && !cfg.dpointer) {
result += QLatin1String{" "} + (!e->signalList.isEmpty() ? QStringLiteral("KConfigCompilerSignallingItem") : type);
result += QLatin1String(" *item%1;\n").arg(fCap + argSuffix);
}
if (!e->signalList.isEmpty()) {
result += QLatin1String(" %1 *%2;\n").arg(type, innerItemVar(e, cfg) + argSuffix);
}
return result;
}
// 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 result;
if (cfg.itemAccessors) {
if (!cfg.dpointer) {
result = QLatin1String("m%1Item").arg(e->name);
result[1] = result.at(1).toUpper();
} else {
result = e->name + QLatin1String{"Item"};
result[0] = result.at(0).toLower();
}
} else {
result = QLatin1String{"item"} + e->name;
result[4] = result.at(4).toUpper();
}
return result;
}
// returns the name of the local inner item if there is one
// (before wrapping with KConfigCompilerSignallingItem)
// Otherwise return itemVar()
QString innerItemVar(const CfgEntry *e, const KConfigParameters &cfg)
{
if (e->signalList.isEmpty()) {
return itemPath(e, cfg);
}
QString result = QLatin1String{"innerItem"} + e->name;
result[9] = result.at(9).toUpper();
return result;
}
QString itemPath(const CfgEntry *e, const KConfigParameters &cfg)
{
return cfg.dpointer ? QLatin1String{"d->"} + itemVar(e, cfg) : itemVar(e, cfg);
}
QString newInnerItem(const CfgEntry *entry, const QString &key, const QString &defaultValue, const KConfigParameters &cfg, const QString &param)
{
QString str = QLatin1String("new %1::Item%2").arg(cfg.inherits, itemType(entry->type));
str += QLatin1String("( currentGroup(), %1, %2").arg(key, varPath(entry->name, cfg) + param);
if (entry->type == QLatin1String("Enum")) {
str += QLatin1String{", values"} + entry->name;
}
if (!defaultValue.isEmpty()) {
str += QLatin1String(", ") + defaultValue;
}
str += QLatin1String(" );");
return str;
}
QString newItem(const CfgEntry *entry, const QString &key, const QString &defaultValue, const KConfigParameters &cfg, const QString &param)
{
const QList<Signal> sigs = entry->signalList;
if (sigs.isEmpty()) {
return newInnerItem(entry, key, defaultValue, cfg, param);
}
QString str;
str += QLatin1String("new KConfigCompilerSignallingItem(%1, this, notifyFunction, ").arg(innerItemVar(entry, cfg) + param);
// Append the signal flags
const int listSize = sigs.size();
for (int i = 0; i < listSize; ++i) {
if (i != 0) {
str += QLatin1String(" | ");
}
str += signalEnumName(sigs[i].name);
}
str += QLatin1String(");");
return str;
}
QString paramString(const QString &s, const CfgEntry *e, int i)
{
QString result = s;
const QString needle = QLatin1String("$(%1)").arg(e->param);
if (result.contains(needle)) {
const QString tmp = e->paramType == QLatin1String{"Enum"} ? e->paramValues.at(i) : QString::number(i);
result.replace(needle, tmp);
}
return result;
}
QString paramString(const QString &group, const QList<Param> &parameters)
{
QString paramString = group;
QString arguments;
int i = 1;
bool firstArg = true;
for (const auto &param : parameters) {
const QString paramName = param.name;
const QString str = QLatin1String("$(%1)").arg(paramName);
if (paramString.contains(str)) {
const QString tmp = QStringLiteral("%%1").arg(i++);
paramString.replace(str, tmp);
if (firstArg) {
arguments += QLatin1String{".arg( "};
firstArg = false;
}
arguments += QLatin1String("mParam%1, ").arg(paramName);
}
}
if (!arguments.isEmpty()) {
// Remove the last ", "
arguments.chop(2);
// Close the ".arg( "
arguments += QLatin1String{" )"};
} else {
return QLatin1String("QStringLiteral( \"%1\" )").arg(group);
}
return QLatin1String("QStringLiteral( \"%1\" )%2").arg(paramString, arguments);
}
QString translatedString(const KConfigParameters &cfg, const QString &string, const QString &context, const QString &param, const QString &paramValue)
{
QString result;
switch (cfg.translationSystem) {
case KConfigParameters::QtTranslation:
if (!context.isEmpty()) {
result += QLatin1String("/*: %1 */ QCoreApplication::translate(\"").arg(context);
} else {
result += QLatin1String{"QCoreApplication::translate(\""};
}
result += QLatin1String("%1\", ").arg(cfg.className);
break;
case KConfigParameters::KdeTranslation:
if (!cfg.translationDomain.isEmpty() && !context.isEmpty()) {
result += QLatin1String("i18ndc(%1, %2, ").arg(quoteString(cfg.translationDomain), quoteString(context));
} else if (!cfg.translationDomain.isEmpty()) {
result += QLatin1String("i18nd(%1, ").arg(quoteString(cfg.translationDomain));
} else if (!context.isEmpty()) {
result += QLatin1String("i18nc(%1, ").arg(quoteString(context));
} else {
result += QLatin1String{"i18n("};
}
break;
}
if (!param.isEmpty()) {
QString resolvedString = string;
resolvedString.replace(QLatin1String("$(%1)").arg(param), paramValue);
result += quoteString(resolvedString);
} else {
result += quoteString(string);
}
result += QLatin1Char{')'};
return result;
}
/* int i is the value of the parameter */
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()) {
txt += QLatin1String(" %1->setLabel( %2 );\n").arg(itemVarStr, translatedString(cfg, e->label, e->labelContext, e->param, i));
}
if (!e->toolTip.isEmpty()) {
txt += QLatin1String(" %1->setToolTip( %2 );\n").arg(itemVarStr, translatedString(cfg, e->toolTip, e->toolTipContext, e->param, i));
}
if (!e->whatsThis.isEmpty()) {
txt += QLatin1String(" %1->setWhatsThis( %2 );\n").arg(itemVarStr, translatedString(cfg, e->whatsThis, e->whatsThisContext, e->param, i));
}
return txt;
}
// returns the member mutator implementation
// which should go in the h file if inline
// or the cpp file if not inline
// 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);
} else {
out << " qCDebug(" << cfg.qCategoryLoggingName << ") << \"" << setFunction(n);
}
}
// returns the member get default implementation
// which should go in the h file if inline
// or the cpp file if not inline
QString memberGetDefaultBody(const CfgEntry *e)
{
QString result = e->code;
QTextStream out(&result, QIODevice::WriteOnly);
out << '\n';
if (!e->param.isEmpty()) {
out << " switch (i) {\n";
for (int i = 0; i <= e->paramMax; ++i) {
if (!e->paramDefaultValues[i].isEmpty()) {
out << " case " << i << ": return " << e->paramDefaultValues[i] << ";\n";
}
}
QString defaultValue = e->defaultValue;
out << " default:\n";
out << " return " << defaultValue.replace(QLatin1String("$(%1)").arg(e->param), QLatin1String("i")) << ";\n";
out << " }\n";
} else {
out << " return " << e->defaultValue << ';';
}
return result;
}
// returns the item accessor implementation
// which should go in the h file if inline
// or the cpp file if not inline
QString itemAccessorBody(const CfgEntry *e, const KConfigParameters &cfg)
{
QString result;
QTextStream out(&result, QIODevice::WriteOnly);
out << "return " << itemPath(e, cfg);
if (!e->param.isEmpty()) {
out << "[i]";
}
out << ";\n";
return result;
}
// indents text adding X spaces per line
QString indent(QString text, int spaces)
{
QString result;
QTextStream out(&result, QIODevice::WriteOnly);
QTextStream in(&text, QIODevice::ReadOnly);
QString currLine;
while (!in.atEnd()) {
currLine = in.readLine();
if (!currLine.isEmpty()) {
for (int i = 0; i < spaces; ++i) {
out << " ";
}
}
out << currLine << '\n';
}
return result;
}
bool hasErrors(KConfigXmlParser &parser, const ParseResult &parseResult, const KConfigParameters &cfg)
{
Q_UNUSED(parser)
if (cfg.className.isEmpty()) {
std::cerr << "Class name missing" << std::endl;
return true;
}
if (cfg.singleton && !parseResult.parameters.isEmpty()) {
std::cerr << "Singleton class can not have parameters" << std::endl;
return true;
}
if (!parseResult.cfgFileName.isEmpty() && parseResult.cfgFileNameArg) {
std::cerr << "Having both a fixed filename and a filename as argument is not possible." << std::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()) {
std::cerr << "No entries." << std::endl;
return false;
}
return false;
}
int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
app.setApplicationName(QStringLiteral("kconfig_compiler"));
app.setApplicationVersion(QStringLiteral(KCONFIG_VERSION_STRING));
QString inputFilename;
QString 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"));
parser.addOption(targetDirectoryOption);
parser.addOption(licenseOption);
parser.addVersionOption();
parser.addHelpOption();
parser.process(app);
if (parser.isSet(licenseOption)) {
std::cout << "Copyright 2003 Cornelius Schumacher, Waldo Bastian, Zack Rusin," << std::endl;
std::cout << " Reinhold Kainhofer, Duncan Mac-Vicar P., Harald Fernengel" << std::endl;
std::cout << "This program comes with ABSOLUTELY NO WARRANTY." << std::endl;
std::cout << "You may redistribute copies of this program" << std::endl;
std::cout << "under the terms of the GNU Library Public License." << std::endl;
std::cout << "For more information about these matters, see the file named COPYING." << std::endl;
return 0;
}
const QStringList args = parser.positionalArguments();
if (args.count() < 2) {
std::cerr << "Too few arguments." << std::endl;
return 1;
}
if (args.count() > 2) {
std::cerr << "Too many arguments." << std::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
if (!baseDir.endsWith(QLatin1Char{'/'}) && !baseDir.endsWith(QLatin1Char{'\\'})) {
#else
if (!baseDir.endsWith(QLatin1Char{'/'})) {
#endif
baseDir.append(QLatin1Char{'/'});
}
KConfigParameters cfg(codegenFilename);
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();
ParseResult parseResult = xmlParser.getParseResult();
if (hasErrors(xmlParser, parseResult, cfg)) {
return 1;
}
// TODO: Move this to somewhere saner.
for (const auto &signal : std::as_const(parseResult.signalList)) {
parseResult.hasNonModifySignals |= !signal.modify;
}
// remove '.kcfg' from the name.
const QString baseName = inputFilename.mid(0, inputFilename.size() - 5);
KConfigHeaderGenerator headerGenerator(baseName, baseDir, cfg, parseResult);
headerGenerator.start();
headerGenerator.save();
KConfigSourceGenerator sourceGenerator(baseName, baseDir, cfg, parseResult);
sourceGenerator.start();
sourceGenerator.save();
qDeleteAll(parseResult.entries);
}