diff --git a/README b/README index 647dc8d..0994dc3 100644 --- a/README +++ b/README @@ -47,4 +47,16 @@ Version history: not moving member comments before the member but keeping it after the member instead * these changes lead into need of enabling JAVADOC_AUTOBRIEF - - added steps for enabling the filter in Doxygen in this file \ No newline at end of file + - added steps for enabling the filter in Doxygen in this file +-------------------- + 0.7-beta (2018-04-19) OSI + - Include changes from University of California. + - Support for all OSI *.proto files. + - Separate statement and comments to treat both parts differently (remove bugs + regarding string modifications). + - Remove "option" statements. + - Add support for "extend" statements. + - Change "repeat" from Template to standard member. --> Better collaboration + diagrams. + - Fix problems with references of nested messages (replace "." with "::"). + - Change mapping from C to C++. diff --git a/doxyfile b/doxyfile index ad14802..5a89ee2 100644 --- a/doxyfile +++ b/doxyfile @@ -32,7 +32,7 @@ PROJECT_NAME = "Protocol Buffers demo project" # This could be handy for archiving the generated documentation or # if some version control system is used. -PROJECT_NUMBER = 0.6-beta +PROJECT_NUMBER = 0.7-beta # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer @@ -238,7 +238,7 @@ OPTIMIZE_OUTPUT_VHDL = NO # (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions # you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. -EXTENSION_MAPPING = proto=C +EXTENSION_MAPPING = proto=C++ # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should diff --git a/proto2cpp.py b/proto2cpp.py index 96506aa..c3a5ffd 100644 --- a/proto2cpp.py +++ b/proto2cpp.py @@ -1,9 +1,10 @@ +#!/usr/bin/env python ## # Doxygen filter for Google Protocol Buffers .proto files. # This script converts .proto files into C++ style ones # and prints the output to standard output. # -# version 0.6-beta +# version 0.7-beta OSI # # How to enable this filter in Doxygen: # 1. Generate Doxygen configuration file with command 'doxygen -g ' @@ -12,7 +13,7 @@ # JAVADOC_AUTOBRIEF = YES # 3. In the Doxygen configuration file, find FILE_PATTERNS and add *.proto # FILE_PATTERNS = *.proto -# 4. In the Doxygen configuration file, find EXTENSION_MAPPING and add proto=C +# 4. In the Doxygen configuration file, find EXTENSION_MAPPING and add proto=C++ # EXTENSION_MAPPING = proto=C # 5. In the Doxygen configuration file, find INPUT_FILTER and add this script # INPUT_FILTER = "python proto2cpp.py" @@ -20,7 +21,9 @@ # doxygen doxyfile # # -# Copyright (C) 2012-2015 Timo Marjoniemi +# Version 0.7 2018 Bugfix and extensions have been made by Open Simulation Interface (OSI) Carsten Kuebler https://github.com/OpenSimulationInterface +# Copyright (C) 2016 Regents of the University of California https://github.com/vgteam/vg +# Copyright (C) 2012-2015 Timo Marjoniemi https://sourceforge.net/p/proto2cpp/wiki/Home/ # All rights reserved. # # This library is free software; you can redistribute it and/or @@ -72,7 +75,7 @@ def __init__(self): self.logFile = "proto2cpp.log" ## Error log file name. self.errorLogFile = "proto2cpp.error.log" - ## Logging level. + ## Logging level. self.logLevel = self.logNone ## Handles a file. @@ -88,7 +91,7 @@ def handleFile(self, fileName): self.log('\nXXXXXXXXXX\nXX ' + filename + '\nXXXXXXXXXX\n\n') # Open the file. Use try to detect whether or not we have an actual file. try: - with open(filename, 'r') as inputFile: + with open(filename, 'r', encoding='utf8') as inputFile: self.parseFile(inputFile) pass except IOError as e: @@ -97,7 +100,7 @@ def handleFile(self, fileName): elif not fnmatch.fnmatch(filename, os.path.basename(inspect.getfile(inspect.currentframe()))): self.log('\nXXXXXXXXXX\nXX ' + filename + '\nXXXXXXXXXX\n\n') try: - with open(filename, 'r') as theFile: + with open(filename, 'r', encoding='utf8') as theFile: output = '' for theLine in theFile: output += theLine @@ -120,6 +123,7 @@ def handleFile(self, fileName): def parseFile(self, inputFile): # Go through the input file line by line. isEnum = False + inPackage = False # This variable is here as a workaround for not getting extra line breaks (each line # ends with a line separator and print() method will add another one). # We will be adding lines into this var and then print the var out at the end. @@ -129,24 +133,54 @@ def parseFile(self, inputFile): # block to make Doxygen detect it. matchComment = re.search("//", line) # Search for semicolon and if one is found before comment, add a third slash character - # ("/") and a smaller than ("<") chracter to the comment to make Doxygen detect it. + # ("/") and a smaller than ("<") character to the comment to make Doxygen detect it. matchSemicolon = re.search(";", line) if matchSemicolon is not None and (matchComment is not None and matchSemicolon.start() < matchComment.start()): - line = line[:matchComment.start()] + "///<" + line[matchComment.end():] + comment = "///<" + line[matchComment.end():] + # Replace '.' in nested message references with '::' + # don't work for multi-nested references and generates problems with URLs and acronyms + #comment = re.sub(r'\s(\w+)\.(\w+)\s', r' \1::\2 ', comment) + line = line[:matchComment.start()] elif matchComment is not None: - line = line[:matchComment.start()] + "///" + line[matchComment.end():] + comment = "///" + line[matchComment.end():] + # replace '.' in nested message references with '::' + # don't work for multi-nested references and generates problems with URLs and acronyms + #comment = re.sub(r'\s(\w+)\.(\w+)\s', r' \1::\2 ', comment) + line = line[:matchComment.start()] + else: + comment = "" + + # line = line.replace(".", "::") but not in quoted strings (Necessary for import statement) + line = re.sub(r'\.(?=(?:[^"]*"[^"]*")*[^"]*$)',r'::',line) - # Search for "enum" and if one is found before comment, - # start changing all semicolons (";") to commas (","). - matchEnum = re.search("enum", line) - if matchEnum is not None and (matchComment is None or matchEnum.start() < matchComment.start()): + # Search for " option ...;", remove it + line = re.sub(r'\boption\b[^;]+;', r'', line) + + # Search for " package ", make a namespace + matchPackage = re.search(r"\bpackage\b", line) + if matchPackage is not None: + isPackage = True + # Convert to C++-style separator and block instead of statement + line = "namespace" + line[:matchPackage.start()] + line[matchPackage.end():].replace(";", " {") + + # Search for " repeated " fields and make them ... + #matchRepeated = re.search(r"\brepeated\b", line) + #if matchRepeated is not None: + # # Convert + # line = re.sub(r'\brepeated\s+(\S+)', r' repeated \1', line) + + # Search for "enum", start changing all semicolons (";") to commas (","). + matchEnum = re.search(r"\benum\b", line) + if matchEnum is not None: isEnum = True + # Search again for semicolon if we have detected an enum, and replace semicolon with comma. if isEnum is True and re.search(";", line) is not None: matchSemicolon = re.search(";", line) line = line[:matchSemicolon.start()] + "," + line[matchSemicolon.end():] + # Search for a closing brace. - matchClosingBrace = re.search("}", line[:matchComment.start()] if matchComment else line) + matchClosingBrace = re.search("}", line) if isEnum is True and matchClosingBrace is not None: line = line[:matchClosingBrace.start()] + "};" + line[matchClosingBrace.end():] isEnum = False @@ -154,13 +188,32 @@ def parseFile(self, inputFile): # Message (to be struct) ends => add semicolon so that it'll # be a proper C(++) struct and Doxygen will handle it correctly. line = line[:matchClosingBrace.start()] + "};" + line[matchClosingBrace.end():] - # Search for 'message' and replace it with 'struct' unless 'message' is behind a comment. - matchMsg = re.search("message", line) - if matchMsg is not None and (matchComment is None or matchMsg.start() < matchComment.start()): - output = "struct" + line[:matchMsg.start()] + line[matchMsg.end():] - theOutput += output - else: - theOutput += line + + # Replacements change start of comment... + matchMsg = re.search(r"\bmessage\b", line) + if matchMsg is not None: + line = line[:matchMsg.start()] + "struct" + line[matchMsg.end():] + + # Replacements change start of comment... + matchExt = re.search(r"\bextend\b", line) + if matchExt is not None: + a_extend = line[matchExt.end():] + matchName = re.search(r"\b\w[\S:]+\b", a_extend) + if matchName is not None: + name = a_extend[matchName.start():matchName.end()] + name = re.sub(r'\w+::',r'',name) + a_extend = a_extend[:matchName.start()] + name + ": public " + a_extend[matchName.start():] + else: + a_extend = "_Dummy: public " + a_extend; + line = line[:matchExt.start()] + "struct " + a_extend + + theOutput += line + comment + + if isPackage: + # Close the package namespace + theOutput += "}" + isPackage = False + # Now that we've got all lines in the string let's split the lines and print out # one by one. # This is a workaround to get rid of extra empty line at the end which print() method adds.