[Zope-CVS] SVN: zope.tutorial/trunk/ My first stab at the necessary abstraction of running a doctest as a

Stephan Richter srichter at cosmos.phy.tufts.edu
Sun Nov 13 08:41:58 EST 2005


Log message for revision 40081:
  My first stab at the necessary abstraction of running a doctest as a 
  tutorial. I am so unhappy with that, it's not even funny.
  

Changed:
  U   zope.tutorial/trunk/README.txt
  U   zope.tutorial/trunk/browser/configure.zcml
  A   zope.tutorial/trunk/browser/index.pt
  D   zope.tutorial/trunk/browser/tutorial.pt
  A   zope.tutorial/trunk/cli.py
  U   zope.tutorial/trunk/configure.zcml
  A   zope.tutorial/trunk/manager.py
  A   zope.tutorial/trunk/session.txt
  U   zope.tutorial/trunk/tests.py
  U   zope.tutorial/trunk/tutorial.py

-=-
Modified: zope.tutorial/trunk/README.txt
===================================================================
--- zope.tutorial/trunk/README.txt	2005-11-13 02:58:15 UTC (rev 40080)
+++ zope.tutorial/trunk/README.txt	2005-11-13 13:41:57 UTC (rev 40081)
@@ -13,7 +13,7 @@
 tutorials. This package provides the necessary framework to run testbrowser
 tests inside a real browser in a tutorial style:
 
-  >>> from zope.tutorial import tutorial
+  >>> from zope.tutorial import tutorial, manager
 
 Tutorial
 --------
@@ -28,12 +28,12 @@
 All tutorials are managed by the tutorial manager, which also serves as an
 entrance point in the Web UI.
 
-  >>> manager = tutorial.TutorialManager()
+  >>> tm = manager.TutorialManager()
 
 The tutorial manager implements the `IReadContainer` interface to query for
 tutorials. Initially there are no tutorials:
 
-  >>> manager.keys()
+  >>> tm.keys()
   []
 
 Once we add some tutorials by registering soem utilities,
@@ -48,7 +48,7 @@
 
 we have some results:
 
-  >>> manager.items()
+  >>> tm.items()
   [(u'tut1', <Tutorial title='Tutorial 1', file='tut1.txt'>),
    (u'tut2', <Tutorial title='Tutorial 2', file='tut2.txt'>)]
 
@@ -60,13 +60,13 @@
 an entrance point to the tutorial application. Once the namespace is created.
 
   >>> parent = object()
-  >>> namespace = tutorial.tutorialsNamespace(parent)
+  >>> namespace = manager.tutorialsNamespace(parent)
 
 you can traverse the parent to the tutorial manager. If an empty name is
 passed into the namespace, the manager is returned:
 
   >>> namespace.traverse('')
-  <zope.tutorial.tutorial.TutorialManager object at ...>
+  <zope.tutorial.manager.TutorialManager object at ...>
 
 If a name is provided, then the actual tutorial is looked up:
 

Modified: zope.tutorial/trunk/browser/configure.zcml
===================================================================
--- zope.tutorial/trunk/browser/configure.zcml	2005-11-13 02:58:15 UTC (rev 40080)
+++ zope.tutorial/trunk/browser/configure.zcml	2005-11-13 13:41:57 UTC (rev 40081)
@@ -3,8 +3,37 @@
     xmlns:zope="http://namespaces.zope.org/zope"
     i18n_domain="zope">
 
+  <resource
+      name="tutorial.css"
+      file="tutorial.css"
+      />
+
+  <page
+      for="*"
+      name="tutorial_macros"
+      permission="zope.View"
+      class=".tutorial.TutorialMacros"
+      allowed_interface="zope.interface.common.mapping.IItemMapping"
+      />
+
+  <page
+      for="*"
+      name="runner_macros"
+      permission="zope.View"
+      template="runner_macros.pt"
+      />
+
+  <page
+      name="index.html"
+      for="..interfaces.ITutorialManager"
+      class=".tutorial.TutorialsRunner"
+      template="index.pt"
+      permission="zope.View"
+      />
+
+  <!-- Make Selenium available -->
   <resourceDirectory
-      name="selenium-driver"
+      name="selenium"
       directory="../selenium/javascript" />
 
 </configure>

Copied: zope.tutorial/trunk/browser/index.pt (from rev 40074, zope.tutorial/trunk/browser/tutorial.pt)
===================================================================
--- zope.tutorial/trunk/browser/tutorial.pt	2005-11-12 18:29:16 UTC (rev 40074)
+++ zope.tutorial/trunk/browser/index.pt	2005-11-13 13:41:57 UTC (rev 40081)
@@ -0,0 +1,62 @@
+<html>
+  <head>
+    <meta content="text/html; charset=ISO-8859-1" http-equiv="content-type" />
+    <title>Online Tutorial Viewer</title>
+    <link rel="stylesheet" type="text/css"
+          href="/@@/tutorial.css" />
+    <script
+        language="JavaScript" type="text/javascript"
+        src="/@@/selenium/jsunit/app/jsUnitCore.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>
+    <script
+        language="JavaScript" type="text/javascript"
+        src="/@@/selenium/selenium-commandhandlers.js"></script>
+    <script
+        language="JavaScript" type="text/javascript"
+        src="/@@/selenium/selenium-executionloop.js"></script>
+    <script
+        language="JavaScript" type="text/javascript"
+        src="/@@/selenium/selenium-seleneserunner.js"></script>
+    <script
+        language="JavaScript" type="text/javascript"
+        src="/@@/selenium/selenium-logging.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>
+  </head>
+  <body>
+
+    <table width="100%" style="height: 100%;">
+      <tr>
+        <td width="30%" height="30%">
+          <metal:block use-macro="context/@@tutorial_macros/runner" />
+        </td>
+        <td width="70%" height="30%">
+          <b>Last Four Commands</b><br/>
+          <div id="commandList"></div>
+        </td>
+      </tr>
+      <tr>
+        <td colspan="2" height="70%">
+          <iframe name="myiframe" id="myiframe" src=""
+                  height="100%" width="100%"></iframe>
+        </td>
+      </tr>
+    </table>
+
+  </body>
+</html>

Deleted: zope.tutorial/trunk/browser/tutorial.pt
===================================================================
--- zope.tutorial/trunk/browser/tutorial.pt	2005-11-13 02:58:15 UTC (rev 40080)
+++ zope.tutorial/trunk/browser/tutorial.pt	2005-11-13 13:41:57 UTC (rev 40081)
@@ -1,64 +0,0 @@
-<html>
-  <head>
-    <meta content="text/html; charset=ISO-8859-1" http-equiv="content-type">
-      <title>Online Tutorial Viewer</title>
-      <link rel="stylesheet" type="text/css" href="selenium.css" />
-      <script
-          language="JavaScript" type="text/javascript"
-          src="jsunit/app/jsUnitCore.js"></script>
-      <script
-          language="JavaScript" type="text/javascript"
-          src="xmlextras.js"></script>
-      <script
-          language="JavaScript" type="text/javascript"
-          src="selenium-browserbot.js"></script>
-      <script
-          language="JavaScript" type="text/javascript"
-          src="selenium-api.js"></script>
-      <script
-          language="JavaScript" type="text/javascript"
-          src="selenium-commandhandlers.js"></script>
-      <script
-          language="JavaScript" type="text/javascript"
-          src="selenium-executionloop.js"></script>
-      <script
-          language="JavaScript" type="text/javascript"
-          src="selenium-seleneserunner.js"></script>
-      <script
-          language="JavaScript" type="text/javascript"
-          src="selenium-logging.js"></script>
-      <script
-          language="JavaScript" type="text/javascript"
-          src="htmlutils.js"></script>
-      <script
-          language="JavaScript" type="text/javascript"
-          src="xpath.js"></script>
-      <script
-          language="JavaScript" type="text/javascript"
-          src="user-extensions.js"></script>
-  </head>
-
-  <body onLoad="runTest()">
-
-    <table border="1" style="height: 100%;">
-      <tr>
-        <td width="50%" height="30%">
-          <form action="">
-            <label id="context" name="context"></label>
-          </form>
-        </td>
-        <td width="50%" height="30%">
-          <b>Last Four Commands</b><br/>
-          <div id="commandList"></div>
-        </td>
-      </tr>
-      <tr>
-        <td colspan="2" height="70%">
-          <iframe name="myiframe" id="myiframe" src=""
-                  height="100%" width="100%"></iframe>
-        </td>
-      </tr>
-    </table>
-
-  </body>
-</html>

Added: zope.tutorial/trunk/cli.py
===================================================================
--- zope.tutorial/trunk/cli.py	2005-11-13 02:58:15 UTC (rev 40080)
+++ zope.tutorial/trunk/cli.py	2005-11-13 13:41:57 UTC (rev 40081)
@@ -0,0 +1,79 @@
+##############################################################################
+#
+# 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"
+import types
+import zope.interface
+
+from zope.tutorial import interfaces
+
+class SimpleCLIController(object):
+    """A dummy Command-line based controller.
+
+    Instead of running the tests, this controller simply displays the text and
+    examples. This makes this controller well-suited for testing.
+    """
+    #zope.interface.implements(interfaces.ITutorialController)
+
+    PYTHON_PROMPT = '>>> '
+    PYTHON_CONTINUE = '... '
+
+    def __init__(self, session):
+        self.session = session
+        self.running = False
+
+    def start(self):
+        """See interfaces.ITutorialController"""
+        self.running = True
+        print 'Starting Tutorial: ' + self.session.tutorial.title
+
+    def end(self):
+        """See interfaces.ITutorialController"""
+        print '---------- The End ----------'
+        self.running = False
+
+    def display(self, text):
+        """See interfaces.ITutorialController"""
+        print
+        print text.strip()
+        print
+
+    def run(self, example):
+        """See interfaces.ITutorialController"""
+        # Prepare the source and print it
+        source = example.source.strip()
+        source = ' '*example.indent + self.PYTHON_PROMPT + source
+        source = source.replace(
+            '\n', '\n' + ' '*example.indent + self.PYTHON_CONTINUE)
+        print source
+
+        # Prepare the expected output and print it
+        if example.want:
+            want = example.want.strip()
+            want = ' '*example.indent + want
+            want = want.replace('\n', '\n' + ' '*example.indent)
+            print want
+
+    def doNextStep(self):
+        """See interfaces.ITutorialController"""
+        step = self.session.getNextStep()
+        if isinstance(step, types.StringTypes):
+            self.display(step)
+        elif step is None:
+            self.end()
+        else:
+            self.run(step)


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

Modified: zope.tutorial/trunk/configure.zcml
===================================================================
--- zope.tutorial/trunk/configure.zcml	2005-11-13 02:58:15 UTC (rev 40080)
+++ zope.tutorial/trunk/configure.zcml	2005-11-13 13:41:57 UTC (rev 40081)
@@ -18,7 +18,7 @@
 
   <!-- Setup of initial tutorials -->
 
-  <include file="tutorials.zcml" />
+  <!--include file="tutorials.zcml" /-->
 
   <!-- Browser Configuration -->
 

Added: zope.tutorial/trunk/manager.py
===================================================================
--- zope.tutorial/trunk/manager.py	2005-11-13 02:58:15 UTC (rev 40080)
+++ zope.tutorial/trunk/manager.py	2005-11-13 13:41:57 UTC (rev 40081)
@@ -0,0 +1,64 @@
+##############################################################################
+#
+# 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 Manager Implementation
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+import zope.interface
+from zope.app.apidoc import utilities
+from zope.app import location
+from zope.app import zapi
+
+from zope.tutorial import interfaces
+
+
+class TutorialManager(utilities.ReadContainerBase):
+    """TutorialManager"""
+    zope.interface.implements(interfaces.ITutorialManager,
+                              location.interfaces.ILocation)
+
+    def __init__(self, parent=None):
+        self.__parent__ = parent
+        self.__name__ = '++tutorials++'
+
+    def get(self, key, default=None):
+        """See zope.app.container.interfaces.IReadContainer"""
+        utility = zapi.queryUtility(interfaces.ITutorial, key, default)
+        if utility != default:
+            location.locate(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
+
+
+class tutorialsNamespace(object):
+    """Used to traverse the `++tutorials++` namespace"""
+
+    def __init__(self, ob=None, request=None):
+        self.tutorialManager = TutorialManager(ob)
+
+    def traverse(self, name, ignore=None):
+        if name == '':
+            return self.tutorialManager
+        else:
+            return self.tutorialManager[name]


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

Added: zope.tutorial/trunk/session.txt
===================================================================
--- zope.tutorial/trunk/session.txt	2005-11-13 02:58:15 UTC (rev 40080)
+++ zope.tutorial/trunk/session.txt	2005-11-13 13:41:57 UTC (rev 40081)
@@ -0,0 +1,77 @@
+====================
+The Tutorial Session
+====================
+
+A tutorial session is created whenever a user watches or takes a tutorial. A
+session is an adapter to a tutorial, so we have to create that first:
+
+  >>> import os, tempfile
+  >>> temp_dir = tempfile.mkdtemp()
+  >>> sample_txt = os.path.join(temp_dir, 'sample.txt')
+
+  >>> open(sample_txt, 'w').write('''
+  ... Sample Documentation
+  ... ====================
+  ...
+  ... Here is a simple print statement:
+  ...
+  ...   >>> print 'sample'
+  ...   sample
+  ...
+  ... And now a variable assignment with a return value:
+  ...
+  ...   >>> id = 5
+  ...   >>> id
+  ...   5
+  ...
+  ... That's it!
+  ... ''')
+
+  >>> from zope.tutorial import tutorial
+  >>> sample = tutorial.Tutorial('Sample Documentation', sample_txt)
+
+Now that we have the tutorial, we can create a session:
+
+  >>> session = tutorial.TutorialSession(sample)
+  >>> session.initialize()
+
+Nothing is setup until ``initialize()`` is called. Once the session is
+prepared, we can choose a controller that knows about the input and output
+interfaces. The simplest controller is the `SimpleCLIController`, which simply
+displays the text and examples:
+
+  >>> from zope.tutorial import cli
+  >>> controller = cli.SimpleCLIController(session)
+
+Since we are in a unit test file already, the Python prompt needs to be
+changed, so that the test does not get confused:
+
+  >>> controller.PYTHON_PROMPT = 'Py: '
+
+We can now write a simple function that runs the tutorial for us:
+
+  >>> 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: id = 5
+    Py: id
+    5
+  <BLANKLINE>
+  That's it!
+  <BLANKLINE>
+  ---------- The End ----------


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

Modified: zope.tutorial/trunk/tests.py
===================================================================
--- zope.tutorial/trunk/tests.py	2005-11-13 02:58:15 UTC (rev 40080)
+++ zope.tutorial/trunk/tests.py	2005-11-13 13:41:57 UTC (rev 40081)
@@ -30,6 +30,11 @@
                      tearDown=placelesssetup.tearDown,
                      optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
                      ),
+        DocFileSuite('session.txt',
+                     setUp=placelesssetup.setUp,
+                     tearDown=placelesssetup.tearDown,
+                     optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+                     ),
         DocFileSuite('directives.txt',
                      setUp=placelesssetup.setUp,
                      tearDown=placelesssetup.tearDown,

Modified: zope.tutorial/trunk/tutorial.py
===================================================================
--- zope.tutorial/trunk/tutorial.py	2005-11-13 02:58:15 UTC (rev 40080)
+++ zope.tutorial/trunk/tutorial.py	2005-11-13 13:41:57 UTC (rev 40081)
@@ -11,47 +11,21 @@
 # FOR A PARTICULAR PURPOSE.
 #
 ##############################################################################
-"""Tutorial Manager Implementation
+"""Tutorial Implementation
 
 $Id$
 """
 __docformat__ = "reStructuredText"
+import doctest
 import os
+import persistent
+import types
+import zope.component
 import zope.interface
-from zope.app.apidoc import utilities
-from zope.app import location
-from zope.app import zapi
 
 from zope.tutorial import interfaces
 
 
-class TutorialManager(utilities.ReadContainerBase):
-    """TutorialManager"""
-    zope.interface.implements(interfaces.ITutorialManager,
-                              location.interfaces.ILocation)
-
-    def __init__(self, parent=None):
-        self.__parent__ = parent
-        self.__name__ = '++tutorials++'
-
-    def get(self, key, default=None):
-        """See zope.app.container.interfaces.IReadContainer"""
-        utility = zapi.queryUtility(interfaces.ITutorial, key, default)
-        if utility != default:
-            location.locate(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
-
-
 class Tutorial(object):
     """Tutorial"""
     zope.interface.implements(interfaces.ITutorial)
@@ -65,14 +39,35 @@
             self.__class__.__name__, self.title, os.path.split(self.path)[-1])
 
 
-class tutorialsNamespace(object):
-    """Used to traverse the `++tutorials++` namespace"""
+class TutorialSession(persistent.Persistent):
+    """Tutorial Session"""
 
-    def __init__(self, ob=None, request=None):
-        self.tutorialManager = TutorialManager(ob)
+    zope.component.adapts(interfaces.ITutorial)
+    #zope.interface.implements(interfaces.ITutorialSession)
 
-    def traverse(self, name, ignore=None):
-        if name == '':
-            return self.tutorialManager
-        else:
-            return self.tutorialManager[name]
+    def __init__(self, tutorial):
+        self.tutorial = tutorial
+
+
+    def initialize(self):
+        """See interfaces.ITutorialSession"""
+        text = open(self.tutorial.path, 'r').read()
+        parser = doctest.DocTestParser()
+        self.parts = parser.parse(text)
+        # Clean up the parts by removing empty strings
+        self.parts = [part for part in self.parts
+                      if (not isinstance(part, types.StringTypes) or
+                          part.strip())]
+        # Create a parts stack
+        self.parts.reverse()
+
+        # Set some runtime variables
+        self.globs = {}
+
+
+    def getNextStep(self):
+        """See interfaces.ITutorialSession"""
+        try:
+            return self.parts.pop()
+        except IndexError:
+            return None



More information about the Zope-CVS mailing list