From c3d555524c2ed5287be3bce75d9e41c4de023956 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Fri, 27 Sep 2019 00:53:12 +0200 Subject: sphinx extension: typescript domain --- _exts/typescriptdomain.py | 545 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 545 insertions(+) create mode 100644 _exts/typescriptdomain.py (limited to '_exts') diff --git a/_exts/typescriptdomain.py b/_exts/typescriptdomain.py new file mode 100644 index 00000000..73a5adaa --- /dev/null +++ b/_exts/typescriptdomain.py @@ -0,0 +1,545 @@ +""" +TypeScript domain. + +:copyright: Copyright 2019 by Taler Systems SA +:license: LGPLv3+ +:author: Florian Dold +""" + +import re + +from docutils import nodes +from typing import List, Optional, Iterable, Dict, Tuple +from typing import cast + +from pygments.lexers import get_lexer_by_name +from pygments.filter import Filter +from pygments.token import Literal, Text, Operator, Keyword, Name, Number +from pygments.token import Comment, Token, _TokenType +from pygments.token import * +from pygments.lexer import RegexLexer, bygroups, include +from pygments.formatters import HtmlFormatter + +from docutils import nodes +from docutils.nodes import Element, Node + +from sphinx.roles import XRefRole +from sphinx.domains import Domain, ObjType, Index +from sphinx.directives import directives +from sphinx.util.docutils import SphinxDirective +from sphinx.util.nodes import make_refnode +from sphinx.util import logging +from sphinx.highlighting import PygmentsBridge +from sphinx.builders.html import StandaloneHTMLBuilder +from sphinx.pygments_styles import SphinxStyle + +logger = logging.getLogger(__name__) + + +class TypeScriptDefinition(SphinxDirective): + """ + Directive for a code block with special highlighting or line numbering + settings. + """ + + has_content = True + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = False + option_spec = { + 'force': directives.flag, + 'linenos': directives.flag, + 'dedent': int, + 'lineno-start': int, + 'emphasize-lines': directives.unchanged_required, + 'caption': directives.unchanged_required, + 'class': directives.class_option, + } + + def run(self) -> List[Node]: + document = self.state.document + code = '\n'.join(self.content) + location = self.state_machine.get_source_and_line(self.lineno) + + linespec = self.options.get('emphasize-lines') + if linespec: + try: + nlines = len(self.content) + hl_lines = parselinenos(linespec, nlines) + if any(i >= nlines for i in hl_lines): + logger.warning( + __('line number spec is out of range(1-%d): %r') % + (nlines, self.options['emphasize-lines']), + location=location + ) + + hl_lines = [x + 1 for x in hl_lines if x < nlines] + except ValueError as err: + return [document.reporter.warning(err, line=self.lineno)] + else: + hl_lines = None + + if 'dedent' in self.options: + location = self.state_machine.get_source_and_line(self.lineno) + lines = code.split('\n') + lines = dedent_lines( + lines, self.options['dedent'], location=location + ) + code = '\n'.join(lines) + + literal = nodes.literal_block(code, code) # type: Element + if 'linenos' in self.options or 'lineno-start' in self.options: + literal['linenos'] = True + literal['classes'] += self.options.get('class', []) + literal['force'] = 'force' in self.options + literal['language'] = "tsref" + extra_args = literal['highlight_args'] = {} + if hl_lines is not None: + extra_args['hl_lines'] = hl_lines + if 'lineno-start' in self.options: + extra_args['linenostart'] = self.options['lineno-start'] + self.set_source_info(literal) + + caption = self.options.get('caption') + if caption: + try: + literal = container_wrapper(self, literal, caption) + except ValueError as exc: + return [document.reporter.warning(exc, line=self.lineno)] + + tsid = "tsref-type-" + self.arguments[0] + literal['ids'].append(tsid) + + tsname = self.arguments[0] + ts = self.env.get_domain('ts') + ts.add_object('type', tsname, self.env.docname, tsid) + + return [literal] + + +class TypeScriptDomain(Domain): + """TypeScript domain.""" + + name = 'ts' + label = 'TypeScript' + + directives = { + 'def': TypeScriptDefinition, + } + + roles = { + 'type': + XRefRole( + lowercase=False, warn_dangling=True, innernodeclass=nodes.inline + ), + } + + dangling_warnings = { + 'type': 'undefined TypeScript type: %(target)s', + } + + def resolve_xref( + self, env, fromdocname, builder, typ, target, node, contnode + ): + try: + info = self.objects[(str(typ), str(target))] + except KeyError: + logger.warn("type {}/{} not found".format(typ, target)) + return None + else: + anchor = "tsref-type-{}".format(str(target)) + title = typ.upper() + ' ' + target + return make_refnode( + builder, fromdocname, info[0], anchor, contnode, title + ) + + def resolve_any_xref( + self, env, fromdocname, builder, target, node, contnode + ): + """Resolve the pending_xref *node* with the given *target*. + + The reference comes from an "any" or similar role, which means that Sphinx + don't know the type. + + For now sphinxcontrib-httpdomain doesn't resolve any xref nodes. + + :return: + list of tuples ``('domain:role', newnode)``, where ``'domain:role'`` + is the name of a role that could have created the same reference, + """ + ret = [] + try: + info = self.objects[("type", str(target))] + except KeyError: + pass + else: + anchor = "tsref-type-{}".format(str(target)) + title = "TYPE" + ' ' + target + node = make_refnode( + builder, fromdocname, info[0], anchor, contnode, title + ) + ret.append(("ts:type", node)) + return ret + + @property + def objects(self) -> Dict[Tuple[str, str], Tuple[str, str]]: + return self.data.setdefault( + 'objects', {} + ) # (objtype, name) -> docname, labelid + + def add_object( + self, objtype: str, name: str, docname: str, labelid: str + ) -> None: + self.objects[objtype, name] = (docname, labelid) + + +class BetterTypeScriptLexer(RegexLexer): + """ + For `TypeScript `_ source code. + """ + + name = 'TypeScript' + aliases = ['ts'] + filenames = ['*.ts'] + mimetypes = ['text/x-typescript'] + + flags = re.DOTALL + tokens = { + 'commentsandwhitespace': [(r'\s+', Text), (r'