##// END OF EJS Templates
Add option to generate API docs based on __all__
Thomas Kluyver -
Show More
@@ -1,217 +1,223 b''
1 1 """The official API for working with notebooks in the current format version."""
2 2
3 3 from __future__ import print_function
4 4
5 5 from xml.etree import ElementTree as ET
6 6 import re
7 7
8 8 from IPython.utils.py3compat import unicode_type
9 9
10 10 from IPython.nbformat.v3 import (
11 11 NotebookNode,
12 12 new_code_cell, new_text_cell, new_notebook, new_output, new_worksheet,
13 13 parse_filename, new_metadata, new_author, new_heading_cell, nbformat,
14 14 nbformat_minor, nbformat_schema, to_notebook_json
15 15 )
16 16 from IPython.nbformat import v3 as _v_latest
17 17
18 18 from .reader import reads as reader_reads
19 19 from .reader import versions
20 20 from .convert import convert
21 21 from .validator import validate
22 22
23 23 from IPython.utils.log import get_logger
24 24
25 __all__ = ['NotebookNode', 'new_code_cell', 'new_text_cell', 'new_notebook',
26 'new_output', 'new_worksheet', 'parse_filename', 'new_metadata', 'new_author',
27 'new_heading_cell', 'nbformat', 'nbformat_minor', 'nbformat_schema',
28 'to_notebook_json', 'convert', 'validate', 'NBFormatError', 'parse_py',
29 'reads_json', 'writes_json', 'reads_py', 'writes_py', 'reads', 'writes', 'read',
30 'write']
25 31
26 32 current_nbformat = nbformat
27 33 current_nbformat_minor = nbformat_minor
28 34 current_nbformat_module = _v_latest.__name__
29 35
30 36
31 37 def docstring_nbformat_mod(func):
32 38 """Decorator for docstrings referring to classes/functions accessed through
33 39 nbformat.current.
34 40
35 41 Put {nbformat_mod} in the docstring in place of 'IPython.nbformat.v3'.
36 42 """
37 43 func.__doc__ = func.__doc__.format(nbformat_mod=current_nbformat_module)
38 44 return func
39 45
40 46
41 47 class NBFormatError(ValueError):
42 48 pass
43 49
44 50
45 51 def parse_py(s, **kwargs):
46 52 """Parse a string into a (nbformat, string) tuple."""
47 53 nbf = current_nbformat
48 54 nbm = current_nbformat_minor
49 55
50 56 pattern = r'# <nbformat>(?P<nbformat>\d+[\.\d+]*)</nbformat>'
51 57 m = re.search(pattern,s)
52 58 if m is not None:
53 59 digits = m.group('nbformat').split('.')
54 60 nbf = int(digits[0])
55 61 if len(digits) > 1:
56 62 nbm = int(digits[1])
57 63
58 64 return nbf, nbm, s
59 65
60 66
61 67 def reads_json(nbjson, **kwargs):
62 68 """Read a JSON notebook from a string and return the NotebookNode
63 69 object. Report if any JSON format errors are detected.
64 70
65 71 """
66 72 nb = reader_reads(nbjson, **kwargs)
67 73 nb_current = convert(nb, current_nbformat)
68 74 errors = validate(nb_current)
69 75 if errors:
70 76 get_logger().error(
71 77 "Notebook JSON is invalid (%d errors detected during read)",
72 78 len(errors))
73 79 return nb_current
74 80
75 81
76 82 def writes_json(nb, **kwargs):
77 83 """Take a NotebookNode object and write out a JSON string. Report if
78 84 any JSON format errors are detected.
79 85
80 86 """
81 87 errors = validate(nb)
82 88 if errors:
83 89 get_logger().error(
84 90 "Notebook JSON is invalid (%d errors detected during write)",
85 91 len(errors))
86 92 nbjson = versions[current_nbformat].writes_json(nb, **kwargs)
87 93 return nbjson
88 94
89 95
90 96 def reads_py(s, **kwargs):
91 97 """Read a .py notebook from a string and return the NotebookNode object."""
92 98 nbf, nbm, s = parse_py(s, **kwargs)
93 99 if nbf in (2, 3):
94 100 nb = versions[nbf].to_notebook_py(s, **kwargs)
95 101 else:
96 102 raise NBFormatError('Unsupported PY nbformat version: %i' % nbf)
97 103 return nb
98 104
99 105
100 106 def writes_py(nb, **kwargs):
101 107 # nbformat 3 is the latest format that supports py
102 108 return versions[3].writes_py(nb, **kwargs)
103 109
104 110
105 111 # High level API
106 112
107 113
108 114 def reads(s, format, **kwargs):
109 115 """Read a notebook from a string and return the NotebookNode object.
110 116
111 117 This function properly handles notebooks of any version. The notebook
112 118 returned will always be in the current version's format.
113 119
114 120 Parameters
115 121 ----------
116 122 s : unicode
117 123 The raw unicode string to read the notebook from.
118 124 format : (u'json', u'ipynb', u'py')
119 125 The format that the string is in.
120 126
121 127 Returns
122 128 -------
123 129 nb : NotebookNode
124 130 The notebook that was read.
125 131 """
126 132 format = unicode_type(format)
127 133 if format == u'json' or format == u'ipynb':
128 134 return reads_json(s, **kwargs)
129 135 elif format == u'py':
130 136 return reads_py(s, **kwargs)
131 137 else:
132 138 raise NBFormatError('Unsupported format: %s' % format)
133 139
134 140
135 141 def writes(nb, format, **kwargs):
136 142 """Write a notebook to a string in a given format in the current nbformat version.
137 143
138 144 This function always writes the notebook in the current nbformat version.
139 145
140 146 Parameters
141 147 ----------
142 148 nb : NotebookNode
143 149 The notebook to write.
144 150 format : (u'json', u'ipynb', u'py')
145 151 The format to write the notebook in.
146 152
147 153 Returns
148 154 -------
149 155 s : unicode
150 156 The notebook string.
151 157 """
152 158 format = unicode_type(format)
153 159 if format == u'json' or format == u'ipynb':
154 160 return writes_json(nb, **kwargs)
155 161 elif format == u'py':
156 162 return writes_py(nb, **kwargs)
157 163 else:
158 164 raise NBFormatError('Unsupported format: %s' % format)
159 165
160 166
161 167 def read(fp, format, **kwargs):
162 168 """Read a notebook from a file and return the NotebookNode object.
163 169
164 170 This function properly handles notebooks of any version. The notebook
165 171 returned will always be in the current version's format.
166 172
167 173 Parameters
168 174 ----------
169 175 fp : file
170 176 Any file-like object with a read method.
171 177 format : (u'json', u'ipynb', u'py')
172 178 The format that the string is in.
173 179
174 180 Returns
175 181 -------
176 182 nb : NotebookNode
177 183 The notebook that was read.
178 184 """
179 185 return reads(fp.read(), format, **kwargs)
180 186
181 187
182 188 def write(nb, fp, format, **kwargs):
183 189 """Write a notebook to a file in a given format in the current nbformat version.
184 190
185 191 This function always writes the notebook in the current nbformat version.
186 192
187 193 Parameters
188 194 ----------
189 195 nb : NotebookNode
190 196 The notebook to write.
191 197 fp : file
192 198 Any file-like object with a write method.
193 199 format : (u'json', u'ipynb', u'py')
194 200 The format to write the notebook in.
195 201
196 202 Returns
197 203 -------
198 204 s : unicode
199 205 The notebook string.
200 206 """
201 207 return fp.write(writes(nb, format, **kwargs))
202 208
203 209 def _convert_to_metadata():
204 210 """Convert to a notebook having notebook metadata."""
205 211 import glob
206 212 for fname in glob.glob('*.ipynb'):
207 213 print('Converting file:',fname)
208 214 with open(fname,'r') as f:
209 215 nb = read(f,u'json')
210 216 md = new_metadata()
211 217 if u'name' in nb:
212 218 md.name = nb.name
213 219 del nb[u'name']
214 220 nb.metadata = md
215 221 with open(fname,'w') as f:
216 222 write(nb, f, u'json')
217 223
@@ -1,45 +1,58 b''
1 1 #!/usr/bin/env python
2 2 """Script to auto-generate our API docs.
3 3 """
4 4 # stdlib imports
5 5 import os
6 6 import sys
7 7
8 8 # local imports
9 9 sys.path.append(os.path.abspath('sphinxext'))
10 10 from apigen import ApiDocWriter
11 11
12 12 #*****************************************************************************
13 13 if __name__ == '__main__':
14 14 pjoin = os.path.join
15 15 package = 'IPython'
16 16 outdir = pjoin('source','api','generated')
17 17 docwriter = ApiDocWriter(package,rst_extension='.rst')
18 18 # You have to escape the . here because . is a special char for regexps.
19 19 # You must do make clean if you change this!
20 20 docwriter.package_skip_patterns += [r'\.external$',
21 21 # Extensions are documented elsewhere.
22 22 r'\.extensions',
23 23 r'\.config\.profile',
24 # These should be accessed via nbformat.current
25 r'\.nbformat\.v\d+',
24 26 ]
25 27
26 28 # The inputhook* modules often cause problems on import, such as trying to
27 29 # load incompatible Qt bindings. It's easiest to leave them all out. The
28 30 # main API is in the inputhook module, which is documented.
29 31 docwriter.module_skip_patterns += [ r'\.lib\.inputhook.+',
30 32 r'\.ipdoctest',
31 33 r'\.testing\.plugin',
32 34 # This just prints a deprecation msg:
33 35 r'\.frontend$',
34 36 # We document this manually.
35 37 r'\.utils\.py3compat',
38 # These are exposed by nbformat.current
39 r'\.nbformat\.convert',
40 r'\.nbformat\.validator',
36 41 ]
42
43 # These modules import functions and classes from other places to expose
44 # them as part of the public API. They must have __all__ defined. The
45 # non-API modules they import from should be excluded by the skip patterns
46 # above.
47 docwriter.names_from__all__.update({
48 'IPython.nbformat.current',
49 })
37 50
38 51 # Now, generate the outputs
39 52 docwriter.write_api_docs(outdir)
40 53 # Write index with .txt extension - we can include it, but Sphinx won't try
41 54 # to compile it
42 55 docwriter.write_index(outdir, 'gen.txt',
43 56 relative_to = pjoin('source','api')
44 57 )
45 58 print ('%d files written' % len(docwriter.written_modules))
@@ -1,422 +1,454 b''
1 1 """Attempt to generate templates for module reference with Sphinx
2 2
3 3 XXX - we exclude extension modules
4 4
5 5 To include extension modules, first identify them as valid in the
6 6 ``_uri2path`` method, then handle them in the ``_parse_module`` script.
7 7
8 8 We get functions and classes by parsing the text of .py files.
9 9 Alternatively we could import the modules for discovery, and we'd have
10 10 to do that for extension modules. This would involve changing the
11 11 ``_parse_module`` method to work via import and introspection, and
12 12 might involve changing ``discover_modules`` (which determines which
13 13 files are modules, and therefore which module URIs will be passed to
14 14 ``_parse_module``).
15 15
16 16 NOTE: this is a modified version of a script originally shipped with the
17 17 PyMVPA project, which we've adapted for NIPY use. PyMVPA is an MIT-licensed
18 18 project."""
19 19
20 20 from __future__ import print_function
21 21
22 22 # Stdlib imports
23 23 import ast
24 import inspect
24 25 import os
25 26 import re
26 27
27 28 class Obj(object):
28 29 '''Namespace to hold arbitrary information.'''
29 30 def __init__(self, **kwargs):
30 31 for k, v in kwargs.items():
31 32 setattr(self, k, v)
32 33
33 34 class FuncClsScanner(ast.NodeVisitor):
34 35 """Scan a module for top-level functions and classes.
35 36
36 37 Skips objects with an @undoc decorator, or a name starting with '_'.
37 38 """
38 39 def __init__(self):
39 40 ast.NodeVisitor.__init__(self)
40 41 self.classes = []
41 42 self.classes_seen = set()
42 43 self.functions = []
43 44
44 45 @staticmethod
45 46 def has_undoc_decorator(node):
46 47 return any(isinstance(d, ast.Name) and d.id == 'undoc' \
47 48 for d in node.decorator_list)
48 49
49 50 def visit_If(self, node):
50 51 if isinstance(node.test, ast.Compare) \
51 52 and isinstance(node.test.left, ast.Name) \
52 53 and node.test.left.id == '__name__':
53 54 return # Ignore classes defined in "if __name__ == '__main__':"
54 55
55 56 self.generic_visit(node)
56 57
57 58 def visit_FunctionDef(self, node):
58 59 if not (node.name.startswith('_') or self.has_undoc_decorator(node)) \
59 60 and node.name not in self.functions:
60 61 self.functions.append(node.name)
61 62
62 63 def visit_ClassDef(self, node):
63 64 if not (node.name.startswith('_') or self.has_undoc_decorator(node)) \
64 65 and node.name not in self.classes_seen:
65 66 cls = Obj(name=node.name)
66 67 cls.has_init = any(isinstance(n, ast.FunctionDef) and \
67 68 n.name=='__init__' for n in node.body)
68 69 self.classes.append(cls)
69 70 self.classes_seen.add(node.name)
70 71
71 72 def scan(self, mod):
72 73 self.visit(mod)
73 74 return self.functions, self.classes
74 75
75 76 # Functions and classes
76 77 class ApiDocWriter(object):
77 78 ''' Class for automatic detection and parsing of API docs
78 79 to Sphinx-parsable reST format'''
79 80
80 81 # only separating first two levels
81 82 rst_section_levels = ['*', '=', '-', '~', '^']
82 83
83 84 def __init__(self,
84 85 package_name,
85 86 rst_extension='.rst',
86 87 package_skip_patterns=None,
87 88 module_skip_patterns=None,
89 names_from__all__=None,
88 90 ):
89 91 ''' Initialize package for parsing
90 92
91 93 Parameters
92 94 ----------
93 95 package_name : string
94 96 Name of the top-level package. *package_name* must be the
95 97 name of an importable package
96 98 rst_extension : string, optional
97 99 Extension for reST files, default '.rst'
98 100 package_skip_patterns : None or sequence of {strings, regexps}
99 101 Sequence of strings giving URIs of packages to be excluded
100 102 Operates on the package path, starting at (including) the
101 103 first dot in the package path, after *package_name* - so,
102 104 if *package_name* is ``sphinx``, then ``sphinx.util`` will
103 105 result in ``.util`` being passed for earching by these
104 106 regexps. If is None, gives default. Default is:
105 107 ['\.tests$']
106 108 module_skip_patterns : None or sequence
107 109 Sequence of strings giving URIs of modules to be excluded
108 110 Operates on the module name including preceding URI path,
109 111 back to the first dot after *package_name*. For example
110 112 ``sphinx.util.console`` results in the string to search of
111 113 ``.util.console``
112 114 If is None, gives default. Default is:
113 115 ['\.setup$', '\._']
116 names_from__all__ : set, optional
117 Modules listed in here will be scanned by doing ``from mod import *``,
118 rather than finding function and class definitions by scanning the
119 AST. This is intended for API modules which expose things defined in
120 other files. Modules listed here must define ``__all__`` to avoid
121 exposing everything they import.
114 122 '''
115 123 if package_skip_patterns is None:
116 124 package_skip_patterns = ['\\.tests$']
117 125 if module_skip_patterns is None:
118 126 module_skip_patterns = ['\\.setup$', '\\._']
119 127 self.package_name = package_name
120 128 self.rst_extension = rst_extension
121 129 self.package_skip_patterns = package_skip_patterns
122 130 self.module_skip_patterns = module_skip_patterns
131 self.names_from__all__ = names_from__all__ or set()
123 132
124 133 def get_package_name(self):
125 134 return self._package_name
126 135
127 136 def set_package_name(self, package_name):
128 137 ''' Set package_name
129 138
130 139 >>> docwriter = ApiDocWriter('sphinx')
131 140 >>> import sphinx
132 141 >>> docwriter.root_path == sphinx.__path__[0]
133 142 True
134 143 >>> docwriter.package_name = 'docutils'
135 144 >>> import docutils
136 145 >>> docwriter.root_path == docutils.__path__[0]
137 146 True
138 147 '''
139 148 # It's also possible to imagine caching the module parsing here
140 149 self._package_name = package_name
141 150 self.root_module = __import__(package_name)
142 151 self.root_path = self.root_module.__path__[0]
143 152 self.written_modules = None
144 153
145 154 package_name = property(get_package_name, set_package_name, None,
146 155 'get/set package_name')
147 156
148 157 def _uri2path(self, uri):
149 158 ''' Convert uri to absolute filepath
150 159
151 160 Parameters
152 161 ----------
153 162 uri : string
154 163 URI of python module to return path for
155 164
156 165 Returns
157 166 -------
158 167 path : None or string
159 168 Returns None if there is no valid path for this URI
160 169 Otherwise returns absolute file system path for URI
161 170
162 171 Examples
163 172 --------
164 173 >>> docwriter = ApiDocWriter('sphinx')
165 174 >>> import sphinx
166 175 >>> modpath = sphinx.__path__[0]
167 176 >>> res = docwriter._uri2path('sphinx.builder')
168 177 >>> res == os.path.join(modpath, 'builder.py')
169 178 True
170 179 >>> res = docwriter._uri2path('sphinx')
171 180 >>> res == os.path.join(modpath, '__init__.py')
172 181 True
173 182 >>> docwriter._uri2path('sphinx.does_not_exist')
174 183
175 184 '''
176 185 if uri == self.package_name:
177 186 return os.path.join(self.root_path, '__init__.py')
178 187 path = uri.replace('.', os.path.sep)
179 188 path = path.replace(self.package_name + os.path.sep, '')
180 189 path = os.path.join(self.root_path, path)
181 190 # XXX maybe check for extensions as well?
182 191 if os.path.exists(path + '.py'): # file
183 192 path += '.py'
184 193 elif os.path.exists(os.path.join(path, '__init__.py')):
185 194 path = os.path.join(path, '__init__.py')
186 195 else:
187 196 return None
188 197 return path
189 198
190 199 def _path2uri(self, dirpath):
191 200 ''' Convert directory path to uri '''
192 201 relpath = dirpath.replace(self.root_path, self.package_name)
193 202 if relpath.startswith(os.path.sep):
194 203 relpath = relpath[1:]
195 204 return relpath.replace(os.path.sep, '.')
196 205
197 206 def _parse_module(self, uri):
198 207 ''' Parse module defined in *uri* '''
199 208 filename = self._uri2path(uri)
200 209 if filename is None:
201 210 # nothing that we could handle here.
202 211 return ([],[])
203 212 with open(filename, 'rb') as f:
204 213 mod = ast.parse(f.read())
205 214 return FuncClsScanner().scan(mod)
206 215
216 def _import_funcs_classes(self, uri):
217 """Import * from uri, and separate out functions and classes."""
218 ns = {}
219 exec('from %s import *' % uri, ns)
220 funcs, classes = [], []
221 for name, obj in ns.items():
222 if inspect.isclass(obj):
223 cls = Obj(name=name, has_init='__init__' in obj.__dict__)
224 classes.append(cls)
225 elif inspect.isfunction(obj):
226 funcs.append(name)
227
228 return sorted(funcs), sorted(classes, key=lambda x: x.name)
229
230 def find_funcs_classes(self, uri):
231 """Find the functions and classes defined in the module ``uri``"""
232 if uri in self.names_from__all__:
233 # For API modules which expose things defined elsewhere, import them
234 return self._import_funcs_classes(uri)
235 else:
236 # For other modules, scan their AST to see what they define
237 return self._parse_module(uri)
238
207 239 def generate_api_doc(self, uri):
208 240 '''Make autodoc documentation template string for a module
209 241
210 242 Parameters
211 243 ----------
212 244 uri : string
213 245 python location of module - e.g 'sphinx.builder'
214 246
215 247 Returns
216 248 -------
217 249 S : string
218 250 Contents of API doc
219 251 '''
220 252 # get the names of all classes and functions
221 functions, classes = self._parse_module(uri)
253 functions, classes = self.find_funcs_classes(uri)
222 254 if not len(functions) and not len(classes):
223 255 #print ('WARNING: Empty -', uri) # dbg
224 256 return ''
225 257
226 258 # Make a shorter version of the uri that omits the package name for
227 259 # titles
228 260 uri_short = re.sub(r'^%s\.' % self.package_name,'',uri)
229 261
230 262 ad = '.. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n'
231 263
232 264 # Set the chapter title to read 'Module:' for all modules except for the
233 265 # main packages
234 266 if '.' in uri:
235 267 chap_title = 'Module: :mod:`' + uri_short + '`'
236 268 else:
237 269 chap_title = ':mod:`' + uri_short + '`'
238 270 ad += chap_title + '\n' + self.rst_section_levels[1] * len(chap_title)
239 271
240 272 ad += '\n.. automodule:: ' + uri + '\n'
241 273 ad += '\n.. currentmodule:: ' + uri + '\n'
242 274
243 275 if classes:
244 276 subhead = str(len(classes)) + (' Classes' if len(classes) > 1 else ' Class')
245 277 ad += '\n'+ subhead + '\n' + \
246 278 self.rst_section_levels[2] * len(subhead) + '\n'
247 279
248 280 for c in classes:
249 281 ad += '\n.. autoclass:: ' + c.name + '\n'
250 282 # must NOT exclude from index to keep cross-refs working
251 283 ad += ' :members:\n' \
252 284 ' :show-inheritance:\n'
253 285 if c.has_init:
254 286 ad += '\n .. automethod:: __init__\n'
255 287
256 288 if functions:
257 289 subhead = str(len(functions)) + (' Functions' if len(functions) > 1 else ' Function')
258 290 ad += '\n'+ subhead + '\n' + \
259 291 self.rst_section_levels[2] * len(subhead) + '\n'
260 292 for f in functions:
261 293 # must NOT exclude from index to keep cross-refs working
262 294 ad += '\n.. autofunction:: ' + uri + '.' + f + '\n\n'
263 295 return ad
264 296
265 297 def _survives_exclude(self, matchstr, match_type):
266 298 ''' Returns True if *matchstr* does not match patterns
267 299
268 300 ``self.package_name`` removed from front of string if present
269 301
270 302 Examples
271 303 --------
272 304 >>> dw = ApiDocWriter('sphinx')
273 305 >>> dw._survives_exclude('sphinx.okpkg', 'package')
274 306 True
275 307 >>> dw.package_skip_patterns.append('^\\.badpkg$')
276 308 >>> dw._survives_exclude('sphinx.badpkg', 'package')
277 309 False
278 310 >>> dw._survives_exclude('sphinx.badpkg', 'module')
279 311 True
280 312 >>> dw._survives_exclude('sphinx.badmod', 'module')
281 313 True
282 314 >>> dw.module_skip_patterns.append('^\\.badmod$')
283 315 >>> dw._survives_exclude('sphinx.badmod', 'module')
284 316 False
285 317 '''
286 318 if match_type == 'module':
287 319 patterns = self.module_skip_patterns
288 320 elif match_type == 'package':
289 321 patterns = self.package_skip_patterns
290 322 else:
291 323 raise ValueError('Cannot interpret match type "%s"'
292 324 % match_type)
293 325 # Match to URI without package name
294 326 L = len(self.package_name)
295 327 if matchstr[:L] == self.package_name:
296 328 matchstr = matchstr[L:]
297 329 for pat in patterns:
298 330 try:
299 331 pat.search
300 332 except AttributeError:
301 333 pat = re.compile(pat)
302 334 if pat.search(matchstr):
303 335 return False
304 336 return True
305 337
306 338 def discover_modules(self):
307 339 ''' Return module sequence discovered from ``self.package_name``
308 340
309 341
310 342 Parameters
311 343 ----------
312 344 None
313 345
314 346 Returns
315 347 -------
316 348 mods : sequence
317 349 Sequence of module names within ``self.package_name``
318 350
319 351 Examples
320 352 --------
321 353 >>> dw = ApiDocWriter('sphinx')
322 354 >>> mods = dw.discover_modules()
323 355 >>> 'sphinx.util' in mods
324 356 True
325 357 >>> dw.package_skip_patterns.append('\.util$')
326 358 >>> 'sphinx.util' in dw.discover_modules()
327 359 False
328 360 >>>
329 361 '''
330 362 modules = [self.package_name]
331 363 # raw directory parsing
332 364 for dirpath, dirnames, filenames in os.walk(self.root_path):
333 365 # Check directory names for packages
334 366 root_uri = self._path2uri(os.path.join(self.root_path,
335 367 dirpath))
336 368 for dirname in dirnames[:]: # copy list - we modify inplace
337 369 package_uri = '.'.join((root_uri, dirname))
338 370 if (self._uri2path(package_uri) and
339 371 self._survives_exclude(package_uri, 'package')):
340 372 modules.append(package_uri)
341 373 else:
342 374 dirnames.remove(dirname)
343 375 # Check filenames for modules
344 376 for filename in filenames:
345 377 module_name = filename[:-3]
346 378 module_uri = '.'.join((root_uri, module_name))
347 379 if (self._uri2path(module_uri) and
348 380 self._survives_exclude(module_uri, 'module')):
349 381 modules.append(module_uri)
350 382 return sorted(modules)
351 383
352 384 def write_modules_api(self, modules,outdir):
353 385 # write the list
354 386 written_modules = []
355 387 for m in modules:
356 388 api_str = self.generate_api_doc(m)
357 389 if not api_str:
358 390 continue
359 391 # write out to file
360 392 outfile = os.path.join(outdir,
361 393 m + self.rst_extension)
362 394 fileobj = open(outfile, 'wt')
363 395 fileobj.write(api_str)
364 396 fileobj.close()
365 397 written_modules.append(m)
366 398 self.written_modules = written_modules
367 399
368 400 def write_api_docs(self, outdir):
369 401 """Generate API reST files.
370 402
371 403 Parameters
372 404 ----------
373 405 outdir : string
374 406 Directory name in which to store files
375 407 We create automatic filenames for each module
376 408
377 409 Returns
378 410 -------
379 411 None
380 412
381 413 Notes
382 414 -----
383 415 Sets self.written_modules to list of written modules
384 416 """
385 417 if not os.path.exists(outdir):
386 418 os.mkdir(outdir)
387 419 # compose list of modules
388 420 modules = self.discover_modules()
389 421 self.write_modules_api(modules,outdir)
390 422
391 423 def write_index(self, outdir, path='gen.rst', relative_to=None):
392 424 """Make a reST API index file from written files
393 425
394 426 Parameters
395 427 ----------
396 428 outdir : string
397 429 Directory to which to write generated index file
398 430 path : string
399 431 Filename to write index to
400 432 relative_to : string
401 433 path to which written filenames are relative. This
402 434 component of the written file path will be removed from
403 435 outdir, in the generated index. Default is None, meaning,
404 436 leave path as it is.
405 437 """
406 438 if self.written_modules is None:
407 439 raise ValueError('No modules written')
408 440 # Get full filename path
409 441 path = os.path.join(outdir, path)
410 442 # Path written into index is relative to rootpath
411 443 if relative_to is not None:
412 444 relpath = outdir.replace(relative_to + os.path.sep, '')
413 445 else:
414 446 relpath = outdir
415 447 idx = open(path,'wt')
416 448 w = idx.write
417 449 w('.. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n')
418 450 w('.. autosummary::\n'
419 451 ' :toctree: %s\n\n' % relpath)
420 452 for mod in self.written_modules:
421 453 w(' %s\n' % mod)
422 454 idx.close()
General Comments 0
You need to be logged in to leave comments. Login now