codekingpro/portable-devtools
115k
1"""2 pygments.sphinxext3 ~~~~~~~~~~~~~~~~~~4 5 Sphinx extension to generate automatic documentation of lexers,6 formatters and filters.7 8 :copyright: Copyright 2006-present by the Pygments team, see AUTHORS.9 :license: BSD, see LICENSE for details.10"""11 12import sys13 14from docutils import nodes15from docutils.statemachine import ViewList16from docutils.parsers.rst import Directive17from sphinx.util.nodes import nested_parse_with_titles18 19 20MODULEDOC = '''21.. module:: %s22 23%s24%s25'''26 27LEXERDOC = '''28.. class:: %s29 30 :Short names: %s31 :Filenames: %s32 :MIME types: %s33 34 %s35 36 %s37 38'''39 40FMTERDOC = '''41.. class:: %s42 43 :Short names: %s44 :Filenames: %s45 46 %s47 48'''49 50FILTERDOC = '''51.. class:: %s52 53 :Name: %s54 55 %s56 57'''58 59 60class PygmentsDoc(Directive):61 """62 A directive to collect all lexers/formatters/filters and generate63 autoclass directives for them.64 """65 has_content = False66 required_arguments = 167 optional_arguments = 068 final_argument_whitespace = False69 option_spec = {}70 71 def run(self):72 self.filenames = set()73 if self.arguments[0] == 'lexers':74 out = self.document_lexers()75 elif self.arguments[0] == 'formatters':76 out = self.document_formatters()77 elif self.arguments[0] == 'filters':78 out = self.document_filters()79 elif self.arguments[0] == 'lexers_overview':80 out = self.document_lexers_overview()81 else:82 raise Exception('invalid argument for "pygmentsdoc" directive')83 node = nodes.compound()84 vl = ViewList(out.split('\n'), source='')85 nested_parse_with_titles(self.state, vl, node)86 for fn in self.filenames:87 self.state.document.settings.record_dependencies.add(fn)88 return node.children89 90 def document_lexers_overview(self):91 """Generate a tabular overview of all lexers.92 93 The columns are the lexer name, the extensions handled by this lexer94 (or "None"), the aliases and a link to the lexer class."""95 from pygments.lexers._mapping import LEXERS96 import pygments.lexers97 out = []98 99 table = []100 101 def format_link(name, url):102 if url:103 return f'`{name} <{url}>`_'104 return name105 106 for classname, data in sorted(LEXERS.items(), key=lambda x: x[1][1].lower()):107 lexer_cls = pygments.lexers.find_lexer_class(data[1])108 extensions = lexer_cls.filenames + lexer_cls.alias_filenames109 110 table.append({111 'name': format_link(data[1], lexer_cls.url),112 'extensions': ', '.join(extensions).replace('*', '\\*').replace('_', '\\') or 'None',113 'aliases': ', '.join(data[2]),114 'class': f'{data[0]}.{classname}'115 })116 117 column_names = ['name', 'extensions', 'aliases', 'class']118 column_lengths = [max([len(row[column]) for row in table if row[column]])119 for column in column_names]120 121 def write_row(*columns):122 """Format a table row"""123 out = []124 for length, col in zip(column_lengths, columns):125 if col:126 out.append(col.ljust(length))127 else:128 out.append(' '*length)129 130 return ' '.join(out)131 132 def write_seperator():133 """Write a table separator row"""134 sep = ['='*c for c in column_lengths]135 return write_row(*sep)136 137 out.append(write_seperator())138 out.append(write_row('Name', 'Extension(s)', 'Short name(s)', 'Lexer class'))139 out.append(write_seperator())140 for row in table:141 out.append(write_row(142 row['name'],143 row['extensions'],144 row['aliases'],145 f':class:`~{row["class"]}`'))146 out.append(write_seperator())147 148 return '\n'.join(out)149 150 def document_lexers(self):151 from pygments.lexers._mapping import LEXERS152 import pygments153 import inspect154 import pathlib155 156 out = []157 modules = {}158 moduledocstrings = {}159 for classname, data in sorted(LEXERS.items(), key=lambda x: x[0]):160 module = data[0]161 mod = __import__(module, None, None, [classname])162 self.filenames.add(mod.__file__)163 cls = getattr(mod, classname)164 if not cls.__doc__:165 print(f"Warning: {classname} does not have a docstring.")166 docstring = cls.__doc__167 if isinstance(docstring, bytes):168 docstring = docstring.decode('utf8')169 170 example_file = getattr(cls, '_example', None)171 if example_file:172 p = pathlib.Path(inspect.getabsfile(pygments)).parent.parent /\173 'tests' / 'examplefiles' / example_file174 content = p.read_text(encoding='utf-8')175 if not content:176 raise Exception(177 f"Empty example file '{example_file}' for lexer "178 f"{classname}")179 180 if data[2]:181 lexer_name = data[2][0]182 docstring += '\n\n .. admonition:: Example\n'183 docstring += f'\n .. code-block:: {lexer_name}\n\n'184 for line in content.splitlines():185 docstring += f' {line}\n'186 187 if cls.version_added:188 version_line = f'.. versionadded:: {cls.version_added}'189 else:190 version_line = ''191 192 modules.setdefault(module, []).append((193 classname,194 ', '.join(data[2]) or 'None',195 ', '.join(data[3]).replace('*', '\\*').replace('_', '\\') or 'None',196 ', '.join(data[4]) or 'None',197 docstring,198 version_line))199 if module not in moduledocstrings:200 moddoc = mod.__doc__201 if isinstance(moddoc, bytes):202 moddoc = moddoc.decode('utf8')203 moduledocstrings[module] = moddoc204 205 for module, lexers in sorted(modules.items(), key=lambda x: x[0]):206 if moduledocstrings[module] is None:207 raise Exception(f"Missing docstring for {module}")208 heading = moduledocstrings[module].splitlines()[4].strip().rstrip('.')209 out.append(MODULEDOC % (module, heading, '-'*len(heading)))210 for data in lexers:211 out.append(LEXERDOC % data)212 213 return ''.join(out)214 215 def document_formatters(self):216 from pygments.formatters import FORMATTERS217 218 out = []219 for classname, data in sorted(FORMATTERS.items(), key=lambda x: x[0]):220 module = data[0]221 mod = __import__(module, None, None, [classname])222 self.filenames.add(mod.__file__)223 cls = getattr(mod, classname)224 docstring = cls.__doc__225 if isinstance(docstring, bytes):226 docstring = docstring.decode('utf8')227 heading = cls.__name__228 out.append(FMTERDOC % (heading, ', '.join(data[2]) or 'None',229 ', '.join(data[3]).replace('*', '\\*') or 'None',230 docstring))231 return ''.join(out)232 233 def document_filters(self):234 from pygments.filters import FILTERS235 236 out = []237 for name, cls in FILTERS.items():238 self.filenames.add(sys.modules[cls.__module__].__file__)239 docstring = cls.__doc__240 if isinstance(docstring, bytes):241 docstring = docstring.decode('utf8')242 out.append(FILTERDOC % (cls.__name__, name, docstring))243 return ''.join(out)244 245 246def setup(app):247 app.add_directive('pygmentsdoc', PygmentsDoc)248 