# -*- coding: utf-8 -*- """ test_intl ~~~~~~~~~ Test message patching for internationalization purposes. Runs the text builder in the test root. :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ from __future__ import print_function import os import re from subprocess import Popen, PIPE from xml.etree import ElementTree from nose.tools import assert_equal from six import string_types from util import tempdir, rootdir, path, gen_with_app, SkipTest, \ assert_re_search, assert_not_re_search, assert_in, assert_not_in, \ assert_startswith root = tempdir / 'test-intl' def gen_with_intl_app(*args, **kw): default_kw = { 'testroot': 'intl', 'confoverrides': { 'language': 'xx', 'locale_dirs': ['.'], 'gettext_compact': False, }, } default_kw.update(kw) return gen_with_app(*args, **default_kw) def setup_module(): if not root.exists(): (rootdir / 'roots' / 'test-intl').copytree(root) # Delete remnants left over after failed build # Compile all required catalogs into binary format (*.mo). for dirpath, dirs, files in os.walk(root): dirpath = path(dirpath) for f in [f for f in files if f.endswith('.po')]: po = dirpath / f mo = root / 'xx' / 'LC_MESSAGES' / ( os.path.relpath(po[:-3], root) + '.mo') if not mo.parent.exists(): mo.parent.makedirs() try: p = Popen(['msgfmt', po, '-o', mo], stdout=PIPE, stderr=PIPE) except OSError: raise SkipTest # most likely msgfmt was not found else: stdout, stderr = p.communicate() if p.returncode != 0: print(stdout) print(stderr) assert False, \ 'msgfmt exited with return code %s' % p.returncode assert mo.isfile(), 'msgfmt failed' def elem_gettexts(elem): def itertext(self): # this function copied from Python-2.7 'ElementTree.itertext'. # for compatibility to Python-2.6 tag = self.tag if not isinstance(tag, string_types) and tag is not None: return if self.text: yield self.text for e in self: for s in itertext(e): yield s if e.tail: yield e.tail return [_f for _f in [s.strip() for s in itertext(elem)] if _f] def elem_getref(elem): return elem.attrib.get('refid') or elem.attrib.get('refuri') def assert_elem(elem, texts=None, refs=None, names=None): if texts is not None: _texts = elem_gettexts(elem) assert _texts == texts if refs is not None: _refs = [elem_getref(x) for x in elem.findall('reference')] assert _refs == refs if names is not None: _names = elem.attrib.get('names').split() assert _names == names @gen_with_intl_app('text', freshenv=True) def test_text_builder(app, status, warning): app.builder.build_all() # --- warnings in translation warnings = warning.getvalue().replace(os.sep, '/') warning_expr = u'.*/warnings.txt:4: ' \ u'WARNING: Inline literal start-string without end-string.\n' yield assert_re_search, warning_expr, warnings result = (app.outdir / 'warnings.txt').text(encoding='utf-8') expect = (u"\nI18N WITH REST WARNINGS" u"\n***********************\n" u"\nLINE OF >>``<reference') yield assert_equal, len(re.findall(expected_expr, result)), 2 expected_expr = ('reference') yield assert_equal, len(re.findall(expected_expr, result)), 0 expected_expr = ('I18N WITH ' 'REFS INCONSISTENCY') yield assert_equal, len(re.findall(expected_expr, result)), 1 # --- index entries: regression test for #976 result = (app.outdir / 'genindex.html').text(encoding='utf-8') def wrap(tag, keyword): start_tag = "<%s[^>]*>" % tag end_tag = "" % tag return r"%s\s*%s\s*%s" % (start_tag, keyword, end_tag) expected_exprs = [ wrap('a', 'NEWSLETTER'), wrap('a', 'MAILING LIST'), wrap('a', 'RECIPIENTS LIST'), wrap('a', 'FIRST SECOND'), wrap('a', 'SECOND THIRD'), wrap('a', 'THIRD, FIRST'), wrap('dt', 'ENTRY'), wrap('dt', 'SEE'), wrap('a', 'MODULE'), wrap('a', 'KEYWORD'), wrap('a', 'OPERATOR'), wrap('a', 'OBJECT'), wrap('a', 'EXCEPTION'), wrap('a', 'STATEMENT'), wrap('a', 'BUILTIN'), ] for expr in expected_exprs: yield assert_re_search, expr, result, re.M # --- versionchanges result = (app.outdir / 'versionchange.html').text(encoding='utf-8') def get_content(result, name): matched = re.search(r'
\n*(.*?)
' % name, result, re.DOTALL) if matched: return matched.group(1) else: return '' expect1 = ( u"""

Deprecated since version 1.0: """ u"""THIS IS THE FIRST PARAGRAPH OF DEPRECATED.

\n""" u"""

THIS IS THE SECOND PARAGRAPH OF DEPRECATED.

\n""") matched_content = get_content(result, "deprecated") yield assert_equal, expect1, matched_content expect2 = ( u"""

New in version 1.0: """ u"""THIS IS THE FIRST PARAGRAPH OF VERSIONADDED.

\n""") matched_content = get_content(result, "versionadded") yield assert_equal, expect2, matched_content expect3 = ( u"""

Changed in version 1.0: """ u"""THIS IS THE FIRST PARAGRAPH OF VERSIONCHANGED.

\n""") matched_content = get_content(result, "versionchanged") yield assert_equal, expect3, matched_content # --- docfields # expect no error by build (app.outdir / 'docfields.html').text(encoding='utf-8') # --- gettext template result = (app.outdir / 'index.html').text(encoding='utf-8') yield assert_in, "WELCOME", result yield assert_in, "SPHINX 2013.120", result # --- rebuild by .mo mtime app.builder.build_update() updated = app.env.update(app.config, app.srcdir, app.doctreedir, app) yield assert_equal, len(updated), 0 (app.srcdir / 'xx' / 'LC_MESSAGES' / 'bom.mo').utime(None) updated = app.env.update(app.config, app.srcdir, app.doctreedir, app) yield assert_equal, len(updated), 1 @gen_with_intl_app('xml', freshenv=True) def test_xml_builder(app, status, warning): app.builder.build_all() # --- footnotes: regression test for fix #955, #1176 et = ElementTree.parse(app.outdir / 'footnote.xml') secs = et.findall('section') para0 = secs[0].findall('paragraph') yield (assert_elem, para0[0], ['I18N WITH FOOTNOTE', 'INCLUDE THIS CONTENTS', '2', '[ref]', '1', '100', '.'], ['i18n-with-footnote', 'ref']) footnote0 = secs[0].findall('footnote') yield (assert_elem, footnote0[0], ['1', 'THIS IS A AUTO NUMBERED FOOTNOTE.'], None, ['1']) yield (assert_elem, footnote0[1], ['100', 'THIS IS A NUMBERED FOOTNOTE.'], None, ['100']) yield (assert_elem, footnote0[2], ['2', 'THIS IS A AUTO NUMBERED NAMED FOOTNOTE.'], None, ['named']) citation0 = secs[0].findall('citation') yield (assert_elem, citation0[0], ['ref', 'THIS IS A NAMED FOOTNOTE.'], None, ['ref']) warnings = warning.getvalue().replace(os.sep, '/') warning_expr = u'.*/footnote.xml:\\d*: SEVERE: Duplicate ID: ".*".\n' yield assert_not_re_search, warning_expr, warnings # --- footnote backlinks: i18n test for #1058 et = ElementTree.parse(app.outdir / 'footnote.xml') secs = et.findall('section') para0 = secs[0].findall('paragraph') refs0 = para0[0].findall('footnote_reference') refid2id = dict([ (r.attrib.get('refid'), r.attrib.get('ids')) for r in refs0]) footnote0 = secs[0].findall('footnote') for footnote in footnote0: ids = footnote.attrib.get('ids') backrefs = footnote.attrib.get('backrefs') yield assert_equal, refid2id[ids], backrefs # --- refs in the Python domain et = ElementTree.parse(app.outdir / 'refs_python_domain.xml') secs = et.findall('section') # regression test for fix #1363 para0 = secs[0].findall('paragraph') yield (assert_elem, para0[0], ['SEE THIS DECORATOR:', 'sensitive_variables()', '.'], ['sensitive.sensitive_variables']) # --- keep external links: regression test for #1044 et = ElementTree.parse(app.outdir / 'external_links.xml') secs = et.findall('section') para0 = secs[0].findall('paragraph') # external link check yield (assert_elem, para0[0], ['EXTERNAL LINK TO', 'Python', '.'], ['http://python.org/index.html']) # internal link check yield (assert_elem, para0[1], ['EXTERNAL LINKS', 'IS INTERNAL LINK.'], ['i18n-with-external-links']) # inline link check yield (assert_elem, para0[2], ['INLINE LINK BY', 'THE SPHINX SITE', '.'], ['http://sphinx-doc.org']) # unnamed link check yield (assert_elem, para0[3], ['UNNAMED', 'LINK', '.'], ['http://google.com']) # link target swapped translation para1 = secs[1].findall('paragraph') yield (assert_elem, para1[0], ['LINK TO', 'external2', 'AND', 'external1', '.'], ['http://example.com/external2', 'http://example.com/external1']) yield (assert_elem, para1[1], ['LINK TO', 'THE PYTHON SITE', 'AND', 'THE SPHINX SITE', '.'], ['http://python.org', 'http://sphinx-doc.org']) # multiple references in the same line para2 = secs[2].findall('paragraph') yield (assert_elem, para2[0], ['LINK TO', 'EXTERNAL LINKS', ',', 'Python', ',', 'THE SPHINX SITE', ',', 'UNNAMED', 'AND', 'THE PYTHON SITE', '.'], ['i18n-with-external-links', 'http://python.org/index.html', 'http://sphinx-doc.org', 'http://google.com', 'http://python.org']) # --- role xref: regression test for #1090, #1193 et = ElementTree.parse(app.outdir / 'role_xref.xml') sec1, sec2 = et.findall('section') para1, = sec1.findall('paragraph') yield (assert_elem, para1, ['LINK TO', "I18N ROCK'N ROLE XREF", ',', 'CONTENTS', ',', 'SOME NEW TERM', '.'], ['i18n-role-xref', 'contents', 'glossary_terms#term-some-term']) para2 = sec2.findall('paragraph') yield (assert_elem, para2[0], ['LINK TO', 'SOME OTHER NEW TERM', 'AND', 'SOME NEW TERM', '.'], ['glossary_terms#term-some-other-term', 'glossary_terms#term-some-term']) yield(assert_elem, para2[1], ['LINK TO', 'SAME TYPE LINKS', 'AND', "I18N ROCK'N ROLE XREF", '.'], ['same-type-links', 'i18n-role-xref']) yield (assert_elem, para2[2], ['LINK TO', 'I18N WITH GLOSSARY TERMS', 'AND', 'CONTENTS', '.'], ['glossary_terms', 'contents']) yield (assert_elem, para2[3], ['LINK TO', '--module', 'AND', '-m', '.'], ['cmdoption--module', 'cmdoption-m']) yield (assert_elem, para2[4], ['LINK TO', 'env2', 'AND', 'env1', '.'], ['envvar-env2', 'envvar-env1']) yield (assert_elem, para2[5], ['LINK TO', 'token2', 'AND', 'token1', '.'], []) # TODO: how do I link token role to productionlist? yield (assert_elem, para2[6], ['LINK TO', 'same-type-links', 'AND', "i18n-role-xref", '.'], ['same-type-links', 'i18n-role-xref']) # warnings warnings = warning.getvalue().replace(os.sep, '/') yield assert_not_in, 'term not in glossary', warnings yield assert_not_in, 'undefined label', warnings yield assert_not_in, 'unknown document', warnings # --- label targets: regression test for #1193, #1265 et = ElementTree.parse(app.outdir / 'label_target.xml') secs = et.findall('section') para0 = secs[0].findall('paragraph') yield (assert_elem, para0[0], ['X SECTION AND LABEL', 'POINT TO', 'implicit-target', 'AND', 'X SECTION AND LABEL', 'POINT TO', 'section-and-label', '.'], ['implicit-target', 'section-and-label']) para1 = secs[1].findall('paragraph') yield (assert_elem, para1[0], ['X EXPLICIT-TARGET', 'POINT TO', 'explicit-target', 'AND', 'X EXPLICIT-TARGET', 'POINT TO DUPLICATED ID LIKE', 'id1', '.'], ['explicit-target', 'id1']) para2 = secs[2].findall('paragraph') yield (assert_elem, para2[0], ['X IMPLICIT SECTION NAME', 'POINT TO', 'implicit-section-name', '.'], ['implicit-section-name']) sec2 = secs[2].findall('section') para2_0 = sec2[0].findall('paragraph') yield (assert_elem, para2_0[0], ['`X DUPLICATED SUB SECTION`_', 'IS BROKEN LINK.'], []) para3 = secs[3].findall('paragraph') yield (assert_elem, para3[0], ['X', 'bridge label', 'IS NOT TRANSLATABLE BUT LINKED TO TRANSLATED ' + 'SECTION TITLE.'], ['label-bridged-target-section']) yield (assert_elem, para3[1], ['X', 'bridge label', 'POINT TO', 'LABEL BRIDGED TARGET SECTION', 'AND', 'bridge label2', 'POINT TO', 'SECTION AND LABEL', '. THE SECOND APPEARED', 'bridge label2', 'POINT TO CORRECT TARGET.'], ['label-bridged-target-section', 'section-and-label', 'section-and-label'])