From: spo Date: Fri, 27 May 2016 08:07:51 +0000 (+0300) Subject: Use doxy2swig for C++HighAPI documentation X-Git-Url: http://git.salome-platform.org/gitweb/?a=commitdiff_plain;h=322ba74c9dbe8e621e204066f3a02a8fac92ac7f;p=modules%2Fshaper.git Use doxy2swig for C++HighAPI documentation --- diff --git a/CMakeCommon/PythonAPI.cmake b/CMakeCommon/PythonAPI.cmake index 7612bf4c8..123354160 100644 --- a/CMakeCommon/PythonAPI.cmake +++ b/CMakeCommon/PythonAPI.cmake @@ -6,7 +6,32 @@ include(${SWIG_USE_FILE}) #TODO(spo): is -threads necessary? set(CMAKE_SWIG_FLAGS -threads -Wall) -# For doxyhelp.i -include_directories( - ${PROJECT_BINARY_DIR}/doc/swig +# Using doxy2swig: +# Add to ModuleName.i before any %inlcude: +# %include "doxyhelp.i" +# Add to CMakeLists.txt before SWIG_ADD_MODULE(...): +# SET(SWIG_MODULE_ModuleName_EXTRA_DEPS ${SWIG_MODULE_ModuleName_EXTRA_DEPS} doxyhelp.i) + +#TODO(spo): why not use FindDoxygen.cmake module? +if(WIN32) + find_program(DOXYGEN_EXECUTABLE Doxygen) +else() + find_program(DOXYGEN_EXECUTABLE doxygen) +endif() + +set(EXCLUDE_DOC_DIR "*/Test/* */Mock/*") + +configure_file(${PROJECT_SOURCE_DIR}/doc/swig/doxyfile.in doxyfile @ONLY) + +set(doc_sources) +foreach(it "*.h;*.cpp;*.hpp;*.cxx") + file(GLOB doc_source RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${it}) + list(APPEND doc_sources ${doc_source}) +endforeach() +#message(STATUS "${doc_sources}") + +add_custom_command(OUTPUT doxyhelp.i + COMMAND "${DOXYGEN_EXECUTABLE}" + COMMAND ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/scripts/doxy2swig.py xml/index.xml doxyhelp.i + DEPENDS ${doc_sources} ) diff --git a/doc/swig/doxyfile.in b/doc/swig/doxyfile.in new file mode 100644 index 000000000..bffdb82f1 --- /dev/null +++ b/doc/swig/doxyfile.in @@ -0,0 +1,300 @@ +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +DOXYFILE_ENCODING = UTF-8 +PROJECT_NAME = @CMAKE_PROJECT_NAME@ +PROJECT_NUMBER = @SHAPER_Version@ +PROJECT_BRIEF = +PROJECT_LOGO = +OUTPUT_DIRECTORY = +CREATE_SUBDIRS = NO +OUTPUT_LANGUAGE = English +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = +ALWAYS_DETAILED_SEC = YES +INLINE_INHERITED_MEMB = YES +FULL_PATH_NAMES = YES +STRIP_FROM_PATH = @PROJECT_BINARY_DIR@ \ + @PROJECT_SOURCE_DIR@ +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = YES +QT_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 5 +ALIASES = +TCL_SUBST = +OPTIMIZE_OUTPUT_FOR_C = YES +OPTIMIZE_OUTPUT_JAVA = NO +OPTIMIZE_FOR_FORTRAN = NO +OPTIMIZE_OUTPUT_VHDL = NO +EXTENSION_MAPPING = +MARKDOWN_SUPPORT = YES +AUTOLINK_SUPPORT = YES +BUILTIN_STL_SUPPORT = NO +CPP_CLI_SUPPORT = NO +SIP_SUPPORT = NO +IDL_PROPERTY_SUPPORT = YES +DISTRIBUTE_GROUP_DOC = NO +SUBGROUPING = YES +INLINE_GROUPED_CLASSES = NO +INLINE_SIMPLE_STRUCTS = NO +TYPEDEF_HIDES_STRUCT = NO +SYMBOL_CACHE_SIZE = 0 +LOOKUP_CACHE_SIZE = 0 +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = NO +EXTRACT_PRIVATE = NO +EXTRACT_PACKAGE = NO +EXTRACT_STATIC = YES +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_METHODS = NO +EXTRACT_ANON_NSPACES = NO +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = YES +CASE_SENSE_NAMES = YES +HIDE_SCOPE_NAMES = NO +SHOW_INCLUDE_FILES = NO +FORCE_LOCAL_INCLUDES = NO +INLINE_INFO = YES +SORT_MEMBER_DOCS = NO +SORT_BRIEF_DOCS = NO +SORT_MEMBERS_CTORS_1ST = NO +SORT_GROUP_NAMES = NO +SORT_BY_SCOPE_NAME = YES +STRICT_PROTO_MATCHING = NO +GENERATE_TODOLIST = NO +GENERATE_TESTLIST = NO +GENERATE_BUGLIST = NO +GENERATE_DEPRECATEDLIST= NO +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 25 +SHOW_USED_FILES = NO +SHOW_FILES = YES +SHOW_NAMESPACES = YES +FILE_VERSION_FILTER = +LAYOUT_FILE = +CITE_BIB_FILES = +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = NO +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = log.txt +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = @CMAKE_CURRENT_SOURCE_DIR@ +INPUT_ENCODING = UTF-8 +FILE_PATTERNS = *.h \ + *.cpp \ + *.hxx \ + *.cxx \ + *.doc \ + *.py +RECURSIVE = YES +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = */Test/* @EXCLUDE_DOC_DIR@ +EXCLUDE_SYMBOLS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = @CMAKE_CURRENT_SOURCE_DIR@ +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +FILTER_SOURCE_PATTERNS = +USE_MDFILE_AS_MAINPAGE = +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = NO +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = NO +REFERENCES_RELATION = NO +REFERENCES_LINK_SOURCE = YES +USE_HTAGS = NO +VERBATIM_HEADERS = YES +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = YES +COLS_IN_ALPHA_INDEX = 3 +IGNORE_PREFIX = +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = NO +HTML_OUTPUT = . +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_EXTRA_STYLESHEET = +HTML_EXTRA_FILES = +HTML_COLORSTYLE_HUE = 220 +HTML_COLORSTYLE_SAT = 100 +HTML_COLORSTYLE_GAMMA = 80 +HTML_TIMESTAMP = YES +HTML_DYNAMIC_SECTIONS = NO +HTML_INDEX_NUM_ENTRIES = 100 +GENERATE_DOCSET = NO +DOCSET_FEEDNAME = "Doxygen generated docs" +DOCSET_BUNDLE_ID = org.doxygen.Project +DOCSET_PUBLISHER_ID = org.doxygen.Publisher +DOCSET_PUBLISHER_NAME = Publisher +GENERATE_HTMLHELP = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +CHM_INDEX_ENCODING = +BINARY_TOC = YES +TOC_EXPAND = YES +GENERATE_QHP = NO +QCH_FILE = +QHP_NAMESPACE = org.doxygen.Project +QHP_VIRTUAL_FOLDER = doc +QHP_CUST_FILTER_NAME = +QHP_SECT_FILTER_ATTRS = +QHG_LOCATION = +GENERATE_ECLIPSEHELP = NO +ECLIPSE_DOC_ID = org.doxygen.Project +DISABLE_INDEX = NO +GENERATE_TREEVIEW = YES +ENUM_VALUES_PER_LINE = 4 +TREEVIEW_WIDTH = 250 +EXT_LINKS_IN_WINDOW = NO +FORMULA_FONTSIZE = 10 +FORMULA_TRANSPARENT = YES +USE_MATHJAX = NO +MATHJAX_FORMAT = HTML-CSS +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest +MATHJAX_EXTENSIONS = +SEARCHENGINE = YES +SERVER_BASED_SEARCH = NO +EXTERNAL_SEARCH = NO +SEARCHENGINE_URL = +SEARCHDATA_FILE = searchdata.xml +EXTERNAL_SEARCH_ID = +EXTRA_SEARCH_MAPPINGS = +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = NO +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4wide +EXTRA_PACKAGES = +LATEX_HEADER = +LATEX_FOOTER = +PDF_HYPERLINKS = NO +USE_PDFLATEX = NO +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +LATEX_SOURCE_CODE = NO +LATEX_BIB_STYLE = plain +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_LINKS = NO +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = YES +XML_OUTPUT = xml +XML_SCHEMA = +XML_DTD = +XML_PROGRAMLISTING = YES +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = YES +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = Standard_EXPORT __Standard_API __Draw_API Handle(a):=Handle DEFINE_STANDARD_ALLOC DEFINE_NCOLLECTION_ALLOC DEFINE_STANDARD_HANDLE DEFINE_STANDARD_RTTI +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = NO +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +PERL_PATH = /usr/bin/perl +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = YES +MSCGEN_PATH = +HIDE_UNDOC_RELATIONS = NO +HAVE_DOT = YES +DOT_NUM_THREADS = 0 +DOT_FONTNAME = Arial +DOT_FONTSIZE = 10 +DOT_FONTPATH = +CLASS_GRAPH = YES +COLLABORATION_GRAPH = NO +GROUP_GRAPHS = NO +UML_LOOK = NO +UML_LIMIT_NUM_FIELDS = 10 +TEMPLATE_RELATIONS = YES +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = NO +CALL_GRAPH = NO +CALLER_GRAPH = NO +GRAPHICAL_HIERARCHY = NO +DIRECTORY_GRAPH = NO +DOT_IMAGE_FORMAT = png +INTERACTIVE_SVG = NO +DOT_PATH = +DOTFILE_DIRS = +MSCFILE_DIRS = +DOT_GRAPH_MAX_NODES = 50 +MAX_DOT_GRAPH_DEPTH = 0 +DOT_TRANSPARENT = NO +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = NO +DOT_CLEANUP = YES diff --git a/scripts/doxy2swig.py b/scripts/doxy2swig.py new file mode 100755 index 000000000..4179dcb13 --- /dev/null +++ b/scripts/doxy2swig.py @@ -0,0 +1,840 @@ +#!/usr/bin/env python +"""doxy2swig.py [options] index.xml output.i + +Doxygen XML to SWIG docstring converter (improved version). + +Converts Doxygen generated XML files into a file containing docstrings +for use by SWIG. + +index.xml is your doxygen generated XML file and output.i is where the +output will be written (the file will be clobbered). +""" +# +# The current version of this code is hosted on a github repository: +# https://github.com/m7thon/doxy2swig +# +# This code is implemented using Mark Pilgrim's code as a guideline: +# http://www.faqs.org/docs/diveintopython/kgp_divein.html +# +# Original Author: Prabhu Ramachandran +# Modified by: Michael Thon (June 2015) +# License: BSD style +# +# Thanks: +# Johan Hake: the include_function_definition feature +# Bill Spotz: bug reports and testing. +# Sebastian Henschel: Misc. enhancements. +# +# Changes: +# June 2015 (Michael Thon): +# - class documentation: +# -c: add constructor call signatures and a "Constructors" section +# collecting the respective docs (e.g. for python) +# -a: add "Attributes" section collecting the documentation for member +# variables (e.g. for python) +# - overloaded functions: +# -o: collect all documentation into one "Overloaded function" section +# - option to include function definition / signature renamed to -f +# - formatting: +# + included function signatures slightly reformatted +# + option (-t) to turn off/on type information for funciton signatures +# + lists (incl. nested and ordered) +# + attempt to produce docstrings that render nicely as markdown +# + translate code, emphasis, bold, linebreak, hruler, blockquote, +# verbatim, heading tags to markdown +# + new text-wrapping and option -w to specify the text width +# + +from xml.dom import minidom +import re +import textwrap +import sys +import os.path +import optparse + + +def my_open_read(source): + if hasattr(source, "read"): + return source + else: + try: + return open(source, encoding='utf-8') + except TypeError: + return open(source) + +def my_open_write(dest): + if hasattr(dest, "write"): + return dest + else: + try: + return open(dest, 'w', encoding='utf-8') + except TypeError: + return open(dest, 'w') + +# MARK: Text handling: +def shift(txt, indent = ' ', prepend = ''): + """Return a list corresponding to the lines of text in the `txt` list + indented by `indent`. Prepend instead the string given in `prepend` to the + beginning of the first line. Note that if len(prepend) > len(indent), then + `prepend` will be truncated (doing better is tricky!). This preserves a + special '' entry at the end of `txt` (see `do_para` for the meaning). + """ + if type(indent) is int: + indent = indent * ' ' + special_end = txt[-1:] == [''] + lines = ''.join(txt).splitlines(True) + for i in range(1,len(lines)): + if lines[i].strip() or indent.strip(): + lines[i] = indent + lines[i] + if not lines: + return prepend + prepend = prepend[:len(indent)] + indent = indent[len(prepend):] + lines[0] = prepend + indent + lines[0] + ret = [''.join(lines)] + if special_end: + ret.append('') + return ret + +class Doxy2SWIG: + """Converts Doxygen generated XML files into a file containing + docstrings that can be used by SWIG-1.3.x that have support for + feature("docstring"). Once the data is parsed it is stored in + self.pieces. + + """ + + def __init__(self, src, + with_function_signature = False, + with_type_info = False, + with_constructor_list = False, + with_attribute_list = False, + with_overloaded_functions = False, + textwidth = 80, + quiet = False): + """Initialize the instance given a source object. `src` can + be a file or filename. If you do not want to include function + definitions from doxygen then set + `include_function_definition` to `False`. This is handy since + this allows you to use the swig generated function definition + using %feature("autodoc", [0,1]). + + """ + # options: + self.with_function_signature = with_function_signature + self.with_type_info = with_type_info + self.with_constructor_list = with_constructor_list + self.with_attribute_list = with_attribute_list + self.with_overloaded_functions = with_overloaded_functions + self.textwidth = textwidth + self.quiet = quiet + + # state: + self.indent = 0 + self.listitem = '' + self.pieces = [] + + f = my_open_read(src) + self.my_dir = os.path.dirname(f.name) + self.xmldoc = minidom.parse(f).documentElement + f.close() + + self.pieces.append('\n// File: %s\n' % + os.path.basename(f.name)) + + self.space_re = re.compile(r'\s+') + self.lead_spc = re.compile(r'^(%feature\S+\s+\S+\s*?)"\s+(\S)') + self.multi = 0 + self.ignores = ['inheritancegraph', 'param', 'listofallmembers', + 'innerclass', 'name', 'declname', 'incdepgraph', + 'invincdepgraph', 'programlisting', 'type', + 'references', 'referencedby', 'location', + 'collaborationgraph', 'reimplements', + 'reimplementedby', 'derivedcompoundref', + 'basecompoundref', + 'argsstring', 'definition', 'exceptions'] + #self.generics = [] + + def generate(self): + """Parses the file set in the initialization. The resulting + data is stored in `self.pieces`. + + """ + self.parse(self.xmldoc) + + def write(self, fname): + o = my_open_write(fname) + o.write(''.join(self.pieces)) + o.write('\n') + o.close() + + def parse(self, node): + """Parse a given node. This function in turn calls the + `parse_` functions which handle the respective + nodes. + + """ + pm = getattr(self, "parse_%s" % node.__class__.__name__) + pm(node) + + def parse_Document(self, node): + self.parse(node.documentElement) + + def parse_Text(self, node): + txt = node.data + if txt == ' ': + # this can happen when two tags follow in a text, e.g., + # " ... $..." etc. + # here we want to keep the space. + self.add_text(txt) + return + txt = txt.replace('\\', r'\\') + txt = txt.replace('"', r'\"') + # ignore pure whitespace + m = self.space_re.match(txt) + if m and len(m.group()) == len(txt): + pass + else: + self.add_text(txt) + + def parse_Comment(self, node): + """Parse a `COMMENT_NODE`. This does nothing for now.""" + return + + def parse_Element(self, node): + """Parse an `ELEMENT_NODE`. This calls specific + `do_` handers for different elements. If no handler + is available the `subnode_parse` method is called. All + tagNames specified in `self.ignores` are simply ignored. + + """ + name = node.tagName + ignores = self.ignores + if name in ignores: + return + attr = "do_%s" % name + if hasattr(self, attr): + handlerMethod = getattr(self, attr) + handlerMethod(node) + else: + self.subnode_parse(node) + #if name not in self.generics: self.generics.append(name) + +# MARK: Special format parsing + def subnode_parse(self, node, pieces=None, indent=0, ignore=[], restrict=None): + """Parse the subnodes of a given node. Subnodes with tags in the + `ignore` list are ignored. If pieces is given, use this as target for + the parse results instead of self.pieces. Indent all lines by the amount + given in `indent`. Note that the initial content in `pieces` is not + indented. The final result is in any case added to self.pieces.""" + if pieces is not None: + old_pieces, self.pieces = self.pieces, pieces + else: + old_pieces = [] + if type(indent) is int: + indent = indent * ' ' + if len(indent) > 0: + pieces = ''.join(self.pieces) + i_piece = pieces[:len(indent)] + if self.pieces[-1:] == ['']: + self.pieces = [pieces[len(indent):]] + [''] + elif self.pieces != []: + self.pieces = [pieces[len(indent):]] + self.indent += len(indent) + for n in node.childNodes: + if restrict is not None: + if n.nodeType == n.ELEMENT_NODE and n.tagName in restrict: + self.parse(n) + elif n.nodeType != n.ELEMENT_NODE or n.tagName not in ignore: + self.parse(n) + if len(indent) > 0: + self.pieces = shift(self.pieces, indent, i_piece) + self.indent -= len(indent) + old_pieces.extend(self.pieces) + self.pieces = old_pieces + + def surround_parse(self, node, pre_char, post_char): + """Parse the subnodes of a given node. Subnodes with tags in the + `ignore` list are ignored. Prepend `pre_char` and append `post_char` to + the output in self.pieces.""" + self.add_text(pre_char) + self.subnode_parse(node) + self.add_text(post_char) + +# MARK: Helper functions + def get_specific_subnodes(self, node, name, recursive=0): + """Given a node and a name, return a list of child `ELEMENT_NODEs`, that + have a `tagName` matching the `name`. Search recursively for `recursive` + levels. + """ + children = [x for x in node.childNodes if x.nodeType == x.ELEMENT_NODE] + ret = [x for x in children if x.tagName == name] + if recursive > 0: + for x in children: + ret.extend(self.get_specific_subnodes(x, name, recursive-1)) + return ret + + def get_specific_nodes(self, node, names): + """Given a node and a sequence of strings in `names`, return a + dictionary containing the names as keys and child + `ELEMENT_NODEs`, that have a `tagName` equal to the name. + + """ + nodes = [(x.tagName, x) for x in node.childNodes + if x.nodeType == x.ELEMENT_NODE and + x.tagName in names] + return dict(nodes) + + def add_text(self, value): + """Adds text corresponding to `value` into `self.pieces`.""" + if isinstance(value, (list, tuple)): + self.pieces.extend(value) + else: + self.pieces.append(value) + + def start_new_paragraph(self): + """Make sure to create an empty line. This is overridden, if the previous + text ends with the special marker ''. In that case, nothing is done. + """ + if self.pieces[-1:] == ['']: # respect special marker + return + elif self.pieces == []: # first paragraph, add '\n', override with '' + self.pieces = ['\n'] + elif self.pieces[-1][-1:] != '\n': # previous line not ended + self.pieces.extend([' \n' ,'\n']) + else: #default + self.pieces.append('\n') + + def add_line_with_subsequent_indent(self, line, indent=4): + """Add line of text and wrap such that subsequent lines are indented + by `indent` spaces. + """ + if isinstance(line, (list, tuple)): + line = ''.join(line) + line = line.strip() + width = self.textwidth-self.indent-indent + wrapped_lines = textwrap.wrap(line[indent:], width=width) + for i in range(len(wrapped_lines)): + if wrapped_lines[i] != '': + wrapped_lines[i] = indent * ' ' + wrapped_lines[i] + self.pieces.append(line[:indent] + '\n'.join(wrapped_lines)[indent:] + ' \n') + + def extract_text(self, node): + """Return the string representation of the node or list of nodes by parsing the + subnodes, but returning the result as a string instead of adding it to `self.pieces`. + Note that this allows extracting text even if the node is in the ignore list. + """ + if not isinstance(node, (list, tuple)): + node = [node] + pieces, self.pieces = self.pieces, [''] + for n in node: + for sn in n.childNodes: + self.parse(sn) + ret = ''.join(self.pieces) + self.pieces = pieces + return ret + + def get_function_signature(self, node): + """Returns the function signature string for memberdef nodes.""" + name = self.extract_text(self.get_specific_subnodes(node, 'name')) + if self.with_type_info: + argsstring = self.extract_text(self.get_specific_subnodes(node, 'argsstring')) + else: + argsstring = [] + param_id = 1 + for n_param in self.get_specific_subnodes(node, 'param'): + declname = self.extract_text(self.get_specific_subnodes(n_param, 'declname')) + if not declname: + declname = 'arg' + str(param_id) + defval = self.extract_text(self.get_specific_subnodes(n_param, 'defval')) + if defval: + defval = '=' + defval + argsstring.append(declname + defval) + param_id = param_id + 1 + argsstring = '(' + ', '.join(argsstring) + ')' + type = self.extract_text(self.get_specific_subnodes(node, 'type')) + function_definition = name + argsstring + if type != '' and type != 'void': + function_definition = function_definition + ' -> ' + type + return '`' + function_definition + '` ' + +# MARK: Special parsing tasks (need to be called manually) + def make_constructor_list(self, constructor_nodes, classname): + """Produces the "Constructors" section and the constructor signatures + (since swig does not do so for classes) for class docstrings.""" + if constructor_nodes == []: + return + self.add_text(['\n', 'Constructors', + '\n', '------------']) + for n in constructor_nodes: + self.add_text('\n') + self.add_line_with_subsequent_indent('* ' + self.get_function_signature(n)) + self.subnode_parse(n, pieces = [], indent=4, ignore=['definition', 'name']) + + def make_attribute_list(self, node): + """Produces the "Attributes" section in class docstrings for public + member variables (attributes). + """ + atr_nodes = [] + for n in self.get_specific_subnodes(node, 'memberdef', recursive=2): + if n.attributes['kind'].value == 'variable' and n.attributes['prot'].value == 'public': + atr_nodes.append(n) + if not atr_nodes: + return + self.add_text(['\n', 'Attributes', + '\n', '----------']) + for n in atr_nodes: + name = self.extract_text(self.get_specific_subnodes(n, 'name')) + self.add_text(['\n* ', '`', name, '`', ' : ']) + self.add_text(['`', self.extract_text(self.get_specific_subnodes(n, 'type')), '`']) + self.add_text(' \n') + restrict = ['briefdescription', 'detaileddescription'] + self.subnode_parse(n, pieces=[''], indent=4, restrict=restrict) + + def get_memberdef_nodes_and_signatures(self, node, kind): + """Collects the memberdef nodes and corresponding signatures that + correspond to public function entries that are at most depth 2 deeper + than the current (compounddef) node. Returns a dictionary with + function signatures (what swig expects after the %feature directive) + as keys, and a list of corresponding memberdef nodes as values.""" + sig_dict = {} + sig_prefix = '' + if kind in ('file', 'namespace'): + ns_node = node.getElementsByTagName('innernamespace') + if not ns_node and kind == 'namespace': + ns_node = node.getElementsByTagName('compoundname') + if ns_node: + sig_prefix = self.extract_text(ns_node[0]) + '::' + elif kind in ('class', 'struct'): + # Get the full function name. + cn_node = node.getElementsByTagName('compoundname') + sig_prefix = self.extract_text(cn_node[0]) + '::' + + md_nodes = self.get_specific_subnodes(node, 'memberdef', recursive=2) + for n in md_nodes: + if n.attributes['prot'].value != 'public': + continue + if n.attributes['kind'].value in ['variable', 'typedef']: + continue + if not self.get_specific_subnodes(n, 'definition'): + continue + name = self.extract_text(self.get_specific_subnodes(n, 'name')) + if name[:8] == 'operator': + continue + sig = sig_prefix + name + if sig in sig_dict: + sig_dict[sig].append(n) + else: + sig_dict[sig] = [n] + return sig_dict + + def handle_typical_memberdefs_no_overload(self, signature, memberdef_nodes): + """Produce standard documentation for memberdef_nodes.""" + for n in memberdef_nodes: + self.add_text(['\n', '%feature("docstring") ', signature, ' "', '\n']) + if self.with_function_signature: + self.add_line_with_subsequent_indent(self.get_function_signature(n)) + self.subnode_parse(n, pieces=[], ignore=['definition', 'name']) + self.add_text(['";', '\n']) + + def handle_typical_memberdefs(self, signature, memberdef_nodes): + """Produces docstring entries containing an "Overloaded function" + section with the documentation for each overload, if the function is + overloaded and self.with_overloaded_functions is set. Else, produce + normal documentation. + """ + if len(memberdef_nodes) == 1 or not self.with_overloaded_functions: + self.handle_typical_memberdefs_no_overload(signature, memberdef_nodes) + return + + self.add_text(['\n', '%feature("docstring") ', signature, ' "', '\n']) + if self.with_function_signature: + for n in memberdef_nodes: + self.add_line_with_subsequent_indent(self.get_function_signature(n)) + self.add_text('\n') + self.add_text(['Overloaded function', '\n', + '-------------------']) + for n in memberdef_nodes: + self.add_text('\n') + self.add_line_with_subsequent_indent('* ' + self.get_function_signature(n)) + self.subnode_parse(n, pieces=[], indent=4, ignore=['definition', 'name']) + self.add_text(['";', '\n']) + + +# MARK: Tag handlers + def do_linebreak(self, node): + self.add_text(' ') + + def do_ndash(self, node): + self.add_text('--') + + def do_mdash(self, node): + self.add_text('---') + + def do_emphasis(self, node): + self.surround_parse(node, '*', '*') + + def do_bold(self, node): + self.surround_parse(node, '**', '**') + + def do_computeroutput(self, node): + self.surround_parse(node, '`', '`') + + def do_heading(self, node): + self.start_new_paragraph() + pieces, self.pieces = self.pieces, [''] + level = int(node.attributes['level'].value) + self.subnode_parse(node) + if level == 1: + self.pieces.insert(0, '\n') + self.add_text(['\n', len(''.join(self.pieces).strip()) * '=']) + elif level == 2: + self.add_text(['\n', len(''.join(self.pieces).strip()) * '-']) + elif level >= 3: + self.pieces.insert(0, level * '#' + ' ') + # make following text have no gap to the heading: + pieces.extend([''.join(self.pieces) + ' \n', '']) + self.pieces = pieces + + def do_verbatim(self, node): + self.start_new_paragraph() + self.subnode_parse(node, pieces=[''], indent=4) + + def do_blockquote(self, node): + self.start_new_paragraph() + self.subnode_parse(node, pieces=[''], indent='> ') + + def do_hruler(self, node): + self.start_new_paragraph() + self.add_text('* * * * * \n') + + def do_includes(self, node): + self.add_text('\nC++ includes: ') + self.subnode_parse(node) + self.add_text('\n') + +# MARK: Para tag handler + def do_para(self, node): + """This is the only place where text wrapping is automatically performed. + Generally, this function parses the node (locally), wraps the text, and + then adds the result to self.pieces. However, it may be convenient to + allow the previous content of self.pieces to be included in the text + wrapping. For this, use the following *convention*: + If self.pieces ends with '', treat the _previous_ entry as part of the + current paragraph. Else, insert new-line and start a new paragraph + and "wrapping context". + Paragraphs always end with ' \n', but if the parsed content ends with + the special symbol '', this is passed on. + """ + if self.pieces[-1:] == ['']: + pieces, self.pieces = self.pieces[:-2], self.pieces[-2:-1] + else: + self.add_text('\n') + pieces, self.pieces = self.pieces, [''] + self.subnode_parse(node) + dont_end_paragraph = self.pieces[-1:] == [''] + # Now do the text wrapping: + width = self.textwidth - self.indent + wrapped_para = [] + for line in ''.join(self.pieces).splitlines(): + keep_markdown_newline = line[-2:] == ' ' + w_line = textwrap.wrap(line, width=width, break_long_words=False) + if w_line == []: + w_line = [''] + if keep_markdown_newline: + w_line[-1] = w_line[-1] + ' ' + for wl in w_line: + wrapped_para.append(wl + '\n') + if wrapped_para: + if wrapped_para[-1][-3:] != ' \n': + wrapped_para[-1] = wrapped_para[-1][:-1] + ' \n' + if dont_end_paragraph: + wrapped_para.append('') + pieces.extend(wrapped_para) + self.pieces = pieces + +# MARK: List tag handlers + def do_itemizedlist(self, node): + if self.listitem == '': + self.start_new_paragraph() + elif self.pieces != [] and self.pieces[-1:] != ['']: + self.add_text('\n') + listitem = self.listitem + if self.listitem in ['*', '-']: + self.listitem = '-' + else: + self.listitem = '*' + self.subnode_parse(node) + self.listitem = listitem + + def do_orderedlist(self, node): + if self.listitem == '': + self.start_new_paragraph() + elif self.pieces != [] and self.pieces[-1:] != ['']: + self.add_text('\n') + listitem = self.listitem + self.listitem = 0 + self.subnode_parse(node) + self.listitem = listitem + + def do_listitem(self, node): + try: + self.listitem = int(self.listitem) + 1 + item = str(self.listitem) + '. ' + except: + item = str(self.listitem) + ' ' + self.subnode_parse(node, item, indent=4) + +# MARK: Parameter list tag handlers + def do_parameterlist(self, node): + self.start_new_paragraph() + text = 'unknown' + for key, val in node.attributes.items(): + if key == 'kind': + if val == 'param': + text = 'Parameters' + elif val == 'exception': + text = 'Exceptions' + elif val == 'retval': + text = 'Returns' + else: + text = val + break + if self.indent == 0: + self.add_text([text, '\n', len(text) * '-', '\n']) + else: + self.add_text([text, ': \n']) + self.subnode_parse(node) + + def do_parameteritem(self, node): + self.subnode_parse(node, pieces=['* ', '']) + + def do_parameternamelist(self, node): + self.subnode_parse(node) + self.add_text([' :', ' \n']) + + def do_parametername(self, node): + if self.pieces != [] and self.pieces != ['* ', '']: + self.add_text(', ') + data = self.extract_text(node) + self.add_text(['`', data, '`']) + + def do_parameterdescription(self, node): + self.subnode_parse(node, pieces=[''], indent=4) + +# MARK: Section tag handler + def do_simplesect(self, node): + kind = node.attributes['kind'].value + if kind in ('date', 'rcs', 'version'): + return + self.start_new_paragraph() + if kind == 'warning': + self.subnode_parse(node, pieces=['**Warning**: ',''], indent=4) + elif kind == 'see': + self.subnode_parse(node, pieces=['See also: ',''], indent=4) + elif kind == 'return': + if self.indent == 0: + pieces = ['Returns', '\n', len('Returns') * '-', '\n', ''] + else: + pieces = ['Returns:', '\n', ''] + self.subnode_parse(node, pieces=pieces) + else: + self.subnode_parse(node, pieces=[kind + ': ',''], indent=4) + +# MARK: %feature("docstring") producing tag handlers + def do_compounddef(self, node): + """This produces %feature("docstring") entries for classes, and handles + class, namespace and file memberdef entries specially to allow for + overloaded functions. For other cases, passes parsing on to standard + handlers (which may produce unexpected results). + """ + kind = node.attributes['kind'].value + if kind in ('class', 'struct'): + prot = node.attributes['prot'].value + if prot != 'public': + return + self.add_text('\n\n') + classdefn = self.extract_text(self.get_specific_subnodes(node, 'compoundname')) + classname = classdefn.split('::')[-1] + self.add_text('%%feature("docstring") %s "\n' % classdefn) + + if self.with_constructor_list: + constructor_nodes = [] + for n in self.get_specific_subnodes(node, 'memberdef', recursive=2): + if n.attributes['prot'].value == 'public': + if self.extract_text(self.get_specific_subnodes(n, 'definition')) == classdefn + '::' + classname: + constructor_nodes.append(n) + for n in constructor_nodes: + self.add_line_with_subsequent_indent(self.get_function_signature(n)) + + names = ('briefdescription','detaileddescription') + sub_dict = self.get_specific_nodes(node, names) + for n in ('briefdescription','detaileddescription'): + if n in sub_dict: + self.parse(sub_dict[n]) + if self.with_constructor_list: + self.make_constructor_list(constructor_nodes, classname) + if self.with_attribute_list: + self.make_attribute_list(node) + + sub_list = self.get_specific_subnodes(node, 'includes') + if sub_list: + self.parse(sub_list[0]) + self.add_text(['";', '\n']) + + names = ['compoundname', 'briefdescription','detaileddescription', 'includes'] + self.subnode_parse(node, ignore = names) + + elif kind in ('file', 'namespace'): + nodes = node.getElementsByTagName('sectiondef') + for n in nodes: + self.parse(n) + + # now explicitely handle possibly overloaded member functions. + if kind in ['class', 'struct','file', 'namespace']: + md_nodes = self.get_memberdef_nodes_and_signatures(node, kind) + for sig in md_nodes: + self.handle_typical_memberdefs(sig, md_nodes[sig]) + + def do_memberdef(self, node): + """Handle cases outside of class, struct, file or namespace. These are + now dealt with by `handle_overloaded_memberfunction`. + Do these even exist??? + """ + prot = node.attributes['prot'].value + id = node.attributes['id'].value + kind = node.attributes['kind'].value + tmp = node.parentNode.parentNode.parentNode + compdef = tmp.getElementsByTagName('compounddef')[0] + cdef_kind = compdef.attributes['kind'].value + if cdef_kind in ('file', 'namespace', 'class', 'struct'): + # These cases are now handled by `handle_typical_memberdefs` + return + if prot != 'public': + return + first = self.get_specific_nodes(node, ('definition', 'name')) + name = self.extract_text(first['name']) + if name[:8] == 'operator': # Don't handle operators yet. + return + if not 'definition' in first or kind in ['variable', 'typedef']: + return + + data = self.extract_text(first['definition']) + self.add_text('\n') + self.add_text(['/* where did this entry come from??? */', '\n']) + self.add_text('%feature("docstring") %s "\n%s' % (data, data)) + + for n in node.childNodes: + if n not in first.values(): + self.parse(n) + self.add_text(['";', '\n']) + +# MARK: Entry tag handlers (dont print anything meaningful) + def do_sectiondef(self, node): + kind = node.attributes['kind'].value + if kind in ('public-func', 'func', 'user-defined', ''): + self.subnode_parse(node) + + def do_header(self, node): + """For a user defined section def a header field is present + which should not be printed as such, so we comment it in the + output.""" + data = self.extract_text(node) + self.add_text('\n/*\n %s \n*/\n' % data) + # If our immediate sibling is a 'description' node then we + # should comment that out also and remove it from the parent + # node's children. + parent = node.parentNode + idx = parent.childNodes.index(node) + if len(parent.childNodes) >= idx + 2: + nd = parent.childNodes[idx + 2] + if nd.nodeName == 'description': + nd = parent.removeChild(nd) + self.add_text('\n/*') + self.subnode_parse(nd) + self.add_text('\n*/\n') + + def do_member(self, node): + kind = node.attributes['kind'].value + refid = node.attributes['refid'].value + if kind == 'function' and refid[:9] == 'namespace': + self.subnode_parse(node) + + def do_doxygenindex(self, node): + self.multi = 1 + comps = node.getElementsByTagName('compound') + for c in comps: + refid = c.attributes['refid'].value + fname = refid + '.xml' + if not os.path.exists(fname): + fname = os.path.join(self.my_dir, fname) + if not self.quiet: + print("parsing file: %s" % fname) + p = Doxy2SWIG(fname, + with_function_signature = self.with_function_signature, + with_type_info = self.with_type_info, + with_constructor_list = self.with_constructor_list, + with_attribute_list = self.with_attribute_list, + with_overloaded_functions = self.with_overloaded_functions, + textwidth = self.textwidth, + quiet = self.quiet) + p.generate() + self.pieces.extend(p.pieces) + +# MARK: main +def main(): + usage = __doc__ + parser = optparse.OptionParser(usage) + parser.add_option("-f", '--function-signature', + action='store_true', + default=False, + dest='f', + help='include function signature in the documentation. This is handy when not using swig auto-generated function definitions %feature("autodoc", [0,1])') + parser.add_option("-t", '--type-info', + action='store_true', + default=False, + dest='t', + help='include type information for arguments in function signatures. This is similar to swig autodoc level 1') + parser.add_option("-c", '--constructor-list', + action='store_true', + default=False, + dest='c', + help='generate a constructor list for class documentation. Useful for target languages where the object construction should be documented in the class documentation.') + parser.add_option("-a", '--attribute-list', + action='store_true', + default=False, + dest='a', + help='generate an attributes list for class documentation. Useful for target languages where class attributes should be documented in the class documentation.') + parser.add_option("-o", '--overloaded-functions', + action='store_true', + default=False, + dest='o', + help='collect all documentation for overloaded functions. Useful for target languages that have no concept of overloaded functions, but also to avoid having to attach the correct docstring to each function overload manually') + parser.add_option("-w", '--width', type="int", + action='store', + dest='w', + default=80, + help='textwidth for wrapping (default: 80). Note that the generated lines may include 2 additional spaces (for markdown).') + parser.add_option("-q", '--quiet', + action='store_true', + default=False, + dest='q', + help='be quiet and minimize output') + + options, args = parser.parse_args() + if len(args) != 2: + parser.error("no input and output specified") + + p = Doxy2SWIG(args[0], + with_function_signature = options.f, + with_type_info = options.t, + with_constructor_list = options.c, + with_attribute_list = options.a, + with_overloaded_functions = options.o, + textwidth = options.w, + quiet = options.q) + p.generate() + p.write(args[1]) + +if __name__ == '__main__': + main() diff --git a/src/ConstructionAPI/CMakeLists.txt b/src/ConstructionAPI/CMakeLists.txt index ae9364b45..ec44137b4 100644 --- a/src/ConstructionAPI/CMakeLists.txt +++ b/src/ConstructionAPI/CMakeLists.txt @@ -34,7 +34,7 @@ INCLUDE(PythonAPI) SET_SOURCE_FILES_PROPERTIES(ConstructionAPI.i PROPERTIES CPLUSPLUS ON) SET_SOURCE_FILES_PROPERTIES(ConstructionAPI.i PROPERTIES SWIG_DEFINITIONS "-shadow") -#TODO(spo): is ModelAPI necessary or it could be received by INTERFACE_ ? +#TODO(spo): is ModelAPI necessary or it could be received by INTERFACE_ (may require modern CMake)? SET(SWIG_LINK_LIBRARIES ConstructionAPI ModelHighAPI @@ -44,6 +44,7 @@ SET(SWIG_LINK_LIBRARIES SET(SWIG_MODULE_ConstructionAPI_EXTRA_DEPS ${SWIG_MODULE_ConstructionAPI_EXTRA_DEPS} ${PROJECT_SOURCE_DIR}/src/ModelHighAPI/ModelHighAPI.i + doxyhelp.i ${PROJECT_HEADERS} ) diff --git a/src/ConstructionAPI/ConstructionAPI.i b/src/ConstructionAPI/ConstructionAPI.i index 8ac763a3c..0ba605b9c 100644 --- a/src/ConstructionAPI/ConstructionAPI.i +++ b/src/ConstructionAPI/ConstructionAPI.i @@ -6,6 +6,8 @@ #include "ConstructionAPI_swig.h" %} +%include "doxyhelp.i" + // import other modules %import "ModelHighAPI.i" diff --git a/src/ConstructionAPI/ConstructionAPI_Point.cpp b/src/ConstructionAPI/ConstructionAPI_Point.cpp index 41349f9da..7464536c4 100644 --- a/src/ConstructionAPI/ConstructionAPI_Point.cpp +++ b/src/ConstructionAPI/ConstructionAPI_Point.cpp @@ -89,7 +89,7 @@ std::shared_ptr ConstructionAPI_Point::z() const //-------------------------------------------------------------------------------------- // TODO(spo): make add* as static functions of the class PointPtr addPoint( - std::shared_ptr thePart, + const std::shared_ptr & thePart, const ModelHighAPI_Double& theX, const ModelHighAPI_Double& theY, const ModelHighAPI_Double& theZ) diff --git a/src/ConstructionAPI/ConstructionAPI_Point.h b/src/ConstructionAPI/ConstructionAPI_Point.h index 56d3a5716..4df2ded97 100644 --- a/src/ConstructionAPI/ConstructionAPI_Point.h +++ b/src/ConstructionAPI/ConstructionAPI_Point.h @@ -23,7 +23,7 @@ class ModelHighAPI_Double; class ConstructionAPI_Point : public ModelHighAPI_Interface { public: - /// Constructor + /// Constructor without values explicit ConstructionAPI_Point(const std::shared_ptr & theFeature); /// Constructor with values ConstructionAPI_Point(const std::shared_ptr & theFeature, @@ -38,11 +38,11 @@ public: const ModelHighAPI_Double & theY, const ModelHighAPI_Double & theZ); - /// X coordinate for the point + /// X attribute std::shared_ptr x() const; - /// Y coordinate for the point + /// Y attribute std::shared_ptr y() const; - /// Z coordinate for the point + /// Z attribute std::shared_ptr z() const; protected: @@ -57,7 +57,7 @@ typedef std::shared_ptr PointPtr; /**\ingroup CPPHighAPI * \brief Create Point feature */ -PointPtr addPoint(std::shared_ptr thePart, +PointPtr addPoint(const std::shared_ptr & thePart, const ModelHighAPI_Double & theX, const ModelHighAPI_Double & theY, const ModelHighAPI_Double & theZ); diff --git a/src/ModelHighAPI/CMakeLists.txt b/src/ModelHighAPI/CMakeLists.txt index 9ae3be6ef..7ae927be6 100644 --- a/src/ModelHighAPI/CMakeLists.txt +++ b/src/ModelHighAPI/CMakeLists.txt @@ -42,6 +42,11 @@ INCLUDE_DIRECTORIES( ${PROJECT_SOURCE_DIR}/src/ModelAPI ) +set(SWIG_MODULE_ModelHighAPI_EXTRA_DEPS + doxyhelp.i + ${PROJECT_HEADERS} +) + SWIG_ADD_MODULE(ModelHighAPI python ModelHighAPI.i ${PROJECT_HEADERS}) SWIG_LINK_LIBRARIES(ModelHighAPI ${SWIG_LINK_LIBRARIES}) diff --git a/src/ModelHighAPI/ModelHighAPI.i b/src/ModelHighAPI/ModelHighAPI.i index 5c6369cb5..754aa7707 100644 --- a/src/ModelHighAPI/ModelHighAPI.i +++ b/src/ModelHighAPI/ModelHighAPI.i @@ -1,9 +1,12 @@ /* ModelHighAPI.i */ %module ModelHighAPI + %{ #include "ModelHighAPI_swig.h" %} +%include "doxyhelp.i" + // to avoid error on this #define ModelHighAPI_EXPORT