@@ -141,6 +141,7 @@ class FillInTheBlank(RunestoneIdDirective):
141141 option_spec = RunestoneIdDirective .option_spec .copy ()
142142 option_spec .update (
143143 {
144+ "dynamic" : directives .unchanged ,
144145 "casei" : directives .flag , # case insensitive matching
145146 }
146147 )
@@ -180,8 +181,17 @@ def run(self):
180181
181182 self .updateContent ()
182183
183- self . state . nested_parse ( self . content , self . content_offset , fitbNode )
184+ # Process dynamic problem content.
184185 env = self .state .document .settings .env
186+ dynamic = self .options .get ("dynamic" )
187+ if dynamic :
188+ # Make sure we're server-side.
189+ if not env .config .runestone_server_side_grading :
190+ raise self .error ("Dynamic problems require server-side grading." )
191+ # Add in a header to set up the RNG.
192+ fitbNode .template_start = "{{{{ random = get_random({})\n exec({}) }}}}\n " .format (repr (self .options ["divid" ]), repr (dynamic )) + fitbNode .template_start
193+
194+ self .state .nested_parse (self .content , self .content_offset , fitbNode )
185195 self .options ["divclass" ] = env .config .fitb_div_class
186196
187197 # Expected _`structure`, with assigned variable names and transformations made:
@@ -211,17 +221,21 @@ def run(self):
211221 # self.feedbackArray = [
212222 # [ # blankArray
213223 # { # blankFeedbackDict: feedback 1
214- # "regex" : feedback_field_name # (An answer, as a regex;
215- # "regexFlags" : "x" # "i" if ``:casei:`` was specified, otherwise "".) OR
216- # "number" : [min, max] # a range of correct numeric answers.
217- # "feedback": feedback_field_body (after being rendered as HTML) # Provides feedback for this answer.
224+ # "regex" : feedback_field_name, # (An answer, as a regex;
225+ # "regexFlags" : "x", # "i" if ``:casei:`` was specified, otherwise "".) OR
226+ # "number" : [min, max], # a range of correct numeric answers OR
227+ # "solution_code" : source_code, # (For dynamic problems -- the dynamically-computed answer.
228+ # "dynamic_code" : source_code, # The first blank also contains setup code.)
229+ # "feedback": feedback_field_body, (after being rendered as HTML) # Provides feedback for this answer.
218230 # },
219231 # { # Feedback 2
220232 # Same as above.
221233 # }
222234 # ],
223235 # [ # Blank 2, same as above.
224- # ]
236+ # ],
237+ # ...,
238+ # [dynamic_source] # For dynamic problems only.
225239 # ]
226240 #
227241 # ...and a transformed node structure:
@@ -263,47 +277,54 @@ def run(self):
263277 feedback_field_name = feedback_field [0 ]
264278 assert isinstance (feedback_field_name , nodes .field_name )
265279 feedback_field_name_raw = feedback_field_name .rawsource
266- # See if this is a number, optinonally followed by a tolerance.
267- try :
268- # Parse the number. In Python 3 syntax, this would be ``str_num, *list_tol = feedback_field_name_raw.split()``.
269- tmp = feedback_field_name_raw .split ()
270- str_num = tmp [0 ]
271- list_tol = tmp [1 :]
272- num = ast .literal_eval (str_num )
273- assert isinstance (num , Number )
274- # If no tolerance is given, use a tolarance of 0.
275- if len (list_tol ) == 0 :
276- tol = 0
277- else :
278- assert len (list_tol ) == 1
279- tol = ast .literal_eval (list_tol [0 ])
280- assert isinstance (tol , Number )
281- # We have the number and a tolerance. Save that.
282- blankFeedbackDict = {"number" : [num - tol , num + tol ]}
283- except (SyntaxError , ValueError , AssertionError ):
284- # We can't parse this as a number, so assume it's a regex.
285- regex = (
286- # The given regex must match the entire string, from the beginning (which may be preceded by whitespaces) ...
287- r"^\s*"
288- +
289- # ... to the contents (where a single space in the provided pattern is treated as one or more whitespaces in the student's answer) ...
290- feedback_field_name .rawsource .replace (" " , r"\s+" )
291- # ... to the end (also with optional spaces).
292- + r"\s*$"
293- )
294- blankFeedbackDict = {
295- "regex" : regex ,
296- "regexFlags" : "i" if "casei" in self .options else "" ,
297- }
298- # Test out the regex to make sure it compiles without an error.
280+ # Simply store the solution code for a dynamic problem.
281+ if dynamic :
282+ blankFeedbackDict = {"solution_code" : feedback_field_name_raw }
283+ # For the first blank, also include the dynamic source code.
284+ if not blankArray :
285+ blankFeedbackDict ["dynamic_code" ] = dynamic
286+ else :
287+ # See if this is a number, optionally followed by a tolerance.
299288 try :
300- re .compile (regex )
301- except Exception as ex :
302- raise self .error (
303- 'Error when compiling regex "{}": {}.' .format (
304- regex , str (ex )
305- )
289+ # Parse the number. In Python 3 syntax, this would be ``str_num, *list_tol = feedback_field_name_raw.split()``.
290+ tmp = feedback_field_name_raw .split ()
291+ str_num = tmp [0 ]
292+ list_tol = tmp [1 :]
293+ num = ast .literal_eval (str_num )
294+ assert isinstance (num , Number )
295+ # If no tolerance is given, use a tolerance of 0.
296+ if len (list_tol ) == 0 :
297+ tol = 0
298+ else :
299+ assert len (list_tol ) == 1
300+ tol = ast .literal_eval (list_tol [0 ])
301+ assert isinstance (tol , Number )
302+ # We have the number and a tolerance. Save that.
303+ blankFeedbackDict = {"number" : [num - tol , num + tol ]}
304+ except (SyntaxError , ValueError , AssertionError ):
305+ # We can't parse this as a number, so assume it's a regex.
306+ regex = (
307+ # The given regex must match the entire string, from the beginning (which may be preceded by whitespaces) ...
308+ r"^\s*"
309+ +
310+ # ... to the contents (where a single space in the provided pattern is treated as one or more whitespaces in the student's answer) ...
311+ feedback_field_name .rawsource .replace (" " , r"\s+" )
312+ # ... to the end (also with optional spaces).
313+ + r"\s*$"
306314 )
315+ blankFeedbackDict = {
316+ "regex" : regex ,
317+ "regexFlags" : "i" if "casei" in self .options else "" ,
318+ }
319+ # Test out the regex to make sure it compiles without an error.
320+ try :
321+ re .compile (regex )
322+ except Exception as ex :
323+ raise self .error (
324+ 'Error when compiling regex "{}": {}.' .format (
325+ regex , str (ex )
326+ )
327+ )
307328 blankArray .append (blankFeedbackDict )
308329
309330 feedback_field_body = feedback_field [1 ]
0 commit comments