"""
This file is part of GNU TALER.
Copyright (C) 2014, 2015 GNUnet e.V. and INRIA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Lesser General Public License as published by the Free Software
Foundation; either version 2.1, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along with
TALER; see the file COPYING. If not, see
@author Florian Dold
"""
"""
This extension adds a new lexer "tsref" for TypeScript, which
allows reST-style links inside comments (`LinkName`_),
and semi-automatically adds links to the definition of types.
For type TYPE, a reference to tsref-type-TYPE is added.
Known bugs and limitations:
- The way the extension works right now interferes wiht
Sphinx's caching, the build directory should be cleared
before every build.
"""
from pygments.util import get_bool_opt
from pygments.token import Name, Comment, Token, _TokenType
from pygments.filter import Filter
from sphinx.highlighting import PygmentsBridge
from sphinx.builders.html import StandaloneHTMLBuilder
from sphinx.pygments_styles import SphinxStyle
from pygments.formatters import HtmlFormatter
from docutils import nodes
from docutils.nodes import make_id
import re
_escape_html_table = {
ord('&'): u'&',
ord('<'): u'<',
ord('>'): u'>',
ord('"'): u'"',
ord("'"): u''',
}
class LinkingHtmlFormatter(HtmlFormatter):
def __init__(self, **kwargs):
super(LinkingHtmlFormatter, self).__init__(**kwargs)
self._builder = kwargs['_builder']
def _fmt(self, value, tok):
cls = self._get_css_class(tok)
href = tok_getprop(tok, "href")
caption = tok_getprop(tok, "caption")
content = caption if caption is not None else value
if href:
value = '%s' % (href, content)
if cls is None or cls == "":
return value
return '%s' % (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}
self.formatter = LinkingHtmlFormatter
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 write_doc(self, docname, doctree):
self._current_docname = docname
super(MyHtmlBuilder, self).write_doc(docname, doctree)
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*`_")
# 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(self, lexer, stream):
id_to_doc = self.app.env.domaindata.get("_tsref", {})
for ttype, value in stream:
if ttype in Token.Keyword.Type:
defname = make_id('tsref-type-' + value);
t = copy_token(ttype)
if defname in id_to_doc:
docname = id_to_doc[defname]
href = self.app.builder.get_target_uri(docname) + "#" + defname
tok_setprop(t, "href", href)
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 ttype, pre
t = copy_token(ttype)
x1, x2 = m.groups()
if x2 is None:
caption = x1.strip()
id = make_id(x1)
else:
caption = x1.strip()
id = make_id(x2)
if id in id_to_doc:
docname = id_to_doc[id]
href = self.app.builder.get_target_uri(docname) + "#" + id
tok_setprop(t, "href", href)
tok_setprop(t, "caption", caption)
else:
self.app.builder.warn("unresolved link target in comment: " + id)
yield t, m.group(1)
last = m.end()
post = value[last:]
if post:
yield ttype, post
else:
yield ttype, value
def remember_targets(app, doctree):
docname = app.env.docname
id_to_doc = app.env.domaindata.get("_tsref", None)
if id_to_doc is None:
id_to_doc = app.env.domaindata["_tsref"] = {}
for node in doctree.traverse():
if not isinstance(node, nodes.Element):
continue
ids = node.get("ids")
if ids:
for id in ids:
id_to_doc[id] = docname
def setup(app):
from sphinx.highlighting import lexers
from pygments.token import Name
from pygments.filters import NameHighlightFilter
from tslex import BetterTypeScriptLexer
lexer = BetterTypeScriptLexer()
lexer.add_filter(LinkFilter(app))
app.add_lexer('tsref', lexer)
app.add_builder(MyHtmlBuilder)
app.connect("doctree-read", remember_targets)