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