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