munchboka-edutools 0.1.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of munchboka-edutools might be problematic. Click here for more details.
- munchboka_edutools/__init__.py +184 -0
- munchboka_edutools/_plotmath_shim.py +126 -0
- munchboka_edutools/_version.py +2 -0
- munchboka_edutools/directives/__init__.py +1 -0
- munchboka_edutools/directives/admonitions.py +389 -0
- munchboka_edutools/directives/cas_popup.py +272 -0
- munchboka_edutools/directives/clear.py +103 -0
- munchboka_edutools/directives/dialogue.py +137 -0
- munchboka_edutools/directives/escape_room.py +296 -0
- munchboka_edutools/directives/factor_tree.py +549 -0
- munchboka_edutools/directives/ggb.py +209 -0
- munchboka_edutools/directives/ggb_icon.py +105 -0
- munchboka_edutools/directives/ggb_popup.py +165 -0
- munchboka_edutools/directives/horner.py +324 -0
- munchboka_edutools/directives/interactive_code.py +75 -0
- munchboka_edutools/directives/jeopardy.py +252 -0
- munchboka_edutools/directives/multi_plot.py +1126 -0
- munchboka_edutools/directives/pair_puzzle.py +191 -0
- munchboka_edutools/directives/parsons.py +109 -0
- munchboka_edutools/directives/plot.py +3105 -0
- munchboka_edutools/directives/poly_icon.py +111 -0
- munchboka_edutools/directives/polydiv.py +344 -0
- munchboka_edutools/directives/popup.py +245 -0
- munchboka_edutools/directives/quiz.py +291 -0
- munchboka_edutools/directives/signchart.py +516 -0
- munchboka_edutools/directives/timed_quiz.py +436 -0
- munchboka_edutools/directives/turtle.py +157 -0
- munchboka_edutools/static/css/admonitions.css +2012 -0
- munchboka_edutools/static/css/cas_popup.css +242 -0
- munchboka_edutools/static/css/code_mirror_themes/github_dark_cm.css +112 -0
- munchboka_edutools/static/css/code_mirror_themes/github_dark_default_cm.css +40 -0
- munchboka_edutools/static/css/code_mirror_themes/github_dark_high_contrast_cm.css +141 -0
- munchboka_edutools/static/css/code_mirror_themes/github_light_cm.css +120 -0
- munchboka_edutools/static/css/code_mirror_themes/github_light_default_cm.css +108 -0
- munchboka_edutools/static/css/code_mirror_themes/one_dark_cm.css +121 -0
- munchboka_edutools/static/css/dialogue.css +92 -0
- munchboka_edutools/static/css/escapeRoom/escape-room.css +223 -0
- munchboka_edutools/static/css/figures.css +274 -0
- munchboka_edutools/static/css/general_style.css +74 -0
- munchboka_edutools/static/css/github-dark-high-contrast.css +141 -0
- munchboka_edutools/static/css/github-dark.css +112 -0
- munchboka_edutools/static/css/github-light.css +120 -0
- munchboka_edutools/static/css/interactive_code/style.css +575 -0
- munchboka_edutools/static/css/interactive_code.css +582 -0
- munchboka_edutools/static/css/jeopardy.css +529 -0
- munchboka_edutools/static/css/pairPuzzle/style.css +177 -0
- munchboka_edutools/static/css/parsons/parsonsPuzzle.css +331 -0
- munchboka_edutools/static/css/popup.css +115 -0
- munchboka_edutools/static/css/quiz.css +312 -0
- munchboka_edutools/static/css/timedQuiz.css +375 -0
- munchboka_edutools/static/icons/ggb/mode_evaluate.svg +1 -0
- munchboka_edutools/static/icons/ggb/mode_extremum.svg +1 -0
- munchboka_edutools/static/icons/ggb/mode_intersect.svg +1 -0
- munchboka_edutools/static/icons/ggb/mode_nsolve.svg +1 -0
- munchboka_edutools/static/icons/ggb/mode_numeric.svg +1 -0
- munchboka_edutools/static/icons/ggb/mode_point.svg +1 -0
- munchboka_edutools/static/icons/ggb/mode_solve.svg +1 -0
- munchboka_edutools/static/icons/misc/windows-logo.svg +1 -0
- munchboka_edutools/static/icons/outline/dark_mode/academic.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/backward.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/book.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/chat_bubble.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/check.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/cmd_line.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/file.svg +1 -0
- munchboka_edutools/static/icons/outline/dark_mode/fire.svg +4 -0
- munchboka_edutools/static/icons/outline/dark_mode/key.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/magnifying.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/pencil_square.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/play.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/question.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/square_check.svg +1 -0
- munchboka_edutools/static/icons/outline/dark_mode/stop.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/summary.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/undo.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/unlock.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/academic.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/backward.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/book.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/chat_bubble.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/check.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/cmd_line.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/file.svg +1 -0
- munchboka_edutools/static/icons/outline/light_mode/fire.svg +4 -0
- munchboka_edutools/static/icons/outline/light_mode/key.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/magnifying.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/pencil_square.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/play.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/question.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/square_check.svg +1 -0
- munchboka_edutools/static/icons/outline/light_mode/stop.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/summary.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/undo.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/unlock.svg +3 -0
- munchboka_edutools/static/icons/polyicons/cubicdown.svg +3 -0
- munchboka_edutools/static/icons/polyicons/cubicup.svg +3 -0
- munchboka_edutools/static/icons/polyicons/frown.svg +3 -0
- munchboka_edutools/static/icons/polyicons/smile.svg +3 -0
- munchboka_edutools/static/icons/solid/dark_mode/academic.svg +5 -0
- munchboka_edutools/static/icons/solid/dark_mode/backward.svg +3 -0
- munchboka_edutools/static/icons/solid/dark_mode/book.svg +3 -0
- munchboka_edutools/static/icons/solid/dark_mode/brain.svg +1 -0
- munchboka_edutools/static/icons/solid/dark_mode/file.svg +1 -0
- munchboka_edutools/static/icons/solid/dark_mode/fire.svg +3 -0
- munchboka_edutools/static/icons/solid/dark_mode/key.svg +3 -0
- munchboka_edutools/static/icons/solid/dark_mode/pencil_square.svg +4 -0
- munchboka_edutools/static/icons/solid/dark_mode/play.svg +3 -0
- munchboka_edutools/static/icons/solid/dark_mode/python.svg +1 -0
- munchboka_edutools/static/icons/solid/dark_mode/scroll.svg +1 -0
- munchboka_edutools/static/icons/solid/dark_mode/stop.svg +3 -0
- munchboka_edutools/static/icons/solid/light_mode/academic.svg +5 -0
- munchboka_edutools/static/icons/solid/light_mode/backward.svg +3 -0
- munchboka_edutools/static/icons/solid/light_mode/book.svg +3 -0
- munchboka_edutools/static/icons/solid/light_mode/brain.svg +1 -0
- munchboka_edutools/static/icons/solid/light_mode/file.svg +1 -0
- munchboka_edutools/static/icons/solid/light_mode/fire.svg +3 -0
- munchboka_edutools/static/icons/solid/light_mode/key.svg +3 -0
- munchboka_edutools/static/icons/solid/light_mode/pencil_square.svg +4 -0
- munchboka_edutools/static/icons/solid/light_mode/play.svg +3 -0
- munchboka_edutools/static/icons/solid/light_mode/python.svg +1 -0
- munchboka_edutools/static/icons/solid/light_mode/scroll.svg +1 -0
- munchboka_edutools/static/icons/solid/light_mode/stop.svg +3 -0
- munchboka_edutools/static/icons/stickers/edit.svg +1 -0
- munchboka_edutools/static/icons/stickers/pencil_square.svg +3 -0
- munchboka_edutools/static/js/casThemeManager.js +99 -0
- munchboka_edutools/static/js/escapeRoom/escape-room.js +219 -0
- munchboka_edutools/static/js/geogebra-setup.js +6 -0
- munchboka_edutools/static/js/highlight-init.js +6 -0
- munchboka_edutools/static/js/interactiveCode/codeEditor.js +632 -0
- munchboka_edutools/static/js/interactiveCode/interactiveCodeSetup.js +348 -0
- munchboka_edutools/static/js/interactiveCode/pythonRunner.js +336 -0
- munchboka_edutools/static/js/interactiveCode/turtleCode.js +203 -0
- munchboka_edutools/static/js/interactiveCode/workerManager.js +353 -0
- munchboka_edutools/static/js/jeopardy.js +523 -0
- munchboka_edutools/static/js/pairPuzzle/draggableItem.js +64 -0
- munchboka_edutools/static/js/pairPuzzle/dropZone.js +119 -0
- munchboka_edutools/static/js/pairPuzzle/game.js +160 -0
- munchboka_edutools/static/js/parsons/parsonsPuzzle.js +641 -0
- munchboka_edutools/static/js/popup.js +85 -0
- munchboka_edutools/static/js/quiz.js +422 -0
- munchboka_edutools/static/js/skulpt/skulpt.js +35721 -0
- munchboka_edutools/static/js/timedQuiz/multipleChoiceQuestion.js +184 -0
- munchboka_edutools/static/js/timedQuiz/timedMultipleChoiceQuiz.js +244 -0
- munchboka_edutools/static/js/timedQuiz/utils.js +6 -0
- munchboka_edutools/static/js/utils.js +3 -0
- munchboka_edutools-0.1.13.dist-info/METADATA +108 -0
- munchboka_edutools-0.1.13.dist-info/RECORD +149 -0
- munchboka_edutools-0.1.13.dist-info/WHEEL +4 -0
- munchboka_edutools-0.1.13.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
from ._version import __version__
|
|
2
|
+
|
|
3
|
+
import importlib
|
|
4
|
+
import pkgutil
|
|
5
|
+
import os
|
|
6
|
+
from importlib.resources import files
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _copy_static(app):
|
|
10
|
+
"""Copy packaged static assets to the builder's _static directory and register defaults."""
|
|
11
|
+
try:
|
|
12
|
+
static_root = files(__name__) / "static"
|
|
13
|
+
if not static_root.exists():
|
|
14
|
+
return
|
|
15
|
+
|
|
16
|
+
out_static_root = os.path.join(app.outdir, "_static", "munchboka")
|
|
17
|
+
os.makedirs(out_static_root, exist_ok=True)
|
|
18
|
+
|
|
19
|
+
# Recursively copy packaged static files
|
|
20
|
+
for path in static_root.rglob("*"):
|
|
21
|
+
if path.is_file():
|
|
22
|
+
rel = path.relative_to(static_root)
|
|
23
|
+
dest = os.path.join(out_static_root, str(rel))
|
|
24
|
+
os.makedirs(os.path.dirname(dest), exist_ok=True)
|
|
25
|
+
with path.open("rb") as src, open(dest, "wb") as dst:
|
|
26
|
+
dst.write(src.read())
|
|
27
|
+
|
|
28
|
+
# Register a couple of common defaults if present
|
|
29
|
+
def _register(relpath: str, priority: int = 500):
|
|
30
|
+
full = os.path.join(out_static_root, relpath)
|
|
31
|
+
if os.path.exists(full):
|
|
32
|
+
if relpath.endswith(".css"):
|
|
33
|
+
app.add_css_file(f"munchboka/{relpath}", priority=priority)
|
|
34
|
+
elif relpath.endswith(".js"):
|
|
35
|
+
app.add_js_file(f"munchboka/{relpath}", priority=priority)
|
|
36
|
+
|
|
37
|
+
# Register CSS files (including CodeMirror CSS for interactive code)
|
|
38
|
+
app.add_css_file(
|
|
39
|
+
"https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.css"
|
|
40
|
+
)
|
|
41
|
+
# jQuery UI CSS for dialog/popup functionality
|
|
42
|
+
app.add_css_file("https://code.jquery.com/ui/1.13.2/themes/base/jquery-ui.css")
|
|
43
|
+
# KaTeX CSS for math rendering (required by pair-puzzle directive)
|
|
44
|
+
app.add_css_file("https://cdn.jsdelivr.net/npm/katex/dist/katex.min.css")
|
|
45
|
+
_register("css/admonitions.css")
|
|
46
|
+
_register("css/dialogue.css")
|
|
47
|
+
_register("css/figures.css")
|
|
48
|
+
_register("css/interactive_code.css")
|
|
49
|
+
_register("css/jeopardy.css")
|
|
50
|
+
_register("css/general_style.css")
|
|
51
|
+
_register("css/popup.css")
|
|
52
|
+
_register("css/quiz.css")
|
|
53
|
+
_register("css/timedQuiz.css")
|
|
54
|
+
_register("css/cas_popup.css")
|
|
55
|
+
_register("css/github-light.css")
|
|
56
|
+
_register("css/github-dark.css")
|
|
57
|
+
_register("css/github-dark-high-contrast.css")
|
|
58
|
+
_register("css/pairPuzzle/style.css")
|
|
59
|
+
_register("css/escapeRoom/escape-room.css")
|
|
60
|
+
_register("css/parsons/parsonsPuzzle.css")
|
|
61
|
+
|
|
62
|
+
# Register JS files with explicit priorities to match matematikk_r1 loading order:
|
|
63
|
+
# 1. utils.js (early - priority 450)
|
|
64
|
+
# 2. KaTeX (priority 490-495 - before pair-puzzle)
|
|
65
|
+
# 3. quiz.js, jeopardy.js (priority 500 - default)
|
|
66
|
+
# 4. interactive code scripts (priority 500 - default)
|
|
67
|
+
# 5. pair-puzzle scripts (priority 700-720)
|
|
68
|
+
# 6. jQuery, jQuery UI, and GeoGebra (priority 800-900)
|
|
69
|
+
# 7. CodeMirror (last - priority 900)
|
|
70
|
+
_register("js/utils.js", priority=450)
|
|
71
|
+
_register("js/casThemeManager.js", priority=480) # Before jQuery UI for theme handling
|
|
72
|
+
|
|
73
|
+
# Add KaTeX before pair-puzzle scripts (which depend on it)
|
|
74
|
+
app.add_js_file(
|
|
75
|
+
"https://cdn.jsdelivr.net/npm/katex/dist/katex.min.js",
|
|
76
|
+
priority=490,
|
|
77
|
+
)
|
|
78
|
+
app.add_js_file(
|
|
79
|
+
"https://cdn.jsdelivr.net/npm/katex/dist/contrib/auto-render.min.js",
|
|
80
|
+
priority=495,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
_register("js/jeopardy.js")
|
|
84
|
+
_register("js/popup.js")
|
|
85
|
+
_register("js/quiz.js")
|
|
86
|
+
_register("js/timedQuiz/utils.js")
|
|
87
|
+
_register("js/timedQuiz/multipleChoiceQuestion.js")
|
|
88
|
+
_register("js/timedQuiz/timedMultipleChoiceQuiz.js")
|
|
89
|
+
_register("js/interactiveCode/pythonRunner.js")
|
|
90
|
+
_register("js/interactiveCode/codeEditor.js")
|
|
91
|
+
_register("js/interactiveCode/interactiveCodeSetup.js")
|
|
92
|
+
_register("js/interactiveCode/workerManager.js")
|
|
93
|
+
# Skulpt must be loaded before turtleCode.js (lower priority = loaded first)
|
|
94
|
+
_register("js/skulpt/skulpt.js", priority=490)
|
|
95
|
+
_register("js/interactiveCode/turtleCode.js")
|
|
96
|
+
_register("js/pairPuzzle/draggableItem.js", priority=700)
|
|
97
|
+
_register("js/pairPuzzle/dropZone.js", priority=710)
|
|
98
|
+
_register("js/pairPuzzle/game.js", priority=720)
|
|
99
|
+
_register("js/escapeRoom/escape-room.js", priority=730)
|
|
100
|
+
_register("js/parsons/parsonsPuzzle.js", priority=740)
|
|
101
|
+
|
|
102
|
+
# Add jQuery and jQuery UI for dialog functionality
|
|
103
|
+
app.add_js_file(
|
|
104
|
+
"https://code.jquery.com/jquery-3.6.0.min.js",
|
|
105
|
+
priority=800,
|
|
106
|
+
)
|
|
107
|
+
app.add_js_file(
|
|
108
|
+
"https://code.jquery.com/ui/1.13.2/jquery-ui.min.js",
|
|
109
|
+
priority=850,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Add highlight.js for syntax highlighting in Parsons puzzles and other directives
|
|
113
|
+
app.add_css_file(
|
|
114
|
+
"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/styles/github.min.css",
|
|
115
|
+
id="highlight-theme-light",
|
|
116
|
+
)
|
|
117
|
+
app.add_css_file(
|
|
118
|
+
"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/styles/github-dark.min.css",
|
|
119
|
+
id="highlight-theme-dark",
|
|
120
|
+
)
|
|
121
|
+
app.add_js_file(
|
|
122
|
+
"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/highlight.min.js",
|
|
123
|
+
priority=855,
|
|
124
|
+
)
|
|
125
|
+
_register("js/highlight-init.js", priority=856)
|
|
126
|
+
|
|
127
|
+
# Add GeoGebra API
|
|
128
|
+
app.add_js_file(
|
|
129
|
+
"https://www.geogebra.org/apps/deployggb.js",
|
|
130
|
+
priority=860,
|
|
131
|
+
)
|
|
132
|
+
_register("js/geogebra-setup.js", priority=870)
|
|
133
|
+
|
|
134
|
+
# Add CodeMirror LAST with high priority so it loads at the end
|
|
135
|
+
app.add_js_file(
|
|
136
|
+
"https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.js",
|
|
137
|
+
priority=900,
|
|
138
|
+
)
|
|
139
|
+
app.add_js_file(
|
|
140
|
+
"https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/python/python.min.js",
|
|
141
|
+
priority=900,
|
|
142
|
+
)
|
|
143
|
+
except Exception as exc: # pragma: no cover - non-fatal
|
|
144
|
+
try:
|
|
145
|
+
from sphinx.util import logging
|
|
146
|
+
|
|
147
|
+
logging.getLogger(__name__).warning(
|
|
148
|
+
"munchboka_edutools: failed to copy static assets: %s", exc
|
|
149
|
+
)
|
|
150
|
+
except Exception:
|
|
151
|
+
pass
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def setup(app):
|
|
155
|
+
"""
|
|
156
|
+
Sphinx entry point for the extension.
|
|
157
|
+
|
|
158
|
+
- Copies packaged static assets on builder init
|
|
159
|
+
- Auto-loads all modules under munchboka_edutools.directives that expose setup(app)
|
|
160
|
+
"""
|
|
161
|
+
# Ensure packaged static assets are available for HTML builder
|
|
162
|
+
app.connect("builder-inited", _copy_static)
|
|
163
|
+
|
|
164
|
+
# Auto discover and load directive modules
|
|
165
|
+
pkg_prefix = __name__ + ".directives"
|
|
166
|
+
try:
|
|
167
|
+
pkg = importlib.import_module(pkg_prefix)
|
|
168
|
+
for modinfo in pkgutil.iter_modules(pkg.__path__, pkg_prefix + "."):
|
|
169
|
+
app.setup_extension(modinfo.name)
|
|
170
|
+
except Exception as exc: # pragma: no cover - non-fatal
|
|
171
|
+
try:
|
|
172
|
+
from sphinx.util import logging
|
|
173
|
+
|
|
174
|
+
logging.getLogger(__name__).warning(
|
|
175
|
+
"munchboka_edutools: failed to auto-load directives: %s", exc
|
|
176
|
+
)
|
|
177
|
+
except Exception:
|
|
178
|
+
pass
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
"version": __version__,
|
|
182
|
+
"parallel_read_safe": True,
|
|
183
|
+
"parallel_write_safe": True,
|
|
184
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Minimal plotmath shim for development and tests.
|
|
2
|
+
|
|
3
|
+
This is NOT a full replacement for the real plotmath package. It implements
|
|
4
|
+
just enough for the plot directive tests to run:
|
|
5
|
+
- plot(): create fig/ax and set up ticks/grid/limits.
|
|
6
|
+
- annotate(): arrow annotation on current axes.
|
|
7
|
+
- polygon(): draw edges and/or filled polygon.
|
|
8
|
+
- make_bar(): draw a simple double-headed measurement bar.
|
|
9
|
+
- COLORS: a small named color palette.
|
|
10
|
+
|
|
11
|
+
If the real plotmath is installed, the directive will use that instead.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import Iterable, Tuple, List, Optional
|
|
17
|
+
|
|
18
|
+
import matplotlib
|
|
19
|
+
|
|
20
|
+
matplotlib.use("Agg")
|
|
21
|
+
import matplotlib.pyplot as plt
|
|
22
|
+
|
|
23
|
+
COLORS = {
|
|
24
|
+
"red": "#d62728",
|
|
25
|
+
"blue": "#1f77b4",
|
|
26
|
+
"green": "#2ca02c",
|
|
27
|
+
"orange": "#ff7f0e",
|
|
28
|
+
"purple": "#9467bd",
|
|
29
|
+
"teal": "#17becf",
|
|
30
|
+
"black": "#000000",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def plot(
|
|
35
|
+
functions: List = None,
|
|
36
|
+
fn_labels: bool = False,
|
|
37
|
+
xmin: float = -6,
|
|
38
|
+
xmax: float = 6,
|
|
39
|
+
ymin: float = -6,
|
|
40
|
+
ymax: float = 6,
|
|
41
|
+
xstep: float = 1,
|
|
42
|
+
ystep: float = 1,
|
|
43
|
+
ticks: bool = True,
|
|
44
|
+
grid: bool = True,
|
|
45
|
+
lw: float = 2.5,
|
|
46
|
+
alpha: float | None = None,
|
|
47
|
+
fontsize: int = 20,
|
|
48
|
+
):
|
|
49
|
+
fig, ax = plt.subplots()
|
|
50
|
+
ax.set_xlim(xmin, xmax)
|
|
51
|
+
ax.set_ylim(ymin, ymax)
|
|
52
|
+
if ticks:
|
|
53
|
+
try:
|
|
54
|
+
ax.set_xticks([x for x in frange(xmin, xmax, xstep)])
|
|
55
|
+
ax.set_yticks([y for y in frange(ymin, ymax, ystep)])
|
|
56
|
+
except Exception:
|
|
57
|
+
pass
|
|
58
|
+
else:
|
|
59
|
+
ax.set_xticks([])
|
|
60
|
+
ax.set_yticks([])
|
|
61
|
+
if grid:
|
|
62
|
+
ax.grid(True, which="both", alpha=0.25)
|
|
63
|
+
for spine in ax.spines.values():
|
|
64
|
+
spine.set_linewidth(1.0)
|
|
65
|
+
return fig, ax
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def annotate(xy, xytext, s: str, arc: float = 0.3, fontsize: int = 14):
|
|
69
|
+
ax = plt.gca()
|
|
70
|
+
ax.annotate(
|
|
71
|
+
s,
|
|
72
|
+
xy=xy,
|
|
73
|
+
xytext=xytext,
|
|
74
|
+
arrowprops=dict(arrowstyle="->", connectionstyle=f"arc3,rad={arc}"),
|
|
75
|
+
fontsize=fontsize,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def polygon(
|
|
80
|
+
*pts: Tuple[float, float],
|
|
81
|
+
edges: bool = True,
|
|
82
|
+
color=None,
|
|
83
|
+
facecolor=None,
|
|
84
|
+
alpha=None,
|
|
85
|
+
show_vertices: bool = False,
|
|
86
|
+
):
|
|
87
|
+
ax = plt.gca()
|
|
88
|
+
xs = [p[0] for p in pts] + [pts[0][0]]
|
|
89
|
+
ys = [p[1] for p in pts] + [pts[0][1]]
|
|
90
|
+
if facecolor or (color and not edges):
|
|
91
|
+
ax.fill(xs, ys, color=(facecolor or color), alpha=alpha or 0.1)
|
|
92
|
+
if edges:
|
|
93
|
+
ax.plot(xs, ys, color=(color or COLORS.get("black", "black")))
|
|
94
|
+
if show_vertices:
|
|
95
|
+
ax.plot(
|
|
96
|
+
[p[0] for p in pts],
|
|
97
|
+
[p[1] for p in pts],
|
|
98
|
+
"o",
|
|
99
|
+
color=(color or COLORS.get("black", "black")),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def make_bar(xy: Tuple[float, float], length: float, orientation: str = "horizontal"):
|
|
104
|
+
x, y = xy
|
|
105
|
+
ax = plt.gca()
|
|
106
|
+
if orientation.lower().startswith("h"):
|
|
107
|
+
ax.annotate("", xy=(x, y), xytext=(x + length, y), arrowprops=dict(arrowstyle="|-|"))
|
|
108
|
+
else:
|
|
109
|
+
ax.annotate("", xy=(x, y), xytext=(x, y + length), arrowprops=dict(arrowstyle="|-|"))
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def frange(start: float, stop: float, step: float):
|
|
113
|
+
vals = []
|
|
114
|
+
if step == 0:
|
|
115
|
+
return vals
|
|
116
|
+
x = start
|
|
117
|
+
# ensure inclusion of stop within floating tolerance
|
|
118
|
+
if start <= stop:
|
|
119
|
+
while x <= stop + 1e-9:
|
|
120
|
+
vals.append(x)
|
|
121
|
+
x += step
|
|
122
|
+
else:
|
|
123
|
+
while x >= stop - 1e-9:
|
|
124
|
+
vals.append(x)
|
|
125
|
+
x -= abs(step)
|
|
126
|
+
return vals
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Namespace package for directive modules. Each module should expose `setup(app)`.
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom Admonition Directives
|
|
3
|
+
=============================
|
|
4
|
+
|
|
5
|
+
A collection of custom styled admonition directives for educational content.
|
|
6
|
+
|
|
7
|
+
Directives included:
|
|
8
|
+
- answer: For showing answers to problems (with dropdown option)
|
|
9
|
+
- example: For worked examples
|
|
10
|
+
- exercise: For exercise problems
|
|
11
|
+
- explore: For exploratory activities
|
|
12
|
+
- goals: For learning objectives/goals
|
|
13
|
+
- hints: For providing hints (with dropdown option)
|
|
14
|
+
- solution: For full solutions (with dropdown option, default on)
|
|
15
|
+
- summary: For chapter/section summaries
|
|
16
|
+
- theory: For theoretical content
|
|
17
|
+
|
|
18
|
+
All directives support MyST colon-fence syntax (:::).
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from docutils import nodes
|
|
22
|
+
from docutils.parsers.rst import Directive, directives
|
|
23
|
+
from sphinx.util.docutils import SphinxDirective
|
|
24
|
+
from sphinx.util import logging
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AnswerDirective(SphinxDirective):
|
|
30
|
+
"""
|
|
31
|
+
Answer directive with optional dropdown.
|
|
32
|
+
|
|
33
|
+
Usage:
|
|
34
|
+
.. answer::
|
|
35
|
+
.. answer:: Custom Title
|
|
36
|
+
.. answer::
|
|
37
|
+
:dropdown: 0
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
has_content = True
|
|
41
|
+
required_arguments = 0
|
|
42
|
+
optional_arguments = 1
|
|
43
|
+
final_argument_whitespace = True
|
|
44
|
+
|
|
45
|
+
option_spec = {
|
|
46
|
+
"dropdown": directives.unchanged,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
def run(self):
|
|
50
|
+
if len(self.arguments) > 0:
|
|
51
|
+
title = self.arguments[0]
|
|
52
|
+
else:
|
|
53
|
+
title = "Fasit"
|
|
54
|
+
|
|
55
|
+
# Create the admonition node
|
|
56
|
+
admonition_node = nodes.admonition()
|
|
57
|
+
admonition_node["classes"] = ["admonition", "answer"]
|
|
58
|
+
|
|
59
|
+
if "dropdown" in self.options:
|
|
60
|
+
dropdown_val = int(self.options["dropdown"])
|
|
61
|
+
else:
|
|
62
|
+
dropdown_val = 1
|
|
63
|
+
|
|
64
|
+
if dropdown_val == 1:
|
|
65
|
+
admonition_node["classes"].append("dropdown")
|
|
66
|
+
|
|
67
|
+
# Create the title node
|
|
68
|
+
title_node = nodes.title(text=title)
|
|
69
|
+
admonition_node += title_node
|
|
70
|
+
|
|
71
|
+
# Parse the content
|
|
72
|
+
self.state.nested_parse(self.content, self.content_offset, admonition_node)
|
|
73
|
+
|
|
74
|
+
return [admonition_node]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class ExampleDirective(SphinxDirective):
|
|
78
|
+
"""
|
|
79
|
+
Example directive for worked examples.
|
|
80
|
+
|
|
81
|
+
Usage:
|
|
82
|
+
.. example:: Example Title
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
has_content = True
|
|
86
|
+
required_arguments = 1
|
|
87
|
+
optional_arguments = 0
|
|
88
|
+
final_argument_whitespace = True
|
|
89
|
+
|
|
90
|
+
def run(self):
|
|
91
|
+
title = self.arguments[0]
|
|
92
|
+
|
|
93
|
+
# Create the admonition node
|
|
94
|
+
admonition_node = nodes.admonition()
|
|
95
|
+
admonition_node["classes"] = ["admonition", "example"]
|
|
96
|
+
|
|
97
|
+
# Create the title node
|
|
98
|
+
title_node = nodes.title()
|
|
99
|
+
parsed_title, _ = self.state.inline_text(title, self.lineno)
|
|
100
|
+
title_node += parsed_title
|
|
101
|
+
admonition_node += title_node
|
|
102
|
+
|
|
103
|
+
# Parse the content
|
|
104
|
+
self.state.nested_parse(self.content, self.content_offset, admonition_node)
|
|
105
|
+
|
|
106
|
+
return [admonition_node]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class ExerciseDirective(SphinxDirective):
|
|
110
|
+
"""
|
|
111
|
+
Exercise directive for problems.
|
|
112
|
+
|
|
113
|
+
Usage:
|
|
114
|
+
.. exercise:: Exercise Title
|
|
115
|
+
.. exercise:: Exercise Title
|
|
116
|
+
:level: 2
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
has_content = True
|
|
120
|
+
required_arguments = 1
|
|
121
|
+
optional_arguments = 0
|
|
122
|
+
final_argument_whitespace = True
|
|
123
|
+
option_spec = {
|
|
124
|
+
"level": directives.unchanged,
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
def run(self):
|
|
128
|
+
title = self.arguments[0]
|
|
129
|
+
# Create the admonition node
|
|
130
|
+
admonition_node = nodes.admonition()
|
|
131
|
+
admonition_node["classes"] = ["admonition", "exercise"]
|
|
132
|
+
|
|
133
|
+
# Create the title node
|
|
134
|
+
title_node = nodes.title()
|
|
135
|
+
parsed_title, _ = self.state.inline_text(title, self.lineno)
|
|
136
|
+
title_node += parsed_title
|
|
137
|
+
admonition_node += title_node
|
|
138
|
+
|
|
139
|
+
# Parse the content
|
|
140
|
+
self.state.nested_parse(self.content, self.content_offset, admonition_node)
|
|
141
|
+
|
|
142
|
+
return [admonition_node]
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class ExploreDirective(SphinxDirective):
|
|
146
|
+
"""
|
|
147
|
+
Explore directive for exploratory activities.
|
|
148
|
+
|
|
149
|
+
Usage:
|
|
150
|
+
.. explore:: Activity Title
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
has_content = True
|
|
154
|
+
required_arguments = 1
|
|
155
|
+
optional_arguments = 0
|
|
156
|
+
final_argument_whitespace = True
|
|
157
|
+
|
|
158
|
+
def run(self):
|
|
159
|
+
title = self.arguments[0]
|
|
160
|
+
|
|
161
|
+
# Create the admonition node
|
|
162
|
+
admonition_node = nodes.admonition()
|
|
163
|
+
admonition_node["classes"] = ["admonition", "explore"]
|
|
164
|
+
|
|
165
|
+
# Create the title node
|
|
166
|
+
title_node = nodes.title()
|
|
167
|
+
parsed_title, _ = self.state.inline_text(title, self.lineno)
|
|
168
|
+
title_node += parsed_title
|
|
169
|
+
admonition_node += title_node
|
|
170
|
+
|
|
171
|
+
# Parse the content
|
|
172
|
+
self.state.nested_parse(self.content, self.content_offset, admonition_node)
|
|
173
|
+
|
|
174
|
+
return [admonition_node]
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class GoalsDirective(SphinxDirective):
|
|
178
|
+
"""
|
|
179
|
+
Goals directive for learning objectives.
|
|
180
|
+
|
|
181
|
+
Usage:
|
|
182
|
+
.. goals:: Learning Goals
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
has_content = True
|
|
186
|
+
required_arguments = 1
|
|
187
|
+
optional_arguments = 0
|
|
188
|
+
final_argument_whitespace = True
|
|
189
|
+
|
|
190
|
+
def run(self):
|
|
191
|
+
title = self.arguments[0]
|
|
192
|
+
|
|
193
|
+
# Create the admonition node
|
|
194
|
+
admonition_node = nodes.admonition()
|
|
195
|
+
admonition_node["classes"] = ["admonition", "tip"]
|
|
196
|
+
|
|
197
|
+
title_node = nodes.title()
|
|
198
|
+
parsed_title, _ = self.state.inline_text(title, self.lineno)
|
|
199
|
+
title_node += parsed_title
|
|
200
|
+
admonition_node += title_node
|
|
201
|
+
|
|
202
|
+
# Parse the content
|
|
203
|
+
self.state.nested_parse(self.content, self.content_offset, admonition_node)
|
|
204
|
+
|
|
205
|
+
return [admonition_node]
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class HintsDirective(SphinxDirective):
|
|
209
|
+
"""
|
|
210
|
+
Hints directive with optional dropdown.
|
|
211
|
+
|
|
212
|
+
Usage:
|
|
213
|
+
.. hints::
|
|
214
|
+
.. hints:: Custom Title
|
|
215
|
+
.. hints::
|
|
216
|
+
:dropdown: 0
|
|
217
|
+
"""
|
|
218
|
+
|
|
219
|
+
has_content = True
|
|
220
|
+
required_arguments = 0
|
|
221
|
+
optional_arguments = 1
|
|
222
|
+
final_argument_whitespace = True
|
|
223
|
+
|
|
224
|
+
option_spec = {
|
|
225
|
+
"dropdown": directives.unchanged,
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
def run(self):
|
|
229
|
+
if len(self.arguments) > 0:
|
|
230
|
+
title = self.arguments[0]
|
|
231
|
+
else:
|
|
232
|
+
title = "Hint"
|
|
233
|
+
|
|
234
|
+
# Create the admonition node
|
|
235
|
+
admonition_node = nodes.admonition()
|
|
236
|
+
admonition_node["classes"] = ["admonition", "hints"]
|
|
237
|
+
|
|
238
|
+
if "dropdown" in self.options:
|
|
239
|
+
dropdown_val = int(self.options["dropdown"])
|
|
240
|
+
else:
|
|
241
|
+
dropdown_val = 1
|
|
242
|
+
|
|
243
|
+
if dropdown_val == 1:
|
|
244
|
+
admonition_node["classes"].append("dropdown")
|
|
245
|
+
|
|
246
|
+
# Create the title node
|
|
247
|
+
title_node = nodes.title(text=title)
|
|
248
|
+
admonition_node += title_node
|
|
249
|
+
|
|
250
|
+
# Parse the content
|
|
251
|
+
self.state.nested_parse(self.content, self.content_offset, admonition_node)
|
|
252
|
+
|
|
253
|
+
return [admonition_node]
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
class SolutionDirective(SphinxDirective):
|
|
257
|
+
"""
|
|
258
|
+
Solution directive with dropdown (default on).
|
|
259
|
+
|
|
260
|
+
Usage:
|
|
261
|
+
.. solution::
|
|
262
|
+
.. solution:: Custom Title
|
|
263
|
+
.. solution::
|
|
264
|
+
:dropdown: 0
|
|
265
|
+
"""
|
|
266
|
+
|
|
267
|
+
has_content = True
|
|
268
|
+
required_arguments = 0
|
|
269
|
+
optional_arguments = 1
|
|
270
|
+
final_argument_whitespace = True
|
|
271
|
+
|
|
272
|
+
option_spec = {
|
|
273
|
+
"dropdown": directives.unchanged,
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
def run(self):
|
|
277
|
+
if len(self.arguments) > 0:
|
|
278
|
+
title = self.arguments[0]
|
|
279
|
+
else:
|
|
280
|
+
title = "Løsning"
|
|
281
|
+
|
|
282
|
+
# Create the admonition node
|
|
283
|
+
admonition_node = nodes.admonition()
|
|
284
|
+
admonition_node["classes"] = ["admonition", "solution"]
|
|
285
|
+
|
|
286
|
+
if self.options.get("dropdown"):
|
|
287
|
+
dropdown = self.options.get("dropdown")
|
|
288
|
+
if dropdown == "1":
|
|
289
|
+
admonition_node["classes"].append("dropdown")
|
|
290
|
+
elif dropdown == "0":
|
|
291
|
+
pass
|
|
292
|
+
else:
|
|
293
|
+
admonition_node["classes"].append("dropdown")
|
|
294
|
+
|
|
295
|
+
# Create the title node
|
|
296
|
+
title_node = nodes.title(text=title)
|
|
297
|
+
admonition_node += title_node
|
|
298
|
+
|
|
299
|
+
# Parse the content
|
|
300
|
+
self.state.nested_parse(self.content, self.content_offset, admonition_node)
|
|
301
|
+
|
|
302
|
+
return [admonition_node]
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
class SummaryDirective(SphinxDirective):
|
|
306
|
+
"""
|
|
307
|
+
Summary directive for chapter/section summaries.
|
|
308
|
+
|
|
309
|
+
Usage:
|
|
310
|
+
.. summary:: Summary Title
|
|
311
|
+
"""
|
|
312
|
+
|
|
313
|
+
has_content = True
|
|
314
|
+
required_arguments = 1
|
|
315
|
+
optional_arguments = 0
|
|
316
|
+
final_argument_whitespace = True
|
|
317
|
+
|
|
318
|
+
def run(self):
|
|
319
|
+
title = self.arguments[0]
|
|
320
|
+
|
|
321
|
+
# Create the admonition node
|
|
322
|
+
admonition_node = nodes.admonition()
|
|
323
|
+
admonition_node["classes"] = ["admonition", "summary"]
|
|
324
|
+
|
|
325
|
+
title_node = nodes.title()
|
|
326
|
+
parsed_title, _ = self.state.inline_text(title, self.lineno)
|
|
327
|
+
title_node += parsed_title
|
|
328
|
+
admonition_node += title_node
|
|
329
|
+
|
|
330
|
+
# Parse the content
|
|
331
|
+
self.state.nested_parse(self.content, self.content_offset, admonition_node)
|
|
332
|
+
|
|
333
|
+
return [admonition_node]
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
class TheoryDirective(SphinxDirective):
|
|
337
|
+
"""
|
|
338
|
+
Theory directive for theoretical content.
|
|
339
|
+
|
|
340
|
+
Usage:
|
|
341
|
+
.. theory:: Theory Title
|
|
342
|
+
"""
|
|
343
|
+
|
|
344
|
+
has_content = True
|
|
345
|
+
required_arguments = 1
|
|
346
|
+
optional_arguments = 0
|
|
347
|
+
final_argument_whitespace = True
|
|
348
|
+
|
|
349
|
+
def run(self):
|
|
350
|
+
title = self.arguments[0]
|
|
351
|
+
|
|
352
|
+
# Create the admonition node
|
|
353
|
+
admonition_node = nodes.admonition()
|
|
354
|
+
admonition_node["classes"] = ["admonition", "theory"]
|
|
355
|
+
|
|
356
|
+
# Create the title node
|
|
357
|
+
title_node = nodes.title()
|
|
358
|
+
parsed_title, _ = self.state.inline_text(title, self.lineno)
|
|
359
|
+
title_node += parsed_title
|
|
360
|
+
admonition_node += title_node
|
|
361
|
+
|
|
362
|
+
# Parse the content
|
|
363
|
+
self.state.nested_parse(self.content, self.content_offset, admonition_node)
|
|
364
|
+
|
|
365
|
+
return [admonition_node]
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def setup(app):
|
|
369
|
+
"""
|
|
370
|
+
Setup all custom admonition directives.
|
|
371
|
+
|
|
372
|
+
Note: CSS files are registered in __init__.py with the munchboka/ prefix.
|
|
373
|
+
The admonitions.css file contains all styling for these directives.
|
|
374
|
+
"""
|
|
375
|
+
app.add_directive("answer", AnswerDirective)
|
|
376
|
+
app.add_directive("example", ExampleDirective)
|
|
377
|
+
app.add_directive("exercise", ExerciseDirective)
|
|
378
|
+
app.add_directive("explore", ExploreDirective)
|
|
379
|
+
app.add_directive("goals", GoalsDirective)
|
|
380
|
+
app.add_directive("hints", HintsDirective)
|
|
381
|
+
app.add_directive("solution", SolutionDirective)
|
|
382
|
+
app.add_directive("summary", SummaryDirective)
|
|
383
|
+
app.add_directive("theory", TheoryDirective)
|
|
384
|
+
|
|
385
|
+
return {
|
|
386
|
+
"version": "0.1",
|
|
387
|
+
"parallel_read_safe": True,
|
|
388
|
+
"parallel_write_safe": True,
|
|
389
|
+
}
|