utils.py 3.8 KB

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