diff options
| author | Takeshi KOMIYA <i.tkomiya@gmail.com> | 2019-01-20 00:28:08 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-01-20 00:28:08 +0900 |
| commit | 6968889206afd2d417e4ccac26f6a9ac1d1cefc6 (patch) | |
| tree | 3a61f9f4dfae912c3f6e4d901a23d7f7a9a564a7 | |
| parent | ff5031c96e90027510ad2d0251972e12da46402c (diff) | |
| parent | 6be9d2a08eb9f990ddffafd2617f9074541a6463 (diff) | |
| download | sphinx-git-6968889206afd2d417e4ccac26f6a9ac1d1cefc6.tar.gz | |
Merge pull request #5968 from tk0miya/separate_qthelp
Separate qthelp to sphinxcontrib package
| -rw-r--r-- | CHANGES | 1 | ||||
| -rw-r--r-- | doc/extdev/index.rst | 5 | ||||
| -rw-r--r-- | doc/usage/builders/index.rst | 6 | ||||
| -rw-r--r-- | setup.py | 1 | ||||
| -rw-r--r-- | sphinx/application.py | 3 | ||||
| -rw-r--r-- | sphinx/builders/qthelp.py | 269 | ||||
| -rw-r--r-- | sphinx/templates/qthelp/project.qhcp | 19 | ||||
| -rw-r--r-- | sphinx/templates/qthelp/project.qhp | 26 | ||||
| -rw-r--r-- | tests/test_build.py | 2 | ||||
| -rw-r--r-- | tests/test_build_qthelp.py | 117 |
10 files changed, 30 insertions, 419 deletions
@@ -22,6 +22,7 @@ Dependencies * Some packages are separated to sub packages: - sphinxcontrib.jsmath + - sphinxcontrib.qthelp Incompatible changes -------------------- diff --git a/doc/extdev/index.rst b/doc/extdev/index.rst index 8a22b1f7a..fa73681b9 100644 --- a/doc/extdev/index.rst +++ b/doc/extdev/index.rst @@ -290,6 +290,11 @@ The following is a list of deprecated interfaces. - 4.0 - ``open()`` + * - ``sphinx.builders.qthelp`` + - 2.0 + - 4.0 + - ``sphinxcontrib.qthelp`` + * - ``sphinx.cmd.quickstart.term_decode()`` - 2.0 - 4.0 diff --git a/doc/usage/builders/index.rst b/doc/usage/builders/index.rst index b9e107699..d8c3a26c1 100644 --- a/doc/usage/builders/index.rst +++ b/doc/usage/builders/index.rst @@ -72,13 +72,17 @@ The builder's "name" must be given to the **-b** command-line option of .. autoattribute:: supported_image_types -.. module:: sphinx.builders.qthelp +.. module:: sphinxcontrib.qthelp .. class:: QtHelpBuilder This builder produces the same output as the standalone HTML builder, but also generates `Qt help`_ collection support files that allow the Qt collection generator to compile them. + .. versionchanged:: 2.0 + + Moved to sphinxcontrib.qthelp from sphinx.builders package. + .. autoattribute:: name .. autoattribute:: format @@ -16,6 +16,7 @@ if sys.version_info < (3, 5): install_requires = [ 'sphinxcontrib-jsmath', + 'sphinxcontrib-qthelp', 'Jinja2>=2.3', 'Pygments>=2.0', 'docutils>=0.12', diff --git a/sphinx/application.py b/sphinx/application.py index e4aa9de86..052426ff7 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -73,7 +73,6 @@ builtin_extensions = ( 'sphinx.builders.latex', 'sphinx.builders.linkcheck', 'sphinx.builders.manpage', - 'sphinx.builders.qthelp', 'sphinx.builders.singlehtml', 'sphinx.builders.texinfo', 'sphinx.builders.text', @@ -107,6 +106,8 @@ builtin_extensions = ( 'sphinx.environment.collectors.title', 'sphinx.environment.collectors.toctree', 'sphinx.environment.collectors.indexentries', + # 1st party extensions + 'sphinxcontrib.qthelp', # Strictly, alabaster theme is not a builtin extension, # but it is loaded automatically to use it as default theme. 'alabaster', diff --git a/sphinx/builders/qthelp.py b/sphinx/builders/qthelp.py index 4a5fdb099..d87246148 100644 --- a/sphinx/builders/qthelp.py +++ b/sphinx/builders/qthelp.py @@ -8,275 +8,36 @@ :license: BSD, see LICENSE for details. """ -import html -import os -import posixpath -import re -from os import path -from typing import Iterable, cast +import warnings -from docutils import nodes +from sphinxcontrib.qthelp import QtHelpBuilder, render_file -from sphinx import addnodes -from sphinx import package_dir -from sphinx.builders.html import StandaloneHTMLBuilder -from sphinx.environment.adapters.indexentries import IndexEntries -from sphinx.locale import __ -from sphinx.util import logging -from sphinx.util.nodes import NodeMatcher -from sphinx.util.osutil import canon_path, make_filename -from sphinx.util.template import SphinxRenderer +import sphinx +from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias if False: # For type annotation - from typing import Any, Dict, List, Tuple # NOQA + from typing import Any, Dict # NOQA from sphinx.application import Sphinx # NOQA -logger = logging.getLogger(__name__) - - -_idpattern = re.compile( - r'(?P<title>.+) (\((class in )?(?P<id>[\w\.]+)( (?P<descr>\w+))?\))$') - - -section_template = '<section title="%(title)s" ref="%(ref)s"/>' - - -def render_file(filename, **kwargs): - # type: (str, Any) -> str - pathname = os.path.join(package_dir, 'templates', 'qthelp', filename) - return SphinxRenderer.render_from_file(pathname, kwargs) - - -class QtHelpBuilder(StandaloneHTMLBuilder): - """ - Builder that also outputs Qt help project, contents and index files. - """ - name = 'qthelp' - epilog = __('You can now run "qcollectiongenerator" with the .qhcp ' - 'project file in %(outdir)s, like this:\n' - '$ qcollectiongenerator %(outdir)s/%(project)s.qhcp\n' - 'To view the help file:\n' - '$ assistant -collectionFile %(outdir)s/%(project)s.qhc') - - # don't copy the reST source - copysource = False - supported_image_types = ['image/svg+xml', 'image/png', 'image/gif', - 'image/jpeg'] - - # don't add links - add_permalinks = False - - # don't add sidebar etc. - embedded = True - # disable download role - download_support = False - - # don't generate the search index or include the search page - search = False - - def init(self): - # type: () -> None - super().init() - # the output files for HTML help must be .html only - self.out_suffix = '.html' - self.link_suffix = '.html' - # self.config.html_style = 'traditional.css' - - def get_theme_config(self): - # type: () -> Tuple[str, Dict] - return self.config.qthelp_theme, self.config.qthelp_theme_options - - def handle_finish(self): - # type: () -> None - self.build_qhp(self.outdir, self.config.qthelp_basename) - - def build_qhp(self, outdir, outname): - # type: (str, str) -> None - logger.info(__('writing project file...')) - - # sections - tocdoc = self.env.get_and_resolve_doctree(self.config.master_doc, self, - prune_toctrees=False) - - sections = [] - matcher = NodeMatcher(addnodes.compact_paragraph, toctree=True) - for node in tocdoc.traverse(matcher): # type: addnodes.compact_paragraph - sections.extend(self.write_toc(node)) - - for indexname, indexcls, content, collapse in self.domain_indices: - item = section_template % {'title': indexcls.localname, - 'ref': '%s.html' % indexname} - sections.append(' ' * 4 * 4 + item) - sections = '\n'.join(sections) # type: ignore - - # keywords - keywords = [] - index = IndexEntries(self.env).create_index(self, group_entries=False) - for (key, group) in index: - for title, (refs, subitems, key_) in group: - keywords.extend(self.build_keywords(title, refs, subitems)) - keywords = '\n'.join(keywords) # type: ignore - - # it seems that the "namespace" may not contain non-alphanumeric - # characters, and more than one successive dot, or leading/trailing - # dots, are also forbidden - if self.config.qthelp_namespace: - nspace = self.config.qthelp_namespace - else: - nspace = 'org.sphinx.%s.%s' % (outname, self.config.version) - - nspace = re.sub(r'[^a-zA-Z0-9.\-]', '', nspace) - nspace = re.sub(r'\.+', '.', nspace).strip('.') - nspace = nspace.lower() - - # write the project file - with open(path.join(outdir, outname + '.qhp'), 'w', encoding='utf-8') as f: - body = render_file('project.qhp', outname=outname, - title=self.config.html_title, version=self.config.version, - project=self.config.project, namespace=nspace, - master_doc=self.config.master_doc, - sections=sections, keywords=keywords, - files=self.get_project_files(outdir)) - f.write(body) - - homepage = 'qthelp://' + posixpath.join( - nspace, 'doc', self.get_target_uri(self.config.master_doc)) - startpage = 'qthelp://' + posixpath.join(nspace, 'doc', 'index.html') - - logger.info(__('writing collection project file...')) - with open(path.join(outdir, outname + '.qhcp'), 'w', encoding='utf-8') as f: - body = render_file('project.qhcp', outname=outname, - title=self.config.html_short_title, - homepage=homepage, startpage=startpage) - f.write(body) - - def isdocnode(self, node): - # type: (nodes.Node) -> bool - if not isinstance(node, nodes.list_item): - return False - if len(node.children) != 2: - return False - if not isinstance(node[0], addnodes.compact_paragraph): - return False - if not isinstance(node[0][0], nodes.reference): - return False - if not isinstance(node[1], nodes.bullet_list): - return False - return True - - def write_toc(self, node, indentlevel=4): - # type: (nodes.Node, int) -> List[str] - parts = [] # type: List[str] - if isinstance(node, nodes.list_item) and self.isdocnode(node): - compact_paragraph = cast(addnodes.compact_paragraph, node[0]) - reference = cast(nodes.reference, compact_paragraph[0]) - link = reference['refuri'] - title = html.escape(reference.astext()).replace('"', '"') - item = '<section title="%(title)s" ref="%(ref)s">' % \ - {'title': title, 'ref': link} - parts.append(' ' * 4 * indentlevel + item) - - bullet_list = cast(nodes.bullet_list, node[1]) - list_items = cast(Iterable[nodes.list_item], bullet_list) - for list_item in list_items: - parts.extend(self.write_toc(list_item, indentlevel + 1)) - parts.append(' ' * 4 * indentlevel + '</section>') - elif isinstance(node, nodes.list_item): - for subnode in node: - parts.extend(self.write_toc(subnode, indentlevel)) - elif isinstance(node, nodes.reference): - link = node['refuri'] - title = html.escape(node.astext()).replace('"', '"') - item = section_template % {'title': title, 'ref': link} - item = ' ' * 4 * indentlevel + item - parts.append(item.encode('ascii', 'xmlcharrefreplace').decode()) - elif isinstance(node, nodes.bullet_list): - for subnode in node: - parts.extend(self.write_toc(subnode, indentlevel)) - elif isinstance(node, addnodes.compact_paragraph): - for subnode in node: - parts.extend(self.write_toc(subnode, indentlevel)) - - return parts - - def keyword_item(self, name, ref): - # type: (str, Any) -> str - matchobj = _idpattern.match(name) - if matchobj: - groupdict = matchobj.groupdict() - shortname = groupdict['title'] - id = groupdict.get('id') - # descr = groupdict.get('descr') - if shortname.endswith('()'): - shortname = shortname[:-2] - id = '%s.%s' % (id, shortname) - else: - id = None - - nameattr = html.escape(name, quote=True) - refattr = html.escape(ref[1], quote=True) - if id: - item = ' ' * 12 + '<keyword name="%s" id="%s" ref="%s"/>' % (nameattr, id, refattr) - else: - item = ' ' * 12 + '<keyword name="%s" ref="%s"/>' % (nameattr, refattr) - item.encode('ascii', 'xmlcharrefreplace') - return item - - def build_keywords(self, title, refs, subitems): - # type: (str, List[Any], Any) -> List[str] - keywords = [] # type: List[str] - - # if len(refs) == 0: # XXX - # write_param('See Also', title) - if len(refs) == 1: - keywords.append(self.keyword_item(title, refs[0])) - elif len(refs) > 1: - for i, ref in enumerate(refs): # XXX - # item = (' '*12 + - # '<keyword name="%s [%d]" ref="%s"/>' % ( - # title, i, ref)) - # item.encode('ascii', 'xmlcharrefreplace') - # keywords.append(item) - keywords.append(self.keyword_item(title, ref)) - - if subitems: - for subitem in subitems: - keywords.extend(self.build_keywords(subitem[0], subitem[1], [])) - - return keywords - - def get_project_files(self, outdir): - # type: (str) -> List[str] - if not outdir.endswith(os.sep): - outdir += os.sep - olen = len(outdir) - project_files = [] - staticdir = path.join(outdir, '_static') - imagesdir = path.join(outdir, self.imagedir) - for root, dirs, files in os.walk(outdir): - resourcedir = root.startswith((staticdir, imagesdir)) - for fn in sorted(files): - if (resourcedir and not fn.endswith('.js')) or fn.endswith('.html'): - filename = path.join(root, fn)[olen:] - project_files.append(canon_path(filename)) - - return project_files +deprecated_alias('sphinx.builders.qthelp', + { + 'render_file': render_file, + 'QtHelpBuilder': QtHelpBuilder, + }, + RemovedInSphinx40Warning) def setup(app): # type: (Sphinx) -> Dict[str, Any] - app.setup_extension('sphinx.builders.html') - app.add_builder(QtHelpBuilder) + warnings.warn('sphinx.builders.qthelp has been moved to sphinxcontrib-qthelp.', + RemovedInSphinx40Warning) - app.add_config_value('qthelp_basename', lambda self: make_filename(self.project), None) - app.add_config_value('qthelp_namespace', None, 'html', [str]) - app.add_config_value('qthelp_theme', 'nonav', 'html') - app.add_config_value('qthelp_theme_options', {}, 'html') + app.setup_extension('sphinxcontrib.qthelp') return { - 'version': 'builtin', + 'version': sphinx.__display_version__, 'parallel_read_safe': True, 'parallel_write_safe': True, } diff --git a/sphinx/templates/qthelp/project.qhcp b/sphinx/templates/qthelp/project.qhcp deleted file mode 100644 index fe12eaa14..000000000 --- a/sphinx/templates/qthelp/project.qhcp +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8" ?> -<QHelpCollectionProject version="1.0"> - <assistant> - <title>{{ title|e }}</title> - <homePage>{{ homepage|e }}</homePage> - <startPage>{{ startpage|e }}</startPage> - </assistant> - <docFiles> - <generate> - <file> - <input>{{ outname|e }}.qhp</input> - <output>{{ outname|e }}.qch</output> - </file> - </generate> - <register> - <file>{{ outname|e }}.qch</file> - </register> - </docFiles> -</QHelpCollectionProject> diff --git a/sphinx/templates/qthelp/project.qhp b/sphinx/templates/qthelp/project.qhp deleted file mode 100644 index 53f999043..000000000 --- a/sphinx/templates/qthelp/project.qhp +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="utf-8" ?> -<QtHelpProject version="1.0"> - <namespace>{{ namespace|e }}</namespace> - <virtualFolder>doc</virtualFolder> - <customFilter name="{{ project|e }} {{ version|e }}"> - <filterAttribute>{{ outname|e }}</filterAttribute> - <filterAttribute>{{ version|e }}</filterAttribute> - </customFilter> - <filterSection> - <filterAttribute>{{ outname|e }}</filterAttribute> - <filterAttribute>{{ version|e }}</filterAttribute> - <toc> - <section title="{{ title|e }}" ref="{{ master_doc|e }}.html"> -{{ sections }} - </section> - </toc> - <keywords> -{{ keywords }} - </keywords> - <files> - {%- for filename in files %} - <file>{{ filename|e }}</file> - {%- endfor %} - </files> - </filterSection> -</QtHelpProject> diff --git a/tests/test_build.py b/tests/test_build.py index c62b365fc..399530ed6 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -60,7 +60,7 @@ def nonascii_srcdir(request, rootdir, sphinx_test_tempdir): "buildername", [ # note: no 'html' - if it's ok with dirhtml it's ok with html - 'dirhtml', 'singlehtml', 'pickle', 'json', 'text', 'htmlhelp', 'qthelp', + 'dirhtml', 'singlehtml', 'pickle', 'json', 'text', 'htmlhelp', 'applehelp', 'changes', 'xml', 'pseudoxml', 'linkcheck', ], ) diff --git a/tests/test_build_qthelp.py b/tests/test_build_qthelp.py deleted file mode 100644 index 222577db5..000000000 --- a/tests/test_build_qthelp.py +++ /dev/null @@ -1,117 +0,0 @@ -""" - test_build_qthelp - ~~~~~~~~~~~~~~~~~ - - Test the Qt Help builder and check its output. We don't need to - test the HTML itself; that's already handled by - :file:`test_build_html.py`. - - :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" - -import pytest - -from sphinx.testing.util import etree_parse - - -@pytest.mark.sphinx('qthelp', testroot='basic') -def test_qthelp_basic(app, status, warning): - app.builder.build_all() - - qhp = (app.outdir / 'Python.qhp').text() - assert '<customFilter name="Python ">' in qhp - assert '<filterAttribute>Python</filterAttribute>' in qhp - assert '<filterAttribute></filterAttribute>' in qhp - assert '<section title="Python documentation" ref="index.html">' in qhp - assert '<file>genindex.html</file>' in qhp - assert '<file>index.html</file>' in qhp - assert '<file>_static/basic.css</file>' in qhp - - qhcp = (app.outdir / 'Python.qhcp').text() - assert '<title>Python documentation</title>' in qhcp - assert '<homePage>qthelp://org.sphinx.python/doc/index.html</homePage>' in qhcp - assert '<startPage>qthelp://org.sphinx.python/doc/index.html</startPage>' in qhcp - assert '<input>Python.qhp</input>' in qhcp - assert '<output>Python.qch</output>' in qhcp - assert '<file>Python.qch</file>' in qhcp - - -@pytest.mark.sphinx('qthelp', testroot='need-escaped') -def test_qthelp_escaped(app, status, warning): - app.builder.build_all() - - et = etree_parse(app.outdir / 'needbescapedbproject.qhp') - customFilter = et.find('.//customFilter') - assert len(customFilter) == 2 - assert customFilter.attrib == {'name': 'need <b>"escaped"</b> project '} - assert customFilter[0].text == 'needbescapedbproject' - assert customFilter[1].text is None - - toc = et.find('.//toc') - assert len(toc) == 1 - assert toc[0].attrib == {'title': 'need <b>"escaped"</b> project documentation', - 'ref': 'index.html'} - assert len(toc[0]) == 4 - assert toc[0][0].attrib == {'title': '<foo>', 'ref': 'foo.html'} - assert toc[0][0][0].attrib == {'title': 'quux', 'ref': 'quux.html'} - assert toc[0][0][1].attrib == {'title': 'foo "1"', 'ref': 'foo.html#foo-1'} - assert toc[0][0][1][0].attrib == {'title': 'foo.1-1', 'ref': 'foo.html#foo-1-1'} - assert toc[0][0][2].attrib == {'title': 'foo.2', 'ref': 'foo.html#foo-2'} - assert toc[0][1].attrib == {'title': 'bar', 'ref': 'bar.html'} - assert toc[0][2].attrib == {'title': 'http://sphinx-doc.org/', - 'ref': 'http://sphinx-doc.org/'} - assert toc[0][3].attrib == {'title': 'baz', 'ref': 'baz.html'} - - keywords = et.find('.//keywords') - assert len(keywords) == 2 - assert keywords[0].attrib == {'name': '<subsection>', 'ref': 'index.html#index-0'} - assert keywords[1].attrib == {'name': '"subsection"', 'ref': 'index.html#index-0'} - - -@pytest.mark.sphinx('qthelp', testroot='basic') -def test_qthelp_namespace(app, status, warning): - # default namespace - app.builder.build_all() - - qhp = (app.outdir / 'Python.qhp').text() - assert '<namespace>org.sphinx.python</namespace>' in qhp - - qhcp = (app.outdir / 'Python.qhcp').text() - assert '<homePage>qthelp://org.sphinx.python/doc/index.html</homePage>' in qhcp - assert '<startPage>qthelp://org.sphinx.python/doc/index.html</startPage>' in qhcp - - # give a namespace - app.config.qthelp_namespace = 'org.sphinx-doc.sphinx' - app.builder.build_all() - - qhp = (app.outdir / 'Python.qhp').text() - assert '<namespace>org.sphinx-doc.sphinx</namespace>' in qhp - - qhcp = (app.outdir / 'Python.qhcp').text() - assert '<homePage>qthelp://org.sphinx-doc.sphinx/doc/index.html</homePage>' in qhcp - assert '<startPage>qthelp://org.sphinx-doc.sphinx/doc/index.html</startPage>' in qhcp - - -@pytest.mark.sphinx('qthelp', testroot='basic') -def test_qthelp_title(app, status, warning): - # default title - app.builder.build_all() - - qhp = (app.outdir / 'Python.qhp').text() - assert '<section title="Python documentation" ref="index.html">' in qhp - - qhcp = (app.outdir / 'Python.qhcp').text() - assert '<title>Python documentation</title>' in qhcp - - # give a title - app.config.html_title = 'Sphinx <b>"full"</b> title' - app.config.html_short_title = 'Sphinx <b>"short"</b> title' - app.builder.build_all() - - qhp = (app.outdir / 'Python.qhp').text() - assert ('<section title="Sphinx <b>"full"</b> title" ref="index.html">' - in qhp) - - qhcp = (app.outdir / 'Python.qhcp').text() - assert '<title>Sphinx <b>"short"</b> title</title>' in qhcp |
