Merge with http://bitbucket.org/tpowers/sphinx/ (rewriting the external/internal distinction code)

In HTML output, references now get the class ``internal`` if they are internal to the whole project, as opposed to internal to the current page.

The ``menuselection`` and ``guilabel`` roles now support ampersand accelerators.
This commit is contained in:
Georg Brandl
2010-05-24 12:57:07 +02:00
19 changed files with 141 additions and 47 deletions

View File

@@ -26,6 +26,13 @@ Release 1.0 (in development)
allowing styles to customize their appearance. Domain-specific allowing styles to customize their appearance. Domain-specific
roles get two classes, ``domain`` and ``domain-rolename``. roles get two classes, ``domain`` and ``domain-rolename``.
* In HTML output, references now get the class ``internal`` if they
are internal to the whole project, as opposed to internal to the
current page.
* The ``menuselection`` and ``guilabel`` roles now support ampersand
accelerators.
* New more compact doc field syntax is now recognized: * New more compact doc field syntax is now recognized:
``:param type name: description``. ``:param type name: description``.

View File

@@ -200,7 +200,7 @@ General configuration
The name of the default :ref:`domain <domains>`. Can also be ``None`` to The name of the default :ref:`domain <domains>`. Can also be ``None`` to
disable a default domain. The default is ``'py'``. Those objects in other disable a default domain. The default is ``'py'``. Those objects in other
domains (whether the domain name is given explicitly, or selected by a domains (whether the domain name is given explicitly, or selected by a
:dir:`default-domain` directive) will have the domain name explicitly :rst:dir:`default-domain` directive) will have the domain name explicitly
prepended when named (e.g., when the default domain is C, Python functions prepended when named (e.g., when the default domain is C, Python functions
will be named "Python function", not just "function"). will be named "Python function", not just "function").

View File

@@ -182,6 +182,11 @@ in a different style:
labels, window titles, field names, menu and menu selection names, and even labels, window titles, field names, menu and menu selection names, and even
values in selection lists. values in selection lists.
.. versionchanged:: 1.0
An accelerator key for the GUI label can be included using an ampersand;
this will be stripped and displayed underlined in the output (example:
``:guilabel:`&Cancel```). To include a literal ampersand, double it.
.. rst:role:: kbd .. rst:role:: kbd
Mark a sequence of keystrokes. What form the key sequence takes may depend Mark a sequence of keystrokes. What form the key sequence takes may depend
@@ -227,6 +232,9 @@ in a different style:
ellipsis some operating systems use to indicate that the command opens a ellipsis some operating systems use to indicate that the command opens a
dialog, the indicator should be omitted from the selection name. dialog, the indicator should be omitted from the selection name.
``menuselection`` also supports ampersand accelerators just like
:rst:role:`guilabel`.
.. rst:role:: mimetype .. rst:role:: mimetype
The name of a MIME type, or a component of a MIME type (the major or minor The name of a MIME type, or a component of a MIME type (the major or minor

View File

@@ -105,6 +105,9 @@ These themes are:
doesn't scroll out of view for long body content. This may not work well doesn't scroll out of view for long body content. This may not work well
with all browsers. Defaults to false. with all browsers. Defaults to false.
- **externalrefs** (true or false): Display external links differently from
internal links. Defaults to false.
There are also various color and font options that can change the color scheme There are also various color and font options that can change the color scheme
without having to write a custom stylesheet: without having to write a custom stylesheet:

View File

@@ -607,7 +607,7 @@ class StandaloneHTMLBuilder(Builder):
# the parent node here. # the parent node here.
continue continue
uri = node['uri'] uri = node['uri']
reference = nodes.reference() reference = nodes.reference('', '', internal=True)
if uri in self.images: if uri in self.images:
reference['refuri'] = posixpath.join(self.imgpath, reference['refuri'] = posixpath.join(self.imgpath,
self.images[uri]) self.images[uri])

View File

@@ -441,7 +441,7 @@ class StandardDomain(Domain):
# 'precede a section header.', node.line) # 'precede a section header.', node.line)
if not docname: if not docname:
return None return None
newnode = nodes.reference('', '') newnode = nodes.reference('', '', internal=True)
innernode = nodes.emphasis(sectname, sectname) innernode = nodes.emphasis(sectname, sectname)
if docname == fromdocname: if docname == fromdocname:
newnode['refid'] = labelid newnode['refid'] = labelid

View File

@@ -1008,9 +1008,9 @@ class BuildEnvironment:
else: else:
anchorname = '#' + sectionnode['ids'][0] anchorname = '#' + sectionnode['ids'][0]
numentries[0] += 1 numentries[0] += 1
reference = nodes.reference('', '', refuri=docname, reference = nodes.reference(
anchorname=anchorname, '', '', internal=True, refuri=docname,
*nodetext) anchorname=anchorname, *nodetext)
para = addnodes.compact_paragraph('', '', reference) para = addnodes.compact_paragraph('', '', reference)
item = nodes.list_item('', para) item = nodes.list_item('', para)
if maxdepth == 0 or depth < maxdepth: if maxdepth == 0 or depth < maxdepth:
@@ -1148,7 +1148,7 @@ class BuildEnvironment:
for (title, ref) in refs: for (title, ref) in refs:
try: try:
if url_re.match(ref): if url_re.match(ref):
reference = nodes.reference('', '', reference = nodes.reference('', '', internal=False,
refuri=ref, anchorname='', refuri=ref, anchorname='',
*[nodes.Text(title)]) *[nodes.Text(title)])
para = addnodes.compact_paragraph('', '', reference) para = addnodes.compact_paragraph('', '', reference)
@@ -1160,7 +1160,7 @@ class BuildEnvironment:
ref = toctreenode['parent'] ref = toctreenode['parent']
if not title: if not title:
title = clean_astext(self.titles[ref]) title = clean_astext(self.titles[ref])
reference = nodes.reference('', '', reference = nodes.reference('', '', internal=True,
refuri=ref, refuri=ref,
anchorname='', anchorname='',
*[nodes.Text(title)]) *[nodes.Text(title)])
@@ -1275,7 +1275,7 @@ class BuildEnvironment:
else: else:
caption = clean_astext(self.titles[docname]) caption = clean_astext(self.titles[docname])
innernode = nodes.emphasis(caption, caption) innernode = nodes.emphasis(caption, caption)
newnode = nodes.reference('', '') newnode = nodes.reference('', '', internal=True)
newnode['refuri'] = builder.get_relative_uri( newnode['refuri'] = builder.get_relative_uri(
fromdocname, docname) fromdocname, docname)
newnode.append(innernode) newnode.append(innernode)

View File

@@ -46,7 +46,7 @@ def make_link_role(base_url, prefix):
title = full_url title = full_url
else: else:
title = prefix + part title = prefix + part
pnode = nodes.reference(title, title, refuri=full_url) pnode = nodes.reference(title, title, internal=False, refuri=full_url)
return [pnode], [] return [pnode], []
return role return role

View File

@@ -201,10 +201,8 @@ def missing_reference(app, env, node, contnode):
if objtype not in inventory or target not in inventory[objtype]: if objtype not in inventory or target not in inventory[objtype]:
continue continue
proj, version, uri, dispname = inventory[objtype][target] proj, version, uri, dispname = inventory[objtype][target]
newnode = nodes.reference('', '') newnode = nodes.reference('', '', internal=False, refuri=uri,
newnode['refuri'] = uri reftitle='(in %s v%s)' % (proj, version))
newnode['reftitle'] = '(in %s v%s)' % (proj, version)
newnode['class'] = 'external-xref'
if dispname == '-': if dispname == '-':
newnode.append(contnode) newnode.append(contnode)
else: else:

View File

@@ -113,9 +113,8 @@ def process_todo_nodes(app, doctree, fromdocname):
para += nodes.Text(desc1, desc1) para += nodes.Text(desc1, desc1)
# Create a reference # Create a reference
newnode = nodes.reference('', '') newnode = nodes.reference('', '', internal=True)
innernode = nodes.emphasis(_('original entry'), _('original entry')) innernode = nodes.emphasis(_('original entry'), _('original entry'))
newnode['refdocname'] = todo_info['docname']
try: try:
newnode['refuri'] = app.builder.get_relative_uri( newnode['refuri'] = app.builder.get_relative_uri(
fromdocname, todo_info['docname']) fromdocname, todo_info['docname'])

View File

@@ -24,7 +24,6 @@ from sphinx.util.nodes import split_explicit_title
generic_docroles = { generic_docroles = {
'command' : nodes.strong, 'command' : nodes.strong,
'dfn' : nodes.emphasis, 'dfn' : nodes.emphasis,
'guilabel' : nodes.strong,
'kbd' : nodes.literal, 'kbd' : nodes.literal,
'mailheader' : addnodes.literal_emphasis, 'mailheader' : addnodes.literal_emphasis,
'makevar' : nodes.strong, 'makevar' : nodes.strong,
@@ -40,7 +39,6 @@ for rolename, nodeclass in generic_docroles.iteritems():
role = roles.CustomRole(rolename, generic, {'classes': [rolename]}) role = roles.CustomRole(rolename, generic, {'classes': [rolename]})
roles.register_local_role(rolename, role) roles.register_local_role(rolename, role)
# -- generic cross-reference role ---------------------------------------------- # -- generic cross-reference role ----------------------------------------------
class XRefRole(object): class XRefRole(object):
@@ -184,7 +182,7 @@ def indexmarkup_role(typ, rawtext, etext, lineno, inliner,
return [prb], [msg] return [prb], [msg]
ref = inliner.document.settings.pep_base_url + 'pep-%04d' % pepnum ref = inliner.document.settings.pep_base_url + 'pep-%04d' % pepnum
sn = nodes.strong('PEP '+text, 'PEP '+text) sn = nodes.strong('PEP '+text, 'PEP '+text)
rn = nodes.reference('', '', refuri=ref, classes=[typ]) rn = nodes.reference('', '', internal=False, refuri=ref, classes=[typ])
rn += sn rn += sn
return [indexnode, targetnode, rn], [] return [indexnode, targetnode, rn], []
elif typ == 'rfc': elif typ == 'rfc':
@@ -199,17 +197,36 @@ def indexmarkup_role(typ, rawtext, etext, lineno, inliner,
return [prb], [msg] return [prb], [msg]
ref = inliner.document.settings.rfc_base_url + inliner.rfc_url % rfcnum ref = inliner.document.settings.rfc_base_url + inliner.rfc_url % rfcnum
sn = nodes.strong('RFC '+text, 'RFC '+text) sn = nodes.strong('RFC '+text, 'RFC '+text)
rn = nodes.reference('', '', refuri=ref, classes=[typ]) rn = nodes.reference('', '', internal=False, refuri=ref, classes=[typ])
rn += sn rn += sn
return [indexnode, targetnode, rn], [] return [indexnode, targetnode, rn], []
def menusel_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): _amp_re = re.compile(r'(?<!&)&(?![&\s])')
return [nodes.emphasis(
rawtext, utils.unescape(text).replace('-->', u'\N{TRIANGULAR BULLET}'),
classes=[typ])], []
return role
def menusel_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
if typ == 'menuselection':
text = utils.unescape(text).replace('-->', u'\N{TRIANGULAR BULLET}')
spans = _amp_re.split(text)
node = nodes.emphasis(rawtext=rawtext)
for i, span in enumerate(spans):
span = span.replace('&&', '&')
if i == 0:
if len(span) > 0:
textnode = nodes.Text(span)
node += textnode
continue
accel_node = nodes.inline()
letter_node = nodes.Text(span[0])
accel_node += letter_node
accel_node['classes'].append('accelerator')
node += accel_node
textnode = nodes.Text(span[1:])
node += textnode
node['classes'].append(typ)
return [node], []
_litvar_re = re.compile('{([^}]+)}') _litvar_re = re.compile('{([^}]+)}')
@@ -249,6 +266,7 @@ specific_docroles = {
'pep': indexmarkup_role, 'pep': indexmarkup_role,
'rfc': indexmarkup_role, 'rfc': indexmarkup_role,
'guilabel': menusel_role,
'menuselection': menusel_role, 'menuselection': menusel_role,
'file': emph_literal_role, 'file': emph_literal_role,
'samp': emph_literal_role, 'samp': emph_literal_role,

View File

@@ -368,6 +368,14 @@ dl.glossary dt {
margin-left: 1.5em; margin-left: 1.5em;
} }
.guilabel, .menuselection {
font-family: sans-serif;
}
.accelerator {
text-decoration: underline;
}
/* -- code displays --------------------------------------------------------- */ /* -- code displays --------------------------------------------------------- */
pre { pre {

View File

@@ -147,7 +147,8 @@ div.sphinxsidebar input {
font-size: 1em; font-size: 1em;
} }
/* -- body styles ----------------------------------------------------------- */
/* -- hyperlink styles ------------------------------------------------------ */
a { a {
color: {{ theme_linkcolor }}; color: {{ theme_linkcolor }};
@@ -163,6 +164,20 @@ a:hover {
text-decoration: underline; text-decoration: underline;
} }
{% if theme_externalrefs %}
a.external {
text-decoration: none;
border-bottom: 1px dashed {{ theme_linkcolor }};
}
a.external:hover {
text-decoration: none;
border-bottom: none;
}
{% endif %}
/* -- body styles ----------------------------------------------------------- */
div.body h1, div.body h1,
div.body h2, div.body h2,
div.body h3, div.body h3,

View File

@@ -7,6 +7,8 @@ pygments_style = sphinx
rightsidebar = false rightsidebar = false
stickysidebar = false stickysidebar = false
externalrefs = false
footerbgcolor = #11303d footerbgcolor = #11303d
footertextcolor = #ffffff footertextcolor = #ffffff
sidebarbgcolor = #1c4e63 sidebarbgcolor = #1c4e63

View File

@@ -80,7 +80,7 @@ def inline_all_toctrees(builder, docnameset, docname, tree, colorfunc):
def make_refnode(builder, fromdocname, todocname, targetid, child, title=None): def make_refnode(builder, fromdocname, todocname, targetid, child, title=None):
"""Shortcut to create a reference node.""" """Shortcut to create a reference node."""
node = nodes.reference('', '') node = nodes.reference('', '', internal=True)
if fromdocname == todocname: if fromdocname == todocname:
node['refid'] = targetid node['refid'] = targetid
else: else:

View File

@@ -157,14 +157,28 @@ class HTMLTranslator(BaseTranslator):
# overwritten # overwritten
def visit_reference(self, node): def visit_reference(self, node):
BaseTranslator.visit_reference(self, node) atts = {'class': 'reference'}
if node.hasattr('reftitle'): if node.get('internal'):
# ugly hack to add a title attribute atts['class'] += ' internal'
starttag = self.body[-1] else:
if not starttag.startswith('<a '): atts['class'] += ' external'
return if 'refuri' in node:
self.body[-1] = '<a title="%s"' % self.attval(node['reftitle']) + \ atts['href'] = node['refuri']
starttag[2:] if self.settings.cloak_email_addresses and \
atts['href'].startswith('mailto:'):
atts['href'] = self.cloak_mailto(atts['href'])
self.in_mailto = 1
else:
assert 'refid' in node, \
'References must have "refuri" or "refid" attribute.'
atts['href'] = '#' + node['refid']
if not isinstance(node.parent, nodes.TextElement):
assert len(node) == 1 and isinstance(node[0], nodes.image)
atts['class'] += ' image-reference'
if 'reftitle' in node:
atts['title'] = node['reftitle']
self.body.append(self.starttag(node, 'a', '', **atts))
if node.hasattr('secnumber'): if node.hasattr('secnumber'):
self.body.append(('%s' + self.secnumber_suffix) % self.body.append(('%s' + self.secnumber_suffix) %
'.'.join(map(str, node['secnumber']))) '.'.join(map(str, node['secnumber'])))
@@ -283,8 +297,9 @@ class HTMLTranslator(BaseTranslator):
def visit_download_reference(self, node): def visit_download_reference(self, node):
if node.hasattr('filename'): if node.hasattr('filename'):
self.body.append('<a href="%s">' % posixpath.join( self.body.append(
self.builder.dlpath, node['filename'])) '<a class="reference download internal" href="%s">' %
posixpath.join(self.builder.dlpath, node['filename']))
self.context.append('</a>') self.context.append('</a>')
else: else:
self.context.append('') self.context.append('')

View File

@@ -70,6 +70,8 @@ Body directives
b b
.. _admonition-section:
Admonitions Admonitions
^^^^^^^^^^^ ^^^^^^^^^^^
@@ -84,6 +86,8 @@ Admonitions
Warning text. Warning text.
.. _some-label:
.. tip:: .. tip::
Tip text. Tip text.
@@ -95,7 +99,7 @@ Inline markup
* :command:`command` * :command:`command`
* :dfn:`dfn` * :dfn:`dfn`
* :guilabel:`guilabel` * :guilabel:`guilabel with &accelerator`
* :kbd:`kbd` * :kbd:`kbd`
* :mailheader:`mailheader` * :mailheader:`mailheader`
* :makevar:`makevar` * :makevar:`makevar`
@@ -105,6 +109,7 @@ Inline markup
* :program:`program` * :program:`program`
* :regexp:`regexp` * :regexp:`regexp`
* :menuselection:`File --> Close` * :menuselection:`File --> Close`
* :menuselection:`&File --> &Print`
* :file:`a/{varpart}/b` * :file:`a/{varpart}/b`
* :samp:`print {i}` * :samp:`print {i}`
@@ -115,6 +120,8 @@ Inline markup
* :envvar:`HOME` * :envvar:`HOME`
* :keyword:`with` * :keyword:`with`
* :token:`try statement <try_stmt>` * :token:`try statement <try_stmt>`
* :ref:`admonition-section`
* :ref:`here <some-label>`
* :doc:`subdir/includes` * :doc:`subdir/includes`
* ``:download:`` is tested in includes.txt * ``:download:`` is tested in includes.txt
* :option:`Python -c option <python -c>` * :option:`Python -c option <python -c>`

View File

@@ -104,13 +104,20 @@ HTML_XPATH = {
".//li/tt/span[@class='pre']": '^a/$', ".//li/tt/span[@class='pre']": '^a/$',
".//li/tt/em/span[@class='pre']": '^varpart$', ".//li/tt/em/span[@class='pre']": '^varpart$',
".//li/tt/em/span[@class='pre']": '^i$', ".//li/tt/em/span[@class='pre']": '^i$',
".//a[@href='http://www.python.org/dev/peps/pep-0008']/strong": 'PEP 8', ".//a[@href='http://www.python.org/dev/peps/pep-0008']"
".//a[@href='http://tools.ietf.org/html/rfc1.html']/strong": 'RFC 1', "[@class='pep reference external']/strong": 'PEP 8',
".//a[@href='objects.html#envvar-HOME']/tt/span[@class='pre']": 'HOME', ".//a[@href='http://tools.ietf.org/html/rfc1.html']"
".//a[@href='#with']/tt/span[@class='pre']": '^with$', "[@class='rfc reference external']/strong": 'RFC 1',
".//a[@href='#grammar-token-try_stmt']/tt/span": '^statement$', ".//a[@href='objects.html#envvar-HOME']"
".//a[@href='subdir/includes.html']/em": 'Including in subdir', "[@class='reference internal']/tt/span[@class='pre']": 'HOME',
".//a[@href='objects.html#cmdoption-python-c']/em": 'Python -c option', ".//a[@href='#with']"
"[@class='reference internal']/tt/span[@class='pre']": '^with$',
".//a[@href='#grammar-token-try_stmt']"
"[@class='reference internal']/tt/span": '^statement$',
".//a[@href='subdir/includes.html']"
"[@class='reference internal']/em": 'Including in subdir',
".//a[@href='objects.html#cmdoption-python-c']"
"[@class='reference internal']/em": 'Python -c option',
# abbreviations # abbreviations
".//abbr[@title='abbreviation']": '^abbr$', ".//abbr[@title='abbreviation']": '^abbr$',
# version stuff # version stuff
@@ -139,7 +146,7 @@ HTML_XPATH = {
'objects.html': { 'objects.html': {
".//dt[@id='mod.Cls.meth1']": '', ".//dt[@id='mod.Cls.meth1']": '',
".//dt[@id='errmod.Error']": '', ".//dt[@id='errmod.Error']": '',
".//a[@href='#mod.Cls']": '', ".//a[@href='#mod.Cls'][@class='reference internal']": '',
".//dl[@class='userdesc']": '', ".//dl[@class='userdesc']": '',
".//dt[@id='userdesc-myobj']": '', ".//dt[@id='userdesc-myobj']": '',
".//a[@href='#userdesc-myobj']": '', ".//a[@href='#userdesc-myobj']": '',
@@ -168,7 +175,8 @@ HTML_XPATH = {
".//li[@class='toctree-l2']/a": 'Inline markup', ".//li[@class='toctree-l2']/a": 'Inline markup',
".//title": 'Sphinx <Tests>', ".//title": 'Sphinx <Tests>',
".//div[@class='footer']": 'Georg Brandl & Team', ".//div[@class='footer']": 'Georg Brandl & Team',
".//a[@href='http://python.org/']": '', ".//a[@href='http://python.org/']"
"[@class='reference external']": '',
".//li/a[@href='genindex.html']/em": 'Index', ".//li/a[@href='genindex.html']/em": 'Index',
".//li/a[@href='py-modindex.html']/em": 'Module Index', ".//li/a[@href='py-modindex.html']/em": 'Module Index',
".//li/a[@href='search.html']/em": 'Search Page', ".//li/a[@href='search.html']/em": 'Search Page',

View File

@@ -97,6 +97,12 @@ def test_inline():
u'<p><em class="menuselection">a \N{TRIANGULAR BULLET} b</em></p>', u'<p><em class="menuselection">a \N{TRIANGULAR BULLET} b</em></p>',
'\\emph{a \\(\\rightarrow\\) b}') '\\emph{a \\(\\rightarrow\\) b}')
# interpolation of ampersands in guilabel/menuselection
yield (verify, ':guilabel:`&Foo -&&- &Bar`',
u'<p><em class="guilabel"><span class="accelerator">F</span>oo '
'-&amp;- <span class="accelerator">B</span>ar</em></p>',
'\\emph{\\DUspan{accelerator}{F}oo -\\&- \\DUspan{accelerator}{B}ar}')
# non-interpolation of dashes in option role # non-interpolation of dashes in option role
yield (verify_re, ':option:`--with-option`', yield (verify_re, ':option:`--with-option`',
'<p><em( class="xref std std-option")?>--with-option</em></p>$', '<p><em( class="xref std std-option")?>--with-option</em></p>$',