You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
250 lines
7.5 KiB
250 lines
7.5 KiB
# -*- 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
|
|
|
|
|
|
# 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",
|
|
}
|
|
|
|
# Types that accept the :members: option.
|
|
MEMBERS_TYPES = ["class", "group", "interface", "namespace", "struct"]
|
|
|
|
|
|
def print_info(msg, args):
|
|
if not args.quiet:
|
|
print(msg)
|
|
|
|
|
|
def write_file(name, text, args):
|
|
"""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
|
|
|
|
with open(fname, "w") as target:
|
|
target.write(text)
|
|
|
|
|
|
def format_heading(level, text):
|
|
"""Create a heading of <level> [1, 2 or 3 supported]."""
|
|
underlining = ["=", "-", "~",][
|
|
level - 1
|
|
] * len(text)
|
|
return "%s\n%s\n\n" % (text, underlining)
|
|
|
|
|
|
def format_directive(package_type, package, args):
|
|
"""Create the breathe directive and add the options."""
|
|
directive = ".. doxygen%s:: %s\n" % (package_type, package)
|
|
if args.project:
|
|
directive += " :project: %s\n" % args.project
|
|
if args.members and package_type in MEMBERS_TYPES:
|
|
directive += " :members:\n"
|
|
return directive
|
|
|
|
|
|
def create_package_file(package, package_type, package_id, args):
|
|
"""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 %s" % (TYPEDICT[package_type], package))
|
|
text += format_directive(package_type, package, args)
|
|
|
|
write_file(os.path.join(package_type, package_id), text, args)
|
|
|
|
|
|
def create_modules_toc_file(key, value, args):
|
|
"""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 += ".. toctree::\n"
|
|
text += " :glob:\n\n"
|
|
text += " %s/*\n" % key
|
|
|
|
write_file("%slist" % key, text, args)
|
|
|
|
|
|
def recurse_tree(args):
|
|
"""
|
|
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"))
|
|
|
|
# Assuming this is a valid Doxygen XML
|
|
for compound in index.getroot():
|
|
create_package_file(
|
|
compound.findtext("name"), compound.get("kind"), compound.get("refid"), args
|
|
)
|
|
|
|
|
|
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())
|
|
|
|
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)
|
|
|
|
|
|
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>.
|
|
|
|
Note: By default this script will not overwrite already created files.""",
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
)
|
|
|
|
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(
|
|
"-m",
|
|
"--members",
|
|
action="store_true",
|
|
dest="members",
|
|
help="Include members for types: %s" % MEMBERS_TYPES,
|
|
)
|
|
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()
|
|
|
|
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)
|
|
|
|
|
|
# So program can be started with "python -m breathe.apidoc ..."
|
|
if __name__ == "__main__":
|
|
main()
|
|
|