forked from web2py/web2py
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgenerate_modules.py
executable file
·258 lines (227 loc) · 9.4 KB
/
generate_modules.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
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