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. |
---|
4 | |
---|
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 |
---|
8 | parameters. |
---|
9 | |
---|
10 | Examples: |
---|
11 | |
---|
12 | .. hidden-code-block:: python |
---|
13 | :starthidden: False |
---|
14 | |
---|
15 | a = 10 |
---|
16 | b = a + 5 |
---|
17 | |
---|
18 | .. hidden-code-block:: python |
---|
19 | :label: --- SHOW/HIDE --- |
---|
20 | |
---|
21 | x = 10 |
---|
22 | y = x + 5 |
---|
23 | |
---|
24 | Thanks to http://www.javascriptkit.com/javatutors/dom3.shtml for |
---|
25 | inspiration on the javascript. |
---|
26 | |
---|
27 | Thanks to Milad 'animal' Fatenejad for suggesting this extension |
---|
28 | in the first place. |
---|
29 | |
---|
30 | Written by Anthony 'el Scopz' Scopatz, January 2012. |
---|
31 | |
---|
32 | Released under the WTFPL (http://sam.zoy.org/wtfpl/). |
---|
33 | """ |
---|
34 | |
---|
35 | from docutils import nodes |
---|
36 | from docutils.parsers.rst import directives |
---|
37 | from sphinx.directives.code import CodeBlock |
---|
38 | |
---|
39 | HCB_COUNTER = 0 |
---|
40 | |
---|
41 | js_showhide = """\ |
---|
42 | <script type="text/javascript"> |
---|
43 | function showhide(element){ |
---|
44 | if (!document.getElementById) |
---|
45 | return |
---|
46 | |
---|
47 | if (element.style.display == "block") |
---|
48 | element.style.display = "none" |
---|
49 | else |
---|
50 | element.style.display = "block" |
---|
51 | }; |
---|
52 | </script> |
---|
53 | """ |
---|
54 | |
---|
55 | def nice_bool(arg): |
---|
56 | tvalues = ('true', 't', 'yes', 'y') |
---|
57 | fvalues = ('false', 'f', 'no', 'n') |
---|
58 | arg = directives.choice(arg, tvalues + fvalues) |
---|
59 | return arg in tvalues |
---|
60 | |
---|
61 | |
---|
62 | class hidden_code_block(nodes.General, nodes.FixedTextElement): |
---|
63 | pass |
---|
64 | |
---|
65 | |
---|
66 | class HiddenCodeBlock(CodeBlock): |
---|
67 | """Hidden code block is Hidden""" |
---|
68 | |
---|
69 | option_spec = dict(starthidden=nice_bool, |
---|
70 | label=str, |
---|
71 | **CodeBlock.option_spec) |
---|
72 | |
---|
73 | def run(self): |
---|
74 | # Body of the method is more or less copied from CodeBlock |
---|
75 | code = u'\n'.join(self.content) |
---|
76 | hcb = hidden_code_block(code, code) |
---|
77 | hcb['language'] = self.arguments[0] |
---|
78 | hcb['linenos'] = 'linenos' in self.options |
---|
79 | hcb['starthidden'] = self.options.get('starthidden', True) |
---|
80 | hcb['label'] = self.options.get('label', '+ show/hide code') |
---|
81 | hcb.line = self.lineno |
---|
82 | return [hcb] |
---|
83 | |
---|
84 | |
---|
85 | def visit_hcb_html(self, node): |
---|
86 | """Visit hidden code block""" |
---|
87 | global HCB_COUNTER |
---|
88 | HCB_COUNTER += 1 |
---|
89 | |
---|
90 | # We want to use the original highlighter so that we don't |
---|
91 | # have to reimplement it. However it raises a SkipNode |
---|
92 | # error at the end of the function call. Thus we intercept |
---|
93 | # it and raise it again later. |
---|
94 | try: |
---|
95 | self.visit_literal_block(node) |
---|
96 | except nodes.SkipNode: |
---|
97 | pass |
---|
98 | |
---|
99 | # The last element of the body should be the literal code |
---|
100 | # block that was just made. |
---|
101 | code_block = self.body[-1] |
---|
102 | |
---|
103 | fill_header = {'divname': 'hiddencodeblock{0}'.format(HCB_COUNTER), |
---|
104 | 'startdisplay': 'none' if node['starthidden'] else 'block', |
---|
105 | 'label': node.get('label'), |
---|
106 | } |
---|
107 | |
---|
108 | divheader = ("""<a href="javascript:showhide(document.getElementById('{divname}'))">""" |
---|
109 | """{label}</a><br />""" |
---|
110 | '''<div id="{divname}" style="display: {startdisplay}">''' |
---|
111 | ).format(**fill_header) |
---|
112 | |
---|
113 | code_block = js_showhide + divheader + code_block + "</div>" |
---|
114 | |
---|
115 | # reassign and exit |
---|
116 | self.body[-1] = code_block |
---|
117 | raise nodes.SkipNode |
---|
118 | |
---|
119 | |
---|
120 | def depart_hcb_html(self, node): |
---|
121 | """Depart hidden code block""" |
---|
122 | # Stub because of SkipNode in visit |
---|
123 | |
---|
124 | |
---|
125 | def setup(app): |
---|
126 | app.add_directive('hidden-code-block', HiddenCodeBlock) |
---|
127 | app.add_node(hidden_code_block, html=(visit_hcb_html, depart_hcb_html)) |
---|