Skip to content
This repository was archived by the owner on Jun 7, 2023. It is now read-only.

Commit 135358e

Browse files
committed
Merge branch 'xmlconvert'
2 parents bb8d519 + ce1236e commit 135358e

File tree

29 files changed

+585
-89
lines changed

29 files changed

+585
-89
lines changed

requirements-dev.in

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ pyvirtualdisplay
99
pytest
1010
pylint
1111
readme-renderer>24
12-
myst_parser
12+
myst_parser
13+
json2xml

requirements-dev.txt

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,17 @@ babel==2.9.1
2222
# via sphinx
2323
bleach==4.1.0
2424
# via readme-renderer
25-
certifi==2021.10.8
25+
certifi==2021.5.30
2626
# via
27+
# json2xml
2728
# requests
2829
# urllib3
2930
cffi==1.15.0
3031
# via cryptography
31-
charset-normalizer==2.0.12
32-
# via requests
32+
charset-normalizer==2.0.6
33+
# via
34+
# json2xml
35+
# requests
3336
click==8.0.4
3437
# via
3538
# -r requirements.in
@@ -44,6 +47,8 @@ cryptography==36.0.2
4447
# via
4548
# pyopenssl
4649
# urllib3
50+
defusedxml==0.7.1
51+
# via json2xml
4752
dill==0.3.4
4853
# via pylint
4954
docutils==0.17.1
@@ -54,8 +59,9 @@ docutils==0.17.1
5459
# sphinx
5560
h11==0.13.0
5661
# via wsproto
57-
idna==3.3
62+
idna==3.2
5863
# via
64+
# json2xml
5965
# requests
6066
# trio
6167
# urllib3
@@ -75,6 +81,8 @@ jinja2==3.0.3
7581
# -r requirements.in
7682
# myst-parser
7783
# sphinx
84+
json2xml==3.16.0
85+
# via -r requirements-dev.in
7886
keyring==23.5.0
7987
# via twine
8088
lazy-object-proxy==1.7.1
@@ -145,8 +153,9 @@ readme-renderer==34.0
145153
# via
146154
# -r requirements-dev.in
147155
# twine
148-
requests==2.27.1
156+
requests==2.26.0
149157
# via
158+
# json2xml
150159
# requests-toolbelt
151160
# sphinx
152161
# twine
@@ -208,8 +217,9 @@ typing-extensions==4.1.1
208217
# astroid
209218
# myst-parser
210219
# pylint
211-
urllib3[secure,socks]==1.26.9
220+
urllib3[secure,socks]==1.26.7
212221
# via
222+
# json2xml
213223
# requests
214224
# selenium
215225
# twine
@@ -223,6 +233,8 @@ wrapt==1.14.0
223233
# via astroid
224234
wsproto==1.1.0
225235
# via trio-websocket
236+
xmltodict==0.12.0
237+
# via json2xml
226238
zipp==3.7.0
227239
# via importlib-metadata
228240

runestone/__main__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,8 +324,9 @@ def rs2ptx(course, sourcedir, outdir):
324324
del os.environ["DBURL"]
325325
try:
326326
import pavement
327-
except:
327+
except Exception as e:
328328
click.echo("Could not read pavement.py file, aborting")
329+
click.echo(f"Details: {e}")
329330
sys.exit(1)
330331

331332
if not course:

runestone/activecode/activecode.py

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def setup(app):
4747
app.add_role("textfield", textfield_role)
4848
app.add_config_value(
4949
"activecode_div_class",
50-
"runestone explainer ac_section alert alert-warning",
50+
"runestone explainer ac_section",
5151
"html",
5252
)
5353
app.add_config_value("activecode_hide_load_history", False, "html")
@@ -60,18 +60,30 @@ def setup(app):
6060
app.connect("env-purge-doc", purge_activecodes)
6161

6262

63-
XML_START = """
64-
<listing xml:id="{divid}">
65-
<caption>{caption}</caption>
66-
<program xml:id="{divid}_editor" interactive='yes' language="{language}">
63+
XML_EX_START = """
64+
<exercise xml:id="{divid}">
65+
<statement>
66+
"""
67+
68+
XML_EX_END = """
69+
</statement>
70+
<program xml:id="{divid}_editor" interactive='activecode' language="{language}">
6771
<input>
68-
{initialcode}
72+
{initialcode}
6973
</input>
7074
</program>
71-
</listing>
75+
</exercise>
76+
"""
7277

78+
XML_LISTING_START = """
79+
<program xml:id="{divid}" interactive='activecode' language="{language}">
80+
<input>
81+
{initialcode}
82+
</input>
83+
</program>
7384
"""
7485

86+
7587
TEMPLATE_START = """
7688
<div class="%(divclass)s %(optclass)s">
7789
<div data-component="activecode" id=%(divid)s data-question_label="%(question_label)s">
@@ -97,12 +109,21 @@ class ActivecodeNode(nodes.General, nodes.Element, RunestoneIdNode):
97109

98110

99111
def visit_ac_xml(self, node):
100-
res = XML_START.format(**node["runestone_options"])
101-
self.output.append(res)
112+
if node["runestone_options"]["has_problem_statement"]:
113+
res = XML_EX_START.format(**node["runestone_options"])
114+
self.output.append(res)
115+
else:
116+
node["runestone_options"]["initialcode"] = node["runestone_options"]["initialcode"].replace(
117+
"<", "&lt;").replace(">", "&gt;")
102118

103119

104120
def depart_ac_xml(self, node):
105-
pass
121+
if node["runestone_options"]["has_problem_statement"]:
122+
res = XML_EX_END.format(**node["runestone_options"])
123+
self.output.append(res)
124+
else:
125+
res = XML_LISTING_START.format(**node["runestone_options"])
126+
self.output.append(res)
106127

107128
# self for these functions is an instance of the writer class. For example
108129
# in html, self is sphinx.writers.html.SmartyPantsHTMLTranslator
@@ -252,7 +273,13 @@ def run(self):
252273
else:
253274
source = "\n"
254275

255-
self.explain_text = explain_text or ["Not an Exercise"]
276+
if explain_text:
277+
self.options["has_problem_statement"] = True
278+
self.explain_text = explain_text
279+
else:
280+
self.options["has_problem_statement"] = False
281+
self.explain_text = ["Not an Exercise"]
282+
256283
addQuestionToDB(self)
257284

258285
self.options["initialcode"] = source

runestone/activecode/css/activecode.css

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,12 @@
6161
position: initial;
6262
}
6363

64+
.unittest-results {
65+
margin-left: 20px;
66+
}
67+
6468
.ac_output {
69+
margin-top: 10px;
6570
display: none;
6671
background-color: inherit;
6772
}
@@ -79,7 +84,7 @@
7984
}
8085

8186
.ac_question {
82-
background-color: rgb(224, 236, 217);
87+
background-color: var(--questionBgColor);
8388
padding-left: 10px;
8489
padding-top: 10px;
8590
margin: 5px;
@@ -114,6 +119,11 @@
114119
padding: 3px;
115120
}
116121

122+
.CodeMirror {
123+
border: solid 2px;
124+
margin-bottom: 10px;
125+
}
126+
117127
.ac_sql_result {
118128
background-color: lightgrey;
119129
padding: 10px;

runestone/activecode/js/activecode.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,6 @@ export class ActiveCode extends RunestoneBase {
151151
this.outerDiv = document.createElement("div");
152152
var linkdiv = document.createElement("div");
153153
linkdiv.id = this.divid.replace(/_/g, "-").toLowerCase(); // :ref: changes _ to - so add this as a target
154-
$(this.outerDiv).addClass("ac_section alert alert-warning");
155154
var codeDiv = document.createElement("div");
156155
$(codeDiv).addClass("ac_code_div col-md-12");
157156
this.codeDiv = codeDiv;

runestone/clickableArea/clickable.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,16 @@
2424
maybeAddToAssignment,
2525
)
2626
from runestone.common.runestonedirective import RunestoneIdDirective, RunestoneIdNode
27+
from runestone.common.xmlcommon import write_substitute, substitute_visitor, substitute_departure
2728

2829

2930
def setup(app):
3031
app.add_directive("clickablearea", ClickableArea)
3132

32-
app.add_node(ClickableAreaNode, html=(visit_ca_html, depart_ca_html))
33+
app.add_node(ClickableAreaNode, html=(visit_ca_html, depart_ca_html),
34+
xml=(visit_ca_xml, depart_ca_xml))
3335

34-
app.add_config_value("clickable_div_class", "runestone alert alert-warning", "html")
36+
app.add_config_value("clickable_div_class", "", "html")
3537

3638

3739
TEMPLATE = """
@@ -44,6 +46,11 @@ def setup(app):
4446
</div>
4547
"""
4648

49+
XML_START = """
50+
<exercise xml:id="{divid}">
51+
<statement><p>{question}</p></statement>
52+
"""
53+
4754

4855
class ClickableAreaNode(nodes.General, nodes.Element, RunestoneIdNode):
4956
pass
@@ -95,6 +102,29 @@ def depart_ca_html(self, node):
95102
self.body.remove(node["delimiter"])
96103

97104

105+
def visit_ca_xml(self, node):
106+
res = XML_START.format(**node["runestone_options"])
107+
if node["runestone_options"].get("feedback", None):
108+
res += "<feedback><p>{feedback}</p></feedback>\n".format(
109+
**node["runestone_options"])
110+
res += "<areas>\n"
111+
if "iscode" in node["runestone_options"]:
112+
# The case where iscode is not in options makes no sense and probably does not exist in
113+
# any runestone books
114+
for row in node["runestone_options"]["raw_source"]:
115+
row = row.replace("\n", "")
116+
row = row.replace(":click-correct:", "<area correct='yes'>")
117+
row = row.replace(":click-incorrect:", "<area>")
118+
row = row.replace(":endclick", "</area>")
119+
row = "<cline>" + row + "</cline>\n"
120+
res += row
121+
self.output.append(res)
122+
123+
124+
def depart_ca_xml(self, node):
125+
self.output.append("</areas></exercise>")
126+
127+
98128
class ClickableArea(RunestoneIdDirective):
99129
"""
100130
.. clickablearea:: identifier
@@ -157,6 +187,7 @@ def run(self):
157187
source = source.replace(":endclick:", "</span>")
158188
source = "<pre>" + source + "</pre>"
159189
self.options["clickcode"] = source
190+
self.options["raw_source"] = self.content
160191
else:
161192
self.options["clickcode"] = ""
162193
clickNode = ClickableAreaNode()

runestone/codelens/visualizer.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@
2929
def setup(app):
3030
app.add_directive("codelens", Codelens)
3131

32-
app.add_config_value("codelens_div_class", "alert alert-warning cd_section", "html")
32+
app.add_config_value("codelens_div_class", "cd_section", "html")
3333
app.add_config_value("trace_url", "http://tracer.runestone.academy:5000", "html")
34-
app.add_node(CodeLensNode, html=(visit_codelens_html, depart_codelens_html))
34+
app.add_node(CodeLensNode, html=(visit_codelens_html, depart_codelens_html),
35+
xml=(visit_codelens_xml, depart_codelens_xml))
3536

3637

3738
# data-tracefile="pytutor-embed-demo/java.json"
@@ -60,11 +61,30 @@ def setup(app):
6061
</div>
6162
"""
6263

64+
PTX_TEMPLATE = """
65+
<program xml:id="{divid} interactive="codelens" language="{language}>
66+
<input>
67+
{source}
68+
</input>
69+
</program>
70+
"""
71+
6372

6473
class CodeLensNode(nodes.General, nodes.Element, RunestoneIdNode):
6574
pass
6675

6776

77+
def visit_codelens_xml(self, node):
78+
html = VIS
79+
if "caption" not in node["runestone_options"]:
80+
node["runestone_options"]["caption"] = node["runestone_options"]["question_label"]
81+
if "tracedata" in node["runestone_options"]:
82+
node["runestone_options"]["tracedata"] = node["runestone_options"]["tracedata"].replace(
83+
"<", "&lt;").replace(">", "&gt;")
84+
85+
res = PTX_TEMPLATE.format(**node["runestone_options"])
86+
87+
6888
def visit_codelens_html(self, node):
6989
html = VIS
7090
if "caption" not in node["runestone_options"]:
@@ -85,6 +105,10 @@ def depart_codelens_html(self, node):
85105
pass
86106

87107

108+
def depart_codelens_xml(self, node):
109+
pass
110+
111+
88112
# Some documentation to help the author.
89113
# Here's and example of a single stack frame.
90114
# you might ask a qestion about the value of a global variable
@@ -183,7 +207,7 @@ def js_var_finalizer(input_code, output_trace):
183207
source = "\n".join(self.content)
184208
else:
185209
source = "\n"
186-
210+
self.options["source"] = source.replace("<", "&lt;")
187211
CUMULATIVE_MODE = False
188212
self.JS_VARNAME = self.options["divid"] + "_trace"
189213
env = self.state.document.settings.env

0 commit comments

Comments
 (0)