| """ |
| Sphinx plugins for Django documentation. |
| """ |
| import os |
| import re |
| |
| from docutils import nodes, transforms |
| try: |
| import json |
| except ImportError: |
| try: |
| import simplejson as json |
| except ImportError: |
| try: |
| from django.utils import simplejson as json |
| except ImportError: |
| json = None |
| |
| from sphinx import addnodes, roles |
| from sphinx.builders.html import StandaloneHTMLBuilder |
| from sphinx.writers.html import SmartyPantsHTMLTranslator |
| from sphinx.util.console import bold |
| from sphinx.util.compat import Directive |
| |
| # RE for option descriptions without a '--' prefix |
| simple_option_desc_re = re.compile( |
| r'([-_a-zA-Z0-9]+)(\s*.*?)(?=,\s+(?:/|-|--)|$)') |
| |
| def setup(app): |
| app.add_crossref_type( |
| directivename = "setting", |
| rolename = "setting", |
| indextemplate = "pair: %s; setting", |
| ) |
| app.add_crossref_type( |
| directivename = "templatetag", |
| rolename = "ttag", |
| indextemplate = "pair: %s; template tag" |
| ) |
| app.add_crossref_type( |
| directivename = "templatefilter", |
| rolename = "tfilter", |
| indextemplate = "pair: %s; template filter" |
| ) |
| app.add_crossref_type( |
| directivename = "fieldlookup", |
| rolename = "lookup", |
| indextemplate = "pair: %s; field lookup type", |
| ) |
| app.add_description_unit( |
| directivename = "django-admin", |
| rolename = "djadmin", |
| indextemplate = "pair: %s; django-admin command", |
| parse_node = parse_django_admin_node, |
| ) |
| app.add_description_unit( |
| directivename = "django-admin-option", |
| rolename = "djadminopt", |
| indextemplate = "pair: %s; django-admin command-line option", |
| parse_node = parse_django_adminopt_node, |
| ) |
| app.add_config_value('django_next_version', '0.0', True) |
| app.add_directive('versionadded', VersionDirective) |
| app.add_directive('versionchanged', VersionDirective) |
| app.add_transform(SuppressBlockquotes) |
| app.add_builder(DjangoStandaloneHTMLBuilder) |
| |
| |
| class VersionDirective(Directive): |
| has_content = True |
| required_arguments = 1 |
| optional_arguments = 1 |
| final_argument_whitespace = True |
| option_spec = {} |
| |
| def run(self): |
| env = self.state.document.settings.env |
| arg0 = self.arguments[0] |
| is_nextversion = env.config.django_next_version == arg0 |
| ret = [] |
| node = addnodes.versionmodified() |
| ret.append(node) |
| if not is_nextversion: |
| if len(self.arguments) == 1: |
| linktext = 'Please, see the release notes </releases/%s>' % (arg0) |
| xrefs = roles.XRefRole()('doc', linktext, linktext, self.lineno, self.state) |
| node.extend(xrefs[0]) |
| node['version'] = arg0 |
| else: |
| node['version'] = "Development version" |
| node['type'] = self.name |
| if len(self.arguments) == 2: |
| inodes, messages = self.state.inline_text(self.arguments[1], self.lineno+1) |
| node.extend(inodes) |
| if self.content: |
| self.state.nested_parse(self.content, self.content_offset, node) |
| ret = ret + messages |
| env.note_versionchange(node['type'], node['version'], node, self.lineno) |
| return ret |
| |
| |
| class SuppressBlockquotes(transforms.Transform): |
| """ |
| Remove the default blockquotes that encase indented list, tables, etc. |
| """ |
| default_priority = 300 |
| |
| suppress_blockquote_child_nodes = ( |
| nodes.bullet_list, |
| nodes.enumerated_list, |
| nodes.definition_list, |
| nodes.literal_block, |
| nodes.doctest_block, |
| nodes.line_block, |
| nodes.table |
| ) |
| |
| def apply(self): |
| for node in self.document.traverse(nodes.block_quote): |
| if len(node.children) == 1 and isinstance(node.children[0], self.suppress_blockquote_child_nodes): |
| node.replace_self(node.children[0]) |
| |
| class DjangoHTMLTranslator(SmartyPantsHTMLTranslator): |
| """ |
| Django-specific reST to HTML tweaks. |
| """ |
| |
| # Don't use border=1, which docutils does by default. |
| def visit_table(self, node): |
| self.body.append(self.starttag(node, 'table', CLASS='docutils')) |
| |
| # <big>? Really? |
| def visit_desc_parameterlist(self, node): |
| self.body.append('(') |
| self.first_param = 1 |
| |
| def depart_desc_parameterlist(self, node): |
| self.body.append(')') |
| |
| # |
| # Don't apply smartypants to literal blocks |
| # |
| def visit_literal_block(self, node): |
| self.no_smarty += 1 |
| SmartyPantsHTMLTranslator.visit_literal_block(self, node) |
| |
| def depart_literal_block(self, node): |
| SmartyPantsHTMLTranslator.depart_literal_block(self, node) |
| self.no_smarty -= 1 |
| |
| # |
| # Turn the "new in version" stuff (versionadded/versionchanged) into a |
| # better callout -- the Sphinx default is just a little span, |
| # which is a bit less obvious that I'd like. |
| # |
| # FIXME: these messages are all hardcoded in English. We need to change |
| # that to accomodate other language docs, but I can't work out how to make |
| # that work. |
| # |
| version_text = { |
| 'deprecated': 'Deprecated in Django %s', |
| 'versionchanged': 'Changed in Django %s', |
| 'versionadded': 'New in Django %s', |
| } |
| |
| def visit_versionmodified(self, node): |
| self.body.append( |
| self.starttag(node, 'div', CLASS=node['type']) |
| ) |
| title = "%s%s" % ( |
| self.version_text[node['type']] % node['version'], |
| len(node) and ":" or "." |
| ) |
| self.body.append('<span class="title">%s</span> ' % title) |
| |
| def depart_versionmodified(self, node): |
| self.body.append("</div>\n") |
| |
| # Give each section a unique ID -- nice for custom CSS hooks |
| def visit_section(self, node): |
| old_ids = node.get('ids', []) |
| node['ids'] = ['s-' + i for i in old_ids] |
| node['ids'].extend(old_ids) |
| SmartyPantsHTMLTranslator.visit_section(self, node) |
| node['ids'] = old_ids |
| |
| def parse_django_admin_node(env, sig, signode): |
| command = sig.split(' ')[0] |
| env._django_curr_admin_command = command |
| title = "django-admin.py %s" % sig |
| signode += addnodes.desc_name(title, title) |
| return sig |
| |
| def parse_django_adminopt_node(env, sig, signode): |
| """A copy of sphinx.directives.CmdoptionDesc.parse_signature()""" |
| from sphinx.domains.std import option_desc_re |
| count = 0 |
| firstname = '' |
| for m in option_desc_re.finditer(sig): |
| optname, args = m.groups() |
| if count: |
| signode += addnodes.desc_addname(', ', ', ') |
| signode += addnodes.desc_name(optname, optname) |
| signode += addnodes.desc_addname(args, args) |
| if not count: |
| firstname = optname |
| count += 1 |
| if not count: |
| for m in simple_option_desc_re.finditer(sig): |
| optname, args = m.groups() |
| if count: |
| signode += addnodes.desc_addname(', ', ', ') |
| signode += addnodes.desc_name(optname, optname) |
| signode += addnodes.desc_addname(args, args) |
| if not count: |
| firstname = optname |
| count += 1 |
| if not firstname: |
| raise ValueError |
| return firstname |
| |
| |
| class DjangoStandaloneHTMLBuilder(StandaloneHTMLBuilder): |
| """ |
| Subclass to add some extra things we need. |
| """ |
| |
| name = 'djangohtml' |
| |
| def finish(self): |
| super(DjangoStandaloneHTMLBuilder, self).finish() |
| if json is None: |
| self.warn("cannot create templatebuiltins.js due to missing simplejson dependency") |
| return |
| self.info(bold("writing templatebuiltins.js...")) |
| xrefs = self.env.domaindata["std"]["objects"] |
| templatebuiltins = { |
| "ttags": [n for ((t, n), (l, a)) in xrefs.items() |
| if t == "templatetag" and l == "ref/templates/builtins"], |
| "tfilters": [n for ((t, n), (l, a)) in xrefs.items() |
| if t == "templatefilter" and l == "ref/templates/builtins"], |
| } |
| outfilename = os.path.join(self.outdir, "templatebuiltins.js") |
| f = open(outfilename, 'wb') |
| f.write('var django_template_builtins = ') |
| json.dump(templatebuiltins, f) |
| f.write(';\n') |
| f.close(); |