[Zope-CVS] SVN: zope.tutorial/trunk/ A really, really bad attempt to get the tutorial working on a sample

Stephan Richter srichter at cosmos.phy.tufts.edu
Sun Nov 20 22:46:47 EST 2005


Log message for revision 40291:
  A really, really bad attempt to get the tutorial working on a sample 
  doctest. But once you install jsonserver, it will work. I have some new 
  ideas, so the next revision should be *much* cleaner.
  
  

Changed:
  U   zope.tutorial/trunk/browser/configure.zcml
  U   zope.tutorial/trunk/browser/index.pt
  A   zope.tutorial/trunk/browser/runner_macros.pt
  A   zope.tutorial/trunk/browser/tutorial-browser.js
  A   zope.tutorial/trunk/browser/tutorial.css
  A   zope.tutorial/trunk/browser/tutorial.py
  A   zope.tutorial/trunk/browser/tutorials-runner.js
  U   zope.tutorial/trunk/cli.py
  U   zope.tutorial/trunk/configure.zcml
  U   zope.tutorial/trunk/interfaces.py
  U   zope.tutorial/trunk/manager.py
  A   zope.tutorial/trunk/runner.py
  A   zope.tutorial/trunk/sample_tutorial.txt
  D   zope.tutorial/trunk/selenium.py
  U   zope.tutorial/trunk/session.txt
  A   zope.tutorial/trunk/testbrowser.py
  U   zope.tutorial/trunk/tutorial.py
  U   zope.tutorial/trunk/tutorials.zcml

-=-
Modified: zope.tutorial/trunk/browser/configure.zcml
===================================================================
--- zope.tutorial/trunk/browser/configure.zcml	2005-11-21 03:29:06 UTC (rev 40290)
+++ zope.tutorial/trunk/browser/configure.zcml	2005-11-21 03:46:47 UTC (rev 40291)
@@ -1,6 +1,7 @@
 <configure
     xmlns="http://namespaces.zope.org/browser"
     xmlns:zope="http://namespaces.zope.org/zope"
+    xmlns:jsonrpc="http://namespaces.zope.org/jsonrpc"
     i18n_domain="zope">
 
   <resource
@@ -8,6 +9,16 @@
       file="tutorial.css"
       />
 
+  <resource
+      name="tutorials-runner.js"
+      file="tutorials-runner.js"
+      />
+
+  <resource
+      name="tutorial-browser.js"
+      file="tutorial-browser.js"
+      />
+
   <page
       for="*"
       name="tutorial_macros"
@@ -31,6 +42,22 @@
       permission="zope.View"
       />
 
+  <!-- JSON Methods -->
+
+  <jsonrpc:view
+      for="..interfaces.ITutorialSessionManager"
+      methods="createSession deleteSession"
+      class=".tutorial.TutorialSessionManager"
+      permission="zope.View"
+      />
+
+  <jsonrpc:view
+      for="..interfaces.ITutorialSession"
+      methods="getNextStep setCommandResult keepGoing"
+      class=".tutorial.TutorialSession"
+      permission="zope.View"
+      />
+
   <!-- Make Selenium available -->
   <resourceDirectory
       name="selenium"

Modified: zope.tutorial/trunk/browser/index.pt
===================================================================
--- zope.tutorial/trunk/browser/index.pt	2005-11-21 03:29:06 UTC (rev 40290)
+++ zope.tutorial/trunk/browser/index.pt	2005-11-21 03:46:47 UTC (rev 40291)
@@ -6,39 +6,30 @@
           href="/@@/tutorial.css" />
     <script
         language="JavaScript" type="text/javascript"
-        src="/@@/selenium/jsunit/app/jsUnitCore.js"></script>
+        src="/@@/selenium/htmlutils.js"></script>
     <script
         language="JavaScript" type="text/javascript"
-        src="/@@/selenium/xmlextras.js"></script>
-    <script
-        language="JavaScript" type="text/javascript"
         src="/@@/selenium/selenium-browserbot.js"></script>
     <script
         language="JavaScript" type="text/javascript"
-        src="/@@/selenium/selenium-api.js"></script>
+        src="/@@/selenium/xpath.js"></script>
     <script
         language="JavaScript" type="text/javascript"
-        src="/@@/selenium/selenium-commandhandlers.js"></script>
+        src="/@@/jsolait/init.js"></script>
     <script
         language="JavaScript" type="text/javascript"
-        src="/@@/selenium/selenium-executionloop.js"></script>
+        src="/@@/jsolait/lib/urllib.js"></script>
     <script
         language="JavaScript" type="text/javascript"
-        src="/@@/selenium/selenium-seleneserunner.js"></script>
+        src="/@@/jsolait/lib/jsonrpc.js"></script>
     <script
         language="JavaScript" type="text/javascript"
-        src="/@@/selenium/selenium-logging.js"></script>
+        src="/@@/tutorial-browser.js"></script>
     <script
         language="JavaScript" type="text/javascript"
-        src="/@@/selenium/htmlutils.js"></script>
-    <script
-        language="JavaScript" type="text/javascript"
-        src="/@@/selenium/xpath.js"></script>
-    <script
-        language="JavaScript" type="text/javascript"
-        src="/@@/selenium/user-extensions.js"></script>
+        src="/@@/tutorials-runner.js"></script>
   </head>
-  <body>
+  <body onload="javascript:initializeTutorialRunner()">
 
     <table width="100%" style="height: 100%;">
       <tr>
@@ -46,13 +37,12 @@
           <metal:block use-macro="context/@@tutorial_macros/runner" />
         </td>
         <td width="70%" height="30%">
-          <b>Last Four Commands</b><br/>
-          <div id="commandList"></div>
+          <div id="tutorial-text"></div>
         </td>
       </tr>
       <tr>
         <td colspan="2" height="70%">
-          <iframe name="myiframe" id="myiframe" src=""
+          <iframe name="tutorial-content" id="tutorial-content" src=""
                   height="100%" width="100%"></iframe>
         </td>
       </tr>

Added: zope.tutorial/trunk/browser/runner_macros.pt
===================================================================
--- zope.tutorial/trunk/browser/runner_macros.pt	2005-11-21 03:29:06 UTC (rev 40290)
+++ zope.tutorial/trunk/browser/runner_macros.pt	2005-11-21 03:46:47 UTC (rev 40291)
@@ -0,0 +1,30 @@
+<metal:block define-macro="runner">
+
+  <h1 class="main">Tutorials Runner</h1>
+
+  <div style="padding: 3px">
+    <p>
+      Please select one of the tutorials:
+    </p>
+
+    <div style="text-align: center">
+      <select id="tutorial-selector" style="width: 80%">
+        <option tal:repeat="tutorial context/values"
+                tal:attributes="value tutorial/zope:name"
+                tal:content="tutorial/title">
+          Tutorial 1
+        </option>
+      </select>
+    </div>
+
+    <p style="text-align: center">
+      <input type="submit" id="start-button" name="START" value="Start"
+             onclick="javascript:startTutorial()"/>
+      <input type="submit" id="stop-button" name="STOP" value="Stop"
+             style="display: None" onclick="javascript:stopTutorial()"/>
+      <input type="submit" id="next-button" name="NEXT" value="Next"
+             style="display: None" onclick="javascript:runNextStep()"/>
+    </p>
+  </div>
+
+</metal:block>
\ No newline at end of file


Property changes on: zope.tutorial/trunk/browser/runner_macros.pt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: zope.tutorial/trunk/browser/tutorial-browser.js
===================================================================
--- zope.tutorial/trunk/browser/tutorial-browser.js	2005-11-21 03:29:06 UTC (rev 40290)
+++ zope.tutorial/trunk/browser/tutorial-browser.js	2005-11-21 03:46:47 UTC (rev 40291)
@@ -0,0 +1,100 @@
+var commands = {};
+
+function getContentDocument() {
+    return frames['tutorial-content'].document;
+}
+
+function setHighlighted(element) {
+    element.style.border = "1px red solid";
+}
+
+function setOriginal(element, original) {
+    element.style.border = original;
+}
+
+function blinkElement(element) {
+    var original = element.style.border;
+
+    frames['tutorial-content'].setTimeout("setHighlighted(element)", 1000);
+    frames['tutorial-content'].setTimeout("setOriginal(element, original)", 1000);
+}
+
+function nullAction() {
+}
+commands['nullAction'] = nullAction;
+
+function displayText(text) {
+    div = bot.locateElementByIdentifier('tutorial-text', document);
+    div.innerHTML = text;
+}
+commands['displayText'] = displayText;
+
+
+function finishTutorial() {
+    displayText(Array(''));
+    stopTutorial();
+}
+commands['finishTutorial'] = finishTutorial
+
+/* ------------------------------------------------------------------------ */
+
+function openUrl(url) {
+    iframe = bot.locateElementByIdentifier('tutorial-content', document);
+    iframe.contentDocument.location.href = url;
+    return url
+}
+commands['openUrl'] = openUrl;
+
+function getUrl() {
+    iframe = bot.locateElementByIdentifier('tutorial-content', document);
+    return iframe.contentDocument.location.href;
+}
+commands['getUrl'] = getUrl;
+
+function getTitle() {
+    iframe = bot.locateElementByIdentifier('tutorial-content', document);
+    return iframe.contentDocument.title;
+}
+commands['getTitle'] = getTitle;
+
+function getContent() {
+    // TODO: There seems to be no way of getting the entire source. Sigh. :-(
+    iframe = bot.locateElementByIdentifier('tutorial-content', document);
+    return iframe.contentDocument.body.innerHTML;
+}
+commands['getContent'] = getContent;
+
+function reload() {
+    iframe = bot.locateElementByIdentifier('tutorial-content', document);
+    return iframe.contentDocument.location.reload()
+}
+commands['reload'] = reload;
+
+function goBack(steps) {
+    for (x=0; x<steps; x++) {
+        frames['tutorial-content'].history.back()
+    }
+}
+commands['goBack'] = goBack;
+
+
+
+function clickLink(text, url, id) {
+    if (text) {
+        link = contentBot.findElementByTagNameAndText(
+            getContentDocument(), 'a', text);
+    }
+
+      if (url) {
+        link = contentBott.findElementByTagNameAndAttributeValue(
+            getContentDocument(), 'a', 'href', url);
+    }
+
+    if (id) {
+        link = contentBot.locateElementByIdentifier(
+            'tutorial-content', getContentDocument());
+    }
+    blinkElement(link);
+    contentBot.clickElement(link);
+}
+commands['clickLink'] = clickLink;

Added: zope.tutorial/trunk/browser/tutorial.css
===================================================================
--- zope.tutorial/trunk/browser/tutorial.css	2005-11-21 03:29:06 UTC (rev 40290)
+++ zope.tutorial/trunk/browser/tutorial.css	2005-11-21 03:46:47 UTC (rev 40291)
@@ -0,0 +1,47 @@
+html {
+    height: 100%;
+}
+
+body {
+    font-family: Verdana, Helvetica, Arial, sans-serif;
+    font-size: 0.8em;
+    margin: 0;
+    padding: 0;
+    overflow: auto;
+}
+
+div, p {
+    font-size: 0.8em;
+}
+
+td {
+    padding: 0;
+    margin: 0;
+    position: static;
+    border: 2px solid #2475BB;
+}
+
+tr {
+    vertical-align: top;
+}
+
+iframe {
+    width: 100%;
+    height: 100%;
+    border: 0;
+    background: white;
+    overflow: auto;
+}
+
+/* --- Style --- */
+
+h1.main {
+  color: white;
+  font-size: 100%;
+  background-color: #2475BB;
+  padding: 2px;
+}
+
+#tutorial-text {
+  padding: 1em;
+}
\ No newline at end of file

Added: zope.tutorial/trunk/browser/tutorial.py
===================================================================
--- zope.tutorial/trunk/browser/tutorial.py	2005-11-21 03:29:06 UTC (rev 40290)
+++ zope.tutorial/trunk/browser/tutorial.py	2005-11-21 03:46:47 UTC (rev 40291)
@@ -0,0 +1,87 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Tutorial and Tutorial Manager views
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+import thread
+import types
+import time
+import zope.proxy
+
+from jsonserver.jsonrpc import MethodPublisher
+from zope.app.basicskin.standardmacros import StandardMacros
+from zope.app.apidoc.utilities import renderText
+
+from zope.tutorial import testbrowser
+import zope.testbrowser
+
+class TutorialMacros(StandardMacros):
+    """Page Template METAL macros for Tutorial"""
+    macro_pages = ('runner_macros',)
+
+
+class TutorialsRunner(object):
+
+    pass
+
+
+class TutorialSessionManager(MethodPublisher):
+
+    def createSession(self):
+        name = self.context.createSession()
+        self.context[name].initialize()
+        return name
+
+    def deleteSession(self, id):
+        self.context.deleteSession(id)
+
+
+def run(tutorial, example):
+    OldBrowser = zope.testbrowser.Browser
+    zope.testbrowser.Browser = testbrowser.Browser
+    exec compile(example.source, '<string>', "single") in tutorial.globs
+    # Eek, gotta remove the __builtins__
+    del tutorial.globs['__builtins__']
+    tutorial.locked = False
+    zope.testbrowser.Browser = OldBrowser
+
+
+class TutorialSession(MethodPublisher):
+
+    def getNextStep(self):
+        tutorial = zope.proxy.removeAllProxies(self.context)
+        step = tutorial.getNextStep()
+        if isinstance(step, types.StringTypes):
+            text = renderText(step, format='zope.source.rest')
+            return {'action': 'displayText',
+                    'params': (text,)}
+        elif step is None:
+            return {'action': 'finishTutorial',
+                    'params': ()}
+        else:
+            tutorial.locked = True
+            testbrowser.State.reset()
+            thread.start_new_thread(run, (tutorial, step))
+            while tutorial.locked and not testbrowser.State.hasAction():
+                time.sleep(0.1)
+            return testbrowser.State.action
+
+    def setCommandResult(self, result):
+        testbrowser.State.result = result
+        return True
+
+    def keepGoing(self):
+        return self.context.keepGoing()


Property changes on: zope.tutorial/trunk/browser/tutorial.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: zope.tutorial/trunk/browser/tutorials-runner.js
===================================================================
--- zope.tutorial/trunk/browser/tutorials-runner.js	2005-11-21 03:29:06 UTC (rev 40290)
+++ zope.tutorial/trunk/browser/tutorials-runner.js	2005-11-21 03:46:47 UTC (rev 40291)
@@ -0,0 +1,82 @@
+var CurrentTutorial = '';
+var SessionId = '';
+var ServerConnection = null;
+
+var bot = null;
+var contentBot = null;
+
+function toggleDisplayElement(id) {
+
+    element = bot.locateElementByIdentifier(id, document)
+
+    // Reference the style ...
+    if (element.style) {
+        style = element.style;
+    }
+
+    if (typeof(style.display) == 'undefined' &&
+        !( window.ScriptEngine && ScriptEngine().indexOf('InScript') + 1 ) ) {
+        //The browser does not allow us to change the display style
+        //Alert something sensible (not what I have here ...)
+        window.alert( 'Your browser does not support this' );
+        return;
+    }
+
+   // Change the display style
+    if (style.display != '') {
+        style.display = '';
+   }
+    else {
+        style.display = 'none'
+    }
+}
+
+function initializeTutorialRunner() {
+    /* Create bots */
+    bot = PageBot.createForWindow(window);
+    contentBot = PageBot.createForWindow(frames['tutorial-content']);
+}
+
+function startTutorial() {
+    CurrentTutorial = bot.locateElementByIdentifier(
+        'tutorial-selector', document).value;
+    toggleDisplayElement('start-button');
+    toggleDisplayElement('stop-button');
+    toggleDisplayElement('next-button');
+
+    /* Create a session */
+    var addr = document.URL + CurrentTutorial + '/++sessions++/'
+    var jsonrpc = importModule("jsonrpc");
+    var server = new jsonrpc.ServiceProxy(addr, ['createSession']);
+    SessionId = server.createSession()
+
+    /* Create a new server connection to the session */
+    var addr = document.URL + CurrentTutorial + '/++sessions++' + SessionId
+        ServerConnection = new jsonrpc.ServiceProxy(
+        addr, ['getNextStep', 'setCommandResult', 'keepGoing']);
+}
+
+function stopTutorial() {
+    /* Delete the session */
+    addr = document.URL + CurrentTutorial + '/++sessions++/'
+    var jsonrpc = importModule("jsonrpc");
+    var server = new jsonrpc.ServiceProxy(addr, ['deleteSession']);
+    server.deleteSession(SessionId);
+
+    CurrentTutorial = '';
+    SessionId = '';
+    ServerConnection = null;
+    toggleDisplayElement('start-button');
+    toggleDisplayElement('stop-button');
+    toggleDisplayElement('next-button');
+}
+
+function runNextStep() {
+    var keepGoing = true;
+    while (keepGoing) {
+        command = ServerConnection.getNextStep();
+        result = commands[command.action].apply(null, command.params);
+        answer = ServerConnection.setCommandResult(result);
+        keepGoing = ServerConnection.keepGoing();
+    }
+}

Modified: zope.tutorial/trunk/cli.py
===================================================================
--- zope.tutorial/trunk/cli.py	2005-11-21 03:29:06 UTC (rev 40290)
+++ zope.tutorial/trunk/cli.py	2005-11-21 03:46:47 UTC (rev 40291)
@@ -19,7 +19,7 @@
 import types
 import zope.interface
 
-from zope.tutorial import interfaces
+from zope.tutorial import interfaces, runner
 
 class SimpleCLIController(object):
     """A dummy Command-line based controller.
@@ -77,3 +77,15 @@
             self.end()
         else:
             self.run(step)
+
+
+class ExecutingCLIController(SimpleCLIController):
+
+    def __init__(self, session):
+        super(ExecutingCLIController, self).__init__(session)
+        self.erunner = runner.ExampleRunner(session.globs)
+
+    def run(self, example):
+        """See interfaces.ITutorialController"""
+        super(ExecutingCLIController, self).run(example)
+        self.erunner.run(example)

Modified: zope.tutorial/trunk/configure.zcml
===================================================================
--- zope.tutorial/trunk/configure.zcml	2005-11-21 03:29:06 UTC (rev 40290)
+++ zope.tutorial/trunk/configure.zcml	2005-11-21 03:46:47 UTC (rev 40291)
@@ -2,23 +2,78 @@
     xmlns="http://namespaces.zope.org/zope"
     i18n_domain="zope">
 
+  <!-- Tutorial Configuration -->
+
+  <content class=".tutorial.Tutorial">
+    <require
+        permission="zope.View"
+        interface=".interfaces.ITutorial"
+        />
+  </content>
+
+  <!-- Tutorial Manager Configuration -->
+
+  <content class=".manager.TutorialManager">
+    <implements
+        interface="zope.app.annotation.interfaces.IAttributeAnnotatable"
+        />
+    <require
+        permission="zope.View"
+        interface="zope.app.container.interfaces.IReadContainer"
+        />
+  </content>
+
   <!-- ++tutorials++ Namespace Registration -->
 
   <view
       name="tutorials" type="*"
       provides="zope.app.traversing.interfaces.ITraversable" for="*"
-      factory=".tutorial.tutorialsNamespace"
+      factory=".manager.tutorialsNamespace"
       />
 
   <adapter
       name="tutorials"
       provides="zope.app.traversing.interfaces.ITraversable" for="*"
-      factory=".tutorial.tutorialsNamespace"
+      factory=".manager.tutorialsNamespace"
       />
 
+  <!-- Tutorial Session Configuration -->
+
+  <content class=".tutorial.TutorialSession">
+    <require
+        permission="zope.View"
+        interface=".interfaces.ITutorialSession"
+        />
+  </content>
+
+  <!-- Tutorial Session Manager Configuration -->
+
+  <content class=".tutorial.TutorialSessionManager">
+    <require
+        permission="zope.View"
+        interface=".interfaces.ITutorialSessionManager"
+        />
+  </content>
+
+  <!-- ++sessions++ Namespace Registration -->
+
+  <view
+      name="sessions" type="*"
+      for=".interfaces.ITutorial"
+      provides="zope.app.traversing.interfaces.ITraversable"
+      factory=".tutorial.sessionsNamespace"
+      />
+
+  <adapter
+      name="sessions"
+      for=".interfaces.ITutorial"
+      provides="zope.app.traversing.interfaces.ITraversable"
+      factory=".tutorial.sessionsNamespace"
+      />
+
   <!-- Setup of initial tutorials -->
 
-  <!--include file="tutorials.zcml" /-->
+  <include file="tutorials.zcml" />
 
   <!-- Browser Configuration -->
 

Modified: zope.tutorial/trunk/interfaces.py
===================================================================
--- zope.tutorial/trunk/interfaces.py	2005-11-21 03:29:06 UTC (rev 40290)
+++ zope.tutorial/trunk/interfaces.py	2005-11-21 03:46:47 UTC (rev 40291)
@@ -19,7 +19,7 @@
 
 import zope.interface
 import zope.schema
-from zope.app.container import interfaces
+from zope.app.container import interfaces, constraints
 
 
 class ITutorialManager(interfaces.IReadContainer):
@@ -46,3 +46,38 @@
         title=u'File Path',
         description=u'Path to the file used for the tutorial',
         required=True)
+
+
+class ITutorialSessionManager(interfaces.IContainer):
+    """Tutorial Session Manager
+
+    The tutorial sessoin manager keeps track of all sessions for a given
+    tutorial.
+    """
+    constraints.contains('.ITutorialSession')
+
+    def createSession():
+        """Create a session and return its name."""
+
+    def deleteSession(name):
+        """Delete a session for the given name."""
+
+
+class ITutorialSession(interfaces.IContained):
+    """Tutorial Session
+
+    The session keeps track of the state of the tutorial for the user.
+    """
+    constraints.containers(ITutorialSessionManager)
+
+    def initialize():
+        """Initialize the session."""
+
+    def getNextStep():
+        """Return the next step in the tutorial.
+
+        Can be text or an example.
+        """
+
+    def keepGoing():
+        """ """

Modified: zope.tutorial/trunk/manager.py
===================================================================
--- zope.tutorial/trunk/manager.py	2005-11-21 03:29:06 UTC (rev 40290)
+++ zope.tutorial/trunk/manager.py	2005-11-21 03:46:47 UTC (rev 40291)
@@ -37,18 +37,15 @@
         """See zope.app.container.interfaces.IReadContainer"""
         utility = zapi.queryUtility(interfaces.ITutorial, key, default)
         if utility != default:
-            location.locate(utility, self, key)
+            utility = location.LocationProxy(utility, self, key)
         return utility
 
     def items(self):
         """See zope.app.container.interfaces.IReadContainer"""
         items = list(zapi.getUtilitiesFor(interfaces.ITutorial))
         items.sort()
-        utils = []
-        for key, value in items:
-            location.locate(value, self, key)
-            utils.append((key, value))
-        return utils
+        return [(name, location.LocationProxy(tutorial, self, name))
+                for name, tutorial in items]
 
 
 class tutorialsNamespace(object):

Added: zope.tutorial/trunk/runner.py
===================================================================
--- zope.tutorial/trunk/runner.py	2005-11-21 03:29:06 UTC (rev 40290)
+++ zope.tutorial/trunk/runner.py	2005-11-21 03:46:47 UTC (rev 40291)
@@ -0,0 +1,40 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Simple Text Controller implementation.
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+from zope.testing import doctest
+
+
+class PermissiveOutputChecker(object):
+
+    def check_output(self, want, got, optionflags):
+        return True
+
+
+class ExampleRunner(doctest.DocTestRunner):
+    """Example Runner"""
+
+    def __init__(self, globs, checker=None, verbose=None, optionflags=0):
+        if checker is None:
+            checker = PermissiveOutputChecker()
+        doctest.DocTestRunner.__init__(self, checker, verbose, optionflags)
+        self.globs = globs
+
+    def run(self, example, compileflags=None, out=None):
+        """ """
+        test = doctest.DocTest([example], self.globs, '', '', 0, '')
+        return doctest.DocTestRunner.run(self, test, clear_globs=False)


Property changes on: zope.tutorial/trunk/runner.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: zope.tutorial/trunk/sample_tutorial.txt
===================================================================
--- zope.tutorial/trunk/sample_tutorial.txt	2005-11-21 03:29:06 UTC (rev 40290)
+++ zope.tutorial/trunk/sample_tutorial.txt	2005-11-21 03:46:47 UTC (rev 40291)
@@ -0,0 +1,16 @@
+Hello World
+===========
+
+Hi there.
+
+  >>> from zope.testbrowser import Browser
+  >>> browser = Browser()
+  >>> browser.open('http://localhost:8080/manage')
+  >>> browser.url
+  >>> browser.title
+  >>> browser.contents
+  >>> browser.reload()
+  >>> browser.getLink('Buddy Folder').click()
+  >>> browser.goBack()
+
+That's it!
\ No newline at end of file


Property changes on: zope.tutorial/trunk/sample_tutorial.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Deleted: zope.tutorial/trunk/selenium.py
===================================================================
--- zope.tutorial/trunk/selenium.py	2005-11-21 03:29:06 UTC (rev 40290)
+++ zope.tutorial/trunk/selenium.py	2005-11-21 03:46:47 UTC (rev 40291)
@@ -1,77 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2005 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""Selenium-based Test Browser
-
-$Id$
-"""
-__docformat__ = "reStructuredText"
-import zope.interface
-
-#class SeleniumBrowser(SetattrErrorsMixin):
-#    """A web user agent."""
-#    zope.interface.implements(interfaces.IBrowser)
-#
-#    _contents = None
-#    _counter = 0
-#
-#    def __init__(self, url=None, mech_browser=None):
-#        if mech_browser is None:
-#            mech_browser = mechanize.Browser()
-#        self.mech_browser = mech_browser
-#        if url is not None:
-#            self.open(url)
-#        self.timer = PystoneTimer()
-#        self._enable_setattr_errors = True
-#
-#    @property
-#    def url(self):
-#        """See zope.testbrowser.interfaces.IBrowser"""
-#        return self.mech_browser.geturl()
-#
-#    @property
-#    def isHtml(self):
-#        """See zope.testbrowser.interfaces.IBrowser"""
-#        return self.mech_browser.viewing_html()
-#
-#    @property
-#    def title(self):
-#        """See zope.testbrowser.interfaces.IBrowser"""
-#        return self.mech_browser.title()
-#
-#    @property
-#    def contents(self):
-#        """See zope.testbrowser.interfaces.IBrowser"""
-#        if self._contents is not None:
-#            return self._contents
-#        response = self.mech_browser.response()
-#        old_location = response.tell()
-#        response.seek(0)
-#        for line in iter(lambda: response.readline().strip(), ''):
-#            pass
-#        self._contents = response.read()
-#        response.seek(old_location)
-#        return self._contents
-#
-#    @property
-#    def headers(self):
-#        """See zope.testbrowser.interfaces.IBrowser"""
-#        return self.mech_browser.response().info()
-#
-#    def open(self, url, data=None):
-#        """See zope.testbrowser.interfaces.IBrowser"""
-#        self._start_timer()
-#        self.mech_browser.open(url, data)
-#        self._stop_timer()
-#        self._changed()
-#

Modified: zope.tutorial/trunk/session.txt
===================================================================
--- zope.tutorial/trunk/session.txt	2005-11-21 03:29:06 UTC (rev 40290)
+++ zope.tutorial/trunk/session.txt	2005-11-21 03:46:47 UTC (rev 40291)
@@ -20,8 +20,8 @@
   ...
   ... And now a variable assignment with a return value:
   ...
-  ...   >>> id = 5
-  ...   >>> id
+  ...   >>> num = 5
+  ...   >>> num
   ...   5
   ...
   ... That's it!
@@ -68,10 +68,45 @@
   <BLANKLINE>
   And now a variable assignment with a return value:
   <BLANKLINE>
-    Py: id = 5
-    Py: id
+    Py: num = 5
+    Py: num
     5
   <BLANKLINE>
   That's it!
   <BLANKLINE>
   ---------- The End ----------
+
+Next let's try a little bit more interesting. There is also a CLI controller
+that actually executes the examples:
+
+  >>> session = tutorial.TutorialSession(sample)
+  >>> session.initialize()
+
+  >>> controller = cli.ExecutingCLIController(session)
+  >>> controller.PYTHON_PROMPT = 'Py: '
+
+  >>> def run():
+  ...     controller.start()
+  ...     while controller.running:
+  ...         controller.doNextStep()
+
+  >>> run()
+  Starting Tutorial: Sample Documentation
+  <BLANKLINE>
+  Sample Documentation
+  ====================
+  <BLANKLINE>
+  Here is a simple print statement:
+  <BLANKLINE>
+    Py: print 'sample'
+    sample
+  <BLANKLINE>
+  And now a variable assignment with a return value:
+  <BLANKLINE>
+    Py: num = 5
+    Py: num
+    5
+  <BLANKLINE>
+  That's it!
+  <BLANKLINE>
+  ---------- The End ----------

Added: zope.tutorial/trunk/testbrowser.py
===================================================================
--- zope.tutorial/trunk/testbrowser.py	2005-11-21 03:29:06 UTC (rev 40290)
+++ zope.tutorial/trunk/testbrowser.py	2005-11-21 03:46:47 UTC (rev 40291)
@@ -0,0 +1,227 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Test Browser implementation that works with the Tutorial Runner
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+
+import time
+import zope.interface
+from zope import testbrowser
+
+
+NORESULT = object()
+NOACTION = {'action': 'nullAction', 'params': ()}
+
+# TODO: Make this user specific later; this should be really stored in the
+# session, but the test browser does not know about the session :-(
+class State(object):
+    __slots__ = ('result', 'action')
+
+    def __init__(self):
+        self.reset()
+
+    def reset(self):
+        self.result = NORESULT
+        self.action = NOACTION
+
+    def hasAction(self):
+        return self.action is not NOACTION
+
+    def hasResult(self):
+        return self.result is not NORESULT
+
+    def executeAction(self, action, *args):
+        self.result = None
+        self.action = {'action': action, 'params': args}
+        # wait for the answer to come in
+        while self.result is NORESULT:
+            time.sleep(0.5)
+        return self.result
+
+    def __getattr__(self, name):
+        def action(*args):
+            return self.executeAction(name, *args)
+        return action
+
+State = State()
+
+
+class Browser(testbrowser.browser.SetattrErrorsMixin):
+    """ """
+    zope.interface.implements(testbrowser.interfaces.IBrowser)
+
+    _contents = None
+    _counter = 0
+
+    def __init__(self, url=None):
+        self.timer = testbrowser.browser.PystoneTimer()
+        if url:
+            self.open(url)
+
+    @property
+    def url(self):
+        """See zope.testbrowser.interfaces.IBrowser"""
+        return State.getUrl()
+
+    @property
+    def isHtml(self):
+        """See zope.testbrowser.interfaces.IBrowser"""
+        # TODO: It is always HTML for now ;-)
+        return True
+
+    @property
+    def title(self):
+        """See zope.testbrowser.interfaces.IBrowser"""
+        return State.getTitle()
+
+    @property
+    def contents(self):
+        """See zope.testbrowser.interfaces.IBrowser"""
+        if self._contents is None:
+            self._contents = State.getContent()
+        return self._contents
+
+    @property
+    def headers(self):
+        """See zope.testbrowser.interfaces.IBrowser"""
+        # TODO: How do we get those? Can we get them at all?
+        return []
+
+    # See zope.testbrowser.interfaces.IBrowser
+    # TODO: We cannot copy this functionality here.
+    handleErrors = False
+
+    def open(self, url, data=None):
+        """See zope.testbrowser.interfaces.IBrowser"""
+        self._start_timer()
+        State.openUrl(url, data)
+        self._stop_timer()
+        self._changed()
+
+    def _start_timer(self):
+        self.timer.start()
+
+    def _stop_timer(self):
+        self.timer.stop()
+
+    @property
+    def last_request_pystones(self):
+        return self.timer.elapsed_pystones
+
+    @property
+    def last_request_seconds(self):
+        return self.timer.elapsed_seconds
+
+    def reload(self):
+        """See zope.testbrowser.interfaces.IBrowser"""
+        self._start_timer()
+        State.reload()
+        self._stop_timer()
+        self._changed()
+
+    def goBack(self, count=1):
+        """See zope.testbrowser.interfaces.IBrowser"""
+        self._start_timer()
+        State.goBack(count)
+        self._stop_timer()
+        self._changed()
+
+    def addHeader(self, key, value):
+        """See zope.testbrowser.interfaces.IBrowser"""
+        # TODO: How can this be done?
+        #self.mech_browser.addheaders.append( (key, value) )
+
+    def getLink(self, text=None, url=None, id=None):
+        return Link(self, text, url, id)
+
+    def getControl(self, label=None, name=None, index=None):
+        """See zope.testbrowser.interfaces.IBrowser"""
+        return Control(self, label, name, index)
+
+    def getForm(self, id=None, name=None, action=None, index=None):
+        zeroOrOne([id, name, action], '"id", "name", and "action"')
+        if index is None and not any([id, name, action]):
+            raise ValueError(
+                'if no other arguments are given, index is required.')
+
+        matching_forms = []
+        for form in self.mech_browser.forms():
+            if ((id is not None and form.attrs.get('id') == id)
+            or (name is not None and form.name == name)
+            or (action is not None and re.search(action, str(form.action)))
+            or id == name == action == None):
+                matching_forms.append(form)
+
+        form = disambiguate(matching_forms, '', index)
+        self.mech_browser.form = form
+        return Form(self, form)
+
+    def _changed(self):
+        self._counter += 1
+        self._contents = None
+
+
+class Link(object):
+    zope.interface.implements(testbrowser.interfaces.ILink)
+
+    def __init__(self, browser, text=None, url=None, id=None):
+        self.browser = browser
+        self._text = text
+        self._url = url
+        self._id = id
+
+        self._info = None
+
+    def click(self):
+        return State.executeAction('clickLink', self._text, self._url, self._id)
+
+    def getInfo(self):
+        if self._info is None:
+            self._info = State.executeAction(
+                'getLinkInfo', self._text, self._url, self._id)
+        return self._info
+
+    @property
+    def url(self):
+        return self.getInfo()['href']
+
+    @property
+    def text(self):
+        return self.getInfo()['text']
+
+    @property
+    def tag(self):
+        return self.getInfo()['tag']
+
+    @property
+    def attrs(self):
+        return self._info
+
+    def __repr__(self):
+        return "<%s text=%r url=%r id=%s>" % (
+            self.__class__.__name__, self._text, self._url, self._id)
+
+
+class Control(object):
+
+    def __init__(self, browser, label=None, name=None, index=None):
+        self.browser = browser
+        self._label = label
+        self._name = name
+        self._index = index
+
+    def click(self):
+        return State.executeAction('clickControl', self._text, self._url, self._id)


Property changes on: zope.tutorial/trunk/testbrowser.py
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: zope.tutorial/trunk/tutorial.py
===================================================================
--- zope.tutorial/trunk/tutorial.py	2005-11-21 03:29:06 UTC (rev 40290)
+++ zope.tutorial/trunk/tutorial.py	2005-11-21 03:46:47 UTC (rev 40291)
@@ -22,10 +22,19 @@
 import types
 import zope.component
 import zope.interface
+import zope.proxy
+from zope.app import annotation
+from zope.app import zapi
+from zope.app.component import hooks
+from zope.app.container import btree
+from zope.app.location import location
 
 from zope.tutorial import interfaces
 
 
+SessionManagerKey = 'zope.tutorial.SessionManager'
+
+
 class Tutorial(object):
     """Tutorial"""
     zope.interface.implements(interfaces.ITutorial)
@@ -39,16 +48,17 @@
             self.__class__.__name__, self.title, os.path.split(self.path)[-1])
 
 
-class TutorialSession(persistent.Persistent):
+class TutorialSession(persistent.Persistent, location.Location):
     """Tutorial Session"""
 
     zope.component.adapts(interfaces.ITutorial)
-    #zope.interface.implements(interfaces.ITutorialSession)
+    zope.interface.implements(interfaces.ITutorialSession)
 
+    locked = False
+
     def __init__(self, tutorial):
         self.tutorial = tutorial
 
-
     def initialize(self):
         """See interfaces.ITutorialSession"""
         text = open(self.tutorial.path, 'r').read()
@@ -60,14 +70,65 @@
                           part.strip())]
         # Create a parts stack
         self.parts.reverse()
+        self.current = None
 
         # Set some runtime variables
         self.globs = {}
 
-
     def getNextStep(self):
         """See interfaces.ITutorialSession"""
+        if self.locked:
+            return None
         try:
-            return self.parts.pop()
+            self.current = self.parts.pop()
         except IndexError:
+            self.current = None
             return None
+
+        return self.current
+
+    def keepGoing(self):
+        return type(self.parts[-1]) == type(self.current)
+
+
+class TutorialSessionManager(btree.BTreeContainer):
+    """A session manager based on BTrees."""
+    zope.component.adapter(interfaces.ITutorial)
+    zope.interface.implements(interfaces.ITutorialSessionManager)
+
+    def __init__(self):
+        super(TutorialSessionManager, self).__init__()
+        self.__counter = 0
+
+    def createSession(self):
+        name = unicode(self.__counter)
+        self[name] = TutorialSession(zapi.getParent(self))
+        self.__counter += 1;
+        return name
+
+    def deleteSession(self, name):
+        del self[name]
+
+
+class sessionsNamespace(object):
+    """Used to traverse the `++sessions++` namespace"""
+
+    def __init__(self, ob=None, request=None):
+        site = hooks.getSite()
+        annotations = annotation.interfaces.IAnnotations(site)
+        manager = annotations.get(SessionManagerKey)
+
+        if manager is None:
+            manager = TutorialSessionManager()
+            tutorial = zope.proxy.removeAllProxies(ob)
+            location.locate(manager, tutorial, '++sessions++')
+            annotations[SessionManagerKey] = manager
+
+        self.sessionManager = manager
+
+
+    def traverse(self, name, ignore=None):
+        if name == '':
+            return self.sessionManager
+        else:
+            return self.sessionManager[name]

Modified: zope.tutorial/trunk/tutorials.zcml
===================================================================
--- zope.tutorial/trunk/tutorials.zcml	2005-11-21 03:29:06 UTC (rev 40290)
+++ zope.tutorial/trunk/tutorials.zcml	2005-11-21 03:46:47 UTC (rev 40291)
@@ -2,11 +2,16 @@
     xmlns="http://namespaces.zope.org/zope"
     i18n_domain="zope">
 
+    <tutorial
+        name="sample_tutorial"
+        title="Sample Tutorial"
+        path="sample_tutorial.txt" />
+
   <configure package="zope.testbrowser">
 
     <tutorial
         name="testbrowser"
-        title="Test Browser Features"
+        title="The Test Browser"
         path="README.txt" />
 
   </configure>



More information about the Zope-CVS mailing list