The branch, master has been updated
via a67c3be47d1af396f4ad1eb7e770fb3be4d58334 (commit)
via f9e62be7d02503877cc042833ae24ce1093bf38b (commit)
via 148bdc830ce6fb63c8f202db392367e1f3a38582 (commit)
from ccbd6cf1934056386772debba8583bc9b3721072 (commit)
- Log -----------------------------------------------------------------
commit a67c3be47d1af396f4ad1eb7e770fb3be4d58334
Author: Dan White <dan@xxxxxxxxxxxxxx>
Date: Wed Oct 31 21:46:53 2012 -0500
Tweak doxy2swig.py output to include Returns header
Underlines are proportional to the title now also.
commit f9e62be7d02503877cc042833ae24ce1093bf38b
Author: Dan White <dan@xxxxxxxxxxxxxx>
Date: Sat Oct 27 00:12:37 2012 -0500
Extract doxygen from ftdi.c to python docstrings
Uses the doxy2swig.py script to process the doxygen-generated
doc/xml/ftdi_8c.xml file into a SWIG file ftdi1_doc.i.
This allows the documentation to show in a python docstring viewer from
something like ipython.
commit 148bdc830ce6fb63c8f202db392367e1f3a38582
Author: Dan White <dan@xxxxxxxxxxxxxx>
Date: Sat Oct 27 00:11:10 2012 -0500
Enable separate doxygen XML output
The xml is to be post-processed for other purposes, e.g. generating python
docstrings via a script.
-----------------------------------------------------------------------
Summary of changes:
.gitignore | 2 +
CMakeLists.txt | 17 +-
bindings/CMakeLists.txt | 25 +++
bindings/doxy2swig.py | 452 +++++++++++++++++++++++++++++++++++++++++++++++
bindings/ftdi1.i | 3 +
doc/Doxyfile.xml.in | 26 +++
6 files changed, 517 insertions(+), 8 deletions(-)
create mode 100644 bindings/doxy2swig.py
create mode 100644 doc/Doxyfile.xml.in
diff --git a/.gitignore b/.gitignore
index 2559aa2..d05881d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,9 +16,11 @@ build/
# Doxygen documentation
Doxyfile
+Doxyfile.xml
doc/Doxyfile
doc/html
doc/man
+doc/xml
# examples
examples/baud_test
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ae68f1b..4e14961 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -98,14 +98,6 @@ if(${UNIX})
set(CPACK_SET_DESTDIR "ON")
endif(${UNIX})
-add_subdirectory(src)
-add_subdirectory(ftdipp)
-add_subdirectory(bindings)
-add_subdirectory(ftdi_eeprom)
-add_subdirectory(examples)
-add_subdirectory(packages)
-add_subdirectory(test)
-
# "make dist" target
set(ARCHIVE_NAME ${CMAKE_PROJECT_NAME}-${VERSION_STRING})
add_custom_target(dist
@@ -128,6 +120,7 @@ if(DOCUMENTATION AND DOXYGEN_FOUND)
# Copy doxy.config.in
set(top_srcdir ${CMAKE_SOURCE_DIR})
configure_file(${CMAKE_SOURCE_DIR}/doc/Doxyfile.in
${CMAKE_BINARY_DIR}/Doxyfile )
+ configure_file(${CMAKE_SOURCE_DIR}/doc/Doxyfile.xml.in
${CMAKE_BINARY_DIR}/Doxyfile.xml )
# Run doxygen
add_custom_command(
@@ -144,6 +137,14 @@ else(DOCUMENTATION AND DOXYGEN_FOUND)
message(STATUS "Not generating API documentation")
endif(DOCUMENTATION AND DOXYGEN_FOUND)
+add_subdirectory(src)
+add_subdirectory(ftdipp)
+add_subdirectory(bindings)
+add_subdirectory(ftdi_eeprom)
+add_subdirectory(examples)
+add_subdirectory(packages)
+add_subdirectory(test)
+
# PkgConfig
set(prefix ${CMAKE_INSTALL_PREFIX})
set(exec_prefix ${CMAKE_INSTALL_PREFIX}/bin)
diff --git a/bindings/CMakeLists.txt b/bindings/CMakeLists.txt
index acf4d11..8f14c1a 100644
--- a/bindings/CMakeLists.txt
+++ b/bindings/CMakeLists.txt
@@ -10,6 +10,31 @@ if(PYTHON_BINDINGS AND SWIG_FOUND AND PYTHONLIBS_FOUND AND
PYTHONINTERP_FOUND)
INCLUDE_DIRECTORIES(${PYTHON_INCLUDE_PATH})
LINK_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}/../src)
+ if(DOCUMENTATION AND DOXYGEN_FOUND)
+ set(SWIG_MODULE_ftdi1_EXTRA_DEPS
${CMAKE_CURRENT_BINARY_DIR}/ftdi1_doc.i)
+ set(CMAKE_SWIG_FLAGS -DDOXYGEN=${DOXYGEN_FOUND})
+
+ # Run doxygen to only generate the xml
+ add_custom_command(
+ OUTPUT ${CMAKE_BINARY_DIR}/doc/xml/ftdi_8c.xml
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/doc
+ COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_BINARY_DIR}/Doxyfile.xml
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+ DEPENDS ${c_headers};${c_sources};${cpp_sources};${cpp_headers}
+ )
+ add_custom_target(doc_xml ALL DEPENDS
${CMAKE_BINARY_DIR}/doc/xml/ftdi_8c.xml)
+
+ # generate .i from doxygen .xml
+ add_custom_command(
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/ftdi1_doc.i
+ COMMAND ${PYTHON_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/doxy2swig.py -n
+ ${CMAKE_BINARY_DIR}/doc/xml/ftdi_8c.xml
+ ${CMAKE_CURRENT_BINARY_DIR}/ftdi1_doc.i
+ DEPENDS doc_xml
+ )
+
+ endif(DOCUMENTATION AND DOXYGEN_FOUND)
+
swig_add_module ( ftdi1 python ftdi1.i )
swig_link_libraries ( ftdi1 ftdi1 )
diff --git a/bindings/doxy2swig.py b/bindings/doxy2swig.py
new file mode 100644
index 0000000..c6c972c
--- /dev/null
+++ b/bindings/doxy2swig.py
@@ -0,0 +1,452 @@
+#!/usr/bin/env python
+"""Doxygen XML to SWIG docstring converter.
+
+Usage:
+
+ doxy2swig.py [options] input.xml output.i
+
+Converts Doxygen generated XML files into a file containing docstrings
+that can be used by SWIG-1.3.x. Note that you need to get SWIG
+version > 1.3.23 or use Robin Dunn's docstring patch to be able to use
+the resulting output.
+
+input.xml is your doxygen generated XML file and output.i is where the
+output will be written (the file will be clobbered).
+
+"""
+######################################################################
+#
+# This code is implemented using Mark Pilgrim's code as a guideline:
+# http://www.faqs.org/docs/diveintopython/kgp_divein.html
+#
+# Author: Prabhu Ramachandran
+# License: BSD style
+#
+# Thanks:
+# Johan Hake: the include_function_definition feature
+# Bill Spotz: bug reports and testing.
+# Sebastian Henschel: Misc. enhancements.
+#
+######################################################################
+
+from xml.dom import minidom
+import re
+import textwrap
+import sys
+import types
+import os.path
+import optparse
+
+
+def my_open_read(source):
+ if hasattr(source, "read"):
+ return source
+ else:
+ return open(source)
+
+def my_open_write(dest):
+ if hasattr(dest, "write"):
+ return dest
+ else:
+ return open(dest, 'w')
+
+
+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, include_function_definition=True, 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]).
+
+ """
+ f = my_open_read(src)
+ self.my_dir = os.path.dirname(f.name)
+ self.xmldoc = minidom.parse(f).documentElement
+ f.close()
+
+ self.pieces = []
+ 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']
+ #self.generics = []
+ self.include_function_definition = include_function_definition
+ if not include_function_definition:
+ self.ignores.append('argsstring')
+
+ self.quiet = quiet
+
+
+ def generate(self):
+ """Parses the file set in the initialization. The resulting
+ data is stored in `self.pieces`.
+
+ """
+ self.parse(self.xmldoc)
+
+ def parse(self, node):
+ """Parse a given node. This function in turn calls the
+ `parse_<nodeType>` 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
+ 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(textwrap.fill(txt, break_long_words=False))
+
+ def parse_Element(self, node):
+ """Parse an `ELEMENT_NODE`. This calls specific
+ `do_<tagName>` handers for different elements. If no handler
+ is available the `generic_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.generic_parse(node)
+ #if name not in self.generics: self.generics.append(name)
+
+ def parse_Comment(self, node):
+ """Parse a `COMMENT_NODE`. This does nothing for now."""
+ return
+
+ def add_text(self, value):
+ """Adds text corresponding to `value` into `self.pieces`."""
+ if type(value) in (types.ListType, types.TupleType):
+ self.pieces.extend(value)
+ else:
+ self.pieces.append(value)
+
+ 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 generic_parse(self, node, pad=0):
+ """A Generic parser for arbitrary tags in a node.
+
+ Parameters:
+
+ - node: A node in the DOM.
+ - pad: `int` (default: 0)
+
+ If 0 the node data is not padded with newlines. If 1 it
+ appends a newline after parsing the childNodes. If 2 it
+ pads before and after the nodes are processed. Defaults to
+ 0.
+
+ """
+ npiece = 0
+ if pad:
+ npiece = len(self.pieces)
+ if pad == 2:
+ self.add_text('\n')
+ for n in node.childNodes:
+ self.parse(n)
+ if pad:
+ if len(self.pieces) > npiece:
+ self.add_text('\n')
+
+ def space_parse(self, node):
+ self.add_text(' ')
+ self.generic_parse(node)
+
+ do_ref = space_parse
+ do_emphasis = space_parse
+ do_bold = space_parse
+ do_computeroutput = space_parse
+ do_formula = space_parse
+
+ def do_compoundname(self, node):
+ self.add_text('\n\n')
+ data = node.firstChild.data
+ self.add_text('%%feature("docstring") %s "\n'%data)
+
+ def do_compounddef(self, node):
+ kind = node.attributes['kind'].value
+ if kind in ('class', 'struct'):
+ prot = node.attributes['prot'].value
+ if prot <> 'public':
+ return
+ names = ('compoundname', 'briefdescription',
+ 'detaileddescription', 'includes')
+ first = self.get_specific_nodes(node, names)
+ for n in names:
+ if first.has_key(n):
+ self.parse(first[n])
+ self.add_text(['";','\n'])
+ for n in node.childNodes:
+ if n not in first.values():
+ self.parse(n)
+ elif kind in ('file', 'namespace'):
+ nodes = node.getElementsByTagName('sectiondef')
+ for n in nodes:
+ self.parse(n)
+
+ def do_includes(self, node):
+ self.add_text('C++ includes: ')
+ self.generic_parse(node, pad=1)
+
+ def do_parameterlist(self, node):
+ 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
+ self.add_text(['\n', '\n', text, ':', '\n'])
+ self.generic_parse(node, pad=1)
+
+ def do_para(self, node):
+ self.add_text('\n')
+ self.generic_parse(node, pad=1)
+
+ def do_parametername(self, node):
+ self.add_text('\n')
+ try:
+ data=node.firstChild.data
+ except AttributeError: # perhaps a <ref> tag in it
+ data=node.firstChild.firstChild.data
+ if data.find('Exception') != -1:
+ self.add_text(data)
+ else:
+ self.add_text("%s: "%data)
+
+ def do_parameterdefinition(self, node):
+ self.generic_parse(node, pad=1)
+
+ def do_detaileddescription(self, node):
+ self.generic_parse(node, pad=1)
+
+ def do_briefdescription(self, node):
+ self.generic_parse(node, pad=1)
+
+ def do_memberdef(self, node):
+ 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 prot == 'public':
+ first = self.get_specific_nodes(node, ('definition', 'name'))
+ name = first['name'].firstChild.data
+ if name[:8] == 'operator': # Don't handle operators yet.
+ return
+
+ if not first.has_key('definition') or \
+ kind in ['variable', 'typedef']:
+ return
+
+ if self.include_function_definition:
+ defn = first['definition'].firstChild.data
+ else:
+ defn = ""
+ self.add_text('\n')
+ self.add_text('%feature("docstring") ')
+
+ anc = node.parentNode.parentNode
+ if cdef_kind in ('file', 'namespace'):
+ ns_node = anc.getElementsByTagName('innernamespace')
+ if not ns_node and cdef_kind == 'namespace':
+ ns_node = anc.getElementsByTagName('compoundname')
+ if ns_node:
+ ns = ns_node[0].firstChild.data
+ self.add_text(' %s::%s "\n%s'%(ns, name, defn))
+ else:
+ self.add_text(' %s "\n%s'%(name, defn))
+ elif cdef_kind in ('class', 'struct'):
+ # Get the full function name.
+ anc_node = anc.getElementsByTagName('compoundname')
+ cname = anc_node[0].firstChild.data
+ self.add_text(' %s::%s "\n%s'%(cname, name, defn))
+
+ for n in node.childNodes:
+ if n not in first.values():
+ self.parse(n)
+ self.add_text(['";', '\n'])
+
+ def do_definition(self, node):
+ data = node.firstChild.data
+ self.add_text('%s "\n%s'%(data, data))
+
+ def do_sectiondef(self, node):
+ kind = node.attributes['kind'].value
+ if kind in ('public-func', 'func', 'user-defined', ''):
+ self.generic_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 = node.firstChild.data
+ 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.generic_parse(nd)
+ self.add_text('\n*/\n')
+
+ def do_simplesect(self, node):
+ kind = node.attributes['kind'].value
+ if kind in ('date', 'rcs', 'version'):
+ pass
+ elif kind == 'warning':
+ self.add_text(['\n', 'WARNING: '])
+ self.generic_parse(node)
+ elif kind == 'see':
+ self.add_text('\n')
+ self.add_text('See: ')
+ self.generic_parse(node)
+ else:
+ self.generic_parse(node)
+
+ def do_argsstring(self, node):
+ self.generic_parse(node, pad=1)
+
+ def do_member(self, node):
+ kind = node.attributes['kind'].value
+ refid = node.attributes['refid'].value
+ if kind == 'function' and refid[:9] == 'namespace':
+ self.generic_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, self.include_function_definition, self.quiet)
+ p.generate()
+ self.pieces.extend(self.clean_pieces(p.pieces))
+
+ def write(self, fname):
+ o = my_open_write(fname)
+ if self.multi:
+ o.write("".join(self.pieces))
+ else:
+ o.write("".join(self.clean_pieces(self.pieces)))
+ o.close()
+
+ def clean_pieces(self, pieces):
+ """Cleans the list of strings given as `pieces`. It replaces
+ multiple newlines by a maximum of 2 and returns a new list.
+ It also wraps the paragraphs nicely.
+
+ """
+ ret = []
+ count = 0
+ for i in pieces:
+ if i == '\n':
+ count = count + 1
+ else:
+ if i == '";':
+ if count:
+ ret.append('\n')
+ elif count > 2:
+ ret.append('\n\n')
+ elif count:
+ ret.append('\n'*count)
+ count = 0
+ ret.append(i)
+
+ _data = "".join(ret)
+ ret = []
+ for i in _data.split('\n\n'):
+ if i == 'Parameters:' or i == 'Exceptions:' or i == 'Returns:':
+ ret.extend([i, '\n'+'-'*len(i), '\n\n'])
+ elif i.find('// File:') > -1: # leave comments alone.
+ ret.extend([i, '\n'])
+ else:
+ _tmp = textwrap.fill(i.strip(), break_long_words=False)
+ _tmp = self.lead_spc.sub(r'\1"\2', _tmp)
+ ret.extend([_tmp, '\n\n'])
+ return ret
+
+
+def convert(input, output, include_function_definition=True, quiet=False):
+ p = Doxy2SWIG(input, include_function_definition, quiet)
+ p.generate()
+ p.write(output)
+
+def main():
+ usage = __doc__
+ parser = optparse.OptionParser(usage)
+ parser.add_option("-n", '--no-function-definition',
+ action='store_true',
+ default=False,
+ dest='func_def',
+ help='do not include doxygen function definitions')
+ parser.add_option("-q", '--quiet',
+ action='store_true',
+ default=False,
+ dest='quiet',
+ help='be quiet and minimize output')
+
+ options, args = parser.parse_args()
+ if len(args) != 2:
+ parser.error("error: no input and output specified")
+
+ convert(args[0], args[1], not options.func_def, options.quiet)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/bindings/ftdi1.i b/bindings/ftdi1.i
index d6fc05c..f132162 100644
--- a/bindings/ftdi1.i
+++ b/bindings/ftdi1.i
@@ -2,6 +2,9 @@
%module(docstring="Python interface to libftdi1") ftdi1
%feature("autodoc","1");
+#ifdef DOXYGEN
+%include "ftdi1_doc.i"
+#endif
%{
#include "Python.h"
diff --git a/doc/Doxyfile.xml.in b/doc/Doxyfile.xml.in
new file mode 100644
index 0000000..8a32509
--- /dev/null
+++ b/doc/Doxyfile.xml.in
@@ -0,0 +1,26 @@
+# Doxyfile 1.7.4
+
+# xml generation only
+# keep settings but shut off all other generation
+@INCLUDE = Doxyfile
+
+GENERATE_TODOLIST = NO
+GENERATE_TESTLIST = NO
+GENERATE_BUGLIST = NO
+GENERATE_DEPRECATEDLIST= NO
+GENERATE_HTML = NO
+GENERATE_DOCSET = NO
+GENERATE_HTMLHELP = NO
+GENERATE_CHI = NO
+GENERATE_QHP = NO
+GENERATE_ECLIPSEHELP = NO
+GENERATE_TREEVIEW = NO
+GENERATE_LATEX = NO
+GENERATE_RTF = NO
+GENERATE_MAN = NO
+GENERATE_AUTOGEN_DEF = NO
+GENERATE_PERLMOD = NO
+GENERATE_TAGFILE =
+GENERATE_LEGEND = NO
+
+GENERATE_XML = YES
hooks/post-receive
--
A library to talk to FTDI chips
--
libftdi-git - see http://www.intra2net.com/en/developer/libftdi for details.
To unsubscribe send a mail to libftdi-git+unsubscribe@xxxxxxxxxxxxxxxxxxxxxxx
|