diff -Nru clearsilver-0.9.1/ClearSilver.h clearsilver-0.9.2/ClearSilver.h
--- clearsilver-0.9.1/ClearSilver.h	Mon Apr 14 16:05:07 2003
+++ clearsilver-0.9.2/ClearSilver.h	Thu Jul 24 22:45:40 2003
@@ -14,6 +14,23 @@
 
 #include "cs_config.h"
 
+/* If you need these backward compatible definitions, define CS_COMPAT */
+/* These changed after v0.9.1 */
+#define CS_COMPAT 0
+
+#if defined(CS_COMPAT) || !defined(HASH)
+#define HASH NE_HASH
+#define HASHNODE NE_HASHNODE
+#define hash_init ne_hash_init
+#define hash_destroy ne_hash_destroy
+#define hash_lookup ne_hash_lookup
+#define hash_has_key ne_hash_has_key
+#define hash_remove ne_hash_remove
+#define hash_next ne_hash_next
+#define hash_str_comp ne_hash_str_comp
+#define hash_str_hash ne_hash_str_hash
+#endif /* CS_COMPAT */
+
 #include <stdlib.h>
 #include <sys/stat.h>
 
diff -Nru clearsilver-0.9.1/INSTALL clearsilver-0.9.2/INSTALL
--- clearsilver-0.9.1/INSTALL	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.2/INSTALL	Mon Aug 18 13:24:45 2003
@@ -0,0 +1,119 @@
+********************************************
+*
+* Clearsilver INSTALL
+*
+* http://www.clearsilver.net
+*
+* Brandon Long, David Jeske
+*
+********************************************
+
+1) Compile  ------------------------------------------------
+
+Simplest case:
+
+# ./configure
+# make
+ 
+Options to configure:
+  --disable-compression         Disables HTML Compression support
+  --enable-remote-debugger      Enables remote X CGI debugging
+  --disable-apache              Disables building of apache 1.3.x module
+  --with-apache=path            Set location of Apache installation
+  --disable-python              Disables building of python module
+  --with-python=path            Set location of Python Includes
+  --disable-perl                Disables building of perl module
+  --with-perl=path              Set location of Perl binary
+  --disable-ruby                Disables building of ruby module
+  --with-ruby=path              Set location of Ruby binary
+  --disable-java                Disables building of java module
+  --with-java=path              Set location of J2SDK
+  --disable-csharp      	Disables building of csharp module
+  --with-csharp=path    	Set location of csharp
+ 
+--disable-compression:  Currently, the CGI output code in the cgi
+kit automatically attempts to detect whether the remote browser can
+handle compressed data, and if it does, compresses the output for
+text/html.  This is run-time configurable via Config.CompressionEnabled.
+Disabling it at compile time eliminates the dependency on libz.
+ 
+--enable-remote-debugger: The CGI kit contains code for remotely
+debuggin CGI programs by launching an X based debugger (such as xxgdb or
+ddd) at the X display you specify in the HTTP request.  There are
+controls such as a configurable list of allowed displays, but remote
+debugging is disabled by default.
+ 
+The rest of the --disable/--with either disable a specific module, or
+point ClearSilver at the right program to enable it.  The configure
+script will simple not build any module it can't find the right versions
+of programs to build against.  Alternatively, if you are making
+ClearSilver part of your build environment, you can simple delete
+whichever module directory you don't want, and the build will ignore it.
+
+For information about compiling on Windows under MingW and MSYS, see
+python/README.txt
+
+The csharp wrapper was built with Mono (www.go-mono.com) and should work
+with v0.24 and later (give or take).  In theory, it should be fairly
+simple to get this working on MS.Net as well.
+
+2) Install  ------------------------------------------------
+
+# make install
+
+The make install is relatively new, and just installs the
+libraries/header files (and probably the perl and python modules, but
+that's a guess)
+
+3) Example 1, Apache static.cgi  ----------------------------------
+
+"static.cgi" is a simple binary which will allow you to write static
+HDF files, static ClearSilver templates, and render them. This is a
+good way to get started and learn the clearsilver model and
+syntax. Follow the steps below to install static.cgi into your Apache
+configuration and then read the documentation while playing with
+static files.
+
+   http://www.clearsilver.net/docs/man_hdf.hdf
+
+Add the following to your Apache configuration and copy the
+Clearsilver static.cgi binary from cgi/static.cgi into your Apache
+cgi-bin directory.
+
+   AddHandler cgi-script .cgi
+   Action cs-handler /cgi-bin/static.cgi
+   AddHandler cs-handler .cs
+
+Since any html file is a valid CS file, I also run with:
+
+   AddHandler cs-handler .html
+
+* About static.cgi:
+
+static.cgi works by assuming that whatever file it was pointed at is a
+CS template. It first tries to load common.hdf in the same directory,
+then it tries to load some other hdf files, and then it parses and
+displays the template file.
+
+Ie, if its pointed at foo.cs, it will load common.hdf, try to load
+foo.cs.hdf, if that fails, it tries foo.hdf. If the hdf defines
+CGI.StaticContent, it will assume that's the real template file (which
+is how we use it on www.clearsilver.net, we just point at he .hdf files,
+which define StaticContent as a wrapper.cs, which includes another file
+defined in the .hdf file).
+
+4) Example 2, imd image server ------------------------------------
+
+"imd" is a simple image server written with C and clearsilver. The imd
+directory has a README on how to set up imd with Apache.
+
+5) Example 3, using with Python -----------------------------------
+
+The clearsilver python wrapper comes with CSPage.py. This file
+contains the CSPage superclass, and this file gives some pointers on
+how to use the cs handler via python. A much more thorough example
+is Scott Hassan's image_server, available from
+
+  http://www.dotfunk.com/projects/image_server
+
+
diff -Nru clearsilver-0.9.1/Makefile clearsilver-0.9.2/Makefile
--- clearsilver-0.9.1/Makefile	Mon Jul  7 23:33:47 2003
+++ clearsilver-0.9.2/Makefile	Mon Aug 18 13:24:45 2003
@@ -105,9 +105,9 @@
 		mkdir -p $$mdir; \
 	done
 
-CS_DISTDIR = clearsilver-0.9.1
-CS_LABEL = CLEARSILVER-0_9_1
-CS_FILES = LICENSE CS_LICENSE rules.mk.in Makefile util cs cgi python scripts mod_ecs imd java-jni perl ruby acconfig.h autogen.sh config.guess config.sub configure.in cs_config.h.in mkinstalldirs install-sh ClearSilver.h
+CS_DISTDIR = clearsilver-0.9.2
+CS_LABEL = CLEARSILVER-0_9_2
+CS_FILES = LICENSE CS_LICENSE rules.mk.in Makefile util cs cgi python scripts mod_ecs imd java-jni perl ruby acconfig.h autogen.sh config.guess config.sub configure.in cs_config.h.in mkinstalldirs install-sh ClearSilver.h ports
 cs_dist:
 	rm -rf $(CS_DISTDIR)
 	cvs -q tag -F $(CS_LABEL) $(CS_FILES)
diff -Nru clearsilver-0.9.1/cgi/Makefile clearsilver-0.9.2/cgi/Makefile
--- clearsilver-0.9.1/cgi/Makefile	Mon Apr 14 17:13:40 2003
+++ clearsilver-0.9.2/cgi/Makefile	Thu Jul 24 22:46:11 2003
@@ -24,6 +24,7 @@
 
 $(CGI_LIB): $(CGI_OBJ)
 	$(AR) $@ $(CGI_OBJ)
+	$(RANLIB) $@
 
 $(STATIC_EXE): $(STATIC_OBJ) $(DEP_LIBS)
 	$(LD) $@ $(STATIC_OBJ) $(LIBS)
diff -Nru clearsilver-0.9.1/cgi/cgi.c clearsilver-0.9.2/cgi/cgi.c
--- clearsilver-0.9.1/cgi/cgi.c	Sun Jun 29 22:32:58 2003
+++ clearsilver-0.9.2/cgi/cgi.c	Mon Aug 18 13:41:08 2003
@@ -315,7 +315,7 @@
   {
     if (buf[l] == '/' || buf[l] == '+' || buf[l] == '=' || buf[l] == '&' || 
 	buf[l] == '"' || buf[l] == '%' || buf[l] == '?' || buf[l] == '#' ||
-	buf[l] < 32 || buf[l] > 122)
+	buf[l] == '<' || buf[l] == '>' || buf[l] < 32 || buf[l] > 122)
     {
       nl += 2;
     } 
@@ -354,7 +354,7 @@
     {
       if (buf[l] == '/' || buf[l] == '+' || buf[l] == '=' || buf[l] == '&' || 
 	  buf[l] == '"' || buf[l] == '%' || buf[l] == '?' || buf[l] == '#' ||
-	  buf[l] < 32 || buf[l] > 122)
+	  buf[l] == '<' || buf[l] == '>' || buf[l] < 32 || buf[l] > 122)
       {
 	match = 1;
       }
@@ -1245,21 +1245,47 @@
   return nerr_pass(err);
 }
 
-static NEOERR *_html_escape_strfunc(unsigned char *str, unsigned char **ret)
+NEOERR *cgi_html_escape_strfunc(unsigned char *str, unsigned char **ret)
 {
   return nerr_pass(html_escape_alloc(str, strlen(str), ret));
 }
 
-static NEOERR *_html_strip_strfunc(unsigned char *str, unsigned char **ret)
+NEOERR *cgi_html_strip_strfunc(unsigned char *str, unsigned char **ret)
 {
   return nerr_pass(html_strip_alloc(str, strlen(str), ret));
 }
 
-static NEOERR *_text_html_strfunc(unsigned char *str, unsigned char **ret)
+NEOERR *cgi_text_html_strfunc(unsigned char *str, unsigned char **ret)
 {
   return nerr_pass(convert_text_html_alloc(str, strlen(str), ret));
 }
 
+NEOERR *cgi_cs_init(CGI *cgi, CSPARSE **cs)
+{
+  NEOERR *err;
+
+  *cs = NULL;
+
+  do
+  {
+    err = cs_init (cs, cgi->hdf);
+    if (err != STATUS_OK) break;
+    err = cs_register_strfunc(*cs, "url_escape", cgi_url_escape);
+    if (err != STATUS_OK) break;
+    err = cs_register_strfunc(*cs, "html_escape", cgi_html_escape_strfunc);
+    if (err != STATUS_OK) break;
+    err = cs_register_strfunc(*cs, "text_html", cgi_text_html_strfunc);
+    if (err != STATUS_OK) break;
+    err = cs_register_strfunc(*cs, "js_escape", cgi_js_escape);
+    if (err != STATUS_OK) break;
+    err = cs_register_strfunc(*cs, "html_strip", cgi_html_strip_strfunc);
+    if (err != STATUS_OK) break;
+  } while (0);
+
+  if (err && *cs) cs_destroy(cs);
+  return nerr_pass(err);
+}
+
 NEOERR *cgi_display (CGI *cgi, char *cs_file)
 {
   NEOERR *err = STATUS_OK;
@@ -1281,13 +1307,13 @@
     if (err != STATUS_OK) break;
     err = cs_register_strfunc(cs, "url_escape", cgi_url_escape);
     if (err != STATUS_OK) break;
-    err = cs_register_strfunc(cs, "html_escape", _html_escape_strfunc);
+    err = cs_register_strfunc(cs, "html_escape", cgi_html_escape_strfunc);
     if (err != STATUS_OK) break;
-    err = cs_register_strfunc(cs, "text_html", _text_html_strfunc);
+    err = cs_register_strfunc(cs, "text_html", cgi_text_html_strfunc);
     if (err != STATUS_OK) break;
     err = cs_register_strfunc(cs, "js_escape", cgi_js_escape);
     if (err != STATUS_OK) break;
-    err = cs_register_strfunc(cs, "html_strip", _html_strip_strfunc);
+    err = cs_register_strfunc(cs, "html_strip", cgi_html_strip_strfunc);
     if (err != STATUS_OK) break;
     err = cs_parse_file (cs, cs_file);
     if (err != STATUS_OK) break;
diff -Nru clearsilver-0.9.1/cgi/cgi.h clearsilver-0.9.2/cgi/cgi.h
--- clearsilver-0.9.1/cgi/cgi.h	Mon Mar 31 18:59:45 2003
+++ clearsilver-0.9.2/cgi/cgi.h	Thu Jul 17 12:44:08 2003
@@ -14,6 +14,7 @@
 #include <stdarg.h>
 #include "util/neo_err.h"
 #include "util/neo_hdf.h"
+#include "cs/cs.h"
 
 __BEGIN_DECLS
 
@@ -113,6 +114,17 @@
  * Return: None
  */
 void cgi_destroy (CGI **cgi);
+
+/*
+ * Function: cgi_cs_init - initialize CS parser with the CGI defaults
+ * Description: cgi_cs_init initializes a CS parser with the CGI HDF
+ *              context, and registers the standard CGI filters
+ * Input: cgi - a pointer a CGI struct allocated with cgi_init
+ *        cs - a pointer to a CS struct pointer
+ * Output: cs - the allocated/initialized CS struct
+ * Return: NERR_NOMEM - no memory was available to render the template
+ */
+NEOERR *cgi_cs_init(CGI *cgi, CSPARSE **cs);
 
 /*
  * Function: cgi_display - render and display the CGI output to the user
diff -Nru clearsilver-0.9.1/cgi/static.c clearsilver-0.9.2/cgi/static.c
--- clearsilver-0.9.1/cgi/static.c	Mon Apr 14 16:05:09 2003
+++ clearsilver-0.9.2/cgi/static.c	Mon Aug 18 13:41:23 2003
@@ -55,6 +55,13 @@
       return -1;
     }
   }
+  err = hdf_read_file (cgi->hdf, "common.hdf");
+  if (err && !nerr_handle(&err, NERR_NOT_FOUND))
+  {
+    cgi_neo_error(cgi, err);
+    nerr_log_error(err);
+    return -1;
+  }
   snprintf (hdf_file, sizeof(hdf_file), "%s.hdf", cs_file);
   err = hdf_read_file (cgi->hdf, hdf_file);
   if (err && !nerr_handle(&err, NERR_NOT_FOUND))
diff -Nru clearsilver-0.9.1/configure.in clearsilver-0.9.2/configure.in
--- clearsilver-0.9.1/configure.in	Tue Jun 17 14:20:40 2003
+++ clearsilver-0.9.2/configure.in	Mon Aug 18 13:24:45 2003
@@ -6,6 +6,8 @@
 AC_PROG_CC
 AC_PROG_CPP
 AC_PROG_LN_S
+AC_CHECK_PROGS(AR, ar aal, ar)
+AC_PROG_RANLIB
 
 AC_PROG_MAKE_SET
 AC_PROG_INSTALL
@@ -87,13 +89,63 @@
 
 dnl Check for missing re-entrant functions
 cs_cv_missing=no
-AC_CHECK_FUNC(strtok_r, [AC_DEFINE(HAVE_STRTOK_R)], [cs_cv_missing=yes])
-AC_CHECK_FUNC(localtime_r, [AC_DEFINE(HAVE_LOCALTIME_R)], [cs_cv_missing=yes])
-AC_CHECK_FUNC(gmtime_r, [AC_DEFINE(HAVE_GMTIME_R)], [cs_cv_missing=yes])
+cs_cv_need_reentrant=no
+dnl copied from libcurl
+AC_CHECK_FUNCS(localtime_r, [
+  AC_MSG_CHECKING(whether localtime_r is declared)
+  AC_EGREP_CPP(localtime_r,[
+#include <time.h>],[
+    AC_DEFINE(HAVE_LOCALTIME_R)
+    AC_MSG_RESULT(yes)],[
+    AC_MSG_RESULT(no)
+    AC_MSG_CHECKING(whether localtime_r with -D_REENTRANT is declared)
+    AC_EGREP_CPP(localtime_r,[
+#define _REENTRANT
+#include <time.h>],[
+      cs_cv_need_reentrant=yes
+      AC_MSG_RESULT(yes)],[
+      cs_cv_missing=yes
+      AC_MSG_RESULT(no)])])])
+
+AC_CHECK_FUNCS(gmtime_r, [
+  AC_MSG_CHECKING(whether gmtime_r is declared)
+  AC_EGREP_CPP(gmtime_r,[
+#include <time.h>],[
+    AC_DEFINE(HAVE_LOCALTIME_R)
+    AC_MSG_RESULT(yes)],[
+    AC_MSG_RESULT(no)
+    AC_MSG_CHECKING(whether gmtime_r with -D_REENTRANT is declared)
+    AC_EGREP_CPP(gmtime_r,[
+#define _REENTRANT
+#include <time.h>],[
+      cs_cv_need_reentrant=yes
+      AC_MSG_RESULT(yes)],[
+      cs_cv_missing=yes
+      AC_MSG_RESULT(no)])])])
+
+AC_CHECK_FUNCS(strtok_r, [
+  AC_MSG_CHECKING(whether strtok_r is declared)
+  AC_EGREP_CPP(strtok_r,[
+#include <string.h>],[
+    AC_DEFINE(HAVE_LOCALTIME_R)
+    AC_MSG_RESULT(yes)],[
+    AC_MSG_RESULT(no)
+    AC_MSG_CHECKING(whether strtok_r with -D_REENTRANT is declared)
+    AC_EGREP_CPP(strtok_r,[
+#define _REENTRANT
+#include <string.h>],[
+      cs_cv_need_reentrant=yes
+      AC_MSG_RESULT(yes)],[
+      cs_cv_missing=yes
+      AC_MSG_RESULT(no)])])])
+
 AC_CHECK_FUNC(mkstemp, [AC_DEFINE(HAVE_MKSTEMP)], [cs_cv_missing=yes])
 if test $cs_cv_missing = yes; then
   EXTRA_UTL_OBJS="$EXTRA_UTL_OBJS missing.o"
 fi
+if test $cs_cv_need_reentrant = yes; then
+  CPPFLAGS="$CPPFLAGS -D_REENTRANT"
+fi
 
 cs_cv_regex=yes
 AC_CHECK_FUNC(regexec, [AC_DEFINE(HAVE_REGEX)], [cs_cv_regex=no])
@@ -195,6 +247,9 @@
   else
     for vers in $python_versions; do
       for path in $python_search_path; do
+        if test -x $path/bin/python$vers; then
+	  python_bin=$path/bin/python$vers
+	fi
 	if test -f $path/include/python$vers/Python.h; then
 	  python_inc=$path/include/python$vers
 	  python_lib="-L$path/lib/python$vers/config -lpython$vers"
@@ -214,11 +269,13 @@
   fi
   if test "x$python_inc" = "xno"; then
     AC_MSG_RESULT(not found)
+    PYTHON=
     PYTHON_INC=
     PYTHON_LIB=
     PYTHON_SITE=
   else
     AC_MSG_RESULT(found $python_inc)
+    PYTHON=$python_bin
     PYTHON_INC="-I$python_inc"
     PYTHON_LIB=$python_lib
     PYTHON_SITE=$python_site
@@ -346,16 +403,53 @@
   fi
 fi
 
+dnl Check for C# library/includes
+cs_cv_csharp=yes
+AC_ARG_ENABLE(csharp, [  --disable-csharp	Disables building of csharp module],
+  [if test $enableval = no; then
+     cs_cv_csharp=no;
+     AC_MSG_RESULT(Disabling csharp module)
+   fi])
+AC_ARG_WITH(csharp, [  --with-csharp=path	Set location of csharp], [cs_cv_csharp_path="$withval"], [cs_cv_csharp_path=no])
+if test $cs_cv_csharp = yes; then
+  AC_MSG_CHECKING(for csharp path)
+  csharp_path=no
+  if test $cs_cv_csharp_path != "no" -a -d $cs_cv_csharp_path; then
+    csharp_path=$cs_cv_csharp_path
+  else
+    csharp_search_path="/neo/opt /usr/local /usr"
+    for path in $csharp_search_path; do
+      if test -f $path/bin/mcs; then
+	csharp_path=$path
+	break
+      fi
+    done
+  fi
+  if test "x$csharp_path" = "xno"; then
+    AC_MSG_RESULT(not found)
+    CSHARP_PATH=
+  else
+    AC_MSG_RESULT(found $csharp_path/bin/mcs)
+    CSHARP_PATH="$csharp_path"
+    BUILD_WRAPPERS="$BUILD_WRAPPERS dso csharp"
+  fi
+fi
+
+
+AC_SUBST(RANLIB)
+AC_SUBST(AR)
 AC_SUBST(USE_MINGW32)
 AC_SUBST(APXS_PATH)
 AC_SUBST(PERL)
 AC_SUBST(RUBY)
 AC_SUBST(BUILD_WRAPPERS)
 AC_SUBST(JAVA_PATH)
+AC_SUBST(PYTHON)
 AC_SUBST(PYTHON_INC)
 AC_SUBST(PYTHON_LIB)
 AC_SUBST(PYTHON_SITE)
 AC_SUBST(EXTRA_UTL_SRC)
 AC_SUBST(EXTRA_UTL_OBJS)
+AC_SUBST(CSHARP_PATH)
 
 AC_OUTPUT(rules.mk)
diff -Nru clearsilver-0.9.1/cs/Makefile clearsilver-0.9.2/cs/Makefile
--- clearsilver-0.9.1/cs/Makefile	Wed Jul  2 17:40:06 2003
+++ clearsilver-0.9.2/cs/Makefile	Sun Jul 27 09:26:19 2003
@@ -33,6 +33,7 @@
 
 $(CS_LIB): $(CS_OBJ)
 	$(AR) $@ $(CS_OBJ)
+	$(RANLIB) $@
 
 $(CSTEST_EXE): $(CSTEST_OBJ) $(CS_LIB)
 	$(LD) $@ $(CSTEST_OBJ) $(LIBS) # -lefence
@@ -58,10 +59,12 @@
 	for test in $(CS_TESTS); do \
 		rm -f $$test.out; \
 		./cstest test.hdf $$test > $$test.out 2>&1; \
-		diff --brief $$test.out $$test.gold; \
+		diff --brief $$test.out $$test.gold 2>&1 > /dev/null; \
 		return_code=$$?; \
 		if [ $$return_code -ne 0 ]; then \
+		  diff $$test.gold $$test.out > $$test.err; \
 		  echo "Failed Regression Test: $$test"; \
+		  echo "  See $$test.out and $$test.err"; \
 		  failed=1; \
 		fi; \
 	done; \
diff -Nru clearsilver-0.9.1/cs/cs.h clearsilver-0.9.2/cs/cs.h
--- clearsilver-0.9.1/cs/cs.h	Wed Jun 25 20:11:36 2003
+++ clearsilver-0.9.2/cs/cs.h	Fri Aug  8 23:54:12 2003
@@ -41,6 +41,7 @@
 #ifndef __CSHDF_H_
 #define __CSHDF_H_ 1
 
+#include "util/neo_misc.h"
 #include "util/neo_err.h"
 #include "util/ulist.h"
 #include "util/neo_hdf.h"
diff -Nru clearsilver-0.9.1/cs/csparse.c clearsilver-0.9.2/cs/csparse.c
--- clearsilver-0.9.1/cs/csparse.c	Mon Jul  7 23:33:28 2003
+++ clearsilver-0.9.2/cs/csparse.c	Mon Aug  4 18:49:40 2003
@@ -1924,7 +1924,7 @@
       }
       else if ((arg1.op_type & (CS_TYPE_NUM | CS_TYPE_VAR_NUM)) ||
 	  (arg2.op_type & (CS_TYPE_NUM | CS_TYPE_VAR_NUM)) ||
-	  (expr->op_type & (CS_OP_AND | CS_OP_OR | CS_OP_SUB | CS_OP_MULT | CS_OP_DIV | CS_OP_MOD)))
+	  (expr->op_type & (CS_OP_AND | CS_OP_OR | CS_OP_SUB | CS_OP_MULT | CS_OP_DIV | CS_OP_MOD | CS_OP_GT | CS_OP_GTE | CS_OP_LT | CS_OP_LTE )))
       {
 	/* eval as num */
 	err = eval_expr_num(parse, &arg1, &arg2, expr->op_type, result);
diff -Nru clearsilver-0.9.1/cs/test.cs clearsilver-0.9.2/cs/test.cs
--- clearsilver-0.9.1/cs/test.cs	Wed Jul  2 18:04:16 2003
+++ clearsilver-0.9.2/cs/test.cs	Sun Jul 27 09:57:42 2003
@@ -61,6 +61,12 @@
   TestIf == else
 <?cs /if ?>
 
+<?cs if:"1" == "1" ?>
+Correct, "1" == "1"
+<?cs else ?>
+WRONG, "1" != "1"
+<?cs /if ?>
+
 <?cs # This is a ClearSilver Comment ?>
 
 between comments
diff -Nru clearsilver-0.9.1/cs/test.cs.gold clearsilver-0.9.2/cs/test.cs.gold
--- clearsilver-0.9.1/cs/test.cs.gold	Wed Jul  2 18:04:16 2003
+++ clearsilver-0.9.2/cs/test.cs.gold	Sun Jul 27 09:57:42 2003
@@ -106,6 +106,10 @@
 
 
 
+Correct, "1" == "1"
+
+
+
 
 between comments
 
diff -Nru clearsilver-0.9.1/cs/test4.cs clearsilver-0.9.2/cs/test4.cs
--- clearsilver-0.9.1/cs/test4.cs	Wed Jun 25 20:11:36 2003
+++ clearsilver-0.9.2/cs/test4.cs	Sun Jul 27 09:26:19 2003
@@ -67,6 +67,20 @@
 ERROR! "0" <= #5
 <?cs /if ?>
 
+<?cs # -- double digits -- ?>
+
+<?cs if:"9" > #14 ?>
+ERROR! "9" > #14
+<?cs else ?>
+right "9" > #14
+<?cs /if ?>
+
+<?cs if:"9" > "14" ?>
+ERROR! "9" > "14"
+<?cs else ?>
+right "9" > "14"
+<?cs /if ?>
+
 
 <?cs each:msg = CGI.box.msgs ?>
 
diff -Nru clearsilver-0.9.1/cs/test4.cs.gold clearsilver-0.9.2/cs/test4.cs.gold
--- clearsilver-0.9.1/cs/test4.cs.gold	Thu Mar 28 17:40:56 2002
+++ clearsilver-0.9.2/cs/test4.cs.gold	Sun Jul 27 09:59:05 2003
@@ -59,6 +59,16 @@
 
 
 
+right "9" > #14
+
+
+
+right "9" > "14"
+
+
+
+
+
 
 
 /box_bm_body_frm.cs?boxid=2&cur=1&idx_cur=1&split=1&filter=0&sort=t&sort_dir=u&from_search=
diff -Nru clearsilver-0.9.1/cs_config.h.in clearsilver-0.9.2/cs_config.h.in
--- clearsilver-0.9.1/cs_config.h.in	Mon Apr 14 16:05:07 2003
+++ clearsilver-0.9.2/cs_config.h.in	Fri Aug  8 23:55:25 2003
@@ -78,15 +78,9 @@
 /* Does your system have the vsnprintf() call? */
 #undef HAVE_VSNPRINTF
 
-/* Does your system have the strtok_r() call? */
-#undef HAVE_STRTOK_R
-
 /* Does your system have the localtime_r() call? */
 #undef HAVE_LOCALTIME_R
 
-/* Does your system have the gmtime_r() call? */
-#undef HAVE_GMTIME_R
-
 /* Does your system have the mkstemp() call? */
 #undef HAVE_MKSTEMP
 
@@ -108,6 +102,12 @@
 /* Define if you have the gettimeofday function.  */
 #undef HAVE_GETTIMEOFDAY
 
+/* Define if you have the gmtime_r function.  */
+#undef HAVE_GMTIME_R
+
+/* Define if you have the localtime_r function.  */
+#undef HAVE_LOCALTIME_R
+
 /* Define if you have the mktime function.  */
 #undef HAVE_MKTIME
 
@@ -128,6 +128,9 @@
 
 /* Define if you have the strtod function.  */
 #undef HAVE_STRTOD
+
+/* Define if you have the strtok_r function.  */
+#undef HAVE_STRTOK_R
 
 /* Define if you have the strtol function.  */
 #undef HAVE_STRTOL
diff -Nru clearsilver-0.9.1/csharp/CS.cs clearsilver-0.9.2/csharp/CS.cs
--- clearsilver-0.9.1/csharp/CS.cs	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.2/csharp/CS.cs	Mon Aug 11 14:37:10 2003
@@ -0,0 +1,145 @@
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace Clearsilver {
+
+// opaque types
+public unsafe struct HDF {};
+public unsafe struct NEOERR {};
+
+public unsafe class Hdf {
+
+  [DllImport("libneo", EntryPoint="hdf_init")]
+  private static extern unsafe NEOERR* hdf_init(HDF **foo);
+
+  // NEOERR* hdf_set_value (HDF *hdf, char *name, char *value)
+  [DllImport("libneo")]
+  private static unsafe extern NEOERR* hdf_set_value(HDF *hdf,
+       [MarshalAs(UnmanagedType.LPStr)] 
+        string name,
+       [MarshalAs(UnmanagedType.LPStr)] 
+        string value);
+
+  // char* hdf_get_value (HDF *hdf, char *name, char *defval)
+
+  [DllImport("libneo")]
+  [return: MarshalAs(UnmanagedType.LPStr)] 
+  private static unsafe extern string hdf_get_value(HDF *hdf,
+       [MarshalAs(UnmanagedType.LPStr)] 
+        string name,
+       [MarshalAs(UnmanagedType.LPStr)] 
+        string defval);
+
+  // NEOERR* hdf_dump (HDF *hdf, char *prefix);
+
+  [DllImport("libneo", EntryPoint="hdf_dump")]
+  private static extern void hdf_dump(
+       HDF *hdf,
+       [MarshalAs(UnmanagedType.LPStr)]
+         string prefix);
+
+  // HDF* hdf_get_obj (HDF *hdf, char *name)
+
+  [DllImport("libneo", EntryPoint="hdf_get_obj")]
+  private static extern HDF* hdf_get_obj(
+     HDF *hdf, 
+       [MarshalAs(UnmanagedType.LPStr)]
+     string name);
+
+
+  // -----------------------------------------------------------
+
+    public HDF *hdf_root;
+
+    public Hdf() {
+      NEOERR* err = hdf_init(&hdf_root);
+      // Console.WriteLine((int)hdf_root);
+    }
+
+    public void setValue(string name,string value) {
+         NEOERR* err = hdf_set_value(hdf_root,name,value);
+         
+    }
+    public string getValue(string name,string defvalue) {
+         return hdf_get_value(hdf_root,name,defvalue);
+    }
+
+    public void test() {
+       hdf_set_value(hdf_root,"b","1");
+       // hdf_read_file(hdf_root,"test.hdf");
+       // Console.WriteLine("b ", hdf_get_value(hdf_root,"b","5"));
+       hdf_dump(hdf_root,null);
+
+       // HDF *n = hdf_get_obj(hdf_root,"b");
+       // Console.WriteLine("object name {0}", 
+       // Marshal.PtrToStringAnsi((IntPtr)n->name));
+    }
+
+};
+
+unsafe struct CSPARSE {};
+
+public class CSTContext {
+   CSPARSE *csp;
+   unsafe public CSTContext(Hdf hdf) {
+     NEOERR *err = cs_init(&csp, hdf.hdf_root);
+   } 
+
+   [DllImport("libneo")]
+   extern static unsafe NEOERR *cs_init (CSPARSE **parse, HDF *hdf);
+
+   public unsafe void parseFile(string filename) {
+      NEOERR* err = cs_parse_file(csp,filename);
+   }
+
+   [DllImport("libneo")]
+   extern static unsafe NEOERR *cs_parse_file (CSPARSE *parse, 
+       [MarshalAs(UnmanagedType.LPStr)] 
+       string path);
+
+//   [DllImport("libneo")]
+//   extern static unsafe NEOERR *cs_parse_string (CSPARSE *parse, 
+//                    char *buf, 
+//                    size_t blen);
+
+
+   //  NEOERR *cs_render (CSPARSE *parse, void *ctx, CSOUTFUNC cb);
+   //  typedef NEOERR* (*CSOUTFUNC)(void *ctx, char *more_str_bytes);
+
+   [DllImport("libneo")]
+   extern static unsafe NEOERR *cs_render (CSPARSE *parse, 
+           void *ctx, 
+           [MarshalAs(UnmanagedType.FunctionPtr)]
+           CSOUTFUNC cb);
+
+   private unsafe delegate NEOERR* CSOUTFUNC(void* ctx, sbyte* more_bytes);
+
+   private class OutputBuilder {
+      private string output = "";
+      public unsafe NEOERR* handleOutput(void* ctx, sbyte* more_bytes) {
+           // add the more_bytes to the current string buffer
+
+           output += new String(more_bytes);
+           // Console.WriteLine("handleOutput called");
+           return null;
+      }
+      public string result() {
+         return output;
+      }
+   }
+
+   public unsafe string render() {
+     OutputBuilder ob = new OutputBuilder();
+     NEOERR* err = cs_render(csp,null,new CSOUTFUNC(ob.handleOutput));
+     return ob.result();
+   }
+
+
+   [DllImport("libneo")]
+   extern static unsafe void cs_destroy (CSPARSE **parse);
+
+};
+
+
+} // namespace Clearsilver
\ No newline at end of file
diff -Nru clearsilver-0.9.1/csharp/Makefile clearsilver-0.9.2/csharp/Makefile
--- clearsilver-0.9.1/csharp/Makefile	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.2/csharp/Makefile	Mon Aug 11 14:37:10 2003
@@ -0,0 +1,64 @@
+
+# default values....
+
+CSHARP_CC=$(CSHARP_PATH)/bin/mcs
+MONO_JIT=$(CSHARP_PATH)/bin/mono
+
+# common build environment
+
+ifeq ($(NEOTONIC_ROOT),)
+NEOTONIC_ROOT = ../
+endif
+
+include $(NEOTONIC_ROOT)rules.mk
+
+# our targets
+
+TARGETS = clearsilver.dll cstest.exe csperftest.exe testcs
+
+all: $(TARGETS)
+
+clearsilver.dll: CS.cs
+	$(CSHARP_CC) -target:library --unsafe CS.cs -out:clearsilver.dll
+
+cstest.exe: clearsilver.dll ../dso/libneo.so cstest.cs
+	$(CSHARP_CC) -r:clearsilver.dll --unsafe cstest.cs 
+
+csperftest.exe: clearsilver.dll ../dso/libneo.so csperftest.cs
+	$(CSHARP_CC) -r:clearsilver.dll --unsafe csperftest.cs
+
+perf: csperftest.exe
+	export LD_LIBRARY_PATH=../dso; \
+	$(MONO_JIT) csperftest.exe 
+	
+
+testcs: cstest.exe
+	@echo "Running csharp test"
+	@failed=0; \
+	rm -f cstest.out; \
+	export LD_LIBRARY_PATH=../dso; \
+	$(MONO_JIT) cstest.exe > cstest.out; \
+	diff cstest.out cstest.gold > /dev/null; \
+	return_code=$$?; \
+	if [ $$return_code -ne 0 ]; then \
+	  diff cstest.out cstest.gold > cstest.err; \
+	  echo "Failed csharp test: cstest.cs"; \
+	  echo "    See cstest.out and cstest.err"; \
+	  failed=1; \
+	fi; \
+	if [ $$failed -eq 1 ]; then \
+	  exit 1; \
+	fi;
+	@echo "Passed csharp test"
+
+gold: cstest.exe
+	export LD_LIBRARY_PATH=../dso; \
+	$(MONO_JIT) cstest.exe > cstest.gold;
+	@echo "Generated gold files"
+
+
+clean:
+	rm -f core.*
+
+distclean:
+	rm -f $(TARGETS) core.* Makefile.depend
diff -Nru clearsilver-0.9.1/csharp/README clearsilver-0.9.2/csharp/README
--- clearsilver-0.9.1/csharp/README	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.2/csharp/README	Mon Aug 11 14:45:00 2003
@@ -0,0 +1,37 @@
+#################################
+#
+# C# Clearsilver wrapper
+#
+# David Jeske
+#
+
+This wrapper was developed using the Mono project's C# implementation.
+It isn't done, and it hasn't yet been tested with the MS C#
+implementation, however, in theory it should work. Here are some salient
+ideas behind this implementation:
+
+- I don't use IntPtr, because it is basically a void* that
+  removes type information and begs for coredumps to occur later.
+
+- I also don't use data marshaling for aggregate C-types. Instead I
+  merely access the structure members directly from C# unsafe code, just
+  like you would if this was C code. 
+
+- The only data which is marshalled is function arguments and data which
+  is pulled out of or put into C-structures.
+
+- There are many things still todo before this is a fully working
+  module, but the basics are there.
+
+
+TODO:
+
+- more tests (look at ruby/test/hdftest.rb for an example)
+- do something better about mapping hdferror to exceptions
+- recheck the function prototypes to make sure I'm handling all
+  arguments
+- handle next(), prev()
+- make iterator for child nodes
+- allow registration of upload callbacks and data-formatters from C#
+- open up access to other parts of cgi and util
+- sample code for using inside mod_mono, includign CSPage.cs
diff -Nru clearsilver-0.9.1/csharp/csperftest.cs clearsilver-0.9.2/csharp/csperftest.cs
--- clearsilver-0.9.1/csharp/csperftest.cs	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.2/csharp/csperftest.cs	Mon Aug 11 14:37:10 2003
@@ -0,0 +1,49 @@
+using System;
+using System.Runtime.InteropServices;
+
+using Clearsilver;
+
+
+public class CSPerfTest {
+   public delegate void test_function(int INDEX);
+
+    public static void timefunk(String label, test_function f, int count) {
+      int start, end, elapsed;
+      start = Environment.TickCount;
+      f(count);
+      end = Environment.TickCount;
+
+      elapsed = end-start;
+
+      Console.WriteLine(label + "   " + count + " elapsed: " + (elapsed / 1000.0));
+    }
+
+   public static unsafe int Main(string[] argv) {
+      Console.WriteLine("C# Clearsilver wrapper performance test");
+      Hdf h = new Hdf();
+
+      h.setValue("foo.1","1");
+      h.setValue("foo.2","2");
+
+      int call_count = 100000;
+      int start = Environment.TickCount;
+      for (int i=0;i<call_count;i++) {
+         h.setValue(String.Format("foo.{0}",i),"5");
+      }
+      int end = Environment.TickCount;
+
+      Console.WriteLine("call count = {0}, time = {1} ms - time per call {2} ns", 
+           call_count, end-start, (((float)end-start)/call_count) * 1000);
+      
+
+      CSTContext cs = new CSTContext(h);
+//      cs.parseFile("test.cst");
+      Console.WriteLine(cs.render());
+      
+      return 0;
+   }
+  
+
+
+}
+
diff -Nru clearsilver-0.9.1/csharp/cstest.cs clearsilver-0.9.2/csharp/cstest.cs
--- clearsilver-0.9.1/csharp/cstest.cs	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.2/csharp/cstest.cs	Mon Aug 11 14:37:10 2003
@@ -0,0 +1,23 @@
+using System;
+using System.Runtime.InteropServices;
+
+using Clearsilver;
+
+public class CSTest {
+   public static unsafe int Main(string[] argv) {
+      Hdf h = new Hdf();
+
+      h.setValue("foo.1","1");
+      h.setValue("foo.2","2");
+      Console.WriteLine("foo.2 = {0}", h.getValue("foo.2","def"));
+
+      CSTContext cs = new CSTContext(h);
+//      cs.parseFile("test.cst");
+      Console.WriteLine(cs.render());
+      
+      return 0;
+   }
+  
+
+
+}
diff -Nru clearsilver-0.9.1/csharp/cstest.gold clearsilver-0.9.2/csharp/cstest.gold
--- clearsilver-0.9.1/csharp/cstest.gold	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.2/csharp/cstest.gold	Mon Aug 11 14:37:10 2003
@@ -0,0 +1,2 @@
+foo.2 = 2
+
diff -Nru clearsilver-0.9.1/csharp/data_cstest.cst clearsilver-0.9.2/csharp/data_cstest.cst
--- clearsilver-0.9.1/csharp/data_cstest.cst	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.2/csharp/data_cstest.cst	Mon Aug 11 14:37:10 2003
@@ -0,0 +1,5 @@
+
+iterate foo elements:
+<?cs each:f = foo ?>
+  <?cs var:name(f) + " = " + f ?>
+<?cs /each ?>
\ No newline at end of file
diff -Nru clearsilver-0.9.1/csharp/data_cstest.hdf clearsilver-0.9.2/csharp/data_cstest.hdf
--- clearsilver-0.9.1/csharp/data_cstest.hdf	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.2/csharp/data_cstest.hdf	Mon Aug 11 14:37:10 2003
@@ -0,0 +1 @@
+a=1
diff -Nru clearsilver-0.9.1/dso/Makefile clearsilver-0.9.2/dso/Makefile
--- clearsilver-0.9.1/dso/Makefile	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.2/dso/Makefile	Mon Aug 11 13:04:54 2003
@@ -0,0 +1,35 @@
+
+
+ifeq ($(NEOTONIC_ROOT),)
+NEOTONIC_ROOT = ../
+endif
+
+include $(NEOTONIC_ROOT)rules.mk
+
+NEO_SO = libneo.so
+NEO_STATIC_LIBS =  
+
+LIBS += -L$(LIB_DIR) $(DLIBS) $(DB2_LIB)
+
+
+TARGETS = $(NEO_SO) dsotest
+
+all: $(TARGETS)
+
+$(NEO_SO): $(NEO_STATIC_LIBS) $(DEP_LIBS) Makefile 
+	 /usr/bin/ld  -rpath ../libs -shared -o libneo.so -whole-archive ../libs/libneo_cgi.a ../libs/libneo_cs.a ../libs/libneo_utl.a -no-whole-archive $(LIBS)
+
+#	$(LDSHARED) -o $@ $(LDFLAGS) -Wl,-whole-archive $(DLIBS)
+
+install: all
+	$(NEOTONIC_ROOT)mkinstalldirs $(DESTDIR)$(PYTHON_SITE)
+	$(INSTALL) $(TARGETS) $(DESTDIR)$(PYTHON_SITE)
+
+dsotest: dsotest.c $(NEO_SO)
+	gcc -o dsotest dsotest.c -lneo -L. -I..
+
+clean:
+	$(RM) *.o 
+
+distclean:
+	$(RM) Makefile.depends $(TARGETS) *.o
diff -Nru clearsilver-0.9.1/dso/README clearsilver-0.9.2/dso/README
--- clearsilver-0.9.1/dso/README	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.2/dso/README	Mon Aug 18 13:24:47 2003
@@ -0,0 +1,11 @@
+
+08-18-2003 blong
+
+This directory builds a "dynamic shared object" or dso.  In theory, you
+could use this for various things, but in reality this is for the
+Mono/CSharp module, which requires a shared library loadable by the C#
+run time.
+
+Building this dso is very system specific, and this is currently very
+Linux specific at the moment.  Patches to build this on other platforms
+are appreciated.
diff -Nru clearsilver-0.9.1/dso/dsotest.c clearsilver-0.9.2/dso/dsotest.c
--- clearsilver-0.9.1/dso/dsotest.c	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.2/dso/dsotest.c	Mon Aug 11 13:04:54 2003
@@ -0,0 +1,17 @@
+
+#include <ClearSilver.h>
+
+
+int main() {
+  HDF *hdf;
+  NEOERR *err;
+
+  err = hdf_init(&hdf);
+
+  if (err) {
+      printf("error: %s\n", err->desc);
+      return 1;
+   }
+
+   printf("success: 0x%X\n", hdf);
+}
diff -Nru clearsilver-0.9.1/imd/imd.c clearsilver-0.9.2/imd/imd.c
--- clearsilver-0.9.1/imd/imd.c	Mon Apr 14 16:05:28 2003
+++ clearsilver-0.9.2/imd/imd.c	Tue Aug  5 12:12:51 2003
@@ -67,11 +67,7 @@
     t[6] = '\0';
     strcpy(mname,&t[3]);
     x = atoi(&t[7]);
-    /* Prevent
-     * wraparound
-     * from
-     * ambiguity
-     * */
+    /* Prevent wraparound from ambiguity */
     if(x < 70)
       x += 100;
     year = 1900 + x;
@@ -153,6 +149,7 @@
     {
       *height = data[pos+5] * 256 + data[pos+6];
       *width = data[pos+7] * 256 + data[pos+8];
+      ne_warn("%s: %dx%d", file, *width, *height);
       return 0;
     }
     pos += length;
@@ -214,7 +211,8 @@
   snprintf (rpath, _POSIX_PATH_MAX, "%s/%s", path, file);
   ch = strrchr(rpath, '.');
   if ((!strcasecmp(ch, ".jpg")) ||
-      (!strcasecmp(ch, ".jpeg")))
+      (!strcasecmp(ch, ".jpeg")) ||
+      (!strcasecmp(ch, ".thm")))
   {
     is_jpeg = 1;
   } 
@@ -299,11 +297,13 @@
 
     l = strlen(fname);
     if ((l>4 && !strcasecmp(fname+l-4, ".jpg")) ||
+	(l>4 && !strcasecmp(fname+l-4, ".thm")) ||
 	(l>5 && !strcasecmp(fname+l-5, ".jpeg")))
       is_jpeg = 1;
     else if (l>4 && !strcasecmp(fname+l-4, ".gif"))
       is_gif = 1;
 
+
     if (is_jpeg)
     {
       if (!quality)
@@ -347,7 +347,7 @@
 
 	/* figure out if we need to scale it */
 
-	if ((srcW > maxW) || (srcH > maxH)) {
+	if ((maxW && srcW > maxW) || (maxH && srcH > maxH)) {
 	  /* scale paramaters */
 	  int dstX,dstY,dstW,dstH;
 	  /* Declare output file */
@@ -449,8 +449,7 @@
       }
     }
     else {
-      ne_warn("How'd I get here?");
-      return nerr_raise(NERR_ASSERT, "I shouldn't get here...");
+      dispfile = fopen(fname,"rb");
     }
   }
 
@@ -459,6 +458,15 @@
 
     char buf[8192];
     int count;
+
+    if (!fstat(fileno(dispfile), &s) && s.st_size)
+    {
+      cgiwrap_writef("Content-Length: %ld\n\n", s.st_size);
+    }
+    else
+    {
+      cgiwrap_writef("\n");
+    }
     
     fseek(dispfile,0,SEEK_SET);
 
@@ -477,55 +485,6 @@
   return nerr_pass(err);
 }
 
-char *url_escape (char *buf)
-{
-  int nl = 0;
-  int l = 0;
-  char *s;
-
-  while (buf[l])
-  {
-    if (buf[l] == '/' || buf[l] == '+' || buf[l] == '=' || buf[l] == '&' || 
-	buf[l] == '"' ||
-	buf[l] < 32 || buf[l] > 122)
-    {
-      nl += 2;
-    }
-    nl++;
-    l++;
-  }
-
-  s = (char *) malloc (sizeof(char) * (nl + 1));
-  if (s == NULL) return NULL;
-
-  nl = 0; l = 0;
-  while (buf[l])
-  {
-    if (buf[l] == ' ')
-    {
-      s[nl++] = '+';
-      l++;
-    }
-    else
-    if (buf[l] == '/' || buf[l] == '+' || buf[l] == '=' || buf[l] == '&' || 
-	buf[l] == '"' ||
-	buf[l] < 32 || buf[l] > 122)
-    {
-      s[nl++] = '%';
-      s[nl++] = "0123456789ABCDEF"[buf[l] / 16];
-      s[nl++] = "0123456789ABCDEF"[buf[l] % 16];
-      l++;
-    }
-    else
-    {
-      s[nl++] = buf[l++];
-    }
-  }
-  s[nl] = '\0';
-
-  return s;
-}
-
 NEOERR *load_images (char *path, ULIST **rfiles, char *partial, int descend)
 {
   NEOERR *err = STATUS_OK;
@@ -577,6 +536,7 @@
 	is_jpeg = 0; is_gif = 0;
 
 	if ((l>4 && !strcasecmp(de->d_name+l-4, ".jpg")) ||
+	    (l>4 && !strcasecmp(de->d_name+l-4, ".thm")) ||
 	    (l>5 && !strcasecmp(de->d_name+l-5, ".jpeg")))
 	  is_jpeg = 1;
 	else if (l>4 && !strcasecmp(de->d_name+l-4, ".gif"))
@@ -611,7 +571,7 @@
   int r, l;
   int width, height;
   char ipath[_POSIX_PATH_MAX];
-  int is_jpeg = 0, is_gif = 0;
+  int is_jpeg = 0, is_gif = 0, is_thm = 0;
 
   l = strlen(file);
   if ((l>4 && !strcasecmp(file+l-4, ".jpg")) ||
@@ -619,12 +579,14 @@
     is_jpeg = 1;
   else if (l>4 && !strcasecmp(file+l-4, ".gif"))
     is_gif = 1;
+  else if (l>4 && !strcasecmp(file+l-4, ".thm"))
+    is_thm = 1;
 
   snprintf (buf, sizeof(buf), "%s.%d", prefix, i);
-  err = hdf_set_buf (cgi->hdf, prefix, url_escape(file));
+  err = hdf_set_value (cgi->hdf, prefix, file);
   if (err != STATUS_OK) return nerr_pass(err);
   snprintf (ipath, sizeof(ipath), "%s/%s", path, file);
-  if (is_jpeg)
+  if (is_jpeg || is_thm)
     r = jpeg_size(ipath, &width, &height);
   else
     r = gif_size(ipath, &width, &height);
@@ -639,6 +601,14 @@
     err = hdf_set_value (cgi->hdf, buf, num);
     if (err != STATUS_OK) return nerr_pass(err);
   }
+  if (is_thm)
+  {
+    strcpy(ipath, file);
+    strcpy(ipath+l-4, ".avi");
+    snprintf(buf, sizeof(buf), "%s.avi", prefix);
+    err = hdf_set_value (cgi->hdf, buf, ipath);
+    if (err != STATUS_OK) return nerr_pass(err);
+  }
   return STATUS_OK;
 }
 
@@ -702,13 +672,50 @@
   return strcmp(*sa, *sb);
 }
 
+static NEOERR *export_album_path(CGI *cgi, char *album, char *prefix)
+{
+  NEOERR *err = STATUS_OK;
+  char *p, *l;
+  int n = 0;
+  char buf[256];
+
+  l = album;
+  p = strchr(album, '/');
+
+  while (p != NULL)
+  {
+    *p = '\0';
+    snprintf(buf, sizeof(buf), "%s.%d", prefix, n);
+    err = hdf_set_value(cgi->hdf, buf, l);
+    if (err) break;
+    snprintf(buf, sizeof(buf), "%s.%d.path", prefix, n++);
+    err = hdf_set_value(cgi->hdf, buf, album);
+    if (err) break;
+    *p = '/';
+    l = p+1;
+    p = strchr(l, '/');
+  }
+  if (err) return nerr_pass(err);
+  if (strlen(l))
+  {
+    snprintf(buf, sizeof(buf), "%s.%d", prefix, n);
+    err = hdf_set_value(cgi->hdf, buf, l);
+    if (err) return nerr_pass(err);
+    snprintf(buf, sizeof(buf), "%s.%d.path", prefix, n++);
+    err = hdf_set_value(cgi->hdf, buf, album);
+    if (err) return nerr_pass(err);
+  }
+
+  return STATUS_OK;
+}
+
+
 NEOERR *dowork_picture (CGI *cgi, char *album, char *picture)
 {
   NEOERR *err = STATUS_OK;
   char *base, *name;
   char path[_POSIX_PATH_MAX];
   char buf[256];
-  char *enc_picture;
   int i, x, factor, y;
   int thumb_width, thumb_height;
   int pic_width, pic_height;
@@ -717,6 +724,7 @@
   char t_pic[_POSIX_PATH_MAX];
   char nfile[_POSIX_PATH_MAX];
   char *ch;
+  char *avi = NULL;
   int rotate;
 
   ch = strrchr(picture, '/');
@@ -754,12 +762,13 @@
     picture = strrchr(nfile, '/') + 1;
   }
 
-  err = hdf_set_buf (cgi->hdf, "Album", url_escape(album));
+  err = hdf_set_value (cgi->hdf, "Album", album);
   if (err != STATUS_OK) return nerr_pass(err);
   err = hdf_set_value (cgi->hdf, "Album.Raw", album);
   if (err != STATUS_OK) return nerr_pass(err);
-  enc_picture = url_escape(picture);
-  err = hdf_set_buf (cgi->hdf, "Picture", enc_picture);
+  err = export_album_path(cgi, album, "Album.Path");
+  if (err) return nerr_pass(err);
+  err = hdf_set_value (cgi->hdf, "Picture", picture);
   if (err != STATUS_OK) return nerr_pass(err);
 
   err = load_images(path, &files, NULL, 0);
@@ -806,9 +815,10 @@
       y = x;
       while (y > pic_width)
       {
-	/* factor = factor * 2; */
-	factor++;
+	factor = factor * 2;
+	/* factor++; */
 	y = x / factor;
+	ne_warn("factor = %d, y = %d", factor, y);
       }
       snprintf (buf, sizeof(buf), "%d", y);
       hdf_set_value (cgi->hdf, "Picture.width", buf);
@@ -825,6 +835,12 @@
       snprintf (buf, sizeof(buf), "%d", pic_height);
       hdf_set_value (cgi->hdf, "Picture.height", buf);
     }
+    snprintf (buf, sizeof(buf), "Show.%d.avi", i);
+    avi = hdf_get_value (cgi->hdf, buf, NULL);
+    if (avi) 
+    {
+      err = hdf_set_value(cgi->hdf, "Picture.avi", avi);
+    }
 
     err = scale_images (cgi, "Show", thumb_width, thumb_height, 0);
   }
@@ -833,6 +849,17 @@
   return nerr_pass(err);
 }
 
+static int is_album(void *rock, char *filename)
+{
+  char path[_POSIX_PATH_MAX];
+  char *prefix = (char *)rock;
+
+  if (filename[0] == '.') return 0;
+  snprintf(path, sizeof(path), "%s/%s", prefix, filename);
+  if (isdir(path)) return 1;
+  return 0;
+}
+
 NEOERR *dowork_album_overview (CGI *cgi, char *album)
 {
   NEOERR *err = STATUS_OK;
@@ -840,55 +867,52 @@
   struct dirent *de;
   char path[_POSIX_PATH_MAX];
   char buf[256];
-  int i = 0, x;
+  int i = 0, x, y;
   int thumb_width, thumb_height;
   ULIST *files = NULL;
+  ULIST *albums = NULL;
   char *name;
 
   thumb_width = hdf_get_int_value (cgi->hdf, "ThumbWidth", 120);
   thumb_height = hdf_get_int_value (cgi->hdf, "ThumbWidth", 90);
 
-  if ((dp = opendir (album)) == NULL)
-  {
-    return nerr_raise(NERR_IO, "Unable to opendir %s: [%d] %s", album, errno, 
-	strerror(errno));
-  }
+  err = ne_listdir_fmatch(album, &albums, is_album, album);
+  if (err) return nerr_pass(err);
 
-  while ((de = readdir (dp)) != NULL)
+
+  err = uListSort(albums, alpha_sort);
+  if (err) return nerr_pass(err);
+  for (y = 0; y < uListLength(albums); y++)
   {
-    if (de->d_name[0] != '.')
+    err = uListGet(albums, y, (void *)&name);
+    if (err) break;
+
+    snprintf(path, sizeof(path), "%s/%s", album, name);
+    snprintf(buf, sizeof(buf), "Albums.%d", i);
+    err = hdf_set_value (cgi->hdf, buf, name);
+    if (err != STATUS_OK) break;
+    err = load_images(path, &files, NULL, 1);
+    if (err != STATUS_OK) break;
+    err = uListSort(files, alpha_sort);
+    if (err != STATUS_OK) break;
+    snprintf(buf, sizeof(buf), "Albums.%d.Count", i);
+    err = hdf_set_int_value(cgi->hdf, buf, uListLength(files));
+    if (err != STATUS_OK) break;
+    for (x = 0; (x < 4) && (x < uListLength(files)); x++)
     {
-      snprintf(path, sizeof(path), "%s/%s", album, de->d_name);
-      if (isdir(path))
-      {
-	snprintf(buf, sizeof(buf), "Albums.%d", i);
-	err = hdf_set_buf (cgi->hdf, buf, url_escape(de->d_name));
-	if (err != STATUS_OK) break;
-	err = load_images(path, &files, NULL, 1);
-	if (err != STATUS_OK) break;
-	err = uListSort(files, alpha_sort);
-	if (err != STATUS_OK) break;
-	snprintf(buf, sizeof(buf), "Albums.%d.Count", i);
-	err = hdf_set_int_value(cgi->hdf, buf, uListLength(files));
-	if (err != STATUS_OK) break;
-	for (x = 0; (x < 4) && (x < uListLength(files)); x++)
-	{
-	  err = uListGet(files, x, (void *)&name);
-	  if (err) break;
-	  snprintf(buf, sizeof(buf), "Albums.%d.Images.%d", i, x);
-	  err = export_image(cgi, buf, path, name);
-	  if (err) break;
-	}
-	uListDestroy(&files, ULIST_FREE);
-	if (err != STATUS_OK) break;
-	snprintf(buf, sizeof(buf), "Albums.%d.Images", i);
-	err = scale_images (cgi, buf, thumb_width, thumb_height, 0);
-	if (err != STATUS_OK) break;
-	i++;
-      }
+      err = uListGet(files, x, (void *)&name);
+      if (err) break;
+      snprintf(buf, sizeof(buf), "Albums.%d.Images.%d", i, x);
+      err = export_image(cgi, buf, path, name);
+      if (err) break;
     }
+    uListDestroy(&files, ULIST_FREE);
+    if (err != STATUS_OK) break;
+    snprintf(buf, sizeof(buf), "Albums.%d.Images", i);
+    err = scale_images (cgi, buf, thumb_width, thumb_height, 0);
+    if (err != STATUS_OK) break;
+    i++;
   }
-  closedir(dp);
   return nerr_pass(err);
 }
 
@@ -915,14 +939,18 @@
   per_page = hdf_get_int_value (cgi->hdf, "PerPage", 50);
   start = hdf_get_int_value (cgi->hdf, "Query.start", 0);
 
-  err = hdf_set_buf (cgi->hdf, "Album", url_escape(album));
+  err = hdf_set_value (cgi->hdf, "Album", album);
   if (err != STATUS_OK) return nerr_pass(err);
   err = hdf_set_value (cgi->hdf, "Album.Raw", album);
   if (err != STATUS_OK) return nerr_pass(err);
+  err = export_album_path(cgi, album, "Album.Path");
+  if (err) return nerr_pass(err);
+
 
   err = hdf_set_value (cgi->hdf, "Context", "album");
   if (err != STATUS_OK) return nerr_pass(err);
 
+
   snprintf (path, sizeof(path), "%s/%s", base, album);
   err = dowork_album_overview(cgi, path);
   if (err != STATUS_OK) return nerr_pass(err);
@@ -966,7 +994,7 @@
 NEOERR *dowork_image (CGI *cgi, char *image) 
 {
   NEOERR *err = STATUS_OK;
-  int maxW = 1024, maxH = 1024;
+  int maxW = 0, maxH = 0;
   char *basepath = "";
   char *cache_basepath = "/tmp/.imgcache/";
   char srcpath[_POSIX_PATH_MAX] = "";
@@ -1016,17 +1044,22 @@
 
   /* fprintf(stderr,"cachepath: %s\n",cachepath); */
 
+  ne_warn("srcpath: %s", srcpath);
   l = strlen(srcpath);
   if ((l>4 && !strcasecmp(srcpath+l-4, ".jpg")) ||
+      (l>4 && !strcasecmp(srcpath+l-4, ".thm")) ||
       (l>5 && !strcasecmp(srcpath+l-5, ".jpeg")))
     cgiwrap_writef("Content-Type: image/jpeg\n");
   else if (l>4 && !strcasecmp(srcpath+l-4, ".gif"))
     cgiwrap_writef("Content-Type: image/gif\n");
+  else if (l>4 && !strcasecmp(srcpath+l-4, ".avi"))
+  {
+    ne_warn("found avi");
+    cgiwrap_writef("Content-Type: video/x-msvideo\n");
+  }
   t = gmtime(&(s.st_mtime));
   strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", t);
-  cgiwrap_writef("Last-modified: %s\n\n", buf);
-
-  fflush(stdout);
+  cgiwrap_writef("Last-modified: %s\n", buf);
 
   err = scale_and_display_image(srcpath,maxW,maxH,cachepath,quality);
   return nerr_pass(err);
@@ -1042,11 +1075,13 @@
   char *cs_file;
   char *picture;
 
+  ne_warn("Starting IMD");
   cgi_debug_init (argc,argv);
   cgiwrap_init_std (argc, argv, envp);
   
   nerr_init();
 
+  ne_warn("CGI init");
   err = cgi_init(&cgi, NULL);
   if (err != STATUS_OK)
   {
@@ -1055,6 +1090,7 @@
     return -1;
   }
   imd_file = hdf_get_value(cgi->hdf, "CGI.PathTranslated", NULL);
+  ne_warn("Reading IMD file %s", imd_file);
   err = hdf_read_file (cgi->hdf, imd_file);
   if (err != STATUS_OK)
   {
@@ -1091,6 +1127,12 @@
       if (nerr_handle(&err, CGIFinished))
       {
 	/* pass */
+      }
+      else
+      {
+	cgi_neo_error(cgi, err);
+	nerr_log_error(err);
+	return -1;
       }
     }
     else
diff -Nru clearsilver-0.9.1/imd/imd.cs clearsilver-0.9.2/imd/imd.cs
--- clearsilver-0.9.1/imd/imd.cs	Mon Feb 11 12:16:23 2002
+++ clearsilver-0.9.2/imd/imd.cs	Tue Aug  5 12:12:51 2003
@@ -7,7 +7,7 @@
       </TR>
       <TR>
 	<TD><IMG name="frame3" border=0 height=<?cs var:image.height ?> width=8 src="3.gif"></TD>
-	<TD><a href="<?cs var:link ?>"><img border=0 width=<?cs var:image.width ?> height=<?cs var:image.height ?> src="<?cs var:CGI.PathInfo?>?image=<?cs var:Album ?>/<?cs var:image ?>&width=<?cs var:image.width ?>&height=<?cs var:image.height ?>"></a></TD>
+	<TD><a href="<?cs var:link ?>"><img border=0 width=<?cs var:image.width ?> height=<?cs var:image.height ?> src="<?cs var:CGI.PathInfo?>?image=<?cs var:url_escape(Album) ?>/<?cs var:url_escape(image) ?>&width=<?cs var:image.width ?>&height=<?cs var:image.height ?>"></a></TD>
 	<TD><IMG name="frame4" border=0 height=<?cs var:image.height ?> width=8 src="4.gif"></TD>
       </TR>
       <TR>
@@ -23,16 +23,21 @@
 - <?cs var:Picture ?><?cs /if ?></TITLE>
 </HEAD>
 <BODY BGCOLOR=#ffffff>
+<?cs include:"/home/www/header.html" ?>
+  <A HREF="<?cs var:CGI.PathInfo?>"><?cs var:Title ?></A> 
+  <?cs each:part = Album.Path ?>
+    <?cs if:part.path == Album ?>
+    / <?cs var:part ?>
+    <?cs else ?>
+    / <A HREF="<?cs var:CGI.PathInfo?>?album=<?cs var:url_escape(part.path) ?>"><?cs var:part ?></A> 
+    <?cs /if ?>
+  <?cs /each ?>
   <?cs if:Context == "album" ?>
-    <table border=0 bgcolor=#cccccc width=100%>
-      <tr><td align=center><font size=+2><?cs var:Album.Raw ?></font></td></tr>
-    </table>
   <?cs if:Albums.0 ?>
-    <H1><?cs var:Title ?></H1>
-    <CENTER>
+    <p>
       <?cs each:album = Albums ?>
-	<TABLE BORDER=0 CELLSPACING=1 CELLPADDING=1>
-	  <TR><TD BGCOLOR=#cccccc COLSPAN=4><font size=+2>
+	<TABLE BORDER=0 CELLSPACING=1 CELLPADDING=1 width=100%>
+	  <TR><TD style="border-bottom:2px solid #888888;" COLSPAN=4><font size=+2>
 	    <a href="<?cs var:CGI.PathInfo?>?album=<?cs if:Album ?><?cs var:Album ?>/<?cs /if ?><?cs var:album ?>"><?cs var:album ?></a></font> (<?cs var:album.Count ?> images)
 	  </td></tr>
 	  <TR>
@@ -46,7 +51,7 @@
       </TR>
       <TR>
 	<TD><IMG name="frame3" border=0 height=<?cs var:image.height ?> width=8 src="3.gif"></TD>
-	<TD><a href="<?cs var:CGI.PathInfo?>?album=<?cs if:Album ?><?cs var:Album ?>/<?cs /if ?><?cs var:album ?>&picture=<?cs var:image ?>"><img border=0 width=<?cs var:image.width ?> height=<?cs var:image.height ?> src="<?cs var:CGI.PathInfo?>?image=<?cs if:Album ?><?cs var:Album ?>/<?cs /if ?><?cs var:album ?>/<?cs var:image ?>&width=<?cs var:image.width ?>&height=<?cs var:image.height ?>"></a></TD>
+	<TD><a href="<?cs var:CGI.PathInfo?>?album=<?cs if:Album ?><?cs var:Album ?>/<?cs /if ?><?cs var:album ?>&picture=<?cs var:url_escape(image) ?>"><img border=0 width=<?cs var:image.width ?> height=<?cs var:image.height ?> src="<?cs var:CGI.PathInfo?>?image=<?cs if:Album ?><?cs var:url_escape(Album) ?>/<?cs /if ?><?cs var:url_escape(album) ?>/<?cs var:url_escape(image) ?>&width=<?cs var:image.width ?>&height=<?cs var:image.height ?>"></a></TD>
 	<TD><IMG name="frame4" border=0 height=<?cs var:image.height ?> width=8 src="4.gif"></TD>
       </TR>
       <TR>
@@ -60,10 +65,8 @@
 	  </TR>
 	</TABLE>
       <?cs /each ?>
-    </CENTER>
     <?cs /if ?>
     <?cs if:#Album.Count ?>
-    <A HREF="<?cs var:CGI.PathInfo?>"><?cs var:Title ?></A> 
     <DIV ALIGN=RIGHT>
     <?cs if:Album.Start > #0 ?>
     <A HREF="<?cs var:CGI.PathInfo ?>?album=<?cs var:Album ?>">First</A>
@@ -92,16 +95,13 @@
     <?cs /if ?>
     </DIV>
     
-    <table border=0 bgcolor=#cccccc width=100%>
-      <tr><td align=center><font size=+2><?cs var:Album.Raw ?></font></td></tr>
-    </table>
     <TABLE>
       <TR>
        <?cs set:TotalWidth = #0 ?>
     <?cs each:image=Images ?>
       <?cs set:nextWidth = #TotalWidth + #image.width ?>
       <?cs if:#nextWidth > #PageWidth ?></TR></TABLE><TABLE><TR><?cs set:TotalWidth = image.width ?><?cs else ?><?cs set:TotalWidth = nextWidth ?><?cs /if ?>
-      <TD><?cs call:frame_picture(image, CGI.PathInfo + "?album=" + Album + "&picture=" + image, "8") ?></TD>
+      <TD><?cs call:frame_picture(image, CGI.PathInfo + "?album=" + Album + "&picture=" + url_escape(image), "8") ?></TD>
     <?cs /each ?>
       </TR>
     </TABLE>
@@ -134,22 +134,20 @@
     </DIV>
   <?cs /if ?>
   <?cs else ?><?cs # picture ?>
-    <A HREF="<?cs var:CGI.PathInfo?>"><?cs var:Title ?></A> 
-    -- <A HREF="<?cs var:CGI.PathInfo?>?album=<?cs var:Album ?>"><?cs var:Album.Raw ?></A>
     <DIV ALIGN=RIGHT>
       <?cs set:count = #0 ?>
       <?cs each:image=Show ?>
-	<a href="<?cs var:CGI.PathInfo?>?album=<?cs var:Album ?>&picture=<?cs var:image ?>">
+	<a href="<?cs var:CGI.PathInfo?>?album=<?cs var:Album ?>&picture=<?cs var:url_escape(image) ?>">
 	 <?cs if:count == #0 ?>
-	   -2
+	   More
 	 <?cs elif:count == #1 ?>
-	   -1
+	   Previous
 	 <?cs elif:count == #2 ?>
-	    <?cs Name:image ?>
+	    Image #<?cs Name:image ?>
 	 <?cs elif:count == #3 ?>
-	    1
+	   Next
 	 <?cs elif:count == #4 ?>
-	   +2
+	   More
 	 <?cs /if ?>
 	 <?cs set:count = count + #1 ?>
 	</a> &nbsp;
@@ -164,7 +162,11 @@
       </TR>
       <TR>
         <TD><IMG name="frame3" border=0 height=<?cs var:Picture.height ?> width=18 src="3.gif"></TD>
-	<TD><img border=0 width=<?cs var:Picture.width?> height=<?cs var:Picture.height?> src="<?cs var:CGI.PathInfo?>?image=<?cs var:Album ?>/<?cs var:Picture ?>&width=<?cs var:Picture.width ?>&height=<?cs var:Picture.height?>&quality=1"></TD>
+	<?cs if:#0 && Picture.avi ?>
+	<TD><EMBED CONTROLborder=0 width=<?cs var:Picture.width?> height=<?cs var:Picture.height?> src="<?cs var:CGI.PathInfo?>?image=<?cs var:url_escape(Album) ?>/<?cs var:url_escape(Picture.avi) ?>" AUTOSTART=true></EMBED></TD>
+	<?cs else ?>
+	<TD><img border=0 width=<?cs var:Picture.width?> height=<?cs var:Picture.height?> src="<?cs var:CGI.PathInfo?>?image=<?cs var:url_escape(Album) ?>/<?cs var:url_escape(Picture) ?>&width=<?cs var:Picture.width ?>&height=<?cs var:Picture.height?>&quality=1" <?cs if:Picture.avi ?>dynsrc="<?cs var:CGI.PathInfo?>?image=<?cs var:url_escape(Album) ?>/<?cs var:url_escape(Picture.avi) ?>&width=<?cs var:Picture.width ?>&height=<?cs var:Picture.height?>&quality=1"<?cs /if ?>></TD>
+	<?cs /if ?>
         <TD><IMG name="frame4" border=0 height=<?cs var:Picture.height ?> width=18 src="4.gif"></TD>
       </TR>
       <TR>
@@ -172,24 +174,32 @@
         <TD><IMG name="frame6" border=0 height=18 width=<?cs var:Picture.width ?> src="6.gif"></TD>
         <TD><IMG name="frame7" border=0 height=18 width=18 src="7.gif"></TD>
       </TR>
+      <TR><TD COLSPAN=3 ALIGN=CENTER>
+        <?cs if:Picture.avi ?>
+	  <a href="<?cs var:CGI.PathInfo?>?image=<?cs var:url_escape(Album) ?>/<?cs var:url_escape(Picture.avi) ?>">Raw Video <?cs var:html_escape(Picture.avi) ?></a>
+	<?cs else ?>
+	  <a href="<?cs var:CGI.PathInfo?>?image=<?cs var:url_escape(Album) ?>/<?cs var:url_escape(Picture) ?>&quality=1">Raw Picture <?cs var:html_escape(Picture) ?></a>
+	<?cs /if ?>
+      </TD></TR>
     </TABLE>
     <CENTER>
       <TABLE>
 	<TR>
 	  <?cs each:image=Show ?>
 	  <?cs if:image != Picture ?>
-	    <TD><?cs call:frame_picture(image, CGI.PathInfo + "?album=" + Album + "&picture=" + image, "8") ?></TD>
+	    <TD><?cs call:frame_picture(image, CGI.PathInfo + "?album=" + Album + "&picture=" + url_escape(image), "8") ?></TD>
 	  <?cs /if ?>
 	  <?cs /each ?>
 	</TR>
       </TABLE>
     </CENTER>
-    <?cs if:CGI.RemoteAddress == "216.103.193.236" ?>
+    <?cs if:0 && CGI.RemoteAddress == "66.125.228.221" ?>
       &nbsp; Rotate: 
-      <A HREF="<?cs var:CGI.PathInfo ?>?album=<?cs var:Album ?>&picture=<?cs var:Picture ?>&rotate=90">Right</A> -
-      <A HREF="<?cs var:CGI.PathInfo ?>?album=<?cs var:Album ?>&picture=<?cs var:Picture ?>&rotate=-90">Left</A> -
-      <A HREF="<?cs var:CGI.PathInfo ?>?album=<?cs var:Album ?>&picture=<?cs var:Picture ?>&rotate=180">Flip</A>
+      <A HREF="<?cs var:CGI.PathInfo ?>?album=<?cs var:Album ?>&picture=<?cs var:url_escape(Picture) ?>&rotate=-90">Left</A> -
+      <A HREF="<?cs var:CGI.PathInfo ?>?album=<?cs var:Album ?>&picture=<?cs var:url_escape(Picture) ?>&rotate=90">Right</A> -
+      <A HREF="<?cs var:CGI.PathInfo ?>?album=<?cs var:Album ?>&picture=<?cs var:url_escape(Picture) ?>&rotate=180">Flip</A>
     <?cs /if ?>
   <?cs /if ?>
+<?cs include:"/home/www/footer.html" ?>
 </BODY>
 </HTML>
diff -Nru clearsilver-0.9.1/java-jni/CSTest.java clearsilver-0.9.2/java-jni/CSTest.java
--- clearsilver-0.9.1/java-jni/CSTest.java	Thu Sep 19 22:55:30 2002
+++ clearsilver-0.9.2/java-jni/CSTest.java	Mon Aug 18 13:09:20 2003
@@ -6,9 +6,6 @@
 
     public static void main( String [] args ) throws IOException {
 	org.clearsilver.HDF hdf = new org.clearsilver.HDF();
-	System.out.print("hdfptr: ");
-	
-	System.out.println( hdf.hdfptr );
 
 	hdf.setValue("Foo.Bar","10");
         hdf.setValue("Foo.Baz","20");
diff -Nru clearsilver-0.9.1/java-jni/Makefile clearsilver-0.9.2/java-jni/Makefile
--- clearsilver-0.9.1/java-jni/Makefile	Mon Apr 14 17:13:42 2003
+++ clearsilver-0.9.2/java-jni/Makefile	Mon Aug 18 13:09:20 2003
@@ -40,8 +40,29 @@
 CSTest.class: CSTest.java
 	$(JAVAC) -classpath $(NEO_UTIL_JAVA_JAR) CSTest.java
 
+gold: CSTest.class
+	@/bin/sh -c "LD_LIBRARY_PATH=$(NEOTONIC_ROOT)/java-jni; export LD_LIBRARY_PATH; CLASSPATH=$(NEO_UTIL_JAVA_JAR):.; export CLASSPATH; $(JAVA_PATH)/bin/java CSTest" > javatest.gold
+	@echo "Generated gold files"
+
 test: CSTest.class
-	@/bin/sh -c "LD_LIBRARY_PATH=$(NEOTONIC_ROOT)/java-jni; export LD_LIBRARY_PATH; CLASSPATH=$(NEO_UTIL_JAVA_JAR):.; export CLASSPATH; $(JAVA_PATH)/bin/java CSTest"
+	@echo "Running java test"
+	@failed=0; \
+	rm -f javatest.out; \
+	LD_LIBRARY_PATH=$(NEOTONIC_ROOT)/java-jni; export LD_LIBRARY_PATH; \
+	CLASSPATH=$(NEO_UTIL_JAVA_JAR):.; export CLASSPATH; \
+	$(JAVA_PATH)/bin/java CSTest > javatest.out; \
+	diff -brief javatest.out javatest.gold 2>%1 > /dev/null; \
+	return_code=$$?; \
+	if [ $$return_code -ne 0 ]; then \
+	  diff javatest.out javatest.gold > javatest.err; \
+	  echo "Failed Java Test: CSTest"; \
+	  echo "    See javatest.out and javatest.err"; \
+	  failed=1; \
+	fi; \
+	if [ $$failed -eq 1 ]; then \
+	  exit 1; \
+	fi;
+	@echo  "Passed java test"
 
 CGI.h: CGI.class
 	$(JAVAH) -jni CGI
diff -Nru clearsilver-0.9.1/java-jni/javatest.gold clearsilver-0.9.2/java-jni/javatest.gold
--- clearsilver-0.9.1/java-jni/javatest.gold	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.2/java-jni/javatest.gold	Mon Aug 18 13:09:20 2003
@@ -0,0 +1,11 @@
+Foo.Bar = 10
+Foo.Baz = 20
+
+----
+Foo.Bar:<?cs var:Foo.Bar ?>
+Foo.Baz:<?cs var:Foo.Baz ?>
+
+----
+Foo.Bar:10
+Foo.Baz:20
+
diff -Nru clearsilver-0.9.1/ports/rpm/clearsilver.spec clearsilver-0.9.2/ports/rpm/clearsilver.spec
--- clearsilver-0.9.1/ports/rpm/clearsilver.spec	Tue Jun 17 14:29:28 2003
+++ clearsilver-0.9.2/ports/rpm/clearsilver.spec	Mon Aug 11 15:11:32 2003
@@ -3,14 +3,16 @@
 #
 
 %define python_sitepath	%(eval `python -c 'import sys; print "%%s/lib/python%%s/site-packages" %% (sys.exec_prefix, sys.version[:3])'`)
+%define perl_sitearch %(eval "`perl -V:installsitearch`"; echo %$installsitearch)
+%define apache_libexec %(eval `/httpd/bin/apxs -q LIBEXECDIR`)
 
 Summary: Neotonic ClearSilver
 Name: clearsilver
-Version: 0.8.2
+Version: 0.9.2
 Release: 1
 Copyright: Open Source - Neotonic ClearSilver License (Apache 1.1 based)
 Group: Development/Libraries
-Source: http://www.clearsilver.net/downloads/clearsilver-0.8.2.tar.gz
+Source: http://www.clearsilver.net/downloads/clearsilver-0.9.2.tar.gz
 URL: http://www.clearsilver.net/
 Vendor: Neotonic Software Corporation, Inc.
 Packager: Brandon Long <blong@neotonic.com>
@@ -69,6 +71,15 @@
 The clearsilver-apache package provides an Apache 1.3.x module for
 loading ClearSilver CGI's as shared libraries.
 
+%package ruby
+Summary: Neotonic ClearSilver Apache Module
+Group: Development/Libraries
+Requires: clearsilver = %PACKAGE_VERSION
+Requires: ruby >= 1.4.5
+
+%description ruby
+The clearsilver-ruby package provides a ruby interface to the
+clearsilver templating system.
 %prep
 %setup 
 
@@ -123,4 +134,8 @@
 %{__prefix}/lib/libclearsilver-jni.so
 
 %files apache
-%{__prefix}/libexec/mod_ecs.so
+%{apache_libexec}/mod_ecs.so
+
+%files ruby
+%{ruby_sitepath}/%(ruby_version}/neo.rb
+%{ruby_sitepath}/%(ruby_version}/$(ruby_arch}/hdf.so
diff -Nru clearsilver-0.9.1/python/Makefile clearsilver-0.9.2/python/Makefile
--- clearsilver-0.9.1/python/Makefile	Mon Apr 14 17:13:45 2003
+++ clearsilver-0.9.2/python/Makefile	Thu Jul 24 11:36:50 2003
@@ -23,9 +23,15 @@
 
 all: $(TARGETS)
 
-$(NEO_UTIL_SO): $(NEO_UTIL_OBJ) $(DEP_LIBS)
+$(NEO_UTIL_SO): setup.py
+	$(PYTHON) setup.py build_ext --inplace
+
+OLD_NEO_UTIL_SO:
 	$(LDSHARED) -o $@ $(LDFLAGS) $(NEO_UTIL_OBJ) $(LIBS)
 
+setup:
+	$(PYTHON) setup.py build_ext --inplace
+
 $(NEO_UTIL_PYD): $(NEO_UTIL_OBJ) $(DEP_LIBS)
 	dllwrap --dllname neo_cgi.pyd --driver-name gcc \
 		--def neo_cgi.def -o neo_cgi.pyd \
@@ -42,3 +48,4 @@
 
 distclean:
 	$(RM) Makefile.depends $(TARGETS) *.o
+	$(RM) -r build
diff -Nru clearsilver-0.9.1/python/examples/CSPage.py clearsilver-0.9.2/python/examples/CSPage.py
--- clearsilver-0.9.1/python/examples/CSPage.py	Sun Jun 15 18:54:49 2003
+++ clearsilver-0.9.2/python/examples/CSPage.py	Wed Dec 31 16:00:00 1969
@@ -1,209 +0,0 @@
-#!/neo/opt/bin/python
-
-import neo_cgi
-import sys, os, string
-import time
-from log import *
-
-# errors thrown...
-NoPageName = "NoPageName"
-NoDisplayMethod = "NoDisplayMethod"
-
-# errors signaled back to here
-Redirected = "Redirected"
-DisplayDone = "DisplayDone"
-DisplayError = "DisplayError"
-
-class Context:
-    def __init__ (self):
-        self.argv = sys.argv
-        self.stdin = sys.stdin
-        self.stdout = sys.stdout
-        self.stderr = sys.stderr
-        self.environ = os.environ
-
-class CSPage:
-    def __init__(self, context, pagename=0,readDefaultHDF=1,israwpage=0):
-	if pagename == 0:
-	    raise NoPageName, "missing pagename"
-	self.pagename = pagename
-	self.readDefaultHDF = readDefaultHDF
-        self._israwpage = israwpage
-        self.context = context
-
-        self._error_template = None
-
-        self.page_start_time = time.time()
-	neo_cgi.cgiWrap(context.stdin, context.stdout, context.environ)
-        neo_cgi.IgnoreEmptyFormVars(1)
-	self.ncgi = neo_cgi.CGI()
-        self.ncgi.parse()
-        self._path_num = 0
-	domain = self.ncgi.hdf.getValue("CGI.ServerName","")
-	domain = self.ncgi.hdf.getValue("HTTP.Host", domain)
-        self.domain = domain
-        self.subclassinit()
-        self.setPaths([self.ncgi.hdf.getValue("CGI.DocumentRoot","")])
-
-    def subclassinit(self):
-        pass
-
-    def setPaths(self, paths):
-        for path in paths:  
-            self.ncgi.hdf.setValue("hdf.loadpaths.%d" % self._path_num, path)
-            self._path_num = self._path_num + 1
-            
-    def redirectUri(self,redirectTo):
-        ncgi = self.ncgi
-        if ncgi.hdf.getIntValue("Cookie.debug",0) == 1:
-            ncgi.hdf.setValue("CGI.REDIRECT_TO",redirectTo)
-            ncgi.display("dbg/redirect.cs")
-            print "<PRE>"
-            print neo_cgi.htmlEscape(ncgi.hdf.dump())
-            print "</PRE>"
-            raise DisplayDone
-
-        self.ncgi.redirectUri(redirectTo)
-        raise Redirected, "redirected To: %s" % redirectTo
-
-    ## ----------------------------------
-    ## methods to be overridden in subclass when necessary:
-
-    def setup(self):
-        pass
-
-    def display(self):
-        raise NoDisplayMethod, "no display method present in %s" % repr(self)
-
-    def main(self):
-        self.setup()
-        self.handle_actions()
-        self.display()
-
-    ## ----------------------------------
-        
-    def handle_actions(self):
-        hdf = self.ncgi.hdf
-        hdfobj = hdf.getObj("Query.Action")
-        if hdfobj:
-            firstchild = hdfobj.child()
-            if firstchild:
-                action = firstchild.name()
-                if firstchild.next():
-                    raise "multiple actions present!!!"
-
-                method_name = "Action_%s" % action
-                method = getattr(self,method_name)
-                apply(method,[])
-
-    def start(self):
-	SHOULD_DISPLAY = 1
-        if self._israwpage:
-            SHOULD_DISPLAY = 0
-        
-	ncgi = self.ncgi
-	
-	if self.readDefaultHDF:
-            try:
-                if not self.pagename is None:
-                    ncgi.hdf.readFile("%s.hdf" % self.pagename)
-            except:
-                log("Error reading HDF file: %s.hdf" % (self.pagename))
-
-        DISPLAY_ERROR = 0
-        ERROR_MESSAGE = ""
-        # call page main function!
-        try:
-            self.main()
-        except DisplayDone:
-            SHOULD_DISPLAY = 0
-        except Redirected:
-            # catch redirect exceptions
-            SHOULD_DISPLAY = 0
-        except DisplayError, num:
-            ncgi.hdf.setValue("Query.error", str(num))
-            if self._error_template:
-                ncgi.hdf.setValue("Content", self._error_template)
-            else:
-                DISPLAY_ERROR = 1
-        except:
-            SHOULD_DISPLAY = 0
-            DISPLAY_ERROR = 1
-            
-            import handle_error
-            handle_error.handleException("Display Failed!")
-            ERROR_MESSAGE = handle_error.exceptionString()
-
-        if DISPLAY_ERROR:
-            print "Content-Type: text/html\n\n"
-
-            # print the page
-
-            print "<H1> Error in Page </H1>"
-            print "A copy of this error report has been submitted to the developers. "
-            print "The details of the error report are below."
-
-
-            print "<PRE>"
-            print neo_cgi.htmlEscape(ERROR_MESSAGE)
-            print "</PRE>\n"
-            # print debug info always on page error...
-            print "<HR>\n"
-            print "<PRE>"
-            print neo_cgi.htmlEscape(ncgi.hdf.dump())
-            print "</PRE>"
-
-
-        etime = time.time() - self.page_start_time
-        ncgi.hdf.setValue("CGI.debug.execute_time","%f" % (etime))
-
-        if SHOULD_DISPLAY and self.pagename:
-            debug_output = ncgi.hdf.getIntValue("page.debug",ncgi.hdf.getIntValue("Cookie.debug",0))
-
-            if not debug_output:
-              ncgi.hdf.setValue("Config.CompressionEnabled","1")
-
-	    # default display
-	    template_name = ncgi.hdf.getValue("Content","%s.cs" % self.pagename)
-            # ncgi.hdf.setValue ("cgiout.charset", "utf-8");
-
-	    ncgi.display(template_name)
-
-	    # debug output
-	    if debug_output:
-		print "<HR>\n"
-                print "Execution Time: %5.3f<BR><HR>" % (etime)
-		print "<PRE>"
-		print neo_cgi.htmlEscape(ncgi.hdf.dump())
-		print "</PRE>"
-		# ncgi.hdf.setValue("hdf.DEBUG",ncgi.hdf.dump())
-		# ncgi.display("debug.cs")
-                
-        script_name = ncgi.hdf.getValue("CGI.ScriptName","")
-        if script_name:
-            script_name = string.split(script_name,"/")[-1]
-            
-        log ("[%s] etime/dtime: %5.3f/%5.3f %s (%s)" % (self.domain, etime, time.time() - etime - self.page_start_time,  script_name, self.pagename))
-
-    # a protected output function to catch the output errors that occur when
-    # the server is either restarted or the user pushes the stop button on the
-    # browser
-    def output(self, str):
-        try:
-            self.context.stdout.write(str)
-        except IOError, reason:
-            log("IOError: %s" % (repr(reason)))
-            raise DisplayDone
-
-
-    def allQuery (self, s):
-        l = []
-        if self.ncgi.hdf.getValue ("Query.%s.0" % s, ""):
-          obj = self.ncgi.hdf.getChild ("Query.%s" % s)
-          while obj:
-            l.append(obj.value())
-            obj = obj.next()
-        else:
-          t = self.ncgi.hdf.getValue ("Query.%s" % s, "")
-          if t: l.append(t)
-        return l
diff -Nru clearsilver-0.9.1/python/examples/base/CSPage.py clearsilver-0.9.2/python/examples/base/CSPage.py
--- clearsilver-0.9.1/python/examples/base/CSPage.py	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.2/python/examples/base/CSPage.py	Sun Aug 17 22:24:27 2003
@@ -0,0 +1,217 @@
+#!/neo/opt/bin/python
+
+import neo_cgi
+import sys, os, string
+import time
+from log import *
+
+# errors thrown...
+NoPageName = "NoPageName"
+NoDisplayMethod = "NoDisplayMethod"
+
+# errors signaled back to here
+Redirected = "Redirected"
+DisplayDone = "DisplayDone"
+DisplayError = "DisplayError"
+
+class Context:
+    def __init__ (self):
+        self.argv = sys.argv
+        self.stdin = sys.stdin
+        self.stdout = sys.stdout
+        self.stderr = sys.stderr
+        self.environ = os.environ
+
+class CSPage:
+    def __init__(self, context, pagename=0,readDefaultHDF=1,israwpage=0):
+	if pagename == 0:
+	    raise NoPageName, "missing pagename"
+	self.pagename = pagename
+	self.readDefaultHDF = readDefaultHDF
+        self._israwpage = israwpage
+        self.context = context
+
+        self._error_template = None
+
+        self.page_start_time = time.time()
+	neo_cgi.cgiWrap(context.stdin, context.stdout, context.environ)
+        neo_cgi.IgnoreEmptyFormVars(1)
+	self.ncgi = neo_cgi.CGI()
+        self.ncgi.parse()
+        self._path_num = 0
+	domain = self.ncgi.hdf.getValue("CGI.ServerName","")
+	domain = self.ncgi.hdf.getValue("HTTP.Host", domain)
+        self.domain = domain
+        self.subclassinit()
+        self.setPaths([self.ncgi.hdf.getValue("CGI.DocumentRoot","")])
+
+    def subclassinit(self):
+        pass
+
+    def setPaths(self, paths):
+        for path in paths:  
+            self.ncgi.hdf.setValue("hdf.loadpaths.%d" % self._path_num, path)
+            self._path_num = self._path_num + 1
+            
+    def redirectUri(self,redirectTo):
+        ncgi = self.ncgi
+        if ncgi.hdf.getIntValue("Cookie.debug",0) == 1:
+            ncgi.hdf.setValue("CGI.REDIRECT_TO",redirectTo)
+            ncgi.display("dbg/redirect.cs")
+            print "<PRE>"
+            print neo_cgi.htmlEscape(ncgi.hdf.dump())
+            print "</PRE>"
+            raise DisplayDone
+
+        self.ncgi.redirectUri(redirectTo)
+        raise Redirected, "redirected To: %s" % redirectTo
+
+    ## ----------------------------------
+    ## methods to be overridden in subclass when necessary:
+
+    def setup(self):
+        pass
+
+    def display(self):
+        raise NoDisplayMethod, "no display method present in %s" % repr(self)
+
+    def main(self):
+        self.setup()
+        self.handle_actions()
+        self.display()
+
+    ## ----------------------------------
+        
+    def handle_actions(self):
+        hdf = self.ncgi.hdf
+        hdfobj = hdf.getObj("Query.Action")
+        if hdfobj:
+            firstchild = hdfobj.child()
+            if firstchild:
+                action = firstchild.name()
+                if firstchild.next():
+                    raise "multiple actions present!!!"
+
+                method_name = "Action_%s" % action
+                method = getattr(self,method_name)
+                apply(method,[])
+
+    def start(self):
+	SHOULD_DISPLAY = 1
+        if self._israwpage:
+            SHOULD_DISPLAY = 0
+        
+	ncgi = self.ncgi
+	
+	if self.readDefaultHDF:
+            try:
+                if not self.pagename is None:
+                    ncgi.hdf.readFile("%s.hdf" % self.pagename)
+            except:
+                log("Error reading HDF file: %s.hdf" % (self.pagename))
+
+        DISPLAY_ERROR = 0
+        ERROR_MESSAGE = ""
+        # call page main function!
+        try:
+            self.main()
+        except DisplayDone:
+            SHOULD_DISPLAY = 0
+        except Redirected:
+            # catch redirect exceptions
+            SHOULD_DISPLAY = 0
+        except DisplayError, num:
+            ncgi.hdf.setValue("Query.error", str(num))
+            if self._error_template:
+                ncgi.hdf.setValue("Content", self._error_template)
+            else:
+                DISPLAY_ERROR = 1
+        except:
+            SHOULD_DISPLAY = 0
+            DISPLAY_ERROR = 1
+            
+            import handle_error
+            handle_error.handleException("Display Failed!")
+            ERROR_MESSAGE = handle_error.exceptionString()
+
+        if DISPLAY_ERROR:
+            print "Content-Type: text/html\n\n"
+
+            # print the page
+
+            print "<H1> Error in Page </H1>"
+            print "A copy of this error report has been submitted to the developers. "
+            print "The details of the error report are below."
+
+
+            print "<PRE>"
+            print neo_cgi.htmlEscape(ERROR_MESSAGE)
+            print "</PRE>\n"
+            # print debug info always on page error...
+            print "<HR>\n"
+            print "<PRE>"
+            print neo_cgi.htmlEscape(ncgi.hdf.dump())
+            print "</PRE>"
+
+
+        etime = time.time() - self.page_start_time
+        ncgi.hdf.setValue("CGI.debug.execute_time","%f" % (etime))
+
+        if SHOULD_DISPLAY and self.pagename:
+            debug_output = ncgi.hdf.getIntValue("page.debug",ncgi.hdf.getIntValue("Cookie.debug",0))
+
+            if not debug_output:
+              ncgi.hdf.setValue("Config.CompressionEnabled","1")
+
+	    # default display
+	    template_name = ncgi.hdf.getValue("Content","%s.cs" % self.pagename)
+            # ncgi.hdf.setValue ("cgiout.charset", "utf-8");
+
+            try:
+	        ncgi.display(template_name)
+            except:
+                print "Content-Type: text/html\n\n"
+                print "CSPage: Error occured"
+                import handle_error
+                print "<pre>" + handle_error.exceptionString() + "</pre>"
+                debug_output = 1
+                 
+
+	    # debug output
+	    if debug_output:
+		print "<HR>\n"
+                print "Execution Time: %5.3f<BR><HR>" % (etime)
+		print "<PRE>"
+		print neo_cgi.htmlEscape(ncgi.hdf.dump())
+		print "</PRE>"
+		# ncgi.hdf.setValue("hdf.DEBUG",ncgi.hdf.dump())
+		# ncgi.display("debug.cs")
+                
+        script_name = ncgi.hdf.getValue("CGI.ScriptName","")
+        if script_name:
+            script_name = string.split(script_name,"/")[-1]
+            
+        log ("[%s] etime/dtime: %5.3f/%5.3f %s (%s)" % (self.domain, etime, time.time() - etime - self.page_start_time,  script_name, self.pagename))
+
+    # a protected output function to catch the output errors that occur when
+    # the server is either restarted or the user pushes the stop button on the
+    # browser
+    def output(self, str):
+        try:
+            self.context.stdout.write(str)
+        except IOError, reason:
+            log("IOError: %s" % (repr(reason)))
+            raise DisplayDone
+
+
+    def allQuery (self, s):
+        l = []
+        if self.ncgi.hdf.getValue ("Query.%s.0" % s, ""):
+          obj = self.ncgi.hdf.getChild ("Query.%s" % s)
+          while obj:
+            l.append(obj.value())
+            obj = obj.next()
+        else:
+          t = self.ncgi.hdf.getValue ("Query.%s" % s, "")
+          if t: l.append(t)
+        return l
diff -Nru clearsilver-0.9.1/python/examples/base/handle_error.py clearsilver-0.9.2/python/examples/base/handle_error.py
--- clearsilver-0.9.1/python/examples/base/handle_error.py	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.2/python/examples/base/handle_error.py	Sun Jun 15 18:54:49 2003
@@ -0,0 +1,91 @@
+
+import traceback, sys, string, time, socket, os
+import who_calls
+
+DUMP_DIR = "/neo/data/bugs"
+
+Warning = "handle_error.Warning"
+
+# levels
+LV_MESSAGE = "LV_MESSAGE"
+LV_WARNING = "LV_WARNING"
+LV_ERROR   = "LV_ERROR"
+
+Count = 0
+
+gErrorCount = 0
+DISABLE_DUMP = 0
+
+def exceptionReason():
+  return "%s.%s" % (str(sys.exc_type), str(sys.exc_value))
+
+def exceptionString():
+  tb_list = traceback.format_exception(sys.exc_type,sys.exc_value,sys.exc_traceback)
+  return string.join(tb_list,"")
+
+  #### old way
+  import StringIO
+  ## get the traceback message  
+  sfp = StringIO.StringIO()
+  traceback.print_exc(file=sfp)
+  exception = sfp.getvalue()
+  sfp.close()
+
+  return exception
+
+
+def handleException (msg=None, lvl=LV_ERROR, dump = 1):
+  global gErrorCount
+  gErrorCount = gErrorCount + 1
+
+  tb_list = traceback.format_exception(sys.exc_type,sys.exc_value,sys.exc_traceback)
+  if msg:
+    sys.stderr.write ("%s\n" % msg)
+  else:
+    msg = "Unhandled Exception"
+        
+  sys.stderr.write (string.join(tb_list,""))
+  try:
+    if dump: dump_bug(lvl, "handleException", msg, string.join(tb_list, ""))
+  except:
+    handleException("Unable to dump_bug", dump = 0)
+
+def handleWarning (msg=""):
+  header = "*** handleWarning: %s\n" % msg
+  sys.stderr.write(header)
+  tb = who_calls.pretty_who_calls(strip=1) + "\n"
+  sys.stderr.write(tb)
+
+  try:
+    dump_bug(LV_WARNING, "handleException", msg, tb)
+  except:
+    handleException("Unable to dump_bug", dump = 0)
+
+def dump_bug (level, etype, msg, location=None, nhdf=None):
+    global DISABLE_DUMP
+    if DISABLE_DUMP: return
+
+    now = int(time.time())
+    pid = os.getpid()
+
+    import neo_cgi, neo_util
+    hdf = neo_util.HDF()
+    hdf.setValue("Required.Level", level)
+    hdf.setValue("Required.When", str(int(time.time())))
+    hdf.setValue("Required.Type", etype)
+    hdf.setValue("Required.Title", msg)
+    hdf.setValue("Optional.Hostname", socket.gethostname())
+    if location:
+        hdf.setValue("Optional.Location", location)
+
+    for (key, value) in os.environ.items():
+        hdf.setValue ("Environ.%s" % key, value)
+
+    global Count
+    Count = Count + 1
+    fname = "%d.%d_%d.%s" % (now, pid, Count, socket.gethostname())
+    tpath = os.path.join (DUMP_DIR, "tmp", fname)
+    npath = os.path.join (DUMP_DIR, "new", fname)
+    hdf.writeFile(tpath)
+    os.rename(tpath, npath)
+
diff -Nru clearsilver-0.9.1/python/examples/base/hdfhelp.py clearsilver-0.9.2/python/examples/base/hdfhelp.py
--- clearsilver-0.9.1/python/examples/base/hdfhelp.py	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.2/python/examples/base/hdfhelp.py	Sun Aug 17 22:24:27 2003
@@ -0,0 +1,144 @@
+#!/neo/opt/bin/python
+#
+# Copyright (C) 2001 by Neotonic Software Corporation
+# All Rights Reserved.
+#
+# hdfhelp.py
+#
+# This code makes using odb with Clearsilver as "easy as stealing candy
+# from a baby". - jeske
+#
+# How to use:
+#
+#  rows = tbl.fetchAllRows()
+#  rows.hdfExport("CGI.rows", hdf_dataset)
+#
+#  row  = tbl.fetchRow( ('primary_key', value) )
+#  row.hdfExport("CGI.row", hdf_dataset)
+#
+# How to setup:
+#   
+#  # define table
+#  class AgentsTable(odb.Table):
+#    def _defineRows(self):
+#      self.d_addColumn("agent_id",kInteger,None,primarykey = 1,autoincrement = 1)
+#      self.d_addColumn("login",kVarString,200,notnull=1)
+#      self.d_addColumn("ticket_count",kIncInteger,None)
+#
+#    # make sure you return a subclass of hdfhelp.HdfRow
+#
+#    def defaultRowClass(self):
+#      return hdfhelp.HdfRow
+#    def defaultRowListClass(self):
+#      return hdfhelp.HdfItemList
+#
+
+import string
+import neo_cgi
+import neo_cs
+import neo_util
+import odb
+import time
+
+import UserList
+
+SECS_IN_MIN = 60
+SECS_IN_HOUR = (SECS_IN_MIN * 60)
+SECS_IN_DAY = (SECS_IN_HOUR * 24)
+SECS_IN_WEEK = (SECS_IN_DAY * 7)
+SECS_IN_MONTH = (SECS_IN_DAY * 30)
+
+kYearPos = 0
+kMonthPos = 1
+kDayPos = 2
+kHourPos = 3
+kMinutePos = 4
+kSecondPos = 5
+kWeekdayPos = 6
+kJulianDayPos = 7
+kDSTPos = 8
+
+
+def renderDate(then_time,day=0):
+    if then_time is None:
+        then_time = 0
+    then_time = int(then_time)
+    if then_time == 0 or then_time == -1:
+        return ""
+    
+    then_tuple = time.localtime(then_time)
+
+    now_tuple = time.localtime(time.time())
+    
+    if day or (then_tuple[kHourPos]==0 and then_tuple[kMinutePos]==0 and then_tuple[kSecondPos]==0):
+        # it's just a date
+        if then_tuple[kYearPos] == now_tuple[kYearPos]:
+            # no year
+            return time.strftime("%m/%d",then_tuple)
+        else:
+            # add year
+            return time.strftime("%m/%d/%Y",then_tuple)
+
+    else:
+        # it's a full time/date
+
+        return time.strftime("%m/%d/%Y %H:%M%p",then_tuple)
+
+class HdfRow(odb.Row):
+    def hdfExport(self,prefix,hdf_dataset,skip_fields = None, translate_dict = None):
+
+        for col_name,value in self.items():
+            if skip_fields and (col_name in skip_fields):
+                continue
+            try:
+                name,col_type,col_options = self._table.getColumnDef(col_name)
+            except:
+                col_type = odb.kVarString
+                col_options = {}
+            
+	    if (col_name != "value") and (value is not None):
+		if type(value) in [ type(0), type(0L) ]:
+		    hdf_dataset.setValue(prefix + "." + col_name,"%d" % value)
+		else:
+                    if translate_dict:
+                        for k,v in translate_dict.items():
+                            value = string.replace(value,k,v)
+		    hdf_dataset.setValue(prefix + "." + col_name,neo_cgi.htmlEscape(value))
+                if col_options.get("int_date",0):
+                    hdf_dataset.setValue(prefix + "." + col_name + ".string",renderDate(value))
+                    hdf_dataset.setValue(prefix + "." + col_name + ".day_string",renderDate(value,day=1))
+
+
+
+class HdfItemList(UserList.UserList):
+    def hdfExport(self,prefix,hdf_dataset,*extra,**extranamed):
+	n = 0
+	for row in self:
+	    row.hdfExport("%s.%d" % (prefix,n),hdf_dataset,*extra,**extranamed)
+	    n = n + 1
+
+
+def eval_cs(hdf,a_cs_string):
+    cs = neo_cs.CS(hdf)
+    try:
+      cs.parseStr(a_cs_string)
+      return cs.render()
+    except:
+      return "Error in CS tags: %s" % neo_cgi.htmlEscape(repr(a_cs_string))
+
+# ----------------------------
+
+def test():
+    import neo_util
+    hdf = neo_util.HDF()
+    hdf.setValue("foo","1")
+    print eval_cs(hdf,"this should say 1  ===> <?cs var:foo ?>")
+    
+
+if __name__ == "__main__":
+    test()
+
+
+
+
+
diff -Nru clearsilver-0.9.1/python/examples/base/log.py clearsilver-0.9.2/python/examples/base/log.py
--- clearsilver-0.9.1/python/examples/base/log.py	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.2/python/examples/base/log.py	Sun Jun 15 23:14:49 2003
@@ -0,0 +1,51 @@
+#!/neo/opt/bin/python
+
+# log.py
+
+import sys, time
+
+DEV = "development"
+DEV_UPDATE = "update queries"
+DEV_SELECT = "select queries"
+DEV_REPORT = "report log"
+
+LOGGING_STATUS = {
+   DEV : 1,
+   DEV_UPDATE : 0,
+   DEV_SELECT : 0,
+   DEV_REPORT : 0}
+
+tstart = 0
+
+def dlog(when,astr):
+    global LOGGING_STATUS
+    try:
+        if LOGGING_STATUS[when]:
+            log(astr)
+    except KeyError:
+        pass
+
+def tlog(astr):
+    global tstart
+    t = time.time()
+    if tstart == 0:
+        tstart = t
+    time_stamp = "%5.5f" % (t-tstart)
+    if len(astr):
+        if astr[-1] == "\n":
+            sys.stderr.write("[%s] %s" % (time_stamp, astr))
+        else:
+            sys.stderr.write("[%s] %s\n" % (time_stamp, astr))
+
+def log(astr):
+    if len(astr) > 1024:
+        astr = astr[:1024]
+
+    t = time.time()
+    time_stamp = time.strftime("%m/%d %T", time.localtime(t))
+    if len(astr):
+        if astr[-1] == "\n":
+            sys.stderr.write("[%s] %s" % (time_stamp, astr))
+        else:
+            sys.stderr.write("[%s] %s\n" % (time_stamp, astr))
+    # sys.stderr.flush()
diff -Nru clearsilver-0.9.1/python/examples/base/odb.py clearsilver-0.9.2/python/examples/base/odb.py
--- clearsilver-0.9.1/python/examples/base/odb.py	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.2/python/examples/base/odb.py	Sun Aug 17 22:24:27 2003
@@ -0,0 +1,1475 @@
+#!/neo/opt/bin/python
+#
+# odb.py
+#
+# Object Database Api
+#
+# Written by David Jeske <jeske@neotonic.com>, 2001/07. 
+# Inspired by eGroups' sqldb.py originally written by Scott Hassan circa 1998.
+#
+# Copyright (C) 2001, by David Jeske and Neotonic
+#
+# Goals:
+#       - a simple object-like interface to database data
+#       - database independent (someday)
+#       - relational-style "rigid schema definition"
+#       - object style easy-access
+#
+# Example:
+#
+#  import odb
+#  import MySQLdb
+#
+#  # define table
+#  class AgentsTable(odb.Table):
+#    def _defineRows(self):
+#      self.d_addColumn("agent_id",kInteger,None,primarykey = 1,autoincrement = 1)
+#      self.d_addColumn("login",kVarString,200,notnull=1)
+#      self.d_addColumn("ticket_count",kIncInteger,None)
+#
+#  if __name__ == "__main__":
+#    # open database
+#    ndb = MySQLdb.connect(host = 'localhost',
+#                          user='username', 
+#                          passwd = 'password', 
+#                          db='testdb')
+#    db = Database(ndb)
+#    tbl = AgentsTable(db,"agents")
+#
+#    # create row
+#    agent_row = tbl.newRow()
+#    agent_row.login = "foo"
+#    agent_row.save()
+#
+#    # fetch row (must use primary key)
+#    try:
+#      get_row = tbl.fetchRow( ('agent_id', agent_row.agent_id) )
+#    except odb.eNoMatchingRows:
+#      print "this is bad, we should have found the row"
+#
+#    # fetch rows (can return empty list)
+#    list_rows = tbl.fetchRows( ('login', "foo") )
+#
+
+import tstart
+
+import string
+import sys, zlib
+from log import *
+
+import MySQLdb
+
+eNoSuchColumn         = "odb.eNoSuchColumn"
+eNonUniqueMatchSpec   = "odb.eNonUniqueMatchSpec"
+eNoMatchingRows       = "odb.eNoMatchingRows"
+eInternalError        = "odb.eInternalError"
+eInvalidMatchSpec     = "odb.eInvalidMatchSpec"
+eInvalidData          = "odb.eInvalidData"
+eUnsavedObjectLost    = "odb.eUnsavedObjectLost"
+eDuplicateKey         = "odb.eDuplicateKey"
+
+#####################################
+# COLUMN TYPES                       
+################                     ######################
+# typename     ####################### size data means:
+#              #                     # 
+kInteger       = "kInteger"          # -
+kFixedString   = "kFixedString"      # size
+kVarString     = "kVarString"        # maxsize
+kBigString     = "kBigString"        # -
+kIncInteger    = "kIncInteger"       # -
+kDateTime      = "kDateTime"
+kTimeStamp     = "kTimeStamp"
+
+
+DEBUG = 0
+
+##############
+# Database
+#
+# this will ultimately turn into a mostly abstract base class for
+# the DB adaptors for different database types....
+#
+
+class Database:
+    def __init__(self,db):
+        self._tables = {}
+        self.db = db
+        self._cursor = None
+        self.compression_enabled = 0
+
+    # __init__ = None
+    # list_tables = None   # list_tables() -> ['a_table','b_table']
+    # list_fields = None   # list_fields(tbl_name) -> ['
+    # checkTable = None    # checkTable
+    # createTable
+
+    def defaultCursor(self):
+        if self._cursor is None:
+            self._cursor = self.db.cursor()
+        return self._cursor
+
+    def escape(self,str):
+        return MySQLdb.escape_string(str)
+    def defaultRowClass(self):
+	return Row
+
+    def defaultRowListClass(self):
+        # base type is list...
+	return list
+
+    def addTable(self, attrname, tblname, tblclass, rowClass = None, check = 0, create = 0, rowListClass = None):
+        self._tables[attrname] = tblclass(self, tblname, rowClass=rowClass, check=check, create=create, rowListClass=rowListClass)
+
+    def close(self):
+        for name, tbl in self._tables.items():
+            tbl.db = None
+        self._tables = {}
+        if self.db is not None:
+            self.db.close()
+            self.db = None
+
+    def __getattr__(self, key):
+        if key == "_tables":
+            raise AttributeError, "odb.Database: not initialized properly, self._tables does not exist"
+
+        try:
+            table_dict = getattr(self,"_tables")
+            return table_dict[key]
+        except KeyError:
+            raise AttributeError, "odb.Database: unknown attribute %s" % (key)
+        
+    def beginTransaction(self, cursor=None):
+        if cursor is None:
+            cursor = self.defaultCursor()
+        dlog(DEV_UPDATE,"begin")
+        cursor.execute("begin")
+
+    def commitTransaction(self, cursor=None):
+        if cursor is None:
+            cursor = self.defaultCursor()
+        dlog(DEV_UPDATE,"commit")
+        cursor.execute("commit")
+
+    def rollbackTransaction(self, cursor=None):
+        if cursor is None:
+            cursor = self.defaultCursor()
+        dlog(DEV_UPDATE,"rollback")
+        cursor.execute("rollback")
+
+##########################################
+# Table
+#
+
+
+class Table:
+    def subclassinit(self):
+        pass
+    def __init__(self,database,table_name,rowClass = None, check = 0, create = 0, rowListClass = None):
+	self.db = database
+	self.__table_name = table_name
+	if rowClass:
+	    self.__defaultRowClass = rowClass
+	else:
+	    self.__defaultRowClass = database.defaultRowClass()
+
+	if rowListClass:
+	    self.__defaultRowListClass = rowListClass
+	else:
+	    self.__defaultRowListClass = database.defaultRowListClass()
+
+	# get this stuff ready!
+	
+	self.__column_list = []
+	self.__vcolumn_list = []
+	self.__columns_locked = 0
+	self.__has_value_column = 0
+
+	# this will be used during init...
+	self.__col_def_hash = None
+	self.__vcol_def_hash = None
+	self.__primary_key_list = None
+        self.__relations_by_table = {}
+
+	# ask the subclass to def his rows
+	self._defineRows()
+
+	# get ready to run!
+	self.__lockColumnsAndInit()
+
+        self.subclassinit()
+        
+	if create:
+	    self.db.createTable(self)
+
+	if check:
+	    self.db.checkTable(self)
+
+    def getColumnDef(self,column_name):
+        try:
+            return self.__col_def_hash[column_name]
+        except KeyError:
+            try:
+                return self.__vcol_def_hash[column_name]
+            except KeyError:
+                raise eNoSuchColumn, "no column (%s) on table %s" % (column_name,self.__table_name)
+
+    def getColumnList(self):
+        return self.__column_list + self.__vcolumn_list
+
+    def databaseSizeForData_ColumnName_(self,data,col_name):
+	try:
+	    col_def = self.__col_def_hash[col_name]
+	except KeyError:
+	    try:
+		col_def = self.__vcol_def_hash[col_name]
+	    except KeyError:
+		raise eNoSuchColumn, "no column (%s) on table %s" % (col_name,self.__table_name)
+
+        c_name,c_type,c_options = col_def
+
+        if c_type == kBigString:
+            if c_options.get("compress_ok",0) and self.db.compression_enabled:
+                z_size = len(zlib.compress(data,9))
+                r_size = len(data)
+                if z_size < r_size:
+                    return z_size
+                else:
+                    return r_size
+            else:
+                return len(data)
+        else:
+            # really simplistic database size computation:
+            try:
+                a = data[0]
+                return len(data)
+            except:
+                return 4
+            
+
+    def columnType(self, col_name):
+	try:
+	    col_def = self.__col_def_hash[col_name]
+	except KeyError:
+	    try:
+		col_def = self.__vcol_def_hash[col_name]
+	    except KeyError:
+		raise eNoSuchColumn, "no column (%s) on table %s" % (col_name,self.__table_name)
+
+	c_name,c_type,c_options = col_def
+        return c_type
+
+    def convertDataForColumn(self,data,col_name):
+	try:
+	    col_def = self.__col_def_hash[col_name]
+	except KeyError:
+	    try:
+		col_def = self.__vcol_def_hash[col_name]
+	    except KeyError:
+		raise eNoSuchColumn, "no column (%s) on table %s" % (col_name,self.__table_name)
+
+	c_name,c_type,c_options = col_def
+
+        if c_type == kIncInteger:
+            raise eInvalidData, "invalid operation for column (%s:%s) on table (%s)" % (col_name,c_type,self.__table_name)
+
+	if c_type == kInteger:
+	    try:
+                if data is None: data = 0
+                else: return long(data)
+	    except (ValueError,TypeError):
+		raise eInvalidData, "invalid data (%s) for col (%s:%s) on table (%s)" % (repr(data),col_name,c_type,self.__table_name)
+	else:
+	    if type(data) == type(long(0)):
+		return "%d" % data
+	    else:
+		return str(data)
+
+    def getPrimaryKeyList(self):
+	return self.__primary_key_list
+    
+    def getTableName(self):
+	return self.__table_name
+    def hasValueColumn(self):
+	return self.__has_value_column
+
+    def hasColumn(self,name):
+	return self.__col_def_hash.has_key(name)
+    def hasVColumn(self,name):
+	return self.__vcol_def_hash.has_key(name)
+	
+
+    def _defineRows(self):
+	raise "can't instantiate base odb.Table type, make a subclass and override _defineRows()"
+
+    def __lockColumnsAndInit(self):
+	# add a 'odb_value column' before we lockdown the table def
+	if self.__has_value_column:
+	    self.d_addColumn("odb_value",kBigText,default='')
+
+	self.__columns_locked = 1
+	# walk column list and make lookup hashes, primary_key_list, etc..
+
+	primary_key_list = []
+	col_def_hash = {}
+	for a_col in self.__column_list:
+	    name,type,options = a_col
+	    col_def_hash[name] = a_col
+	    if options.has_key('primarykey'):
+		primary_key_list.append(name)
+
+	self.__col_def_hash = col_def_hash
+	self.__primary_key_list = primary_key_list
+
+	# setup the value columns!
+
+	if (not self.__has_value_column) and (len(self.__vcolumn_list) > 0):
+	    raise "can't define vcolumns on table without ValueColumn, call d_addValueColumn() in your _defineRows()"
+
+	vcol_def_hash = {}
+	for a_col in self.__vcolumn_list:
+	    name,type,size_data,options = a_col
+	    vcol_def_hash[name] = a_col
+
+	self.__vcol_def_hash = vcol_def_hash
+	
+	
+    def __checkColumnLock(self):
+	if self.__columns_locked:
+	    raise "can't change column definitions outside of subclass' _defineRows() method!"
+
+    # table definition methods, these are only available while inside the
+    # subclass's _defineRows method
+    #
+    # Ex:
+    #
+    # import odb
+    # class MyTable(odb.Table):
+    #   def _defineRows(self):
+    #     self.d_addColumn("id",kInteger,primarykey = 1,autoincrement = 1)
+    #     self.d_addColumn("name",kVarString,120)
+    #     self.d_addColumn("type",kInteger,
+    #                      enum_values = { 0 : "alive", 1 : "dead" }
+
+    def d_addColumn(self,col_name,ctype,size=None,primarykey = 0, notnull = 0,indexed=0,
+		    default=None,unique=0,autoincrement=0,safeupdate=0,enum_values = None,
+                    relations=None,compress_ok=0,int_date=0):
+
+	self.__checkColumnLock()
+
+	options = {}
+	options['default']       = default
+	if primarykey:
+	    options['primarykey']    = primarykey
+	if indexed:
+	    options['indexed']       = indexed
+	if unique:
+	    options['unique']        = unique
+	if safeupdate:
+	    options['safeupdate']    = safeupdate
+	if autoincrement:
+	    options['autoincrement'] = autoincrement
+	if notnull:
+	    options['notnull']       = notnull
+	if size:
+	    options['size']          = size
+        if int_date:
+            if ctype != kInteger:
+                raise eInvalidData, "can't flag columns int_date unless they are kInteger"
+            else:
+                options['int_date'] = int_date
+            
+	if enum_values:
+	    options['enum_values']   = enum_values
+	    inv_enum_values = {}
+	    for k,v in enum_values.items():
+		if inv_enum_values.has_key(v):
+		    raise eInvalidData, "enum_values paramater must be a 1 to 1 mapping for Table(%s)" % self.__table_name
+		else:
+		    inv_enum_values[v] = k
+	    options['inv_enum_values'] = inv_enum_values
+        if relations:
+            options['relations']      = relations
+            for a_relation in relations:
+                table, foreign_column_name = a_relation
+                if self.__relations_by_table.has_key(table):
+                    raise eInvalidData, "multiple relations for the same foreign table are not yet supported" 
+                self.__relations_by_table[table] = (col_name,foreign_column_name)
+        if compress_ok:
+            if ctype == kBigString:
+                options['compress_ok'] = 1
+            else:
+                raise eInvalidData, "only kBigString fields can be compress_ok=1"
+	
+	self.__column_list.append( (col_name,ctype,options) )
+	
+
+    def d_addValueColumn(self):
+	self.__checkColumnLock()
+	self.__has_value_column = 1
+
+    def d_addVColumn(self,col_name,type,size=None,default=None):
+	self.__checkColumnLock()
+
+	if (not self.__has_value_column):
+	    raise "can't define VColumns on table without ValueColumn, call d_addValueColumn() first"
+
+	options = {}
+	if default:
+	    options['default'] = default
+	if size:
+	    options['size']    = size
+
+	self.__vcolumn_list.append( (col_name,type,options) )
+
+    #####################
+    # _checkColMatchSpec(col_match_spec,should_match_unique_row = 0)
+    #
+    # raise an error if the col_match_spec contains invalid columns, or
+    # (in the case of should_match_unique_row) if it does not fully specify
+    # a unique row.
+    #
+    # NOTE: we don't currently support where clauses with value column fields!
+    #
+    
+    def _fixColMatchSpec(self,col_match_spec, should_match_unique_row = 0):
+	if type(col_match_spec) == type([]):
+	    if type(col_match_spec[0]) != type((0,)):
+		raise eInvalidMatchSpec, "invalid types in match spec, use [(,)..] or (,)"
+	elif type(col_match_spec) == type((0,)):
+	    col_match_spec = [ col_match_spec ]
+        elif type(col_match_spec) == type(None):
+            if should_match_unique_row:
+                raise eNonUniqueMatchSpec, "can't use a non-unique match spec (%s) here" % col_match_spec
+            else:
+                return None
+	else:
+	    raise eInvalidMatchSpec, "invalid types in match spec, use [(,)..] or (,)"
+
+	if should_match_unique_row:
+            unique_column_lists = []
+
+            # first the primary key list
+	    my_primary_key_list = []
+	    for a_key in self.__primary_key_list:
+		my_primary_key_list.append(a_key)
+
+            # then other unique keys
+            for a_col in self.__column_list:
+                col_name,a_type,options = a_col
+                if options.has_key('unique'):
+                    unique_column_lists.append( (col_name, [col_name]) )
+
+            unique_column_lists.append( ('primary_key', my_primary_key_list) )
+                
+	
+	new_col_match_spec = []
+	for a_col in col_match_spec:
+	    name,val = a_col
+	    # newname = string.lower(name)
+	    #  what is this doing?? - jeske
+	    newname = name
+	    if not self.__col_def_hash.has_key(newname):
+		raise eNoSuchColumn, "no such column in match spec: '%s'" % newname
+
+	    new_col_match_spec.append( (newname,val) )
+
+	    if should_match_unique_row:
+                for name,a_list in unique_column_lists:
+                    try:
+                        a_list.remove(newname)
+                    except ValueError:
+                        # it's okay if they specify too many columns!
+                        pass
+
+	if should_match_unique_row:
+            for name,a_list in unique_column_lists:
+                if len(a_list) == 0:
+                    # we matched at least one unique colum spec!
+                    # log("using unique column (%s) for query %s" % (name,col_match_spec))
+                    return new_col_match_spec
+            
+            raise eNonUniqueMatchSpec, "can't use a non-unique match spec (%s) here" % col_match_spec
+
+	return new_col_match_spec
+
+    def __buildWhereClause (self, col_match_spec,other_clauses = None):
+	sql_where_list = []
+
+        if not col_match_spec is None:
+            for m_col in col_match_spec:
+                m_col_name,m_col_val = m_col
+                c_name,c_type,c_options = self.__col_def_hash[m_col_name]
+                if c_type in (kIncInteger, kInteger):
+                    try:
+                        m_col_val_long = long(m_col_val)
+                    except ValueError:
+                        raise ValueError, "invalid literal for long(%s) in table %s" % (repr(m_col_val),self.__table_name)
+                        
+                    sql_where_list.append("%s = %d" % (c_name, m_col_val_long))
+                else:
+                    sql_where_list.append("%s = '%s'" % (c_name, self.db.escape(m_col_val)))
+
+        if other_clauses is None:
+            pass
+        elif type(other_clauses) == type(""):
+            sql_where_list = sql_where_list + [other_clauses]
+        elif type(other_clauses) == type([]):
+            sql_where_list = sql_where_list + other_clauses
+        else:
+            raise eInvalidData, "unknown type of extra where clause: %s" % repr(other_clauses)
+                    
+        return sql_where_list
+
+    def __fetchRows(self,col_match_spec,cursor = None, where = None, order_by = None, limit_to = None,
+                    skip_to = None, join = None):
+	if cursor is None:
+	    cursor = self.db.defaultCursor()
+
+        # build column list
+        sql_columns = []
+        for name,t,options in self.__column_list:
+            sql_columns.append(name)
+
+        # build join information
+
+        joined_cols = []
+        joined_cols_hash = {}
+        join_clauses = []
+        if not join is None:
+            for a_table,retrieve_foreign_cols in join:
+                try:
+                    my_col,foreign_col = self.__relations_by_table[a_table]
+                    for a_col in retrieve_foreign_cols:
+                        full_col_name = "%s.%s" % (my_col,a_col)
+                        joined_cols_hash[full_col_name] = 1
+                        joined_cols.append(full_col_name)
+                        sql_columns.append( full_col_name )
+
+                    join_clauses.append(" left join %s as %s on %s=%s " % (a_table,my_col,my_col,foreign_col))
+                        
+                except KeyError:
+                    eInvalidJoinSpec, "can't find table %s in defined relations for %s" % (a_table,self.__table_name)
+                    
+        # start buildling SQL
+    	sql = "select %s from %s" % (string.join(sql_columns,","),
+                                     self.__table_name)
+
+        # add join clause
+        if join_clauses:
+            sql = sql + string.join(join_clauses," ")
+	
+	# add where clause elements
+        sql_where_list = self.__buildWhereClause (col_match_spec,where)
+	if sql_where_list:
+	    sql = sql + " where %s" % (string.join(sql_where_list," and "))
+
+        # add order by clause
+        if order_by:
+            sql = sql + " order by %s " % string.join(order_by,",")
+
+        # add limit
+        if not limit_to is None:
+            if not skip_to is None:
+                sql = sql + " limit %s, %s" % (skip_to,limit_to)
+            else:
+                sql = sql + " limit %s" % limit_to
+        else:
+            if not skip_to is None:
+                raise eInvalidData, "can't specify skip_to without limit_to in MySQL"
+
+        dlog(DEV_SELECT,sql)
+	cursor.execute(sql)
+
+        # create defaultRowListClass instance...
+        return_rows = self.__defaultRowListClass()
+	    
+	# should do fetchmany!
+	all_rows = cursor.fetchall()
+	for a_row in all_rows:
+	    data_dict = {}
+
+	    col_num = 0
+            
+            #	    for a_col in cursor.description:
+            #		(name,type_code,display_size,internal_size,precision,scale,null_ok) = a_col
+            for name in sql_columns:
+		if self.__col_def_hash.has_key(name) or joined_cols_hash.has_key(name):
+		    # only include declared columns!
+                    if self.__col_def_hash.has_key(name):
+                        c_name,c_type,c_options = self.__col_def_hash[name]
+                        if c_type == kBigString and c_options.get("compress_ok",0) and a_row[col_num]:
+                            try:
+                                a_col_data = zlib.decompress(a_row[col_num])
+                            except zlib.error:
+                                a_col_data = a_row[col_num]
+
+                            data_dict[name] = a_col_data
+                        else:
+                            data_dict[name] = a_row[col_num]
+
+                    else:
+                        data_dict[name] = a_row[col_num]
+                        
+		    col_num = col_num + 1
+
+	    newrowobj = self.__defaultRowClass(self,data_dict,joined_cols = joined_cols)
+
+	    return_rows.append(newrowobj)
+	    
+	return return_rows
+
+    def __deleteRow(self,a_row,cursor = None):
+	if cursor is None:
+	    cursor = self.db.defaultCursor()
+
+        # build the where clause!
+        match_spec = a_row.getPKMatchSpec()
+        sql_where_list = self.__buildWhereClause (match_spec)
+
+        sql = "delete from %s where %s" % (self.__table_name,
+                                           string.join(sql_where_list," and "))
+        dlog(DEV_UPDATE,sql)
+        cursor.execute(sql)
+       
+
+    def __updateRowList(self,a_row_list,cursor = None):
+	if cursor is None:
+	    cursor = self.db.defaultCursor()
+
+	for a_row in a_row_list:
+	    update_list = a_row.changedList()
+
+	    # build the set list!
+	    sql_set_list = []
+	    for a_change in update_list:
+		col_name,col_val,col_inc_val = a_change
+		c_name,c_type,c_options = self.__col_def_hash[col_name]
+
+                if c_type != kIncInteger and col_val is None:
+                    sql_set_list.append("%s = NULL" % c_name)
+                elif c_type == kIncInteger and col_inc_val is None:
+                    sql_set_list.append("%s = 0" % c_name)
+                else:
+                    if c_type == kInteger:
+                        sql_set_list.append("%s = %d" % (c_name, long(col_val)))
+                    elif c_type == kIncInteger:
+                        sql_set_list.append("%s = %s + %d" % (c_name,c_name,long(col_inc_val)))
+                    elif c_type == kBigString and c_options.get("compress_ok",0) and self.db.compression_enabled:
+                        compressed_data = zlib.compress(col_val,9)
+                        if len(compressed_data) < len(col_val):
+                            sql_set_list.append("%s = '%s'" % (c_name, self.db.escape(compressed_data)))
+                        else:
+                            sql_set_list.append("%s = '%s'" % (c_name, self.db.escape(col_val)))
+                    else:
+                        sql_set_list.append("%s = '%s'" % (c_name, self.db.escape(col_val)))
+
+	    # build the where clause!
+	    match_spec = a_row.getPKMatchSpec()
+            sql_where_list = self.__buildWhereClause (match_spec)
+
+	    if sql_set_list:
+		sql = "update %s set %s where %s" % (self.__table_name,
+						 string.join(sql_set_list,","),
+						 string.join(sql_where_list," and "))
+
+                dlog(DEV_UPDATE,sql)
+                try:
+                    cursor.execute(sql)
+                except Exception, reason:
+                    if string.find(str(reason), "Duplicate entry") != -1:
+                        raise eDuplicateKey, reason
+                    raise Exception, reason
+		a_row.markClean()
+
+    def __insertRow(self,a_row_obj,cursor = None):
+	if cursor is None:
+	    cursor = self.db.defaultCursor()
+
+	sql_col_list = []
+	sql_data_list = []
+	auto_increment_column_name = None
+
+	for a_col in self.__column_list:
+	    name,type,options = a_col
+
+	    try:
+		data = a_row_obj[name]
+
+		sql_col_list.append(name)
+                if data is None:
+                    sql_data_list.append("NULL")
+                else:
+                    if type == kInteger or type == kIncInteger:
+                        sql_data_list.append("%d" % data)
+                    elif type == kBigString and options.get("compress_ok",0) and self.db.compression_enabled:
+                        compressed_data = zlib.compress(data,9)
+                        if len(compressed_data) < len(data):
+                            sql_data_list.append("'%s'" % self.db.escape(compressed_data))
+                        else:
+                            sql_data_list.append("'%s'" % self.db.escape(data))
+                    else:
+                        sql_data_list.append("'%s'" % self.db.escape(data))
+
+	    except KeyError:
+		if options.has_key("autoincrement"):
+		    if auto_increment_column_name:
+			raise eInternalError, "two autoincrement columns (%s,%s) in table (%s)" % (auto_increment_column_name, name,self.__table_name)
+		    else:
+			auto_increment_column_name = name
+		
+	
+	sql = "insert into %s (%s) values (%s)" % (self.__table_name,
+						   string.join(sql_col_list,","),
+						   string.join(sql_data_list,","))
+
+        dlog(DEV_UPDATE,sql)
+        try:
+          cursor.execute(sql)
+        except Exception, reason:
+          if string.find(str(reason), "Duplicate entry") != -1:
+            raise eDuplicateKey, reason
+          raise Exception, reason
+            
+	if auto_increment_column_name:
+	    a_row_obj[auto_increment_column_name] = cursor.insert_id()
+
+    # ----------------------------------------------------
+    #   Helper methods for Rows...
+    # ----------------------------------------------------
+
+
+	
+    #####################
+    # r_deleteRow(a_row_obj,cursor = None)
+    #
+    # normally this is called from within the Row "delete()" method
+    # but you can call it yourself if you want
+    #
+
+    def r_deleteRow(self,a_row_obj, cursor = None):
+	curs = cursor
+	self.__deleteRow(a_row_obj, cursor = curs)
+
+
+    #####################
+    # r_updateRow(a_row_obj,cursor = None)
+    #
+    # normally this is called from within the Row "save()" method
+    # but you can call it yourself if you want
+    #
+
+    def r_updateRow(self,a_row_obj, cursor = None):
+	curs = cursor
+	self.__updateRowList([a_row_obj], cursor = curs)
+
+    #####################
+    # InsertRow(a_row_obj,cursor = None)
+    #
+    # normally this is called from within the Row "save()" method
+    # but you can call it yourself if you want
+    #
+
+    def r_insertRow(self,a_row_obj, cursor = None):
+	curs = cursor
+	self.__insertRow(a_row_obj, cursor = curs)
+
+
+    # ----------------------------------------------------
+    #   Public Methods
+    # ----------------------------------------------------
+
+
+	
+    #####################
+    # deleteRow(col_match_spec)
+    #
+    # The col_match_spec paramaters must include all primary key columns.
+    #
+    # Ex:
+    #    a_row = tbl.fetchRow( ("order_id", 1) )
+    #    a_row = tbl.fetchRow( [ ("order_id", 1), ("enterTime", now) ] )
+
+
+    def deleteRow(self,col_match_spec, where=None):
+        n_match_spec = self._fixColMatchSpec(col_match_spec)
+        cursor = self.db.defaultCursor()
+
+        # build sql where clause elements
+        sql_where_list = self.__buildWhereClause (n_match_spec,where)
+        if not sql_where_list:
+            return
+
+        sql = "delete from %s where %s" % (self.__table_name, string.join(sql_where_list," and "))
+
+        dlog(DEV_UPDATE,sql)
+        cursor.execute(sql)
+	
+    #####################
+    # fetchRow(col_match_spec)
+    #
+    # The col_match_spec paramaters must include all primary key columns.
+    #
+    # Ex:
+    #    a_row = tbl.fetchRow( ("order_id", 1) )
+    #    a_row = tbl.fetchRow( [ ("order_id", 1), ("enterTime", now) ] )
+
+
+    def fetchRow(self, col_match_spec, cursor = None):
+	n_match_spec = self._fixColMatchSpec(col_match_spec, should_match_unique_row = 1)
+
+	rows = self.__fetchRows(n_match_spec, cursor = cursor)
+	if len(rows) == 0:
+	    raise eNoMatchingRows, "no row matches %s" % repr(n_match_spec)
+
+	if len(rows) > 1:
+	    raise eInternalError, "unique where clause shouldn't return > 1 row"
+
+	return rows[0]
+	    
+
+    #####################
+    # fetchRows(col_match_spec)
+    #
+    # Ex:
+    #    a_row_list = tbl.fetchRows( ("order_id", 1) )
+    #    a_row_list = tbl.fetchRows( [ ("order_id", 1), ("enterTime", now) ] )
+
+
+    def fetchRows(self, col_match_spec = None, cursor = None, where = None, order_by = None, limit_to = None, skip_to = None, join = None):
+	n_match_spec = self._fixColMatchSpec(col_match_spec)
+
+	return self.__fetchRows(n_match_spec,
+                                cursor = cursor,
+                                where = where,
+                                order_by = order_by,
+                                limit_to = limit_to,
+                                skip_to = skip_to,
+                                join = join)
+
+    def fetchRowCount (self, col_match_spec = None, cursor = None, where = None):
+	n_match_spec = self._fixColMatchSpec(col_match_spec)
+
+        sql_where_list = self.__buildWhereClause (n_match_spec,where)
+
+    	sql = "select count(*) from %s" % self.__table_name
+	if sql_where_list:
+	    sql = "%s where %s" % (sql,string.join(sql_where_list," and "))
+
+        if cursor is None:
+          cursor = self.db.defaultCursor()
+        dlog(DEV_SELECT,sql)
+	cursor.execute(sql)
+        try:
+	    count, = cursor.fetchone()
+        except TypeError:
+            count = 0
+        return count
+
+
+    #####################
+    # fetchAllRows()
+    #
+    # Ex:
+    #    a_row_list = tbl.fetchRows( ("order_id", 1) )
+    #    a_row_list = tbl.fetchRows( [ ("order_id", 1), ("enterTime", now) ] )
+
+    def fetchAllRows(self):
+        try:
+            return self.__fetchRows([])
+        except eNoMatchingRows:
+            # else return empty list...
+            return self.__defaultRowListClass()
+
+    def newRow(self):
+	row = self.__defaultRowClass(self,None,create=1)
+        for (cname, ctype, opts) in self.__column_list:
+            if opts['default'] is not None and ctype is not kIncInteger:
+                row[cname] = opts['default']
+        return row
+
+class Row:
+    __instance_data_locked  = 0
+    def subclassinit(self):
+        pass
+    def __init__(self,_table,data_dict,create=0,joined_cols = None):
+
+        self._inside_getattr = 0  # stop recursive __getattr__
+	self._table = _table
+	self._should_insert = create
+        self._rowInactive = None
+        self._joinedRows = []
+	
+	self.__pk_match_spec = None
+	self.__vcoldata = {}
+        self.__inc_coldata = {}
+
+        self.__joined_cols_dict = {}
+        for a_col in joined_cols or []:
+            self.__joined_cols_dict[a_col] = 1
+	
+	if create:
+	    self.__coldata = {}
+	else:
+	    if type(data_dict) != type({}):
+		raise eInternalError, "rowdict instantiate with bad data_dict"
+	    self.__coldata = data_dict
+	    self.__unpackVColumn()
+
+	self.markClean()
+
+        self.subclassinit()
+	self.__instance_data_locked = 1
+
+    def joinRowData(self,another_row):
+        self._joinedRows.append(another_row)
+
+    def getPKMatchSpec(self):
+	return self.__pk_match_spec
+
+    def markClean(self):
+	self.__vcolchanged = 0
+	self.__colchanged_dict = {}
+
+        for key in self.__inc_coldata.keys():
+	    self.__coldata[key] = self.__coldata.get(key, 0) + self.__inc_coldata[key]
+
+        self.__inc_coldata = {}
+
+	if not self._should_insert:
+	    # rebuild primary column match spec
+	    new_match_spec = []
+	    for col_name in self._table.getPrimaryKeyList():
+		try:
+		    rdata = self[col_name]
+		except KeyError:
+		    raise eInternalError, "must have primary key data filled in to save %s:Row(col:%s)" % (self._table.getTableName(),col_name)
+		    
+		new_match_spec.append( (col_name, rdata) )
+	    self.__pk_match_spec = new_match_spec
+
+    def __unpackVColumn(self):
+	if self._table.hasValueColumn():
+	    pass
+	
+    def __packVColumn(self):
+	if self._table.hasValueColumn():
+	    pass
+
+    ## ----- utility stuff ----------------------------------
+
+    def __del__(self):
+	# check for unsaved changes
+	changed_list = self.changedList()
+	if len(changed_list):
+            info = "unsaved Row for table (%s) lost, call discard() to avoid this error. Lost changes: %s\n" % (self._table.getTableName(), repr(changed_list)[:256])
+            if 0:
+                raise eUnsavedObjectLost, info
+            else:
+                sys.stderr.write(info)
+                
+
+    def __repr__(self):
+	return "Row from (%s): %s" % (self._table.getTableName(),repr(self.__coldata) + repr(self.__vcoldata))
+
+    ## ---- class emulation --------------------------------
+
+    def __getattr__(self,key):
+        if self._inside_getattr:
+          raise AttributeError, "recursively called __getattr__ (%s,%s)" % (key,self._table.getTableName())
+        try:
+            self._inside_getattr = 1
+            try:
+                return self[key]
+            except KeyError:
+                if self._table.hasColumn(key) or self._table.hasVColumn(key):
+                    return None
+                else:
+                    raise AttributeError, "unknown field '%s' in Row(%s)" % (key,self._table.getTableName())
+        finally:
+            self._inside_getattr = 0
+
+    def __setattr__(self,key,val):
+	if not self.__instance_data_locked:
+	    self.__dict__[key] = val
+	else:
+	    my_dict = self.__dict__
+	    if my_dict.has_key(key):
+		my_dict[key] = val
+	    else:
+		# try and put it into the rowdata
+		try:
+		    self[key] = val
+		except KeyError, reason:
+		    raise AttributeError, reason
+
+
+    ## ---- dict emulation ---------------------------------
+    
+    def __getitem__(self,key):
+        self.checkRowActive()
+
+        try:
+            c_type = self._table.columnType(key)
+        except eNoSuchColumn:
+            # Ugh, this sucks, we can't determine the type for a joined
+            # row, so we just default to kVarString and let the code below
+            # determine if this is a joined column or not
+            c_type = kVarString
+
+        if c_type == kIncInteger:
+            c_data = self.__coldata.get(key, 0) 
+            if c_data is None: c_data = 0
+            i_data = self.__inc_coldata.get(key, 0)
+            if i_data is None: i_data = 0
+            return c_data + i_data
+        
+	try:
+	    return self.__coldata[key]
+	except KeyError:
+            try:
+                return self.__vcoldata[key]
+            except KeyError:
+                for a_joined_row in self._joinedRows:
+                    try:
+                        return a_joined_row[key]
+                    except KeyError:
+                        pass
+
+                raise KeyError, "unknown column %s in %s" % (key,self)
+
+    def __setitem__(self,key,data):
+        self.checkRowActive()
+        
+	try:
+	    newdata = self._table.convertDataForColumn(data,key)
+	except eNoSuchColumn, reason:
+	    raise KeyError, reason
+
+	if self._table.hasColumn(key):
+	    self.__coldata[key] = newdata
+	    self.__colchanged_dict[key] = 1
+	elif self._table.hasVColumn(key):
+	    self.__vcoldata[key] = newdata
+	    self.__vcolchanged = 1
+	else:
+            for a_joined_row in self._joinedRows:
+                try:
+                    a_joined_row[key] = data
+                    return
+                except KeyError:
+                    pass
+	    raise KeyError, "unknown column name %s" % key
+
+    def __delitem__(self,key,data):
+        self.checkRowActive()
+        
+	if self.table.hasVColumn(key):
+	    del self.__vcoldata[key]
+	else:
+            for a_joined_row in self._joinedRows:
+                try:
+                    del a_joined_row[key]
+                    return
+                except KeyError:
+                    pass
+	    raise KeyError, "unknown column name %s" % key
+
+
+    def copyFrom(self,source):
+        for name,t,options in self._table.getColumnList():
+            if not options.has_key("autoincrement"):
+                self[name] = source[name]
+
+
+    # make sure that .keys(), and .items() come out in a nice order!
+
+    def keys(self):
+        self.checkRowActive()
+        
+        key_list = []
+        for name,t,options in self._table.getColumnList():
+            key_list.append(name)
+        for name in self.__joined_cols_dict.keys():
+            key_list.append(name)
+
+        for a_joined_row in self._joinedRows:
+            key_list = key_list + a_joined_row.keys()
+            
+        return key_list
+
+
+    def items(self):
+        self.checkRowActive()
+        
+        item_list = []
+        for name,t,options in self._table.getColumnList():
+            item_list.append( (name,self[name]) )
+        for name in self.__joined_cols_dict.keys():
+            item_list.append( (name,self[name]) )
+
+        for a_joined_row in self._joinedRows:
+            item_list = item_list + a_joined_row.items()
+
+
+        return item_list
+
+    def values(elf):
+        self.checkRowActive()
+
+        value_list = self.__coldata.values() + self.__vcoldata.values()
+
+        for a_joined_row in self._joinedRows:
+            value_list = value_list + a_joined_row.values()
+
+        return value_list
+        
+
+    def __len__(self):
+        self.checkRowActive()
+        
+	my_len = len(self.__coldata) + len(self.__vcoldata)
+
+        for a_joined_row in self._joinedRows:
+            my_len = my_len + len(a_joined_row)
+
+        return my_len
+
+    def has_key(self,key):
+        self.checkRowActive()
+        
+	if self.__coldata.has_key(key) or self.__vcoldata.has_key(key):
+	    return 1
+	else:
+
+            for a_joined_row in self._joinedRows:
+                if a_joined_row.has_key(key):
+                    return 1
+	    return 0
+	
+    def get(self,key,default = None):
+        self.checkRowActive()
+
+        
+        
+	if self.__coldata.has_key(key):
+	    return self.__coldata[key]
+	elif self.__vcoldata.has_key(key):
+	    return self.__vcoldata[key]
+	else:
+            for a_joined_row in self._joinedRows:
+                try:
+                    return a_joined_row.get(key,default)
+                except eNoSuchColumn:
+                    pass
+
+            if self._table.hasColumn(key):
+                return default
+            
+	    raise eNoSuchColumn, "no such column %s" % key
+
+    def inc(self,key,count=1):
+        self.checkRowActive()
+
+        if self._table.hasColumn(key):
+            try:
+                self.__inc_coldata[key] = self.__inc_coldata[key] + count
+            except KeyError:
+                self.__inc_coldata[key] = count
+
+            self.__colchanged_dict[key] = 1
+        else:
+            raise AttributeError, "unknown field '%s' in Row(%s)" % (key,self._table.getTableName())
+    
+
+    ## ----------------------------------
+    ## real interface
+
+
+    def fillDefaults(self):
+	for field_def in self._table.fieldList():
+	    name,type,size,options = field_def
+	    if options.has_key("default"):
+		self[name] = options["default"]
+
+    ###############
+    # changedList()
+    #
+    # returns a list of tuples for the columns which have changed
+    #
+    #   changedList() -> [ ('name', 'fred'), ('age', 20) ]
+
+    def changedList(self):
+	if self.__vcolchanged:
+	    self.__packVColumn()
+
+	changed_list = []
+	for a_col in self.__colchanged_dict.keys():
+	    changed_list.append( (a_col,self.get(a_col,None),self.__inc_coldata.get(a_col,None)) )
+
+	return changed_list
+
+    def discard(self):
+	self.__coldata = None
+	self.__vcoldata = None
+	self.__colchanged_dict = {}
+	self.__vcolchanged = 0
+
+    def delete(self,cursor = None):
+        self.checkRowActive()
+
+        
+        fromTable = self._table
+        curs = cursor
+        fromTable.r_deleteRow(self,cursor=curs)
+        self._rowInactive = "deleted"
+
+    def save(self,cursor = None):
+	toTable = self._table
+
+        self.checkRowActive()
+
+	if self._should_insert:
+	    toTable.r_insertRow(self)
+	    self._should_insert = 0
+	    self.markClean()  # rebuild the primary key list
+	else:
+            curs = cursor
+	    toTable.r_updateRow(self,cursor = curs)
+
+	# the table will mark us clean!
+	# self.markClean()
+
+    def checkRowActive(self):
+        if self._rowInactive:
+            raise eInvalidData, "row is inactive: %s" % self._rowInactive
+
+    def databaseSizeForColumn(self,key):
+        return self._table.databaseSizeForData_ColumnName_(self[key],key)
+
+## -----------------------------------------------------------------------
+##                            T  E  S  T S
+## -----------------------------------------------------------------------
+
+	
+def TEST(output=log):
+    LOGGING_STATUS[DEV_SELECT] = 1
+    LOGGING_STATUS[DEV_UPDATE] = 1
+    class AgentsTable(Table):
+	def _defineRows(self):
+	    self.d_addColumn("agent_id",kInteger,None,primarykey = 1,autoincrement = 1)
+	    self.d_addColumn("login",kVarString,200,notnull=1)
+	    self.d_addColumn("ext_email",kVarString,200,notnull=1)
+	    self.d_addColumn("hashed_pw",kVarString,20,notnull=1)
+	    self.d_addColumn("name",kBigString,compress_ok=1)
+	    self.d_addColumn("auth_level",kInteger,None)
+            self.d_addColumn("ticket_count",kIncInteger,None)
+
+
+    import MySQLdb
+    rdb = MySQLdb.connect(host = 'localhost',user='root', passwd = '', db='testdb')
+    ndb = MySQLdb.connect(host = 'localhost',user='trakken', passwd = 'trakpas', db='testdb')
+    db = Database(ndb)
+
+    tbl = AgentsTable(db,"agents")
+
+    cursor = rdb.cursor()
+
+    # ---------------------------------------------------------------
+    # initialize
+    output("drop table agents")
+    try:
+        cursor.execute("drop table agents")   # clean out the table
+    except MySQLdb.OperationalError:
+        pass
+    output("creating table")
+    cursor.execute("create table agents (agent_id integer not null primary key auto_increment, login varchar(200) not null, unique (login), ext_email varchar(200) not null, hashed_pw varchar(20) not null, name varchar(200), auth_level integer default 0, ticket_count integer default 0)")
+
+    TEST_INSERT_COUNT = 5
+
+    # ---------------------------------------------------------------
+    # make sure we can catch a missing row
+
+    try:
+	a_row = tbl.fetchRow( ("agent_id", 1000) )
+	raise "test error"
+    except eNoMatchingRows:
+	pass
+
+    output("PASSED! fetch missing row test")
+
+    # --------------------------------------------------------------
+    # create new rows and insert them
+
+    for n in range(TEST_INSERT_COUNT):
+	new_id = n + 1
+	
+	newrow = tbl.newRow()
+	newrow.name = "name #%d" % new_id
+	newrow.login = "name%d" % new_id
+        newrow.ext_email = "%d@name" % new_id
+	newrow.save()
+	if newrow.agent_id != new_id:
+	    raise "new insert id (%s) does not match expected value (%d)" % (newrow.agent_id,new_id)
+
+    output("PASSED! autoinsert test")
+
+    # --------------------------------------------------------------
+    # fetch one row
+    a_row = tbl.fetchRow( ("agent_id", 1) )
+
+    if a_row.name != "name #1":
+	raise "row data incorrect"
+
+    output("PASSED! fetch one row test")
+
+    # ---------------------------------------------------------------
+    # don't change and save it
+    # (i.e. the "dummy cursor" string should never be called!)
+    #
+    try:
+	a_row.save(cursor = "dummy cursor")
+    except AttributeError, reason:
+	raise "row tried to access cursor on save() when no changes were made!"
+
+    output("PASSED! don't save when there are no changed")
+
+    # ---------------------------------------------------------------
+    # change, save, load, test
+    
+    a_row.auth_level = 10
+    a_row.save()
+    b_row = tbl.fetchRow( ("agent_id", 1) )
+    if b_row.auth_level != 10:
+	raise "save and load failed"
+    
+
+    output("PASSED! change, save, load")
+
+    # --------------------------------------------------------------
+    # access unknown attribute
+    try:
+	a = a_row.UNKNOWN_ATTRIBUTE
+	raise "test error"
+    except AttributeError, reason:
+	pass
+
+    try:
+	a_row.UNKNOWN_ATTRIBUTE = 1
+	raise "test error"
+    except AttributeError, reason:
+	pass
+
+    output("PASSED! unknown attribute exception")
+
+    # --------------------------------------------------------------
+    # access unknown dict item
+
+    try:
+	a = a_row["UNKNOWN_ATTRIBUTE"]
+	raise "test error"
+    except KeyError, reason:
+	pass
+
+    try:
+	a_row["UNKNOWN_ATTRIBUTE"] = 1
+	raise "test error"
+    except KeyError, reason:
+	pass
+
+    output("PASSED! unknown dict item exception")
+
+    # --------------------------------------------------------------
+    # use wrong data for column type
+
+    try:
+	a_row.agent_id = "this is a string"
+	raise "test error"
+    except eInvalidData, reason:
+	pass
+
+    output("PASSED! invalid data for column type")
+
+    # --------------------------------------------------------------
+    # fetch 1 rows
+
+    rows = tbl.fetchRows( ('agent_id', 1) )
+    if len(rows) != 1:
+	raise "fetchRows() did not return 1 row!" % (TEST_INSERT_COUNT)
+
+    output("PASSED! fetch one row")
+
+
+    # --------------------------------------------------------------
+    # fetch All rows
+    
+    rows = tbl.fetchAllRows()
+    if len(rows) != TEST_INSERT_COUNT:
+        for a_row in rows:
+            output(repr(a_row))
+	raise "fetchAllRows() did not return TEST_INSERT_COUNT(%d) rows!" % (TEST_INSERT_COUNT)
+
+    output("PASSED! fetchall rows")
+
+  
+    # --------------------------------------------------------------
+    # delete row object
+
+    row = tbl.fetchRow( ('agent_id', 1) )
+    row.delete()
+    try:
+        row = tbl.fetchRow( ('agent_id', 1) )
+        raise "delete failed to delete row!"
+    except eNoMatchingRows:
+        pass
+
+    # --------------------------------------------------------------
+    # table deleteRow() call
+
+    row = tbl.fetchRow( ('agent_id',2) )
+    tbl.deleteRow( ('agent_id', 2) )
+    try:
+        row = tbl.fetchRow( ('agent_id',2) )
+        raise "table delete failed"
+    except eNoMatchingRows:
+        pass
+
+    # --------------------------------------------------------------
+    # table deleteRow() call
+
+    row = tbl.fetchRow( ('agent_id',3) )
+    if row.databaseSizeForColumn('name') != len(row.name):
+        raise "databaseSizeForColumn('name') failed"
+    
+    # --------------------------------------------------------------
+    # test inc fields
+    row = tbl.newRow()
+    new_id = 1092
+    row.name = "name #%d" % new_id
+    row.login = "name%d" % new_id
+    row.ext_email = "%d@name" % new_id
+    row.inc('ticket_count')
+    row.save()
+    new_id = row.agent_id
+
+    trow = tbl.fetchRow( ('agent_id',new_id) )
+    if trow.ticket_count != 1:
+        raise "ticket_count didn't inc!"
+
+    row.inc('ticket_count', count=2)
+    row.save()
+    trow = tbl.fetchRow( ('agent_id',new_id) )
+    if trow.ticket_count != 3:
+        raise "ticket_count wrong, expected 3, got %d" % trow.ticket_count
+
+    trow.inc('ticket_count')
+    trow.save()
+    if trow.ticket_count != 4:
+        raise "ticket_count wrong, expected 4, got %d" % trow.ticket_count
+
+    output("\n==== ALL TESTS PASSED ====")
+    
+
+if __name__ == "__main__":
+    TEST()
+
diff -Nru clearsilver-0.9.1/python/examples/base/profiler.py clearsilver-0.9.2/python/examples/base/profiler.py
--- clearsilver-0.9.1/python/examples/base/profiler.py	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.2/python/examples/base/profiler.py	Wed Aug 13 23:32:41 2003
@@ -0,0 +1,73 @@
+
+import time
+import who_calls
+import neo_cgi
+
+PROFILER_DATA = []
+PROFILER_START = 0
+PROFILER_ENABLED = 0
+PROFILER_DEPTH = 0
+
+def enable():
+    global PROFILER_START
+    global PROFILER_ENABLED
+    global PROFILER_DATA
+    PROFILER_START = time.time()
+    PROFILER_ENABLED = 1
+    PROFILER_DATA = []
+
+def disable():
+    global PROFILER_START
+    global PROFILER_ENABLED
+    global PROFILER_DATA
+    PROFILER_START = 0
+    PROFILER_ENABLED = 0
+    PROFILER_DATA = []
+
+def hdfExport(prefix, hdf):
+    global PROFILER_DATA
+    n = 0
+    for p in PROFILER_DATA:
+        hdf.setValue("%s.%d.when" % (prefix, n), "%5.2f" % (p.when))
+        hdf.setValue("%s.%d.time" % (prefix, n), "%5.2f" % (p.length))
+        hdf.setValue("%s.%d.klass" % (prefix, n), p.klass)
+        hdf.setValue("%s.%d.what" % (prefix, n), "&nbsp;" * p.depth + p.what)
+        hdf.setValue("%s.%d.where" % (prefix, n), neo_cgi.htmlEscape(p.where))
+
+class Profiler:
+    def __init__ (self, klass, what):
+        global PROFILER_START
+        global PROFILER_ENABLED
+        global PROFILER_DATA
+        global PROFILER_DEPTH
+        if not PROFILER_ENABLED: return
+        self.when = time.time() - PROFILER_START
+        self.klass = klass
+        self.where = who_calls.pretty_who_calls()
+        self.what = what
+        self.length = 0
+        self.depth = PROFILER_DEPTH
+        PROFILER_DEPTH = PROFILER_DEPTH + 1
+
+        PROFILER_DATA.append(self)
+
+    def end(self):
+        global PROFILER_ENABLED
+        global PROFILER_DEPTH
+        if not PROFILER_ENABLED: return
+        self.length = time.time() - self.when - PROFILER_START
+        PROFILER_DEPTH = PROFILER_DEPTH - 1
+        if PROFILER_DEPTH < 0: PROFILER_DEPTH = 0
+
+class ProfilerCursor:
+    def __init__ (self, real_cursor):
+        self.real_cursor = real_cursor
+
+    def execute (self, query, args=None):
+        p = Profiler("SQL", query)
+        r = self.real_cursor.execute(query, args)
+        p.end()
+        return r
+
+    def __getattr__ (self, key):
+        return getattr(self.real_cursor, key)
diff -Nru clearsilver-0.9.1/python/examples/base/who_calls.py clearsilver-0.9.2/python/examples/base/who_calls.py
--- clearsilver-0.9.1/python/examples/base/who_calls.py	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.2/python/examples/base/who_calls.py	Sun Jun 15 18:54:49 2003
@@ -0,0 +1,141 @@
+
+# who_calls.py
+# by Sam Rushing for Medusa
+
+import string
+import sys
+
+from log import *
+
+whoCallsError = "whoCallsError"
+
+#--------------------------------------------------------------
+# Example use:
+#
+# import who_calls
+# log(who_calls.pretty_who_calls())
+#
+#--------------------------------------------------------------
+
+def test():
+  for i in range(1,1000):
+    pretty_who_calls()
+
+  print_top_100()
+
+def who_calls_helper():
+  tinfo = []
+  exc_info = sys.exc_info()
+
+  f = exc_info[2].tb_frame.f_back
+  while f:
+	  tinfo.append ( (
+		  f.f_code.co_filename,
+		  f.f_code.co_name,
+		  f.f_lineno )
+		  )
+	  f = f.f_back
+
+  del exc_info
+  return tinfo
+  
+
+def who_calls():
+  try:
+    raise whoCallsError
+  except whoCallsError:
+    tinfo = who_calls_helper()
+  return tinfo
+
+def pretty_who_calls(strip=0):
+	info = who_calls()
+	buf = []
+
+	for file,function,line in info[1 + strip:]:
+		buf.append("   %s(%s): %s()" % (file,line,function))
+		
+	return string.join(buf,"\n")
+
+# ---------------------------------------------------------------------------
+# used for debugging.
+# ---------------------------------------------------------------------------
+
+def compact_traceback ():
+	t,v,tb = sys.exc_info()
+	tbinfo = []
+	if tb is None:
+		# this should never happen, but then again, lots of things
+		# should never happen but do.
+		return (('','',''), str(t), str(v), 'traceback is None!!!')
+	while 1:
+		tbinfo.append (
+			tb.tb_frame.f_code.co_filename,
+			tb.tb_frame.f_code.co_name,				
+			str(tb.tb_lineno)
+			)
+		tb = tb.tb_next
+		if not tb:
+			break
+
+	# just to be safe
+	del tb
+
+	file, function, line = tbinfo[-1]
+	info = '[' + string.join (
+		map (
+			lambda x: string.join (x, '|'),
+			tbinfo
+			),
+		'] ['
+		) + ']'
+
+	return (file, function, line), str(t), str(v), info
+
+## ----------------------------------------------------
+## Refcount printing
+		
+import sys
+import types
+
+def real_get_refcounts(base = None, set_base = 0):
+    d = {}
+    sys.modules
+    # collect all classes
+    for modname,m in sys.modules.items():
+        for sym in dir(m):
+            o = getattr (m, sym)
+            if type(o) is types.ClassType:
+                name = "%s:%s" % (modname,o.__name__)
+                cnt = sys.getrefcount (o)
+                if base:
+                    if set_base:
+                        base[name] = cnt
+                    elif cnt > base.get(name, 0):
+                        d[name] = cnt - base.get(name, 0)
+                else:
+                    d[name] = cnt
+    return d
+
+def get_refcounts(base=None):
+        d = real_get_refcounts(base = base)
+        # sort by refcount
+        pairs = map (lambda x: (x[1],x[0]), d.items())
+        pairs.sort()
+        pairs.reverse()
+        return pairs
+
+REFCOUNTS = {}
+
+def set_refcount_base():
+    global REFCOUNTS
+    real_get_refcounts(REFCOUNTS, set_base = 1)
+
+def print_top_100():
+        print_top_N(100)
+
+def print_top_N(N):
+    global REFCOUNTS
+    for n, c in get_refcounts(REFCOUNTS)[:N]:
+       log('%10d %s' % (n, c))
+
+
diff -Nru clearsilver-0.9.1/python/examples/handle_error.py clearsilver-0.9.2/python/examples/handle_error.py
--- clearsilver-0.9.1/python/examples/handle_error.py	Sun Jun 15 18:54:49 2003
+++ clearsilver-0.9.2/python/examples/handle_error.py	Wed Dec 31 16:00:00 1969
@@ -1,91 +0,0 @@
-
-import traceback, sys, string, time, socket, os
-import who_calls
-
-DUMP_DIR = "/neo/data/bugs"
-
-Warning = "handle_error.Warning"
-
-# levels
-LV_MESSAGE = "LV_MESSAGE"
-LV_WARNING = "LV_WARNING"
-LV_ERROR   = "LV_ERROR"
-
-Count = 0
-
-gErrorCount = 0
-DISABLE_DUMP = 0
-
-def exceptionReason():
-  return "%s.%s" % (str(sys.exc_type), str(sys.exc_value))
-
-def exceptionString():
-  tb_list = traceback.format_exception(sys.exc_type,sys.exc_value,sys.exc_traceback)
-  return string.join(tb_list,"")
-
-  #### old way
-  import StringIO
-  ## get the traceback message  
-  sfp = StringIO.StringIO()
-  traceback.print_exc(file=sfp)
-  exception = sfp.getvalue()
-  sfp.close()
-
-  return exception
-
-
-def handleException (msg=None, lvl=LV_ERROR, dump = 1):
-  global gErrorCount
-  gErrorCount = gErrorCount + 1
-
-  tb_list = traceback.format_exception(sys.exc_type,sys.exc_value,sys.exc_traceback)
-  if msg:
-    sys.stderr.write ("%s\n" % msg)
-  else:
-    msg = "Unhandled Exception"
-        
-  sys.stderr.write (string.join(tb_list,""))
-  try:
-    if dump: dump_bug(lvl, "handleException", msg, string.join(tb_list, ""))
-  except:
-    handleException("Unable to dump_bug", dump = 0)
-
-def handleWarning (msg=""):
-  header = "*** handleWarning: %s\n" % msg
-  sys.stderr.write(header)
-  tb = who_calls.pretty_who_calls(strip=1) + "\n"
-  sys.stderr.write(tb)
-
-  try:
-    dump_bug(LV_WARNING, "handleException", msg, tb)
-  except:
-    handleException("Unable to dump_bug", dump = 0)
-
-def dump_bug (level, etype, msg, location=None, nhdf=None):
-    global DISABLE_DUMP
-    if DISABLE_DUMP: return
-
-    now = int(time.time())
-    pid = os.getpid()
-
-    import neo_cgi, neo_util
-    hdf = neo_util.HDF()
-    hdf.setValue("Required.Level", level)
-    hdf.setValue("Required.When", str(int(time.time())))
-    hdf.setValue("Required.Type", etype)
-    hdf.setValue("Required.Title", msg)
-    hdf.setValue("Optional.Hostname", socket.gethostname())
-    if location:
-        hdf.setValue("Optional.Location", location)
-
-    for (key, value) in os.environ.items():
-        hdf.setValue ("Environ.%s" % key, value)
-
-    global Count
-    Count = Count + 1
-    fname = "%d.%d_%d.%s" % (now, pid, Count, socket.gethostname())
-    tpath = os.path.join (DUMP_DIR, "tmp", fname)
-    npath = os.path.join (DUMP_DIR, "new", fname)
-    hdf.writeFile(tpath)
-    os.rename(tpath, npath)
-
diff -Nru clearsilver-0.9.1/python/examples/hdfhelp.py clearsilver-0.9.2/python/examples/hdfhelp.py
--- clearsilver-0.9.1/python/examples/hdfhelp.py	Fri Mar  7 14:44:58 2003
+++ clearsilver-0.9.2/python/examples/hdfhelp.py	Wed Dec 31 16:00:00 1969
@@ -1,108 +0,0 @@
-#!/neo/opt/bin/python
-#
-# Copyright (C) 2001 by Neotonic Software Corporation
-# All Rights Reserved.
-#
-# hdfhelp.py
-#
-# This code makes using odb with Clearsilver as "easy as stealing candy
-# from a baby". - jeske
-#
-# How to use:
-#
-#  rows = tbl.fetchAllRows()
-#  rows.hdfExport("CGI.rows", hdf_dataset)
-#
-#  row  = tbl.fetchRow( ('primary_key', value) )
-#  row.hdfExport("CGI.row", hdf_dataset)
-#
-# How to setup:
-#   
-#  # define table
-#  class AgentsTable(odb.Table):
-#    def _defineRows(self):
-#      self.d_addColumn("agent_id",kInteger,None,primarykey = 1,autoincrement = 1)
-#      self.d_addColumn("login",kVarString,200,notnull=1)
-#      self.d_addColumn("ticket_count",kIncInteger,None)
-#
-#    # make sure you return a subclass of hdfhelp.HdfRow
-#
-#    def defaultRowClass(self):
-#      return hdfhelp.HdfRow
-#    def defaultRowListClass(self):
-#      return hdfhelp.HdfItemList
-#
-
-import string
-import neo_cgi
-import neo_cs
-import neo_util
-import odb
-
-import UserList
-
-def renderDate(time_t_val):
-    if then_time is None:
-        then_time = 0
-    then_time = int(then_time)
-    then_tuple = time.localtime(then_time)
-    return time.strftime("%m/%d/%Y %H:%M%p",then_tuple)
-
-class HdfRow(odb.Row):
-    def hdfExport(self,prefix,hdf_dataset,skip_fields = None, translate_dict = None):
-
-        for col_name,value in self.items():
-            if skip_fields and (col_name in skip_fields):
-                continue
-            try:
-                name,col_type,col_options = self._table.getColumnDef(col_name)
-            except:
-                col_type = odb.kVarString
-                col_options = {}
-            
-	    if (col_name != "value") and (value is not None):
-		if type(value) in [ type(0), type(0L) ]:
-		    hdf_dataset.setValue(prefix + "." + col_name,"%d" % value)
-		else:
-                    if translate_dict:
-                        for k,v in translate_dict.items():
-                            value = string.replace(value,k,v)
-		    hdf_dataset.setValue(prefix + "." + col_name,neo_cgi.htmlEscape(value))
-                if col_options.get("int_date",0):
-                    hdf_dataset.setValue(prefix + "." + col_name + ".string",renderDate(value))
-                    hdf_dataset.setValue(prefix + "." + col_name + ".day_string",renderDate(value,day=1))
-
-
-
-class HdfItemList(UserList.UserList):
-    def hdfExport(self,prefix,hdf_dataset,*extra,**extranamed):
-	n = 0
-	for row in self:
-	    row.hdfExport("%s.%d" % (prefix,n),hdf_dataset,*extra,**extranamed)
-	    n = n + 1
-
-
-def eval_cs(hdf,a_cs_string):
-    cs = neo_cs.CS(hdf)
-    try:
-      cs.parseStr(a_cs_string)
-      return cs.render()
-    except:
-      return "Error in CS tags: %s" % neo_cgi.htmlEscape(repr(a_cs_string))
-
-# ----------------------------
-
-def test():
-    import neo_util
-    hdf = neo_util.HDF()
-    hdf.setValue("foo","1")
-    print eval_cs(hdf,"this should say 1  ===> <?cs var:foo ?>")
-    
-
-if __name__ == "__main__":
-    test()
-
-
-
-
-
diff -Nru clearsilver-0.9.1/python/examples/log.py clearsilver-0.9.2/python/examples/log.py
--- clearsilver-0.9.1/python/examples/log.py	Sun Jun 15 23:14:49 2003
+++ clearsilver-0.9.2/python/examples/log.py	Wed Dec 31 16:00:00 1969
@@ -1,51 +0,0 @@
-#!/neo/opt/bin/python
-
-# log.py
-
-import sys, time
-
-DEV = "development"
-DEV_UPDATE = "update queries"
-DEV_SELECT = "select queries"
-DEV_REPORT = "report log"
-
-LOGGING_STATUS = {
-   DEV : 1,
-   DEV_UPDATE : 0,
-   DEV_SELECT : 0,
-   DEV_REPORT : 0}
-
-tstart = 0
-
-def dlog(when,astr):
-    global LOGGING_STATUS
-    try:
-        if LOGGING_STATUS[when]:
-            log(astr)
-    except KeyError:
-        pass
-
-def tlog(astr):
-    global tstart
-    t = time.time()
-    if tstart == 0:
-        tstart = t
-    time_stamp = "%5.5f" % (t-tstart)
-    if len(astr):
-        if astr[-1] == "\n":
-            sys.stderr.write("[%s] %s" % (time_stamp, astr))
-        else:
-            sys.stderr.write("[%s] %s\n" % (time_stamp, astr))
-
-def log(astr):
-    if len(astr) > 1024:
-        astr = astr[:1024]
-
-    t = time.time()
-    time_stamp = time.strftime("%m/%d %T", time.localtime(t))
-    if len(astr):
-        if astr[-1] == "\n":
-            sys.stderr.write("[%s] %s" % (time_stamp, astr))
-        else:
-            sys.stderr.write("[%s] %s\n" % (time_stamp, astr))
-    # sys.stderr.flush()
diff -Nru clearsilver-0.9.1/python/examples/odb.py clearsilver-0.9.2/python/examples/odb.py
--- clearsilver-0.9.1/python/examples/odb.py	Fri Mar  7 14:44:58 2003
+++ clearsilver-0.9.2/python/examples/odb.py	Wed Dec 31 16:00:00 1969
@@ -1,1471 +0,0 @@
-#!/neo/opt/bin/python
-#
-# odb.py
-#
-# Object Database Api
-#
-# Written by David Jeske <jeske@neotonic.com>, 2001/07. 
-# Inspired by eGroups' sqldb.py originally written by Scott Hassan circa 1998.
-#
-# Copyright (C) 2001, by David Jeske and Neotonic
-#
-# Goals:
-#       - a simple object-like interface to database data
-#       - database independent (someday)
-#       - relational-style "rigid schema definition"
-#       - object style easy-access
-#
-# Example:
-#
-#  import odb
-#  import MySQLdb
-#
-#  # define table
-#  class AgentsTable(odb.Table):
-#    def _defineRows(self):
-#      self.d_addColumn("agent_id",kInteger,None,primarykey = 1,autoincrement = 1)
-#      self.d_addColumn("login",kVarString,200,notnull=1)
-#      self.d_addColumn("ticket_count",kIncInteger,None)
-#
-#  if __name__ == "__main__":
-#    # open database
-#    ndb = MySQLdb.connect(host = 'localhost',
-#                          user='username', 
-#                          passwd = 'password', 
-#                          db='testdb')
-#    db = Database(ndb)
-#    tbl = AgentsTable(db,"agents")
-#
-#    # create row
-#    agent_row = tbl.newRow()
-#    agent_row.login = "foo"
-#    agent_row.save()
-#
-#    # fetch row (must use primary key)
-#    try:
-#      get_row = tbl.fetchRow( ('agent_id', agent_row.agent_id) )
-#    except odb.eNoMatchingRows:
-#      print "this is bad, we should have found the row"
-#
-#    # fetch rows (can return empty list)
-#    list_rows = tbl.fetchRows( ('login', "foo") )
-#
-
-import tstart
-
-import string
-import sys, zlib
-from log import *
-
-import MySQLdb
-
-eNoSuchColumn         = "odb.eNoSuchColumn"
-eNonUniqueMatchSpec   = "odb.eNonUniqueMatchSpec"
-eNoMatchingRows       = "odb.eNoMatchingRows"
-eInternalError        = "odb.eInternalError"
-eInvalidMatchSpec     = "odb.eInvalidMatchSpec"
-eInvalidData          = "odb.eInvalidData"
-eUnsavedObjectLost    = "odb.eUnsavedObjectLost"
-eDuplicateKey         = "odb.eDuplicateKey"
-
-#####################################
-# COLUMN TYPES                       
-################                     ######################
-# typename     ####################### size data means:
-#              #                     # 
-kInteger       = "kInteger"          # -
-kFixedString   = "kFixedString"      # size
-kVarString     = "kVarString"        # maxsize
-kBigString     = "kBigString"        # -
-kIncInteger    = "kIncInteger"       # -
-kDateTime      = "kDateTime"
-kTimeStamp     = "kTimeStamp"
-
-
-DEBUG = 0
-
-##############
-# Database
-#
-# this will ultimately turn into a mostly abstract base class for
-# the DB adaptors for different database types....
-#
-
-class Database:
-    def __init__(self,db):
-        self._tables = {}
-        self.db = db
-        self._cursor = None
-        self.compression_enabled = 0
-
-    # __init__ = None
-    # list_tables = None   # list_tables() -> ['a_table','b_table']
-    # list_fields = None   # list_fields(tbl_name) -> ['
-    # checkTable = None    # checkTable
-    # createTable
-
-    def defaultCursor(self):
-        if self._cursor is None:
-            self._cursor = self.db.cursor()
-        return self._cursor
-
-    def escape(self,str):
-        return MySQLdb.escape_string(str)
-    def defaultRowClass(self):
-	return Row
-
-    def defaultRowListClass(self):
-        # base type is list...
-	return list
-
-    def addTable(self, attrname, tblname, tblclass, rowClass = None, check = 0, create = 0, rowListClass = None):
-        self._tables[attrname] = tblclass(self, tblname, rowClass=rowClass, check=check, create=create, rowListClass=rowListClass)
-
-    def close(self):
-        for name, tbl in self._tables.items():
-            tbl.db = None
-        self._tables = {}
-        if self.db is not None:
-            self.db.close()
-            self.db = None
-
-    def __getattr__(self, key):
-        try:
-            return self._tables[key]
-        except KeyError:
-            raise AttributeError, "unknown attribute %s" % (key)
-        
-    def beginTransaction(self, cursor=None):
-        if cursor is None:
-            cursor = self.defaultCursor()
-        dlog(DEV_UPDATE,"begin")
-        cursor.execute("begin")
-
-    def commitTransaction(self, cursor=None):
-        if cursor is None:
-            cursor = self.defaultCursor()
-        dlog(DEV_UPDATE,"commit")
-        cursor.execute("commit")
-
-    def rollbackTransaction(self, cursor=None):
-        if cursor is None:
-            cursor = self.defaultCursor()
-        dlog(DEV_UPDATE,"rollback")
-        cursor.execute("rollback")
-
-##########################################
-# Table
-#
-
-
-class Table:
-    def subclassinit(self):
-        pass
-    def __init__(self,database,table_name,rowClass = None, check = 0, create = 0, rowListClass = None):
-	self.db = database
-	self.__table_name = table_name
-	if rowClass:
-	    self.__defaultRowClass = rowClass
-	else:
-	    self.__defaultRowClass = database.defaultRowClass()
-
-	if rowListClass:
-	    self.__defaultRowListClass = rowListClass
-	else:
-	    self.__defaultRowListClass = database.defaultRowListClass()
-
-	# get this stuff ready!
-	
-	self.__column_list = []
-	self.__vcolumn_list = []
-	self.__columns_locked = 0
-	self.__has_value_column = 0
-
-	# this will be used during init...
-	self.__col_def_hash = None
-	self.__vcol_def_hash = None
-	self.__primary_key_list = None
-        self.__relations_by_table = {}
-
-	# ask the subclass to def his rows
-	self._defineRows()
-
-	# get ready to run!
-	self.__lockColumnsAndInit()
-
-        self.subclassinit()
-        
-	if create:
-	    self.db.createTable(self)
-
-	if check:
-	    self.db.checkTable(self)
-
-    def getColumnDef(self,column_name):
-        try:
-            return self.__col_def_hash[column_name]
-        except KeyError:
-            try:
-                return self.__vcol_def_hash[column_name]
-            except KeyError:
-                raise eNoSuchColumn, "no column (%s) on table %s" % (column_name,self.__table_name)
-
-    def getColumnList(self):
-        return self.__column_list + self.__vcolumn_list
-
-    def databaseSizeForData_ColumnName_(self,data,col_name):
-	try:
-	    col_def = self.__col_def_hash[col_name]
-	except KeyError:
-	    try:
-		col_def = self.__vcol_def_hash[col_name]
-	    except KeyError:
-		raise eNoSuchColumn, "no column (%s) on table %s" % (col_name,self.__table_name)
-
-        c_name,c_type,c_options = col_def
-
-        if c_type == kBigString:
-            if c_options.get("compress_ok",0) and self.db.compression_enabled:
-                z_size = len(zlib.compress(data,9))
-                r_size = len(data)
-                if z_size < r_size:
-                    return z_size
-                else:
-                    return r_size
-            else:
-                return len(data)
-        else:
-            # really simplistic database size computation:
-            try:
-                a = data[0]
-                return len(data)
-            except:
-                return 4
-            
-
-    def columnType(self, col_name):
-	try:
-	    col_def = self.__col_def_hash[col_name]
-	except KeyError:
-	    try:
-		col_def = self.__vcol_def_hash[col_name]
-	    except KeyError:
-		raise eNoSuchColumn, "no column (%s) on table %s" % (col_name,self.__table_name)
-
-	c_name,c_type,c_options = col_def
-        return c_type
-
-    def convertDataForColumn(self,data,col_name):
-	try:
-	    col_def = self.__col_def_hash[col_name]
-	except KeyError:
-	    try:
-		col_def = self.__vcol_def_hash[col_name]
-	    except KeyError:
-		raise eNoSuchColumn, "no column (%s) on table %s" % (col_name,self.__table_name)
-
-	c_name,c_type,c_options = col_def
-
-        if c_type == kIncInteger:
-            raise eInvalidData, "invalid operation for column (%s:%s) on table (%s)" % (col_name,c_type,self.__table_name)
-
-	if c_type == kInteger:
-	    try:
-                if data is None: data = 0
-                else: return long(data)
-	    except (ValueError,TypeError):
-		raise eInvalidData, "invalid data (%s) for col (%s:%s) on table (%s)" % (repr(data),col_name,c_type,self.__table_name)
-	else:
-	    if type(data) == type(long(0)):
-		return "%d" % data
-	    else:
-		return str(data)
-
-    def getPrimaryKeyList(self):
-	return self.__primary_key_list
-    
-    def getTableName(self):
-	return self.__table_name
-    def hasValueColumn(self):
-	return self.__has_value_column
-
-    def hasColumn(self,name):
-	return self.__col_def_hash.has_key(name)
-    def hasVColumn(self,name):
-	return self.__vcol_def_hash.has_key(name)
-	
-
-    def _defineRows(self):
-	raise "can't instantiate base odb.Table type, make a subclass and override _defineRows()"
-
-    def __lockColumnsAndInit(self):
-	# add a 'odb_value column' before we lockdown the table def
-	if self.__has_value_column:
-	    self.d_addColumn("odb_value",kBigText,default='')
-
-	self.__columns_locked = 1
-	# walk column list and make lookup hashes, primary_key_list, etc..
-
-	primary_key_list = []
-	col_def_hash = {}
-	for a_col in self.__column_list:
-	    name,type,options = a_col
-	    col_def_hash[name] = a_col
-	    if options.has_key('primarykey'):
-		primary_key_list.append(name)
-
-	self.__col_def_hash = col_def_hash
-	self.__primary_key_list = primary_key_list
-
-	# setup the value columns!
-
-	if (not self.__has_value_column) and (len(self.__vcolumn_list) > 0):
-	    raise "can't define vcolumns on table without ValueColumn, call d_addValueColumn() in your _defineRows()"
-
-	vcol_def_hash = {}
-	for a_col in self.__vcolumn_list:
-	    name,type,size_data,options = a_col
-	    vcol_def_hash[name] = a_col
-
-	self.__vcol_def_hash = vcol_def_hash
-	
-	
-    def __checkColumnLock(self):
-	if self.__columns_locked:
-	    raise "can't change column definitions outside of subclass' _defineRows() method!"
-
-    # table definition methods, these are only available while inside the
-    # subclass's _defineRows method
-    #
-    # Ex:
-    #
-    # import odb
-    # class MyTable(odb.Table):
-    #   def _defineRows(self):
-    #     self.d_addColumn("id",kInteger,primarykey = 1,autoincrement = 1)
-    #     self.d_addColumn("name",kVarString,120)
-    #     self.d_addColumn("type",kInteger,
-    #                      enum_values = { 0 : "alive", 1 : "dead" }
-
-    def d_addColumn(self,col_name,ctype,size=None,primarykey = 0, notnull = 0,indexed=0,
-		    default=None,unique=0,autoincrement=0,safeupdate=0,enum_values = None,
-                    relations=None,compress_ok=0,int_date=0):
-
-	self.__checkColumnLock()
-
-	options = {}
-	options['default']       = default
-	if primarykey:
-	    options['primarykey']    = primarykey
-	if indexed:
-	    options['indexed']       = indexed
-	if unique:
-	    options['unique']        = unique
-	if safeupdate:
-	    options['safeupdate']    = safeupdate
-	if autoincrement:
-	    options['autoincrement'] = autoincrement
-	if notnull:
-	    options['notnull']       = notnull
-	if size:
-	    options['size']          = size
-        if int_date:
-            if ctype != kInteger:
-                raise eInvalidData, "can't flag columns int_date unless they are kInteger"
-            else:
-                options['int_date'] = int_date
-            
-	if enum_values:
-	    options['enum_values']   = enum_values
-	    inv_enum_values = {}
-	    for k,v in enum_values.items():
-		if inv_enum_values.has_key(v):
-		    raise eInvalidData, "enum_values paramater must be a 1 to 1 mapping for Table(%s)" % self.__table_name
-		else:
-		    inv_enum_values[v] = k
-	    options['inv_enum_values'] = inv_enum_values
-        if relations:
-            options['relations']      = relations
-            for a_relation in relations:
-                table, foreign_column_name = a_relation
-                if self.__relations_by_table.has_key(table):
-                    raise eInvalidData, "multiple relations for the same foreign table are not yet supported" 
-                self.__relations_by_table[table] = (col_name,foreign_column_name)
-        if compress_ok:
-            if ctype == kBigString:
-                options['compress_ok'] = 1
-            else:
-                raise eInvalidData, "only kBigString fields can be compress_ok=1"
-	
-	self.__column_list.append( (col_name,ctype,options) )
-	
-
-    def d_addValueColumn(self):
-	self.__checkColumnLock()
-	self.__has_value_column = 1
-
-    def d_addVColumn(self,col_name,type,size=None,default=None):
-	self.__checkColumnLock()
-
-	if (not self.__has_value_column):
-	    raise "can't define VColumns on table without ValueColumn, call d_addValueColumn() first"
-
-	options = {}
-	if default:
-	    options['default'] = default
-	if size:
-	    options['size']    = size
-
-	self.__vcolumn_list.append( (col_name,type,options) )
-
-    #####################
-    # _checkColMatchSpec(col_match_spec,should_match_unique_row = 0)
-    #
-    # raise an error if the col_match_spec contains invalid columns, or
-    # (in the case of should_match_unique_row) if it does not fully specify
-    # a unique row.
-    #
-    # NOTE: we don't currently support where clauses with value column fields!
-    #
-    
-    def _fixColMatchSpec(self,col_match_spec, should_match_unique_row = 0):
-	if type(col_match_spec) == type([]):
-	    if type(col_match_spec[0]) != type((0,)):
-		raise eInvalidMatchSpec, "invalid types in match spec, use [(,)..] or (,)"
-	elif type(col_match_spec) == type((0,)):
-	    col_match_spec = [ col_match_spec ]
-        elif type(col_match_spec) == type(None):
-            if should_match_unique_row:
-                raise eNonUniqueMatchSpec, "can't use a non-unique match spec (%s) here" % col_match_spec
-            else:
-                return None
-	else:
-	    raise eInvalidMatchSpec, "invalid types in match spec, use [(,)..] or (,)"
-
-	if should_match_unique_row:
-            unique_column_lists = []
-
-            # first the primary key list
-	    my_primary_key_list = []
-	    for a_key in self.__primary_key_list:
-		my_primary_key_list.append(a_key)
-
-            # then other unique keys
-            for a_col in self.__column_list:
-                col_name,a_type,options = a_col
-                if options.has_key('unique'):
-                    unique_column_lists.append( (col_name, [col_name]) )
-
-            unique_column_lists.append( ('primary_key', my_primary_key_list) )
-                
-	
-	new_col_match_spec = []
-	for a_col in col_match_spec:
-	    name,val = a_col
-	    # newname = string.lower(name)
-	    #  what is this doing?? - jeske
-	    newname = name
-	    if not self.__col_def_hash.has_key(newname):
-		raise eNoSuchColumn, "no such column in match spec: '%s'" % newname
-
-	    new_col_match_spec.append( (newname,val) )
-
-	    if should_match_unique_row:
-                for name,a_list in unique_column_lists:
-                    try:
-                        a_list.remove(newname)
-                    except ValueError:
-                        # it's okay if they specify too many columns!
-                        pass
-
-	if should_match_unique_row:
-            for name,a_list in unique_column_lists:
-                if len(a_list) == 0:
-                    # we matched at least one unique colum spec!
-                    # log("using unique column (%s) for query %s" % (name,col_match_spec))
-                    return new_col_match_spec
-            
-            raise eNonUniqueMatchSpec, "can't use a non-unique match spec (%s) here" % col_match_spec
-
-	return new_col_match_spec
-
-    def __buildWhereClause (self, col_match_spec,other_clauses = None):
-	sql_where_list = []
-
-        if not col_match_spec is None:
-            for m_col in col_match_spec:
-                m_col_name,m_col_val = m_col
-                c_name,c_type,c_options = self.__col_def_hash[m_col_name]
-                if c_type in (kIncInteger, kInteger):
-                    try:
-                        m_col_val_long = long(m_col_val)
-                    except ValueError:
-                        raise ValueError, "invalid literal for long(%s) in table %s" % (repr(m_col_val),self.__table_name)
-                        
-                    sql_where_list.append("%s = %d" % (c_name, m_col_val_long))
-                else:
-                    sql_where_list.append("%s = '%s'" % (c_name, self.db.escape(m_col_val)))
-
-        if other_clauses is None:
-            pass
-        elif type(other_clauses) == type(""):
-            sql_where_list = sql_where_list + [other_clauses]
-        elif type(other_clauses) == type([]):
-            sql_where_list = sql_where_list + other_clauses
-        else:
-            raise eInvalidData, "unknown type of extra where clause: %s" % repr(other_clauses)
-                    
-        return sql_where_list
-
-    def __fetchRows(self,col_match_spec,cursor = None, where = None, order_by = None, limit_to = None,
-                    skip_to = None, join = None):
-	if cursor is None:
-	    cursor = self.db.defaultCursor()
-
-        # build column list
-        sql_columns = []
-        for name,t,options in self.__column_list:
-            sql_columns.append(name)
-
-        # build join information
-
-        joined_cols = []
-        joined_cols_hash = {}
-        join_clauses = []
-        if not join is None:
-            for a_table,retrieve_foreign_cols in join:
-                try:
-                    my_col,foreign_col = self.__relations_by_table[a_table]
-                    for a_col in retrieve_foreign_cols:
-                        full_col_name = "%s.%s" % (my_col,a_col)
-                        joined_cols_hash[full_col_name] = 1
-                        joined_cols.append(full_col_name)
-                        sql_columns.append( full_col_name )
-
-                    join_clauses.append(" left join %s as %s on %s=%s " % (a_table,my_col,my_col,foreign_col))
-                        
-                except KeyError:
-                    eInvalidJoinSpec, "can't find table %s in defined relations for %s" % (a_table,self.__table_name)
-                    
-        # start buildling SQL
-    	sql = "select %s from %s" % (string.join(sql_columns,","),
-                                     self.__table_name)
-
-        # add join clause
-        if join_clauses:
-            sql = sql + string.join(join_clauses," ")
-	
-	# add where clause elements
-        sql_where_list = self.__buildWhereClause (col_match_spec,where)
-	if sql_where_list:
-	    sql = sql + " where %s" % (string.join(sql_where_list," and "))
-
-        # add order by clause
-        if order_by:
-            sql = sql + " order by %s " % string.join(order_by,",")
-
-        # add limit
-        if not limit_to is None:
-            if not skip_to is None:
-                sql = sql + " limit %s, %s" % (skip_to,limit_to)
-            else:
-                sql = sql + " limit %s" % limit_to
-        else:
-            if not skip_to is None:
-                raise eInvalidData, "can't specify skip_to without limit_to in MySQL"
-
-        dlog(DEV_SELECT,sql)
-	cursor.execute(sql)
-
-        # create defaultRowListClass instance...
-        return_rows = self.__defaultRowListClass()
-	    
-	# should do fetchmany!
-	all_rows = cursor.fetchall()
-	for a_row in all_rows:
-	    data_dict = {}
-
-	    col_num = 0
-            
-            #	    for a_col in cursor.description:
-            #		(name,type_code,display_size,internal_size,precision,scale,null_ok) = a_col
-            for name in sql_columns:
-		if self.__col_def_hash.has_key(name) or joined_cols_hash.has_key(name):
-		    # only include declared columns!
-                    if self.__col_def_hash.has_key(name):
-                        c_name,c_type,c_options = self.__col_def_hash[name]
-                        if c_type == kBigString and c_options.get("compress_ok",0) and a_row[col_num]:
-                            try:
-                                a_col_data = zlib.decompress(a_row[col_num])
-                            except zlib.error:
-                                a_col_data = a_row[col_num]
-
-                            data_dict[name] = a_col_data
-                        else:
-                            data_dict[name] = a_row[col_num]
-
-                    else:
-                        data_dict[name] = a_row[col_num]
-                        
-		    col_num = col_num + 1
-
-	    newrowobj = self.__defaultRowClass(self,data_dict,joined_cols = joined_cols)
-
-	    return_rows.append(newrowobj)
-	    
-	return return_rows
-
-    def __deleteRow(self,a_row,cursor = None):
-	if cursor is None:
-	    cursor = self.db.defaultCursor()
-
-        # build the where clause!
-        match_spec = a_row.getPKMatchSpec()
-        sql_where_list = self.__buildWhereClause (match_spec)
-
-        sql = "delete from %s where %s" % (self.__table_name,
-                                           string.join(sql_where_list," and "))
-        dlog(DEV_UPDATE,sql)
-        cursor.execute(sql)
-       
-
-    def __updateRowList(self,a_row_list,cursor = None):
-	if cursor is None:
-	    cursor = self.db.defaultCursor()
-
-	for a_row in a_row_list:
-	    update_list = a_row.changedList()
-
-	    # build the set list!
-	    sql_set_list = []
-	    for a_change in update_list:
-		col_name,col_val,col_inc_val = a_change
-		c_name,c_type,c_options = self.__col_def_hash[col_name]
-
-                if c_type != kIncInteger and col_val is None:
-                    sql_set_list.append("%s = NULL" % c_name)
-                elif c_type == kIncInteger and col_inc_val is None:
-                    sql_set_list.append("%s = 0" % c_name)
-                else:
-                    if c_type == kInteger:
-                        sql_set_list.append("%s = %d" % (c_name, long(col_val)))
-                    elif c_type == kIncInteger:
-                        sql_set_list.append("%s = %s + %d" % (c_name,c_name,long(col_inc_val)))
-                    elif c_type == kBigString and c_options.get("compress_ok",0) and self.db.compression_enabled:
-                        compressed_data = zlib.compress(col_val,9)
-                        if len(compressed_data) < len(col_val):
-                            sql_set_list.append("%s = '%s'" % (c_name, self.db.escape(compressed_data)))
-                        else:
-                            sql_set_list.append("%s = '%s'" % (c_name, self.db.escape(col_val)))
-                    else:
-                        sql_set_list.append("%s = '%s'" % (c_name, self.db.escape(col_val)))
-
-	    # build the where clause!
-	    match_spec = a_row.getPKMatchSpec()
-            sql_where_list = self.__buildWhereClause (match_spec)
-
-	    if sql_set_list:
-		sql = "update %s set %s where %s" % (self.__table_name,
-						 string.join(sql_set_list,","),
-						 string.join(sql_where_list," and "))
-
-                dlog(DEV_UPDATE,sql)
-                try:
-                    cursor.execute(sql)
-                except Exception, reason:
-                    if string.find(str(reason), "Duplicate entry") != -1:
-                        raise eDuplicateKey, reason
-                    raise Exception, reason
-		a_row.markClean()
-
-    def __insertRow(self,a_row_obj,cursor = None):
-	if cursor is None:
-	    cursor = self.db.defaultCursor()
-
-	sql_col_list = []
-	sql_data_list = []
-	auto_increment_column_name = None
-
-	for a_col in self.__column_list:
-	    name,type,options = a_col
-
-	    try:
-		data = a_row_obj[name]
-
-		sql_col_list.append(name)
-                if data is None:
-                    sql_data_list.append("NULL")
-                else:
-                    if type == kInteger or type == kIncInteger:
-                        sql_data_list.append("%d" % data)
-                    elif type == kBigString and options.get("compress_ok",0) and self.db.compression_enabled:
-                        compressed_data = zlib.compress(data,9)
-                        if len(compressed_data) < len(data):
-                            sql_data_list.append("'%s'" % self.db.escape(compressed_data))
-                        else:
-                            sql_data_list.append("'%s'" % self.db.escape(data))
-                    else:
-                        sql_data_list.append("'%s'" % self.db.escape(data))
-
-	    except KeyError:
-		if options.has_key("autoincrement"):
-		    if auto_increment_column_name:
-			raise eInternalError, "two autoincrement columns (%s,%s) in table (%s)" % (auto_increment_column_name, name,self.__table_name)
-		    else:
-			auto_increment_column_name = name
-		
-	
-	sql = "insert into %s (%s) values (%s)" % (self.__table_name,
-						   string.join(sql_col_list,","),
-						   string.join(sql_data_list,","))
-
-        dlog(DEV_UPDATE,sql)
-        try:
-          cursor.execute(sql)
-        except Exception, reason:
-          if string.find(str(reason), "Duplicate entry") != -1:
-            raise eDuplicateKey, reason
-          raise Exception, reason
-            
-	if auto_increment_column_name:
-	    a_row_obj[auto_increment_column_name] = cursor.insert_id()
-
-    # ----------------------------------------------------
-    #   Helper methods for Rows...
-    # ----------------------------------------------------
-
-
-	
-    #####################
-    # r_deleteRow(a_row_obj,cursor = None)
-    #
-    # normally this is called from within the Row "delete()" method
-    # but you can call it yourself if you want
-    #
-
-    def r_deleteRow(self,a_row_obj, cursor = None):
-	curs = cursor
-	self.__deleteRow(a_row_obj, cursor = curs)
-
-
-    #####################
-    # r_updateRow(a_row_obj,cursor = None)
-    #
-    # normally this is called from within the Row "save()" method
-    # but you can call it yourself if you want
-    #
-
-    def r_updateRow(self,a_row_obj, cursor = None):
-	curs = cursor
-	self.__updateRowList([a_row_obj], cursor = curs)
-
-    #####################
-    # InsertRow(a_row_obj,cursor = None)
-    #
-    # normally this is called from within the Row "save()" method
-    # but you can call it yourself if you want
-    #
-
-    def r_insertRow(self,a_row_obj, cursor = None):
-	curs = cursor
-	self.__insertRow(a_row_obj, cursor = curs)
-
-
-    # ----------------------------------------------------
-    #   Public Methods
-    # ----------------------------------------------------
-
-
-	
-    #####################
-    # deleteRow(col_match_spec)
-    #
-    # The col_match_spec paramaters must include all primary key columns.
-    #
-    # Ex:
-    #    a_row = tbl.fetchRow( ("order_id", 1) )
-    #    a_row = tbl.fetchRow( [ ("order_id", 1), ("enterTime", now) ] )
-
-
-    def deleteRow(self,col_match_spec, where=None):
-        n_match_spec = self._fixColMatchSpec(col_match_spec)
-        cursor = self.db.defaultCursor()
-
-        # build sql where clause elements
-        sql_where_list = self.__buildWhereClause (n_match_spec,where)
-        if not sql_where_list:
-            return
-
-        sql = "delete from %s where %s" % (self.__table_name, string.join(sql_where_list," and "))
-
-        dlog(DEV_UPDATE,sql)
-        cursor.execute(sql)
-	
-    #####################
-    # fetchRow(col_match_spec)
-    #
-    # The col_match_spec paramaters must include all primary key columns.
-    #
-    # Ex:
-    #    a_row = tbl.fetchRow( ("order_id", 1) )
-    #    a_row = tbl.fetchRow( [ ("order_id", 1), ("enterTime", now) ] )
-
-
-    def fetchRow(self, col_match_spec, cursor = None):
-	n_match_spec = self._fixColMatchSpec(col_match_spec, should_match_unique_row = 1)
-
-	rows = self.__fetchRows(n_match_spec, cursor = cursor)
-	if len(rows) == 0:
-	    raise eNoMatchingRows, "no row matches %s" % repr(n_match_spec)
-
-	if len(rows) > 1:
-	    raise eInternalError, "unique where clause shouldn't return > 1 row"
-
-	return rows[0]
-	    
-
-    #####################
-    # fetchRows(col_match_spec)
-    #
-    # Ex:
-    #    a_row_list = tbl.fetchRows( ("order_id", 1) )
-    #    a_row_list = tbl.fetchRows( [ ("order_id", 1), ("enterTime", now) ] )
-
-
-    def fetchRows(self, col_match_spec = None, cursor = None, where = None, order_by = None, limit_to = None, skip_to = None, join = None):
-	n_match_spec = self._fixColMatchSpec(col_match_spec)
-
-	return self.__fetchRows(n_match_spec,
-                                cursor = cursor,
-                                where = where,
-                                order_by = order_by,
-                                limit_to = limit_to,
-                                skip_to = skip_to,
-                                join = join)
-
-    def fetchRowCount (self, col_match_spec = None, cursor = None, where = None):
-	n_match_spec = self._fixColMatchSpec(col_match_spec)
-
-        sql_where_list = self.__buildWhereClause (n_match_spec,where)
-
-    	sql = "select count(*) from %s" % self.__table_name
-	if sql_where_list:
-	    sql = "%s where %s" % (sql,string.join(sql_where_list," and "))
-
-        if cursor is None:
-          cursor = self.db.defaultCursor()
-        dlog(DEV_SELECT,sql)
-	cursor.execute(sql)
-        try:
-	    count, = cursor.fetchone()
-        except TypeError:
-            count = 0
-        return count
-
-
-    #####################
-    # fetchAllRows()
-    #
-    # Ex:
-    #    a_row_list = tbl.fetchRows( ("order_id", 1) )
-    #    a_row_list = tbl.fetchRows( [ ("order_id", 1), ("enterTime", now) ] )
-
-    def fetchAllRows(self):
-        try:
-            return self.__fetchRows([])
-        except eNoMatchingRows:
-            # else return empty list...
-            return self.__defaultRowListClass()
-
-    def newRow(self):
-	row = self.__defaultRowClass(self,None,create=1)
-        for (cname, ctype, opts) in self.__column_list:
-            if opts['default'] is not None and ctype is not kIncInteger:
-                row[cname] = opts['default']
-        return row
-
-class Row:
-    __instance_data_locked  = 0
-    def subclassinit(self):
-        pass
-    def __init__(self,_table,data_dict,create=0,joined_cols = None):
-
-        self._inside_getattr = 0  # stop recursive __getattr__
-	self._table = _table
-	self._should_insert = create
-        self._rowInactive = None
-        self._joinedRows = []
-	
-	self.__pk_match_spec = None
-	self.__vcoldata = {}
-        self.__inc_coldata = {}
-
-        self.__joined_cols_dict = {}
-        for a_col in joined_cols or []:
-            self.__joined_cols_dict[a_col] = 1
-	
-	if create:
-	    self.__coldata = {}
-	else:
-	    if type(data_dict) != type({}):
-		raise eInternalError, "rowdict instantiate with bad data_dict"
-	    self.__coldata = data_dict
-	    self.__unpackVColumn()
-
-	self.markClean()
-
-        self.subclassinit()
-	self.__instance_data_locked = 1
-
-    def joinRowData(self,another_row):
-        self._joinedRows.append(another_row)
-
-    def getPKMatchSpec(self):
-	return self.__pk_match_spec
-
-    def markClean(self):
-	self.__vcolchanged = 0
-	self.__colchanged_dict = {}
-
-        for key in self.__inc_coldata.keys():
-	    self.__coldata[key] = self.__coldata.get(key, 0) + self.__inc_coldata[key]
-
-        self.__inc_coldata = {}
-
-	if not self._should_insert:
-	    # rebuild primary column match spec
-	    new_match_spec = []
-	    for col_name in self._table.getPrimaryKeyList():
-		try:
-		    rdata = self[col_name]
-		except KeyError:
-		    raise eInternalError, "must have primary key data filled in to save %s:Row(col:%s)" % (self._table.getTableName(),col_name)
-		    
-		new_match_spec.append( (col_name, rdata) )
-	    self.__pk_match_spec = new_match_spec
-
-    def __unpackVColumn(self):
-	if self._table.hasValueColumn():
-	    pass
-	
-    def __packVColumn(self):
-	if self._table.hasValueColumn():
-	    pass
-
-    ## ----- utility stuff ----------------------------------
-
-    def __del__(self):
-	# check for unsaved changes
-	changed_list = self.changedList()
-	if len(changed_list):
-            info = "unsaved Row for table (%s) lost, call discard() to avoid this error. Lost changes: %s\n" % (self._table.getTableName(), repr(changed_list)[:256])
-            if 0:
-                raise eUnsavedObjectLost, info
-            else:
-                sys.stderr.write(info)
-                
-
-    def __repr__(self):
-	return "Row from (%s): %s" % (self._table.getTableName(),repr(self.__coldata) + repr(self.__vcoldata))
-
-    ## ---- class emulation --------------------------------
-
-    def __getattr__(self,key):
-        if self._inside_getattr:
-          raise AttributeError, "recursively called __getattr__ (%s,%s)" % (key,self._table.getTableName())
-        try:
-            self._inside_getattr = 1
-            try:
-                return self[key]
-            except KeyError:
-                if self._table.hasColumn(key) or self._table.hasVColumn(key):
-                    return None
-                else:
-                    raise AttributeError, "unknown field '%s' in Row(%s)" % (key,self._table.getTableName())
-        finally:
-            self._inside_getattr = 0
-
-    def __setattr__(self,key,val):
-	if not self.__instance_data_locked:
-	    self.__dict__[key] = val
-	else:
-	    my_dict = self.__dict__
-	    if my_dict.has_key(key):
-		my_dict[key] = val
-	    else:
-		# try and put it into the rowdata
-		try:
-		    self[key] = val
-		except KeyError, reason:
-		    raise AttributeError, reason
-
-
-    ## ---- dict emulation ---------------------------------
-    
-    def __getitem__(self,key):
-        self.checkRowActive()
-
-        try:
-            c_type = self._table.columnType(key)
-        except eNoSuchColumn:
-            # Ugh, this sucks, we can't determine the type for a joined
-            # row, so we just default to kVarString and let the code below
-            # determine if this is a joined column or not
-            c_type = kVarString
-
-        if c_type == kIncInteger:
-            c_data = self.__coldata.get(key, 0) 
-            if c_data is None: c_data = 0
-            i_data = self.__inc_coldata.get(key, 0)
-            if i_data is None: i_data = 0
-            return c_data + i_data
-        
-	try:
-	    return self.__coldata[key]
-	except KeyError:
-            try:
-                return self.__vcoldata[key]
-            except KeyError:
-                for a_joined_row in self._joinedRows:
-                    try:
-                        return a_joined_row[key]
-                    except KeyError:
-                        pass
-
-                raise KeyError, "unknown column %s in %s" % (key,self)
-
-    def __setitem__(self,key,data):
-        self.checkRowActive()
-        
-	try:
-	    newdata = self._table.convertDataForColumn(data,key)
-	except eNoSuchColumn, reason:
-	    raise KeyError, reason
-
-	if self._table.hasColumn(key):
-	    self.__coldata[key] = newdata
-	    self.__colchanged_dict[key] = 1
-	elif self._table.hasVColumn(key):
-	    self.__vcoldata[key] = newdata
-	    self.__vcolchanged = 1
-	else:
-            for a_joined_row in self._joinedRows:
-                try:
-                    a_joined_row[key] = data
-                    return
-                except KeyError:
-                    pass
-	    raise KeyError, "unknown column name %s" % key
-
-    def __delitem__(self,key,data):
-        self.checkRowActive()
-        
-	if self.table.hasVColumn(key):
-	    del self.__vcoldata[key]
-	else:
-            for a_joined_row in self._joinedRows:
-                try:
-                    del a_joined_row[key]
-                    return
-                except KeyError:
-                    pass
-	    raise KeyError, "unknown column name %s" % key
-
-
-    def copyFrom(self,source):
-        for name,t,options in self._table.getColumnList():
-            if not options.has_key("autoincrement"):
-                self[name] = source[name]
-
-
-    # make sure that .keys(), and .items() come out in a nice order!
-
-    def keys(self):
-        self.checkRowActive()
-        
-        key_list = []
-        for name,t,options in self._table.getColumnList():
-            key_list.append(name)
-        for name in self.__joined_cols_dict.keys():
-            key_list.append(name)
-
-        for a_joined_row in self._joinedRows:
-            key_list = key_list + a_joined_row.keys()
-            
-        return key_list
-
-
-    def items(self):
-        self.checkRowActive()
-        
-        item_list = []
-        for name,t,options in self._table.getColumnList():
-            item_list.append( (name,self[name]) )
-        for name in self.__joined_cols_dict.keys():
-            item_list.append( (name,self[name]) )
-
-        for a_joined_row in self._joinedRows:
-            item_list = item_list + a_joined_row.items()
-
-
-        return item_list
-
-    def values(elf):
-        self.checkRowActive()
-
-        value_list = self.__coldata.values() + self.__vcoldata.values()
-
-        for a_joined_row in self._joinedRows:
-            value_list = value_list + a_joined_row.values()
-
-        return value_list
-        
-
-    def __len__(self):
-        self.checkRowActive()
-        
-	my_len = len(self.__coldata) + len(self.__vcoldata)
-
-        for a_joined_row in self._joinedRows:
-            my_len = my_len + len(a_joined_row)
-
-        return my_len
-
-    def has_key(self,key):
-        self.checkRowActive()
-        
-	if self.__coldata.has_key(key) or self.__vcoldata.has_key(key):
-	    return 1
-	else:
-
-            for a_joined_row in self._joinedRows:
-                if a_joined_row.has_key(key):
-                    return 1
-	    return 0
-	
-    def get(self,key,default = None):
-        self.checkRowActive()
-
-        
-        
-	if self.__coldata.has_key(key):
-	    return self.__coldata[key]
-	elif self.__vcoldata.has_key(key):
-	    return self.__vcoldata[key]
-	else:
-            for a_joined_row in self._joinedRows:
-                try:
-                    return a_joined_row.get(key,default)
-                except eNoSuchColumn:
-                    pass
-
-            if self._table.hasColumn(key):
-                return default
-            
-	    raise eNoSuchColumn, "no such column %s" % key
-
-    def inc(self,key,count=1):
-        self.checkRowActive()
-
-        if self._table.hasColumn(key):
-            try:
-                self.__inc_coldata[key] = self.__inc_coldata[key] + count
-            except KeyError:
-                self.__inc_coldata[key] = count
-
-            self.__colchanged_dict[key] = 1
-        else:
-            raise AttributeError, "unknown field '%s' in Row(%s)" % (key,self._table.getTableName())
-    
-
-    ## ----------------------------------
-    ## real interface
-
-
-    def fillDefaults(self):
-	for field_def in self._table.fieldList():
-	    name,type,size,options = field_def
-	    if options.has_key("default"):
-		self[name] = options["default"]
-
-    ###############
-    # changedList()
-    #
-    # returns a list of tuples for the columns which have changed
-    #
-    #   changedList() -> [ ('name', 'fred'), ('age', 20) ]
-
-    def changedList(self):
-	if self.__vcolchanged:
-	    self.__packVColumn()
-
-	changed_list = []
-	for a_col in self.__colchanged_dict.keys():
-	    changed_list.append( (a_col,self.get(a_col,None),self.__inc_coldata.get(a_col,None)) )
-
-	return changed_list
-
-    def discard(self):
-	self.__coldata = None
-	self.__vcoldata = None
-	self.__colchanged_dict = {}
-	self.__vcolchanged = 0
-
-    def delete(self,cursor = None):
-        self.checkRowActive()
-
-        
-        fromTable = self._table
-        curs = cursor
-        fromTable.r_deleteRow(self,cursor=curs)
-        self._rowInactive = "deleted"
-
-    def save(self,cursor = None):
-	toTable = self._table
-
-        self.checkRowActive()
-
-	if self._should_insert:
-	    toTable.r_insertRow(self)
-	    self._should_insert = 0
-	    self.markClean()  # rebuild the primary key list
-	else:
-            curs = cursor
-	    toTable.r_updateRow(self,cursor = curs)
-
-	# the table will mark us clean!
-	# self.markClean()
-
-    def checkRowActive(self):
-        if self._rowInactive:
-            raise eInvalidData, "row is inactive: %s" % self._rowInactive
-
-    def databaseSizeForColumn(self,key):
-        return self._table.databaseSizeForData_ColumnName_(self[key],key)
-
-## -----------------------------------------------------------------------
-##                            T  E  S  T S
-## -----------------------------------------------------------------------
-
-	
-def TEST(output=log):
-    LOGGING_STATUS[DEV_SELECT] = 1
-    LOGGING_STATUS[DEV_UPDATE] = 1
-    class AgentsTable(Table):
-	def _defineRows(self):
-	    self.d_addColumn("agent_id",kInteger,None,primarykey = 1,autoincrement = 1)
-	    self.d_addColumn("login",kVarString,200,notnull=1)
-	    self.d_addColumn("ext_email",kVarString,200,notnull=1)
-	    self.d_addColumn("hashed_pw",kVarString,20,notnull=1)
-	    self.d_addColumn("name",kBigString,compress_ok=1)
-	    self.d_addColumn("auth_level",kInteger,None)
-            self.d_addColumn("ticket_count",kIncInteger,None)
-
-
-    import MySQLdb
-    rdb = MySQLdb.connect(host = 'localhost',user='root', passwd = '', db='testdb')
-    ndb = MySQLdb.connect(host = 'localhost',user='trakken', passwd = 'trakpas', db='testdb')
-    db = Database(ndb)
-
-    tbl = AgentsTable(db,"agents")
-
-    cursor = rdb.cursor()
-
-    # ---------------------------------------------------------------
-    # initialize
-    output("drop table agents")
-    try:
-        cursor.execute("drop table agents")   # clean out the table
-    except MySQLdb.OperationalError:
-        pass
-    output("creating table")
-    cursor.execute("create table agents (agent_id integer not null primary key auto_increment, login varchar(200) not null, unique (login), ext_email varchar(200) not null, hashed_pw varchar(20) not null, name varchar(200), auth_level integer default 0, ticket_count integer default 0)")
-
-    TEST_INSERT_COUNT = 5
-
-    # ---------------------------------------------------------------
-    # make sure we can catch a missing row
-
-    try:
-	a_row = tbl.fetchRow( ("agent_id", 1000) )
-	raise "test error"
-    except eNoMatchingRows:
-	pass
-
-    output("PASSED! fetch missing row test")
-
-    # --------------------------------------------------------------
-    # create new rows and insert them
-
-    for n in range(TEST_INSERT_COUNT):
-	new_id = n + 1
-	
-	newrow = tbl.newRow()
-	newrow.name = "name #%d" % new_id
-	newrow.login = "name%d" % new_id
-        newrow.ext_email = "%d@name" % new_id
-	newrow.save()
-	if newrow.agent_id != new_id:
-	    raise "new insert id (%s) does not match expected value (%d)" % (newrow.agent_id,new_id)
-
-    output("PASSED! autoinsert test")
-
-    # --------------------------------------------------------------
-    # fetch one row
-    a_row = tbl.fetchRow( ("agent_id", 1) )
-
-    if a_row.name != "name #1":
-	raise "row data incorrect"
-
-    output("PASSED! fetch one row test")
-
-    # ---------------------------------------------------------------
-    # don't change and save it
-    # (i.e. the "dummy cursor" string should never be called!)
-    #
-    try:
-	a_row.save(cursor = "dummy cursor")
-    except AttributeError, reason:
-	raise "row tried to access cursor on save() when no changes were made!"
-
-    output("PASSED! don't save when there are no changed")
-
-    # ---------------------------------------------------------------
-    # change, save, load, test
-    
-    a_row.auth_level = 10
-    a_row.save()
-    b_row = tbl.fetchRow( ("agent_id", 1) )
-    if b_row.auth_level != 10:
-	raise "save and load failed"
-    
-
-    output("PASSED! change, save, load")
-
-    # --------------------------------------------------------------
-    # access unknown attribute
-    try:
-	a = a_row.UNKNOWN_ATTRIBUTE
-	raise "test error"
-    except AttributeError, reason:
-	pass
-
-    try:
-	a_row.UNKNOWN_ATTRIBUTE = 1
-	raise "test error"
-    except AttributeError, reason:
-	pass
-
-    output("PASSED! unknown attribute exception")
-
-    # --------------------------------------------------------------
-    # access unknown dict item
-
-    try:
-	a = a_row["UNKNOWN_ATTRIBUTE"]
-	raise "test error"
-    except KeyError, reason:
-	pass
-
-    try:
-	a_row["UNKNOWN_ATTRIBUTE"] = 1
-	raise "test error"
-    except KeyError, reason:
-	pass
-
-    output("PASSED! unknown dict item exception")
-
-    # --------------------------------------------------------------
-    # use wrong data for column type
-
-    try:
-	a_row.agent_id = "this is a string"
-	raise "test error"
-    except eInvalidData, reason:
-	pass
-
-    output("PASSED! invalid data for column type")
-
-    # --------------------------------------------------------------
-    # fetch 1 rows
-
-    rows = tbl.fetchRows( ('agent_id', 1) )
-    if len(rows) != 1:
-	raise "fetchRows() did not return 1 row!" % (TEST_INSERT_COUNT)
-
-    output("PASSED! fetch one row")
-
-
-    # --------------------------------------------------------------
-    # fetch All rows
-    
-    rows = tbl.fetchAllRows()
-    if len(rows) != TEST_INSERT_COUNT:
-        for a_row in rows:
-            output(repr(a_row))
-	raise "fetchAllRows() did not return TEST_INSERT_COUNT(%d) rows!" % (TEST_INSERT_COUNT)
-
-    output("PASSED! fetchall rows")
-
-  
-    # --------------------------------------------------------------
-    # delete row object
-
-    row = tbl.fetchRow( ('agent_id', 1) )
-    row.delete()
-    try:
-        row = tbl.fetchRow( ('agent_id', 1) )
-        raise "delete failed to delete row!"
-    except eNoMatchingRows:
-        pass
-
-    # --------------------------------------------------------------
-    # table deleteRow() call
-
-    row = tbl.fetchRow( ('agent_id',2) )
-    tbl.deleteRow( ('agent_id', 2) )
-    try:
-        row = tbl.fetchRow( ('agent_id',2) )
-        raise "table delete failed"
-    except eNoMatchingRows:
-        pass
-
-    # --------------------------------------------------------------
-    # table deleteRow() call
-
-    row = tbl.fetchRow( ('agent_id',3) )
-    if row.databaseSizeForColumn('name') != len(row.name):
-        raise "databaseSizeForColumn('name') failed"
-    
-    # --------------------------------------------------------------
-    # test inc fields
-    row = tbl.newRow()
-    new_id = 1092
-    row.name = "name #%d" % new_id
-    row.login = "name%d" % new_id
-    row.ext_email = "%d@name" % new_id
-    row.inc('ticket_count')
-    row.save()
-    new_id = row.agent_id
-
-    trow = tbl.fetchRow( ('agent_id',new_id) )
-    if trow.ticket_count != 1:
-        raise "ticket_count didn't inc!"
-
-    row.inc('ticket_count', count=2)
-    row.save()
-    trow = tbl.fetchRow( ('agent_id',new_id) )
-    if trow.ticket_count != 3:
-        raise "ticket_count wrong, expected 3, got %d" % trow.ticket_count
-
-    trow.inc('ticket_count')
-    trow.save()
-    if trow.ticket_count != 4:
-        raise "ticket_count wrong, expected 4, got %d" % trow.ticket_count
-
-    output("\n==== ALL TESTS PASSED ====")
-    
-
-if __name__ == "__main__":
-    TEST()
-
diff -Nru clearsilver-0.9.1/python/examples/trans/README clearsilver-0.9.2/python/examples/trans/README
--- clearsilver-0.9.1/python/examples/trans/README	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.2/python/examples/trans/README	Thu Aug 14 00:15:12 2003
@@ -0,0 +1,118 @@
+*********************************
+*
+* trans.py - language extraction toolkit
+*
+* - Brandon Long, David Jeske
+*
+
+** Getting Started
+
+1) First you need to create a MySQL trans database for your application.
+
+  > mysql -uroot
+  mysql> create database trans_data
+  mysql> quit
+  > mysql -uroot trans_data < trans.sql
+
+2) Then just run trans on the test data to verify that it is working..
+
+  > ./trans.py
+
+   Check out the 'testroot/gen' directory.
+
+3) Then, in your application, make sure your CSPage setup equivilant is
+   doing the following:
+
+   1) Make sure "gen/tmpl/<lang>" and "gen/tmpl" are in hdf.loadpaths
+      before your template directories. This assures that the load will
+      prefer files in the language specific template directory first,
+      the language directory second, and your template directories last.
+
+   2) after loading your page-specific HDF file, be sure to 
+      load "lang_<lang>.hdf" over the top. This will automatically
+      override HDF lang strings with the proper values from the translated
+      language files.
+
+4) Configure trans.py in the __init__ function (sorry for the hardcoded
+   stuff)
+
+5) When you want to translate into a new language, take the
+   'strings_en.hdf' file and copy it to trans_XX.hdf. Translate
+   the strings in it, and then run:
+
+   > ./trans.py --lang XX --load trans_XX.hdf
+   > ./trans.py
+
+   Each time trans is run, it will dump the list of missing strings for
+   every language which has any strings at all. Simply follow the same
+   procedure above with the missing strings file to update the
+   translation in that language.
+
+
+** About trans.py
+
+This tools allows you to (mostly) automatically extract language
+strings from HTML Clearsilver templates, and from Clearsilver HDF 
+files. trans inserts all unique strings into a database, and
+provides you facilities for exporting and importing the strings in the
+database. trans then creates a 'gen' tree where your source files
+reference language strings, and dumps lang_XX.hdf files which
+define those strings. 
+
+
+Two mechanisms are used to find language strings:
+
+1) Any language string present in an HDF file should be marked
+   with the [Lang] attribute. For example:
+
+     Menu.Name [Lang] = Start Here
+ 
+   Trans will automatically replace this with a copy reference to
+   the lang hash-keyed string in the currently imported language.
+
+     Menu.Name : Lang.L112414
+
+2) Parsing of html attempts to find language strings automatically.
+   This allows you to leave most of your language strings in-tact
+   in your primary language, making working on your application
+   much easier.
+ 
+   Some parts of HTML structure, including some tag attributes and 
+   javascript, are too complicated for trans to automatically do
+   the right thing. In these cases, you must manually extract
+   the string into an HDF file and then reference it in your HTML.
+   
+   For example, in the following HTML/Clearsilver fragment,
+   the heading will be automatically identified, but the submit 
+   button title cannot be extracted by trans safetly.
+
+    <html>
+    <body>
+    <h1> Send us your Feedback </h1>
+    <form>
+    <input type=text name=feedback>
+    <input type=submit name="Action.Submit" 
+           value="Send Feedback">
+    </form>
+    </body>
+    </html>
+
+   You must convert the above fragment into something like this:
+
+    <html>
+    <body>
+    <h1> Send us your Feedback </h1>
+    <form>
+    <input type=text name=feedback>
+    <input type=submit name="Action.Submit" 
+           value="<?cs var:Lang.SendFeedback ?>">
+    </form>
+    </body>
+    </html>
+
+   The "Lang.SendFeedback" item must be declared in your static page
+   HDF, and must be marked with the '[Lang]' attribute.
+
+    Lang.SendFeedback [Lang] = Send Feedback
+
+
diff -Nru clearsilver-0.9.1/python/examples/trans/TODO clearsilver-0.9.2/python/examples/trans/TODO
--- clearsilver-0.9.1/python/examples/trans/TODO	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.2/python/examples/trans/TODO	Thu Aug 14 00:18:05 2003
@@ -0,0 +1,22 @@
+******************************
+**
+** trans.py TODO
+**
+** David Jeske
+
+- create 'translation configuration file' which gets placed
+  in application root or template root directory. This should
+  specify all paramaters which are currently hardcoded in
+  trans.py.
+  
+  Require the user to supply this configuration file each time
+  trans is called.
+
+- extend database schema to handle more than one application
+  in a single trans database.
+
+- make web UI for browsing trans database
+
+- clearsilver/trans 'z-tag live language QA system' like
+  we had at egroups
+
diff -Nru clearsilver-0.9.1/python/examples/trans/db_trans.py clearsilver-0.9.2/python/examples/trans/db_trans.py
--- clearsilver-0.9.1/python/examples/trans/db_trans.py	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.2/python/examples/trans/db_trans.py	Thu Aug 14 00:07:02 2003
@@ -0,0 +1,82 @@
+
+
+from odb import *
+import profiler
+import socket
+
+USER = 'root'
+PASSWORD = ''
+DATABASE = 'trans_data'
+
+class TransStringTable(Table):
+    # access patterns:
+    #   -> lookup individual entries by string_id
+    #   -> lookup entry by string
+    def _defineRows(self):
+        self.d_addColumn("string_id", kInteger, primarykey=1, autoincrement=1)
+        # we can't actually index this... but we can index part with myisam
+        self.d_addColumn("string", kBigString, indexed=1)
+
+## hmm, on second thought, storing this is in the database is kind 
+## of silly..., since it essentially could change with each run.  It may
+## not even be necessary to store this anywhere except in memory while 
+## trans is running
+class TransLocTable(Table):
+    # access patterns:
+    #   -> find "same" entry by filename/offset
+    #   -> dump all locations for a version
+    #   -> maybe: find all locations for a filename
+    def _defineRows(self):
+        self.d_addColumn("loc_id", kInteger, primarykey=1, autoincrement=1)
+        self.d_addColumn("string_id", kInteger, indexed=1)
+        self.d_addColumn("version", kInteger, default=0)
+        self.d_addColumn("filename", kVarString, 255, indexed=1)
+        self.d_addColumn("location", kVarString, 255)
+        # this can either be:
+        # ofs:x:y
+        # hdf:foo.bar.baz
+
+class TransMapTable(Table):
+    # access patterns:
+    #   -> dump all for a language
+    #   -> lookup entry by string_id/lang
+    def _defineRows(self):
+        self.d_addColumn("string_id", kInteger, primarykey=1)
+        self.d_addColumn("lang", kFixedString, 2, primarykey=1)
+        self.d_addColumn("string", kBigString)
+
+class DB(Database):
+    def __init__(self, db, debug=0):
+	self.db = db
+        self._cursor = None
+        self.debug = debug
+
+        self.strings = TransStringTable(self, "nt_trans_strings")
+        self.locs = TransLocTable(self, "nt_trans_locs")
+        self.maps = TransMapTable(self, "nt_trans_maps")
+
+    def defaultCursor(self):
+        # share one cursor for this db object!
+        if self._cursor is None:
+            if self.debug:
+                self._cursor = profiler.ProfilerCursor(self.db.cursor())
+            else:
+                self._cursor = self.db.cursor()
+
+        return self._cursor
+
+def trans_connect(host = 'localhost', debug=0):
+    # try to optimize connection if on this machine
+    if host != 'localhost':
+        local_name = socket.gethostname()
+        if string.find(local_name, '.') == -1:
+            local_name = local_name + ".neotonic.com"
+        if local_name == host:
+            host = 'localhost'
+
+    if debug: p = profiler.Profiler("SQL", "Connect -- %s:trans" % (host))
+    db = MySQLdb.connect(host = host, user=USER, passwd = PASSWORD, db=DATABASE)
+    if debug: p.end()
+
+    retval = DB(db, debug=debug)
+    return retval
diff -Nru clearsilver-0.9.1/python/examples/trans/testroot/tmpl/test1.cs clearsilver-0.9.2/python/examples/trans/testroot/tmpl/test1.cs
--- clearsilver-0.9.1/python/examples/trans/testroot/tmpl/test1.cs	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.2/python/examples/trans/testroot/tmpl/test1.cs	Thu Aug 14 00:07:03 2003
@@ -0,0 +1,8 @@
+<html>
+<body>
+
+<p>
+This is an html file language string which should be translated..</p>
+
+</body>
+</html>
diff -Nru clearsilver-0.9.1/python/examples/trans/testroot/tmpl/test1.hdf clearsilver-0.9.2/python/examples/trans/testroot/tmpl/test1.hdf
--- clearsilver-0.9.1/python/examples/trans/testroot/tmpl/test1.hdf	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.2/python/examples/trans/testroot/tmpl/test1.hdf	Thu Aug 14 00:07:03 2003
@@ -0,0 +1,5 @@
+
+hdfdata = This is hdf data, not language data
+
+hdflang [Lang] = This is a language string and should be translated
+
diff -Nru clearsilver-0.9.1/python/examples/trans/trans.py clearsilver-0.9.2/python/examples/trans/trans.py
--- clearsilver-0.9.1/python/examples/trans/trans.py	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.2/python/examples/trans/trans.py	Thu Aug 14 00:15:12 2003
@@ -0,0 +1,594 @@
+#!/neo/opt/bin/python
+
+import sys, string, os, getopt, pwd, signal, time, re
+import fcntl
+
+import tstart
+
+import db_trans
+from log import *
+import neo_cgi, neo_util
+import odb
+
+eTransError = "eTransError"
+
+DONE = 0
+DEBUG = 0
+
+TIER2_DIV = 11
+TIER1_DIV = 11 * TIER2_DIV
+
+if not DEBUG: LOGGING_STATUS[DEV_UPDATE] = 0
+
+def handleSignal(*arg):
+  global DONE
+  DONE = 1
+
+def usage():
+  print "usage info!!"
+
+def exceptionString():
+  import StringIO, traceback
+
+  ## get the traceback message  
+  sfp = StringIO.StringIO()
+  traceback.print_exc(file=sfp)
+  exception = sfp.getvalue()
+  sfp.close()
+
+  return exception
+
+class TransLoc:
+    def __init__ (self, string_id, filename, location):
+        self.string_id = string_id
+        self.filename = filename
+        self.location = location
+
+class Translator:
+    _HTML_TAG_RE = None
+    _HTML_TAG_REGEX = '<[^!][^>]*?>'
+    _HTML_CMT_RE = None
+    _HTML_CMT_REGEX = '<!--.*?-->'
+    _CS_TAG_RE = None
+    _CS_TAG_REGEX = '<\\?.+?\\?>'
+
+    def __init__ (self):
+        self.tdb = db_trans.trans_connect()
+
+        # configuration data ......
+        #  - we should stop hardcoding this... - jeske
+        
+        self.root = "testroot"
+        self.languages = ['es', 'en'] 
+
+        self.ignore_paths = ['tmpl/m']  # common place for mockups
+        self.ignore_files = ['blah_ignore.cs'] # ignore clearsilver file
+
+        # ignore clearsilver javascript files
+        self.ignore_patterns = ['tmpl/[^ ]*_js.cs'] 
+
+        # ............................
+
+
+        if self.root is None:
+            raise "Unable to determine installation root"
+
+
+        if Translator._HTML_TAG_RE is None:
+            Translator._HTML_TAG_RE = re.compile(Translator._HTML_TAG_REGEX, re.MULTILINE | re.DOTALL)
+        if Translator._HTML_CMT_RE is None:
+            Translator._HTML_CMT_RE = re.compile(Translator._HTML_CMT_REGEX, re.MULTILINE | re.DOTALL)
+        if Translator._CS_TAG_RE is None:
+            Translator._CS_TAG_RE = re.compile(Translator._CS_TAG_REGEX, re.MULTILINE | re.DOTALL)
+
+        self._html_state = 0
+
+       
+    def parseHTMLTag(self, data):
+        # this is only called if we see a full tag in one parse...
+        i = 0
+        if len(data) == 0: return []
+        if data[0] in '/?': return []
+        while i < len(data) and data[i] not in ' \n\r\t>': i = i + 1
+        if i == len(data): return []
+        tag = data[:i].lower()
+        #print "Searching tag: %s" % data
+        #print "Found tag: %s" % tag
+        results = []
+        attrfind = re.compile(
+            r'\s*([a-zA-Z_][-.a-zA-Z_0-9]*)(\s*=\s*'
+            r'(\'[^\']*\'|"[^"]*"|[^ \t\n<>]*))?')
+        k = i
+        attrs = {}
+        attrs_beg = {}
+        while k < len(data):
+            match = attrfind.match(data, k)
+            if not match: break
+            attrname, rest, attrvalue = match.group(1, 2, 3)
+            if not rest:
+               attrvalue = attrname
+            elif attrvalue[:1] == '\'' == attrvalue[-1:] or \
+                 attrvalue[:1] == '"' == attrvalue[-1:]:
+               attrvalue = attrvalue[1:-1]
+            attrname = attrname.lower()
+            if attrs.has_key(attrname):
+                log("Can't handle duplicate attrs: %s" % attrname)
+            attrs[attrname] = attrvalue
+            attrs_beg[attrname] = match.start(3)
+            k = match.end(0)
+
+        find_l = []
+        if tag == "input":
+            if attrs.get('type', "").lower() in ["submit", "button"]:
+                find_l.append((attrs.get('value', ''), attrs_beg.get('value', 0)))
+
+        for s,k in find_l:
+            if s:
+                x = data[k:].find(s)
+                if x != -1: results.append((s, x+k, 1))
+
+        return results
+
+    def parseHTML(self, data, reset=1):
+        if reset: self._html_state = 0
+        if DEBUG: print "- %d ---------\n%s\n- E ---------" % (self._html_state, data)
+
+        results = []
+        i = 0
+        n = len(data)
+        # if we had state from the last parse... find it
+        if self._html_state:
+            if self._html_state == 2:
+                x = string.find(data[i:], '-->')
+                l = 3
+            else:
+                x = string.find(data[i:], '>')
+                l = 1
+            if x == -1: return results
+            i = i + x + l
+            self._html_state = 0
+        while i < n:
+            if DEBUG: print "MATCHING>%s<MATCHING" % data[i:]
+            cmt_b = string.find(data[i:], '<!--')
+            cmt_e = string.find(data[i:], '-->')
+            tag_b = string.find(data[i:], '<')
+            tag_e = string.find(data[i:], '>')
+            if DEBUG: print "B> %d %d %d %d <B" % (cmt_b, cmt_e, tag_b, tag_e)
+            if cmt_b != -1 and cmt_b <= tag_b:
+                x = i
+                y = i+cmt_b-1
+                while x < y and data[x] in string.whitespace: x+=1
+                while y > x and data[y] in string.whitespace: y-=1
+                results.append((data[x:y+1], x, 1))
+                if cmt_e == -1: # partial comment:
+                    self._html_state = 2
+                    break
+                i = i + cmt_e + 3
+            elif tag_b != -1:
+                x = i
+                y = i+tag_b-1
+                while x < y and data[x] in string.whitespace: x+=1
+                while y > x and data[y] in string.whitespace: y-=1
+                results.append((data[x:y+1], x, 1))
+                if tag_e == -1: # partial tag
+                    self._html_state = 1
+                    break
+                h_results = self.parseHTMLTag(data[i+tag_b+1:i+tag_e])
+                h_results = map(lambda x: (x[0], x[1] + i+tag_b+1, x[2]), h_results)
+                results = results + h_results
+                i = i + tag_e + 1
+            else:
+                x = i
+                y = n-1 
+                while x < y and data[x] in string.whitespace: x+=1
+                while y > x and data[y] in string.whitespace: y-=1
+                results.append((data[x:y+1], x, 1))
+                break
+        return results
+
+    def parseCS(self, data):
+        results = []
+        i = 0
+        n = len(data)
+        while i < n:
+            m = Translator._CS_TAG_RE.search(data, i)
+            if not m:
+                # search for a partial...
+                x = string.find(data[i:], '<?')
+                if x == -1:
+                    results.append((data[i:], i))
+                else:
+                    results.append((data[i:x], i))
+                break
+            (b, e) = m.span()
+            if i != b: results.append((data[i:b], i))
+            i = e 
+        t_results = []
+        self._html_in = 0
+        for (s, ofs) in results:
+            r = self.parseHTML(s, reset=0)
+            r = map(lambda x: (x[0], x[1] + ofs, x[2]), r)
+            t_results = t_results + r
+        return t_results
+
+    def descendHDF(self, obj, prefix):
+        results = []
+        while obj is not None:
+            if obj.value():
+                attrs = obj.attrs()
+                attrs = map(lambda x: x[0], attrs)
+                if "Lang" in attrs:
+                    if prefix:
+                        results.append((obj.value(), "%s.%s" % (prefix, obj.name()), 0))
+                    else:
+                        results.append((obj.value(), "%s" % (obj.name()), 0))
+            if obj.child():
+                if prefix:
+                    results = results + self.descendHDF(obj.child(), "%s.%s" % (prefix, obj.name()))
+                else:
+                    results = results + self.descendHDF(obj.child(), (obj.name()))
+            obj = obj.next()
+        return results
+
+    def parseHDF(self, data):
+        # Ok, we handle HDF files specially.. the theory is, we only
+        # extract entire HDF elements which have the attribute Lang
+        hdf = neo_util.HDF()
+        hdf.readString(data, 1)
+        return self.descendHDF(hdf, "")
+
+    def handleFile(self, file):
+        if file in self.ignore_files: return []
+        for a_re in self.ignore_patterns:
+            if re.match(a_re,file): 
+                return []
+        fpath = self.root + '/' + file
+        x = string.rfind(file, '.')
+        if x == -1: return []
+        data = open(fpath, 'r').read()
+        ext = file[x:]
+        strings = []
+        if ext in ['.cst', '.cs']:
+            strings = self.parseCS(data)
+        elif ext in ['.html', '.htm']:
+            strings = self.parseHTML(data)
+        elif ext in ['.hdf']:
+            strings = self.parseHDF(data)
+        if len(strings):
+            print "Found %d strings in %s" % (len(strings), file)
+            return strings
+        return []
+
+    def walkDirectory(self, path):
+        if path in self.ignore_paths: return []
+        fpath = self.root + '/' + path
+        files = os.listdir(fpath)
+        dirs = []
+        results = []
+        for file in files:
+            if file[0] == '.': continue
+            fname = fpath + '/' + file
+            if os.path.isdir(fname):
+                dirs.append(file)
+            else:
+                strings = self.handleFile(path + '/' + file)
+                if len(strings):
+                    results.append((path + '/' + file, strings))
+        for dir in dirs:
+            if dir not in ["release"]:
+                results = results + self.walkDirectory(path + '/' + dir)
+        return results
+
+    def cleanHtmlString(self, s):
+        s = re.sub("\s+", " ", s)
+        return string.strip(s)
+
+    def containsWords(self, s, ishtml):
+        if ishtml:
+            s = string.replace(s, '&nbsp;', ' ')
+            s = string.replace(s, '&quot;', '"')
+            s = string.replace(s, '&copy;', '')
+            s = string.replace(s, '&lt;', '<')
+            s = string.replace(s, '&gt;', '>')
+            s = string.replace(s, '&amp;', '&')
+        for x in range (len (s)):
+          n = ord(s[x])
+          if (n>47 and n<58) or (n>64 and n<91) or (n>96 and n<123): return 1
+        return 0
+        
+    def findString(self, s):
+        rows = self.tdb.strings.fetchRows( ('string', s) )
+        if len(rows) == 0:
+            row = self.tdb.strings.newRow()
+            row.string = s
+            row.save()
+            return row.string_id
+        elif len(rows) > 1:
+            raise eTransError, "String %s exists multiple times!" % s
+        else:
+            return rows[0].string_id
+
+    def loadStrings(self, one_file=None, verbose=0):
+        if one_file is not None:
+            strings = self.handleFile(one_file)
+            results = [(one_file, strings)]
+        else:
+            results = self.walkDirectory('tmpl')
+        uniq = {}
+        cnt = 0
+        seen_hdf = {}
+        for fname, strings in results:
+            for (s, ofs, ishtml) in strings:
+                if s and string.strip(s):
+                    l = len(s)
+                    if ishtml:
+                        s = self.cleanHtmlString(s)
+                    if self.containsWords(s, ishtml):
+                        if type(ofs) == type(""): # HDF
+                            if seen_hdf.has_key(ofs):
+                                if seen_hdf[ofs][0] != s:
+                                    log("Duplicate HDF Name %s:\n\t file %s = %s\n\t file %s = %s" % (ofs, seen_hdf[ofs][1], seen_hdf[ofs][0], fname, s))
+                            else:
+                                seen_hdf[ofs] = (s, fname)
+                        try:
+                            uniq[s].append((fname, ofs, l))
+                        except KeyError:
+                            uniq[s] = [(fname, ofs, l)]
+                        cnt = cnt + 1
+        print "%d strings, %d unique" % (cnt, len(uniq.keys()))
+        fp = open("map", 'w')
+        for (s, locs) in uniq.items():
+            locs = map(lambda x: "%s:%s:%d" % x, locs)
+            fp.write('#: %s\n' % (string.join(locs, ',')))
+            fp.write('msgid=%s\n\n' % repr(s))
+
+        log("Loading strings/locations into database")
+        locations = []
+        for (s, locs) in uniq.items():
+            s_id = self.findString(s)
+            for (fname, ofs, l) in locs:
+                if type(ofs) == type(""): # ie, its HDF
+                    location = "hdf:%s" % ofs
+                else:
+                    location = "ofs:%d:%d" % (ofs, l)
+                loc_r = TransLoc(s_id, fname, location)
+                locations.append(loc_r)
+        return locations
+
+    def stringsHDF(self, prefix, locations, lang='en', exist=0, tiered=0):
+        hdf = neo_util.HDF()
+        if exist and lang == 'en': return hdf
+        done = {}
+        locations.sort()
+        maps = self.tdb.maps.fetchRows( ('lang', lang) )
+        maps_d = {}
+        for map in maps:
+            maps_d[int(map.string_id)] = map
+        strings = self.tdb.strings.fetchRows()
+        strings_d = {}
+        for string in strings:
+            strings_d[int(string.string_id)] = string
+        count = 0
+        for loc in locations:
+            s_id = int(loc.string_id)
+            if done.has_key(s_id): continue
+            try:
+                s_row = maps_d[s_id]
+                if exist: continue
+            except KeyError:
+                try:
+                    s_row = strings_d[s_id]
+                except KeyError:
+                    log("Missing string_id %d, skipping" % s_id)
+                    continue
+            count = count + 1
+            if tiered:
+                hdf.setValue("%s.%d.%d.%s" % (prefix, int(s_id) / TIER1_DIV, int(s_id) / TIER2_DIV, s_id), s_row.string)
+            else:
+                hdf.setValue("%s.%s" % (prefix, s_id), s_row.string)
+            done[s_id] = 1
+        if exist == 1: log("Missing %d strings for lang %s" % (count, lang))
+        return hdf
+
+    def dumpStrings(self, locations, lang=None):
+        log("Dumping strings to HDF")
+        if lang is None:
+            langs = ['en']
+            sql = "select lang from nt_trans_maps group by lang"
+            cursor = self.tdb.defaultCursor()
+            cursor.execute(sql)
+            rows = cursor.fetchall()
+            for row in rows:
+                langs.append(row[0])
+        else:
+            langs = [lang]
+
+        for a_lang in langs:
+            hdf = self.stringsHDF('S', locations, a_lang)
+            hdf.writeFile("strings_%s.hdf" % a_lang)
+
+        for a_lang in langs:
+            hdf = self.stringsHDF('S', locations, a_lang, exist=1)
+            if hdf.child():
+                hdf.writeFile("strings_missing_%s.hdf" % a_lang)
+
+    def fetchString(self, s_id, lang):
+        if lang == "hdf":
+            return "<?cs var:Lang.Extracted.%d.%d.%s ?>" % (int(s_id) / TIER1_DIV, int(s_id) / TIER2_DIV, s_id)
+        rows = self.tdb.maps.fetchRows( [('string_id', s_id), ('lang', lang)] )
+        if len(rows) == 0:
+            try:
+                row = self.tdb.strings.fetchRow( ('string_id', s_id) )
+            except odb.eNoMatchingRows:
+                log("Unable to find string id %s" % s_id)
+                raise eNoString
+            if lang != 'en':
+                log("Untranslated string for id %s" % s_id)
+            return row.string
+        else:
+            return rows[0].string
+
+    def dumpFiles(self, locations, lang):
+        log("Dumping files for %s" % lang)
+        files = {}
+        for row in locations:
+            try:
+                files[row.filename].append(row)
+            except KeyError:
+                files[row.filename] = [row]
+
+        hdf_map = []
+
+        os.system("rm -rf %s/gen/tmpl" % (self.root))
+        for file in files.keys():
+            fname = "%s/gen/%s" % (self.root, file)
+            try:
+                os.makedirs(os.path.dirname(fname))
+            except OSError, reason:
+                if reason[0] != 17:
+                    raise
+            do_hdf = 0
+            x = string.rfind(file, '.')
+            if x != -1 and file[x:] == '.hdf':
+                do_hdf = 1
+            ofs = []
+            for loc in files[file]:
+                parts = string.split(loc.location, ':')
+                if len(parts) == 3 and parts[0] == 'ofs' and do_hdf == 0: 
+                    ofs.append((int(parts[1]), int(parts[2]), loc.string_id))
+                elif len(parts) == 2 and parts[0] == 'hdf' and do_hdf == 1:
+                    hdf_map.append((parts[1], loc.string_id))
+                else:
+                    log("Invalid location for loc_id %s" % loc.loc_id)
+                    continue
+            if not do_hdf:
+                ofs.sort()
+                data = open(self.root + '/' + file).read()
+                # ok, now we split up the original data into sections
+                x = 0
+                n = len(data)
+                out = []
+                #sys.stderr.write("%s\n" % repr(ofs))
+                while len(ofs):
+                    if ofs[0][0] > x:
+                        out.append(data[x:ofs[0][0]])
+                        x = ofs[0][0]
+                    elif ofs[0][0] == x:
+                        out.append(self.fetchString(ofs[0][2], lang))
+                        x = ofs[0][0] + ofs[0][1]
+                        ofs = ofs[1:]
+                    else:
+                        log("How did we get here? %s x=%d ofs=%d sid=%d" % (file, x, ofs[0][0], ofs[0][2]))
+                        log("Data[x:20]: %s" % data[x:20])
+                        log("Data[ofs:20]: %s" % data[ofs[0][0]:20])
+                        break
+                if n > x:
+                    out.append(data[x:])
+                odata = string.join(out, '')
+                open(fname, 'w').write(odata)
+
+        if lang == "hdf":
+            langs = self.languages
+        else:
+            langs = [lang]
+
+        for d_lang in langs:
+          # dumping the extracted strings
+          hdf = self.stringsHDF('Lang.Extracted', locations, d_lang, tiered=1)
+          fname = "%s/gen/tmpl/lang_%s.hdf" % (self.root, d_lang)
+          hdf.writeFile(fname)
+          data = open(fname).read()
+          fp = open(fname, 'w')
+          fp.write('## AUTOMATICALLY GENERATED -- DO NOT EDIT\n\n')
+          fp.write(data)
+          fp.write('\n#include "lang_map.hdf"\n')
+
+          # dumping the hdf strings file
+          if d_lang == "en":
+            map_file = "%s/gen/tmpl/lang_map.hdf" % (self.root)
+          else:
+            map_file = "%s/gen/tmpl/%s/lang_map.hdf" % (self.root, d_lang)
+          try:
+              os.makedirs(os.path.dirname(map_file))
+          except OSError, reason: 
+              if reason[0] != 17: raise
+          map_hdf = neo_util.HDF()
+          for (name, s_id) in hdf_map:
+              str = hdf.getValue('Lang.Extracted.%d.%d.%s' % (int(s_id) / TIER1_DIV, int(s_id) / TIER2_DIV, s_id), '')
+              map_hdf.setValue(name, str)
+          map_hdf.writeFile(map_file)
+
+    def loadMap(self, file, prefix, lang):
+        log("Loading map for language %s" % lang)
+        hdf = neo_util.HDF()
+        hdf.readFile(file)
+        obj = hdf.getChild(prefix)
+        updates = 0
+        new_r = 0
+        while obj is not None:
+            s_id = obj.name()
+            str = obj.value()
+
+            try:
+                map_r = self.tdb.maps.fetchRow( [('string_id', s_id), ('lang', lang)])
+            except odb.eNoMatchingRows:
+                map_r = self.tdb.maps.newRow()
+                map_r.string_id = s_id
+                map_r.lang = lang
+                new_r = new_r + 1
+
+            if map_r.string != str:
+                updates = updates + 1
+                map_r.string = str
+                map_r.save()
+
+            obj = obj.next()
+        log("New maps: %d  Updates: %d" % (new_r, updates - new_r))
+        
+
+def main(argv):
+  alist, args = getopt.getopt(argv[1:], "f:v:", ["help", "load=", "lang="])
+
+  one_file = None
+  verbose = 0
+  load_file = None
+  lang = 'en'
+  for (field, val) in alist:
+    if field == "--help":
+      usage(argv[0])
+      return -1
+    if field == "-f":
+      one_file = val
+    if field == "-v":
+      verbose = int(val)
+    if field == "--load":
+        load_file = val
+    if field == "--lang":
+        lang = val
+        
+
+  global DONE
+
+  #signal.signal(signal.SIGTERM, handleSignal)
+  #signal.signal(signal.SIGINT, handleSignal)
+
+  log("trans: start")
+
+  start_time = time.time()
+
+  try:
+    t = Translator()
+    if load_file:
+        t.loadMap(load_file, 'S', lang)
+    else:
+        locations = t.loadStrings(one_file, verbose=verbose)
+        t.dumpStrings(locations)
+        t.dumpFiles(locations, 'hdf')
+  except KeyboardInterrupt:
+    pass
+  except:
+    import handle_error
+    handle_error.handleException("Translation Error")
+
+if __name__ == "__main__":
+  main(sys.argv)
diff -Nru clearsilver-0.9.1/python/examples/trans/trans.sql clearsilver-0.9.2/python/examples/trans/trans.sql
--- clearsilver-0.9.1/python/examples/trans/trans.sql	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.2/python/examples/trans/trans.sql	Sun Aug 17 10:09:49 2003
@@ -0,0 +1,26 @@
+create table nt_trans_strings (
+  string_id integer not null primary key auto_increment,
+  string text,
+
+  index(string(150))
+);
+
+create table nt_trans_locs (
+  loc_id integer not null primary key auto_increment,
+  string_id integer not null,
+  version integer default 0,
+  filename varchar(255),
+  location varchar(255),
+
+  index(string_id),
+  index(filename)
+) TYPE=INNODB;
+
+create table nt_trans_maps (
+  map_id integer not null primary key auto_increment,
+  string_id integer not null,
+  lang char(2),
+  string text,
+
+  index(string_id)
+) TYPE=INNODB;
diff -Nru clearsilver-0.9.1/python/examples/trans/tstart.py clearsilver-0.9.2/python/examples/trans/tstart.py
--- clearsilver-0.9.1/python/examples/trans/tstart.py	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.2/python/examples/trans/tstart.py	Sun Aug 17 09:50:07 2003
@@ -0,0 +1,13 @@
+# this starts up the T-environment...
+# 
+# The root dir should point to the top of the python tree
+
+
+import sys
+
+sys.path.insert(0, "../base") # pickup base libs
+
+sys.path.insert(0, "../../") # pickup neo_cgi.so
+
+# don't put anything above this because the path isn't
+# extended yet...
diff -Nru clearsilver-0.9.1/python/examples/who_calls.py clearsilver-0.9.2/python/examples/who_calls.py
--- clearsilver-0.9.1/python/examples/who_calls.py	Sun Jun 15 18:54:49 2003
+++ clearsilver-0.9.2/python/examples/who_calls.py	Wed Dec 31 16:00:00 1969
@@ -1,141 +0,0 @@
-
-# who_calls.py
-# by Sam Rushing for Medusa
-
-import string
-import sys
-
-from log import *
-
-whoCallsError = "whoCallsError"
-
-#--------------------------------------------------------------
-# Example use:
-#
-# import who_calls
-# log(who_calls.pretty_who_calls())
-#
-#--------------------------------------------------------------
-
-def test():
-  for i in range(1,1000):
-    pretty_who_calls()
-
-  print_top_100()
-
-def who_calls_helper():
-  tinfo = []
-  exc_info = sys.exc_info()
-
-  f = exc_info[2].tb_frame.f_back
-  while f:
-	  tinfo.append ( (
-		  f.f_code.co_filename,
-		  f.f_code.co_name,
-		  f.f_lineno )
-		  )
-	  f = f.f_back
-
-  del exc_info
-  return tinfo
-  
-
-def who_calls():
-  try:
-    raise whoCallsError
-  except whoCallsError:
-    tinfo = who_calls_helper()
-  return tinfo
-
-def pretty_who_calls(strip=0):
-	info = who_calls()
-	buf = []
-
-	for file,function,line in info[1 + strip:]:
-		buf.append("   %s(%s): %s()" % (file,line,function))
-		
-	return string.join(buf,"\n")
-
-# ---------------------------------------------------------------------------
-# used for debugging.
-# ---------------------------------------------------------------------------
-
-def compact_traceback ():
-	t,v,tb = sys.exc_info()
-	tbinfo = []
-	if tb is None:
-		# this should never happen, but then again, lots of things
-		# should never happen but do.
-		return (('','',''), str(t), str(v), 'traceback is None!!!')
-	while 1:
-		tbinfo.append (
-			tb.tb_frame.f_code.co_filename,
-			tb.tb_frame.f_code.co_name,				
-			str(tb.tb_lineno)
-			)
-		tb = tb.tb_next
-		if not tb:
-			break
-
-	# just to be safe
-	del tb
-
-	file, function, line = tbinfo[-1]
-	info = '[' + string.join (
-		map (
-			lambda x: string.join (x, '|'),
-			tbinfo
-			),
-		'] ['
-		) + ']'
-
-	return (file, function, line), str(t), str(v), info
-
-## ----------------------------------------------------
-## Refcount printing
-		
-import sys
-import types
-
-def real_get_refcounts(base = None, set_base = 0):
-    d = {}
-    sys.modules
-    # collect all classes
-    for modname,m in sys.modules.items():
-        for sym in dir(m):
-            o = getattr (m, sym)
-            if type(o) is types.ClassType:
-                name = "%s:%s" % (modname,o.__name__)
-                cnt = sys.getrefcount (o)
-                if base:
-                    if set_base:
-                        base[name] = cnt
-                    elif cnt > base.get(name, 0):
-                        d[name] = cnt - base.get(name, 0)
-                else:
-                    d[name] = cnt
-    return d
-
-def get_refcounts(base=None):
-        d = real_get_refcounts(base = base)
-        # sort by refcount
-        pairs = map (lambda x: (x[1],x[0]), d.items())
-        pairs.sort()
-        pairs.reverse()
-        return pairs
-
-REFCOUNTS = {}
-
-def set_refcount_base():
-    global REFCOUNTS
-    real_get_refcounts(REFCOUNTS, set_base = 1)
-
-def print_top_100():
-        print_top_N(100)
-
-def print_top_N(N):
-    global REFCOUNTS
-    for n, c in get_refcounts(REFCOUNTS)[:N]:
-       log('%10d %s' % (n, c))
-
-
diff -Nru clearsilver-0.9.1/python/neo_cgi.c clearsilver-0.9.2/python/neo_cgi.c
--- clearsilver-0.9.1/python/neo_cgi.c	Mon Apr 14 16:05:11 2003
+++ clearsilver-0.9.2/python/neo_cgi.c	Thu Jul 17 12:44:09 2003
@@ -303,6 +303,20 @@
   return PyFile_FromFile (fp, name, "w+", NULL);
 }
 
+static PyObject * p_cgi_cs_init (PyObject *self, PyObject *args)
+{
+  CGI *cgi = ((CGIObject *) self)->cgi;
+  NEOERR *err;
+  CSPARSE *cs;
+
+  if (!PyArg_ParseTuple(args, ":cs()"))
+    return NULL;
+
+  err = cgi_cs_init(cgi, &cs);
+  if (err) return p_neo_error (err);
+  return p_cs_to_object(cs);
+}
+
 static PyMethodDef CGIMethods[] =
 {
 #if 0
@@ -319,6 +333,7 @@
   {"cookieSet", (PyCFunction)p_cgi_cookie_set, METH_VARARGS|METH_KEYWORDS, NULL},
   {"cookieClear", p_cgi_cookie_clear, METH_VARARGS, NULL},
   {"filehandle", p_cgi_filehandle, METH_VARARGS, NULL},
+  {"cs", p_cgi_cs_init, METH_VARARGS, NULL},
   {NULL, NULL}
 };
 
diff -Nru clearsilver-0.9.1/python/p_neo_util.h clearsilver-0.9.2/python/p_neo_util.h
--- clearsilver-0.9.1/python/p_neo_util.h	Wed Apr  2 17:43:07 2003
+++ clearsilver-0.9.2/python/p_neo_util.h	Thu Jul 17 12:44:09 2003
@@ -34,12 +34,18 @@
 #define P_NEO_ERROR_RETURN PyObject *
 #define P_NEO_ERROR_PROTO (NEOERR *err)
 
-#define P_NEO_CGI_POINTERS 3
+/* external CS object interface */
+#define P_CS_TO_OBJECT_NUM 3
+#define P_CS_TO_OBJECT_RETURN PyObject *
+#define P_CS_TO_OBJECT_PROTO (CSPARSE *data)
+
+#define P_NEO_CGI_POINTERS 4
 
 #ifdef NEO_CGI_MODULE
 P_HDF_TO_OBJECT_RETURN p_hdf_to_object P_HDF_TO_OBJECT_PROTO;
 P_OBJECT_TO_HDF_RETURN p_object_to_hdf P_OBJECT_TO_HDF_PROTO;
 P_NEO_ERROR_RETURN p_neo_error P_NEO_ERROR_PROTO;
+P_CS_TO_OBJECT_RETURN p_cs_to_object P_CS_TO_OBJECT_PROTO;
 
 /* other functions */
 
@@ -57,6 +63,9 @@
 
 #define p_neo_error \
   (*(P_NEO_ERROR_RETURN (*)P_NEO_ERROR_PROTO) NEO_PYTHON_API[P_NEO_ERROR_NUM])
+
+#define p_cs_to_object \
+  (*(P_CS_TO_OBJECT_RETURN (*)P_CS_TO_OBJECT_PROTO) NEO_PYTHON_API[P_CS_TO_OBJECT_NUM])
 
 #define import_neo_cgi() \
 { \
diff -Nru clearsilver-0.9.1/python/setup.py clearsilver-0.9.2/python/setup.py
--- clearsilver-0.9.1/python/setup.py	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.2/python/setup.py	Thu Jul 24 11:36:50 2003
@@ -0,0 +1,85 @@
+
+import os, string, re, sys
+from distutils.core import setup, Extension
+
+VERSION = "0.9.1"
+INC_DIRS = ["../"]
+LIBRARIES = ["neo_cgi", "neo_cs", "neo_utl"]
+LIB_DIRS = ["../libs"]
+
+## ARGGH!!  It looks like you can only specify a single item on the
+## command-line or in the setup.cfg file for options which take multiple
+## lists... and it overrides what is defined here.  So I have to do all
+## the work of the configure file AGAIN here.  At least its in python,
+## which is easier...
+## Actually, forget that, I'm just going to load and parse the rules.mk
+## file and build what I need
+
+if not os.path.exists("../rules.mk"):
+  raise "You need to run configure first to generate the rules.mk file!"
+
+make_vars = {}
+rules = open("../rules.mk").read()
+for line in string.split(rules, "\n"):
+  parts = string.split(line, '=', 1)
+  if len(parts) != 2: continue
+  var, val = parts
+  var = var.strip()
+  make_vars[var] = val
+  if var == "CFLAGS":
+    matches = re.findall("-I(\S+)", val)
+    inserted = []
+    for inc_path in matches:
+      # inc_path = match.group(1)
+      if inc_path not in INC_DIRS:
+      	inserted.append(inc_path)
+	sys.stderr.write("adding inc_path %s\n" % inc_path)
+    INC_DIRS = inserted + INC_DIRS
+  elif var == "LIBS":
+    matches = re.findall("-l(\S+)", val)
+    inserted = []
+    for lib in matches:
+      # lib = match.group(1)
+      if lib not in LIBRARIES:
+      	inserted.append(lib)
+	sys.stderr.write("adding lib %s\n" % lib)
+    LIBRARIES = inserted + LIBRARIES
+  elif var == "LDFLAGS":
+    matches = re.findall("-L(\S+)", val)
+    inserted = []
+    for lib_path in matches:
+      # lib_path = match.group(1)
+      if lib_path not in LIB_DIRS:
+      	inserted.append(lib_path)
+	sys.stderr.write("adding lib_path %s\n" % lib_path)
+    LIB_DIRS = inserted + LIB_DIRS
+
+def expand_vars(vlist, vars):
+  nlist = []
+  for val in vlist:
+    if val[:2] == "$(" and val[-1] == ")":
+      var = val[2:-1]
+      val = vars.get(val, "")
+      if val: nlist.append(val)
+    else:
+      nlist.append(val)
+  return nlist
+
+INC_DIRS = expand_vars(INC_DIRS, make_vars)
+LIB_DIRS = expand_vars(LIB_DIRS, make_vars)
+LIBRARIES = expand_vars(LIBRARIES, make_vars)
+
+setup(name="clearsilver",
+      version=VERSION,
+      description="Python ClearSilver Wrapper",
+      author="Brandon Long",
+      author_email="blong@fiction.net",
+      url="http://www.clearsilver.net/",
+      ext_modules=[Extension(
+        name="neo_cgi",
+	sources=["neo_cgi.c", "neo_cs.c", "neo_util.c"],
+	include_dirs=INC_DIRS,
+	library_dirs=LIB_DIRS,
+	libraries=LIBRARIES,
+	)]
+      )
diff -Nru clearsilver-0.9.1/ruby/Makefile clearsilver-0.9.2/ruby/Makefile
--- clearsilver-0.9.1/ruby/Makefile	Mon Jul  7 23:10:28 2003
+++ clearsilver-0.9.2/ruby/Makefile	Mon Aug 11 15:03:56 2003
@@ -6,7 +6,7 @@
 
 include $(NEOTONIC_ROOT)rules.mk
 
-all: config.save ext/hdf/hdf.so test
+all: config.save ext/hdf/hdf.so testrb
 
 config.save: install.rb
 	$(RUBY) install.rb config -- --with-hdf-include=../../.. --with-hdf-lib=../../../libs
@@ -17,13 +17,34 @@
 ext/hdf/hdf.so: config.save
 	$(RUBY) install.rb setup 
 
-test: ext/hdf/hdf.so
-	$(RUBY) -Ilib -Iext/hdf test/hdftest.rb
+gold: ext/hdf/hdf.so
+	$(RUBY) -Ilib -Iext/hdf test/hdftest.rb > hdftest.gold;
+	@echo "Generated gold files"
+
+testrb: ext/hdf/hdf.so
+	@echo "Running ruby test"
+	@failed=0; \
+	rm -f hdftest.out; \
+	$(RUBY) -Ilib -Iext/hdf test/hdftest.rb > hdftest.out; \
+	diff -brief hdftest.out hdftest.gold 2>%1 > /dev/null; \
+	return_code=$$?; \
+	if [ $$return_code -ne 0 ]; then \
+	  diff hdftest.out hdftest.gold > hdftest.err; \
+	  echo "Failed Ruby Test: hdftest.rb"; \
+	  echo "    See hdftest.out and hdftest.err"; \
+	  failed=1; \
+	fi; \
+	if [ $$failed -eq 1 ]; then \
+	  exit 1; \
+	fi;
+	@echo  "Passed ruby test"
+
 
 install: all
+	$(RUBY) install.rb install
 
 clean:
-	$(RM) ext/hdf/*.o
+	$(RM) ext/hdf/*.o ext/hdf/*.so
 
 distclean:
 	$(RM) Makefile.depends config.save ext/hdf/hdf.so
diff -Nru clearsilver-0.9.1/ruby/ext/hdf/neo_cs.c clearsilver-0.9.2/ruby/ext/hdf/neo_cs.c
--- clearsilver-0.9.1/ruby/ext/hdf/neo_cs.c	Mon Jul  7 23:10:29 2003
+++ clearsilver-0.9.2/ruby/ext/hdf/neo_cs.c	Fri Aug  8 23:54:14 2003
@@ -17,6 +17,8 @@
 
 VALUE r_neo_error(NEOERR *err);
 
+#define Srb_raise(val) rb_raise(eHdfError, "%s/%d %s",__FILE__,__LINE__,RSTRING(val)->ptr)
+
 static void c_free (CSPARSE *csd) {
   if (csd) {
     cs_destroy (&csd);
@@ -39,7 +41,7 @@
 
   err = cs_init (&cs, hdf);
 
-  if (err) rb_raise(eHdfError, "%s", r_neo_error(err));
+  if (err) Srb_raise(r_neo_error(err));
 
   r_cs = Data_Wrap_Struct(class, 0, c_free, cs);
   rb_obj_call_init(r_cs, 0, NULL);
@@ -55,7 +57,7 @@
   path = STR2CSTR(oPath);
 
   err = cs_parse_file (cs, path);
-  if (err) rb_raise(eHdfError, "%s", r_neo_error(err));
+  if (err) Srb_raise(r_neo_error(err));
 
   return self;
 }
@@ -72,10 +74,11 @@
 
   /* This should be changed to use memory from the gc */
   ms = strdup(s);
-  if (ms == NULL) rb_raise(rb_eException, "out of memory");
+  if (ms == NULL) rb_raise(rb_eNoMemError, "out of memory");
 
   err = cs_parse_string (cs, ms, l);
-  if (err) rb_raise(eHdfError, "%s", r_neo_error(err));
+
+  if (err) Srb_raise(r_neo_error(err));
 
   return self;
 }
@@ -98,14 +101,14 @@
 
   string_init(&str);
   err = cs_render (cs, &str, render_cb);
-  if (err) rb_raise(eHdfError, "%s", r_neo_error(err));
+  if (err) Srb_raise(r_neo_error(err));
 
   rv = rb_str_new2(str.buf);
   string_clear (&str);
   return rv;
 }
 
-void Init_Cs() {
+void Init_cs() {
   cCs = rb_define_class_under(mNeotonic, "Cs", rb_cObject);
   rb_define_singleton_method(cCs, "new", c_new, 1);
 
diff -Nru clearsilver-0.9.1/ruby/ext/hdf/neo_util.c clearsilver-0.9.2/ruby/ext/hdf/neo_util.c
--- clearsilver-0.9.1/ruby/ext/hdf/neo_util.c	Mon Jul  7 23:10:29 2003
+++ clearsilver-0.9.2/ruby/ext/hdf/neo_util.c	Fri Aug  8 23:54:14 2003
@@ -9,12 +9,14 @@
  */
 
 #include <ruby.h>
+#include <version.h>
 #include "ClearSilver.h"
 
 VALUE mNeotonic;
 static VALUE cHdf;
 VALUE eHdfError;
 
+#define Srb_raise(val) rb_raise(eHdfError, "%s/%d %s",__FILE__,__LINE__,RSTRING(val)->ptr)
 
 VALUE r_neo_error (NEOERR *err)
 {
@@ -22,20 +24,16 @@
   VALUE errstr;
 
   string_init (&str);
+  nerr_error_string (err, &str);
+  errstr = rb_str_new2(str.buf);
+  /*
   if (nerr_match(err, NERR_PARSE)) {
-    nerr_error_string (err, &str);
-    errstr = rb_str_new2(str.buf);
-    string_clear(&str);
-    return errstr;
   }
   else {
-    nerr_error_traceback (err, &str);
-    errstr = rb_str_new2(str.buf);
-    string_clear(&str);
-    return errstr;
   }
+  */
   string_clear (&str);
-  return Qnil;
+  return errstr;
 }
 
 static void h_free(void *p) {
@@ -55,7 +53,7 @@
   VALUE r_hdf;
 
   err = hdf_init (&hdf);
-  if (err) rb_raise(eHdfError, "%s", r_neo_error(err));
+  if (err) Srb_raise(r_neo_error(err));
 
   r_hdf = Data_Wrap_Struct(class, 0, h_free, hdf);
   rb_obj_call_init(r_hdf, 0, NULL);
@@ -101,7 +99,7 @@
     value = STR2CSTR(oValue);
 
   err = hdf_set_attr(hdf, name, key, value);
-  if (err) rb_raise(eHdfError, "%s", r_neo_error(err));
+  if (err) Srb_raise(r_neo_error(err));
 
   return self;
 }
@@ -119,7 +117,7 @@
 
   err = hdf_set_value (hdf, name, value);
 
-  if (err) rb_raise(eHdfError, "%s", r_neo_error(err));
+  if (err) Srb_raise(r_neo_error(err));
 
   return self;
 }
@@ -293,7 +291,7 @@
   path=STR2CSTR(oPath);
 
   err = hdf_read_file (hdf, path);
-  if (err) rb_raise(eHdfError, "%s", r_neo_error(err));
+  if (err) Srb_raise(r_neo_error(err));
 
   return self;
 }
@@ -309,7 +307,8 @@
   path=STR2CSTR(oPath);
 
   err = hdf_write_file (hdf, path);
-  if (err) rb_raise(eHdfError, "%s", r_neo_error(err));
+
+  if (err) Srb_raise(r_neo_error(err));
 
   return self;
 }
@@ -325,7 +324,7 @@
   path=STR2CSTR(oPath);
 
   err = hdf_write_file_atomic (hdf, path);
-  if (err) rb_raise(eHdfError, "%s", r_neo_error(err));
+  if (err) Srb_raise(r_neo_error(err));
 
   return self;
 }
@@ -340,7 +339,7 @@
   name = STR2CSTR(oName);
 
   err = hdf_remove_tree (hdf, name);
-  if (err) rb_raise(eHdfError, "%s", r_neo_error(err));
+  if (err) Srb_raise(r_neo_error(err));
 
   return self;
 }
@@ -357,7 +356,7 @@
   Data_Get_Struct(self, HDF, hdf);
 
   err = hdf_dump_str (hdf, NULL, 0, &str);
-  if (err) rb_raise(eHdfError, "%s", r_neo_error(err));
+  if (err) Srb_raise(r_neo_error(err));
 
   rv = rb_str_new2(str.buf);
   string_clear (&str);
@@ -375,7 +374,7 @@
 
   err = hdf_write_string (hdf, &s);
 
-  if (err) rb_raise(eHdfError, "%s", r_neo_error(err));
+  if (err) Srb_raise(r_neo_error(err));
 
   rv = rb_str_new2(s);
   if (s) free(s);
@@ -396,7 +395,7 @@
 
   err = hdf_read_string_ignore (hdf, s, ignore);
 
-  if (err) rb_raise(eHdfError, "%s", r_neo_error(err));
+  if (err) Srb_raise(r_neo_error(err));
 
   return self;
 }
@@ -416,7 +415,7 @@
   if (src == NULL) rb_raise(eHdfError, "second argument must be an Hdf object");
 
   err = hdf_copy (hdf, name, src);
-  if (err) rb_raise(eHdfError, "%s", r_neo_error(err));
+  if (err) Srb_raise(r_neo_error(err));
 
   return self;
 }
@@ -433,7 +432,7 @@
   dest = STR2CSTR(oDest);
 
   err = hdf_set_symlink (hdf, src, dest);
-  if (err) rb_raise(eHdfError, "%s", r_neo_error(err));
+  if (err) Srb_raise(r_neo_error(err));
 
   return self;
 }
@@ -454,7 +453,7 @@
 
   err = neos_escape(s, buflen, esc_char[0], escape, &ret);
 
-  if (err) rb_raise(eHdfError, "%s", r_neo_error(err));
+  if (err) Srb_raise(r_neo_error(err));
 
   rv = rb_str_new2(ret);
   free(ret);
@@ -474,7 +473,7 @@
 
   /* This should be changed to use memory from the gc */
   copy = strdup(s);
-  if (copy == NULL) rb_raise(rb_eException, "out of memory");
+  if (copy == NULL) rb_raise(rb_eNoMemError, "out of memory");
 
   neos_unescape(copy, buflen, esc_char[0]);
 
@@ -516,7 +515,12 @@
   rb_define_singleton_method(cHdf, "escape", h_escape, 3);
   rb_define_singleton_method(cHdf, "unescape", h_unescape, 3);
 
-  eHdfError = rb_define_class_under(mNeotonic, "HdfError", rb_eException);
+  eHdfError = rb_define_class_under(mNeotonic, "HdfError",
+#if RUBY_VERSION_MINOR >= 6
+				    rb_eStandardError);
+#else
+                                    rb_eException);
+#endif
 
-  Init_Cs();
+  Init_cs();
 }
diff -Nru clearsilver-0.9.1/ruby/hdftest.gold clearsilver-0.9.2/ruby/hdftest.gold
--- clearsilver-0.9.1/ruby/hdftest.gold	Wed Dec 31 16:00:00 1969
+++ clearsilver-0.9.2/ruby/hdftest.gold	Mon Aug 11 13:04:56 2003
@@ -0,0 +1,27 @@
+1 = farming
+2 = sewing
+3 = bowling
+party.1 [Drool="True"]  = baloons
+party.2 [Pink]  = noise makers
+party.3 << EOM
+telling long
+stories
+EOM
+arf.1 = farming
+arf.2 = sewing
+arf.3 = bowling
+arf.party.1 [Drool="True"]  = baloons
+arf.party.2 [Pink]  = noise makers
+arf.party.3 << EOM
+telling long
+stories
+EOM
+party.2 attr (Pink=1)
+This is a funny test. farming.
+
+baloons
+
+noise makers
+
+telling long
+stories
diff -Nru clearsilver-0.9.1/ruby/test/hdftest.rb clearsilver-0.9.2/ruby/test/hdftest.rb
--- clearsilver-0.9.1/ruby/test/hdftest.rb	Tue Jun 17 14:20:14 2003
+++ clearsilver-0.9.2/ruby/test/hdftest.rb	Fri Aug  8 23:54:16 2003
@@ -31,7 +31,6 @@
 <?cs each:p = arf.party ?>
 <?cs var:p ?>
 <?cs /each ?>"
-
 c = Neo::Cs.new q
 
 c.parse_string(s)
diff -Nru clearsilver-0.9.1/rules.mk.in clearsilver-0.9.2/rules.mk.in
--- clearsilver-0.9.1/rules.mk.in	Tue Jun 17 14:20:40 2003
+++ clearsilver-0.9.2/rules.mk.in	Mon Aug 11 14:37:09 2003
@@ -1,3 +1,11 @@
+############################################################
+#
+# rules.mk  is  A U T O   G E N E R A T E D 
+#
+# you must edit:     rules.mk.in
+#
+############################################################
+
 ##
 ## Global Makefile Rules
 ##
@@ -48,6 +56,7 @@
 PYTHON_LIB = @PYTHON_LIB@
 PYTHON_SITE = @PYTHON_SITE@
 JAVA_PATH  = @JAVA_PATH@
+CSHARP_PATH = @CSHARP_PATH@
 
 ## Programs
 @SET_MAKE@
@@ -60,6 +69,7 @@
 JAVAH      = $(JAVA_PATH)/bin/javah
 JAR        = $(JAVA_PATH)/bin/jar
 APXS       = @APXS_PATH@
+PYTHON     = @PYTHON@
 PERL	   = @PERL@
 RUBY       = @RUBY@
 
@@ -70,7 +80,8 @@
 LDFLAGS    = -L$(LIB_DIR) @LDFLAGS@
 LDSHARED   = $(CC) -shared -fPic
 CPPLDSHARED   = $(CPP) -shared -fPic
-AR         = ar -cr
+AR         = @AR@ cr
+RANLIB     = @RANLIB@
 DEP_LIBS   = $(DLIBS:-l%=$(LIB_DIR)lib%.a)
 LIBS       = @LIBS@
 LS         = /bin/ls
@@ -78,6 +89,11 @@
 BUILD_WRAPPERS = @BUILD_WRAPPERS@
 EXTRA_UTL_OBJS = @EXTRA_UTL_OBJS@
 EXTRA_UTL_SRC  = @EXTRA_UTL_SRC@
+
+## I don't really feel like writing a configure thing for this yet
+ifeq ($(OSNAME),SunOS)
+LDSHARED   = ld -G -fPIC
+endif
 
 ## --------------win32 options
 
diff -Nru clearsilver-0.9.1/util/Makefile clearsilver-0.9.2/util/Makefile
--- clearsilver-0.9.1/util/Makefile	Mon Apr 14 17:13:46 2003
+++ clearsilver-0.9.2/util/Makefile	Thu Jul 24 22:46:15 2003
@@ -19,6 +19,7 @@
 
 $(UTL_LIB): $(UTL_OBJ)
 	$(AR) $@ $(UTL_OBJ)
+	$(RANLIB) $@
 
 install: all
 	$(NEOTONIC_ROOT)mkinstalldirs $(DESTDIR)$(cs_includedir)/util
diff -Nru clearsilver-0.9.1/util/neo_hash.c clearsilver-0.9.2/util/neo_hash.c
--- clearsilver-0.9.1/util/neo_hash.c	Wed Apr  2 15:06:35 2003
+++ clearsilver-0.9.2/util/neo_hash.c	Thu Jul 24 22:45:02 2003
@@ -17,27 +17,27 @@
 #include "neo_err.h"
 #include "neo_hash.h"
 
-static NEOERR *hash_resize(HASH *hash);
-static HASHNODE **hash_lookup_node (HASH *hash, void *key, UINT32 *hashv);
+static NEOERR *_hash_resize(NE_HASH *hash);
+static NE_HASHNODE **_hash_lookup_node (NE_HASH *hash, void *key, UINT32 *hashv);
 
-NEOERR *hash_init (HASH **hash, HASH_FUNC hash_func, COMP_FUNC comp_func)
+NEOERR *ne_hash_init (NE_HASH **hash, NE_HASH_FUNC hash_func, NE_COMP_FUNC comp_func)
 {
-  HASH *my_hash = NULL;
+  NE_HASH *my_hash = NULL;
 
-  my_hash = (HASH *) calloc(1, sizeof(HASH));
+  my_hash = (NE_HASH *) calloc(1, sizeof(NE_HASH));
   if (my_hash == NULL)
-    return nerr_raise(NERR_NOMEM, "Unable to allocate memory for HASH");
+    return nerr_raise(NERR_NOMEM, "Unable to allocate memory for NE_HASH");
 
   my_hash->size = 256;
   my_hash->num = 0;
   my_hash->hash_func = hash_func;
   my_hash->comp_func = comp_func;
 
-  my_hash->nodes = (HASHNODE **) calloc (my_hash->size, sizeof(HASHNODE *));
+  my_hash->nodes = (NE_HASHNODE **) calloc (my_hash->size, sizeof(NE_HASHNODE *));
   if (my_hash->nodes == NULL)
   {
     free(my_hash);
-    return nerr_raise(NERR_NOMEM, "Unable to allocate memory for HASHNODES");
+    return nerr_raise(NERR_NOMEM, "Unable to allocate memory for NE_HASHNODES");
   }
 
   *hash = my_hash;
@@ -45,10 +45,10 @@
   return STATUS_OK;
 }
 
-void hash_destroy (HASH **hash)
+void ne_hash_destroy (NE_HASH **hash)
 {
-  HASH *my_hash;
-  HASHNODE *node, *next;
+  NE_HASH *my_hash;
+  NE_HASHNODE *node, *next;
   int x;
 
   if (hash == NULL || *hash == NULL)
@@ -72,12 +72,12 @@
   *hash = NULL;
 }
 
-NEOERR *hash_insert(HASH *hash, void *key, void *value)
+NEOERR *ne_hash_insert(NE_HASH *hash, void *key, void *value)
 {
   UINT32 hashv;
-  HASHNODE **node;
+  NE_HASHNODE **node;
 
-  node = hash_lookup_node(hash, key, &hashv);
+  node = _hash_lookup_node(hash, key, &hashv);
 
   if (*node)
   {
@@ -85,9 +85,9 @@
   }
   else
   {
-    *node = (HASHNODE *) malloc(sizeof(HASHNODE));
+    *node = (NE_HASHNODE *) malloc(sizeof(NE_HASHNODE));
     if (node == NULL)
-      return nerr_raise(NERR_NOMEM, "Unable to allocate HASHNODE");
+      return nerr_raise(NERR_NOMEM, "Unable to allocate NE_HASHNODE");
 
     (*node)->hashv = hashv;
     (*node)->key = key;
@@ -96,51 +96,53 @@
   }
   hash->num++;
 
-  return hash_resize(hash);
+  return _hash_resize(hash);
 }
 
-void *hash_lookup(HASH *hash, void *key)
+void *ne_hash_lookup(NE_HASH *hash, void *key)
 {
-  HASHNODE *node;
+  NE_HASHNODE *node;
 
-  node = *hash_lookup_node(hash, key, NULL);
+  node = *_hash_lookup_node(hash, key, NULL);
 
   return (node) ? node->value : NULL;
 }
 
-void *hash_remove(HASH *hash, void *key)
+void *ne_hash_remove(NE_HASH *hash, void *key)
 {
-  HASHNODE **node;
+  NE_HASHNODE **node, *remove;
   void *value = NULL;
 
-  node = hash_lookup_node(hash, key, NULL);
+  node = _hash_lookup_node(hash, key, NULL);
   if (*node)
   {
-    value = (*node)->value;
-    free((*node));
-    *node = NULL;
+    remove = *node;
+    *node = remove->next;
+    value = remove->value;
+    free(remove);
+    hash->num--;
   }
   return value;
 }
 
-int hash_has_key(HASH *hash, void *key)
+int ne_hash_has_key(NE_HASH *hash, void *key)
 {
-  HASHNODE *node;
+  NE_HASHNODE *node;
 
-  node = *hash_lookup_node(hash, key, NULL);
+  node = *_hash_lookup_node(hash, key, NULL);
 
   if (node) return 1;
   return 0;
 }
 
-void *hash_next(HASH *hash, void **key)
+void *ne_hash_next(NE_HASH *hash, void **key)
 {
-  HASHNODE **node = 0;
+  NE_HASHNODE **node = 0;
   UINT32 hashv, bucket;
 
   if (*key)
   {
-    node = hash_lookup_node(hash, key, NULL);
+    node = _hash_lookup_node(hash, key, NULL);
 
     if (*node)
     {
@@ -180,10 +182,10 @@
   return NULL;
 }
 
-static HASHNODE **hash_lookup_node (HASH *hash, void *key, UINT32 *o_hashv)
+static NE_HASHNODE **_hash_lookup_node (NE_HASH *hash, void *key, UINT32 *o_hashv)
 {
   UINT32 hashv, bucket;
-  HASHNODE **node;
+  NE_HASHNODE **node;
 
   hashv = hash->hash_func(key);
   if (o_hashv) *o_hashv = hashv;
@@ -209,10 +211,10 @@
 }
 
 /* Ok, we're doing some weirdness here... */
-static NEOERR *hash_resize(HASH *hash)
+static NEOERR *_hash_resize(NE_HASH *hash)
 {
-  HASHNODE **new_nodes;
-  HASHNODE *entry, *prev;
+  NE_HASHNODE **new_nodes;
+  NE_HASHNODE *entry, *prev;
   int x, next_bucket;
   int orig_size = hash->size;
   UINT32 hash_mask;
@@ -221,9 +223,9 @@
     return STATUS_OK;
 
   /* We always double in size */
-  new_nodes = (HASHNODE **) realloc (hash->nodes, (hash->size*2) * sizeof(HASHNODE));
+  new_nodes = (NE_HASHNODE **) realloc (hash->nodes, (hash->size*2) * sizeof(NE_HASHNODE));
   if (new_nodes == NULL)
-    return nerr_raise(NERR_NOMEM, "Unable to allocate memory to resize HASH");
+    return nerr_raise(NERR_NOMEM, "Unable to allocate memory to resize NE_HASH");
 
   hash->nodes = new_nodes;
   orig_size = hash->size;
@@ -268,12 +270,12 @@
   return STATUS_OK;
 }
 
-int hash_str_comp(const void *a, const void *b)
+int ne_hash_str_comp(const void *a, const void *b)
 {
   return !strcmp((const char *)a, (const char *)b);
 }
 
-UINT32 hash_str_hash(const void *a)
+UINT32 ne_hash_str_hash(const void *a)
 {
   return ne_crc((char *)a, strlen((const char *)a));
 }
diff -Nru clearsilver-0.9.1/util/neo_hash.h clearsilver-0.9.2/util/neo_hash.h
--- clearsilver-0.9.1/util/neo_hash.h	Wed Apr  2 15:06:35 2003
+++ clearsilver-0.9.2/util/neo_hash.h	Thu Jul 24 22:45:02 2003
@@ -16,37 +16,37 @@
 #include <stdlib.h>
 #include "util/neo_misc.h"
 
-typedef UINT32 (*HASH_FUNC)(const void *);
-typedef int (*COMP_FUNC)(const void *, const void *);
+typedef UINT32 (*NE_HASH_FUNC)(const void *);
+typedef int (*NE_COMP_FUNC)(const void *, const void *);
 
-typedef struct _HASHNODE
+typedef struct _NE_HASHNODE
 {
   void *key;
   void *value;
   UINT32 hashv;
-  struct _HASHNODE *next;
-} HASHNODE;
+  struct _NE_HASHNODE *next;
+} NE_HASHNODE;
 
 typedef struct _HASH
 {
   UINT32 size;
   UINT32 num;
 
-  HASHNODE **nodes;
-  HASH_FUNC hash_func;
-  COMP_FUNC comp_func;
-} HASH;
+  NE_HASHNODE **nodes;
+  NE_HASH_FUNC hash_func;
+  NE_COMP_FUNC comp_func;
+} NE_HASH;
 
-NEOERR *hash_init (HASH **hash, HASH_FUNC hash_func, COMP_FUNC comp_func);
-void hash_destroy (HASH **hash);
-NEOERR *hash_insert(HASH *hash, void *key, void *value);
-void *hash_lookup(HASH *hash, void *key);
-int hash_has_key(HASH *hash, void *key);
-void *hash_remove(HASH *hash, void *key);
-void *hash_next(HASH *hash, void **key);
+NEOERR *ne_hash_init (NE_HASH **hash, NE_HASH_FUNC hash_func, NE_COMP_FUNC comp_func);
+void ne_hash_destroy (NE_HASH **hash);
+NEOERR *ne_hash_insert(NE_HASH *hash, void *key, void *value);
+void *ne_hash_lookup(NE_HASH *hash, void *key);
+int ne_hash_has_key(NE_HASH *hash, void *key);
+void *ne_hash_remove(NE_HASH *hash, void *key);
+void *ne_hash_next(NE_HASH *hash, void **key);
 
-int hash_str_comp(const void *a, const void *b);
-UINT32 hash_str_hash(const void *a);
+int ne_hash_str_comp(const void *a, const void *b);
+UINT32 ne_hash_str_hash(const void *a);
 
 __END_DECLS
 
diff -Nru clearsilver-0.9.1/util/neo_hdf.c clearsilver-0.9.2/util/neo_hdf.c
--- clearsilver-0.9.1/util/neo_hdf.c	Wed Apr  2 15:07:36 2003
+++ clearsilver-0.9.2/util/neo_hdf.c	Mon Aug 18 13:40:03 2003
@@ -138,7 +138,7 @@
   }
   if ((*hdf)->hash != NULL)
   {
-    hash_destroy(&(*hdf)->hash);
+    ne_hash_destroy(&(*hdf)->hash);
   }
   free (*hdf);
   *hdf = NULL;
@@ -223,7 +223,7 @@
     {
       hash_key.name = n;
       hash_key.name_len = x;
-      hp = hash_lookup(parent->hash, &hash_key);
+      hp = ne_hash_lookup(parent->hash, &hash_key);
     }
     else 
     {
@@ -507,20 +507,20 @@
   NEOERR *err;
   HDF *child;
 
-  err = hash_init(&(hdf->hash), hash_hdf_hash, hash_hdf_comp);
+  err = ne_hash_init(&(hdf->hash), hash_hdf_hash, hash_hdf_comp);
   if (err) return nerr_pass(err);
 
   child = hdf->child;
   while (child)
   {
-    err = hash_insert(hdf->hash, child, child);
+    err = ne_hash_insert(hdf->hash, child, child);
     if (err) return nerr_pass(err);
     child = child->next;
   }
   return STATUS_OK;
 }
 
-NEOERR* _set_value (HDF *hdf, char *name, char *value, int dup, int wf, int link, HDF_ATTR *attr, HDF **set_node)
+static NEOERR* _set_value (HDF *hdf, char *name, char *value, int dup, int wf, int link, HDF_ATTR *attr, HDF **set_node)
 {
   NEOERR *err;
   HDF *hn, *hp, *hs;
@@ -614,7 +614,7 @@
     {
       hash_key.name = n;
       hash_key.name_len = x;
-      hp = hash_lookup(hn->hash, &hash_key);
+      hp = ne_hash_lookup(hn->hash, &hash_key);
       hs = hn->last_child;
     }
     else 
@@ -673,7 +673,7 @@
       }
       else if (hn->hash != NULL)
       {
-	err = hash_insert(hn->hash, hp, hp);
+	err = ne_hash_insert(hn->hash, hp, hp);
 	if (err) return nerr_pass(err);
       }
     }
@@ -775,6 +775,16 @@
   return nerr_raise (NERR_NOT_FOUND, "Unable to find %s", src);
 }
 
+NEOERR* hdf_get_node (HDF *hdf, char *name, HDF **ret)
+{
+  _walk_hdf(hdf, name, ret);
+  if (*ret == NULL)
+  {
+    return nerr_pass(_set_value (hdf, name, NULL, 0, 1, 0, NULL, ret));
+  }
+  return STATUS_OK;
+}
+
 /* Ok, this version avoids the bubble sort by walking the level once to
  * load them all into a ULIST, qsort'ing the list, and then dumping them
  * back out... */
@@ -815,7 +825,7 @@
 NEOERR* hdf_remove_tree (HDF *hdf, char *name)
 {
   HDF *hp = hdf;
-  HDF *lp = NULL, *ln = NULL;
+  HDF *lp = NULL, *ln = NULL; /* last parent, last node */
   int x = 0;
   char *s = name;
   char *n = name;
@@ -841,11 +851,10 @@
     {
       if (hp->name && (x == hp->name_len) && !strncmp(hp->name, n, x))
       {
-	break;
+      break;
       }
       else
       {
-	lp = NULL;
 	ln = hp;
 	hp = hp->next;
       }
@@ -864,14 +873,18 @@
     x = (s == NULL) ? strlen(n) : s - n;
   } 
 
-  if (lp)
+  if (lp->hash != NULL)
   {
-    lp->child = hp->next;
-    hp->next = NULL;
+    ne_hash_remove(lp->hash, hp);
   }
-  else if (ln)
+  if (ln)
   {
     ln->next = hp->next;
+    hp->next = NULL;
+  }
+  else 
+  {
+    lp->child = hp->next;
     hp->next = NULL;
   }
   _dealloc_hdf (&hp);
diff -Nru clearsilver-0.9.1/util/neo_hdf.h clearsilver-0.9.2/util/neo_hdf.h
--- clearsilver-0.9.1/util/neo_hdf.h	Mon Mar 31 17:45:54 2003
+++ clearsilver-0.9.2/util/neo_hdf.h	Mon Aug 18 13:40:03 2003
@@ -45,7 +45,7 @@
 
   /* the following HASH is used when we reach more than FORCE_HASH_AT
    * elements */
-  HASH *hash;
+  NE_HASH *hash;
   /* When using the HASH, we need to know where to append new children */
   struct _hdf *last_child;
 } HDF;
@@ -58,6 +58,9 @@
 NEOERR* hdf_get_copy (HDF *hdf, char *name, char **value, char *defval);
 
 HDF* hdf_get_obj (HDF *hdf, char *name);
+/* Always returns the node (except on NOMEM error).  Creates if
+ * necessary */
+NEOERR * hdf_get_node (HDF *hdf, char *name, HDF **ret);
 HDF* hdf_get_child (HDF *hdf, char *name);
 HDF_ATTR* hdf_get_attr (HDF *hdf, char *name);
 NEOERR* hdf_set_attr (HDF *hdf, char *name, char *key, char *value);
diff -Nru clearsilver-0.9.1/util/neo_misc.h clearsilver-0.9.2/util/neo_misc.h
--- clearsilver-0.9.1/util/neo_misc.h	Wed Apr  2 19:03:01 2003
+++ clearsilver-0.9.2/util/neo_misc.h	Fri Aug  8 23:54:17 2003
@@ -13,6 +13,7 @@
 
 #include <stdlib.h>
 #include <time.h>
+#include "cs_config.h"
 
 /* Fix Up for systems that don't define these standard things... */
 #ifndef __BEGIN_DECLS
diff -Nru clearsilver-0.9.1/util/neo_net.h clearsilver-0.9.2/util/neo_net.h
--- clearsilver-0.9.1/util/neo_net.h	Wed Oct 23 18:13:21 2002
+++ clearsilver-0.9.2/util/neo_net.h	Thu Jul 24 11:35:13 2003
@@ -48,7 +48,7 @@
 NEOERR *net_write_str(NSOCK *sock, char *s);
 NEOERR *net_write_int(NSOCK *sock, int i);
 NEOERR *net_flush(NSOCK *sock);
-void net_shutdown();
+void net_shutdown(void);
 
 __END_DECLS
 
