From 8cdcfd7de2d6c4d32df26320fd36cfd23ef952c7 Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Fri, 14 Jul 2017 17:11:55 -0500 Subject: [PATCH 1/4] Fix: Port test improvements to Windows. --- runestone/unittest_base.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/runestone/unittest_base.py b/runestone/unittest_base.py index 63a0b7692..eec4c03aa 100644 --- a/runestone/unittest_base.py +++ b/runestone/unittest_base.py @@ -45,9 +45,12 @@ def module_fixture_maker(module_path): # Provide a base test case which sets up the `Selenium `_ driver. class RunestoneTestCase(unittest.TestCase): def setUp(self): - - self.display = Display(visible=0, size=(1280, 1024)) - self.display.start() + # `PyVirtualDisplay `_ only runs on X-windows, meaning Linux. Mac seems to have `some support `_. Windows is out of the question. + if os.name != 'nt': + self.display = Display(visible=0, size=(1280, 1024)) + self.display.start() + else: + self.display = None #self.driver = webdriver.PhantomJS() # use this for Jenkins auto testing # options = webdriver.ChromeOptions() # options.add_argument("headless") @@ -60,4 +63,5 @@ def setUp(self): def tearDown(self): self.driver.quit() - self.display.stop() + if self.display: + self.display.stop() From 0010eb0b09d1733153b7151857226f705ef48f3d Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Sat, 15 Jul 2017 22:43:09 -0500 Subject: [PATCH 2/4] Fix: Work around test failure on Windows. --- runestone/activecode/test/test_activecode.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/runestone/activecode/test/test_activecode.py b/runestone/activecode/test/test_activecode.py index 9efa88707..da08a2317 100644 --- a/runestone/activecode/test/test_activecode.py +++ b/runestone/activecode/test/test_activecode.py @@ -1,5 +1,8 @@ -from runestone.unittest_base import module_fixture_maker, RunestoneTestCase from selenium.webdriver import ActionChains +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.common.by import By +from runestone.unittest_base import module_fixture_maker, RunestoneTestCase setUpModule, tearDownModule = module_fixture_maker(__file__) @@ -39,6 +42,7 @@ def test_history(self): ta = t1.find_element_by_class_name("cm-s-default") self.assertIsNotNone(ta) self.driver.execute_script("""edList['test1'].editor.setValue("print('GoodBye')")""") + WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.CLASS_NAME, "run-button"))) rb.click() output = t1.find_element_by_class_name("ac_output") self.assertEqual(output.text.strip(),"GoodBye") From b3ddb0f89d6a02e874f09dc0de8727fc0cc85170 Mon Sep 17 00:00:00 2001 From: "Bryan A. Jones" Date: Fri, 14 Jul 2017 16:43:17 -0500 Subject: [PATCH 3/4] Fix: Provide source file and line number information to all reST nodes. Fix: Ensure that files are closed in chapternames.py. This provides better diagnostic information to authors when they encounter an error, and follows the pattern in docutils source. --- runestone/activecode/activecode.py | 1 + runestone/animation/animation.py | 4 ++- runestone/assess/assess.py | 4 ++- runestone/assess/multiplechoice.py | 1 + runestone/assess/timedassessment.py | 5 +-- runestone/assignment/__init__.py | 4 ++- runestone/blockly/blockly.py | 50 ++++++++++++++------------- runestone/clickableArea/clickable.py | 1 + runestone/codelens/visualizer.py | 4 ++- runestone/datafile/__init__.py | 4 ++- runestone/disqus/disqus.py | 13 +++---- runestone/dragndrop/dragndrop.py | 1 + runestone/external/external.py | 1 + runestone/fitb/fitb.py | 4 +-- runestone/livecode/livecode.py | 4 ++- runestone/meta/meta.py | 4 ++- runestone/parsons/parsons.py | 10 +++--- runestone/poll/poll.py | 5 +-- runestone/question/question.py | 1 + runestone/reveal/reveal.py | 1 + runestone/server/chapternames.py | 6 ++-- runestone/shortanswer/shortanswer.py | 1 + runestone/tabbedStuff/tabbedStuff.py | 1 + runestone/usageAssignment/__init__.py | 4 ++- runestone/video/video.py | 4 ++- 25 files changed, 86 insertions(+), 52 deletions(-) diff --git a/runestone/activecode/activecode.py b/runestone/activecode/activecode.py index 99f4fb8a5..3a38f104f 100644 --- a/runestone/activecode/activecode.py +++ b/runestone/activecode/activecode.py @@ -353,6 +353,7 @@ def run(self): acnode = ActivcodeNode(self.options) + acnode.source, acnode.line = self.state_machine.get_source_and_line(self.lineno) self.add_name(acnode) # make this divid available as a target for :ref: if explain_text: diff --git a/runestone/animation/animation.py b/runestone/animation/animation.py index 07cdbfbcb..07f2b93fd 100644 --- a/runestone/animation/animation.py +++ b/runestone/animation/animation.py @@ -80,7 +80,9 @@ def run(self): res = res + SRC % self.options - return [nodes.raw('',res , format='html')] + rawnode = nodes.raw('',res , format='html') + rawnode.source, rawnode.line = self.state_machine.get_source_and_line(self.lineno) + return [rawnode] source = ''' diff --git a/runestone/assess/assess.py b/runestone/assess/assess.py index 9418940c6..b95eede3b 100644 --- a/runestone/assess/assess.py +++ b/runestone/assess/assess.py @@ -79,7 +79,9 @@ def run(self): res = TEMPLATE_START % self.options res += TEMPLATE_END % self.options - return [nodes.raw('', res, format='html')] + rawnode = nodes.raw('', res, format='html') + rawnode.source, rawnode.line = self.state_machine.get_source_and_line(self.lineno) + return [rawnode] class QuestionNumber(Directive): diff --git a/runestone/assess/multiplechoice.py b/runestone/assess/multiplechoice.py index 8c5ddfb19..a9400614a 100644 --- a/runestone/assess/multiplechoice.py +++ b/runestone/assess/multiplechoice.py @@ -195,6 +195,7 @@ def run(self): mcNode = MChoiceNode(self.options) + mcNode.source, mcNode.line = self.state_machine.get_source_and_line(self.lineno) mcNode.template_start = TEMPLATE_START mcNode.template_option = OPTION mcNode.template_end = TEMPLATE_END diff --git a/runestone/assess/timedassessment.py b/runestone/assess/timedassessment.py index a50901263..5674b1e7b 100644 --- a/runestone/assess/timedassessment.py +++ b/runestone/assess/timedassessment.py @@ -42,12 +42,12 @@ def visit_timed_node(self, node): node.timed_options['nofeedback'] = 'data-no-feedback' else: node.timed_options['nofeedback'] = '' - + if 'notimer' in node.timed_options: node.timed_options['notimer'] = 'data-no-timer' else: node.timed_options['notimer'] = '' - + if 'fullwidth' in node.timed_options: node.timed_options['fullwidth'] = 'data-fullwidth' else: @@ -107,6 +107,7 @@ def run(self): self.options['divid'] = self.arguments[0] timed_node = TimedNode(self.options) + timed_node.source, timed_node.line = self.state_machine.get_source_and_line(self.lineno) self.state.nested_parse(self.content, self.content_offset, timed_node) diff --git a/runestone/assignment/__init__.py b/runestone/assignment/__init__.py index 69376a1c0..57b36e7b4 100644 --- a/runestone/assignment/__init__.py +++ b/runestone/assignment/__init__.py @@ -157,6 +157,8 @@ def run(self): self.options['question_ids'] = question_names if 'generate_html' in self.options: - return [AssignmentNode(self.options)] + assignment_node = AssignmentNode(self.options) + assignment_node.source, assignment_node.line = self.state_machine.get_source_and_line(self.lineno) + return [assignment_node] else: return [] diff --git a/runestone/blockly/blockly.py b/runestone/blockly/blockly.py index f65acc2dd..422d488eb 100644 --- a/runestone/blockly/blockly.py +++ b/runestone/blockly/blockly.py @@ -134,14 +134,14 @@ def __init__(self,content): } } - Blockly.JavaScript['text_print'] = function(block) { - // Print statement override. - var argument0 = Blockly.JavaScript.valueToCode(block, 'TEXT', - Blockly.JavaScript.ORDER_NONE) || '\\'\\''; - return 'my_custom_print(' + argument0 + ', "%(divid)s" );\\n'; - }; - - function my_custom_print(text,divid) { + Blockly.JavaScript['text_print'] = function(block) { + // Print statement override. + var argument0 = Blockly.JavaScript.valueToCode(block, 'TEXT', + Blockly.JavaScript.ORDER_NONE) || '\\'\\''; + return 'my_custom_print(' + argument0 + ', "%(divid)s" );\\n'; + }; + + function my_custom_print(text,divid) { var p = document.getElementById(divid+"_pre"); p.innerHTML += text + "\\n" } @@ -151,7 +151,7 @@ def __init__(self,content): Blockly.Xml.domToWorkspace(Blockly.mainWorkspace, xmlDom); - +

   
   
@@ -213,7 +213,7 @@ def run(self):
 
         pathDepth = rel_filename.count("/")
         self.options['blocklyHomePrefix'] = "../"*pathDepth
-        
+
         plstart = len(self.content)
         if 'preload::' in self.content:
             plstart = self.content.index('preload::')
@@ -221,21 +221,23 @@ def run(self):
 
         if self.content:
             self.options['controls'] = self.content[:plstart]
-        
-        return [BlocklyNode(self.options)]
+
+        blockly_node = BlocklyNode(self.options)
+        blockly_node.source, blockly_node.line = self.state_machine.get_source_and_line(self.lineno)
+        return [blockly_node]
 
 
 
 
 '''
-    Blockly.JavaScript['text_print'] = function(block) { 
-      // Print statement override. 
-      var argument0 = Blockly.JavaScript.valueToCode(block, 'TEXT', 
-          Blockly.JavaScript.ORDER_NONE) || '\'\''; 
-      return 'my_custom_print(' + argument0 + ');\n'; 
-    }; 
-
-    function my_custom_print(text) { 
+    Blockly.JavaScript['text_print'] = function(block) {
+      // Print statement override.
+      var argument0 = Blockly.JavaScript.valueToCode(block, 'TEXT',
+          Blockly.JavaScript.ORDER_NONE) || '\'\'';
+      return 'my_custom_print(' + argument0 + ');\n';
+    };
+
+    function my_custom_print(text) {
       var p = document.getElementById("blockly1_pre");
       p.innerHTML += text
       }
@@ -244,16 +246,16 @@ def run(self):
 
 
 # to preload blockly with a finished or partial program, do the following
-# 
+#
 # https://blockly-demo.appspot.com/static/apps/code/index.html?lang=en
-# 
+#
 # Now save the xml string.  And append something like the following to the script after blockly
 # is created:
-# 
+#
 #       var xmlText = '      X                  10            '
 #       xmlDom = Blockly.Xml.textToDom(xmlText);
 #       Blockly.Xml.domToWorkspace(Blockly.mainWorkspace, xmlDom);
- 
+
 
 
 
diff --git a/runestone/clickableArea/clickable.py b/runestone/clickableArea/clickable.py
index 1defcbe1f..a7dc32a6e 100644
--- a/runestone/clickableArea/clickable.py
+++ b/runestone/clickableArea/clickable.py
@@ -141,6 +141,7 @@ def run(self):
         else:
             self.options['clickcode'] = ''
         clickNode = ClickableAreaNode(self.options)
+        clickNode.source, clickNode.line = self.state_machine.get_source_and_line(self.lineno)
         clickNode.template_start = TEMPLATE
 
         if "table" in self.options:
diff --git a/runestone/codelens/visualizer.py b/runestone/codelens/visualizer.py
index dc80afec8..5035eab04 100644
--- a/runestone/codelens/visualizer.py
+++ b/runestone/codelens/visualizer.py
@@ -238,7 +238,9 @@ def js_var_finalizer(input_code, output_trace):
         else:
             res += ''
         addHTMLToDB(self.options['divid'], self.options['basecourse'], res % self.options)
-        return [nodes.raw('', res % self.options, format='html')]
+        raw_node = nodes.raw('', res % self.options, format='html')
+        raw_node.source, raw_node.line = self.state_machine.get_source_and_line(self.lineno)
+        return [raw_node]
 
     def inject_questions(self, curTrace):
         if 'breakline' not in self.options:
diff --git a/runestone/datafile/__init__.py b/runestone/datafile/__init__.py
index 4ca750c5b..6ae1eb38a 100644
--- a/runestone/datafile/__init__.py
+++ b/runestone/datafile/__init__.py
@@ -166,4 +166,6 @@ def run(self):
             print("This should only affect the grading interface. Everything else should be fine.")
 
 
-        return [DataFileNode(self.options)]
+        data_file_node = DataFileNode(self.options)
+        data_file_node.source, data_file_node.line = self.state_machine.get_source_and_line(self.lineno)
+        return [data_file_node]
diff --git a/runestone/disqus/disqus.py b/runestone/disqus/disqus.py
index 19d9ed4a6..34a0c986f 100644
--- a/runestone/disqus/disqus.py
+++ b/runestone/disqus/disqus.py
@@ -22,7 +22,7 @@
 
 DISQUS_BOX = """\
 \n'''
 
-class Animation(Directive):
+class Animation(RunestoneDirective):
     required_arguments = 1
     optional_arguments = 1
     final_argument_whitespace = True
@@ -80,7 +80,7 @@ def run(self):
 
 
         res = res + SRC % self.options
-        rawnode = nodes.raw('',res , format='html')
+        rawnode = nodes.raw(self.block_text, res, format='html')
         rawnode.source, rawnode.line = self.state_machine.get_source_and_line(self.lineno)
         return [rawnode]
 
diff --git a/runestone/assess/assess.py b/runestone/assess/assess.py
index b95eede3b..ed7b2fb5a 100644
--- a/runestone/assess/assess.py
+++ b/runestone/assess/assess.py
@@ -17,7 +17,7 @@
 
 from docutils import nodes
 from docutils.parsers.rst import directives
-from docutils.parsers.rst import Directive
+from runestone.common.runestonedirective import RunestoneDirective
 from .assessbase import Assessment
 from .multiplechoice import *
 from .timedassessment import *
@@ -47,7 +47,7 @@ def setup(app):
 
 
 
-class AddButton(Directive):
+class AddButton(RunestoneDirective):
     required_arguments = 1
     optional_arguments = 1
     final_argument_whitespace = True
@@ -79,12 +79,12 @@ def run(self):
         res = TEMPLATE_START % self.options
 
         res += TEMPLATE_END % self.options
-        rawnode = nodes.raw('', res, format='html')
+        rawnode = nodes.raw(self.block_text, res, format='html')
         rawnode.source, rawnode.line = self.state_machine.get_source_and_line(self.lineno)
         return [rawnode]
 
 
-class QuestionNumber(Directive):
+class QuestionNumber(RunestoneDirective):
     """Set Parameters for Question Numbering
 .. qnum::
    'prefix': character prefix before the number
diff --git a/runestone/assess/assessbase.py b/runestone/assess/assessbase.py
index 662465c20..42d30496b 100644
--- a/runestone/assess/assessbase.py
+++ b/runestone/assess/assessbase.py
@@ -15,10 +15,7 @@
 #
 __author__ = 'bmiller'
 
-from docutils import nodes
-from docutils.parsers.rst import directives
-from docutils.parsers.rst import Directive
-from runestone.common.runestonedirective import RunestoneDirective
+from runestone.common.runestonedirective import RunestoneDirective, RunestoneNode
 
 _base_js_escapes = (
     ('\\', r'\u005C'),
@@ -76,7 +73,7 @@ def getNumber(self):
     def run(self):
 
         self.options['qnumber'] = self.getNumber()
-        
+
         self.options['divid'] = self.arguments[0]
 
         if self.content[0][:2] == '..':  # first line is a directive
diff --git a/runestone/assess/multiplechoice.py b/runestone/assess/multiplechoice.py
index a9400614a..0aaaefad7 100644
--- a/runestone/assess/multiplechoice.py
+++ b/runestone/assess/multiplechoice.py
@@ -21,15 +21,15 @@
 from runestone.server.componentdb import addQuestionToDB, addHTMLToDB
 
 
-class MChoiceNode(nodes.General, nodes.Element):
-    def __init__(self,content):
+class MChoiceNode(nodes.General, nodes.Element, RunestoneNode):
+    def __init__(self, content, **kwargs):
         """
 
         Arguments:
         - `self`:
         - `content`:
         """
-        super(MChoiceNode,self).__init__()
+        super(MChoiceNode, self).__init__(**kwargs)
         self.mc_options = content
 
 def visit_mc_node(self,node):
@@ -194,7 +194,7 @@ def run(self):
 
 
 
-        mcNode = MChoiceNode(self.options)
+        mcNode = MChoiceNode(self.options, rawsource=self.block_text)
         mcNode.source, mcNode.line = self.state_machine.get_source_and_line(self.lineno)
         mcNode.template_start = TEMPLATE_START
         mcNode.template_option = OPTION
@@ -278,19 +278,19 @@ def run(self):
 
 
 # Define a bullet_list which contains answers (see the structure_).
-class AnswersBulletList(nodes.bullet_list):
+class AnswersBulletList(nodes.bullet_list, RunestoneNode):
     pass
 
 # Define a list_item which contains answers (see the structure_).
-class AnswerListItem(nodes.list_item):
+class AnswerListItem(nodes.list_item, RunestoneNode):
     pass
 
 # Define a bullet_list which contains feedback (see the structure_).
-class FeedbackBulletList(nodes.bullet_list):
+class FeedbackBulletList(nodes.bullet_list, RunestoneNode):
     pass
 
 # Define a list_item which contains answers (see the structure_).
-class FeedbackListItem(nodes.list_item):
+class FeedbackListItem(nodes.list_item, RunestoneNode):
     pass
 
 # The ``
    `` tag will be generated already -- don't output it. diff --git a/runestone/assess/timedassessment.py b/runestone/assess/timedassessment.py index 5674b1e7b..7a35d1fc3 100644 --- a/runestone/assess/timedassessment.py +++ b/runestone/assess/timedassessment.py @@ -14,12 +14,12 @@ from docutils import nodes from docutils.parsers.rst import directives -from docutils.parsers.rst import Directive +from runestone.common.runestonedirective import RunestoneDirective, RunestoneNode #add directives/javascript/css -class TimedNode(nodes.General, nodes.Element): +class TimedNode(nodes.General, nodes.Element, RunestoneNode): def __init__(self,content): super(TimedNode,self).__init__() self.timed_options = content @@ -69,7 +69,7 @@ def depart_timed_node(self,node): TEMPLATE_END = '''
''' -class TimedDirective(Directive): +class TimedDirective(RunestoneDirective): """ .. timed:: identifier :timelimit: Number of minutes student has to take the timed assessment--if not provided, no time limit @@ -106,7 +106,7 @@ def run(self): self.options['divid'] = self.arguments[0] - timed_node = TimedNode(self.options) + timed_node = TimedNode(self.options, rawsource=self.block_text) timed_node.source, timed_node.line = self.state_machine.get_source_and_line(self.lineno) self.state.nested_parse(self.content, self.content_offset, timed_node) diff --git a/runestone/assignment/__init__.py b/runestone/assignment/__init__.py index 57b36e7b4..f112dc0d5 100644 --- a/runestone/assignment/__init__.py +++ b/runestone/assignment/__init__.py @@ -19,9 +19,8 @@ from docutils import nodes from docutils.parsers.rst import directives -from docutils.parsers.rst import Directive from runestone.server.componentdb import addAssignmentToDB, addAssignmentQuestionToDB, getCourseID, getOrCreateAssignmentType, getQuestionID, get_HTML_from_DB -from runestone.common.runestonedirective import RunestoneDirective +from runestone.common.runestonedirective import RunestoneDirective, RunestoneNode from datetime import datetime def setup(app): @@ -31,15 +30,15 @@ def setup(app): app.connect('doctree-resolved',process_nodes) app.connect('env-purge-doc', purge) -class AssignmentNode(nodes.General, nodes.Element): - def __init__(self, content): +class AssignmentNode(nodes.General, nodes.Element, RunestoneNode): + def __init__(self, content, **kwargs): """ Arguments: - `self`: - `content`: """ - super(AssignmentNode, self).__init__(name=content['name']) + super(AssignmentNode, self).__init__(name=content['name'], **kwargs) self.a_components = content @@ -157,7 +156,7 @@ def run(self): self.options['question_ids'] = question_names if 'generate_html' in self.options: - assignment_node = AssignmentNode(self.options) + assignment_node = AssignmentNode(self.options, rawsource=self.block_text) assignment_node.source, assignment_node.line = self.state_machine.get_source_and_line(self.lineno) return [assignment_node] else: diff --git a/runestone/blockly/blockly.py b/runestone/blockly/blockly.py index 422d488eb..1afc5eb1b 100644 --- a/runestone/blockly/blockly.py +++ b/runestone/blockly/blockly.py @@ -16,11 +16,10 @@ __author__ = 'bmiller' -from docutils import nodes -from docutils.parsers.rst import directives -from docutils.parsers.rst import Directive -import json import os +from docutils import nodes +from runestone.common import RunestoneDirective, RunestoneNode + # try: # import conf @@ -45,15 +44,15 @@ def setup(app): #' -class BlocklyNode(nodes.General, nodes.Element): - def __init__(self,content): +class BlocklyNode(nodes.General, nodes.Element, RunestoneNode): + def __init__(self,content, **kwargs): """ Arguments: - `self`: - `content`: """ - super(BlocklyNode,self).__init__() + super(BlocklyNode,self).__init__(**kwargs) self.ac_components = content @@ -199,7 +198,7 @@ def purge_activecodes(app,env,docname): pass -class Blockly(Directive): +class Blockly(RunestoneDirective): required_arguments = 1 optional_arguments = 0 has_content = True @@ -222,7 +221,7 @@ def run(self): if self.content: self.options['controls'] = self.content[:plstart] - blockly_node = BlocklyNode(self.options) + blockly_node = BlocklyNode(self.options, rawsource=self.block_text) blockly_node.source, blockly_node.line = self.state_machine.get_source_and_line(self.lineno) return [blockly_node] diff --git a/runestone/clickableArea/clickable.py b/runestone/clickableArea/clickable.py index a7dc32a6e..16b2486c8 100644 --- a/runestone/clickableArea/clickable.py +++ b/runestone/clickableArea/clickable.py @@ -18,9 +18,8 @@ from docutils import nodes from docutils.parsers.rst import directives -from docutils.parsers.rst import Directive from runestone.server.componentdb import addQuestionToDB, addHTMLToDB -from runestone.common.runestonedirective import RunestoneDirective +from runestone.common.runestonedirective import RunestoneDirective, RunestoneNode def setup(app): app.add_directive('clickablearea',ClickableArea) @@ -39,14 +38,14 @@ def setup(app): """ -class ClickableAreaNode(nodes.General, nodes.Element): - def __init__(self,content): +class ClickableAreaNode(nodes.General, nodes.Element, RunestoneNode): + def __init__(self,content, **kwargs): """ Arguments: - `self`: - `content`: """ - super(ClickableAreaNode,self).__init__() + super(ClickableAreaNode,self).__init__(**kwargs) self.ca_options = content # self for these functions is an instance of the writer class. For example @@ -140,7 +139,7 @@ def run(self): self.options['clickcode'] = source else: self.options['clickcode'] = '' - clickNode = ClickableAreaNode(self.options) + clickNode = ClickableAreaNode(self.options, rawsource=self.block_text) clickNode.source, clickNode.line = self.state_machine.get_source_and_line(self.lineno) clickNode.template_start = TEMPLATE diff --git a/runestone/codelens/visualizer.py b/runestone/codelens/visualizer.py index 5035eab04..c0bcfb814 100644 --- a/runestone/codelens/visualizer.py +++ b/runestone/codelens/visualizer.py @@ -18,7 +18,6 @@ from docutils import nodes from docutils.parsers.rst import directives -from docutils.parsers.rst import Directive from .pg_logger import exec_script_str_local import json import six @@ -238,7 +237,7 @@ def js_var_finalizer(input_code, output_trace): else: res += '' addHTMLToDB(self.options['divid'], self.options['basecourse'], res % self.options) - raw_node = nodes.raw('', res % self.options, format='html') + raw_node = nodes.raw(self.block_text, res % self.options, format='html') raw_node.source, raw_node.line = self.state_machine.get_source_and_line(self.lineno) return [raw_node] diff --git a/runestone/common/__init__.py b/runestone/common/__init__.py index 9e1586ee3..719e0058e 100644 --- a/runestone/common/__init__.py +++ b/runestone/common/__init__.py @@ -1 +1 @@ -from .runestonedirective import RunestoneDirective +from .runestonedirective import RunestoneDirective, RunestoneNode diff --git a/runestone/common/runestonedirective.py b/runestone/common/runestonedirective.py index 87d260365..cf50c6d90 100644 --- a/runestone/common/runestonedirective.py +++ b/runestone/common/runestonedirective.py @@ -21,6 +21,11 @@ from docutils.parsers.rst import Directive import os +# Provide a class which all Runestone nodes will inherit from. +class RunestoneNode(nodes.Node): + pass + + # Notes # env = self.state.document.settings.env # env.config.html_context['course_id'] diff --git a/runestone/datafile/__init__.py b/runestone/datafile/__init__.py index 6ae1eb38a..f3444c833 100644 --- a/runestone/datafile/__init__.py +++ b/runestone/datafile/__init__.py @@ -19,10 +19,9 @@ from docutils import nodes from docutils.parsers.rst import directives -from docutils.parsers.rst import Directive -from sqlalchemy import create_engine, Table, MetaData, select, delete +from sqlalchemy import Table from runestone.server.componentdb import engine, meta -from runestone.common.runestonedirective import RunestoneDirective +from runestone.common.runestonedirective import RunestoneDirective, RunestoneNode def setup(app): app.add_directive('datafile',DataFile) @@ -43,14 +42,14 @@ def setup(app): %(filecontent)s """ -class DataFileNode(nodes.General, nodes.Element): - def __init__(self,content): +class DataFileNode(nodes.General, nodes.Element, RunestoneNode): + def __init__(self,content, **kwargs): """ Arguments: - `self`: - `content`: """ - super(DataFileNode,self).__init__() + super(DataFileNode,self).__init__(**kwargs) self.df_content = content # self for these functions is an instance of the writer class. For example @@ -166,6 +165,6 @@ def run(self): print("This should only affect the grading interface. Everything else should be fine.") - data_file_node = DataFileNode(self.options) + data_file_node = DataFileNode(self.options, rawsource=self.block_text) data_file_node.source, data_file_node.line = self.state_machine.get_source_and_line(self.lineno) return [data_file_node] diff --git a/runestone/disqus/disqus.py b/runestone/disqus/disqus.py index 34a0c986f..80aabca52 100644 --- a/runestone/disqus/disqus.py +++ b/runestone/disqus/disqus.py @@ -17,7 +17,7 @@ from docutils import nodes from docutils.parsers.rst import directives -from docutils.parsers.rst import Directive +from runestone.common.runestonedirective import RunestoneDirective, RunestoneNode DISQUS_BOX = """\ @@ -70,9 +70,9 @@ def setup(app): app.connect('doctree-resolved' ,process_disqus_nodes) app.connect('env-purge-doc', purge_disqus_nodes) -class DisqusNode(nodes.General, nodes.Element): - def __init__(self,content): - super(DisqusNode,self).__init__() +class DisqusNode(nodes.General, nodes.Element, RunestoneNode): + def __init__(self,content, **kwargs): + super(DisqusNode,self).__init__(**kwargs) self.disqus_components = content @@ -94,7 +94,7 @@ def purge_disqus_nodes(app, env, docname): pass -class DisqusDirective(Directive): +class DisqusDirective(RunestoneDirective): """ .. disqus:: :shortname: Your registered disqus id @@ -116,6 +116,6 @@ def run(self): :return: """ - disqus_node = DisqusNode(self.options) + disqus_node = DisqusNode(self.options, rawsource=self.block_text) disqus_node.source, disqus_node.line = self.state_machine.get_source_and_line(self.lineno) return [disqus_node] diff --git a/runestone/dragndrop/dragndrop.py b/runestone/dragndrop/dragndrop.py index c22e91ca7..896f6fd31 100644 --- a/runestone/dragndrop/dragndrop.py +++ b/runestone/dragndrop/dragndrop.py @@ -20,7 +20,7 @@ from docutils.parsers.rst import directives from docutils.parsers.rst import Directive from runestone.server.componentdb import addQuestionToDB, addHTMLToDB -from runestone.common.runestonedirective import RunestoneDirective +from runestone.common.runestonedirective import RunestoneDirective, RunestoneNode def setup(app): app.add_directive('dragndrop',DragNDrop) @@ -45,14 +45,14 @@ def setup(app): TEMPLATE_END = """""" -class DragNDropNode(nodes.General, nodes.Element): - def __init__(self,content): +class DragNDropNode(nodes.General, nodes.Element, RunestoneNode): + def __init__(self,content, **kwargs): """ Arguments: - `self`: - `content`: """ - super(DragNDropNode,self).__init__() + super(DragNDropNode,self).__init__(**kwargs) self.dnd_options = content # self for these functions is an instance of the writer class. For example @@ -161,7 +161,7 @@ def run(self): self.options['question'] = source - dndNode = DragNDropNode(self.options) + dndNode = DragNDropNode(self.options, rawsource=self.block_text) dndNode.source, dndNode.line = self.state_machine.get_source_and_line(self.lineno) dndNode.template_start = TEMPLATE_START dndNode.template_option = TEMPLATE_OPTION diff --git a/runestone/external/external.py b/runestone/external/external.py index 508612214..1b831f771 100644 --- a/runestone/external/external.py +++ b/runestone/external/external.py @@ -16,11 +16,8 @@ from docutils import nodes from docutils.parsers.rst import directives -from runestone.common.runestonedirective import RunestoneDirective -from docutils.parsers.rst import Directive -from sqlalchemy import create_engine, Table, MetaData, select, delete from runestone.server.componentdb import addQuestionToDB, addHTMLToDB -from runestone.common.runestonedirective import RunestoneDirective +from runestone.common.runestonedirective import RunestoneDirective, RunestoneNode try: from html import escape # py3 @@ -38,9 +35,9 @@ def setup(app): app.add_node(ExternalNode, html=(visit_external_node, depart_external_node)) -class ExternalNode(nodes.General, nodes.Element): - def __init__(self, content): - super(ExternalNode, self).__init__() +class ExternalNode(nodes.General, nodes.Element, RunestoneNode): + def __init__(self, content, **kwargs): + super(ExternalNode, self).__init__(**kwargs) self.external_options = content @@ -103,7 +100,7 @@ def run(self): self.options['name'] = self.arguments[0].strip() - external_node = ExternalNode(self.options) + external_node = ExternalNode(self.options, rawsource=self.block_text) external_node.source, external_node.line = self.state_machine.get_source_and_line(self.lineno) self.add_name(external_node) diff --git a/runestone/fitb/fitb.py b/runestone/fitb/fitb.py index 7b6878797..a99ffe873 100644 --- a/runestone/fitb/fitb.py +++ b/runestone/fitb/fitb.py @@ -21,7 +21,7 @@ from docutils import nodes from docutils.parsers.rst import directives from runestone.server.componentdb import addQuestionToDB, addHTMLToDB -from runestone.common import RunestoneDirective +from runestone.common import RunestoneDirective, RunestoneNode def setup(app): @@ -34,16 +34,15 @@ def setup(app): app.add_node(BlankNode, html=(visit_blank_node, depart_blank_node)) app.add_node(FITBFeedbackNode, html=(visit_fitb_feedback_node, depart_fitb_feedback_node)) - -class FITBNode(nodes.General, nodes.Element): - def __init__(self,content): +class FITBNode(nodes.General, nodes.Element, RunestoneNode): + def __init__(self, content, **kwargs): """ Arguments: - `self`: - `content`: """ - super(FITBNode,self).__init__() + super(FITBNode,self).__init__(**kwargs) self.fitb_options = content # Create a data structure of feedback. self.feedbackArray = [] @@ -135,7 +134,7 @@ def run(self): self.options['divid'] = self.arguments[0] - fitbNode = FITBNode(self.options) + fitbNode = FITBNode(self.options, rawsource=self.block_text) fitbNode.source, fitbNode.line = self.state_machine.get_source_and_line(self.lineno) fitbNode.template_start = TEMPLATE_START fitbNode.template_end = TEMPLATE_END @@ -195,6 +194,7 @@ def run(self): # ... # FITBFeedbackNode(), which contains all the nodes in blank n's feedback_field_body # + self.assert_has_content() feedback_bullet_list = fitbNode.pop() if not isinstance(feedback_bullet_list, nodes.bullet_list): self.error('The last item in a fill-in-the-blank question must be a bulleted list.') @@ -278,9 +278,11 @@ def BlankRole( content=[]): # Blanks ignore all arguments, just inserting a blank. - return [BlankNode(rawtext)], [] + blank_node = BlankNode(rawtext) + blank_node.line = lineno + return [blank_node], [] -class BlankNode(nodes.Inline, nodes.TextElement): +class BlankNode(nodes.Inline, nodes.TextElement, RunestoneNode): pass def visit_blank_node(self, node): @@ -291,7 +293,7 @@ def depart_blank_node(self, node): # Contains feedback for one answer. -class FITBFeedbackNode(nodes.General, nodes.Element): +class FITBFeedbackNode(nodes.General, nodes.Element, RunestoneNode): pass def visit_fitb_feedback_node(self, node): diff --git a/runestone/livecode/livecode.py b/runestone/livecode/livecode.py index 6bd965f03..8eaf0921d 100644 --- a/runestone/livecode/livecode.py +++ b/runestone/livecode/livecode.py @@ -18,10 +18,9 @@ from docutils import nodes from docutils.parsers.rst import directives -from docutils.parsers.rst import Directive -import json import os from jinja2 import Environment, FileSystemLoader +from runestone.common.runestonedirective import RunestoneDirective # try: # import conf @@ -38,7 +37,7 @@ def setup(app): app.add_javascript('livecode.js') app.add_javascript('clike.js') -class LiveCode(Directive): +class LiveCode(RunestoneDirective): required_arguments = 1 optional_arguments = 0 has_content = True @@ -73,7 +72,7 @@ def run(self): template = env.get_template('livecode.html') output = template.render(**self.options) - raw_node = nodes.raw('', output, format='html') + raw_node = nodes.raw(self.block_text, output, format='html') raw_node.source, raw_node.line = self.state_machine.get_source_and_line(self.lineno) return [raw_node] diff --git a/runestone/meta/meta.py b/runestone/meta/meta.py index 1bbee40c1..891824e32 100644 --- a/runestone/meta/meta.py +++ b/runestone/meta/meta.py @@ -17,15 +17,14 @@ from docutils import nodes from docutils.parsers.rst import directives -from docutils.parsers.rst import Directive - +from runestone.common.runestonedirective import RunestoneDirective def setup(app): app.add_directive('shortname',Meta) app.add_directive('description',Meta) -class Meta(Directive): +class Meta(RunestoneDirective): required_arguments = 1 optional_arguments = 50 @@ -35,7 +34,7 @@ def run(self): :param self: :return: """ - raw_node = nodes.raw('','', format='html') + raw_node = nodes.raw(self.block_text,'', format='html') raw_node.source, raw_node.line = self.state_machine.get_source_and_line(self.lineno) return [raw_node] diff --git a/runestone/parsons/parsons.py b/runestone/parsons/parsons.py index b4d522346..919f8af1b 100755 --- a/runestone/parsons/parsons.py +++ b/runestone/parsons/parsons.py @@ -15,13 +15,11 @@ # __author__ = 'isaiahmayerchak' -import re from docutils import nodes from docutils.parsers.rst import directives -from docutils.parsers.rst import Directive from runestone.assess import Assessment from runestone.server.componentdb import addQuestionToDB, addHTMLToDB -from runestone.common.runestonedirective import RunestoneDirective +from runestone.common.runestonedirective import RunestoneDirective, RunestoneNode def setup(app): app.add_directive('parsonsprob', ParsonsProblem) @@ -42,9 +40,9 @@ def setup(app): ''' -class ParsonsNode(nodes.General, nodes.Element): - def __init__(self, options): - super(ParsonsNode, self).__init__() +class ParsonsNode(nodes.General, nodes.Element, RunestoneNode): + def __init__(self, options, **kwargs): + super(ParsonsNode, self).__init__(**kwargs) self.parsonsnode_components = options def visit_parsons_node(self, node): @@ -181,6 +179,6 @@ def findmax(alist): self.assert_has_content() - parsons_node = ParsonsNode(self.options) + parsons_node = ParsonsNode(self.options, rawsource=self.block_text) parsons_node.source, parsons_node.line = self.state_machine.get_source_and_line(self.lineno) return [parsons_node] diff --git a/runestone/poll/poll.py b/runestone/poll/poll.py index 369fe2506..74fa8d04b 100644 --- a/runestone/poll/poll.py +++ b/runestone/poll/poll.py @@ -18,7 +18,7 @@ from docutils import nodes from docutils.parsers.rst import directives -from docutils.parsers.rst import Directive +from runestone.common.runestonedirective import RunestoneDirective, RunestoneNode from runestone.server.componentdb import addQuestionToDB @@ -41,14 +41,14 @@ def setup(app): TEMPLATE_END = """""" -class PollNode(nodes.General, nodes.Element): - def __init__(self,content): +class PollNode(nodes.General, nodes.Element, RunestoneNode): + def __init__(self,content, **kwargs): """ Arguments: - `self`: - `content`: """ - super(PollNode,self).__init__() + super(PollNode,self).__init__(**kwargs) self.poll_content = content # self for these functions is an instance of the writer class. For example @@ -80,7 +80,7 @@ def depart_poll_node(self,node): pass -class Poll(Directive): +class Poll(RunestoneDirective): """ .. poll:: identifier :scale: Mode 1--Implements the "On a scale of 1 to x" type method of poll--x is provided by author @@ -136,6 +136,6 @@ def run(self): else: self.options["comment"] = "" - poll_node = PollNode(self.options) + poll_node = PollNode(self.options, rawsource=self.block_text) poll_node.source, poll_node.line = self.state_machine.get_source_and_line(self.lineno) return [poll_node] diff --git a/runestone/question/question.py b/runestone/question/question.py index 729614da2..66f41e96e 100644 --- a/runestone/question/question.py +++ b/runestone/question/question.py @@ -16,7 +16,7 @@ from docutils import nodes from docutils.parsers.rst import directives -from runestone.common.runestonedirective import RunestoneDirective +from runestone.common.runestonedirective import RunestoneDirective, RunestoneNode __author__ = 'bmiller' @@ -27,9 +27,9 @@ def setup(app): app.add_node(QuestionNode, html=(visit_question_node, depart_question_node)) -class QuestionNode(nodes.General, nodes.Element): - def __init__(self, content): - super(QuestionNode, self).__init__() +class QuestionNode(nodes.General, nodes.Element, RunestoneNode): + def __init__(self, content, **kwargs): + super(QuestionNode, self).__init__(**kwargs) self.question_options = content @@ -92,7 +92,7 @@ def run(self): self.options['name'] = self.arguments[0].strip() - question_node = QuestionNode(self.options) + question_node = QuestionNode(self.options, rawsource=self.block_text) question_node.source, question_node.line = self.state_machine.get_source_and_line(self.lineno) self.add_name(question_node) diff --git a/runestone/reveal/reveal.py b/runestone/reveal/reveal.py index 97e5680bb..b2c4d76e5 100644 --- a/runestone/reveal/reveal.py +++ b/runestone/reveal/reveal.py @@ -17,8 +17,7 @@ from docutils import nodes from docutils.parsers.rst import directives -from docutils.parsers.rst import Directive -from runestone.common.runestonedirective import RunestoneDirective +from runestone.common.runestonedirective import RunestoneDirective, RunestoneNode #add directives/javascript/css def setup(app): @@ -28,9 +27,9 @@ def setup(app): app.add_node(RevealNode, html=(visit_reveal_node, depart_reveal_node)) -class RevealNode(nodes.General, nodes.Element): - def __init__(self,content): - super(RevealNode,self).__init__() +class RevealNode(nodes.General, nodes.Element, RunestoneNode): + def __init__(self,content, **kwargs): + super(RevealNode,self).__init__(**kwargs) self.reveal_options = content @@ -112,7 +111,7 @@ def run(self): self.options['divid'] = self.arguments[0] - reveal_node = RevealNode(self.options) + reveal_node = RevealNode(self.options, rawsource=self.block_text) reveal_node.source, reveal_node.line = self.state_machine.get_source_and_line(self.lineno) self.state.nested_parse(self.content, self.content_offset, reveal_node) diff --git a/runestone/shortanswer/shortanswer.py b/runestone/shortanswer/shortanswer.py index 870d41d60..fec4b0ed2 100755 --- a/runestone/shortanswer/shortanswer.py +++ b/runestone/shortanswer/shortanswer.py @@ -15,14 +15,12 @@ # __author__ = 'isaiahmayerchak' #acbart did most of this code, I mostly just changed the template -import os from docutils import nodes from docutils.parsers.rst import directives -from docutils.parsers.rst import Directive from runestone.assess import Assessment from runestone.server.componentdb import addQuestionToDB, addHTMLToDB -from runestone.common.runestonedirective import RunestoneDirective +from runestone.common.runestonedirective import RunestoneDirective, RunestoneNode def setup(app): app.add_directive('shortanswer', JournalDirective) @@ -37,9 +35,9 @@ def setup(app): """ -class JournalNode(nodes.General, nodes.Element): - def __init__(self, options): - super(JournalNode, self).__init__() +class JournalNode(nodes.General, nodes.Element, RunestoneNode): + def __init__(self, options, **kwargs): + super(JournalNode, self).__init__(**kwargs) self.journalnode_components = options def visit_journal_node(self, node): @@ -82,7 +80,7 @@ def run(self): self.options['divid'] = self.arguments[0] self.options['content'] = "

".join(self.content) self.options['qnum'] = self.getNumber() - journal_node = JournalNode(self.options) + journal_node = JournalNode(self.options, rawsource=self.block_text) journal_node.source, journal_node.line = self.state_machine.get_source_and_line(self.lineno) return [journal_node] diff --git a/runestone/tabbedStuff/tabbedStuff.py b/runestone/tabbedStuff/tabbedStuff.py index dbc072be2..f22947537 100644 --- a/runestone/tabbedStuff/tabbedStuff.py +++ b/runestone/tabbedStuff/tabbedStuff.py @@ -17,7 +17,7 @@ from docutils import nodes from docutils.parsers.rst import directives -from docutils.parsers.rst import Directive +from runestone.common.runestonedirective import RunestoneDirective, RunestoneNode def setup(app): #Add directives/javascript/css @@ -45,8 +45,8 @@ def setup(app): """ class TabNode(nodes.General, nodes.Element): - def __init__(self, content): - super(TabNode, self).__init__() + def __init__(self, content, **kwargs): + super(TabNode, self).__init__(**kwargs) self.tabnode_options = content self.tabname = content['tabname'] @@ -69,7 +69,7 @@ def depart_tab_node(self,node): #Set options and format templates accordingly self.body.append(TABDIV_END) -class TabbedStuffNode(nodes.General, nodes.Element): +class TabbedStuffNode(nodes.General, nodes.Element, RunestoneNode): '''A TabbedStuffNode contains one or more TabNodes''' def __init__(self,content): super(TabbedStuffNode,self).__init__() @@ -101,7 +101,7 @@ def depart_tabbedstuff_node(self,node): -class TabDirective(Directive): +class TabDirective(RunestoneDirective): """ .. tab:: identifier :active: Optional flag that specifies this tab to be opened when page is loaded (default is first tab)--overridden by :inactive: flag on tabbedStuff @@ -139,7 +139,7 @@ def run(self): self.state.nested_parse(self.content, self.content_offset, tab_node) return [tab_node] -class TabbedStuffDirective(Directive): +class TabbedStuffDirective(RunestoneDirective): """ .. tabbed:: identifier :inactive: Optional flag that calls for no tabs to be open on page load @@ -172,7 +172,7 @@ def run(self): self.options['divid'] = self.arguments[0] # Create the node, to be populated by "nested_parse". - tabbedstuff_node = TabbedStuffNode(self.options) + tabbedstuff_node = TabbedStuffNode(self.options, rawsource=self.block_text) tabbedstuff_node.source, tabbedstuff_node.line = self.state_machine.get_source_and_line(self.lineno) # Parse the directive contents (should be 1 or more tab directives) diff --git a/runestone/usageAssignment/__init__.py b/runestone/usageAssignment/__init__.py index 5f50bd29c..b26fd2c72 100644 --- a/runestone/usageAssignment/__init__.py +++ b/runestone/usageAssignment/__init__.py @@ -19,14 +19,12 @@ from docutils import nodes from docutils.parsers.rst import directives -from docutils.parsers.rst import Directive -from sqlalchemy import create_engine, Table, MetaData, select, delete +from sqlalchemy import Table from sqlalchemy.orm import sessionmaker -from runestone.common.runestonedirective import RunestoneDirective +from runestone.common.runestonedirective import RunestoneDirective, RunestoneNode from runestone.server.componentdb import addAssignmentToDB, getOrCreateAssignmentType, getCourseID, addAssignmentQuestionToDB, getOrInsertQuestionForPage, engine, meta from datetime import datetime from collections import OrderedDict -import os def setup(app): app.add_directive('usageassignment',usageAssignment) @@ -36,14 +34,14 @@ def setup(app): app.connect('doctree-resolved',process_nodes) app.connect('env-purge-doc', purge) -class usageAssignmentNode(nodes.General, nodes.Element): - def __init__(self,content): +class usageAssignmentNode(nodes.General, nodes.Element, RunestoneNode): + def __init__(self,content, **kwargs): """ Arguments: - `self`: - `content`: """ - super(usageAssignmentNode,self).__init__() + super(usageAssignmentNode,self).__init__(**kwargs) self.ua_content = content # self for these functions is an instance of the writer class. For example @@ -101,7 +99,7 @@ def purge(app,env,docname): pass -class usageAssignment(Directive): +class usageAssignment(RunestoneDirective): """ .. usageassignment:: prep_1 :chapters: chap_name1[, chapname2]* @@ -242,6 +240,6 @@ def run(self): q_id = getOrInsertQuestionForPage(base_course=basecourse_name, name=acid, is_private='F', question_type="page", autograde = "visited", difficulty=1,chapter=None) addAssignmentQuestionToDB(q_id, assignment_id, 1, autograde="visited") - usage_assignment_node = usageAssignmentNode(self.options) + usage_assignment_node = usageAssignmentNode(self.options, rawsource=self.block_text) usage_assignment_node.source, usage_assignment_node.line = self.state_machine.get_source_and_line(self.lineno) return [usage_assignment_node] diff --git a/runestone/video/video.py b/runestone/video/video.py index 36f529fe7..4b986166c 100644 --- a/runestone/video/video.py +++ b/runestone/video/video.py @@ -128,7 +128,7 @@ def run(self): res += INLINE % self.options addHTMLToDB(self.options['divid'], self.options['basecourse'], res) - return [nodes.raw('',res , format='html')] + return [nodes.raw(self.block_text, res, format='html')] """ @@ -177,7 +177,7 @@ def run(self): self.options['height'] = self.default_height if not self.options.get('align'): self.options['align'] = 'left' - raw_node = nodes.raw('', self.html % self.options, format='html') + raw_node = nodes.raw(self.block_text, self.html % self.options, format='html') raw_node.source, raw_node.line = self.state_machine.get_source_and_line(self.lineno) return [raw_node]