Commits

Georg Brandl committed 494e382 Merge

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.

Comments (0)

Files changed (19)

   allowing styles to customize their appearance.  Domain-specific
   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:
   ``:param type name: description``.
 
    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
    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
    will be named "Python function", not just "function").
 

doc/markup/inline.rst

    labels, window titles, field names, menu and menu selection names, and even
    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
 
    Mark a sequence of keystrokes.  What form the key sequence takes may depend
    ellipsis some operating systems use to indicate that the command opens a
    dialog, the indicator should be omitted from the selection name.
 
+   ``menuselection`` also supports ampersand accelerators just like
+   :rst:role:`guilabel`.
+
 .. rst:role:: mimetype
 
    The name of a MIME type, or a component of a MIME type (the major or minor
     doesn't scroll out of view for long body content.  This may not work well
     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
   without having to write a custom stylesheet:
 

sphinx/builders/html.py

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

sphinx/domains/std.py

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

sphinx/environment.py

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

sphinx/ext/extlinks.py

                 title = full_url
             else:
                 title = prefix + part
-        pnode = nodes.reference(title, title, refuri=full_url)
+        pnode = nodes.reference(title, title, internal=False, refuri=full_url)
         return [pnode], []
     return role
 

sphinx/ext/intersphinx.py

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

sphinx/ext/todo.py

             para += nodes.Text(desc1, desc1)
 
             # Create a reference
-            newnode = nodes.reference('', '')
+            newnode = nodes.reference('', '', internal=True)
             innernode = nodes.emphasis(_('original entry'), _('original entry'))
-            newnode['refdocname'] = todo_info['docname']
             try:
                 newnode['refuri'] = app.builder.get_relative_uri(
                     fromdocname, todo_info['docname'])
 generic_docroles = {
     'command' : nodes.strong,
     'dfn' : nodes.emphasis,
-    'guilabel' : nodes.strong,
     'kbd' : nodes.literal,
     'mailheader' : addnodes.literal_emphasis,
     'makevar' : nodes.strong,
     role = roles.CustomRole(rolename, generic, {'classes': [rolename]})
     roles.register_local_role(rolename, role)
 
-
 # -- generic cross-reference role ----------------------------------------------
 
 class XRefRole(object):
             return [prb], [msg]
         ref = inliner.document.settings.pep_base_url + 'pep-%04d' % pepnum
         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
         return [indexnode, targetnode, rn], []
     elif typ == 'rfc':
             return [prb], [msg]
         ref = inliner.document.settings.rfc_base_url + inliner.rfc_url % rfcnum
         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
         return [indexnode, targetnode, rn], []
 
 
+_amp_re = re.compile(r'(?<!&)&(?![&\s])')
+
 def menusel_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
-    return [nodes.emphasis(
-        rawtext, utils.unescape(text).replace('-->', u'\N{TRIANGULAR BULLET}'),
-        classes=[typ])], []
-    return role
+    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('{([^}]+)}')
 
 
     'pep': indexmarkup_role,
     'rfc': indexmarkup_role,
+    'guilabel': menusel_role,
     'menuselection': menusel_role,
     'file': emph_literal_role,
     'samp': emph_literal_role,

sphinx/themes/basic/static/basic.css

     margin-left: 1.5em;
 }
 
+.guilabel, .menuselection {
+    font-family: sans-serif;
+}
+
+.accelerator {
+    text-decoration: underline;
+}
+
 /* -- code displays --------------------------------------------------------- */
 
 pre {

sphinx/themes/default/static/default.css_t

     font-size: 1em;
 }
 
-/* -- body styles ----------------------------------------------------------- */
+
+/* -- hyperlink styles ------------------------------------------------------ */
 
 a {
     color: {{ theme_linkcolor }};
     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 h2,
 div.body h3,

sphinx/themes/default/theme.conf

 rightsidebar = false
 stickysidebar = false
 
+externalrefs = false
+
 footerbgcolor    = #11303d
 footertextcolor  = #ffffff
 sidebarbgcolor   = #1c4e63

sphinx/util/nodes.py

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

sphinx/writers/html.py

 
     # overwritten
     def visit_reference(self, node):
-        BaseTranslator.visit_reference(self, node)
-        if node.hasattr('reftitle'):
-            # ugly hack to add a title attribute
-            starttag = self.body[-1]
-            if not starttag.startswith('<a '):
-                return
-            self.body[-1] = '<a title="%s"' % self.attval(node['reftitle']) + \
-                            starttag[2:]
+        atts = {'class': 'reference'}
+        if node.get('internal'):
+            atts['class'] += ' internal'
+        else:
+            atts['class'] += ' external'
+        if 'refuri' in node:
+            atts['href'] = node['refuri']
+            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'):
             self.body.append(('%s' + self.secnumber_suffix) %
                              '.'.join(map(str, node['secnumber'])))
 
     def visit_download_reference(self, node):
         if node.hasattr('filename'):
-            self.body.append('<a href="%s">' % posixpath.join(
-                self.builder.dlpath, node['filename']))
+            self.body.append(
+                '<a class="reference download internal" href="%s">' %
+                posixpath.join(self.builder.dlpath, node['filename']))
             self.context.append('</a>')
         else:
             self.context.append('')

tests/root/markup.txt

    b
 
 
+.. _admonition-section:
+
 Admonitions
 ^^^^^^^^^^^
 
 
    Warning text.
 
+.. _some-label:
+
 .. tip::
    Tip text.
 
 
 * :command:`command`
 * :dfn:`dfn`
-* :guilabel:`guilabel`
+* :guilabel:`guilabel with &accelerator`
 * :kbd:`kbd`
 * :mailheader:`mailheader`
 * :makevar:`makevar`
 * :program:`program`
 * :regexp:`regexp`
 * :menuselection:`File --> Close`
+* :menuselection:`&File --> &Print`
 * :file:`a/{varpart}/b`
 * :samp:`print {i}`
 
 * :envvar:`HOME`
 * :keyword:`with`
 * :token:`try statement <try_stmt>`
+* :ref:`admonition-section`
+* :ref:`here <some-label>`
 * :doc:`subdir/includes`
 * ``:download:`` is tested in includes.txt
 * :option:`Python -c option <python -c>`

tests/test_build_html.py

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

tests/test_markup.py

            u'<p><em class="menuselection">a \N{TRIANGULAR BULLET} b</em></p>',
            '\\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
     yield (verify_re, ':option:`--with-option`',
            '<p><em( class="xref std std-option")?>--with-option</em></p>$',