4 Copyright (C) 1999-2000 Timo Sirainen
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 #include "special-vars.h"
28 #include "servers-redirect.h"
31 #include "lib-config/iconfig.h"
35 char *current_command;
37 static int signal_default_command;
39 static GSList *alias_runstack;
41 COMMAND_REC *command_find(const char *cmd)
45 g_return_val_if_fail(cmd != NULL, NULL);
47 for (tmp = commands; tmp != NULL; tmp = tmp->next) {
48 COMMAND_REC *rec = tmp->data;
50 if (g_strcasecmp(rec->cmd, cmd) == 0)
57 static COMMAND_MODULE_REC *command_module_find(COMMAND_REC *rec,
62 g_return_val_if_fail(rec != NULL, NULL);
63 g_return_val_if_fail(module != NULL, NULL);
65 for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
66 COMMAND_MODULE_REC *rec = tmp->data;
68 if (g_strcasecmp(rec->name, module) == 0)
75 static COMMAND_MODULE_REC *command_module_find_func(COMMAND_REC *rec,
80 g_return_val_if_fail(rec != NULL, NULL);
81 g_return_val_if_fail(func != NULL, NULL);
83 for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
84 COMMAND_MODULE_REC *rec = tmp->data;
86 if (g_slist_find(rec->signals, func) != NULL)
93 int command_have_sub(const char *command)
98 g_return_val_if_fail(command != NULL, FALSE);
100 /* find "command "s */
101 len = strlen(command);
102 for (tmp = commands; tmp != NULL; tmp = tmp->next) {
103 COMMAND_REC *rec = tmp->data;
105 if (g_strncasecmp(rec->cmd, command, len) == 0 &&
106 rec->cmd[len] == ' ')
113 static COMMAND_MODULE_REC *command_module_get(COMMAND_REC *rec,
116 COMMAND_MODULE_REC *modrec;
118 g_return_val_if_fail(rec != NULL, NULL);
120 modrec = command_module_find(rec, module);
121 if (modrec == NULL) {
122 modrec = g_new0(COMMAND_MODULE_REC, 1);
123 modrec->name = g_strdup(module);
124 rec->modules = g_slist_append(rec->modules, modrec);
130 void command_bind_to(const char *module, int pos, const char *cmd,
131 const char *category, SIGNAL_FUNC func)
134 COMMAND_MODULE_REC *modrec;
137 g_return_if_fail(module != NULL);
138 g_return_if_fail(cmd != NULL);
140 rec = command_find(cmd);
142 rec = g_new0(COMMAND_REC, 1);
143 rec->cmd = g_strdup(cmd);
144 rec->category = category == NULL ? NULL : g_strdup(category);
145 commands = g_slist_append(commands, rec);
147 modrec = command_module_get(rec, module);
149 modrec->signals = g_slist_append(modrec->signals, func);
152 str = g_strconcat("command ", cmd, NULL);
153 signal_add_to(module, pos, str, func);
157 signal_emit("commandlist new", 1, rec);
160 static void command_free(COMMAND_REC *rec)
162 commands = g_slist_remove(commands, rec);
163 signal_emit("commandlist remove", 1, rec);
165 g_free_not_null(rec->category);
166 g_strfreev(rec->options);
171 static void command_module_free(COMMAND_MODULE_REC *modrec, COMMAND_REC *rec)
173 rec->modules = g_slist_remove(rec->modules, modrec);
175 g_slist_free(modrec->signals);
176 g_free(modrec->name);
177 g_free_not_null(modrec->options);
181 static void command_module_destroy(COMMAND_REC *rec,
182 COMMAND_MODULE_REC *modrec)
184 GSList *tmp, *freelist;
186 command_module_free(modrec, rec);
188 /* command_set_options() might have added module declaration of it's
189 own without any signals .. check if they're the only ones left
190 and if so, destroy them. */
192 for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
193 COMMAND_MODULE_REC *rec = tmp->data;
195 if (rec->signals == NULL)
196 freelist = g_slist_append(freelist, rec);
198 g_slist_free(freelist);
204 g_slist_foreach(freelist, (GFunc) command_module_free, rec);
205 g_slist_free(freelist);
207 if (rec->modules == NULL)
211 void command_unbind(const char *cmd, SIGNAL_FUNC func)
214 COMMAND_MODULE_REC *modrec;
217 g_return_if_fail(cmd != NULL);
218 g_return_if_fail(func != NULL);
220 rec = command_find(cmd);
222 modrec = command_module_find_func(rec, func);
223 modrec->signals = g_slist_remove(modrec->signals, func);
224 if (modrec->signals == NULL)
225 command_module_destroy(rec, modrec);
228 str = g_strconcat("command ", cmd, NULL);
229 signal_remove(str, func);
233 /* Expand `cmd' - returns `cmd' if not found, NULL if more than one
235 static const char *command_expand(char *cmd)
241 g_return_val_if_fail(cmd != NULL, NULL);
246 for (tmp = commands; tmp != NULL; tmp = tmp->next) {
247 COMMAND_REC *rec = tmp->data;
249 if (g_strncasecmp(rec->cmd, cmd, len) == 0 &&
250 strchr(rec->cmd+len, ' ') == NULL) {
251 if (rec->cmd[len] == '\0') {
257 /* multiple matches, we still need to check
258 if there's some command left that is a
263 /* check that this is the only match */
269 signal_emit("error command", 2,
270 GINT_TO_POINTER(CMDERR_AMBIGUOUS), cmd);
274 return match != NULL ? match : cmd;
277 void command_runsub(const char *cmd, const char *data,
278 void *server, void *item)
281 char *orig, *subcmd, *defcmd, *args;
283 g_return_if_fail(data != NULL);
286 /* no subcommand given - list the subcommands */
287 signal_emit("list subcommands", 2, cmd);
292 orig = subcmd = g_strdup_printf("command %s %s", cmd, data);
293 args = strchr(subcmd+8 + strlen(cmd)+1, ' ');
294 if (args != NULL) *args++ = '\0'; else args = "";
295 while (*args == ' ') args++;
297 /* check if this command can be expanded */
298 newcmd = command_expand(subcmd+8);
299 if (newcmd == NULL) {
300 /* ambiguous command */
305 subcmd = g_strconcat("command ", newcmd, NULL);
308 if (!signal_emit(subcmd, 3, args, server, item)) {
309 defcmd = g_strdup_printf("default command %s", cmd);
310 if (!signal_emit(defcmd, 3, data, server, item)) {
311 signal_emit("error command", 2,
312 GINT_TO_POINTER(CMDERR_UNKNOWN), subcmd+8);
321 static GSList *optlist_find(GSList *optlist, const char *option)
323 while (optlist != NULL) {
324 char *name = optlist->data;
325 if (iscmdtype(*name)) name++;
327 if (g_strcasecmp(name, option) == 0)
330 optlist = optlist->next;
336 int command_have_option(const char *cmd, const char *option)
341 g_return_val_if_fail(cmd != NULL, FALSE);
342 g_return_val_if_fail(option != NULL, FALSE);
344 rec = command_find(cmd);
345 g_return_val_if_fail(rec != NULL, FALSE);
347 if (rec->options == NULL)
350 for (tmp = rec->options; *tmp != NULL; tmp++) {
351 char *name = iscmdtype(**tmp) ? (*tmp)+1 : *tmp;
353 if (g_strcasecmp(name, option) == 0)
360 static void command_calc_options(COMMAND_REC *rec, const char *options)
362 char **optlist, **tmp, *name, *str;
363 GSList *list, *oldopt;
365 optlist = g_strsplit(options, " ", -1);
367 if (rec->options == NULL) {
368 /* first call - use specified args directly */
369 rec->options = optlist;
373 /* save old options to linked list */
375 for (tmp = rec->options; *tmp != NULL; tmp++)
376 list = g_slist_append(list, g_strdup(*tmp));
377 g_strfreev(rec->options);
379 /* merge the options */
380 for (tmp = optlist; *tmp != NULL; tmp++) {
381 name = iscmdtype(**tmp) ? (*tmp)+1 : *tmp;
383 oldopt = optlist_find(list, name);
384 if (oldopt != NULL) {
385 /* already specified - overwrite old defination */
386 g_free(oldopt->data);
387 oldopt->data = g_strdup(*tmp);
389 /* new option, append to list */
390 list = g_slist_append(list, g_strdup(*tmp));
395 /* linked list -> string[] */
396 str = gslist_to_string(list, " ");
397 rec->options = g_strsplit(str, " ", -1);
400 g_slist_foreach(list, (GFunc) g_free, NULL);
404 /* recalculate options to command from options in all modules */
405 static void command_update_options(COMMAND_REC *rec)
409 g_strfreev(rec->options);
412 for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
413 COMMAND_MODULE_REC *modrec = tmp->data;
415 if (modrec->options != NULL)
416 command_calc_options(rec, modrec->options);
420 void command_set_options_module(const char *module,
421 const char *cmd, const char *options)
424 COMMAND_MODULE_REC *modrec;
427 g_return_if_fail(module != NULL);
428 g_return_if_fail(cmd != NULL);
429 g_return_if_fail(options != NULL);
431 rec = command_find(cmd);
432 g_return_if_fail(rec != NULL);
433 modrec = command_module_get(rec, module);
435 reload = modrec->options != NULL;
437 /* options already set for the module ..
438 we need to recalculate everything */
439 g_free(modrec->options);
442 modrec->options = g_strdup(options);
445 command_update_options(rec);
447 command_calc_options(rec, options);
450 char *cmd_get_param(char **data)
454 g_return_val_if_fail(data != NULL, NULL);
455 g_return_val_if_fail(*data != NULL, NULL);
457 while (**data == ' ') (*data)++;
460 while (**data != '\0' && **data != ' ') (*data)++;
461 if (**data == ' ') *(*data)++ = '\0';
466 static char *cmd_get_quoted_param(char **data)
470 g_return_val_if_fail(data != NULL, NULL);
471 g_return_val_if_fail(*data != NULL, NULL);
473 while (**data == ' ') (*data)++;
474 if (**data != '\'' && **data != '"')
475 return cmd_get_param(data);
477 quote = **data; (*data)++;
480 while (**data != '\0' && **data != quote) {
481 if (**data == '\\' && (*data)[1] != '\0')
482 g_memmove(*data, (*data)+1, strlen(*data));
486 if (**data != '\0') *(*data)++ = '\0';
491 /* Find specified option from list of options - the `option' might be
492 shortened version of the full command. Returns index where the
493 option was found, -1 if not found or -2 if there was multiple matches. */
494 static int option_find(char **array, const char *option)
497 int index, found, len, multiple;
499 g_return_val_if_fail(array != NULL, -1);
500 g_return_val_if_fail(option != NULL, -1);
502 len = strlen(option);
504 found = -1; index = 0; multiple = FALSE;
505 for (tmp = array; *tmp != NULL; tmp++, index++) {
506 const char *text = *tmp + iscmdtype(**tmp);
508 if (g_strncasecmp(text, option, len) == 0) {
509 if (text[len] == '\0') {
515 /* multiple matches - we still need to check
516 if there's a full match left.. */
520 /* partial match, check that it's the only one */
531 static int get_cmd_options(char **data, int ignore_unknown,
532 const char *cmd, GHashTable *options)
535 char *option, *arg, **optlist;
538 /* get option definations */
539 rec = cmd == NULL ? NULL : command_find(cmd);
540 optlist = rec == NULL ? NULL : rec->options;
542 option = NULL; pos = -1;
545 if (option != NULL && *optlist[pos] == '+') {
546 /* required argument missing! */
547 *data = optlist[pos] + 1;
548 return CMDERR_OPTION_ARG_MISSING;
552 if (**data == '-' && isspace((*data)[1])) {
553 /* -- option means end of options even
554 if next word starts with - */
556 while (isspace(**data)) (*data)++;
560 if (!isspace(**data))
561 option = cmd_get_param(data);
567 /* check if this option can have argument */
568 pos = optlist == NULL ? -1 :
569 option_find(optlist, option);
570 if (pos == -1 && !ignore_unknown) {
571 /* unknown option! */
573 return CMDERR_OPTION_UNKNOWN;
575 if (pos == -2 && !ignore_unknown) {
576 /* multiple matches */
578 return CMDERR_OPTION_AMBIGUOUS;
581 /* if we used a shortcut of parameter, put
582 the whole parameter name in options table */
583 option = optlist[pos] +
584 iscmdtype(*optlist[pos]);
587 g_hash_table_insert(options, option, "");
589 if (pos < 0 || !iscmdtype(*optlist[pos]) ||
590 *optlist[pos] == '!')
593 while (isspace(**data)) (*data)++;
600 if (*optlist[pos] == '@' && !isdigit(**data))
601 break; /* expected a numeric argument */
603 /* save the argument */
604 arg = cmd_get_quoted_param(data);
605 if (options != NULL) {
606 g_hash_table_remove(options, option);
607 g_hash_table_insert(options, option, arg);
611 while (isspace(**data)) (*data)++;
622 static char *get_optional_channel(CHANNEL_REC *active_channel, char **data)
624 CHANNEL_REC *chanrec;
625 char *tmp, *origtmp, *channel, *ret;
627 if (active_channel == NULL) {
628 /* no active channel in window, channel required */
629 return cmd_get_param(data);
632 origtmp = tmp = g_strdup(*data);
633 channel = cmd_get_param(&tmp);
635 if (strcmp(channel, "*") == 0 ||
636 !active_channel->server->ischannel(channel))
637 ret = active_channel->name;
639 /* Find the channel first and use it's name if found.
640 This allows automatic !channel -> !XXXXXchannel replaces. */
641 chanrec = channel_find(active_channel->server, channel);
642 ret = chanrec == NULL ? channel : chanrec->name;
650 int cmd_get_params(const char *data, gpointer *free_me, int count, ...)
652 CHANNEL_REC *chanrec;
654 GHashTable **opthash;
655 char **str, *arg, *datad;
657 int cnt, error, ignore_unknown;
659 g_return_val_if_fail(data != NULL, FALSE);
661 va_start(args, count);
663 rec = g_new0(CMD_TEMP_REC, 1);
664 rec->data = g_strdup(data);
670 chanrec = (count & PARAM_FLAG_OPTCHAN) == 0 ? NULL:
671 (CHANNEL_REC *) va_arg(args, CHANNEL_REC *);
673 if (count & PARAM_FLAG_OPTIONS) {
674 arg = (char *) va_arg(args, char *);
675 opthash = (GHashTable **) va_arg(args, GHashTable **);
677 rec->options = *opthash =
678 g_hash_table_new((GHashFunc) g_istr_hash,
679 (GCompareFunc) g_istr_equal);
681 ignore_unknown = count & PARAM_FLAG_UNKNOWN_OPTIONS;
682 error = get_cmd_options(&datad, ignore_unknown,
687 /* and now handle the string */
688 cnt = PARAM_WITHOUT_FLAGS(count);
689 if (count & PARAM_FLAG_OPTCHAN) {
690 /* optional channel as first parameter */
691 arg = get_optional_channel(chanrec, &datad);
693 str = (char **) va_arg(args, char **);
694 if (str != NULL) *str = arg;
699 if (cnt == 0 && count & PARAM_FLAG_GETREST) {
703 arg = (count & PARAM_FLAG_NOQUOTES) ?
704 cmd_get_param(&datad) :
705 cmd_get_quoted_param(&datad);
708 str = (char **) va_arg(args, char **);
709 if (str != NULL) *str = arg;
715 signal_emit("error command", 2, GINT_TO_POINTER(error), datad);
718 cmd_params_free(rec);
725 void cmd_params_free(void *free_me)
727 CMD_TEMP_REC *rec = free_me;
729 if (rec->options != NULL) g_hash_table_destroy(rec->options);
734 static void command_module_unbind_all(COMMAND_REC *rec,
735 COMMAND_MODULE_REC *modrec)
739 for (tmp = modrec->signals; tmp != NULL; tmp = next) {
742 command_unbind(rec->cmd, tmp->data);
745 if (g_slist_find(commands, rec) != NULL) {
746 /* this module might have removed some options
747 from command, update them. */
748 command_update_options(rec);
752 void commands_remove_module(const char *module)
754 GSList *tmp, *next, *modlist;
756 g_return_if_fail(module != NULL);
758 for (tmp = commands; tmp != NULL; tmp = next) {
759 COMMAND_REC *rec = tmp->data;
762 modlist = gslist_find_string(rec->modules, module);
764 command_module_unbind_all(rec, modlist->data);
768 #define alias_runstack_push(alias) \
769 alias_runstack = g_slist_append(alias_runstack, alias)
771 #define alias_runstack_pop(alias) \
772 alias_runstack = g_slist_remove(alias_runstack, alias)
774 #define alias_runstack_find(alias) \
775 (gslist_find_icase_string(alias_runstack, alias) != NULL)
777 static void parse_command(const char *command, int expand_aliases,
778 SERVER_REC *server, void *item)
780 const char *alias, *newcmd;
781 char *cmd, *orig, *args, *oldcmd;
783 g_return_if_fail(command != NULL);
785 cmd = orig = g_strconcat("command ", command, NULL);
786 args = strchr(cmd+8, ' ');
787 if (args != NULL) *args++ = '\0'; else args = "";
789 /* check if there's an alias for command. Don't allow
791 alias = !expand_aliases || alias_runstack_find(cmd+8) ? NULL :
794 alias_runstack_push(cmd+8);
795 eval_special_string(alias, args, server, item);
796 alias_runstack_pop(cmd+8);
801 /* check if this command can be expanded */
802 newcmd = command_expand(cmd+8);
803 if (newcmd == NULL) {
804 /* ambiguous command */
809 cmd = g_strconcat("command ", newcmd, NULL);
811 server_redirect_default(SERVER(server), cmd);
814 oldcmd = current_command;
815 current_command = cmd+8;
816 if (!signal_emit(cmd, 3, args, server, item)) {
817 signal_emit_id(signal_default_command, 3,
818 command, server, item);
820 current_command = oldcmd;
826 static void event_command(const char *line, SERVER_REC *server, void *item)
829 int expand_aliases = TRUE;
831 g_return_if_fail(line != NULL);
834 /* empty line, forget it. */
839 cmdchar = strchr(settings_get_str("cmdchars"), *line);
840 if (cmdchar != NULL && line[1] == ' ') {
841 /* "/ text" = same as sending "text" to active channel. */
845 if (cmdchar == NULL) {
846 /* non-command - let someone else handle this */
847 signal_emit("send text", 3, line, server, item);
851 /* same cmdchar twice ignores aliases ignores aliases */
853 if (*line == *cmdchar) {
855 expand_aliases = FALSE;
858 /* ^command hides the output - we'll do this at fe-common but
859 we have to skip the ^ char here.. */
860 if (*line == '^') line++;
862 parse_command(line, expand_aliases, server, item);
865 /* SYNTAX: EVAL <command(s)> */
866 static void cmd_eval(const char *data, SERVER_REC *server, void *item)
868 g_return_if_fail(data != NULL);
870 eval_special_string(data, "", server, item);
873 /* SYNTAX: CD <directory> */
874 static void cmd_cd(const char *data)
878 g_return_if_fail(data != NULL);
879 if (*data == '\0') return;
881 str = convert_home(data);
886 void commands_init(void)
889 current_command = NULL;
890 alias_runstack = NULL;
892 signal_default_command = signal_get_uniq_id("default command");
894 settings_add_str("misc", "cmdchars", "/");
895 signal_add("send command", (SIGNAL_FUNC) event_command);
897 command_bind("eval", NULL, (SIGNAL_FUNC) cmd_eval);
898 command_bind("cd", NULL, (SIGNAL_FUNC) cmd_cd);
901 void commands_deinit(void)
903 g_free_not_null(current_command);
905 signal_remove("send command", (SIGNAL_FUNC) event_command);
907 command_unbind("eval", (SIGNAL_FUNC) cmd_eval);
908 command_unbind("cd", (SIGNAL_FUNC) cmd_cd);