Made apidoc.py handle subfolders

Breathe apidoc.py used to classify by type (class, struct, etc.)
This is not useful when you have too many classes, so it's now
ordered by subfolders.
This commit is contained in:
xarkes 2019-02-26 17:39:49 +01:00
parent f67e34697f
commit 2b85d7bfaf
3 changed files with 85 additions and 141 deletions

View File

@ -813,7 +813,7 @@ WARN_LOGFILE =
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.
INPUT = ../src ../src/common ../src/widgets ../src/dialogs ../src/menus
INPUT = ../src ../src/core ../src/common ../src/widgets ../src/dialogs ../src/menus ../src/plugins
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses

View File

@ -17,5 +17,11 @@ help:
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
doxygen
python3 apidoc.py -g class,interface,struct,union -f -o source/api doxygen-out/xml/
python3 apidoc.py
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
clean:
rm -fr doxygen-out
rm -fr source/api
rm -fr build
@$(SPHINXBUILD) -M clean "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS)

View File

@ -1,82 +1,34 @@
# -*- coding: utf-8 -*-
"""
breathe.apidoc
~~~~~~~~~~~~~~
Parses doxygen XML tree looking for C/C++ modules and creates ReST files
appropriately to create code documentation with Sphinx. It also creates a
modules index (See TYPEDICT below.).
This is derived from the "sphinx-autopackage" script, which is:
Copyright 2008 Société des arts technologiques (SAT),
http://www.sat.qc.ca/
:copyright: Originally by Sphinx Team, C++ modifications by Tatsuyuki Ishi
:license: BSD, see LICENSE for details.
"""
from __future__ import print_function
import os
import sys
import argparse
import errno
import xml.etree.ElementTree
from breathe import __version__
# Account for FileNotFoundError in Python 2
# IOError is broader but will hopefully suffice
try:
FileNotFoundError
except NameError:
FileNotFoundError = IOError
ALLOWED_TYPES = ['class', 'interface', 'struct', 'union']
# Reference: Doxygen XSD schema file, CompoundKind only
# Only what breathe supports are included
# Translates identifier to English
TYPEDICT = {'class': 'Class',
'interface': 'Interface',
'struct': 'Struct',
'union': 'Union',
'file': 'File',
'namespace': 'Namespace',
'group': 'Group'}
def print_info(msg, args):
if not args.quiet:
print(msg)
def write_file(name, text, args):
def write_file(name, text, destdir):
"""Write the output file for module/package <name>."""
fname = os.path.join(args.destdir, '%s.%s' % (name, args.suffix))
if args.dryrun:
print_info('Would create file %s.' % fname, args)
return
if not args.force and os.path.isfile(fname):
print_info('File %s already exists, skipping.' % fname, args)
else:
print_info('Creating file %s.' % fname, args)
if not os.path.exists(os.path.dirname(fname)):
try:
os.makedirs(os.path.dirname(fname))
except OSError as exc: # Guard against race condition
if exc.errno != errno.EEXIST:
raise
try:
with open(fname, 'r') as target:
orig = target.read()
if orig == text:
print_info('File %s up to date, skipping.' % fname, args)
return
except FileNotFoundError:
# Don't mind if it isn't there
pass
fname = os.path.join(destdir, '%s.%s' % (name, 'rst'))
with open(fname, 'w') as target:
target.write(text)
if not os.path.exists(os.path.dirname(fname)):
try:
os.makedirs(os.path.dirname(fname))
except OSError as exc: # Guard against race condition
if exc.errno != errno.EEXIST:
raise
try:
with open(fname, 'r') as target:
orig = target.read()
if orig == text:
return
except FileNotFoundError:
# Don't mind if it isn't there
pass
with open(fname, 'w') as target:
target.write(text)
def format_heading(level, text):
@ -85,7 +37,7 @@ def format_heading(level, text):
return '%s\n%s\n\n' % (text, underlining)
def format_directive(package_type, package, project):
def format_directive(package_type, package, project = None):
"""Create the breathe directive and add the options."""
directive = '.. doxygen%s:: %s\n' % (package_type, package)
if project:
@ -93,104 +45,90 @@ def format_directive(package_type, package, project):
return directive
def create_package_file(package, package_type, package_id, args):
def create_package_file(package, package_type, package_id, package_folder, rootpath, destdir):
"""Build the text of the file and write the file."""
# Skip over types that weren't requested
if package_type not in args.outtypes:
return
text = format_heading(1, '%s' % (package))
text += format_directive(package_type, package, args.project)
text += format_directive(package_type, package)
write_file(os.path.join(package_type, package_id), text, args)
xmlfile = os.path.join(rootpath, package_id + '.xml')
f = xml.etree.ElementTree.parse(os.path.join(xmlfile))
write_file(os.path.join(package_folder, package_id), text, destdir)
def create_modules_toc_file(key, value, args):
def create_modules_toc_file(key, value, destdir):
"""Create the module's index."""
if not os.path.isdir(os.path.join(args.destdir, key)):
return
text = format_heading(1, '%s list' % value)
text = format_heading(1, '%s' % value)
text += '.. toctree::\n'
text += ' :glob:\n\n'
text += ' %s/*\n' % key
write_file('%slist' % key, text, args)
write_file('%slist' % key, text, destdir)
def recurse_tree(args):
def get_compound_folder(rootpath, compound):
fxml = xml.etree.ElementTree.parse(os.path.join(rootpath, compound.get('refid')) + '.xml')
loc = fxml.getroot()[0].find('location')
dirname = os.path.basename(os.path.split(loc.get('file'))[0])
return dirname
def recurse_tree(rootpath, destdir):
"""
Look for every file in the directory tree and create the corresponding
ReST files.
"""
index = xml.etree.ElementTree.parse(os.path.join(args.rootpath, 'index.xml'))
index = xml.etree.ElementTree.parse(os.path.join(rootpath, 'index.xml'))
# Assuming this is a valid Doxygen XML
for compound in index.getroot():
if compound.get('kind') not in ALLOWED_TYPES:
continue
create_package_file(compound.findtext('name'), compound.get('kind'),
compound.get('refid'), args)
compound.get('refid'), get_compound_folder(rootpath, compound),
rootpath, destdir)
def get_folders_tree(rootpath):
tmp = []
class TypeAction(argparse.Action):
def __init__(self, option_strings, dest, **kwargs):
super(TypeAction, self).__init__(option_strings, dest, **kwargs)
self.default = TYPEDICT.keys()
self.metavar = ','.join(TYPEDICT.keys())
# Retrieve the subfolders indexes
for root, _, files in os.walk(rootpath):
for xmlfile in files:
if not xmlfile.startswith('dir_'):
continue
tmp.append(xmlfile)
def __call__(self, parser, namespace, values, option_string=None):
value_list = values.split(',')
for value in value_list:
if value not in TYPEDICT:
raise ValueError("%s not a valid option" % value)
setattr(namespace, self.dest, value_list)
# Iterate on them
dirs = []
for xmlfile in tmp:
data = xml.etree.ElementTree.parse(os.path.join(rootpath, xmlfile))
if not data:
continue
for compound in data.getroot():
name = compound.findtext('compoundname')
dirs.append(name)
return dirs
def main():
"""Parse and check the command line arguments."""
parser = argparse.ArgumentParser(
description="""\
Parse XML created by Doxygen in <rootpath> and create one reST file with
breathe generation directives per definition in the <DESTDIR>.
rootpath = './doxygen-out/xml'
destdir = './source/api'
Note: By default this script will not overwrite already created files.""",
formatter_class=argparse.RawDescriptionHelpFormatter)
if not os.path.exists(destdir):
os.makedirs(destdir)
parser.add_argument('-o', '--output-dir', action='store', dest='destdir',
help='Directory to place all output', required=True)
parser.add_argument('-f', '--force', action='store_true', dest='force',
help='Overwrite existing files')
parser.add_argument('-n', '--dry-run', action='store_true', dest='dryrun',
help='Run the script without creating files')
parser.add_argument('-T', '--no-toc', action='store_true', dest='notoc',
help='Don\'t create a table of contents file')
parser.add_argument('-s', '--suffix', action='store', dest='suffix',
help='file suffix (default: rst)', default='rst')
parser.add_argument('-p', '--project', action='store', dest='project',
help='project to add to generated directives')
parser.add_argument('-g', '--generate', action=TypeAction, dest='outtypes',
help='types of output to generate, comma-separated list')
parser.add_argument('-q', '--quiet', action='store_true', dest='quiet',
help='suppress informational messages')
parser.add_argument('--version', action='version',
version='Breathe (breathe-apidoc) %s' % __version__)
parser.add_argument('rootpath', type=str,
help='The directory contains index.xml')
args = parser.parse_args()
dirs = sorted(get_folders_tree(rootpath))
source_root = dirs[0]
source_dirs = dirs[1:]
out_dirs = [os.path.basename(d) for d in dirs]
if args.suffix.startswith('.'):
args.suffix = args.suffix[1:]
if not os.path.isdir(args.rootpath):
print('%s is not a directory.' % args.rootpath, file=sys.stderr)
sys.exit(1)
if 'index.xml' not in os.listdir(args.rootpath):
print('%s does not contain a index.xml' % args.rootpath, file=sys.stderr)
sys.exit(1)
if not os.path.isdir(args.destdir):
if not args.dryrun:
os.makedirs(args.destdir)
args.rootpath = os.path.abspath(args.rootpath)
recurse_tree(args)
if not args.notoc:
for key in args.outtypes:
create_modules_toc_file(key, TYPEDICT[key], args)
# TODO Handle only one level subfolders
for key in out_dirs:
ddir = os.path.join(destdir, key)
if not os.path.exists(ddir):
os.makedirs(ddir)
create_modules_toc_file(key, key.capitalize(), destdir)
recurse_tree(rootpath, destdir)
# So program can be started with "python -m breathe.apidoc ..."