/*********************************************************************** * generate a cpp source that adds the json string to the registry **********************************************************************/ static void jsonArrayToCppStaticBlock(const Poco::JSON::Array &arrayOut, std::ostream &os, const std::string &target) { os << "#include <Pothos/Framework.hpp>\n"; os << "#include <Pothos/Plugin.hpp>\n"; os << Poco::format("pothos_static_block(registerPothosBlockDocs%s)\n", target); os << "{\n"; for (size_t i = 0; i < arrayOut.size(); i++) { auto obj = arrayOut.getObject(i); assert(obj); //create escaped string of json std::stringstream ossJsonObj; obj->stringify(ossJsonObj); std::string escaped; for (const auto &ch : ossJsonObj.str()) { escaped += "\\x" + Poco::NumberFormatter::formatHex(int(ch), 2/*width*/, false/*no 0x*/); } //get a list of all paths including aliases std::vector<std::string> paths; paths.push_back(obj->getValue<std::string>("path")); if (obj->has("aliases")) for (const auto &alias : *obj->getArray("aliases")) { paths.push_back(alias.toString()); } //register the block description for every path for (const auto &path : paths) { const auto pluginPath = Pothos::PluginPath("/blocks/docs").join(path.substr(1)); os << Poco::format(" Pothos::PluginRegistry::add(\"%s\", std::string(\"%s\"));\n", pluginPath.toString(), escaped); } } os << "}\n"; }
/*********************************************************************** * Parse a single documentation block for markup **********************************************************************/ static Poco::JSON::Object::Ptr parseCommentBlockForMarkup(const CodeBlock &commentBlock) { Poco::JSON::Object::Ptr topObj(new Poco::JSON::Object()); Poco::JSON::Array calls; Poco::JSON::Array keywords; Poco::JSON::Array::Ptr aliases(new Poco::JSON::Array()); Poco::JSON::Array categories; Poco::JSON::Array params; Poco::JSON::Array::Ptr topDocs(new Poco::JSON::Array()); Poco::JSON::Object::Ptr currentParam; std::string state; std::string indent; std::string instruction; std::string payload; //search for the markup begin tag and record the indent for (const auto &codeLine : commentBlock) { std::string line = codeLine.text; Poco::RegularExpression::MatchVec matches; if (not state.empty()) { if (line.size() >= indent.size() and line.substr(0, indent.size()) != indent) { if (codeLine.lineNo == commentBlock.back().lineNo) line = ""; else throw Pothos::SyntaxException("Inconsistent indentation", codeLine.toString()); } if (line.size() >= indent.size()) line = line.substr(indent.size()); else line = ""; Poco::RegularExpression("^\\|(\\w+)\\s+(.*)$").match(line, 0, matches); if (not matches.empty()) { assert(matches.size() == 3); instruction = line.substr(matches[1].offset, matches[1].length); payload = line.substr(matches[2].offset, matches[2].length); } } if (state.empty()) { Poco::RegularExpression("^(.*)\\|PothosDoc\\s+(.*)$").match(line, 0, matches); if (matches.empty()) continue; assert(matches.size() == 3); indent = line.substr(matches[1].offset, matches[1].length); topObj->set("name", Poco::trim(line.substr(matches[2].offset, matches[2].length))); state = "DOC"; } else if (matches.empty() and state == "DOC") { topDocs->add(line); } else if (matches.empty() and state == "PARAM") { auto array = currentParam->getArray("desc"); array->add(line); currentParam->set("desc", stripDocArray(array)); } else if (instruction == "category" and state == "DOC") { categories.add(Poco::trim(payload)); } else if (instruction == "keywords" and state == "DOC") { for (const auto &keyword : Poco::StringTokenizer( payload, " \t", Poco::StringTokenizer::TOK_TRIM | Poco::StringTokenizer::TOK_IGNORE_EMPTY)) { keywords.add(Poco::trim(keyword)); } } else if (instruction == "alias" and state == "DOC") { const std::string alias(Poco::trim(payload)); try {Pothos::PluginPath(alias);} catch (const Pothos::PluginPathError &) { throw Pothos::SyntaxException("Invalid alias path", codeLine.toString()); } aliases->add(alias); } else if (instruction == "param" and (state == "DOC" or state == "PARAM")) { payload = bracketEscapeEncode(payload); Poco::RegularExpression::MatchVec fields; Poco::RegularExpression("^\\s*(\\w+)(\\s*\\[(.*)\\]\\s*)?(.*)$").match(payload, 0, fields); if (fields.empty()) throw Pothos::SyntaxException( "Expected |param key[name] description", codeLine.toString()); assert(fields.size() == 5); const std::string key = bracketEscapeDecode(Poco::trim(payload.substr(fields[1].offset, fields[1].length))); std::string name = titleCase(key); if (fields[3].length != 0) name = bracketEscapeDecode(Poco::trim(payload.substr(fields[3].offset, fields[3].length))); const std::string desc = bracketEscapeDecode(Poco::trim(payload.substr(fields[4].offset, fields[4].length))); currentParam = Poco::JSON::Object::Ptr(new Poco::JSON::Object()); params.add(currentParam); currentParam->set("key", key); currentParam->set("name", name); Poco::JSON::Array::Ptr descArr(new Poco::JSON::Array()); descArr->add(desc); currentParam->set("desc", descArr); state = "PARAM"; } else if (instruction == "default" and state == "PARAM") { if (currentParam->has("default")) throw Pothos::SyntaxException( "Multiple occurrence of |default for param", codeLine.toString()); currentParam->set("default", payload); } else if (instruction == "units" and state == "PARAM") { if (currentParam->has("units")) throw Pothos::SyntaxException( "Multiple occurrence of |units for param", codeLine.toString()); currentParam->set("units", payload); } else if (instruction == "widget" and state == "PARAM") { if (currentParam->has("widgetType")) throw Pothos::SyntaxException( "Multiple occurrence of |widget for param", codeLine.toString()); Poco::RegularExpression::MatchVec fields; Poco::RegularExpression("^\\s*(\\w+)\\s*\\((.*)\\)$").match(payload, 0, fields); if (fields.empty()) throw Pothos::SyntaxException( "Expected |widget SpinBox(args...)", codeLine.toString()); assert(fields.size() == 3); const std::string widgetType = Poco::trim(payload.substr(fields[1].offset, fields[1].length)); const std::string argsStr = Poco::trim(payload.substr(fields[2].offset, fields[2].length)); currentParam->set("widgetType", widgetType); loadArgs(codeLine, *currentParam, argsStr, "widgetArgs", "widgetKwargs"); } else if (instruction == "tab" and state == "PARAM") { if (currentParam->has("tab")) throw Pothos::SyntaxException( "Multiple occurrence of |tab for param", codeLine.toString()); currentParam->set("tab", payload); } else if (instruction == "preview" and state == "PARAM") { if (currentParam->has("preview")) throw Pothos::SyntaxException( "Multiple occurrence of preview for param", codeLine.toString()); Poco::RegularExpression::MatchVec fields; Poco::RegularExpression("^\\s*(\\w+)(\\s*\\((.*)\\))?$").match(payload, 0, fields); if (fields.empty()) throw Pothos::SyntaxException( "Expected |preview previewType(args...)", codeLine.toString()); assert(fields.size() == 2 or fields.size() == 4); const std::string previewType = Poco::trim(payload.substr(fields[1].offset, fields[1].length)); if (previewType != "disable" and previewType != "enable" and previewType != "valid" and previewType != "invalid" and previewType != "when" ) throw Pothos::SyntaxException( "Only supports enable/disable/valid/invalid/when as value for preview option of param", codeLine.toString()); currentParam->set("preview", previewType); if (fields.size() == 4) { const std::string argsStr = Poco::trim(payload.substr(fields[3].offset, fields[3].length)); loadArgs(codeLine, *currentParam, argsStr, "previewArgs", "previewKwargs"); } } else if (instruction == "option" and state == "PARAM") { payload = bracketEscapeEncode(payload); Poco::RegularExpression::MatchVec fields; Poco::RegularExpression("^(\\s*\\[(.*)\\]\\s*)?(.*)$").match(payload, 0, fields); if (fields.empty()) throw Pothos::SyntaxException( "Expected |option [name] value", codeLine.toString()); assert(fields.size() == 4); const std::string value = bracketEscapeDecode(Poco::trim(payload.substr(fields[3].offset, fields[3].length))); std::string name = titleCase(value); if (fields[2].length != 0) name = bracketEscapeDecode(Poco::trim(payload.substr(fields[2].offset, fields[2].length))); Poco::JSON::Object option; option.set("value", value); option.set("name", name); if (not currentParam->has("options")) currentParam->set( "options", Poco::JSON::Array::Ptr(new Poco::JSON::Array())); currentParam->getArray("options")->add(option); } else if (instruction == "factory" and (state == "DOC" or state == "PARAM")) { Poco::RegularExpression::MatchVec fields; Poco::RegularExpression("^\\s*(/.*)\\s*\\((.*)\\)$").match(payload, 0, fields); if (fields.empty()) throw Pothos::SyntaxException( "Expected |factory /registry/path(args...)", codeLine.toString()); assert(fields.size() == 3); const std::string path = Poco::trim(payload.substr(fields[1].offset, fields[1].length)); const std::string argsStr = Poco::trim(payload.substr(fields[2].offset, fields[2].length)); //add the path try {Pothos::PluginPath(path);} catch (const Pothos::PluginPathError &) { throw Pothos::SyntaxException("Invalid factory path", codeLine.toString()); } if (topObj->has("path")) throw Pothos::SyntaxException( "Multiple occurrence of |factory", codeLine.toString()); topObj->set("path", path); //split and extract args loadArgs(codeLine, *topObj, argsStr); state = "DOC"; } else if ((instruction == "setter" or instruction == "initializer") and (state == "DOC" or state == "PARAM")) { Poco::RegularExpression::MatchVec fields; Poco::RegularExpression("^\\s*(\\w+)\\s*\\((.*)\\)$").match(payload, 0, fields); if (fields.empty()) throw Pothos::SyntaxException( "Expected |"+instruction+" setFooBar(args...)", codeLine.toString()); assert(fields.size() == 3); const std::string callName = Poco::trim(payload.substr(fields[1].offset, fields[1].length)); const std::string argsStr = Poco::trim(payload.substr(fields[2].offset, fields[2].length)); //add to calls Poco::JSON::Object call; call.set("type", instruction); call.set("name", callName); loadArgs(codeLine, call, argsStr); calls.add(call); state = "DOC"; } else if (instruction == "mode" and (state == "DOC" or state == "PARAM")) { if (topObj->has("mode")) throw Pothos::SyntaxException( "Multiple occurrence of |mode", codeLine.toString()); topObj->set("mode", payload); } } //empty state means this was a regular comment block, return null if (state.empty()) return Poco::JSON::Object::Ptr(); topDocs = stripDocArray(topDocs); if (topDocs->size() > 0) topObj->set("docs", topDocs); if (categories.size() > 0) topObj->set("categories", categories); if (keywords.size() > 0) topObj->set("keywords", keywords); if (aliases->size() > 0) topObj->set("aliases", aliases); if (params.size() > 0) topObj->set("params", params); if (calls.size() > 0) topObj->set("calls", calls); //sanity check for required stuff if (not topObj->has("path")) { throw Pothos::SyntaxException("missing |factory declaration"); } return topObj; }
void GraphEditor::exportToJSONTopology(const QString &fileName) { poco_information_f1(_logger, "Exporting %s", fileName.toStdString()); Poco::JSON::Object topObj; //all graph objects excluding widgets which are not exported //we will have to filter out graphical blocks as well const auto graphObjects = this->getGraphObjects(~GRAPH_WIDGET); //global variables Poco::JSON::Array globals; for (const auto &name : this->listGlobals()) { const auto &value = this->getGlobalExpression(name); Poco::JSON::Object globalObj; globalObj.set("name", name.toStdString()); globalObj.set("value", value.toStdString()); globals.add(globalObj); } if (globals.size() > 0) topObj.set("globals", globals); //thread pools (filled in by blocks loop) Poco::JSON::Object threadPools; auto affinityZones = AffinityZonesDock::global(); //blocks Poco::JSON::Array blocks; std::map<size_t, const GraphBlock*> uidToBlock; for (const auto *obj : graphObjects) { const auto block = dynamic_cast<const GraphBlock *>(obj); if (block == nullptr) continue; if (block->isGraphWidget()) continue; uidToBlock[block->uid()] = block; //copy in the the id and path Poco::JSON::Object blockObj; blockObj.set("id", block->getId().toStdString()); blockObj.set("path", block->getBlockDescPath()); //setup the thread pool when specified const auto affinityZone = block->getAffinityZone(); if (not affinityZone.isEmpty() and affinityZone != "gui") { const auto config = affinityZones->zoneToConfig(affinityZone); threadPools.set(affinityZone.toStdString(), config); blockObj.set("threadPool", affinityZone.toStdString()); } //block description args are in the same format const auto desc = block->getBlockDesc(); if (desc->has("args")) blockObj.set("args", desc->get("args")); //copy in the named calls in array format Poco::JSON::Array calls; if (desc->isArray("calls")) for (const auto &elem : *desc->getArray("calls")) { const auto callObj = elem.extract<Poco::JSON::Object::Ptr>(); Poco::JSON::Array call; call.add(callObj->get("name")); for (const auto &arg : *callObj->getArray("args")) call.add(arg); calls.add(call); } blockObj.set("calls", calls); //copy in the parameters as local variables Poco::JSON::Array locals; for (const auto &name : block->getProperties()) { Poco::JSON::Object local; local.set("name", name.toStdString()); local.set("value", block->getPropertyValue(name).toStdString()); locals.add(local); } blockObj.set("locals", locals); blocks.add(blockObj); } topObj.set("blocks", blocks); if (threadPools.size() > 0) topObj.set("threadPools", threadPools); //connections Poco::JSON::Array connections; for (const auto &connInfo : TopologyEval::getConnectionInfo(graphObjects)) { //the block may have been filtered out //check that its found in the mapping auto srcIt = uidToBlock.find(connInfo.srcBlockUID); if (srcIt == uidToBlock.end()) continue; auto dstIt = uidToBlock.find(connInfo.dstBlockUID); if (dstIt == uidToBlock.end()) continue; //create the connection information in order Poco::JSON::Array connArr; connArr.add(srcIt->second->getId().toStdString()); connArr.add(connInfo.srcPort); connArr.add(dstIt->second->getId().toStdString()); connArr.add(connInfo.dstPort); connections.add(connArr); } topObj.set("connections", connections); //write to file try { std::ofstream outFile(fileName.toStdString().c_str()); topObj.stringify(outFile, 4/*indent*/); } catch (const std::exception &ex) { poco_error_f2(_logger, "Error exporting %s: %s", fileName, std::string(ex.what())); } }