summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTakeshi KOMIYA <i.tkomiya@gmail.com>2019-01-20 00:28:08 +0900
committerGitHub <noreply@github.com>2019-01-20 00:28:08 +0900
commit6968889206afd2d417e4ccac26f6a9ac1d1cefc6 (patch)
tree3a61f9f4dfae912c3f6e4d901a23d7f7a9a564a7
parentff5031c96e90027510ad2d0251972e12da46402c (diff)
parent6be9d2a08eb9f990ddffafd2617f9074541a6463 (diff)
downloadsphinx-git-6968889206afd2d417e4ccac26f6a9ac1d1cefc6.tar.gz
Merge pull request #5968 from tk0miya/separate_qthelp
Separate qthelp to sphinxcontrib package
-rw-r--r--CHANGES1
-rw-r--r--doc/extdev/index.rst5
-rw-r--r--doc/usage/builders/index.rst6
-rw-r--r--setup.py1
-rw-r--r--sphinx/application.py3
-rw-r--r--sphinx/builders/qthelp.py269
-rw-r--r--sphinx/templates/qthelp/project.qhcp19
-rw-r--r--sphinx/templates/qthelp/project.qhp26
-rw-r--r--tests/test_build.py2
-rw-r--r--tests/test_build_qthelp.py117
10 files changed, 30 insertions, 419 deletions
diff --git a/CHANGES b/CHANGES
index 9274dc16d..fc85ceb21 100644
--- a/CHANGES
+++ b/CHANGES
@@ -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
diff --git a/setup.py b/setup.py
index 473b85788..826a74f20 100644
--- a/setup.py
+++ b/setup.py
@@ -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('"', '&quot;')
- 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('"', '&quot;')
- 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 &lt;b&gt;&#34;full&#34;&lt;/b&gt; title" ref="index.html">'
- in qhp)
-
- qhcp = (app.outdir / 'Python.qhcp').text()
- assert '<title>Sphinx &lt;b&gt;&#34;short&#34;&lt;/b&gt; title</title>' in qhcp