1
2
3
4
5
6
7
8
9 """Various helpers to improve docstrings and textual output"""
10
11 __docformat__ = 'restructuredtext'
12
13 import re, textwrap
14
15
16 import numpy as N
17 from math import ceil
18 from StringIO import StringIO
19 from mvpa import cfg
20
21 from mvpa.base import externals
22 if __debug__:
23 from mvpa.base import debug
24
25 __add_init2doc = False
26 __in_ipython = externals.exists('running ipython env')
27
28 if __in_ipython:
29 __rst_mode = 0
30 _rst_sep = ""
31 _rst_sep2 = ""
32 from IPython import Release
33
34
35 if Release.version <= '0.8.1':
36 __add_init2doc = True
37 else:
38 __rst_mode = 1
39 _rst_sep = "`"
40 _rst_sep2 = ":"
41
42 -def _rst(s, snotrst=''):
43 """Produce s only in __rst mode"""
44 if __rst_mode:
45 return s
46 else:
47 return snotrst
48
50 """Add and underline RsT string matching the length of the given string.
51 """
52 return text + '\n' + markup * len(text)
53
54
56 """Little helper to spit out single or plural version of a word.
57 """
58 ni = int(n)
59 if ni > 1 or ni == 0:
60
61 return plural
62 else:
63 return single
64
65
67 """Take care of empty and non existing doc strings."""
68 if text == None or not len(text):
69 if polite:
70 return 'No documentation found. Sorry!'
71 else:
72 return ''
73 else:
74
75
76 if not text.startswith(' '):
77 lines = text.split('\n')
78 text2 = '\n'.join(lines[1:])
79 return lines[0] + "\n" + textwrap.dedent(text2)
80 else:
81 return textwrap.dedent(text)
82
83
85 """Simple indenter
86 """
87 return '\n'.join(istr + s for s in text.split('\n'))
88
89 __parameters_str_re = re.compile("[\n^]\s*:?Parameters?:?\s*\n")
90 """regexp to match :Parameter: and :Parameters: stand alone in a line"""
91
93 """Split documentation into (header, parameters, suffix)
94
95 :Parameters:
96 initdoc : string
97 The documentation string
98 """
99
100
101 p_res = __parameters_str_re.search(initdoc)
102 if p_res is None:
103 result = initdoc, "", ""
104 else:
105
106
107
108
109 ph_i = p_res.start()
110
111
112 pb_i = p_res.end()
113
114
115 try:
116 pe_i = initdoc.index('\n\n', pb_i)
117 except ValueError:
118 pe_i = len(initdoc)
119
120 result = initdoc[:ph_i].rstrip('\n '), \
121 initdoc[pb_i:pe_i], initdoc[pe_i:]
122
123
124
125 return [handleDocString(x, polite=False).strip('\n') for x in result]
126
127
128 __re_params = re.compile('(?:\n\S.*?)+$')
129 __re_spliter1 = re.compile('(?:\n|\A)(?=\S)')
130 __re_spliter2 = re.compile('[\n:]')
132 """Parse parameters and return list of (name, full_doc_string)
133
134 It is needed to remove multiple entries for the same parameter
135 like it could be with adding parameters from the parent class
136
137 It assumes that previousely parameters were unwrapped, so their
138 documentation starts at the begining of the string, like what
139 should it be after _splitOutParametersStr
140 """
141 entries = __re_spliter1.split(paramdoc)
142 result = [(__re_spliter2.split(e)[0].strip(), e)
143 for e in entries if e != '']
144 if __debug__:
145 debug('DOCH', 'parseParameters: Given "%s", we split into %s' %
146 (paramdoc, result))
147 return result
148
149
151 """Generate enhanced doc strings for various items.
152
153 :Parameters:
154 item : basestring or class
155 What object requires enhancing of documentation
156 *args : list
157 Includes base classes to look for parameters, as well, first item
158 must be a dictionary of locals if item is given by a string
159 force_extend : bool
160 Either to force looking for the documentation in the parents.
161 By default force_extend = False, and lookup happens only if kwargs
162 is one of the arguments to the respective function (e.g. item.__init__)
163 skip_params : list of basestring
164 List of parameters (in addition to [kwargs]) which should not
165 be added to the documentation of the class.
166
167 It is to be used from a collector, ie whenever class is already created
168 """
169
170 if len(kwargs):
171 if set(kwargs.keys()).issubset(set(['force_extend'])):
172 raise ValueError, "Got unknown keyword arguments (smth among %s)" \
173 " in enhancedDocString." % kwargs
174 force_extend = kwargs.get('force_extend', False)
175 skip_params = kwargs.get('skip_params', [])
176
177
178 if isinstance(item, basestring):
179 if len(args)<1 or not isinstance(args[0], dict):
180 raise ValueError, \
181 "Please provide locals for enhancedDocString of %s" % item
182 name = item
183 lcl = args[0]
184 args = args[1:]
185 elif hasattr(item, "im_class"):
186
187 raise NotImplementedError, \
188 "enhancedDocString is not yet implemented for methods"
189 elif hasattr(item, "__name__"):
190 name = item.__name__
191 lcl = item.__dict__
192 else:
193 raise ValueError, "Don't know how to extend docstring for %s" % item
194
195
196 if not cfg.getboolean('doc', 'pimp docstrings', True):
197 return lcl['__doc__']
198
199
200 rst_lvlmarkup = ["=", "-", "_"]
201
202
203 if hasattr(item, '_customizeDoc') and name=='SVM':
204 item._customizeDoc()
205
206 initdoc = ""
207 if lcl.has_key('__init__'):
208 func = lcl['__init__']
209 initdoc = func.__doc__
210
211
212
213
214 extend_args = force_extend or \
215 'kwargs' in (func.func_code.co_names +
216 func.func_code.co_varnames)
217
218 if __debug__ and not extend_args:
219 debug('DOCH', 'Not extending parameters for %s' % name)
220
221 if initdoc is None:
222 initdoc = "Initialize instance of %s" % name
223
224 initdoc, params, suffix = _splitOutParametersStr(initdoc)
225
226 if lcl.has_key('_paramsdoc'):
227 params += '\n' + handleDocString(lcl['_paramsdoc'])
228
229 params_list = _parseParameters(params)
230 known_params = set([i[0] for i in params_list])
231
232 skip_params = set(skip_params + ['kwargs', '**kwargs'])
233
234
235
236
237
238
239 if hasattr(item, '_clf_internals'):
240 clf_internals = item._clf_internals
241 skip_params.update([i for i in ('regression', 'retrainable')
242 if not (i in clf_internals)])
243
244 known_params.update(skip_params)
245 if extend_args:
246
247 parent_params_list = []
248 for i in args:
249 if hasattr(i, '__init__'):
250
251 initdoc_ = i.__init__.__doc__
252 if initdoc_ is None:
253 continue
254 splits_ = _splitOutParametersStr(initdoc_)
255 params_ = splits_[1]
256 parent_params_list += _parseParameters(params_.lstrip())
257
258
259 for i, v in parent_params_list:
260 if not (i in known_params):
261 params_list += [(i, v)]
262 known_params.update([i])
263
264
265 if len(params_list):
266 params_ = '\n'.join([i[1].rstrip() for i in params_list
267 if not i[0] in skip_params])
268 initdoc += "\n\n%sParameters%s\n" % ( (_rst_sep2,)*2 ) \
269 + _indent(params_)
270
271 if suffix != "":
272 initdoc += "\n\n" + suffix
273
274 initdoc = handleDocString(initdoc)
275
276
277 lcl['__init__'].__doc__ = initdoc
278
279 docs = [ handleDocString(lcl['__doc__']) ]
280
281
282 if __add_init2doc and initdoc != "":
283 docs += [ rstUnderline('Constructor information for `%s` class' % name,
284 rst_lvlmarkup[2]),
285 initdoc ]
286
287
288 if lcl.has_key('_statesdoc'):
289
290 docs += [_rst('.. note::\n ') + 'Available state variables:',
291 handleDocString(item._statesdoc)]
292
293 if len(args):
294 bc_intro = _rst(' ') + 'Please refer to the documentation of the ' \
295 'base %s for more information:' \
296 % (singleOrPlural('class', 'classes', len(args)))
297
298 docs += [_rst('\n.. seealso::'),
299 bc_intro,
300 ' ' + ',\n '.join(['%s%s.%s%s' % (_rst(':class:`~'),
301 i.__module__,
302 i.__name__,
303 _rst_sep)
304 for i in args])
305 ]
306
307 itemdoc = '\n\n'.join(docs)
308
309 result = re.sub("\s*\n\s*\n\s*\n", "\n\n", itemdoc)
310
311 return result
312
313
315 """Given list of lists figure out their common widths and print to out
316
317 :Parameters:
318 table : list of lists of strings
319 What is aimed to be printed
320 out : None or stream
321 Where to print. If None -- will print and return string
322
323 :Returns:
324 string if out was None
325 """
326
327 print2string = out is None
328 if print2string:
329 out = StringIO()
330
331
332 Nelements_max = max(len(x) for x in table)
333 for i, table_ in enumerate(table):
334 table[i] += [''] * (Nelements_max - len(table_))
335
336
337 atable = N.asarray(table)
338 markup_strip = re.compile('^@[lrc]')
339 col_width = [ max( [len(markup_strip.sub('', x))
340 for x in column] ) for column in atable.T ]
341 string = ""
342 for i, table_ in enumerate(table):
343 string_ = ""
344 for j, item in enumerate(table_):
345 item = str(item)
346 if item.startswith('@'):
347 align = item[1]
348 item = item[2:]
349 if not align in ['l', 'r', 'c']:
350 raise ValueError, 'Unknown alignment %s. Known are l,r,c'
351 else:
352 align = 'c'
353
354 NspacesL = ceil((col_width[j] - len(item))/2.0)
355 NspacesR = col_width[j] - NspacesL - len(item)
356
357 if align == 'c':
358 pass
359 elif align == 'l':
360 NspacesL, NspacesR = 0, NspacesL + NspacesR
361 elif align == 'r':
362 NspacesL, NspacesR = NspacesL + NspacesR, 0
363 else:
364 raise RuntimeError, 'Should not get here with align=%s' % align
365
366 string_ += "%%%ds%%s%%%ds " \
367 % (NspacesL, NspacesR) % ('', item, '')
368 string += string_.rstrip() + '\n'
369 out.write(string)
370
371 if print2string:
372 value = out.getvalue()
373 out.close()
374 return value
375