pluginopts.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. """
  2. Adds a sphinx directive that can be used to automatically document a plugin.
  3. this::
  4. .. autoplugin :: nose.plugins.foo
  5. :plugin: Pluggy
  6. produces::
  7. .. automodule :: nose.plugins.foo
  8. Options
  9. -------
  10. .. cmdoption :: --foo=BAR, --fooble=BAR
  11. Do the foo thing to the new thing.
  12. Plugin
  13. ------
  14. .. autoclass :: nose.plugins.foo.Pluggy
  15. :members:
  16. Source
  17. ------
  18. .. include :: path/to/nose/plugins/foo.py
  19. :literal:
  20. """
  21. import os
  22. try:
  23. from docutils import nodes, utils
  24. from docutils.statemachine import ViewList
  25. from docutils.parsers.rst import directives
  26. except ImportError:
  27. pass # won't run anyway
  28. from nose.util import resolve_name
  29. from nose.plugins.base import Plugin
  30. from nose.plugins.manager import BuiltinPluginManager
  31. from nose.config import Config
  32. from nose.core import TestProgram
  33. from inspect import isclass
  34. def autoplugin_directive(dirname, arguments, options, content, lineno,
  35. content_offset, block_text, state, state_machine):
  36. mod_name = arguments[0]
  37. mod = resolve_name(mod_name)
  38. plug_name = options.get('plugin', None)
  39. if plug_name:
  40. obj = getattr(mod, plug_name)
  41. else:
  42. for entry in dir(mod):
  43. obj = getattr(mod, entry)
  44. if isclass(obj) and issubclass(obj, Plugin) and obj is not Plugin:
  45. plug_name = '%s.%s' % (mod_name, entry)
  46. break
  47. # mod docstring
  48. rst = ViewList()
  49. rst.append('.. automodule :: %s\n' % mod_name, '<autodoc>')
  50. rst.append('', '<autodoc>')
  51. # options
  52. rst.append('Options', '<autodoc>')
  53. rst.append('-------', '<autodoc>')
  54. rst.append('', '<autodoc>')
  55. plug = obj()
  56. opts = OptBucket()
  57. plug.options(opts, {})
  58. for opt in opts:
  59. rst.append(opt.options(), '<autodoc>')
  60. rst.append(' \n', '<autodoc>')
  61. rst.append(' ' + opt.help + '\n', '<autodoc>')
  62. rst.append('\n', '<autodoc>')
  63. # plugin class
  64. rst.append('Plugin', '<autodoc>')
  65. rst.append('------', '<autodoc>')
  66. rst.append('', '<autodoc>')
  67. rst.append('.. autoclass :: %s\n' % plug_name, '<autodoc>')
  68. rst.append(' :members:\n', '<autodoc>')
  69. rst.append(' :show-inheritance:\n', '<autodoc>')
  70. rst.append('', '<autodoc>')
  71. # source
  72. rst.append('Source', '<autodoc>')
  73. rst.append('------', '<autodoc>')
  74. rst.append(
  75. '.. include :: %s\n' % utils.relative_path(
  76. state_machine.document['source'],
  77. os.path.abspath(mod.__file__.replace('.pyc', '.py'))),
  78. '<autodoc>')
  79. rst.append(' :literal:\n', '<autodoc>')
  80. rst.append('', '<autodoc>')
  81. node = nodes.section()
  82. node.document = state.document
  83. surrounding_title_styles = state.memo.title_styles
  84. surrounding_section_level = state.memo.section_level
  85. state.memo.title_styles = []
  86. state.memo.section_level = 0
  87. state.nested_parse(rst, 0, node, match_titles=1)
  88. state.memo.title_styles = surrounding_title_styles
  89. state.memo.section_level = surrounding_section_level
  90. return node.children
  91. def autohelp_directive(dirname, arguments, options, content, lineno,
  92. content_offset, block_text, state, state_machine):
  93. """produces rst from nose help"""
  94. config = Config(parserClass=OptBucket,
  95. plugins=BuiltinPluginManager())
  96. parser = config.getParser(TestProgram.usage())
  97. rst = ViewList()
  98. for line in parser.format_help().split('\n'):
  99. rst.append(line, '<autodoc>')
  100. rst.append('Options', '<autodoc>')
  101. rst.append('-------', '<autodoc>')
  102. rst.append('', '<autodoc>')
  103. for opt in parser:
  104. rst.append(opt.options(), '<autodoc>')
  105. rst.append(' \n', '<autodoc>')
  106. rst.append(' ' + opt.help + '\n', '<autodoc>')
  107. rst.append('\n', '<autodoc>')
  108. node = nodes.section()
  109. node.document = state.document
  110. surrounding_title_styles = state.memo.title_styles
  111. surrounding_section_level = state.memo.section_level
  112. state.memo.title_styles = []
  113. state.memo.section_level = 0
  114. state.nested_parse(rst, 0, node, match_titles=1)
  115. state.memo.title_styles = surrounding_title_styles
  116. state.memo.section_level = surrounding_section_level
  117. return node.children
  118. class OptBucket(object):
  119. def __init__(self, doc=None, prog='nosetests'):
  120. self.opts = []
  121. self.doc = doc
  122. self.prog = prog
  123. def __iter__(self):
  124. return iter(self.opts)
  125. def format_help(self):
  126. return self.doc.replace('%prog', self.prog).replace(':\n', '::\n')
  127. def add_option(self, *arg, **kw):
  128. self.opts.append(Opt(*arg, **kw))
  129. class Opt(object):
  130. def __init__(self, *arg, **kw):
  131. self.opts = arg
  132. self.action = kw.pop('action', None)
  133. self.default = kw.pop('default', None)
  134. self.metavar = kw.pop('metavar', None)
  135. self.help = kw.pop('help', None)
  136. def options(self):
  137. buf = []
  138. for optstring in self.opts:
  139. desc = optstring
  140. if self.action not in ('store_true', 'store_false'):
  141. desc += '=%s' % self.meta(optstring)
  142. buf.append(desc)
  143. return '.. cmdoption :: ' + ', '.join(buf)
  144. def meta(self, optstring):
  145. # FIXME optparser default metavar?
  146. return self.metavar or 'DEFAULT'
  147. def setup(app):
  148. app.add_directive('autoplugin',
  149. autoplugin_directive, 1, (1, 0, 1),
  150. plugin=directives.unchanged)
  151. app.add_directive('autohelp', autohelp_directive, 0, (0, 0, 1))