/*************************************************************************
* TinyFugue - programmable mud client
* Copyright (C) 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2002, 2003, 2004, 2005, 2006-2007 Ken Keys
*
* TinyFugue (aka "tf") is protected under the terms of the GNU
* General Public License. See the file "COPYING" for details.
************************************************************************/
static const char RCSid[] = "$Id: command.c,v 35004.141 2007/01/13 23:12:39 kkeys Exp $";
/*****************************************************************
* Fugue command handlers
*****************************************************************/
#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"
int exiting = 0;
static char *pattern, *body;
static int quietload = 0;
static void split_args(char *args);
static BuiltinCmd cmd_table[] =
{
#define defcmd(name, func, reserved) \
{ name, func, reserved },
#include "cmdlist.h"
};
#define NUM_CMDS (sizeof(cmd_table) / sizeof(BuiltinCmd))
STATIC_STRING(limit_none, "% No previous limit.", 0);
STATIC_STRING(limit_no_match, "% No lines matched criteria.", 0);
/*****************************************
* Find, process and run commands/macros *
*****************************************/
BuiltinCmd *find_builtin_cmd(const char *name)
{
return (BuiltinCmd *)bsearch((void*)name, (void*)cmd_table,
NUM_CMDS, sizeof(BuiltinCmd), cstrstructcmp);
}
struct Value *handle_trigger_command(String *args, int offset)
{
World *world = NULL;
int usedefault = TRUE, is_global = FALSE, result = 0, exec_list_long = 0;
int opt;
int hooknum = -1;
String *old_incoming_text;
const char *ptr;
if (!borg) return shareval(val_zero);
startopt(CS(args), "gw:h:nl");
while ((opt = nextopt(&ptr, NULL, NULL, &offset))) {
switch (opt) {
case 'g':
usedefault = FALSE;
is_global = TRUE;
break;
case 'w':
usedefault = FALSE;
if (!(world = named_or_current_world(ptr)))
return shareval(val_zero);
break;
case 'h':
if ((hooknum = hookname2int(ptr)) < 0)
return shareval(val_zero);
break;
case 'n':
if (exec_list_long == 0)
exec_list_long = 1;
break;
case 'l':
exec_list_long = 2;
break;
default:
return shareval(val_zero);
}
}
if (usedefault) {
is_global = TRUE;
world = xworld();
}
old_incoming_text = incoming_text;
(incoming_text = Stringodup(CS(args), offset))->links++;
result = find_and_run_matches(NULL, hooknum, &incoming_text, world,
is_global, exec_list_long);
Stringfree(incoming_text);
incoming_text = old_incoming_text;
return newint(result);
}
int handle_substitute_func(
conString *src,
const char *attrstr,
int inline_flag)
{
attr_t attrs;
String *newstr;
if (!incoming_text) {
eprintf("not called from trigger");
return 0;
}
if (!parse_attrs(attrstr, &attrs, 0))
return 0;
newstr = inline_flag ? decode_attr(src, 0, 0) : Stringdup(src);
if (!newstr)
return 0;
/* Start w/ incoming_text->attrs, adjust with src->attrs and attrstr. */
newstr->attrs = adj_attr(incoming_text->attrs, src->attrs);
newstr->attrs = adj_attr(newstr->attrs, attrs);
newstr->time = incoming_text->time;
Stringfree(incoming_text);
(incoming_text = newstr)->links++;
return 1;
}
/**********
* Worlds *
**********/
struct Value *handle_connect_command(String *args, int offset)
{
char *host, *port = NULL;
int opt, flags = 0;
if (login) flags |= CONN_AUTOLOGIN;
if (quietflag) flags |= CONN_QUIETLOGIN;
startopt(CS(args), "lqxfb");
while ((opt = nextopt(NULL, NULL, NULL, &offset))) {
switch (opt) {
case 'l': flags &= ~CONN_AUTOLOGIN; break;
case 'q': flags |= CONN_QUIETLOGIN; break;
case 'x': flags |= CONN_SSL; break;
case 'f': flags |= CONN_FG; break;
case 'b': flags |= CONN_BG; break;
default: return shareval(val_zero);
}
}
host = args->data + offset;
for (port = host; *port && !is_space(*port); port++);
if (*port) {
*port = '\0';
while (is_space(*++port));
}
return newint(openworld(host, *port ? port : NULL, flags));
}
struct Value *handle_localecho_command(String *args, int offset)
{
if (!(args->len - offset)) return newint(local_echo(-1));
else if (cstrcmp(args->data + offset, "on") == 0) local_echo(1);
else if (cstrcmp(args->data + offset, "off") == 0) local_echo(0);
return shareval(val_one);
}
/*************
* Variables *
*************/
struct Value *handle_set_command(String *args, int offset)
{
return newint(command_set(args, offset, FALSE, FALSE));
}
struct Value *handle_setenv_command(String *args, int offset)
{
return newint(command_set(args, offset, TRUE, FALSE));
}
struct Value *handle_let_command(String *args, int offset)
{
return newint(command_set(args, offset, FALSE, TRUE));
}
/********
* Misc *
********/
struct Value *handle_quit_command(String *args, int offset)
{
int yes = 0;
int c;
startopt(CS(args), "y");
while ((c = nextopt(NULL, NULL, NULL, &offset))) {
if (c == 'y') yes++;
else return shareval(val_zero);
}
if (interactive && have_active_socks() && !yes) {
fix_screen();
puts("There are sockets with unseen text. Really quit tf? (y/N)\r");
fflush(stdout);
c = igetchar();
redraw();
if (lcase(c) != 'y') {
return shareval(val_zero);
}
}
quit_flag = 1;
return shareval(val_one);
}
struct Value *handle_sh_command(String *args, int offset)
{
const char *cmd;
char c;
int quiet = 0;
if (restriction >= RESTRICT_SHELL) {
eprintf("restricted");
return shareval(val_zero);
}
startopt(CS(args), "q");
while ((c = nextopt(NULL, NULL, NULL, &offset))) {
if (c == 'q') quiet++;
else return shareval(val_zero);
}
if (args->len - offset) {
cmd = args->data + offset;
if (!quiet)
do_hook(H_SHELL, "%% Executing %s: %s", "%s %s", "command", cmd);
} else {
/* Note: on unix, system("") does nothing, but SHELL should always be
* defined, so it won't happen. On os/2, SHELL usually isn't defined,
* and system("") will choose choose the default interpreter; SHELL
* will be used if defined, of course.
*/
cmd = getvar("SHELL");
if (!quiet)
do_hook(H_SHELL, "%% Executing %s: %s", "%s %s", "shell", cmd);
/* XXX BUG: SHELL hook might unset %SHELL, then we're screwed. */
}
return newint(shell(cmd));
}
struct Value *handle_suspend_command(String *args, int offset)
{
return newint(suspend());
}
struct Value *handle_version_command(String *args, int offset)
{
oprintf("%% %s.", version);
oprintf("%% %s.", copyright);
if (*contrib) oprintf("%% %s", contrib);
if (*mods) oprintf("%% %s", mods);
if (*sysname) oprintf("%% Built for %s", sysname);
return shareval(val_one);
}
/* for debugging */
struct Value *handle_core_command(String *args, int offset)
{
internal_error(__FILE__, __LINE__, "command: /core %s",
args->data + offset);
core("/core command", __FILE__, __LINE__, 0);
return NULL; /* never reached */
}
struct Value *handle_features_command(String *args, int offset)
{
struct feature *f;
if (args->len > offset) {
for (f = features; f->name; f++) {
if (cstrcmp(f->name, args->data + offset) == 0)
return *f->flag ? shareval(val_one) : shareval(val_zero);
}
return shareval(val_zero);
} else {
oputline(CS(featurestr));
return shareval(val_one);
}
}
struct Value *handle_lcd_command(String *args, int offset)
{
char buffer[PATH_MAX + 1], *name;
if (restriction >= RESTRICT_FILE) {
eprintf("restricted");
return shareval(val_zero);
}
name = expand_filename(stripstr(args->data + offset));
if (*name && chdir(name) < 0) {
operror(name);
return shareval(val_zero);
}
#if HAVE_GETCWD
oprintf("%% Current directory is %s", getcwd(buffer, PATH_MAX));
#else
# if HAVE_GETWD
oprintf("%% Current directory is %s", getwd(buffer));
# endif
#endif
return shareval(val_one);
}
int handle_echo_func(
conString *src,
const char *attrstr,
int inline_flag,
const char *dest)
{
attr_t attrs;
int raw = 0;
TFILE *file = tfout;
World *world = NULL;
conString *newstr;
if (!parse_attrs(attrstr, &attrs, 0))
return 0;
switch(*dest) {
case 'r': raw = 1; break;
case 'o': file = tfout; break;
case 'e': file = tferr; if (!*attrstr) attrs = error_attr; break;
case 'a': file = tfalert; break;
case 'w':
dest++;
if (!(world = named_or_current_world(dest)))
return 0;
break;
default:
eprintf("illegal destination '%c'", *dest);
return 0;
}
if (raw) {
write(STDOUT_FILENO, src->data, src->len);
return 1;
}
newstr = inline_flag ? CS(decode_attr(src, 0, 0)) : CS(Stringdup(src));
if (!newstr)
return 0;
newstr->links++;
newstr->attrs = adj_attr(newstr->attrs, attrs);
if (world)
world_output(world, newstr);
else
tfputline(newstr, file);
conStringfree(newstr);
return 1;
}
struct Value *handle_restrict_command(String *args, int offset)
{
int level;
static conString enum_restrict[] = {
STRING_LITERAL("none"), STRING_LITERAL("shell"),
STRING_LITERAL("file"), STRING_LITERAL("world"),
STRING_NULL };
if (!(args->len - offset)) {
oprintf("%% restriction level: %S", &enum_restrict[restriction]);
return newint(restriction);
} else if ((level = enum2int(args->data + offset, 0, enum_restrict,
"/restrict")) < 0)
{
return shareval(val_zero);
} else if (level < restriction) {
oputs("% Restriction level can not be lowered.");
return shareval(val_zero);
}
return newint(restriction = level);
}
struct Value *handle_limit_command(String *args, int offset)
{
int mflag = matching;
int got_opts = 0;
int result, had_filter, has_new_pat;
char c;
const char *ptr;
Screen *screen = display_screen;
int attr_flag = 0, sense = 1;
Pattern pat;
startopt(CS(args), "avm:");
while ((c = nextopt(&ptr, NULL, NULL, &offset))) {
got_opts++;
switch (c) {
case 'a':
attr_flag = 1;
break;
case 'v':
sense = 0;
break;
case 'm':
if ((mflag = enum2int(ptr, 0, enum_match, "-m")) < 0)
return shareval(val_zero);
break;
default:
return shareval(val_zero);
}
}
if (!got_opts && offset == args->len) {
result = screen_has_filter(screen);
goto end;
}
if ((has_new_pat = (offset != args->len))) {
if (!init_pattern(&pat, args->data + offset, mflag)) {
result = 0;
goto end;
}
}
had_filter = screen_has_filter(screen);
clear_screen_filter(screen);
set_screen_filter(screen, has_new_pat ? &pat : NULL, attr_flag, sense);
if (!(result = redraw_window(screen, 0))) {
alert(limit_no_match);
}
update_status_field(NULL, STAT_MORE);
end:
return result ? shareval(val_one) : shareval(val_zero);
}
struct Value *handle_relimit_command(String *args, int offset)
{
Screen *screen = display_screen;
Value *result = val_one;
if (!enable_screen_filter(screen)) {
alert(limit_none);
result = val_zero;
} else if (!redraw_window(screen, 0)) {
alert(limit_no_match);
result = val_zero;
}
update_status_field(NULL, STAT_MORE);
return shareval(result);
}
struct Value *handle_unlimit_command(String *args, int offset)
{
Screen *screen = display_screen;
if (!screen_has_filter(screen))
return shareval(val_zero);
clear_screen_filter(screen);
if (!screen->paused)
screen_end(0);
redraw_window(screen, 0);
update_status_field(NULL, STAT_MORE);
return shareval(val_one);
}
/********************
* Generic handlers *
********************/
/* Returns -1 if file can't be read, 0 for an error within the file, or 1 for
* success.
*/
int do_file_load(const char *args, int tinytalk)
{
AUTO_BUFFER(line);
AUTO_BUFFER(cmd);
int error = 0, new_cmd = 1;
TFILE *file, *old_file = loadfile;
int old_loadline = loadline;
int old_loadstart = loadstart;
int last_cmd_line = 0;
const char *path;
STATIC_BUFFER(libfile);
if (!loadfile)
exiting = 0;
file = tfopen(expand_filename(args), "r");
if (!file && !tinytalk && errno == ENOENT && !is_absolute_path(args)) {
/* Relative file was not found, so look in TFPATH or TFLIBDIR. */
if (TFPATH && *TFPATH) {
path = TFPATH;
do {
while (is_space(*path)) ++path;
if (!*path) break;
Stringtrunc(libfile, 0);
while (*path && !is_space(*path)) {
if (*path == '\\' && path[1]) path++;
Stringadd(libfile, *path++);
}
if (!is_absolute_path(libfile->data)) {
wprintf("invalid directory in TFPATH: %S", libfile);
} else {
Sappendf(libfile, "/%s", args);
file = tfopen(expand_filename(libfile->data), "r");
}
} while (!file && *path);
} else {
if (!is_absolute_path(TFLIBDIR)) {
wprintf("invalid TFLIBDIR: %s", TFLIBDIR);
} else {
Sprintf(libfile, "%s/%s", TFLIBDIR, args);
file = tfopen(expand_filename(libfile->data), "r");
}
}
}
if (!file) {
if (!tinytalk || errno != ENOENT)
do_hook(H_LOADFAIL, "!%s: %s", "%s %s", args, strerror(errno));
return -1;
}
do_hook(H_LOAD, quietload ? NULL : "%% Loading commands from %s.",
"%s", file->name);
oflush(); /* Load could take awhile, so flush pending output first. */
Stringninit(line, 80);
Stringninit(cmd, 192);
loadstart = loadline = 0;
loadfile = file; /* if this were done earlier, error msgs would be wrong */
while (1) {
int i;
if (exiting) {
--exiting;
break;
}
if (interrupted()) {
eprintf("file load interrupted.");
error = 1;
break;
}
if (!tfgetS(line, loadfile))
break;
loadline++;
if (new_cmd) loadstart = loadline;
if (line->data[0] == ';' || line->data[0] == '#') /* ignore comments */
continue;
for (i = 0; is_space(line->data[i]); i++); /* skip leading space */
if (line->data[i]) {
if (new_cmd && is_space(line->data[0]) && last_cmd_line > 0)
tfprintf(tferr,
"%% %s: line %d: Warning: possibly missing trailing \\%A",
loadfile->name, last_cmd_line, warning_attr);
last_cmd_line = loadline;
SStringocat(cmd, CS(line), i);
if (line->data[line->len - 1] == '\\') {
if (line->len < 2 || line->data[line->len - 2] != '%') {
Stringtrunc(cmd, cmd->len - 1);
new_cmd = 0;
continue;
}
} else {
i = line->len - 1;
while (i > 0 && is_space(line->data[i])) i--;
if (line->data[i] == '\\')
wprintf("whitespace following final '\\'");
}
} else {
last_cmd_line = 0;
}
new_cmd = 1;
if (!cmd->len) continue;
if (*cmd->data == '/') {
tinytalk = FALSE;
/* Never use SUB_FULL here. Libraries will break. */
macro_run(CS(cmd), 0, NULL, 0, SUB_KEYWORD, "\bLOAD");
} else if (tinytalk) {
static int warned = 0;
Macro *addworld = find_macro("addworld");
if (addworld && do_macro(addworld, cmd, 0, USED_NAME, 0) &&
!user_result->u.ival && !warned)
{
eprintf("(This line was implicitly treated as an /addworld "
"because it occured before the first '/' line and did not "
"start with a '/', ';', or '#'.)");
warned = 1;
}
} else {
eprintf("Invalid command. Aborting.");
error = 1;
break;
}
Stringtrunc(cmd, 0);
}
if (cmd->len) {
tfprintf(tferr,
"%% %s: line %d: last command is incomplete because of trailing \\",
loadfile->name, last_cmd_line);
}
Stringfree(line);
Stringfree(cmd);
if (tfclose(loadfile) != 0)
eputs("load: unknown error reading file");
loadfile = old_file;
loadline = old_loadline;
loadstart = old_loadstart;
if (!loadfile)
exiting = 0;
return !error;
}
/**************************
* Toggles with arguments *
**************************/
struct Value *handle_beep_command(String *args, int offset)
{
int n = 0;
if (!(args->len - offset)) n = 3;
else if (cstrcmp(args->data + offset, "on") == 0)
set_var_by_id(VAR_beep, 1);
else if (cstrcmp(args->data + offset, "off") == 0)
set_var_by_id(VAR_beep, 0);
else if ((n = atoi(args->data + offset)) < 0)
return shareval(val_zero);
dobell(n);
return shareval(val_one);
}
/**********
* Macros *
**********/
struct Value *handle_undef_command(String *args, int offset)
{
char *name, *next;
int result = 0;
next = args->data + offset;
while (*(name = stringarg(&next, NULL)))
result += remove_macro_by_name(name);
return newint(result);
}
struct Value *handle_save_command(String *args, int offset)
{
if (restriction >= RESTRICT_FILE) {
eprintf("restricted");
return shareval(val_zero);
}
if (args->len - offset) return newint(save_macros(args, offset));
eprintf("missing filename");
return shareval(val_zero);
}
struct Value *handle_exit_command(String *args, int offset)
{
if (!loadfile) return shareval(val_zero);
if ((exiting = atoi(args->data + offset)) <= 0) exiting = 1;
return shareval(val_one);
}
struct Value *handle_load_command(String *args, int offset)
{
int quiet = 0, result = 0;
char c;
if (restriction >= RESTRICT_FILE) {
eprintf("restricted");
return shareval(val_zero);
}
startopt(CS(args), "q");
while ((c = nextopt(NULL, NULL, NULL, &offset))) {
if (c == 'q') quiet = 1;
else return shareval(val_zero);
}
quietload += quiet;
if (args->len - offset)
result = (do_file_load(stripstr(args->data + offset), FALSE) > 0);
else eprintf("missing filename");
quietload -= quiet;
return newint(result);
}
/* Generic utility to split arguments into pattern and body.
* Note: I can get away with this only because none of the functions
* that use it are reentrant. Be careful.
*/
static void split_args(char *args)
{
char *place;
for (place = args; *place && *place != '='; place++);
if (*place) *place++ = '\0';
pattern = stripstr(args);
body = stripstr(place);
}
/***********
* Hilites *
***********/
struct Value *handle_hilite_command(String *args, int offset)
{
if (!(args->len - offset)) {
set_var_by_id(VAR_hilite, 1);
oputs("% Hilites enabled.");
return shareval(val_zero);
} else {
split_args(args->data + offset);
return newint(add_new_macro(pattern, "", NULL, NULL, body,
hpri, 100, F_HILITE, 0, matching));
}
}
/********
* Gags *
********/
struct Value *handle_gag_command(String *args, int offset)
{
if (!(args->len - offset)) {
set_var_by_id(VAR_gag, 1);
oputs("% Gags enabled.");
return shareval(val_zero);
} else {
split_args(args->data + offset);
return newint(add_new_macro(pattern, "", NULL, NULL, body,
gpri, 100, F_GAG, 0, matching));
}
}
/************
* Triggers *
************/
struct Value *handle_trigpc_command(String *args, int offset)
{
int pri, prob;
const char *ptr = args->data + offset;
if ((pri = numarg(&ptr)) < 0) return shareval(val_zero);
if ((prob = numarg(&ptr)) < 0) return shareval(val_zero);
split_args(args->data + (ptr - args->data));
return newint(add_new_macro(pattern, "", NULL, NULL, body, pri,
prob, 0, 0, matching));
}
/*********
* Hooks *
*********/
struct Value *handle_hook_command(String *args, int offset)
{
if (!(args->len - offset))
oprintf("%% Hooks %sabled", hookflag ? "en" : "dis");
else if (cstrcmp(args->data + offset, "off") == 0)
set_var_by_id(VAR_hook, 0);
else if (cstrcmp(args->data + offset, "on") == 0)
set_var_by_id(VAR_hook, 1);
else {
split_args(args->data + offset);
return newint(add_hook(pattern, body));
}
return shareval(val_one);
}
/********
* Keys *
********/
struct Value *handle_unbind_command(String *args, int offset)
{
Macro *macro;
if (!(args->len - offset)) return shareval(val_zero);
if ((macro = find_key(print_to_ascii(NULL, args->data + offset)->data)))
kill_macro(macro);
else eprintf("No binding for %s", args->data + offset);
return newint(!!macro);
}
struct Value *handle_bind_command(String *args, int offset)
{
if (!(args->len - offset)) return shareval(val_zero);
split_args(args->data + offset);
return newint(add_new_macro(NULL, print_to_ascii(NULL, pattern)->data,
NULL, NULL, body, 1, 100, 0, FALSE, 0));
}