summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sphinx/util/rst.py35
-rw-r--r--sphinx/util/template.py16
-rw-r--r--tests/test_util_rst.py36
-rw-r--r--tests/test_util_template.py37
4 files changed, 121 insertions, 3 deletions
diff --git a/sphinx/util/rst.py b/sphinx/util/rst.py
index c3d32feb7..c897b075a 100644
--- a/sphinx/util/rst.py
+++ b/sphinx/util/rst.py
@@ -9,11 +9,14 @@
"""
import re
+from collections import defaultdict
from contextlib import contextmanager
+from unicodedata import east_asian_width
from docutils.parsers.rst import roles
from docutils.parsers.rst.languages import en as english
from docutils.utils import Reporter
+from jinja2 import environmentfilter
from sphinx.locale import __
from sphinx.util import docutils
@@ -21,13 +24,20 @@ from sphinx.util import logging
if False:
# For type annotation
- from typing import Generator # NOQA
+ from typing import Callable, Dict, Generator # NOQA
from docutils.statemachine import StringList # NOQA
+ from jinja2 import Environment # NOQA
logger = logging.getLogger(__name__)
docinfo_re = re.compile(':\\w+:.*?')
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):
@@ -37,6 +47,29 @@ def escape(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
def default_role(docname, name):
# type: (str, str) -> Generator
diff --git a/sphinx/util/template.py b/sphinx/util/template.py
index c33e16819..b521c5c79 100644
--- a/sphinx/util/template.py
+++ b/sphinx/util/template.py
@@ -15,7 +15,7 @@ from jinja2.sandbox import SandboxedEnvironment
from sphinx import package_dir
from sphinx.jinja2glue import SphinxFileSystemLoader
from sphinx.locale import get_translator
-from sphinx.util import texescape
+from sphinx.util import rst, texescape
if False:
# For type annotation
@@ -84,3 +84,17 @@ class LaTeXRenderer(SphinxRenderer):
self.env.variable_end_string = '%>'
self.env.block_start_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
diff --git a/tests/test_util_rst.py b/tests/test_util_rst.py
index ba836ff1e..1e72eda45 100644
--- a/tests/test_util_rst.py
+++ b/tests/test_util_rst.py
@@ -9,8 +9,11 @@
"""
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():
@@ -83,3 +86,34 @@ def test_prepend_prolog_without_CR(app):
('<generated>', 0, ''),
('dummy.rst', 0, 'hello Sphinx world'),
('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'
+ '======================='
+ )
diff --git a/tests/test_util_template.py b/tests/test_util_template.py
new file mode 100644
index 000000000..b25e9dc87
--- /dev/null
+++ b/tests/test_util_template.py
@@ -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'
+ '=======================')