PageRenderTime 55ms CodeModel.GetById 30ms RepoModel.GetById 1ms app.codeStats 0ms

/docs/_ext/djangodocs.py

https://github.com/h3/django
Python | 250 lines | 201 code | 23 blank | 26 comment | 17 complexity | 444e6b4adde469e90180c37b25bee791 MD5 | raw file
  1. """
  2. Sphinx plugins for Django documentation.
  3. """
  4. import os
  5. import re
  6. from docutils import nodes, transforms
  7. try:
  8. import json
  9. except ImportError:
  10. try:
  11. import simplejson as json
  12. except ImportError:
  13. try:
  14. from django.utils import simplejson as json
  15. except ImportError:
  16. json = None
  17. from sphinx import addnodes, roles
  18. from sphinx.builders.html import StandaloneHTMLBuilder
  19. from sphinx.writers.html import SmartyPantsHTMLTranslator
  20. from sphinx.util.console import bold
  21. from sphinx.util.compat import Directive
  22. # RE for option descriptions without a '--' prefix
  23. simple_option_desc_re = re.compile(
  24. r'([-_a-zA-Z0-9]+)(\s*.*?)(?=,\s+(?:/|-|--)|$)')
  25. def setup(app):
  26. app.add_crossref_type(
  27. directivename = "setting",
  28. rolename = "setting",
  29. indextemplate = "pair: %s; setting",
  30. )
  31. app.add_crossref_type(
  32. directivename = "templatetag",
  33. rolename = "ttag",
  34. indextemplate = "pair: %s; template tag"
  35. )
  36. app.add_crossref_type(
  37. directivename = "templatefilter",
  38. rolename = "tfilter",
  39. indextemplate = "pair: %s; template filter"
  40. )
  41. app.add_crossref_type(
  42. directivename = "fieldlookup",
  43. rolename = "lookup",
  44. indextemplate = "pair: %s; field lookup type",
  45. )
  46. app.add_description_unit(
  47. directivename = "django-admin",
  48. rolename = "djadmin",
  49. indextemplate = "pair: %s; django-admin command",
  50. parse_node = parse_django_admin_node,
  51. )
  52. app.add_description_unit(
  53. directivename = "django-admin-option",
  54. rolename = "djadminopt",
  55. indextemplate = "pair: %s; django-admin command-line option",
  56. parse_node = parse_django_adminopt_node,
  57. )
  58. app.add_config_value('django_next_version', '0.0', True)
  59. app.add_directive('versionadded', VersionDirective)
  60. app.add_directive('versionchanged', VersionDirective)
  61. app.add_transform(SuppressBlockquotes)
  62. app.add_builder(DjangoStandaloneHTMLBuilder)
  63. class VersionDirective(Directive):
  64. has_content = True
  65. required_arguments = 1
  66. optional_arguments = 1
  67. final_argument_whitespace = True
  68. option_spec = {}
  69. def run(self):
  70. env = self.state.document.settings.env
  71. arg0 = self.arguments[0]
  72. is_nextversion = env.config.django_next_version == arg0
  73. ret = []
  74. node = addnodes.versionmodified()
  75. ret.append(node)
  76. if not is_nextversion:
  77. if len(self.arguments) == 1:
  78. linktext = 'Please, see the release notes </releases/%s>' % (arg0)
  79. xrefs = roles.XRefRole()('doc', linktext, linktext, self.lineno, self.state)
  80. node.extend(xrefs[0])
  81. node['version'] = arg0
  82. else:
  83. node['version'] = "Development version"
  84. node['type'] = self.name
  85. if len(self.arguments) == 2:
  86. inodes, messages = self.state.inline_text(self.arguments[1], self.lineno+1)
  87. node.extend(inodes)
  88. if self.content:
  89. self.state.nested_parse(self.content, self.content_offset, node)
  90. ret = ret + messages
  91. env.note_versionchange(node['type'], node['version'], node, self.lineno)
  92. return ret
  93. class SuppressBlockquotes(transforms.Transform):
  94. """
  95. Remove the default blockquotes that encase indented list, tables, etc.
  96. """
  97. default_priority = 300
  98. suppress_blockquote_child_nodes = (
  99. nodes.bullet_list,
  100. nodes.enumerated_list,
  101. nodes.definition_list,
  102. nodes.literal_block,
  103. nodes.doctest_block,
  104. nodes.line_block,
  105. nodes.table
  106. )
  107. def apply(self):
  108. for node in self.document.traverse(nodes.block_quote):
  109. if len(node.children) == 1 and isinstance(node.children[0], self.suppress_blockquote_child_nodes):
  110. node.replace_self(node.children[0])
  111. class DjangoHTMLTranslator(SmartyPantsHTMLTranslator):
  112. """
  113. Django-specific reST to HTML tweaks.
  114. """
  115. # Don't use border=1, which docutils does by default.
  116. def visit_table(self, node):
  117. self._table_row_index = 0 # Needed by Sphinx
  118. self.body.append(self.starttag(node, 'table', CLASS='docutils'))
  119. # <big>? Really?
  120. def visit_desc_parameterlist(self, node):
  121. self.body.append('(')
  122. self.first_param = 1
  123. self.param_separator = node.child_text_separator
  124. def depart_desc_parameterlist(self, node):
  125. self.body.append(')')
  126. #
  127. # Don't apply smartypants to literal blocks
  128. #
  129. def visit_literal_block(self, node):
  130. self.no_smarty += 1
  131. SmartyPantsHTMLTranslator.visit_literal_block(self, node)
  132. def depart_literal_block(self, node):
  133. SmartyPantsHTMLTranslator.depart_literal_block(self, node)
  134. self.no_smarty -= 1
  135. #
  136. # Turn the "new in version" stuff (versionadded/versionchanged) into a
  137. # better callout -- the Sphinx default is just a little span,
  138. # which is a bit less obvious that I'd like.
  139. #
  140. # FIXME: these messages are all hardcoded in English. We need to change
  141. # that to accomodate other language docs, but I can't work out how to make
  142. # that work.
  143. #
  144. version_text = {
  145. 'deprecated': 'Deprecated in Django %s',
  146. 'versionchanged': 'Changed in Django %s',
  147. 'versionadded': 'New in Django %s',
  148. }
  149. def visit_versionmodified(self, node):
  150. self.body.append(
  151. self.starttag(node, 'div', CLASS=node['type'])
  152. )
  153. title = "%s%s" % (
  154. self.version_text[node['type']] % node['version'],
  155. len(node) and ":" or "."
  156. )
  157. self.body.append('<span class="title">%s</span> ' % title)
  158. def depart_versionmodified(self, node):
  159. self.body.append("</div>\n")
  160. # Give each section a unique ID -- nice for custom CSS hooks
  161. def visit_section(self, node):
  162. old_ids = node.get('ids', [])
  163. node['ids'] = ['s-' + i for i in old_ids]
  164. node['ids'].extend(old_ids)
  165. SmartyPantsHTMLTranslator.visit_section(self, node)
  166. node['ids'] = old_ids
  167. def parse_django_admin_node(env, sig, signode):
  168. command = sig.split(' ')[0]
  169. env._django_curr_admin_command = command
  170. title = "django-admin.py %s" % sig
  171. signode += addnodes.desc_name(title, title)
  172. return sig
  173. def parse_django_adminopt_node(env, sig, signode):
  174. """A copy of sphinx.directives.CmdoptionDesc.parse_signature()"""
  175. from sphinx.domains.std import option_desc_re
  176. count = 0
  177. firstname = ''
  178. for m in option_desc_re.finditer(sig):
  179. optname, args = m.groups()
  180. if count:
  181. signode += addnodes.desc_addname(', ', ', ')
  182. signode += addnodes.desc_name(optname, optname)
  183. signode += addnodes.desc_addname(args, args)
  184. if not count:
  185. firstname = optname
  186. count += 1
  187. if not count:
  188. for m in simple_option_desc_re.finditer(sig):
  189. optname, args = m.groups()
  190. if count:
  191. signode += addnodes.desc_addname(', ', ', ')
  192. signode += addnodes.desc_name(optname, optname)
  193. signode += addnodes.desc_addname(args, args)
  194. if not count:
  195. firstname = optname
  196. count += 1
  197. if not firstname:
  198. raise ValueError
  199. return firstname
  200. class DjangoStandaloneHTMLBuilder(StandaloneHTMLBuilder):
  201. """
  202. Subclass to add some extra things we need.
  203. """
  204. name = 'djangohtml'
  205. def finish(self):
  206. super(DjangoStandaloneHTMLBuilder, self).finish()
  207. if json is None:
  208. self.warn("cannot create templatebuiltins.js due to missing simplejson dependency")
  209. return
  210. self.info(bold("writing templatebuiltins.js..."))
  211. xrefs = self.env.domaindata["std"]["objects"]
  212. templatebuiltins = {
  213. "ttags": [n for ((t, n), (l, a)) in xrefs.items()
  214. if t == "templatetag" and l == "ref/templates/builtins"],
  215. "tfilters": [n for ((t, n), (l, a)) in xrefs.items()
  216. if t == "templatefilter" and l == "ref/templates/builtins"],
  217. }
  218. outfilename = os.path.join(self.outdir, "templatebuiltins.js")
  219. f = open(outfilename, 'wb')
  220. f.write('var django_template_builtins = ')
  221. json.dump(templatebuiltins, f)
  222. f.write(';\n')
  223. f.close();