Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

executable file 259 lines (227 sloc) 9.629 kB
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Sropulpof
# Copyright (C) 2008 Société des arts technologiques (SAT)
# http://www.sat.qc.ca
# All rights reserved.
#
# This file 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.
#
# Sropulpof 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 Sropulpof. If not, see <http:#www.gnu.org/licenses/>.
"""
This script parse a directory tree looking for python modules and packages and
create ReST files appropriately to create code documentation with Sphinx.
It also create a modules index.
"""
import os
import optparse
# automodule options
options = ['members',
'undoc-members',
# 'inherited-members', # disable because there's a bug in sphinx
'show-inheritance']
def create_file_name(base, opts):
"""Create file name from base name, path and suffix"""
return os.path.join(opts.destdir, "%s.%s" % (base, opts.suffix))
def write_directive(module):
"""Create the automodule directive and add the options"""
directive = '.. automodule:: %s\n' % module
for option in options:
directive += ' :%s:\n' % option
return directive
def write_heading(module, kind='Module'):
"""Create the page heading."""
module = module.title()
heading = title_line(module + ' Documentation', '=')
heading += 'This page contains the %s %s documentation.\n\n' % (module, kind)
return heading
def write_sub(module, kind='Module'):
"""Create the module subtitle"""
sub = title_line('The :mod:`%s` %s' % (module, kind), '-')
return sub
def title_line(title, char):
""" Underline the title with the character pass, with the right length."""
return '%s\n%s\n\n' % (title, len(title) * char)
def create_module_file(root, module, opts):
"""Build the text of the file and write the file."""
name = create_file_name(module, opts)
if not opts.force and os.path.isfile(name):
print 'File %s already exists.' % name
elif check_for_code('%s/%s.py' % (root, module)): # don't build the file if there's no code in it
print 'Creating file %s for module.' % name
text = write_heading(module)
text += write_sub(module)
text += write_directive(module)
# write the file
if not opts.dryrun:
fd = open(name, 'w')
fd.write(text)
fd.close()
def create_package_file(root, subroot, py_files, opts, subs=None):
"""Build the text of the file and write the file."""
package = root.rpartition('/')[2].lower()
name = create_file_name(subroot, opts)
if not opts.force and os.path.isfile(name):
print 'File %s already exists.' % name
else:
print 'Creating file %s for package.' % name
text = write_heading(package, 'Package')
if subs == None:
subs = []
else:
# build a list of directories that are package (they contain an __init_.py file)
subs = [sub for sub in subs if os.path.isfile('%s/%s/__init__.py' % (root, sub))]
# if there's some package directories, add a TOC for theses subpackages
if subs:
text += title_line('Subpackages', '-')
text += '.. toctree::\n\n'
for sub in subs:
text += ' %s.%s\n' % (subroot, sub)
text += '\n'
# add each package's module
for py_file in py_files:
if not check_for_code('%s/%s' % (root, py_file)):
# don't build the file if there's no code in it
continue
py_file = py_file[:-3]
py_path = '%s.%s' % (subroot, py_file)
kind = "Module"
if py_file == '__init__':
kind = "Package"
text += write_sub(kind == 'Package' and package or py_file, kind)
text += write_directive(kind == "Package" and subroot or py_path)
text += '\n'
# write the file
if not opts.dryrun:
fd = open(name, 'w')
fd.write(text)
fd.close()
def check_for_code(module):
"""
Check if there's at least one class or one function in the module.
"""
fd = open(module, 'r')
for line in fd:
if line.startswith('def ') or line.startswith('class '):
fd.close()
return True
fd.close()
return False
def recurse_tree(path, excludes, opts):
"""
Look for every file in the directory tree and create the corresponding
ReST files.
"""
toc = []
excludes = format_excludes(path, excludes)
tree = os.walk(path, False)
for root, subs, files in tree:
# keep only the Python script files
py_files = check_py_file(files)
# remove hidden ('.') and private ('_') directories
subs = [sub for sub in subs if sub[0] not in ['.', '_']]
# check if there's valid files to process
if "/." in root or "/_" in root \
or not py_files \
or check_excludes(root, excludes):
continue
subroot = root[len(path):].lstrip('/').replace('/', '.')
if root == path:
# we are at the root level so we create only modules
for py_file in py_files:
module = py_file[:-3]
create_module_file(root, module, opts)
toc.append(module)
elif not subs and "__init__.py" in py_files:
# we are in a package without sub package
create_package_file(root, subroot, py_files, opts=opts)
toc.append(subroot)
elif "__init__.py" in py_files:
# we are in package with subpackage(s)
create_package_file(root, subroot, py_files, opts, subs)
toc.append(subroot)
# create the module's index
if not opts.notoc:
modules_toc(toc, opts)
def modules_toc(modules, opts, name='modules'):
"""
Create the module's index.
"""
fname = create_file_name(name, opts)
if not opts.force and os.path.exists(fname):
print "File %s already exists." % name
return
print "Creating module's index modules.txt."
text = write_heading(opts.header, 'Modules')
text += title_line('Modules:', '-')
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 the file
if not opts.dryrun:
fd = open(fname, 'w')
fd.write(text)
fd.close()
def format_excludes(path, excludes):
"""
Format the excluded directory list.
(verify that the path is not from the root of the volume or the root of the
package)
"""
f_excludes = []
for exclude in excludes:
if exclude[0] != '/' and exclude[:len(path)] != path:
exclude = '%s/%s' % (path, exclude)
# remove trailing slash
f_excludes.append(exclude.rstrip('/'))
return f_excludes
def check_excludes(root, excludes):
"""
Check if the directory is in the exclude list.
"""
for exclude in excludes:
if root[:len(exclude)] == exclude:
return True
return False
def check_py_file(files):
"""
Return a list with only the python scripts (remove all other files).
"""
py_files = [fich for fich in files if fich[-3:] == '.py']
return py_files
if __name__ == '__main__':
parser = optparse.OptionParser(usage="""usage: %prog [options] <package path> [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 len(args) < 1:
parser.error("package path is required.")
else:
if os.path.isdir(args[0]):
# if there's some exclude arguments, build the list of excludes
excludes = args[1:]
recurse_tree(args[0], excludes, opts)
else:
print '%s is not a valid directory.' % args
Jump to Line
Something went wrong with that request. Please try again.