1 """Simple, inelegant Sphinx extension which adds a directive for a
2 highlighted code-block that may be toggled hidden and shown in HTML.
3 This is possibly useful for teaching courses.
5 The directive, like the standard code-block directive, takes
6 a language argument and an optional linenos parameter. The
7 hidden-code-block adds starthidden and label as optional
12 .. hidden-code-block:: python
18 .. hidden-code-block:: python
19 :label: --- SHOW/HIDE ---
24 Thanks to http://www.javascriptkit.com/javatutors/dom3.shtml for
25 inspiration on the javascript.
27 Thanks to Milad 'animal' Fatenejad for suggesting this extension
30 Written by Anthony 'el Scopz' Scopatz, January 2012.
31 https://github.com/scopatz/hiddencode
33 Released under the WTFPL (http://sam.zoy.org/wtfpl/).
36 from docutils import nodes
37 from docutils.parsers.rst import directives
38 from sphinx.directives.code import CodeBlock
43 <script type="text/javascript">
44 function showhide(element){
45 if (!document.getElementById)
48 if (element.style.display == "block")
49 element.style.display = "none"
51 element.style.display = "block"
57 tvalues = ('true', 't', 'yes', 'y')
58 fvalues = ('false', 'f', 'no', 'n')
59 arg = directives.choice(arg, tvalues + fvalues)
63 class hidden_code_block(nodes.General, nodes.FixedTextElement):
67 class HiddenCodeBlock(CodeBlock):
68 """Hidden code block is Hidden"""
70 option_spec = dict(starthidden=nice_bool,
72 **CodeBlock.option_spec)
75 # Body of the method is more or less copied from CodeBlock
76 code = u'\n'.join(self.content)
77 hcb = hidden_code_block(code, code)
78 hcb['language'] = self.arguments[0]
79 hcb['linenos'] = 'linenos' in self.options
80 hcb['starthidden'] = self.options.get('starthidden', True)
81 hcb['label'] = self.options.get('label', '+ show/hide code')
82 hcb.line = self.lineno
86 def visit_hcb_html(self, node):
87 """Visit hidden code block"""
91 # We want to use the original highlighter so that we don't
92 # have to reimplement it. However it raises a SkipNode
93 # error at the end of the function call. Thus we intercept
94 # it and raise it again later.
96 self.visit_literal_block(node)
97 except nodes.SkipNode:
100 # The last element of the body should be the literal code
101 # block that was just made.
102 code_block = self.body[-1]
104 fill_header = {'divname': 'hiddencodeblock{0}'.format(HCB_COUNTER),
105 'startdisplay': 'none' if node['starthidden'] else 'block',
106 'label': node.get('label'),
109 divheader = ("""<a href="javascript:showhide(document.getElementById('{divname}'))">"""
110 """{label}</a><br />"""
111 '''<div id="{divname}" style="display: {startdisplay}">'''
112 ).format(**fill_header)
114 code_block = js_showhide + divheader + code_block + "</div>"
117 self.body[-1] = code_block
121 def depart_hcb_html(self, node):
122 """Depart hidden code block"""
123 # Stub because of SkipNode in visit
127 app.add_directive('hidden-code-block', HiddenCodeBlock)
128 app.add_node(hidden_code_block, html=(visit_hcb_html, depart_hcb_html))