diff --git a/runestone/activecode/activecode.py b/runestone/activecode/activecode.py index 99f4fb8a5..03c1b3799 100644 --- a/runestone/activecode/activecode.py +++ b/runestone/activecode/activecode.py @@ -19,12 +19,10 @@ from docutils import nodes from docutils.parsers.rst import directives -from docutils.parsers.rst import Directive from .textfield import * -from sqlalchemy import create_engine, Table, MetaData, select, delete -from runestone.server import get_dburl +from sqlalchemy import Table from runestone.server.componentdb import addQuestionToDB, addHTMLToDB, engine, meta -from runestone.common.runestonedirective import RunestoneDirective +from runestone.common.runestonedirective import RunestoneDirective, RunestoneNode try: from html import escape # py3 @@ -69,15 +67,15 @@ def setup(app): """ -class ActivcodeNode(nodes.General, nodes.Element): - def __init__(self, content): +class ActivcodeNode(nodes.General, nodes.Element, RunestoneNode): + def __init__(self, content, **kwargs): """ Arguments: - `self`: - `content`: """ - super(ActivcodeNode, self).__init__(name=content['name']) + super(ActivcodeNode, self).__init__(name=content['name'], **kwargs) self.ac_components = content @@ -352,7 +350,8 @@ def run(self): print("This should only affect the grading interface. Everything else should be fine.") - acnode = ActivcodeNode(self.options) + acnode = ActivcodeNode(self.options, rawsource=self.block_text) + 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/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") diff --git a/runestone/animation/animation.py b/runestone/animation/animation.py index 07cdbfbcb..13b1b3fcd 100644 --- a/runestone/animation/animation.py +++ b/runestone/animation/animation.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 def setup(app): @@ -52,7 +52,7 @@ def setup(app): SCRIPTTAG = '''\n''' -class Animation(Directive): +class Animation(RunestoneDirective): required_arguments = 1 optional_arguments = 1 final_argument_whitespace = True @@ -80,7 +80,9 @@ def run(self): res = res + SRC % self.options - return [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] source = ''' diff --git a/runestone/assess/assess.py b/runestone/assess/assess.py index 9418940c6..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,10 +79,12 @@ def run(self): res = TEMPLATE_START % self.options res += TEMPLATE_END % self.options - return [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 8c5ddfb19..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,8 @@ 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 mcNode.template_end = TEMPLATE_END @@ -277,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 `` ''' -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,8 @@ 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 69376a1c0..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,6 +156,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, rawsource=self.block_text) + 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..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 @@ -134,14 +133,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'; - }; + 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) { + function my_custom_print(text,divid) { var p = document.getElementById(divid+"_pre"); p.innerHTML += text + "\\n" } @@ -151,7 +150,7 @@ def __init__(self,content): Blockly.Xml.domToWorkspace(Blockly.mainWorkspace, xmlDom); - +

   
   
@@ -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
@@ -213,7 +212,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 +220,23 @@ def run(self):
 
         if self.content:
             self.options['controls'] = self.content[:plstart]
-        
-        return [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]
 
 
 
 
 '''
-    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 +245,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..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,8 @@ 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
 
         if "table" in self.options:
diff --git a/runestone/codelens/visualizer.py b/runestone/codelens/visualizer.py
index dc80afec8..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,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(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]
 
     def inject_questions(self, curTrace):
         if 'breakline' not in self.options:
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 4ca750c5b..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,4 +165,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, 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 19d9ed4a6..80aabca52 100644
--- a/runestone/disqus/disqus.py
+++ b/runestone/disqus/disqus.py
@@ -17,12 +17,12 @@
 
 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 = """\