Просмотр исходного кода

doxygen: fix doxygen generation and inital pydoc generation

Signed-off-by: Brendan Le Foll <brendan.le.foll@intel.com>
Brendan Le Foll 10 лет назад
Родитель
Сommit
edf88ef6a7
4 измененных файлов: 496 добавлений и 20 удалений
  1. 4
    3
      CMakeLists.txt
  2. 457
    0
      src/doxy2swig.py
  3. 28
    15
      src/hmc5883l/CMakeLists.txt
  4. 7
    2
      src/hmc5883l/pyupm_hmc5883l.i

+ 4
- 3
CMakeLists.txt Просмотреть файл

@@ -1,9 +1,10 @@
1 1
 cmake_minimum_required (VERSION 2.8)
2 2
 project (upm)
3 3
 
4
-set (SWIG_EXECUTABLE /usr/bin/swig)
5
-find_package (SWIG REQUIRED)
6
-include (${SWIG_USE_FILE})
4
+find_package (SWIG)
5
+if (SWIG_FOUND)
6
+  include (${SWIG_USE_FILE})
7
+endif ()
7 8
 
8 9
 find_package (PkgConfig REQUIRED)
9 10
 pkg_check_modules (MAA maa>=0.2.1)

+ 457
- 0
src/doxy2swig.py Просмотреть файл

@@ -0,0 +1,457 @@
1
+#!/usr/bin/env python
2
+"""Doxygen XML to SWIG docstring converter.
3
+
4
+Usage:
5
+
6
+  doxy2swig.py [options] input.xml output.i
7
+
8
+Converts Doxygen generated XML files into a file containing docstrings
9
+that can be used by SWIG-1.3.x.  Note that you need to get SWIG
10
+version > 1.3.23 or use Robin Dunn's docstring patch to be able to use
11
+the resulting output.
12
+
13
+input.xml is your doxygen generated XML file and output.i is where the
14
+output will be written (the file will be clobbered).
15
+
16
+"""
17
+#
18
+#
19
+# This code is implemented using Mark Pilgrim's code as a guideline:
20
+#   http://www.faqs.org/docs/diveintopython/kgp_divein.html
21
+#
22
+# Author: Prabhu Ramachandran
23
+# License: BSD style
24
+#
25
+# Thanks:
26
+#   Johan Hake:  the include_function_definition feature
27
+#   Bill Spotz:  bug reports and testing.
28
+#   Sebastian Henschel:   Misc. enhancements.
29
+#
30
+#
31
+
32
+from xml.dom import minidom
33
+import re
34
+import textwrap
35
+import sys
36
+import os.path
37
+import optparse
38
+
39
+
40
+def my_open_read(source):
41
+    if hasattr(source, "read"):
42
+        return source
43
+    else:
44
+        return open(source)
45
+
46
+
47
+def my_open_write(dest):
48
+    if hasattr(dest, "write"):
49
+        return dest
50
+    else:
51
+        return open(dest, 'w')
52
+
53
+
54
+class Doxy2SWIG:
55
+
56
+    """Converts Doxygen generated XML files into a file containing
57
+    docstrings that can be used by SWIG-1.3.x that have support for
58
+    feature("docstring").  Once the data is parsed it is stored in
59
+    self.pieces.
60
+
61
+    """
62
+
63
+    def __init__(self, src, include_function_definition=True, quiet=False):
64
+        """Initialize the instance given a source object.  `src` can
65
+        be a file or filename.  If you do not want to include function
66
+        definitions from doxygen then set
67
+        `include_function_definition` to `False`.  This is handy since
68
+        this allows you to use the swig generated function definition
69
+        using %feature("autodoc", [0,1]).
70
+
71
+        """
72
+        f = my_open_read(src)
73
+        self.my_dir = os.path.dirname(f.name)
74
+        self.xmldoc = minidom.parse(f).documentElement
75
+        f.close()
76
+
77
+        self.pieces = []
78
+        self.pieces.append('\n// File: %s\n' %
79
+                           os.path.basename(f.name))
80
+
81
+        self.space_re = re.compile(r'\s+')
82
+        self.lead_spc = re.compile(r'^(%feature\S+\s+\S+\s*?)"\s+(\S)')
83
+        self.multi = 0
84
+        self.ignores = ['inheritancegraph', 'param', 'listofallmembers',
85
+                        'innerclass', 'name', 'declname', 'incdepgraph',
86
+                        'invincdepgraph', 'programlisting', 'type',
87
+                        'references', 'referencedby', 'location',
88
+                        'collaborationgraph', 'reimplements',
89
+                        'reimplementedby', 'derivedcompoundref',
90
+                        'basecompoundref']
91
+        #self.generics = []
92
+        self.include_function_definition = include_function_definition
93
+        if not include_function_definition:
94
+            self.ignores.append('argsstring')
95
+
96
+        self.quiet = quiet
97
+
98
+    def generate(self):
99
+        """Parses the file set in the initialization.  The resulting
100
+        data is stored in `self.pieces`.
101
+
102
+        """
103
+        self.parse(self.xmldoc)
104
+
105
+    def parse(self, node):
106
+        """Parse a given node.  This function in turn calls the
107
+        `parse_<nodeType>` functions which handle the respective
108
+        nodes.
109
+
110
+        """
111
+        pm = getattr(self, "parse_%s" % node.__class__.__name__)
112
+        pm(node)
113
+
114
+    def parse_Document(self, node):
115
+        self.parse(node.documentElement)
116
+
117
+    def parse_Text(self, node):
118
+        txt = node.data
119
+        txt = txt.replace('\\', r'\\\\')
120
+        txt = txt.replace('"', r'\"')
121
+        # ignore pure whitespace
122
+        m = self.space_re.match(txt)
123
+        if m and len(m.group()) == len(txt):
124
+            pass
125
+        else:
126
+            self.add_text(textwrap.fill(txt, break_long_words=False))
127
+
128
+    def parse_Element(self, node):
129
+        """Parse an `ELEMENT_NODE`.  This calls specific
130
+        `do_<tagName>` handers for different elements.  If no handler
131
+        is available the `generic_parse` method is called.  All
132
+        tagNames specified in `self.ignores` are simply ignored.
133
+
134
+        """
135
+        name = node.tagName
136
+        ignores = self.ignores
137
+        if name in ignores:
138
+            return
139
+        attr = "do_%s" % name
140
+        if hasattr(self, attr):
141
+            handlerMethod = getattr(self, attr)
142
+            handlerMethod(node)
143
+        else:
144
+            self.generic_parse(node)
145
+            #if name not in self.generics: self.generics.append(name)
146
+
147
+    def parse_Comment(self, node):
148
+        """Parse a `COMMENT_NODE`.  This does nothing for now."""
149
+        return
150
+
151
+    def add_text(self, value):
152
+        """Adds text corresponding to `value` into `self.pieces`."""
153
+        if isinstance(value, (list, tuple)):
154
+            self.pieces.extend(value)
155
+        else:
156
+            self.pieces.append(value)
157
+
158
+    def get_specific_nodes(self, node, names):
159
+        """Given a node and a sequence of strings in `names`, return a
160
+        dictionary containing the names as keys and child
161
+        `ELEMENT_NODEs`, that have a `tagName` equal to the name.
162
+
163
+        """
164
+        nodes = [(x.tagName, x) for x in node.childNodes
165
+                 if x.nodeType == x.ELEMENT_NODE and
166
+                 x.tagName in names]
167
+        return dict(nodes)
168
+
169
+    def generic_parse(self, node, pad=0):
170
+        """A Generic parser for arbitrary tags in a node.
171
+
172
+        Parameters:
173
+
174
+         - node:  A node in the DOM.
175
+         - pad: `int` (default: 0)
176
+
177
+           If 0 the node data is not padded with newlines.  If 1 it
178
+           appends a newline after parsing the childNodes.  If 2 it
179
+           pads before and after the nodes are processed.  Defaults to
180
+           0.
181
+
182
+        """
183
+        npiece = 0
184
+        if pad:
185
+            npiece = len(self.pieces)
186
+            if pad == 2:
187
+                self.add_text('\n')
188
+        for n in node.childNodes:
189
+            self.parse(n)
190
+        if pad:
191
+            if len(self.pieces) > npiece:
192
+                self.add_text('\n')
193
+
194
+    def space_parse(self, node):
195
+        self.add_text(' ')
196
+        self.generic_parse(node)
197
+
198
+    do_ref = space_parse
199
+    do_emphasis = space_parse
200
+    do_bold = space_parse
201
+    do_computeroutput = space_parse
202
+    do_formula = space_parse
203
+
204
+    def do_compoundname(self, node):
205
+        self.add_text('\n\n')
206
+        data = node.firstChild.data
207
+        self.add_text('%%feature("docstring") %s "\n' % data)
208
+
209
+    def do_compounddef(self, node):
210
+        kind = node.attributes['kind'].value
211
+        if kind in ('class', 'struct'):
212
+            prot = node.attributes['prot'].value
213
+            if prot != 'public':
214
+                return
215
+            names = ('compoundname', 'briefdescription',
216
+                     'detaileddescription', 'includes')
217
+            first = self.get_specific_nodes(node, names)
218
+            for n in names:
219
+                if first.has_key(n):
220
+                    self.parse(first[n])
221
+            self.add_text(['";', '\n'])
222
+            for n in node.childNodes:
223
+                if n not in first.values():
224
+                    self.parse(n)
225
+        elif kind in ('file', 'namespace'):
226
+            nodes = node.getElementsByTagName('sectiondef')
227
+            for n in nodes:
228
+                self.parse(n)
229
+
230
+    def do_includes(self, node):
231
+        self.add_text('C++ includes: ')
232
+        self.generic_parse(node, pad=1)
233
+
234
+    def do_parameterlist(self, node):
235
+        text = 'unknown'
236
+        for key, val in node.attributes.items():
237
+            if key == 'kind':
238
+                if val == 'param':
239
+                    text = 'Parameters'
240
+                elif val == 'exception':
241
+                    text = 'Exceptions'
242
+                elif val == 'retval':
243
+                    text = 'Returns'
244
+                else:
245
+                    text = val
246
+                break
247
+        self.add_text(['\n', '\n', text, ':', '\n'])
248
+        self.generic_parse(node, pad=1)
249
+
250
+    def do_para(self, node):
251
+        self.add_text('\n')
252
+        self.generic_parse(node, pad=1)
253
+
254
+    def do_parametername(self, node):
255
+        self.add_text('\n')
256
+        try:
257
+            data = node.firstChild.data
258
+        except AttributeError:  # perhaps a <ref> tag in it
259
+            data = node.firstChild.firstChild.data
260
+        if data.find('Exception') != -1:
261
+            self.add_text(data)
262
+        else:
263
+            self.add_text("%s: " % data)
264
+
265
+    def do_parameterdefinition(self, node):
266
+        self.generic_parse(node, pad=1)
267
+
268
+    def do_detaileddescription(self, node):
269
+        self.generic_parse(node, pad=1)
270
+
271
+    def do_briefdescription(self, node):
272
+        self.generic_parse(node, pad=1)
273
+
274
+    def do_memberdef(self, node):
275
+        prot = node.attributes['prot'].value
276
+        id = node.attributes['id'].value
277
+        kind = node.attributes['kind'].value
278
+        tmp = node.parentNode.parentNode.parentNode
279
+        compdef = tmp.getElementsByTagName('compounddef')[0]
280
+        cdef_kind = compdef.attributes['kind'].value
281
+
282
+        if prot == 'public':
283
+            first = self.get_specific_nodes(node, ('definition', 'name'))
284
+            name = first['name'].firstChild.data
285
+            if name[:8] == 'operator':  # Don't handle operators yet.
286
+                return
287
+
288
+            if not 'definition' in first or \
289
+                   kind in ['variable', 'typedef']:
290
+                return
291
+
292
+            if self.include_function_definition:
293
+                defn = first['definition'].firstChild.data
294
+            else:
295
+                defn = ""
296
+            self.add_text('\n')
297
+            self.add_text('%feature("docstring") ')
298
+
299
+            anc = node.parentNode.parentNode
300
+            if cdef_kind in ('file', 'namespace'):
301
+                ns_node = anc.getElementsByTagName('innernamespace')
302
+                if not ns_node and cdef_kind == 'namespace':
303
+                    ns_node = anc.getElementsByTagName('compoundname')
304
+                if ns_node:
305
+                    ns = ns_node[0].firstChild.data
306
+                    self.add_text(' %s::%s "\n%s' % (ns, name, defn))
307
+                else:
308
+                    self.add_text(' %s "\n%s' % (name, defn))
309
+            elif cdef_kind in ('class', 'struct'):
310
+                # Get the full function name.
311
+                anc_node = anc.getElementsByTagName('compoundname')
312
+                cname = anc_node[0].firstChild.data
313
+                self.add_text(' %s::%s "\n%s' % (cname, name, defn))
314
+
315
+            for n in node.childNodes:
316
+                if n not in first.values():
317
+                    self.parse(n)
318
+            self.add_text(['";', '\n'])
319
+
320
+    def do_definition(self, node):
321
+        data = node.firstChild.data
322
+        self.add_text('%s "\n%s' % (data, data))
323
+
324
+    def do_sectiondef(self, node):
325
+        kind = node.attributes['kind'].value
326
+        if kind in ('public-func', 'func', 'user-defined', ''):
327
+            self.generic_parse(node)
328
+
329
+    def do_header(self, node):
330
+        """For a user defined section def a header field is present
331
+        which should not be printed as such, so we comment it in the
332
+        output."""
333
+        data = node.firstChild.data
334
+        self.add_text('\n/*\n %s \n*/\n' % data)
335
+        # If our immediate sibling is a 'description' node then we
336
+        # should comment that out also and remove it from the parent
337
+        # node's children.
338
+        parent = node.parentNode
339
+        idx = parent.childNodes.index(node)
340
+        if len(parent.childNodes) >= idx + 2:
341
+            nd = parent.childNodes[idx + 2]
342
+            if nd.nodeName == 'description':
343
+                nd = parent.removeChild(nd)
344
+                self.add_text('\n/*')
345
+                self.generic_parse(nd)
346
+                self.add_text('\n*/\n')
347
+
348
+    def do_simplesect(self, node):
349
+        kind = node.attributes['kind'].value
350
+        if kind in ('date', 'rcs', 'version'):
351
+            pass
352
+        elif kind == 'warning':
353
+            self.add_text(['\n', 'WARNING: '])
354
+            self.generic_parse(node)
355
+        elif kind == 'see':
356
+            self.add_text('\n')
357
+            self.add_text('See: ')
358
+            self.generic_parse(node)
359
+        else:
360
+            self.generic_parse(node)
361
+
362
+    def do_argsstring(self, node):
363
+        self.generic_parse(node, pad=1)
364
+
365
+    def do_member(self, node):
366
+        kind = node.attributes['kind'].value
367
+        refid = node.attributes['refid'].value
368
+        if kind == 'function' and refid[:9] == 'namespace':
369
+            self.generic_parse(node)
370
+
371
+    def do_doxygenindex(self, node):
372
+        self.multi = 1
373
+        comps = node.getElementsByTagName('compound')
374
+        for c in comps:
375
+            refid = c.attributes['refid'].value
376
+            fname = refid + '.xml'
377
+            if not os.path.exists(fname):
378
+                fname = os.path.join(self.my_dir,  fname)
379
+            if not self.quiet:
380
+                print("parsing file: %s" % fname)
381
+            p = Doxy2SWIG(fname, self.include_function_definition, self.quiet)
382
+            p.generate()
383
+            self.pieces.extend(self.clean_pieces(p.pieces))
384
+
385
+    def write(self, fname):
386
+        o = my_open_write(fname)
387
+        if self.multi:
388
+            o.write("".join(self.pieces))
389
+        else:
390
+            o.write("".join(self.clean_pieces(self.pieces)))
391
+        o.close()
392
+
393
+    def clean_pieces(self, pieces):
394
+        """Cleans the list of strings given as `pieces`.  It replaces
395
+        multiple newlines by a maximum of 2 and returns a new list.
396
+        It also wraps the paragraphs nicely.
397
+
398
+        """
399
+        ret = []
400
+        count = 0
401
+        for i in pieces:
402
+            if i == '\n':
403
+                count = count + 1
404
+            else:
405
+                if i == '";':
406
+                    if count:
407
+                        ret.append('\n')
408
+                elif count > 2:
409
+                    ret.append('\n\n')
410
+                elif count:
411
+                    ret.append('\n' * count)
412
+                count = 0
413
+                ret.append(i)
414
+
415
+        _data = "".join(ret)
416
+        ret = []
417
+        for i in _data.split('\n\n'):
418
+            if i == 'Parameters:' or i == 'Exceptions:' or i == 'Returns:':
419
+                ret.extend([i, '\n' + '-' * len(i), '\n\n'])
420
+            elif i.find('// File:') > -1:  # leave comments alone.
421
+                ret.extend([i, '\n'])
422
+            else:
423
+                _tmp = textwrap.fill(i.strip(), break_long_words=False)
424
+                _tmp = self.lead_spc.sub(r'\1"\2', _tmp)
425
+                ret.extend([_tmp, '\n\n'])
426
+        return ret
427
+
428
+
429
+def convert(input, output, include_function_definition=True, quiet=False):
430
+    p = Doxy2SWIG(input, include_function_definition, quiet)
431
+    p.generate()
432
+    p.write(output)
433
+
434
+
435
+def main():
436
+    usage = __doc__
437
+    parser = optparse.OptionParser(usage)
438
+    parser.add_option("-n", '--no-function-definition',
439
+                      action='store_true',
440
+                      default=False,
441
+                      dest='func_def',
442
+                      help='do not include doxygen function definitions')
443
+    parser.add_option("-q", '--quiet',
444
+                      action='store_true',
445
+                      default=False,
446
+                      dest='quiet',
447
+                      help='be quiet and minimize output')
448
+
449
+    options, args = parser.parse_args()
450
+    if len(args) != 2:
451
+        parser.error("error: no input and output specified")
452
+
453
+    convert(args[0], args[1], not options.func_def, options.quiet)
454
+
455
+
456
+if __name__ == '__main__':
457
+    main()

+ 28
- 15
src/hmc5883l/CMakeLists.txt Просмотреть файл

@@ -1,22 +1,35 @@
1
+set (libname "hmc5883l")
1 2
 add_library (hmc5883l SHARED hmc5883l.cxx)
2
-
3 3
 include_directories (${MAA_INCLUDE_DIR})
4
-
5 4
 target_link_libraries (hmc5883l ${MAA_LIBRARIES})
6 5
 
7
-find_package (PythonLibs)
6
+if (DOXYGEN_FOUND AND SWIG_FOUND)
7
+  find_package (PythonLibs)
8
+
9
+  include_directories (
10
+    ${PYTHON_INCLUDE_PATH}
11
+    ${PYTHON_INCLUDE_DIRS}
12
+    ${MAA_INCLUDE_DIR}
13
+    .
14
+  )
15
+
16
+  set_source_files_properties (pyupm_hmc5883l.i PROPERTIES CPLUSPLUS ON)
17
+  set_source_files_properties (jsupm_hmc5883l.i PROPERTIES CPLUSPLUS ON)
8 18
 
9
-include_directories (
10
-  ${PYTHON_INCLUDE_PATH}
11
-  ${PYTHON_INCLUDE_DIRS}
12
-  ${MAA_INCLUDE_DIR}
13
-  .
14
-)
19
+  swig_add_module (pyupm_hmc5883l python pyupm_hmc5883l.i hmc5883l.cxx)
20
+  swig_add_module (jsupm_hmc5883l python jsupm_hmc5883l.i hmc5883l.cxx)
21
+  swig_link_libraries (pyupm_hmc5883l ${PYTHON_LIBRARIES} ${MAA_LIBRARIES})
22
+  swig_link_libraries (jsupm_hmc5883l ${PYTHON_LIBRARIES} ${MAA_LIBRARIES})
15 23
 
16
-set_source_files_properties (pyupm_hmc5883l.i PROPERTIES CPLUSPLUS ON)
17
-set_source_files_properties (jsupm_hmc5883l.i PROPERTIES CPLUSPLUS ON)
24
+  set (CMAKE_SWIG_FLAGS -DDOXYGEN=${DOXYGEN_FOUND})
25
+  add_custom_command (OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${libname}_doc.i
26
+    COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/../doxy2swig.py -n
27
+      ${CMAKE_BINARY_DIR}/xml/${libname}_8h.xml
28
+      ${CMAKE_CURRENT_BINARY_DIR}/${libname}_doc.i
29
+      DEPENDS ${CMAKE_BINARY_DIR}/xml/${libname}_8h.xml
30
+  )
31
+  add_custom_target (${libname}doc_i DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${libname}_doc.i)
32
+  add_dependencies (${libname}doc_i doc)
33
+  add_dependencies (${SWIG_MODULE_pyupm_hmc5883l_REAL_NAME} ${libname}doc_i)
18 34
 
19
-swig_add_module (pyupm_hmc5883l python pyupm_hmc5883l.i hmc5883l.cxx)
20
-swig_add_module (jsupm_hmc5883l python jsupm_hmc5883l.i hmc5883l.cxx)
21
-swig_link_libraries (pyupm_hmc5883l ${PYTHON_LIBRARIES} ${MAA_LIBRARIES})
22
-swig_link_libraries (jsupm_hmc5883l ${PYTHON_LIBRARIES} ${MAA_LIBRARIES})
35
+endif ()

+ 7
- 2
src/hmc5883l/pyupm_hmc5883l.i Просмотреть файл

@@ -1,7 +1,12 @@
1 1
 %module pyupm_hmc5883l
2 2
 
3
+%feature("autodoc", "3");
4
+
5
+#ifdef DOXYGEN
6
+%include "hmc5883l_doc.i"
7
+#endif
8
+
9
+%include "hmc5883l.h"
3 10
 %{
4 11
     #include "hmc5883l.h"
5 12
 %}
6
-
7
-%include "hmc5883l.h"