comparing with http://hg.hunch.se/smisk
searching for changes
changeset:   137:58cfded07361
user:        Eric Moritz <eric@themoritzfamily.com>
date:        Thu May 08 22:03:59 2008 +0000
summary:     Fixed a typo when trying to call request.has_begun

diff -r 0ddab23e8dc8 -r 58cfded07361 lib/smisk/wsgi.py
--- a/lib/smisk/wsgi.py	Thu May 08 22:07:11 2008 +0200
+++ b/lib/smisk/wsgi.py	Thu May 08 22:03:59 2008 +0000
@@ -84,7 +84,7 @@ class Application(smisk.Application):
     """'start_response()' callable as specified by PEP 333"""
     if exc_info:
       try:
-        if self.response.has_begun:
+        if self.response.has_begun():
           raise exc_info[0],exc_info[1],exc_info[2]
         else:
           # In this case of response not being initiated yet, this will replace 
@@ -133,7 +133,8 @@ class Application(smisk.Application):
 
 if __name__ == '__main__':
   import sys
-  
+  from wsgiref.validate import validator # Import the wsgi validator app
+
   def hello_app(env, start_response):
     start_response("200 OK", [])
     return ["Hello, World"]
@@ -144,4 +145,6 @@ if __name__ == '__main__':
     print "port given in argv[1]"
 
   smisk.bind(sys.argv[1])
-  Application(hello_app).run()
+
+  app = validator(hello_app)
+  Application(app).run()

changeset:   138:fcda869a6d89
user:        Eric Moritz <eric@themoritzfamily.com>
date:        Fri May 09 02:12:11 2008 +0000
summary:     Added a test to try to replicate the issue I'm having with form posts inside of

diff -r 58cfded07361 -r fcda869a6d89 tests/wsgi/html/test_POST.html
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/wsgi/html/test_POST.html	Fri May 09 02:12:11 2008 +0000
@@ -0,0 +1,311 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en-us" xml:lang="en-us" >
+<head>
+<title>Change feed | Django site admin</title>
+<link rel="stylesheet" type="text/css" href="/admin/media/css/forms.css" />
+
+
+
+<script type="text/javascript" src="../../../jsi18n/"></script>
+<script type="text/javascript" src="/admin/media/js/core.js"></script><script type="text/javascript" src="/admin/media/js/admin/RelatedObjectLookups.js"></script><script type="text/javascript" src="/admin/media/js/calendar.js"></script><script type="text/javascript" src="/admin/media/js/admin/DateTimeShortcuts.js"></script><script type="text/javascript" src="/admin/media/js/admin/CollapsedFieldsets.js"></script>
+
+<meta name="robots" content="NONE,NOARCHIVE" />
+</head>
+
+
+<body class="feedjack-feed change-form">
+
+<!-- Container -->
+<div id="container">
+
+    
+    <!-- Header -->
+    <div id="header">
+        <div id="branding">
+        
+
+<h1 id="site-name">Django administration</h1>
+
+        </div>
+        
+        <div id="user-tools">
+        Welcome, <strong>eric</strong>.
+        
+        <a href="/admin/doc/">Documentation</a>
+        / <a href="/admin/password_change/">Change password</a>
+
+        / <a href="/admin/logout/">Log out</a>
+        
+        </div>
+        
+        
+    </div>
+    <!-- END Header -->
+    
+<div class="breadcrumbs">
+     <a href="../../../">Home</a> &rsaquo;
+     <a href="../">Feeds</a> &rsaquo;
+
+     Blog (http://eric.themoritzfamily.com/feed/rss/)
+</div>
+
+    
+
+        
+
+    <!-- Content -->
+    <div id="content" class="colM">
+        
+        <h1>Change feed</h1>
+        <div id="content-main">
+
+
+  <ul class="object-tools"><li><a href="history/" class="historylink">History</a></li>
+
+  
+  </ul>
+
+
+<form enctype="multipart/form-data" action="" method="post" id="feed_form">
+<div>
+
+
+
+
+   <fieldset class="module aligned ()">
+    
+    
+    
+        
+<div class="form-row" >
+
+
+  <label for="id_feed_url" class="required">Feed url:</label> 
+  <input type="text" id="id_feed_url" class="vURLField required" name="feed_url" size="50" value="http://eric.themoritzfamily.com/feed/rss/" maxlength="200" />
+
+  
+  
+
+</div>
+
+        
+            
+        
+    
+        
+<div class="form-row" >
+
+
+  <label for="id_name" class="required">Name:</label> 
+  <input type="text" id="id_name" class="vTextField required" name="name" size="30" value="Blog" maxlength="100" />
+
+  
+  
+
+</div>
+
+        
+            
+        
+    
+        
+
+<div class="form-row" >
+
+
+  <label for="id_shortname" class="required">Shortname:</label> 
+  <input type="text" id="id_shortname" class="vTextField required" name="shortname" size="30" value="blog" maxlength="50" />
+
+  
+  
+
+</div>
+
+        
+            
+        
+    
+        
+<div class="form-row checkbox-row" >
+
+
+  
+  <input type="checkbox" id="id_is_active" class="vCheckboxField" name="is_active" checked="checked" />
+
+  <label for="id_is_active" class="vCheckboxLabel">Is active</label> 
+  <p class="help">If disabled, this feed will not be further updated.</p>
+
+</div>
+
+        
+            
+        
+    
+        
+<div class="form-row" >
+ 
+
+  <label for="id_icon_file">Feed image:</label> 
+  
+Currently: <a href="/media/feeds/favicon.png" > feeds/favicon.png </a><br />
+
+Change:<input type="file" id="id_icon_file" class="vImageUploadField" name="icon_file" /><input type="hidden" id="id_icon" name="icon" value="feeds/favicon.png" />
+
+
+  
+  
+
+</div>
+
+        
+            
+        
+    
+   </fieldset>
+
+   <fieldset class="module aligned collapse">
+    <h2>Fields updated automatically by Feedjack</h2>
+    
+    
+        
+<div class="form-row" >
+
+
+  <label for="id_title">Title:</label> 
+  <input type="text" id="id_title" class="vTextField" name="title" size="30" value="eric.themoritzfamily.com&#39;s Weblog" maxlength="200" />
+
+  
+  
+
+</div>
+
+        
+            
+        
+    
+        
+<div class="form-row" >
+
+
+  <label for="id_tagline">Tagline:</label> 
+  <textarea id="id_tagline" class="vLargeTextField" name="tagline" rows="10" cols="40">Latest entries on Weblog</textarea>
+
+  
+  
+
+</div>
+
+        
+            
+        
+    
+        
+<div class="form-row" >
+
+
+  <label for="id_link">Link:</label> 
+  <input type="text" id="id_link" class="vURLField" name="link" size="50" value="http://eric.themoritzfamily.com/" maxlength="200" />
+
+  
+  
+
+</div>
+
+        
+            
+        
+    
+        
+
+<div class="form-row" >
+
+
+  <label for="id_etag">Etag:</label> 
+  <input type="text" id="id_etag" class="vTextField" name="etag" size="30" value="" maxlength="50" />
+
+  
+  
+
+</div>
+
+        
+            
+        
+    
+        
+<div class="form-row" >
+ 
+
+  <label for="id_last_modified_date">Last modified:</label> 
+  
+<p class="datetime"> 
+   Date: <input type="text" id="id_last_modified_date" class="vDateField" name="last_modified_date" size="10" value="" maxlength="10" /><br />
+
+   Time: <input type="text" id="id_last_modified_time" class="vTimeField" name="last_modified_time" size="8" value="" maxlength="8" />
+</p>
+
+  
+  
+
+</div>
+
+        
+            
+        
+    
+        
+<div class="form-row" >
+ 
+
+  <label for="id_last_checked_date">Last checked:</label> 
+  
+<p class="datetime"> 
+   Date: <input type="text" id="id_last_checked_date" class="vDateField" name="last_checked_date" size="10" value="2008-04-15" maxlength="10" /><br />
+   Time: <input type="text" id="id_last_checked_time" class="vTimeField" name="last_checked_time" size="8" value="16:36:02" maxlength="8" />
+
+</p>
+
+  
+  
+
+</div>
+
+        
+            
+        
+    
+   </fieldset>
+
+
+
+   
+
+
+
+
+<div class="submit-row">
+<p class="float-left"><a href="delete/" class="deletelink">Delete</a></p>
+
+<input type="submit" value="Save and add another" name="_addanother"  />
+<input type="submit" value="Save and continue editing" name="_continue" />
+
+<input type="submit" value="Save" class="default" />
+</div>
+
+
+
+</div>
+</form></div>
+
+        
+        <br class="clear" />
+    </div>
+    <!-- END Content -->
+
+    <div id="footer"></div>
+
+</div>
+<!-- END Container -->
+
+</body>
+</html>
diff -r 58cfded07361 -r fcda869a6d89 tests/wsgi/test_POST.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/wsgi/test_POST.py	Fri May 09 02:12:11 2008 +0000
@@ -0,0 +1,22 @@
+"""This module exposes the post bug that Eric Moritz is experiences
+
+where smisk segfaults
+"""
+from smisk import wsgi
+import smisk
+
+def WSGIPostTest(environ, start_request):
+
+    if environ['REQUEST_METHOD'] == 'GET':
+        fh = file("./html/test_POST.html")
+        lines = fh.readlines()
+        fh.close()
+        start_request("200 OK", [])
+        return lines
+    elif environ['REQUEST_METHOD'] == 'POST':
+        input = environ['wsgi.input'].read()
+        start_request("200 OK", [])
+        return [input]
+
+smisk.bind("127.0.0.1:3030")
+wsgi.Application(WSGIPostTest).run()

changeset:   139:7c0397277571
user:        Eric Moritz <eric@themoritzfamily.com>
date:        Fri May 09 02:37:19 2008 +0000
summary:     Sweet I found the offending code from Django.

diff -r fcda869a6d89 -r 7c0397277571 tests/wsgi/test_POST.py
--- a/tests/wsgi/test_POST.py	Fri May 09 02:12:11 2008 +0000
+++ b/tests/wsgi/test_POST.py	Fri May 09 02:37:19 2008 +0000
@@ -4,6 +4,40 @@ where smisk segfaults
 """
 from smisk import wsgi
 import smisk
+from StringIO import StringIO
+
+def safe_copyfileobj(fsrc, fdst, length=16*1024, size=0):
+    """
+    A version of shutil.copyfileobj that will not read more than 'size' bytes.
+    This makes it safe from clients sending more than CONTENT_LENGTH bytes of
+    data in the body.
+    """
+    if not size:
+        return
+    while size > 0:
+        buf = fsrc.read(min(length, size))
+        if not buf:
+            break
+        fdst.write(buf)
+        size -= len(buf)
+
+
+# I think this is the offender, taken from Django's WSGIRequest object in 
+# django.core.handlers.wsgi
+def _get_raw_post_data(environ):
+    buf = StringIO()
+    try:
+        # CONTENT_LENGTH might be absent if POST doesn't have content at all (lighttpd)
+        content_length = int(environ.get('CONTENT_LENGTH', 0))
+    except ValueError: # if CONTENT_LENGTH was empty string or not an integer
+        content_length = 0
+    if content_length > 0:
+        safe_copyfileobj(environ['wsgi.input'], buf,
+                         size=content_length)
+    _raw_post_data = buf.getvalue()
+    buf.close()
+    return _raw_post_data
+
 
 def WSGIPostTest(environ, start_request):
 
@@ -14,9 +48,9 @@ def WSGIPostTest(environ, start_request)
         start_request("200 OK", [])
         return lines
     elif environ['REQUEST_METHOD'] == 'POST':
-        input = environ['wsgi.input'].read()
+        raw_post_data = _get_raw_post_data(environ)
         start_request("200 OK", [])
-        return [input]
+        return [raw_post_data]
 
 smisk.bind("127.0.0.1:3030")
 wsgi.Application(WSGIPostTest).run()

changeset:   140:900f51abcf62
user:        Eric Moritz <eric@themoritzfamily.com>
date:        Fri May 09 03:45:42 2008 +0000
summary:     Changed index of type argument for the Stream.read and Stream.readline methods

diff -r 7c0397277571 -r 900f51abcf62 src/Stream.c
--- a/src/Stream.c	Fri May 09 02:37:19 2008 +0000
+++ b/src/Stream.c	Fri May 09 03:45:42 2008 +0000
@@ -69,20 +69,20 @@ PyDoc_STRVAR(smisk_Stream_readline_DOC,
   ":rtype: string\n"
   ":returns: the line read or None if EOF");
 PyObject* smisk_Stream_readline(smisk_Stream* self, PyObject* args) {
-  PyObject *str, *arg1;
+  PyObject *str, *arg0;
   Py_ssize_t length;
   
   // Get length
   if (args && PyTuple_GET_SIZE(args) > 0) {
-    if( (arg1 = PyTuple_GET_ITEM(args, 1)) == NULL ) {
+    if( (arg0 = PyTuple_GET_ITEM(args, 0)) == NULL ) {
       length = SMISK_STREAM_READLINE_LENGTH;
     }
-    else if(!PyInt_Check(arg1)) {
+    else if(!PyInt_Check(arg0)) {
       PyErr_Format(PyExc_TypeError, "length argument must be an integer");
       return NULL;
     }
     else {
-      length = PyInt_AS_LONG(arg1);
+      length = PyInt_AS_LONG(arg0);
     }
   }
   else {
@@ -146,21 +146,21 @@ PyDoc_STRVAR(smisk_Stream_read_DOC,
   ":rtype: string");
 PyObject* smisk_Stream_read(smisk_Stream* self, PyObject* args) {
   //log_debug("ENTER smisk_Stream_read");
-  PyObject *str, *arg1;
+  PyObject *str, *arg0;
   Py_ssize_t length;
   int rc;
   
   // Get length
   if (PyTuple_GET_SIZE(args) > 0) {
-    if( (arg1 = PyTuple_GET_ITEM(args, 1)) == NULL ) { // None
+    if( (arg0 = PyTuple_GET_ITEM(args, 0)) == NULL ) { // None
       length = -1;
     }
-    else if(!PyInt_Check(arg1)) {
+    else if(!PyInt_Check(arg0)) {
       PyErr_Format(PyExc_TypeError, "length argument must be an integer");
       return NULL;
     }
     else {
-      length = PyInt_AS_LONG(arg1);
+      length = PyInt_AS_LONG(arg0);
     }
   }
   else {

changeset:   168:548804b3594c
tag:         tip
parent:      140:900f51abcf62
parent:      167:f53964d40165
user:        Eric Moritz <eric@themoritzfamily.com>
date:        Fri May 09 03:51:44 2008 +0000
summary:     Merged from Trunk

diff -r 900f51abcf62 -r 548804b3594c .hgignore
--- a/.hgignore	Fri May 09 03:45:42 2008 +0000
+++ b/.hgignore	Fri May 09 03:51:44 2008 +0000
@@ -1,7 +1,5 @@ syntax:glob
 syntax:glob
 .DS_Store
-build*
-dist
 *.log
 fcgi.so
 *.sock*
@@ -10,7 +8,18 @@ fcgi.so
 *.crash
 MANIFEST
 
+# Dirs
+build*
+dist
+doc/api*
+
 # Generated by build.py
 _config.py
 src/system_config.h
 src/version.h
+
+# Debian
+debian/*.debhelper
+debian/*.substvars
+debian/python-smisk/*
+debian/files
diff -r 900f51abcf62 -r 548804b3594c .hgtags
--- a/.hgtags	Fri May 09 03:45:42 2008 +0000
+++ b/.hgtags	Fri May 09 03:51:44 2008 +0000
@@ -1,2 +1,3 @@ 5eb82b4f8cc173b06bac4528f675265f7ba07d26
 5eb82b4f8cc173b06bac4528f675265f7ba07d26 smisk-pre-remove-ob-071109
 08a0784c68a49ef97cf43de456a5002012927363 smisk-0.1-basic-osx-debian-tested
+0ddab23e8dc82d0f9273a2098297f18411a679c1 added-wsgi-support
diff -r 900f51abcf62 -r 548804b3594c debian/changelog
--- a/debian/changelog	Fri May 09 03:45:42 2008 +0000
+++ b/debian/changelog	Fri May 09 03:51:44 2008 +0000
@@ -1,17 +1,30 @@ python-smisk (1.0b-r113-7) unstable; urg
+python-smisk (1.0b-fab839178d3f-8) unstable; urgency=low
+
+  * Bumped up the version
+
+ -- Rasmus Andersson <rasmus@flajm.se>  Fri, 09 May 2008 03:17:26 +0200
+
+python-smisk (1.0b-3cd977fa701e-7) unstable; urgency=low
+
+  * Incorporated WSGI module by Eric Moritz <eric@themoritzfamily.com>
+  * Some bug fixes, one possible bug that might have caused a segfault
+
+ -- Rasmus Andersson <rasmus@flajm.se>  Fri, 09 May 2008 01:36:48 +0200
+
 python-smisk (1.0b-r113-7) unstable; urgency=low
 
   * Release candidate 2
   * Complete set of functionality
   * Has gone through testing on different platforms
 
- -- Rasmus Andersson <rasmus@flajm.se> Sun, 16 Mar 2008 00:40:04 +0100
+ -- Rasmus Andersson <rasmus@flajm.se>  Sun, 16 Mar 2008 00:40:04 +0100
 
 python-smisk (0.1r74-6) unstable; urgency=low
 
   * Adjusted naming convention to be consistent lower_case.
   * Fixed content-length too-short-bug in automated 500 error responses.
 
- -- Rasmus Andersson <rasmus@flajm.se> Sat, 16 Feb 2008 22:15:06 +0100
+ -- Rasmus Andersson <rasmus@flajm.se>  Sat, 16 Feb 2008 22:15:06 +0100
 
 python-smisk (0.1r65-5) unstable; urgency=low
 
@@ -19,7 +32,7 @@ python-smisk (0.1r65-5) unstable; urgenc
     would go too low and result in strange replacement in a multi-value
     parameter.
 
- -- Rasmus Andersson <rasmus@flajm.se> Fri, 15 Feb 2008 11:10:30 +0100
+ -- Rasmus Andersson <rasmus@flajm.se>  Fri, 15 Feb 2008 11:10:30 +0100
 
 python-smisk (0.1r55-4) unstable; urgency=low
 
@@ -27,19 +40,19 @@ python-smisk (0.1r55-4) unstable; urgenc
   * Added multipart stream parser.
   * Fixed alot of faulty refcounts, means almost no leaks.
 
- -- Rasmus Andersson <rasmus@flajm.se> Wed, 13 Feb 2008 03:08:33 +0100
+ -- Rasmus Andersson <rasmus@flajm.se>  Wed, 13 Feb 2008 03:08:33 +0100
 
 python-smisk (0.1r50-3) unstable; urgency=low
 
   * Fixed virtual package dep.
 
- -- Rasmus Andersson <rasmus@flajm.se> Mon, 11 Feb 2008 01:37:49 +0100
+ -- Rasmus Andersson <rasmus@flajm.se>  Mon, 11 Feb 2008 01:37:49 +0100
 
 python-smisk (0.1r48-2) unstable; urgency=low
 
   * Cleaner debian structure. No change of program code.
 
- -- Rasmus Andersson <rasmus@flajm.se> Mon, 11 Feb 2008 00:46:37 +0100
+ -- Rasmus Andersson <rasmus@flajm.se>  Mon, 11 Feb 2008 00:46:37 +0100
 
 python-smisk (0.1r32-1) unstable; urgency=low
 
diff -r 900f51abcf62 -r 548804b3594c dist-debian.sh
--- a/dist-debian.sh	Fri May 09 03:45:42 2008 +0000
+++ b/dist-debian.sh	Fri May 09 03:51:44 2008 +0000
@@ -24,7 +24,7 @@ for r in $(grep -E '\([0-9\.]+r[0-9]+-[0
     PREV_DEB_DEBV=$(echo $r|cut -d - -f 2)
   fi
 done
-if [ $PREV_DEB_REV -lt $(expr $REV - 1) ]; then
+#if [ $PREV_DEB_REV -lt $(expr $REV - 1) ]; then
   LESS=$(which less)
   if [ "$LESS" == "" ]; then LESS=$(which more); fi
   if [ "$LESS" == "" ]; then LESS=$(which cat); fi
@@ -38,8 +38,7 @@ if [ $PREV_DEB_REV -lt $(expr $REV - 1) 
   while [ $NEED_ANSWER -eq 1 ]; do
     echo "[1] Review changes between r${REV} and r$(expr $PREV_DEB_REV - 1), then return here."
     echo '[2] Help me edit debian/changelog and continue.'
-    echo -n 'Enter your choice [1-2]: (2) '
-    read ANSWER
+    read -n 1 -p 'Enter your choice [1-2]: (2) ' ANSWER
     if [ "$ANSWER" == "" ]; then ANSWER=2; fi
     case $ANSWER in
       1) curl --silent "${CHANGELOG_URL}&format=changelog"|$LESS ;;
@@ -57,7 +56,7 @@ if [ $PREV_DEB_REV -lt $(expr $REV - 1) 
   echo >> debian/changelog.tmp
   echo '  * '>> debian/changelog.tmp
   echo >> debian/changelog.tmp
-  echo " -- $PREV_DEB_CONTACT $(date --rfc-2822)">> debian/changelog.tmp
+  echo " -- $PREV_DEB_CONTACT  $(date --rfc-2822)">> debian/changelog.tmp
   echo >> debian/changelog.tmp
   cat debian/changelog >> debian/changelog.tmp
   NEED_ANSWER=1
@@ -67,8 +66,7 @@ if [ $PREV_DEB_REV -lt $(expr $REV - 1) 
     SUM_AFTER=$(md5sum debian/changelog.tmp|cut -d ' ' -f 1)
     if [ "$SUM_AFTER" == "$SUM_BEFORE" ]; then
       echo 'The debian/changelog message unchanged or not specified.'
-      echo -n 'a)bort, c)ontinue, e)dit: (e) '
-      read ANSWER
+      read -n 1 -p 'a)bort, c)ontinue, e)dit: (e) ' ANSWER
       case $ANSWER in
         a) rm debian/changelog.tmp ; exit 2 ;;
         c) NEED_ANSWER=0 ;;
@@ -81,12 +79,15 @@ if [ $PREV_DEB_REV -lt $(expr $REV - 1) 
   mv debian/changelog.tmp debian/changelog
   echo 'debian/changelog updated.'
   if [ -d .svn ]; then
-    echo 'Committing changelog update to subversion'
-    svn ci -m 'Debian changelog message added' debian/changelog
+    echo 'Committing changelog update'
+    svn ci -m 'Debian changelog message added (dist-debian.sh)' debian/changelog
     svn up
+  elif [ -d .hg ]; then
+    echo 'Committing changelog update'
+    hg ci -m 'Debian changelog message added (dist-debian.sh)' debian/changelog
   fi
   ensure_clean_working_revision
-fi
+#fi
 
 
 # Build
diff -r 900f51abcf62 -r 548804b3594c dist-generic.sh
--- a/dist-generic.sh	Fri May 09 03:45:42 2008 +0000
+++ b/dist-generic.sh	Fri May 09 03:51:44 2008 +0000
@@ -33,6 +33,7 @@ rm -vf dist/ready/*.tar.gz
 rm -vf dist/ready/*.tar.gz
 mkdir -vp dist/ready
 
+DIST_ID="$VERV-$(date '+%y%m%d')-$REV"
 
 # Execute for each python environment
 for PYTHON in $@; do
@@ -40,40 +41,52 @@ for PYTHON in $@; do
   # Find package name & version if not found already.
   # Also, create source distribution:
   if [ "$HAS_PERFORMED_SDIST" == "" ]; then
-    $PYTHON setup.py sdist --formats=gztar
-    mv -v dist/$PACKAGE-$VER.tar.gz dist/ready/
+    $PYTHON setup.py sdist --formats=gztar || exit 1
+    SDIST_FILE="$(echo "$PACKAGE-$VER.tar.gz" | sed 's/'$PACKAGE'-'$VER'/'$PACKAGE'-'$DIST_ID'/g')"
+    mv -v "dist/$PACKAGE-$VER.tar.gz" "dist/ready/$SDIST_FILE"
     HAS_PERFORMED_SDIST=y
   fi
 
   # Run distutils
-  $PYTHON setup.py build --force
-  $PYTHON setup.py bdist --formats=gztar
+  $PYTHON setup.py build --force || exit 1
+  $PYTHON setup.py bdist --formats=gztar || exit 1
 
   # Add python version
-  PY_VER=$(echo $($PYTHON -V 2>&1)|sed 's/[^0-9\.]//g'|cut -d . -f 1,2)
-  BDIST_FILE_ORG=$(echo dist/$PACKAGE-$VER???????*.tar.gz)
-  BDIST_FILE=$(echo "$BDIST_FILE_ORG"|sed 's/\.tar\.gz$/-py'$PY_VER'.tar.gz/g');
-  mv -v $BDIST_FILE_ORG $BDIST_FILE
-  if [ $? -ne 0 ]; then
-    echo "Failed to mv $BDIST_FILE_ORG $BDIST_FILE" >&2
-    exit 1
-  fi
-  mv -v $BDIST_FILE dist/ready/
+  PY_VER=$(echo $($PYTHON -V 2>&1) | sed 's/[^0-9\.]//g' | cut -d . -f 1,2)
+  BDIST_FILE_ORG=$(echo dist/$PACKAGE-$VERV*.tar.gz)
+  BDIST_FILE="$(echo "$BDIST_FILE_ORG" | sed 's/\.tar\.gz$/-py'$PY_VER'.tar.gz/g')"
+  BDIST_FILE="$(echo "$BDIST_FILE" | sed 's/'$PACKAGE'-'$VER'/'$PACKAGE'-'$DIST_ID'/g')"
+  mv -v $BDIST_FILE_ORG $BDIST_FILE || exit 1
+  mv -v $BDIST_FILE dist/ready/ || exit 1
   
 done # end of each python env
+
 
 # Generate documentation
 $PYTHON setup.py apidocs
 
 # Upload & update links on server
-echo "Uploading dist/ready/$PACKAGE-$VER*.tar.gz to $REMOTE_HOST"
-scp -qC dist/ready/$PACKAGE-$VER*.tar.gz $REMOTE_HOST:$REMOTE_PATH
-ssh $REMOTE_HOST "cd $REMOTE_PATH;\
-for f in $PACKAGE-$VER*.tar.gz;do \
+CMD="cd $REMOTE_PATH;\
+for f in $PACKAGE-$DIST_ID*.tar.gz;do \
   if [ -f \"\$f\" ]; then\
-    lname=\`echo \"\$f\"|sed 's/$VER/latest/g'\`;\
+    lname=\`echo \"\$f\"|sed 's/$DIST_ID/latest/g'\`;\
     ln -sf \"\$f\" \"\$lname\";\
   fi;\
 done"
-echo "Uploading doc/api to $REMOTE_HOST"
-scp -qCr doc/api $REMOTE_HOST:$REMOTE_PATH_DOCS
+if is_local_host $REMOTE_HOST; then
+  echo "Copying dist/ready/$PACKAGE-$DIST_ID*.tar.gz to $REMOTE_PATH"
+  cp dist/ready/$PACKAGE-$DIST_ID*.tar.gz $REMOTE_PATH
+  echo $CMD | sh --verbose
+  if [ -d doc/api ]; then
+    echo "Copying doc/api"
+    cp -rf doc/api $REMOTE_PATH_DOCS
+  fi
+else
+  echo "Uploading dist/ready/$PACKAGE-$DIST_ID*.tar.gz to $REMOTE_HOST"
+  scp -qC dist/ready/$PACKAGE-$DIST_ID*.tar.gz $REMOTE_HOST:$REMOTE_PATH || exit 1
+  ssh $REMOTE_HOST $CMD || exit 1
+  if [ -d doc/api ]; then
+    echo "Uploading doc/api to $REMOTE_HOST"
+    scp -qCr doc/api $REMOTE_HOST:$REMOTE_PATH_DOCS
+  fi
+fi
diff -r 900f51abcf62 -r 548804b3594c dist.sh
--- a/dist.sh	Fri May 09 03:45:42 2008 +0000
+++ b/dist.sh	Fri May 09 03:51:44 2008 +0000
@@ -10,22 +10,27 @@ DEFAULT_PYTHON=$(which python)
 DEFAULT_PYTHON=$(which python)
 PACKAGE=$($DEFAULT_PYTHON setup.py --name)
 VER=$($DEFAULT_PYTHON setup.py --version)
-REV=$(echo "$VER"|cut -d r -f 2)
+VERV=$(echo "$VER"|cut -d - -f 1)
+REV=$(echo "$VER"|cut -d - -f 2)
 
 
 # Confirm working revision is synchronized with repository
 ensure_clean_working_revision() {
   RREV=$REV
-  if [ -d .svn ]; then RREV=$(svnversion -n); fi
-  if [ "$(echo "$RREV"|$GREP -E '[:SM]')" != "" ]; then
-    echo "Working revision $RREV is not up-to-date. Commit and/or update first."
+  if (echo "$RREV"|$GREP '+' > /dev/null); then
+    echo "Working revision $RREV is not up-to-date. You need to sort things out first."
     exit 1
   fi
 }
 
 
 is_local_host() {
-  if [ "$(host -Qt A $1|cut -f 3)" == "$(host -Qt A $(hostname -a)|cut -f 3)" ]; then
+  if (uname|grep 'Darwin' > /dev/null); then
+    hostname=$(hostname)
+  else
+    hostname=$(hostname --fqdn)
+  fi
+  if [ "$(host -t A $1|cut -f 3)" == "$(host -t A $hostname|cut -f 3)" ]; then
     return 0
   fi
   return 1
diff -r 900f51abcf62 -r 548804b3594c doc/epydoc.conf
--- a/doc/epydoc.conf	Fri May 09 03:45:42 2008 +0000
+++ b/doc/epydoc.conf	Fri May 09 03:51:44 2008 +0000
@@ -3,7 +3,7 @@ name: Smisk API Documentation
 name: Smisk API Documentation
 url: http://trac.hunch.se/smisk
 modules: smisk, smisk.core, smisk.session, smisk.core.xml
-verbosity: 5
+verbosity: 1
 simple-term: 1
 
 # Extraction
diff -r 900f51abcf62 -r 548804b3594c examples/wsgi/README
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/wsgi/README	Fri May 09 03:51:44 2008 +0000
@@ -0,0 +1,7 @@
+Shows how to use Smisk as a backend for a WSGI application.
+
+The example handler is very simplistic, but is interchangable
+with a handler in a real application.
+
+Run it like this:
+lighttpd -Df lighttpd.conf
diff -r 900f51abcf62 -r 548804b3594c examples/wsgi/lighttpd.conf
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/wsgi/lighttpd.conf	Fri May 09 03:51:44 2008 +0000
@@ -0,0 +1,14 @@
+include "../lighttpd.conf"
+server.modules += ("mod_fastcgi")
+#fastcgi.debug = 1
+fastcgi.server = (
+  "/" => ((
+    "socket" => var.CWD + "/smisk.sock",
+    "bin-path" => var.CWD + "/process.py",
+    "check-local" => "disable",
+    "bin-environment" => ("PYTHONOPTIMIZE" => "YES"),
+    "bin-copy-environment" => ("PATH", "SHELL", "USER"),
+    "min-procs" => 1,
+    "max-procs" => 1
+  )),
+)
diff -r 900f51abcf62 -r 548804b3594c examples/wsgi/process.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/wsgi/process.py	Fri May 09 03:51:44 2008 +0000
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+# encoding: utf-8
+import smisk, sys
+from smisk.wsgi import Gateway
+
+# A simple WSGI handler
+def hello_app(env, start_response):
+  start_response("200 OK", [('Content-type', 'text/plain')])
+  response = ["Hello, World!\n\n"]
+  response.append("Environment:\n\n")
+  for k in sorted(env.keys()):
+    response.append(" %s: %s\n" % (k, env[k]) )
+  return response
+
+# If any arguments was passed to us, we bind as a stand-alone process:
+if len(sys.argv) > 1:
+  smisk.bind(sys.argv[1])
+  print "Listening on %s" % sys.argv[1]
+
+# Start the application
+Gateway(hello_app).run()
diff -r 900f51abcf62 -r 548804b3594c lib/smisk/wsgi.py
--- a/lib/smisk/wsgi.py	Fri May 09 03:45:42 2008 +0000
+++ b/lib/smisk/wsgi.py	Fri May 09 03:51:44 2008 +0000
@@ -1,34 +1,50 @@
-"""This module provides a way to tie Smisk to a wsgi app
+'''
+This module provides a way to use Smisk as a WSGI backend.
 
-Copyright (c) 2008, Eric Moritz <eric@themoritzfamily.com>
-All rights reserved.
+Conforms to `PEP 333 <http://www.python.org/dev/peps/pep-0333/>`__
 
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
+Simple example:
 
-  * Redistributions of source code must retain the above copyright
-  * notice, this list of conditions and the following disclaimer.
-  * Redistributions in binary form must reproduce the above
-  * copyright notice, this list of conditions and the following
-  * disclaimer in the documentation and/or other materials provided
-  * with the distribution.  Neither the name of the <ORGANIZATION>
-  * nor the names of its contributors may be used to endorse or
-  * promote products derived from this software without specific
-  * prior written permission.
+>>> from smisk.wsgi import Gateway
+>>> def hello_app(env, start_response):
+>>>   start_response("200 OK", [])
+>>>   return ["Hello, World"]
+>>> 
+>>> Gateway(hello_app).run()
 
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-"""
+:see: http://www.python.org/dev/peps/pep-0333/
+:author: Eric Moritz
+:author: Rasmus Andersson
+'''
+# Copyright (c) 2008, Eric Moritz <eric@themoritzfamily.com>
+# All rights reserved.
+# 
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+# 
+#   * Redistributions of source code must retain the above copyright
+#   * notice, this list of conditions and the following disclaimer.
+#   * Redistributions in binary form must reproduce the above
+#   * copyright notice, this list of conditions and the following
+#   * disclaimer in the documentation and/or other materials provided
+#   * with the distribution.  Neither the name of the <ORGANIZATION>
+#   * nor the names of its contributors may be used to endorse or
+#   * promote products derived from this software without specific
+#   * prior written permission.
+# 
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
 import smisk
 
 __version__ = (0,1,0)
@@ -67,8 +83,8 @@ class Request(smisk.Request):
     raise NotImplementedError('unprepared request does not have a valid send_file')
   
 
-class Application(smisk.Application):
-  """This is the Smisk Wsgi adapter."""
+class Gateway(smisk.Application):
+  """This is the Smisk WSGI adapter"""
   # Configuration parameters; can override per-subclass or per-instance
   wsgi_version = (1,0)
   wsgi_multithread = False
@@ -76,12 +92,12 @@ class Application(smisk.Application):
   wsgi_run_once = False
   
   def __init__(self, wsgi_app):
-    smisk.Application.__init__(self)
+    super(Gateway, self).__init__()
     self.request_class = Request
     self.wsgi_app = wsgi_app
   
   def start_response(self, status, headers, exc_info=None):
-    """'start_response()' callable as specified by PEP 333"""
+    """`start_response()` callable as specified by `PEP 333 <http://www.python.org/dev/peps/pep-0333/>`__"""
     if exc_info:
       try:
         if self.response.has_begun():
@@ -147,4 +163,4 @@ if __name__ == '__main__':
   smisk.bind(sys.argv[1])
 
   app = validator(hello_app)
-  Application(app).run()
+  Gateway(app).run()
diff -r 900f51abcf62 -r 548804b3594c setup.py
--- a/setup.py	Fri May 09 03:45:42 2008 +0000
+++ b/setup.py	Fri May 09 03:51:44 2008 +0000
@@ -38,14 +38,8 @@ os.chdir(os.path.dirname(os.path.abspath
 os.chdir(os.path.dirname(os.path.abspath(__file__)))
 py_version = ".".join([str(s) for s in sys.version_info[0:2]]) # "M.m"
 
-# get revision
-revision = ''
-try:
-  (child_stdin, child_stdout) = os.popen2('svnversion -n .')
-  revision = child_stdout.read()
-except:
-  pass
-
+# Default. This may be overwritten later on if we are in a checkout
+revision = '-'
 
 required_libraries = [
   ('fcgi', ['fastcgi.h', 'fcgiapp.h']),
@@ -72,6 +66,40 @@ if os.path.isfile(sys_conf_py) and 'conf
 if os.path.isfile(sys_conf_py) and 'config' not in sys.argv:
   execfile(sys_conf_py, globals(), locals())
 
+def shell_cmd(cmd):
+  child_stdin = None
+  child_stdout = None
+  try:
+    (child_stdin, child_stdout) = os.popen2(cmd)
+    return child_stdout.read().strip()
+  finally:
+    if child_stdin: child_stdin.close()
+    if child_stdout: child_stdout.close()
+
+def revision_from_version_h():
+  f = open('src/version.h', "r")
+  try:
+    for line in f:
+      if line[:17] == '#define SMISK_REV':
+        return line[24:].strip(' "\n\r\t')
+  finally:
+    f.close() 
+  return None
+
+def coll_wild_unique(seq):
+  # Not order preserving
+  return list(set(seq))
+
+def coll_ordered_unique(seq, idfun=None):
+  # Order preserving
+  seen = set()
+  return [x for x in seq if x not in seen and not seen.add(x)]
+
+revision = shell_cmd("hg id -i")
+repo_has_changed = not os.path.exists('src/version.h') \
+  or os.path.getmtime('src/version.h') < os.path.getmtime('.hg') \
+  or revision != revision_from_version_h()
+
 #---------------------------------------
 # Commands
 
@@ -108,12 +136,16 @@ class smisk_build_core(build_ext):
     build_ext.run(self)
   
   def _update_version_h(self):
-    # write version.h
+    # write version.h if needed
+    if not repo_has_changed:
+      return
     f = open('src/version.h', "w")
     try:
       f.write("#ifndef SMISK_VERSION\n#define SMISK_VERSION \"%s\"\n#endif\n" % version)
       f.write("#ifndef SMISK_REVISION\n#define SMISK_REVISION \"%s\"\n#endif\n" % revision)
-    finally: f.close()
+      print 'wrote version info to src/version.h'
+    finally:
+      f.close()
   
   def _run_config_if_needed(self):
     run_configure = True
@@ -190,7 +222,7 @@ class smisk_config(config):
     log._global_log.threshold = log_threshold
   
   def _include_dirs(self):
-    sys.stdout.write('locating header search paths ... ')
+    sys.stdout.write('locating Python header search paths ... ')
     sys.stdout.flush()
     found_py = False
     for dn in [
@@ -214,6 +246,7 @@ class smisk_config(config):
       sys.exit(1)
     else:
       print 'found:'
+      self.include_dirs = coll_wild_unique(self.include_dirs)
       for s in self.include_dirs:
         print ' ', s
   
@@ -348,7 +381,7 @@ setup (
 setup (
   distclass=SmiskDistribution,
   name = 'smisk',
-  version = version + '-r' + revision,
+  version = version + '-' + revision,
   description = "High-performance web service framework",
   long_description = """
 Smisk is a simple, high-performance and scalable web service framework
diff -r 900f51abcf62 -r 548804b3594c src/Application.c
--- a/src/Application.c	Fri May 09 03:45:42 2008 +0000
+++ b/src/Application.c	Fri May 09 03:51:44 2008 +0000
@@ -308,10 +308,32 @@ PyDoc_STRVAR(smisk_Application_error_DOC
 PyDoc_STRVAR(smisk_Application_error_DOC,
   "Service a error message.\n"
   "\n"
-  "You might override this to display a custom error response, but it is recommended you use this implementation, or at least filter certain higher level exceptions and let the lower ones through to this handler."
-  "\n"
-  "The result is a bit different depending on if output has started or not. (See `Response.`)"
-  "\n"
+  "You might override this to display a custom error response, but it is "
+    "recommended you use this implementation, or at least filter certain "
+    "higher level exceptions and let the lower ones through to this handler.\n"
+  "\n"
+  "Normally, this is what you do:\n"
+  "\n"
+  ">>> class MyApp(Application):\n"
+  ">>>   def error(self, typ, val, tb):\n"
+  ">>>     if issubclass(MyExceptionType, typ):\n"
+  ">>>       self.nice_error_response(typ, val)\n"
+  ">>>     else:\n"
+  ">>>       Application.error(self, typ, val, tb)\n"
+  ">>> \n"
+  "\n"
+  "What is sent as response depends on if output has started or not: If "
+    "output has started, if `Response.has_begun` is ``1``, calling this "
+    "method will insert a HTML formatted error message at the end of what "
+    "has already been sent. If output has not yet begun, any headers set "
+    "will be discarded and a complete HTTP response will be sent, including "
+    "the same HTML message describet earlier.\n"
+  "\n"
+  "If `show_traceback` evaluates to true, the error message will also include "
+    "a somewhat detailed backtrace. You should disable `show_traceback` in "
+    "production environments.\n"
+  "\n"
+  ":see:  Response.has_begun\n"
   ":param typ: Exception type\n"
   ":type  typ: type\n"
   ":param val: Exception value\n"
@@ -519,7 +541,50 @@ static int smisk_Application_set_session
 /********** type configuration **********/
 
 PyDoc_STRVAR(smisk_Application_DOC,
-  "An application.\n");
+  "An application.\n"
+  "\n"
+  "Simple example:\n"
+  "\n"
+  ">>> from smisk import Application\n"
+  ">>> class MyApp(Application):\n"
+  ">>>   def service(self):\n"
+  ">>>     self.response.write('<h1>Hello World!</h1>')\n"
+  ">>>\n"
+  ">>> MyApp().run()\n"
+  "\n"
+  "Example of standalone/listening/slave process:\n"
+  "\n"
+  ">>> import smisk\n"
+  ">>> class MyApp(smisk.Application):\n"
+  ">>>   def service(self):\n"
+  ">>>     self.response.write('<h1>Hello World!</h1>')\n"
+  ">>>\n"
+  ">>> from smisk.core import bind\n"
+  ">>> smisk.bind('hostname:1234')\n"
+  ">>> MyApp().run()\n"
+  "\n"
+  "It is also possible to use your own types to represent ``Requests`` and ``Responses``. "
+    "You set `request_class` and/or `response_class` to a type, before "
+    "`application_will_start()` has been called. For example:\n"
+  "\n"
+  ">>> from smisk import Application, Request\n"
+  ">>> class MyRequest(Request):\n"
+  ">>>   def from_internet_explorer(self):\n"
+  ">>>     return self.env.get('HTTP_USER_AGENT','').find('MSIE') != -1\n"
+  ">>>\n"
+  ">>> class MyApp(Application):\n"
+  ">>>   def __init__(self):\n"
+  ">>>     super(MyApp, self).__init__()\n"
+  ">>>     self.request_class = MyRequest\n"
+  ">>>   \n"
+  ">>>   def service(self):\n"
+  ">>>     if self.request.from_internet_explorer():\n"
+  ">>>       self.response.write('<h1>Good bye, cruel World!</h1>')\n"
+  ">>>     else:\n"
+  ">>>       self.response.write('<h1>Hello World!</h1>')\n"
+  ">>>\n"
+  ">>> MyApp().run()\n"
+  "\n");
 
 // Methods
 static PyMethodDef smisk_Application_methods[] = {
diff -r 900f51abcf62 -r 548804b3594c src/Request.c
--- a/src/Request.c	Fri May 09 03:45:42 2008 +0000
+++ b/src/Request.c	Fri May 09 03:51:44 2008 +0000
@@ -347,9 +347,9 @@ void smisk_Request_dealloc(smisk_Request
 
 
 PyDoc_STRVAR(smisk_Request_log_error_DOC,
-  "Log something through `err` including process name and id.\n"
+  "Log something through `errors` including process name and id.\n"
   "\n"
-  "Normally, `err` ends up in the host server error log.\n"
+  "Normally, `errors` ends up in the host server error log.\n"
   "\n"
   ":param  message: Message\n"
   ":type   message: string\n"
diff -r 900f51abcf62 -r 548804b3594c src/xml/__init__.c
--- a/src/xml/__init__.c	Fri May 09 03:45:42 2008 +0000
+++ b/src/xml/__init__.c	Fri May 09 03:51:44 2008 +0000
@@ -119,6 +119,14 @@ PyDoc_STRVAR(smisk_xml_encode_DOC,
 PyDoc_STRVAR(smisk_xml_encode_DOC,
   "Encode reserved and unsafe characters for use in XML or HTML context.\n"
   "\n"
+  "Example:\n"
+  "\n"
+  ">>> from smisk.core.xml import encode\n"
+  ">>> s = \"Your's & not mine <says> \\\"you\\\"\"\n"
+  ">>> encode(s)\n"
+  "\"Your's &#x26; not mine &#x3C;says&#x3E; &#x22;you&#x22;\"\n"
+  ">>> \n"
+  "\n"
   ":param s: Raw string to be encoded"
   ":type  s: string\n"
   ":rtype: string");

