--- /dev/null
+"""Simple, inelegant Sphinx extension which adds a directive for a
+highlighted code-block that may be toggled hidden and shown in HTML.
+This is possibly useful for teaching courses.
+
+The directive, like the standard code-block directive, takes
+a language argument and an optional linenos parameter. The
+hidden-code-block adds starthidden and label as optional
+parameters.
+
+Examples:
+
+.. hidden-code-block:: python
+ :starthidden: False
+
+ a = 10
+ b = a + 5
+
+.. hidden-code-block:: python
+ :label: --- SHOW/HIDE ---
+
+ x = 10
+ y = x + 5
+
+Thanks to http://www.javascriptkit.com/javatutors/dom3.shtml for
+inspiration on the javascript.
+
+Thanks to Milad 'animal' Fatenejad for suggesting this extension
+in the first place.
+
+Written by Anthony 'el Scopz' Scopatz, January 2012.
+https://github.com/scopatz/hiddencode
+
+Released under the WTFPL (http://sam.zoy.org/wtfpl/).
+"""
+
+from docutils import nodes
+from docutils.parsers.rst import directives
+from sphinx.directives.code import CodeBlock
+
+HCB_COUNTER = 0
+
+js_showhide = """\
+<script type="text/javascript">
+ function showhide(element){
+ if (!document.getElementById)
+ return
+
+ if (element.style.display == "block")
+ element.style.display = "none"
+ else
+ element.style.display = "block"
+ };
+</script>
+"""
+
+def nice_bool(arg):
+ tvalues = ('true', 't', 'yes', 'y')
+ fvalues = ('false', 'f', 'no', 'n')
+ arg = directives.choice(arg, tvalues + fvalues)
+ return arg in tvalues
+
+
+class hidden_code_block(nodes.General, nodes.FixedTextElement):
+ pass
+
+
+class HiddenCodeBlock(CodeBlock):
+ """Hidden code block is Hidden"""
+
+ option_spec = dict(starthidden=nice_bool,
+ label=str,
+ **CodeBlock.option_spec)
+
+ def run(self):
+ # Body of the method is more or less copied from CodeBlock
+ code = u'\n'.join(self.content)
+ hcb = hidden_code_block(code, code)
+ hcb['language'] = self.arguments[0]
+ hcb['linenos'] = 'linenos' in self.options
+ hcb['starthidden'] = self.options.get('starthidden', True)
+ hcb['label'] = self.options.get('label', '+ show/hide code')
+ hcb.line = self.lineno
+ return [hcb]
+
+
+def visit_hcb_html(self, node):
+ """Visit hidden code block"""
+ global HCB_COUNTER
+ HCB_COUNTER += 1
+
+ # We want to use the original highlighter so that we don't
+ # have to reimplement it. However it raises a SkipNode
+ # error at the end of the function call. Thus we intercept
+ # it and raise it again later.
+ try:
+ self.visit_literal_block(node)
+ except nodes.SkipNode:
+ pass
+
+ # The last element of the body should be the literal code
+ # block that was just made.
+ code_block = self.body[-1]
+
+ fill_header = {'divname': 'hiddencodeblock{0}'.format(HCB_COUNTER),
+ 'startdisplay': 'none' if node['starthidden'] else 'block',
+ 'label': node.get('label'),
+ }
+
+ divheader = ("""<a href="javascript:showhide(document.getElementById('{divname}'))">"""
+ """{label}</a><br />"""
+ '''<div id="{divname}" style="display: {startdisplay}">'''
+ ).format(**fill_header)
+
+ code_block = js_showhide + divheader + code_block + "</div>"
+
+ # reassign and exit
+ self.body[-1] = code_block
+ raise nodes.SkipNode
+
+
+def depart_hcb_html(self, node):
+ """Depart hidden code block"""
+ # Stub because of SkipNode in visit
+
+
+def setup(app):
+ app.add_directive('hidden-code-block', HiddenCodeBlock)
+ app.add_node(hidden_code_block, html=(visit_hcb_html, depart_hcb_html))