/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright (C) 2019 Michael Zanetti * * * * This file is part of nymea. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2.1 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with this library; If not, see * * . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "plugininfocompiler.h" #include #include #include #include #include #include PluginInfoCompiler::PluginInfoCompiler() { } int PluginInfoCompiler::compile(const QString &inputFile, const QString &outputFile, const QString outputFileExtern, const QString &translationsPath) { // First, process the input json... QFile jsonFile(inputFile); if (!jsonFile.open(QFile::ReadOnly)) { qWarning() << "Error opening input JSON file for reading. Aborting."; return 1; } QJsonParseError error; QByteArray data = jsonFile.readAll(); QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); jsonFile.close(); if (error.error != QJsonParseError::NoError) { int errorOffset = error.offset; int newLineIndex = data.indexOf("\n"); int lineIndex = 1; while (newLineIndex > 0 && errorOffset > newLineIndex) { data.remove(0, newLineIndex + 2); errorOffset -= (newLineIndex + 2); newLineIndex = data.indexOf("\n"); lineIndex++; } if (newLineIndex >= 0) { data = data.left(newLineIndex); } QString spacer; for (int i = 0; i < errorOffset; i++) { spacer += ' '; } QDebug dbg = qWarning().nospace().noquote(); dbg << inputFile << ":" << lineIndex << ":" << errorOffset + 2 << ": error: JSON parsing failed: " << error.errorString() << ": " << data.trimmed() << endl; dbg << data << endl; dbg << spacer << "^"; return 1; } QJsonObject jsonObject = QJsonObject::fromVariantMap(jsonDoc.toVariant().toMap()); PluginMetadata metadata(jsonObject); if (!metadata.isValid()) { foreach (const QString &error, metadata.validationErrors()) { QDebug dbg = qWarning().noquote().nospace(); dbg << inputFile << ": error: Plugin JSON failed validation: " << error; } return 2; } // OK. Json parsed fine. Let's open files for writing if (!outputFile.isEmpty()) { if (outputFile == "-") { if (!m_outputFile.open(stdout, QFile::WriteOnly | QFile::Text)) { qWarning() << "Error opening stdout for writing. Aborting."; return 1; } } else { m_outputFile.setFileName(outputFile); if (!m_outputFile.open(QFile::WriteOnly | QFile::Text)) { qWarning() << "Error opening output file for writing. Aborting."; return 1; } } } if (!outputFileExtern.isEmpty()) { if (outputFileExtern == "-") { if (!m_outputFileExtern.open(stdout, QFile::WriteOnly | QFile::Text)) { qWarning() << "Error opening stdout for writing. Aborting."; return 1; } } else { m_outputFileExtern.setFileName(outputFileExtern); if (!m_outputFileExtern.open(QFile::WriteOnly | QFile::Text)) { qWarning() << "Error opening output file for writing. Aborting."; return 1; } } } if (!translationsPath.isEmpty()) { QDir dir; if (!dir.exists(translationsPath)) { if(!dir.mkpath(translationsPath)) { qWarning() << "Error creating translation file directory" << translationsPath; return 1; } qDebug() << "Created translations dir"; } QFile f(translationsPath + '/' + metadata.pluginId().toString().remove(QRegExp("[{}]")) + "-en_US.ts"); QByteArray translationsStub = ""; if (!f.exists()) { if (!f.open(QFile::WriteOnly | QFile::Text)) { qWarning() << "Error creating translation file"; return 1; } if (f.write(translationsStub) == -1) { qWarning() << "Error writing translation file"; return 1; } f.close(); qDebug() << "Created translations stub"; } } // Files are open. Ready to write content. QString header; header.append("/* This file is generated by the nymea build system. Any changes to this file will *\n"); header.append(" * be lost. If you want to change this file, edit the plugin's json file. */\n"); write(header); writeExtern(header); write("#ifndef PLUGININFO_H"); write("#define PLUGININFO_H"); write(); writeExtern("#ifndef EXTERNPLUGININFO_H"); writeExtern("#define EXTERNPLUGININFO_H"); writeExtern(); write("#include \"typeutils.h\""); write(); writeExtern("#include \"typeutils.h\""); writeExtern(); write("#include "); writeExtern("#include "); write("#include "); write(); writeExtern(); // Include our API version in plugininfo.h so we can know against which library this plugin was built. write(QString("extern \"C\" const QString libnymea_api_version() { return QString(\"%1\");}").arg(LIBNYMEA_API_VERSION)); write(); // Declare a logging category for this plugin QString debugCategoryName = metadata.pluginName()[0].toUpper() + metadata.pluginName().right(metadata.pluginName().length() - 1); QString debugCategoryDeclaration = QString("Q_DECLARE_LOGGING_CATEGORY(dc%1)").arg(debugCategoryName); write(debugCategoryDeclaration); write(QString("Q_LOGGING_CATEGORY(dc%1, \"%1\")").arg(debugCategoryName)); write(); writeExtern(debugCategoryDeclaration); writeExtern(); // Write down all the IDs writePlugin(metadata); write(); writeExtern(); // And the translations write(QString("const QString translations[] {")); for (auto i = m_translationStrings.begin(); i != m_translationStrings.end();) { write(QString(" //: %1").arg(i.value())); QString line = QString(" QT_TRANSLATE_NOOP(\"%1\", \"%2\")").arg(metadata.pluginName(), i.key()); i++; if (i != m_translationStrings.end()) { line.append(",\n"); } write(line); } write("};"); write(); write("#endif // PLUGININFO_H"); writeExtern("#endif // EXTERNPLUGININFO_H"); m_outputFile.close(); m_outputFileExtern.close(); return 0; } void PluginInfoCompiler::writePlugin(const PluginMetadata &metadata) { write(QString("PluginId pluginId = PluginId(\"%1\");").arg(metadata.pluginId().toString())); m_translationStrings.insert(metadata.pluginDisplayName(), QString("The name of the plugin %1 (%2)").arg(metadata.pluginName()).arg(metadata.pluginId().toString())); writeExtern(QString("extern %1 %2;").arg("PluginId").arg("pluginId")); writeParams(metadata.pluginSettings(), metadata.pluginName()[0].toLower() + metadata.pluginName().right(metadata.pluginName().length() - 1), "", "plugin"); foreach (const Vendor &vendor, metadata.vendors()) { writeVendor(vendor); } foreach (const DeviceClass &deviceClass, metadata.deviceClasses()) { writeDeviceClass(deviceClass); } } void PluginInfoCompiler::writeParams(const ParamTypes ¶mTypes, const QString &deviceClassName, const QString &typeClass, const QString &typeName) { foreach (const ParamType ¶mType, paramTypes) { QString variableName = QString("%1ParamTypeId").arg(deviceClassName + typeName[0].toUpper() + typeName.right(typeName.length()-1) + typeClass + paramType.name()[0].toUpper() + paramType.name().right(paramType.name().length() -1 )); if (m_variableNames.contains(variableName)) { qWarning().nospace() << "Error: Duplicate name " << variableName << " for ParamTypeId " << paramType.id() << ". Skipping entry."; continue; } m_variableNames.append(variableName); write(QString("ParamTypeId %1 = ParamTypeId(\"%2\");").arg(variableName).arg(paramType.id().toString())); m_translationStrings.insert(paramType.displayName(), QString("The name of the ParamType (DeviceClass: %1, %2Type: %3, ID: %4)").arg(deviceClassName).arg(typeClass).arg(typeName).arg(paramType.id().toString())); writeExtern(QString("extern ParamTypeId %1;").arg(variableName)); } } void PluginInfoCompiler::writeVendor(const Vendor &vendor) { QString variableName = QString("%1VendorId").arg(vendor.name()); if (m_variableNames.contains(variableName)) { qWarning().nospace() << "Error: Duplicate name " << variableName << " for Vendor " << vendor.id() << ". Skipping entry."; return; } m_variableNames.append(variableName); write(QString("VendorId %1 = VendorId(\"%2\");").arg(variableName).arg(vendor.id().toString())); m_translationStrings.insert(vendor.displayName(), QString("The name of the vendor (%1)").arg(vendor.id().toString())); writeExtern(QString("extern VendorId %1;").arg(variableName)); } void PluginInfoCompiler::writeDeviceClass(const DeviceClass &deviceClass) { QString variableName = QString("%1DeviceClassId").arg(deviceClass.name()); if (m_variableNames.contains(variableName)) { qWarning().nospace() << "Error: Duplicate name " << variableName << " for DeviceClass " << deviceClass.id() << ". Skipping entry."; return; } m_variableNames.append(variableName); write(QString("DeviceClassId %1 = DeviceClassId(\"%2\");").arg(variableName).arg(deviceClass.id().toString())); m_translationStrings.insert(deviceClass.displayName(), QString("The name of the DeviceClass (%1)").arg(deviceClass.id().toString())); writeExtern(QString("extern DeviceClassId %1;").arg(variableName)); writeParams(deviceClass.paramTypes(), deviceClass.name(), "", "device"); writeParams(deviceClass.settingsTypes(), deviceClass.name(), "", "settings"); writeParams(deviceClass.discoveryParamTypes(), deviceClass.name(), "", "discovery"); writeStateTypes(deviceClass.stateTypes(), deviceClass.name()); writeEventTypes(deviceClass.eventTypes(), deviceClass.name()); writeActionTypes(deviceClass.actionTypes(), deviceClass.name()); writeBrowserItemActionTypes(deviceClass.browserItemActionTypes(), deviceClass.name()); } void PluginInfoCompiler::writeStateTypes(const StateTypes &stateTypes, const QString &deviceClassName) { foreach (const StateType &stateType, stateTypes) { QString variableName = QString("%1%2StateTypeId").arg(deviceClassName, stateType.name()[0].toUpper() + stateType.name().right(stateType.name().length() - 1)); if (m_variableNames.contains(variableName)) { qWarning().nospace() << "Error: Duplicate name " << variableName << " for StateType " << stateType.name() << " in DeviceClass " << deviceClassName << ". Skipping entry."; return; } m_variableNames.append(variableName); write(QString("StateTypeId %1 = StateTypeId(\"%2\");").arg(variableName).arg(stateType.id().toString())); m_translationStrings.insert(stateType.displayName(), QString("The name of the StateType (%1) of DeviceClass %2").arg(stateType.id().toString()).arg(deviceClassName)); writeExtern(QString("extern StateTypeId %1;").arg(variableName)); } } void PluginInfoCompiler::writeEventTypes(const EventTypes &eventTypes, const QString &deviceClassName) { foreach (const EventType &eventType, eventTypes) { QString variableName = QString("%1%2EventTypeId").arg(deviceClassName, eventType.name()[0].toUpper() + eventType.name().right(eventType.name().length() - 1)); if (m_variableNames.contains(variableName)) { qWarning().nospace() << "Error: Duplicate name " << variableName << " for EventType " << eventType.name() << " in DeviceClass " << deviceClassName << ". Skipping entry."; return; } m_variableNames.append(variableName); write(QString("EventTypeId %1 = EventTypeId(\"%2\");").arg(variableName).arg(eventType.id().toString())); m_translationStrings.insert(eventType.displayName(), QString("The name of the EventType (%1) of DeviceClass %2").arg(eventType.id().toString()).arg(deviceClassName)); writeExtern(QString("extern EventTypeId %1;").arg(variableName)); writeParams(eventType.paramTypes(), deviceClassName, "Event", eventType.name()); } } void PluginInfoCompiler::writeActionTypes(const ActionTypes &actionTypes, const QString &deviceClassName) { foreach (const ActionType &actionType, actionTypes) { QString variableName = QString("%1%2ActionTypeId").arg(deviceClassName, actionType.name()[0].toUpper() + actionType.name().right(actionType.name().length() - 1)); if (m_variableNames.contains(variableName)) { qWarning().nospace() << "Error: Duplicate name " << variableName << " for ActionType " << actionType.name() << " in DeviceClass " << deviceClassName << ". Skipping entry."; return; } m_variableNames.append(variableName); write(QString("ActionTypeId %1 = ActionTypeId(\"%2\");").arg(variableName).arg(actionType.id().toString())); m_translationStrings.insert(actionType.displayName(), QString("The name of the ActionType (%1) of DeviceClass %2").arg(actionType.id().toString()).arg(deviceClassName)); writeExtern(QString("extern ActionTypeId %1;").arg(variableName)); writeParams(actionType.paramTypes(), deviceClassName, "Action", actionType.name()); } } void PluginInfoCompiler::writeBrowserItemActionTypes(const ActionTypes &actionTypes, const QString &deviceClassName) { foreach (const ActionType &actionType, actionTypes) { QString variableName = QString("%1%2BrowserItemActionTypeId").arg(deviceClassName, actionType.name()[0].toUpper() + actionType.name().right(actionType.name().length() - 1)); if (m_variableNames.contains(variableName)) { qWarning().nospace() << "Error: Duplicate name " << variableName << " for Browser Item ActionType " << actionType.name() << " in DeviceClass " << deviceClassName << ". Skipping entry."; return; } m_variableNames.append(variableName); write(QString("ActionTypeId %1 = ActionTypeId(\"%2\");").arg(variableName).arg(actionType.id().toString())); m_translationStrings.insert(actionType.displayName(), QString("The name of the Browser Item ActionType (%1) of DeviceClass %2").arg(actionType.id().toString()).arg(deviceClassName)); writeExtern(QString("extern ActionTypeId %1;").arg(variableName)); writeParams(actionType.paramTypes(), deviceClassName, "BrowserItemAction", actionType.name()); } } void PluginInfoCompiler::write(const QString &line) { if (m_outputFile.isOpen()) { m_outputFile.write(QString("%1\n").arg(line).toUtf8()); } } void PluginInfoCompiler::writeExtern(const QString &line) { if (m_outputFileExtern.isOpen()) { m_outputFileExtern.write(QString("%1\n").arg(line).toUtf8()); } }