Author: ianb
Date: 2005-04-05 02:32:43 -0600 (Tue, 05 Apr 2005)
New Revision: 2301
Added:
WSGIKit/trunk/docs/TodoTutorial.txt
WSGIKit/trunk/docs/default.css
WSGIKit/trunk/docs/resources/
WSGIKit/trunk/docs/resources/TodoTutorial/
WSGIKit/trunk/docs/resources/TodoTutorial/default-frontpage.html
WSGIKit/trunk/wsgikit/tests/doctest_webapp.py
Modified:
WSGIKit/trunk/docs/
Log:
Added a (only partially complete) Todo Tutorial.
Also started infrastructure for doctests of tutorials. The current
tutorial is completely doctestable (at least on my system).
Property changes on: WSGIKit/trunk/docs
___________________________________________________________________
Name: svn:ignore
+ *.html
Added: WSGIKit/trunk/docs/TodoTutorial.txt
===================================================================
--- WSGIKit/trunk/docs/TodoTutorial.txt 2005-04-05 08:31:21 UTC (rev 2300)
+++ WSGIKit/trunk/docs/TodoTutorial.txt 2005-04-05 08:32:43 UTC (rev 2301)
@@ -0,0 +1,287 @@
++++++++++++++++++
+To-Do: A Tutorial
++++++++++++++++++
+
+Setting up the files
+====================
+
+.. note::
+
+ We're doing all this in the Python, even though you'd normally do
+ some of this in the shell. This way the authors of this tutorial
+ can use something called doctest_, which allows this tutorial to
+ be tested Python in an automated way.
+
+ .. _doctest: http://python.org/doc/current/lib/module-doctest.html
+
+.. comment (setup doctests)
+
+ >>> from wsgikit.tests.doctest_webapp import *
+
+Lets start out quickly. We'll be installing the application in
+``/var/www/todo_sql``::
+
+ >>> BASE = '/var/www/todo_sql'
+ >>> import sys
+ >>> sys.path.append('/path/to/WSGIKit')
+
+.. comment (setup)
+
+ >>> clear_dir(BASE)
+
+::
+
+ >>> run("app-setup create -o %s webkit_zpt" % BASE)
+ >>> os.chdir(BASE)
+ >>> ls(recurse=True)
+ __init__.py
+ server.conf
+ sitepage.py
+ templates/
+ generic_error.pt
+ index.pt
+ standard_template.pt
+ web/
+ __init__.py
+ index.py
+ static/
+ stylesheet.css
+
+I want to give a quick explanation of the file structure and the
+nature of the files.
+
+``__init__.py``:
+ This is a special file that Python looks for in a "package". It
+ needs to exist for Python to allow you to import modules from the
+ directory. This module is also the first thing imported in our
+ application, so any setup code can go here.
+
+``server.conf``:
+ This contains the main configuration of our application. This is
+ a python-format file, and all the variables assigned in it are
+ configuration keys.
+
+``sitepage.py``:
+ This module contains an abstract class that all our servlets
+ subclass from. Each servlet is a class, and a page in our web
+ application; by making them all subclass from a single class
+ (``SitePage``) we can provide functionality that is global to our
+ application.
+
+``templates/``:
+ This contains all the "templates" -- there is a template for every
+ page in our application.
+
+``templates/standard_template.pt``:
+ This defines the look of our application -- changes we make here
+ effect all pages in the application.
+
+``templates/index.pt``:
+ This is a template for a single servlet (the ``index`` servlet, in
+ ``web/index.py``).
+
+``templates/generic_error.pt``:
+ This template is used when we just want to output a simple
+ message to the user, wrapped in the site look.
+
+``web/``:
+ This directory contains your servlets. Actually, any file in this
+ directory will be served up -- ``.py`` files are expected to
+ contain servlets, and most other files are served up as
+ themselves.
+
+``web/__init__.py``:
+ Unlike the other ``.py`` files, this one is special, and can
+ contain "hooks" called during the URL parsing.
+
+``web/index.py``:
+ This is a simple example servlet. Anything named ``index`` is
+ also used as the default page (like ``index.html``). WSGIKit
+ mostly ignores extensions when finding pages, so ``/index`` can
+ refer to ``index.py``, ``index.html``, or any other page named
+ ``index`` regardless of extension.
+
+``web/static/``:
+ This contains files that don't have any dynamic content, like
+ images and Javascript. Based on deployment, these files could be
+ served up by Apache or another web server without WSGIKit being
+ involved at all (with some CPU savings), so we keep them
+ separated.
+
+``web/static/stylesheet.css``:
+ A simple stylesheet for your application.
+
+
+Running the application
+=======================
+
+It's just the barest example application, but we can still run it and
+get some basic output. Change into the directory and run::
+
+ $ path/to/wsgikit/scripts/wsgi-server
+
+This will run a server on http://localhost:8080
+
+.. comment (a psuedo-server for doctest)
+
+ >>> set_default_app(make_app(BASE), 'http://localhost:8080')
+ >>> show('/', 'default-frontpage')
+
+.. raw:: html
+ :file: resources/TodoTutorial/default-frontpage.html
+
+You can run the server in different ways by passing command-line
+options to ``wsgi-server``, or by editing ``server.conf``. The
+default setup only serves connection from the local computer
+(use ``host='0.0.0.0'`` to allow connections from anywhere), and
+serves on port 8080 (use ``port = 80`` to make it the default
+server). However, this default server (WSGIUtils) is not suggested
+for production use. Twisted includes a better WSGI server, and there
+are a variety of ways to connection your application to Apache or
+IIS. We'll ignore those deployment issues now, though, and just use
+locahost:8080.
+
+
+The Sample Application
+======================
+
+We'll be making a simple to-do list application. The application will
+have multiple lists, and each list has multiple items.
+
+
+Using a database
+================
+
+.. note::
+
+ These SQLObject classes could be considered your "model", but
+ really your model is whatever you want it to be -- there's no
+ formal concept of a model in this tutorial.
+
+The first thing we'll set up is a database connection. This example
+uses SQLObject_, which is a object-relationer mapper -- basically it
+makes your database tables look like Python classes, and each row in
+those tables are instances of those classes.
+
+We'll be creating two tables:
+
++--------------------+
+| todo_list |
++=============+======+
+| id | INT |
++-------------+------+
+| description | TEXT |
++-------------+------+
+
++--------------------------------+
+| todo_item |
++==============+=================+
+| id | INT PRIMARY KEY |
++--------------+-----------------+
+| todo_list_id | INT NOT NULL |
++--------------+-----------------+
+| description | TEXT |
++--------------+-----------------+
+| done | BOOLEAN |
++--------------+-----------------+
+
+The actual types will differ somewhat depending on what database we'll
+be using. PostgreSQL, for instance, has a ``BOOLEAN`` data type, but
+on MySQL we'll just use ``INT``. SQLObject, however, handles all this
+for us. We just define the classes:
+
+.. _SQLObject: http://sqlobject.org
+
+.. comment (create db.py)
+
+ >>> os.chdir(BASE)
+ >>> create_file('db.py', r"""
+ ... from sqlobject import *
+ ...
+ ... class TodoList(SQLObject):
+ ...
+ ... description = StringCol(notNull=True)
+ ... items = MultipleJoin('TodoItem')
+ ...
+ ... class TodoItem(SQLObject):
+ ...
+ ... todo_list = ForeignKey('TodoList')
+ ... description = StringCol(notNull=True)
+ ... done = BoolCol(notNull=True, default=False)
+ ... """, syntax="python")
+
+.. raw:: html
+ :file: resources/TodoTutorial/db.py.gen.html
+
+Some things to notice. First, all the symbols and classes you see in
+this example were imported with ``from sqlobject import *``.
+We create two new classes -- ``TodoList`` and ``TodoItem``.
+
+Each table is a class (that subclasses from ``SQLObject``). We use
+the normal naming style of classes (StudlyCaps), but SQLObject
+translates this to an underscore style for the database.
+
+Each column is an attribute of the class, using special classes to
+indicate the type (``StringCol``, ``BoolCol``, etc). Keyword
+arguments are used to indicate things like whether ``NULL`` is allowed
+(by default it is), and if there's a default value (SQLObject doesn't
+treat NULL as a default), and you could give the size of the text
+fields (by default they are all ``TEXT`` -- in these modern days
+it's not necessary to specify the length of your fields).
+
+SQLObject knows about several databases -- PostgreSQL, MySQL, and
+SQLite are especially well supported. It can hide much of the
+specifics of the database, including generating the ``CREATE``
+statement. We'll use SQLite in this example, but it's pretty much
+trivial to use another backend.
+
+First, we have to add configuration to our ``server.conf`` file.
+We'll add these lines::
+
+ import os
+ database = 'sqlite:%s/data.db' % os.path.dirname(__file__)
+
+We use ``os.path.dirname(__file__)`` to put the database file in the
+same directory as ``server.conf``. You could also use something
+like::
+
+ database = 'mysql://user:passwd@...>'
+ # or:
+ database = 'postgresql://user:password@...>'
+
+.. comment (change server.conf)
+
+ >>> change_file('server.conf', [('insert', 2, r"""import os
+ ... database = 'sqlite:%s/data.db' % os.path.dirname(__file__)
+ ...
+ ... """)])
+
+.. comment (put sqlobject-admin in path)
+
+Now we have to actually create the tables; we'll use the
+``sqlobject-admin`` script to help us with this. First::
+
+ >>> run('sqlobject-admin sql -f server.conf -m todo_sql.db')
+ CREATE TABLE todo_item (
+ id INTEGER PRIMARY KEY,
+ done TINYINT NOT NULL,
+ todo_list_id INT,
+ description TEXT NOT NULL
+ );
+ CREATE TABLE todo_list (
+ id INTEGER PRIMARY KEY,
+ description TEXT NOT NULL
+ );
+
+You have to be sure ``/var/www`` is in your ``$PYTHONPATH`` so that
+your ``todo_sql/`` directory is a module that Python can load. ``-m
+todo_sql.db`` tells ``sqlobject-admin`` to load that module, look for
+SQLObject classes, and use them for its command -- in this case
+(``sqlobject-admin sql``) showing the ``CREATE`` statements. Now lets
+actually create the tables::
+
+ >>> run('sqlobject-admin create -f server.conf -m todo_sql.db')
+
+It prints nothing on success (or use ``-v`` or even ``-vv`` to get
+more messages).
+
Added: WSGIKit/trunk/docs/default.css
===================================================================
--- WSGIKit/trunk/docs/default.css 2005-04-05 08:31:21 UTC (rev 2300)
+++ WSGIKit/trunk/docs/default.css 2005-04-05 08:32:43 UTC (rev 2301)
@@ -0,0 +1,390 @@
+/*
+:Author: David Goodger, Ian Bicking
+:Contact: ianb@...
+:date: $Date: 2003/11/01 20:35:45 $
+:version: $Revision: 1.3 $
+:copyright: This stylesheet has been placed in the public domain.
+
+A modification of the default cascading style sheet (v.1.3) for the
+HTML output of Docutils.
+*/
+
+body {
+ font-family: Arial, sans-serif;
+ background-color: #eeeeee;
+}
+
+em, i {
+ /* Typically serif fonts have much nicer italics */
+ font-family: Times New Roman, Times, serif;
+}
+
+li {
+ list-style-type: circle;
+}
+
+a.target {
+ color: blue;
+}
+
+a.toc-backref {
+ text-decoration: none;
+ color: black;
+}
+
+a.toc-backref:hover {
+ background-color: inherit;
+}
+
+a:hover {
+ background-color: #cccccc;
+}
+
+cite {
+ font-style: normal;
+ font-family: monospace;
+ font-weight: bold;
+}
+
+dd {
+ margin-bottom: 0.5em;
+}
+
+div.abstract {
+ margin: 2em 5em;
+}
+
+div.abstract p.topic-title {
+ font-weight: bold;
+ text-align: center;
+}
+
+div.attention, div.caution, div.danger, div.error, div.hint,
+div.important, div.note, div.tip, div.warning {
+ background-color: #cccccc;
+ width: 40%;
+ border: medium outset;
+ padding: 3px;
+ float: right
+}
+
+div.attention p.admonition-title, div.caution p.admonition-title,
+div.danger p.admonition-title, div.error p.admonition-title,
+div.warning p.admonition-title {
+ color: #cc0000;
+ font-weight: bold;
+ font-family: sans-serif;
+ text-align: center;
+ background-color: #999999;
+ display: block;
+ margin: 0;
+}
+
+div.hint p.admonition-title, div.important p.admonition-title,
+div.note p.admonition-title, div.tip p.admonition-title {
+ font-weight: bold;
+ font-family: sans-serif;
+ text-align: center;
+ background-color: #999999;
+ display: block;
+ margin: 0;
+}
+
+div.dedication {
+ margin: 2em 5em;
+ text-align: center;
+ font-style: italic;
+}
+
+div.dedication p.topic-title {
+ font-weight: bold;
+ font-style: normal;
+}
+
+div.figure {
+ margin-left: 2em;
+}
+
+div.footer, div.header {
+ font-size: smaller;
+}
+
+div.system-messages {
+ margin: 5em;
+}
+
+div.system-messages h1 {
+ color: red;
+}
+
+div.system-message {
+ border: medium outset;
+ padding: 1em;
+}
+
+div.system-message p.system-message-title {
+ color: red;
+ font-weight: bold;
+}
+
+div.topic {
+ margin: 2em;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ font-family: Helvetica, Arial, sans-serif;
+ border: thin solid black;
+ /* This makes the borders rounded on Mozilla, which pleases me */
+ -moz-border-radius: 8px;
+ padding: 4px;
+}
+
+h1 {
+ background-color: #444499;
+ color: #ffffff;
+ border: medium solid black;
+}
+
+h1 a.toc-backref, h2 a.toc-backref {
+ color: #ffffff;
+}
+
+h2 {
+ background-color: #666666;
+ color: #ffffff;
+ border: medium solid black;
+}
+
+h3, h4, h5, h6 {
+ background-color: #cccccc;
+ color: #000000;
+}
+
+h3 a.toc-backref, h4 a.toc-backref, h5 a.toc-backref,
+h6 a.toc-backref {
+ color: #000000;
+}
+
+h1.title {
+ text-align: center;
+ background-color: #444499;
+ color: #ffffff;
+ border: thick solid black;
+ -moz-border-radius: 20px;
+}
+
+h2.subtitle {
+ text-align: center;
+}
+
+hr {
+ width: 75%;
+}
+
+ol.simple, ul.simple {
+ margin-bottom: 1em;
+}
+
+ol.arabic {
+ list-style: decimal;
+}
+
+ol.loweralpha {
+ list-style: lower-alpha;
+}
+
+ol.upperalpha {
+ list-style: upper-alpha;
+}
+
+ol.lowerroman {
+ list-style: lower-roman;
+}
+
+ol.upperroman {
+ list-style: upper-roman;
+}
+
+p.caption {
+ font-style: italic;
+}
+
+p.credits {
+ font-style: italic;
+ font-size: smaller;
+}
+
+p.first {
+ margin-top: 0;
+}
+
+p.label {
+ white-space: nowrap;
+}
+
+p.topic-title {
+ font-weight: bold;
+}
+
+pre.address {
+ margin-bottom: 0;
+ margin-top: 0;
+ font-family: serif;
+ font-size: 100%;
+}
+
+pre.line-block {
+ font-family: serif;
+ font-size: 100%;
+}
+
+pre.literal-block, pre.doctest-block {
+ margin-left: 2em;
+ margin-right: 2em;
+ background-color: #ffffff;
+ border: thin black solid;
+ padding: 5px;
+}
+
+span.classifier {
+ font-family: sans-serif;
+ font-style: oblique;
+}
+
+span.classifier-delimiter {
+ font-family: sans-serif;
+ font-weight: bold;
+}
+
+span.interpreted {
+ font-family: sans-serif;
+}
+
+span.option-argument {
+ font-style: italic;
+}
+
+span.pre {
+ white-space: pre;
+}
+
+span.problematic {
+ color: red;
+}
+
+table {
+ margin-top: 0.5em;
+ margin-bottom: 0.5em;
+}
+
+table.citation {
+ border-left: solid thin gray;
+ padding-left: 0.5ex
+}
+
+table.docinfo {
+ margin: 2em 4em;
+}
+
+table.footnote {
+ border-left: solid thin black;
+ padding-left: 0.5ex;
+}
+
+td, th {
+ padding-left: 0.5em;
+ padding-right: 0.5em;
+ vertical-align: top;
+}
+
+td > p:first-child, th > p:first-child {
+ margin-top: 0em;
+}
+
+th.docinfo-name, th.field-name {
+ font-weight: bold;
+ text-align: left;
+ white-space: nowrap;
+}
+
+h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt {
+ font-size: 100%;
+}
+
+code, tt {
+ color: #000066;
+}
+
+ul.auto-toc {
+ list-style-type: none;
+}
+
+/*****************************************
+ * Doctest embedded examples
+ *****************************************/
+
+span.doctest-url {
+ background-color: #fff;
+ border-top: 2px outset #666;
+ border-left: 2px outset #666;
+ border-right: 2px outset #666;
+ padding: 0.25em;
+}
+
+div.doctest-example {
+ border: outset 5px #666;
+ background-color: #fff;
+ font-family: default;
+ padding: 0.5em;
+}
+
+div.doctest-example h1 {
+ background-color: inherit;
+ border: none;
+ color: inherit;
+ font-family: default;
+}
+
+div.doctest-example tt {
+ color: inherit;
+}
+
+div.doctest-status {
+ background-color: #006600;
+ color: #ffffff;
+}
+
+span.doctest-header {
+ background-color: #000066;
+ color: #ffffff;
+ font-family: sans-serif;
+}
+
+pre.doctest-errors {
+ border: none;
+ background-color: #ffffff;
+ color: #660000;
+}
+
+div.source-code {
+ background-color: #000000;
+ border: inset #999999 3px;
+ overflow: auto;
+}
+
+pre.source-code {
+ background-color: #000000;
+ border: inset #999999 3px;
+ overflow: auto;
+ font-family: sans-serif;
+}
+
+span.source-filename {
+ background-color: #000;
+ border-top: 2px outset #999;
+ border-left: 2px outset #999;
+ border-right: 2px outset #999;
+ padding: 0.25em;
+ color: #fff
+}
+
Property changes on: WSGIKit/trunk/docs/resources/TodoTutorial
___________________________________________________________________
Name: svn:ignore
+ *.gen.html
Added: WSGIKit/trunk/docs/resources/TodoTutorial/default-frontpage.html
===================================================================
--- WSGIKit/trunk/docs/resources/TodoTutorial/default-frontpage.html 2005-04-05 08:31:21 UTC (rev 2300)
+++ WSGIKit/trunk/docs/resources/TodoTutorial/default-frontpage.html 2005-04-05 08:32:43 UTC (rev 2301)
@@ -0,0 +1,110 @@
+<span class="doctest-url"><a href="http://localhost:8080/">http://localhost:8080/</a></span><br>
+<div class="doctest-example">
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
+ "http://www.w3.org/TR/REC-html40/loose.dtd">
+<html lang="en-US">
+<head>
+<title>Welcome to your new app</title>
+<link rel="stylesheet" type="text/css"
+ href="/static/stylesheet.css">
+</head>
+<body>
+<h1>Welcome to your new app</h1>
+<div id="content">
+<p>
+Congratulations, you have set up your new application instance.
+This page serves as an example; feel free to overwrite it.
+</p>
+<p>
+These are all the environmental variables defined:
+</p>
+<table border="1">
+ <tr>
+ <td>CONTENT_LENGTH</td>
+ <td><tt>0</tt></td>
+ </tr>
+ <tr>
+ <td>HTTP_HOST</td>
+ <td><tt>localhost:80</tt></td>
+ </tr>
+ <tr>
+ <td>PATH_INFO</td>
+ <td><tt></tt></td>
+ </tr>
+ <tr>
+ <td>REQUEST_METHOD</td>
+ <td><tt>GET</tt></td>
+ </tr>
+ <tr>
+ <td>REQUEST_URI</td>
+ <td><tt>/</tt></td>
+ </tr>
+ <tr>
+ <td>SCRIPT_NAME</td>
+ <td><tt>/</tt></td>
+ </tr>
+ <tr>
+ <td>SERVER_NAME</td>
+ <td><tt>localhost</tt></td>
+ </tr>
+ <tr>
+ <td>SERVER_PORT</td>
+ <td><tt>80</tt></td>
+ </tr>
+ <tr>
+ <td>todo_sql.base_url</td>
+ <td><tt></tt></td>
+ </tr>
+ <tr>
+ <td>wsgi.errors</td>
+ <td><tt><cStringIO.StringO object at ...></tt></td>
+ </tr>
+ <tr>
+ <td>wsgi.input</td>
+ <td><tt><cStringIO.StringI object at ...></tt></td>
+ </tr>
+ <tr>
+ <td>wsgi.multiprocess</td>
+ <td><tt>False</tt></td>
+ </tr>
+ <tr>
+ <td>wsgi.multithread</td>
+ <td><tt>False</tt></td>
+ </tr>
+ <tr>
+ <td>wsgi.run_once</td>
+ <td><tt>False</tt></td>
+ </tr>
+ <tr>
+ <td>wsgi.url_scheme</td>
+ <td><tt>http</tt></td>
+ </tr>
+ <tr>
+ <td>wsgi.version</td>
+ <td><tt>(1, 0)</tt></td>
+ </tr>
+ <tr>
+ <td>wsgikit.config</td>
+ <td><tt>{'webkit_dir': '/var/www/todo_sql/web', 'sys_path': ['/var/www'], 'verbose': True, 'index_names': ['index', 'Index', 'main', 'Main'], 'app_template': 'webkit_zpt', 'server': 'wsgiutils', 'reload': True, 'host': 'localhost', 'debug': True, 'port': 8080, 'app_name': 'todo_sql'}</tt></td>
+ </tr>
+ <tr>
+ <td>wsgikit.recursive.forward</td>
+ <td><tt><wsgikit.recursive.Forwarder object at ...></tt></td>
+ </tr>
+ <tr>
+ <td>wsgikit.recursive.include</td>
+ <td><tt><wsgikit.recursive.Includer object at ...></tt></td>
+ </tr>
+ <tr>
+ <td>wsgikit.session.factory</td>
+ <td><tt><wsgikit.session.SessionFactory object at ...></tt></td>
+ </tr>
+ <tr>
+ <td>wsgikit.urlparser.base_python_name</td>
+ <td><tt>web</tt></td>
+ </tr>
+</table>
+</div>
+</body>
+</html>
+</div>
Added: WSGIKit/trunk/wsgikit/tests/doctest_webapp.py
===================================================================
--- WSGIKit/trunk/wsgikit/tests/doctest_webapp.py 2005-04-05 08:31:21 UTC (rev 2300)
+++ WSGIKit/trunk/wsgikit/tests/doctest_webapp.py 2005-04-05 08:32:43 UTC (rev 2301)
@@ -0,0 +1,206 @@
+#!/usr/bin/env python2.4
+"""
+These are functions for use when doctest-testing a document.
+"""
+
+import subprocess
+import doctest
+import os
+import sys
+import shutil
+import re
+from cStringIO import StringIO
+from wsgikit import server
+from wsgikit import wsgilib
+from wsgikit.util.thirdparty import add_package
+add_package('PySourceColor')
+import PySourceColor
+
+
+here = os.path.abspath(__file__)
+wsgikit_parent = os.path.dirname(
+ os.path.dirname(os.path.dirname(here)))
+
+def run(command):
+ """
+ Runs the string command, prints any output.
+ """
+ env = os.environ.copy()
+ env['PATH'] = (env.get('PATH', '')
+ + ':'
+ + os.path.join(wsgikit_parent, 'scripts')
+ + ':'
+ + os.path.join(wsgikit_parent, 'wsgikit', '3rd-party',
+ 'sqlobject-files', 'scripts'))
+ env['PYTHONPATH'] = (env.get('PYTHONPATH', '')
+ + ':'
+ + wsgikit_parent)
+ proc = subprocess.Popen(command, shell=True, stderr=subprocess.STDOUT,
+ stdout=subprocess.PIPE, env=env)
+ data = proc.stdout.read()
+ proc.wait()
+ while data.endswith('\n') or data.endswith('\r'):
+ data = data[:-1]
+ if data:
+ data = '\n'.join(
+ [l for l in data.splitlines() if l])
+ print data
+
+def clear_dir(dir):
+ """
+ Clears (deletes) the given directory
+ """
+ shutil.rmtree(dir, True)
+
+def ls(dir=None, recurse=False, indent=0):
+ """
+ Show a directory listing
+ """
+ dir = dir or os.getcwd()
+ fns = os.listdir(dir)
+ fns.sort()
+ for fn in fns:
+ full = os.path.join(dir, fn)
+ if os.path.isdir(full):
+ fn = fn + '/'
+ print ' '*indent + fn
+ if os.path.isdir(full) and recurse:
+ ls(dir=full, recurse=True, indent=indent+2)
+
+def make_app(dir):
+ os.chdir(dir)
+ sys.path.append(os.path.dirname(dir))
+ conf, app = server.load_commandline((), allow_reload=False)
+ assert conf is not None, (
+ "server.load_commandline requested exit with code %r"
+ % app)
+ return app
+
+default_app = None
+default_url = None
+
+def set_default_app(app, url):
+ global default_app
+ global default_url
+ default_app = app
+ default_url = url
+
+def resource_filename(fn):
+ """
+ Returns the filename of the resource -- generally in the directory
+ resources/DocumentName/fn
+ """
+ return os.path.join(
+ os.path.dirname(sys.testing_document_filename),
+ 'resources',
+ os.path.splitext(os.path.basename(sys.testing_document_filename))[0],
+ fn)
+
+def show(path_info, example_name, **environ):
+ fn = resource_filename(example_name + '.html')
+ out = StringIO()
+ assert default_app is not None, (
+ "No default_app set")
+ url = default_url + path_info
+ out.write('<span class="doctest-url"><a href="%s">%s</a></span><br>\n'
+ % (url, url))
+ out.write('<div class="doctest-example">\n')
+ status, headers, content, errors = wsgilib.raw_interactive(
+ default_app, path_info, **environ)
+ if int(status.split()[0]) != 200:
+ out.write('<span class="doctest-status">%s</span><br>\n'
+ % status)
+ for header, value in headers:
+ if header.lower() in ('content-type', 'content-length'):
+ continue
+ if (header.lower() == 'set-cookie'
+ and value.startswith('_SID_')):
+ continue
+ out.write('<span class="doctest-header">%s: %s</span><br>\n'
+ % (header, value))
+ lines = [l for l in content.splitlines() if l.strip()]
+ for line in lines:
+ out.write(line + '\n')
+ if errors:
+ out.write('<pre class="doctest-errors">%s</pre>'
+ % errors)
+ out.write('</div>\n')
+ result = out.getvalue()
+ if not os.path.exists(fn):
+ f = open(fn, 'wb')
+ f.write(result)
+ f.close()
+ else:
+ f = open(fn, 'rb')
+ expected = f.read()
+ f.close()
+ if not html_matches(expected, result):
+ print 'Pages did not match. Expected from %s:' % fn
+ print '-'*60
+ print expected
+ print '='*60
+ print 'Actual output:'
+ print '-'*60
+ print result
+
+def html_matches(pattern, text):
+ regex = re.escape(pattern)
+ regex = regex.replace(r'\.\.\.', '.*')
+ regex = '^%s$' % regex
+ return re.search(regex, text)
+
+def create_file(path, data, syntax=None):
+ if data.startswith('\n'):
+ data = data[1:]
+ lines = data.splitlines()
+ new_lines = []
+ for line in lines:
+ if line.rstrip() == '.':
+ new_lines.append('')
+ else:
+ new_lines.append(line)
+ data = '\n'.join(new_lines)
+ if syntax is None:
+ html = '<pre class="source-code">%s</pre>' % cgi.escape(data, 1)
+ elif syntax == 'python':
+ html = ('<div class="source-code">%s</div>'
+ % PySourceColor.str2html(data, PySourceColor.dark))
+ else:
+ assert 0, (
+ "Unknown syntax: %r" % syntax)
+ html = '<span class="source-filename">%s</span><br>%s' % (
+ path, html)
+ fn = resource_filename('%s.gen.html' % path)
+ f = open(fn, 'wb')
+ f.write(html)
+ f.close()
+ f = open(path, 'wb')
+ f.write(data)
+ f.close()
+
+def change_file(path, changes):
+ f = open(path, 'rb')
+ lines = f.readlines()
+ f.close()
+ for change_type, line, text in changes:
+ if change_type == 'insert':
+ lines[line:line] = text
+ else:
+ assert 0, (
+ "Unknown change_type: %r" % change_type)
+ f = open(path, 'wb')
+ f.write(''.join(lines))
+ f.close()
+
+if __name__ == '__main__':
+ import sys
+ if not wsgikit_parent in sys.path:
+ sys.path.append(wsgikit_parent)
+ for fn in sys.argv[1:]:
+ fn = os.path.abspath(fn)
+ # @@: OK, ick; but this module gets loaded twice
+ sys.testing_document_filename = fn
+ doctest.testfile(fn, module_relative=False)
+ new = os.path.splitext(fn)[0] + '.html'
+ assert new != fn
+ os.system('rest2html %s > %s' % (fn, new))
Property changes on: WSGIKit/trunk/wsgikit/tests/doctest_webapp.py
___________________________________________________________________
Name: svn:executable
+ *
|