Ticket #23751: djangodocs-latex-pdf-fix-gw-2015-11-14.py

File djangodocs-latex-pdf-fix-gw-2015-11-14.py, 12.2 KB (added by Graham Wideman, 8 years ago)

djangodocs.py with fixed visit_snippet_latex() and depart_snippet_latex

Line 
1"""
2Sphinx plugins for Django documentation. Version GW 2015-11-14
3"""
4import json
5import os
6import re
7
8from docutils import nodes
9from docutils.parsers.rst import directives
10from sphinx import __version__ as sphinx_ver, addnodes
11from sphinx.builders.html import StandaloneHTMLBuilder
12from sphinx.util.compat import Directive
13from sphinx.util.console import bold
14from sphinx.util.nodes import set_source_info
15from sphinx.writers.html import SmartyPantsHTMLTranslator
16
17# RE for option descriptions without a '--' prefix
18simple_option_desc_re = re.compile(
19 r'([-_a-zA-Z0-9]+)(\s*.*?)(?=,\s+(?:/|-|--)|$)')
20
21
22def setup(app):
23 app.add_crossref_type(
24 directivename="setting",
25 rolename="setting",
26 indextemplate="pair: %s; setting",
27 )
28 app.add_crossref_type(
29 directivename="templatetag",
30 rolename="ttag",
31 indextemplate="pair: %s; template tag"
32 )
33 app.add_crossref_type(
34 directivename="templatefilter",
35 rolename="tfilter",
36 indextemplate="pair: %s; template filter"
37 )
38 app.add_crossref_type(
39 directivename="fieldlookup",
40 rolename="lookup",
41 indextemplate="pair: %s; field lookup type",
42 )
43 app.add_description_unit(
44 directivename="django-admin",
45 rolename="djadmin",
46 indextemplate="pair: %s; django-admin command",
47 parse_node=parse_django_admin_node,
48 )
49 app.add_description_unit(
50 directivename="django-admin-option",
51 rolename="djadminopt",
52 indextemplate="pair: %s; django-admin command-line option",
53 parse_node=parse_django_adminopt_node,
54 )
55 app.add_config_value('django_next_version', '0.0', True)
56 app.add_directive('versionadded', VersionDirective)
57 app.add_directive('versionchanged', VersionDirective)
58 app.add_builder(DjangoStandaloneHTMLBuilder)
59
60 # register the snippet directive
61 app.add_directive('snippet', SnippetWithFilename)
62 # register a node for snippet directive so that the xml parser
63 # knows how to handle the enter/exit parsing event
64 app.add_node(snippet_with_filename,
65 html=(visit_snippet, depart_snippet_literal),
66 latex=(visit_snippet_latex, depart_snippet_latex),
67 man=(visit_snippet_literal, depart_snippet_literal),
68 text=(visit_snippet_literal, depart_snippet_literal),
69 texinfo=(visit_snippet_literal, depart_snippet_literal))
70
71
72class snippet_with_filename(nodes.literal_block):
73 """
74 Subclass the literal_block to override the visit/depart event handlers
75 """
76 pass
77
78
79def visit_snippet_literal(self, node):
80 """
81 default literal block handler
82 """
83 self.visit_literal_block(node)
84
85
86def depart_snippet_literal(self, node):
87 """
88 default literal block handler
89 """
90 self.depart_literal_block(node)
91
92
93def visit_snippet(self, node):
94 """
95 HTML document generator visit handler
96 """
97 lang = self.highlightlang
98 linenos = node.rawsource.count('\n') >= self.highlightlinenothreshold - 1
99 fname = node['filename']
100 highlight_args = node.get('highlight_args', {})
101 if 'language' in node:
102 # code-block directives
103 lang = node['language']
104 highlight_args['force'] = True
105 if 'linenos' in node:
106 linenos = node['linenos']
107
108 def warner(msg):
109 self.builder.warn(msg, (self.builder.current_docname, node.line))
110
111 highlighted = self.highlighter.highlight_block(node.rawsource, lang,
112 warn=warner,
113 linenos=linenos,
114 **highlight_args)
115 starttag = self.starttag(node, 'div', suffix='',
116 CLASS='highlight-%s' % lang)
117 self.body.append(starttag)
118 self.body.append('<div class="snippet-filename">%s</div>\n''' % (fname,))
119 self.body.append(highlighted)
120 self.body.append('</div>\n')
121 raise nodes.SkipNode
122
123
124def visit_snippet_latex(self, node):
125 """
126 Latex document generator visit handler
127 """
128 # self.verbatim = ''
129
130 #----------------------------------
131 # Moved from depart_snippet_latex -- GW
132
133 # code = self.verbatim.rstrip('\n')
134 code = node.rawsource.rstrip('\n') # GW added
135
136 lang = self.hlsettingstack[-1][0]
137 linenos = code.count('\n') >= self.hlsettingstack[-1][1] - 1
138 fname = node['filename']
139 highlight_args = node.get('highlight_args', {})
140 if 'language' in node:
141 # code-block directives
142 lang = node['language']
143 highlight_args['force'] = True
144 if 'linenos' in node:
145 linenos = node['linenos']
146
147 def warner(msg):
148 self.builder.warn(msg, (self.curfilestack[-1], node.line))
149
150 hlcode = self.highlighter.highlight_block(code, lang, warn=warner,
151 linenos=linenos,
152 **highlight_args)
153
154 self.body.append('\n{\\colorbox[rgb]{0.9,0.9,0.9}'
155 '{\\makebox[\\textwidth][l]'
156 '{\\small\\texttt{%s}}}}\n' % (fname,))
157
158 if self.table:
159 hlcode = hlcode.replace('\\begin{Verbatim}',
160 '\\begin{OriginalVerbatim}')
161 self.table.has_problematic = True
162 self.table.has_verbatim = True
163
164 hlcode = hlcode.rstrip()[:-14] # strip \end{Verbatim}
165 hlcode = hlcode.rstrip() + '\n'
166 self.body.append('\n' + hlcode + '\\end{%sVerbatim}\n' %
167 (self.table and 'Original' or ''))
168
169 self.verbatim = None # unneeded? -- GW
170
171 # End moved code -- GW
172 #----------------------------------
173
174 # Added
175 raise nodes.SkipNode # prevents rawsource from appearing in output a second time
176
177def depart_snippet_latex(self, node):
178 """
179 Latex document generator depart handler.
180 """
181 # Code moved to visit_snippet_latex.
182
183 pass
184
185
186class SnippetWithFilename(Directive):
187 """
188 The 'snippet' directive that allows to add the filename (optional)
189 of a code snippet in the document. This is modeled after CodeBlock.
190 """
191 has_content = True
192 optional_arguments = 1
193 option_spec = {'filename': directives.unchanged_required}
194
195 def run(self):
196 code = '\n'.join(self.content)
197
198 literal = snippet_with_filename(code, code)
199 if self.arguments:
200 literal['language'] = self.arguments[0]
201 literal['filename'] = self.options['filename']
202 set_source_info(self, literal)
203 return [literal]
204
205
206class VersionDirective(Directive):
207 has_content = True
208 required_arguments = 1
209 optional_arguments = 1
210 final_argument_whitespace = True
211 option_spec = {}
212
213 def run(self):
214 if len(self.arguments) > 1:
215 msg = """Only one argument accepted for directive '{directive_name}::'.
216 Comments should be provided as content,
217 not as an extra argument.""".format(directive_name=self.name)
218 raise self.error(msg)
219
220 env = self.state.document.settings.env
221 ret = []
222 node = addnodes.versionmodified()
223 ret.append(node)
224
225 if self.arguments[0] == env.config.django_next_version:
226 node['version'] = "Development version"
227 else:
228 node['version'] = self.arguments[0]
229
230 node['type'] = self.name
231 if self.content:
232 self.state.nested_parse(self.content, self.content_offset, node)
233 env.note_versionchange(node['type'], node['version'], node, self.lineno)
234 return ret
235
236
237class DjangoHTMLTranslator(SmartyPantsHTMLTranslator):
238 """
239 Django-specific reST to HTML tweaks.
240 """
241
242 # Don't use border=1, which docutils does by default.
243 def visit_table(self, node):
244 self.context.append(self.compact_p)
245 self.compact_p = True
246 self._table_row_index = 0 # Needed by Sphinx
247 self.body.append(self.starttag(node, 'table', CLASS='docutils'))
248
249 def depart_table(self, node):
250 self.compact_p = self.context.pop()
251 self.body.append('</table>\n')
252
253 def visit_desc_parameterlist(self, node):
254 self.body.append('(') # by default sphinx puts <big> around the "("
255 self.first_param = 1
256 self.optional_param_level = 0
257 self.param_separator = node.child_text_separator
258 self.required_params_left = sum([isinstance(c, addnodes.desc_parameter)
259 for c in node.children])
260
261 def depart_desc_parameterlist(self, node):
262 self.body.append(')')
263
264 if sphinx_ver < '1.0.8':
265 #
266 # Don't apply smartypants to literal blocks
267 #
268 def visit_literal_block(self, node):
269 self.no_smarty += 1
270 SmartyPantsHTMLTranslator.visit_literal_block(self, node)
271
272 def depart_literal_block(self, node):
273 SmartyPantsHTMLTranslator.depart_literal_block(self, node)
274 self.no_smarty -= 1
275
276 #
277 # Turn the "new in version" stuff (versionadded/versionchanged) into a
278 # better callout -- the Sphinx default is just a little span,
279 # which is a bit less obvious that I'd like.
280 #
281 # FIXME: these messages are all hardcoded in English. We need to change
282 # that to accommodate other language docs, but I can't work out how to make
283 # that work.
284 #
285 version_text = {
286 'versionchanged': 'Changed in Django %s',
287 'versionadded': 'New in Django %s',
288 }
289
290 def visit_versionmodified(self, node):
291 self.body.append(
292 self.starttag(node, 'div', CLASS=node['type'])
293 )
294 version_text = self.version_text.get(node['type'])
295 if version_text:
296 title = "%s%s" % (
297 version_text % node['version'],
298 ":" if len(node) else "."
299 )
300 self.body.append('<span class="title">%s</span> ' % title)
301
302 def depart_versionmodified(self, node):
303 self.body.append("</div>\n")
304
305 # Give each section a unique ID -- nice for custom CSS hooks
306 def visit_section(self, node):
307 old_ids = node.get('ids', [])
308 node['ids'] = ['s-' + i for i in old_ids]
309 node['ids'].extend(old_ids)
310 SmartyPantsHTMLTranslator.visit_section(self, node)
311 node['ids'] = old_ids
312
313
314def parse_django_admin_node(env, sig, signode):
315 command = sig.split(' ')[0]
316 env._django_curr_admin_command = command
317 title = "django-admin %s" % sig
318 signode += addnodes.desc_name(title, title)
319 return sig
320
321
322def parse_django_adminopt_node(env, sig, signode):
323 """A copy of sphinx.directives.CmdoptionDesc.parse_signature()"""
324 from sphinx.domains.std import option_desc_re
325 count = 0
326 firstname = ''
327 for m in option_desc_re.finditer(sig):
328 optname, args = m.groups()
329 if count:
330 signode += addnodes.desc_addname(', ', ', ')
331 signode += addnodes.desc_name(optname, optname)
332 signode += addnodes.desc_addname(args, args)
333 if not count:
334 firstname = optname
335 count += 1
336 if not count:
337 for m in simple_option_desc_re.finditer(sig):
338 optname, args = m.groups()
339 if count:
340 signode += addnodes.desc_addname(', ', ', ')
341 signode += addnodes.desc_name(optname, optname)
342 signode += addnodes.desc_addname(args, args)
343 if not count:
344 firstname = optname
345 count += 1
346 if not firstname:
347 raise ValueError
348 return firstname
349
350
351class DjangoStandaloneHTMLBuilder(StandaloneHTMLBuilder):
352 """
353 Subclass to add some extra things we need.
354 """
355
356 name = 'djangohtml'
357
358 def finish(self):
359 super(DjangoStandaloneHTMLBuilder, self).finish()
360 self.info(bold("writing templatebuiltins.js..."))
361 xrefs = self.env.domaindata["std"]["objects"]
362 templatebuiltins = {
363 "ttags": [n for ((t, n), (l, a)) in xrefs.items()
364 if t == "templatetag" and l == "ref/templates/builtins"],
365 "tfilters": [n for ((t, n), (l, a)) in xrefs.items()
366 if t == "templatefilter" and l == "ref/templates/builtins"],
367 }
368 outfilename = os.path.join(self.outdir, "templatebuiltins.js")
369 with open(outfilename, 'w') as fp:
370 fp.write('var django_template_builtins = ')
371 json.dump(templatebuiltins, fp)
372 fp.write(';\n')
Back to Top