4

I've created python bindings for a large body of C++ code, using boost::python. The python bindings have docs like this:

BOOST_PYTHON_MODULE(mymodule)
{
    using namespace boost::python;
    def("foo1", foo1, arg("i"), "foo1 doc");
}

The rest of the project is documented with doxygen. I'd like to know if there is a way to produce doxygen docs from the python bindings' docstrings.

To me it seems like I have two options:

  • Use a magic tool to import the python file and output the docs. Sphinx works to a degree, as its autodoc facility actually loads the python modules and scans for docstrings. However, it's not producing an output format doxygen can use (I think?).
  • Write a conversion process to import the BOOST_PYTHON_MODULE. Call help(mymodule). Parse the output to produce skeleton python files. Feed those into doxygen as normal.

Is there a better way?

sipickles
  • 1,637
  • 1
  • 19
  • 30

1 Answers1

2

From this topic, you can do the following:

1 - Write the doxygen documentation:

// DocString: foo
/**
 * @brief Foo doc
 * @param i an integer
 * @return something
 *
 */
int foo(int i);

2 - Update your binding doc:

BOOST_PYTHON_MODULE(mymodule)
{
    using namespace boost::python;
    def("foo1", foo1, arg("i"), "@DocString(foo)");
}

3 - Configure your file (in a build chain) with a script like this:

import re
import sys

def parse_doc_string(istr):
    pattern = re.compile(r'@(\w+)\s+(.*)')
    docstring = list()
    for line in map(lambda s : s.strip(), istr):
        if line == '/**':
            continue
        if line == '*/':
            return docstring
        line = line.lstrip('* ')
        match = pattern.match(line)
        if match:
            docstring.append((match.group(1), match.group(2)))

def extract(istr, docstrings):
    pattern = re.compile(r'^//\s*DocString:\s*(\w+)$')
    for line in map(lambda s : s.strip(), istr):
        match = pattern.match(line)
        if match:
            token = match.group(1)
            docstrings[token] = parse_doc_string(istr)

def format_doc_string(docstring):
    return '\n'.join('{}: {}'.format(k, v) for (k, v) in docstring)

def escape(string):
    return string.replace('\n', r'\n')

def substitute(istr, ostr, docstrings):
    pattern = re.compile(r'@DocString\((\w+)\)')
    for line in map(lambda s : s.rstrip(), istr):
        for match in pattern.finditer(line):
            token = match.group(1)
            docstring = format_doc_string(docstrings[token])
            line = line.replace(match.group(0), escape(docstring))
        print(line, file=ostr)

if __name__ == '__main__':
    sourcefile = sys.argv[1]
    docstrings = dict()
    with open(sourcefile) as istr:
        extract(istr, docstrings)
    with open(sourcefile) as istr:
        with sys.stdout as ostr:
            substitute(istr, ostr, docstrings)

It will replace your binding by:

def("foo1", foo1, arg("i"), "brief: Foo doc\nparam: i an integer\nreturn: something");
Community
  • 1
  • 1
billx
  • 185
  • 11