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

Commit 2a75a93

Browse files
authored
Merge pull request #1307 from amy21206/master
Adding new directive: horizontal Parsons problems
2 parents 0deb386 + 8991af6 commit 2a75a93

File tree

18 files changed

+20583
-210
lines changed

18 files changed

+20583
-210
lines changed

package-lock.json

Lines changed: 3497 additions & 209 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,4 @@
4141
"vega-embed": "3.14.0",
4242
"wavedrom": "^2.0.0"
4343
}
44-
}
44+
}

runestone/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from .dragndrop import DragNDrop
1414
from .fitb import FillInTheBlank
1515
from .groupsub import GroupSubmission
16+
from .hparsons import HParsonsNode
1617
from .khanex import Khanex
1718
from .selectquestion import SelectQuestion
1819
from .matrixeq import MatrixEq
@@ -246,6 +247,7 @@ def build(options):
246247
"disqus": DisqusDirective,
247248
"dragndrop": DragNDrop,
248249
"groupsub": GroupSubmission,
250+
"hparsons": HParsonsNode,
249251
"parsonsprob": ParsonsProblem,
250252
"poll": Poll,
251253
"quizly": Quizly,

runestone/hparsons/README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<h2>Short Answer</h2>
2+
3+
```html
4+
<p data-component="shortanswer" data-optional id="example1">What is the best thing about the color blue?</p>
5+
```
6+
7+
The <code>p</code> tag represents the entire Short Answer component to be rendered.
8+
(more info about the use)
9+
10+
11+
Option spec:
12+
13+
<ul>
14+
<li><code>data-component="shortanswer"</code> Identifies this as a Short Answer component</li>
15+
<li><code>id</code> Must be unique in the document</li>
16+
<li><code>data-optional</code> Makes this component optional for the student to answer--it isn't required.</li>
17+
</ul>

runestone/hparsons/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .hparsons import *
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
.hparsons_section {
2+
position: relative;
3+
margin-right: auto;
4+
margin-left: auto;
5+
max-width: 800px;
6+
clear: both;
7+
}
8+
9+
.hparsons_section > *:not(.hparsons_section) {
10+
max-width: 500pt;
11+
margin-left: auto;
12+
margin-right: auto;
13+
}
14+
15+
.hp_question {
16+
padding-left: 10px;
17+
padding-top: 10px;
18+
margin: 5px;
19+
}
20+
21+
.hp_actions {
22+
text-align: center;
23+
}
24+
25+
.hp_output {
26+
display: none;
27+
max-width: 450px;
28+
background-color: inherit;
29+
}
30+
.hp_output pre {
31+
background-color: lightgray;
32+
}
33+
34+
.hp_sql_result {
35+
background-color: lightgrey;
36+
padding: 10px;
37+
border-radius: 6px;
38+
margin-bottom: 10px;
39+
}
40+
41+
.hp_sql_result_success {
42+
background-color: transparent;
43+
color: green;
44+
border: 0px;
45+
padding: 0px;
46+
margin-top: 10px;
47+
margin-bottom: 10px;
48+
min-height: 0px !important;
49+
}
50+
51+
.hp_sql_result_failure {
52+
background-color: transparent;
53+
color: red;
54+
border: 0px;
55+
padding: 0px;
56+
margin-top: 10px;
57+
margin-bottom: 10px;
58+
min-height: 0px !important;
59+
}

runestone/hparsons/hparsons.py

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
# *********
2+
# |docname|
3+
# *********
4+
# Copyright (C) 2011 Bradley N. Miller
5+
#
6+
# This program is free software: you can redistribute it and/or modify
7+
# it under the terms of the GNU General Public License as published by
8+
# the Free Software Foundation, either version 3 of the License, or
9+
# (at your option) any later version.
10+
#
11+
# This program is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
# GNU General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU General Public License
17+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
#
19+
__author__ = "bmiller"
20+
21+
from docutils import nodes
22+
from docutils.parsers.rst import directives
23+
from sqlalchemy import Table
24+
from runestone.server.componentdb import (
25+
addQuestionToDB,
26+
addHTMLToDB,
27+
get_engine_meta,
28+
maybeAddToAssignment,
29+
)
30+
from runestone.common.runestonedirective import (
31+
RunestoneIdDirective,
32+
RunestoneIdNode,
33+
)
34+
35+
def setup(app):
36+
app.add_directive("hparsons", HParsonsDirective)
37+
app.add_node(HParsonsNode, html=(visit_hp_html, depart_hp_html))
38+
39+
40+
TEMPLATE_START = """
41+
<div>
42+
<div data-component="hparsons" id=%(divid)s data-question_label="%(question_label)s" class="alert alert-warning hparsons_section">
43+
<div class="hp_question col-md-12">
44+
"""
45+
46+
TEMPLATE_END = """
47+
</div>
48+
<div class='hparsons'></div>
49+
<textarea data-lang="%(language)s"
50+
%(optional)s
51+
%(dburl)s
52+
%(textentry)s
53+
%(reuse)s
54+
%(randomize)s
55+
style="visibility: hidden;">
56+
%(initialsetting)s
57+
</textarea>
58+
</div>
59+
</div>
60+
"""
61+
62+
63+
class HParsonsNode(nodes.General, nodes.Element, RunestoneIdNode):
64+
pass
65+
66+
67+
# self for these functions is an instance of the writer class. For example
68+
# in html, self is sphinx.writers.html.SmartyPantsHTMLTranslator
69+
# The node that is passed as a parameter is an instance of our node class.
70+
def visit_hp_html(self, node):
71+
72+
node["delimiter"] = "_start__{}_".format(node["runestone_options"]["divid"])
73+
74+
self.body.append(node["delimiter"])
75+
76+
res = TEMPLATE_START % node["runestone_options"]
77+
self.body.append(res)
78+
79+
80+
def depart_hp_html(self, node):
81+
res = TEMPLATE_END % node["runestone_options"]
82+
self.body.append(res)
83+
84+
addHTMLToDB(
85+
node["runestone_options"]["divid"],
86+
node["runestone_options"]["basecourse"],
87+
"".join(self.body[self.body.index(node["delimiter"]) + 1 :]),
88+
)
89+
90+
self.body.remove(node["delimiter"])
91+
92+
93+
class HParsonsDirective(RunestoneIdDirective):
94+
# only keep: language, autograde, dburl
95+
"""
96+
.. hparsons:: uniqueid
97+
:language: sql, regex
98+
:dburl: only for sql -- url to load database
99+
:randomize: randomize the order of horizontal parsons
100+
TODO: fix textentry
101+
:reuse: only for parsons -- make the blocks reusable
102+
:textentry: if you will use text entry instead of horizontal parsons
103+
104+
Here is the problem description. It must ends with the tildes.
105+
Make sure you use the correct delimitier for each section below.
106+
~~~~
107+
--blocks--
108+
block 1
109+
block 2
110+
--explanations--
111+
explanations for block 1
112+
explanations for block 2
113+
--unittest--
114+
assert 1,1 == world
115+
assert 0,1 == hello
116+
assert 2,1 == 42
117+
"""
118+
119+
required_arguments = 1
120+
optional_arguments = 1
121+
has_content = True
122+
option_spec = RunestoneIdDirective.option_spec.copy()
123+
option_spec.update(
124+
{
125+
"dburl": directives.unchanged,
126+
"language": directives.unchanged,
127+
"textentry": directives.flag,
128+
"reuse": directives.flag,
129+
"randomize": directives.flag,
130+
}
131+
)
132+
133+
def run(self):
134+
super(HParsonsDirective, self).run()
135+
136+
env = self.state.document.settings.env
137+
138+
if "textentry" in self.options:
139+
self.options['textentry'] = ' data-textentry="true"'
140+
else:
141+
self.options['textentry'] = ''
142+
143+
if "reuse" in self.options:
144+
self.options['reuse'] = ' data-reuse="true"'
145+
else:
146+
self.options['reuse'] = ''
147+
148+
if "randomize" in self.options:
149+
self.options['randomize'] = ' data-randomize="true"'
150+
else:
151+
self.options['randomize'] = ''
152+
153+
explain_text = None
154+
if self.content:
155+
if "~~~~" in self.content:
156+
idx = self.content.index("~~~~")
157+
explain_text = self.content[:idx]
158+
self.content = self.content[idx + 1 :]
159+
source = "\n".join(self.content)
160+
else:
161+
source = "\n"
162+
163+
self.explain_text = explain_text or ["Not an Exercise"]
164+
addQuestionToDB(self)
165+
166+
self.options["initialsetting"] = source
167+
168+
# TODO: change this
169+
if "language" not in self.options:
170+
self.options["language"] = "python"
171+
172+
# SQL Options
173+
if "dburl" in self.options:
174+
self.options["dburl"] = "data-dburl='{}'".format(self.options["dburl"])
175+
else:
176+
self.options["dburl"] = ""
177+
178+
course_name = env.config.html_context["course_id"]
179+
divid = self.options["divid"]
180+
181+
engine, meta, sess = get_engine_meta()
182+
183+
if engine:
184+
Source_code = Table(
185+
"source_code", meta, autoload=True, autoload_with=engine
186+
)
187+
engine.execute(
188+
Source_code.delete()
189+
.where(Source_code.c.acid == divid)
190+
.where(Source_code.c.course_id == course_name)
191+
)
192+
engine.execute(
193+
Source_code.insert().values(
194+
acid=divid,
195+
course_id=course_name,
196+
main_code=source,
197+
suffix_code=suffix,
198+
)
199+
)
200+
else:
201+
if (
202+
not hasattr(env, "dberr_activecode_reported")
203+
or not env.dberr_activecode_reported
204+
):
205+
env.dberr_activecode_reported = True
206+
print(
207+
"Unable to save to source_code table in activecode.py. Possible problems:"
208+
)
209+
print(" 1. dburl or course_id are not set in conf.py for your book")
210+
print(" 2. unable to connect to the database using dburl")
211+
print("")
212+
print(
213+
"This should only affect the grading interface. Everything else should be fine."
214+
)
215+
216+
hpnode = HParsonsNode()
217+
hpnode["runestone_options"] = self.options
218+
hpnode["source"], hpnode["line"] = self.state_machine.get_source_and_line(self.lineno)
219+
self.add_name(hpnode) # make this divid available as a target for :ref:
220+
221+
maybeAddToAssignment(self)
222+
if explain_text:
223+
self.updateContent()
224+
self.state.nested_parse(explain_text, self.content_offset, hpnode)
225+
226+
return [hpnode]

0 commit comments

Comments
 (0)