/* m_this file is part of the KDE libraries Copyright (C) 2020 Tomaz Cananbrava (tcanabrava@kde.org) Copyright (c) 2003 Cornelius Schumacher Copyright (c) 2003 Waldo Bastian Copyright (c) 2003 Zack Rusin Copyright (c) 2006 MichaĆ«l Larouche Copyright (c) 2008 Allen Winter Copyright (C) 2020 Tomaz Cananbrava (tcanabrava@kde.org) m_this library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. m_this library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KConfigCodeGeneratorBase.h" #include "KConfigParameters.h" #include "KConfigCommonStructs.h" #include #include #include #include #include using std::endl; namespace { QTextStream cout(stdout); QTextStream cerr(stderr); } KConfigCodeGeneratorBase::KConfigCodeGeneratorBase( const QString &inputFile, const QString &baseDir, const QString &fileName, const KConfigParameters ¶meters, ParseResult &parseResult) : m_inputFile(inputFile), m_baseDir(baseDir), m_fileName(fileName), m_cfg(parameters), parseResult(parseResult) { m_file.setFileName(m_fileName); if (!m_file.open(QIODevice::WriteOnly)) { cerr << "Can not open '" << m_fileName << "for writing." << endl; exit(1); } m_stream.setDevice(&m_file); m_stream.setCodec("utf-8"); if (m_cfg.staticAccessors) { m_this = QStringLiteral("self()->"); } else { m_const = QStringLiteral(" const"); } } KConfigCodeGeneratorBase::~KConfigCodeGeneratorBase() { save(); } void KConfigCodeGeneratorBase::save() { m_file.close(); } //TODO: Remove this weird logic and adapt the testcases void KConfigCodeGeneratorBase::indent() { if (m_indentLevel >= 4) { m_indentLevel += 2; } else { m_indentLevel += 4; } } void KConfigCodeGeneratorBase::unindent() { if (m_indentLevel > 4) { m_indentLevel -= 2; } else { m_indentLevel -= 4; } } QString KConfigCodeGeneratorBase::whitespace() const { QString spaces; for (int i = 0; i < m_indentLevel; i++) { spaces.append(QLatin1Char(' ')); } return spaces; } void KConfigCodeGeneratorBase::startScope() { m_stream << whitespace() << QLatin1Char('{'); m_stream << endl; indent(); } void KConfigCodeGeneratorBase::endScope(ScopeFinalizer finalizer) { unindent(); m_stream << whitespace() << QLatin1Char('}'); if (finalizer == ScopeFinalizer::Semicolon) { m_stream << ';'; } m_stream << endl; } void KConfigCodeGeneratorBase::start() { const QString m_fileName = QFileInfo(m_inputFile).fileName(); m_stream << "// This file is generated by kconfig_compiler_kf5 from " << m_fileName << ".kcfg" << "." << endl; m_stream << "// All changes you do to this file will be lost." << endl; } void KConfigCodeGeneratorBase::addHeaders(const QStringList &headerList) { for (auto include : qAsConst(headerList)) { if (include.startsWith(QLatin1Char('"'))) { m_stream << "#include " << include << endl; } else { m_stream << "#include <" << include << ">" << endl; } } } // adds as many 'namespace foo {' lines to p_out as // there are namespaces in p_ns void KConfigCodeGeneratorBase::beginNamespaces() { if (!m_cfg.nameSpace.isEmpty()) { for (const QString &ns : m_cfg.nameSpace.split(QStringLiteral("::"))) { m_stream << "namespace " << ns << " {" << endl; } m_stream << endl; } } // adds as many '}' lines to p_out as // there are namespaces in p_ns void KConfigCodeGeneratorBase::endNamespaces() { if (!m_cfg.nameSpace.isEmpty()) { m_stream << endl; const int namespaceCount = m_cfg.nameSpace.count(QStringLiteral("::")) + 1; for (int i = 0; i < namespaceCount; ++i) { m_stream << "}" << endl; } } } // returns the member accesor implementation // which should go in the h file if inline // or the cpp file if not inline QString KConfigCodeGeneratorBase::memberAccessorBody(const CfgEntry *e, bool globalEnums) const { QString result; QTextStream out(&result, QIODevice::WriteOnly); QString n = e->name; QString t = e->type; bool useEnumType = m_cfg.useEnumTypes && t == QLatin1String("Enum"); out << "return "; if (useEnumType) { out << "static_cast<" << enumType(e, globalEnums) << ">("; } out << m_this << varPath(n, m_cfg); if (!e->param.isEmpty()) { out << "[i]"; } if (useEnumType) { out << ")"; } out << ";" << endl; return result; } void KConfigCodeGeneratorBase::createIfSetLogic(const CfgEntry *e, const QString &varExpression) { const QString n = e->name; const QString t = e->type; const bool hasBody = !e->signalList.empty() || m_cfg.generateProperties; m_stream << whitespace() << "if ("; if (hasBody) { m_stream << "v != " << varExpression << " && "; } m_stream << "!" << m_this << "isImmutable( QStringLiteral( \""; if (!e->param.isEmpty()) { QString paramName = e->paramName; m_stream << paramName.replace(QStringLiteral("$(") + e->param + QStringLiteral(")"), QLatin1String("%1")) << "\" ).arg( "; if (e->paramType == QLatin1String("Enum")) { m_stream << "QLatin1String( "; if (m_cfg.globalEnums) { m_stream << enumName(e->param) << "ToString[i]"; } else { m_stream << enumName(e->param) << "::enumToString[i]"; } m_stream << " )"; } else { m_stream << "i"; } m_stream << " )"; } else { m_stream << n << "\" )"; } m_stream << " ))"; } void KConfigCodeGeneratorBase::memberMutatorBody(const CfgEntry *e) { QString n = e->name; QString t = e->type; // HACK: Don't open '{' manually, use startScope / endScope to automatically handle whitespace indentation. if (!e->min.isEmpty()) { if (e->min != QLatin1String("0") || !isUnsigned(t)) { // skip writing "if uint<0" (#187579) m_stream << whitespace() << "if (v < " << e->min << ")" << endl; m_stream << whitespace() << "{" << endl; m_stream << whitespace(); addDebugMethod(m_stream, m_cfg, n); m_stream << ": value \" << v << \" is less than the minimum value of " << e->min << "\";" << endl; m_stream << whitespace() << " v = " << e->min << ";" << endl; m_stream << whitespace() << "}" << endl; } } if (!e->max.isEmpty()) { m_stream << endl; m_stream << whitespace() << "if (v > " << e->max << ")" << endl; m_stream << whitespace() << "{" << endl; m_stream << whitespace(); addDebugMethod(m_stream, m_cfg, n); m_stream << ": value \" << v << \" is greater than the maximum value of " << e->max << "\";" << endl; m_stream << whitespace() << " v = " << e->max << ";" << endl; m_stream << whitespace() << "}" << endl << endl; } const QString varExpression = m_this + varPath(n, m_cfg) + (e->param.isEmpty() ? QString() : QStringLiteral("[i]")); // TODO: Remove this `hasBody` logic, always use an '{' for the if. const bool hasBody = !e->signalList.empty() || m_cfg.generateProperties; // m_this call creates an `if (someTest ...) that's just to long to throw over the code. createIfSetLogic(e, varExpression); m_stream << (hasBody ? " {" : "") << endl; m_stream << whitespace() << " " << varExpression << " = v;" << endl; const auto listSignal = e->signalList; for (const Signal &signal : qAsConst(listSignal)) { if (signal.modify) { m_stream << whitespace() << " Q_EMIT " << m_this << signal.name << "();" << endl; } else { m_stream << whitespace() << " " << m_this << varPath(QStringLiteral("settingsChanged"), m_cfg) << " |= " << signalEnumName(signal.name) << ";" << endl; } } if (hasBody) { m_stream << whitespace() << "}" << endl; } }