doccer.py 7.5 KB

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