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