parts/django/docs/_ext/djangodocs.py
changeset 69 c6bca38c1cbf
equal deleted inserted replaced
68:5ff1fc726848 69:c6bca38c1cbf
       
     1 """
       
     2 Sphinx plugins for Django documentation.
       
     3 """
       
     4 import os
       
     5 import re
       
     6 
       
     7 from docutils import nodes, transforms
       
     8 try:
       
     9     import json
       
    10 except ImportError:
       
    11     try:
       
    12         import simplejson as json
       
    13     except ImportError:
       
    14         try:
       
    15             from django.utils import simplejson as json
       
    16         except ImportError:
       
    17             json = None
       
    18 
       
    19 from sphinx import addnodes, roles
       
    20 from sphinx.builders.html import StandaloneHTMLBuilder
       
    21 from sphinx.writers.html import SmartyPantsHTMLTranslator
       
    22 from sphinx.util.console import bold
       
    23 from sphinx.util.compat import Directive
       
    24 
       
    25 # RE for option descriptions without a '--' prefix
       
    26 simple_option_desc_re = re.compile(
       
    27     r'([-_a-zA-Z0-9]+)(\s*.*?)(?=,\s+(?:/|-|--)|$)')
       
    28 
       
    29 def setup(app):
       
    30     app.add_crossref_type(
       
    31         directivename = "setting",
       
    32         rolename      = "setting",
       
    33         indextemplate = "pair: %s; setting",
       
    34     )
       
    35     app.add_crossref_type(
       
    36         directivename = "templatetag",
       
    37         rolename      = "ttag",
       
    38         indextemplate = "pair: %s; template tag"
       
    39     )
       
    40     app.add_crossref_type(
       
    41         directivename = "templatefilter",
       
    42         rolename      = "tfilter",
       
    43         indextemplate = "pair: %s; template filter"
       
    44     )
       
    45     app.add_crossref_type(
       
    46         directivename = "fieldlookup",
       
    47         rolename      = "lookup",
       
    48         indextemplate = "pair: %s; field lookup type",
       
    49     )
       
    50     app.add_description_unit(
       
    51         directivename = "django-admin",
       
    52         rolename      = "djadmin",
       
    53         indextemplate = "pair: %s; django-admin command",
       
    54         parse_node    = parse_django_admin_node,
       
    55     )
       
    56     app.add_description_unit(
       
    57         directivename = "django-admin-option",
       
    58         rolename      = "djadminopt",
       
    59         indextemplate = "pair: %s; django-admin command-line option",
       
    60         parse_node    = parse_django_adminopt_node,
       
    61     )
       
    62     app.add_config_value('django_next_version', '0.0', True)
       
    63     app.add_directive('versionadded', VersionDirective)
       
    64     app.add_directive('versionchanged', VersionDirective)
       
    65     app.add_transform(SuppressBlockquotes)
       
    66     app.add_builder(DjangoStandaloneHTMLBuilder)
       
    67 
       
    68 
       
    69 class VersionDirective(Directive):
       
    70     has_content = True
       
    71     required_arguments = 1
       
    72     optional_arguments = 1
       
    73     final_argument_whitespace = True
       
    74     option_spec = {}
       
    75 
       
    76     def run(self):
       
    77         env = self.state.document.settings.env
       
    78         arg0 = self.arguments[0]
       
    79         is_nextversion = env.config.django_next_version == arg0
       
    80         ret = []
       
    81         node = addnodes.versionmodified()
       
    82         ret.append(node)
       
    83         if not is_nextversion:
       
    84             if len(self.arguments) == 1:
       
    85                 linktext = 'Please, see the release notes </releases/%s>' % (arg0)
       
    86                 try:
       
    87                     xrefs = roles.XRefRole()('doc', linktext, linktext, self.lineno, self.state) # Sphinx >= 1.0
       
    88                 except AttributeError:
       
    89                     xrefs = roles.xfileref_role('doc', linktext, linktext, self.lineno, self.state) # Sphinx < 1.0
       
    90                 node.extend(xrefs[0])
       
    91             node['version'] = arg0
       
    92         else:
       
    93             node['version'] = "Development version"
       
    94         node['type'] = self.name
       
    95         if len(self.arguments) == 2:
       
    96             inodes, messages = self.state.inline_text(self.arguments[1], self.lineno+1)
       
    97             node.extend(inodes)
       
    98             if self.content:
       
    99                 self.state.nested_parse(self.content, self.content_offset, node)
       
   100             ret = ret + messages
       
   101         env.note_versionchange(node['type'], node['version'], node, self.lineno)
       
   102         return ret
       
   103 
       
   104 
       
   105 class SuppressBlockquotes(transforms.Transform):
       
   106     """
       
   107     Remove the default blockquotes that encase indented list, tables, etc.
       
   108     """
       
   109     default_priority = 300
       
   110 
       
   111     suppress_blockquote_child_nodes = (
       
   112         nodes.bullet_list,
       
   113         nodes.enumerated_list,
       
   114         nodes.definition_list,
       
   115         nodes.literal_block,
       
   116         nodes.doctest_block,
       
   117         nodes.line_block,
       
   118         nodes.table
       
   119     )
       
   120 
       
   121     def apply(self):
       
   122         for node in self.document.traverse(nodes.block_quote):
       
   123             if len(node.children) == 1 and isinstance(node.children[0], self.suppress_blockquote_child_nodes):
       
   124                 node.replace_self(node.children[0])
       
   125 
       
   126 class DjangoHTMLTranslator(SmartyPantsHTMLTranslator):
       
   127     """
       
   128     Django-specific reST to HTML tweaks.
       
   129     """
       
   130 
       
   131     # Don't use border=1, which docutils does by default.
       
   132     def visit_table(self, node):
       
   133         self.body.append(self.starttag(node, 'table', CLASS='docutils'))
       
   134 
       
   135     # <big>? Really?
       
   136     def visit_desc_parameterlist(self, node):
       
   137         self.body.append('(')
       
   138         self.first_param = 1
       
   139 
       
   140     def depart_desc_parameterlist(self, node):
       
   141         self.body.append(')')
       
   142 
       
   143     #
       
   144     # Don't apply smartypants to literal blocks
       
   145     #
       
   146     def visit_literal_block(self, node):
       
   147         self.no_smarty += 1
       
   148         SmartyPantsHTMLTranslator.visit_literal_block(self, node)
       
   149 
       
   150     def depart_literal_block(self, node):
       
   151         SmartyPantsHTMLTranslator.depart_literal_block(self, node)
       
   152         self.no_smarty -= 1
       
   153 
       
   154     #
       
   155     # Turn the "new in version" stuff (versionadded/versionchanged) into a
       
   156     # better callout -- the Sphinx default is just a little span,
       
   157     # which is a bit less obvious that I'd like.
       
   158     #
       
   159     # FIXME: these messages are all hardcoded in English. We need to change
       
   160     # that to accomodate other language docs, but I can't work out how to make
       
   161     # that work.
       
   162     #
       
   163     version_text = {
       
   164         'deprecated':       'Deprecated in Django %s',
       
   165         'versionchanged':   'Changed in Django %s',
       
   166         'versionadded':     'New in Django %s',
       
   167     }
       
   168 
       
   169     def visit_versionmodified(self, node):
       
   170         self.body.append(
       
   171             self.starttag(node, 'div', CLASS=node['type'])
       
   172         )
       
   173         title = "%s%s" % (
       
   174             self.version_text[node['type']] % node['version'],
       
   175             len(node) and ":" or "."
       
   176         )
       
   177         self.body.append('<span class="title">%s</span> ' % title)
       
   178 
       
   179     def depart_versionmodified(self, node):
       
   180         self.body.append("</div>\n")
       
   181 
       
   182     # Give each section a unique ID -- nice for custom CSS hooks
       
   183     def visit_section(self, node):
       
   184         old_ids = node.get('ids', [])
       
   185         node['ids'] = ['s-' + i for i in old_ids]
       
   186         node['ids'].extend(old_ids)
       
   187         SmartyPantsHTMLTranslator.visit_section(self, node)
       
   188         node['ids'] = old_ids
       
   189 
       
   190 def parse_django_admin_node(env, sig, signode):
       
   191     command = sig.split(' ')[0]
       
   192     env._django_curr_admin_command = command
       
   193     title = "django-admin.py %s" % sig
       
   194     signode += addnodes.desc_name(title, title)
       
   195     return sig
       
   196 
       
   197 def parse_django_adminopt_node(env, sig, signode):
       
   198     """A copy of sphinx.directives.CmdoptionDesc.parse_signature()"""
       
   199     try:
       
   200         from sphinx.domains.std import option_desc_re # Sphinx >= 1.0
       
   201     except ImportError:
       
   202         from sphinx.directives.desc import option_desc_re # Sphinx < 1.0
       
   203     count = 0
       
   204     firstname = ''
       
   205     for m in option_desc_re.finditer(sig):
       
   206         optname, args = m.groups()
       
   207         if count:
       
   208             signode += addnodes.desc_addname(', ', ', ')
       
   209         signode += addnodes.desc_name(optname, optname)
       
   210         signode += addnodes.desc_addname(args, args)
       
   211         if not count:
       
   212             firstname = optname
       
   213         count += 1
       
   214     if not count:
       
   215         for m in simple_option_desc_re.finditer(sig):
       
   216             optname, args = m.groups()
       
   217             if count:
       
   218                 signode += addnodes.desc_addname(', ', ', ')
       
   219             signode += addnodes.desc_name(optname, optname)
       
   220             signode += addnodes.desc_addname(args, args)
       
   221             if not count:
       
   222                 firstname = optname
       
   223             count += 1
       
   224     if not firstname:
       
   225         raise ValueError
       
   226     return firstname
       
   227 
       
   228 
       
   229 class DjangoStandaloneHTMLBuilder(StandaloneHTMLBuilder):
       
   230     """
       
   231     Subclass to add some extra things we need.
       
   232     """
       
   233 
       
   234     name = 'djangohtml'
       
   235 
       
   236     def finish(self):
       
   237         super(DjangoStandaloneHTMLBuilder, self).finish()
       
   238         if json is None:
       
   239             self.warn("cannot create templatebuiltins.js due to missing simplejson dependency")
       
   240             return
       
   241         self.info(bold("writing templatebuiltins.js..."))
       
   242         try:
       
   243             # Sphinx < 1.0
       
   244             xrefs = self.env.reftargets.items()
       
   245             templatebuiltins = dict([('ttags', [n for ((t,n),(l,a)) in xrefs
       
   246                                                 if t == 'ttag' and
       
   247                                                 l == 'ref/templates/builtins']),
       
   248                                      ('tfilters', [n for ((t,n),(l,a)) in xrefs
       
   249                                                    if t == 'tfilter' and
       
   250                                                    l == 'ref/templates/builtins'])])
       
   251         except AttributeError:
       
   252             # Sphinx >= 1.0
       
   253             xrefs = self.env.domaindata["std"]["objects"]
       
   254             templatebuiltins = dict([('ttags', [n for ((t,n), (l,a)) in xrefs.items()
       
   255                                                 if t == 'templatetag' and
       
   256                                                 l == 'ref/templates/builtins' ]),
       
   257                                      ('tfilters', [n for ((t,n), (l,a)) in xrefs.items()
       
   258                                                    if t == 'templatefilter' and
       
   259                                                    t == 'ref/templates/builtins'])])
       
   260         outfilename = os.path.join(self.outdir, "templatebuiltins.js")
       
   261         f = open(outfilename, 'wb')
       
   262         f.write('var django_template_builtins = ')
       
   263         json.dump(templatebuiltins, f)
       
   264         f.write(';\n')
       
   265         f.close();