4 Copyright (C) 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
23 #include "special-vars.h"
29 #define ALIGN_RIGHT 0x01
30 #define ALIGN_CUT 0x02
31 #define ALIGN_PAD 0x04
33 #define isvarchar(c) \
34 (i_isalnum(c) || (c) == '_')
37 (i_isdigit(c) || (c) == '*' || (c) == '~' || (c) == '-')
39 static SPECIAL_HISTORY_FUNC history_func = NULL;
41 static char *get_argument(char **cmd, char **arglist)
45 int max, arg, argcount;
50 argcount = arglist == NULL ? 0 : strarray_length(arglist);
53 /* get all arguments */
54 } else if (**cmd == '~') {
55 /* get last argument */
56 arg = max = argcount-1;
58 if (i_isdigit(**cmd)) {
60 arg = max = (**cmd)-'0';
65 /* get more than one argument */
67 if (!i_isdigit(**cmd))
68 max = -1; /* get all the rest */
77 str = g_string_new(NULL);
78 while (arg >= 0 && arg < argcount && (arg <= max || max == -1)) {
79 g_string_append(str, arglist[arg]);
80 g_string_append_c(str, ' ');
83 if (str->len > 0) g_string_truncate(str, str->len-1);
86 g_string_free(str, FALSE);
90 static char *get_internal_setting(const char *key, int type, int *free_ret)
93 case SETTING_TYPE_BOOLEAN:
94 return settings_get_bool(key) ? "yes" : "no";
95 case SETTING_TYPE_INT:
97 return g_strdup_printf("%d", settings_get_int(key));
98 case SETTING_TYPE_STRING:
99 return (char *) settings_get_str(key);
105 static char *get_long_variable_value(const char *key, SERVER_REC *server,
106 void *item, int *free_ret)
115 func = expando_find_long(key);
117 current_expando = key;
118 return func(server, item, free_ret);
121 /* internal setting? */
122 type = settings_get_type(key);
124 return get_internal_setting(key, type, free_ret);
126 /* environment variable? */
134 static char *get_long_variable(char **cmd, SERVER_REC *server,
135 void *item, int *free_ret, int getname)
137 char *start, *var, *ret;
139 /* get variable name */
141 while (isvarchar((*cmd)[1])) (*cmd)++;
143 var = g_strndup(start, (int) (*cmd-start)+1);
148 ret = get_long_variable_value(var, server, item, free_ret);
153 /* return the value of the variable found from `cmd'.
154 if 'getname' is TRUE, return the name of the variable instead it's value */
155 static char *get_variable(char **cmd, SERVER_REC *server, void *item,
156 char **arglist, int *free_ret, int *arg_used,
164 if (arg_used != NULL) *arg_used = TRUE;
165 return getname ? g_strdup_printf("%c", **cmd) :
166 get_argument(cmd, arglist);
169 if (i_isalpha(**cmd) && isvarchar((*cmd)[1])) {
170 /* long variable name.. */
171 return get_long_variable(cmd, server, item, free_ret, getname);
174 /* single character variable. */
177 return g_strdup_printf("%c", **cmd);
180 func = expando_find_char(**cmd);
186 str[0] = **cmd; str[1] = '\0';
187 current_expando = str;
188 return func(server, item, free_ret);
192 static char *get_history(char **cmd, void *item, int *free_ret)
194 char *start, *text, *ret;
196 /* get variable name */
198 while (**cmd != '\0' && **cmd != '!') (*cmd)++;
200 if (history_func == NULL)
203 text = g_strndup(start, (int) (*cmd-start));
204 ret = history_func(text, item, free_ret);
208 if (**cmd == '\0') (*cmd)--;
212 static char *get_special_value(char **cmd, SERVER_REC *server, void *item,
213 char **arglist, int *free_ret, int *arg_used,
216 char command, *value, *p;
219 if ((flags & PARSE_FLAG_ONLY_ARGS) && !isarg(**cmd)) {
221 return g_strdup_printf("$%c", **cmd);
225 /* find text from command history */
226 if (flags & PARSE_FLAG_GETNAME)
229 return get_history(cmd, item, free_ret);
233 if (**cmd == '#' || **cmd == '@') {
235 if ((*cmd)[1] != '\0')
239 char *temp_cmd = "*";
241 if (flags & PARSE_FLAG_GETNAME)
245 return get_argument(&temp_cmd, arglist);
249 value = get_variable(cmd, server, item, arglist, free_ret,
250 arg_used, flags & PARSE_FLAG_GETNAME);
252 if (flags & PARSE_FLAG_GETNAME)
255 if (command == '#') {
256 /* number of words */
257 if (value == NULL || *value == '\0') {
258 if (value != NULL && *free_ret) {
266 for (p = value; *p != '\0'; p++) {
267 if (*p == ' ' && (p[1] != ' ' && p[1] != '\0'))
270 if (*free_ret) g_free(value);
273 return g_strdup_printf("%d", len);
276 if (command == '@') {
277 /* number of characters */
278 if (value == NULL) return "0";
281 if (*free_ret) g_free(value);
284 return g_strdup_printf("%d", len);
290 /* get alignment arguments (inside the []) */
291 static int get_alignment_args(char **data, int *align, int *flags, char *pad)
296 *flags = ALIGN_CUT|ALIGN_PAD;
299 /* '!' = don't cut, '-' = right padding */
301 while (*str != '\0' && *str != ']' && !i_isdigit(*str)) {
303 *flags &= ~ALIGN_CUT;
304 else if (*str == '-')
305 *flags |= ALIGN_RIGHT;
306 else if (*str == '.')
307 *flags &= ~ALIGN_PAD;
310 if (!i_isdigit(*str))
311 return FALSE; /* expecting number */
313 /* get the alignment size */
314 while (i_isdigit(*str)) {
315 *align = (*align) * 10 + (*str-'0');
319 /* get the pad character */
320 while (*str != '\0' && *str != ']') {
325 if (*str++ != ']') return FALSE;
331 /* return the aligned text */
332 static char *get_alignment(const char *text, int align, int flags, char pad)
337 g_return_val_if_fail(text != NULL, NULL);
339 str = g_string_new(text);
342 if ((flags & ALIGN_CUT) && align > 0 && str->len > align)
343 g_string_truncate(str, align);
345 /* add pad characters */
346 if (flags & ALIGN_PAD) {
347 while (str->len < align) {
348 if (flags & ALIGN_RIGHT)
349 g_string_prepend_c(str, pad);
351 g_string_append_c(str, pad);
356 g_string_free(str, FALSE);
360 /* Parse and expand text after '$' character. return value has to be
361 g_free()'d if `free_ret' is TRUE. */
362 char *parse_special(char **cmd, SERVER_REC *server, void *item,
363 char **arglist, int *free_ret, int *arg_used, int flags)
365 static char **nested_orig_cmd = NULL; /* FIXME: KLUDGE! */
366 char command, *value;
369 int align, align_flags;
372 int brackets, nest_free;
378 command = **cmd; (*cmd)++;
382 if (!get_alignment_args(cmd, &align, &align_flags,
383 &align_pad) || **cmd == '\0') {
393 nest_free = FALSE; nest_value = NULL;
394 if (**cmd == '(' && (*cmd)[1] != '\0') {
396 int toplevel = nested_orig_cmd == NULL;
398 if (toplevel) nested_orig_cmd = cmd;
405 nest_value = parse_special(cmd, server, item, arglist,
406 &nest_free, arg_used,
410 if (nest_value == NULL || *nest_value == '\0')
413 while ((*nested_orig_cmd)[1] != '\0') {
414 (*nested_orig_cmd)++;
415 if (**nested_orig_cmd == ')')
420 if (toplevel) nested_orig_cmd = NULL;
426 /* special value is inside {...} (foo${test}bar -> fooXXXbar) */
427 if ((*cmd)[1] == '\0')
433 value = get_special_value(cmd, server, item, arglist,
434 free_ret, arg_used, flags);
436 g_error("parse_special() : buffer overflow!");
438 if (value != NULL && *value != '\0' && (flags & PARSE_FLAG_ISSET_ANY))
442 while (**cmd != '}' && (*cmd)[1] != '\0')
446 if (nest_free) g_free(nest_value);
448 if (command == '[' && (flags & PARSE_FLAG_GETNAME) == 0) {
452 if (value == NULL) return "";
454 p = get_alignment(value, align, align_flags, align_pad);
455 if (*free_ret) g_free(value);
464 static void gstring_append_escaped(GString *str, const char *text, int flags)
466 char esc[4], *escpos;
469 if (flags & PARSE_FLAG_ESCAPE_VARS)
471 if (flags & PARSE_FLAG_ESCAPE_THEME) {
477 g_string_append(str, text);
482 while (*text != '\0') {
483 for (escpos = esc; *escpos != '\0'; escpos++) {
484 if (*text == *escpos) {
485 g_string_append_c(str, '%');
489 g_string_append_c(str, *text);
494 /* parse the whole string. $ and \ chars are replaced */
495 char *parse_special_string(const char *cmd, SERVER_REC *server, void *item,
496 const char *data, int *arg_used, int flags)
498 char code, **arglist, *ret;
502 g_return_val_if_fail(cmd != NULL, NULL);
503 g_return_val_if_fail(data != NULL, NULL);
505 /* create the argument list */
506 arglist = g_strsplit(data, " ", -1);
508 if (arg_used != NULL) *arg_used = FALSE;
510 str = g_string_new(NULL);
511 while (*cmd != '\0') {
514 g_string_append_c(str, ';');
516 chr = expand_escape(&cmd);
517 g_string_append_c(str, chr != -1 ? chr : *cmd);
520 } else if (code == '$') {
523 ret = parse_special((char **) &cmd, server, item,
524 arglist, &need_free, arg_used,
527 gstring_append_escaped(str, ret, flags);
528 if (need_free) g_free(ret);
532 if (*cmd == '\\' || *cmd == '$')
535 g_string_append_c(str, *cmd);
543 g_string_free(str, FALSE);
547 #define is_split_char(str, start) \
548 ((str)[0] == ';' && ((start) == (str) || \
549 ((str)[-1] != '\\' && (str)[-1] != '$')))
551 /* execute the commands in string - commands can be split with ';' */
552 void eval_special_string(const char *cmd, const char *data,
553 SERVER_REC *server, void *item)
555 const char *cmdchars;
556 char *orig, *str, *start, *ret;
557 int arg_used, arg_used_ever;
561 arg_used_ever = FALSE;
562 cmdchars = settings_get_str("cmdchars");
564 /* get a list of all the commands to run */
565 orig = start = str = g_strdup(cmd);
567 if (is_split_char(str, start)) {
569 while (*str == ' ') str++;
570 } else if (*str != '\0') {
575 ret = parse_special_string(start, server, item,
578 if (arg_used) arg_used_ever = TRUE;
580 if (strchr(cmdchars, *ret) == NULL) {
581 /* no command char - let's put it there.. */
584 ret = g_strdup_printf("%c%s", *cmdchars, old);
587 commands = g_slist_append(commands, ret);
590 } while (*start != '\0');
592 /* run the command, if no arguments were ever used, append all of them
593 after each command */
594 while (commands != NULL) {
595 ret = commands->data;
597 if (!arg_used_ever && *data != '\0') {
600 ret = g_strconcat(old, " ", data, NULL);
606 signal_emit("send command", 3, ret, server, item);
608 if (server != NULL && !server_unref(server)) {
609 /* the server was destroyed */
614 /* FIXME: window item would need reference counting as well,
615 eg. "/EVAL win close;say hello" wouldn't work now.. */
618 commands = g_slist_remove(commands, commands->data);
623 void special_history_func_set(SPECIAL_HISTORY_FUNC func)
628 static void update_signals_hash(GHashTable **hash, int *signals)
634 *hash = g_hash_table_new((GHashFunc) g_direct_hash,
635 (GCompareFunc) g_direct_equal);
638 while (*signals != -1) {
639 signal_id = GINT_TO_POINTER(*signals);
640 arg_type = GPOINTER_TO_INT(g_hash_table_lookup(*hash, signal_id));
641 if (arg_type != 0 && arg_type != signals[1]) {
642 /* same signal is used for different purposes ..
643 not sure if this should ever happen, but change
644 the argument type to none so it will at least
646 arg_type = EXPANDO_ARG_NONE;
649 if (arg_type == 0) arg_type = signals[1];
650 g_hash_table_insert(*hash, signal_id,
651 GINT_TO_POINTER(arg_type));
656 static void get_signal_hash(void *signal_id, void *arg_type, int **pos)
658 (*pos)[0] = GPOINTER_TO_INT(signal_id);
659 (*pos)[1] = GPOINTER_TO_INT(arg_type);
663 static int *get_signals_list(GHashTable *hash)
668 /* no expandos in text - never needs updating */
672 pos = signals = g_new(int, g_hash_table_size(hash)*2 + 1);
673 g_hash_table_foreach(hash, (GHFunc) get_signal_hash, &pos);
676 g_hash_table_destroy(hash);
682 #define TASK_UNBIND 2
683 #define TASK_GET_SIGNALS 3
685 static int *special_vars_signals_task(const char *text, int funccount,
686 SIGNAL_FUNC *funcs, int task)
690 int need_free, *expando_signals;
693 while (*text != '\0') {
694 if (*text == '\\' && text[1] != '\0') {
697 } else if (*text == '$' && text[1] != '\0') {
700 expando = parse_special((char **) &text, NULL, NULL,
701 NULL, &need_free, NULL,
708 expando_bind(expando, funccount, funcs);
711 expando_unbind(expando, funccount, funcs);
713 case TASK_GET_SIGNALS:
714 expando_signals = expando_get_signals(expando);
715 if (expando_signals != NULL) {
716 update_signals_hash(&signals,
718 g_free(expando_signals);
722 if (need_free) g_free(expando);
729 if (task == TASK_GET_SIGNALS)
730 return get_signals_list(signals);
735 void special_vars_add_signals(const char *text,
736 int funccount, SIGNAL_FUNC *funcs)
738 special_vars_signals_task(text, funccount, funcs, TASK_BIND);
741 void special_vars_remove_signals(const char *text,
742 int funccount, SIGNAL_FUNC *funcs)
744 special_vars_signals_task(text, funccount, funcs, TASK_UNBIND);
747 int *special_vars_get_signals(const char *text)
749 return special_vars_signals_task(text, 0, NULL, TASK_GET_SIGNALS);