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 @@
+
TinyFugue: /python_call
+
+/python_call
+
+
+ Usage:
+
+
+ /python_call module.function argument
+
+
+ This calls a function in a python module you have loaded with
+ /python_load.
+ 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.
+
+
+ Example:
+ /python_load mymodule
+ /def -mglob -p9Fq -thowdy howdy =
+ /python_call mymodule.howdy %{*}
+
+
+ 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.
+
+
+ For a more complex example, see the included tf-lib/urlwatch.py which catches
+ all urls going by and writes a url launching page.
+
+
+ See tf python for calling back into TinyFugue.
+
+
+
+
+ Back to index
+ Back to tf home page
+
+ Copyright © 1995, 1996, 1997, 1998, 1999, 2002, 2003, 2004, 2005, 2006-2007 Ken Keys
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 @@
+TinyFugue: /python
+
+
+python()
+
+
+ Function Usage:
+
+
+ python( command )
+
+
+ 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.
+
+
+ Example:
+ /test echo( python( strcat("'The current world is ", ${world_name}, ".'") ) )
+ The current world is (unnamed1).
+
+ Note that quoting is slightly tricky here - you have to quote once for TF's sake, then
+ once for python.
+
+
+ Command usage:
+
+
+ /PYTHON [command]
+
+
+ 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
+
+
+ /python import glob
+ /python tf.out( "Logfiles: " + ", ".join(glob.glob("*.log")) )
+ Logfiles: naughty.log, nice.log
+
+
+ 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().
+
+
+ See /python_load and
+ /python_call instead.
+
+
+ See /help tf python for calling back into TinyFugue
+ and for overall issues of performance and persistence.
+
+
+
+
+ Back to index
+ Back to tf home page
+
+ Copyright © 1995, 1996, 1997, 1998, 1999, 2002, 2003, 2004, 2005, 2006-2007 Ken Keys
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 @@
+TinyFugue: /python_kill
+
+/python_kill
+
+
+ Usage:
+
+
+ /python_kill module
+
+
+ This dumps the python interpreter and any loaded python modules. The next
+ time any of the other python commands
+ are used it will be reloaded and reinitialized.
+
+
+ This is intended as dead-man switch to prevent a required restart TinyFugue
+ if anything goes wrong with this beta-level python support.
+
+
+
+
+ Back to index
+ Back to tf home page
+
+ Copyright © 1995, 1996, 1997, 1998, 1999, 2002, 2003, 2004, 2005, 2006-2007 Ken Keys
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 @@
+TinyFugue: /python_load
+
+/python_load
+
+
+ Usage:
+
+
+ /python_load module
+
+
+ This (re)imports a python module for you from your current PYTHONPATH, TFPATH,
+ and TFLIBDIR. Use this instead of just
+ '/python 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
+ /python_call.
+
+
+ Example:
+ /python_load mymodule
+ /python_call mymodule.dothis 1 2 3
+
+
+
+
+ Back to index
+ Back to tf home page
+
+ Copyright © 1995, 1996, 1997, 1998, 1999, 2002, 2003, 2004, 2005, 2006-2007 Ken Keys
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 @@
+TinyFugue: TF Python
+
+
+
+
+
+
+
+
+TF Python Module
+
+
+ The TF Python module lets your python module do useful things with TinyFugue.
+ When using /python or
+ python() module tf is already present in the
+ local namespace, but any /python_load
+ script needs to 'import tf'.
+
+
+ tf.err( text ) - sends text to your screen (tferr stream).
+
+ tf.eval( string arg ) - is the same as /eval string arg.
+
+ tf.getvar( var, (default ) - gets value of var or default if it's not set.
+
+ tf.out( text ) - sends text to your screen (tfout stream).
+
+ tf.send( text, (world ) - sends text to world (default current)
+ without worrying about the flag/quoting problems of tf.eval()
+
+ tf.tfrc() - returns the file name of the loaded .tfrc file (or empty string if none)
+
+ tf.world() - returns the name of the current world or empty string if none.
+
+
+ Arbitrary data from TF: Your program can retrieve any information it needs
+ from TinyFugue by doing the following (for example):
+ tf.eval( '/test "${world_name}"' )
+ will return a string containing the name of the current world or:
+ tf.eval( '/test columns()' )
+ will return an integer with the number of screen columns. Notice the
+ quoting is slightly depending on what we expect it to return.
+
+
+ See the included tf-lib/urlwatch.py and tf-lib/config.py for fairly complex
+ examples.
+
+
+ See also: tf python for overall info.
+
+
+
+
+ Back to index
+ Back to tf home page
+
+ Copyright © 1995, 1996, 1997, 1998, 1999, 2002, 2003, 2004, 2005, 2006-2007 Ken Keys
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 @@
+TinyFugue: TF Python
+
+TF Python
+
+
+ TF Python is a patch (which you apparently have installed) for TinyFugue 5.08b
+ which allows TinyFugue to use Python for scripting.
+
+
+ TinyFugue Commands (see each for details):
+ /python command
+ python( command )
+ /python_load module
+ /python_call module.function argument
+ /python_kill
+
+
+ Calling back into TinyFugue: TF Python provides Python scripts with a 'tf'
+ module that has several methods - without these it's all fairly useless.
+ Please see /help tf module for info on this.
+
+
+ Speed: The python interpreter is (dynamically) embedded in TinyFugue to
+ reduce overhead and allow tighter coupling. When using
+ /python_load and
+ /python_call
+ all the code is byte compiled on load, so it's quite snappy.
+
+
+ Environment: 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.
+
+
+ Arbitrary data from TF: Your program can retrieve any information it needs
+ from TinyFugue by using tf.eval.
+ Please see /help tf module for info on this.
+
+
+ stdout and stderr: by default, stdout is discarded (so you don't see the
+ output of every /python command) and stderr
+ is sent to tferr. You can make stdout active again with:
+ /python sys.stdout.output = tf.out
+ and shut it up again with:
+ /python sys.stdout.output = None
+ You can do the same with sys.stderr.output, but use tf.err instead.
+
+
+ See the included tf-lib/urlwatch.py and tf-lib/config.py for fairly complex
+ examples.
+
+
+
+
+ Back to index
+ Back to tf home page
+
+ Copyright © 1995, 1996, 1997, 1998, 1999, 2002, 2003, 2004, 2005, 2006-2007 Ken Keys
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( , =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( )\n"
+ "Shows error locally (to the tferr stream).\n"
+ },
+ {
+ "eval", tf_eval, METH_VARARGS,
+ "tf.eval( )\n"
+ "Calls TinyFugue's /eval with .\n"
+ "Returns the return code of the evaluation if any."
+ },
+ {
+ "getvar", tf_getvar, METH_VARARGS,
+ "tf.getvar( , () )\n"
+ "use tf.eval() for setvar functionality\n"
+ "Returns the value of if it exists, or default."
+ },
+ {
+ "out", tf_out, METH_VARARGS,
+ "tf.out( )\n"
+ "Shows locally (to the tfout stream).\n"
+ },
+ {
+ "send", tf_send, METH_VARARGS,
+ "tf.send( , () )\n"
+ "Send to (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 foo
+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 '"
+
+ # 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=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 ''), \
+ 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 (-) (-r)
+
+ Uploads minimal commands necessary to turn centents of into
+ by diff-ing against the last version. This is per
+ per world.
+
+ options:
+ - If -S or -0, send all commands immediately. Otherwise sends
+ 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
+ for a specific world -r is implied.
+ -p() Show progress every lines. Default 100 if -p present.
+
+ arguments:
+ Which editor class to upload with. Current choices are:
+ ( lsedit | muf )
+ Which 'thing' to upload it to.
+ lsedit: = ex: #1234=mydesc
+ muf: ex: #1234
+ 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
+ Set where the diffedit keeps the last known version of each file for a
+ _ 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 -. 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="" ):
+ 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 may have or not, and 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, )
+
+ set TF variable to , 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='', body='', 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='', body='', flags='' )
+
+ Create a new key binding for . 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 = {} # { : }
+ self.olddef = {} # { : }
+ self.newdef = [] # [ ]
+ self.oldkey = {} # { : }
+ self.newkey = [] # [ ]
+ 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 =' 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': 'reload page',
+}
+
+# ---------------------------------------------------------------------------
+# 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( '%s' % ( 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( '%s\n' % CONFIG.get('title') )
+ if CONFIG.get( 'reload' ):
+ f.write( '' % CONFIG['reload'] )
+ if CONFIG.get( 'css' ):
+ f.write( '\n' % CONFIG['css'] )
+ f.write( '\n' )
+ f.write( CONFIG.get( 'header', '' ) )
+
+ # write each URL
+ if URLS:
+ f.write( '\n' )
+ odd = True
+ for world, url in reversed( URLS ):
+ classname = odd and "odd" or "even"
+ odd = not odd
+ f.write( ' | %s | %s |
\n' % ( classname, world, url ) )
+ f.write( '
\n' )
+ else:
+ f.write( "
No URLs caught yet.
\n" )
+ f.write( '\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 =" )
+
+# ---------------------------------------------------------------------------
+# 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