munchboka-edutools 0.2.3__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 +428 -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/escape_room2.py +318 -0
- munchboka_edutools/directives/factor_tree.py +552 -0
- munchboka_edutools/directives/flashcards.py +233 -0
- munchboka_edutools/directives/ggb.py +209 -0
- munchboka_edutools/directives/ggb_icon.py +105 -0
- munchboka_edutools/directives/ggb_popup.py +308 -0
- munchboka_edutools/directives/horner.py +326 -0
- munchboka_edutools/directives/interactive_code.py +75 -0
- munchboka_edutools/directives/jeopardy.py +252 -0
- munchboka_edutools/directives/jeopardy2.py +636 -0
- munchboka_edutools/directives/multi_plot.py +2524 -0
- munchboka_edutools/directives/multi_plot2.py +252 -0
- munchboka_edutools/directives/pair_puzzle.py +191 -0
- munchboka_edutools/directives/parsons.py +109 -0
- munchboka_edutools/directives/plot.py +3758 -0
- munchboka_edutools/directives/poly_icon.py +111 -0
- munchboka_edutools/directives/polydiv.py +346 -0
- munchboka_edutools/directives/popup.py +245 -0
- munchboka_edutools/directives/quiz.py +291 -0
- munchboka_edutools/directives/quiz2.py +453 -0
- munchboka_edutools/directives/signchart.py +519 -0
- munchboka_edutools/directives/signchart2.py +1545 -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 +321 -0
- munchboka_edutools/static/css/flashcards.css +219 -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 +147 -0
- munchboka_edutools/static/css/github-light.css +155 -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 +553 -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 +377 -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/flashcards.js +199 -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 +648 -0
- munchboka_edutools/static/js/interactiveCode/interactiveCodeSetup.js +441 -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 +560 -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 +566 -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.2.3.dist-info/METADATA +109 -0
- munchboka_edutools-0.2.3.dist-info/RECORD +157 -0
- munchboka_edutools-0.2.3.dist-info/WHEEL +4 -0
- munchboka_edutools-0.2.3.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from docutils import nodes
|
|
2
|
+
from docutils.parsers.rst import Directive, directives
|
|
3
|
+
import re
|
|
4
|
+
import uuid
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class InteractiveCodeDirective(Directive):
|
|
8
|
+
has_content = True
|
|
9
|
+
required_arguments = 0 # The unique identifier
|
|
10
|
+
optional_arguments = 1
|
|
11
|
+
final_argument_whitespace = True
|
|
12
|
+
option_spec = {
|
|
13
|
+
"lang": directives.unchanged,
|
|
14
|
+
"predict": directives.flag,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
def run(self):
|
|
18
|
+
# Get the unique identifier from arguments
|
|
19
|
+
# Generate a unique identifier or use the provided one
|
|
20
|
+
if self.arguments:
|
|
21
|
+
identifier = self.arguments[0]
|
|
22
|
+
else:
|
|
23
|
+
identifier = f"code-{uuid.uuid4().hex[:8]}"
|
|
24
|
+
|
|
25
|
+
container_id = f"container-{identifier}"
|
|
26
|
+
|
|
27
|
+
# Get code content from the directive content
|
|
28
|
+
code_content = "\n".join(self.content)
|
|
29
|
+
|
|
30
|
+
# Escape code for JavaScript
|
|
31
|
+
escaped_code = code_content.replace("`", "\\`").replace("$", "\\$")
|
|
32
|
+
|
|
33
|
+
is_prediction = "predict" in self.options
|
|
34
|
+
# Choose the appropriate function based on the predict flag
|
|
35
|
+
function_name = "makePredictionInteractiveCode" if is_prediction else "makeInteractiveCode"
|
|
36
|
+
|
|
37
|
+
# Create the HTML with the template
|
|
38
|
+
html = f"""
|
|
39
|
+
<div id="{container_id}"></div>
|
|
40
|
+
<script type="text/javascript">
|
|
41
|
+
document.addEventListener("DOMContentLoaded", () => {{
|
|
42
|
+
const code =
|
|
43
|
+
`{escaped_code}`;
|
|
44
|
+
|
|
45
|
+
{function_name}(
|
|
46
|
+
"{container_id}",
|
|
47
|
+
code,
|
|
48
|
+
);
|
|
49
|
+
}});
|
|
50
|
+
</script>
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
raw_node = nodes.raw("", html, format="html")
|
|
54
|
+
return [raw_node]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def setup(app):
|
|
58
|
+
app.add_directive("interactive-code", InteractiveCodeDirective)
|
|
59
|
+
|
|
60
|
+
# Ensure assets loaded even if submodule loaded directly
|
|
61
|
+
try:
|
|
62
|
+
app.add_css_file("munchboka/css/interactive_code.css")
|
|
63
|
+
app.add_js_file("munchboka/js/interactiveCode/interactiveCodeSetup.js")
|
|
64
|
+
app.add_js_file("munchboka/js/interactiveCode/codeEditor.js")
|
|
65
|
+
app.add_js_file("munchboka/js/interactiveCode/pythonRunner.js")
|
|
66
|
+
app.add_js_file("munchboka/js/interactiveCode/workerManager.js")
|
|
67
|
+
app.add_js_file("munchboka/js/interactiveCode/turtleCode.js")
|
|
68
|
+
except Exception:
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
"version": "0.1",
|
|
73
|
+
"parallel_read_safe": True,
|
|
74
|
+
"parallel_write_safe": True,
|
|
75
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import html as _html
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import uuid
|
|
8
|
+
from typing import Any, Dict, List
|
|
9
|
+
|
|
10
|
+
from docutils import nodes
|
|
11
|
+
from docutils.parsers.rst import directives
|
|
12
|
+
from sphinx.util.docutils import SphinxDirective
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class JeopardyDirective(SphinxDirective):
|
|
16
|
+
has_content = True
|
|
17
|
+
required_arguments = 0
|
|
18
|
+
option_spec = {
|
|
19
|
+
"teams": directives.unchanged,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
def run(self):
|
|
23
|
+
self.board_id = uuid.uuid4().hex
|
|
24
|
+
container_id = f"jeopardy-{self.board_id}"
|
|
25
|
+
|
|
26
|
+
data = self._parse_board()
|
|
27
|
+
|
|
28
|
+
# Compute relative prefix to _static (if needed later)
|
|
29
|
+
source_file = self.state.document["source"]
|
|
30
|
+
source_dir = os.path.dirname(source_file)
|
|
31
|
+
app_src_dir = self.env.srcdir
|
|
32
|
+
depth = os.path.relpath(source_dir, app_src_dir).count(os.sep)
|
|
33
|
+
rel_prefix = "../" * (depth + 1)
|
|
34
|
+
|
|
35
|
+
# Prepare config both as data-config (HTML-escaped) and inline JSON script inside the container
|
|
36
|
+
cfg_str_attr = _html.escape(json.dumps(data, ensure_ascii=False), quote=True)
|
|
37
|
+
json_str = json.dumps(data, ensure_ascii=False)
|
|
38
|
+
|
|
39
|
+
# Include KaTeX like the quiz/legacy extension to ensure math renders
|
|
40
|
+
html = f"""
|
|
41
|
+
<div id="{container_id}" class=\"jeopardy-container\" lang=\"no\" data-config=\"{cfg_str_attr}\">
|
|
42
|
+
<script type=\"application/json\" class=\"jeopardy-data\">{json_str}</script>
|
|
43
|
+
</div>
|
|
44
|
+
<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex/dist/katex.min.css\">
|
|
45
|
+
<script defer src=\"https://cdn.jsdelivr.net/npm/katex/dist/katex.min.js\"></script>
|
|
46
|
+
<script defer src=\"https://cdn.jsdelivr.net/npm/katex/dist/contrib/auto-render.min.js\"></script>
|
|
47
|
+
"""
|
|
48
|
+
return [nodes.raw("", html, format="html")]
|
|
49
|
+
|
|
50
|
+
def _parse_board(self) -> Dict[str, Any]:
|
|
51
|
+
teams_opt = self.options.get("teams")
|
|
52
|
+
try:
|
|
53
|
+
teams = max(1, int(str(teams_opt).strip())) if teams_opt is not None else 2
|
|
54
|
+
except Exception:
|
|
55
|
+
teams = 2
|
|
56
|
+
|
|
57
|
+
categories: List[Dict[str, Any]] = []
|
|
58
|
+
current_cat: Dict[str, Any] | None = None
|
|
59
|
+
current_tile: Dict[str, Any] | None = None
|
|
60
|
+
current_section: str | None = None
|
|
61
|
+
values_set = set()
|
|
62
|
+
|
|
63
|
+
def flush_tile():
|
|
64
|
+
nonlocal current_tile
|
|
65
|
+
if current_cat is not None and current_tile is not None:
|
|
66
|
+
for k in ("question", "answer"):
|
|
67
|
+
if current_tile.get(k) is None:
|
|
68
|
+
current_tile[k] = ""
|
|
69
|
+
(current_cat.setdefault("tiles", [])).append(current_tile)
|
|
70
|
+
current_tile = None
|
|
71
|
+
|
|
72
|
+
for raw in self.content:
|
|
73
|
+
line = self._process_figures(raw)
|
|
74
|
+
if line is None:
|
|
75
|
+
line = raw
|
|
76
|
+
s = line.rstrip("\n")
|
|
77
|
+
|
|
78
|
+
m = re.match(r"^\s*Category\s*:\s*(.+?)\s*$", s, flags=re.IGNORECASE)
|
|
79
|
+
if m:
|
|
80
|
+
flush_tile()
|
|
81
|
+
current_cat = {"name": m.group(1).strip(), "tiles": []}
|
|
82
|
+
categories.append(current_cat)
|
|
83
|
+
current_section = None
|
|
84
|
+
continue
|
|
85
|
+
|
|
86
|
+
m = re.match(r"^\s*(\d+)\s*:\s*$", s)
|
|
87
|
+
if m and current_cat is not None:
|
|
88
|
+
flush_tile()
|
|
89
|
+
v = int(m.group(1))
|
|
90
|
+
values_set.add(v)
|
|
91
|
+
current_tile = {"value": v, "question": "", "answer": ""}
|
|
92
|
+
current_section = None
|
|
93
|
+
continue
|
|
94
|
+
|
|
95
|
+
m = re.match(r"^\s*Q\s*:\s*(.*)$", s, flags=re.IGNORECASE)
|
|
96
|
+
if m and current_tile is not None:
|
|
97
|
+
current_section = "Q"
|
|
98
|
+
current_tile["question"] = (current_tile.get("question") or "") + m.group(1)
|
|
99
|
+
continue
|
|
100
|
+
m = re.match(r"^\s*A\s*:\s*(.*)$", s, flags=re.IGNORECASE)
|
|
101
|
+
if m and current_tile is not None:
|
|
102
|
+
current_section = "A"
|
|
103
|
+
current_tile["answer"] = (current_tile.get("answer") or "") + m.group(1)
|
|
104
|
+
continue
|
|
105
|
+
|
|
106
|
+
if current_section == "Q" and current_tile is not None:
|
|
107
|
+
current_tile["question"] = (current_tile.get("question") or "") + "\n" + s
|
|
108
|
+
continue
|
|
109
|
+
if current_section == "A" and current_tile is not None:
|
|
110
|
+
current_tile["answer"] = (current_tile.get("answer") or "") + "\n" + s
|
|
111
|
+
continue
|
|
112
|
+
|
|
113
|
+
flush_tile()
|
|
114
|
+
|
|
115
|
+
values = sorted(values_set)
|
|
116
|
+
for cat in categories:
|
|
117
|
+
by_val = {t.get("value"): t for t in (cat.get("tiles") or [])}
|
|
118
|
+
cat["tiles"] = [by_val.get(v) for v in values if v in by_val]
|
|
119
|
+
|
|
120
|
+
for cat in categories:
|
|
121
|
+
for t in cat.get("tiles") or []:
|
|
122
|
+
for key in ("question", "answer"):
|
|
123
|
+
s = t.get(key) or ""
|
|
124
|
+
t[key] = self._process_code_blocks(s)
|
|
125
|
+
|
|
126
|
+
return {"teams": teams, "categories": categories, "values": values}
|
|
127
|
+
|
|
128
|
+
def _process_figures(self, text):
|
|
129
|
+
import shutil
|
|
130
|
+
import json as _json
|
|
131
|
+
|
|
132
|
+
if not hasattr(self, "_image_counter"):
|
|
133
|
+
self._image_counter = 0
|
|
134
|
+
|
|
135
|
+
def _parse_figure_options(alt_text):
|
|
136
|
+
opts: Dict[str, Any] = {}
|
|
137
|
+
s = (alt_text or "").strip()
|
|
138
|
+
|
|
139
|
+
def parse_pairs(text: str):
|
|
140
|
+
for m in re.finditer(r'(\w+)\s*=\s*(?:"([^"]*)"|\'([^\']*)\'|([^\s}]+))', text):
|
|
141
|
+
val = m.group(2) or m.group(3) or m.group(4) or ""
|
|
142
|
+
opts[m.group(1)] = val
|
|
143
|
+
|
|
144
|
+
if s.startswith("{") and s.endswith("}"):
|
|
145
|
+
inner = s[1:-1].strip()
|
|
146
|
+
ok = False
|
|
147
|
+
try:
|
|
148
|
+
js = s
|
|
149
|
+
js = re.sub(r"(\w+)\s*:", r'"\\1":', js)
|
|
150
|
+
js = re.sub(r':\s*([^",}]+)', r': "\\1"', js)
|
|
151
|
+
opts.update(_json.loads(js))
|
|
152
|
+
ok = True
|
|
153
|
+
except Exception:
|
|
154
|
+
ok = False
|
|
155
|
+
if not ok:
|
|
156
|
+
parse_pairs(inner)
|
|
157
|
+
else:
|
|
158
|
+
parse_pairs(s)
|
|
159
|
+
if not opts and s:
|
|
160
|
+
opts["alt"] = s
|
|
161
|
+
return opts
|
|
162
|
+
|
|
163
|
+
def _build_figure_html(html_img_path, options):
|
|
164
|
+
user_opts = dict(options or {})
|
|
165
|
+
user_class = user_opts.pop("class", "").strip()
|
|
166
|
+
classes = "jeopardy-image adaptive-figure" + (f" {user_class}" if user_class else "")
|
|
167
|
+
|
|
168
|
+
alt_text = user_opts.pop("alt", "Figure")
|
|
169
|
+
title = user_opts.pop("title", None)
|
|
170
|
+
width = user_opts.pop("width", None)
|
|
171
|
+
height = user_opts.pop("height", None)
|
|
172
|
+
extra_style = user_opts.pop("style", None)
|
|
173
|
+
|
|
174
|
+
def _normalize_wh(val: Any) -> str:
|
|
175
|
+
s = str(val).strip()
|
|
176
|
+
if re.fullmatch(r"\d+(?:\.\d+)?", s):
|
|
177
|
+
return f"{s}px"
|
|
178
|
+
s = re.sub(r"\s+(?=(px|%|em|rem|vh|vw)$)", "", s)
|
|
179
|
+
return s
|
|
180
|
+
|
|
181
|
+
styles: List[str] = []
|
|
182
|
+
if width is not None:
|
|
183
|
+
styles.append(f"width: {_normalize_wh(width)};")
|
|
184
|
+
if height is not None:
|
|
185
|
+
styles.append(f"height: {_normalize_wh(height)};")
|
|
186
|
+
if extra_style:
|
|
187
|
+
styles.append(str(extra_style))
|
|
188
|
+
|
|
189
|
+
attrs = [f'src="{html_img_path}"', f'class="{classes}"', f'alt="{alt_text}"']
|
|
190
|
+
if title:
|
|
191
|
+
attrs.append(f'title="{title}"')
|
|
192
|
+
if styles:
|
|
193
|
+
style_str = " ".join(styles)
|
|
194
|
+
attrs.append(f'style="{style_str}"')
|
|
195
|
+
for k, v in user_opts.items():
|
|
196
|
+
if k not in {"src", "class", "alt", "title", "width", "height", "style"}:
|
|
197
|
+
attrs.append(f'{k}="{v}"')
|
|
198
|
+
img = f"<img {' '.join(attrs)} >"
|
|
199
|
+
return f'<div class="jeopardy-image-container">{img}</div>'
|
|
200
|
+
|
|
201
|
+
def replace(m):
|
|
202
|
+
alt_or_opts = m.group(1).strip()
|
|
203
|
+
raw_src = m.group(2)
|
|
204
|
+
self._image_counter += 1
|
|
205
|
+
options = _parse_figure_options(alt_or_opts)
|
|
206
|
+
|
|
207
|
+
source_file = self.state.document["source"]
|
|
208
|
+
source_dir = os.path.dirname(source_file)
|
|
209
|
+
app_src_dir = self.env.srcdir
|
|
210
|
+
|
|
211
|
+
abs_fig_src = os.path.normpath(os.path.join(source_dir, raw_src))
|
|
212
|
+
if not os.path.exists(abs_fig_src):
|
|
213
|
+
return f'<img src="{raw_src}" class="jeopardy-image adaptive-figure" alt="Figure (missing)">'
|
|
214
|
+
|
|
215
|
+
relative_doc_path = os.path.relpath(source_dir, app_src_dir)
|
|
216
|
+
figure_dest_dir = os.path.join(app_src_dir, "_static", "figurer", relative_doc_path)
|
|
217
|
+
os.makedirs(figure_dest_dir, exist_ok=True)
|
|
218
|
+
|
|
219
|
+
rel_path_from_source = os.path.relpath(abs_fig_src, source_dir)
|
|
220
|
+
safe_path = rel_path_from_source.replace(os.sep, "_").replace("/", "_")
|
|
221
|
+
base, ext = os.path.splitext(safe_path)
|
|
222
|
+
fig_filename = f"{self.board_id}_img{self._image_counter}_{base}{ext}"
|
|
223
|
+
fig_dest_path = os.path.join(figure_dest_dir, fig_filename)
|
|
224
|
+
shutil.copy2(abs_fig_src, fig_dest_path)
|
|
225
|
+
|
|
226
|
+
depth = os.path.relpath(source_dir, app_src_dir).count(os.sep)
|
|
227
|
+
rel_prefix = "../" * (depth + 1)
|
|
228
|
+
html_img_path = f"{rel_prefix}_static/figurer/{relative_doc_path}/{fig_filename}"
|
|
229
|
+
return _build_figure_html(html_img_path, options)
|
|
230
|
+
|
|
231
|
+
return re.sub(r"!\[([^\]]*)\]\(([^)]+)\)", replace, text)
|
|
232
|
+
|
|
233
|
+
def _process_code_blocks(self, text: str) -> str:
|
|
234
|
+
def replace_newlines(match):
|
|
235
|
+
code = match.group(2).replace("\\n", "\n")
|
|
236
|
+
lang = match.group(1)
|
|
237
|
+
return f'<pre><code class="{lang}">{code}</code></pre>'
|
|
238
|
+
|
|
239
|
+
pattern = r'<pre><code class="([\w-]+)">(.*?)</code></pre>'
|
|
240
|
+
return re.sub(pattern, replace_newlines, text, flags=re.DOTALL)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def setup(app):
|
|
244
|
+
app.add_directive("jeopardy", JeopardyDirective)
|
|
245
|
+
# Ensure assets loaded even if submodule loaded directly
|
|
246
|
+
try:
|
|
247
|
+
app.add_css_file("munchboka/css/jeopardy.css")
|
|
248
|
+
app.add_js_file("munchboka/js/jeopardy.js")
|
|
249
|
+
app.add_css_file("munchboka/css/general_style.css")
|
|
250
|
+
except Exception:
|
|
251
|
+
pass
|
|
252
|
+
return {"version": "0.1", "parallel_read_safe": True, "parallel_write_safe": True}
|