##// END OF EJS Templates
Autodocument classes defined inside if blocks, such as in py3compat
Thomas Kluyver -
Show More
@@ -1,406 +1,414 b''
1 """Attempt to generate templates for module reference with Sphinx
1 """Attempt to generate templates for module reference with Sphinx
2
2
3 XXX - we exclude extension modules
3 XXX - we exclude extension modules
4
4
5 To include extension modules, first identify them as valid in the
5 To include extension modules, first identify them as valid in the
6 ``_uri2path`` method, then handle them in the ``_parse_module`` script.
6 ``_uri2path`` method, then handle them in the ``_parse_module`` script.
7
7
8 We get functions and classes by parsing the text of .py files.
8 We get functions and classes by parsing the text of .py files.
9 Alternatively we could import the modules for discovery, and we'd have
9 Alternatively we could import the modules for discovery, and we'd have
10 to do that for extension modules. This would involve changing the
10 to do that for extension modules. This would involve changing the
11 ``_parse_module`` method to work via import and introspection, and
11 ``_parse_module`` method to work via import and introspection, and
12 might involve changing ``discover_modules`` (which determines which
12 might involve changing ``discover_modules`` (which determines which
13 files are modules, and therefore which module URIs will be passed to
13 files are modules, and therefore which module URIs will be passed to
14 ``_parse_module``).
14 ``_parse_module``).
15
15
16 NOTE: this is a modified version of a script originally shipped with the
16 NOTE: this is a modified version of a script originally shipped with the
17 PyMVPA project, which we've adapted for NIPY use. PyMVPA is an MIT-licensed
17 PyMVPA project, which we've adapted for NIPY use. PyMVPA is an MIT-licensed
18 project."""
18 project."""
19
19
20 # Stdlib imports
20 # Stdlib imports
21 import ast
21 import ast
22 import os
22 import os
23 import re
23 import re
24
24
25 class Obj(object):
25 class Obj(object):
26 '''Namespace to hold arbitrary information.'''
26 '''Namespace to hold arbitrary information.'''
27 def __init__(self, **kwargs):
27 def __init__(self, **kwargs):
28 for k, v in kwargs.items():
28 for k, v in kwargs.items():
29 setattr(self, k, v)
29 setattr(self, k, v)
30
31 class FuncClsScanner(ast.NodeVisitor):
32 """Scan a module for top-level functions and classes.
33
34 Skips objects with an @undoc decorator, or a name starting with '_'.
35 """
36 def __init__(self):
37 ast.NodeVisitor.__init__(self)
38 self.classes = []
39 self.classes_seen = set()
40 self.functions = []
41
42 @staticmethod
43 def has_undoc_decorator(node):
44 return any(isinstance(d, ast.Name) and d.id == 'undoc' \
45 for d in node.decorator_list)
46
47 def visit_FunctionDef(self, node):
48 if not (node.name.startswith('_') or self.has_undoc_decorator(node)) \
49 and node.name not in self.functions:
50 self.functions.append(node.name)
51
52 def visit_ClassDef(self, node):
53 if not (node.name.startswith('_') or self.has_undoc_decorator(node)) \
54 and node.name not in self.classes_seen:
55 cls = Obj(name=node.name)
56 cls.has_init = any(isinstance(n, ast.FunctionDef) and \
57 n.name=='__init__' for n in node.body)
58 self.classes.append(cls)
59 self.classes_seen.add(node.name)
60
61 def scan(self, mod):
62 self.visit(mod)
63 return self.functions, self.classes
30
64
31 # Functions and classes
65 # Functions and classes
32 class ApiDocWriter(object):
66 class ApiDocWriter(object):
33 ''' Class for automatic detection and parsing of API docs
67 ''' Class for automatic detection and parsing of API docs
34 to Sphinx-parsable reST format'''
68 to Sphinx-parsable reST format'''
35
69
36 # only separating first two levels
70 # only separating first two levels
37 rst_section_levels = ['*', '=', '-', '~', '^']
71 rst_section_levels = ['*', '=', '-', '~', '^']
38
72
39 def __init__(self,
73 def __init__(self,
40 package_name,
74 package_name,
41 rst_extension='.rst',
75 rst_extension='.rst',
42 package_skip_patterns=None,
76 package_skip_patterns=None,
43 module_skip_patterns=None,
77 module_skip_patterns=None,
44 ):
78 ):
45 ''' Initialize package for parsing
79 ''' Initialize package for parsing
46
80
47 Parameters
81 Parameters
48 ----------
82 ----------
49 package_name : string
83 package_name : string
50 Name of the top-level package. *package_name* must be the
84 Name of the top-level package. *package_name* must be the
51 name of an importable package
85 name of an importable package
52 rst_extension : string, optional
86 rst_extension : string, optional
53 Extension for reST files, default '.rst'
87 Extension for reST files, default '.rst'
54 package_skip_patterns : None or sequence of {strings, regexps}
88 package_skip_patterns : None or sequence of {strings, regexps}
55 Sequence of strings giving URIs of packages to be excluded
89 Sequence of strings giving URIs of packages to be excluded
56 Operates on the package path, starting at (including) the
90 Operates on the package path, starting at (including) the
57 first dot in the package path, after *package_name* - so,
91 first dot in the package path, after *package_name* - so,
58 if *package_name* is ``sphinx``, then ``sphinx.util`` will
92 if *package_name* is ``sphinx``, then ``sphinx.util`` will
59 result in ``.util`` being passed for earching by these
93 result in ``.util`` being passed for earching by these
60 regexps. If is None, gives default. Default is:
94 regexps. If is None, gives default. Default is:
61 ['\.tests$']
95 ['\.tests$']
62 module_skip_patterns : None or sequence
96 module_skip_patterns : None or sequence
63 Sequence of strings giving URIs of modules to be excluded
97 Sequence of strings giving URIs of modules to be excluded
64 Operates on the module name including preceding URI path,
98 Operates on the module name including preceding URI path,
65 back to the first dot after *package_name*. For example
99 back to the first dot after *package_name*. For example
66 ``sphinx.util.console`` results in the string to search of
100 ``sphinx.util.console`` results in the string to search of
67 ``.util.console``
101 ``.util.console``
68 If is None, gives default. Default is:
102 If is None, gives default. Default is:
69 ['\.setup$', '\._']
103 ['\.setup$', '\._']
70 '''
104 '''
71 if package_skip_patterns is None:
105 if package_skip_patterns is None:
72 package_skip_patterns = ['\\.tests$']
106 package_skip_patterns = ['\\.tests$']
73 if module_skip_patterns is None:
107 if module_skip_patterns is None:
74 module_skip_patterns = ['\\.setup$', '\\._']
108 module_skip_patterns = ['\\.setup$', '\\._']
75 self.package_name = package_name
109 self.package_name = package_name
76 self.rst_extension = rst_extension
110 self.rst_extension = rst_extension
77 self.package_skip_patterns = package_skip_patterns
111 self.package_skip_patterns = package_skip_patterns
78 self.module_skip_patterns = module_skip_patterns
112 self.module_skip_patterns = module_skip_patterns
79
113
80 def get_package_name(self):
114 def get_package_name(self):
81 return self._package_name
115 return self._package_name
82
116
83 def set_package_name(self, package_name):
117 def set_package_name(self, package_name):
84 ''' Set package_name
118 ''' Set package_name
85
119
86 >>> docwriter = ApiDocWriter('sphinx')
120 >>> docwriter = ApiDocWriter('sphinx')
87 >>> import sphinx
121 >>> import sphinx
88 >>> docwriter.root_path == sphinx.__path__[0]
122 >>> docwriter.root_path == sphinx.__path__[0]
89 True
123 True
90 >>> docwriter.package_name = 'docutils'
124 >>> docwriter.package_name = 'docutils'
91 >>> import docutils
125 >>> import docutils
92 >>> docwriter.root_path == docutils.__path__[0]
126 >>> docwriter.root_path == docutils.__path__[0]
93 True
127 True
94 '''
128 '''
95 # It's also possible to imagine caching the module parsing here
129 # It's also possible to imagine caching the module parsing here
96 self._package_name = package_name
130 self._package_name = package_name
97 self.root_module = __import__(package_name)
131 self.root_module = __import__(package_name)
98 self.root_path = self.root_module.__path__[0]
132 self.root_path = self.root_module.__path__[0]
99 self.written_modules = None
133 self.written_modules = None
100
134
101 package_name = property(get_package_name, set_package_name, None,
135 package_name = property(get_package_name, set_package_name, None,
102 'get/set package_name')
136 'get/set package_name')
103
137
104 def _uri2path(self, uri):
138 def _uri2path(self, uri):
105 ''' Convert uri to absolute filepath
139 ''' Convert uri to absolute filepath
106
140
107 Parameters
141 Parameters
108 ----------
142 ----------
109 uri : string
143 uri : string
110 URI of python module to return path for
144 URI of python module to return path for
111
145
112 Returns
146 Returns
113 -------
147 -------
114 path : None or string
148 path : None or string
115 Returns None if there is no valid path for this URI
149 Returns None if there is no valid path for this URI
116 Otherwise returns absolute file system path for URI
150 Otherwise returns absolute file system path for URI
117
151
118 Examples
152 Examples
119 --------
153 --------
120 >>> docwriter = ApiDocWriter('sphinx')
154 >>> docwriter = ApiDocWriter('sphinx')
121 >>> import sphinx
155 >>> import sphinx
122 >>> modpath = sphinx.__path__[0]
156 >>> modpath = sphinx.__path__[0]
123 >>> res = docwriter._uri2path('sphinx.builder')
157 >>> res = docwriter._uri2path('sphinx.builder')
124 >>> res == os.path.join(modpath, 'builder.py')
158 >>> res == os.path.join(modpath, 'builder.py')
125 True
159 True
126 >>> res = docwriter._uri2path('sphinx')
160 >>> res = docwriter._uri2path('sphinx')
127 >>> res == os.path.join(modpath, '__init__.py')
161 >>> res == os.path.join(modpath, '__init__.py')
128 True
162 True
129 >>> docwriter._uri2path('sphinx.does_not_exist')
163 >>> docwriter._uri2path('sphinx.does_not_exist')
130
164
131 '''
165 '''
132 if uri == self.package_name:
166 if uri == self.package_name:
133 return os.path.join(self.root_path, '__init__.py')
167 return os.path.join(self.root_path, '__init__.py')
134 path = uri.replace('.', os.path.sep)
168 path = uri.replace('.', os.path.sep)
135 path = path.replace(self.package_name + os.path.sep, '')
169 path = path.replace(self.package_name + os.path.sep, '')
136 path = os.path.join(self.root_path, path)
170 path = os.path.join(self.root_path, path)
137 # XXX maybe check for extensions as well?
171 # XXX maybe check for extensions as well?
138 if os.path.exists(path + '.py'): # file
172 if os.path.exists(path + '.py'): # file
139 path += '.py'
173 path += '.py'
140 elif os.path.exists(os.path.join(path, '__init__.py')):
174 elif os.path.exists(os.path.join(path, '__init__.py')):
141 path = os.path.join(path, '__init__.py')
175 path = os.path.join(path, '__init__.py')
142 else:
176 else:
143 return None
177 return None
144 return path
178 return path
145
179
146 def _path2uri(self, dirpath):
180 def _path2uri(self, dirpath):
147 ''' Convert directory path to uri '''
181 ''' Convert directory path to uri '''
148 relpath = dirpath.replace(self.root_path, self.package_name)
182 relpath = dirpath.replace(self.root_path, self.package_name)
149 if relpath.startswith(os.path.sep):
183 if relpath.startswith(os.path.sep):
150 relpath = relpath[1:]
184 relpath = relpath[1:]
151 return relpath.replace(os.path.sep, '.')
185 return relpath.replace(os.path.sep, '.')
152
186
153 def _parse_module(self, uri):
187 def _parse_module(self, uri):
154 ''' Parse module defined in *uri* '''
188 ''' Parse module defined in *uri* '''
155 filename = self._uri2path(uri)
189 filename = self._uri2path(uri)
156 if filename is None:
190 if filename is None:
157 # nothing that we could handle here.
191 # nothing that we could handle here.
158 return ([],[])
192 return ([],[])
159 with open(filename, 'rb') as f:
193 with open(filename, 'rb') as f:
160 mod = ast.parse(f.read())
194 mod = ast.parse(f.read())
161 return self._find_functions_classes(mod)
195 return FuncClsScanner().scan(mod)
162
163 @staticmethod
164 def _find_functions_classes(mod):
165 """Extract top-level functions and classes from a module AST.
166
167 Skips objects with an @undoc decorator, or a name starting with '_'.
168 """
169 def has_undoc_decorator(node):
170 return any(isinstance(d, ast.Name) and d.id == 'undoc' \
171 for d in node.decorator_list)
172
173 functions, classes = [], []
174 for node in mod.body:
175 if isinstance(node, ast.FunctionDef) and \
176 not node.name.startswith('_') and \
177 not has_undoc_decorator(node):
178 functions.append(node.name)
179 elif isinstance(node, ast.ClassDef) and \
180 not node.name.startswith('_') and \
181 not has_undoc_decorator(node):
182 cls = Obj(name=node.name)
183 cls.has_init = any(isinstance(n, ast.FunctionDef) and \
184 n.name=='__init__' for n in node.body)
185 classes.append(cls)
186
187 return functions, classes
188
196
189 def generate_api_doc(self, uri):
197 def generate_api_doc(self, uri):
190 '''Make autodoc documentation template string for a module
198 '''Make autodoc documentation template string for a module
191
199
192 Parameters
200 Parameters
193 ----------
201 ----------
194 uri : string
202 uri : string
195 python location of module - e.g 'sphinx.builder'
203 python location of module - e.g 'sphinx.builder'
196
204
197 Returns
205 Returns
198 -------
206 -------
199 S : string
207 S : string
200 Contents of API doc
208 Contents of API doc
201 '''
209 '''
202 # get the names of all classes and functions
210 # get the names of all classes and functions
203 functions, classes = self._parse_module(uri)
211 functions, classes = self._parse_module(uri)
204 if not len(functions) and not len(classes):
212 if not len(functions) and not len(classes):
205 print 'WARNING: Empty -',uri # dbg
213 print 'WARNING: Empty -',uri # dbg
206 return ''
214 return ''
207
215
208 # Make a shorter version of the uri that omits the package name for
216 # Make a shorter version of the uri that omits the package name for
209 # titles
217 # titles
210 uri_short = re.sub(r'^%s\.' % self.package_name,'',uri)
218 uri_short = re.sub(r'^%s\.' % self.package_name,'',uri)
211
219
212 ad = '.. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n'
220 ad = '.. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n'
213
221
214 # Set the chapter title to read 'Module:' for all modules except for the
222 # Set the chapter title to read 'Module:' for all modules except for the
215 # main packages
223 # main packages
216 if '.' in uri:
224 if '.' in uri:
217 chap_title = 'Module: :mod:`' + uri_short + '`'
225 chap_title = 'Module: :mod:`' + uri_short + '`'
218 else:
226 else:
219 chap_title = ':mod:`' + uri_short + '`'
227 chap_title = ':mod:`' + uri_short + '`'
220 ad += chap_title + '\n' + self.rst_section_levels[1] * len(chap_title)
228 ad += chap_title + '\n' + self.rst_section_levels[1] * len(chap_title)
221
229
222 ad += '\n.. automodule:: ' + uri + '\n'
230 ad += '\n.. automodule:: ' + uri + '\n'
223 ad += '\n.. currentmodule:: ' + uri + '\n'
231 ad += '\n.. currentmodule:: ' + uri + '\n'
224
232
225 if classes:
233 if classes:
226 subhead = str(len(classes)) + (' Classes' if len(classes) > 1 else ' Class')
234 subhead = str(len(classes)) + (' Classes' if len(classes) > 1 else ' Class')
227 ad += '\n'+ subhead + '\n' + \
235 ad += '\n'+ subhead + '\n' + \
228 self.rst_section_levels[2] * len(subhead) + '\n'
236 self.rst_section_levels[2] * len(subhead) + '\n'
229
237
230 for c in classes:
238 for c in classes:
231 ad += '\n.. autoclass:: ' + c.name + '\n'
239 ad += '\n.. autoclass:: ' + c.name + '\n'
232 # must NOT exclude from index to keep cross-refs working
240 # must NOT exclude from index to keep cross-refs working
233 ad += ' :members:\n' \
241 ad += ' :members:\n' \
234 ' :show-inheritance:\n'
242 ' :show-inheritance:\n'
235 if c.has_init:
243 if c.has_init:
236 ad += '\n .. automethod:: __init__\n'
244 ad += '\n .. automethod:: __init__\n'
237
245
238 if functions:
246 if functions:
239 subhead = str(len(functions)) + (' Functions' if len(functions) > 1 else ' Function')
247 subhead = str(len(functions)) + (' Functions' if len(functions) > 1 else ' Function')
240 ad += '\n'+ subhead + '\n' + \
248 ad += '\n'+ subhead + '\n' + \
241 self.rst_section_levels[2] * len(subhead) + '\n'
249 self.rst_section_levels[2] * len(subhead) + '\n'
242 for f in functions:
250 for f in functions:
243 # must NOT exclude from index to keep cross-refs working
251 # must NOT exclude from index to keep cross-refs working
244 ad += '\n.. autofunction:: ' + uri + '.' + f + '\n\n'
252 ad += '\n.. autofunction:: ' + uri + '.' + f + '\n\n'
245 return ad
253 return ad
246
254
247 def _survives_exclude(self, matchstr, match_type):
255 def _survives_exclude(self, matchstr, match_type):
248 ''' Returns True if *matchstr* does not match patterns
256 ''' Returns True if *matchstr* does not match patterns
249
257
250 ``self.package_name`` removed from front of string if present
258 ``self.package_name`` removed from front of string if present
251
259
252 Examples
260 Examples
253 --------
261 --------
254 >>> dw = ApiDocWriter('sphinx')
262 >>> dw = ApiDocWriter('sphinx')
255 >>> dw._survives_exclude('sphinx.okpkg', 'package')
263 >>> dw._survives_exclude('sphinx.okpkg', 'package')
256 True
264 True
257 >>> dw.package_skip_patterns.append('^\\.badpkg$')
265 >>> dw.package_skip_patterns.append('^\\.badpkg$')
258 >>> dw._survives_exclude('sphinx.badpkg', 'package')
266 >>> dw._survives_exclude('sphinx.badpkg', 'package')
259 False
267 False
260 >>> dw._survives_exclude('sphinx.badpkg', 'module')
268 >>> dw._survives_exclude('sphinx.badpkg', 'module')
261 True
269 True
262 >>> dw._survives_exclude('sphinx.badmod', 'module')
270 >>> dw._survives_exclude('sphinx.badmod', 'module')
263 True
271 True
264 >>> dw.module_skip_patterns.append('^\\.badmod$')
272 >>> dw.module_skip_patterns.append('^\\.badmod$')
265 >>> dw._survives_exclude('sphinx.badmod', 'module')
273 >>> dw._survives_exclude('sphinx.badmod', 'module')
266 False
274 False
267 '''
275 '''
268 if match_type == 'module':
276 if match_type == 'module':
269 patterns = self.module_skip_patterns
277 patterns = self.module_skip_patterns
270 elif match_type == 'package':
278 elif match_type == 'package':
271 patterns = self.package_skip_patterns
279 patterns = self.package_skip_patterns
272 else:
280 else:
273 raise ValueError('Cannot interpret match type "%s"'
281 raise ValueError('Cannot interpret match type "%s"'
274 % match_type)
282 % match_type)
275 # Match to URI without package name
283 # Match to URI without package name
276 L = len(self.package_name)
284 L = len(self.package_name)
277 if matchstr[:L] == self.package_name:
285 if matchstr[:L] == self.package_name:
278 matchstr = matchstr[L:]
286 matchstr = matchstr[L:]
279 for pat in patterns:
287 for pat in patterns:
280 try:
288 try:
281 pat.search
289 pat.search
282 except AttributeError:
290 except AttributeError:
283 pat = re.compile(pat)
291 pat = re.compile(pat)
284 if pat.search(matchstr):
292 if pat.search(matchstr):
285 return False
293 return False
286 return True
294 return True
287
295
288 def discover_modules(self):
296 def discover_modules(self):
289 ''' Return module sequence discovered from ``self.package_name``
297 ''' Return module sequence discovered from ``self.package_name``
290
298
291
299
292 Parameters
300 Parameters
293 ----------
301 ----------
294 None
302 None
295
303
296 Returns
304 Returns
297 -------
305 -------
298 mods : sequence
306 mods : sequence
299 Sequence of module names within ``self.package_name``
307 Sequence of module names within ``self.package_name``
300
308
301 Examples
309 Examples
302 --------
310 --------
303 >>> dw = ApiDocWriter('sphinx')
311 >>> dw = ApiDocWriter('sphinx')
304 >>> mods = dw.discover_modules()
312 >>> mods = dw.discover_modules()
305 >>> 'sphinx.util' in mods
313 >>> 'sphinx.util' in mods
306 True
314 True
307 >>> dw.package_skip_patterns.append('\.util$')
315 >>> dw.package_skip_patterns.append('\.util$')
308 >>> 'sphinx.util' in dw.discover_modules()
316 >>> 'sphinx.util' in dw.discover_modules()
309 False
317 False
310 >>>
318 >>>
311 '''
319 '''
312 modules = [self.package_name]
320 modules = [self.package_name]
313 # raw directory parsing
321 # raw directory parsing
314 for dirpath, dirnames, filenames in os.walk(self.root_path):
322 for dirpath, dirnames, filenames in os.walk(self.root_path):
315 # Check directory names for packages
323 # Check directory names for packages
316 root_uri = self._path2uri(os.path.join(self.root_path,
324 root_uri = self._path2uri(os.path.join(self.root_path,
317 dirpath))
325 dirpath))
318 for dirname in dirnames[:]: # copy list - we modify inplace
326 for dirname in dirnames[:]: # copy list - we modify inplace
319 package_uri = '.'.join((root_uri, dirname))
327 package_uri = '.'.join((root_uri, dirname))
320 if (self._uri2path(package_uri) and
328 if (self._uri2path(package_uri) and
321 self._survives_exclude(package_uri, 'package')):
329 self._survives_exclude(package_uri, 'package')):
322 modules.append(package_uri)
330 modules.append(package_uri)
323 else:
331 else:
324 dirnames.remove(dirname)
332 dirnames.remove(dirname)
325 # Check filenames for modules
333 # Check filenames for modules
326 for filename in filenames:
334 for filename in filenames:
327 module_name = filename[:-3]
335 module_name = filename[:-3]
328 module_uri = '.'.join((root_uri, module_name))
336 module_uri = '.'.join((root_uri, module_name))
329 if (self._uri2path(module_uri) and
337 if (self._uri2path(module_uri) and
330 self._survives_exclude(module_uri, 'module')):
338 self._survives_exclude(module_uri, 'module')):
331 modules.append(module_uri)
339 modules.append(module_uri)
332 return sorted(modules)
340 return sorted(modules)
333
341
334 def write_modules_api(self, modules,outdir):
342 def write_modules_api(self, modules,outdir):
335 # write the list
343 # write the list
336 written_modules = []
344 written_modules = []
337 for m in modules:
345 for m in modules:
338 api_str = self.generate_api_doc(m)
346 api_str = self.generate_api_doc(m)
339 if not api_str:
347 if not api_str:
340 continue
348 continue
341 # write out to file
349 # write out to file
342 outfile = os.path.join(outdir,
350 outfile = os.path.join(outdir,
343 m + self.rst_extension)
351 m + self.rst_extension)
344 fileobj = open(outfile, 'wt')
352 fileobj = open(outfile, 'wt')
345 fileobj.write(api_str)
353 fileobj.write(api_str)
346 fileobj.close()
354 fileobj.close()
347 written_modules.append(m)
355 written_modules.append(m)
348 self.written_modules = written_modules
356 self.written_modules = written_modules
349
357
350 def write_api_docs(self, outdir):
358 def write_api_docs(self, outdir):
351 """Generate API reST files.
359 """Generate API reST files.
352
360
353 Parameters
361 Parameters
354 ----------
362 ----------
355 outdir : string
363 outdir : string
356 Directory name in which to store files
364 Directory name in which to store files
357 We create automatic filenames for each module
365 We create automatic filenames for each module
358
366
359 Returns
367 Returns
360 -------
368 -------
361 None
369 None
362
370
363 Notes
371 Notes
364 -----
372 -----
365 Sets self.written_modules to list of written modules
373 Sets self.written_modules to list of written modules
366 """
374 """
367 if not os.path.exists(outdir):
375 if not os.path.exists(outdir):
368 os.mkdir(outdir)
376 os.mkdir(outdir)
369 # compose list of modules
377 # compose list of modules
370 modules = self.discover_modules()
378 modules = self.discover_modules()
371 self.write_modules_api(modules,outdir)
379 self.write_modules_api(modules,outdir)
372
380
373 def write_index(self, outdir, froot='gen', relative_to=None):
381 def write_index(self, outdir, froot='gen', relative_to=None):
374 """Make a reST API index file from written files
382 """Make a reST API index file from written files
375
383
376 Parameters
384 Parameters
377 ----------
385 ----------
378 path : string
386 path : string
379 Filename to write index to
387 Filename to write index to
380 outdir : string
388 outdir : string
381 Directory to which to write generated index file
389 Directory to which to write generated index file
382 froot : string, optional
390 froot : string, optional
383 root (filename without extension) of filename to write to
391 root (filename without extension) of filename to write to
384 Defaults to 'gen'. We add ``self.rst_extension``.
392 Defaults to 'gen'. We add ``self.rst_extension``.
385 relative_to : string
393 relative_to : string
386 path to which written filenames are relative. This
394 path to which written filenames are relative. This
387 component of the written file path will be removed from
395 component of the written file path will be removed from
388 outdir, in the generated index. Default is None, meaning,
396 outdir, in the generated index. Default is None, meaning,
389 leave path as it is.
397 leave path as it is.
390 """
398 """
391 if self.written_modules is None:
399 if self.written_modules is None:
392 raise ValueError('No modules written')
400 raise ValueError('No modules written')
393 # Get full filename path
401 # Get full filename path
394 path = os.path.join(outdir, froot+self.rst_extension)
402 path = os.path.join(outdir, froot+self.rst_extension)
395 # Path written into index is relative to rootpath
403 # Path written into index is relative to rootpath
396 if relative_to is not None:
404 if relative_to is not None:
397 relpath = outdir.replace(relative_to + os.path.sep, '')
405 relpath = outdir.replace(relative_to + os.path.sep, '')
398 else:
406 else:
399 relpath = outdir
407 relpath = outdir
400 idx = open(path,'wt')
408 idx = open(path,'wt')
401 w = idx.write
409 w = idx.write
402 w('.. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n')
410 w('.. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n')
403 w('.. toctree::\n\n')
411 w('.. toctree::\n\n')
404 for f in self.written_modules:
412 for f in self.written_modules:
405 w(' %s\n' % os.path.join(relpath,f))
413 w(' %s\n' % os.path.join(relpath,f))
406 idx.close()
414 idx.close()
General Comments 0
You need to be logged in to leave comments. Login now