123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239 |
- """
- pygments.sphinxext
- ~~~~~~~~~~~~~~~~~~
- Sphinx extension to generate automatic documentation of lexers,
- formatters and filters.
- :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
- :license: BSD, see LICENSE for details.
- """
- import sys
- from docutils import nodes
- from docutils.statemachine import ViewList
- from docutils.parsers.rst import Directive
- from sphinx.util.nodes import nested_parse_with_titles
- MODULEDOC = '''
- .. module:: %s
- %s
- %s
- '''
- LEXERDOC = '''
- .. class:: %s
- :Short names: %s
- :Filenames: %s
- :MIME types: %s
- %s
- '''
- FMTERDOC = '''
- .. class:: %s
- :Short names: %s
- :Filenames: %s
- %s
- '''
- FILTERDOC = '''
- .. class:: %s
- :Name: %s
- %s
- '''
- class PygmentsDoc(Directive):
- """
- A directive to collect all lexers/formatters/filters and generate
- autoclass directives for them.
- """
- has_content = False
- required_arguments = 1
- optional_arguments = 0
- final_argument_whitespace = False
- option_spec = {}
- def run(self):
- self.filenames = set()
- if self.arguments[0] == 'lexers':
- out = self.document_lexers()
- elif self.arguments[0] == 'formatters':
- out = self.document_formatters()
- elif self.arguments[0] == 'filters':
- out = self.document_filters()
- elif self.arguments[0] == 'lexers_overview':
- out = self.document_lexers_overview()
- else:
- raise Exception('invalid argument for "pygmentsdoc" directive')
- node = nodes.compound()
- vl = ViewList(out.split('\n'), source='')
- nested_parse_with_titles(self.state, vl, node)
- for fn in self.filenames:
- self.state.document.settings.record_dependencies.add(fn)
- return node.children
- def document_lexers_overview(self):
- """Generate a tabular overview of all lexers.
- The columns are the lexer name, the extensions handled by this lexer
- (or "None"), the aliases and a link to the lexer class."""
- from pygments.lexers._mapping import LEXERS
- import pygments.lexers
- out = []
- table = []
- def format_link(name, url):
- if url:
- return f'`{name} <{url}>`_'
- return name
- for classname, data in sorted(LEXERS.items(), key=lambda x: x[1][1].lower()):
- lexer_cls = pygments.lexers.find_lexer_class(data[1])
- extensions = lexer_cls.filenames + lexer_cls.alias_filenames
- table.append({
- 'name': format_link(data[1], lexer_cls.url),
- 'extensions': ', '.join(extensions).replace('*', '\\*').replace('_', '\\') or 'None',
- 'aliases': ', '.join(data[2]),
- 'class': f'{data[0]}.{classname}'
- })
- column_names = ['name', 'extensions', 'aliases', 'class']
- column_lengths = [max([len(row[column]) for row in table if row[column]])
- for column in column_names]
- def write_row(*columns):
- """Format a table row"""
- out = []
- for l, c in zip(column_lengths, columns):
- if c:
- out.append(c.ljust(l))
- else:
- out.append(' '*l)
- return ' '.join(out)
- def write_seperator():
- """Write a table separator row"""
- sep = ['='*c for c in column_lengths]
- return write_row(*sep)
- out.append(write_seperator())
- out.append(write_row('Name', 'Extension(s)', 'Short name(s)', 'Lexer class'))
- out.append(write_seperator())
- for row in table:
- out.append(write_row(
- row['name'],
- row['extensions'],
- row['aliases'],
- f':class:`~{row["class"]}`'))
- out.append(write_seperator())
- return '\n'.join(out)
- def document_lexers(self):
- from pygments.lexers._mapping import LEXERS
- import pygments
- import inspect
- import pathlib
- out = []
- modules = {}
- moduledocstrings = {}
- for classname, data in sorted(LEXERS.items(), key=lambda x: x[0]):
- module = data[0]
- mod = __import__(module, None, None, [classname])
- self.filenames.add(mod.__file__)
- cls = getattr(mod, classname)
- if not cls.__doc__:
- print("Warning: %s does not have a docstring." % classname)
- docstring = cls.__doc__
- if isinstance(docstring, bytes):
- docstring = docstring.decode('utf8')
- example_file = getattr(cls, '_example', None)
- if example_file:
- p = pathlib.Path(inspect.getabsfile(pygments)).parent.parent /\
- 'tests' / 'examplefiles' / example_file
- content = p.read_text(encoding='utf-8')
- if not content:
- raise Exception(
- f"Empty example file '{example_file}' for lexer "
- f"{classname}")
- if data[2]:
- lexer_name = data[2][0]
- docstring += '\n\n .. admonition:: Example\n'
- docstring += f'\n .. code-block:: {lexer_name}\n\n'
- for line in content.splitlines():
- docstring += f' {line}\n'
- modules.setdefault(module, []).append((
- classname,
- ', '.join(data[2]) or 'None',
- ', '.join(data[3]).replace('*', '\\*').replace('_', '\\') or 'None',
- ', '.join(data[4]) or 'None',
- docstring))
- if module not in moduledocstrings:
- moddoc = mod.__doc__
- if isinstance(moddoc, bytes):
- moddoc = moddoc.decode('utf8')
- moduledocstrings[module] = moddoc
- for module, lexers in sorted(modules.items(), key=lambda x: x[0]):
- if moduledocstrings[module] is None:
- raise Exception("Missing docstring for %s" % (module,))
- heading = moduledocstrings[module].splitlines()[4].strip().rstrip('.')
- out.append(MODULEDOC % (module, heading, '-'*len(heading)))
- for data in lexers:
- out.append(LEXERDOC % data)
- return ''.join(out)
- def document_formatters(self):
- from pygments.formatters import FORMATTERS
- out = []
- for classname, data in sorted(FORMATTERS.items(), key=lambda x: x[0]):
- module = data[0]
- mod = __import__(module, None, None, [classname])
- self.filenames.add(mod.__file__)
- cls = getattr(mod, classname)
- docstring = cls.__doc__
- if isinstance(docstring, bytes):
- docstring = docstring.decode('utf8')
- heading = cls.__name__
- out.append(FMTERDOC % (heading, ', '.join(data[2]) or 'None',
- ', '.join(data[3]).replace('*', '\\*') or 'None',
- docstring))
- return ''.join(out)
- def document_filters(self):
- from pygments.filters import FILTERS
- out = []
- for name, cls in FILTERS.items():
- self.filenames.add(sys.modules[cls.__module__].__file__)
- docstring = cls.__doc__
- if isinstance(docstring, bytes):
- docstring = docstring.decode('utf8')
- out.append(FILTERDOC % (cls.__name__, name, docstring))
- return ''.join(out)
- def setup(app):
- app.add_directive('pygmentsdoc', PygmentsDoc)
|