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"
26 #include "window-item-def.h"
29 #include "servers-redirect.h"
32 #include "lib-config/iconfig.h"
36 char *current_command;
38 static int signal_default_command;
40 static GSList *alias_runstack;
42 COMMAND_REC *command_find(const char *cmd)
46 g_return_val_if_fail(cmd != NULL, NULL);
48 for (tmp = commands; tmp != NULL; tmp = tmp->next) {
49 COMMAND_REC *rec = tmp->data;
51 if (g_strcasecmp(rec->cmd, cmd) == 0)
58 static COMMAND_MODULE_REC *command_module_find(COMMAND_REC *rec,
63 g_return_val_if_fail(rec != NULL, NULL);
64 g_return_val_if_fail(module != NULL, NULL);
66 for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
67 COMMAND_MODULE_REC *rec = tmp->data;
69 if (g_strcasecmp(rec->name, module) == 0)
76 static COMMAND_MODULE_REC *command_module_find_func(COMMAND_REC *rec,
81 g_return_val_if_fail(rec != NULL, NULL);
82 g_return_val_if_fail(func != NULL, NULL);
84 for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
85 COMMAND_MODULE_REC *rec = tmp->data;
87 if (g_slist_find(rec->signals, func) != NULL)
94 int command_have_sub(const char *command)
99 g_return_val_if_fail(command != NULL, FALSE);
101 /* find "command "s */
102 len = strlen(command);
103 for (tmp = commands; tmp != NULL; tmp = tmp->next) {
104 COMMAND_REC *rec = tmp->data;
106 if (g_strncasecmp(rec->cmd, command, len) == 0 &&
107 rec->cmd[len] == ' ')
114 static COMMAND_MODULE_REC *command_module_get(COMMAND_REC *rec,
117 COMMAND_MODULE_REC *modrec;
119 g_return_val_if_fail(rec != NULL, NULL);
121 modrec = command_module_find(rec, module);
122 if (modrec == NULL) {
123 modrec = g_new0(COMMAND_MODULE_REC, 1);
124 modrec->name = g_strdup(module);
125 rec->modules = g_slist_append(rec->modules, modrec);
131 void command_bind_to(const char *module, int pos, const char *cmd,
132 const char *category, SIGNAL_FUNC func)
135 COMMAND_MODULE_REC *modrec;
138 g_return_if_fail(module != NULL);
139 g_return_if_fail(cmd != NULL);
141 rec = command_find(cmd);
143 rec = g_new0(COMMAND_REC, 1);
144 rec->cmd = g_strdup(cmd);
145 rec->category = category == NULL ? NULL : g_strdup(category);
146 commands = g_slist_append(commands, rec);
148 modrec = command_module_get(rec, module);
150 modrec->signals = g_slist_append(modrec->signals, func);
153 str = g_strconcat("command ", cmd, NULL);
154 signal_add_to(module, pos, str, func);
158 signal_emit("commandlist new", 1, rec);
161 static void command_free(COMMAND_REC *rec)
163 commands = g_slist_remove(commands, rec);
164 signal_emit("commandlist remove", 1, rec);
166 g_free_not_null(rec->category);
167 g_strfreev(rec->options);
172 static void command_module_free(COMMAND_MODULE_REC *modrec, COMMAND_REC *rec)
174 rec->modules = g_slist_remove(rec->modules, modrec);
176 g_slist_free(modrec->signals);
177 g_free(modrec->name);
178 g_free_not_null(modrec->options);
182 static void command_module_destroy(COMMAND_REC *rec,
183 COMMAND_MODULE_REC *modrec)
185 GSList *tmp, *freelist;
187 command_module_free(modrec, rec);
189 /* command_set_options() might have added module declaration of it's
190 own without any signals .. check if they're the only ones left
191 and if so, destroy them. */
193 for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
194 COMMAND_MODULE_REC *rec = tmp->data;
196 if (rec->signals == NULL)
197 freelist = g_slist_append(freelist, rec);
199 g_slist_free(freelist);
205 g_slist_foreach(freelist, (GFunc) command_module_free, rec);
206 g_slist_free(freelist);
208 if (rec->modules == NULL)
212 void command_unbind(const char *cmd, SIGNAL_FUNC func)
215 COMMAND_MODULE_REC *modrec;
218 g_return_if_fail(cmd != NULL);
219 g_return_if_fail(func != NULL);
221 rec = command_find(cmd);
223 modrec = command_module_find_func(rec, func);
224 modrec->signals = g_slist_remove(modrec->signals, func);
225 if (modrec->signals == NULL)
226 command_module_destroy(rec, modrec);
229 str = g_strconcat("command ", cmd, NULL);
230 signal_remove(str, func);
234 /* Expand `cmd' - returns `cmd' if not found, NULL if more than one
236 static const char *command_expand(char *cmd)
242 g_return_val_if_fail(cmd != NULL, NULL);
247 for (tmp = commands; tmp != NULL; tmp = tmp->next) {
248 COMMAND_REC *rec = tmp->data;
250 if (g_strncasecmp(rec->cmd, cmd, len) == 0 &&
251 strchr(rec->cmd+len, ' ') == NULL) {
252 if (rec->cmd[len] == '\0') {
258 /* multiple matches, we still need to check
259 if there's some command left that is a
264 /* check that this is the only match */
270 signal_emit("error command", 2,
271 GINT_TO_POINTER(CMDERR_AMBIGUOUS), cmd);
275 return match != NULL ? match : cmd;
278 void command_runsub(const char *cmd, const char *data,
279 void *server, void *item)
282 char *orig, *subcmd, *defcmd, *args;
284 g_return_if_fail(data != NULL);
287 /* no subcommand given - list the subcommands */
288 signal_emit("list subcommands", 2, cmd);
293 orig = subcmd = g_strdup_printf("command %s %s", cmd, data);
294 args = strchr(subcmd+8 + strlen(cmd)+1, ' ');
295 if (args != NULL) *args++ = '\0'; else args = "";
296 while (*args == ' ') args++;
298 /* check if this command can be expanded */
299 newcmd = command_expand(subcmd+8);
300 if (newcmd == NULL) {
301 /* ambiguous command */
306 subcmd = g_strconcat("command ", newcmd, NULL);
309 if (!signal_emit(subcmd, 3, args, server, item)) {
310 defcmd = g_strdup_printf("default command %s", cmd);
311 if (!signal_emit(defcmd, 3, data, server, item)) {
312 signal_emit("error command", 2,
313 GINT_TO_POINTER(CMDERR_UNKNOWN), subcmd+8);
322 static GSList *optlist_find(GSList *optlist, const char *option)
324 while (optlist != NULL) {
325 char *name = optlist->data;
326 if (iscmdtype(*name)) name++;
328 if (g_strcasecmp(name, option) == 0)
331 optlist = optlist->next;
337 int command_have_option(const char *cmd, const char *option)
342 g_return_val_if_fail(cmd != NULL, FALSE);
343 g_return_val_if_fail(option != NULL, FALSE);
345 rec = command_find(cmd);
346 g_return_val_if_fail(rec != NULL, FALSE);
348 if (rec->options == NULL)
351 for (tmp = rec->options; *tmp != NULL; tmp++) {
352 char *name = iscmdtype(**tmp) ? (*tmp)+1 : *tmp;
354 if (g_strcasecmp(name, option) == 0)
361 static void command_calc_options(COMMAND_REC *rec, const char *options)
363 char **optlist, **tmp, *name, *str;
364 GSList *list, *oldopt;
366 optlist = g_strsplit(options, " ", -1);
368 if (rec->options == NULL) {
369 /* first call - use specified args directly */
370 rec->options = optlist;
374 /* save old options to linked list */
376 for (tmp = rec->options; *tmp != NULL; tmp++)
377 list = g_slist_append(list, g_strdup(*tmp));
378 g_strfreev(rec->options);
380 /* merge the options */
381 for (tmp = optlist; *tmp != NULL; tmp++) {
382 name = iscmdtype(**tmp) ? (*tmp)+1 : *tmp;
384 oldopt = optlist_find(list, name);
385 if (oldopt != NULL) {
386 /* already specified - overwrite old defination */
387 g_free(oldopt->data);
388 oldopt->data = g_strdup(*tmp);
390 /* new option, append to list */
391 list = g_slist_append(list, g_strdup(*tmp));
396 /* linked list -> string[] */
397 str = gslist_to_string(list, " ");
398 rec->options = g_strsplit(str, " ", -1);
401 g_slist_foreach(list, (GFunc) g_free, NULL);
405 /* recalculate options to command from options in all modules */
406 static void command_update_options(COMMAND_REC *rec)
410 g_strfreev(rec->options);
413 for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
414 COMMAND_MODULE_REC *modrec = tmp->data;
416 if (modrec->options != NULL)
417 command_calc_options(rec, modrec->options);
421 void command_set_options_module(const char *module,
422 const char *cmd, const char *options)
425 COMMAND_MODULE_REC *modrec;
428 g_return_if_fail(module != NULL);
429 g_return_if_fail(cmd != NULL);
430 g_return_if_fail(options != NULL);
432 rec = command_find(cmd);
433 g_return_if_fail(rec != NULL);
434 modrec = command_module_get(rec, module);
436 reload = modrec->options != NULL;
438 /* options already set for the module ..
439 we need to recalculate everything */
440 g_free(modrec->options);
443 modrec->options = g_strdup(options);
446 command_update_options(rec);
448 command_calc_options(rec, options);
451 char *cmd_get_param(char **data)
455 g_return_val_if_fail(data != NULL, NULL);
456 g_return_val_if_fail(*data != NULL, NULL);
458 while (**data == ' ') (*data)++;
461 while (**data != '\0' && **data != ' ') (*data)++;
462 if (**data == ' ') *(*data)++ = '\0';
467 static char *cmd_get_quoted_param(char **data)
471 g_return_val_if_fail(data != NULL, NULL);
472 g_return_val_if_fail(*data != NULL, NULL);
474 while (**data == ' ') (*data)++;
475 if (**data != '\'' && **data != '"')
476 return cmd_get_param(data);
478 quote = **data; (*data)++;
481 while (**data != '\0' && **data != quote) {
482 if (**data == '\\' && (*data)[1] != '\0')
483 g_memmove(*data, (*data)+1, strlen(*data));
487 if (**data != '\0') *(*data)++ = '\0';
492 /* Find specified option from list of options - the `option' might be
493 shortened version of the full command. Returns index where the
494 option was found, -1 if not found or -2 if there was multiple matches. */
495 static int option_find(char **array, const char *option)
498 int index, found, len, multiple;
500 g_return_val_if_fail(array != NULL, -1);
501 g_return_val_if_fail(option != NULL, -1);
503 len = strlen(option);
505 found = -1; index = 0; multiple = FALSE;
506 for (tmp = array; *tmp != NULL; tmp++, index++) {
507 const char *text = *tmp + iscmdtype(**tmp);
509 if (g_strncasecmp(text, option, len) == 0) {
510 if (text[len] == '\0') {
516 /* multiple matches - we still need to check
517 if there's a full match left.. */
521 /* partial match, check that it's the only one */
532 static int get_cmd_options(char **data, int ignore_unknown,
533 const char *cmd, GHashTable *options)
536 char *option, *arg, **optlist;
539 /* get option definations */
540 rec = cmd == NULL ? NULL : command_find(cmd);
541 optlist = rec == NULL ? NULL : rec->options;
543 option = NULL; pos = -1;
546 if (option != NULL && *optlist[pos] == '+') {
547 /* required argument missing! */
548 *data = optlist[pos] + 1;
549 return CMDERR_OPTION_ARG_MISSING;
553 if (**data == '-' && isspace((*data)[1])) {
554 /* -- option means end of options even
555 if next word starts with - */
557 while (isspace(**data)) (*data)++;
561 if (!isspace(**data))
562 option = cmd_get_param(data);
568 /* check if this option can have argument */
569 pos = optlist == NULL ? -1 :
570 option_find(optlist, option);
571 if (pos == -1 && !ignore_unknown) {
572 /* unknown option! */
574 return CMDERR_OPTION_UNKNOWN;
576 if (pos == -2 && !ignore_unknown) {
577 /* multiple matches */
579 return CMDERR_OPTION_AMBIGUOUS;
582 /* if we used a shortcut of parameter, put
583 the whole parameter name in options table */
584 option = optlist[pos] +
585 iscmdtype(*optlist[pos]);
588 g_hash_table_insert(options, option, "");
590 if (pos < 0 || !iscmdtype(*optlist[pos]) ||
591 *optlist[pos] == '!')
594 while (isspace(**data)) (*data)++;
601 if (*optlist[pos] == '@' && !isdigit(**data))
602 break; /* expected a numeric argument */
604 /* save the argument */
605 arg = cmd_get_quoted_param(data);
606 if (options != NULL) {
607 g_hash_table_remove(options, option);
608 g_hash_table_insert(options, option, arg);
612 while (isspace(**data)) (*data)++;
623 static char *get_optional_channel(WI_ITEM_REC *active_item, char **data)
625 CHANNEL_REC *chanrec;
626 char *tmp, *origtmp, *channel, *ret;
628 if (active_item == NULL) {
629 /* no active channel in window, channel required */
630 return cmd_get_param(data);
633 origtmp = tmp = g_strdup(*data);
634 channel = cmd_get_param(&tmp);
636 if (strcmp(channel, "*") == 0 ||
637 !active_item->server->ischannel(channel))
638 ret = active_item->name;
640 /* Find the channel first and use it's name if found.
641 This allows automatic !channel -> !XXXXXchannel replaces. */
642 channel = cmd_get_param(data);
644 chanrec = channel_find(active_item->server, channel);
645 ret = chanrec == NULL ? channel : chanrec->name;
652 int cmd_get_params(const char *data, gpointer *free_me, int count, ...)
656 GHashTable **opthash;
657 char **str, *arg, *datad;
659 int cnt, error, ignore_unknown;
661 g_return_val_if_fail(data != NULL, FALSE);
663 va_start(args, count);
665 rec = g_new0(CMD_TEMP_REC, 1);
666 rec->data = g_strdup(data);
672 item = (count & PARAM_FLAG_OPTCHAN) == 0 ? NULL:
673 (WI_ITEM_REC *) va_arg(args, WI_ITEM_REC *);
675 if (count & PARAM_FLAG_OPTIONS) {
676 arg = (char *) va_arg(args, char *);
677 opthash = (GHashTable **) va_arg(args, GHashTable **);
679 rec->options = *opthash =
680 g_hash_table_new((GHashFunc) g_istr_hash,
681 (GCompareFunc) g_istr_equal);
683 ignore_unknown = count & PARAM_FLAG_UNKNOWN_OPTIONS;
684 error = get_cmd_options(&datad, ignore_unknown,
689 /* and now handle the string */
690 cnt = PARAM_WITHOUT_FLAGS(count);
691 if (count & PARAM_FLAG_OPTCHAN) {
692 /* optional channel as first parameter */
693 arg = get_optional_channel(item, &datad);
695 str = (char **) va_arg(args, char **);
696 if (str != NULL) *str = arg;
701 if (cnt == 0 && count & PARAM_FLAG_GETREST) {
705 arg = (count & PARAM_FLAG_NOQUOTES) ?
706 cmd_get_param(&datad) :
707 cmd_get_quoted_param(&datad);
710 str = (char **) va_arg(args, char **);
711 if (str != NULL) *str = arg;
717 signal_emit("error command", 2, GINT_TO_POINTER(error), datad);
720 cmd_params_free(rec);
727 void cmd_params_free(void *free_me)
729 CMD_TEMP_REC *rec = free_me;
731 if (rec->options != NULL) g_hash_table_destroy(rec->options);
736 static void command_module_unbind_all(COMMAND_REC *rec,
737 COMMAND_MODULE_REC *modrec)
741 for (tmp = modrec->signals; tmp != NULL; tmp = next) {
744 command_unbind(rec->cmd, tmp->data);
747 if (g_slist_find(commands, rec) != NULL) {
748 /* this module might have removed some options
749 from command, update them. */
750 command_update_options(rec);
754 void commands_remove_module(const char *module)
756 GSList *tmp, *next, *modlist;
758 g_return_if_fail(module != NULL);
760 for (tmp = commands; tmp != NULL; tmp = next) {
761 COMMAND_REC *rec = tmp->data;
764 modlist = gslist_find_string(rec->modules, module);
766 command_module_unbind_all(rec, modlist->data);
770 #define alias_runstack_push(alias) \
771 alias_runstack = g_slist_append(alias_runstack, alias)
773 #define alias_runstack_pop(alias) \
774 alias_runstack = g_slist_remove(alias_runstack, alias)
776 #define alias_runstack_find(alias) \
777 (gslist_find_icase_string(alias_runstack, alias) != NULL)
779 static void parse_command(const char *command, int expand_aliases,
780 SERVER_REC *server, void *item)
782 const char *alias, *newcmd;
783 char *cmd, *orig, *args, *oldcmd;
785 g_return_if_fail(command != NULL);
787 cmd = orig = g_strconcat("command ", command, NULL);
788 args = strchr(cmd+8, ' ');
789 if (args != NULL) *args++ = '\0'; else args = "";
791 /* check if there's an alias for command. Don't allow
793 alias = !expand_aliases || alias_runstack_find(cmd+8) ? NULL :
796 alias_runstack_push(cmd+8);
797 eval_special_string(alias, args, server, item);
798 alias_runstack_pop(cmd+8);
803 /* check if this command can be expanded */
804 newcmd = command_expand(cmd+8);
805 if (newcmd == NULL) {
806 /* ambiguous command */
811 cmd = g_strconcat("command ", newcmd, NULL);
813 server_redirect_default(SERVER(server), cmd);
816 oldcmd = current_command;
817 current_command = cmd+8;
818 if (!signal_emit(cmd, 3, args, server, item)) {
819 signal_emit_id(signal_default_command, 3,
820 command, server, item);
822 current_command = oldcmd;
828 static void event_command(const char *line, SERVER_REC *server, void *item)
831 int expand_aliases = TRUE;
833 g_return_if_fail(line != NULL);
836 /* empty line, forget it. */
841 cmdchar = strchr(settings_get_str("cmdchars"), *line);
842 if (cmdchar != NULL && line[1] == ' ') {
843 /* "/ text" = same as sending "text" to active channel. */
847 if (cmdchar == NULL) {
848 /* non-command - let someone else handle this */
849 signal_emit("send text", 3, line, server, item);
853 /* same cmdchar twice ignores aliases ignores aliases */
855 if (*line == *cmdchar) {
857 expand_aliases = FALSE;
860 /* ^command hides the output - we'll do this at fe-common but
861 we have to skip the ^ char here.. */
862 if (*line == '^') line++;
864 parse_command(line, expand_aliases, server, item);
867 /* SYNTAX: EVAL <command(s)> */
868 static void cmd_eval(const char *data, SERVER_REC *server, void *item)
870 g_return_if_fail(data != NULL);
872 eval_special_string(data, "", server, item);
875 /* SYNTAX: CD <directory> */
876 static void cmd_cd(const char *data)
880 g_return_if_fail(data != NULL);
881 if (*data == '\0') return;
883 str = convert_home(data);
888 void commands_init(void)
891 current_command = NULL;
892 alias_runstack = NULL;
894 signal_default_command = signal_get_uniq_id("default command");
896 settings_add_str("misc", "cmdchars", "/");
897 signal_add("send command", (SIGNAL_FUNC) event_command);
899 command_bind("eval", NULL, (SIGNAL_FUNC) cmd_eval);
900 command_bind("cd", NULL, (SIGNAL_FUNC) cmd_cd);
903 void commands_deinit(void)
905 g_free_not_null(current_command);
907 signal_remove("send command", (SIGNAL_FUNC) event_command);
909 command_unbind("eval", (SIGNAL_FUNC) cmd_eval);
910 command_unbind("cd", (SIGNAL_FUNC) cmd_cd);