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