@@ -144,6 +144,7 @@ class FillInTheBlank(RunestoneIdDirective):
144144 option_spec = RunestoneIdDirective .option_spec .copy ()
145145 option_spec .update (
146146 {
147+ "dynamic" : directives .unchanged ,
147148 "casei" : directives .flag , # case insensitive matching
148149 }
149150 )
@@ -183,8 +184,17 @@ def run(self):
183184
184185 self .updateContent ()
185186
186- self . state . nested_parse ( self . content , self . content_offset , fitbNode )
187+ # Process dynamic problem content.
187188 env = self .state .document .settings .env
189+ dynamic = self .options .get ("dynamic" )
190+ if dynamic :
191+ # Make sure we're server-side.
192+ if not env .config .runestone_server_side_grading :
193+ raise self .error ("Dynamic problems require server-side grading." )
194+ # Add in a header to set up the RNG.
195+ fitbNode .template_start = "{{{{ random = get_random({})\n exec({}) }}}}\n " .format (repr (self .options ["divid" ]), repr (dynamic )) + fitbNode .template_start
196+
197+ self .state .nested_parse (self .content , self .content_offset , fitbNode )
188198 self .options ["divclass" ] = env .config .fitb_div_class
189199
190200 # Expected _`structure`, with assigned variable names and transformations made:
@@ -214,17 +224,21 @@ def run(self):
214224 # self.feedbackArray = [
215225 # [ # blankArray
216226 # { # blankFeedbackDict: feedback 1
217- # "regex" : feedback_field_name # (An answer, as a regex;
218- # "regexFlags" : "x" # "i" if ``:casei:`` was specified, otherwise "".) OR
219- # "number" : [min, max] # a range of correct numeric answers.
220- # "feedback": feedback_field_body (after being rendered as HTML) # Provides feedback for this answer.
227+ # "regex" : feedback_field_name, # (An answer, as a regex;
228+ # "regexFlags" : "x", # "i" if ``:casei:`` was specified, otherwise "".) OR
229+ # "number" : [min, max], # a range of correct numeric answers OR
230+ # "solution_code" : source_code, # (For dynamic problems -- the dynamically-computed answer.
231+ # "dynamic_code" : source_code, # The first blank also contains setup code.)
232+ # "feedback": feedback_field_body, (after being rendered as HTML) # Provides feedback for this answer.
221233 # },
222234 # { # Feedback 2
223235 # Same as above.
224236 # }
225237 # ],
226238 # [ # Blank 2, same as above.
227- # ]
239+ # ],
240+ # ...,
241+ # [dynamic_source] # For dynamic problems only.
228242 # ]
229243 #
230244 # ...and a transformed node structure:
@@ -266,47 +280,54 @@ def run(self):
266280 feedback_field_name = feedback_field [0 ]
267281 assert isinstance (feedback_field_name , nodes .field_name )
268282 feedback_field_name_raw = feedback_field_name .rawsource
269- # See if this is a number, optinonally followed by a tolerance.
270- try :
271- # Parse the number. In Python 3 syntax, this would be ``str_num, *list_tol = feedback_field_name_raw.split()``.
272- tmp = feedback_field_name_raw .split ()
273- str_num = tmp [0 ]
274- list_tol = tmp [1 :]
275- num = ast .literal_eval (str_num )
276- assert isinstance (num , Number )
277- # If no tolerance is given, use a tolarance of 0.
278- if len (list_tol ) == 0 :
279- tol = 0
280- else :
281- assert len (list_tol ) == 1
282- tol = ast .literal_eval (list_tol [0 ])
283- assert isinstance (tol , Number )
284- # We have the number and a tolerance. Save that.
285- blankFeedbackDict = {"number" : [num - tol , num + tol ]}
286- except (SyntaxError , ValueError , AssertionError ):
287- # We can't parse this as a number, so assume it's a regex.
288- regex = (
289- # The given regex must match the entire string, from the beginning (which may be preceded by whitespaces) ...
290- r"^\s*"
291- +
292- # ... to the contents (where a single space in the provided pattern is treated as one or more whitespaces in the student's answer) ...
293- feedback_field_name .rawsource .replace (" " , r"\s+" )
294- # ... to the end (also with optional spaces).
295- + r"\s*$"
296- )
297- blankFeedbackDict = {
298- "regex" : regex ,
299- "regexFlags" : "i" if "casei" in self .options else "" ,
300- }
301- # Test out the regex to make sure it compiles without an error.
283+ # Simply store the solution code for a dynamic problem.
284+ if dynamic :
285+ blankFeedbackDict = {"solution_code" : feedback_field_name_raw }
286+ # For the first blank, also include the dynamic source code.
287+ if not blankArray :
288+ blankFeedbackDict ["dynamic_code" ] = dynamic
289+ else :
290+ # See if this is a number, optionally followed by a tolerance.
302291 try :
303- re .compile (regex )
304- except Exception as ex :
305- raise self .error (
306- 'Error when compiling regex "{}": {}.' .format (
307- regex , str (ex )
308- )
292+ # Parse the number. In Python 3 syntax, this would be ``str_num, *list_tol = feedback_field_name_raw.split()``.
293+ tmp = feedback_field_name_raw .split ()
294+ str_num = tmp [0 ]
295+ list_tol = tmp [1 :]
296+ num = ast .literal_eval (str_num )
297+ assert isinstance (num , Number )
298+ # If no tolerance is given, use a tolerance of 0.
299+ if len (list_tol ) == 0 :
300+ tol = 0
301+ else :
302+ assert len (list_tol ) == 1
303+ tol = ast .literal_eval (list_tol [0 ])
304+ assert isinstance (tol , Number )
305+ # We have the number and a tolerance. Save that.
306+ blankFeedbackDict = {"number" : [num - tol , num + tol ]}
307+ except (SyntaxError , ValueError , AssertionError ):
308+ # We can't parse this as a number, so assume it's a regex.
309+ regex = (
310+ # The given regex must match the entire string, from the beginning (which may be preceded by whitespaces) ...
311+ r"^\s*"
312+ +
313+ # ... to the contents (where a single space in the provided pattern is treated as one or more whitespaces in the student's answer) ...
314+ feedback_field_name .rawsource .replace (" " , r"\s+" )
315+ # ... to the end (also with optional spaces).
316+ + r"\s*$"
309317 )
318+ blankFeedbackDict = {
319+ "regex" : regex ,
320+ "regexFlags" : "i" if "casei" in self .options else "" ,
321+ }
322+ # Test out the regex to make sure it compiles without an error.
323+ try :
324+ re .compile (regex )
325+ except Exception as ex :
326+ raise self .error (
327+ 'Error when compiling regex "{}": {}.' .format (
328+ regex , str (ex )
329+ )
330+ )
310331 blankArray .append (blankFeedbackDict )
311332
312333 feedback_field_body = feedback_field [1 ]
0 commit comments