Merge pull request #6332 from tk0miya/rest_renderer

Add ReSTRenderer; a helper for rendering reST text
This commit is contained in:
Takeshi KOMIYA 2019-05-03 23:15:35 +09:00 committed by GitHub
commit 035d5507f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 121 additions and 3 deletions

View File

@ -9,11 +9,14 @@
""" """
import re import re
from collections import defaultdict
from contextlib import contextmanager from contextlib import contextmanager
from unicodedata import east_asian_width
from docutils.parsers.rst import roles from docutils.parsers.rst import roles
from docutils.parsers.rst.languages import en as english from docutils.parsers.rst.languages import en as english
from docutils.utils import Reporter from docutils.utils import Reporter
from jinja2 import environmentfilter
from sphinx.locale import __ from sphinx.locale import __
from sphinx.util import docutils from sphinx.util import docutils
@ -21,13 +24,20 @@ from sphinx.util import logging
if False: if False:
# For type annotation # For type annotation
from typing import Generator # NOQA from typing import Callable, Dict, Generator # NOQA
from docutils.statemachine import StringList # NOQA from docutils.statemachine import StringList # NOQA
from jinja2 import Environment # NOQA
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
docinfo_re = re.compile(':\\w+:.*?') docinfo_re = re.compile(':\\w+:.*?')
symbols_re = re.compile(r'([!-\-/:-@\[-`{-~])') # symbols without dot(0x2e) symbols_re = re.compile(r'([!-\-/:-@\[-`{-~])') # symbols without dot(0x2e)
SECTIONING_CHARS = ['=', '-', '~']
# width of characters
WIDECHARS = defaultdict(lambda: "WF") # type: Dict[str, str]
# WF: Wide + Full-width
WIDECHARS["ja"] = "WFA" # In Japanese, Ambiguous characters also have double width
def escape(text): def escape(text):
@ -37,6 +47,29 @@ def escape(text):
return text return text
def textwidth(text, widechars='WF'):
# type: (str, str) -> int
"""Get width of text."""
def charwidth(char, widechars):
# type: (str, str) -> int
if east_asian_width(char) in widechars:
return 2
else:
return 1
return sum(charwidth(c, widechars) for c in text)
@environmentfilter
def heading(env, text, level=1):
# type: (Environment, str, int) -> str
"""Create a heading for *level*."""
assert level <= 3
width = textwidth(text, WIDECHARS[env.language]) # type: ignore
sectioning_char = SECTIONING_CHARS[level - 1]
return '%s\n%s' % (text, sectioning_char * width)
@contextmanager @contextmanager
def default_role(docname, name): def default_role(docname, name):
# type: (str, str) -> Generator # type: (str, str) -> Generator

View File

@ -15,7 +15,7 @@ from jinja2.sandbox import SandboxedEnvironment
from sphinx import package_dir from sphinx import package_dir
from sphinx.jinja2glue import SphinxFileSystemLoader from sphinx.jinja2glue import SphinxFileSystemLoader
from sphinx.locale import get_translator from sphinx.locale import get_translator
from sphinx.util import texescape from sphinx.util import rst, texescape
if False: if False:
# For type annotation # For type annotation
@ -84,3 +84,17 @@ class LaTeXRenderer(SphinxRenderer):
self.env.variable_end_string = '%>' self.env.variable_end_string = '%>'
self.env.block_start_string = '<%' self.env.block_start_string = '<%'
self.env.block_end_string = '%>' self.env.block_end_string = '%>'
class ReSTRenderer(SphinxRenderer):
def __init__(self, template_path=None, language=None):
# type: (str, str) -> None
super().__init__(template_path)
# add language to environment
self.env.extend(language=language)
# use texescape as escape filter
self.env.filters['e'] = rst.escape
self.env.filters['escape'] = rst.escape
self.env.filters['heading'] = rst.heading

View File

@ -9,8 +9,11 @@
""" """
from docutils.statemachine import StringList from docutils.statemachine import StringList
from jinja2 import Environment
from sphinx.util.rst import append_epilog, escape, prepend_prolog from sphinx.util.rst import (
append_epilog, escape, heading, prepend_prolog, textwidth
)
def test_escape(): def test_escape():
@ -83,3 +86,34 @@ def test_prepend_prolog_without_CR(app):
('<generated>', 0, ''), ('<generated>', 0, ''),
('dummy.rst', 0, 'hello Sphinx world'), ('dummy.rst', 0, 'hello Sphinx world'),
('dummy.rst', 1, 'Sphinx is a document generator')] ('dummy.rst', 1, 'Sphinx is a document generator')]
def test_textwidth():
assert textwidth('Hello') == 5
assert textwidth('русский язык') == 12
assert textwidth('русский язык', 'WFA') == 23 # Cyrillic are ambiguous chars
def test_heading():
env = Environment()
env.extend(language=None)
assert heading(env, 'Hello') == ('Hello\n'
'=====')
assert heading(env, 'Hello', 1) == ('Hello\n'
'=====')
assert heading(env, 'Hello', 2) == ('Hello\n'
'-----')
assert heading(env, 'Hello', 3) == ('Hello\n'
'~~~~~')
assert heading(env, 'русский язык', 1) == (
'русский язык\n'
'============'
)
# language=ja: ambiguous
env.language = 'ja'
assert heading(env, 'русский язык', 1) == (
'русский язык\n'
'======================='
)

View File

@ -0,0 +1,37 @@
"""
test_util_template
~~~~~~~~~~~~~~~~~~
Tests sphinx.util.template functions.
:copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
from sphinx.util.template import ReSTRenderer
def test_ReSTRenderer_escape():
r = ReSTRenderer()
template = '{{ "*hello*" | e }}'
assert r.render_string(template, {}) == r'\*hello\*'
def test_ReSTRenderer_heading():
r = ReSTRenderer()
template = '{{ "hello" | heading }}'
assert r.render_string(template, {}) == 'hello\n====='
template = '{{ "hello" | heading(1) }}'
assert r.render_string(template, {}) == 'hello\n====='
template = '{{ "русский язык" | heading(2) }}'
assert r.render_string(template, {}) == ('русский язык\n'
'------------')
# language: ja
r.env.language = 'ja'
template = '{{ "русский язык" | heading }}'
assert r.render_string(template, {}) == ('русский язык\n'
'=======================')