doccer.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. ''' Utilities to allow inserting docstring fragments for common
  2. parameters into function and method docstrings'''
  3. import sys
  4. __all__ = [
  5. 'docformat', 'inherit_docstring_from', 'indentcount_lines',
  6. 'filldoc', 'unindent_dict', 'unindent_string', 'extend_notes_in_docstring',
  7. 'replace_notes_in_docstring', 'doc_replace'
  8. ]
  9. def docformat(docstring, docdict=None):
  10. ''' Fill a function docstring from variables in dictionary
  11. Adapt the indent of the inserted docs
  12. Parameters
  13. ----------
  14. docstring : string
  15. docstring from function, possibly with dict formatting strings
  16. docdict : dict, optional
  17. dictionary with keys that match the dict formatting strings
  18. and values that are docstring fragments to be inserted. The
  19. indentation of the inserted docstrings is set to match the
  20. minimum indentation of the ``docstring`` by adding this
  21. indentation to all lines of the inserted string, except the
  22. first.
  23. Returns
  24. -------
  25. outstring : string
  26. string with requested ``docdict`` strings inserted
  27. Examples
  28. --------
  29. >>> docformat(' Test string with %(value)s', {'value':'inserted value'})
  30. ' Test string with inserted value'
  31. >>> docstring = 'First line\\n Second line\\n %(value)s'
  32. >>> inserted_string = "indented\\nstring"
  33. >>> docdict = {'value': inserted_string}
  34. >>> docformat(docstring, docdict)
  35. 'First line\\n Second line\\n indented\\n string'
  36. '''
  37. if not docstring:
  38. return docstring
  39. if docdict is None:
  40. docdict = {}
  41. if not docdict:
  42. return docstring
  43. lines = docstring.expandtabs().splitlines()
  44. # Find the minimum indent of the main docstring, after first line
  45. if len(lines) < 2:
  46. icount = 0
  47. else:
  48. icount = indentcount_lines(lines[1:])
  49. indent = ' ' * icount
  50. # Insert this indent to dictionary docstrings
  51. indented = {}
  52. for name, dstr in docdict.items():
  53. lines = dstr.expandtabs().splitlines()
  54. try:
  55. newlines = [lines[0]]
  56. for line in lines[1:]:
  57. newlines.append(indent+line)
  58. indented[name] = '\n'.join(newlines)
  59. except IndexError:
  60. indented[name] = dstr
  61. return docstring % indented
  62. def inherit_docstring_from(cls):
  63. """
  64. This decorator modifies the decorated function's docstring by
  65. replacing occurrences of '%(super)s' with the docstring of the
  66. method of the same name from the class `cls`.
  67. If the decorated method has no docstring, it is simply given the
  68. docstring of `cls`s method.
  69. Parameters
  70. ----------
  71. cls : Python class or instance
  72. A class with a method with the same name as the decorated method.
  73. The docstring of the method in this class replaces '%(super)s' in the
  74. docstring of the decorated method.
  75. Returns
  76. -------
  77. f : function
  78. The decorator function that modifies the __doc__ attribute
  79. of its argument.
  80. Examples
  81. --------
  82. In the following, the docstring for Bar.func created using the
  83. docstring of `Foo.func`.
  84. >>> class Foo:
  85. ... def func(self):
  86. ... '''Do something useful.'''
  87. ... return
  88. ...
  89. >>> class Bar(Foo):
  90. ... @inherit_docstring_from(Foo)
  91. ... def func(self):
  92. ... '''%(super)s
  93. ... Do it fast.
  94. ... '''
  95. ... return
  96. ...
  97. >>> b = Bar()
  98. >>> b.func.__doc__
  99. 'Do something useful.\n Do it fast.\n '
  100. """
  101. def _doc(func):
  102. cls_docstring = getattr(cls, func.__name__).__doc__
  103. func_docstring = func.__doc__
  104. if func_docstring is None:
  105. func.__doc__ = cls_docstring
  106. else:
  107. new_docstring = func_docstring % dict(super=cls_docstring)
  108. func.__doc__ = new_docstring
  109. return func
  110. return _doc
  111. def extend_notes_in_docstring(cls, notes):
  112. """
  113. This decorator replaces the decorated function's docstring
  114. with the docstring from corresponding method in `cls`.
  115. It extends the 'Notes' section of that docstring to include
  116. the given `notes`.
  117. """
  118. def _doc(func):
  119. cls_docstring = getattr(cls, func.__name__).__doc__
  120. # If python is called with -OO option,
  121. # there is no docstring
  122. if cls_docstring is None:
  123. return func
  124. end_of_notes = cls_docstring.find(' References\n')
  125. if end_of_notes == -1:
  126. end_of_notes = cls_docstring.find(' Examples\n')
  127. if end_of_notes == -1:
  128. end_of_notes = len(cls_docstring)
  129. func.__doc__ = (cls_docstring[:end_of_notes] + notes +
  130. cls_docstring[end_of_notes:])
  131. return func
  132. return _doc
  133. def replace_notes_in_docstring(cls, notes):
  134. """
  135. This decorator replaces the decorated function's docstring
  136. with the docstring from corresponding method in `cls`.
  137. It replaces the 'Notes' section of that docstring with
  138. the given `notes`.
  139. """
  140. def _doc(func):
  141. cls_docstring = getattr(cls, func.__name__).__doc__
  142. notes_header = ' Notes\n -----\n'
  143. # If python is called with -OO option,
  144. # there is no docstring
  145. if cls_docstring is None:
  146. return func
  147. start_of_notes = cls_docstring.find(notes_header)
  148. end_of_notes = cls_docstring.find(' References\n')
  149. if end_of_notes == -1:
  150. end_of_notes = cls_docstring.find(' Examples\n')
  151. if end_of_notes == -1:
  152. end_of_notes = len(cls_docstring)
  153. func.__doc__ = (cls_docstring[:start_of_notes + len(notes_header)] +
  154. notes +
  155. cls_docstring[end_of_notes:])
  156. return func
  157. return _doc
  158. def indentcount_lines(lines):
  159. ''' Minimum indent for all lines in line list
  160. >>> lines = [' one', ' two', ' three']
  161. >>> indentcount_lines(lines)
  162. 1
  163. >>> lines = []
  164. >>> indentcount_lines(lines)
  165. 0
  166. >>> lines = [' one']
  167. >>> indentcount_lines(lines)
  168. 1
  169. >>> indentcount_lines([' '])
  170. 0
  171. '''
  172. indentno = sys.maxsize
  173. for line in lines:
  174. stripped = line.lstrip()
  175. if stripped:
  176. indentno = min(indentno, len(line) - len(stripped))
  177. if indentno == sys.maxsize:
  178. return 0
  179. return indentno
  180. def filldoc(docdict, unindent_params=True):
  181. ''' Return docstring decorator using docdict variable dictionary
  182. Parameters
  183. ----------
  184. docdict : dictionary
  185. dictionary containing name, docstring fragment pairs
  186. unindent_params : {False, True}, boolean, optional
  187. If True, strip common indentation from all parameters in
  188. docdict
  189. Returns
  190. -------
  191. decfunc : function
  192. decorator that applies dictionary to input function docstring
  193. '''
  194. if unindent_params:
  195. docdict = unindent_dict(docdict)
  196. def decorate(f):
  197. f.__doc__ = docformat(f.__doc__, docdict)
  198. return f
  199. return decorate
  200. def unindent_dict(docdict):
  201. ''' Unindent all strings in a docdict '''
  202. can_dict = {}
  203. for name, dstr in docdict.items():
  204. can_dict[name] = unindent_string(dstr)
  205. return can_dict
  206. def unindent_string(docstring):
  207. ''' Set docstring to minimum indent for all lines, including first
  208. >>> unindent_string(' two')
  209. 'two'
  210. >>> unindent_string(' two\\n three')
  211. 'two\\n three'
  212. '''
  213. lines = docstring.expandtabs().splitlines()
  214. icount = indentcount_lines(lines)
  215. if icount == 0:
  216. return docstring
  217. return '\n'.join([line[icount:] for line in lines])
  218. def doc_replace(obj, oldval, newval):
  219. """Decorator to take the docstring from obj, with oldval replaced by newval
  220. Equivalent to ``func.__doc__ = obj.__doc__.replace(oldval, newval)``
  221. Parameters
  222. ----------
  223. obj : object
  224. The object to take the docstring from.
  225. oldval : string
  226. The string to replace from the original docstring.
  227. newval : string
  228. The string to replace ``oldval`` with.
  229. """
  230. # __doc__ may be None for optimized Python (-OO)
  231. doc = (obj.__doc__ or '').replace(oldval, newval)
  232. def inner(func):
  233. func.__doc__ = doc
  234. return func
  235. return inner