Browse Source

Add code annotations generation

 - Copy comments for messages and fields in .proto file to generated
   headers accordingly

Fixes: #65
Alexey Edelev 5 years ago
parent
commit
a26ae61c54

+ 2 - 0
README.md

@@ -226,6 +226,8 @@ Due to cmake restrictions it's required to specify resulting artifacts manually
 
 *QML* - Enables/disables QML code generation in protobuf classes. If set to TRUE QML related code for lists and QML registration to be generated.
 
+*COMMENTS* - Enables/disables comments copying from .proto files. If set to TRUE message and field related comments will be copied to generated header files.
+
 ### qtprotobuf_link_archive
 
 qtprotobuf_link_archive is cmake helper function that links whole archive to your library or executable target. It's useful when you try to link generated target to shared library or/and to executable that doesn't utilize all protobuf generated classes directly from C++ code, but requires them from QML.

+ 1 - 0
cmake/QtProtobufCommon.cmake

@@ -70,6 +70,7 @@ function(add_test_target)
         EXCLUDE_HEADERS ${add_test_target_EXCLUDE_HEADERS}
         MULTI ${add_test_target_MULTI}
         QML ${add_test_target_QML}
+        COMMENTS TRUE
         PROTO_INCLUDES ${add_test_target_PROTO_INCLUDES})
 
     if(Qt5_POSITION_INDEPENDENT_CODE)

+ 6 - 1
cmake/QtProtobufGen.cmake

@@ -18,7 +18,7 @@ endfunction()
 
 function(qtprotobuf_generate)
     set(options)
-    set(oneValueArgs OUT_DIR TARGET GENERATED_TARGET MULTI QML GENERATED_HEADERS_VAR)
+    set(oneValueArgs OUT_DIR TARGET GENERATED_TARGET MULTI QML COMMENTS GENERATED_HEADERS_VAR)
     set(multiValueArgs GENERATED_HEADERS EXCLUDE_HEADERS PROTO_FILES PROTO_INCLUDES)
     cmake_parse_arguments(qtprotobuf_generate "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
 
@@ -50,6 +50,11 @@ function(qtprotobuf_generate)
         set(GENERATION_OPTIONS "${GENERATION_OPTIONS}:QML")
     endif()
 
+    if("${qtprotobuf_generate_COMMENTS}" STREQUAL "TRUE")
+        message(STATUS "Enabled COMMENTS generation for ${GENERATED_TARGET_NAME}")
+        set(GENERATION_OPTIONS "${GENERATION_OPTIONS}:COMMENTS")
+    endif()
+
     find_program(GO_EXECUTABLE "go")
     if (GO_EXECUTABLE STREQUAL GO_EXECUTABLE-NOTFOUND)
         message(FATAL_ERROR "Golang is mandatory dependency for QtProtobuf. Please install it and ensure that it's accessible by PATH environment variable")

+ 34 - 0
src/generator/classgeneratorbase.h

@@ -31,6 +31,7 @@
 
 #include "utils.h"
 #include "templates.h"
+#include "generatoroptions.h"
 
 namespace google { namespace protobuf {
 class FieldDescriptor;
@@ -120,6 +121,39 @@ public:
         Outdent();
     }
 
+    template<typename T>
+    void printComments(T *descriptor) {
+        if (!GeneratorOptions::instance().generateComments()) {
+            return;
+        }
+
+        ::google::protobuf::SourceLocation loc;
+        descriptor->GetSourceLocation(&loc);
+
+        utils::trim(loc.leading_comments);
+        if (loc.leading_comments.size() > 0) {
+            auto firstEntry = loc.leading_comments.find('\n');
+            bool isSingleLine = firstEntry == std::string::npos;
+
+            if (loc.leading_comments[0] != '!' && loc.leading_comments[0] != '*' && loc.leading_comments[0] != ' ') {
+                loc.leading_comments = " " + loc.leading_comments;
+                if (!isSingleLine) {
+                    loc.leading_comments = "\n" + loc.leading_comments;
+                }
+            }
+            mPrinter->Print("\n/*");
+            if (isSingleLine) {
+                mPrinter->Print(loc.leading_comments.c_str());
+            } else {
+                utils::replace(loc.leading_comments, "\n", "\n *");
+                mPrinter->Print(loc.leading_comments.c_str());
+                if (loc.leading_comments[loc.leading_comments.size() - 1] != '\n') {
+                    mPrinter->Print("\n");
+                }
+            }
+            mPrinter->Print(" */");
+        }
+    }
 
     void Indent() {
         mPrinter->Indent();

+ 7 - 2
src/generator/generatoroptions.cpp

@@ -31,11 +31,13 @@
 
 static const std::string MultifileBuildOption("MULTI");
 static const std::string QmlPluginOption("QML");
+static const std::string CommentsGenerationOption("COMMENTS");
 
 using namespace ::QtProtobuf::generator;
 
 GeneratorOptions::GeneratorOptions() : mIsMulti(false)
-  ,mHasQml(false)
+  , mHasQml(false)
+  , mGenerateComments(false)
 {
 }
 
@@ -45,12 +47,15 @@ void GeneratorOptions::parseFromEnv(const std::string &options)
     utils::split(options, optionsList, ':');
     for (auto option : optionsList) {
         QT_PROTOBUF_DEBUG("option: " << option);
-        if(option.compare(MultifileBuildOption) == 0) {
+        if (option.compare(MultifileBuildOption) == 0) {
             QT_PROTOBUF_DEBUG("set mIsMulti: true");
             mIsMulti = true;
         } else if (option.compare(QmlPluginOption) == 0) {
             QT_PROTOBUF_DEBUG("set mHasQml: true");
             mHasQml = true;
+        } else if (option.compare(CommentsGenerationOption)) {
+            QT_PROTOBUF_DEBUG("set mGenerateComments: true");
+            mGenerateComments = true;
         }
     }
 }

+ 3 - 0
src/generator/generatoroptions.h

@@ -48,9 +48,12 @@ public:
 
     bool isMulti() const { return mIsMulti; }
     bool hasQml() const { return mHasQml; }
+    bool generateComments() const { return mGenerateComments; }
+
 private:
     bool mIsMulti;
     bool mHasQml;
+    bool mGenerateComments;
 };
 
 }}

+ 4 - 0
src/generator/protobufclassgenerator.cpp

@@ -30,6 +30,7 @@
 #include <iostream>
 #include <google/protobuf/descriptor.h>
 #include <google/protobuf/io/zero_copy_stream.h>
+#include <google/protobuf/descriptor.pb.h>
 
 using namespace ::QtProtobuf::generator;
 using namespace ::google::protobuf;
@@ -242,6 +243,8 @@ void ProtobufClassGenerator::printProperties()
 
     for (int i = 0; i < mMessage->field_count(); i++) {
         const FieldDescriptor *field = mMessage->field(i);
+        printComments(field);
+        mPrinter->Print("\n");
         if (field->type() == FieldDescriptor::TYPE_MESSAGE && !field->is_map() && !field->is_repeated()) {
             printField(mMessage, field, Templates::GetterPrivateMessageDeclarationTemplate);
             printField(mMessage, field, Templates::GetterMessageDeclarationTemplate);
@@ -333,6 +336,7 @@ void ProtobufClassGenerator::run()
     printIncludes();
     printNamespaces();
     printFieldClassDeclaration();
+    printComments(mMessage);
     printClassDeclaration();
     printProperties();
     printPrivate();

+ 1 - 0
src/generator/singlefilegenerator.cpp

@@ -157,6 +157,7 @@ bool SingleFileGenerator::GenerateMessages(const ::google::protobuf::FileDescrip
                                         outHeaderPrinter);
 
         classGen.printNamespaces();
+        classGen.printComments(message);
         classGen.printClassDeclaration();
         classGen.printProperties();
         classGen.printPrivate();

+ 19 - 0
src/generator/utils.h

@@ -98,6 +98,25 @@ static std::string lowerCaseName(const std::string &name)
     return lowerCaseName;
 }
 
+inline static std::string& rtrim(std::string& s)
+{
+    s.erase(s.find_last_not_of(" \t\n\r\f\v") + 1);
+    return s;
+}
+
+// trim from beginning of string (left)
+inline static std::string& ltrim(std::string& s)
+{
+    s.erase(0, s.find_first_not_of(" \t\n\r\f\v"));
+    return s;
+}
+
+// trim from both ends of string (right then left)
+inline static std::string& trim(std::string& s)
+{
+    return ltrim(rtrim(s));
+}
+
 };
 
 #define UNUSED(expr) do { (void)(expr); } while (0)

+ 114 - 0
tests/test_protobuf/proto/annotation.proto

@@ -0,0 +1,114 @@
+syntax = "proto3";
+
+package qtprotobufnamespace.annotationtest;
+
+//test annotation
+message AnnotatedMessage1 {
+    sint32 testField = 1;
+}
+
+/*test annotation*/
+message AnnotatedMessage2 {
+    sint32 testField = 1;
+}
+
+/*
+ * test annotation
+ */
+message AnnotatedMessage3 {
+    sint32 testField = 1;
+}
+
+/*
+  test annotation
+ */
+message AnnotatedMessage4 {
+    sint32 testField = 1;
+}
+
+//! \brief test annotation
+message AnnotatedMessage5 {
+    sint32 testField = 1;
+}
+
+//* \brief test annotation
+message AnnotatedMessage6 {
+    sint32 testField = 1;
+}
+
+/*!
+ * \brief test annotation
+ */
+message AnnotatedMessage7 {
+    sint32 testField = 1;
+}
+
+/**
+ * \brief test annotation
+ */
+message AnnotatedMessage8 {
+    sint32 testField = 1;
+}
+
+/*
+ * test annotation
+ * test annotation secondline
+ */
+message AnnotatedMessage9 {
+    sint32 testField = 1;
+}
+
+
+message AnnotatedMessage10 {
+    //Field annotation
+    sint32 testField = 1;
+}
+
+message AnnotatedMessage11 {
+    /*Field annotation*/
+    sint32 testField = 1;
+}
+
+message AnnotatedMessage12 {
+    //! Field annotation
+    sint32 testField = 1;
+}
+
+message AnnotatedMessage13 {
+    //* Field annotation
+    sint32 testField = 1;
+}
+
+message AnnotatedMessage14 {
+    //* Field annotation
+    sint32 testField = 1;
+}
+
+message AnnotatedMessage15 {
+    /*
+     * Field annotation
+     */
+    sint32 testField = 1;
+}
+
+message AnnotatedMessage16 {
+    /*
+     * Field annotation
+     * Field annotation secondline
+     */
+    sint32 testField = 1;
+}
+
+message AnnotatedMessage17 {
+    /*!
+     * \brief Field annotation
+     */
+    sint32 testField = 1;
+}
+
+message AnnotatedMessage18 {
+    /**
+     * \brief Field annotation
+     */
+    sint32 testField = 1;
+}