web/lib/django/contrib/admindocs/utils.py
changeset 38 77b6da96e6f1
equal deleted inserted replaced
37:8d941af65caf 38:77b6da96e6f1
       
     1 "Misc. utility functions/classes for admin documentation generator."
       
     2 
       
     3 import re
       
     4 from email.Parser import HeaderParser
       
     5 from email.Errors import HeaderParseError
       
     6 from django.utils.safestring import mark_safe
       
     7 from django.core.urlresolvers import reverse
       
     8 from django.utils.encoding import smart_str
       
     9 try:
       
    10     import docutils.core
       
    11     import docutils.nodes
       
    12     import docutils.parsers.rst.roles
       
    13 except ImportError:
       
    14     docutils_is_available = False
       
    15 else:
       
    16     docutils_is_available = True
       
    17 
       
    18 def trim_docstring(docstring):
       
    19     """
       
    20     Uniformly trims leading/trailing whitespace from docstrings.
       
    21 
       
    22     Based on http://www.python.org/peps/pep-0257.html#handling-docstring-indentation
       
    23     """
       
    24     if not docstring or not docstring.strip():
       
    25         return ''
       
    26     # Convert tabs to spaces and split into lines
       
    27     lines = docstring.expandtabs().splitlines()
       
    28     indent = min([len(line) - len(line.lstrip()) for line in lines if line.lstrip()])
       
    29     trimmed = [lines[0].lstrip()] + [line[indent:].rstrip() for line in lines[1:]]
       
    30     return "\n".join(trimmed).strip()
       
    31 
       
    32 def parse_docstring(docstring):
       
    33     """
       
    34     Parse out the parts of a docstring.  Returns (title, body, metadata).
       
    35     """
       
    36     docstring = trim_docstring(docstring)
       
    37     parts = re.split(r'\n{2,}', docstring)
       
    38     title = parts[0]
       
    39     if len(parts) == 1:
       
    40         body = ''
       
    41         metadata = {}
       
    42     else:
       
    43         parser = HeaderParser()
       
    44         try:
       
    45             metadata = parser.parsestr(parts[-1])
       
    46         except HeaderParseError:
       
    47             metadata = {}
       
    48             body = "\n\n".join(parts[1:])
       
    49         else:
       
    50             metadata = dict(metadata.items())
       
    51             if metadata:
       
    52                 body = "\n\n".join(parts[1:-1])
       
    53             else:
       
    54                 body = "\n\n".join(parts[1:])
       
    55     return title, body, metadata
       
    56 
       
    57 def parse_rst(text, default_reference_context, thing_being_parsed=None):
       
    58     """
       
    59     Convert the string from reST to an XHTML fragment.
       
    60     """
       
    61     overrides = {
       
    62         'doctitle_xform' : True,
       
    63         'inital_header_level' : 3,
       
    64         "default_reference_context" : default_reference_context,
       
    65         "link_base" : reverse('django-admindocs-docroot').rstrip('/')
       
    66     }
       
    67     if thing_being_parsed:
       
    68         thing_being_parsed = smart_str("<%s>" % thing_being_parsed)
       
    69     parts = docutils.core.publish_parts(text, source_path=thing_being_parsed,
       
    70                 destination_path=None, writer_name='html',
       
    71                 settings_overrides=overrides)
       
    72     return mark_safe(parts['fragment'])
       
    73 
       
    74 #
       
    75 # reST roles
       
    76 #
       
    77 ROLES = {
       
    78     'model'    : '%s/models/%s/',
       
    79     'view'     : '%s/views/%s/',
       
    80     'template' : '%s/templates/%s/',
       
    81     'filter'   : '%s/filters/#%s',
       
    82     'tag'      : '%s/tags/#%s',
       
    83 }
       
    84 
       
    85 def create_reference_role(rolename, urlbase):
       
    86     def _role(name, rawtext, text, lineno, inliner, options=None, content=None):
       
    87         if options is None: options = {}
       
    88         if content is None: content = []
       
    89         node = docutils.nodes.reference(rawtext, text, refuri=(urlbase % (inliner.document.settings.link_base, text.lower())), **options)
       
    90         return [node], []
       
    91     docutils.parsers.rst.roles.register_canonical_role(rolename, _role)
       
    92 
       
    93 def default_reference_role(name, rawtext, text, lineno, inliner, options=None, content=None):
       
    94     if options is None: options = {}
       
    95     if content is None: content = []
       
    96     context = inliner.document.settings.default_reference_context
       
    97     node = docutils.nodes.reference(rawtext, text, refuri=(ROLES[context] % (inliner.document.settings.link_base, text.lower())), **options)
       
    98     return [node], []
       
    99 
       
   100 if docutils_is_available:
       
   101     docutils.parsers.rst.roles.register_canonical_role('cmsreference', default_reference_role)
       
   102     docutils.parsers.rst.roles.DEFAULT_INTERPRETED_ROLE = 'cmsreference'
       
   103 
       
   104     for name, urlbase in ROLES.items():
       
   105         create_reference_role(name, urlbase)