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?

1 Answer 1

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");
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.