djangodocs.py 7.3 KB

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