From 910e488e39bf8d25e04c568e3736de18b8bf3a05 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 9 Mar 2008 18:18:41 +0000 Subject: [PATCH] * Allow registering arbitrary cross-referencing directives/roles. * Allow labels anywhere, and allow giving an explicit caption in :ref: links. * Some fixes to the sphinxdoc style. * Add an option to show author information in the output. * Search user-defined templates in the order they occur in the config (thanks Nick). --- sphinx/_jinja.py | 13 ++++-- sphinx/application.py | 8 ++++ sphinx/builder.py | 11 ++--- sphinx/config.py | 3 +- sphinx/directives.py | 61 +++++++++++++++++++------- sphinx/environment.py | 50 ++++++++++++++++------ sphinx/quickstart.py | 10 +++-- sphinx/roles.py | 16 +++++++ sphinx/static/sphinxdoc.css | 85 +++++++++++++++++++++++++++++-------- 9 files changed, 197 insertions(+), 60 deletions(-) diff --git a/sphinx/_jinja.py b/sphinx/_jinja.py index 92975b85a..1cb5f5500 100644 --- a/sphinx/_jinja.py +++ b/sphinx/_jinja.py @@ -25,12 +25,19 @@ class SphinxFileSystemLoader(BaseLoader): paths, or from an absolute path. """ - def __init__(self, paths): - self.searchpaths = map(path.abspath, paths) + def __init__(self, basepath, extpaths): + self.basepath = path.abspath(basepath) + self.extpaths = map(path.abspath, extpaths) + self.searchpaths = self.extpaths + [self.basepath] def get_source(self, environment, name, parent): name = name.replace('/', path.sep) - if path.isabs(name): + if name.startswith('!'): + name = name[1:] + if not path.exists(path.join(self.basepath, name)): + raise TemplateNotFound(name) + filename = path.join(self.basepath, name) + elif path.isabs(name): if not path.exists(name): raise TemplateNotFound(name) filename = name diff --git a/sphinx/application.py b/sphinx/application.py index 00b5da23a..8ed8c73fb 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -18,8 +18,10 @@ from docutils import nodes from docutils.parsers.rst import directives, roles import sphinx +from sphinx.roles import xfileref_role from sphinx.config import Config from sphinx.builder import builtin_builders +from sphinx.directives import desc_directive, additional_xref_types from sphinx.util.console import bold @@ -185,3 +187,9 @@ class Sphinx(object): def add_role(self, name, role): roles.register_canonical_role(name, role) + + def add_description_unit(self, directivename, rolename, indexdesc='', + parse_node=None): + additional_xref_types[directivename] = (rolename, indexdesc, parse_node) + directives.register_directive(directivename, desc_directive) + roles.register_canonical_role(rolename, xfileref_role) diff --git a/sphinx/builder.py b/sphinx/builder.py index d298c01bb..feeb00171 100644 --- a/sphinx/builder.py +++ b/sphinx/builder.py @@ -79,10 +79,9 @@ class Builder(object): # load templates self.templates = {} - templates_path = [path.join(path.dirname(__file__), 'templates')] - templates_path.extend(self.config.templates_path) - templates_path.reverse() - self.jinja_env = Environment(loader=SphinxFileSystemLoader(templates_path), + base_templates_path = path.join(path.dirname(__file__), 'templates') + loader = SphinxFileSystemLoader(base_templates_path, self.config.templates_path) + self.jinja_env = Environment(loader=loader, # disable traceback, more likely that something # in the application is broken than in the templates friendly_traceback=False) @@ -438,13 +437,11 @@ class StandaloneHTMLBuilder(Builder): # additional pages from conf.py for pagename, template in self.config.html_additional_pages.items(): - template = path.join(self.srcdir, template) self.handle_page(pagename, {}, template) # the index page indextemplate = self.config.html_index if indextemplate: - indextemplate = path.join(self.srcdir, indextemplate) self.handle_page('index', {'indextemplate': indextemplate}, 'index.html') # copy static files @@ -515,7 +512,7 @@ class StandaloneHTMLBuilder(Builder): ctx['hasdoc'] = lambda name: name in self.env.all_docs sidebarfile = self.config.html_sidebars.get(pagename) if sidebarfile: - ctx['customsidebar'] = path.join(self.srcdir, sidebarfile) + ctx['customsidebar'] = sidebarfile ctx.update(addctx) output = self.get_template(templatename).render(ctx) diff --git a/sphinx/config.py b/sphinx/config.py index 40d48019e..e544052ab 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -20,7 +20,7 @@ class Config(object): # the values are: (default, needs fresh doctrees if changed) # If you add a value here, don't forget to include it in the - # quickstart.py file template as well! + # quickstart.py file template as well as in the docs! config_values = dict( # general substitutions @@ -41,6 +41,7 @@ class Config(object): unused_docs = ([], True), add_function_parentheses = (True, True), add_module_names = (True, True), + show_authors = (False, True), pygments_style = ('sphinx', False), # HTML options diff --git a/sphinx/directives.py b/sphinx/directives.py index 5a712c322..349526fbd 100644 --- a/sphinx/directives.py +++ b/sphinx/directives.py @@ -310,21 +310,30 @@ def desc_directive(desctype, arguments, options, content, lineno, targetname, targetname) env.note_reftarget('option', optname, targetname) continue - elif desctype == 'envvar': + elif desctype == 'describe': signode.clear() signode += addnodes.desc_name(sig, sig) - if not noindex: - targetname = 'envvar-' + sig - signode['ids'].append(targetname) - state.document.note_explicit_target(signode) - env.note_index_entry('pair', 'environment variable; %s' % sig, - targetname, targetname) - env.note_reftarget('envvar', sig, targetname) continue else: - # for "describe": use generic fallback - raise ValueError + # another registered generic x-ref directive + rolename, indextext, parse_node = additional_xref_types[desctype] + if parse_node: + parse_node(sig, signode) + else: + signode.clear() + signode += addnodes.desc_name(sig, sig) + if not noindex: + targetname = '%s-%s' % (rolename, sig) + signode['ids'].append(targetname) + state.document.note_explicit_target(signode) + if indextext: + env.note_index_entry('pair', '%s; %s' % (indextext, sig), + targetname, targetname) + env.note_reftarget(rolename, sig, targetname) + # don't use object indexing below + continue except ValueError, err: + # signature parsing failed signode.clear() signode += addnodes.desc_name(sig, sig) continue # we don't want an index entry here @@ -384,15 +393,22 @@ desctypes = [ 'cvar', # the odd one 'opcode', - # the generic ones - 'cmdoption', # for command line options - 'envvar', # for environment variables + # for command line options + 'cmdoption', + # the generic one 'describe', + 'envvar', ] for _name in desctypes: directives.register_directive(_name, desc_directive) +# Generic cross-reference types; they can be registered in the application +additional_xref_types = { + # directive name: (role name, index text) + 'envvar': ('envvar', 'environment variable', None), +} + # ------ versionadded/versionchanged ----------------------------------------------- @@ -526,8 +542,23 @@ directives.register_directive('module', module_directive) def author_directive(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine): - # The author directives aren't included in the built document - return [] + # Show authors only if the show_authors option is on + env = state.document.settings.env + if not env.config.show_authors: + return [] + para = nodes.paragraph() + emph = nodes.emphasis() + para += emph + if name == 'sectionauthor': + text = 'Section author: ' + elif name == 'moduleauthor': + text = 'Module author: ' + else: + text = 'Author: ' + emph += nodes.Text(text, text) + inodes, messages = state.inline_text(arguments[0], lineno) + emph.extend(inodes) + return [para] + messages author_directive.arguments = (1, 0, 1) directives.register_directive('sectionauthor', author_directive) diff --git a/sphinx/environment.py b/sphinx/environment.py index f0ca831a9..21327903b 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -44,6 +44,7 @@ Body.enum.converters['loweralpha'] = \ from sphinx import addnodes from sphinx.util import get_matching_docs, SEP +from sphinx.directives import additional_xref_types default_settings = { 'embed_stylesheet': False, @@ -56,7 +57,7 @@ default_settings = { # This is increased every time a new environment attribute is added # to properly invalidate pickle files. -ENV_VERSION = 16 +ENV_VERSION = 17 def walk_depth(node, depth, maxdepth): @@ -226,6 +227,7 @@ class BuildEnvironment: self.filemodules = {} # docname -> [modules] self.modules = {} # modname -> docname, synopsis, platform, deprecated self.labels = {} # labelname -> docname, labelid, sectionname + self.anonlabels = {} # labelname -> docname, labelid self.reftargets = {} # (type, name) -> docname, labelid # where type is term, token, option, envvar @@ -471,13 +473,19 @@ class BuildEnvironment: continue labelid = document.nameids[name] node = document.ids[labelid] - if not isinstance(node, nodes.section): - # e.g. desc-signatures + if name.isdigit() or node.has_key('refuri') or \ + node.tagname.startswith('desc_'): + # ignore footnote labels, labels automatically generated from a + # link and description units continue - sectname = node[0].astext() # node[0] == title node if name in self.labels: self.warn(docname, 'duplicate label %s, ' % name + 'other instance in %s' % self.doc2path(self.labels[name][0])) + self.anonlabels[name] = docname, labelid + if not isinstance(node, nodes.section): + # anonymous-only labels + continue + sectname = node[0].astext() # node[0] == title node self.labels[name] = docname, labelid, sectname def note_toctree(self, docname, toctreenode): @@ -654,23 +662,37 @@ class BuildEnvironment: typ = node['reftype'] target = node['reftarget'] + reftarget_roles = set(('token', 'term', 'option')) + # add all custom xref types too + reftarget_roles.update(i[0] for i in additional_xref_types.values()) + try: if typ == 'ref': - # reference to the named label; the final node will contain the - # section name after the label - docname, labelid, sectname = self.labels.get(target, ('','','')) - if not docname: - newnode = doctree.reporter.system_message( - 2, 'undefined label: %s' % target) - #self.warn(fromdocname, 'undefined label: %s' % target) + if node['refcaption']: + # reference to anonymous label; the reference uses the supplied + # link caption + docname, labelid = self.anonlabels.get(target, ('','')) + sectname = node.astext() + if not docname: + newnode = doctree.reporter.system_message( + 2, 'undefined label: %s' % target) else: + # reference to the named label; the final node will contain the + # section name after the label + docname, labelid, sectname = self.labels.get(target, ('','','')) + if not docname: + newnode = doctree.reporter.system_message( + 2, 'undefined label: %s -- if you don\'t ' % target + + 'give a link caption the label must precede a section ' + 'header.') + if docname: newnode = nodes.reference('', '') innernode = nodes.emphasis(sectname, sectname) if docname == fromdocname: newnode['refid'] = labelid else: - # set more info in contnode in case the following call - # raises NoUri, the builder will have to resolve these + # set more info in contnode in case the get_relative_uri call + # raises NoUri, the builder will then have to resolve these contnode = addnodes.pending_xref('') contnode['refdocname'] = docname contnode['refsectname'] = sectname @@ -693,7 +715,7 @@ class BuildEnvironment: newnode['refuri'] = builder.get_relative_uri( fromdocname, docname) + '#' + labelid newnode.append(contnode) - elif typ in ('token', 'term', 'envvar', 'option'): + elif typ in reftarget_roles: docname, labelid = self.reftargets.get((typ, target), ('', '')) if not docname: if typ == 'term': diff --git a/sphinx/quickstart.py b/sphinx/quickstart.py index 8739e1d78..19331e3a9 100644 --- a/sphinx/quickstart.py +++ b/sphinx/quickstart.py @@ -78,6 +78,10 @@ today_fmt = '%%B %%d, %%Y' # unit titles (such as .. function::). #add_module_names = True +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' @@ -103,14 +107,14 @@ html_last_updated_fmt = '%%b %%d, %%Y' # typographically correct entities. #html_use_smartypants = True -# Content template for the index page, filename relative to this file. +# Content template for the index page. #html_index = '' -# Custom sidebar templates, maps page names to filenames relative to this file. +# Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to -# filenames relative to this file. +# template names. #html_additional_pages = {} # If true, the reST sources are included in the HTML build as _sources/. diff --git a/sphinx/roles.py b/sphinx/roles.py index 4570fbc7b..99922845a 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -17,6 +17,7 @@ from docutils.parsers.rst import roles from sphinx import addnodes ws_re = re.compile(r'\s+') +caption_ref_re = re.compile(r'^([^<]+?)\s*<(.+)>$') generic_docroles = { 'command' : nodes.strong, @@ -127,6 +128,21 @@ def xfileref_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): pnode['refspecific'] = True if typ == 'term': pnode['reftarget'] = ws_re.sub(' ', text).lower() + elif typ == 'ref': + brace = text.find('<') + if brace != -1: + pnode['refcaption'] = True + m = caption_ref_re.match(text) + if not m: + # fallback + pnode['reftarget'] = text[brace+1:] + text = text[:brace] + else: + pnode['reftarget'] = m.group(2) + text = m.group(1) + else: + pnode['refcaption'] = False + pnode['reftarget'] = ws_re.sub('', text) elif typ == 'option': if text[0] in '-/': pnode['reftarget'] = text[1:] diff --git a/sphinx/static/sphinxdoc.css b/sphinx/static/sphinxdoc.css index 6079232dc..bc7ec674d 100644 --- a/sphinx/static/sphinxdoc.css +++ b/sphinx/static/sphinxdoc.css @@ -64,11 +64,24 @@ tt.descname { tt.descclassname { background-color: transparent; + border: 0; } -tt.xref, a tt { +tt.xref { background-color: transparent; font-weight: bold; + border: 0; +} + +a tt { + background-color: transparent; + font-weight: bold; + border: 0; + color: #CA7900; +} + +a tt:hover { + color: #2491CF; } dl { @@ -99,20 +112,11 @@ dt:target, background-color: #fbe54e; } -/* -dt { - margin-top: 0.8em; +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; } -dd p.first { - margin-top: 0; -} - -dd p.last { - margin-bottom: 0; -} -*/ - pre { line-height: 120%; } @@ -122,10 +126,6 @@ pre a { text-decoration: underline; } -div.syntax { - background-color: transparent; -} - div.document { background-color: white; text-align: left; @@ -226,6 +226,10 @@ div.bodywrapper { border-right: 1px solid #ccc; } +div.body a { + text-decoration: underline; +} + div.sidebar { margin: 0; padding: 0.5em 15px 15px 0; @@ -447,3 +451,50 @@ a.headerlink:hover { background-color: #ccc; color: white!important; } + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable dl, table.indextable dd { + margin-top: 0; + margin-bottom: 0; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +form.pfform { + margin: 10px 0 20px 0; +} + +table.contentstable { + width: 90%; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +}