You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

143 lines
4.1 KiB

  1. "Misc. utility functions/classes for admin documentation generator."
  2. import re
  3. from email.errors import HeaderParseError
  4. from email.parser import HeaderParser
  5. from django.core.urlresolvers import reverse
  6. from django.utils.encoding import force_bytes
  7. from django.utils.safestring import mark_safe
  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 trim leading/trailing whitespace from docstrings.
  19. Based on https://www.python.org/dev/peps/pep-0257/#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. Return (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. 'raw_enabled': False,
  62. 'file_insertion_enabled': False,
  63. }
  64. if thing_being_parsed:
  65. thing_being_parsed = force_bytes("<%s>" % thing_being_parsed)
  66. # Wrap ``text`` in some reST that sets the default role to ``cmsreference``,
  67. # then restores it.
  68. source = """
  69. .. default-role:: cmsreference
  70. %s
  71. .. default-role::
  72. """
  73. parts = docutils.core.publish_parts(source % text,
  74. source_path=thing_being_parsed, destination_path=None,
  75. writer_name='html', settings_overrides=overrides)
  76. return mark_safe(parts['fragment'])
  77. #
  78. # reST roles
  79. #
  80. ROLES = {
  81. 'model': '%s/models/%s/',
  82. 'view': '%s/views/%s/',
  83. 'template': '%s/templates/%s/',
  84. 'filter': '%s/filters/#%s',
  85. 'tag': '%s/tags/#%s',
  86. }
  87. def create_reference_role(rolename, urlbase):
  88. def _role(name, rawtext, text, lineno, inliner, options=None, content=None):
  89. if options is None:
  90. options = {}
  91. if content is None:
  92. content = []
  93. node = docutils.nodes.reference(
  94. rawtext,
  95. text,
  96. refuri=(urlbase % (
  97. inliner.document.settings.link_base,
  98. text.lower(),
  99. )),
  100. **options
  101. )
  102. return [node], []
  103. docutils.parsers.rst.roles.register_canonical_role(rolename, _role)
  104. def default_reference_role(name, rawtext, text, lineno, inliner, options=None, content=None):
  105. if options is None:
  106. options = {}
  107. if content is None:
  108. content = []
  109. context = inliner.document.settings.default_reference_context
  110. node = docutils.nodes.reference(
  111. rawtext,
  112. text,
  113. refuri=(ROLES[context] % (
  114. inliner.document.settings.link_base,
  115. text.lower(),
  116. )),
  117. **options
  118. )
  119. return [node], []
  120. if docutils_is_available:
  121. docutils.parsers.rst.roles.register_canonical_role('cmsreference', default_reference_role)
  122. for name, urlbase in ROLES.items():
  123. create_reference_role(name, urlbase)