//################################################################################################## // // Custom Visualization Core library // Copyright (C) Ceetron Solutions AS // // This library may be used under the terms of either the GNU General Public License or // the GNU Lesser General Public License as follows: // // GNU General Public License Usage // This library is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 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 General Public License at <> // for more details. // // GNU Lesser General Public License Usage // 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 at <> // for more details. // //################################################################################################## #include "cafPdmMarkdownBuilder.h" #include "cafPdmAbstractFieldScriptingCapability.h" #include "cafPdmChildArrayField.h" #include "cafPdmChildField.h" #include "cafPdmObject.h" #include "cafPdmObjectFactory.h" #include "cafPdmObjectScriptingCapabilityRegister.h" #include "cafPdmProxyValueField.h" #include "cafPdmPythonGenerator.h" #include "cafPdmXmlFieldHandle.h" #include #include #include #include #include #include using namespace caf; //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- ; QString caf::PdmMarkdownBuilder::generateDocDataModelObjects( std::vector>& dataModelObjects ) { QString generatedCode; QTextStream out( &generatedCode ); // Sort to make sure super classes get created before sub classes std::sort( dataModelObjects.begin(), dataModelObjects.end(), []( std::shared_ptr lhs, std::shared_ptr rhs ) { auto lhsStack = lhs->classInheritanceStack(); auto rhsStack = rhs->classInheritanceStack(); auto maxItems = std::min( lhsStack.size(), rhsStack.size() ); auto lhsIt = lhsStack.begin(); auto rhsIt = rhsStack.begin(); for ( size_t i = 0; i < maxItems; i++ ) { if ( *lhsIt != *rhsIt ) { return ( *lhsIt < *rhsIt ); } lhsIt++; rhsIt++; } return lhsStack.size() < rhsStack.size(); } ); std::map>> classAttributesGenerated; std::map> classMethodsGenerated; std::map classCommentsGenerated; // First generate all attributes and comments to go into each object for ( std::shared_ptr object : dataModelObjects ) { const std::list& classInheritanceStack = object->classInheritanceStack(); for ( auto it = classInheritanceStack.begin(); it != classInheritanceStack.end(); ++it ) { const QString& classKeyword = *it; QString scriptClassComment = PdmObjectScriptingCapabilityRegister::scriptClassComment( classKeyword ); std::map attributesGenerated; if ( !scriptClassComment.isEmpty() ) classCommentsGenerated[classKeyword] = scriptClassComment; if ( classKeyword == object->classKeyword() ) { std::vector fields = object->fields(); for ( auto field : fields ) { auto scriptability = field->template capability(); if ( scriptability != nullptr ) { QString snake_field_name = PdmPythonGenerator::camelToSnakeCase( scriptability->scriptFieldName() ); QString comment; { QStringList commentComponents; commentComponents << field->capability()->uiName(); commentComponents << field->capability()->uiWhatsThis(); commentComponents.removeAll( QString( "" ) ); comment = commentComponents.join( ". " ); } auto pdmValueField = dynamic_cast( field ); auto pdmChildField = dynamic_cast( field ); auto pdmChildArrayField = dynamic_cast( field ); if ( pdmValueField ) { QString dataType = PdmPythonGenerator::dataTypeString( field, true ); if ( field->xmlCapability()->isVectorField() ) { dataType = QString( "List of %1" ).arg( dataType ); } bool shouldBeMethod = false; auto proxyField = dynamic_cast( field ); if ( proxyField && proxyField->isStreamingField() ) shouldBeMethod = true; if ( classAttributesGenerated[field->ownerClass()].count( snake_field_name ) ) continue; if ( classMethodsGenerated[field->ownerClass()].count( snake_field_name ) ) continue; QVariant valueVariant = pdmValueField->toQVariant(); if ( shouldBeMethod ) { if ( proxyField->hasGetter() ) { QString fullComment = QString( " \"\"\"%1\n Returns:\n %2\n \"\"\"" ) .arg( comment ) .arg( dataType ); QString fieldCode = QString( " def %1(self):\n%2\n return " "self._call_get_method(\"%3\")\n" ) .arg( snake_field_name ) .arg( fullComment ) .arg( scriptability->scriptFieldName() ); classMethodsGenerated[field->ownerClass()][snake_field_name] = fieldCode; } if ( proxyField->hasSetter() ) { QString fullComment = QString( " \"\"\"Set %1\n Arguments:\n" " values (%2): data\n \"\"\"" ) .arg( comment ) .arg( dataType ); QString fieldCode = QString( " def set_%1(self, values):\n%2\n " "self._call_set_method(\"%3\", values)\n" ) .arg( snake_field_name ) .arg( fullComment ) .arg( scriptability->scriptFieldName() ); classMethodsGenerated[field->ownerClass()][QString( "set_%1" ).arg( snake_field_name )] = fieldCode; } } else { QString valueString; QTextStream valueStream( &valueString ); scriptability->readFromField( valueStream, true, true ); if ( valueString.isEmpty() ) valueString = QString( "\"\"" ); valueString = PdmPythonGenerator::pythonifyDataValue( valueString ); QString fieldCode = QString( " self.%1 = %2\n" ).arg( snake_field_name ).arg( valueString ); QString fullComment = QString( "%1|%2|%3" ).arg( snake_field_name ).arg( dataType ).arg( comment ); classAttributesGenerated[field->ownerClass()][snake_field_name].first = fieldCode; classAttributesGenerated[field->ownerClass()][snake_field_name].second = fullComment; } } else if ( pdmChildField || pdmChildArrayField ) { QString dataType = PdmPythonGenerator::dataTypeString( field, false ); QString scriptDataType = PdmObjectScriptingCapabilityRegister::scriptClassNameFromClassKeyword( dataType ); QString commentDataType = field->xmlCapability()->isVectorField() ? QString( "List of %1" ).arg( scriptDataType ) : scriptDataType; QString fullComment = QString( " \"\"\"%1\n Returns:\n %2\n \"\"\"" ) .arg( comment ) .arg( commentDataType ); if ( pdmChildField ) { QString fieldCode = QString( " def %1(self):\n%2\n return " "self.children(\"%3\", %4)[0] if len(self.children) > 0 else None\n" ) .arg( snake_field_name ) .arg( fullComment ) .arg( scriptability->scriptFieldName() ) .arg( scriptDataType ); classMethodsGenerated[field->ownerClass()][snake_field_name] = fieldCode; } else { QString fieldCode = QString( " def %1(self):\n%2\n return " "self.children(\"%3\", %4)\n" ) .arg( snake_field_name ) .arg( fullComment ) .arg( scriptability->scriptFieldName() ) .arg( scriptDataType ); classMethodsGenerated[field->ownerClass()][snake_field_name] = fieldCode; } } } } } } } // Write out classes std::set classesWritten; for ( std::shared_ptr object : dataModelObjects ) { const std::list& classInheritanceStack = object->classInheritanceStack(); std::list scriptSuperClassNames; size_t inheritanceLevel = 0; for ( auto it = classInheritanceStack.begin(); it != classInheritanceStack.end(); ++it ) { const QString& classKeyword = *it; QString scriptClassName = PdmObjectScriptingCapabilityRegister::scriptClassNameFromClassKeyword( classKeyword ); if ( scriptClassName.isEmpty() ) scriptClassName = classKeyword; if ( !classesWritten.count( scriptClassName ) ) { QString classCode; { size_t headingLevel = inheritanceLevel + 1; size_t maxHeadingLevel = 4; size_t minHeadingLevel = 2; headingLevel = std::max( headingLevel, minHeadingLevel ); headingLevel = std::min( headingLevel, maxHeadingLevel ); for ( size_t level = 0; level < headingLevel; level++ ) { classCode += "#"; } } classCode += QString( " %1\n" ).arg( scriptClassName ); if ( !scriptSuperClassNames.empty() ) { classCode += QString( "**Inherits %1**\n\n" ).arg( scriptSuperClassNames.back() ); } if ( !classCommentsGenerated[classKeyword].isEmpty() || !classAttributesGenerated[classKeyword].empty() ) { if ( !classCommentsGenerated[classKeyword].isEmpty() ) { classCode += QString( "%1\n\n" ).arg( classCommentsGenerated[classKeyword] ); } if ( !classAttributesGenerated[classKeyword].empty() ) { classCode += "Attribute | Type | Description\n"; classCode += "--------- | ---- | -----------\n"; for ( auto keyWordValuePair : classAttributesGenerated[classKeyword] ) { QStringList items = keyWordValuePair.second.second.split( "|" ); if ( !items.empty() ) { classCode += items[0]; for ( int i = 1; i < items.size(); i++ ) { classCode += " | "; classCode += items[i]; } classCode += "\n"; } } } } out << classCode << "\n"; classesWritten.insert( scriptClassName ); } scriptSuperClassNames.push_back( scriptClassName ); inheritanceLevel++; } } return generatedCode; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector> caf::PdmMarkdownBuilder::createAllObjects( caf::PdmObjectFactory* factory ) { std::vector> objects; std::vector classKeywords = factory->classKeywords(); for ( QString classKeyword : classKeywords ) { auto objectHandle = factory->create( classKeyword ); PdmObject* object = dynamic_cast( objectHandle ); CAF_ASSERT( object ); std::shared_ptr sharedObject( object ); objects.push_back( sharedObject ); } return objects; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString caf::PdmMarkdownBuilder::generateDocCommandObjects( std::vector>& commandObjects ) { QString generatedText; QTextStream out( &generatedText ); struct CommandDocData { QString snakeClassName; QString description; std::vector parameters; }; std::vector objs; // First generate all attributes and comments to go into each object for ( std::shared_ptr object : commandObjects ) { QString snakeCommandName = PdmPythonGenerator::camelToSnakeCase( object->classKeyword() ); std::vector attributes; std::vector fields = object->fields(); for ( auto field : fields ) { QString snake_field_name = PdmPythonGenerator::camelToSnakeCase( field->keyword() ); QString pythonDataType = PdmPythonGenerator::dataTypeString( field, true ); QString nativeDataType = field->xmlCapability()->dataTypeName(); if ( nativeDataType.contains( "std::vector" ) ) { pythonDataType = QString( "List of %1" ).arg( pythonDataType ); } QString comment; { QStringList commentComponents; commentComponents << field->capability()->uiName(); commentComponents << field->capability()->uiWhatsThis(); commentComponents.removeAll( QString( "" ) ); comment = commentComponents.join( ". " ); } attributes.push_back( { snake_field_name, comment, pythonDataType } ); } QString comment = caf::PdmObjectScriptingCapabilityRegister::scriptClassComment( object->classKeyword() ); objs.push_back( { snakeCommandName, comment, attributes } ); // objectsAndAttributes[snakeCommandName] = attributes; } for ( auto object : objs ) { out << QString( "## %1\n\n" ).arg( object.snakeClassName ); if ( !object.description.isEmpty() ) { out << object.description << "\n\n"; } out << "Parameter | Type | Description\n"; out << "--------- | ---- | -----------\n"; for ( auto keyWordValuePair : object.parameters ) { out << keyWordValuePair.name << " | "; out << keyWordValuePair.type << " | "; out << keyWordValuePair.description << "\n"; } out << "\n"; } return generatedText; }