Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authormike <michael.jmontcalm@gmail.com>2009-04-21 19:14:59 (GMT)
committer mike <michael.jmontcalm@gmail.com>2009-04-21 19:14:59 (GMT)
commitaa5cba82a4ae34f60bb6a5f1c585c6c13759d312 (patch)
tree74eddd6da80a26a83f06c49b24e183a64986cfac /src
parent841ecb1a4b807680cbba283bd6b2594848c6dda9 (diff)
LP 341760 Core : Draft architecture for XML loading
Diffstat (limited to 'src')
-rw-r--r--src/sugar/tutorius/actions.py2
-rw-r--r--src/sugar/tutorius/bundler.py262
-rw-r--r--src/sugar/tutorius/tests/serializertests.py184
3 files changed, 345 insertions, 103 deletions
diff --git a/src/sugar/tutorius/actions.py b/src/sugar/tutorius/actions.py
index ad91fb4..2dca6c7 100644
--- a/src/sugar/tutorius/actions.py
+++ b/src/sugar/tutorius/actions.py
@@ -157,7 +157,7 @@ class BubbleMessage(Action):
def set_pos(self, x, y):
self._position = [x, y]
def get_pos(self):
- return self.position
+ return self._position
position = property(fget=get_pos, fset=set_pos, doc="Position in [x, y] on the screen")
def set_tail_pos(self, x, y):
diff --git a/src/sugar/tutorius/bundler.py b/src/sugar/tutorius/bundler.py
index 6f61779..37de0ac 100644
--- a/src/sugar/tutorius/bundler.py
+++ b/src/sugar/tutorius/bundler.py
@@ -24,17 +24,17 @@ import logging
import os
import uuid
import xml.dom.minidom
-import xml.dom.ext
+#import xml.dom.ext
from sugar.tutorius import gtkutils, overlayer
from sugar.tutorius.core import Tutorial, State, FiniteStateMachine
-import sugar.tutorius.actions
+from sugar.tutorius.actions import *
import sugar.tutorius.filters
from ConfigParser import SafeConfigParser
-def __get_store_root():
+def _get_store_root():
return os.path.join(os.getenv("SUGAR_PREFIX"),"share","tutorius","data")
-def __get_bundle_root():
+def _get_bundle_root():
return os.path.join(os.getenv("SUGAR_BUNDLE_PATH"),"data","tutorius","data")
INI_ACTIVITY_SECTION = "RELATED_ACTIVITIES"
@@ -51,8 +51,8 @@ class TutorialStore:
given activity.
"""
- store_root = __get_store_root()
- bundle_root = __get_bundle_root()
+ store_root = _get_store_root()
+ bundle_root = _get_bundle_root()
logging.debug("*********** Path of store_root : " + store_root)
@@ -125,10 +125,11 @@ class XMLSerializer(Serializer):
"""
statesList = doc.createElement("States")
for state_name, state in state_dict.items():
- stateNode = statesList.appendChild("State")
- stateNode.setAttribute("State:Name", state_name)
- stateNode = stateNode.appendChild(create_action_list_node(state.action_list, doc))
- stateNode = stateNode.appendChild(create_event_filters_node(state.event_filters, doc))
+ stateNode = doc.createElement("State")
+ statesList.appendChild(stateNode)
+ stateNode.setAttribute("Name", state_name)
+ stateNode.appendChild(self.create_action_list_node(state.get_action_list(), doc))
+ stateNode.appendChild(self.create_event_filters_node(state.get_event_filter_list(), doc))
return statesList
def create_action_list_node(self, action_list, doc):
@@ -137,32 +138,33 @@ class XMLSerializer(Serializer):
"""
actionsList = doc.createElement("Actions")
for action in action_list:
- actionNode = actionsList.appendChild("Action")
+ actionNode = doc.createElement("Action")
+ actionsList.appendChild(actionNode)
if type(action) is DialogMessage:
- actionNode.setAttribute("Action:Class", type(action))
- actionNode.setAttribute("Action:Message", action.message)
- actionNode.setAttribute("Action:Position", action.position)
+ actionNode.setAttribute("Class", type(action))
+ actionNode.setAttribute("Message", action.message)
+ actionNode.setAttribute("Position", action.position)
elif type(action) is BubbleMessage:
- actionNode.setAttribute("Action:Class", str(type(action)))
- actionNode.setAttribute("Action:Message", action.message)
- actionNode.setAttribute("Action:Position", action.position)
- actionNode.setAttribute("Action:Tail_pos", action.tail_pos)
+ actionNode.setAttribute("Class", str(type(action)))
+ actionNode.setAttribute("Message", action.message)
+ actionNode.setAttribute("Position", str(action.position))
+ actionNode.setAttribute("Tail_pos", str(action.tail_pos))
# TODO : elif for each type of action
elif type(action) is WidgetIdentifyAction:
- actionNode.setAttribute("Action:Class", str(type(action)))
+ actionNode.setAttribute("Class", str(type(action)))
# TODO
elif type(action) is ChainAction:
# TODO
- actionNode.setAttribute("Action:Class", str(type(action)))
+ actionNode.setAttribute("Class", str(type(action)))
elif type(action) is DisableWidgetAction:
# TODO
- actionNode.setAttribute("Action:Class", str(type(action)))
+ actionNode.setAttribute("Class", str(type(action)))
elif type(action) is TypeTextAction:
# TODO
- actionNode.setAttribute("Action:Class", str(type(action)))
+ actionNode.setAttribute("Class", str(type(action)))
elif type(action) is ClickAction:
# TODO
- actionNode.setAttribute("Action:Class", str(type(action)))
+ actionNode.setAttribute("Class", str(type(action)))
return actionsList
@@ -170,7 +172,7 @@ class XMLSerializer(Serializer):
"""
Create and return a xml Node from a event filters.
"""
- eventFiltersList = doc.createElement("EventFiltersList")
+ eventFiltersList = doc.createElement("EventFilters")
for event_f in event_filters:
eventFilterNode = eventFiltersList.appendChild("EventFilter")
# TODO : elif for each type of event filters
@@ -189,28 +191,214 @@ class XMLSerializer(Serializer):
def save_fsm(self, fsm, xml_filename, path):
"""
Save fsm to disk, in the xml file specified by "xml_filename", in the
- "path" folder. If the specified file dont exist, it will be created.
+ "path" folder. If the specified file doesn't exist, it will be created.
"""
doc = xml.dom.minidom.Document()
fsm_element = doc.createElement("FSM")
doc.appendChild(fsm_element)
- fsm_element.setAttribute("fsm:Name", fsm.name)
- fsm_element.setAttribute("fsm:StartStateName", fsm.start_state_name)
- fsm_element = fsm_element.appendChild(create_state_dict_node(fsm.state_dict, doc))
- fsm_element = fsm_element.appendChild(create_action_list_node(fsm.action_list, doc))
+ fsm_element.setAttribute("Name", fsm.name)
+ fsm_element.setAttribute("StartStateName", fsm.start_state_name)
+ fsm_element.appendChild(self.create_state_dict_node(fsm._states, doc))
+ fsm_element.appendChild(self.create_action_list_node(fsm.actions, doc))
- file_object = open(path + "/" + xml_filename, "w")
- xml.dom.ext.PrettyPrint(doc, file_object)
+ file_object = open(os.path.join(path, xml_filename), "w")
+ # Commented for DEBUG purposes
+ #xml.dom.ext.PrettyPrint(doc, file_object)
+ file_object.write(doc.toprettyxml())
file_object.close()
+ def _find_tutorial_dir_with_guid(self, guid):
+ """
+ Finds the tutorial with the associated GUID. If it is found, return
+ the path to the tutorial's directory. If it doesn't exist, raise an
+ IOError.
+
+ A note : if there are two tutorials with this GUID in the folders,
+ they will both be inspected and the one with the highest version
+ number will be returned. If they have the same version number, the one
+ from the global store will be returned.
+
+ @param guid The GUID of the tutorial that is to be loaded.
+ """
+ # Attempt to find the tutorial's directory in the global directory
+ global_dir = os.path.join(_get_store_root(), guid)
+ # Then in the activty's bundle path
+ activity_dir = os.path.join(_get_bundle_root(), guid)
+
+ # If they both exist
+ if os.path.isdir(global_dir) and os.path.isdir(activity_dir):
+ # Inspect both metadata files
+ global_meta = os.path.join(global_dir, "meta.ini")
+ activity_meta = os.path.join(activity_dir, "meta.ini")
+
+ # Open both config files
+ global_parser = SafeConfigParser()
+ global_parser.read(global_meta)
+
+ activity_parser = SafeConfigParser()
+ activity_parser.read(activity_meta)
+
+ # Get the version number for each tutorial
+ global_version = global_parser.get(INI_METADATA_SECTION, "version")
+ activity_version = activity_parser.get(INI_METADATA_SECTION, "version")
+
+ # If the global version is higher or equal, we'll take it
+ if global_version >= activity_version:
+ return global_dir
+ else:
+ return activity_dir
+
+ # Do we just have the global directory?
+ if os.path.isdir(global_dir):
+ return global_dir
+
+ # Or just the activity's bundle directory?
+ if os.path.isdir(activity_dir):
+ return activity_dir
+
+ # Error : none of these directories contain the tutorial
+ raise IOError(2, "Neither the global nor the bundle directory contained the tutorial with GUID %s"%guid)
+
+ def _load_xml_properties(self, properties_elem):
+ """
+ Changes a list of properties into fully instanciated properties.
+
+ @param properties_elem An XML element reprensenting a list of
+ properties
+ """
+ return []
+
+ def _load_xml_event_filters(self, filters_elem):
+ """
+ Loads up a list of Event Filters.
+
+ @param filters_elem An XML Element representing a list of event filters
+ """
+ filter_object_list = []
+
+ filter_node_list = filters_elem.getElementsByTagName("EventFilter")
+
+ for filter_node in filter_node_list:
+ class_name = filter_node.getAttribute("Class")
+
+ #if class_name == "GtkWidgetEventFilter":
+ # event_name =
+
+ return filter_object_list
+
+ def _load_xml_actions(self, actions_elem):
+ """
+ Transforms an Actions element into a list of instanciated Action.
+
+ @param actions_elem An XML Element representing a list of Actions
+ """
+ action_object_list = []
+
+ action_node_list = actions_elem.getElementsByTagName("Action")
+
+ for action_node in action_node_list:
+ class_name = action_node.getAttribute("Class")
+ action_object = None
+ # TODO : We will want an automatic way of registering new actions?
+ # I'm thinking of the case where we add an action to the core
+ # and we don't want to have to write this XML interop bit every
+ # time.
+ if class_name == "<class 'sugar.tutorius.actions.BubbleMessage'>":
+ # Get the message, position and tail_position
+ message = action_node.getAttribute("Message")
+ position = action_node.getAttribute("Position")
+ tail_pos = action_node.getAttribute("Tail_Pos")
+ action_object = BubbleMessage(message, [0,0])
+ # Elif for all the other actions...
+
+ if action_object is not None:
+ action_object_list.append(action_object)
+
+ return action_object_list
+
+ def _load_xml_states(self, states_elem):
+ """
+ Takes in a States element and fleshes out a complete list of State
+ objects.
+
+ @param states_elem An XML Element that represents a list of States
+ """
+ state_node_list = states_elem.getElementsByTagName("State")
+
+ state_object_list = []
+
+ for state_node in state_node_list:
+ # Get the state name
+ state_name = state_node.getAttribute("Name")
+
+ actions_node_list = state_node.getElementsByTagName("Actions")
+ action_object_list = []
+ if len(actions_node_list) > 0:
+ action_object_list = self._load_xml_actions(actions_node_list[0])
+
+ #filters_node_list = state_node.getElementsByTagName("EventFilters")
+ filter_object_list = self._load_xml_event_filters(state_node.getElementsByTagName("EventFilters")[0])
+ state_object = State(state_name, action_list=action_object_list, event_filter_list = filter_object_list)
+
+ state_object_list.append(state_object)
+
+ return state_object_list
+
+ def _load_xml_fsm(self, fsm_elem):
+ """
+ Takes in an XML element representing an FSM and returns the fully
+ crafted FSM.
+
+ @param fsm_elem The XML element that describes a FSM
+ """
+ # Load the FSM's name and start state's name
+ fsm_name = fsm_elem.getAttribute("Name")
+
+ fsm_start_state_name = None
+ try:
+ fsm_start_state_name = fsm_elem.getAttribute("StartStateName")
+ except:
+ pass
+
+ fsm = FiniteStateMachine(fsm_name, start_state_name=fsm_start_state_name)
+
+ # Load the states
+ states = self._load_xml_states(fsm_elem.getElementsByTagName("States")[0])
+ for state in states:
+ fsm.add_state(state)
+
+ # Load the actions on this FSM
+ actions = self._load_xml_actions(fsm_elem.getElementsByTagName("Actions")[0])
+ for action in actions:
+ fsm.add_action(action)
+
+ # Load the event filters
+ events = self._load_xml_event_filters(fsm_elem.getElementsByTagName("EventFilters")[0])
+ for event in events:
+ fsm.add_event_filter(event)
+
+ return fsm
+
+
def load_fsm(self, guid):
"""
- Load fsm from xml file who .ini file guid match argument guid.
+ Load fsm from xml file whose .ini file guid match argument guid.
"""
+ # Fetch the directory (if any)
+ tutorial_dir = self._find_tutorial_dir_with_guid(guid)
+
+ # Open the XML file
+ tutorial_file = os.path.join(tutorial_dir, "fsm.xml")
+ xml_dom = xml.dom.minidom.parse(tutorial_file)
+ fsm_elem = xml_dom.getElementsByTagName("FSM")[0]
+
+ return self._load_xml_fsm(fsm_elem)
+
+
class TutorialBundler:
"""
This class provide the various data handling methods useable by the tutorial
@@ -230,12 +418,12 @@ class TutorialBundler:
#Look for the file in the path if a uid is supplied
if generated_guid:
#General store
- store_path = os.path.join(__get_store_root(), generated_guid, INI_FILENAME)
+ store_path = os.path.join(_get_store_root(), generated_guid, INI_FILENAME)
if os.path.isfile(store_path):
self.Path = os.path.dirname(store_path)
else:
#Bundle store
- bundle_path = os.path.join(__get_bundle_root(), generated_guid, INI_FILENAME)
+ bundle_path = os.path.join(_get_bundle_root(), generated_guid, INI_FILENAME)
if os.path.isfile(bundle_path):
self.Path = os.path.dirname(bundle_path)
else:
@@ -243,7 +431,7 @@ class TutorialBundler:
else:
#Create the folder, any failure will go through to the caller for now
- store_path = os.path.join(__get_store_root(), generated_guid)
+ store_path = os.path.join(_get_store_root(), generated_guid)
os.mkdir(store_path)
self.Path = store_path
@@ -292,8 +480,8 @@ class TutorialBundler:
more than one path, the store_root is given priority.
"""
- store_root = __get_store_root()
- bundle_root = __get_bundle_root()
+ store_root = _get_store_root()
+ bundle_root = _get_bundle_root()
config = SafeConfigParser()
path = None
diff --git a/src/sugar/tutorius/tests/serializertests.py b/src/sugar/tutorius/tests/serializertests.py
index 7a31602..9a21d08 100644
--- a/src/sugar/tutorius/tests/serializertests.py
+++ b/src/sugar/tutorius/tests/serializertests.py
@@ -34,87 +34,141 @@ from sugar.tutorius import gtkutils, overlayer
from sugar.tutorius.core import Tutorial, State, FiniteStateMachine
from sugar.tutorius.actions import DialogMessage, OnceWrapper, BubbleMessage
from sugar.tutorius.filters import GtkWidgetEventFilter, TimerEvent
-from sugar.tutorius.tutoserialize import TutoSerializer
+from sugar.tutorius.bundler import *
+from uuid import *
+import rpdb2
-# Helper classes to help testing
-
-
-
-
-class SerializerTest(unittest.TestCase):
+class XMLSerializerTest(unittest.TestCase):
"""
- This class has to test the Serializer methods as well as the expected
- functionality.
+ Tests the transformation of XML to FSM, then back.
"""
+ def setUp(self):
+ # Create the sample FSM
+ self.fsm = FiniteStateMachine("testingMachine")
- def test_pickle_integrity(self):
- """
- Validates content is uncorrupted trough a pickle file save/load.
- """
+ # Add a few states
+ act1 = BubbleMessage(message="Hi", pos=[300, 450])
+ ev1 = GtkWidgetEventFilter("0.12.31.2.2", "clicked", "Second")
+ act2 = BubbleMessage(message="Second message", pos=[250, 150], tailpos=[1,2])
- # Sample valid FSM dict
- sampleDict = {
- "INIT":State("INIT",
- action_list=[
- OnceWrapper(BubbleMessage(message="Welcome to the text editor tutorial!\n\n Click on the canvas and type a letter.", pos=[100,100], tailpos=[-10,-20])),
- ],
- event_filter_list=[
- GtkWidgetEventFilter("TEXT","0.0.0.1.0.0.0","key-press-event"),
- TimerEvent("LOST",15),
- ],
- ),
- "LOST":State("LOST",
- action_list=[BubbleMessage("Click in the canvas and type on your keyboard", [400, 400]),],
- event_filter_list=[
- GtkWidgetEventFilter("TEXT","0.0.0.1.0.0.0","key-press-event"),
- TimerEvent("INIT",5),
- ],
- ),
- "TEXT":State("TEXT",
- action_list=[OnceWrapper(BubbleMessage(" You can type more letters if you want!\n\n" +
- "To proceed to the next step, select your text.\n\n Click and drag over the text!", [200,150])),],
- event_filter_list=[
- GtkWidgetEventFilter("SELECTED","0.0.0.1.0.0","text-selected"),
- ],
- ),
- }
-
- testpath = "/tmp/testdata/"
+ st1 = State("INIT")
+ st1.add_action(act1)
- # Create testdata/ folder if no exists
- if not os.path.exists(testpath):
- os.mkdir(testpath)
+ st2 = State("Second")
- serialize = TutoSerializer()
+ st2.add_action(act2)
- # Make the class believe the test is in a activity path
- os.environ["SUGAR_ACTIVITY_ROOT"] = testpath
+ self.fsm.add_state(st1)
+ self.fsm.add_state(st2)
- fsm = FiniteStateMachine("Test", state_dict=sampleDict)
+ self.uuid = uuid1()
+
+ def test_save(self):
+ """
+ Writes an FSM to disk, then compares the file to the expected results.
+ """
+ xml_ser = XMLSerializer()
+ os.mkdir(os.path.join(sugar.tutorius.bundler._get_store_root(), str(self.uuid)))
+ #rpdb2.start_embedded_debugger('flakyPass')
+ xml_ser.save_fsm(self.fsm, "fsm.xml", os.path.join(sugar.tutorius.bundler._get_store_root(), str(self.uuid)))
+
+ # Compare the two files
- serialize.save_tutorial("Test", "Test", fsm, "serializeTest")
+
+ def test_save_and_load(self):
+ """
+ Load up the written FSM and compare it with the object representation.
+ """
+ self.test_save()
- fileDict = serialize.load_tuto_list()
+ #rpdb2.start_embedded_debugger('flakyPass')
+ xml_ser = XMLSerializer()
- for filekey, tutorial in fileDict.items():
- if filekey == "Test":
- reformedTuto = serialize.build_tutorial(filekey)
-
- reformedfsm = reformedTuto.get("Test").state_machine
+ rpdb2.start_embedded_debugger('pass')
+ # This interface needs to be redone... It's not clean because there is
+ # a responsibility mixup between the XML reader and the bundler.
+ loaded_fsm = xml_ser.load_fsm(str(self.uuid))
- #Tests
- assert reformedfsm._states.get("INIT").name == fsm._states.get("INIT").name, \
- 'FSM underlying dictionary differ from original to pickled/reformed one'
- assert reformedfsm._states.get("LOST").name == fsm._states.get("LOST").name, \
- 'FSM underlying dictionary differ from original to pickled/reformed one'
- assert reformedfsm._states.get("TEXT").name == fsm._states.get("TEXT").name, \
- 'FSM underlying dictionary differ from original to pickled/reformed one'
+ # Compare the two FSMs
+
+# Helper classes to help testing
+class SerializerTest(unittest.TestCase):
+ """
+ This class has to test the Serializer methods as well as the expected
+ functionality.
+ """
- os.remove(testpath + "serializeTest.tml")
- os.rmdir(testpath)
- os.rmdir("/tmp")
+ # Voiding test as it is meant to be used with the pickle serializer,
+ # that was deprecated
+## def test_pickle_integrity(self):
+## """
+## Validates content is uncorrupted trough a pickle file save/load.
+## """
+##
+## # Sample valid FSM dict
+## sampleDict = {
+## "INIT":State("INIT",
+## action_list=[
+## OnceWrapper(BubbleMessage(message="Welcome to the text editor tutorial!\n\n Click on the canvas and type a letter.", pos=[100,100], tailpos=[-10,-20])),
+## ],
+## event_filter_list=[
+## GtkWidgetEventFilter("TEXT","0.0.0.1.0.0.0","key-press-event"),
+## TimerEvent("LOST",15),
+## ],
+## ),
+## "LOST":State("LOST",
+## action_list=[BubbleMessage("Click in the canvas and type on your keyboard", [400, 400]),],
+## event_filter_list=[
+## GtkWidgetEventFilter("TEXT","0.0.0.1.0.0.0","key-press-event"),
+## TimerEvent("INIT",5),
+## ],
+## ),
+## "TEXT":State("TEXT",
+## action_list=[OnceWrapper(BubbleMessage(" You can type more letters if you want!\n\n" +
+## "To proceed to the next step, select your text.\n\n Click and drag over the text!", [200,150])),],
+## event_filter_list=[
+## GtkWidgetEventFilter("SELECTED","0.0.0.1.0.0","text-selected"),
+## ],
+## ),
+## }
+##
+## testpath = "/tmp/testdata/"
+##
+## # Create testdata/ folder if no exists
+## if not os.path.exists(testpath):
+## os.mkdir(testpath)
+##
+## serialize = TutoSerializer()
+##
+## # Make the class believe the test is in a activity path
+## os.environ["SUGAR_ACTIVITY_ROOT"] = testpath
+##
+## fsm = FiniteStateMachine("Test", state_dict=sampleDict)
+##
+## serialize.save_tutorial("Test", "Test", fsm, "serializeTest")
+##
+## fileDict = serialize.load_tuto_list()
+##
+## for filekey, tutorial in fileDict.items():
+## if filekey == "Test":
+## reformedTuto = serialize.build_tutorial(filekey)
+##
+## reformedfsm = reformedTuto.get("Test").state_machine
+##
+## #Tests
+## assert reformedfsm._states.get("INIT").name == fsm._states.get("INIT").name, \
+## 'FSM underlying dictionary differ from original to pickled/reformed one'
+## assert reformedfsm._states.get("LOST").name == fsm._states.get("LOST").name, \
+## 'FSM underlying dictionary differ from original to pickled/reformed one'
+## assert reformedfsm._states.get("TEXT").name == fsm._states.get("TEXT").name, \
+## 'FSM underlying dictionary differ from original to pickled/reformed one'
+##
+##
+## os.remove(testpath + "serializeTest.tml")
+## os.rmdir(testpath)
+## os.rmdir("/tmp")
if __name__ == "__main__":