gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[GNUnet-SVN] [taler-docs] 03/05: sphinx extension: typescript domain


From: gnunet
Subject: [GNUnet-SVN] [taler-docs] 03/05: sphinx extension: typescript domain
Date: Fri, 27 Sep 2019 00:55:11 +0200

This is an automated email from the git hooks/post-receive script.

dold pushed a commit to branch master
in repository docs.

commit c3d555524c2ed5287be3bce75d9e41c4de023956
Author: Florian Dold <address@hidden>
AuthorDate: Fri Sep 27 00:53:12 2019 +0200

    sphinx extension: typescript domain
---
 _exts/typescriptdomain.py | 545 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 545 insertions(+)

diff --git a/_exts/typescriptdomain.py b/_exts/typescriptdomain.py
new file mode 100644
index 0000000..73a5ada
--- /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 <https://www.typescriptlang.org/>`_ source code.
+    """
+
+    name = 'TypeScript'
+    aliases = ['ts']
+    filenames = ['*.ts']
+    mimetypes = ['text/x-typescript']
+
+    flags = re.DOTALL
+    tokens = {
+        'commentsandwhitespace': [(r'\s+', Text), (r'<!--', Comment),
+                                  (r'//.*?\n', Comment.Single),
+                                  (r'/\*.*?\*/', Comment.Multiline)],
+        'slashstartsregex': [
+            include('commentsandwhitespace'),
+            (
+                r'/(\\.|[^[/\\\n]|\[(\\.|[^\]\\\n])*])+/'
+                r'([gim]+\b|\B)', String.Regex, '#pop'
+            ), (r'(?=/)', Text, ('#pop', 'badregex')), (r'', Text, '#pop')
+        ],
+        'badregex': [(r'\n', Text, '#pop')],
+        'typeexp': [
+            (r'[a-zA-Z0-9_?.$]+', Keyword.Type),
+            (r'\s+', Text),
+            (r'[|]', Text),
+            (r'\n', Text, "#pop"),
+            (r';', Text, "#pop"),
+            (r'', Text, "#pop"),
+        ],
+        'root': [
+            (r'^(?=\s|/|<!--)', Text, 'slashstartsregex'),
+            include('commentsandwhitespace'),
+            (
+                r'\+\+|--|~|&&|\?|:|\|\||\\(?=\n)|'
+                r'(<<|>>>?|==?|!=?|[-<>+*%&\|\^/])=?', Operator,
+                'slashstartsregex'
+            ),
+            (r'[{(\[;,]', Punctuation, 'slashstartsregex'),
+            (r'[})\].]', Punctuation),
+            (
+                
r'(for|in|while|do|break|return|continue|switch|case|default|if|else|'
+                r'throw|try|catch|finally|new|delete|typeof|instanceof|void|'
+                r'this)\b', Keyword, 'slashstartsregex'
+            ),
+            (
+                r'(var|let|const|with|function)\b', Keyword.Declaration,
+                'slashstartsregex'
+            ),
+            (
+                
r'(abstract|boolean|byte|char|class|const|debugger|double|enum|export|'
+                
r'extends|final|float|goto|implements|import|int|interface|long|native|'
+                
r'package|private|protected|public|short|static|super|synchronized|throws|'
+                r'transient|volatile)\b', Keyword.Reserved
+            ),
+            (r'(true|false|null|NaN|Infinity|undefined)\b', Keyword.Constant),
+            (
+                r'(Array|Boolean|Date|Error|Function|Math|netscape|'
+                r'Number|Object|Packages|RegExp|String|sun|decodeURI|'
+                r'decodeURIComponent|encodeURI|encodeURIComponent|'
+                r'Error|eval|isFinite|isNaN|parseFloat|parseInt|document|this|'
+                r'window)\b', Name.Builtin
+            ),
+            # Match stuff like: module name {...}
+            (
+                r'\b(module)(\s*)(\s*[a-zA-Z0-9_?.$][\w?.$]*)(\s*)',
+                bygroups(Keyword.Reserved, Text, Name.Other,
+                         Text), 'slashstartsregex'
+            ),
+            # Match variable type keywords
+            (r'\b(string|bool|number)\b', Keyword.Type),
+            # Match stuff like: constructor
+            (r'\b(constructor|declare|interface|as|AS)\b', Keyword.Reserved),
+            # Match stuff like: super(argument, list)
+            (
+                r'(super)(\s*)\(([a-zA-Z0-9,_?.$\s]+\s*)\)',
+                bygroups(Keyword.Reserved, Text), 'slashstartsregex'
+            ),
+            # Match stuff like: function() {...}
+            (r'([a-zA-Z_?.$][\w?.$]*)\(\) \{', Name.Other, 'slashstartsregex'),
+            # Match stuff like: (function: return type)
+            (
+                r'([a-zA-Z0-9_?.$][\w?.$]*)(\s*:\s*)',
+                bygroups(Name.Other, Text), 'typeexp'
+            ),
+            # Match stuff like: type Foo = Bar | Baz
+            (
+                r'\b(type)(\s*)([a-zA-Z0-9_?.$]+)(\s*)(=)(\s*)',
+                bygroups(
+                    Keyword.Reserved, Text, Name.Other, Text, Operator, Text
+                ), 'typeexp'
+            ),
+            (r'[$a-zA-Z_][a-zA-Z0-9_]*', Name.Other),
+            (r'[0-9][0-9]*\.[0-9]+([eE][0-9]+)?[fd]?', Number.Float),
+            (r'0x[0-9a-fA-F]+', Number.Hex),
+            (r'[0-9]+', Number.Integer),
+            (r'"(\\\\|\\"|[^"])*"', String.Double),
+            (r"'(\\\\|\\'|[^'])*'", String.Single),
+        ]
+    }
+
+
+# Map from token id to props.
+# Properties can't be added to tokens
+# since they derive from Python's tuple.
+token_props = {}
+
+
+class LinkFilter(Filter):
+    def __init__(self, app, **options):
+        self.app = app
+        Filter.__init__(self, **options)
+
+    def _filter_one_literal(self, ttype, value):
+        last = 0
+        for m in re.finditer(literal_reg, value):
+            pre = value[last:m.start()]
+            if pre:
+                yield ttype, pre
+            t = copy_token(ttype)
+            tok_setprop(t, "is_literal", True)
+            yield t, m.group(1)
+            last = m.end()
+        post = value[last:]
+        if post:
+            yield ttype, post
+
+
+    def filter(self, lexer, stream):
+        for ttype, value in stream:
+            if ttype in Token.Keyword.Type:
+                t = copy_token(ttype)
+                tok_setprop(t, "xref", value.strip())
+                tok_setprop(t, "is_identifier", True)
+                yield t, value
+            elif ttype in Token.Comment:
+                last = 0
+                for m in re.finditer(link_reg, value):
+                    pre = value[last:m.start()]
+                    if pre:
+                        yield from self._filter_one_literal(ttype, pre)
+                    t = copy_token(ttype)
+                    x1, x2 = m.groups()
+                    x0 = m.group(0)
+                    if x2 is None:
+                        caption = x1.strip()
+                        xref = x1.strip()
+                    else:
+                        caption = x1.strip()
+                        xref = x2.strip()
+                    tok_setprop(t, "xref", xref)
+                    tok_setprop(t, "caption", caption)
+                    if x0.endswith("_"):
+                        tok_setprop(t, "trailing_underscore", True)
+                    yield t, m.group(1)
+                    last = m.end()
+                post = value[last:]
+                if post:
+                    yield from self._filter_one_literal(ttype, post)
+            else:
+                yield ttype, value
+
+
+_escape_html_table = {
+    ord('&'): u'&amp;',
+    ord('<'): u'&lt;',
+    ord('>'): u'&gt;',
+    ord('"'): u'&quot;',
+    ord("'"): u'&#39;',
+}
+
+
+class LinkingHtmlFormatter(HtmlFormatter):
+    def __init__(self, **kwargs):
+        super(LinkingHtmlFormatter, self).__init__(**kwargs)
+        self._builder = kwargs['_builder']
+        self._bridge = kwargs['_bridge']
+
+    def _get_value(self, value, tok):
+        xref = tok_getprop(tok, "xref")
+        caption = tok_getprop(tok, "caption")
+
+        if tok_getprop(tok, "is_literal"):
+            return '<span style="font-weight: bolder">%s</span>' % (value,)
+
+        if tok_getprop(tok, "trailing_underscore"):
+            logger.warn(
+                "{}:{}: code block contains xref to '{}' with unsupported 
trailing underscore"
+                .format(self._bridge.path, self._bridge.line, xref)
+            )
+
+        if tok_getprop(tok, "is_identifier"):
+            if xref.startswith('"'):
+                return value
+            if xref in ("number", "object", "string", "boolean", "any", 
"true", "false"):
+                return value
+
+        if xref is None:
+            return value
+        content = caption if caption is not None else value
+        ts = self._builder.env.get_domain('ts')
+        r1 = ts.objects.get(("type", xref), None)
+        if r1 is not None:
+            rel_uri = self._builder.get_relative_uri(self._bridge.docname, 
r1[0]) + "#" + r1[1]
+            return '<a style="color:inherit;text-decoration:underline" 
href="%s">%s</a>' % (
+                rel_uri, content
+            )
+
+        std = self._builder.env.get_domain('std')
+        r2 = std.labels.get(xref.lower(), None)
+        if r2 is not None:
+            rel_uri = self._builder.get_relative_uri(self._bridge.docname, 
r2[0]) + "#" + r2[1]
+            return '<a style="color:inherit;text-decoration:underline" 
href="%s">%s</a>' % (
+                rel_uri, content
+            )
+        r3 = std.anonlabels.get(xref.lower(), None)
+        if r3 is not None:
+            rel_uri = self._builder.get_relative_uri(self._bridge.docname, 
r3[0]) + "#" + r3[1]
+            return '<a style="color:inherit;text-decoration:underline" 
href="%s">%s</a>' % (
+                rel_uri, content
+            )
+
+        logger.warn("{}:{}: code block contains unresolved xref 
'{}'".format(self._bridge.path, self._bridge.line, xref))
+
+        return value
+
+
+    def _fmt(self, value, tok):
+        cls = self._get_css_class(tok)
+        value = self._get_value(value, tok)
+        if cls is None or cls == "":
+            return value
+        return '<span class="%s">%s</span>' % (cls, value)
+
+    def _format_lines(self, tokensource):
+        """
+        Just format the tokens, without any wrapping tags.
+        Yield individual lines.
+        """
+        lsep = self.lineseparator
+        escape_table = _escape_html_table
+
+        line = ''
+        for ttype, value in tokensource:
+            link = get_annotation(ttype, "link")
+
+            parts = value.translate(escape_table).split('\n')
+
+            if len(parts) == 0:
+                # empty token, usually should not happen
+                pass
+            elif len(parts) == 1:
+                # no newline before or after token
+                line += self._fmt(parts[0], ttype)
+            else:
+                line += self._fmt(parts[0], ttype)
+                yield 1, line + lsep
+                for part in parts[1:-1]:
+                    yield 1, self._fmt(part, ttype) + lsep
+                line = self._fmt(parts[-1], ttype)
+
+        if line:
+            yield 1, line + lsep
+
+
+class MyPygmentsBridge(PygmentsBridge):
+    def __init__(self, builder, trim_doctest_flags):
+        self.dest = "html"
+        self.trim_doctest_flags = trim_doctest_flags
+        self.formatter_args = {
+            'style': SphinxStyle,
+            '_builder': builder,
+            '_bridge': self
+        }
+        self.formatter = LinkingHtmlFormatter
+        self.builder = builder
+        self.path = None
+        self.line = None
+        self.docname = None
+
+    def highlight_block(
+        self, source, lang, opts=None, force=False, location=None, **kwargs
+    ):
+        docname, line = location
+        self.line = line
+        self.path = self.builder.env.doc2path(docname)
+        self.docname = docname
+        return super().highlight_block(
+            source, lang, opts, force, location, **kwargs
+        )
+
+
+class MyHtmlBuilder(StandaloneHTMLBuilder):
+    name = "html-linked"
+
+    def init_highlighter(self):
+        if self.config.pygments_style is not None:
+            style = self.config.pygments_style
+        elif self.theme:
+            style = self.theme.get_confstr('theme', 'pygments_style', 'none')
+        else:
+            style = 'sphinx'
+        self.highlighter = MyPygmentsBridge(
+            self, self.config.trim_doctest_flags
+        )
+
+
+def get_annotation(tok, key):
+    if not hasattr(tok, "kv"):
+        return None
+    return tok.kv.get(key)
+
+
+def copy_token(tok):
+    new_tok = _TokenType(tok)
+    # This part is very fragile against API changes ...
+    new_tok.subtypes = set(tok.subtypes)
+    new_tok.parent = tok.parent
+    return new_tok
+
+
+def tok_setprop(tok, key, value):
+    tokid = id(tok)
+    e = token_props.get(tokid)
+    if e is None:
+        e = token_props[tokid] = (tok, {})
+    _, kv = e
+    kv[key] = value
+
+
+def tok_getprop(tok, key):
+    tokid = id(tok)
+    e = token_props.get(tokid)
+    if e is None:
+        return None
+    _, kv = e
+    return kv.get(key)
+
+
+link_reg = re.compile(r"(?<!`)`([^`<]+)\s*(?:<([^>]+)>)?\s*`_?")
+literal_reg = re.compile(r"``([^`]+)``")
+
+
+def setup(app):
+    lexer = BetterTypeScriptLexer()
+    lexer.add_filter(LinkFilter(app))
+    app.add_lexer('tsref', lexer)
+    app.add_domain(TypeScriptDomain)
+    app.add_builder(MyHtmlBuilder)

-- 
To stop receiving notification emails like this one, please contact
address@hidden.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]