-
Notifications
You must be signed in to change notification settings - Fork 19
/
directives.py
176 lines (152 loc) · 6 KB
/
directives.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
"""Add some sphinx-docutils directives related to lessons.
"""
# pylint: disable=E701
import os
import warnings
from docutils import nodes
from docutils.parsers.rst.directives.admonitions \
import Admonition as AdmonitionDirective
from sphinx.util.docutils import SphinxDirective
from sphinx.util.logging import getLogger
from . import __version__
LOG = getLogger(__name__)
def class_name_to_slug(name):
"""Strip Directive and turn name into slug
Example:
Hands_OnDirective --> hands-on
"""
return name.split('Directive')[0].lower().replace('_', '-')
# This includes a heading, to not then have
class _BaseCRDirective(AdmonitionDirective, SphinxDirective):
"""A directive to handle CodeRefinery styles
"""
# node_class = challenge
required_arguments = 0
optional_arguments = 1
final_argument_whitespace = True
extra_classes = [ ]
allow_empty = True
@classmethod
def get_cssname(cls):
"""Return the CSS class name and Sphinx directive name.
- Remove 'Directive' from the name of the class
- All lowercase
- '_' replaced with '-'
"""
return class_name_to_slug(cls.__name__)
@classmethod
def cssname(cls):
"""Backwards compatibility for get_cssname, do not use."""
warnings.warn(
"You should use `get_cssname` (#71, 2021-08-22) and update old code to use it. This may be removed someday.\n",
category=FutureWarning,
stacklevel=2)
return class_name_to_slug(cls.__name__)
def run(self):
"""Run the normal admonition class, but add in a new features.
title_text: some old classes had a title which was added at the
CSS level. If this is set, then this title will be added by the
directive.
"""
name = self.get_cssname()
self.node_class = nodes.admonition
# Some jekyll-common nodes have CSS-generated titles, some don't. The
# Admonition class requires a title. Add one if missing. The title is
# the first argument to the directive.
if len(self.arguments) == 0:
if hasattr(self, 'title_text'):
self.arguments = [self.title_text]
else:
self.arguments = [name.title()]
# Run the upstream directive
ret = super().run()
# Set CSS classes
ret[0].attributes['classes'].append(name)
ret[0].attributes['classes'].extend(self.extra_classes)
# Give it a reference
target_id = name+'-%d' % self.env.new_serialno(name)
targetnode = nodes.target('', '', ids=[target_id])
ret[0].target_id = target_id
ret[0].target_docname = self.env.docname
ret.insert(0, targetnode)
return ret
def assert_has_content(self):
"""Allow empty directive blocks.
This override skips the content check, if self.allow_empty is set
to True. This adds the admonition-no-content to the CSS
classes, which reduces a bit of the empty space. This is a hack
of docutils, and may need fixing later on.
"""
if not self.allow_empty:
return super().assert_has_content()
if not self.content:
#if not hasattr(self, 'extra_classes'):
# self.extra_classes = [ ]
self.extra_classes = list(self.extra_classes) + ['admonition-no-content']
return
# These are the priamirly recommend directives
class DemoDirective(_BaseCRDirective):
title_text = "Demo"
class Type_AlongDirective(_BaseCRDirective):
extra_classes = ['important']
class ExerciseDirective(_BaseCRDirective):
extra_classes = ['important']
class SolutionDirective(_BaseCRDirective):
extra_classes = ['important', 'dropdown'] #'toggle-shown' = visible by default
class HomeworkDirective(_BaseCRDirective):
extra_classes = ['important']
class Instructor_NoteDirective(_BaseCRDirective):
title_text = "Instructor note"
class PrerequisitesDirective(_BaseCRDirective):
title_text = "Prerequisites"
class DiscussionDirective(_BaseCRDirective):
extra_classes = ['important']
# These are hold-over for carpentries
class QuestionsDirective(_BaseCRDirective):
"""Used at top of lesson for questions which will be answered"""
pass
class ObjectivesDirective(_BaseCRDirective):
"""Used at top of lesson"""
pass
class KeypointsDirective(_BaseCRDirective):
"""Used at bottom of lesson"""
pass
class CalloutDirective(_BaseCRDirective): pass
ChallengeDirective = ExerciseDirective
class ChecklistDirective(_BaseCRDirective): pass
PrereqDirective = PrerequisitesDirective
class TestimonialDirective(_BaseCRDirective): pass
class OutputDirective(_BaseCRDirective):
title_text = 'Output'
# This does work, to add
# from sphinx.writers.html5 import HTML5Translator
# def visit_node(self, node):
# #import pdb ; pdb.set_trace()
# node.attributes['classes'] += [node.__class__.__name__]
# self.visit_admonition(node)
# Add our custom CSS to the headers.
def init_static_path(app):
static_path = os.path.abspath(os.path.join(os.path.dirname(__file__),
'_static'))
#print('sphinx_lesson static path:', static_path)
app.config.html_static_path.append(static_path)
def setup(app):
"Sphinx extension setup"
app.setup_extension('myst_nb')
for name, obj in globals().items():
#print(name, obj)
if (name.endswith('Directive')
and issubclass(obj, _BaseCRDirective)
and not name.startswith('_')):
#print(name, obj.get_cssname())
directive_name = class_name_to_slug(name)
app.add_directive(directive_name, obj)
# Add CSS to build
# Hint is from https://github.com/choldgraf/sphinx-copybutton/blob/master/sphinx_copybutton/__init__.py # pylint: ignore=E501
app.connect('builder-inited', init_static_path)
app.add_css_file("sphinx_lesson.css")
return {
'version': __version__,
'parallel_read_safe': True,
'parallel_write_safe': True,
}