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( ' \n' % ( classname, world, url ) ) + f.write( '
%s%s
\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