initial commit
This commit is contained in:
Executable
+258
@@ -0,0 +1,258 @@
|
||||
#!/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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user