sphinxext.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. # -*- coding: utf-8 -*-
  2. """
  3. pygments.sphinxext
  4. ~~~~~~~~~~~~~~~~~~
  5. Sphinx extension to generate automatic documentation of lexers,
  6. formatters and filters.
  7. :copyright: Copyright 2006-2019 by the Pygments team, see AUTHORS.
  8. :license: BSD, see LICENSE for details.
  9. """
  10. from __future__ import print_function
  11. import sys
  12. from docutils import nodes
  13. from docutils.statemachine import ViewList
  14. from docutils.parsers.rst import Directive
  15. from sphinx.util.nodes import nested_parse_with_titles
  16. MODULEDOC = '''
  17. .. module:: %s
  18. %s
  19. %s
  20. '''
  21. LEXERDOC = '''
  22. .. class:: %s
  23. :Short names: %s
  24. :Filenames: %s
  25. :MIME types: %s
  26. %s
  27. '''
  28. FMTERDOC = '''
  29. .. class:: %s
  30. :Short names: %s
  31. :Filenames: %s
  32. %s
  33. '''
  34. FILTERDOC = '''
  35. .. class:: %s
  36. :Name: %s
  37. %s
  38. '''
  39. class PygmentsDoc(Directive):
  40. """
  41. A directive to collect all lexers/formatters/filters and generate
  42. autoclass directives for them.
  43. """
  44. has_content = False
  45. required_arguments = 1
  46. optional_arguments = 0
  47. final_argument_whitespace = False
  48. option_spec = {}
  49. def run(self):
  50. self.filenames = set()
  51. if self.arguments[0] == 'lexers':
  52. out = self.document_lexers()
  53. elif self.arguments[0] == 'formatters':
  54. out = self.document_formatters()
  55. elif self.arguments[0] == 'filters':
  56. out = self.document_filters()
  57. else:
  58. raise Exception('invalid argument for "pygmentsdoc" directive')
  59. node = nodes.compound()
  60. vl = ViewList(out.split('\n'), source='')
  61. nested_parse_with_titles(self.state, vl, node)
  62. for fn in self.filenames:
  63. self.state.document.settings.record_dependencies.add(fn)
  64. return node.children
  65. def document_lexers(self):
  66. from pygments.lexers._mapping import LEXERS
  67. out = []
  68. modules = {}
  69. moduledocstrings = {}
  70. for classname, data in sorted(LEXERS.items(), key=lambda x: x[0]):
  71. module = data[0]
  72. mod = __import__(module, None, None, [classname])
  73. self.filenames.add(mod.__file__)
  74. cls = getattr(mod, classname)
  75. if not cls.__doc__:
  76. print("Warning: %s does not have a docstring." % classname)
  77. docstring = cls.__doc__
  78. if isinstance(docstring, bytes):
  79. docstring = docstring.decode('utf8')
  80. modules.setdefault(module, []).append((
  81. classname,
  82. ', '.join(data[2]) or 'None',
  83. ', '.join(data[3]).replace('*', '\\*').replace('_', '\\') or 'None',
  84. ', '.join(data[4]) or 'None',
  85. docstring))
  86. if module not in moduledocstrings:
  87. moddoc = mod.__doc__
  88. if isinstance(moddoc, bytes):
  89. moddoc = moddoc.decode('utf8')
  90. moduledocstrings[module] = moddoc
  91. for module, lexers in sorted(modules.items(), key=lambda x: x[0]):
  92. if moduledocstrings[module] is None:
  93. raise Exception("Missing docstring for %s" % (module,))
  94. heading = moduledocstrings[module].splitlines()[4].strip().rstrip('.')
  95. out.append(MODULEDOC % (module, heading, '-'*len(heading)))
  96. for data in lexers:
  97. out.append(LEXERDOC % data)
  98. return ''.join(out)
  99. def document_formatters(self):
  100. from pygments.formatters import FORMATTERS
  101. out = []
  102. for classname, data in sorted(FORMATTERS.items(), key=lambda x: x[0]):
  103. module = data[0]
  104. mod = __import__(module, None, None, [classname])
  105. self.filenames.add(mod.__file__)
  106. cls = getattr(mod, classname)
  107. docstring = cls.__doc__
  108. if isinstance(docstring, bytes):
  109. docstring = docstring.decode('utf8')
  110. heading = cls.__name__
  111. out.append(FMTERDOC % (heading, ', '.join(data[2]) or 'None',
  112. ', '.join(data[3]).replace('*', '\\*') or 'None',
  113. docstring))
  114. return ''.join(out)
  115. def document_filters(self):
  116. from pygments.filters import FILTERS
  117. out = []
  118. for name, cls in FILTERS.items():
  119. self.filenames.add(sys.modules[cls.__module__].__file__)
  120. docstring = cls.__doc__
  121. if isinstance(docstring, bytes):
  122. docstring = docstring.decode('utf8')
  123. out.append(FILTERDOC % (cls.__name__, name, docstring))
  124. return ''.join(out)
  125. def setup(app):
  126. app.add_directive('pygmentsdoc', PygmentsDoc)