Merged with Irssi 0.8.6.
[silc.git] / apps / irssi / src / core / commands.c
1 /*
2  commands.c : irssi
3
4     Copyright (C) 1999-2000 Timo Sirainen
5
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.
10
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.
15
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
19 */
20
21 #include "module.h"
22 #include "signals.h"
23 #include "commands.h"
24 #include "misc.h"
25 #include "special-vars.h"
26 #include "window-item-def.h"
27
28 #include "servers.h"
29 #include "channels.h"
30
31 #include "lib-config/iconfig.h"
32 #include "settings.h"
33
34 GSList *commands;
35 char *current_command;
36
37 static int signal_default_command;
38
39 static GSList *alias_runstack;
40
41 COMMAND_REC *command_find(const char *cmd)
42 {
43         GSList *tmp;
44
45         g_return_val_if_fail(cmd != NULL, NULL);
46
47         for (tmp = commands; tmp != NULL; tmp = tmp->next) {
48                 COMMAND_REC *rec = tmp->data;
49
50                 if (g_strcasecmp(rec->cmd, cmd) == 0)
51                         return rec;
52         }
53
54         return NULL;
55 }
56
57 static COMMAND_MODULE_REC *command_module_find(COMMAND_REC *rec,
58                                                const char *module)
59 {
60         GSList *tmp;
61
62         g_return_val_if_fail(rec != NULL, NULL);
63         g_return_val_if_fail(module != NULL, NULL);
64
65         for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
66                 COMMAND_MODULE_REC *rec = tmp->data;
67
68                 if (g_strcasecmp(rec->name, module) == 0)
69                         return rec;
70         }
71
72         return NULL;
73 }
74
75 static COMMAND_MODULE_REC *
76 command_module_find_and_remove(COMMAND_REC *rec, SIGNAL_FUNC func)
77 {
78         GSList *tmp, *tmp2;
79
80         g_return_val_if_fail(rec != NULL, NULL);
81         g_return_val_if_fail(func != NULL, NULL);
82
83         for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
84                 COMMAND_MODULE_REC *rec = tmp->data;
85
86                 for (tmp2 = rec->callbacks; tmp2 != NULL; tmp2 = tmp2->next) {
87                         COMMAND_CALLBACK_REC *cb = tmp2->data;
88
89                         if (cb->func == func) {
90                                 rec->callbacks =
91                                         g_slist_remove(rec->callbacks, cb);
92                                 return rec;
93                         }
94                 }
95         }
96
97         return NULL;
98 }
99
100 int command_have_sub(const char *command)
101 {
102         GSList *tmp;
103         int len;
104
105         g_return_val_if_fail(command != NULL, FALSE);
106
107         /* find "command "s */
108         len = strlen(command);
109         for (tmp = commands; tmp != NULL; tmp = tmp->next) {
110                 COMMAND_REC *rec = tmp->data;
111
112                 if (g_strncasecmp(rec->cmd, command, len) == 0 &&
113                     rec->cmd[len] == ' ')
114                         return TRUE;
115         }
116
117         return FALSE;
118 }
119
120 static COMMAND_MODULE_REC *
121 command_module_get(COMMAND_REC *rec, const char *module, int protocol)
122 {
123         COMMAND_MODULE_REC *modrec;
124
125         g_return_val_if_fail(rec != NULL, NULL);
126
127         modrec = command_module_find(rec, module);
128         if (modrec == NULL) {
129                 modrec = g_new0(COMMAND_MODULE_REC, 1);
130                 modrec->name = g_strdup(module);
131                 modrec->protocol = -1;
132                 rec->modules = g_slist_append(rec->modules, modrec);
133         }
134
135         if (protocol != -1)
136                 modrec->protocol = protocol;
137
138         return modrec;
139 }
140
141 void command_bind_full(const char *module, int priority, const char *cmd,
142                        int protocol, const char *category, SIGNAL_FUNC func,
143                        void *user_data)
144 {
145         COMMAND_REC *rec;
146         COMMAND_MODULE_REC *modrec;
147         COMMAND_CALLBACK_REC *cb;
148         char *str;
149
150         g_return_if_fail(module != NULL);
151         g_return_if_fail(cmd != NULL);
152
153         rec = command_find(cmd);
154         if (rec == NULL) {
155                 rec = g_new0(COMMAND_REC, 1);
156                 rec->cmd = g_strdup(cmd);
157                 rec->category = category == NULL ? NULL : g_strdup(category);
158                 commands = g_slist_append(commands, rec);
159         }
160         modrec = command_module_get(rec, module, protocol);
161
162         cb = g_new0(COMMAND_CALLBACK_REC, 1);
163         cb->func = func;
164         cb->user_data = user_data;
165         modrec->callbacks = g_slist_append(modrec->callbacks, cb);
166
167         if (func != NULL) {
168                 str = g_strconcat("command ", cmd, NULL);
169                 signal_add_full(module, priority, str, func, user_data);
170                 g_free(str);
171         }
172
173         signal_emit("commandlist new", 1, rec);
174 }
175
176 static void command_free(COMMAND_REC *rec)
177 {
178         commands = g_slist_remove(commands, rec);
179         signal_emit("commandlist remove", 1, rec);
180
181         g_free_not_null(rec->category);
182         g_strfreev(rec->options);
183         g_free(rec->cmd);
184         g_free(rec);
185 }
186
187 static void command_module_free(COMMAND_MODULE_REC *modrec, COMMAND_REC *rec)
188 {
189         rec->modules = g_slist_remove(rec->modules, modrec);
190
191         g_slist_foreach(modrec->callbacks, (GFunc) g_free, NULL);
192         g_slist_free(modrec->callbacks);
193         g_free(modrec->name);
194         g_free_not_null(modrec->options);
195         g_free(modrec);
196 }
197
198 static void command_module_destroy(COMMAND_REC *rec,
199                                    COMMAND_MODULE_REC *modrec)
200 {
201         GSList *tmp, *freelist;
202
203         command_module_free(modrec, rec);
204
205         /* command_set_options() might have added module declaration of it's
206            own without any signals .. check if they're the only ones left
207            and if so, destroy them. */
208         freelist = NULL;
209         for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
210                 COMMAND_MODULE_REC *rec = tmp->data;
211
212                 if (rec->callbacks == NULL)
213                         freelist = g_slist_append(freelist, rec);
214                 else {
215                         g_slist_free(freelist);
216                         freelist = NULL;
217                         break;
218                 }
219         }
220
221         g_slist_foreach(freelist, (GFunc) command_module_free, rec);
222         g_slist_free(freelist);
223
224         if (rec->modules == NULL)
225                 command_free(rec);
226 }
227
228 void command_unbind_full(const char *cmd, SIGNAL_FUNC func, void *user_data)
229 {
230         COMMAND_REC *rec;
231         COMMAND_MODULE_REC *modrec;
232         char *str;
233
234         g_return_if_fail(cmd != NULL);
235         g_return_if_fail(func != NULL);
236
237         rec = command_find(cmd);
238         if (rec != NULL) {
239                 modrec = command_module_find_and_remove(rec, func);
240                 g_return_if_fail(modrec != NULL);
241
242                 if (modrec->callbacks == NULL)
243                         command_module_destroy(rec, modrec);
244         }
245
246         str = g_strconcat("command ", cmd, NULL);
247         signal_remove_data(str, func, user_data);
248         g_free(str);
249 }
250
251 /* Expand `cmd' - returns `cmd' if not found, NULL if more than one
252    match is found */
253 static const char *command_expand(char *cmd)
254 {
255         GSList *tmp;
256         const char *match;
257         int len, multiple;
258
259         g_return_val_if_fail(cmd != NULL, NULL);
260
261         multiple = FALSE;
262         match = NULL;
263         len = strlen(cmd);
264         for (tmp = commands; tmp != NULL; tmp = tmp->next) {
265                 COMMAND_REC *rec = tmp->data;
266
267                 if (g_strncasecmp(rec->cmd, cmd, len) == 0 &&
268                     strchr(rec->cmd+len, ' ') == NULL) {
269                         if (rec->cmd[len] == '\0') {
270                                 /* full match */
271                                 return rec->cmd;
272                         }
273
274                         if (match != NULL) {
275                                 /* multiple matches, we still need to check
276                                    if there's some command left that is a
277                                    full match.. */
278                                 multiple = TRUE;
279                         }
280
281                         /* check that this is the only match */
282                         match = rec->cmd;
283                 }
284         }
285
286         if (multiple) {
287                 signal_emit("error command", 2,
288                             GINT_TO_POINTER(CMDERR_AMBIGUOUS), cmd);
289                 return NULL;
290         }
291
292         return match != NULL ? match : cmd;
293 }
294
295 void command_runsub(const char *cmd, const char *data,
296                     void *server, void *item)
297 {
298         const char *newcmd;
299         char *orig, *subcmd, *defcmd, *args;
300
301         g_return_if_fail(data != NULL);
302
303         while (*data == ' ') data++;
304
305         if (*data == '\0') {
306                 /* no subcommand given - list the subcommands */
307                 signal_emit("list subcommands", 2, cmd);
308                 return;
309         }
310
311         /* get command.. */
312         orig = subcmd = g_strdup_printf("command %s %s", cmd, data);
313         args = strchr(subcmd+8 + strlen(cmd)+1, ' ');
314         if (args != NULL) *args++ = '\0'; else args = "";
315         while (*args == ' ') args++;
316
317         /* check if this command can be expanded */
318         newcmd = command_expand(subcmd+8);
319         if (newcmd == NULL) {
320                 /* ambiguous command */
321                 g_free(orig);
322                 return;
323         }
324
325         subcmd = g_strconcat("command ", newcmd, NULL);
326
327         g_strdown(subcmd);
328         if (!signal_emit(subcmd, 3, args, server, item)) {
329                 defcmd = g_strdup_printf("default command %s", cmd);
330                 if (!signal_emit(defcmd, 3, data, server, item)) {
331                         signal_emit("error command", 2,
332                                     GINT_TO_POINTER(CMDERR_UNKNOWN), subcmd+8);
333                 }
334                 g_free(defcmd);
335         }
336
337         g_free(subcmd);
338         g_free(orig);
339 }
340
341 static GSList *optlist_find(GSList *optlist, const char *option)
342 {
343         while (optlist != NULL) {
344                 char *name = optlist->data;
345                 if (iscmdtype(*name)) name++;
346
347                 if (g_strcasecmp(name, option) == 0)
348                         return optlist;
349
350                 optlist = optlist->next;
351         }
352
353         return NULL;
354 }
355
356 int command_have_option(const char *cmd, const char *option)
357 {
358         COMMAND_REC *rec;
359         char **tmp;
360
361         g_return_val_if_fail(cmd != NULL, FALSE);
362         g_return_val_if_fail(option != NULL, FALSE);
363
364         rec = command_find(cmd);
365         g_return_val_if_fail(rec != NULL, FALSE);
366
367         if (rec->options == NULL)
368                 return FALSE;
369
370         for (tmp = rec->options; *tmp != NULL; tmp++) {
371                 char *name = iscmdtype(**tmp) ? (*tmp)+1 : *tmp;
372
373                 if (g_strcasecmp(name, option) == 0)
374                         return TRUE;
375         }
376
377         return FALSE;
378 }
379
380 static void command_calc_options(COMMAND_REC *rec, const char *options)
381 {
382         char **optlist, **tmp, *name, *str;
383         GSList *list, *oldopt;
384
385         optlist = g_strsplit(options, " ", -1);
386
387         if (rec->options == NULL) {
388                 /* first call - use specified args directly */
389                 rec->options = optlist;
390                 return;
391         }
392
393         /* save old options to linked list */
394         list = NULL;
395         for (tmp = rec->options; *tmp != NULL; tmp++)
396                 list = g_slist_append(list, g_strdup(*tmp));
397         g_strfreev(rec->options);
398
399         /* merge the options */
400         for (tmp = optlist; *tmp != NULL; tmp++) {
401                 name = iscmdtype(**tmp) ? (*tmp)+1 : *tmp;
402
403                 oldopt = optlist_find(list, name);
404                 if (oldopt != NULL) {
405                         /* already specified - overwrite old defination */
406                         g_free(oldopt->data);
407                         oldopt->data = g_strdup(*tmp);
408                 } else {
409                         /* new option, append to list */
410                         list = g_slist_append(list, g_strdup(*tmp));
411                 }
412         }
413         g_strfreev(optlist);
414
415         /* linked list -> string[] */
416         str = gslist_to_string(list, " ");
417         rec->options = g_strsplit(str, " ", -1);
418         g_free(str);
419
420         g_slist_foreach(list, (GFunc) g_free, NULL);
421         g_slist_free(list);
422 }
423
424 /* recalculate options to command from options in all modules */
425 static void command_update_options(COMMAND_REC *rec)
426 {
427         GSList *tmp;
428
429         g_strfreev(rec->options);
430         rec->options = NULL;
431
432         for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
433                 COMMAND_MODULE_REC *modrec = tmp->data;
434
435                 if (modrec->options != NULL)
436                         command_calc_options(rec, modrec->options);
437         }
438 }
439
440 void command_set_options_module(const char *module,
441                                 const char *cmd, const char *options)
442 {
443         COMMAND_REC *rec;
444         COMMAND_MODULE_REC *modrec;
445         int reload;
446
447         g_return_if_fail(module != NULL);
448         g_return_if_fail(cmd != NULL);
449         g_return_if_fail(options != NULL);
450
451         rec = command_find(cmd);
452         g_return_if_fail(rec != NULL);
453         modrec = command_module_get(rec, module, -1);
454
455         reload = modrec->options != NULL;
456         if (reload) {
457                 /* options already set for the module ..
458                    we need to recalculate everything */
459                 g_free(modrec->options);
460         }
461
462         modrec->options = g_strdup(options);
463
464         if (reload)
465                 command_update_options(rec);
466         else
467                 command_calc_options(rec, options);
468 }
469
470 char *cmd_get_param(char **data)
471 {
472         char *pos;
473
474         g_return_val_if_fail(data != NULL, NULL);
475         g_return_val_if_fail(*data != NULL, NULL);
476
477         while (**data == ' ') (*data)++;
478         pos = *data;
479
480         while (**data != '\0' && **data != ' ') (*data)++;
481         if (**data == ' ') *(*data)++ = '\0';
482
483         return pos;
484 }
485
486 char *cmd_get_quoted_param(char **data)
487 {
488         char *pos, quote;
489
490         g_return_val_if_fail(data != NULL, NULL);
491         g_return_val_if_fail(*data != NULL, NULL);
492
493         while (**data == ' ') (*data)++;
494         if (**data != '\'' && **data != '"')
495                 return cmd_get_param(data);
496
497         quote = **data; (*data)++;
498
499         pos = *data;
500         while (**data != '\0' && (**data != quote ||
501                                   ((*data)[1] != ' ' && (*data)[1] != '\0'))) {
502                 if (**data == '\\' && (*data)[1] != '\0')
503                         g_memmove(*data, (*data)+1, strlen(*data));
504                 (*data)++;
505         }
506
507         if (**data == quote) {
508                 *(*data)++ = '\0';
509                 if (**data == ' ')
510                         (*data)++;
511         }
512
513         return pos;
514 }
515
516 /* Find specified option from list of options - the `option' might be
517    shortened version of the full command. Returns index where the
518    option was found, -1 if not found or -2 if there was multiple matches. */
519 static int option_find(char **array, const char *option)
520 {
521         char **tmp;
522         int index, found, len, multiple;
523
524         g_return_val_if_fail(array != NULL, -1);
525         g_return_val_if_fail(option != NULL, -1);
526
527         len = strlen(option);
528
529         found = -1; index = 0; multiple = FALSE;
530         for (tmp = array; *tmp != NULL; tmp++, index++) {
531                 const char *text = *tmp + iscmdtype(**tmp);
532
533                 if (g_strncasecmp(text, option, len) == 0) {
534                         if (text[len] == '\0') {
535                                 /* full match */
536                                 return index;
537                         }
538
539                         if (found != -1) {
540                                 /* multiple matches - we still need to check
541                                    if there's a full match left.. */
542                                 multiple = TRUE;
543                         }
544
545                         /* partial match, check that it's the only one */
546                         found = index;
547                 }
548         }
549
550         if (multiple)
551                 return -2;
552
553         return found;
554 }
555
556 static int get_cmd_options(char **data, int ignore_unknown,
557                            const char *cmd, GHashTable *options)
558 {
559         COMMAND_REC *rec;
560         char *option, *arg, **optlist;
561         int pos;
562
563         /* get option definations */
564         rec = cmd == NULL ? NULL : command_find(cmd);
565         optlist = rec == NULL ? NULL : rec->options;
566
567         option = NULL; pos = -1;
568         for (;;) {
569                 if (**data == '-') {
570                         if (option != NULL && *optlist[pos] == '+') {
571                                 /* required argument missing! */
572                                 *data = optlist[pos] + 1;
573                                 return CMDERR_OPTION_ARG_MISSING;
574                         }
575
576                         (*data)++;
577                         if (**data == '-' && i_isspace((*data)[1])) {
578                                 /* -- option means end of options even
579                                    if next word starts with - */
580                                 (*data)++;
581                                 while (i_isspace(**data)) (*data)++;
582                                 break;
583                         }
584
585                         if (**data == '\0')
586                                 option = "-";
587                         else if (!i_isspace(**data))
588                                 option = cmd_get_param(data);
589                         else {
590                                 option = "-";
591                                 (*data)++;
592                         }
593
594                         /* check if this option can have argument */
595                         pos = optlist == NULL ? -1 :
596                                 option_find(optlist, option);
597
598                         if (pos == -1 && optlist != NULL &&
599                             is_numeric(option, '\0')) {
600                                 /* check if we want -<number> option */
601                                 pos = option_find(optlist, "#");
602                                 if (pos != -1) {
603                                         g_hash_table_insert(options, "#",
604                                                             option);
605                                         pos = -3;
606                                 }
607                         }
608
609                         if (pos == -1 && !ignore_unknown) {
610                                 /* unknown option! */
611                                 *data = option;
612                                 return CMDERR_OPTION_UNKNOWN;
613                         }
614                         if (pos == -2 && !ignore_unknown) {
615                                 /* multiple matches */
616                                 *data = option;
617                                 return CMDERR_OPTION_AMBIGUOUS;
618                         }
619                         if (pos >= 0) {
620                                 /* if we used a shortcut of parameter, put
621                                    the whole parameter name in options table */
622                                 option = optlist[pos] +
623                                         iscmdtype(*optlist[pos]);
624                         }
625                         if (options != NULL && pos != -3)
626                                 g_hash_table_insert(options, option, "");
627
628                         if (pos < 0 || !iscmdtype(*optlist[pos]) ||
629                             *optlist[pos] == '!')
630                                 option = NULL;
631
632                         while (i_isspace(**data)) (*data)++;
633                         continue;
634                 }
635
636                 if (option == NULL)
637                         break;
638
639                 if (*optlist[pos] == '@' && !i_isdigit(**data))
640                         break; /* expected a numeric argument */
641
642                 /* save the argument */
643                 arg = cmd_get_quoted_param(data);
644                 if (options != NULL) {
645                         g_hash_table_remove(options, option);
646                         g_hash_table_insert(options, option, arg);
647                 }
648                 option = NULL;
649
650                 while (i_isspace(**data)) (*data)++;
651         }
652
653         return 0;
654 }
655
656 typedef struct {
657         char *data;
658         GHashTable *options;
659 } CMD_TEMP_REC;
660
661 static const char *
662 get_optional_channel(WI_ITEM_REC *active_item, char **data, int require_name)
663 {
664         CHANNEL_REC *chanrec;
665         const char *ret;
666         char *tmp, *origtmp, *channel;
667
668         if (active_item == NULL) {
669                 /* no active channel in window, channel required */
670                 return cmd_get_param(data);
671         }
672
673         origtmp = tmp = g_strdup(*data);
674         channel = cmd_get_param(&tmp);
675
676         if (strcmp(channel, "*") == 0 && !require_name) {
677                 /* "*" means active channel */
678                 cmd_get_param(data);
679                 ret = window_item_get_target(active_item);
680         } else if (!server_ischannel(active_item->server, channel)) {
681                 /* we don't have channel parameter - use active channel */
682                 ret = window_item_get_target(active_item);
683         } else {
684                 /* Find the channel first and use it's name if found.
685                    This allows automatic !channel -> !XXXXXchannel replaces. */
686                 channel = cmd_get_param(data);
687
688                 chanrec = channel_find(active_item->server, channel);
689                 ret = chanrec == NULL ? channel : chanrec->name;
690         }
691
692         g_free(origtmp);
693         return ret;
694 }
695
696 int cmd_get_params(const char *data, gpointer *free_me, int count, ...)
697 {
698         WI_ITEM_REC *item;
699         CMD_TEMP_REC *rec;
700         GHashTable **opthash;
701         char **str, *arg, *datad;
702         va_list args;
703         int cnt, error, ignore_unknown, require_name;
704
705         g_return_val_if_fail(data != NULL, FALSE);
706
707         va_start(args, count);
708
709         rec = g_new0(CMD_TEMP_REC, 1);
710         rec->data = g_strdup(data);
711         *free_me = rec;
712
713         datad = rec->data;
714         error = FALSE;
715
716         item = (count & PARAM_FLAG_OPTCHAN) == 0 ? NULL:
717                 (WI_ITEM_REC *) va_arg(args, WI_ITEM_REC *);
718
719         if (count & PARAM_FLAG_OPTIONS) {
720                 arg = (char *) va_arg(args, char *);
721                 opthash = (GHashTable **) va_arg(args, GHashTable **);
722
723                 rec->options = *opthash =
724                         g_hash_table_new((GHashFunc) g_istr_hash,
725                                          (GCompareFunc) g_istr_equal);
726
727                 ignore_unknown = count & PARAM_FLAG_UNKNOWN_OPTIONS;
728                 error = get_cmd_options(&datad, ignore_unknown,
729                                         arg, *opthash);
730         }
731
732         if (!error) {
733                 /* and now handle the string */
734                 cnt = PARAM_WITHOUT_FLAGS(count);
735                 if (count & PARAM_FLAG_OPTCHAN) {
736                         /* optional channel as first parameter */
737                         require_name = (count & PARAM_FLAG_OPTCHAN_NAME) ==
738                                 PARAM_FLAG_OPTCHAN_NAME;
739                         arg = (char *) get_optional_channel(item, &datad, require_name);
740
741                         str = (char **) va_arg(args, char **);
742                         if (str != NULL) *str = arg;
743                         cnt--;
744                 }
745
746                 while (cnt-- > 0) {
747                         if (cnt == 0 && count & PARAM_FLAG_GETREST) {
748                                 /* get rest */
749                                 arg = datad;
750                         } else {
751                                 arg = (count & PARAM_FLAG_NOQUOTES) ?
752                                         cmd_get_param(&datad) :
753                                         cmd_get_quoted_param(&datad);
754                         }
755
756                         str = (char **) va_arg(args, char **);
757                         if (str != NULL) *str = arg;
758                 }
759         }
760         va_end(args);
761
762         if (error) {
763                 signal_emit("error command", 2, GINT_TO_POINTER(error), datad);
764                 signal_stop();
765
766                 cmd_params_free(rec);
767                 *free_me = NULL;
768         }
769
770         return !error;
771 }
772
773 void cmd_params_free(void *free_me)
774 {
775         CMD_TEMP_REC *rec = free_me;
776
777         if (rec->options != NULL) g_hash_table_destroy(rec->options);
778         g_free(rec->data);
779         g_free(rec);
780 }
781
782 static void command_module_unbind_all(COMMAND_REC *rec,
783                                       COMMAND_MODULE_REC *modrec)
784 {
785         GSList *tmp, *next;
786
787         for (tmp = modrec->callbacks; tmp != NULL; tmp = next) {
788                 COMMAND_CALLBACK_REC *cb = tmp->data;
789                 next = tmp->next;
790
791                 command_unbind_full(rec->cmd, cb->func, cb->user_data);
792         }
793
794         if (g_slist_find(commands, rec) != NULL) {
795                 /* this module might have removed some options
796                    from command, update them. */
797                 command_update_options(rec);
798         }
799 }
800
801 void commands_remove_module(const char *module)
802 {
803         GSList *tmp, *next, *modlist;
804
805         g_return_if_fail(module != NULL);
806
807         for (tmp = commands; tmp != NULL; tmp = next) {
808                 COMMAND_REC *rec = tmp->data;
809
810                 next = tmp->next;
811                 modlist = gslist_find_string(rec->modules, module);
812                 if (modlist != NULL)
813                         command_module_unbind_all(rec, modlist->data);
814         }
815 }
816
817 static int cmd_protocol_match(COMMAND_REC *cmd, SERVER_REC *server)
818 {
819         GSList *tmp;
820
821         for (tmp = cmd->modules; tmp != NULL; tmp = tmp->next) {
822                 COMMAND_MODULE_REC *rec = tmp->data;
823
824                 if (rec->protocol == -1) {
825                         /* at least one module accepts the command
826                            without specific protocol */
827                         return 1;
828                 }
829
830                 if (server != NULL && rec->protocol == server->chat_type) {
831                         /* matching protocol found */
832                         return 1;
833                 }
834         }
835
836         return 0;
837 }
838
839 #define alias_runstack_push(alias) \
840         alias_runstack = g_slist_append(alias_runstack, alias)
841
842 #define alias_runstack_pop(alias) \
843         alias_runstack = g_slist_remove(alias_runstack, alias)
844
845 #define alias_runstack_find(alias) \
846         (gslist_find_icase_string(alias_runstack, alias) != NULL)
847
848 static void parse_command(const char *command, int expand_aliases,
849                           SERVER_REC *server, void *item)
850 {
851         COMMAND_REC *rec;
852         const char *alias, *newcmd;
853         char *cmd, *orig, *args, *oldcmd;
854
855         g_return_if_fail(command != NULL);
856
857         cmd = orig = g_strconcat("command ", command, NULL);
858         args = strchr(cmd+8, ' ');
859         if (args != NULL) *args++ = '\0'; else args = "";
860
861         /* check if there's an alias for command. Don't allow
862            recursive aliases */
863         alias = !expand_aliases || alias_runstack_find(cmd+8) ? NULL :
864                 alias_find(cmd+8);
865         if (alias != NULL) {
866                 alias_runstack_push(cmd+8);
867                 eval_special_string(alias, args, server, item);
868                 alias_runstack_pop(cmd+8);
869                 g_free(orig);
870                 return;
871         }
872
873         /* check if this command can be expanded */
874         newcmd = command_expand(cmd+8);
875         if (newcmd == NULL) {
876                 /* ambiguous command */
877                 g_free(orig);
878                 return;
879         }
880
881         rec = command_find(newcmd);
882         if (rec != NULL && !cmd_protocol_match(rec, server)) {
883                 g_free(orig);
884
885                 signal_emit("error command", 2,
886                             GINT_TO_POINTER(server == NULL ?
887                                             CMDERR_NOT_CONNECTED :
888                                             CMDERR_ILLEGAL_PROTO));
889                 return;
890         }
891
892         cmd = g_strconcat("command ", newcmd, NULL);
893         g_strdown(cmd);
894
895         oldcmd = current_command;
896         current_command = cmd+8;
897         if (server != NULL) server_ref(server);
898         if (!signal_emit(cmd, 3, args, server, item)) {
899                 signal_emit_id(signal_default_command, 3,
900                                command, server, item);
901         }
902         if (server != NULL) {
903                 if (server->connection_lost)
904                         server_disconnect(server);
905                 server_unref(server);
906         }
907         current_command = oldcmd;
908
909         g_free(cmd);
910         g_free(orig);
911 }
912
913 static void event_command(const char *line, SERVER_REC *server, void *item)
914 {
915         char *cmdchar;
916         int expand_aliases = TRUE;
917
918         g_return_if_fail(line != NULL);
919
920         cmdchar = *line == '\0' ? NULL :
921                 strchr(settings_get_str("cmdchars"), *line);
922         if (cmdchar != NULL && line[1] == ' ') {
923                 /* "/ text" = same as sending "text" to active channel. */
924                 line += 2;
925                 cmdchar = NULL;
926         }
927         if (cmdchar == NULL) {
928                 /* non-command - let someone else handle this */
929                 signal_emit("send text", 3, line, server, item);
930                 return;
931         }
932
933         /* same cmdchar twice ignores aliases ignores aliases */
934         line++;
935         if (*line == *cmdchar) {
936                 line++;
937                 expand_aliases = FALSE;
938         }
939
940         /* ^command hides the output - we'll do this at fe-common but
941            we have to skip the ^ char here.. */
942         if (*line == '^') line++;
943
944         parse_command(line, expand_aliases, server, item);
945 }
946
947 /* SYNTAX: EVAL <command(s)> */
948 static void cmd_eval(const char *data, SERVER_REC *server, void *item)
949 {
950         g_return_if_fail(data != NULL);
951
952         eval_special_string(data, "", server, item);
953 }
954
955 /* SYNTAX: CD <directory> */
956 static void cmd_cd(const char *data)
957 {
958         char *str;
959
960         g_return_if_fail(data != NULL);
961         if (*data == '\0') return;
962
963         str = convert_home(data);
964         chdir(str);
965         g_free(str);
966 }
967
968 void commands_init(void)
969 {
970         commands = NULL;
971         current_command = NULL;
972         alias_runstack = NULL;
973
974         signal_default_command = signal_get_uniq_id("default command");
975
976         settings_add_str("misc", "cmdchars", "/");
977         signal_add("send command", (SIGNAL_FUNC) event_command);
978
979         command_bind("eval", NULL, (SIGNAL_FUNC) cmd_eval);
980         command_bind("cd", NULL, (SIGNAL_FUNC) cmd_cd);
981 }
982
983 void commands_deinit(void)
984 {
985         g_free_not_null(current_command);
986
987         signal_remove("send command", (SIGNAL_FUNC) event_command);
988
989         command_unbind("eval", (SIGNAL_FUNC) cmd_eval);
990         command_unbind("cd", (SIGNAL_FUNC) cmd_cd);
991 }