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
91 """Split documentation into (header, parameters, suffix)
92
93 :Parameters:
94 initdoc : string
95 The documentation string
96 initial : bool
97 Either this is an original initdoc or the one which
98 was already processed (i.e. of parent class)
99 """
100 sep = (_rst_sep2, ':')[int(initial)]
101 parameters_str = "%sParameters%s" % (sep, sep)
102
103
104 if not (parameters_str in initdoc):
105 result = initdoc, "", ""
106 else:
107
108
109
110
111 ph_i = initdoc.index(parameters_str)
112
113
114 pb_i = initdoc.index('\n', ph_i+1)
115
116
117 try:
118 pe_i = initdoc.index('\n\n', pb_i)
119 except ValueError:
120 pe_i = len(initdoc)
121
122 result = initdoc[:ph_i].rstrip('\n '), \
123 initdoc[pb_i:pe_i], initdoc[pe_i:]
124
125
126
127 return [handleDocString(x, polite=False).strip('\n') for x in result]
128
129
130 __re_params = re.compile('(?:\n\S.*?)+$')
131 __re_spliter1 = re.compile('(?:\n|\A)(?=\S)')
132 __re_spliter2 = re.compile('[\n:]')
134 """Parse parameters and return list of (name, full_doc_string)
135
136 It is needed to remove multiple entries for the same parameter
137 like it could be with adding parameters from the parent class
138
139 It assumes that previousely parameters were unwrapped, so their
140 documentation starts at the begining of the string, like what
141 should it be after _splitOutParametersStr
142 """
143 entries = __re_spliter1.split(paramdoc)
144 result = [(__re_spliter2.split(e)[0].strip(), e)
145 for e in entries if e != '']
146 if __debug__:
147 debug('DOCH', 'parseParameters: Given "%s", we split into %s' %
148 (paramdoc, result))
149 return result
150
151
153 """Generate enhanced doc strings for various items.
154
155 :Parameters:
156 item : basestring or class
157 What object requires enhancing of documentation
158 *args : list
159 Includes base classes to look for parameters, as well, first item
160 must be a dictionary of locals if item is given by a string
161 force_extend : bool
162 Either to force looking for the documentation in the parents.
163 By default force_extend = False, and lookup happens only if kwargs
164 is one of the arguments to the respective function (e.g. item.__init__)
165 skip_params : list of basestring
166 List of parameters (in addition to [kwargs]) which should not
167 be added to the documentation of the class.
168
169 It is to be used from a collector, ie whenever class is already created
170 """
171
172 if len(kwargs):
173 if set(kwargs.keys()).issubset(set(['force_extend'])):
174 raise ValueError, "Got unknown keyword arguments (smth among %s)" \
175 " in enhancedDocString." % kwargs
176 force_extend = kwargs.get('force_extend', False)
177 skip_params = kwargs.get('skip_params', [])
178
179
180 if isinstance(item, basestring):
181 if len(args)<1 or not isinstance(args[0], dict):
182 raise ValueError, \
183 "Please provide locals for enhancedDocString of %s" % item
184 name = item
185 lcl = args[0]
186 args = args[1:]
187 elif hasattr(item, "im_class"):
188
189 raise NotImplementedError, \
190 "enhancedDocString is not yet implemented for methods"
191 elif hasattr(item, "__name__"):
192 name = item.__name__
193 lcl = item.__dict__
194 else:
195 raise ValueError, "Don't know how to extend docstring for %s" % item
196
197
198 if not cfg.getboolean('doc', 'pimp docstrings', True):
199 return lcl['__doc__']
200
201
202 rst_lvlmarkup = ["=", "-", "_"]
203
204
205 if hasattr(item, '_customizeDoc') and name=='SVM':
206 item._customizeDoc()
207
208 initdoc = ""
209 if lcl.has_key('__init__'):
210 func = lcl['__init__']
211 initdoc = func.__doc__
212
213
214
215
216 extend_args = force_extend or \
217 'kwargs' in (func.func_code.co_names +
218 func.func_code.co_varnames)
219
220 if __debug__ and not extend_args:
221 debug('DOCH', 'Not extending parameters for %s' % name)
222
223 if initdoc is None:
224 initdoc = "Initialize instance of %s" % name
225
226 initdoc, params, suffix = _splitOutParametersStr(initdoc, initial=True)
227
228 if lcl.has_key('_paramsdoc'):
229 params += '\n' + handleDocString(lcl['_paramsdoc'])
230
231 params_list = _parseParameters(params)
232 known_params = set([i[0] for i in params_list])
233
234 skip_params = set(skip_params + ['kwargs', '**kwargs'])
235
236
237
238
239
240
241 if hasattr(item, '_clf_internals'):
242 clf_internals = item._clf_internals
243 skip_params.update([i for i in ('regression', 'retrainable')
244 if not (i in clf_internals)])
245
246 known_params.update(skip_params)
247 if extend_args:
248
249 parent_params_list = []
250 for i in args:
251 if hasattr(i, '__init__'):
252
253 initdoc_ = i.__init__.__doc__
254 if initdoc_ is None:
255 continue
256 splits_ = _splitOutParametersStr(initdoc_)
257 params_ = splits_[1]
258 parent_params_list += _parseParameters(params_.lstrip())
259
260
261 for i, v in parent_params_list:
262 if not (i in known_params):
263 params_list += [(i, v)]
264 known_params.update([i])
265
266
267 if len(params_list):
268 params_ = '\n'.join([i[1].rstrip() for i in params_list
269 if not i[0] in skip_params])
270 initdoc += "\n\n%sParameters%s\n" % ( (_rst_sep2,)*2 ) \
271 + _indent(params_)
272
273 if suffix != "":
274 initdoc += "\n\n" + suffix
275
276 initdoc = handleDocString(initdoc)
277
278
279 lcl['__init__'].__doc__ = initdoc
280
281 docs = [ handleDocString(lcl['__doc__']) ]
282
283
284 if __add_init2doc and initdoc != "":
285 docs += [ rstUnderline('Constructor information for `%s` class' % name,
286 rst_lvlmarkup[2]),
287 initdoc ]
288
289
290 if lcl.has_key('_statesdoc'):
291
292 docs += [_rst('.. note::\n ') + 'Available state variables:',
293 handleDocString(item._statesdoc)]
294
295 if len(args):
296 bc_intro = _rst(' ') + 'Please refer to the documentation of the ' \
297 'base %s for more information:' \
298 % (singleOrPlural('class', 'classes', len(args)))
299
300 docs += [_rst('\n.. seealso::'),
301 bc_intro,
302 ' ' + ',\n '.join(['%s%s.%s%s' % (_rst(':class:`~'),
303 i.__module__,
304 i.__name__,
305 _rst_sep)
306 for i in args])
307 ]
308
309 itemdoc = '\n\n'.join(docs)
310
311 result = re.sub("\s*\n\s*\n\s*\n", "\n\n", itemdoc)
312
313 return result
314
315
317 """Given list of lists figure out their common widths and print to out
318
319 :Parameters:
320 table : list of lists of strings
321 What is aimed to be printed
322 out : None or stream
323 Where to print. If None -- will print and return string
324
325 :Returns:
326 string if out was None
327 """
328
329 print2string = out is None
330 if print2string:
331 out = StringIO()
332
333
334 Nelements_max = max(len(x) for x in table)
335 for i, table_ in enumerate(table):
336 table[i] += [''] * (Nelements_max - len(table_))
337
338
339 atable = N.asarray(table)
340 markup_strip = re.compile('^@[lrc]')
341 col_width = [ max( [len(markup_strip.sub('', x))
342 for x in column] ) for column in atable.T ]
343 string = ""
344 for i, table_ in enumerate(table):
345 string_ = ""
346 for j, item in enumerate(table_):
347 item = str(item)
348 if item.startswith('@'):
349 align = item[1]
350 item = item[2:]
351 if not align in ['l', 'r', 'c']:
352 raise ValueError, 'Unknown alignment %s. Known are l,r,c'
353 else:
354 align = 'c'
355
356 NspacesL = ceil((col_width[j] - len(item))/2.0)
357 NspacesR = col_width[j] - NspacesL - len(item)
358
359 if align == 'c':
360 pass
361 elif align == 'l':
362 NspacesL, NspacesR = 0, NspacesL + NspacesR
363 elif align == 'r':
364 NspacesL, NspacesR = NspacesL + NspacesR, 0
365 else:
366 raise RuntimeError, 'Should not get here with align=%s' % align
367
368 string_ += "%%%ds%%s%%%ds " \
369 % (NspacesL, NspacesR) % ('', item, '')
370 string += string_.rstrip() + '\n'
371 out.write(string)
372
373 if print2string:
374 value = out.getvalue()
375 out.close()
376 return value
377