Index: src/blob.c
==================================================================
--- src/blob.c
+++ src/blob.c
@@ -19,12 +19,12 @@
 ** or binary data.
 */
 #include "config.h"
 #include <zlib.h>
 #include "blob.h"
-
 #if INTERFACE
+
 /*
 ** A Blob can hold a string or a binary object of arbitrary size.  The
 ** size changes as necessary.
 */
 struct Blob {
@@ -49,11 +49,10 @@
 ** Seek whence parameter values
 */
 #define BLOB_SEEK_SET 1
 #define BLOB_SEEK_CUR 2
 #define BLOB_SEEK_END 3
-
 #endif /* INTERFACE */
 
 /*
 ** Make sure a blob is initialized
 */

Index: src/db.c
==================================================================
--- src/db.c
+++ src/db.c
@@ -1098,10 +1098,16 @@
 ** argument is true.  Ignore unfinalized statements when false.
 */
 void db_close(int reportErrors){
   sqlite3_stmt *pStmt;
   if( g.db==0 ) return;
+  if(g.interp){
+    /* clean up up any query_prepare statements */
+    Th_DeleteInterp(g.interp);
+    g.interp = 0;
+  }
+
   if( g.fSqlStats ){
     int cur, hiwtr;
     sqlite3_db_status(g.db, SQLITE_DBSTATUS_LOOKASIDE_USED, &cur, &hiwtr, 0);
     fprintf(stderr, "-- LOOKASIDE_USED         %10d %10d\n", cur, hiwtr);
     sqlite3_db_status(g.db, SQLITE_DBSTATUS_LOOKASIDE_HIT, &cur, &hiwtr, 0);

Index: src/main.c
==================================================================
--- src/main.c
+++ src/main.c
@@ -679,13 +679,13 @@
 }
 void fossil_free(void *p){
   free(p);
 }
 void *fossil_realloc(void *p, size_t n){
-  p = realloc(p, n);
-  if( p==0 ) fossil_panic("out of memory");
-  return p;
+  void * re = realloc(p, n);
+  if( re==0 && n>0 ) fossil_panic("out of memory");
+  return (n > 0) ? re : 0;
 }
 
 /*
 ** This function implements a cross-platform "system()" interface.
 */
@@ -787,22 +787,42 @@
 /*
 ** Look for a command-line option.  If present, return a pointer.
 ** Return NULL if missing.
 **
 ** hasArg==0 means the option is a flag.  It is either present or not.
-** hasArg==1 means the option has an argument.  Return a pointer to the
-** argument.
+** hasArg==1 means the option has an argument.  Return a pointer to
+** the argument. If hasArg is 0 and the argument is found then a
+** pointer to an empty string is returned (to distinguish from
+** NULL). If hasArg==1 and the option lies at the end of the argument
+** list, it is treated as if it had not been found.
+**
+** Note that this function REMOVES any found entry from the args list,
+** so calling this twice for the same var will cause NULL to be
+** returned after the first time.
+**
+** zLong may not be NULL but zShort may be.
+**
+** Options are accepted in these forms, depending on the value of
+** hasArg:
+**
+** hasArg=true:
+**  -long        (hasArg==0)
+**  -long VALUE  (hasArg==1)
+**  -long=VALUE  (hasArg==1)
+**  -short       (hasArg==0)
+**  -short VALUE (hasArg==1)
+**  -short=VALUE (hasArg==1)
 */
 const char *find_option(const char *zLong, const char *zShort, int hasArg){
   int i;
-  int nLong;
+  int nLong, nShort;
   const char *zReturn = 0;
   assert( hasArg==0 || hasArg==1 );
   nLong = strlen(zLong);
+  nShort = zShort ? strlen(zShort) : 0;
   for(i=1; i<g.argc; i++){
     char *z;
-    if (i+hasArg >= g.argc) break;
     z = g.argv[i];
     if( z[0]!='-' ) continue;
     z++;
     if( z[0]=='-' ){
       if( z[1]==0 ){
@@ -815,20 +835,37 @@
       if( hasArg && z[nLong]=='=' ){
         zReturn = &z[nLong+1];
         remove_from_argv(i, 1);
         break;
       }else if( z[nLong]==0 ){
-        zReturn = g.argv[i+hasArg];
+        if( hasArg ){
+          if (i+hasArg >= g.argc) break;
+          zReturn = g.argv[i+hasArg];
+        }else{
+          zReturn = "";
+        }
+        remove_from_argv(i, 1+hasArg);
+        break;
+      }
+    }else if( strncmp(z,zShort,nShort)==0 ){
+      if( hasArg && z[nShort]=='=' ){
+        zReturn = &z[nShort+1];
+        remove_from_argv(i, 1);
+        break;
+      }else if( z[nShort]==0 ){
+        if( hasArg ){
+          if (i+hasArg >= g.argc) break;
+          zReturn = g.argv[i+hasArg];
+        }else{
+          zReturn = "";
+        }
         remove_from_argv(i, 1+hasArg);
         break;
       }
-    }else if( fossil_strcmp(z,zShort)==0 ){
-      zReturn = g.argv[i+hasArg];
-      remove_from_argv(i, 1+hasArg);
-      break;
     }
   }
+
   return zReturn;
 }
 
 /*
 ** Verify that there are no unprocessed command-line options.  If

Index: src/report.c
==================================================================
--- src/report.c
+++ src/report.c
@@ -81,11 +81,11 @@
     blob_appendf(&ril, "</li>\n");
   }
 
   Th_Store("report_items", blob_str(&ril));
   
-  Th_Render(zScript);
+  Th_Render(zScript, Th_Render_Flags_DEFAULT);
   
   blob_reset(&ril);
   if( g.thTrace ) Th_Trace("END_REPORTLIST<br />\n", -1);
 
   style_footer();

Index: src/style.c
==================================================================
--- src/style.c
+++ src/style.c
@@ -19,11 +19,10 @@
 **
 */
 #include "config.h"
 #include "style.h"
 
-
 /*
 ** Elements of the submenu are collected into the following
 ** structure and displayed below the main menu by style_header().
 **
 ** Populate this structure with calls to style_submenu_element()
@@ -194,11 +193,11 @@
   Th_Store("compiler_name", COMPILER_NAME);
   if( g.zLogin ){
     Th_Store("login", g.zLogin);
   }
   if( g.thTrace ) Th_Trace("BEGIN_HEADER_SCRIPT<br />\n", -1);
-  Th_Render(zHeader);
+  Th_Render(zHeader, Th_Render_Flags_DEFAULT);
   if( g.thTrace ) Th_Trace("END_HEADER<br />\n", -1);
   Th_Unstore("title");   /* Avoid collisions with ticket field names */
   cgi_destination(CGI_BODY);
   g.cgiOutput = 1;
   headerHasBeenGenerated = 1;
@@ -281,11 +280,11 @@
   ** the footer will be generating </html> */
   style_resolve_href();
 
   zFooter = db_get("footer", (char*)zDefaultFooter);
   if( g.thTrace ) Th_Trace("BEGIN_FOOTER<br />\n", -1);
-  Th_Render(zFooter);
+  Th_Render(zFooter, Th_Render_Flags_DEFAULT);
   if( g.thTrace ) Th_Trace("END_FOOTER<br />\n", -1);
   
   /* Render trace log if TH1 tracing is enabled. */
   if( g.thTrace ){
     cgi_append_content("<span class=\"thTrace\"><hr />\n", -1);
@@ -960,11 +959,11 @@
   /* Process through TH1 in order to give an opportunity to substitute
   ** variables such as $baseurl.
   */
   Th_Store("baseurl", g.zBaseURL);
   Th_Store("home", g.zTop);
-  Th_Render(blob_str(&css));
+  Th_Render(blob_str(&css), Th_Render_Flags_DEFAULT);
 
   /* Tell CGI that the content returned by this page is considered cacheable */
   g.isConst = 1;
 }
 

Index: src/th.c
==================================================================
--- src/th.c
+++ src/th.c
@@ -5,25 +5,52 @@
 */
 
 #include "th.h"
 #include <string.h>
 #include <assert.h>
+#include <stdio.h> /* FILE class */
 
 typedef struct Th_Command   Th_Command;
 typedef struct Th_Frame     Th_Frame;
 typedef struct Th_Variable  Th_Variable;
 
+/*
+** Shared instance. See th.h for the docs.
+*/
+const Th_Vtab_OutputMethods Th_Vtab_OutputMethods_FILE = {
+  Th_Output_f_FILE /* xWrite() */,
+  Th_Output_dispose_FILE /* dispose() */,
+  NULL /*pState*/,
+  1/*enabled*/
+};
+
+/*
+** Holds client-provided "garbage collected" data for
+** a Th_Interp instance.
+*/
+struct Th_GcEntry {
+  void * pData;                        /* arbitrary data */
+  void (*xDel)( Th_Interp *, void * ); /* finalizer for pData */
+};
+typedef struct Th_GcEntry Th_GcEntry;
+
 /*
 ** Interpreter structure.
 */
 struct Th_Interp {
-  Th_Vtab *pVtab;     /* Copy of the argument passed to Th_CreateInterp() */
+  Th_Vtab *pVtab;    /* Copy of the argument passed to Th_CreateInterp() */
   char *zResult;     /* Current interpreter result (Th_Malloc()ed) */
-  int nResult;        /* number of bytes in zResult */
-  Th_Hash *paCmd;     /* Table of registered commands */
-  Th_Frame *pFrame;   /* Current execution frame */
-  int isListMode;     /* True if thSplitList() should operate in "list" mode */
+  int nResult;       /* number of bytes in zResult */
+  Th_Hash *paCmd;    /* Table of registered commands */
+  Th_Frame *pFrame;  /* Current execution frame */
+  int isListMode;    /* True if thSplitList() should operate in "list" mode */
+  Th_Hash * paGc;    /* Holds client-provided data owned by this
+                        object. It would be more efficient to store
+                        these in a list (because we don't expect many
+                        entries), but Th_Hash has the strong advantage
+                        of being here and working.
+                     */
 };
 
 /*
 ** Each TH command registered using Th_CreateCommand() is represented
 ** by an instance of the following structure stored in the Th_Interp.paCmd
@@ -106,10 +133,11 @@
 static int  thPushFrame(Th_Interp*, Th_Frame*);
 static void thPopFrame(Th_Interp*);
 
 static void thFreeVariable(Th_HashEntry*, void*);
 static void thFreeCommand(Th_HashEntry*, void*);
+static void thFreeGc(Th_HashEntry*, void*);
 
 /*
 ** The following are used by both the expression and language parsers.
 ** Given that the start of the input string (z, n) is a language 
 ** construct of the relevant type (a command enclosed in [], an escape
@@ -288,10 +316,26 @@
     pCommand->xDel((Th_Interp *)pContext, pCommand->pContext);
   }
   Th_Free((Th_Interp *)pContext, pEntry->pData);
   pEntry->pData = 0;
 }
+
+/*
+** Th_Hash visitor/destructor for Th_Interp::paGc entries. Frees
+** pEntry->pData but not pEntry.
+*/
+static void thFreeGc(Th_HashEntry *pEntry, void *pContext){
+  Th_GcEntry *gc = (Th_GcEntry *)pEntry->pData;
+  if(gc){
+    if( gc->xDel ){
+      gc->xDel( (Th_Interp*)pContext, gc->pData );
+    }
+    Th_Free((Th_Interp *)pContext, pEntry->pData);
+    pEntry->pData = 0;
+  }
+}
+
 
 /*
 ** Push a new frame onto the stack.
 */
 static int thPushFrame(Th_Interp *interp, Th_Frame *pFrame){
@@ -1369,19 +1413,67 @@
 
 /* 
 ** Wrappers around the supplied malloc() and free() 
 */
 void *Th_Malloc(Th_Interp *pInterp, int nByte){
-  void *p = pInterp->pVtab->xMalloc(nByte);
+  void * p = pInterp->pVtab->xRealloc(NULL, nByte);
   if( p ){
     memset(p, 0, nByte);
+  }else{
+    assert( 0 == nByte );
   }
   return p;
 }
 void Th_Free(Th_Interp *pInterp, void *z){
   if( z ){
-    pInterp->pVtab->xFree(z);
+    pInterp->pVtab->xRealloc(z, 0);
+  }
+}
+void *Th_Realloc(Th_Interp *pInterp, void *z, int nByte){
+  void *p = pInterp->pVtab->xRealloc(z, nByte);
+  return p;
+}
+
+
+int Th_Vtab_Output( Th_Vtab *vTab, char const * zData, int nData ){
+  if(!vTab->out.xWrite){
+    return -1;
+  }else if(!vTab->out.enabled){
+    return 0;
+  }else{
+    return vTab->out.xWrite( zData, nData, vTab->out.pState );
+  }
+}
+
+
+int Th_Output( Th_Interp *pInterp, char const * zData, int nData ){
+  return Th_Vtab_Output( pInterp->pVtab, zData, nData );
+}
+
+void Th_OutputEnable( Th_Interp *pInterp, char flag ){
+  pInterp->pVtab->out.enabled = flag;
+}
+
+char Th_OutputEnabled( Th_Interp *pInterp ){
+  return pInterp->pVtab->out.enabled ? 1 : 0;
+}
+
+int Th_Output_f_FILE( char const * zData, int nData, void * pState ){
+  FILE * dest = pState ? (FILE*)pState : stdout;
+  int rc = (int)fwrite(zData, 1, nData, dest);
+  fflush(dest);
+  return rc;
+}
+
+void Th_Output_dispose_FILE( void * pState ){
+  FILE * f = pState ? (FILE*)pState : NULL;
+  if(f
+     && (f != stdout)
+     && (f != stderr)
+     && (f != stdin)){
+    fflush(f);
+    fclose(f);
   }
 }
 
 /*
 ** Install a new th1 command. 
@@ -1638,10 +1730,22 @@
 */
 void Th_DeleteInterp(Th_Interp *interp){
   assert(interp->pFrame);
   assert(0==interp->pFrame->pCaller);
 
+  /* Delete any client-side gc entries first. */
+  if( interp->paGc ){
+    Th_HashIterate(interp, interp->paGc, thFreeGc, (void *)interp);
+    Th_HashDelete(interp, interp->paGc);
+    interp->paGc = NULL;
+  }
+
+  /* Clean up the output abstraction. */
+  if( interp->pVtab && interp->pVtab->out.xDispose ){
+      interp->pVtab->out.xDispose( interp->pVtab->out.pState );
+  }
+  
   /* Delete the contents of the global frame. */
   thPopFrame(interp);
 
   /* Delete any result currently stored in the interpreter. */
   Th_SetResult(interp, 0, 0);
@@ -1648,10 +1752,11 @@
 
   /* Delete all registered commands and the command hash-table itself. */
   Th_HashIterate(interp, interp->paCmd, thFreeCommand, (void *)interp);
   Th_HashDelete(interp, interp->paCmd);
 
+  
   /* Delete the interpreter structure itself. */
   Th_Free(interp, (void *)interp);
 }
 
 /* 
@@ -1659,11 +1764,11 @@
 */
 Th_Interp * Th_CreateInterp(Th_Vtab *pVtab){
   Th_Interp *p;
 
   /* Allocate and initialise the interpreter and the global frame */
-  p = pVtab->xMalloc(sizeof(Th_Interp) + sizeof(Th_Frame));
+  p = pVtab->xRealloc(NULL, sizeof(Th_Interp) + sizeof(Th_Frame));
   memset(p, 0, sizeof(Th_Interp));
   p->pVtab = pVtab;
   p->paCmd = Th_HashNew(p);
   thPushFrame(p, (Th_Frame *)&p[1]);
 
@@ -2271,11 +2376,11 @@
 ** a null pointer.
 */
 int th_strlen(const char *zStr){
   int n = 0;
   if( zStr ){
-    while( zStr[n] ) n++;
+    while( zStr[n] ) ++n;
   }
   return n;
 }
 
 /* Whitespace characters:
@@ -2335,11 +2440,10 @@
 }
 
 #ifndef LONGDOUBLE_TYPE
 # define LONGDOUBLE_TYPE long double
 #endif
-typedef char u8;
 
 
 /*
 ** Return TRUE if z is a pure numeric string.  Return FALSE if the
 ** string contains any character which is not part of a number. If
@@ -2373,19 +2477,22 @@
   return *z==0;
 }
 
 /*
 ** The string z[] is an ascii representation of a real number.
-** Convert this string to a double.
+** Convert this string to a double and assigns its value to
+** pResult.
 **
 ** This routine assumes that z[] really is a valid number.  If it
 ** is not, the result is undefined.
 **
 ** This routine is used instead of the library atof() function because
 ** the library atof() might want to use "," as the decimal point instead
 ** of "." depending on how locale is set.  But that would cause problems
 ** for SQL.  So this routine always uses "." regardless of locale.
+**
+** Returns the number of bytes of z consumed in parsing the value.
 */
 static int sqlite3AtoF(const char *z, double *pResult){
   int sign = 1;
   const char *zBegin = z;
   LONGDOUBLE_TYPE v1 = 0.0;
@@ -2438,18 +2545,16 @@
   *pResult = sign<0 ? -v1 : v1;
   return z - zBegin;
 }
 
 /*
-** Try to convert the string passed as arguments (z, n) to an integer.
-** If successful, store the result in *piOut and return TH_OK. 
-**
-** If the string cannot be converted to an integer, return TH_ERROR. 
-** If the interp argument is not NULL, leave an error message in the 
-** interpreter result too.
+** Attempts to convert (zArg,nArg) to an integer. On success *piOut is
+** assigned to its value and TH_OK is returned, else piOut is not
+** modified and TH_ERROR is returned. Conversion errors are considered
+** non-fatal here, so interp's error state is not set.
 */
-int Th_ToInt(Th_Interp *interp, const char *z, int n, int *piOut){
+int Th_TryInt(Th_Interp *interp, const char *z, int n, int *piOut){
   int i = 0;
   int iOut = 0;
 
   if( n<0 ){
     n = th_strlen(z);
@@ -2458,11 +2563,10 @@
   if( n>0 && (z[0]=='-' || z[0]=='+') ){
     i = 1;
   }
   for(; i<n; i++){
     if( !th_isdigit(z[i]) ){
-      Th_ErrorMessage(interp, "expected integer, got: \"", z, n);
       return TH_ERROR;
     }
     iOut = iOut * 10 + (z[i] - 48);
   }
 
@@ -2471,10 +2575,45 @@
   }
 
   *piOut = iOut;
   return TH_OK;
 }
+
+/*
+** Try to convert the string passed as arguments (z, n) to an integer.
+** If successful, store the result in *piOut and return TH_OK. 
+**
+** If the string cannot be converted to an integer, return TH_ERROR. 
+** If the interp argument is not NULL, leave an error message in the 
+** interpreter result too.
+*/
+int Th_ToInt(Th_Interp *interp, const char *z, int n, int *piOut){
+  const int rc = Th_TryInt(interp, z, n, piOut);
+  if( TH_OK != rc ){
+    Th_ErrorMessage(interp, "expected integer, got: \"", z, n);
+  }
+  return rc;
+}
+
+
+/*
+** Functionally/semantically identical to Th_TryInt() but works on
+** doubles.
+*/
+int Th_TryDouble(
+  Th_Interp *interp, 
+  const char *z, 
+  int n, 
+  double *pfOut
+){
+  if( !sqlite3IsNumber((const char *)z, 0) ){
+    return TH_ERROR;
+  }else{
+    sqlite3AtoF((const char *)z, pfOut);
+    return TH_OK;
+  }
+}
 
 /*
 ** Try to convert the string passed as arguments (z, n) to a double.
 ** If successful, store the result in *pfOut and return TH_OK. 
 **
@@ -2486,17 +2625,15 @@
   Th_Interp *interp, 
   const char *z, 
   int n, 
   double *pfOut
 ){
-  if( !sqlite3IsNumber((const char *)z, 0) ){
-    Th_ErrorMessage(interp, "expected number, got: \"", z, n);
-    return TH_ERROR;
+  const int rc = Th_TryDouble(interp, z, n, pfOut);
+  if( TH_OK != rc ){
+      Th_ErrorMessage(interp, "expected number, got: \"", z, n);
   }
-
-  sqlite3AtoF((const char *)z, pfOut);
-  return TH_OK;
+  return rc;
 }
 
 /*
 ** Set the result of the interpreter to the th1 representation of
 ** the integer iVal and return TH_OK.
@@ -2605,5 +2742,509 @@
   }
 
   *z = '\0';
   return Th_SetResult(interp, zBuf, -1);
 }
+
+
+int Th_SetData( Th_Interp * interp, char const * key,
+                 void * pData,
+                 void (*finalizer)( Th_Interp *, void * ) ){
+  Th_HashEntry * pEnt;
+  Th_GcEntry * pGc;
+  if(NULL == interp->paGc){
+    interp->paGc = Th_HashNew(interp);
+    assert(NULL != interp->paGc);
+    if(!interp->paGc){
+      return TH_ERROR;
+    }
+  }
+  pEnt = Th_HashFind(interp, interp->paGc, key, th_strlen(key), 1);
+  if( pEnt->pData ){
+    thFreeGc( pEnt, interp );
+  }
+  assert( NULL == pEnt->pData );
+  pEnt->pData = pGc = (Th_GcEntry*)Th_Malloc(interp, sizeof(Th_GcEntry));
+  pGc->pData = pData;
+  pGc->xDel = finalizer;
+  return 0;
+ 
+}
+void * Th_GetData( Th_Interp * interp, char const * key ){
+  Th_HashEntry * e = interp->paGc
+    ? Th_HashFind(interp, interp->paGc, key, th_strlen(key), 0)
+    : NULL;
+  return e ? ((Th_GcEntry*)e->pData)->pData : NULL;
+}
+
+int Th_RegisterCommands( Th_Interp * interp,
+                          Th_Command_Reg const * aCommand ){
+  int i;
+  int rc = TH_OK;
+  for(i=0; (TH_OK==rc) && aCommand[i].zName; ++i){
+    if ( !aCommand[i].zName ) break;
+    else if( !aCommand[i].xProc ) continue;
+    else{
+      rc = Th_CreateCommand(interp, aCommand[i].zName, aCommand[i].xProc,
+                            aCommand[i].pContext, 0);
+    }
+  }
+  return rc;
+}
+
+
+
+#ifdef TH_ENABLE_OB
+/* Reminder: the ob code "really" belongs in th_lang.c or th_main.c,
+   but it needs access to Th_Interp::pVtab in order to swap out
+   Th_Vtab_OutputMethods parts for purposes of stacking layers of
+   buffers. We could add access to it via the public interface,
+   but that didn't seem appropriate.
+*/
+
+/*
+** Th_Output_f() impl which redirects output to a Th_Ob_Manager.
+** Requires that pState be a (Th_Ob_Man*).
+*/
+static int Th_Output_f_ob( char const * zData, int len, void * pState );
+
+/*
+** Th_Output::dispose() impl which requires pState to be-a Th_Ob_Manager.
+*/
+static void Th_Output_dispose_ob( void * pState );
+
+/* Empty-initialized Th_Ob_Manager instance. */
+#define Th_Ob_Man_empty_m { \
+  NULL/*aBuf*/,           \
+  0/*nBuf*/,            \
+  -1/*cursor*/,       \
+  NULL/*interp*/,     \
+  NULL/*aOutput*/       \
+}
+
+/* Empty-initialized Th_Vtab_OutputMethods instance. */
+#define Th_Vtab_OutputMethods_empty_m { \
+  NULL /* write() */, \
+  NULL /* dispose() */, \
+  NULL /*pState*/,\
+  1/*enabled*/\
+}
+/* Vtab_OutputMethods instance initialized for OB support. */
+#define Th_Vtab_OutputMethods_ob_m { \
+  Th_Output_f_ob /*write()*/, \
+  Th_Output_dispose_ob /* dispose() */, \
+  NULL /*pState*/,\
+  1/*enabled*/\
+}
+/* Empty-initialized Th_Vtab_Man instance. */
+static const Th_Ob_Manager Th_Ob_Man_empty = Th_Ob_Man_empty_m;
+/* Empty-initialized Th_Vtab_OutputMethods instance. */
+static Th_Vtab_OutputMethods Th_Vtab_OutputMethods_empty = Th_Vtab_OutputMethods_empty_m;
+/* Th_Vtab_OutputMethods instance initialized for OB support. */
+static Th_Vtab_OutputMethods Th_Vtab_OutputMethods_ob = Th_Vtab_OutputMethods_ob_m;
+
+/*
+**   Internal key for Th_Set/GetData(), for storing a Th_Ob_Manager instance.
+*/
+#define Th_Ob_Man_KEY "Th_Ob_Manager"
+
+Th_Ob_Manager * Th_Ob_GetManager(Th_Interp *interp){
+  return (Th_Ob_Manager*) Th_GetData(interp, Th_Ob_Man_KEY );
+}
+
+Blob * Th_Ob_GetCurrentBuffer( Th_Ob_Manager * pMan ){
+  return pMan->nBuf>0 ? pMan->aBuf[pMan->cursor] : 0;
+}
+
+
+/*
+** Th_Output_f() impl which expects pState to be (Th_Ob_Manager*).
+** (zData,len) are appended to pState's current output buffer.
+*/
+int Th_Output_f_ob( char const * zData, int len, void * pState ){
+  Th_Ob_Manager * pMan = (Th_Ob_Manager*)pState;
+  Blob * b = Th_Ob_GetCurrentBuffer( pMan );
+  assert( NULL != pMan );
+  assert( b );
+  blob_append( b, zData, len );
+  return len;
+}
+
+void Th_Output_dispose_ob( void * pState ){
+  /* possible todo: move the cleanup logic from
+     Th_Ob_Pop() to here? */
+#if 0
+  Th_Ob_Manager * pMan = (Th_Ob_Manager*)pState;
+  Blob * b = Th_Ob_GetCurrentBuffer( pMan );
+  assert( NULL != pMan );
+  assert( b );
+#endif
+}
+
+
+
+int Th_Ob_Push( Th_Ob_Manager * pMan,
+                Th_Vtab_OutputMethods const * pWriter,
+                Blob ** pOut ){
+  Blob * pBlob;
+  int x, i;
+  if( NULL == pWriter ){
+    pWriter = &Th_Vtab_OutputMethods_ob;
+  }
+  assert( NULL != pMan->interp );
+  pBlob = (Blob *)Th_Malloc(pMan->interp, sizeof(Blob));
+  *pBlob = empty_blob;
+
+  if( pMan->cursor >= pMan->nBuf-2 ){
+    /* expand if needed */
+    void * re;
+    x = pMan->nBuf + 5;
+    if( pMan->cursor >= x ) {
+      assert( 0 && "This really should not happen." );
+      x = pMan->cursor + 5;
+    }
+    re = Th_Realloc( pMan->interp, pMan->aBuf, x * sizeof(Blob*) );
+    if(NULL==re){
+      goto error;
+    }
+    pMan->aBuf = (Blob **)re;
+    re = Th_Realloc( pMan->interp, pMan->aOutput, x * sizeof(Th_Vtab_OutputMethods) );
+    if(NULL==re){
+      goto error;
+    }
+    pMan->aOutput = (Th_Vtab_OutputMethods*)re;
+    for( i = pMan->nBuf; i < x; ++i ){
+      pMan->aOutput[i] = Th_Vtab_OutputMethods_empty;
+      pMan->aBuf[i] = NULL;
+    }
+    pMan->nBuf = x;
+  }
+  assert( pMan->nBuf > pMan->cursor );
+  assert( pMan->cursor >= -1 );
+  ++pMan->cursor;
+  pMan->aBuf[pMan->cursor] = pBlob;
+  pMan->aOutput[pMan->cursor] = pMan->interp->pVtab->out;
+  pMan->interp->pVtab->out = *pWriter;
+  pMan->interp->pVtab->out.pState = pMan;
+  if( pOut ){
+    *pOut = pBlob;
+  }
+  return TH_OK;
+  error:
+  if( pBlob ){
+    Th_Free( pMan->interp, pBlob );
+  }
+  return TH_ERROR;
+}
+
+Blob * Th_Ob_Pop( Th_Ob_Manager * pMan ){
+  if( pMan->cursor < 0 ){
+    return NULL;
+  }else{
+    Blob * rc;
+    Th_Vtab_OutputMethods * theOut;
+    assert( pMan->nBuf > pMan->cursor );
+    rc = pMan->aBuf[pMan->cursor];
+    pMan->aBuf[pMan->cursor] = NULL;
+    theOut = &pMan->aOutput[pMan->cursor];
+#if 0
+    /* We need something like this (but not this!) if we extend the
+       support to use other (non-Blob) proxies. We will likely need
+       another callback function or two for that case, e.g. xStart()
+       and xEnd(), which would be called when they are pushed/popped
+       to/from the stack.
+    */
+    if( theOut->xDispose ){
+      theOut->xDispose( theOut->pState );
+    }
+#endif
+    pMan->interp->pVtab->out = *theOut;
+    pMan->aOutput[pMan->cursor] = Th_Vtab_OutputMethods_empty;
+    if(-1 == --pMan->cursor){
+      Th_Interp * interp = pMan->interp;
+      Th_Free( pMan->interp, pMan->aBuf );
+      Th_Free( pMan->interp, pMan->aOutput );
+      *pMan = Th_Ob_Man_empty;
+      pMan->interp = interp;
+      assert(-1 == pMan->cursor);
+    }
+    return rc;
+  }
+}
+
+int Th_Ob_PopAndFree( Th_Ob_Manager * pMan ){
+  Blob * b = Th_Ob_Pop( pMan );
+  if(!b) return 1;
+  else {
+    blob_reset(b);
+    Th_Free( pMan->interp, b );
+    return 0;
+  }
+}
+
+
+void Th_ob_cleanup( Th_Ob_Manager * man ){
+  while( 0 == Th_Ob_PopAndFree(man) ){}
+}
+
+
+/*
+** TH command:
+**
+** ob clean
+**
+** Erases any currently buffered contents but does not modify
+** the buffering level.
+*/
+static int ob_clean_command( Th_Interp *interp, void *ctx,
+                             int argc,  const char **argv, int *argl
+){
+  const char doRc = ctx ? 1 : 0;
+  Th_Ob_Manager * pMan = ctx ? (Th_Ob_Manager *)ctx : Th_Ob_GetManager(interp);
+  Blob * b;
+  assert( pMan && (interp == pMan->interp) );
+  b = pMan ? Th_Ob_GetCurrentBuffer(pMan) : NULL;
+  if(!b){
+    Th_ErrorMessage( interp, "Not currently buffering.", NULL, 0 );
+    return TH_ERROR;
+  }else{
+    blob_reset(b);
+    if( doRc ) {
+      Th_SetResultInt( interp, 0 );
+    }
+    return TH_OK;
+  }
+}
+
+/*
+** TH command:
+**
+** ob end
+**
+** Erases any currently buffered contents and pops the current buffer
+** from the stack.
+*/
+static int ob_end_command( Th_Interp *interp, void *ctx,
+                           int argc,  const char **argv, int *argl ){
+  const char doRc = ctx ? 1 : 0;
+  Th_Ob_Manager * pMan = ctx ? (Th_Ob_Manager *)ctx : Th_Ob_GetManager(interp);
+  Blob * b;
+  assert( pMan && (interp == pMan->interp) );
+  b = Th_Ob_Pop(pMan);
+  if(!b){
+    Th_ErrorMessage( interp, "Not currently buffering.", NULL, 0 );
+    return TH_ERROR;
+  }else{
+    blob_reset(b);
+    Th_Free( interp, b );
+    if(doRc){
+      Th_SetResultInt( interp, 0 );
+    }
+    return TH_OK;
+  }
+}
+
+/*
+** TH command:
+**
+** ob flush ?pop|end?
+**
+** Briefly reverts the output layer to the next-lower
+** level, flushes the current buffer to that output layer,
+** and clears out the current buffer. Does not change the
+** buffering level unless "end" is specified, in which case
+** it behaves as if "ob end" had been called (after flushing
+** the buffer).
+*/
+static int ob_flush_command( Th_Interp *interp, void *ctx,
+                             int argc,  const char **argv, int *argl ){
+  Th_Ob_Manager * pMan = (Th_Ob_Manager *)ctx;
+  Blob * b = NULL;
+  Th_Vtab * oldVtab;
+  int rc = TH_OK;
+  assert( pMan && (interp == pMan->interp) );
+  b = Th_Ob_GetCurrentBuffer(pMan);
+  if( NULL == b ){
+    Th_ErrorMessage( interp, "Not currently buffering.", NULL, 0 );
+    return TH_ERROR;
+  }
+  oldVtab = interp->pVtab;
+  interp->pVtab->out = pMan->aOutput[pMan->cursor];
+  Th_Output( interp, blob_str(b), b->nUsed );
+  interp->pVtab = oldVtab;
+  blob_reset(b);
+
+  if(!rc && argc>2){
+    int argPos = 2;
+    char const * sub = argv[argPos];
+    int subL = argl[argPos];
+    /* "flush end" */
+    if(th_strlen(sub)==3 &&
+       ((0==memcmp("end", sub, subL)
+         || (0==memcmp("pop", sub, subL))))){
+      rc |= ob_end_command(interp, NULL, argc-1, argv+1, argl+1);
+    }
+  }
+  Th_SetResultInt( interp, 0 );
+  return rc;
+}
+
+/*
+** TH command:
+**
+** ob get ?clean|end|pop?
+**
+** Fetches the contents of the current buffer level.  If either
+** 'clean' or 'end' are specified then the effect is as if "ob clean"
+** or "ob end", respectively, are called after fetching the
+** value. Calling "ob get end" is functionality equivalent to "ob get"
+** followed by "ob end".
+*/
+static int ob_get_command( Th_Interp *interp, void *ctx,
+                           int argc,  const char **argv, int *argl){
+  Th_Ob_Manager * pMan = (Th_Ob_Manager *)ctx;
+  Blob * b = NULL;
+  assert( pMan && (interp == pMan->interp) );
+  b = Th_Ob_GetCurrentBuffer(pMan);
+  if( NULL == b ){
+    Th_ErrorMessage( interp, "Not currently buffering.", NULL, 0 );
+    return TH_ERROR;
+  }else{
+    int argPos = 2;
+    char const * sub;
+    int subL;
+    int rc = TH_OK;
+    Th_SetResult( interp, blob_str(b), b->nUsed );
+    if(argc>argPos){
+      sub = argv[argPos];
+      subL = argl[argPos];
+      /* "ob get clean" */
+      if(!rc && th_strlen(sub)==5 && 0==memcmp("clean", sub, subL)){
+        rc |= ob_clean_command(interp, NULL, argc-1, argv+1, argl+1);
+      }/* "ob get end" */
+      else if(!rc && th_strlen(sub)==3 &&
+              ((0==memcmp("end", sub, subL))
+               || (0==memcmp("pop", sub, subL)))){
+        rc |= ob_end_command(interp, NULL, argc-1, argv+1, argl+1);
+      }
+    }
+    return rc;
+  }
+}
+
+/*
+** TH command:
+**
+** ob level
+**
+** Returns the buffering level, where 0 means no buffering is
+** active, 1 means 1 level is active, etc.
+*/
+static int ob_level_command( Th_Interp *interp, void *ctx,
+                             int argc,  const char **argv, int *argl
+){
+  Th_Ob_Manager * pMan = (Th_Ob_Manager *)ctx;
+  Th_SetResultInt( interp, 1 + pMan->cursor );
+  return TH_OK;
+}
+
+/*
+** TH command:
+**
+** ob start|push
+**
+** Pushes a new level of buffering onto the buffer stack.
+** Returns the new buffering level (1-based).
+**
+** TODO: take an optional final argument naming the output handler.
+** e.g. "stdout" or "cgi" or "default"
+** 
+*/
+static int ob_start_command( Th_Interp *interp, void *ctx,
+                             int argc,  const char **argv, int *argl
+){
+  Th_Ob_Manager * pMan = (Th_Ob_Manager *)ctx;
+  Blob * b = NULL;
+  int rc;
+  Th_Vtab_OutputMethods const * pWriter = &Th_Vtab_OutputMethods_ob;
+  assert( pMan && (interp == pMan->interp) );
+  rc = Th_Ob_Push(pMan, NULL, &b);
+  if( TH_OK != rc ){
+    assert( NULL == b );
+    return rc;
+  }
+  assert( NULL != b );
+  Th_SetResultInt( interp, 1 + pMan->cursor );
+  return TH_OK;
+}
+
+static void finalizerObMan( Th_Interp * interp, void * p ){
+  Th_Ob_Manager * man = (Th_Ob_Manager*)p;
+  if(man){
+    assert( interp == man->interp );
+    Th_ob_cleanup( man );
+    Th_Free( interp, p );
+  }
+}
+
+/*
+** TH command:
+**
+** ob clean|(end|pop)|flush|get|level|(start|push)
+**
+** Runs the given subcommand. Some subcommands have other subcommands
+** (see their docs for details).
+** 
+*/
+static int ob_cmd(
+  Th_Interp *interp, 
+  void *ignored, 
+  int argc, 
+  const char **argv, 
+  int *argl
+){
+  Th_Ob_Manager * pMan = Th_Ob_GetManager(interp);
+  Th_SubCommand aSub[] = {
+    { "clean",     ob_clean_command },
+    { "end",       ob_end_command },
+    { "flush",     ob_flush_command },
+    { "get",       ob_get_command },
+    { "level",     ob_level_command },
+    { "pop",       ob_end_command },
+    { "push",      ob_start_command },
+    { "start",     ob_start_command },
+    /* TODO: enable/disable commands which call Th_OutputEnable(). */
+    { 0, 0 }
+  };
+  assert(NULL != pMan && pMan->interp==interp);
+  return Th_CallSubCommand(interp, pMan, argc, argv, argl, aSub);
+}
+
+int th_register_ob(Th_Interp * interp){
+  int rc;
+  static Th_Command_Reg aCommand[] = {
+    {"ob",    ob_cmd,   0},
+    {0,0,0}
+  };
+  rc = Th_RegisterCommands( interp, aCommand );
+  if(NULL == Th_Ob_GetManager(interp)){
+    Th_Ob_Manager * pMan;
+    pMan = Th_Malloc(interp, sizeof(Th_Ob_Manager));
+    if(!pMan){
+      rc = TH_ERROR;
+    }else{
+      *pMan = Th_Ob_Man_empty;
+      pMan->interp = interp;
+      assert( -1 == pMan->cursor );
+      Th_SetData( interp, Th_Ob_Man_KEY, pMan, finalizerObMan );
+      assert( NULL != Th_Ob_GetManager(interp) );
+    }
+  }
+  return rc;
+}
+
+#undef Th_Ob_Man_empty_m
+#undef Th_Vtab_OutputMethods_empty_m
+#undef Th_Vtab_OutputMethods_ob_m
+#undef Th_Ob_Man_KEY
+#endif
+/* end TH_ENABLE_OB */

Index: src/th.h
==================================================================
--- src/th.h
+++ src/th.h
@@ -1,32 +1,120 @@
+#include "config.h"
+
+/*
+** TH_ENABLE_QUERY, if defined, enables the "query" family of functions.
+** They provide SELECT-only access to the repository db.
+*/
+#define TH_ENABLE_QUERY
+
+/*
+** TH_ENABLE_OB, if defined, enables the "ob" family of functions.
+** They are functionally similar to PHP's ob_start(), ob_end(), etc.
+** family of functions, providing output capturing/buffering.
+*/
+#define TH_ENABLE_OB
+
+/*
+** TH_ENABLE_ARGV, if defined, enables the "argv" family of functions.
+** They provide access to CLI arguments as well as GET/POST arguments.
+** They do not provide access to POST data submitted in JSON mode.
+*/
+#define TH_ENABLE_ARGV
+
+#ifdef TH_ENABLE_OB
+#ifndef INTERFACE
+#include "blob.h" /* maintenance reminder: also pulls in fossil_realloc() and friends */
+#endif
+#endif
 
 /* This header file defines the external interface to the custom Scripting
 ** Language (TH) interpreter.  TH is very similar to TCL but is not an
 ** exact clone.
 */
 
+/*
+** Th_Output_f() specifies a generic output routine for use by
+** Th_Vtab_OutputMethods and friends. Its first argument is the data to
+** write, the second is the number of bytes to write, and the 3rd is
+** an implementation-specific state pointer (may be NULL, depending on
+** the implementation). The return value is the number of bytes output
+** (which may differ from len due to encoding and whatnot).  On error
+** a negative value must be returned.
+*/
+typedef int (*Th_Output_f)( char const * zData, int len, void * pState );
+
+/*
+** This structure defines the output state associated with a
+** Th_Vtab. It is intended that a given Vtab be able to swap out
+** output back-ends during its lifetime, e.g. to form a stack of
+** buffers.
+*/
+struct Th_Vtab_OutputMethods {
+  Th_Output_f xWrite;   /* output handler */
+    void (*xDispose)( void * pState ); /* Called when the framework is done with
+                                         this output handler,passed this object's
+                                         pState pointer.. */
+  void * pState;   /* final argument for xWrite() and xDispose()*/
+  char enabled;    /* if 0, Th_Output() does nothing. */
+};
+typedef struct Th_Vtab_OutputMethods Th_Vtab_OutputMethods;
+
+/*
+** Shared Th_Vtab_OutputMethods instance used for copy-initialization. This
+** implementation uses Th_Output_f_FILE as its write() impl and
+** Th_Output_dispose_FILE() for cleanup. If its pState member is NULL
+** it outputs to stdout, else pState must be a (FILE*) which it will
+** output to.
+*/
+extern const Th_Vtab_OutputMethods Th_Vtab_OutputMethods_FILE;
+
 /*
 ** Before creating an interpreter, the application must allocate and
 ** populate an instance of the following structure. It must remain valid
 ** for the lifetime of the interpreter.
 */
 struct Th_Vtab {
-  void *(*xMalloc)(unsigned int);
-  void (*xFree)(void *);
+  void *(*xRealloc)(void *, unsigned int); /**
+                                           Re/deallocation routine. Must behave like
+                                           realloc(3), with the minor extension that
+                                           realloc(anything,positiveValue) _must_ return
+                                           NULL on allocation error. The Standard's wording
+                                           allows realloc() to return "some value suitable for
+                                           passing to free()" on error, but because client code
+                                           has no way of knowing if any non-NULL value is an error
+                                           value, no sane realloc() implementation would/should
+                                           return anything _but_ NULL on allocation error.
+                                           */
+  Th_Vtab_OutputMethods out;                      /** Output handler. TH functions which generate
+                                               output should send it here (via Th_Output()).
+                                           */
 };
 typedef struct Th_Vtab Th_Vtab;
+
 
 /*
 ** Opaque handle for interpeter.
 */
 typedef struct Th_Interp Th_Interp;
+
 
 /* 
-** Create and delete interpreters. 
+** Creates a new interpreter instance using the given v-table. pVtab
+** must outlive the returned object, and pVtab->out.xDispose() will be
+** called when the interpreter is cleaned up. The optional "ob" API
+** swaps out Vtab::out instances, so pVtab->out might not be active
+** for the entire lifetime of the interpreter.
+**
+** Potential TODO: we "should probably" add a dispose() method to the
+** Th_Vtab interface.
 */
 Th_Interp * Th_CreateInterp(Th_Vtab *pVtab);
-void Th_DeleteInterp(Th_Interp *);
+
+/*
+** Frees up all resources associated with interp then frees interp.
+*/
+void Th_DeleteInterp(Th_Interp *interp);
 
 /* 
 ** Evaluate an TH program in the stack frame identified by parameter
 ** iFrame, according to the following rules:
 **
@@ -54,19 +142,37 @@
 int Th_GetVar(Th_Interp *, const char *, int);
 int Th_SetVar(Th_Interp *, const char *, int, const char *, int);
 int Th_LinkVar(Th_Interp *, const char *, int, int, const char *, int);
 int Th_UnsetVar(Th_Interp *, const char *, int);
 
-typedef int (*Th_CommandProc)(Th_Interp *, void *, int, const char **, int *);
+/*
+** Typedef for Th interpreter callbacks, i.e. script-bound native C
+** functions.
+**
+** The interp argument is the interpreter running the function. pState
+** is arbitrary state which is passed to Th_CreateCommand(). arg
+** contains the number of arguments (argument #0 is the command's
+** name, in the same way that main()'s argv[0] is the binary's
+** name). argv is the list of arguments. argl is an array argc items
+** long which contains the length of each argument in the
+** list. e.g. argv[0] is argl[0] bytes long.
+*/
+typedef int (*Th_CommandProc)(Th_Interp * interp, void * pState, int argc, const char ** argv, int * argl);
 
 /* 
-** Register new commands. 
+** Registers a new command with interp. zName must be a NUL-terminated
+** name for the function. xProc is the native implementation of the
+** function.  pContext is arbitrary data to pass as xProc()'s 2nd
+** argument. xDel is an optional finalizer which should be called when
+** interpreter is finalized. If xDel is not NULL then it is passed
+** (interp,pContext) when interp is finalized.
+**
+** Return TH_OK on success.
 */
 int Th_CreateCommand(
   Th_Interp *interp, 
   const char *zName, 
-  /* int (*xProc)(Th_Interp *, void *, int, const char **, int *), */
   Th_CommandProc xProc,
   void *pContext,
   void (*xDel)(Th_Interp *, void *)
 );
 
@@ -120,11 +226,11 @@
 ** Access the memory management functions associated with the specified
 ** interpreter.
 */
 void *Th_Malloc(Th_Interp *, int);
 void Th_Free(Th_Interp *, void *);
-
+void *Th_Realloc(Th_Interp *, void *, int);
 /* 
 ** Functions for handling TH lists.
 */
 int Th_ListAppend(Th_Interp *, char **, int *, const char *, int);
 int Th_SplitList(Th_Interp *, const char *, int, char ***, int **, int *);
@@ -136,10 +242,12 @@
 */
 int Th_ToInt(Th_Interp *, const char *, int, int *);
 int Th_ToDouble(Th_Interp *, const char *, int, double *);
 int Th_SetResultInt(Th_Interp *, int);
 int Th_SetResultDouble(Th_Interp *, double);
+int Th_TryInt(Th_Interp *, const char * zArg, int nArg, int * piOut);
+int Th_TryDouble(Th_Interp *, const char * zArg, int nArg, double * pfOut);
 
 /*
 ** Drop in replacements for the corresponding standard library functions.
 */
 int th_strlen(const char *);
@@ -151,14 +259,38 @@
 
 /*
 ** Interfaces to register the language extensions.
 */
 int th_register_language(Th_Interp *interp);            /* th_lang.c */
-int th_register_sqlite(Th_Interp *interp);              /* th_sqlite.c */
 int th_register_vfs(Th_Interp *interp);                 /* th_vfs.c */
 int th_register_testvfs(Th_Interp *interp);             /* th_testvfs.c */
+
+/*
+** Registers the TCL extensions. Only available if FOSSIL_ENABLE_TCL
+** is enabled at compile-time.
+*/
 int th_register_tcl(Th_Interp *interp, void *pContext); /* th_tcl.c */
+
+#ifdef TH_ENABLE_ARGV
+/*
+** Registers the "argv" API. See www/th1_argv.wiki.
+*/
+int th_register_argv(Th_Interp *interp);                /* th_main.c */
+#endif
+
+#ifdef TH_ENABLE_QUERY
+/*
+** Registers the "query" API. See www/th1_query.wiki.
+*/
+int th_register_query(Th_Interp *interp);              /* th_main.c */
+#endif
+#ifdef TH_ENABLE_OB
+/*
+** Registers the "ob" API. See www/th1_ob.wiki.
+*/
+int th_register_ob(Th_Interp * interp);                 /* th.c */
+#endif
 
 /*
 ** General purpose hash table from th_lang.c.
 */
 typedef struct Th_Hash      Th_Hash;
@@ -175,9 +307,223 @@
 Th_HashEntry *Th_HashFind(Th_Interp*, Th_Hash*, const char*, int, int);
 
 /*
 ** Useful functions from th_lang.c.
 */
+
+/*
+** Generic "wrong number of arguments" helper which sets the error
+** state of interp to the given message plus a generic prefix.
+*/
 int Th_WrongNumArgs(Th_Interp *interp, const char *zMsg);
 
+/*
+** Works like Th_WrongNumArgs() but expects (zCmdName,zCmdLen) to be
+** the current command's (name,length), i.e. (argv[0],argl[0]).
+*/
+int Th_WrongNumArgs2(Th_Interp *interp, const char *zCmdName,
+                     int zCmdLen, const char *zMsg);
+
 typedef struct Th_SubCommand {char *zName; Th_CommandProc xProc;} Th_SubCommand;
 int Th_CallSubCommand(Th_Interp*,void*,int,const char**,int*,Th_SubCommand*);
+
+/*
+** Works similarly to Th_CallSubCommand() but adjusts argc/argv/argl
+** by 1 before passing on the call to the subcommand. This allows them
+** to function the same whether they are called as top-level commands
+** or as sub-sub-commands.
+*/
+int Th_CallSubCommand2(Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl, Th_SubCommand *aSub);
+
+/*
+** Sends the given data through vTab->out.f() if vTab->out.enabled is
+** true, otherwise this is a no-op. Returns 0 or higher on success, *
+** a negative value if vTab->out.f is NULL.
+*/
+int Th_Vtab_Output( Th_Vtab *vTab, char const * zData, int len );
+
+/*
+** Sends the given output through pInterp's vtab's output
+** implementation. See Th_Vtab_OutputMethods() for the argument and
+** return value semantics.
+*/
+int Th_Output( Th_Interp *pInterp, char const * zData, int len );
+
+/*
+** Enables or disables output of the current Vtab API, depending on
+** whether flag is true (non-0) or false (0). Note that when output
+** buffering/stacking is enabled (e.g. via the "ob" API) this modifies
+** only the current output mechanism, and not any further down the
+** stack.
+*/
+void Th_OutputEnable( Th_Interp *pInterp, char flag );
+
+/*
+** Returns true if output is enabled for the current output mechanism
+** of pInterp, else false. See Th_OutputEnable().
+*/
+char Th_OutputEnabled( Th_Interp *pInterp );
+
+
+
+/*
+** A Th_Output_f() implementation which sends its output to either
+** pState (which must be NULL or a (FILE*)) or stdout (if pState is
+** NULL).
+*/
+int Th_Output_f_FILE( char const * zData, int len, void * pState );
+
+/*
+** A Th_Vtab_OutputMethods::xDispose() impl for FILE handles. If pState is not
+** one of the standard streams (stdin, stdout, stderr) then it is
+** fclose()d by this call.
+*/
+void Th_Output_dispose_FILE( void * pState );
+
+/*
+** A helper type for holding lists of function registration information.
+** For use with Th_RegisterCommands().
+*/
+struct Th_Command_Reg {
+  const char *zName;     /* Function name. */
+  Th_CommandProc xProc;  /* Callback function */
+  void *pContext;        /* Arbitrary data for the callback. */
+};
+typedef struct Th_Command_Reg Th_Command_Reg;
+
+/*
+** Th_Render_Flags_XXX are flags for Th_Render().
+*/
+/* makeheaders cannot do enums: enum Th_Render_Flags {...};*/
+/*
+** Default flags ("compatibility mode").
+*/
+#define Th_Render_Flags_DEFAULT 0
+/*
+** If set, Th_Render() will not process $var and $<var>
+** variable references outside of TH1 blocks.
+*/
+#define Th_Render_Flags_NO_DOLLAR_DEREF (1 << 1)
+
+/*
+** Runs the given th1 program through Fossil's th1 interpreter. Flags
+** may contain a bitmask made up of any of the Th_Render_Flags_XXX
+** values.
+*/
+int Th_Render(const char *zTh1Program, int Th_Render_Flags);
+
+/*
+** Adds a piece of memory to the given interpreter, such that:
+**
+** a) it will be cleaned up when the interpreter is destroyed, by
+** calling finalizer(interp, pData). The finalizer may be NULL.
+** Cleanup happens in an unspecified/unpredictable order.
+**
+** b) it can be fetched via Th_GetData().
+**
+** If a given key is added more than once then any previous
+** entry is cleaned up before adding it.
+**
+** Returns 0 on success, non-0 on allocation error.
+*/
+int Th_SetData( Th_Interp * interp, char const * key,
+                 void * pData,
+                 void (*finalizer)( Th_Interp *, void * ) );
+
+/*
+** Fetches data added via Th_SetData(), or NULL if no data
+** has been associated with the given key.
+*/
+void * Th_GetData( Th_Interp * interp, char const * key );
+
+
+/*
+** Registers a list of commands with the interpreter. pList must be a non-NULL
+** pointer to an array of Th_Command_Reg objects, the last one of which MUST
+** have a NULL zName field (that is the end-of-list marker).
+** Returns TH_OK on success, "something else" on error.
+*/
+int Th_RegisterCommands( Th_Interp * interp, Th_Command_Reg const * pList );
+
+#ifdef TH_ENABLE_OB
+/*
+** Output buffer stack manager for TH. Used/managed by the Th_ob_xxx()
+** functions. This class manages Th_Interp::pVtab->out for a specific
+** interpreter, swapping it in and out in order to redirect output
+** generated via Th_Output() to internal buffers. The buffers can be
+** pushed and popped from the stack, allowing clients to selectively
+** capture output for a given block of TH1 code.
+*/
+struct Th_Ob_Manager {
+  Blob ** aBuf;        /* Stack of Blobs */
+  int nBuf;            /* Number of blobs */
+  int cursor;          /* Current level (-1=not active) */
+  Th_Interp * interp;  /* The associated interpreter */
+  Th_Vtab_OutputMethods * aOutput
+                       /* Stack of output routines corresponding
+                          to the current buffering level.
+                          Has nBuf entries.
+                       */;
+};
+
+/*
+** Manager of a stack of Th_Vtab_Output objects for output buffering.
+** It gets its name ("ob") from the similarly-named PHP functionality.
+**
+** See Th_Ob_GetManager().
+**
+** Potential TODO: remove the Blob from the interface and replace it
+** with a Th_Output_f (or similar) which clients can pass in to have
+** the data transfered from Th_Ob_Manager to them. We would also need to
+** add APIs for clearing the buffer.
+*/
+typedef struct Th_Ob_Manager Th_Ob_Manager;
+
+/*
+** Returns the ob manager for the given interpreter. The manager gets
+** installed by the th_register_ob(). In Fossil ob support is
+** installed automatically if it is available at built time.
+*/
+Th_Ob_Manager * Th_Ob_GetManager(Th_Interp *ignored);
+
+/*
+** Returns the top-most Blob in pMan's stack, or NULL if buffering is
+** not active or if the current buffering level does not refer to a
+** blob. (Note: the latter will never currently be the case, but may
+** be if the API is expanded to offer other output direction options,
+** e.g.  (ob start file /tmp/foo.out).)
+*/
+Blob * Th_Ob_GetCurrentBuffer( Th_Ob_Manager * pMan );
+
+/*
+** Pushes a new blob onto pMan's stack. On success returns TH_OK and
+** assigns *pOut (if pOut is not NULL) to the new blob (which is owned
+** by pMan). On error pOut is not modified and non-0 is returned. The
+** new blob can be cleaned up via Th_Ob_Pop() or Th_Ob_PopAndFree()
+** (please read both to understand the difference!).
+*/
+int Th_Ob_Push( Th_Ob_Manager * pMan, Th_Vtab_OutputMethods const * pWriter, Blob ** pOut );
+
+/*
+** Pops the top-most output buffer off the stack and returns
+** it. Returns NULL if there is no current buffer. When the last
+** buffer is popped, pMan's internals are cleaned up (but pMan is not
+** freed).
+**
+** The caller owns the returned object and must eventually clean it up
+** by first passing it to blob_reset() and then Th_Free() it.
+**
+** See also: Th_Ob_PopAndFree().
+*/
+Blob * Th_Ob_Pop( Th_Ob_Manager * pMan );
+
+/*
+** Convenience form of Th_Ob_Pop() which pops and frees the
+** top-most buffer. Returns 0 on success, non-0 if there is no
+** stack to pop. Thus is can be used in a loop like:
+**
+** while( !Th_Ob_PopAndFree(theManager) ) {}
+*/
+int Th_Ob_PopAndFree( Th_Ob_Manager * pMan );
+
+#endif
+/* end TH_ENABLE_OB */

Index: src/th_lang.c
==================================================================
--- src/th_lang.c
+++ src/th_lang.c
@@ -16,10 +16,23 @@
 
 int Th_WrongNumArgs(Th_Interp *interp, const char *zMsg){
   Th_ErrorMessage(interp, "wrong # args: should be \"", zMsg, -1);
   return TH_ERROR;
 }
+
+int Th_WrongNumArgs2(Th_Interp *interp, const char *zCmdName,
+                     int zCmdLen, const char *zMsg){
+  char * zBuf = 0;
+  int nBuf = 0;
+  Th_StringAppend(interp, &zBuf, &nBuf, zCmdName, zCmdLen);
+  Th_StringAppend(interp, &zBuf, &nBuf, ": wrong # args: expecting: ", -1);
+  Th_StringAppend(interp, &zBuf, &nBuf, zMsg, -1);
+  Th_StringAppend(interp, &zBuf, &nBuf, "", 1);
+  Th_ErrorMessage(interp, zBuf, NULL, 0);
+  Th_Free(interp, zBuf);
+  return TH_ERROR;
+}
 
 /*
 ** Syntax: 
 **
 **   catch script ?varname?
@@ -458,13 +471,15 @@
 
   /* If the last parameter in the parameter list is "args", then set the
   ** ProcDefn.hasArgs flag. The "args" parameter does not require an
   ** entry in the ProcDefn.azParam[] or ProcDefn.azDefault[] arrays.
   */
-  if( anParam[nParam-1]==4 && 0==memcmp(azParam[nParam-1], "args", 4) ){
-    p->hasArgs = 1;
-    nParam--;
+  if(nParam>0){
+    if( anParam[nParam-1]==4 && 0==memcmp(azParam[nParam-1], "args", 4) ){
+      p->hasArgs = 1;
+      nParam--;
+    }
   }
 
   p->nParam    = nParam;
   p->azParam   = (char **)&p[1];
   p->anParam   = (int *)&p->azParam[nParam];
@@ -870,10 +885,30 @@
   }
 
   Th_ErrorMessage(interp, "Expected sub-command, got:", argv[1], argl[1]);
   return TH_ERROR;
 }
+
+int Th_CallSubCommand2(
+  Th_Interp *interp, 
+  void *ctx,
+  int argc,
+  const char **argv,
+  int *argl,
+  Th_SubCommand *aSub
+){
+  int i;
+  for(i=0; aSub[i].zName; i++){
+    char const *zName = aSub[i].zName;
+    if( th_strlen(zName)==argl[1] && 0==memcmp(zName, argv[1], argl[1]) ){
+      return aSub[i].xProc(interp, ctx, argc-1, argv+1, argl+1);
+    }
+  }
+  Th_ErrorMessage(interp, "Expected sub-command, got:", argv[1], argl[1]);
+  return TH_ERROR;
+}
+
 
 /*
 ** TH Syntax:
 **
 **   string compare STR1 STR2
@@ -1028,16 +1063,13 @@
 /*
 ** Register the built-in th1 language commands with interpreter interp.
 ** Usually this is called soon after interpreter creation.
 */
 int th_register_language(Th_Interp *interp){
+  int rc;
   /* Array of built-in commands. */
-  struct _Command {
-    const char *zName;
-    Th_CommandProc xProc;
-    void *pContext;
-  } aCommand[] = {
+  struct Th_Command_Reg aCommand[] = {
     {"catch",    catch_command,   0},
     {"expr",     expr_command,    0},
     {"for",      for_command,     0},
     {"if",       if_command,      0},
     {"info",     info_command,    0},
@@ -1059,17 +1091,8 @@
     {"continue", simple_command, (void *)TH_CONTINUE}, 
     {"error",    simple_command, (void *)TH_ERROR}, 
 
     {0, 0, 0}
   };
-  int i;
-
-  /* Add the language commands. */
-  for(i=0; i<(sizeof(aCommand)/sizeof(aCommand[0])); i++){
-    void *ctx;
-    if ( !aCommand[i].zName || !aCommand[i].xProc ) continue;
-    ctx = aCommand[i].pContext;
-    Th_CreateCommand(interp, aCommand[i].zName, aCommand[i].xProc, ctx, 0);
-  }
-
-  return TH_OK;
+  rc = Th_RegisterCommands(interp, aCommand);
+  return rc;
 }

Index: src/th_main.c
==================================================================
--- src/th_main.c
+++ src/th_main.c
@@ -19,10 +19,16 @@
 ** (an independent project) and fossil.
 */
 #include "config.h"
 #include "th_main.h"
 
+#ifdef TH_ENABLE_QUERY
+#ifndef INTERFACE
+#include "sqlite3.h"
+#endif
+#endif
+
 /*
 ** Global variable counting the number of outstanding calls to malloc()
 ** made by the th1 implementation. This is used to catch memory leaks
 ** in the interpreter. Obviously, it also means th1 is not threadsafe.
 */
@@ -40,13 +46,30 @@
 }
 static void xFree(void *p){
   if( p ){
     nOutstandingMalloc--;
   }
-  free(p);
+  fossil_free(p);
 }
-static Th_Vtab vtab = { xMalloc, xFree };
+
+/*
+** Default Th_Vtab::xRealloc() implementation.
+*/
+static void *xRealloc(void * p, unsigned int n){
+  assert(n>=0 && "Invalid memory (re/de)allocation size.");
+  if(0 == n){
+    xFree(p);
+    return NULL;
+  }else if(NULL == p){
+    return xMalloc(n);
+  }else{
+    return fossil_realloc(p, n)
+      /* In theory nOutstandingMalloc doesn't need to be updated here
+         unless xRealloc() is sorely misused.
+      */;
+  }
+}
 
 /*
 ** Generate a TH1 trace message if debugging is enabled.
 */
 void Th_Trace(const char *zFormat, ...){
@@ -57,10 +80,18 @@
 }
 
 
 /*
 ** True if output is enabled.  False if disabled.
+**
+** We "could" replace this with Th_OutputEnable() and friends, but
+** there is a functional difference: this particular flag prohibits
+** some extra escaping which would happen (but be discared, unused) if
+** relied solely on that API. Also, because that API only works on the
+** current Vtab_Output handler, relying soly on that handling would
+** introduce incompatible behaviour with the historical enable_output
+** command.
 */
 static int enableOutput = 1;
 
 /*
 ** TH command:     enable_output BOOLEAN
@@ -73,53 +104,97 @@
   int argc, 
   const char **argv, 
   int *argl
 ){
   if( argc!=2 ){
-    return Th_WrongNumArgs(interp, "enable_output BOOLEAN");
+    return Th_WrongNumArgs2(interp,
+                            argv[0], argl[0],
+                           "BOOLEAN");
+  }else{
+    int rc = Th_ToInt(interp, argv[1], argl[1], &enableOutput);
+    return rc;
   }
-  return Th_ToInt(interp, argv[1], argl[1], &enableOutput);
 }
+
+/*
+** Th_Output_f() impl which sends all output to cgi_append_content().
+*/
+static int Th_Output_f_cgi_content( char const * zData, int nData, void * pState ){
+  cgi_append_content(zData, nData);
+  return nData;
+}
+
 
 /*
 ** Send text to the appropriate output:  Either to the console
 ** or to the CGI reply buffer.
 */
-static void sendText(const char *z, int n, int encode){
+static void sendText(Th_Interp *pInterp, const char *z, int n, int encode){
+  if(NULL == pInterp){
+    pInterp = g.interp;
+  }
+  assert( NULL != pInterp );
   if( enableOutput && n ){
     if( n<0 ) n = strlen(z);
     if( encode ){
       z = htmlize(z, n);
       n = strlen(z);
     }
-    if( g.cgiOutput ){
-      cgi_append_content(z, n);
-    }else{
-      fwrite(z, 1, n, stdout);
-      fflush(stdout);
-    }
-    if( encode ) free((char*)z);
+    Th_Output( pInterp, z, n );
+    if( encode ) fossil_free((char*)z);
   }
 }
 
+/*
+** Internal state for the putsCmd() function, allowing it to be used
+** as the basis for multiple implementations with slightly different
+** behaviours based on the context. An instance of this type must be
+** set as the Context parameter for any putsCmd()-based script command
+** binding.
+*/
+struct PutsCmdData {
+  char escapeHtml;    /* If true, htmlize all output. */
+  char const * sep;   /* Optional NUL-terminated separator to output
+                         between arguments. May be NULL. */
+  char const * eol;   /* Optional NUL-terminated end-of-line separator,
+                         output after the final argument. May be NULL. */
+};
+typedef struct PutsCmdData PutsCmdData;
+
 /*
 ** TH command:     puts STRING
 ** TH command:     html STRING
 **
-** Output STRING as HTML (html) or unchanged (puts).  
+** Output STRING as HTML (html) or unchanged (puts).
+**
+** pConvert MUST be a (PutsCmdData [const]*). It is not modified by
+** this function.
 */
 static int putsCmd(
   Th_Interp *interp, 
   void *pConvert, 
   int argc, 
   const char **argv, 
   int *argl
 ){
-  if( argc!=2 ){
-    return Th_WrongNumArgs(interp, "puts STRING");
+  PutsCmdData const * fmt = (PutsCmdData const *)pConvert;
+  const int sepLen = fmt->sep ? strlen(fmt->sep) : 0;
+  int i;
+  if( argc<2 ){
+    return Th_WrongNumArgs2(interp,
+                            argv[0], argl[0],
+                            "STRING ...STRING_N");
   }
-  sendText((char*)argv[1], argl[1], pConvert!=0);
+  for( i = 1; i < argc; ++i ){
+    if(sepLen && (i>1)){
+      sendText(interp, fmt->sep, sepLen, 0);
+    }
+    sendText(interp, (char const*)argv[i], argl[i], fmt->escapeHtml);
+  }
+  if(fmt->eol){
+    sendText(interp, fmt->eol, strlen(fmt->eol), 0);
+  }
   return TH_OK;
 }
 
 /*
 ** TH command:      wiki STRING
@@ -132,11 +207,13 @@
   int argc, 
   const char **argv, 
   int *argl
 ){
   if( argc!=2 ){
-    return Th_WrongNumArgs(interp, "wiki STRING");
+    return Th_WrongNumArgs2(interp,
+                            argv[0], argl[0],
+                            "STRING");
   }
   if( enableOutput ){
     Blob src;
     blob_init(&src, (char*)argv[1], argl[1]);
     wiki_convert(&src, 0, WIKI_INLINE);
@@ -158,18 +235,74 @@
   const char **argv, 
   int *argl
 ){
   char *zOut;
   if( argc!=2 ){
-    return Th_WrongNumArgs(interp, "htmlize STRING");
+    return Th_WrongNumArgs2(interp,
+                            argv[0], argl[0],
+                            "STRING");
   }
   zOut = htmlize((char*)argv[1], argl[1]);
   Th_SetResult(interp, zOut, -1);
   free(zOut);
   return TH_OK;
 }
 
+#if 0
+/* This is not yet needed, but something like it may become useful for
+   custom page/command support, for rendering snippets/templates. */
+/*
+** TH command:      render STRING
+**
+** Render the input string as TH1.
+*/
+static int renderCmd(
+  Th_Interp *interp, 
+  void *p, 
+  int argc, 
+  const char **argv, 
+  int *argl
+){
+  if( argc<2 ){
+    return Th_WrongNumArgs2(interp,
+                            argv[0], argl[0],
+                            "STRING ?STRING...?");
+  }else{
+    Th_Ob_Manager * man = Th_Ob_GetManager(interp);
+    Blob * b = NULL;
+    Blob buf = empty_blob;
+    int rc, i;
+    /*FIXME: assert(NULL != man && man->interp==interp);*/
+    man->interp = interp;
+    /* Combine all inputs into one buffer so that we can use that to
+       embed TH1 tags across argument boundaries.
+
+       FIX:E optimize away buf for the 1-arg case.
+     */
+    for( i = 1; TH_OK==rc && i < argc; ++i ){
+      char const * str = argv[i];
+      blob_append( &buf, str, argl[i] );
+      /*rc = Th_Render( str, Th_Render_Flags_NO_DOLLAR_DEREF );*/
+    }
+    rc = Th_Ob_Push( man, &b );
+    if(rc){
+      blob_reset( &buf );
+      return rc;
+    }
+    rc = Th_Render( buf.aData, Th_Render_Flags_DEFAULT );
+    blob_reset(&buf);
+    b = Th_Ob_Pop( man );
+    if(TH_OK==rc){
+      Th_SetResult( interp, b->aData, b->nUsed );
+    }
+    blob_reset( b );
+    Th_Free( interp, b );
+    return rc;
+  }
+}/* renderCmd() */
+#endif
+
 /*
 ** TH command:      date
 **
 ** Return a string which is the current time and date.  If the
 ** -local option is used, the date appears using localtime instead
@@ -205,11 +338,13 @@
   const char **argv, 
   int *argl
 ){
   int rc;
   if( argc!=2 ){
-    return Th_WrongNumArgs(interp, "hascap STRING");
+    return Th_WrongNumArgs2(interp,
+                           argv[0], argl[0],
+                           "STRING");
   }
   rc = login_has_capability((char*)argv[1],argl[1]);
   if( g.thTrace ){
     Th_Trace("[hascap %#h] => %d<br />\n", argl[1], argv[1], rc);
   }
@@ -236,11 +371,13 @@
   int *argl
 ){
   int rc = 0;
   char const * zArg;
   if( argc!=2 ){
-    return Th_WrongNumArgs(interp, "hasfeature STRING");
+    return Th_WrongNumArgs2(interp,
+                            argv[0], argl[0],
+                            "STRING");
   }
   zArg = (char const*)argv[1];
   if(NULL==zArg){
     /* placeholder for following ifdefs... */
   }
@@ -280,11 +417,13 @@
   int *argl
 ){
   int rc = 0;
   int i;
   if( argc!=2 ){
-    return Th_WrongNumArgs(interp, "anycap STRING");
+    return Th_WrongNumArgs2(interp,
+                            argv[0], argl[0],
+                            "STRING");
   }
   for(i=0; rc==0 && i<argl[1]; i++){
     rc = login_has_capability((char*)&argv[1][i],1);
   }
   if( g.thTrace ){
@@ -310,11 +449,13 @@
   int argc, 
   const char **argv, 
   int *argl
 ){
   if( argc!=4 ){
-    return Th_WrongNumArgs(interp, "combobox NAME TEXT-LIST NUMLINES");
+    return Th_WrongNumArgs2(interp,
+                            argv[0], argl[0],
+                            "NAME TEXT-LIST NUMLINES");
   }
   if( enableOutput ){
     int height;
     Blob name;
     int nValue;
@@ -329,11 +470,11 @@
     Th_SplitList(interp, argv[2], argl[2], &azElem, &aszElem, &nElem);
     blob_init(&name, (char*)argv[1], argl[1]);
     zValue = Th_Fetch(blob_str(&name), &nValue);
     z = mprintf("<select name=\"%z\" size=\"%d\">", 
                  htmlize(blob_buffer(&name), blob_size(&name)), height);
-    sendText(z, -1, 0);
+    sendText(interp, z, -1, 0);
     free(z);
     blob_reset(&name);
     for(i=0; i<nElem; i++){
       zH = htmlize((char*)azElem[i], aszElem[i]);
       if( zValue && aszElem[i]==nValue 
@@ -342,14 +483,14 @@
                      zH, zH);
       }else{
         z = mprintf("<option value=\"%s\">%s</option>", zH, zH);
       }
       free(zH);
-      sendText(z, -1, 0);
+      sendText(interp, z, -1, 0);
       free(z);
     }
-    sendText("</select>", -1, 0);
+    sendText(interp, "</select>", -1, 0);
     Th_Free(interp, azElem);
   }
   return TH_OK;
 }
 
@@ -368,11 +509,13 @@
 ){
   const char *z;
   int size, n, i;
   int iMin, iMax;
   if( argc!=4 ){
-    return Th_WrongNumArgs(interp, "linecount STRING MAX MIN");
+    return Th_WrongNumArgs2(interp,
+                            argv[0], argl[0],
+                            "STRING MAX MIN");
   }
   if( Th_ToInt(interp, argv[2], argl[2], &iMax) ) return TH_ERROR;
   if( Th_ToInt(interp, argv[3], argl[3], &iMin) ) return TH_ERROR;
   z = argv[1];
   size = argl[1];
@@ -403,11 +546,13 @@
   int *argl
 ){
   int openRepository;
 
   if( argc!=1 && argc!=2 ){
-    return Th_WrongNumArgs(interp, "repository ?BOOLEAN?");
+    return Th_WrongNumArgs2(interp,
+                            argv[0], argl[0],
+                            "?BOOLEAN?");
   }
   if( argc==2 ){
     if( Th_ToInt(interp, argv[1], argl[1], &openRepository) ){
       return TH_ERROR;
     }
@@ -415,50 +560,1572 @@
   }
   Th_SetResult(interp, g.zRepositoryName, -1);
   return TH_OK;
 }
 
+
+#ifdef TH_ENABLE_ARGV
+/*
+** TH command:
+**
+** argv len
+**
+** Returns the number of command-line arguments.
+*/
+static int argvArgcCmd(
+  Th_Interp *interp,
+  void *p, 
+  int argc, 
+  const char **argv, 
+  int *argl
+){
+  Th_SetResultInt( interp, g.argc );
+  return TH_OK;
+}
+
+
+
+/*
+** TH command:
+**
+** argv at Index
+**
+** Returns the raw argument at the given index, throwing if
+** out of bounds.
+*/
+static int argvGetAtCmd(
+  Th_Interp *interp,
+  void *p, 
+  int argc, 
+  const char **argv, 
+  int *argl
+){
+  char const * zVal;
+  int pos = 0;
+  if( argc != 2 ){
+    return Th_WrongNumArgs2(interp,
+                            argv[0], argl[0],
+                            "Index");
+  }
+  if( TH_OK != Th_ToInt(interp, argv[1], argl[1], &pos) ){
+    return TH_ERROR;
+  }
+  if( pos < 0 || pos >= g.argc ){
+    Th_ErrorMessage(interp, "Argument out of range:", argv[1], argl[1]);
+    return TH_ERROR;
+  }
+  if( 0 == pos ){/*special case*/
+    zVal = fossil_nameofexe();
+  }else{
+    zVal = (pos>0 && pos<g.argc) ? g.argv[pos] : 0;
+  }
+  Th_SetResult( interp, zVal, zVal ? strlen(zVal) : 0 );
+  return TH_OK;  
+}
+
+
+/*
+** TH command:
+**
+** argv getstr longName ??shortName? ?defaultValue??
+**
+** Functions more or less like Fossil's find_option().
+** If the given argument is found then its value is returned,
+** else defaultValue is returned. If that is not set
+** and the option is not found, an error is thrown.
+** If defaultValue is provided, shortName must also be provided
+** but it may be empty. For example:
+**
+** set foo [argv getstr foo "" "hi, world"]
+**
+** ACHTUNG: find_option() removes any entries it finds from
+** g.argv, such that future calls to find_option() will not
+** find the same option.
+*/
+static int argvFindOptionStringCmd(
+  Th_Interp *interp,
+  void *p, 
+  int argc, 
+  const char **argv, 
+  int *argl
+){
+  enum { BufLen = 100 };
+  char zLong[BufLen] = {0};
+  char zShort[BufLen] = {0};
+  char aBuf[BufLen] = {0};
+  int hasArg;
+  char const * zVal = NULL;
+  char const * zDefault = NULL;
+  int check;
+  if( 1 < argc ){
+    assert( argl[1] < BufLen );
+    check = snprintf( zLong, BufLen, "%s", argv[1] );
+    assert( check <= BufLen );
+  }
+  if( (2 < argc) && (0 < argl[2]) ){
+    assert( argl[2] < BufLen );
+    check = snprintf( zShort, BufLen, "%s", argv[2] );
+    assert( check <= BufLen );
+  }
+  if( 3 < argc){
+    zDefault = argv[3];
+  }
+
+  if(0 == zLong[0]){
+    return Th_WrongNumArgs2(interp,
+                           argv[0], argl[0],
+                           "longName ?shortName? ?defaultVal?");
+  }
+  if(g.cgiOutput){
+      zVal = cgi_parameter( zLong, NULL );
+      if( !zVal && zShort[0] ){
+          zVal = cgi_parameter( zShort, NULL );
+      }
+  }else{
+      zVal = find_option( zLong, zShort[0] ? zShort : NULL, 1 );
+  }
+  if(!zVal){
+    zVal = zDefault;
+    if(!zVal){
+      Th_ErrorMessage(interp, "Option not found and no default provided:", zLong, -1);
+      return TH_ERROR;
+    }
+  }
+  Th_SetResult( interp, zVal, zVal ? strlen(zVal) : 0 );
+  return TH_OK;  
+}
+
+/*
+** TH command:
+**
+** argv getbool longName ??shortName? ?defaultValue??
+**
+** Works just like argv getstr but treats any empty value or one
+** starting with the digit '0' as a boolean false.
+**
+** Returns the result as an integer 0 (false) or 1 (true).
+*/
+static int argvFindOptionBoolCmd(
+  Th_Interp *interp,
+  void *p, 
+  int argc, 
+  const char **argv, 
+  int *argl
+){
+  /* FIXME: refactor to re-use the code from getstr */
+  enum { BufLen = 100 };
+  char zLong[BufLen] = {0};
+  char zShort[BufLen] = {0};
+  char aBuf[BufLen] = {0};
+  int hasArg;
+  char const * zVal = NULL;
+  char const * zDefault = NULL;
+  int val;
+  int rc;
+  int check;
+  if( 1 < argc ){
+    assert( argl[1] < BufLen );
+    check = snprintf( zLong, BufLen, "%s", argv[1] );
+    assert( check <= BufLen );
+  }
+  if( (2 < argc) && (0 < argl[2]) ){
+    assert( argl[2] < BufLen );
+    check = snprintf( zShort, BufLen, "%s", argv[2] );
+    assert( check <= BufLen );
+  }
+  if( 3 < argc){
+    zDefault = argv[3];
+  }
+
+  if(0 == zLong[0]){
+    return Th_WrongNumArgs2(interp,
+                            argv[0], argl[0],
+                           "longName ?shortName? ?defaultVal?");
+  }
+  if(g.cgiOutput){
+      zVal = cgi_parameter( zLong, NULL );
+      if( !zVal && zShort[0] ){
+          zVal = cgi_parameter( zShort, NULL );
+      }
+  }else{
+      zVal = find_option( zLong, zShort[0] ? zShort : NULL, 0 );
+  }
+  if(zVal && !*zVal){
+    zVal = "1";
+  }
+  if(!zVal){
+    zVal = zDefault;
+    if(!zVal){
+      Th_ErrorMessage(interp, "Option not found and no default provided:", zLong, -1);
+      return TH_ERROR;
+    }
+  }
+  if( !*zVal ){
+    zVal = "0";
+  }
+  zVal = (zVal && *zVal && (*zVal!='0')) ? zVal : 0;
+  Th_SetResultInt( interp, zVal ? 1 : 0 );
+  return TH_OK;
+}
+
+/*
+** TH command:
+**
+** argv getint longName ?shortName? ?defaultValue?
+**
+** Works like argv getstr but returns the value as an integer
+** (throwing an error if the argument cannot be converted).
+*/
+static int argvFindOptionIntCmd(
+  Th_Interp *interp,
+  void *p, 
+  int argc, 
+  const char **argv, 
+  int *argl
+){
+  /* FIXME: refactor to re-use the code from getstr */
+  enum { BufLen = 100 };
+  char zLong[BufLen] = {0};
+  char zShort[BufLen] = {0};
+  char aBuf[BufLen] = {0};
+  int hasArg;
+  char const * zVal = NULL;
+  char const * zDefault = NULL;
+  int val = 0;
+  int check;
+  if( 1 < argc ){
+    assert( argl[1] < BufLen );
+    check = snprintf( zLong, BufLen, "%s", argv[1] );
+    assert( check <= BufLen );
+  }
+  if( (2 < argc) && (0 < argl[2]) ){
+    assert( argl[2] < BufLen );
+    check = snprintf( zShort, BufLen, "%s", argv[2] );
+    assert( check <= BufLen );
+  }
+  if( 3 < argc){
+    zDefault = argv[3];
+  }
+
+  if(0 == zLong[0]){
+    return Th_WrongNumArgs2(interp,
+                            argv[0], argl[0],
+                           "longName ?shortName? ?defaultVal?");
+  }
+  if(g.cgiOutput){
+      zVal = cgi_parameter( zLong, NULL );
+      if( !zVal && zShort[0] ){
+          zVal = cgi_parameter( zShort, NULL );
+      }
+  }else{
+      zVal = find_option( zLong, zShort[0] ? zShort : NULL, 1 );
+  }
+  if(!zVal){
+    zVal = zDefault;
+    if(!zVal){
+      Th_ErrorMessage(interp, "Option not found and no default provided:", zLong, -1);
+      return TH_ERROR;
+    }
+  }
+  Th_ToInt(interp, zVal, strlen(zVal), &val);
+  Th_SetResultInt( interp, val );
+  return TH_OK;  
+}
+
+/*
+** TH command:
+**
+** argv subcommand
+**
+** This is the top-level dispatching function.
+*/
+static int argvTopLevelCmd(
+  Th_Interp *interp,
+  void *ctx, 
+  int argc, 
+  const char **argv, 
+  int *argl
+){
+  static Th_SubCommand aSub[] = {
+    {"len",      argvArgcCmd},
+    {"at",       argvGetAtCmd},
+    {"getstr",   argvFindOptionStringCmd},
+    {"string",   argvFindOptionStringCmd},
+    {"getbool",  argvFindOptionBoolCmd},
+    {"bool",     argvFindOptionBoolCmd},
+    {"getint",   argvFindOptionIntCmd},
+    {"int",      argvFindOptionIntCmd},
+    {0, 0}
+  };
+  Th_CallSubCommand2( interp, ctx, argc, argv, argl, aSub );
+}
+
+int th_register_argv(Th_Interp *interp){
+  static Th_Command_Reg aCommand[] = {
+    {"argv",            argvTopLevelCmd, 0 },
+    {0, 0, 0}
+  };
+  Th_RegisterCommands( interp, aCommand );
+}
+
+#endif
+/* end TH_ENABLE_ARGV */
+
+#ifdef TH_ENABLE_QUERY
+
+/*
+** Adds the given prepared statement to the interpreter. Returns the
+** statement's opaque identifier (a positive value). Ownerships of
+** pStmt is transfered to interp and it must be cleaned up by the
+** client by calling Th_query_FinalizeStmt(), passing it the value returned
+** by this function.
+**
+** If interp is destroyed before all statements are finalized,
+** it will finalize them but may emit a warning message.
+*/
+static int Th_query_AddStmt(Th_Interp *interp, sqlite3_stmt * pStmt);
+
+
+/*
+** Internal state for the "query" API.
+*/
+struct Th_Query {
+  sqlite3_stmt ** aStmt; /* Array of statement handles. */
+  int nStmt;             /* number of entries in aStmt. */
+  int colCmdIndex;       /* column index argument. Set by some top-level dispatchers
+                            for their subcommands.
+                         */
+};
+/*
+** Internal key for use with Th_Data_Add().
+*/
+#define Th_Query_KEY "Th_Query"
+typedef struct Th_Query Th_Query;
+
+/*
+** Returns the Th_Query object associated with the given interpreter,
+** or 0 if there is not one.
+*/
+static Th_Query * Th_query_manager( Th_Interp * interp ){
+  void * p = Th_GetData( interp, Th_Query_KEY );
+  return p ? (Th_Query*)p : NULL;
+}
+
+static int Th_query_AddStmt(Th_Interp *interp, sqlite3_stmt * pStmt){
+  Th_Query * sq = Th_query_manager(interp);
+  int i, x;
+  sqlite3_stmt * s;
+  sqlite3_stmt ** list = sq->aStmt;
+  for( i = 0; i < sq->nStmt; ++i ){
+    s = list[i];
+    if(NULL==s){
+      list[i] = pStmt;
+      return i+1;
+    }
+  }
+  x = (sq->nStmt + 1) * 2;
+  list = (sqlite3_stmt**)fossil_realloc( list, sizeof(sqlite3_stmt*)*x );
+  for( i = sq->nStmt; i < x; ++i ){
+    list[i] = NULL;
+  }
+  list[sq->nStmt] = pStmt;
+  x = sq->nStmt;
+  sq->nStmt = i;
+  sq->aStmt = list;
+  return x + 1;
+}
+
+
+/*
+** Expects stmtId to be a statement identifier returned by
+** Th_query_AddStmt(). On success, finalizes the statement and returns 0.
+** On error (statement not found) non-0 is returned. After this
+** call, some subsequent call to Th_query_AddStmt() may return the
+** same statement ID.
+*/
+static int Th_query_FinalizeStmt(Th_Interp *interp, int stmtId){
+  Th_Query * sq = Th_query_manager(interp);
+  sqlite3_stmt * st;
+  int rc = 0;
+  assert( stmtId>0 && stmtId<=sq->nStmt );
+  st = sq->aStmt[stmtId-1];
+  if(NULL != st){
+    sq->aStmt[stmtId-1] = NULL;
+    sqlite3_finalize(st);
+    return 0;
+  }else{
+    return 1;
+  }
+}
+
+/*
+** Works like Th_query_FinalizeStmt() but takes a statement pointer, which
+** must have been Th_query_AddStmt()'d to the given interpreter.
+*/
+static int Th_query_FinalizeStmt2(Th_Interp *interp, sqlite3_stmt * pSt){
+  Th_Query * sq = Th_query_manager(interp);
+  int i = 0;
+  sqlite3_stmt * st = NULL;
+  int rc = 0;
+  for( ; i < sq->nStmt; ++i ){
+    st = sq->aStmt[i];
+    if(st == pSt) break;
+  }
+  if( st == pSt ){
+    assert( i>=0 && i<sq->nStmt );
+    sq->aStmt[i] = NULL;
+    sqlite3_finalize(st);
+    return 0;
+  }else{
+    return 1;
+  }
+}
+
+
+/*
+** Fetches the statement with the given ID, as returned by
+** Th_query_AddStmt(). Returns NULL if stmtId does not refer (or no longer
+** refers) to a statement added via Th_query_AddStmt().
+*/
+static sqlite3_stmt * Th_query_GetStmt(Th_Interp *interp, int stmtId){
+  Th_Query * sq = Th_query_manager(interp);
+  return (!sq || (stmtId<1) || (stmtId > sq->nStmt))
+    ? NULL
+    : sq->aStmt[stmtId-1];
+}
+
+
+/*
+** Th_GCEntry finalizer which requires that p be a (Th_Query*).
+*/
+static void finalizerSqlite( Th_Interp * interp, void * p ){
+  Th_Query * sq = (Th_Query *)p;
+  int i;
+  sqlite3_stmt * st = NULL;
+  if(!sq) {
+    fossil_warning("Got a finalizer call for a NULL Th_Query.");
+    return;
+  }
+  for( i = 0; i < sq->nStmt; ++i ){
+    st = sq->aStmt[i];
+    if(NULL != st){
+      fossil_warning("Auto-finalizing unfinalized "
+                     "statement id #%d: %s",
+                     i+1, sqlite3_sql(st));
+      Th_query_FinalizeStmt( interp, i+1 );
+    }
+  }
+  Th_Free(interp, sq->aStmt);
+  Th_Free(interp, sq);
+}
+
+
+/*
+** TH command:
+**
+** query prepare SQL
+**
+** Returns an opaque statement identifier.
+*/
+static int queryPrepareCmd(
+  Th_Interp *interp,
+  void *p, 
+  int argc, 
+  const char **argv, 
+  int *argl
+){
+  char const * zSql;
+  sqlite3_stmt * pStmt = NULL;
+  int rc;
+  char const * errMsg = NULL;
+  if( argc!=2 ){
+    return Th_WrongNumArgs2(interp,
+                            argv[0], argl[0],
+                            "STRING");
+  }
+  zSql = argv[1];
+  rc = sqlite3_prepare( g.db, zSql, strlen(zSql), &pStmt, NULL );
+  if(SQLITE_OK==rc){
+    if(sqlite3_column_count( pStmt ) < 1){
+      errMsg = "Only SELECT-like queries are supported.";
+      rc = SQLITE_ERROR;
+      sqlite3_finalize( pStmt );
+      pStmt = NULL;
+    }
+  }else{
+    errMsg = sqlite3_errmsg( g.db );
+  }
+  if(SQLITE_OK!=rc){
+    assert(NULL != errMsg);
+    assert(NULL == pStmt);
+    Th_ErrorMessage(interp, "error preparing SQL:", errMsg, -1);
+    return TH_ERROR;
+  }
+  rc = Th_query_AddStmt( interp, pStmt );
+  assert( rc >= 0 && "AddStmt failed.");
+  Th_SetResultInt( interp, rc );
+  return TH_OK;
+}
+
+/*
+** Tries to convert arg, which must be argLen bytes long, to a
+** statement handle id and, in turn, to a sqlite3_stmt. On success
+** (the argument references a prepared statement) it returns the
+** handle and stmtId (if not NULL) is assigned to the integer value of
+** arg. On error NULL is returned and stmtId might be modified (if not
+** NULL). If stmtId is unmodified after an error then it is not a
+** number, else it is a number but does not reference an opened
+** statement.
+*/
+static sqlite3_stmt * queryStmtHandle(Th_Interp *interp, char const * arg, int argLen, int * stmtId ){
+  int rc = 0;
+  sqlite3_stmt * pStmt = NULL;
+  if( 0 == Th_ToInt( interp, arg, argLen, &rc ) ){
+    if(stmtId){
+      *stmtId = rc;
+    }
+    pStmt = Th_query_GetStmt( interp, rc );
+    if(NULL==pStmt){
+      Th_ErrorMessage(interp, "no such statement handle:", arg, -1);
+    }
+  }
+  return pStmt;
+
+}
+
+/*
+** TH command:
+**
+** query finalize stmtId
+** query stmtId finalize 
+**
+** sqlite3_finalize()s the given statement.
+*/
+static int queryFinalizeCmd(
+  Th_Interp *interp,
+  void *p, 
+  int argc, 
+  const char **argv, 
+  int *argl
+){
+  sqlite3_stmt * pStmt = (sqlite3_stmt*)p;
+  int requireArgc = pStmt ? 1 : 2;
+  char * zSql;
+  int stId = 0;
+  char const * arg;
+  int rc;
+  if( argc!=requireArgc ){
+    return Th_WrongNumArgs2(interp,
+                            argv[0], argl[0],
+                            "StmtHandle");
+  }
+  if(!pStmt){
+    arg = argv[1];
+    pStmt = queryStmtHandle(interp, arg, argl[1], &stId);
+    if(!pStmt){
+      Th_ErrorMessage(interp, "Not a valid statement handle argument.", NULL, 0);
+      return TH_ERROR;
+    }
+  }
+  assert( NULL != pStmt );
+  rc = Th_query_FinalizeStmt2( interp, pStmt );
+  Th_SetResultInt( interp, rc );
+  return TH_OK;
+}
+
+/*
+** Reports the current sqlite3_errmsg() via TH and returns TH_ERROR.
+*/
+static int queryReportDbErr( Th_Interp * interp ){
+  char const * msg = sqlite3_errmsg( g.db );
+  Th_ErrorMessage(interp, "db error:", msg, -1);
+  return TH_ERROR;
+}
+
+/*
+** Internal helper for fetching statement handle and index parameters.
+** The first 4 args should be the args passed to the TH1 callback.
+** pStmt must be a pointer to a NULL pointer. pIndex may be NULL or
+** a pointer to store the statement index argument in. If pIndex is
+** NULL then argc is asserted to be at least 2, else it must be at
+** least 3.
+**
+** On success it returns 0, sets *pStmt to the referenced statement
+** handle, and pIndex (if not NULL) to the integer value of argv[2]
+** argument. On error it reports the error via TH, returns non-0, and
+** modifies neither pStmt nor pIndex.
+*/
+static int queryStmtIndexArgs(
+  Th_Interp * interp,
+  int argc,
+  char const ** argv,
+  int *argl,
+  sqlite3_stmt ** pStmt,
+  int * pIndex ){
+  int index = 0;
+  sqlite3_stmt * stmt;
+  if( !pIndex ){
+    if(argc<2){
+      return Th_WrongNumArgs2(interp,
+                              argv[0], argl[0],
+                              "StmtHandle");
+    }
+  }else{
+    if( argc<3 ){
+      return Th_WrongNumArgs2(interp,
+                              argv[0], argl[0],
+                              "StmtHandle Index");
+    }
+    if( 0 != Th_ToInt( interp, argv[2], argl[2], &index ) ){
+      return TH_ERROR;
+    }
+  }
+  stmt = *pStmt ? *pStmt : queryStmtHandle(interp, argv[1], argl[1], NULL);
+  if( NULL == stmt ){
+    return TH_ERROR;
+  }else{
+    *pStmt = stmt;
+    if( pIndex ){
+      *pIndex = index;
+    }
+    return 0;
+  }
+}
+
+/*
+** TH command:
+**
+** query step stmtId
+** query stmtId step
+**
+** Steps the given statement handle. Returns 0 at the end of the set,
+** a positive value if it fetches a row, and throws on error.
+*/
+static int queryStepCmd(
+  Th_Interp *interp,
+  void *p, 
+  int argc, 
+  const char **argv, 
+  int *argl
+){
+  sqlite3_stmt * pStmt = (sqlite3_stmt*)p;
+  int requireArgc = pStmt ? 1 : 2;
+  int rc = 0;
+  if( argc!=requireArgc ){
+    return Th_WrongNumArgs2(interp,
+                            argv[0], argl[0],
+                            "StmtHandle");
+  }
+  if(!pStmt && 0 != queryStmtIndexArgs(interp, argc, argv, argl, &pStmt, NULL)){
+    return TH_ERROR;
+  }
+  assert(NULL != pStmt);
+  rc = sqlite3_step( pStmt );
+  switch(rc){
+    case SQLITE_ROW:
+      rc = 1;
+      break;
+    case SQLITE_DONE:
+      rc = 0;
+      break;
+    default:
+      return queryReportDbErr( interp );
+  }
+  Th_SetResultInt( interp, rc );
+  return TH_OK;
+}
+
+/*
+** TH command:
+**
+** query StmtId reset
+** query reset StmtId
+**
+** Equivalent to sqlite3_reset().
+*/
+static int queryResetCmd(
+  Th_Interp *interp,
+  void *p, 
+  int argc, 
+  const char **argv, 
+  int *argl
+){
+  sqlite3_stmt * pStmt = (sqlite3_stmt*)p;
+  int const rc = sqlite3_reset(pStmt);
+  if(rc){
+    Th_ErrorMessage(interp, "Reset of statement failed.", NULL, 0);
+    return TH_ERROR;
+  }else{
+    return TH_OK;
+  }
+}
+
+
+/*
+** TH command:
+**
+** query col string stmtId Index
+** query stmtId col string Index
+** query stmtId col Index string
+**
+** Returns the result column value at the given 0-based index.
+*/
+static int queryColStringCmd(
+  Th_Interp *interp,
+  void *p, 
+  int argc, 
+  const char **argv, 
+  int *argl
+){
+  Th_Query * sq = Th_query_manager(interp);
+  int index = sq->colCmdIndex;
+  sqlite3_stmt * pStmt = (sqlite3_stmt*)p;
+  int requireArgc = pStmt ? 2 : 3;
+  char const * val;
+  int valLen;
+  if( index >= 0 ) --requireArgc;
+  if( argc!=requireArgc ){
+    return Th_WrongNumArgs2(interp,
+                            argv[0], argl[0],
+                            "StmtHandle Index");
+  }
+  if(!pStmt){
+    queryStmtIndexArgs(interp, argc, argv, argl, &pStmt, &index);
+  }else if(index<0){
+    Th_ToInt(interp, argv[1], argl[1], &index);
+  }
+  if(index < 0){
+    return TH_ERROR;
+  }
+  val = sqlite3_column_text( pStmt, index );
+  valLen = val ? sqlite3_column_bytes( pStmt, index ) : 0;
+  Th_SetResult( interp, val, valLen );
+  return TH_OK;
+}
+
+/*
+** TH command:
+**
+** query col int stmtId Index
+** query stmtId col int Index
+** query stmtId col Index int
+**
+** Returns the result column value at the given 0-based index.
+*/
+static int queryColIntCmd(
+  Th_Interp *interp,
+  void *p, 
+  int argc, 
+  const char **argv, 
+  int *argl
+){
+  Th_Query * sq = Th_query_manager(interp);
+  int index = sq->colCmdIndex;
+  sqlite3_stmt * pStmt = (sqlite3_stmt*)p;
+  int requireArgc = pStmt ? 2 : 3;
+  int rc = 0;
+  if( index >= 0 ) --requireArgc;
+  if( argc!=requireArgc ){
+    return Th_WrongNumArgs2(interp,
+                            argv[0], argl[0],
+                            "StmtHandle Index");
+  }
+  if(!pStmt){
+    queryStmtIndexArgs(interp, argc, argv, argl, &pStmt, &index);
+  }else if(index<0){
+    Th_ToInt(interp, argv[1], argl[1], &index);
+  }
+  if(index < 0){
+    return TH_ERROR;
+  }
+  Th_SetResultInt( interp, sqlite3_column_int( pStmt, index ) );
+  return TH_OK;
+}
+
+/*
+** TH command:
+**
+** query col double stmtId Index
+** query stmtId col double Index
+** query stmtId col Index double
+**
+** Returns the result column value at the given 0-based index.
+*/
+static int queryColDoubleCmd(
+  Th_Interp *interp,
+  void *p, 
+  int argc, 
+  const char **argv, 
+  int *argl
+){
+  Th_Query * sq = Th_query_manager(interp);
+  int index = sq->colCmdIndex;
+  sqlite3_stmt * pStmt = (sqlite3_stmt*)p;
+  int requireArgc = pStmt ? 2 : 3;
+  double rc = 0;
+  if( index >= 0 ) --requireArgc;
+  if( argc!=requireArgc ){
+    return Th_WrongNumArgs2(interp,
+                            argv[0], argl[0],
+                            "StmtHandle Index");
+  }
+  if(!pStmt){
+    queryStmtIndexArgs(interp, argc, argv, argl, &pStmt, &index);
+  }else if(index<0){
+    Th_ToInt(interp, argv[1], argl[1], &index);
+  }
+  if(index < 0){
+    return TH_ERROR;
+  }
+  Th_SetResultDouble( interp, sqlite3_column_double( pStmt, index ) );
+  return TH_OK;
+}
+
+/*
+** TH command:
+**
+** query col isnull stmtId Index
+** query stmtId col isnull Index
+** query stmtId col Index isnull
+**
+** Returns non-0 if the given 0-based result column index contains
+** an SQL NULL value, else returns 0.
+*/
+static int queryColIsNullCmd(
+  Th_Interp *interp,
+  void *p, 
+  int argc, 
+  const char **argv, 
+  int *argl
+){
+  Th_Query * sq = Th_query_manager(interp);
+  int index = sq->colCmdIndex;
+  sqlite3_stmt * pStmt = (sqlite3_stmt*)p;
+  int requireArgc = pStmt ? 2 : 3;
+  if( index >= 0 ) --requireArgc;
+  double rc = 0;
+  if( argc!=requireArgc ){
+    return Th_WrongNumArgs2(interp,
+                            argv[0], argl[0],
+                            "StmtHandle Index");
+  }
+  if(!pStmt){
+    queryStmtIndexArgs(interp, argc, argv, argl, &pStmt, &index);
+  }else if(index<0){
+    Th_ToInt(interp, argv[1], argl[1], &index);
+  }
+  if(index < 0){
+    return TH_ERROR;
+  }
+  Th_SetResultInt( interp,
+                   SQLITE_NULL==sqlite3_column_type( pStmt, index )
+                   ? 1 : 0);
+  return TH_OK;
+}
+
+/*
+** TH command:
+**
+** query col type stmtId Index
+** query stmtId col type Index
+** query stmtId col Index type
+**
+** Returns the sqlite type identifier for the given 0-based result
+** column index. The values are available in TH as $SQLITE_NULL,
+** $SQLITE_INTEGER, etc.
+*/
+static int queryColTypeCmd(
+  Th_Interp *interp,
+  void *p, 
+  int argc, 
+  const char **argv, 
+  int *argl
+){
+  Th_Query * sq = Th_query_manager(interp);
+  int index = sq->colCmdIndex;
+  sqlite3_stmt * pStmt = (sqlite3_stmt*)p;
+  int requireArgc = pStmt ? 2 : 3;
+  if( index >= 0 ) --requireArgc;
+  double rc = 0;
+  if( argc!=requireArgc ){
+    return Th_WrongNumArgs2(interp,
+                            argv[0], argl[0],
+                            "StmtHandle Index");
+  }
+  if(!pStmt){
+    queryStmtIndexArgs(interp, argc, argv, argl, &pStmt, &index);
+  }else if(index<0){
+    Th_ToInt( interp, argv[1], argl[1], &index );
+  }
+  if(index < 0){
+    return TH_ERROR;
+  }
+  Th_SetResultInt( interp, sqlite3_column_type( pStmt, index ) );
+  return TH_OK;
+}
+
+/*
+** TH command:
+**
+** query col count stmtId
+** query stmtId col count
+**
+** Returns the number of result columns in the query.
+*/
+static int queryColCountCmd(
+  Th_Interp *interp,
+  void *p, 
+  int argc, 
+  const char **argv, 
+  int *argl
+){
+  int rc;
+  sqlite3_stmt * pStmt = (sqlite3_stmt*)p;
+  int requireArgc = pStmt ? 1 : 2;
+  if( argc!=requireArgc ){
+    return Th_WrongNumArgs2(interp,
+                           argv[0], argl[0],
+                           "StmtHandle");
+  }
+  if(!pStmt){
+    pStmt = queryStmtHandle(interp, argv[1], argl[1], NULL);
+    if( NULL == pStmt ){
+      return TH_ERROR;
+    }
+  }
+  rc = sqlite3_column_count( pStmt );
+  Th_SetResultInt( interp, rc );
+  return TH_OK;
+}
+
+/*
+** TH command:
+**
+** query col name stmtId Index
+** query stmtId col name Index
+** query stmtId col Index name 
+**
+** Returns the result column name at the given 0-based index.
+*/
+static int queryColNameCmd(
+  Th_Interp *interp,
+  void *p, 
+  int argc, 
+  const char **argv, 
+  int *argl
+){
+  Th_Query * sq = Th_query_manager(interp);
+  int index = sq->colCmdIndex;
+  sqlite3_stmt * pStmt = (sqlite3_stmt*)p;
+  int requireArgc = pStmt ? 2 : 3;
+  char const * val;
+  int rc = 0;
+  if( index >= 0 ) --requireArgc;
+  if( argc!=requireArgc ){
+    return Th_WrongNumArgs2(interp,
+                            argv[0], argl[0],
+                            "StmtHandle Index");
+  }
+  if(!pStmt){
+    queryStmtIndexArgs(interp, argc, argv, argl, &pStmt, &index);
+  }else if(index<0){
+    Th_ToInt( interp, argv[1], argl[1], &index );
+  }
+  if(index < 0){
+    return TH_ERROR;
+  }
+  assert(NULL!=pStmt);
+  val = sqlite3_column_name( pStmt, index );
+  if(NULL==val){
+    Th_ErrorMessage(interp, "Column index out of bounds(?):", argv[2], -1);
+    return TH_ERROR;
+  }else{
+    Th_SetResult( interp, val, strlen( val ) );
+    return TH_OK;
+  }
+}
+
+/*
+** TH command:
+**
+** query col time stmtId Index format
+** query stmtId col name Index format
+** query stmtId col Index name format
+**
+** Returns the result column name at the given 0-based index.
+*/
+static int queryColTimeCmd(
+  Th_Interp *interp,
+  void *ctx, 
+  int argc, 
+  const char **argv, 
+  int *argl
+){
+  Th_Query * sq = Th_query_manager(interp);
+  int index = sq->colCmdIndex;
+  sqlite3_stmt * pStmt = (sqlite3_stmt*)ctx;
+  int minArgs = pStmt ? 3 : 4;
+  int argPos;
+  char const * val;
+  char * fval;
+  int i, rc = 0;
+  char const * fmt;
+  Blob sql = empty_blob;
+  if( index >= 0 ) --minArgs;
+  if( argc<minArgs ){
+    return Th_WrongNumArgs2(interp,
+                            argv[0], argl[0],
+                            "StmtHandle Index Format");
+  }
+  if(!pStmt){
+    queryStmtIndexArgs(interp, argc, argv, argl, &pStmt, &index);
+    argPos = 3;
+  }else if(index<0){
+    Th_ToInt( interp, argv[1], argl[1], &index );
+    argPos = 2;
+  }else{
+    argPos = 1;
+  }
+  if(index < 0){
+    return TH_ERROR;
+  }
+  val = sqlite3_column_text( pStmt, index );
+  fmt = argv[argPos++];
+  assert(NULL!=pStmt);
+  blob_appendf(&sql,"SELECT strftime(%Q,%Q",
+               fmt, val);
+  if(argc>argPos){
+    for(i = argPos; i < argc; ++i ){
+      blob_appendf(&sql, ",%Q", argv[i]);
+    }
+  }
+  blob_append(&sql, ")", 1);
+  fval = db_text(NULL,"%s", sql.aData);
+  
+  blob_reset(&sql);
+  Th_SetResult( interp, fval, fval ? strlen(fval) : 0 );
+  fossil_free(fval);
+  return 0;
+}
+
+/*
+** TH command:
+**
+**  query strftime TimeVal ?Modifiers...?
+**
+** Acts as a proxy to sqlite3's strftime() SQL function.
+*/
+static int queryStrftimeCmd(
+  Th_Interp *interp,
+  void *ctx, 
+  int argc, 
+  const char **argv, 
+  int *argl
+){
+  char const * val;
+  char * fval;
+  int i, rc = 0;
+  int index = -1;
+  char const * fmt;
+  Blob sql = empty_blob;
+  if( argc<3 ){
+    return Th_WrongNumArgs2(interp,
+                            argv[0], argl[0],
+                            "Format Value ?Modifiers...?");
+  }
+  fmt = argv[1];
+  val = argv[2];
+  blob_appendf(&sql,"SELECT strftime(%Q,%Q",
+               fmt, val);
+  if(argc>3){
+    for(i = 3; i < argc; ++i ){
+      blob_appendf(&sql, ",%Q", argv[i]);
+    }
+  }
+  blob_append(&sql, ")", 1);
+  fval = db_text(NULL,"%s", sql.aData);
+  blob_reset(&sql);
+  Th_SetResult( interp, fval, fval ? strlen(fval) : 0 );
+  fossil_free(fval);
+  return 0;
+}
+
+
+/*
+** TH command:
+**
+** query bind null stmtId Index
+** query stmtId bind null Index
+**
+** Binds a value to the given 1-based parameter index.
+*/
+static int queryBindNullCmd(
+  Th_Interp *interp,
+  void *p, 
+  int argc, 
+  const char **argv, 
+  int *argl
+){
+  Th_Query * sq = Th_query_manager(interp);
+  int index = sq->colCmdIndex;
+  sqlite3_stmt * pStmt = (sqlite3_stmt*)p;
+  int requireArgc = pStmt ? 2 : 3;
+  if( index > 0 ) --requireArgc;
+  int rc;
+  if( argc!=requireArgc ){
+    return Th_WrongNumArgs2(interp,
+                            argv[0], argl[0],
+                            "StmtHandle Index");
+  }
+  if(!pStmt){
+    queryStmtIndexArgs(interp, argc, argv, argl, &pStmt, &index);
+  }else if(index<1){
+    Th_ToInt( interp, argv[1], argl[1], &index );
+  }
+  if(index < 1){
+    return TH_ERROR;
+  }
+  rc = sqlite3_bind_null( pStmt, index );
+  if(rc){
+    return queryReportDbErr( interp );
+  }
+  Th_SetResultInt( interp, 0 );
+  return TH_OK;
+}
+
+
+/*
+** TH command:
+**
+** query bind string stmtId Index Value
+** query stmtId bind string Index Value
+**
+** Binds a value to the given 1-based parameter index.
+*/
+static int queryBindStringCmd(
+  Th_Interp *interp,
+  void *p, 
+  int argc, 
+  const char **argv, 
+  int *argl
+){
+  Th_Query * sq = Th_query_manager(interp);
+  int index = sq->colCmdIndex;
+  sqlite3_stmt * pStmt = (sqlite3_stmt*)p;
+  int requireArgc = pStmt ? 3 : 4;
+  int rc;
+  int argPos;
+  if( index > 0 ) --requireArgc;
+  if( argc!=requireArgc ){
+    return Th_WrongNumArgs2(interp,
+                            argv[0], argl[0],
+                            "StmtHandle Index Value");
+  }
+  if(!pStmt){
+    queryStmtIndexArgs(interp, argc, argv, argl, &pStmt, &index);
+    argPos = 3;
+  }else if(index<1){
+    Th_ToInt( interp, argv[1], argl[1], &index );
+    argPos = 2;
+  }else{
+    argPos = 1;
+  }
+  if(index < 1){
+    return TH_ERROR;
+  }
+  rc = sqlite3_bind_text( pStmt, index, argv[argPos], argl[argPos], SQLITE_TRANSIENT );
+  if(rc){
+    return queryReportDbErr( interp );
+  }
+  Th_SetResultInt( interp, 0 );
+  return TH_OK;
+}
+
+/*
+** TH command:
+**
+** query bind int stmtId Index Value
+** query stmtId bind int Index Value
+**
+** Binds a value to the given 1-based parameter index.
+*/
+static int queryBindIntCmd(
+  Th_Interp *interp,
+  void *p, 
+  int argc, 
+  const char **argv, 
+  int *argl
+){
+  Th_Query * sq = Th_query_manager(interp);
+  int index = sq->colCmdIndex;
+  sqlite3_stmt * pStmt = (sqlite3_stmt*)p;
+  int requireArgc = pStmt ? 3 : 4;
+  int rc;
+  int argPos;
+  int val;
+  if( index > 0 ) --requireArgc;
+  if( argc!=requireArgc ){
+    return Th_WrongNumArgs2(interp,
+                            argv[0], argl[0],
+                            "StmtHandle Index Value");
+  }
+  if(!pStmt){
+    queryStmtIndexArgs(interp, argc, argv, argl, &pStmt, &index);
+    argPos = 3;
+  }else if(index<1){
+    Th_ToInt( interp, argv[1], argl[1], &index );
+    argPos = 2;
+  }else{
+    argPos = 1;
+  }
+  if(index < 1){
+    return TH_ERROR;
+  }
+  if( 0 != Th_ToInt( interp, argv[argPos], argl[argPos], &val ) ){
+    return TH_ERROR;
+  }
+
+  rc = sqlite3_bind_int( pStmt, index, val );
+  if(rc){
+    return queryReportDbErr( interp );
+  }
+  Th_SetResultInt( interp, 0 );
+  return TH_OK;
+}
+
+/*
+** TH command:
+**
+** query bind double stmtId Index Value
+** query stmtId bind double Index Value
+**
+** Binds a value to the given 1-based parameter index.
+*/
+static int queryBindDoubleCmd(
+  Th_Interp *interp,
+  void *p, 
+  int argc, 
+  const char **argv, 
+  int *argl
+){
+  Th_Query * sq = Th_query_manager(interp);
+  int index = sq->colCmdIndex;
+  sqlite3_stmt * pStmt = (sqlite3_stmt*)p;
+  int requireArgc = pStmt ? 3 : 4;
+  int rc;
+  int argPos;
+  double val;
+  if( index > 0 ) --requireArgc;
+  if( argc!=requireArgc ){
+    return Th_WrongNumArgs2(interp,
+                            argv[0], argl[0],
+                            "StmtHandle Index Value");
+  }
+  if(!pStmt){
+    queryStmtIndexArgs(interp, argc, argv, argl, &pStmt, &index);
+    argPos = 3;
+  }else if(index<1){
+    Th_ToInt( interp, argv[1], argl[1], &index );
+    argPos = 2;
+  }else{
+    argPos = 1;
+  }
+  if(index < 1){
+    return TH_ERROR;
+  }
+  if( 0 != Th_ToDouble( interp, argv[argPos], argl[argPos], &val ) ){
+    return TH_ERROR;
+  }
+
+  rc = sqlite3_bind_double( pStmt, index, val );
+  if(rc){
+    return queryReportDbErr( interp );
+  }
+  Th_SetResultInt( interp, 0 );
+  return TH_OK;
+}
+
+/*
+** TH command:
+**
+** bind subcommand StmtId...
+** bind StmtId subcommand...
+**
+** This is the top-level dispatcher for the "bind" family of commands.
+*/
+static int queryBindTopLevelCmd(
+  Th_Interp *interp,
+  void *ctx, 
+  int argc, 
+  const char **argv, 
+  int *argl
+){
+  int colIndex = -1;
+  static Th_SubCommand aSub[] = {
+    {"int",    queryBindIntCmd},
+    {"double", queryBindDoubleCmd},
+    {"null",   queryBindNullCmd},
+    {"string", queryBindStringCmd},
+    {0, 0}
+  };
+  Th_Query * sq = Th_query_manager(interp);
+  assert(NULL != sq);
+  if( 1 == argc ){
+      Th_WrongNumArgs2( interp, argv[0], argl[0],
+                        "subcommand: int|double|null|string");
+      return TH_ERROR;
+  }else if( 0 == Th_TryInt(interp,argv[1], argl[1], &colIndex) ){
+    if(colIndex <0){
+      Th_ErrorMessage( interp, "Invalid column index.", NULL, 0);
+      return TH_ERROR;
+    }
+    ++argv;
+    ++argl;
+    --argc;
+  }
+  sq->colCmdIndex = colIndex;
+  Th_CallSubCommand2( interp, ctx, argc, argv, argl, aSub );
+
+}
+
+/*
+** TH command:
+**
+** query col subcommand ...
+** query StmtId col subcommand ...
+**
+** This is the top-level dispatcher for the col subcommands.
+*/
+static int queryColTopLevelCmd(
+  Th_Interp *interp,
+  void *ctx, 
+  int argc, 
+  const char **argv, 
+  int *argl
+){
+  int colIndex = -1;
+  static Th_SubCommand aSub[] = {
+    {"count",   queryColCountCmd},
+    {"is_null", queryColIsNullCmd},
+    {"isnull",  queryColIsNullCmd},
+    {"name",    queryColNameCmd},
+    {"double",  queryColDoubleCmd},
+    {"int",     queryColIntCmd},
+    {"string",  queryColStringCmd},
+    {"time",    queryColTimeCmd},
+    {"type",    queryColTypeCmd},
+    {0, 0}
+  };
+  static Th_SubCommand aSubWithIndex[] = {
+    /*
+      This subset is coded to accept the column index
+      either before the subcommand name or after it.
+      If called like (bind StmtId subcommand) then
+      only these commands will be checked.
+    */
+    {"is_null", queryColIsNullCmd},
+    {"isnull",  queryColIsNullCmd},
+    {"name",    queryColNameCmd},
+    {"double",  queryColDoubleCmd},
+    {"int",     queryColIntCmd},
+    {"string",  queryColStringCmd},
+    {"time",    queryColTimeCmd},
+    {"type",    queryColTypeCmd},
+    {0, 0}
+  };
+  Th_Query * sq = Th_query_manager(interp);
+  assert(NULL != sq);
+  if( 1 == argc ){
+      Th_WrongNumArgs2( interp, argv[0], argl[0],
+                        "subcommand: "
+                        "count|is_null|isnull|name|"
+                        "double|int|string|time|type");
+      return TH_ERROR;
+  }else if( 0 == Th_TryInt(interp,argv[1], argl[1], &colIndex) ){
+    if(colIndex <0){
+      Th_ErrorMessage( interp, "Invalid column index.", NULL, 0);
+      return TH_ERROR;
+    }
+    ++argv;
+    ++argl;
+    --argc;
+  }
+  sq->colCmdIndex = colIndex;
+  Th_CallSubCommand2( interp, ctx, argc, argv, argl,
+                      (colIndex<0) ? aSub : aSubWithIndex );
+}
+
+
+/*
+** TH command:
+**
+** query subcommand ...
+** query StmtId subcommand ...
+**
+** This is the top-level dispatcher for the query subcommand.
+*/
+static int queryTopLevelCmd(
+  Th_Interp *interp,
+  void *ctx, 
+  int argc, 
+  const char **argv, 
+  int *argl
+){
+  int stmtId = 0;
+  sqlite3_stmt * pStmt = NULL;
+  static Th_SubCommand aSubAll[] = {
+    {"bind",        queryBindTopLevelCmd},
+    {"col",         queryColTopLevelCmd},
+    {"finalize",    queryFinalizeCmd},
+    {"prepare",     queryPrepareCmd},
+    {"reset",       queryResetCmd},
+    {"step",        queryStepCmd},
+    {"strftime",    queryStrftimeCmd},
+    {0, 0}
+  };
+  static Th_SubCommand aSubWithStmt[] = {
+    /* This subset is coded to deal with being supplied a statement
+       via pStmt or via one of their args. When called like (query
+       StmtId ...) only these subcommands will be checked.*/
+    {"bind",        queryBindTopLevelCmd},
+    {"col",         queryColTopLevelCmd},
+    {"step",        queryStepCmd},
+    {"finalize",    queryFinalizeCmd},
+    {"reset",       queryResetCmd},
+    {0, 0}
+  };
+
+
+  assert( NULL != Th_query_manager(interp) );
+  if( 1 == argc ){
+      Th_WrongNumArgs2( interp, argv[0], argl[0],
+                        "subcommand: bind|col|finalize|prepare|reset|step|strftime");
+      return TH_ERROR;
+  }else if( 0 == Th_TryInt(interp,argv[1], argl[1], &stmtId) ){
+    ++argv;
+    ++argl;
+    --argc;
+    pStmt = Th_query_GetStmt( interp, stmtId );
+  }
+
+  Th_CallSubCommand2( interp, pStmt, argc, argv, argl,
+                      pStmt ? aSubWithStmt : aSubAll );
+}
+
+/*
+** Registers the "query" API with the given interpreter. Returns TH_OK
+** on success, TH_ERROR on error.
+*/
+int th_register_query(Th_Interp *interp){
+  enum { BufLen = 100 };
+  char buf[BufLen];
+  int i, l;
+#define SET(K) l = snprintf(buf, BufLen, "%d", K);      \
+  Th_SetVar( interp, #K, strlen(#K), buf, l );
+  SET(SQLITE_BLOB);
+  SET(SQLITE_FLOAT);
+  SET(SQLITE_INTEGER);
+  SET(SQLITE_NULL);
+  SET(SQLITE_TEXT);
+#if 0
+  /* so far we don't need these in script code */
+  SET(SQLITE_ERROR);
+  SET(SQLITE_DONE);
+  SET(SQLITE_OK);
+  SET(SQLITE_ROW);
+#endif
+#undef SET
+  int rc = TH_OK;
+  static Th_Command_Reg aCommand[] = {
+    {"query",             queryTopLevelCmd,  0},
+    {0, 0, 0}
+  };
+  rc = Th_RegisterCommands( interp, aCommand );
+  if(TH_OK==rc){
+    Th_Query * sq = Th_Malloc(interp, sizeof(Th_Query));
+    if(!sq){
+      rc = TH_ERROR;
+    }else{
+      assert( NULL == sq->aStmt );
+      assert( 0 == sq->nStmt );
+      Th_SetData( interp, Th_Query_KEY, sq, finalizerSqlite );
+      assert( sq == Th_query_manager(interp) );
+    }
+  }
+  return rc;
+}
+
+#endif
+/* end TH_ENABLE_QUERY */
+
 /*
 ** Make sure the interpreter has been initialized.  Initialize it if
 ** it has not been already.
 **
 ** The interpreter is stored in the g.interp global variable.
 */
 void Th_FossilInit(void){
-  static struct _Command {
-    const char *zName;
-    Th_CommandProc xProc;
-    void *pContext;
-  } aCommand[] = {
+  /* The fossil-internal Th_Vtab instance. */
+  static Th_Vtab vtab = { xRealloc, {/*out*/
+    NULL /*write()*/,
+    NULL/*dispose()*/,
+    NULL/*pState*/,
+    1/*enabled*/
+    }
+  };
+
+  static PutsCmdData puts_Html = {0, 0, 0};
+  static PutsCmdData puts_Normal = {1, 0, 0};
+  static Th_Command_Reg aCommand[] = {
     {"anycap",        anycapCmd,            0},
     {"combobox",      comboboxCmd,          0},
+    {"date",          dateCmd,              0},
     {"enable_output", enableOutputCmd,      0},
-    {"linecount",     linecntCmd,           0},
     {"hascap",        hascapCmd,            0},
     {"hasfeature",    hasfeatureCmd,        0},
+    {"html",          putsCmd,     &puts_Html},
     {"htmlize",       htmlizeCmd,           0},
-    {"date",          dateCmd,              0},
-    {"html",          putsCmd,              0},
-    {"puts",          putsCmd,       (void*)1},
-    {"wiki",          wikiCmd,              0},
+    {"linecount",     linecntCmd,           0},
+    {"puts",          putsCmd,   &puts_Normal},
+#if 0
+    {"render",        renderCmd,            0},
+#endif
     {"repository",    repositoryCmd,        0},
+    {"wiki",          wikiCmd,              0},
+
     {0, 0, 0}
   };
   if( g.interp==0 ){
     int i;
+    if(g.cgiOutput){
+      vtab.out.xWrite = Th_Output_f_cgi_content;
+    }else{
+      vtab.out = Th_Vtab_OutputMethods_FILE;
+      vtab.out.pState = stdout;
+    }
+    vtab.out.enabled = enableOutput;
     g.interp = Th_CreateInterp(&vtab);
     th_register_language(g.interp);       /* Basic scripting commands. */
 #ifdef FOSSIL_ENABLE_TCL
     if( getenv("TH1_ENABLE_TCL")!=0 || db_get_boolean("tcl", 0) ){
       th_register_tcl(g.interp, &g.tcl);  /* Tcl integration commands. */
     }
 #endif
-    for(i=0; i<sizeof(aCommand)/sizeof(aCommand[0]); i++){
-      if ( !aCommand[i].zName || !aCommand[i].xProc ) continue;
-      Th_CreateCommand(g.interp, aCommand[i].zName, aCommand[i].xProc,
-                       aCommand[i].pContext, 0);
-    }
+#ifdef TH_ENABLE_OB
+    th_register_ob(g.interp);
+#endif
+#ifdef TH_ENABLE_QUERY
+    th_register_query(g.interp);
+#endif
+#ifdef TH_ENABLE_ARGV
+    th_register_argv(g.interp);
+#endif
+    Th_RegisterCommands( g.interp, aCommand );
+    Th_Eval( g.interp, 0, "proc incr {name {step 1}} {\n"
+             "upvar $name x\n"
+             "set x [expr $x+$step]\n"
+             "}", -1 );
   }
 }
 
 /*
 ** Store a string value in a variable in the interpreter.
@@ -553,30 +2220,33 @@
   return i;
 }
 
 /*
 ** The z[] input contains text mixed with TH1 scripts.
-** The TH1 scripts are contained within <th1>...</th1>. 
-** TH1 variables are $aaa or $<aaa>.  The first form of
-** variable is literal.  The second is run through htmlize
-** before being inserted.
+** The TH1 scripts are contained within <th1>...</th1>.
+**
+** If flags does NOT contain the Th_Render_Flags_NO_DOLLAR_DEREF bit
+** then TH1 variables are $aaa or $<aaa>.  The first form of variable
+** is literal.  The second is run through htmlize before being
+** inserted.
 **
 ** This routine processes the template and writes the results
-** on either stdout or into CGI.
+** via Th_Output().
 */
-int Th_Render(const char *z){
+int Th_Render(const char *z, int flags){
   int i = 0;
   int n;
   int rc = TH_OK;
-  char *zResult;
+  char const *zResult;
+  char doDollar = !(flags & Th_Render_Flags_NO_DOLLAR_DEREF);
   Th_FossilInit();
   while( z[i] ){
-    if( z[i]=='$' && (n = validVarName(&z[i+1]))>0 ){
+    if( doDollar && z[i]=='$' && (n = validVarName(&z[i+1]))>0 ){
       const char *zVar;
       int nVar;
       int encode = 1;
-      sendText(z, i, 0);
+      sendText(g.interp, z, i, 0);
       if( z[i+1]=='<' ){
         /* Variables of the form $<aaa> are html escaped */
         zVar = &z[i+2];
         nVar = n-2;
       }else{
@@ -583,17 +2253,17 @@
         /* Variables of the form $aaa are output raw */
         zVar = &z[i+1];
         nVar = n;
         encode = 0;
       }
-      rc = Th_GetVar(g.interp, (char*)zVar, nVar);
+      rc = Th_GetVar(g.interp, zVar, nVar);
       z += i+1+n;
       i = 0;
-      zResult = (char*)Th_GetResult(g.interp, &n);
-      sendText((char*)zResult, n, encode);
+      zResult = Th_GetResult(g.interp, &n);
+      sendText(g.interp, zResult, n, encode);
     }else if( z[i]=='<' && isBeginScriptTag(&z[i]) ){
-      sendText(z, i, 0);
+      sendText(g.interp, z, i, 0);
       z += i+5;
       for(i=0; z[i] && (z[i]!='<' || !isEndScriptTag(&z[i])); i++){}
       rc = Th_Eval(g.interp, 0, (const char*)z, i);
       if( rc!=TH_OK ) break;
       z += i;
@@ -602,28 +2272,45 @@
     }else{
       i++;
     }
   }
   if( rc==TH_ERROR ){
-    sendText("<hr><p class=\"thmainError\">ERROR: ", -1, 0);
-    zResult = (char*)Th_GetResult(g.interp, &n);
-    sendText((char*)zResult, n, 1);
-    sendText("</p>", -1, 0);
+    sendText(g.interp, "<hr><p class=\"thmainError\">ERROR: ", -1, 0);
+    zResult = Th_GetResult(g.interp, &n);
+    sendText(g.interp, zResult, n, 1);
+    sendText(g.interp, "</p>", -1, 0);
   }else{
-    sendText(z, i, 0);
+    sendText(g.interp, z, i, 0);
   }
   return rc;
 }
 
 /*
 ** COMMAND: test-th-render
+** COMMAND: th1
+**
+** Processes a file provided on the command line as a TH1-capable
+** script/page. Output is sent to stdout or the CGI output buffer, as
+** appropriate. The input file is assumed to be text/wiki/HTML content
+** which may contain TH1 tag blocks and variables in the form $var or
+** $<var>. Each block is executed in the same TH1 interpreter
+** instance.
+**
+** ACHTUNG: not all of the $variables which are set in CGI mode
+** are available via this (CLI) command.
+**
 */
 void test_th_render(void){
   Blob in;
   if( g.argc<3 ){
     usage("FILE");
+    assert(0 && "usage() does not return");
   }
-  db_open_config(0); /* Needed for global "tcl" setting. */
   blob_zero(&in);
+  db_open_config(0); /* Needed for global "tcl" setting. */
+#ifdef TH_ENABLE_QUERY
+  db_find_and_open_repository(OPEN_ANY_SCHEMA,0)
+    /* required for th1 query API. */;
+#endif
   blob_read_from_file(&in, g.argv[2]);
-  Th_Render(blob_str(&in));
+  Th_Render(blob_str(&in), Th_Render_Flags_DEFAULT);
 }

Index: src/tkt.c
==================================================================
--- src/tkt.c
+++ src/tkt.c
@@ -328,11 +328,11 @@
   if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW<br />\n", -1);
   ticket_init();
   initializeVariablesFromDb();
   zScript = ticket_viewpage_code();
   if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW_SCRIPT<br />\n", -1);
-  Th_Render(zScript);
+  Th_Render(zScript, Th_Render_Flags_DEFAULT );
   if( g.thTrace ) Th_Trace("END_TKTVIEW<br />\n", -1);
 
   zFullName = db_text(0, 
        "SELECT tkt_uuid FROM ticket"
        " WHERE tkt_uuid GLOB '%q*'", zUuid);
@@ -540,11 +540,11 @@
   Th_Store("login", g.zLogin);
   Th_Store("date", db_text(0, "SELECT datetime('now')"));
   Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd,
                    (void*)&zNewUuid, 0);
   if( g.thTrace ) Th_Trace("BEGIN_TKTNEW_SCRIPT<br />\n", -1);
-  if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zNewUuid ){
+  if( Th_Render(zScript, Th_Render_Flags_DEFAULT)==TH_RETURN && !g.thTrace && zNewUuid ){
     cgi_redirect(mprintf("%s/tktview/%s", g.zTop, zNewUuid));
     return;
   }
   @ </form>
   if( g.thTrace ) Th_Trace("END_TKTVIEW<br />\n", -1);
@@ -607,11 +607,11 @@
   Th_Store("login", g.zLogin);
   Th_Store("date", db_text(0, "SELECT datetime('now')"));
   Th_CreateCommand(g.interp, "append_field", appendRemarkCmd, 0, 0);
   Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, (void*)&zName,0);
   if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT_SCRIPT<br />\n", -1);
-  if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zName ){
+  if( Th_Render(zScript, Th_Render_Flags_DEFAULT)==TH_RETURN && !g.thTrace && zName ){
     cgi_redirect(mprintf("%s/tktview/%s", g.zTop, zName));
     return;
   }
   @ </form>
   if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT<br />\n", -1);

ADDED   test/th1-ob-1.th1
Index: test/th1-ob-1.th1
==================================================================
--- test/th1-ob-1.th1
+++ test/th1-ob-1.th1
@@ -0,0 +1,17 @@
+<th1>
+set i 0
+set max 6
+for {} {$i < $max} {incr i} {
+    ob start
+    puts "this is level " [ob level]
+    set buf($i) [ob get]
+}
+
+for {set i [expr $max-1]} {$i >= 0} {incr i -1} {
+    ob pop
+}
+for {set i [expr $max-1]} {$i >= 0} {incr i -1} {
+    puts buf($i) = $buf($i) "\n"
+}
+puts "buffering level = " [ob level] \n
+</th1>

ADDED   test/th1-query-api-1.th1
Index: test/th1-query-api-1.th1
==================================================================
--- test/th1-query-api-1.th1
+++ test/th1-query-api-1.th1
@@ -0,0 +1,157 @@
+This is not a formal test suite, but a tinkering ground.
+Run it through "fossil test-th-render THIS_FILE".
+<th1>
+set stmt [query prepare {SELECT login, cap FROM user}]
+set colCount [query $stmt col count]
+puts "query column count: ${colCount}\n"
+puts "stmt id=${stmt}\n"
+
+proc noop {} {}
+proc incr {name {step 1}} {
+    upvar $name x
+    set x [expr $x+$step]
+}
+
+
+set sep "    "
+set i 0
+set colNames(0) 0
+for {set i 0} {$i < $colCount} {incr i} {
+    set colNames($i) [query $stmt col name $i]
+    puts "colNames($i)=" $colNames($i) "\n"
+}
+
+for {set row 0} {[query $stmt step]} {incr row} {
+    for {set i 0} {$i < $colCount} {incr i} {
+        if {$i > 0} {
+            puts $sep
+        } else {
+            puts "#$row: $sep"
+        }
+        puts $colNames($i) = [query $stmt col string $i]
+    }
+    puts "\n"
+}
+unset row
+
+query $stmt finalize
+#query finalize $stmt 
+
+
+proc query_step_each {{stmt} {callback}} {
+    set colNames(0) 0
+    set colCount [query $stmt col count]
+    for {set i 0} {$i < $colCount} {incr i} {
+        set colNames($i) [query $stmt col name $i]
+    }
+    upvar cb $callback
+    for {set row 0} {[query $stmt step]} {incr row} {
+        #puts "Calling callback: $stmt $colCount colNames\n"
+        $callback $stmt $colCount
+    }
+}
+
+set sql {SELECT uid, login FROM user WHERE uid!=?}
+#set sql {SELECT uid, login FROM user WHERE login=?}
+#set sql {SELECT tagid, value, null FROM tagxref WHERE value IS ? LIMIT 3}
+set stmt [query prepare $sql]
+puts "stmt ID=" $stmt "\n"
+#query bind int $stmt 1 3
+#query $stmt bind 1 int 3
+query $stmt bind 1 string 3
+#query $stmt bind string 1 3
+#set stmt [query prepare $sql]
+#query $stmt bind 1 string 1
+#set stmt [query prepare $sql]
+#query $stmt bind null 1
+puts "USER LIST:\n"
+catch {
+    proc my_each {stmt colCount} {
+        upvar 2 sep sep
+        puts [query $stmt col int 0] " (type=" [query $stmt col type 0] ")" $sep
+        puts [query $stmt col double 0] $sep
+        puts [query $stmt col string 1]  " (type=" [query $stmt col type 1] ")" $sep
+        puts "isnull 0 ?= " [query $stmt col is_null 0] $sep
+        puts "isnull 2 ?= " [query col is_null $stmt 2]
+#        for {set i 0} {$i < $colCount} {incr i} {
+#            if {$i > 0} { puts $sep }
+#        }
+        puts "\n"
+#        error "hi!"
+    }
+    query_step_each $stmt my_each
+#    query reset $stmt
+#    query $stmt reset 
+#    query_step_each $stmt {
+#        proc each {stmt cc} { puts hi "\n" }
+#    }
+    return 0
+} rc
+query finalize $stmt
+if { 0 != $rc } {
+        puts "ERROR: $rc\n"
+}
+
+set consts [list SQLITE_BLOB SQLITE_FLOAT SQLITE_INTEGER SQLITE_NULL SQLITE_TEXT]
+#set consts $SQLITE_CONSTANTS
+puts consts = $consts "\n"
+for {set i 0} {$i < [llength $consts]} {incr i} {
+    set x [lindex $consts $i]
+    puts \$$x = [expr \$$x] "\n"
+}
+
+set ARGC [argv len]
+puts ARGC = $ARGC "\n"
+for {set i 0} {$i < $ARGC} {incr i} {
+    puts "argv at $i = " [argv at $i] \n
+}
+
+set magicDefault hi
+set optA [argv string AA a $magicDefault]
+puts "argv string AA = " $optA \n
+
+set optA [argv bool BB b 0]
+puts "argv bool BB = " $optA \n
+
+set exception 0
+catch {
+    argv int noSuchOptionAndNoDefault
+} exception
+puts exception = $exception "\n"
+
+proc multiStmt {} {
+    set max 5
+    set i 0
+    set s(0) 0
+    for {set i 0} {$i < $max} {incr i} {
+       set s($i) [query prepare "SELECT $i"]
+       puts "s($i) = $s($i)\n"
+    }
+    for {set i 0} {$i < $max} {incr i} {
+       query $s($i) step
+    }
+    for {set i 0} {$i < $max} {incr i} {
+       puts "closing stmt $s($i)\n"
+       query $s($i) finalize
+    }
+
+    puts "Preparing again\n"
+
+    for {set i 0} {$i < $max} {incr i} {
+       set s($i) [query prepare "SELECT $i"]
+       puts "s($i) = $s($i)\n"
+    }
+    for {set i 0} {$i < $max} {incr i} {
+       query $s($i) step
+    }
+    puts "Closing again\n"
+
+    for {set i 0} {$i < $max} {incr i} {
+       puts "closing stmt $s($i)\n"
+       query $s($i) finalize
+    }
+}
+multiStmt
+
+puts "If you got this far, you win!\n"
+</th1>

ADDED   test/th1-query-api-2.th1
Index: test/th1-query-api-2.th1
==================================================================
--- test/th1-query-api-2.th1
+++ test/th1-query-api-2.th1
@@ -0,0 +1,70 @@
+<th1>
+catch {
+    set stmt [query prepare {
+        SELECT login, cap, cexpire, mtime, NULL FROM user
+        WHERE uid<? AND cexpire IS NOT NULL
+        AND mtime IS NOT NULL
+    }]
+    puts "stmt ID=$stmt\n"
+#    query bind int $stmt 1 2
+#    query $stmt bind int 1 2
+    query $stmt bind 1 int 5
+# segfault:    query bind 1 int $stmt 2
+    set sep "\n"
+    for {} {[query $stmt step]} {} {
+        puts [query $stmt col string 0] $sep
+        puts [query $stmt col time 2 {%Y%m%d @ %H:%M:%S}] $sep
+        puts [query $stmt col time 2 {%Y%m%d @ %H:%M:%S} {+10 years}] $sep
+        puts [query $stmt col 2 time {%Y%m%d @ %H:%M:%S} {+10 years}] $sep
+#        puts [query col time $stmt 2 %s] $sep
+        puts [query col time $stmt 3 %s unixepoch] $sep
+#        puts [query strftime %s [query col string $stmt 3] unixepoch]
+        puts [query strftime %s [query col string $stmt 3] unixepoch] $sep
+        puts [query strftime {%Y%m%d @ %H:%M:%S} [query col string $stmt 2] {+10 years}] $sep
+        puts "\n"
+        puts "old isnull: " [query col isnull $stmt 4] "\n"
+
+        puts "new old isnull: " [query col 4 isnull $stmt] "\n"
+        puts "new isnull: " [query $stmt col isnull 4] "\n"
+        puts "new new isnull: " [query $stmt col 4 isnull] "\n"
+
+        puts "old col type: " [query col type $stmt 1] "\n"
+        puts "new col type: " [query $stmt col type 1] "\n"
+        puts "new new col type: " [query $stmt col 1 type] "\n"
+
+        puts "old col name: " [query col name $stmt 1] "\n"
+        puts "new col name: " [query $stmt col name 1] "\n"
+        puts "new new col name: " [query $stmt col 1 name] "\n"
+
+        puts "old col double: " [query col double $stmt 2] "\n"
+        puts "new col double: " [query $stmt col double 2] "\n"
+        puts "new new col double: " [query $stmt col 2 double] "\n"
+
+        puts "old col int: " [query col int $stmt 2] "\n"
+        puts "new col int: " [query $stmt col int 2] "\n"
+        puts "new new col int: " [query $stmt col 2 int] "\n"
+
+        puts "old col string: " [query col string $stmt 2] "\n"
+        puts "new col string: " [query $stmt col string 2] "\n"
+        puts "new new col string: " [query $stmt col 2 string] "\n"
+
+        puts "\n"
+    }
+
+    puts "alt-form col count: " [query $stmt col count] "\n"
+    query finalize $stmt
+    return 0
+} rc
+if {0 != $rc} {
+    puts "ERROR: $rc\n"
+}
+puts "Done!\n"
+
+
+ob start
+puts buffered
+set x [ob get pop]
+puts x=$x
+
+
+</th1>

ADDED   test/th1-variadic-proc.th1
Index: test/th1-variadic-proc.th1
==================================================================
--- test/th1-variadic-proc.th1
+++ test/th1-variadic-proc.th1
@@ -0,0 +1,10 @@
+<th1>
+proc vfunc {args} {
+  set argc [llength $args]
+  puts "argc=$argc Check this for a memleak when args length > 0\n"
+}
+vfunc
+vfunc 1
+vfunc 1 2
+vfunc 1 2 3
+</th1>

ADDED   www/th1_argv.wiki
Index: www/th1_argv.wiki
==================================================================
--- www/th1_argv.wiki
+++ www/th1_argv.wiki
@@ -0,0 +1,80 @@
+<h1>TH1 "argv" API</h1>
+
+The "argv" API provides features for accessing command-line arguments
+and GET/POST values. They (unfortunately) do not provide access to
+POST data submitted in JSON mode (which fossil internally doesn't really
+know about).
+
+Example usage:
+
+<nowiki><pre>
+&lt;th1>
+set argc [argv len]
+set appName [argv at 0]
+# Fetch --foo|-f argument:
+set foo [argv getstr foo f "default value"]
+&lt;th1>
+</pre></nowiki>
+
+(Note that fossil does not actually care if an argument starts
+with 1 or 2 dashes. The convention of using 1 for "short-form"
+flags and 2 for "long-form" is purely historical.)
+
+The various subcommands are described below...
+
+<h2>len</h2>
+
+Returns the number of arguments.
+
+<nowiki><pre>
+set argc [argv len]
+</pre></nowiki>
+
+
+<h2>at</h2>
+
+Fetches the argument at the given index (0-based).
+
+<nowiki><pre>
+set arg [argv at 3]
+</pre></nowiki>
+
+The fossil binary's name is stored in argument #0.
+
+<h2>getstr|string</h2>
+
+Searches for a CLI/GET/POST parameter. In CLI this function has some
+non-intuitive behaviour inherited from fossil's internals: once a
+flag/parameter is fetched, it is removed from the internal arguments
+list, meaning that this function will never see it a second time.
+
+<nowiki><pre>
+set something [argv string "something" "S" "default"]
+</pre></nowiki>
+
+If no default value is provided, an error is triggered if the value is
+not found.
+
+If you do not want to search for a short-form flag, set it to an empty
+string.
+
+NOTE: flag checking does not work in CGI mode when using <em>upper-case</em>
+flags (fossil treats upper-case names as environment variables).
+
+<h2>getbool|bool</h2>
+
+Works almost like <tt>getstr</tt> but searches for boolean flags. CLI boolean flags
+have no explicit value, and are "true" if the are set at all.
+
+<nowiki><pre>
+set doSomething [argv bool "do-something" "D" 0]
+</pre></nowiki>
+
+<h2>getint|int</h2>
+
+Works almost like <tt>getstr</tt> but searches for integer flags.
+
+
+<nowiki><pre>
+set limit [argv int "limit" "L" 10]
+</pre></nowiki>

ADDED   www/th1_ob.wiki
Index: www/th1_ob.wiki
==================================================================
--- www/th1_ob.wiki
+++ www/th1_ob.wiki
@@ -0,0 +1,96 @@
+<h1>TH1 "ob" (Output Buffering) API</h1>
+
+The "ob" API mimics the
+[http://php.net/manual/en/function.ob-start.php|PHP output buffering] fairly closely,
+and provides these features:
+
+   *  Redirect output from <tt>puts</tt> and friends to a buffer.
+   *  Fetch the buffer as a string or discard it (as you wish).
+   *  Supports nesting buffering arbitrarily deep, but each level which gets opened must also be closed by the scripter.
+
+Example usage:
+
+<nowiki><pre>
+&lt;th1>
+puts "this is unbuffered"
+ob push # or: ob start (same thing)
+# all output until the next ob start|end gets collected
+# in a buffer...
+&lt;/th1>
+
+this is buffered
+
+&lt;th1>
+puts "current buffer level = " [ob level] "\n"
+puts "this part is also collected in the buffer."
+
+# Collect the buffer's contents:
+set buf [ob get pop]
+# That is equivalent to:
+#  set buf [ob get]
+#  ob pop
+
+puts "\nThis is now unbuffered, but we buffered: $buf\n"
+&lt;/th1>
+</pre></nowiki>
+
+The functions are summarized below...
+
+<h2>ob push|start</h2>
+
+<tt>push</tt> and <tt>start</tt> are aliases ("start" comes from the PHP API, but
+"push" is probably more natural to those working with th1).
+
+<tt>ob start</tt> pushes a level of buffering onto the buffer stack, such that
+future calls which generate output through the th1-internal mechanism will have it
+transparently redirected to the current buffer.
+
+It is important that every call to <tt>ob start</tt> be followed up (eventually)
+by either <tt>ob end</tt> or <tt>ob get end</tt>.
+
+<h2>ob pop|end</h2>
+
+<tt>pop</tt> and <tt>end</tt> are aliases ("end" comes from the PHP API, but
+"pop" is probably more natural to those working with th1).
+
+This discards any current buffered contents and reverts the output state to
+the one it had before the previous <tt>ob start</tt>. i.e. that might be another
+buffering level or it might be the th1-normal output mechanism.
+
+The global resources associated with buffering are cleaned up when the
+last buffering level is left (and re-created as needed when a new
+level is started).
+
+<h2>ob clean</h2>
+
+This discards the current contents of the current buffer level but
+does not change the buffer stack level.
+
+<h2>ob get</h2>
+
+This fetches the current contents as a string. It optionally accepts
+either <tt>end</tt> (or its alias <tt>pop</tt>) or <tt>clean</tt>, in which cases it behaves like
+either <tt>ob end|pop</tt> or <tt>ob clean</tt>, respectively, in addition
+to returning the buffer contents. i.e. <tt>ob get clean</tt> will
+fetch the contents and clean up the buffer, but does not change the
+buffering level, whereas <tt>ob get end|pop</tt> pops the buffer off the
+stack after fetching its contents.
+
+<h2>ob level</h2>
+
+Returns the current buffering level (0 if not buffering).
+
+<h2>ob flush</h2>
+
+It is not expected that this will be useful all that often, but for
+the cases where it is, here's how it works: this behaves as if we
+fetched the buffer state (<tt>ob get</tt>), reverted TH1 to its
+previous output mechanism, push the buffer state to TH1, revert TH1
+<em>back</em> to the current buffering state, and then clear the
+current buffer contents (like <tt>ob clean</tt>). This does not change
+the buffering level, though it temporarily behaves as if it does.
+
+In other words, this function pushes the current buffer contents to the
+next-lower output mechanism (which may be another ob buffering level,
+fossil's internal CGI output buffer, or it might be be
+<tt>fwrite(stdout)</tt>).

ADDED   www/th1_query.wiki
Index: www/th1_query.wiki
==================================================================
--- www/th1_query.wiki
+++ www/th1_query.wiki
@@ -0,0 +1,199 @@
+<h1>TH1 "query" API</h1>
+
+The "query" API provides limited access to the fossil database.
+It restricts usage to queries which return result columns (i.e.
+<tt>SELECT</tt> and friends).
+Example usage:
+
+<nowiki><pre>
+&lt;th1>
+catch {
+    set stmt [query prepare "SELECT login, cap FROM user"]
+    puts "stmt ID=$stmt\n"
+    for {} {[query $stmt step]} {} {
+        puts [query $stmt col string 0] " " [query $stmt col string 1] \n
+    }
+    query $stmt finalize
+    return 0
+} rc
+if {0 != $rc} {
+    puts "ERROR: $rc\n"
+}
+&lt;th1>
+</pre></nowiki>
+
+The various subcommands are summarized in the following subsections, and here
+are some notes regarding calling conventions:
+
+The (bind, col, step, finalize) functions accept their statement ID argument
+either right after the "query" command or right after the final subcommand.
+The following examples demonstrate this:
+
+<nowiki><pre>
+query $stmt step
+query step $stmt
+
+query $stmt finalize
+query finalize $stmt
+
+query col string $stmt 1
+query $stmt col string 1
+
+query bind string $stmt 1 "foo"
+query $stmt bind string 1 "foo"
+</pre></nowiki>
+
+The "prefered" form is:
+
+<nowiki><pre>
+query StmtId command ...
+</pre></nowiki>
+
+(Why, then, are both forms accepted? Because the "preferred" form only
+evolved only after using the first form in script code.)
+
+
+<h2>prepare</h2>
+
+This subcommand prepares a query for execution. It returns a statement handle
+ID which must be passed to any other functions using the API.
+
+All prepared statements must be <tt>finalize</tt>d when they have outlived
+their usefulness.
+
+<nowiki><pre>
+set stmt [query prepare {SELECT ...}]
+...
+query $stmt finalize
+</pre></nowiki>
+
+
+<h2>finalize</h2>
+
+Releases all resources associated with the statement. Note that future
+calls to <tt>prepare</tt> might re-use the same statement statement
+ID.
+
+<nowiki><pre>
+set stmt [query prepare "SELECT ..."]
+...
+query $stmt finalize
+</pre></nowiki>
+
+
+<h2>step</h2>
+
+This subcommand steps the result set by one row. It returns 0
+at the end of the set, a positive value if a new row is available,
+and throws for any other condition.
+
+<nowiki><pre>
+for {} {[query $stmt step]} {} {
+   puts [query $stmt col string 0] "\n"
+}
+</pre></nowiki>
+
+
+<h2>reset</h2>
+
+Resets a query so that it can be executed again. This is only needed when
+binding parameters in a loop - call it at the end of each loop iteration.
+
+<nowiki><pre>
+query $stmt reset
+query reset $stmt
+</pre></nowiki>
+
+
+<h2>bind xxx</h2>
+
+The <tt>bind xxx</tt> family of subcommands attach values to queries
+before stepping through them. The subcommands include:
+
+   *  <tt>bind StmtId int Index Value</tt>
+   *  <tt>bind StmtId double Index Value</tt>
+   *  <tt>bind StmtId null Index</tt>
+   *  <tt>bind StmtId string Index Value</tt>
+
+Note that all of those optionally accept the statement handle directly after
+the "query" command (before the "col" subcommand). e.g.
+<tt>query bind null $stmt 1</tt> and
+<tt>query $stmt bind null 1</tt> are equivalent. They also accept the column index
+either before or after the type name, e.g.
+<tt>query $stmt bind 1 string ...</tt> and <tt>query $stmt bind string 1 ...</tt> are equivalent.
+
+
+Achtung: the bind API uses 1-based indexes, just like SQL does.
+
+<nowiki><pre>
+set stmt [query prepare "SELECT ... WHERE user=?"]
+query $stmt bind int 1 drh
+if {[query $stmt step]} {
+   puts [query $stmt col string 0] "\n"
+}
+query $stmt finalize
+</pre></nowiki>
+
+
+<h2>col xxx</h2>
+
+The <tt>col xxx</tt> familys of subcommands are for fetching values and metadata from result rows.
+
+   *  <tt>col StmtId count</tt> Returns the number of result columns in the statement.
+   *  <tt>col StmtId isnull Index</tt> Returns non-0 if the given column contains an SQL NULL value.
+   *  <tt>col StmtId (double|int|string) Index</tt> Fetches a column's value as either a number or string.
+   *  <tt>col StmtId time Index Format Modifiers</tt> Formats a time value. See below.
+   *  <tt>col StmtId type Index</tt> Returns the given column's type as a value from the <tt>SQLITE_TYPENAME</tt> family of constants.
+
+Note that all of those optionally accept the statement handle directly after
+the "query" command (before the "col" subcommand). e.g.
+<tt>query $stmt col count</tt> and
+<tt>query col count $stmt</tt> are equivalent. They also accept the column index
+either before or after the type name, e.g.
+<tt>query $stmt col 1 string</tt> and <tt>query $stmt col string 1</tt> are equivalent.
+
+Achtung: the col API uses 0-based indexes, just like SQL does.
+
+<h3>col time</h3>
+
+This function is a proxy for sqlite3's
+<tt>[http://www.sqlite.org/lang_datefunc.html|strftime()]</tt> function. It is used like this:
+
+
+<nowiki><pre>
+query $stmt col time $index {%Y%m%d @ %H:%M:%S}
+</pre></nowiki>
+
+Any remaining arguments are treated as "modifiers" and passed as-is to strfmtime. For example:
+
+<nowiki><pre>
+query $stmt col time $index {%Y%m%d @ %H:%M:%S} {+5 years}
+query $stmt col time $index %s unixepoch
+</pre></nowiki>
+
+
+
+
+<h2>strftime</h2>
+
+This works like <tt>col time</tt> (described below) but takes its
+value from an arbitrary source specified by the 3rd argument.
+
+<nowiki><pre>
+query strftime %s 1319211587 unixepoch
+query strftime {%Y%m%d @ %H:%M:%S} [query $stmt col string 2] {+10 years}]
+</pre></nowiki>
+
+<h2>Global Variables</h2>
+
+This API installs the following global variables, all of which correspond to
+<tt>SQLITE_xxx</tt> constant values:
+
+   *  <tt>SQLITE_BLOB</tt>
+   *  <tt>SQLITE_FLOAT</tt>
+   *  <tt>SQLITE_INTEGER</tt>
+   *  <tt>SQLITE_NULL</tt>
+   *  <tt>SQLITE_TEXT</tt>
+
+These values are used only by the <tt>col type</tt> function. They can be
+accessed from script code via <tt>$::SQLITE_xxx</tt>.