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