Python Patch

From Gwen Morse's Wiki
Jump to: navigation, search

To patch in Cygwin:

To patch in on linux:


diff -rNu -x .svn -x config.log -x config.status -x tf-help -x Makefile -x chartables.c -x tfconfig.h -x tfdefs.h -x '*.build' -x '*.bak' -x '*.idx' -x '*.o' -x '*.pyc' -x html2tf -x default -x makehelp -x tf -x dftables -x 'libpcre*' -x html2tif tf-50b8-clean/configure tf-50b8-py/configure
--- tf-50b8-clean/configure	2008-01-05 15:39:40.000000000 -0800
+++ tf-50b8-py/configure	2008-01-11 15:16:33.000000000 -0800
@@ -857,6 +857,7 @@
                           NAME=BINDIR/tf
   --enable-core           enable debugging core files
   --disable-ssl           disable SSL support
+  --disable-python        disable python scripting support
   --enable-getaddrinfo    enable getaddrinfo() (if configure complains)
   --disable-getaddrinfo   disable getaddrinfo() (implies --disable-inet6)
   --disable-inet6         disable IPv6 support
@@ -1373,6 +1374,13 @@
 else
   enable_ssl=yes
 fi;
+# Check whether --enable-python or --disable-python was given.
+if test "${enable_python+set}" = set; then
+  enableval="$enable_python"
+
+else
+  enable_python=yes
+fi;
 # Check whether --enable-getaddrinfo or --disable-getaddrinfo was given.
 if test "${enable_getaddrinfo+set}" = set; then
   enableval="$enable_getaddrinfo"
@@ -5500,6 +5508,14 @@
     fi
 fi
 
+if test "$enable_python" = "yes"; then
+   CFLAGS="$CFLAGS -DTFPYTHON"
+   pyinc=`python -c "import distutils.sysconfig; print distutils.sysconfig.get_python_inc()"`
+   pyver=`python -c "import distutils.sysconfig; print distutils.sysconfig.get_python_version()"`
+   CPPFLAGS="$CPPFLAGS -I$pyinc"
+   LIBS="$LIBS -lpython${pyver}"
+fi
+
 
 terminal_hardcode="TERM_vt100";
 
diff -rNu -x .svn -x config.log -x config.status -x tf-help -x Makefile -x chartables.c -x tfconfig.h -x tfdefs.h -x '*.build' -x '*.bak' -x '*.idx' -x '*.o' -x '*.pyc' -x html2tf -x default -x makehelp -x tf -x dftables -x 'libpcre*' -x html2tif tf-50b8-clean/configure.in tf-50b8-py/configure.in
--- tf-50b8-clean/configure.in	2008-01-05 15:39:40.000000000 -0800
+++ tf-50b8-py/configure.in	2008-01-11 15:16:33.000000000 -0800
@@ -54,6 +54,10 @@
 AC_ARG_ENABLE(ssl,
 [  --disable-ssl           disable SSL support],
     , enable_ssl=yes)
+# TFPYTHON patch
+AC_ARG_ENABLE(python,
+[  --disable-python        disable python interpreter],
+    , enable_python=yes)
 AC_ARG_ENABLE(getaddrinfo,
 [  --enable-getaddrinfo    enable getaddrinfo() (if configure complains)
   --disable-getaddrinfo   disable getaddrinfo() (implies --disable-inet6)],
@@ -85,7 +89,6 @@
 AC_ARG_ENABLE(float,
 [  --disable-float         disable floating point arithmetic and functions],
     , enable_float=yes)
-
 AC_ARG_WITH(incdirs,
 [  --with-incdirs=DIRS     search for include files in DIRS])
 AC_ARG_WITH(libdirs,
@@ -324,6 +327,19 @@
     fi
 fi
 
+# python patch
+if test "$enable_python" = "yes"; then
+   AC_MSG_NOTICE([/python enabled])
+   AC_CHECK_HEADER( Python.h, , break )
+   CFLAGS="$CFLAGS -DTFPYTHON"
+   pyinc=`python -c "import distutils.sysconfig; print distutils.sysconfig.get_python_inc()"`
+   pyver=`python -c "import distutils.sysconfig; print distutils.sysconfig.get_python_version()"`
+   CPPFLAGS="$CPPFLAGS -I$pyinc"
+   LIBS="$LIBS -lpython${pyver}"
+else
+   AC_MSG_NOTICE([/python disabled])
+fi
+
 dnl ### test termcap.
 dnl # At least one system (Red Hat Linux) has a broken ncurses and a working
 dnl # termcap, so we try termcap before ncurses.
diff -rNu -x .svn -x config.log -x config.status -x tf-help -x Makefile -x chartables.c -x tfconfig.h -x tfdefs.h -x '*.build' -x '*.bak' -x '*.idx' -x '*.o' -x '*.pyc' -x html2tf -x default -x makehelp -x tf -x dftables -x 'libpcre*' -x html2tif tf-50b8-clean/help/commands/python_call.html tf-50b8-py/help/commands/python_call.html
--- tf-50b8-clean/help/commands/python_call.html	1969-12-31 16:00:00.000000000 -0800
+++ tf-50b8-py/help/commands/python_call.html	2008-01-11 15:16:32.000000000 -0800
@@ -0,0 +1,43 @@
+<title>TinyFugue: /python_call</title>
+<!--"@/python_call"-->
+<h1>/python_call</h1>
+
+<p>
+  Usage:
+
+<p>
+  <a href="../commands/python_call.html">/python_call</a> <i>module</i>.<i>function</i> <i>argument</i>
+
+<p>
+  This calls a function in a python module you have loaded with
+  <a href="../commands/python_load.html">/python_load</a>.
+  Since the code is already byte compiled, this is the fastest way to execute
+  python. And since the entire rest of the line is considered the argument,
+  you don't have to worry about quoting issues.
+
+<p>
+  Example:<br>
+  <code><a href="../commands/python_load.html">/python_load</a> mymodule</code><br>
+  <code><a href="../commands/def.html">/def</a> -mglob -p9Fq -thowdy howdy = 
+  <a href="../commands/python_call.html">/python_call</a> mymodule.howdy %{*}</code>
+
+<p>
+  This will call mymodule.howdy() with the entire contents of the triggering
+  line as the argument whenever it matches a 'howdy' in a line. The real power
+  comes when using TinyFugue's powerful triggering and matching systems and
+  using a different method for each trigger type.
+
+<p>
+  For a more complex example, see the included <b>tf-lib/urlwatch.py</b> which catches
+  all urls going by and writes a url launching page.
+
+<p>
+  See <a href="../topics/tf_python.html">tf python</a> for calling back into TinyFugue.
+
+<p>
+<!-- END -->
+<hr>
+  <a href="./">Back to index</a><br>
+  <a href="http://tinyfugue.sourceforge.net/">Back to tf home page</a>
+<hr>
+  <a href="../topics/copyright.html">Copyright</a> © 1995, 1996, 1997, 1998, 1999, 2002, 2003, 2004, 2005, 2006-2007 <a href="http://sourceforge.net/users/kenkeys/">Ken Keys</a>
diff -rNu -x .svn -x config.log -x config.status -x tf-help -x Makefile -x chartables.c -x tfconfig.h -x tfdefs.h -x '*.build' -x '*.bak' -x '*.idx' -x '*.o' -x '*.pyc' -x html2tf -x default -x makehelp -x tf -x dftables -x 'libpcre*' -x html2tif tf-50b8-clean/help/commands/python.html tf-50b8-py/help/commands/python.html
--- tf-50b8-clean/help/commands/python.html	1969-12-31 16:00:00.000000000 -0800
+++ tf-50b8-py/help/commands/python.html	2008-01-11 15:16:32.000000000 -0800
@@ -0,0 +1,61 @@
+<title>TinyFugue: /python</title>
+<!--"@python"-->
+<!--"@/python"-->
+<h1>python()</h1>
+
+<p>
+  Function Usage:
+  
+<p>
+  <a href="../commands/python.html">python</a>( <i>command</i> )<br>
+
+<p>
+  This executes a single python expression. It must be an expression with a
+  return code, not a statement. For instance '1+1' is a expression that returns
+  2, but 'print 1+1' is a statement and would be illegal. It will convert
+  string, float, integer, or boolean return values to equivalent TF values.
+
+<p>
+  Example:<br>  
+  <code>/test echo( <a href="../commands/python.html">python</a>( strcat("'The current world is ", ${world_name},  ".'") ) )</code><br>
+  <code>The current world is (unnamed1).</code><br>
+  <br>
+  Note that quoting is slightly tricky here - you have to quote once for TF's sake, then
+  once for python.
+
+<p>
+  Command usage:
+
+<p>
+  <a href="../commands/python.html">/PYTHON</a> [<i>command</i>]<br>
+
+<p>
+  This invokes the python interpreter just as if you were entering commands at a
+  at a python cli. Each command must be standalone, but they can be statements
+  and there is a persistent environment, so you can do things like
+
+<p>
+  <code>/python import glob</code><br>
+  <code>/python tf.out( "Logfiles: " + ", ".join(glob.glob("*.log")) )</code><br>
+  <code>Logfiles: naughty.log, nice.log</code><br>
+  
+<p>
+  Generally this is only useful in your .tfrc - or other script setup. It is slow since
+  the command has to be recompiled every time, and you don't get any return values.
+  You won't see any output unless there's an error or you explcitly invoke tf.out().
+
+<p>
+  See <a href="../commands/python_load.html">/python_load</a> and
+  <a href="../commands/python_call.html">/python_call</a> instead.
+
+<p>
+  See /help <a href="../topics/tf_python.html">tf python</a> for calling back into TinyFugue
+  and for overall issues of performance and persistence.
+
+<p>
+<!-- END -->
+<hr>
+  <a href="./">Back to index</a><br>
+  <a href="http://tinyfugue.sourceforge.net/">Back to tf home page</a>
+<hr>
+  <a href="../topics/copyright.html">Copyright</a> © 1995, 1996, 1997, 1998, 1999, 2002, 2003, 2004, 2005, 2006-2007 <a href="http://sourceforge.net/users/kenkeys/">Ken Keys</a>
diff -rNu -x .svn -x config.log -x config.status -x tf-help -x Makefile -x chartables.c -x tfconfig.h -x tfdefs.h -x '*.build' -x '*.bak' -x '*.idx' -x '*.o' -x '*.pyc' -x html2tf -x default -x makehelp -x tf -x dftables -x 'libpcre*' -x html2tif tf-50b8-clean/help/commands/python_kill.html tf-50b8-py/help/commands/python_kill.html
--- tf-50b8-clean/help/commands/python_kill.html	1969-12-31 16:00:00.000000000 -0800
+++ tf-50b8-py/help/commands/python_kill.html	2008-01-11 15:16:32.000000000 -0800
@@ -0,0 +1,26 @@
+<title>TinyFugue: /python_kill</title>
+<!--"@/python_kill"-->
+<h1>/python_kill</h1>
+
+<p>
+  Usage:
+
+<p>
+  <a href="../commands/python_kill.html">/python_kill</a> <i>module</i>
+
+<p>
+  This dumps the python interpreter and any loaded python modules. The next
+  time any of the other <a href="../topics/tf_python.html">python</a> commands
+  are used it will be reloaded and reinitialized.
+
+<p>
+  This is intended as dead-man switch to prevent a required restart TinyFugue
+  if anything goes wrong with this beta-level python support.
+
+<p>
+<!-- END -->
+<hr>
+  <a href="./">Back to index</a><br>
+  <a href="http://tinyfugue.sourceforge.net/">Back to tf home page</a>
+<hr>
+  <a href="../topics/copyright.html">Copyright</a> © 1995, 1996, 1997, 1998, 1999, 2002, 2003, 2004, 2005, 2006-2007 <a href="http://sourceforge.net/users/kenkeys/">Ken Keys</a>
diff -rNu -x .svn -x config.log -x config.status -x tf-help -x Makefile -x chartables.c -x tfconfig.h -x tfdefs.h -x '*.build' -x '*.bak' -x '*.idx' -x '*.o' -x '*.pyc' -x html2tf -x default -x makehelp -x tf -x dftables -x 'libpcre*' -x html2tif tf-50b8-clean/help/commands/python_load.html tf-50b8-py/help/commands/python_load.html
--- tf-50b8-clean/help/commands/python_load.html	1969-12-31 16:00:00.000000000 -0800
+++ tf-50b8-py/help/commands/python_load.html	2008-01-11 15:16:32.000000000 -0800
@@ -0,0 +1,30 @@
+<title>TinyFugue: /python_load</title>
+<!--"@/python_load"-->
+<h1>/python_load</h1>
+
+<p>
+  Usage:
+
+<p>
+  <a href="../commands/python_load.html">/python_load</a> <i>module</i>
+
+<p>
+  This (re)imports a python module for you from your current PYTHONPATH, TFPATH,
+  and TFLIBDIR. Use this instead of just
+  '<a href="../commands/python.html">/python</a> import mymodule'
+  because it will also force a reload of the module for you in case it has been
+  changed. Any functions in the module are now ready to use with
+  <a href="../commands/python_call.html">/python_call</a>.
+
+<p>
+  Example:<br>
+  <code><a href="../commands/python_load.html">/python_load</a> mymodule</code><br>
+  <code><a href="../commands/python_call.html">/python_call</a> mymodule.dothis 1 2 3</code><br>
+
+<p>
+<!-- END -->
+<hr>
+  <a href="./">Back to index</a><br>
+  <a href="http://tinyfugue.sourceforge.net/">Back to tf home page</a>
+<hr>
+  <a href="../topics/copyright.html">Copyright</a> © 1995, 1996, 1997, 1998, 1999, 2002, 2003, 2004, 2005, 2006-2007 <a href="http://sourceforge.net/users/kenkeys/">Ken Keys</a>
diff -rNu -x .svn -x config.log -x config.status -x tf-help -x Makefile -x chartables.c -x tfconfig.h -x tfdefs.h -x '*.build' -x '*.bak' -x '*.idx' -x '*.o' -x '*.pyc' -x html2tf -x default -x makehelp -x tf -x dftables -x 'libpcre*' -x html2tif tf-50b8-clean/help/topics/tf_module.html tf-50b8-py/help/topics/tf_module.html
--- tf-50b8-clean/help/topics/tf_module.html	1969-12-31 16:00:00.000000000 -0800
+++ tf-50b8-py/help/topics/tf_module.html	2008-01-21 00:02:17.000000000 -0800
@@ -0,0 +1,57 @@
+<title>TinyFugue: TF Python</title>
+<!--"@tf.err"-->
+<!--"@tf.eval"-->
+<!--"@tf.getvar"-->
+<!--"@tf.out"-->
+<!--"@tf.send"-->
+<!--"@tf.tfrc"-->
+<!--"@tf.world"-->
+<!--"@tf module"-->
+<h1>TF Python Module</h1>
+
+<p>
+  The TF Python module lets your python module do useful things with TinyFugue.
+  When using <a href="../commands/python.html">/python</a> or 
+  <a href="../commands/python.html">python()</a> module tf is already present in the
+  local namespace, but any <a href="../commands/python_load.html">/python_load</a>
+  script needs to 'import tf'.
+
+<p>
+  <b>tf.err</b>( <i>text</i> ) - sends <i>text</i> to your screen (tferr stream).
+<p>
+  <b>tf.eval</b>( <i>string arg</i> ) - is the same as <a href="../commands/eval.html">/eval</a> <i>string arg</i>.
+<p>
+  <b>tf.getvar</b>( <i>var</i>, (<i>default</i> ) - gets value of <i>var</i> or <i>default</i> if it's not set.
+<p>
+  <b>tf.out</b>( <i>text</i> ) - sends <i>text</i> to your screen (tfout stream).
+<p>
+  <b>tf.send</b>( <i>text</i>, (<i>world</i> ) - sends <i>text</i> to <i>world</i> (default current)
+    without worrying about the flag/quoting problems of tf.eval()
+<p>
+  <b>tf.tfrc</b>() - returns the file name of the loaded .tfrc file (or empty string if none)
+<p>
+  <b>tf.world</b>() - returns the name of the current world or empty string if none.
+
+<p>
+  <b>Arbitrary data from TF</b>: Your program can retrieve any information it needs
+  from TinyFugue by doing the following (for example):<br>
+  <code>tf.eval( '/test "${world_name}"' )</code><br>
+  will return a string containing the name of the current world or:<br>
+  <code>tf.eval( '/test columns()' )</code><br>
+  will return an integer with the number of screen columns. Notice the
+  quoting is slightly depending on what we expect it to return.
+
+<p>
+  See the included <b>tf-lib/urlwatch.py</b> and <b>tf-lib/config.py</b> for fairly complex
+  examples.
+
+<p>
+  See also: <a href="./tf_python.html">tf python</a> for overall info.</p>
+
+<p>
+<!-- END -->
+<hr>
+  <a href="./">Back to index</a><br>
+  <a href="http://tinyfugue.sourceforge.net/">Back to tf home page</a>
+<hr>
+  <a href="../topics/copyright.html">Copyright</a> © 1995, 1996, 1997, 1998, 1999, 2002, 2003, 2004, 2005, 2006-2007 <a href="http://sourceforge.net/users/kenkeys/">Ken Keys</a>
diff -rNu -x .svn -x config.log -x config.status -x tf-help -x Makefile -x chartables.c -x tfconfig.h -x tfdefs.h -x '*.build' -x '*.bak' -x '*.idx' -x '*.o' -x '*.pyc' -x html2tf -x default -x makehelp -x tf -x dftables -x 'libpcre*' -x html2tif tf-50b8-clean/help/topics/tf_python.html tf-50b8-py/help/topics/tf_python.html
--- tf-50b8-clean/help/topics/tf_python.html	1969-12-31 16:00:00.000000000 -0800
+++ tf-50b8-py/help/topics/tf_python.html	2008-01-21 00:02:11.000000000 -0800
@@ -0,0 +1,59 @@
+<title>TinyFugue: TF Python</title>
+<!--"@tf python"-->
+<h1>TF Python</h1>
+
+<p>
+  TF Python is a patch (which you apparently have installed) for TinyFugue 5.08b
+  which allows TinyFugue to use Python for scripting.
+
+<p>
+  TinyFugue Commands (see each for details):<br>
+  <a href="../commands/python.html">/python</a> <i>command</i><br>
+  <a href="../commands/python.html">python</a>( <i>command</i> )<br>
+  <a href="../commands/python_load.html">/python_load</a> <i>module</i><br>
+  <a href="../commands/python_call.html">/python_call</a> <i>module</i>.<i>function</i> <i>argument</i><br>
+  <a href="../commands/python_kill.html">/python_kill</a>
+
+<p>
+  <b>Calling back into TinyFugue</b>: TF Python provides Python scripts with a 'tf'
+  module that has several methods - without these it's all fairly useless.
+  Please see <b>/help</b> <a href="./tf_module.html">tf module</a> for info on this.</p>
+
+<p>
+  <b>Speed</b>: The python interpreter is (dynamically) embedded in TinyFugue to
+  reduce overhead and allow tighter coupling. When using
+  <a href="../commands/python_load.html">/python_load</a> and
+  <a href="../commands/python_call.html">/python_call</a>
+  all the code is byte compiled on load, so it's quite snappy.
+
+<p>
+  <b>Environment</b>: The environment is persistent, so after '/python import glob'
+  subsequent commands will be able to reference module glob. More importantly
+  it means that after '/python_load mymodule'  mymodule will have persistent
+  variables between /python_call invocations.
+
+<p>
+  <b>Arbitrary data from TF</b>: Your program can retrieve any information it needs
+  from TinyFugue by using tf.eval.
+  Please see <b>/help</b> <a href="./tf_module.html">tf module</a> for info on this.</p>
+
+<p>
+  <b>stdout and stderr</b>: by default, stdout is discarded (so you don't see the
+  output of every <a href="../commands/python.html">/python</a> command) and stderr
+  is sent to tferr. You can make stdout active again with:<br>
+  <code><a href="../commands/python.html">/python</a> sys.stdout.output = tf.out</code><br>
+  and shut it up again with:<br>
+  <code><a href="../commands/python.html">/python</a> sys.stdout.output = None</code><br>
+  You can do the same with sys.stderr.output, but use tf.err instead.
+
+<p>
+  See the included <b>tf-lib/urlwatch.py</b> and <b>tf-lib/config.py</b> for fairly complex
+  examples.
+
+<p>
+<!-- END -->
+<hr>
+  <a href="./">Back to index</a><br>
+  <a href="http://tinyfugue.sourceforge.net/">Back to tf home page</a>
+<hr>
+  <a href="../topics/copyright.html">Copyright</a> © 1995, 1996, 1997, 1998, 1999, 2002, 2003, 2004, 2005, 2006-2007 <a href="http://sourceforge.net/users/kenkeys/">Ken Keys</a>
diff -rNu -x .svn -x config.log -x config.status -x tf-help -x Makefile -x chartables.c -x tfconfig.h -x tfdefs.h -x '*.build' -x '*.bak' -x '*.idx' -x '*.o' -x '*.pyc' -x html2tf -x default -x makehelp -x tf -x dftables -x 'libpcre*' -x html2tif tf-50b8-clean/README.python tf-50b8-py/README.python
--- tf-50b8-clean/README.python	1969-12-31 16:00:00.000000000 -0800
+++ tf-50b8-py/README.python	2009-06-25 00:27:38.000000000 -0700
@@ -0,0 +1,82 @@
+= TF Python (TinyFugue 5 beta python scripting patch)
+-
+- Copright 2008 Ron Dippold - Modify at will, just add your changes here
+-
+- V1.10 Jun 25-09 Jimun Batty's patch to fix a missing Py_INCREF.
+-                 Jessica Blank's fix to make this compile under OSX.
+- v1.09 Jan-25-08 Add tf->python conversion for TYPE_ENUM, TYPE_POS
+-                 add tfutil.py, convert config.py to use it
+-                 add tf4.py as a really obscene example
+- v1.08 Jan-21-08 don't save virtual worlds at all
+- v1.07 Jan-21-08 config.py output more readable sorted /addworld format
+-                 fix validation of Src Host
+-                 Add special save for default/virtual worlds
+- v1.06 Jan-19-08 Add tf.tfrc() function
+-                 add tf-lib/config.py
+-                 split out /help tf module to its own file
+- v1.05 Jan-13-08 Add convenience tf.getvar().
+-                 Add tf.send() to avoid tf.eval() quoting hassles
+-                 Add tf-lib/diffedit.py utility/example
+- v1.04 Jan-10-08 Add tf.world() function - it's too useful
+-                 Doc retrieving any var using tf.eval
+-                 Fix bug returning string values from tf.eval
+- v1.03 Jan-10-08 Add dummy sys.argv - some libraries expect it
+-                 Be smarter about import/reload to prevent double init
+- v1.02 Jan-10-08 Document stdout/stderr switching in /help tf python
+- v1.01 Jan-10-08 Document that the .tgzs won't work, html2tf problem
+- v1.00 Jan-10-08 Initial release
+
+Installation:
+
+  - Install python2.4 or python2.5 (haven't tried 2.3) and the
+    developer headers (if you don't do this you will get errors about no
+    Python.h found). This is usually easiest as a package, for instance
+    'apt-get install python2.5-dev' for debian, but otherwise:
+         http://python.org
+    If you're building from source make sure you configure with
+         ./configure --enable-shared 
+
+  - NEW: you can skip the next two steps just by checking out the already
+    patched source code anonymously with:
+      svn co svn://sizer99.com/tf-50b8-py
+
+  - If you didn't do the svn checkout:
+    Get the TinyFugue 5.08b source from http://tinyfugue.sourceforge.net/
+
+    You need the zip version, which has everything in it. The .tar.gz versions
+    are missing some files necessary to build the help.
+       # unzip -a tf-50b8.zip
+       # rm tf-50b8/help/html2tf
+    (the .zip version comes with a prebuilt html2tf which will core dump on
+    many systems, so this will cause it to be rebuilt)
+
+  - If you didn't do the svn checkout:
+    Test the patch, then apply it:
+      # cd tf-508b
+      # patch -p 1 -u -N --dry-run < tf-python-patch
+      # patch -p 1 -u -N < tf-python-patch
+
+  - Run configure, which now has --enable-python by default. Add any extra
+    options you want, if any (usually not). IF YOU ALREADY RAN CONFIGURE
+    before you applied the patch, that's okay, but you must do it again.
+      # ./configure
+
+  - Compile
+      # make
+    Don't worry about warnings about HAVE_INET_PTON or _POSIX_C_SOURCE.
+
+  - Install
+      # make install
+
+
+Usage:
+ 
+   - run TinyFugue (tf) and then 
+       /help tf python
+
+   - Check out tf-lib/urlwatch.py for a simple scripting example. This uses
+     only a tiny fraction of what you can do, but I've been so busy making
+     this patch I haven't had time to write all the scripts I want!
+
+Ron
+sizer@san.rr.com
diff -rNu -x .svn -x config.log -x config.status -x tf-help -x Makefile -x chartables.c -x tfconfig.h -x tfdefs.h -x '*.build' -x '*.bak' -x '*.idx' -x '*.o' -x '*.pyc' -x html2tf -x default -x makehelp -x tf -x dftables -x 'libpcre*' -x html2tif tf-50b8-clean/src/cmdlist.h tf-50b8-py/src/cmdlist.h
--- tf-50b8-clean/src/cmdlist.h	2008-01-05 15:39:40.000000000 -0800
+++ tf-50b8-py/src/cmdlist.h	2008-01-11 15:16:31.000000000 -0800
@@ -62,6 +62,12 @@
 defcmd("LOG"         , handle_log_command         , 0)
 defcmd("PS"          , handle_ps_command          , 0)
 defcmd("PURGE"       , handle_purge_command       , 0)
+#ifdef TFPYTHON
+defcmd("PYTHON"      , handle_python_command      , 0)
+defcmd("PYTHON_CALL" , handle_python_call_command , 0)
+defcmd("PYTHON_KILL" , handle_python_kill_command , 0)
+defcmd("PYTHON_LOAD" , handle_python_load_command , 0)
+#endif
 defcmd("QUIT"        , handle_quit_command        , 0)
 defcmd("QUOTE"       , handle_quote_command       , 0)
 defcmd("RECALL"      , handle_recall_command      , 0)
diff -rNu -x .svn -x config.log -x config.status -x tf-help -x Makefile -x chartables.c -x tfconfig.h -x tfdefs.h -x '*.build' -x '*.bak' -x '*.idx' -x '*.o' -x '*.pyc' -x html2tf -x default -x makehelp -x tf -x dftables -x 'libpcre*' -x html2tif tf-50b8-clean/src/command.c tf-50b8-py/src/command.c
--- tf-50b8-clean/src/command.c	2008-01-05 15:39:40.000000000 -0800
+++ tf-50b8-py/src/command.c	2009-06-25 00:43:45.000000000 -0700
@@ -30,6 +30,7 @@
 #include "expand.h"     /* macro_run() */
 #include "signals.h"    /* suspend(), shell() */
 #include "variable.h"
+#include "tfpython.h"
 
 int exiting = 0;
 
@@ -491,9 +492,10 @@
  ********************/   
 
 /* Returns -1 if file can't be read, 0 for an error within the file, or 1 for
- * success.
+ * success. If savename!=NULL and the file is found, *savename will be set to
+ * strdup(file->name) and it's up to you to free() it.
  */
-int do_file_load(const char *args, int tinytalk)
+int do_file_load(const char *args, int tinytalk, char **savename)
 {
     AUTO_BUFFER(line);
     AUTO_BUFFER(cmd);
@@ -522,7 +524,7 @@
 		    Stringadd(libfile, *path++);
 		}
 		if (!is_absolute_path(libfile->data)) {
-		    wprintf("invalid directory in TFPATH: %S", libfile);
+		    wprintf((const wchar_t *)"invalid directory in TFPATH: %S", libfile);
 		} else {
 		    Sappendf(libfile, "/%s", args);
 		    file = tfopen(expand_filename(libfile->data), "r");
@@ -530,7 +532,7 @@
 	    } while (!file && *path);
 	} else {
 	    if (!is_absolute_path(TFLIBDIR)) {
-		wprintf("invalid TFLIBDIR: %s", TFLIBDIR);
+		wprintf((const wchar_t *)"invalid TFLIBDIR: %s", TFLIBDIR);
 	    } else {
 		Sprintf(libfile, "%s/%s", TFLIBDIR, args);
 		file = tfopen(expand_filename(libfile->data), "r");
@@ -546,6 +548,8 @@
 
     do_hook(H_LOAD, quietload ? NULL : "%% Loading commands from %s.",
         "%s", file->name);
+	if( savename )
+		*savename = strdup( file->name );
     oflush();  /* Load could take awhile, so flush pending output first. */
 
     Stringninit(line, 80);
@@ -587,7 +591,7 @@
                 i = line->len - 1;
                 while (i > 0 && is_space(line->data[i])) i--;
                 if (line->data[i] == '\\')
-                    wprintf("whitespace following final '\\'");
+                    wprintf((const wchar_t *)"whitespace following final '\\'");
             }
         } else {
             last_cmd_line = 0;
@@ -710,7 +714,7 @@
 
     quietload += quiet;
     if (args->len - offset)
-        result = (do_file_load(stripstr(args->data + offset), FALSE) > 0);
+        result = (do_file_load(stripstr(args->data + offset), FALSE, NULL) > 0);
     else eprintf("missing filename");
     quietload -= quiet;
     return newint(result);
diff -rNu -x .svn -x config.log -x config.status -x tf-help -x Makefile -x chartables.c -x tfconfig.h -x tfdefs.h -x '*.build' -x '*.bak' -x '*.idx' -x '*.o' -x '*.pyc' -x html2tf -x default -x makehelp -x tf -x dftables -x 'libpcre*' -x html2tif tf-50b8-clean/src/command.h tf-50b8-py/src/command.h
--- tf-50b8-clean/src/command.h	2008-01-05 15:39:40.000000000 -0800
+++ tf-50b8-py/src/command.h	2008-01-20 23:37:36.000000000 -0800
@@ -23,7 +23,7 @@
 
 extern int      handle_command(const conString *cmd_line);
 extern BuiltinCmd *find_builtin_cmd(const char *cmd);
-extern int      do_file_load(const char *args, int tinytalk);
+extern int      do_file_load(const char *args, int tinytalk, char**foundname);
 extern int      handle_echo_func(conString *string, const char *attrstr,
                      int inline_flag, const char *dest);
 extern int      handle_substitute_func(conString *string,
diff -rNu -x .svn -x config.log -x config.status -x tf-help -x Makefile -x chartables.c -x tfconfig.h -x tfdefs.h -x '*.build' -x '*.bak' -x '*.idx' -x '*.o' -x '*.pyc' -x html2tf -x default -x makehelp -x tf -x dftables -x 'libpcre*' -x html2tif tf-50b8-clean/src/expr.c tf-50b8-py/src/expr.c
--- tf-50b8-clean/src/expr.c	2008-01-05 15:39:40.000000000 -0800
+++ tf-50b8-py/src/expr.c	2008-01-11 15:16:31.000000000 -0800
@@ -39,6 +39,7 @@
 #include "tty.h"	/* no_tty */
 #include "history.h"	/* log_count */
 #include "world.h"	/* new_world() */
+#include "tfpython.h"
 
 
 #define STACKSIZE 512
@@ -967,6 +968,18 @@
 		return shareval(val_zero);
 	    return_user_result();
 
+#ifdef TFPYTHON
+        case FN_python:
+		{
+			struct Value *rv = handle_python_function( opdstr(n-0) );
+			if( !rv ) {
+				return shareval(val_zero);
+			} else {
+				return rv;
+			}
+		}
+#endif
+
         case FN_send:
             i = handle_send_function(opdstr(n), (n>1 ? opdstd(n-1) : NULL), 
 		(n>2 ? opdstd(n-2) : ""));
diff -rNu -x .svn -x config.log -x config.status -x tf-help -x Makefile -x chartables.c -x tfconfig.h -x tfdefs.h -x '*.build' -x '*.bak' -x '*.idx' -x '*.o' -x '*.pyc' -x html2tf -x default -x makehelp -x tf -x dftables -x 'libpcre*' -x html2tif tf-50b8-clean/src/funclist.h tf-50b8-py/src/funclist.h
--- tf-50b8-clean/src/funclist.h	2008-01-05 15:39:40.000000000 -0800
+++ tf-50b8-py/src/funclist.h	2008-01-11 15:16:31.000000000 -0800
@@ -65,6 +65,9 @@
 funccode(pad,		1,	1,  (unsigned)-1),
 funccode(pow,		1,	2,  2),
 funccode(prompt,	0,	1,  1),
+#ifdef TFPYTHON
+funccode(python,	0,	1,  1),
+#endif
 funccode(rand,		0,	0,  2),
 funccode(read,		0,	0,  0),
 funccode(regmatch,	0,	2,  2), /* !pure: sets Pn */
diff -rNu -x .svn -x config.log -x config.status -x tf-help -x Makefile -x chartables.c -x tfconfig.h -x tfdefs.h -x '*.build' -x '*.bak' -x '*.idx' -x '*.o' -x '*.pyc' -x html2tf -x default -x makehelp -x tf -x dftables -x 'libpcre*' -x html2tif tf-50b8-clean/src/main.c tf-50b8-py/src/main.c
--- tf-50b8-clean/src/main.c	2008-01-05 15:39:40.000000000 -0800
+++ tf-50b8-py/src/main.c	2008-01-20 23:36:53.000000000 -0800
@@ -69,6 +69,8 @@
 static void read_configuration(const char *fname);
 int main(int argc, char **argv);
 
+char *main_configfile=NULL;
+
 int main(int argc, char *argv[])
 {
     char *opt, *argv0 = argv[0];
@@ -250,25 +252,25 @@
 static void read_configuration(const char *fname)
 {
 #if 1 /* XXX */
-    if (do_file_load(getvar("TFLIBRARY"), FALSE) < 0)
+    if (do_file_load(getvar("TFLIBRARY"), FALSE, NULL) < 0)
         die("Can't read required library.", 0);
 #endif
 
     if (fname) {
-        if (*fname) do_file_load(fname, FALSE);
+        if (*fname) do_file_load(fname, FALSE, &main_configfile);
         return;
     }
-
+	
     (void)(   /* ignore value of expression */
 	/* Try the next file if a file can't be read, but not if there's
 	 * an error _within_ a file. */
-        do_file_load("~/.tfrc", TRUE) >= 0 ||
-        do_file_load("~/tfrc",  TRUE) >= 0 ||
-        do_file_load("./.tfrc", TRUE) >= 0 ||
-        do_file_load("./tfrc",  TRUE)
+        do_file_load("~/.tfrc", TRUE, &main_configfile) >= 0 ||
+        do_file_load("~/tfrc",  TRUE, &main_configfile) >= 0 ||
+        do_file_load("./.tfrc", TRUE, &main_configfile) >= 0 ||
+        do_file_load("./tfrc",  TRUE, &main_configfile)
     );
 
     /* support for old fashioned .tinytalk files */
-    do_file_load((fname = getvar("TINYTALK")) ? fname : "~/.tinytalk", TRUE);
+    do_file_load((fname = getvar("TINYTALK")) ? fname : "~/.tinytalk", TRUE, &main_configfile);
 }
 
diff -rNu -x .svn -x config.log -x config.status -x tf-help -x Makefile -x chartables.c -x tfconfig.h -x tfdefs.h -x '*.build' -x '*.bak' -x '*.idx' -x '*.o' -x '*.pyc' -x html2tf -x default -x makehelp -x tf -x dftables -x 'libpcre*' -x html2tif tf-50b8-clean/src/malloc.c tf-50b8-py/src/malloc.c
--- tf-50b8-clean/src/malloc.c	2008-01-05 15:39:40.000000000 -0800
+++ tf-50b8-py/src/malloc.c	2009-06-25 00:41:05.000000000 -0700
@@ -12,7 +12,9 @@
 #include "signals.h"
 #include "malloc.h"
 
+#ifndef __APPLE__
 caddr_t mmalloc_base = NULL;
+#endif
 int low_memory_warning = 0;
 static char *reserve = NULL;
 
diff -rNu -x .svn -x config.log -x config.status -x tf-help -x Makefile -x chartables.c -x tfconfig.h -x tfdefs.h -x '*.build' -x '*.bak' -x '*.idx' -x '*.o' -x '*.pyc' -x html2tf -x default -x makehelp -x tf -x dftables -x 'libpcre*' -x html2tif tf-50b8-clean/src/socket.c tf-50b8-py/src/socket.c
--- tf-50b8-clean/src/socket.c	2008-01-05 15:39:40.000000000 -0800
+++ tf-50b8-py/src/socket.c	2008-01-25 23:26:56.000000000 -0800
@@ -1029,7 +1029,7 @@
     const char *mfile;
     if (restriction >= RESTRICT_FILE) return;
     if ((mfile = world_mfile(w)))
-        do_file_load(mfile, FALSE);
+        do_file_load(mfile, FALSE, NULL);
 }
 
 int world_hook(const char *fmt, const char *name)
diff -rNu -x .svn -x config.log -x config.status -x tf-help -x Makefile -x chartables.c -x tfconfig.h -x tfdefs.h -x '*.build' -x '*.bak' -x '*.idx' -x '*.o' -x '*.pyc' -x html2tf -x default -x makehelp -x tf -x dftables -x 'libpcre*' -x html2tif tf-50b8-clean/src/tfio.h tf-50b8-py/src/tfio.h
--- tf-50b8-clean/src/tfio.h	2008-01-05 15:39:40.000000000 -0800
+++ tf-50b8-py/src/tfio.h	2009-06-25 00:40:35.000000000 -0700
@@ -153,7 +153,13 @@
                      format_printf(2, 3);
 extern void   eprefix(String *buffer);
 extern void   eprintf(const char *fmt, ...) format_printf(1, 2);
+#ifndef __APPLE__
+#ifdef TFPYTHON
+// wprintf is already in /usr/include/wchar.h, which python includes
+#define wprintf tf_wprintf
 extern void   wprintf(const char *fmt, ...) format_printf(1, 2);
+#endif
+#endif
 extern char   igetchar(void);
 extern int    handle_tfopen_func(const char *name, const char *mode);
 extern TFILE *find_tfile(const char *handle);
diff -rNu -x .svn -x config.log -x config.status -x tf-help -x Makefile -x chartables.c -x tfconfig.h -x tfdefs.h -x '*.build' -x '*.bak' -x '*.idx' -x '*.o' -x '*.pyc' -x html2tf -x default -x makehelp -x tf -x dftables -x 'libpcre*' -x html2tif tf-50b8-clean/src/tfpython.c tf-50b8-py/src/tfpython.c
--- tf-50b8-clean/src/tfpython.c	1969-12-31 16:00:00.000000000 -0800
+++ tf-50b8-py/src/tfpython.c	2009-06-25 00:12:32.000000000 -0700
@@ -0,0 +1,431 @@
+// -------------------------------------------------------------------------------------
+// Python support for TinyFugue 5 beta
+//
+// Copyright 2008 Ron Dippold - Modify at will, just add your changes in README.python
+// -------------------------------------------------------------------------------------
+
+// If this isn't defined, the whole file is null
+#ifdef TFPYTHON
+
+#include "tfpython.h"
+
+// Change this 0 to 1 to add debug printfs
+#if 0
+#define DPRINTF oprintf
+#else
+#define DPRINTF(...)
+#endif
+
+
+static PyObject* tfvar_to_pyvar( const struct Value *rc );
+static struct Value* pyvar_to_tfvar( PyObject *pRc );
+
+// -------------------------------------------------------------------------------------
+// The tf 'module' as seen by python scripts
+// -------------------------------------------------------------------------------------
+
+// def tf.eval( argstring ):
+static PyObject *tf_eval( PyObject *pSelf, PyObject *pArgs )
+{
+	struct String *cmd;
+	const char *cargs;
+	struct Value *rc;
+
+	if( !PyArg_ParseTuple( pArgs, "s", &cargs ) ) {
+		eputs( "tf.eval called with non-string argument" );
+		Py_RETURN_NONE;
+
+	}
+
+	// ask tinyfugue to eval the string
+	( cmd = Stringnew( cargs, -1, 0 ) )->links++;
+	rc = handle_eval_command( cmd, 0 );
+	Stringfree( cmd );
+
+	return tfvar_to_pyvar( rc );
+}
+
+//def tf.out( string ):
+static PyObject *tf_out( PyObject *pSelf, PyObject *pArgs )
+{
+	const char *cargs;
+	if( !PyArg_ParseTuple( pArgs, "s", &cargs ) ) {
+		eputs( "tf.out called with non-string argument" );
+	} else {
+		oputs( cargs );
+	}
+	Py_RETURN_NONE;
+}
+
+//def tf.err( string ):
+static PyObject *tf_err( PyObject *pSelf, PyObject *pArgs )
+{
+	const char *cargs;
+	if( !PyArg_ParseTuple( pArgs, "s", &cargs ) ) {
+		eputs( "tf.err called with non-string argument" );
+	} else {
+		eputs( cargs );
+	}
+	Py_RETURN_NONE;
+}
+
+// tf.send( <string>, <world>=None )
+static PyObject *tf_send( PyObject *pSelf, PyObject *pArgs )
+{
+	const char *string, *worldname=NULL;
+
+	if( !PyArg_ParseTuple( pArgs, "s|s", &string, &worldname ) ) {
+		eputs( "tf.send called with bad arguments" );
+	} else {
+		String *buf = Stringnew( string, -1, 0 );
+		handle_send_function( CS(buf), worldname ? worldname : "" , "" );
+	}
+	Py_RETURN_NONE;
+}
+
+//def tf.tfrc()
+static PyObject *tf_tfrc( PyObject *pSelf, PyObject *pArgs )
+{
+	extern char *main_configfile;
+	return Py_BuildValue( "s", main_configfile ? main_configfile : "" );
+}
+
+//def tf.world():
+static PyObject *tf_world( PyObject *pSelf, PyObject *pArgs )
+{
+	World *world = named_or_current_world( "" );
+	return Py_BuildValue( "s", world ? world->name : "");
+}
+
+// tf.getvar( varname, default )
+static PyObject *tf_getvar( PyObject *pSelf, PyObject *pArgs )
+{
+	const char *cargs;
+	PyObject *def = NULL;
+	Var *var = NULL;
+
+	if( !PyArg_ParseTuple( pArgs, "s|O", &cargs, &def ) ) {
+		eputs( "tf.getvar called with bad arguments" );
+	} else {
+		var = ffindglobalvar( cargs );
+	}
+
+	if( var ) {
+		return tfvar_to_pyvar( &var->val );
+	} else if ( def ) {
+		Py_INCREF( def );
+		return def;
+	} else {
+		Py_RETURN_NONE;
+	}
+}
+
+// Our tf 'module'
+static PyMethodDef tfMethods[] = {
+	{
+		"err", tf_err, METH_VARARGS,
+		"tf.err( <string> )\n"
+		"Shows error <string> locally (to the tferr stream).\n"
+	},
+	{
+		"eval", tf_eval, METH_VARARGS,
+		"tf.eval( <string> )\n"
+		"Calls TinyFugue's /eval with <string>.\n"
+		"Returns the return code of the evaluation if any."
+	},
+	{
+		"getvar", tf_getvar, METH_VARARGS,
+		"tf.getvar( <varname>, (<default>) )\n"
+		"use tf.eval() for setvar functionality\n"
+		"Returns the value of <varname> if it exists, or default."
+	},
+	{
+		"out", tf_out, METH_VARARGS,
+		"tf.out( <string> )\n"
+		"Shows <string> locally (to the tfout stream).\n"
+	},
+	{
+		"send", tf_send, METH_VARARGS,
+		"tf.send( <string>, (<world>) )\n"
+		"Send <string> to <world> (default: current world)"
+	},
+	{	"tfrc", tf_tfrc, METH_VARARGS,
+		"tf.tfrc()\n"
+		"returns the file name of the .tfrc file (or empty string)"
+	},
+	{
+		"world", tf_world, METH_VARARGS,
+		"tf.world()\n"
+		"Returns the name of the current world, or a blank string."
+	},
+	{ NULL, NULL, 0, NULL }
+};
+
+
+// -------------------------------------------------------------------------------------
+// Helpers
+// -------------------------------------------------------------------------------------
+
+// run the command in the context of the __main__ dictionary
+static PyObject *main_module, *main_dict=NULL;
+static PyObject *common_run( const char *cmd, int start )
+{
+	return PyRun_String( cmd, start, main_dict, main_dict );
+}
+
+
+// Convert int, float, and string to python objects for return
+// Otherwise, just return None
+static PyObject* tfvar_to_pyvar( const struct Value *rc )
+{
+	switch( rc->type ) {
+	case TYPE_INT:
+	case TYPE_POS:
+		DPRINTF( "TYPE_INT: %d", rc->u.ival );
+		return Py_BuildValue( "i", rc->u.ival );
+	case TYPE_FLOAT:
+		DPRINTF( "TYPE_FLOAT: %f", rc->u.fval );
+		return Py_BuildValue( "f", rc->u.fval );
+	case TYPE_STR:
+	case TYPE_ENUM:
+		DPRINTF( "TYPE_STR: %s", rc->sval->data );
+		return Py_BuildValue( "s", rc->sval->data );
+	default:
+		DPRINTF( "TYPE ??? %d", rc->type );
+		Py_RETURN_NONE;
+	}
+}
+
+// Helper - take a python return object and covert it to a tf return object.
+// We supprt strings, integers, booleans (False->0, True->1 ), and floats
+static struct Value* pyvar_to_tfvar( PyObject *pRc )
+{
+	struct Value *rc;
+	char *cstr;
+	int len; // Py_ssize_t len;
+
+	// can be null if exception was thrown
+	if( !pRc ) {
+		PyErr_Print();
+		return newstr( "", 0 );  
+	}
+
+	// Convert string back into tf string
+	if( PyString_Check( pRc ) && ( PyString_AsStringAndSize( pRc, &cstr, &len ) != -1 ) ) {
+		DPRINTF( "  rc string: %s", cstr );
+		rc = newstr( cstr, len );
+	} else if( PyInt_Check( pRc ) ) {
+		DPRINTF( "  rc int: %ld", PyInt_AsLong( pRc ) );
+		rc = newint( PyInt_AsLong( pRc ) );
+	} else if( PyLong_Check( pRc ) ) {
+		DPRINTF( "  rc long: %ld", PyLong_AsLong( pRc ) );
+		rc = newint( PyLong_AsLong( pRc ) );
+	} else if( PyFloat_Check( pRc ) ) {
+		DPRINTF( "  rc float: %lf", PyFloat_AsDouble( pRc ) );
+		rc = newfloat( PyFloat_AsDouble( pRc ) );
+	} else if( PyBool_Check( pRc ) ) {
+		DPRINTF( "  rc bool: %lf", pRc == Py_True ? 1 : 0 );
+		rc = newint( pRc == Py_True ? 1 : 0 );
+	} else {
+		DPRINTF( "  rc None" );
+		rc = newstr( "", 0 );
+	}
+	Py_DECREF( pRc );
+
+	// And return
+	return rc;
+}
+
+// -------------------------------------------------------------------------------------
+// Structural work - initialize the interpreter if necessary
+// -------------------------------------------------------------------------------------
+static int py_inited=0;
+
+// All this is executed on first load. Note - you can change the behavior of this
+// on the fly, for instance to turn on stdout, by doing
+//    /python sys.stdout.output=tf.out
+// then returning it with
+//    /python sys.stdout.output=None
+//
+static const char *init_src =
+	"class __dummyout:\n"
+	"	buf=''\n"
+	"\n"
+	"	def __init__( self, output ):\n"
+	"		'pass in None for no output'\n"
+	"		self.output = output\n"
+	"	def write( self, arg ):\n"
+	"		if not self.output:\n"
+	"			return\n"
+	"		self.buf+=arg\n"
+	"		while '\\n' in self.buf:\n"
+	"			a, self.buf = self.buf.split('\\n',1)\n"
+	"			self.output( a )\n"
+	"\n"
+	"sys.stdout = __dummyout( None )\n"  	// this could be to tf.out
+	"sys.stderr = __dummyout( tf.err )\n"
+	"\n"
+	"sys.argv=[ 'tf' ]\n"
+;
+
+static void python_init()
+{
+	if( py_inited )
+		return;
+
+	// Initialize python
+	Py_Initialize();
+	
+	// Tell it about our tf_eval
+	Py_InitModule( "tf", tfMethods );
+
+	// get the basic modules
+	PyRun_SimpleString( "import os, sys, tf" );
+	PyRun_SimpleString( "sys.path.append( '.' )" );
+
+	// modify python path
+	if( TFPATH && *TFPATH ) {
+		String *buf = Stringnew( NULL, 0, 0 );
+		Sprintf( buf, "sys.path.extend( \"%s\".split() )", TFPATH );
+		PyRun_SimpleString( buf->data );
+	}
+	if( TFLIBDIR && *TFLIBDIR ) {
+		String *buf = Stringnew( NULL, 0, 0 );
+		Sprintf( buf, "sys.path.append( \"%s\" )", TFLIBDIR );
+		PyRun_SimpleString( buf->data );
+	}
+
+	PyRun_SimpleString( init_src );
+
+	// These are both borrowed refs, we don't have to DECREF
+	main_module = PyImport_AddModule( "__main__");
+	main_dict = PyModule_GetDict( main_module );
+
+
+	py_inited = 1;
+}
+
+static void python_kill()
+{
+	if( py_inited ) {
+		Py_Finalize();
+		py_inited = 0;
+	}
+}
+
+
+// -------------------------------------------------------------------------------------
+// c -> python calls
+// -------------------------------------------------------------------------------------
+
+
+// Handle a python call with arguments
+struct Value *handle_python_call_command( String *args, int offset )
+{
+	char *funcstr, *argstr;
+	PyObject *pRc=NULL, *function=NULL, *arglist=NULL;
+	//String *buf;
+
+	if( !py_inited )
+		python_init();
+
+	DPRINTF( "handle_python_call_command: %s", args->data + offset );
+
+	// Look for string splitting function name and arguments
+	funcstr = strdup( args->data + offset );
+	if( ( argstr = strchr( funcstr, ' ' ) ) ) {
+		*argstr++ = '\0';
+	} else {
+		argstr = "";
+	}
+
+	// Look up the function in the namespace, make sure it's callable
+	function = common_run( funcstr, Py_eval_input );
+	if( !function ) {
+		goto bail;
+	}
+	if( !PyCallable_Check( function )) {
+		PyErr_SetString(PyExc_TypeError, "parameter must be callable" );
+		goto bail;
+	}
+
+	// Okay, so now it's callable, give it the string.
+	// We go through all this because otherwise the quoting problem is insane.
+	arglist = Py_BuildValue( "(s)", argstr );
+	pRc = PyEval_CallObject( function, arglist );
+
+bail:
+	Py_XDECREF( function );
+	Py_XDECREF( arglist );
+	free( funcstr );
+	return pyvar_to_tfvar( pRc );
+}
+
+// Just kill the interpreter
+struct Value *handle_python_kill_command( String *args, int offset )
+{
+	python_kill();
+	return newint( 0 );
+}
+
+// Import/reload a python module	  
+struct Value *handle_python_load_command( String *args, int offset )
+{
+	PyObject *pRc;
+	struct Value *rv;
+	String *buf;
+	const char *name = args->data + offset;
+
+	if( !py_inited )
+		python_init();
+	
+	DPRINTF( "handle_python_load_command: %s", name );
+
+	// module could invoke tf.eval, so mark it as used
+	( buf = Stringnew( NULL, 0, 0 ) )->links++;
+	Sprintf( buf,
+		// if it exists, reload it, otherwise import it
+		"try:\n"
+		"  reload( %s )\n"
+		"except ( NameError, TypeError ):\n"
+		"  import %s\n",
+		name, name );
+
+	pRc = common_run( buf->data, Py_file_input );
+	if( pRc ) {
+		Py_DECREF( pRc );
+		rv = newint( 0 );
+	} else {
+		PyErr_Print();
+		rv = newint( 1 );
+	}
+	Stringfree( buf );
+	return rv;
+}
+
+// Run arbitrary code
+struct Value *handle_python_command( String *args, int offset )
+{
+	PyObject *pRc;
+
+	if( !py_inited )
+		python_init();
+
+	DPRINTF( "handle_python_command: %s", args->data + offset );
+	pRc = common_run( args->data + offset, Py_single_input );
+	return pyvar_to_tfvar( pRc );
+}
+
+struct Value *handle_python_function( conString *args )
+{
+	PyObject *pRc;
+
+	if( !py_inited )
+		python_init();
+
+	DPRINTF( "handle_python_expression: %s", args->data );
+	pRc = common_run( args->data, Py_eval_input );
+	return pyvar_to_tfvar( pRc );
+}
+
+#endif
diff -rNu -x .svn -x config.log -x config.status -x tf-help -x Makefile -x chartables.c -x tfconfig.h -x tfdefs.h -x '*.build' -x '*.bak' -x '*.idx' -x '*.o' -x '*.pyc' -x html2tf -x default -x makehelp -x tf -x dftables -x 'libpcre*' -x html2tif tf-50b8-clean/src/tfpython.h tf-50b8-py/src/tfpython.h
--- tf-50b8-clean/src/tfpython.h	1969-12-31 16:00:00.000000000 -0800
+++ tf-50b8-py/src/tfpython.h	2008-01-11 15:16:31.000000000 -0800
@@ -0,0 +1,34 @@
+#ifndef TFPYTHON_H
+#define TFPYTHON_H
+
+#ifdef TFPYTHON
+
+#include "Python.h"
+#include "tfconfig.h"
+#include "port.h"
+#include "tf.h"
+#include "util.h"
+#include "pattern.h"
+#include "search.h"
+#include "tfio.h"
+#include "cmdlist.h"
+#include "command.h"
+#include "world.h"	/* World, find_world() */
+#include "socket.h"	/* openworld() */
+#include "output.h"	/* oflush(), dobell() */
+#include "attr.h"
+#include "macro.h"
+#include "keyboard.h"	/* find_key(), find_efunc() */
+#include "expand.h"     /* macro_run() */
+#include "signals.h"    /* suspend(), shell() */
+#include "variable.h"
+
+struct Value *handle_python_function( conString *args );
+struct Value *handle_python_command( String *args, int offset );
+struct Value *handle_python_kill_command( String *args, int offset );
+struct Value *handle_python_call_command( String *args, int offset );
+struct Value *handle_python_load_command( String *args, int offset );
+
+#endif
+
+#endif
diff -rNu -x .svn -x config.log -x config.status -x tf-help -x Makefile -x chartables.c -x tfconfig.h -x tfdefs.h -x '*.build' -x '*.bak' -x '*.idx' -x '*.o' -x '*.pyc' -x html2tf -x default -x makehelp -x tf -x dftables -x 'libpcre*' -x html2tif tf-50b8-clean/src/vars.mak tf-50b8-py/src/vars.mak
--- tf-50b8-clean/src/vars.mak	2008-01-05 15:39:40.000000000 -0800
+++ tf-50b8-py/src/vars.mak	2008-01-11 15:16:31.000000000 -0800
@@ -20,10 +20,11 @@
 
 SOURCE = attr.c command.c dstring.c expand.c expr.c help.c history.c \
   keyboard.c macro.c main.c malloc.c output.c process.c search.c \
-  signals.c socket.c tfio.c tty.c util.c variable.c world.c
+  signals.c socket.c tfio.c tty.c util.c variable.c world.c \
+  tfpython.c
 
 OBJS = attr.$O command.$O dstring.$O expand.$O expr.$O help.$O history.$O \
   keyboard.$O macro.$O main.$O malloc.$O output.$O pattern.$O process.$O \
   search.$O signals.$O socket.$O tfio.$O tty.$O util.$O variable.$O world.$O \
-  $(OTHER_OBJS)
+  tfpython.$O $(OTHER_OBJS)
 
diff -rNu -x .svn -x config.log -x config.status -x tf-help -x Makefile -x chartables.c -x tfconfig.h -x tfdefs.h -x '*.build' -x '*.bak' -x '*.idx' -x '*.o' -x '*.pyc' -x html2tf -x default -x makehelp -x tf -x dftables -x 'libpcre*' -x html2tif tf-50b8-clean/tf-lib/config.py tf-50b8-py/tf-lib/config.py
--- tf-50b8-clean/tf-lib/config.py	1969-12-31 16:00:00.000000000 -0800
+++ tf-50b8-py/tf-lib/config.py	2008-01-26 00:04:32.000000000 -0800
@@ -0,0 +1,585 @@
+#
+# config.py
+#
+# This attempts to manage your TinyFugue configuration, letting you select,
+# edit, and save your worlds using /commands. Will expand this to key bindings
+# later. This is my first curses program, so it's pretty uuuuugly, sorry.
+#
+# Usage:
+#   /python_load config
+#   /python_call config.worldsfile ~/.tf/worlds      (optional)
+#   /worlds
+#
+# If you don't do the config.worldsfile() to tell it where your worlds are
+# located it will attempt to figure it out by looking for a /loadworlds in
+# your .tfrc. Or if it finds addworld commands in your .tfrc it will assume
+# it should just dump them in there.
+#
+#
+# Copyright 2008 Ron Dippold sizer@san.rr.com
+#
+# v1.01 - Jan 23 '08 - Convert to using tfutil
+# v1.00 - Jan 20 '08 - First version
+#
+import curses, os
+import tf, tfutil
+
+# ------------------------------------------------------
+# Where's the worldsfile?
+# ------------------------------------------------------
+
+WORLDSFILE=None
+def worldsfile( fname ):
+	global WORLDSFILE
+	WORLDSFILE=fname
+
+def _find_worldsfile():
+	# already found
+	global WORLDSFILE
+	if WORLDSFILE:
+		return WORLDSFILE
+
+	# look in tfrc
+	tfrc = tf.tfrc()
+	if not tfrc:
+		return None
+
+	# search through tfrc
+	f = open( tfrc, "rU" )
+	for line in f:
+		if line.startswith( "/loadworld " ):
+			WORLDSFILE=line.split()[1].strip()
+			break
+		if "addworld" in line:
+			WORLDSFILE = tfrc
+			break
+	f.close()
+	if WORLDSFILE:
+		WORLDSFILE = os.path.abspath( os.path.expanduser( WORLDSFILE ) )
+	return WORLDSFILE
+	
+# ------------------------------------------------------
+# helpers
+# ------------------------------------------------------
+
+
+# show help line with [f]oo as <b>f</b>oo
+def _showkeys( window, y, x, fields ):
+
+	maxy, maxx = window.getmaxyx()
+	window.addnstr( y, x, " "*132, maxx-x-2 )
+
+	window.addstr( y, x, fields.replace('[','').replace(']','') )
+	offset, pos= 0, 0
+	while True:
+		pos = fields.find( '[', pos )
+		if pos<0: break
+		offset += 1
+		end = fields.find( ']', pos )
+		if end<0: break
+		window.addstr( y, x+pos-offset+1, fields[pos+1:end], curses.A_BOLD )
+		pos = end
+		offset += 1
+
+# -----------------------------------------------------------------------
+# Edit the worlds
+# -----------------------------------------------------------------------
+
+def worlds( argstr ):
+	# wrap the function in case it crashes
+	curses.wrapper( _worlds )
+	# screen is all messed up, redraw it
+	tf.eval( "/dokey REDRAW" )
+
+def _new_undo( undo, wdict, message, saved ):
+	undo.append( ( message, dict( [ ( key, tfutil.World(val) ) \
+									for key, val in wdict.items() ] ), saved ) )
+
+SAVED = True
+def _change_worlds( old, wdict ):
+	# remove old worlds
+	for name, item in old.items():
+		if name not in wdict:
+			tf.eval( "/unworld " + name )
+
+	# add new or changed worlds
+	for name, item in wdict.items():
+		cmd = item.addworld_command( func=True, full=True )
+		if name not in old:
+			if cmd: tf.eval( cmd )
+		elif item.changed_from(old[name]):
+			tf.eval( "/unworld " + name )
+			if cmd: tf.eval( cmd )
+
+def _save_worlds2( fout, wdict ):
+	for name, item in sorted(wdict.items()):
+		cmd = item.addworld_command( func=False, full=True )
+		if cmd:
+			fout.write(  cmd + '\n' )
+	return True
+
+def _save_worlds( wdict ):
+	fname = _find_worldsfile()
+	if not fname:
+		return False, "Can't find world file. Use '/python_call config.worldsfile <filename>'"
+
+	# Read the old lines except for addworld, write to new file
+	try:
+		fout = open( fname+".xxx", "wt", 0600 )
+	except IOError, e:
+		return False, "Error opening %s for write: %s" % ( e.filename, e.strerror )
+
+	fin = open( fname, "rU" )
+	written = False
+	
+	for line in fin:
+		if "addworld" in line:
+			if not written:
+				written = _save_worlds2( fout, wdict )
+			continue
+		# write, converting line endings
+		fout.write( line  )
+	fin.close()
+
+	# write all the worlds to the end of the file if haven't done it yet
+	if not written:
+		written = _save_worlds2( fout, wdict )
+	fout.close()
+
+	# move the new file over the old one
+	os.rename( fin.name, fin.name+".bak" )
+	os.rename( fout.name, fin.name )
+	tf.out( "- saved /worlds to %s" % fin.name )
+
+	return True, None
+
+# This is the real worlds function, invoked by curses safety wrapper
+def _worlds( stdscr ):
+
+	# get dictionary of current worlds
+	wdict = tfutil.listworlds( asdict=True )
+
+	cols, lines = tfutil.screensize()
+	colors = curses.can_change_color()
+
+	# Draw the border and create a world window
+	stdscr.clear()
+	stdscr.border()
+	stdscr.addstr( 0, 4, "| TinyFugue Worlds |", curses.A_BOLD )
+	wpos, lastwpos = 0, 9999
+
+	worldwin, scrollwin = _worldwin( stdscr, lines-4, cols-4, 2, 2 )
+	#worldwin, scrollwin = _worldwin( stdscr, 15, cols-4, 2, 2 )
+	worldwin.refresh()
+
+	# start an undo stack
+	undo, redo = [], []
+	global SAVED
+
+	# now loop
+	message, lastmessage = None, "dummy"
+	while True:
+
+		# sort by name
+		wlist = sorted( wdict.values() )
+
+		# draw the list, get a command
+		_drawworlds( wlist, scrollwin, wpos, lastwpos )
+		lastwpos = wpos
+
+		#
+		# parse keys
+		#
+		c = scrollwin.getkey()
+
+		# Movement
+		if c in ( 'i', 'KEY_UP', '\x10' ):
+			if wpos>0:
+				wpos -= 1
+
+		elif c in ( 'j', 'KEY_DOWN', '\x0e' ) :
+			if (wpos+1) < len( wlist ):
+				wpos += 1
+
+		# actual editing
+		elif wlist and c in ( 'd', 'KEY_DC' ):
+			message = 'Deleted world %s' % wlist[wpos].name
+			_new_undo( undo, wdict, message, SAVED )
+			del wdict[ wlist[wpos].name ]
+			wpos = min( wpos, len(wdict)-1 )
+			SAVED, lastwpos = False, 9999
+
+		elif wlist and c in ( 'c', ):
+			w = wlist[wpos]
+			for i in range( 2, 99 ):
+				newname = '%s(%d)' % ( w.name, i )
+				if not newname in wdict:
+					message = 'Copied world %s' % newname
+					_new_undo( undo, wdict, message, SAVED )
+					newworld = tfutil.World(w)
+					newworld.name = newname
+					wdict[newname] = newworld
+					SAVED, lastwpos = False, 9999
+					break
+
+		elif c in ( 'a', 'KEY_INS' ):
+			w = _editworld( worldwin, wdict.keys(), None )
+			if w:
+				message = 'Added world %s' % w.name
+				_new_undo( undo, wdict, message, SAVED )
+				wdict[ w.name ] = w
+				SAVED = False
+			lastwpos = 9999
+			_worldwin_redraw( worldwin )
+
+		elif wlist and c in ( 'e', ):
+			oldname = wlist[wpos].name
+			w = _editworld( worldwin, wdict.keys(), tfutil.World(wlist[wpos]) )
+			if w:
+				message = 'Edited world %s' % w.name
+				_new_undo( undo, wdict, message, SAVED )
+				if oldname!=w.name:
+					del wdict[oldname]
+				wdict[ w.name ] = w
+				SAVED = False
+			lastwpos = 9999
+			_worldwin_redraw( worldwin )
+
+		# undo/redo
+		elif c == 'u':
+			if undo:
+				message, wdict2, SAVED2 = undo.pop()
+				_new_undo( redo, wdict, message, SAVED )
+				wdict, SAVED = wdict2, SAVED2
+				message = "Undid: " + message
+				lastwpos = 9999
+			else:
+				message = 'Nothing to undo'
+
+		elif c == 'r':
+			if redo:
+				message, wdict2, SAVED2 = redo.pop()
+				_new_undo( undo, wdict, message, SAVED )
+				wdict, SAVED = wdict2, SAVED2
+				message = "Redid: " + message
+				lastwpos = 9999
+			else:
+				message = 'Nothing to redo'
+
+
+		# Anything that terminates us
+		elif wlist and c in ( '\n', 'KEY_ENTER', 'Q' ):
+			if undo:
+				_change_worlds( undo[0][1], wdict )
+			if c != 'Q':
+				tf.eval( "/connect %s" % wlist[wpos].name )
+			if not SAVED:
+				tf.err( "* Warning: your /worlds haven't been saved yet" )
+			break
+			
+		elif c == 'S':
+			if undo:
+				_change_worlds( undo[0][1], wdict )
+			if not SAVED:
+				SAVED, message = _save_worlds( wdict )
+			if SAVED:
+				break
+
+		elif c == 'A':
+			if not SAVED:
+				tf.err( "* all /worlds changes aborted" )
+			SAVED = False
+			break
+
+		message, lastmessage = _show_message( stdscr, message, lastmessage )
+
+
+def _show_message( window, message, lastmessage ):
+	y, x = window.getmaxyx()
+	if message:
+		window.addnstr( 1, 1, " " + message + " "*x, x-2, curses.A_REVERSE )
+		lastmessage = message
+		window.refresh()
+	elif lastmessage:	
+		window.addnstr( 1, 1, " "*x, x-2, curses.A_NORMAL )
+		window.refresh()
+		lastmessage = None
+	return None, lastmessage
+			
+
+def _worldwin_redraw( window ):
+	lines, cols = window.getmaxyx()
+	window.erase()
+
+	# title
+	window.addnstr( 0, 0, "World      Character         Host                  Port",
+					cols, curses.A_BOLD )
+	# help line
+	_showkeys( window, lines-1, 0,
+			   '[enter] - [a]dd [c]opy [d]el [e]dit - [u]ndo/[r]edo - [S]ave [Q]uit [A]bort' )
+
+
+def _worldwin( parent, lines, cols, y, x ):
+	lines -= 4
+	cols -= 4
+	window = parent.derwin( lines, cols, 2, 2 )
+	window.keypad(1)
+
+	_worldwin_redraw( window )
+	
+	scrollwin = window.derwin( lines-2, cols, 1, 0 )
+	scrollwin.keypad(1)
+	scrollwin.erase()
+
+	return window, scrollwin
+
+def _drawworlds( wlist, window, wpos, lastwpos ):
+
+	# figure out the top world we're showing
+	lines, cols = window.getmaxyx()
+	top = wpos - lines + 1
+	if top<0: top = 0
+
+	lasttop = lastwpos - lines + 1
+	if lasttop<0: lasttop=0
+
+	# Try to avoid redrawing the whole thing
+	start, end = min( lastwpos, wpos ), max( lastwpos, wpos )+1
+	if top == lasttop:
+		pass
+	elif top == (lasttop+1):
+		window.move( 0, 0 )
+		window.deleteln()
+	else:
+		start, end = 0, len( wlist )
+
+	# list each visible world
+	for y in range( lines ):
+		window.move( y, 0 )
+		i = top + y
+		if i >= len( wlist ):
+			window.clrtoeol()
+		else:
+			if i<start or i>=end:
+				continue
+			attrib = (wpos == i) and curses.A_REVERSE or curses.A_NORMAL
+			item = wlist[i]
+			ssl = ( 'x' in item.flags ) and '*' or ' '
+			window.addnstr( "%-10s %-16s %s%-20s %5s" % \
+							( item.name, item.character, ssl, item.host, item.port ),
+							cols, attrib )
+	
+	window.move( wpos-top, 0 )
+	window.refresh()
+
+# Define our functions
+tf.eval( "/def worlds=/python_call config.worlds" )
+
+
+# ---------------------------------------------------------------------------
+# Editing a world
+# ---------------------------------------------------------------------------
+
+def _validate_port( world, worldnames, value ):
+	try:
+		if int(value)>0 and int(value)<65536:
+			return None
+	except:
+		pass
+	return "Port number must be from 1 and 65535."
+
+def _validate_name( world, worldnames, value ):
+	if not value:
+		return "The world name may not be blank."
+	if value in worldnames:
+		return "There is already a world with that name."
+	return None
+
+# Required:
+#   'name' : label
+#   'help' : help field
+# Either 'key' or 'get'/'set' must be set:
+#   'key'  : name of dict key
+#   'get'  : function that returns value from ( world )
+#   'set'  : function that sets val in world from ( world, val )
+# Optional:
+#   'edit' : function to edit val from ( world, val ) if you don't want textpad
+#   'validate': function passed ( world, worldnames, value ) and returns error
+#               message, or None if it passes validation
+EDITFIELDS = [
+	{ 'name':	'World',
+	  'help':	'Name of the world, used for /world',
+	  'key':	'name',
+	  'validate': _validate_name,
+	  },
+	{ 'name':	'Type',
+	  'help':	'Mu* type: aber, diku, lp, lpp, telnet, tiny',
+	  'key':	'type',
+	  },
+	{ 'name':	'Host',
+	  'help':	'Host name or IP address',
+	  'key':	'host',
+	  },
+	{ 'name':	'Port',
+	  'help':	'Port number - 1 to 65535',
+	  'key':	'port',
+	  'validate': _validate_port,
+	  },
+	{ 'name':	'Character',
+	  'help':	'Character name for auto-logon',
+	  'key':	'character',
+	  },
+	{ 'name':	'Password',
+	  'help':	'Character password for auto-logon',
+	  'key':	'password',
+	  },
+	{ 'name':	'Use SSL',
+	  'help':	'Use SSL to connect to the world (if compiled in)',
+	  'yesno':	True,
+	  'edit':	lambda w,v: v == 'Yes' and 'No' or 'Yes',
+	  'get':	lambda w: ( 'x' in w.flags ) and 'Yes' or 'No',
+	  'set':	lambda w,v: _setflag( w, 'x', v ),
+	  },
+	{ 'name':	'Ignore Proxy',
+	  'help':	'ignore %{proxy_host} if set',
+	  'yesno':	True,
+	  'edit':	lambda w,v: v == 'Yes' and 'No' or 'Yes',
+	  'get':	lambda w: ( 'p' in w.flags ) and 'Yes' or 'No',
+	  'set':	lambda w,v: _setflag( w, 'p', v ),
+	  },
+	{ 'name':	'Echo input',
+	  'help':	'Echo back all text sent to the world',
+	  'yesno':	True,
+	  'edit':	lambda w,v: v == 'Yes' and 'No' or 'Yes',
+	  'get':	lambda w: ( 'e' in w.flags ) and 'Yes' or 'No',
+	  'set':	lambda w,v: _setflag( w, 'e', v ),
+	  },
+	{ 'name':	'Macro file',
+	  'help':	'Macro file to /load on connect',
+	  'key':	'file',
+	  },
+	{ 'name':	'Src Host',
+	  'help':	'Set source IP to bind to a specific interface',
+	  'key':	'srchost',
+	  },
+	]
+
+def _setflag( world, flag, val ):
+	add = val.lower().startswith("y") and flag or ""
+	world.flags = world.flags.replace(flag,'') + add
+
+
+def _getfield( world, field ):
+	if 'get' in field:
+		return field['get'](world)
+	else:
+		return getattr( world, field['key'] )
+
+def _setfield( world, field, value ):
+	if 'set' in field:
+		field['set'](world,value)
+	else:
+		setattr( world, field['key'], value )
+
+def _editworld( worldwin, worldnames, world ):
+
+	import curses.textpad
+
+	# if the world is empty, create a default world
+	if not world:
+		world = World()
+	else:
+		worldnames.remove( world.name )
+
+	# create a new window as a subwindow of the old
+	lines0, cols0 = worldwin.getmaxyx()
+	lines = min( lines0-4, 17 )
+	cols = cols0-4
+	y = (lines0-lines)/2
+	editwin = worldwin.derwin( lines, cols, y, 2 )
+	editwin.keypad( 1 )
+	editwin.erase()
+	editwin.border()
+	editwin.addstr( 0, 4, "| Editing World: %s |" % (world.name and world.name or '<new>'), \
+					curses.A_BOLD )
+
+	_showkeys( editwin, lines-2, 2,
+			   '[d]elete [enter]/[e]dit - [Q]/[S]ave [A]bort' )
+
+	# now loop
+	pos=0
+	message, lastmessage = None, None
+	while True:
+
+		# Paint all the fields
+		y, x = 2, 2
+		for i, field in enumerate( EDITFIELDS ):
+			editwin.addstr( y, x, field['name']+':', curses.A_BOLD )
+			editwin.addnstr( y, x+15, _getfield( world, field ) + " "*80, cols-19,
+							i==pos and curses.A_REVERSE or curses.A_NORMAL )
+			y += 1
+
+
+		# get input
+		while True:
+
+			# show the help for the current item
+			field = EDITFIELDS[pos]
+			editwin.addnstr( y+1, 2, field['help']+(" "*80), cols-4 )
+
+			# get input
+			editwin.move( 2+pos, 17 )
+			editwin.refresh()
+			c = editwin.getkey()
+
+			if c in ( 'i', 'KEY_UP', '\x10' ):
+				if pos>0:
+					pos -= 1
+					break
+			elif c in ( 'j', 'KEY_DOWN', '\x0e' ):
+				if (pos+1) < len( EDITFIELDS ):
+					pos += 1
+					break
+			elif c in ( 'e', 'KEY_ENTER', ' ', '\n', 'd', 'KEY_DC' ):
+				value = _getfield( world, field )
+				if c in ( 'd', 'KEY_DC' ):
+					value = ''
+				elif 'edit' in field:
+					value = field['edit'](world, value )
+				else:
+					# create a text editing box
+					linewin = editwin.derwin( 1, cols-19, 2+pos, 17 )
+					linewin.erase()
+					if c != ' ':
+						linewin.addstr( 0, 0, value )
+					textpad = curses.textpad.Textbox( linewin )
+					curses.noecho()
+					value = textpad.edit().strip()
+				if ' ' in value:
+					message = 'No spaces allowed in values'
+				elif 'validate' in field:
+					message = field['validate']( world, worldnames, value )
+					if not message:
+						_setfield( world, field, value )
+						
+				else:
+					_setfield( world, field, value )
+				break
+			elif c in ( 'y', 'n', 'Y', 'N' ):
+				if 'yesno' in field:
+					_setfield( world, field, c )
+					break
+			elif c in ( 'S', 'Q' ):
+				# return world only if it has a name
+				if world.category!=world.INVALID:
+					return world
+				else:
+					return None
+			elif c in ( 'A', 'KEY_ESC' ):
+				return None
+
+
+		message, lastmessage = _show_message( editwin, message, lastmessage )
+
+
+tf.out( "% config.py loaded: use '/worlds' to edit your worlds" )
diff -rNu -x .svn -x config.log -x config.status -x tf-help -x Makefile -x chartables.c -x tfconfig.h -x tfdefs.h -x '*.build' -x '*.bak' -x '*.idx' -x '*.o' -x '*.pyc' -x html2tf -x default -x makehelp -x tf -x dftables -x 'libpcre*' -x html2tif tf-50b8-clean/tf-lib/diffedit.py tf-50b8-py/tf-lib/diffedit.py
--- tf-50b8-clean/tf-lib/diffedit.py	1969-12-31 16:00:00.000000000 -0800
+++ tf-50b8-py/tf-lib/diffedit.py	2008-03-24 17:51:52.000000000 -0700
@@ -0,0 +1,396 @@
+#
+# diffedit.py
+#
+# This attempts to help the problem of changing three lines in a 1000 line
+# program and then having to painfully recreate it line by line or reupload
+# the whole thing.
+#
+# It will remember the last version of your world/object and then diff it
+# against the new one and only upload the differences. It will recognize
+# that the same object in two different worlds is a different thing, but
+# it will get confused if you try to do diffedits from two different
+# computers on the same object (because it will apply changes twice) so
+# use the -r (raw) flag to synchronize to a known state at least once.
+#
+# There needs to be a class defined for each type of editor supported.
+# I have supplied editors for 'lsedit' and 'muf' on FuzzBall mucks.
+#
+# Copyright 2008 Ron Dippold sizer@san.rr.com
+#
+# v1.02 - Mar 06 08 - fix bug with no progress shown
+# v1.01 - Jan 13 08 - Clean up docs, make LINES_PER_SECOND settable
+# v1.00 - Jan 13 08 - First version
+
+# You might want to configure these two
+LASTDIR='/tmp/diffedit'
+TABSIZE=4
+LINES_PER_SECOND=50
+
+# real stuff starts here
+import tf, difflib, os, shutil
+
+def help( dummy=None ):
+	for line in """
+diffedit usage:
+
+  /python_call diffedit.upload (-<lines>) (-r) <editor> <remote> <filename>
+
+  Uploads minimal commands necessary to turn centents of <remote> into
+  <filename> by diff-ing against the last version. This is per <remote>
+  per world.
+
+  options:
+    -<lines>  If -S or -0, send all commands immediately. Otherwise sends
+              <lines> per second. Default: 50.
+    -r        Just upload the entire thing. Useful if you think things might
+              be out of sync, or to start fresh. The first time you edit a
+              <remote> for a specific world -r is implied.
+    -p(<n>)   Show progress every <n> lines. Default 100 if -p present.
+
+  arguments:
+    <editor>  Which editor class to upload with. Current choices are:
+              ( lsedit | muf )
+    <remote>  Which 'thing' to upload it to.
+              lsedit: <dbref>=<list name>       ex: #1234=mydesc
+              muf:    <dbref>                   ex: #1234
+    <fname>   The filename that contains the content you want on the object.
+
+Example:
+   To work on my huge scream.muf progam I use:
+     /python_call diffedit.upload muf -p scream.muf ~/muf/scream.muf
+   Since this would be annoying to type every time I'd really use:
+     /def scream=/python_call diffedit.upload muf -p scream.muf ~/muf/scream.muf
+   so I can just type '/scream' in TF after I make local changes.
+
+Other functions:
+   /python_call diffedit.abort
+       Emergency abort all in-progress uploads.
+   /python_call diffedit.lastdir <directory>
+       Set where the diffedit keeps the last known version of each file for a
+	   <world>_<remote> to compare new versions against. Default: /tmp/diffedit
+   /python_call diffedit.tabsize 8 
+       Set the tab to space expansion size. 0 means no expansion. Default: 4
+   /python_call diffedit.lines_per_second 100
+       Set lines per second to send if no -<size>. Default: 50
+   
+""".split("\n"):
+		tf.out( line )
+
+#
+# Configuration
+#
+def lastdir( dirname ):
+	global LASTDIR
+	LASTDIR=dirname
+
+def tabsize( size ):
+	global TABSIZE
+	TABSIZE = int( size )
+
+def lines_per_second( lps ):
+	global LINES_PER_SECOND
+	LINES_PER_SECOND = int( lps )
+
+# -----------------------------------------------------
+# Uploaders
+# -----------------------------------------------------
+class uploader( object ):
+
+	def __init__( self, remote, name2, raw=False ):
+		self.world = tf.world()
+		
+		self.remote = remote
+		self.name2 = name2
+
+		# look for old name
+		self.keyname = "%s_%x" % ( self.world, abs( hash( self.remote) ) )
+		self.name1 = os.path.join( LASTDIR, self.keyname )
+		if not os.path.isdir( LASTDIR ):
+			os.makedirs( LASTDIR, 0755 )
+
+		# We have to be raw if the old version doesn't exist
+		self.raw = raw or not os.path.isfile( self.name1 )
+
+	# see generate_commands for a description of the opcodes
+	def find_diffs( self ):
+
+		# get contents of new and old
+		self.lines2 = open( self.name2, "rt" ).readlines()
+		if self.raw:
+			lines1 = []
+			# raw has an implied delete everything existing first
+			diffs = [ [ 'delete', 0, 99999, 0, 0 ] ]
+		else:
+			lines1 = open( self.name1, "rt" ).readlines()
+			diffs = [ ]
+
+		# We need to do it in reversed so we get the right line numbers
+		diffs += reversed( difflib.SequenceMatcher( None, lines1, self.lines2 ).get_opcodes() )
+
+		# if everything's equal then it's just an 'empty' diff
+		if len(diffs)==1 and diffs[0][0]=='equal':
+			return []
+		else:
+			return diffs
+
+	# generate streamlined sets of opcodes from SequenceMatcher opcodes
+	def massage_opcodes( self, ops ):
+		for opcode, i1, i2, j1, j2 in ops:
+			if opcode == 'equal':
+				continue
+			if opcode in ( 'delete', 'replace' ):
+				yield [ 'd', i1+1, 0, i2-i1 ]
+			if opcode in ( 'insert', 'replace' ):
+				yield [ 'i', i1+1, j1+1, j2-j1 ]
+
+	# This is all you have to override, generally.
+	# You get a list of:
+	#   [ [ opcode, pos1, pos2, n ], ]
+	# All line numbers are 1 based.
+	#   'd': delete n lines at position pos1. ignore pos2
+	#   'i': insert n lines (of file2) from pos2 at position pos1
+	def generate_commands( self, opcodes ):
+		raise Exception( "your uploader class needs to def generate_commands()" )
+
+	# Finally send them to the world
+	def upload_commands( self, lps, prog, cmds ):
+		self.up = self.chunks( lps, cmds )
+		self.lines_sent, self.lines_sent0 = 0, 0
+		self.progress_size = prog
+		
+		self.upload_chunk()
+
+	# returns lps chunks of cmds at a time - or all if no chunking
+	def chunks( self, lps, cmds ):
+		# if lps is 0 just send the whole thing at once
+		if not lps:
+			yield True, cmds
+
+		# generate chunks of lps lines at a time
+		chunk = []
+		while True:
+			try:
+				chunk.append( cmds.next() )
+			except StopIteration:
+				yield True, chunk
+
+			if len( chunk ) >= lps:
+				yield False, chunk
+				chunk = []
+
+	# upload the next chunk of lps lines
+	def upload_chunk( self ):
+
+		done, chunk = self.up.next()
+
+		for cmd in chunk:
+			cmd = cmd.rstrip()
+			# convert tabs to spaces
+			if TABSIZE>0:
+				cmd = cmd.expandtabs( TABSIZE )
+			# /send blank does nothing, so send a space
+			if not cmd:
+				cmd = " "
+
+			# and send it
+			tf.send( cmd, self.world )
+			self.lines_sent += 1
+
+		# clean up and finish, or schedule further upload
+		if done:
+			self.done()
+			del self.up
+		else:
+			# show progress
+			if self.progress_size and ( self.lines_sent - self.lines_sent0)>=self.progress_size:
+				tf.out( "- diffedit.upload -> %s %s - %4d cmds sent" % \
+						( self.world, self.remote, self.lines_sent ) )
+				self.lines_sent0 = self.lines_sent - ( self.lines_sent % self.progress_size )
+			# schedule a callback in 1 second
+			INPROGRESS[self.keyname] = self
+			tf.eval( '/repeat -w%s -1 1 /python_call diffedit.__more %s' % (
+				self.world, self.keyname ) )
+
+	def done( self ):
+		"""copy new file to old file, print done message"""
+
+		# no longer running
+		if self.keyname in INPROGRESS:
+			del INPROGRESS[self.keyname]
+
+		# let this just IOError out if it wants, then user sees error
+		shutil.copy2( self.name2, self.name1 )
+
+		# tell user what we did
+		print "%% diffedit.upload -> %s %s - %4d cmds sent - done" % \
+			  ( self.world, self.remote, self.lines_sent )
+
+#
+# lsedit uploader
+#
+class up_lsedit( uploader ):
+	def __init__( *args ):
+		uploader.__init__( *args )
+
+	# see class uploader for opcodes format
+	def generate_commands( self, opcodes ):
+		yield "lsedit %s" % self.remote
+
+		for opcode, pos1, pos2, n in opcodes:
+			if opcode == 'd':
+				yield ".del %d %d" % ( pos1, pos1+n-1 )
+			elif opcode == 'i':
+				yield ".i %d" % ( pos1 )
+				for i in xrange( pos2, pos2+n ):
+					yield self.lines2[i-1]
+
+		yield ".end"
+			
+#
+# muf uploader
+#
+class up_muf( uploader ):
+	def __init__( *args ):
+		uploader.__init__( *args )
+
+	# see class uploader for opcodes format
+	def generate_commands( self, opcodes ):
+		yield "@edit %s" % self.remote
+
+		for opcode, pos1, pos2, n in opcodes:
+			if opcode == 'd':
+				yield "%d %d d" % ( pos1, pos1+n-1 )
+			elif opcode == 'i':
+				yield "%d i" % ( pos1 )
+				for i in xrange( pos2, pos2+n ):
+					yield self.lines2[i-1]
+				yield "."
+				
+		yield "c"
+		yield "q"
+
+			
+# -----------------------------------------------------
+# Main logic
+# -----------------------------------------------------
+
+# Keep uploading something we started
+def __more( argstr ):
+	editor = INPROGRESS.get( argstr, None )
+	if not editor:
+		return
+
+	#print "uploading more for %s" % ( editor.name2 )
+	editor.upload_chunk()
+
+# abort all uploads! oogah, oogah!
+def abort( argstr ):
+	tf.out( "* diffedit.abort - aborting all in progress uploads" )
+	global INPROGRESS
+	INPROGRESS = {}
+
+# start a new upload
+def upload( argstr ):
+
+	# 
+	# argument parsing is much larger than actual logic!
+	#
+
+	if not argstr:
+		return help()
+
+	args = argstr.split()
+
+	# check for raw flag
+	if "-r" in args:
+		raw = True
+		args.remove( "-r" )
+	else:
+		raw = False
+
+	# lines per second and progress
+	lps, prog = LINES_PER_SECOND, 0
+	args2 = []
+	for i, arg in enumerate( args ):
+
+		# progress
+		if arg.startswith( "-p" ):
+			prog = 100
+			if len(arg) > 2:
+				try:
+					prog = int(arg[2:])
+				except ValueError:
+					return help()
+
+		# no waiting
+		elif arg in ( '-S', '-0' ):
+			lps = 0
+
+		# fractional seconds
+		elif arg.startswith("-"):
+			try:
+				lps = float(arg[1:])
+				if lps<0:
+					lps = abs( lps )
+				if lps<1:
+					lps = 1/lps
+			except ValueError:
+				pass
+
+		else:
+			args2.append( arg )
+
+	# what's left?
+	args = args2
+	if len( args ) != 3 :
+		return help()
+
+	# which type
+	if args[0] == 'muf':
+		editor = up_muf
+	elif args[0] == 'lsedit':
+		editor = up_lsedit
+	else:
+		tf.err( "* diffedit.upload: known editors are 'muf' and 'lsedit'" )
+		return -1
+
+	# Check for the file
+	if not os.path.isfile( args[2] ):
+		tf.err( "* diffedit.upload: file %s not found" % args[2] )
+		return -1
+
+	#
+	# Okay, here's we we actually do some work
+	#
+
+	# Create the object
+	editor = editor( args[1], args[2], raw )
+
+	# Check to make sure we're not already running
+	if editor.keyname in INPROGRESS:
+		tf.err( "* diffedit.upload: upload for '%s:%s' already running." % (
+			editor.world, editor.remote ) )
+		tf.err( "*     use '/python_call diffedit.abort' to reset if this is wrong." )
+		return
+
+	# find the diffs
+	diffs = editor.find_diffs()
+
+	# only do the rest if we need to
+	if diffs:
+		
+		# generate the editor commands
+		cmds = editor.generate_commands( editor.massage_opcodes( diffs ) )
+
+		# upload them
+		editor.upload_commands( lps, prog, cmds )
+
+	else:
+		
+		# all done
+		editor.lines_sent = 0
+		editor.done()
+
+
+# any uploads in progress?
+INPROGRESS = {}
diff -rNu -x .svn -x config.log -x config.status -x tf-help -x Makefile -x chartables.c -x tfconfig.h -x tfdefs.h -x '*.build' -x '*.bak' -x '*.idx' -x '*.o' -x '*.pyc' -x html2tf -x default -x makehelp -x tf -x dftables -x 'libpcre*' -x html2tif tf-50b8-clean/tf-lib/tf4.py tf-50b8-py/tf-lib/tf4.py
--- tf-50b8-clean/tf-lib/tf4.py	1969-12-31 16:00:00.000000000 -0800
+++ tf-50b8-py/tf-lib/tf4.py	2008-01-25 23:31:57.000000000 -0800
@@ -0,0 +1,462 @@
+#
+# Converts your TF5 to act (sort of) like TF4.
+#
+# Witness the power of this fully operational death star! This demonstrates
+# just how powerful TinyFugue's triggers are. With enough hooks you can
+# really transform the behavior, though this is admittedly pretty obscene.
+# I just wanted to prove that it was (mostly) possible.
+#
+# What this does is create a fake world then route all input/output through
+# that world. This approach won't work if you're doing anything too fancy with
+# your own macros. Unfortunately because of the way TF handles color we can't
+# do anything with that - text color are hidden attributes, you can't just
+# pass along ascii codes.
+#
+# NOTE: This seems to blow up on Python 2.5.0 (2.5.1 is fine) because it chokes
+#       on the key=val argument calls for no reason I can find with a None
+#       object error.
+#
+# Usage:
+#    /python_load tf4
+#    /tf4 help
+#
+# Copyright 2008 Ron Dippold
+#
+# v1.00 - Jan 25 '08 - Initial version
+
+import tf, tfutil
+
+#----------------------------------------------------------
+# A glorious real world - which we're hiding
+#----------------------------------------------------------
+class World( object ):
+
+	def __init__( self, name ):
+		self.name = name
+		self.socket = not not name
+
+		global WDICT, WLIST
+		WDICT[self.name.lower()] = self
+		if self.socket:
+			WLIST.append( self )
+		self.buffer = []
+
+	# send a line to the world
+	def send( self, text ):
+		if self.socket:
+			tf.send( text, self.name )
+
+	# called when receiving a line from the world
+	def rx( self, text ):
+		global FG, MODE
+
+		# just accumulate in buffer
+		if MODE == MODE_ON and FG != self:
+			if not self.buffer:
+				tf.out( "%% Activity in world %s" % self.name )
+				ACTIVITY.append( self )
+				_activity_status()
+			self.buffer.append( text )
+			return
+
+		if MODE == MODE_SWITCH:
+			self.activate()
+
+		elif MODE == MODE_MIX:
+			self.check_last()
+			
+		tf.out( text )
+
+	def divider( self ):
+		# show divider
+		if self.name:
+			tf.out( "---- World %s ----" % self.name )
+		else:
+			tf.out( "---- No world ----" )
+
+	def check_last( self ):
+		global LAST_OUT
+		if LAST_OUT != self:
+			self.divider()
+			self.dump_buffer()
+			LAST_OUT = self
+
+	# activate this world
+	def activate( self ):
+		self.check_last()
+
+		global FG
+		if self != FG:
+			FG = self
+			tf.eval( "/set _tf4world=%s" % self.name )
+
+	def dump_buffer( self ):
+		# clean out buffer, no longer active world
+		
+		if not self.buffer:
+			return
+		for line in self.buffer:
+			tf.out( line )
+		self.buffer = []
+
+		global ACTIVITY
+		if self in ACTIVITY:
+			ACTIVITY.remove( self )
+			_activity_status()
+
+	def connect( self ):
+		global WLIST
+		self.socket = True
+		if not self in WLIST:
+			WLIST.append( self )
+		
+		self.activate() #???
+
+	def disconnect( self, reason ):
+		try:
+			WLIST.remove( self )
+		except ValueError: pass
+		self.socket = False
+		
+
+# -----------------------------------------------------------------------------
+# Global data structures
+# -----------------------------------------------------------------------------
+
+# Known worlds - in dict format for fast lookup and list format for ordering.
+# Since everything is by reference, this is cheap.
+WDICT = {}
+WLIST = []
+
+# The world we're pretending is foreground
+FG=World( '' )
+
+# World we last showed output for
+LAST_OUT=FG
+
+# queue of worlds with activity
+ACTIVITY = []
+
+# Our current state
+MODE_OFF, MODE_ON, MODE_MIX, MODE_SWITCH = ( 0, 1, 2, 3 )
+MODE = MODE_OFF
+
+# ------------------------------------------------------------------------------
+# Helper funcs
+# ------------------------------------------------------------------------------
+
+# state singleton
+STATE = None
+def getstate():
+	global STATE
+	if not STATE:
+		STATE = tfutil.State()
+	return STATE
+
+
+def _activity_status():
+	if ACTIVITY:
+		text = "(Active:%2d)" % len(ACTIVITY)
+	else:
+		text = ""
+	tf.eval( "/set _tf4activity="+text )
+	
+# ------------------------------------------------------------------------------
+# Our /python_call functions
+# ------------------------------------------------------------------------------
+
+# Called whenever we get a line from a world
+def rx( argstr ):
+	name, text = argstr.split( ' ', 1 )
+	world = WDICT.get(name.lower())
+	if not world:
+		world = World( name )
+		WDICT[name.lower()] = world
+	world.rx( text )
+
+# Dispatcher
+def tx( argstr ):
+	FG.send( argstr )
+
+# Do a /dc - just don't let it /dc our _tf4 world!
+def dc( argstr ):
+	argstr = argstr.lower().strip()
+
+	# do them all
+	if argstr == "-all":
+		for world in WLIST:
+			tf.out( "/@dc %s" % world.name )
+			tf.eval( "/@dc %s" % world.name )
+			dischook( world.name + " tf4" )
+		return
+
+	# otherwise do foreground world
+	if argstr:
+		world = WDICT.get(argstr)
+	else:
+		world = FG
+
+	if world:
+		tf.eval( "/@dc %s" % world.name )
+		dischook( world.name + " tf4" )
+
+# change world
+def fg( argstr ):
+	cmd, opts, argstr = tfutil.cmd_parse( argstr, "ns<>c:|q" )
+	if not opts and not argstr:
+		return
+
+	# no opts, just a world name
+	if not opts:
+		fg = WDICT.get( argstr.lower() )
+		if fg:
+			fg.activate()
+		else:
+			tf.out( "%% fg: not connected to '%s'" % argstr )
+		return
+
+	# handle opts
+	fg = None
+
+	# easy, just next activity world
+	if 'a' in opts and ACTIVITY:
+		fg = ACTIVITY[0]
+		fg.activate()
+		return
+
+	# relative movement
+	c = 0
+	if 'c' in opts:
+		try:
+			c = int( opts['c'] )
+		except:
+			c = 1
+	elif '<' in opts or 'a' in opts:
+		c = -1
+	elif '>' in opts:
+		c = 1
+		
+	if c != 0:
+		if WLIST:
+			if FG in WLIST:
+				fg = WLIST[(WLIST.index( FG ) + c) % len( WLIST )]
+			else:
+				fg = WLIST[0]
+		else:
+			fg = WDICT['']
+	elif 'n' in opts:
+		fg = WDICT['']
+		
+	if fg:
+		fg.activate()
+
+
+# pending hook, just show locally
+def otherhook( argstr ):
+	tf.out( argstr )
+
+# world connected hook
+def conhook( argstr ):
+	name = argstr.split()[0]
+	world = WDICT.get( name.lower() )
+	if not world:
+		# create a new world - adds itself to WDICT, WLIST
+		world = World( name )
+
+	tf.eval( "/@fg _tf4" ) # just in case
+	world.connect()
+		
+
+# world disconnected hook
+def dischook( argstr ):
+	split = argstr.split(None,1)
+	name = split[0].lower()
+	reason = len(split)>1 and split[1] or ''
+	if name in WDICT:
+		WDICT[name].disconnect( reason )
+	if reason != 'tf4':
+		tf.out( "%% Connection to %s closed." % name )
+	world( "-a" )
+	
+# connection request - just make sure we start in background
+def connect( argstr ):
+	cmd, opts, argstr = tfutil.cmd_parse( argstr, "lqxfb" )
+	opts['b'] = ''
+	if 'f' in opts:
+		del opts['f']
+
+	tf.out( tfutil.cmd_unparse( "/@connect", opts, argstr ) )
+	tf.eval( tfutil.cmd_unparse( "/@connect", opts, argstr ) )
+
+# world is a hybrid of connect and fg
+def world( argstr ):
+	cmd, opts, argstr = tfutil.cmd_parse( argstr, "lqxfb" )
+	name = argstr.lower()
+	if name and name in WDICT:
+		name.activate()
+	elif opts:
+		fg( argstr )
+	else:
+		connect( argstr )
+
+# Just make sure we have a specific world
+def recall( argstr ):
+	cmd, opts, argstr = tfutil.cmd_parse( argstr, 'w:ligvt:a:m:A:B:C:' )
+
+	if not 'w' in opts or not opts['w']:
+		opts['w'] = FG.name
+
+	tf.eval( tfutil.cmd_unparse( "/@recall", opts, argstr ) )
+
+def quit( argstr ):
+	cmd, opts, argstr = tfutil.cmd_parse( argstr, 'y' )
+
+	if not ACTIVITY:
+		opts['y'] = ''
+
+	tf.eval( tfutil.cmd_unparse( "/@quit", opts, argstr ) )
+
+# ----------------------------------------------------------------------------
+# Initial setup
+# ----------------------------------------------------------------------------
+
+def myhelp():
+	for line in """
+/tf4 (ON|mix|switch|off)
+
+on:  Turns on TinyFugue 4 emulation mode where all world activity is shown
+  in a single world separated by ---- World Foo ---- markers. This is
+  emulated with hooks and scripts, so if your own scripts get too fancy
+  it will break, but it seems to work pretty well with mine.
+
+mix: Like 'on', but output in background worlds will be immediately shown
+  (with a divider) so you can just watch the output of all worlds scroll
+  by. However the world does not become the foreground world! Without
+  /visual on you won't really have any indication of what the foreground
+  world is so you won't know where you're sending text.
+
+switch: Like 'mix' but immediately switches you to whatever world has output.
+  Obviously this makes it tough to guarantee that any text you're sending
+  will go to the right world unless you do a /send -wfoo text, since it
+  could switch just before you hit enter.
+ 
+off: Turn off TinyFugue 4 emulation mode, revert all your keybindings and
+  macro definitions back to the way they were.
+""".split("\n"):
+		tf.out( line )
+
+def tf4( argstr ):
+
+	# Create a new base state if we don't have one
+	state = getstate()
+
+
+	# Just parse the command
+	global MODE
+	argstr = argstr.lower()
+	if not argstr or 'on' in argstr:
+		newmode = MODE_ON
+		
+	elif 'help' in argstr:
+		return myhelp()
+
+	elif 'off' in argstr:
+		newmode = MODE_OFF
+
+	elif 'mix' in argstr:
+		newmode = MODE_MIX
+
+	elif 'switch' in argstr:
+		newmode = MODE_SWITCH
+
+	else:
+		return myhelp()
+
+	# Now do what we need to
+	if newmode == MODE_OFF:
+		if newmode != MODE:
+			state.revert()
+			tf.eval( "/dc _tf4" )
+			tf.eval( "/unworld _tf4" )
+			sockets = tfutil.listsockets()
+			for name in sockets:
+				tf.eval( "/@fg -q " + name )
+			MODE = MODE_OFF
+		tf.out( "% tf4 mode is now off. '/tf4' to re-enable it" )
+		return
+		
+	if MODE == MODE_OFF:
+		# Create a virtual world to hold our text
+		sockets = tfutil.listsockets()
+		if not "_tf4" in sockets:
+			tf.eval( "/addworld _tf4" )
+			tf.eval( "/connect _tf4" )
+
+			# We want background processing and triggers
+			state.setvar( 'background', 'on' )
+			state.setvar( 'bg_output', 'off' )
+			state.setvar( 'borg', 'on' )
+			state.setvar( 'gag', 'on' )
+			state.setvar( 'hook', 'on' )
+
+			# change the status bar
+			state.setvar( '_tf4world', tf.eval( '/test fg_world()' ) )
+			state.setvar( '_tf4activity', '' )
+			state.status( "rm @world" )
+			state.status( "add -A@more _tf4world" )
+			state.status( "rm @active" )
+			state.status( "add -A@read _tf4activity:11" )
+
+			# Grab text from any world except our dummy world - we use a regexp instead of a glob
+			# because the glob does not preserve the spacing
+			state.define( flags="-p99999 -w_tf4 -ag -mglob -t*" )
+			state.define( body="/python_call tf4.rx $[world_info()] %P1",
+						  flags="-Fpmaxpri -q -mregexp -t(.*)" )
+			
+			# add a trigger for anything being sent to the generic world, so we can reroute
+			# it to the right world
+			state.define( body="/python_call tf4.tx %{*}", flags="-pmaxpri -w_tf4 -hSEND" )
+
+			# Add our hooks
+			state.define( body="/python_call tf4.dischook %{*}",
+						  flags="-Fpmaxpri -ag -msimple -hDISCONNECT" )
+			state.define( body="/python_call tf4.conhook %{*}",
+						  flags="-Fpmaxpri -ag -msimple -hCONNECT" )
+			state.define( body="/python_call tf4.otherhook %{*}",
+						  flags='-p999 -ag -msimple -hCONFAIL|ICONFAIL|PENDING|DISCONNECT' )
+			state.define( body="", flags="-pmaxpri -ag -msimple -hBGTRIG" )
+			state.define( body="", flags="-pmaxpri -ag -msimple -hACTIVITY" )
+
+			# Bind Esc-b, Esc-f to call us instead
+			state.bind( key="^[b", body="/python_call tf4.fg -<", flags="i" )
+			state.bind( key="^[f", body="/python_call tf4.fg ->", flags="i" )
+			state.bind( key="^[w", body="/python_call tf4.fg -a", flags="i" )
+
+			# def these commands to go to us
+			state.define( "bg", "/python_call tf4.fg -n" )
+			state.define( "connect", "/python_call tf4.connect %{*}" )
+			state.define( "dc", "/python_call tf4.dc" )
+			state.define( "fg", "/python_call tf4.fg %{*}" )
+			state.define( "quit", "/python_call tf4.quit ${*}" )
+			state.define( "recall", "/python_call tf4.recall %{*}" )
+			state.define( "to_active_or_prev_world", "/python_call tf4.fg -a" )
+			state.define( "to_active_world", "/python_call tf4.fg -a" ) # close enough
+			state.define( "world", "/python_call tf4.world %{*}" )
+
+	MODE = newmode
+	if newmode == MODE_SWITCH:
+		tf.out( "% TinyFugue 4 emulation mode with autoswitch to output world." )
+	elif newmode == MODE_MIX:
+		tf.out( "% TinyFugue 4 emulation mode with background output shown." )
+	else:
+		tf.out( "% TinyFugue 4 emulation mode is on." )
+	tf.out( "% '/tf4 off' to disable, '/tf4 help' for help." )
+
+# -------------------------------------
+# When first loaded
+# -------------------------------------
+
+# register ourself
+state = getstate()
+state.define( name="tf4", body="/python_call tf4.tf4 %*", flags="-q" )
+tf.out( "% '/tf4 help' for how to invoke TinyFugue 4 emulation mode." )
diff -rNu -x .svn -x config.log -x config.status -x tf-help -x Makefile -x chartables.c -x tfconfig.h -x tfdefs.h -x '*.build' -x '*.bak' -x '*.idx' -x '*.o' -x '*.pyc' -x html2tf -x default -x makehelp -x tf -x dftables -x 'libpcre*' -x html2tif tf-50b8-clean/tf-lib/tf.py tf-50b8-py/tf-lib/tf.py
--- tf-50b8-clean/tf-lib/tf.py	1969-12-31 16:00:00.000000000 -0800
+++ tf-50b8-py/tf-lib/tf.py	2008-01-21 02:39:33.000000000 -0800
@@ -0,0 +1,25 @@
+# This is a dummy tf module to allow module testing outside of TinyFugue.
+#
+# If you are actually /python_load-ing or import-ing the module in TF
+# then this file is NOT LOADED, it is only a stub for debugging.
+
+def err( argstr ):
+	print "tf.err  |", argstr
+
+def eval( argstr ):
+	print "tf.eval |", argstr
+	return ""
+
+def getvar( var, default="" ):
+	print "tf.getvar |", var, default
+	return default
+
+def out( argstr ):
+	print "tf.out  |", argstr
+
+def send( text, world="<current>" ):
+	print "tf.send %s |" % test
+	
+def world():
+	return "Dummy"
+
diff -rNu -x .svn -x config.log -x config.status -x tf-help -x Makefile -x chartables.c -x tfconfig.h -x tfdefs.h -x '*.build' -x '*.bak' -x '*.idx' -x '*.o' -x '*.pyc' -x html2tf -x default -x makehelp -x tf -x dftables -x 'libpcre*' -x html2tif tf-50b8-clean/tf-lib/tfutil.py tf-50b8-py/tf-lib/tfutil.py
--- tf-50b8-clean/tf-lib/tfutil.py	1969-12-31 16:00:00.000000000 -0800
+++ tf-50b8-py/tf-lib/tfutil.py	2008-01-26 00:09:16.000000000 -0800
@@ -0,0 +1,455 @@
+#
+# Various helper funcs/classes for TF Python that are more suited to writing
+# in python than in C (so aren't in the base tf module)
+#
+# Copyright 2008 Ron Dippold
+
+import tf
+
+# ---------------------------------------------------------------------------
+# Misc helper functions
+# ---------------------------------------------------------------------------
+
+def screensize():
+	"""
+	Returns ( rows, columns )
+	"""
+	return ( tf.eval( "/test columns()" ), tf.eval( "/test lines()" ) )
+
+def visual():
+	"""
+	if visual is off, returns 0, otherwise returns number of input lines
+	"""
+	
+	if tf.getvar( "visual", "off" ) == "on":
+		return tf.getvar( "isize", 3 )
+	else:
+		return 0
+
+def eval_escape( text ):
+	"""
+	returns text with %, \, and $ doubled
+	"""
+	return text.replace('%','%%').replace( '\\','\\\\').replace("$","$$")
+
+def cmd_parse( argstr, opts ):
+	"""
+	Parse tf-style command lines into args and opts- like getopt, but for
+	TinyFugue style where -w<x> may have <x> or not, and <x> must be next
+	to -w, not separated by a space.
+
+	argstr is the command line, opts is a list of 'w:ligvt:a:m:A:B:C:#' where
+	: means an argument - we don't care if it's optional or not. # is a
+	number option like '-42' such as in the /quote command.
+
+	The arguments are considered over when we hit any non-flag or '--' or '-'
+
+	Returns ( cmd, optdict, rest ) where cmd is '/recall' (or blank if there
+	wasn't a command present), optdict is { 'w':'foo', 'l':'', ... }
+	and rest is a string with the rest of the line (unlike getopt, we need to
+	preserve the spacing in the rest of the line).
+
+	See also cmd_unparse - you can put the command back together after changing
+	the args.
+	"""
+
+	# parts opts into an flagsdict - guess we could cache these
+	flagsdict = {}
+	i, olen = 0, len(opts)
+	while i<olen:
+		if i+1<olen and opts[i+1] == ":":
+			flagsdict[opts[i]] = opts[i+1]
+			i += 2
+		else:
+			flagsdict[opts[i]] = ''
+			i += 1
+
+	# grab the command off the front (if any)
+	if argstr.startswith( "/" ):
+		cmd, argstr = argstr.split(None,1)
+	else:
+		cmd = ''
+
+	# run through opts on command line
+	outdict = {}
+	while True:
+		argstr = argstr.lstrip()
+		if not argstr.startswith( '-' ):
+			break
+
+		if ' ' in argstr:
+			arg, argstr = argstr[1:].split( None, 1 )
+		else:
+			arg, argstr = argstr, ''
+		if arg == '' or arg == '-': # -- ends arguments
+			break
+
+		# check each flag in this word
+		i, alen = 0, len( arg )
+		while i<alen:
+			flag = arg[i]
+			if flag.isdigit() and "#" in flagsdict:
+				outdict['#'] = arg[i:]
+				break
+			form = flagsdict.get( flag, '' )
+			if form == '':
+				outdict[flag] = form
+				i += 1
+			elif form == ':':
+				outdict[flag] = arg[i+1:]
+				break
+
+	# all done!
+	return ( cmd, outdict, argstr )
+
+def cmd_unparse( cmd, optsdict, argstr ):
+	"""
+	Takes optional cmd ('/recall'), opts dict as created by cmd_parse,
+	and the rest of the command and recreates a tf.eval() line:
+
+	tfutil.cmd_unparse( '/recall', { 'w':'foo', '#':'12' }, '300' )
+	/recall -wfoo -12 300
+	
+	tfutil.cmd_unparse( '', { 'i':'', 'q':'' ], '300' )
+	-q -i 300
+	"""
+
+	fullcmd = []
+	if cmd:
+		fullcmd.append( cmd )
+
+	for flag, arg in optsdict.items():
+		fullcmd.append( '-%s%s' % ( flag, arg ) )
+
+	# make sure we terminate args before adding command
+	if argstr.startswith('-'):
+		fullcmd.append( '-' )
+	fullcmd.append( argstr )
+	
+	return ' '.join( fullcmd )
+
+# ---------------------------------------------------------------------------
+# /output helpers - there doesn't seem to be a way I can find to reliably
+# get the %| pipes to work, even in the tfio examples (I may be missing
+# something) so these do the job of screen scraping arbitrary commands.
+# Luckily this is pretty fast.
+# ---------------------------------------------------------------------------
+
+_caught = []
+def _scrape( line ):
+	_caught.append( line )
+
+def screenscrape( command ):
+	"""
+	Execute an arbitrary command and returns the output as a list.
+	"""
+	_caught[:] = []
+	x = tf.eval( "/def -iq _tfscrape = /python_call tfutil._scrape \%*" )
+	tf.eval( "/quote -S /_tfscrape `%s" % command )
+	tf.eval( "/undefn %s" % x )
+	return _caught[:]
+
+def listsockets():
+	"""
+	Returns a list of socket names (as strings).
+	We could get fancier later and create Socket classes with more
+	    info, but don't need it now.
+	"""
+
+	return screenscrape( "/listsockets -s " )
+
+def listworlds( asdict = False ):
+	"""
+	Returns a list of tfutil.World classes.
+	If asdict, then as a dictionary	with the World.name as key.
+	"""
+	l = [ World( line ) for line in screenscrape( "/listworlds -c" ) ]
+	if asdict:
+		return dict( [ ( w.name, w ) for w in l ] )
+	else:
+		return l
+
+# ---------------------------------------------------------
+# World class - info about each /addworld
+# ---------------------------------------------------------
+
+class World( object ):
+	"""
+	A World() class contains info about a known worlds (/listworlds).
+	This is not quite the same as a socket, because it only includes
+	non-virtual worlds, and unconnected worlds as well.
+	"""
+
+	# These are the attributes on the World you can get/set - in same order
+	# as listworlds command format
+	KEYS=( 'name', 'type', 'host', 'port', 'character', 'password', 'file', 'flags', 'srchost' )
+
+	# See category() below
+	INVALID, NORMAL, DEFAULT, VIRTUAL = ( 0, 1, 2, 3 )
+
+	# If we don't have a port, use this one
+	DEFPORT = 23
+
+	def __init__( self, arg ):
+		"""
+		You can pass one of three arguments to init the class:
+		   empty string:        new empty (INVALID) world
+		   string:              new world from /listworlds -c format
+		   World:               copy of another world
+		"""
+
+		# copy of another World
+		if isinstance( arg, World ):
+			for key in World.KEYS:
+				setattr( self, key, getattr( arg, key, '' ) )
+			return
+			
+		# empty string
+		if not arg:
+			for key in World.KEYS:
+				setattr( self, key, '' )
+			self.port = World.DEFPORT
+			return
+
+		# /listworlds -c format
+		if arg.startswith( "/test addworld" ):
+			argstr = arg[ arg.find("(")+1 : -1 ]
+			split = argstr.split(", ") + [''] * 10
+			for i, key in enumerate( World.KEYS ):
+				setattr( self, key, split[i].strip('"') )
+			if not self.port:
+				self.port = World.DEFPORT
+			return
+
+		raise Exception( "World() - invalid init string" )
+
+	# some special funcs - comparison is done by name, as is nonzero check
+	def __cmp__( self, other ):
+		if isinstance( other, World ):
+			return cmp( self.name.lower(), other.name.lower() )
+		else:
+			return cmp( self.name.lower(), other.lower() )
+	def __nonzero__( self ):
+		return self.name!=''
+	def __str__( self ):
+		return self.addworld_command()
+	def __repr__( self ):
+		return "tfutil.World( '%s' )" % self.addworld_command( True )
+
+	# okay, really different
+	def changed_from( self, other ):
+		for key in World.KEYS:
+			if getattr( self, key ) != getattr( other, key ):
+				return True
+		return False
+
+	def category( self ):
+		"""
+		Worlds can be one of the following:
+		   World.INVALID      - name is not set
+		   World.NORMAL       - normal World with host name
+		   World.DEFAULT      - World name is 'default', see /help addworld
+		   WOrld.VIRTUAL      - World without a host
+		"""
+		if not self.name:
+			return World.INVALID
+		if not self.host:
+			return World.VIRTUAL
+		if self.name.lower() == 'default':
+			return World.DEFAULT
+		return World.NORMAL
+
+	def send( self, text ):
+		"""
+		Send text to this world, even if it's not foreground. Note that a world
+		existing does not guarantee that it current is connected.
+		"""
+		if self.name:
+			tf.send( text, self.name )
+
+	def addworld_command( self, func=False, full=True ):
+		"""
+		Return an addworld command for this world.
+		If World.category() is World.INVALID, this returns an empty string!
+
+		If Func is True it will return a
+			/test addworld( ... )
+		line, otherwise it will return an
+			/addworld ...
+	    
+		If full is False then it will just return the addworld( ... ) part
+		without the /test or the ... without the /addworld.
+		
+		"""
+
+		which = self.category()
+		if which == World.INVALID:
+			return ''
+
+		# function format
+		if func:
+			cmd = [ '"%s"' % getattr(self,key,'') for key in World.KEYS ]
+			while cmd and cmd[-1]=='""':
+				del cmd[-1]
+			if full:
+				return "/test addworld( %s )" % ", ".join( cmd )
+			else:
+				return "addworld( %s )" % ", ".join( cmd )
+
+		# addworld format
+		if full:
+			cmd = [ '/addworld' ]
+		else:
+			cmd = []
+		
+		if which==World.NORMAL and self.flags:
+			cmd.append( '-' + self.flags )
+		if self.type:
+			cmd.append( '-T' + self.type )
+		if which!=World.DEFAULT and self.srchost:
+			cmd.append( '-s' + self.srchost )
+		cmd.append( self.name )
+		if which!=World.VIRTUAL and self.character and self.password:
+			cmd.append( self.character )
+			cmd.append( self.password )
+		if which==World.NORMAL:
+			cmd.append( self.host )
+			cmd.append( self.port )
+		if which!=World.VIRTUAL and self.file:
+			cmd.append( self.file )
+
+		return ' '.join( cmd )
+		
+
+# ---------------------------------------------------------
+# State class
+# ---------------------------------------------------------
+
+class State( object ):
+	"""
+	This class lets you do various binds and sets and then (the key) revert
+	TF's configuration to undo the changes you made.
+	"""
+
+	def __init__( self ):
+		self.lose_changes()
+
+	def setvar( self, var, value ):
+		"""
+		old = State.setvar( <var>, <value> )
+		
+		set TF variable <var> to <value>, returns the old value.
+		Saves the old value for when reverting State.
+		"""
+		old = tf.getvar( var )
+		tf.eval( "/set %s=%s" % ( var, value ) )
+		if not var in self.oldvar:
+			self.oldvar[ var ] = old
+		return old
+
+	def define( self, name="", body="", flags="" ):
+		"""
+		n = State.define( name='<name>', body='<body>', flags='<flags>' )
+
+		Adds a new /def, returns the number of the new /def. All args optional.
+		Saves the old value of named /def if it exists, for reverting State.
+
+		Not named def() because that's a Python keyword.
+		"""
+
+ 		if name and not name in self.olddef:
+			x = screenscrape( "/list -i -msimple %s" % name )
+			if x:
+				self.olddef[name] = x[0]
+
+		if flags and not flags.startswith("-"):
+			flags = "-"+flags
+
+		n = tf.eval( "/def %s %s = %s" % ( flags, name, eval_escape(body) ) )
+		self.newdef.append( n )
+		
+		return n
+
+	def bind( self, key, body="", flags="" ):
+		"""
+		n = State.bind( key='<key>', body='<body>', flags='<flags>' )
+
+		Create a new key binding for <key>. Returns the number of the new /def.
+		Body and flags are optional. Saves old binding for reverting State.
+		"""
+
+		if not key:
+			raise Exception( "tfutil.bind: empty key name" )
+		
+		if not key in self.oldkey:
+			x = screenscrape( "/list -i -msimple -b'%s'" % key )
+			if x:
+				self.oldkey[key] = x[0]
+
+		if flags and not flags.startswith("-"):
+			flags = "-"+flags
+		key = "-b'%s'" % key.strip("'")
+
+		n = tf.eval( "/def %s %s = %s" % ( flags, key, eval_escape(body) ) )
+		self.newkey.append( n )
+		
+		return n
+
+	def status( self, argstr ):
+		# save current fields
+		if not self.status_saved:
+			tf.eval( "/status_save _tf4status" )
+			self.status_saved = True
+
+		if not argstr.startswith("/"):
+			argstr = "/status_"+argstr
+		tf.eval( argstr )
+		
+
+	def lose_changes( self ):
+		"""
+		Lose track of all the changes you've made since creating this object.
+		"""
+		self.oldvar = {}    # { <name> : <oldvalue> }
+		self.olddef = {}    # { <name> : <oldvalue> }
+		self.newdef = []    # [ <n> ]
+		self.oldkey = {}    # { <key>  : <oldvalue> }
+		self.newkey = []    # [ <n> ]
+		self.status_saved = False
+
+	def revert( self ):
+		"""
+		Revert to previous state - this will undo all the setvar(), define(), and
+		bind()s you've done.
+		"""
+
+		# variables are easy
+		for var, value in self.oldvar.items():
+			tf.eval( "/set %s=%s" % ( var, value ) )
+
+		# get rid of our new definitions
+		if self.newdef:
+			tf.eval( "/undefn %s" % " ".join( [ str(n) for n in self.newdef ] ) )
+		# restore the old ones
+		for name, olddef in self.olddef.items():
+			# '% 829: /def -p2 blah=  blah'
+			tf.eval( eval_escape( olddef.split( ":", 1 )[1].lstrip() ) )
+
+		# get rid of our new bindings
+		if self.newkey:
+			tf.eval( "/undefn %s" % " ".join( [ str(n) for n in self.newkey ] ) )
+		# restore the old ones
+		for name, oldkey in self.oldkey.items():
+			tf.eval( eval_escape( olddef.split( ":", 1 )[1].lstrip() ) )
+
+		# revert status line
+		if self.status_saved:
+			tf.eval( "/status_restore _tf4status" )
+		
+		# now forget all our changes
+		self.lose_changes()
+
+# We need this for the screenscrape hook
+tf.eval( "/python_load tfutil" )
+
+
diff -rNu -x .svn -x config.log -x config.status -x tf-help -x Makefile -x chartables.c -x tfconfig.h -x tfdefs.h -x '*.build' -x '*.bak' -x '*.idx' -x '*.o' -x '*.pyc' -x html2tf -x default -x makehelp -x tf -x dftables -x 'libpcre*' -x html2tif tf-50b8-clean/tf-lib/urlwatch.py tf-50b8-py/tf-lib/urlwatch.py
--- tf-50b8-clean/tf-lib/urlwatch.py	1969-12-31 16:00:00.000000000 -0800
+++ tf-50b8-py/tf-lib/urlwatch.py	2008-01-11 15:16:33.000000000 -0800
@@ -0,0 +1,164 @@
+#---------------------------------------------------------------------------
+# Python URL Watcher v2.00 - By Vash@AnimeMUCK ( sizer@san.rr.com )
+#
+#    Distribute at will! Though I'd like to hear of improvements.
+#
+# This is formatted for 4-space tabs.
+#
+# This will watch your worlds and write an html file with the urls it sees
+# go by. Then you can point your browser at the file and launch the urls
+# from there. It puts the newest urls at the top.
+#
+# THIS REQUIRES THE TF5 PYTHON PATCH from http://sizer99.com/tfpython/
+#
+# ---------------------------------------------------------------------------
+# -- INSTALL
+# - Set the CONFIG variables just below.
+# - in your .tfrc file put a
+#       /python_load /path/to/urlwatch.py
+# - and optionally do a
+#       /python_call urlwatch.config file=/tmp/tfurls.html
+#   if you want to override where the html file is written
+# ---------------------------------------------------------------------------
+# -- VERSIONS
+# - V 2.00 - Jan 09 '08 - Convert to python from tfscript
+# ---------------------------------------------------------------------------
+
+# -- CONFIGURATION
+
+# You can use '/python_call urlwatch.config <key>=<value>' to change any of
+# these is well - but it won't do any error checking.
+
+CONFIG = {
+
+	# This is the file which gets written and you need to view in your browser.
+	#  In Firefox, File -> Open File..., in IE,  File -> Open -> Browse
+	'file':		'/tmp/tfurls.html',
+
+	# change target to '' if you don't want urls opening in a new window/tab
+	'target':	'_blank',
+
+	# Title for the browser title bar/tab title
+	'title':	'TinyFugue URLs',
+
+	# Define your own style sheet info here
+	'css':		"""
+td { padding: 0.5em 1em; }
+.odd { background: #f0fff0; }
+.even { background: #f0f0ff; }
+""",
+
+	# How many urls you want to save for the page, default 20
+	'max':		20,
+
+	# How often you want the page to auto-reload, in seconds (or 0 for not)
+	'reload':	30,
+
+	# anything else you want before the table of urls
+	'header':	'<a href="javascript:window.location.reload()" class="reload">reload page</a>',
+}
+
+# ---------------------------------------------------------------------------
+# Below here is the actual code
+# ---------------------------------------------------------------------------
+
+# regexp for matching
+import re, cgi, tf
+PATTERN = r'(http|ftp|https)://\S+[0-9A-Za-z/]'
+CPATTERN = re.compile( PATTERN )
+
+# our list of urls - each URL is ( world, text )
+URLS = []
+
+def trigger( arg ):
+	"Called from our tinyfugue trigger to do the real work"
+
+	# parse the rest as long as we keep finding urls
+	line = arg
+	output=[]
+	while True:
+		found = CPATTERN.search( line )
+		if not found: break
+		start, end = found.span()
+		url = cgi.escape( line[start:end] )
+		output.append( cgi.escape( line[:start] ) )
+		output.append( '<a href="%s" %s>%s</a>' % ( url, CONFIG['target'], url ) )
+		line = line[end:]
+	# add the remaining to the end
+	if line:
+		output.append( cgi.escape( line ) )
+
+	# add the output to the URLS, then truncate the list
+	global URLS
+	URLS.append( ( tf.world(), "".join( output ) ) )
+	URLS = URLS[ :CONFIG['max'] ]
+
+	# finally, write the file
+	write_file()
+
+def write_file( ):
+	"Write the file using the known URL entries"
+
+	f = open( CONFIG['file'], 'wt' )
+	f.write( '<head><title>%s</title>\n' % CONFIG.get('title') )
+	if CONFIG.get( 'reload' ):
+		f.write( '<meta http-equiv="refresh" content="%s" />' % CONFIG['reload'] )
+	if CONFIG.get( 'css' ):
+		f.write( '<style type="text/css">%s</style>\n' % CONFIG['css'] )
+	f.write( '</head><body>\n' )
+	f.write( CONFIG.get( 'header', '' ) )
+
+	# write each URL
+	if URLS:
+		f.write( '<table>\n' )
+		odd = True
+		for world, url in reversed( URLS ):
+			classname = odd and "odd" or "even"
+			odd = not odd
+			f.write( '  <tr class="%s"><td>%s</td><td>%s</td></tr>\n' % ( classname, world, url ) )
+		f.write( '</table>\n' )
+	else:
+		f.write( "<br/>No URLs caught yet.<br/>\n" )
+	f.write( '</body>\n' )
+		
+	f.close()
+		
+
+def config_massage():
+    	"Do a little CONFIG fixup."
+	if CONFIG.get('target'):
+		CONFIG['target'] = 'target="%s"' % CONFIG['target']
+	else:
+		CONFIG['target'] = ''
+
+	try:
+                CONFIG['max'] = int( CONFIG['max'] )
+	except:
+		CONFIG['max'] = 20
+
+# You could also just do this with
+#     /python urlwatch.CONFIG['file']='value'
+# but that seems a little too fingers-in-pies
+def config( keyval ):
+	if '=' in keyval:
+		key, val = keyval.split( '=', 1)
+		CONFIG[ key ] = val
+		config_massage()
+	else:
+		tf.echo( "use: /python_call urlwatch.config <key>=<value>" )
+	
+# ---------------------------------------------------------------------------
+# Initialization	
+# ---------------------------------------------------------------------------
+
+# Do some CONFIG massaging
+config_massage()
+	
+# create the trigger
+tf.eval( "/def -mregexp -p9 -F -q -t%s urlwatch = /python_call urlwatch.trigger \\%%*" %
+		 PATTERN.replace('//','///').replace('\\','\\\\')
+		 )
+
+# write an empty file so there's at least something there
+write_file()
+
diff -rNu -x .svn -x config.log -x config.status -x tf-help -x Makefile -x chartables.c -x tfconfig.h -x tfdefs.h -x '*.build' -x '*.bak' -x '*.idx' -x '*.o' -x '*.pyc' -x html2tf -x default -x makehelp -x tf -x dftables -x 'libpcre*' -x html2tif tf-50b8-clean/.tfrc tf-50b8-py/.tfrc
--- tf-50b8-clean/.tfrc	1969-12-31 16:00:00.000000000 -0800
+++ tf-50b8-py/.tfrc	2008-01-25 22:16:14.000000000 -0800
@@ -0,0 +1,28 @@
+/python sys.stdout.output=tf.out
+/addworld _dummy localhost 9999
+/addworld L1 localhost 2035
+/addworld L2 localhost 2035
+/addworld -x -Ttiny.muck FSa Sarusa wubba muck.furry.com 8899
+/def con=/connect %{*}
+
+/def -PCcyan -t'^(== )'
+/def -PCcyan -t'^\[[0-9]*:[0-9]*[ap]m\]'
+/def -PBCmagenta -t'^\#\# '
+/def -p9 -P0BCgreen -t'Vash' -F _hivash
+/def -p9 -P0hBCgreen -t'Sarusa' -F _hisarusa
+
+; Scream Channels
+/def -p2 -aCmagenta -mglob -t'\[public\:*\]*' _hipublic
+/def -p2 -ahCmagenta -mglob -t'\[pub\:*\]*' _hipub
+/def -p2 -aBhCred -mglob -t'\[peanut\:*\]*' _hipeanut
+/def -p2 -ahBCcyan -mglob -t'\[unixgeeks\:*\]*' _hiunix
+/def -p2 -ahBCgreen -mglob -t'\[programmers\:*\]*' _hiprog
+/def -p2 -ahBCgreen -mglob -t'\[clu\:*\]*' _hiclu
+/def -p2 -ahBCgreen -mglob -t'\[fb6\:*\]*' _hifb6
+/def -p2 -ahBCred -mglob -t'\[gamers\:*\]*' _higamers
+/def -p2 -aCyellow -mglob -t'\[gamer\:*\]*' _higamer
+/def -p2 -ahBCyellow -mglob -t'\[bafur\:*\]*' _hiba
+/def -p2 -ahBCgreen -mglob -t'\[windowshelp\:*\]*' _hiwin
+/def -p2 -ahBCcyan -mglob -t'\[GPChat\:*\]*' _higp
+
+/python_load tf4