From e2d179e2bf3fbaadf01a0539f3d66c25a0ee30f3 Mon Sep 17 00:00:00 2001 From: frankmorgner Date: Thu, 27 Oct 2011 20:10:41 +0000 Subject: [PATCH] automagically generate documentation of virtualsmartcard git-svn-id: https://vsmartcard.svn.sourceforge.net/svnroot/vsmartcard@588 96b47cad-a561-4643-ad3b-153ac7d7599c --- doc/conf.py | 5 +- doc/index.rst | 1 + npa/doc/Makefile.am | 3 +- virtualsmartcard/Makefile.am | 4 + virtualsmartcard/doc/Makefile.am | 9 +- virtualsmartcard/doc/api.rst | 7 + virtualsmartcard/doc/generate_modules.py | 262 +++++++++++++++++++++++ 7 files changed, 287 insertions(+), 4 deletions(-) create mode 100644 virtualsmartcard/doc/api.rst create mode 100755 virtualsmartcard/doc/generate_modules.py diff --git a/doc/conf.py b/doc/conf.py index d301c0f..f81dac7 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -26,7 +26,7 @@ from sphinxcontrib.doxylink import doxylink # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ["breathe", "sphinxcontrib.doxylink"] +extensions = ["breathe", "sphinxcontrib.doxylink", "sphinx.ext.autosummary"] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -216,7 +216,8 @@ man_pages = [ [u'Dominik Oepen, Frank Morgner'], 1) ] -os.system("cd npa && make doc >/dev/null && touch api.rst") +os.system("make doc -C npa >/dev/null") +os.system("make doc -C virtualsmartcard >/dev/null") breathe_projects = {"npa": "npa/xml"} breathe_default_project = "npa" doxylink = { 'npa' : ('npa/npa.tag', '_static/doxygen-npa/'), } diff --git a/doc/index.rst b/doc/index.rst index 7cb7e4a..91a6d37 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -23,6 +23,7 @@ have a look at these programming guides and try yourself: .. toctree:: + virtualsmartcard/api npa/api Download diff --git a/npa/doc/Makefile.am b/npa/doc/Makefile.am index c1a5d0e..a58870f 100644 --- a/npa/doc/Makefile.am +++ b/npa/doc/Makefile.am @@ -16,9 +16,10 @@ EXTRA_DIST = README.rst Doxyfile.in example.c if DOC_ENABLED .PHONY: doc -doc : +doc: $(do_subst) < Doxyfile.in > Doxyfile $(DOXYGEN) Doxyfile + touch api.rst endif clean-local: diff --git a/virtualsmartcard/Makefile.am b/virtualsmartcard/Makefile.am index 3920780..f7d87a2 100644 --- a/virtualsmartcard/Makefile.am +++ b/virtualsmartcard/Makefile.am @@ -1 +1,5 @@ SUBDIRS = src doc + +.PHONY: doc +doc: + $(MAKE) doc -C doc diff --git a/virtualsmartcard/doc/Makefile.am b/virtualsmartcard/doc/Makefile.am index aa044b3..7d07ced 100644 --- a/virtualsmartcard/doc/Makefile.am +++ b/virtualsmartcard/doc/Makefile.am @@ -1 +1,8 @@ -EXTRA_DIST = README.rst +EXTRA_DIST = README.rst generate_modules.py api + +.PHONY: doc +doc: + ./generate_modules.py $(top_srcdir)/src/vpicc --dest-dir=api --suffix=rst --no-toc + +clean-local: + rm -rf api/* diff --git a/virtualsmartcard/doc/api.rst b/virtualsmartcard/doc/api.rst new file mode 100644 index 0000000..71f0071 --- /dev/null +++ b/virtualsmartcard/doc/api.rst @@ -0,0 +1,7 @@ +***************************** +Creating a virtual smart card +***************************** + +.. toctree:: + + api/virtualsmartcard diff --git a/virtualsmartcard/doc/generate_modules.py b/virtualsmartcard/doc/generate_modules.py new file mode 100755 index 0000000..e38c943 --- /dev/null +++ b/virtualsmartcard/doc/generate_modules.py @@ -0,0 +1,262 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +sphinx-autopackage-script + +This script parses a directory tree looking for python modules and packages and +creates ReST files appropriately to create code documentation with Sphinx. +It also creates a modules index (named modules.). +""" + +# Copyright 2008 Société des arts technologiques (SAT), http://www.sat.qc.ca/ +# Copyright 2010 Thomas Waldmann +# All rights reserved. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +import os +import optparse + + +# automodule options +OPTIONS = ['members', + 'undoc-members', + # 'inherited-members', # disabled because there's a bug in sphinx + 'show-inheritance', + ] + +INIT = '__init__.py' + +def makename(package, module): + """Join package and module with a dot.""" + # Both package and module can be None/empty. + if package: + name = package + if module: + name += '.' + module + else: + name = module + return name + +def write_file(name, text, opts): + """Write the output file for module/package .""" + if opts.dryrun: + return + fname = os.path.join(opts.destdir, "%s.%s" % (name, opts.suffix)) + if not opts.force and os.path.isfile(fname): + print 'File %s already exists, skipping.' % fname + else: + print 'Creating file %s.' % fname + f = open(fname, 'w') + f.write(text) + f.close() + +def format_heading(level, text): + """Create a heading of [1, 2 or 3 supported].""" + underlining = ['=', '-', '~', ][level-1] * len(text) + return '%s\n%s\n\n' % (text, underlining) + +def format_directive(module, package=None): + """Create the automodule directive and add the options.""" + directive = '.. automodule:: %s\n' % makename(package, module) + for option in OPTIONS: + directive += ' :%s:\n' % option + return directive + +def create_module_file(package, module, opts): + """Build the text of the file and write the file.""" + text = format_heading(1, '%s Module' % module) + text += format_heading(2, ':mod:`%s` Module' % module) + text += format_directive(module, package) + write_file(makename(package, module), text, opts) + +def create_package_file(root, master_package, subroot, py_files, opts, subs): + """Build the text of the file and write the file.""" + package = os.path.split(root)[-1] + text = format_heading(1, '%s Package' % package) + # add each package's module + for py_file in py_files: + if shall_skip(os.path.join(root, py_file)): + continue + is_package = py_file == INIT + py_file = os.path.splitext(py_file)[0] + py_path = makename(subroot, py_file) + if is_package: + heading = ':mod:`%s` Package' % package + else: + heading = ':mod:`%s` Module' % py_file + text += format_heading(2, heading) + text += format_directive(is_package and subroot or py_path, master_package) + text += '\n' + + # build a list of directories that are packages (they contain an INIT file) + subs = [sub for sub in subs if os.path.isfile(os.path.join(root, sub, INIT))] + # if there are some package directories, add a TOC for theses subpackages + if subs: + text += format_heading(2, 'Subpackages') + text += '.. toctree::\n\n' + for sub in subs: + text += ' %s.%s\n' % (makename(master_package, subroot), sub) + text += '\n' + + write_file(makename(master_package, subroot), text, opts) + +def create_modules_toc_file(master_package, modules, opts, name='modules'): + """ + Create the module's index. + """ + text = format_heading(1, '%s Modules' % opts.header) + text += '.. toctree::\n' + text += ' :maxdepth: %s\n\n' % opts.maxdepth + + modules.sort() + prev_module = '' + for module in modules: + # look if the module is a subpackage and, if yes, ignore it + if module.startswith(prev_module + '.'): + continue + prev_module = module + text += ' %s\n' % module + + write_file(name, text, opts) + +def shall_skip(module): + """ + Check if we want to skip this module. + """ + # skip it, if there is nothing (or just \n or \r\n) in the file + return os.path.getsize(module) < 3 + +def recurse_tree(path, excludes, opts): + """ + Look for every file in the directory tree and create the corresponding + ReST files. + """ + # use absolute path for root, as relative paths like '../../foo' cause + # 'if "/." in root ...' to filter out *all* modules otherwise + path = os.path.abspath(path) + # check if the base directory is a package and get is name + if INIT in os.listdir(path): + package_name = path.split(os.path.sep)[-1] + else: + package_name = None + + toc = [] + tree = os.walk(path, False) + for root, subs, files in tree: + # keep only the Python script files + py_files = sorted([f for f in files if os.path.splitext(f)[1] == '.py']) + if INIT in py_files: + py_files.remove(INIT) + py_files.insert(0, INIT) + # remove hidden ('.') and private ('_') directories + subs = sorted([sub for sub in subs if sub[0] not in ['.', '_']]) + # check if there are valid files to process + # TODO: could add check for windows hidden files + if "/." in root or "/_" in root \ + or not py_files \ + or is_excluded(root, excludes): + continue + if INIT in py_files: + # we are in package ... + if (# ... with subpackage(s) + subs + or + # ... with some module(s) + len(py_files) > 1 + or + # ... with a not-to-be-skipped INIT file + not shall_skip(os.path.join(root, INIT)) + ): + subroot = root[len(path):].lstrip(os.path.sep).replace(os.path.sep, '.') + create_package_file(root, package_name, subroot, py_files, opts, subs) + toc.append(makename(package_name, subroot)) + elif root == path: + # if we are at the root level, we don't require it to be a package + for py_file in py_files: + if not shall_skip(os.path.join(path, py_file)): + module = os.path.splitext(py_file)[0] + create_module_file(package_name, module, opts) + toc.append(makename(package_name, module)) + + # create the module's index + if not opts.notoc: + create_modules_toc_file(package_name, toc, opts) + +def normalize_excludes(rootpath, excludes): + """ + Normalize the excluded directory list: + * must be either an absolute path or start with rootpath, + * otherwise it is joined with rootpath + * with trailing slash + """ + sep = os.path.sep + f_excludes = [] + for exclude in excludes: + if not os.path.isabs(exclude) and not exclude.startswith(rootpath): + exclude = os.path.join(rootpath, exclude) + if not exclude.endswith(sep): + exclude += sep + f_excludes.append(exclude) + return f_excludes + +def is_excluded(root, excludes): + """ + Check if the directory is in the exclude list. + + Note: by having trailing slashes, we avoid common prefix issues, like + e.g. an exlude "foo" also accidentally excluding "foobar". + """ + sep = os.path.sep + if not root.endswith(sep): + root += sep + for exclude in excludes: + if root.startswith(exclude): + return True + return False + +def main(): + """ + Parse and check the command line arguments. + """ + parser = optparse.OptionParser(usage="""usage: %prog [options] [exclude paths, ...] + +Note: By default this script will not overwrite already created files.""") + parser.add_option("-n", "--doc-header", action="store", dest="header", help="Documentation Header (default=Project)", default="Project") + parser.add_option("-d", "--dest-dir", action="store", dest="destdir", help="Output destination directory", default="") + parser.add_option("-s", "--suffix", action="store", dest="suffix", help="module suffix (default=txt)", default="txt") + parser.add_option("-m", "--maxdepth", action="store", dest="maxdepth", help="Maximum depth of submodules to show in the TOC (default=4)", type="int", default=4) + parser.add_option("-r", "--dry-run", action="store_true", dest="dryrun", help="Run the script without creating the files") + parser.add_option("-f", "--force", action="store_true", dest="force", help="Overwrite all the files") + parser.add_option("-t", "--no-toc", action="store_true", dest="notoc", help="Don't create the table of content file") + (opts, args) = parser.parse_args() + if not args: + parser.error("package path is required.") + else: + rootpath, excludes = args[0], args[1:] + if os.path.isdir(rootpath): + # check if the output destination is a valid directory + if opts.destdir and os.path.isdir(opts.destdir): + excludes = normalize_excludes(rootpath, excludes) + recurse_tree(rootpath, excludes, opts) + else: + print '%s is not a valid output destination directory.' % opts.destdir + else: + print '%s is not a valid directory.' % rootpath + + +if __name__ == '__main__': + main() +