mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge pull request #4749 from tk0miya/qthelp_templates
Qthelp templates
This commit is contained in:
commit
a06e954bd8
1
CHANGES
1
CHANGES
@ -32,6 +32,7 @@ Bugs fixed
|
||||
* #2286: Sphinx crashes when error is happens in rendering HTML pages
|
||||
* #4688: Error to download remote images having long URL
|
||||
* #4754: sphinx/pycode/__init__.py raises AttributeError
|
||||
* #1435: qthelp builder should htmlescape keywords
|
||||
|
||||
Testing
|
||||
--------
|
||||
|
@ -19,12 +19,14 @@ from docutils import nodes
|
||||
from six import text_type
|
||||
|
||||
from sphinx import addnodes
|
||||
from sphinx import package_dir
|
||||
from sphinx.builders.html import StandaloneHTMLBuilder
|
||||
from sphinx.config import string_classes
|
||||
from sphinx.environment.adapters.indexentries import IndexEntries
|
||||
from sphinx.util import force_decode, logging
|
||||
from sphinx.util.osutil import make_filename
|
||||
from sphinx.util.pycompat import htmlescape
|
||||
from sphinx.util.template import SphinxRenderer
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
@ -39,67 +41,13 @@ _idpattern = re.compile(
|
||||
r'(?P<title>.+) (\((class in )?(?P<id>[\w\.]+)( (?P<descr>\w+))?\))$')
|
||||
|
||||
|
||||
# Qt Help Collection Project (.qhcp).
|
||||
# Is the input file for the help collection generator.
|
||||
# It contains references to compressed help files which should be
|
||||
# included in the collection.
|
||||
# It may contain various other information for customizing Qt Assistant.
|
||||
collection_template = u'''\
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<QHelpCollectionProject version="1.0">
|
||||
<assistant>
|
||||
<title>%(title)s</title>
|
||||
<homePage>%(homepage)s</homePage>
|
||||
<startPage>%(startpage)s</startPage>
|
||||
</assistant>
|
||||
<docFiles>
|
||||
<generate>
|
||||
<file>
|
||||
<input>%(outname)s.qhp</input>
|
||||
<output>%(outname)s.qch</output>
|
||||
</file>
|
||||
</generate>
|
||||
<register>
|
||||
<file>%(outname)s.qch</file>
|
||||
</register>
|
||||
</docFiles>
|
||||
</QHelpCollectionProject>
|
||||
'''
|
||||
|
||||
# Qt Help Project (.qhp)
|
||||
# This is the input file for the help generator.
|
||||
# It contains the table of contents, indices and references to the
|
||||
# actual documentation files (*.html).
|
||||
# In addition it defines a unique namespace for the documentation.
|
||||
project_template = u'''\
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<QtHelpProject version="1.0">
|
||||
<namespace>%(namespace)s</namespace>
|
||||
<virtualFolder>doc</virtualFolder>
|
||||
<customFilter name="%(project)s %(version)s">
|
||||
<filterAttribute>%(outname)s</filterAttribute>
|
||||
<filterAttribute>%(version)s</filterAttribute>
|
||||
</customFilter>
|
||||
<filterSection>
|
||||
<filterAttribute>%(outname)s</filterAttribute>
|
||||
<filterAttribute>%(version)s</filterAttribute>
|
||||
<toc>
|
||||
<section title="%(title)s" ref="%(masterdoc)s.html">
|
||||
%(sections)s
|
||||
</section>
|
||||
</toc>
|
||||
<keywords>
|
||||
%(keywords)s
|
||||
</keywords>
|
||||
<files>
|
||||
%(files)s
|
||||
</files>
|
||||
</filterSection>
|
||||
</QtHelpProject>
|
||||
'''
|
||||
|
||||
section_template = '<section title="%(title)s" ref="%(ref)s"/>'
|
||||
file_template = ' ' * 12 + '<file>%(filename)s</file>'
|
||||
|
||||
|
||||
def render_file(filename, **kwargs):
|
||||
# type: (unicode, Any) -> unicode
|
||||
pathname = os.path.join(package_dir, 'templates', 'qthelp', filename)
|
||||
return SphinxRenderer.render_from_file(pathname, kwargs)
|
||||
|
||||
|
||||
class QtHelpBuilder(StandaloneHTMLBuilder):
|
||||
@ -183,24 +131,6 @@ class QtHelpBuilder(StandaloneHTMLBuilder):
|
||||
keywords.extend(self.build_keywords(title, refs, subitems))
|
||||
keywords = u'\n'.join(keywords) # type: ignore
|
||||
|
||||
# files
|
||||
if not outdir.endswith(os.sep):
|
||||
outdir += os.sep
|
||||
olen = len(outdir)
|
||||
projectfiles = []
|
||||
staticdir = path.join(outdir, '_static')
|
||||
imagesdir = path.join(outdir, self.imagedir)
|
||||
for root, dirs, files in os.walk(outdir):
|
||||
resourcedir = root.startswith(staticdir) or \
|
||||
root.startswith(imagesdir)
|
||||
for fn in sorted(files):
|
||||
if (resourcedir and not fn.endswith('.js')) or \
|
||||
fn.endswith('.html'):
|
||||
filename = path.join(root, fn)[olen:]
|
||||
projectfiles.append(file_template %
|
||||
{'filename': htmlescape(filename)})
|
||||
projectfiles = '\n'.join(projectfiles) # 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
|
||||
@ -215,16 +145,13 @@ class QtHelpBuilder(StandaloneHTMLBuilder):
|
||||
|
||||
# write the project file
|
||||
with codecs.open(path.join(outdir, outname + '.qhp'), 'w', 'utf-8') as f: # type: ignore # NOQA
|
||||
f.write(project_template % {
|
||||
'outname': htmlescape(outname),
|
||||
'title': htmlescape(self.config.html_title),
|
||||
'version': htmlescape(self.config.version),
|
||||
'project': htmlescape(self.config.project),
|
||||
'namespace': htmlescape(nspace),
|
||||
'masterdoc': htmlescape(self.config.master_doc),
|
||||
'sections': sections,
|
||||
'keywords': keywords,
|
||||
'files': projectfiles})
|
||||
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))
|
||||
@ -232,11 +159,10 @@ class QtHelpBuilder(StandaloneHTMLBuilder):
|
||||
|
||||
logger.info('writing collection project file...')
|
||||
with codecs.open(path.join(outdir, outname + '.qhcp'), 'w', 'utf-8') as f: # type: ignore # NOQA
|
||||
f.write(collection_template % {
|
||||
'outname': htmlescape(outname),
|
||||
'title': htmlescape(self.config.html_short_title),
|
||||
'homepage': htmlescape(homepage),
|
||||
'startpage': htmlescape(startpage)})
|
||||
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
|
||||
@ -298,11 +224,12 @@ class QtHelpBuilder(StandaloneHTMLBuilder):
|
||||
else:
|
||||
id = None
|
||||
|
||||
nameattr = htmlescape(name, quote=True)
|
||||
refattr = htmlescape(ref[1], quote=True)
|
||||
if id:
|
||||
item = ' ' * 12 + '<keyword name="%s" id="%s" ref="%s"/>' % (
|
||||
name, id, ref[1])
|
||||
item = ' ' * 12 + '<keyword name="%s" id="%s" ref="%s"/>' % (nameattr, id, refattr)
|
||||
else:
|
||||
item = ' ' * 12 + '<keyword name="%s" ref="%s"/>' % (name, ref[1])
|
||||
item = ' ' * 12 + '<keyword name="%s" ref="%s"/>' % (nameattr, refattr)
|
||||
item.encode('ascii', 'xmlcharrefreplace')
|
||||
return item
|
||||
|
||||
@ -310,7 +237,6 @@ class QtHelpBuilder(StandaloneHTMLBuilder):
|
||||
# type: (unicode, List[Any], Any) -> List[unicode]
|
||||
keywords = [] # type: List[unicode]
|
||||
|
||||
title = htmlescape(title)
|
||||
# if len(refs) == 0: # XXX
|
||||
# write_param('See Also', title)
|
||||
if len(refs) == 1:
|
||||
@ -330,6 +256,23 @@ class QtHelpBuilder(StandaloneHTMLBuilder):
|
||||
|
||||
return keywords
|
||||
|
||||
def get_project_files(self, outdir):
|
||||
# type: (unicode) -> List[unicode]
|
||||
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(filename)
|
||||
|
||||
return project_files
|
||||
|
||||
|
||||
def setup(app):
|
||||
# type: (Sphinx) -> Dict[unicode, Any]
|
||||
|
19
sphinx/templates/qthelp/project.qhcp
Normal file
19
sphinx/templates/qthelp/project.qhcp
Normal file
@ -0,0 +1,19 @@
|
||||
<?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>
|
26
sphinx/templates/qthelp/project.qhp
Normal file
26
sphinx/templates/qthelp/project.qhp
Normal file
@ -0,0 +1,26 @@
|
||||
<?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>
|
2
tests/roots/test-need-escaped/bar.rst
Normal file
2
tests/roots/test-need-escaped/bar.rst
Normal file
@ -0,0 +1,2 @@
|
||||
bar
|
||||
===
|
2
tests/roots/test-need-escaped/baz.rst
Normal file
2
tests/roots/test-need-escaped/baz.rst
Normal file
@ -0,0 +1,2 @@
|
||||
baz
|
||||
===
|
5
tests/roots/test-need-escaped/conf.py
Normal file
5
tests/roots/test-need-escaped/conf.py
Normal file
@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
master_doc = 'index'
|
||||
project = 'need <b>"escaped"</b> project'
|
||||
smartquotes = False
|
15
tests/roots/test-need-escaped/foo.rst
Normal file
15
tests/roots/test-need-escaped/foo.rst
Normal file
@ -0,0 +1,15 @@
|
||||
<foo>
|
||||
=====
|
||||
|
||||
.. toctree::
|
||||
|
||||
quux
|
||||
|
||||
foo "1"
|
||||
-------
|
||||
|
||||
foo.1-1
|
||||
^^^^^^^
|
||||
|
||||
foo.2
|
||||
-----
|
30
tests/roots/test-need-escaped/index.rst
Normal file
30
tests/roots/test-need-escaped/index.rst
Normal file
@ -0,0 +1,30 @@
|
||||
.. Sphinx Tests documentation master file, created by sphinx-quickstart on Wed Jun 4 23:49:58 2008.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Welcome to Sphinx Tests's documentation!
|
||||
========================================
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:numbered:
|
||||
:caption: Table of Contents
|
||||
:name: mastertoc
|
||||
|
||||
foo
|
||||
bar
|
||||
http://sphinx-doc.org/
|
||||
baz
|
||||
qux
|
||||
|
||||
.. index::
|
||||
pair: "subsection"; <subsection>
|
||||
|
||||
----------
|
||||
subsection
|
||||
----------
|
||||
|
||||
subsubsection
|
||||
-------------
|
2
tests/roots/test-need-escaped/quux.rst
Normal file
2
tests/roots/test-need-escaped/quux.rst
Normal file
@ -0,0 +1,2 @@
|
||||
quux
|
||||
====
|
1
tests/roots/test-need-escaped/qux.rst
Normal file
1
tests/roots/test-need-escaped/qux.rst
Normal file
@ -0,0 +1 @@
|
||||
qux.rst has no section title
|
@ -13,16 +13,107 @@
|
||||
|
||||
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
|
||||
assert '<file>_static/down.png</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.sphinxdoc.sphinx</namespace>' in qhp
|
||||
|
||||
qhcp = (app.outdir / 'Python.qhcp').text()
|
||||
assert '<homePage>qthelp://org.sphinxdoc.sphinx/doc/index.html</homePage>' in qhcp
|
||||
assert '<startPage>qthelp://org.sphinxdoc.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
|
||||
|
Loading…
Reference in New Issue
Block a user