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