sphinxext.py 4.5 KB

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