diff options
Diffstat (limited to 'src/plugins')
-rw-r--r-- | src/plugins/CMakeLists.txt | 12 | ||||
-rw-r--r-- | src/plugins/Makefile.am | 16 | ||||
-rw-r--r-- | src/plugins/plugin.c | 1 | ||||
-rw-r--r-- | src/plugins/trigger/CMakeLists.txt | 31 | ||||
-rw-r--r-- | src/plugins/trigger/Makefile.am | 42 | ||||
-rw-r--r-- | src/plugins/trigger/trigger-buffer.c | 180 | ||||
-rw-r--r-- | src/plugins/trigger/trigger-buffer.h | 32 | ||||
-rw-r--r-- | src/plugins/trigger/trigger-callback.c | 597 | ||||
-rw-r--r-- | src/plugins/trigger/trigger-callback.h | 38 | ||||
-rw-r--r-- | src/plugins/trigger/trigger-command.c | 352 | ||||
-rw-r--r-- | src/plugins/trigger/trigger-command.h | 25 | ||||
-rw-r--r-- | src/plugins/trigger/trigger-completion.c | 185 | ||||
-rw-r--r-- | src/plugins/trigger/trigger-completion.h | 25 | ||||
-rw-r--r-- | src/plugins/trigger/trigger-config.c | 484 | ||||
-rw-r--r-- | src/plugins/trigger/trigger-config.h | 40 | ||||
-rw-r--r-- | src/plugins/trigger/trigger.c | 894 | ||||
-rw-r--r-- | src/plugins/trigger/trigger.h | 129 | ||||
-rw-r--r-- | src/plugins/weechat-plugin.h | 9 |
18 files changed, 3080 insertions, 12 deletions
diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt index 3e6ac0e26..41a03412a 100644 --- a/src/plugins/CMakeLists.txt +++ b/src/plugins/CMakeLists.txt @@ -96,10 +96,6 @@ IF(ENABLE_SCRIPT) ADD_SUBDIRECTORY( script ) ENDIF(ENABLE_SCRIPT) -IF(ENABLE_XFER) - ADD_SUBDIRECTORY( xfer ) -ENDIF(ENABLE_XFER) - IF(ENABLE_SCRIPTS AND ENABLE_PERL) FIND_PACKAGE(Perl) IF(PERL_FOUND) @@ -142,4 +138,12 @@ IF(ENABLE_SCRIPTS AND ENABLE_GUILE) ENDIF(GUILE_FOUND) ENDIF(ENABLE_SCRIPTS AND ENABLE_GUILE) +IF(ENABLE_TRIGGER) + ADD_SUBDIRECTORY( trigger ) +ENDIF(ENABLE_TRIGGER) + +IF(ENABLE_XFER) + ADD_SUBDIRECTORY( xfer ) +ENDIF(ENABLE_XFER) + INSTALL(FILES weechat-plugin.h DESTINATION ${INCLUDEDIR}) diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am index 16c0e35ac..bfddd6274 100644 --- a/src/plugins/Makefile.am +++ b/src/plugins/Makefile.am @@ -75,10 +75,6 @@ if PLUGIN_SCRIPT script_dir = script endif -if PLUGIN_XFER -xfer_dir = xfer -endif - if PLUGIN_PERL perl_dir = perl endif @@ -103,10 +99,18 @@ if PLUGIN_GUILE guile_dir = guile endif +if PLUGIN_TRIGGER +trigger_dir = trigger +endif + +if PLUGIN_XFER +xfer_dir = xfer +endif + SUBDIRS = . $(alias_dir) $(aspell_dir) $(charset_dir) $(fifo_dir) $(irc_dir) \ - $(logger_dir) $(relay_dir) $(rmodifier_dir) $(script_dir) $(xfer_dir) \ + $(logger_dir) $(relay_dir) $(rmodifier_dir) $(script_dir) \ $(perl_dir) $(python_dir) $(ruby_dir) $(lua_dir) $(tcl_dir) \ - $(guile_dir) + $(guile_dir) $(trigger_dir) $(xfer_dir) EXTRA_DIST = CMakeLists.txt diff --git a/src/plugins/plugin.c b/src/plugins/plugin.c index a10c7874b..424735560 100644 --- a/src/plugins/plugin.c +++ b/src/plugins/plugin.c @@ -521,6 +521,7 @@ plugin_load (const char *filename, int argc, char **argv) new_plugin->string_regcomp = &string_regcomp; new_plugin->string_has_highlight = &string_has_highlight; new_plugin->string_has_highlight_regex = &string_has_highlight_regex; + new_plugin->string_replace_regex = &string_replace_regex; new_plugin->string_split = &string_split; new_plugin->string_free_split = &string_free_split; new_plugin->string_build_with_split_string = &string_build_with_split_string; diff --git a/src/plugins/trigger/CMakeLists.txt b/src/plugins/trigger/CMakeLists.txt new file mode 100644 index 000000000..59d8e1c00 --- /dev/null +++ b/src/plugins/trigger/CMakeLists.txt @@ -0,0 +1,31 @@ +# +# Copyright (C) 2014 Sébastien Helleu <flashcode@flashtux.org> +# +# This file is part of WeeChat, the extensible chat client. +# +# WeeChat is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# WeeChat is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with WeeChat. If not, see <http://www.gnu.org/licenses/>. +# + +ADD_LIBRARY(trigger MODULE +trigger.c trigger.h +trigger-buffer.c trigger-buffer.h +trigger-callback.c trigger-callback.h +trigger-command.c trigger-command.h +trigger-completion.c trigger-completion.h +trigger-config.c trigger-config.h) +SET_TARGET_PROPERTIES(trigger PROPERTIES PREFIX "") + +TARGET_LINK_LIBRARIES(trigger) + +INSTALL(TARGETS trigger LIBRARY DESTINATION ${LIBDIR}/plugins) diff --git a/src/plugins/trigger/Makefile.am b/src/plugins/trigger/Makefile.am new file mode 100644 index 000000000..2486c0359 --- /dev/null +++ b/src/plugins/trigger/Makefile.am @@ -0,0 +1,42 @@ +# +# Copyright (C) 2014 Sébastien Helleu <flashcode@flashtux.org> +# +# This file is part of WeeChat, the extensible chat client. +# +# WeeChat is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# WeeChat is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with WeeChat. If not, see <http://www.gnu.org/licenses/>. +# + +AM_CPPFLAGS = -DLOCALEDIR=\"$(datadir)/locale\" $(TRIGGER_CFLAGS) + +libdir = ${weechat_libdir}/plugins + +lib_LTLIBRARIES = trigger.la + +trigger_la_SOURCES = trigger.c \ + trigger.h \ + trigger-buffer.c \ + trigger-buffer.h \ + trigger-callback.c \ + trigger-callback.h \ + trigger-command.c \ + trigger-command.h \ + trigger-completion.c \ + trigger-completion.h \ + trigger-config.c \ + trigger-config.h + +trigger_la_LDFLAGS = -module -no-undefined +trigger_la_LIBADD = $(TRIGGER_LFLAGS) + +EXTRA_DIST = CMakeLists.txt diff --git a/src/plugins/trigger/trigger-buffer.c b/src/plugins/trigger/trigger-buffer.c new file mode 100644 index 000000000..186c97f0c --- /dev/null +++ b/src/plugins/trigger/trigger-buffer.c @@ -0,0 +1,180 @@ +/* + * trigger-buffer.c - debug buffer for triggers + * + * Copyright (C) 2014 Sébastien Helleu <flashcode@flashtux.org> + * + * This file is part of WeeChat, the extensible chat client. + * + * WeeChat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * WeeChat is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with WeeChat. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include "../weechat-plugin.h" +#include "trigger.h" +#include "trigger-buffer.h" + + +struct t_gui_buffer *trigger_buffer = NULL; + + +/* + * Callback called for each entry in hashtable. + */ + +void +trigger_buffer_hashtable_map_cb (void *data, + struct t_hashtable *hashtable, + const void *key, const void *value) +{ + const char *value_type; + char *value_no_color; + + /* make C compiler happy */ + (void) data; + (void) hashtable; + + value_type = weechat_hashtable_get_string (hashtable, "type_values"); + if (!value_type) + return; + + if (strcmp (value_type, "string") == 0) + { + value_no_color = weechat_string_remove_color ((const char *)value, NULL); + weechat_printf_tags (trigger_buffer, "no_trigger", + "\t %s: \"%s\"", + (char *)key, + (value_no_color) ? value_no_color : (const char *)value); + if (value_no_color) + free (value_no_color); + } + else if (strcmp (value_type, "pointer") == 0) + { + weechat_printf_tags (trigger_buffer, "no_trigger", + "\t %s: 0x%lx", + (char *)key, + value); + } +} + +/* + * Displays a hashtable on trigger buffer. + */ + +void +trigger_buffer_display_hashtable (const char *name, + struct t_hashtable *hashtable) +{ + if (!trigger_buffer) + return; + + weechat_printf_tags (trigger_buffer, "no_trigger", " %s:", name); + + weechat_hashtable_map (hashtable, &trigger_buffer_hashtable_map_cb, NULL); +} + +/* + * Callback for user data in trigger buffer. + */ + +int +trigger_buffer_input_cb (void *data, struct t_gui_buffer *buffer, + const char *input_data) +{ + /* make C compiler happy */ + (void) data; + + /* close buffer */ + if (strcmp (input_data, "q") == 0) + { + weechat_buffer_close (buffer); + return WEECHAT_RC_OK; + } + + return WEECHAT_RC_OK; +} + +/* + * Callback called when trigger buffer is closed. + */ + +int +trigger_buffer_close_cb (void *data, struct t_gui_buffer *buffer) +{ + /* make C compiler happy */ + (void) data; + (void) buffer; + + trigger_buffer = NULL; + + return WEECHAT_RC_OK; +} + +/* + * Restore buffer callbacks (input and close) for buffer created by trigger + * plugin. + */ + +void +trigger_buffer_set_callbacks () +{ + struct t_gui_buffer *ptr_buffer; + + ptr_buffer = weechat_buffer_search (TRIGGER_PLUGIN_NAME, + TRIGGER_BUFFER_NAME); + if (ptr_buffer) + { + trigger_buffer = ptr_buffer; + weechat_buffer_set_pointer (trigger_buffer, "close_callback", + &trigger_buffer_close_cb); + weechat_buffer_set_pointer (trigger_buffer, "input_callback", + &trigger_buffer_input_cb); + } +} + +/* + * Opens script buffer. + */ + +void +trigger_buffer_open (int switch_to_buffer) +{ + if (!trigger_buffer) + { + trigger_buffer = weechat_buffer_new (TRIGGER_BUFFER_NAME, + &trigger_buffer_input_cb, NULL, + &trigger_buffer_close_cb, NULL); + + /* failed to create buffer ? then return */ + if (!trigger_buffer) + return; + + weechat_buffer_set (trigger_buffer, "title", _("Trigger monitor")); + + if (!weechat_buffer_get_integer (trigger_buffer, "short_name_is_set")) + weechat_buffer_set (trigger_buffer, "short_name", TRIGGER_BUFFER_NAME); + weechat_buffer_set (trigger_buffer, "localvar_set_type", "debug"); + weechat_buffer_set (trigger_buffer, "localvar_set_server", TRIGGER_BUFFER_NAME); + weechat_buffer_set (trigger_buffer, "localvar_set_channel", TRIGGER_BUFFER_NAME); + weechat_buffer_set (trigger_buffer, "localvar_set_no_log", "1"); + + /* disable all highlights on this buffer */ + weechat_buffer_set (trigger_buffer, "highlight_words", "-"); + } + + if (switch_to_buffer) + weechat_buffer_set (trigger_buffer, "display", "1"); +} diff --git a/src/plugins/trigger/trigger-buffer.h b/src/plugins/trigger/trigger-buffer.h new file mode 100644 index 000000000..a4bc02fd4 --- /dev/null +++ b/src/plugins/trigger/trigger-buffer.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2014 Sébastien Helleu <flashcode@flashtux.org> + * + * This file is part of WeeChat, the extensible chat client. + * + * WeeChat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * WeeChat is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with WeeChat. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __WEECHAT_TRIGGER_BUFFER_H +#define __WEECHAT_TRIGGER_BUFFER_H 1 + +#define TRIGGER_BUFFER_NAME "monitor" + +struct t_gui_buffer *trigger_buffer; + +extern void trigger_buffer_display_hashtable (const char *name, + struct t_hashtable *hashtable); +extern void trigger_buffer_set_callbacks (); +extern void trigger_buffer_open (int switch_to_buffer); + +#endif /* __WEECHAT_TRIGGER_BUFFER_H */ diff --git a/src/plugins/trigger/trigger-callback.c b/src/plugins/trigger/trigger-callback.c new file mode 100644 index 000000000..e14ee6e2e --- /dev/null +++ b/src/plugins/trigger/trigger-callback.c @@ -0,0 +1,597 @@ +/* + * trigger-callback.c - callbacks for triggers + * + * Copyright (C) 2014 Sébastien Helleu <flashcode@flashtux.org> + * + * This file is part of WeeChat, the extensible chat client. + * + * WeeChat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * WeeChat is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with WeeChat. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <time.h> + +#include "../weechat-plugin.h" +#include "trigger.h" +#include "trigger-buffer.h" + + +/* one hashtable by hook, used in callback to evaluate "conditions" */ +struct t_hashtable *trigger_callback_hashtable_options = NULL; + + +/* + * Checks conditions for a trigger. + * + * Returns: + * 1: conditions are true (or no condition set in trigger) + * 0: conditions are false + */ + +int +trigger_callback_check_conditions (struct t_trigger *trigger, + struct t_hashtable *pointers, + struct t_hashtable *extra_vars) +{ + const char *conditions; + char *value; + int rc; + + conditions = weechat_config_string (trigger->options[TRIGGER_OPTION_CONDITIONS]); + if (!conditions || !conditions[0]) + return 1; + + value = weechat_string_eval_expression (conditions, + pointers, + extra_vars, + trigger_callback_hashtable_options); + rc = (value && (strcmp (value, "1") == 0)); + if (value) + free (value); + + return rc; +} + +/* + * Replaces text using one or more regex in the trigger. + * + * Note: result must be freed after use. + */ + +char * +trigger_callback_replace_regex (struct t_trigger *trigger, const char *string) +{ + char *temp, *res; + int i; + + if (trigger->regex_count == 0) + return strdup (string); + + res = NULL; + + for (i = 0; i < trigger->regex_count; i++) + { + temp = weechat_string_replace_regex ((res) ? res : string, + trigger->regex[i].regex, + trigger->regex[i].replace_eval); + if (!temp) + return res; + res = temp; + } + + return res; +} + +/* + * Executes the trigger command(s). + */ + +void +trigger_callback_run_command (struct t_trigger *trigger, + struct t_gui_buffer *buffer, + struct t_hashtable *pointers, + struct t_hashtable *extra_vars) +{ + const char *command; + char *command_eval, **commands, **ptr_command; + + command = weechat_config_string (trigger->options[TRIGGER_OPTION_COMMAND]); + if (!command || !command[0]) + return; + + command_eval = weechat_string_eval_expression (command, pointers, + extra_vars, NULL); + if (command_eval) + { + commands = weechat_string_split_command (command_eval, ';'); + if (commands) + { + for (ptr_command = commands; *ptr_command; ptr_command++) + { + /* display debug info on trigger buffer */ + if (trigger_buffer) + { + weechat_printf_tags (trigger_buffer, "no_trigger", + "\t running command \"%s\"", + *ptr_command); + } + weechat_command (buffer, *ptr_command); + } + weechat_string_free_split_command (commands); + trigger->hook_count_cmd++; + } + free (command_eval); + } +} + +/* + * Callback for a signal hooked. + */ + +int +trigger_callback_signal_cb (void *data, const char *signal, + const char *type_data, void *signal_data) +{ + struct t_trigger *trigger; + struct t_hashtable *extra_vars; + const char *command, *ptr_signal_data; + char str_data[128], *signal_data2; + int rc; + + /* get trigger pointer, return immediately if not found or trigger running */ + trigger = (struct t_trigger *)data; + if (!trigger || trigger->hook_running) + return WEECHAT_RC_OK; + + trigger->hook_count_cb++; + trigger->hook_running = 1; + + extra_vars = NULL; + + rc = trigger_return_code[weechat_config_integer (trigger->options[TRIGGER_OPTION_RETURN_CODE])]; + + /* + * in a signal, the only possible action is to execute a command; + * so if the command is empty, just exit without checking conditions + */ + command = weechat_config_string (trigger->options[TRIGGER_OPTION_COMMAND]); + if (!command || !command[0]) + goto end; + + /* create hashtable */ + extra_vars = weechat_hashtable_new (32, + WEECHAT_HASHTABLE_STRING, + WEECHAT_HASHTABLE_STRING, + NULL, + NULL); + if (!extra_vars) + goto end; + + /* add data in hashtable used for conditions/replace/command */ + weechat_hashtable_set (extra_vars, "tg_signal", signal); + if (strcmp (type_data, WEECHAT_HOOK_SIGNAL_STRING) == 0) + { + ptr_signal_data = (const char *)signal_data; + } + else if (strcmp (type_data, WEECHAT_HOOK_SIGNAL_INT) == 0) + { + snprintf (str_data, sizeof (str_data), + "%d", *((int *)signal_data)); + ptr_signal_data = str_data; + } + else if (strcmp (type_data, WEECHAT_HOOK_SIGNAL_POINTER) == 0) + { + str_data[0] = '\0'; + if (signal_data) + { + snprintf (str_data, sizeof (str_data), + "0x%lx", (long unsigned int)signal_data); + } + ptr_signal_data = str_data; + } + weechat_hashtable_set (extra_vars, "tg_signal_data", ptr_signal_data); + + /* display debug info on trigger buffer */ + if (!trigger_buffer && (weechat_trigger_plugin->debug >= 1)) + trigger_buffer_open (0); + if (trigger_buffer) + { + weechat_printf_tags (trigger_buffer, "no_trigger", + "signal\t%s%s", + weechat_color ("chat_channel"), + trigger->name); + weechat_printf_tags (trigger_buffer, "no_trigger", + "\t signal_data: \"%s%s\"", + ptr_signal_data, + weechat_color ("reset")); + trigger_buffer_display_hashtable ("extra_vars", extra_vars); + } + + /* check conditions */ + if (!trigger_callback_check_conditions (trigger, NULL, extra_vars)) + goto end; + + /* replace text with regex */ + if (trigger->regex_count > 0) + { + signal_data2 = trigger_callback_replace_regex (trigger, ptr_signal_data); + if (signal_data2) + { + weechat_hashtable_set (extra_vars, "tg_signal_data", signal_data2); + free (signal_data2); + } + } + + /* execute command */ + trigger_callback_run_command (trigger, NULL, NULL, extra_vars); + +end: + if (extra_vars) + weechat_hashtable_free (extra_vars); + trigger->hook_running = 0; + return rc; +} + +/* + * Callback for a hsignal hooked. + */ + +int +trigger_callback_hsignal_cb (void *data, const char *signal, + struct t_hashtable *hashtable) +{ + (void) data; + (void) signal; + (void) hashtable; + + return WEECHAT_RC_OK; +} + +/* + * Callback for a modifier hooked. + */ + +char * +trigger_callback_modifier_cb (void *data, const char *modifier, + const char *modifier_data, const char *string) +{ + struct t_trigger *trigger; + struct t_hashtable *extra_vars; + const char *command; + char *string_modified, *pos, *pos2, *plugin_name, *buffer_name; + char *buffer_full_name, *tags; + int no_trigger, length; + + no_trigger = 0; + + /* get trigger pointer, return immediately if not found or trigger running */ + trigger = (struct t_trigger *)data; + if (!trigger || trigger->hook_running) + return NULL; + + trigger->hook_count_cb++; + trigger->hook_running = 1; + + extra_vars = NULL; + string_modified = NULL; + + /* + * in a modifier, the only possible actions are regex or command; + * so if both are empty, just exit without checking conditions + */ + command = weechat_config_string (trigger->options[TRIGGER_OPTION_COMMAND]); + if ((!command || !command[0]) && !trigger->regex) + goto end; + + /* create hashtable */ + extra_vars = weechat_hashtable_new (32, + WEECHAT_HASHTABLE_STRING, + WEECHAT_HASHTABLE_STRING, + NULL, + NULL); + if (!extra_vars) + goto end; + + /* add data in hashtable used for conditions/replace/command */ + weechat_hashtable_set (extra_vars, "tg_modifier", modifier); + weechat_hashtable_set (extra_vars, "tg_modifier_data", modifier_data); + weechat_hashtable_set (extra_vars, "tg_string", string); + + /* add special variables for a WeeChat message */ + if (strcmp (modifier, "weechat_print") == 0) + { + pos = strchr (modifier_data, ';'); + if (pos) + { + plugin_name = weechat_strndup (modifier_data, pos - modifier_data); + if (plugin_name) + { + weechat_hashtable_set (extra_vars, "tg_plugin", plugin_name); + pos++; + pos2 = strchr (pos, ';'); + if (pos2) + { + buffer_name = weechat_strndup (pos, pos2 - pos); + if (buffer_name) + { + length = strlen (plugin_name) + 1 + strlen (buffer_name) + 1; + buffer_full_name = malloc (length); + if (buffer_full_name) + { + snprintf (buffer_full_name, length, + "%s.%s", plugin_name, buffer_name); + weechat_hashtable_set (extra_vars, "tg_buffer", + buffer_full_name); + free (buffer_full_name); + } + free (buffer_name); + } + pos2++; + if (pos2[0]) + { + length = 1 + strlen (pos2) + 1 + 1; + tags = malloc (length); + if (tags) + { + snprintf (tags, length, ",%s,", pos2); + weechat_hashtable_set (extra_vars, "tg_tags", tags); + if (strstr (tags, ",no_trigger,")) + no_trigger = 1; + free (tags); + } + } + } + free (plugin_name); + } + } + } + + /* + * ignore this modifier if "no_trigger" was in tags + * (for modifier "weechat_print") + */ + if (no_trigger) + goto end; + + /* display debug info on trigger buffer */ + if (!trigger_buffer && (weechat_trigger_plugin->debug >= 1)) + trigger_buffer_open (0); + if (trigger_buffer) + { + weechat_printf_tags (trigger_buffer, "no_trigger", + "modifier\t%s%s", + weechat_color ("chat_channel"), + trigger->name); + weechat_printf_tags (trigger_buffer, "no_trigger", + "\t modifier: %s", modifier); + weechat_printf_tags (trigger_buffer, "no_trigger", + "\t modifier_data: \"%s%s\"", + modifier_data, + weechat_color ("reset")); + trigger_buffer_display_hashtable ("extra_vars", extra_vars); + } + + /* check conditions */ + if (!trigger_callback_check_conditions (trigger, NULL, extra_vars)) + goto end; + + /* replace text with regex */ + if (trigger->regex_count > 0) + { + string_modified = trigger_callback_replace_regex (trigger, string); + if (string_modified) + { + weechat_hashtable_set (extra_vars, "tg_string", string_modified); + if (strcmp (string, string_modified) == 0) + { + /* regex did not change the string, ignore it */ + free (string_modified); + string_modified = NULL; + } + } + } + + /* execute command */ + trigger_callback_run_command (trigger, NULL, NULL, extra_vars); + +end: + if (extra_vars) + weechat_hashtable_free (extra_vars); + trigger->hook_running = 0; + return string_modified; +} + +/* + * Callback for a print hooked. + */ + +int +trigger_callback_print_cb (void *data, struct t_gui_buffer *buffer, + time_t date, int tags_count, const char **tags, + int displayed, int highlight, const char *prefix, + const char *message) +{ + struct t_trigger *trigger; + struct t_hashtable *pointers, *extra_vars; + const char *command, *localvar_type; + char *message2, *str_tags, *str_tags2, str_temp[128]; + int i, rc, length, tag_notify_private; + struct tm *date_tmp; + + /* get trigger pointer, return immediately if not found or trigger running */ + trigger = (struct t_trigger *)data; + if (!trigger || trigger->hook_running) + return WEECHAT_RC_OK; + + trigger->hook_count_cb++; + trigger->hook_running = 1; + + pointers = NULL; + extra_vars = NULL; + + rc = trigger_return_code[weechat_config_integer (trigger->options[TRIGGER_OPTION_RETURN_CODE])]; + + /* return if the buffer does not match buffers defined in the trigger */ + if (trigger->hook_print_buffers + && !weechat_buffer_match_list (buffer, trigger->hook_print_buffers)) + goto end; + + /* + * in a print, the only possible action is to execute a command; + * so if the command is empty, just exit without checking conditions + */ + command = weechat_config_string (trigger->options[TRIGGER_OPTION_COMMAND]); + if (!command || !command[0]) + goto end; + + /* create hashtables */ + pointers = weechat_hashtable_new (32, + WEECHAT_HASHTABLE_STRING, + WEECHAT_HASHTABLE_POINTER, + NULL, + NULL); + if (!pointers) + goto end; + extra_vars = weechat_hashtable_new (32, + WEECHAT_HASHTABLE_STRING, + WEECHAT_HASHTABLE_STRING, + NULL, + NULL); + if (!extra_vars) + goto end; + + /* add data in hashtable used for conditions/replace/command */ + weechat_hashtable_set (pointers, "buffer", buffer); + date_tmp = localtime (&date); + if (date_tmp) + { + strftime (str_temp, sizeof (str_temp), "%Y-%m-%d %H:%M:%S", date_tmp); + weechat_hashtable_set (extra_vars, "tg_date", str_temp); + } + snprintf (str_temp, sizeof (str_temp), "%d", tags_count); + weechat_hashtable_set (extra_vars, "tg_tags_count", str_temp); + str_tags = weechat_string_build_with_split_string (tags, ","); + if (str_tags) + { + /* build string with tags and commas around: ",tag1,tag2,tag3," */ + length = 1 + strlen (str_tags) + 1 + 1; + str_tags2 = malloc (length); + if (str_tags2) + { + snprintf (str_tags2, length, ",%s,", str_tags); + weechat_hashtable_set (extra_vars, "tg_tags", str_tags2); + free (str_tags2); + } + free (str_tags); + } + snprintf (str_temp, sizeof (str_temp), "%d", displayed); + weechat_hashtable_set (extra_vars, "tg_displayed", str_temp); + snprintf (str_temp, sizeof (str_temp), "%d", highlight); + weechat_hashtable_set (extra_vars, "tg_highlight", str_temp); + weechat_hashtable_set (extra_vars, "tg_prefix", prefix); + weechat_hashtable_set (extra_vars, "tg_message", message); + + localvar_type = weechat_buffer_get_string (buffer, "localvar_type"); + tag_notify_private = 0; + + for (i = 0; i < tags_count; i++) + { + if (strcmp (tags[i], "no_trigger") == 0) + { + goto end; + } + else if (strcmp (tags[i], "notify_private") == 0) + { + tag_notify_private = 1; + } + } + snprintf (str_temp, sizeof (str_temp), "%d", + (tag_notify_private && localvar_type && + (strcmp (localvar_type, "private") == 0)) ? 1 : 0); + weechat_hashtable_set (extra_vars, "tg_msg_pv", str_temp); + + /* display debug info on trigger buffer */ + if (!trigger_buffer && (weechat_trigger_plugin->debug >= 1)) + trigger_buffer_open (0); + if (trigger_buffer) + { + weechat_printf_tags (trigger_buffer, "no_trigger", + "print\t%s%s", + weechat_color ("chat_channel"), + trigger->name); + weechat_printf_tags (trigger_buffer, "no_trigger", + "\t buffer: %s", + weechat_buffer_get_string (buffer, "full_name")); + trigger_buffer_display_hashtable ("pointers", pointers); + trigger_buffer_display_hashtable ("extra_vars", extra_vars); + } + + /* check conditions */ + if (!trigger_callback_check_conditions (trigger, pointers, extra_vars)) + goto end; + + /* replace text with regex */ + if (trigger->regex_count > 0) + { + message2 = trigger_callback_replace_regex (trigger, message); + if (message2) + { + weechat_hashtable_set (extra_vars, "tg_message", message); + free (message2); + } + } + + /* execute command */ + trigger_callback_run_command (trigger, buffer, pointers, extra_vars); + +end: + if (pointers) + weechat_hashtable_free (pointers); + if (extra_vars) + weechat_hashtable_free (extra_vars); + trigger->hook_running = 0; + return rc; +} + +/* + * Initializes trigger callback. + */ + +void +trigger_callback_init () +{ + trigger_callback_hashtable_options = weechat_hashtable_new (32, + WEECHAT_HASHTABLE_STRING, + WEECHAT_HASHTABLE_STRING, + NULL, + NULL); + if (trigger_callback_hashtable_options) + { + weechat_hashtable_set (trigger_callback_hashtable_options, + "type", "condition"); + } +} + +/* + * Ends trigger callback. + */ + +void +trigger_callback_end () +{ + if (trigger_callback_hashtable_options) + weechat_hashtable_free (trigger_callback_hashtable_options); +} diff --git a/src/plugins/trigger/trigger-callback.h b/src/plugins/trigger/trigger-callback.h new file mode 100644 index 000000000..b9327709b --- /dev/null +++ b/src/plugins/trigger/trigger-callback.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2014 Sébastien Helleu <flashcode@flashtux.org> + * + * This file is part of WeeChat, the extensible chat client. + * + * WeeChat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * WeeChat is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with WeeChat. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __WEECHAT_TRIGGER_CALLBACK_H +#define __WEECHAT_TRIGGER_CALLBACK_H 1 + +extern int trigger_callback_signal_cb (void *data, const char *signal, + const char *type_data, void *signal_data); +extern int trigger_callback_hsignal_cb (void *data, const char *signal, + struct t_hashtable *hashtable); +extern char *trigger_callback_modifier_cb (void *data, const char *modifier, + const char *modifier_data, + const char *string); +extern int trigger_callback_print_cb (void *data, struct t_gui_buffer *buffer, + time_t date, int tags_count, + const char **tags, int displayed, + int highlight, const char *prefix, + const char *message); +extern void trigger_callback_init (); +extern void trigger_callback_end (); + +#endif /* __WEECHAT_TRIGGER_CALLBACK_H */ diff --git a/src/plugins/trigger/trigger-command.c b/src/plugins/trigger/trigger-command.c new file mode 100644 index 000000000..a38ffcc28 --- /dev/null +++ b/src/plugins/trigger/trigger-command.c @@ -0,0 +1,352 @@ +/* + * trigger-command.c - trigger command + * + * Copyright (C) 2014 Sébastien Helleu <flashcode@flashtux.org> + * + * This file is part of WeeChat, the extensible chat client. + * + * WeeChat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * WeeChat is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with WeeChat. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <stdio.h> + +#include "../weechat-plugin.h" +#include "trigger.h" +#include "trigger-buffer.h" +#include "trigger-config.h" + + +/* + * Callback for command "/trigger": manage triggers. + */ + +int +trigger_command_trigger (void *data, struct t_gui_buffer *buffer, int argc, + char **argv, char **argv_eol) +{ + struct t_trigger *ptr_trigger; + const char *option; + int i, type, count, index_option, enabled; + + /* make C compiler happy */ + (void) data; + (void) buffer; + + /* list all triggers */ + if ((argc == 1) + || ((argc == 2) && (weechat_strcasecmp (argv[1], "list") == 0))) + { + if (triggers) + { + weechat_printf_tags (NULL, "no_trigger", ""); + weechat_printf_tags (NULL, "no_trigger", _("List of triggers:")); + for (ptr_trigger = triggers; ptr_trigger; + ptr_trigger = ptr_trigger->next_trigger) + { + weechat_printf_tags (NULL, "no_trigger", + " %s: %s, \"%s\" (%d hooks, %d/%d) %s", + ptr_trigger->name, + weechat_config_string (ptr_trigger->options[TRIGGER_OPTION_HOOK]), + weechat_config_string (ptr_trigger->options[TRIGGER_OPTION_ARGUMENTS]), + ptr_trigger->hooks_count, + ptr_trigger->hook_count_cb, + ptr_trigger->hook_count_cmd, + weechat_config_boolean (ptr_trigger->options[TRIGGER_OPTION_ENABLED]) ? + "" : _("(disabled)")); + option = weechat_config_string (ptr_trigger->options[TRIGGER_OPTION_CONDITIONS]); + if (option && option[0]) + { + weechat_printf_tags (NULL, "no_trigger", + " conditions: \"%s\"", option); + } + if (ptr_trigger->regex_count > 0) + { + weechat_printf_tags (NULL, "no_trigger", + " %d regex:", + ptr_trigger->regex_count); + for (i = 0; i < ptr_trigger->regex_count; i++) + { + weechat_printf_tags (NULL, "no_trigger", + " %d: %s%s %s-->%s %s", + i + 1, + weechat_color (weechat_config_string (trigger_config_color_regex)), + ptr_trigger->regex[i].str_regex, + weechat_color ("chat_delimiters"), + weechat_color (weechat_config_string (trigger_config_color_replace)), + ptr_trigger->regex[i].replace); + } + } + option = weechat_config_string (ptr_trigger->options[TRIGGER_OPTION_COMMAND]); + if (option && option[0]) + { + weechat_printf_tags (NULL, "no_trigger", + " command: \"%s\"", option); + } + } + } + else + { + weechat_printf_tags (NULL, "no_trigger", _("No trigger defined")); + } + return WEECHAT_RC_OK; + } + + /* add a trigger */ + if (weechat_strcasecmp (argv[1], "add") == 0) + { + if (argc < 4) + { + weechat_printf_tags (NULL, "no_trigger", + _("%sError: missing arguments for \"%s\" " + "command"), + weechat_prefix ("error"), "trigger"); + return WEECHAT_RC_OK; + } + type = trigger_search_hook_type (argv[3]); + if (type < 0) + { + weechat_printf_tags (NULL, "no_trigger", + _("%s%s: invalid hook type \"%s\""), + weechat_prefix ("error"), TRIGGER_PLUGIN_NAME, + argv[3]); + return WEECHAT_RC_OK; + } + ptr_trigger = trigger_alloc (argv[2]); + if (!ptr_trigger) + { + weechat_printf_tags (NULL, "no_trigger", + _("%s%s: error creating trigger \"%s\""), + weechat_prefix ("error"), TRIGGER_PLUGIN_NAME, + argv[2]); + return WEECHAT_RC_OK; + } + if (trigger_new (argv[2], "on", argv[3], + (argc > 4) ? argv_eol[4] : "", + "", "", "", "ok")) + { + weechat_printf_tags (NULL, "no_trigger", + _("Trigger \"%s\" created"), argv[2]); + } + else + { + weechat_printf_tags (NULL, "no_trigger", + _("%sError: failed to create trigger \"%s\""), + weechat_prefix ("error"), argv[2]); + } + return WEECHAT_RC_OK; + } + + /* set option in a trigger */ + if (weechat_strcasecmp (argv[1], "set") == 0) + { + if (argc < 5) + { + weechat_printf_tags (NULL, "no_trigger", + _("%sError: missing arguments for \"%s\" " + "command"), + weechat_prefix ("error"), "trigger"); + return WEECHAT_RC_OK; + } + ptr_trigger = trigger_search (argv[2]); + if (!ptr_trigger) + { + weechat_printf_tags (NULL, "no_trigger", + _("%sTrigger \"%s\" not found"), + weechat_prefix ("error"), argv[2]); + return WEECHAT_RC_OK; + } + if (weechat_strcasecmp (argv[3], "name") == 0) + { + trigger_rename (ptr_trigger, argv_eol[4]); + } + else + { + index_option = trigger_search_option (argv[3]); + if (index_option < 0) + { + weechat_printf_tags (NULL, "no_trigger", + _("%sTrigger option \"%s\" not found"), + weechat_prefix ("error"), argv[3]); + return WEECHAT_RC_OK; + } + weechat_config_option_set (ptr_trigger->options[index_option], + argv_eol[4], 1); + } + weechat_printf_tags (NULL, "no_trigger", + _("Trigger \"%s\" updated"), ptr_trigger->name); + return WEECHAT_RC_OK; + } + + /* delete a trigger */ + if (weechat_strcasecmp (argv[1], "del") == 0) + { + if (argc < 3) + { + weechat_printf_tags (NULL, "no_trigger", + _("%sError: missing arguments for \"%s\" " + "command"), + weechat_prefix ("error"), "trigger"); + return WEECHAT_RC_OK; + } + if (weechat_strcasecmp (argv[2], "-all") == 0) + { + count = triggers_count; + trigger_free_all (); + if (count > 0) + weechat_printf_tags (NULL, "no_trigger", + _("%d triggers removed"), count); + } + else + { + for (i = 2; i < argc; i++) + { + ptr_trigger = trigger_search (argv[i]); + if (ptr_trigger) + { + trigger_free (ptr_trigger); + weechat_printf_tags (NULL, "no_trigger", + _("Trigger \"%s\" removed"), argv[i]); + } + else + { + weechat_printf_tags (NULL, "no_trigger", + _("%sTrigger \"%s\" not found"), + weechat_prefix ("error"), argv[i]); + } + } + } + return WEECHAT_RC_OK; + } + + /* enable/disable/toggle a trigger */ + if ((weechat_strcasecmp (argv[1], "enable") == 0) + || (weechat_strcasecmp (argv[1], "disable") == 0) + || (weechat_strcasecmp (argv[1], "toggle") == 0)) + { + if (argc < 3) + { + weechat_printf_tags (NULL, "no_trigger", + _("%sError: missing arguments for \"%s\" " + "command"), + weechat_prefix ("error"), "trigger"); + return WEECHAT_RC_OK; + } + ptr_trigger = trigger_search (argv[2]); + if (!ptr_trigger) + { + weechat_printf_tags (NULL, "no_trigger", + _("%sTrigger \"%s\" not found"), + weechat_prefix ("error"), argv[2]); + return WEECHAT_RC_OK; + } + if (weechat_strcasecmp (argv[1], "enable") == 0) + enabled = 1; + else if (weechat_strcasecmp (argv[1], "disable") == 0) + enabled = 0; + else + { + enabled = weechat_config_boolean (ptr_trigger->options[TRIGGER_OPTION_ENABLED]) ? + 0 : 1; + } + weechat_config_option_set (ptr_trigger->options[TRIGGER_OPTION_ENABLED], + (enabled) ? "on" : "off", 1); + weechat_printf_tags (NULL, "no_trigger", + (enabled) ? + _("Trigger \"%s\" enabled") : + _("Trigger \"%s\" disabled"), + ptr_trigger->name); + return WEECHAT_RC_OK; + } + + /* open the trigger monitor buffer */ + if (weechat_strcasecmp (argv[1], "monitor") == 0) + { + trigger_buffer_open (1); + return WEECHAT_RC_OK; + } + + return WEECHAT_RC_OK; +} + +/* + * Hooks trigger commands. + */ + +void +trigger_command_init () +{ + weechat_hook_command ( + "trigger", + N_("manage triggers"), + N_("list" + " || add <name> <hook> [<arguments>]" + " || set <name> <option> <value>" + " || del <name>|-all [<name>...]" + " || enable|disable|toggle <name>" + " || monitor"), + N_(" add: add a trigger\n" + " name: name of trigger\n" + " hook: signal, hsignal, modifier, print, timer\n" + "arguments: arguments for the hook, depending on hook:\n" + " signal: name of signal\n" + " hsignal: name of hsignal\n" + " modifier: name of modifier\n" + " print: buffer, tags, message, strip_colors\n" + " timer: interval, align_second, max_calls\n" + " set: set an option in a trigger\n" + " option: name of option: name, hook, arguments, conditions, regex, " + "command, return_code\n" + " (for help on option, you can do /help " + "trigger.trigger.<name>.<option>)\n" + " value: new value for the option\n" + " del: delete a trigger\n" + " -all: delete all triggers\n" + " enable: enable a trigger\n" + " disable: disable a trigger\n" + " toggle: toggle a trigger\n" + " monitor: open the trigger monitor buffer\n" + "\n" + "When a trigger callback is called, following actions are performed, " + "in this order:\n" + " 1. if no regex/command is defined, exit\n" + " 2. check conditions; if false, exit\n" + " 3. replace text using regex (if defined in trigger)\n" + " 4. execute command (if defined in trigger)\n" + "Note: on steps 1 and 2, the exit is made with the return code " + "defined in trigger (or NULL for a modifier).\n" + "\n" + "Examples:\n" + " send alert (BEL) on highlight or private message:\n" + " /trigger add beep print\n" + " /trigger set beep conditions ${tg_highlight} || ${tg_msg_pv}\n" + " /trigger set beep command /print -stderr \\a\n" + " replace password with '*' in /oper command (in command line and " + "command history):\n" + " /trigger add oper modifier input_text_display;history_add\n" + " /trigger set oper regex ==^(/oper +\\S+ +)(.*)==\\1\\*2\n" + " add text attributes in *bold*, _underline_ and /italic/:\n" + " /trigger add effects modifier weechat_print\n" + " /trigger set effects regex " + "==\\*(\\S+)\\*==*${color:bold}\\1${color:-bold}*" + "==_(\\S+)_==_${color:underline}\\1${color:-underline}_" + "==/(\\S+)/==/${color:italic}\\1${color:-italic}/"), + "list" + " || add %(trigger_names) %(trigger_hooks)" + " || set %(trigger_names) %(trigger_options)|name %(trigger_option_value)" + " || del %(trigger_names)|-all %(trigger_names)|%*" + " || enable|disable|toggle %(trigger_names)" + " || monitor", + &trigger_command_trigger, NULL); +} diff --git a/src/plugins/trigger/trigger-command.h b/src/plugins/trigger/trigger-command.h new file mode 100644 index 000000000..a2ad07f25 --- /dev/null +++ b/src/plugins/trigger/trigger-command.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2014 Sébastien Helleu <flashcode@flashtux.org> + * + * This file is part of WeeChat, the extensible chat client. + * + * WeeChat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * WeeChat is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with WeeChat. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __WEECHAT_TRIGGER_COMMAND_H +#define __WEECHAT_TRIGGER_COMMAND_H 1 + +extern void trigger_command_init (); + +#endif /* __WEECHAT_TRIGGER_COMMAND_H */ diff --git a/src/plugins/trigger/trigger-completion.c b/src/plugins/trigger/trigger-completion.c new file mode 100644 index 000000000..26dddb0fe --- /dev/null +++ b/src/plugins/trigger/trigger-completion.c @@ -0,0 +1,185 @@ +/* + * trigger-completion.c - completion for trigger commands + * + * Copyright (C) 2014 Sébastien Helleu <flashcode@flashtux.org> + * + * This file is part of WeeChat, the extensible chat client. + * + * WeeChat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * WeeChat is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with WeeChat. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include "../weechat-plugin.h" +#include "trigger.h" + + +/* + * Adds triggers to completion list. + */ + +int +trigger_completion_triggers_cb (void *data, const char *completion_item, + struct t_gui_buffer *buffer, + struct t_gui_completion *completion) +{ + struct t_trigger *ptr_trigger; + + /* make C compiler happy */ + (void) data; + (void) completion_item; + (void) buffer; + + for (ptr_trigger = triggers; ptr_trigger; + ptr_trigger = ptr_trigger->next_trigger) + { + weechat_hook_completion_list_add (completion, ptr_trigger->name, + 0, WEECHAT_LIST_POS_SORT); + } + + return WEECHAT_RC_OK; +} + +/* + * Adds options for triggers to completion list. + */ + +int +trigger_completion_options_cb (void *data, const char *completion_item, + struct t_gui_buffer *buffer, + struct t_gui_completion *completion) +{ + int i; + + /* make C compiler happy */ + (void) data; + (void) completion_item; + (void) buffer; + + for (i = 0; i < TRIGGER_NUM_OPTIONS; i++) + { + weechat_hook_completion_list_add (completion, + trigger_option_string[i], + 0, WEECHAT_LIST_POS_SORT); + } + + return WEECHAT_RC_OK; +} + +/* + * Adds value of a trigger option to completion list. + */ + +int +trigger_completion_option_value_cb (void *data, const char *completion_item, + struct t_gui_buffer *buffer, + struct t_gui_completion *completion) +{ + const char *args; + char **argv; + int argc, index_option; + struct t_trigger *ptr_trigger; + + /* make C compiler happy */ + (void) data; + (void) completion_item; + (void) buffer; + + args = weechat_hook_completion_get_string (completion, "args"); + if (!args) + return WEECHAT_RC_OK; + + argv = weechat_string_split (args, " ", 0, 0, &argc); + if (!argv) + return WEECHAT_RC_OK; + + if (argc >= 3) + { + ptr_trigger = trigger_search (argv[1]); + if (ptr_trigger) + { + if (weechat_strcasecmp (argv[2], "name") == 0) + { + weechat_hook_completion_list_add (completion, + ptr_trigger->name, + 0, + WEECHAT_LIST_POS_BEGINNING); + } + else + { + index_option = trigger_search_option (argv[2]); + if (index_option >= 0) + { + weechat_hook_completion_list_add (completion, + weechat_config_string (ptr_trigger->options[index_option]), + 0, + WEECHAT_LIST_POS_BEGINNING); + } + } + } + } + + weechat_string_free_split (argv); + + return WEECHAT_RC_OK; +} + +/* + * Adds hooks for triggers to completion list. + */ + +int +trigger_completion_hooks_cb (void *data, const char *completion_item, + struct t_gui_buffer *buffer, + struct t_gui_completion *completion) +{ + int i; + + /* make C compiler happy */ + (void) data; + (void) completion_item; + (void) buffer; + + for (i = 0; i < TRIGGER_NUM_HOOK_TYPES; i++) + { + weechat_hook_completion_list_add (completion, + trigger_hook_type_string[i], + 0, WEECHAT_LIST_POS_SORT); + } + + return WEECHAT_RC_OK; +} + +/* + * Hooks completions. + */ + +void +trigger_completion_init () +{ + weechat_hook_completion ("trigger_names", + N_("triggers"), + &trigger_completion_triggers_cb, NULL); + weechat_hook_completion ("trigger_options", + N_("options for triggers"), + &trigger_completion_options_cb, NULL); + weechat_hook_completion ("trigger_option_value", + N_("value of a trigger option"), + &trigger_completion_option_value_cb, NULL); + weechat_hook_completion ("trigger_hooks", + N_("hooks for triggers"), + &trigger_completion_hooks_cb, NULL); +} diff --git a/src/plugins/trigger/trigger-completion.h b/src/plugins/trigger/trigger-completion.h new file mode 100644 index 000000000..17dcccf9c --- /dev/null +++ b/src/plugins/trigger/trigger-completion.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2014 Sébastien Helleu <flashcode@flashtux.org> + * + * This file is part of WeeChat, the extensible chat client. + * + * WeeChat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * WeeChat is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with WeeChat. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __WEECHAT_TRIGGER_COMPLETION_H +#define __WEECHAT_TRIGGER_COMPLETION_H 1 + +extern void trigger_completion_init (); + +#endif /* __WEECHAT_TRIGGER_COMPLETION_H */ diff --git a/src/plugins/trigger/trigger-config.c b/src/plugins/trigger/trigger-config.c new file mode 100644 index 000000000..416d0ad1f --- /dev/null +++ b/src/plugins/trigger/trigger-config.c @@ -0,0 +1,484 @@ +/* + * trigger-config.c - trigger configuration options (file trigger.conf) + * + * Copyright (C) 2014 Sébastien Helleu <flashcode@flashtux.org> + * + * This file is part of WeeChat, the extensible chat client. + * + * WeeChat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * WeeChat is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with WeeChat. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include "../weechat-plugin.h" +#include "trigger.h" +#include "trigger-config.h" + + +struct t_config_file *trigger_config_file = NULL; +struct t_config_section *trigger_config_section_trigger = NULL; + +/* trigger config, look section */ + +struct t_config_option *trigger_config_look_test; + +/* trigger config, color section */ + +struct t_config_option *trigger_config_color_regex; +struct t_config_option *trigger_config_color_replace; + + +/* + * Callback called when trigger option "enabled" is changed. + */ + +void +trigger_config_change_enabled (void *data, struct t_config_option *option) +{ + struct t_trigger *ptr_trigger; + + /* make C compiler happy */ + (void) data; + + ptr_trigger = trigger_search_with_option (option); + if (!ptr_trigger) + return; + + if (weechat_config_boolean (option)) + trigger_hook (ptr_trigger); + else + trigger_unhook (ptr_trigger); +} + +/* + * Callback called when trigger option "arguments" is changed. + */ + +void +trigger_config_change_arguments (void *data, struct t_config_option *option) +{ + struct t_trigger *ptr_trigger; + + /* make C compiler happy */ + (void) data; + + ptr_trigger = trigger_search_with_option (option); + if (!ptr_trigger) + return; + + trigger_hook (ptr_trigger); +} + +/* + * Callback called when trigger option "regex" is changed. + */ + +void +trigger_config_change_regex (void *data, struct t_config_option *option) +{ + struct t_trigger *ptr_trigger; + + /* make C compiler happy */ + (void) data; + + ptr_trigger = trigger_search_with_option (option); + if (!ptr_trigger) + return; + + trigger_set_regex (ptr_trigger); +} + +/* + * Creates an option for a trigger. + * + * Returns pointer to new option, NULL if error. + */ + +struct t_config_option * +trigger_config_create_option (const char *trigger_name, int index_option, + const char *value) +{ + struct t_config_option *ptr_option; + int length; + char *option_name; + + ptr_option = NULL; + + length = strlen (trigger_name) + 1 + + strlen (trigger_option_string[index_option]) + 1; + option_name = malloc (length); + if (!option_name) + return NULL; + + snprintf (option_name, length, "%s.%s", + trigger_name, trigger_option_string[index_option]); + + switch (index_option) + { + case TRIGGER_OPTION_ENABLED: + ptr_option = weechat_config_new_option ( + trigger_config_file, trigger_config_section_trigger, + option_name, "boolean", + N_("true if trigger is enabled, otherwise false"), + NULL, 0, 0, value, NULL, 0, + NULL, NULL, &trigger_config_change_enabled, NULL, NULL, NULL); + break; + case TRIGGER_OPTION_HOOK: + ptr_option = weechat_config_new_option ( + trigger_config_file, trigger_config_section_trigger, + option_name, "integer", + N_("type of hook used"), + "signal|hsignal|modifier|print|timer", 0, 0, value, NULL, 0, + NULL, NULL, NULL, NULL, NULL, NULL); + break; + case TRIGGER_OPTION_ARGUMENTS: + ptr_option = weechat_config_new_option ( + trigger_config_file, trigger_config_section_trigger, + option_name, "string", + N_("arguments for the hook (depend on the hook type); for " + "signal/hsignal/modifier: name, for print: " + "buffer;tags;strip_colors;message, for timer: " + "interval;align_second;max_calls"), + NULL, 0, 0, value, NULL, 0, + NULL, NULL, &trigger_config_change_arguments, NULL, NULL, NULL); + break; + case TRIGGER_OPTION_CONDITIONS: + ptr_option = weechat_config_new_option ( + trigger_config_file, trigger_config_section_trigger, + option_name, "string", + N_("condition(s) for running the command (it is checked in " + "hook callback)"), + NULL, 0, 0, value, NULL, 0, + NULL, NULL, NULL, NULL, NULL, NULL); + break; + case TRIGGER_OPTION_REGEX: + ptr_option = weechat_config_new_option ( + trigger_config_file, trigger_config_section_trigger, + option_name, "string", + N_("replace text with a POSIX extended regular expression"), + NULL, 0, 0, value, NULL, 0, + NULL, NULL, &trigger_config_change_regex, NULL, NULL, NULL); + break; + case TRIGGER_OPTION_COMMAND: + ptr_option = weechat_config_new_option ( + trigger_config_file, trigger_config_section_trigger, + option_name, "string", + N_("command run if conditions are OK"), + NULL, 0, 0, value, NULL, 0, + NULL, NULL, NULL, NULL, NULL, NULL); + break; + case TRIGGER_OPTION_RETURN_CODE: + ptr_option = weechat_config_new_option ( + trigger_config_file, trigger_config_section_trigger, + option_name, "integer", + N_("return code for hook callback"), + "ok|ok_eat|error", 0, 0, value, NULL, 0, + NULL, NULL, NULL, NULL, NULL, NULL); + break; + case TRIGGER_NUM_OPTIONS: + break; + } + + free (option_name); + + return ptr_option; +} + +/* + * Creates option for a temporary trigger (when reading configuration file). + */ + +void +trigger_config_create_option_temp (struct t_trigger *temp_trigger, + int index_option, const char *value) +{ + struct t_config_option *new_option; + + new_option = trigger_config_create_option (temp_trigger->name, + index_option, value); + if (new_option + && (index_option >= 0) && (index_option < TRIGGER_NUM_OPTIONS)) + { + temp_trigger->options[index_option] = new_option; + } +} + +/* + * Uses temporary triggers (created by reading configuration file). + */ + +void +trigger_config_use_temp_triggers () +{ + struct t_trigger *ptr_temp_trigger, *next_temp_trigger; + int i, num_options_ok; + + for (ptr_temp_trigger = triggers_temp; ptr_temp_trigger; + ptr_temp_trigger = ptr_temp_trigger->next_trigger) + { + num_options_ok = 0; + for (i = 0; i < TRIGGER_NUM_OPTIONS; i++) + { + if (!ptr_temp_trigger->options[i]) + { + ptr_temp_trigger->options[i] = + trigger_config_create_option (ptr_temp_trigger->name, + i, + trigger_option_default[i]); + } + if (ptr_temp_trigger->options[i]) + num_options_ok++; + } + + if (num_options_ok == TRIGGER_NUM_OPTIONS) + { + trigger_new_with_options (ptr_temp_trigger->name, + ptr_temp_trigger->options); + } + else + { + for (i = 0; i < TRIGGER_NUM_OPTIONS; i++) + { + if (ptr_temp_trigger->options[i]) + { + weechat_config_option_free (ptr_temp_trigger->options[i]); + ptr_temp_trigger->options[i] = NULL; + } + } + } + } + + /* free all temporary triggers */ + while (triggers_temp) + { + next_temp_trigger = triggers_temp->next_trigger; + + if (triggers_temp->name) + free (triggers_temp->name); + free (triggers_temp); + + triggers_temp = next_temp_trigger; + } + last_trigger_temp = NULL; +} + +/* + * Reads a trigger option in trigger configuration file. + */ + +int +trigger_config_trigger_read_cb (void *data, struct t_config_file *config_file, + struct t_config_section *section, + const char *option_name, const char *value) +{ + char *pos_option, *trigger_name; + struct t_trigger *ptr_temp_trigger; + int index_option; + + /* make C compiler happy */ + (void) data; + (void) config_file; + (void) section; + + if (!option_name) + return WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE; + + pos_option = strchr (option_name, '.'); + if (!pos_option) + return WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE; + + trigger_name = weechat_strndup (option_name, pos_option - option_name); + if (!trigger_name) + return WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE; + + pos_option++; + + /* search temporary trigger */ + for (ptr_temp_trigger = triggers_temp; ptr_temp_trigger; + ptr_temp_trigger = ptr_temp_trigger->next_trigger) + { + if (strcmp (ptr_temp_trigger->name, trigger_name) == 0) + break; + } + if (!ptr_temp_trigger) + { + /* create new temporary trigger */ + ptr_temp_trigger = trigger_alloc (trigger_name); + if (ptr_temp_trigger) + trigger_add (ptr_temp_trigger, &triggers_temp, &last_trigger_temp); + } + + if (ptr_temp_trigger) + { + index_option = trigger_search_option (pos_option); + if (index_option >= 0) + { + trigger_config_create_option_temp (ptr_temp_trigger, index_option, + value); + } + else + { + weechat_printf (NULL, + _("%sWarning: unknown option for section \"%s\": " + "%s (value: \"%s\")"), + weechat_prefix ("error"), + TRIGGER_CONFIG_SECTION_TRIGGER, + option_name, value); + } + } + + free (trigger_name); + + return WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE; +} + +/* + * Reloads trigger configuration file. + */ + +int +trigger_config_reload_cb (void *data, struct t_config_file *config_file) +{ + int rc; + + /* make C compiler happy */ + (void) data; + + trigger_free_all (); + + rc = weechat_config_reload (config_file); + + trigger_config_use_temp_triggers (); + + return rc; +} + +/* + * Initializes trigger configuration file. + * + * Returns: + * 1: OK + * 0: error + */ + +int +trigger_config_init () +{ + struct t_config_section *ptr_section; + + trigger_config_file = weechat_config_new (TRIGGER_CONFIG_NAME, + &trigger_config_reload_cb, NULL); + if (!trigger_config_file) + return 0; + + /* look */ + ptr_section = weechat_config_new_section (trigger_config_file, "look", + 0, 0, + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + NULL, NULL); + if (!ptr_section) + { + weechat_config_free (trigger_config_file); + return 0; + } + + trigger_config_look_test = weechat_config_new_option ( + trigger_config_file, ptr_section, + "test", "boolean", + "", + NULL, 0, 0, "on", NULL, 0, NULL, NULL, + NULL, NULL, NULL, NULL); + + /* color */ + ptr_section = weechat_config_new_section (trigger_config_file, "color", + 0, 0, + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + NULL, NULL); + if (!ptr_section) + { + weechat_config_free (trigger_config_file); + return 0; + } + + trigger_config_color_regex = weechat_config_new_option ( + trigger_config_file, ptr_section, + "regex", "color", + N_("text color for regular expressions"), + NULL, 0, 0, "white", NULL, 0, + NULL, NULL, NULL, NULL, NULL, NULL); + trigger_config_color_replace = weechat_config_new_option ( + trigger_config_file, ptr_section, + "replace", "color", + N_("text color for replacement text (for regular expressions)"), + NULL, 0, 0, "cyan", NULL, 0, + NULL, NULL, NULL, NULL, NULL, NULL); + + /* trigger */ + ptr_section = weechat_config_new_section (trigger_config_file, + TRIGGER_CONFIG_SECTION_TRIGGER, + 0, 0, + &trigger_config_trigger_read_cb, NULL, + NULL, NULL, + NULL, NULL, NULL, NULL, + NULL, NULL); + if (!ptr_section) + { + weechat_config_free (trigger_config_file); + return 0; + } + + trigger_config_section_trigger = ptr_section; + + return 1; +} + +/* + * Reads trigger configuration file. + */ + +int +trigger_config_read () +{ + int rc; + + rc = weechat_config_read (trigger_config_file); + + trigger_config_use_temp_triggers (); + + return rc; +} + +/* + * Writes trigger configuration file. + */ + +int +trigger_config_write () +{ + return weechat_config_write (trigger_config_file); +} + +/* + * Frees trigger configuration. + */ + +void +trigger_config_free () +{ + weechat_config_free (trigger_config_file); +} diff --git a/src/plugins/trigger/trigger-config.h b/src/plugins/trigger/trigger-config.h new file mode 100644 index 000000000..a6517aa2d --- /dev/null +++ b/src/plugins/trigger/trigger-config.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2014 Sébastien Helleu <flashcode@flashtux.org> + * + * This file is part of WeeChat, the extensible chat client. + * + * WeeChat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * WeeChat is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with WeeChat. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __WEECHAT_TRIGGER_CONFIG_H +#define __WEECHAT_TRIGGER_CONFIG_H 1 + +#define TRIGGER_CONFIG_NAME "trigger" +#define TRIGGER_CONFIG_SECTION_TRIGGER "trigger" + +extern struct t_config_file *trigger_config_file; +extern struct t_config_section *trigger_config_section_trigger; + +extern struct t_config_option *trigger_config_color_regex; +extern struct t_config_option *trigger_config_color_replace; + +extern struct t_config_option *trigger_config_create_option (const char *trigger_name, + int index_option, + const char *value); +extern int trigger_config_init (); +extern int trigger_config_read (); +extern int trigger_config_write (); +extern void trigger_config_free (); + +#endif /* __WEECHAT_TRIGGER_CONFIG_H */ diff --git a/src/plugins/trigger/trigger.c b/src/plugins/trigger/trigger.c new file mode 100644 index 000000000..7efa1ace4 --- /dev/null +++ b/src/plugins/trigger/trigger.c @@ -0,0 +1,894 @@ +/* + * trigger.c - trigger plugin for WeeChat + * + * Copyright (C) 2014 Sébastien Helleu <flashcode@flashtux.org> + * + * This file is part of WeeChat, the extensible chat client. + * + * WeeChat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * WeeChat is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with WeeChat. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <regex.h> + +#include "../weechat-plugin.h" +#include "trigger.h" +#include "trigger-buffer.h" +#include "trigger-callback.h" +#include "trigger-command.h" +#include "trigger-completion.h" +#include "trigger-config.h" + + +WEECHAT_PLUGIN_NAME(TRIGGER_PLUGIN_NAME); +WEECHAT_PLUGIN_DESCRIPTION(N_("Run actions on events triggered by WeeChat/plugins")); +WEECHAT_PLUGIN_AUTHOR("Sébastien Helleu <flashcode@flashtux.org>"); +WEECHAT_PLUGIN_VERSION(WEECHAT_VERSION); +WEECHAT_PLUGIN_LICENSE(WEECHAT_LICENSE); + +struct t_weechat_plugin *weechat_trigger_plugin = NULL; + +char *trigger_option_string[TRIGGER_NUM_OPTIONS] = +{ "enabled", "hook", "arguments", "conditions", "regex", "command", + "return_code" }; +char *trigger_option_default[TRIGGER_NUM_OPTIONS] = +{ "on", "signal", "", "", "", "", "ok" }; + +char *trigger_hook_type_string[TRIGGER_NUM_HOOK_TYPES] = +{ "signal", "hsignal", "modifier", "print", "timer" }; + +char *trigger_return_code_string[TRIGGER_NUM_RETURN_CODES] = +{ "ok", "ok_eat", "error" }; +int trigger_return_code[TRIGGER_NUM_RETURN_CODES] = +{ WEECHAT_RC_OK, WEECHAT_RC_OK_EAT, WEECHAT_RC_ERROR }; + +struct t_trigger *triggers = NULL; /* first trigger */ +struct t_trigger *last_trigger = NULL; /* last trigger */ +int triggers_count = 0; /* number of triggers */ + +struct t_trigger *triggers_temp = NULL; /* first temporary trigger */ +struct t_trigger *last_trigger_temp = NULL; /* last temporary trigger */ + + +/* + * Searches for a trigger option name. + * + * Returns index of option in enum t_trigger_option, -1 if not found. + */ + +int +trigger_search_option (const char *option_name) +{ + int i; + + if (!option_name) + return -1; + + for (i = 0; i < TRIGGER_NUM_OPTIONS; i++) + { + if (weechat_strcasecmp (trigger_option_string[i], option_name) == 0) + return i; + } + + /* trigger option not found */ + return -1; +} + +/* + * Searches for trigger hook type. + * + * Returns index of hook type in enum t_trigger_hook_type, -1 if not found. + */ + +int +trigger_search_hook_type (const char *type) +{ + int i; + + for (i = 0; i < TRIGGER_NUM_HOOK_TYPES; i++) + { + if (weechat_strcasecmp (trigger_hook_type_string[i], type) == 0) + return i; + } + + /* hook type not found */ + return -1; +} + +/* + * Searches for trigger return code. + * + * Returns index of return code in enum t_trigger_return_code, -1 if not found. + */ + +int +trigger_search_return_code (const char *return_code) +{ + int i; + + for (i = 0; i < TRIGGER_NUM_RETURN_CODES; i++) + { + if (weechat_strcasecmp (trigger_return_code_string[i], return_code) == 0) + return i; + } + + /* return code not found */ + return -1; +} + +/* + * Searches for a trigger by name. + * + * Returns pointer to trigger found, NULL if not found. + */ + +struct t_trigger * +trigger_search (const char *name) +{ + struct t_trigger *ptr_trigger; + + if (!name || !name[0]) + return NULL; + + for (ptr_trigger = triggers; ptr_trigger; + ptr_trigger = ptr_trigger->next_trigger) + { + if (strcmp (ptr_trigger->name, name) == 0) + return ptr_trigger; + } + + /* trigger not found */ + return NULL; +} + +/* + * Searches for a trigger with a pointer to a trigger option. + * + * Returns pointer to trigger found, NULL if not found. + */ + +struct t_trigger * +trigger_search_with_option (struct t_config_option *option) +{ + const char *ptr_name; + char *pos_option; + struct t_trigger *ptr_trigger; + + ptr_name = weechat_hdata_string (weechat_hdata_get ("config_option"), + option, "name"); + if (!ptr_name) + return NULL; + + pos_option = strchr (ptr_name, '.'); + if (!pos_option) + return NULL; + + for (ptr_trigger = triggers; ptr_trigger; + ptr_trigger = ptr_trigger->next_trigger) + { + if (strncmp (ptr_trigger->name, ptr_name, pos_option - ptr_name) == 0) + break; + } + + return ptr_trigger; +} + +/* + * Frees all the regex in a trigger. + */ + +void +trigger_free_regex (struct t_trigger *trigger) +{ + int i; + + if (trigger->regex_count > 0) + { + for (i = 0; i < trigger->regex_count; i++) + { + if (trigger->regex[i].str_regex) + free (trigger->regex[i].str_regex); + if (trigger->regex[i].regex) + { + regfree (trigger->regex[i].regex); + free (trigger->regex[i].regex); + } + if (trigger->regex[i].replace) + free (trigger->regex[i].replace); + if (trigger->regex[i].replace_eval) + free (trigger->regex[i].replace_eval); + } + free (trigger->regex); + trigger->regex = NULL; + trigger->regex_count = 0; + } +} + +/* + * Sets the regex and replacement text in a trigger. + */ + +void +trigger_set_regex (struct t_trigger *trigger) +{ + const char *option_regex, *pos, *pos2; + char *delimiter; + int i, length_delimiter, regex_count; + + delimiter = NULL; + + /* remove all regex in the trigger */ + trigger_free_regex (trigger); + + /* get regex option in trigger */ + option_regex = weechat_config_string (trigger->options[TRIGGER_OPTION_REGEX]); + if (!option_regex || !option_regex[0]) + goto end; + + /* min 3 chars, for example: "/a/" */ + if (strlen (option_regex) < 3) + goto format_error; + + /* search the delimiter (which can be more than one char) */ + pos = weechat_utf8_next_char (option_regex); + while (pos[0] && weechat_utf8_charcmp (option_regex, pos) == 0) + { + pos = weechat_utf8_next_char (pos); + } + if (!pos[0]) + goto format_error; + delimiter = weechat_strndup (option_regex, pos - option_regex); + if (!delimiter) + goto memory_error; + if (strcmp (delimiter, "\\") == 0) + goto format_error; + + length_delimiter = strlen (delimiter); + + /* count the number of regex in the option */ + regex_count = 0; + pos = option_regex; + while (pos && pos[0]) + { + /* + * if option "regex" ends with a delimiter, just ignore it + * and exit the loop + */ + pos += length_delimiter; + if (!pos[0]) + break; + + /* search the start of replacement string */ + pos = strstr (pos + length_delimiter, delimiter); + if (!pos) + goto format_error; + + regex_count++; + + /* search the start of next regex */ + pos = strstr (pos + length_delimiter, delimiter); + } + + /* at least one regex is needed */ + if (regex_count == 0) + goto format_error; + + /* allocate with array of regex/replacement */ + trigger->regex = malloc (regex_count * sizeof (trigger->regex[0])); + if (!trigger->regex) + goto memory_error; + + /* initialize regex */ + for (i = 0; i < trigger->regex_count; i++) + { + trigger->regex[i].str_regex = NULL; + trigger->regex[i].regex = NULL; + trigger->regex[i].replace = NULL; + trigger->regex[i].replace_eval = NULL; + } + trigger->regex_count = regex_count; + + /* allocate regex and replacement */ + i = 0; + pos = option_regex; + while (pos && pos[0]) + { + pos += length_delimiter; + if (!pos[0]) + break; + + pos2 = strstr (pos + length_delimiter, delimiter); + if (!pos) + break; + + trigger->regex[i].str_regex = weechat_strndup (pos, pos2 - pos); + if (!trigger->regex[i].str_regex) + goto memory_error; + trigger->regex[i].regex = malloc (sizeof (*trigger->regex[i].regex)); + if (!trigger->regex[i].regex) + goto memory_error; + if (weechat_string_regcomp (trigger->regex[i].regex, + trigger->regex[i].str_regex, + REG_EXTENDED | REG_ICASE) != 0) + { + weechat_printf (NULL, + _("%s%s: error compiling regular expression \"%s\""), + weechat_prefix ("error"), TRIGGER_PLUGIN_NAME, + trigger->regex[i].str_regex); + free (trigger->regex[i].regex); + trigger->regex[i].regex = NULL; + goto end; + } + + pos = pos2 + length_delimiter; + + pos2 = strstr (pos + length_delimiter, delimiter); + trigger->regex[i].replace = (pos2) ? + weechat_strndup (pos, pos2 - pos) : strdup (pos); + if (!trigger->regex[i].replace) + goto memory_error; + trigger->regex[i].replace_eval = + weechat_string_eval_expression (trigger->regex[i].replace, + NULL, NULL, NULL); + + pos = pos2; + + i++; + } + + goto end; + +format_error: + weechat_printf (NULL, + _("%s%s: invalid value for option \"replace\", format " + "is: \"/regex/replace\" (the char '/' can be " + "replaced by one or more identical chars, except '\\' " + "which is used for matching groups)"), + weechat_prefix ("error"), TRIGGER_PLUGIN_NAME); + trigger_free_regex (trigger); + goto end; + +memory_error: + weechat_printf (NULL, + _("%s%s: not enough memory"), + weechat_prefix ("error"), TRIGGER_PLUGIN_NAME); + trigger_free_regex (trigger); + goto end; + +end: + if (delimiter) + free (delimiter); +} + +/* + * Unhooks things hooked in a trigger. + */ + +void +trigger_unhook (struct t_trigger *trigger) +{ + int i; + + if (trigger->hooks) + { + for (i = 0; i < trigger->hooks_count; i++) + { + weechat_unhook (trigger->hooks[i]); + } + free (trigger->hooks); + trigger->hooks = NULL; + trigger->hooks_count = 0; + } + trigger->hook_count_cb = 0; + trigger->hook_count_cmd = 0; + if (trigger->hook_print_buffers) + { + free (trigger->hook_print_buffers); + trigger->hook_print_buffers = NULL; + } +} + +/* + * Creates hook(s) in a trigger. + */ + +void +trigger_hook (struct t_trigger *trigger) +{ + char **argv, **argv_eol, *tags, *message; + int i, argc, strip_colors; + + trigger_unhook (trigger); + + argv = weechat_string_split (weechat_config_string (trigger->options[TRIGGER_OPTION_ARGUMENTS]), + ";", 0, 0, &argc); + argv_eol = weechat_string_split (weechat_config_string (trigger->options[TRIGGER_OPTION_ARGUMENTS]), + ";", 1, 0, NULL); + + switch (weechat_config_integer (trigger->options[TRIGGER_OPTION_HOOK])) + { + case TRIGGER_HOOK_SIGNAL: + if (argv && (argc >= 1)) + { + trigger->hooks = malloc (argc * sizeof (trigger->hooks[0])); + if (trigger->hooks) + { + trigger->hooks_count = argc; + for (i = 0; i < argc; i++) + { + trigger->hooks[i] = weechat_hook_signal (argv[i], + &trigger_callback_signal_cb, + trigger); + } + } + } + break; + case TRIGGER_HOOK_HSIGNAL: + if (argv && (argc >= 1)) + { + trigger->hooks = malloc (argc * sizeof (trigger->hooks[0])); + if (trigger->hooks) + { + trigger->hooks_count = argc; + for (i = 0; i < argc; i++) + { + trigger->hooks[i] = weechat_hook_hsignal (argv[i], + &trigger_callback_hsignal_cb, + trigger); + } + } + } + break; + case TRIGGER_HOOK_MODIFIER: + if (argv && (argc >= 1)) + { + trigger->hooks = malloc (argc * sizeof (trigger->hooks[0])); + if (trigger->hooks) + { + trigger->hooks_count = argc; + for (i = 0; i < argc; i++) + { + trigger->hooks[i] = weechat_hook_modifier (argv[i], + &trigger_callback_modifier_cb, + trigger); + } + } + } + break; + case TRIGGER_HOOK_PRINT: + tags = NULL; + message = NULL; + strip_colors = 0; + if (argv && (argc >= 1)) + { + if (strcmp (argv[0], "*") != 0) + trigger->hook_print_buffers = strdup (argv[0]); + if ((argc >= 2) && (strcmp (argv[1], "*") != 0)) + tags = argv[1]; + if ((argc >= 3) && (strcmp (argv[2], "*") != 0)) + message = argv[2]; + if (argc >= 4) + strip_colors = (strcmp (argv[3], "0") != 0) ? 1 : 0; + } + trigger->hooks = malloc (1 * sizeof (trigger->hooks[0])); + if (trigger->hooks) + { + trigger->hooks_count = 1; + trigger->hooks[0] = weechat_hook_print (NULL, tags, message, + strip_colors, + &trigger_callback_print_cb, + trigger); + } + break; + case TRIGGER_HOOK_TIMER: + if (argv && (argc >= 1)) + { + } + break; + } + + if (!trigger->hooks) + { + weechat_printf (NULL, + _("%sError: unable to create hook for trigger \"%s\" " + "(bad arguments)"), + weechat_prefix ("error"), trigger->name); + } + + if (argv) + weechat_string_free_split (argv); + if (argv_eol) + weechat_string_free_split (argv_eol); +} + +/* + * Allocates and initializes new trigger structure. + * + * Returns pointer to new trigger, NULL if error. + */ + +struct t_trigger * +trigger_alloc (const char *name) +{ + struct t_trigger *new_trigger; + int i; + + if (trigger_search (name)) + return NULL; + + new_trigger = malloc (sizeof (*new_trigger)); + if (!new_trigger) + return NULL; + + new_trigger->name = strdup (name); + for (i = 0; i < TRIGGER_NUM_OPTIONS; i++) + { + new_trigger->options[i] = NULL; + } + new_trigger->regex_count = 0; + new_trigger->regex = NULL; + new_trigger->hooks_count = 0; + new_trigger->hooks = NULL; + new_trigger->hook_count_cb = 0; + new_trigger->hook_count_cmd = 0; + new_trigger->hook_running = 0; + new_trigger->hook_print_buffers = NULL; + new_trigger->prev_trigger = NULL; + new_trigger->next_trigger = NULL; + + return new_trigger; +} + +/* + * Adds trigger to the end of a linked list. + */ + +void +trigger_add (struct t_trigger *trigger, + struct t_trigger **triggers, struct t_trigger **last_trigger) +{ + trigger->prev_trigger = *last_trigger; + trigger->next_trigger = NULL; + if (!*triggers) + *triggers = trigger; + else + (*last_trigger)->next_trigger = trigger; + *last_trigger = trigger; + + triggers_count++; +} + +/* + * Creates a new trigger with options. + * + * Returns pointer to new trigger, NULL if error. + */ + +struct t_trigger * +trigger_new_with_options (const char *name, struct t_config_option **options) +{ + struct t_trigger *new_trigger; + int i; + + new_trigger = trigger_alloc (name); + if (!new_trigger) + return NULL; + + for (i = 0; i < TRIGGER_NUM_OPTIONS; i++) + { + new_trigger->options[i] = options[i]; + } + trigger_add (new_trigger, &triggers, &last_trigger); + + trigger_set_regex (new_trigger); + + if (weechat_config_boolean (new_trigger->options[TRIGGER_OPTION_ENABLED])) + trigger_hook (new_trigger); + + return new_trigger; +} + +/* + * Creates a new trigger. + * + * Returns pointer to new trigger, NULL if error. + */ + +struct t_trigger * +trigger_new (const char *name, const char *enabled, const char *hook, + const char *arguments, const char *conditions, const char *regex, + const char *command, const char *return_code) +{ + struct t_config_option *option[TRIGGER_NUM_OPTIONS]; + const char *value[TRIGGER_NUM_OPTIONS]; + struct t_trigger *new_trigger; + int i; + + /* it's not possible to create 2 triggers with same name */ + if (trigger_search (name)) + return NULL; + + /* look for type */ + if (trigger_search_hook_type (hook) < 0) + return NULL; + + /* look for return code */ + if (trigger_search_return_code (return_code) < 0) + return NULL; + + value[TRIGGER_OPTION_ENABLED] = enabled; + value[TRIGGER_OPTION_HOOK] = hook; + value[TRIGGER_OPTION_ARGUMENTS] = arguments; + value[TRIGGER_OPTION_CONDITIONS] = conditions; + value[TRIGGER_OPTION_REGEX] = regex; + value[TRIGGER_OPTION_COMMAND] = command; + value[TRIGGER_OPTION_RETURN_CODE] = return_code; + + for (i = 0; i < TRIGGER_NUM_OPTIONS; i++) + { + option[i] = trigger_config_create_option (name, i, value[i]); + } + + new_trigger = trigger_new_with_options (name, option); + if (!new_trigger) + { + for (i = 0; i < TRIGGER_NUM_OPTIONS; i++) + { + weechat_config_option_free (option[i]); + } + } + + return new_trigger; +} + +/* + * Renames a trigger. + */ + +void +trigger_rename (struct t_trigger *trigger, const char *name) +{ + int length, i; + char *option_name; + + if (!name || !name[0]) + return; + + length = strlen (name) + 64; + option_name = malloc (length); + if (!option_name) + return; + + for (i = 0; i < TRIGGER_NUM_OPTIONS; i++) + { + if (trigger->options[i]) + { + snprintf (option_name, length, + "%s.%s", + name, + trigger_option_string[i]); + weechat_config_option_rename (trigger->options[i], option_name); + } + } + + if (trigger->name) + free (trigger->name); + trigger->name = strdup (name); + + free (option_name); +} + +/* + * Deletes a trigger. + */ + +void +trigger_free (struct t_trigger *trigger) +{ + int i; + + if (!trigger) + return; + + /* remove trigger from triggers list */ + if (trigger->prev_trigger) + (trigger->prev_trigger)->next_trigger = trigger->next_trigger; + if (trigger->next_trigger) + (trigger->next_trigger)->prev_trigger = trigger->prev_trigger; + if (triggers == trigger) + triggers = trigger->next_trigger; + if (last_trigger == trigger) + last_trigger = trigger->prev_trigger; + + /* free data */ + trigger_unhook (trigger); + trigger_free_regex (trigger); + if (trigger->name) + free (trigger->name); + for (i = 0; i < TRIGGER_NUM_OPTIONS; i++) + { + if (trigger->options[i]) + weechat_config_option_free (trigger->options[i]); + } + + free (trigger); + + triggers_count--; +} + +/* + * Deletes all triggers. + */ + +void +trigger_free_all () +{ + while (triggers) + { + trigger_free (triggers); + } +} + +/* + * Prints trigger infos in WeeChat log file (usually for crash dump). + */ + +void +trigger_print_log () +{ + struct t_trigger *ptr_trigger; + int i; + + for (ptr_trigger = triggers; ptr_trigger; + ptr_trigger = ptr_trigger->next_trigger) + { + weechat_log_printf (""); + weechat_log_printf ("[trigger (addr:0x%lx)]", ptr_trigger); + weechat_log_printf (" name. . . . . . . . . . : '%s'", ptr_trigger->name); + weechat_log_printf (" enabled . . . . . . . . : %d", + weechat_config_integer (ptr_trigger->options[TRIGGER_OPTION_ENABLED])); + weechat_log_printf (" hook . . . . . . . . . : %d ('%s')", + weechat_config_integer (ptr_trigger->options[TRIGGER_OPTION_HOOK]), + trigger_hook_type_string[weechat_config_integer (ptr_trigger->options[TRIGGER_OPTION_HOOK])]); + weechat_log_printf (" arguments . . . . . . . : '%s'", + weechat_config_string (ptr_trigger->options[TRIGGER_OPTION_ARGUMENTS])); + weechat_log_printf (" conditions. . . . . . . : '%s'", + weechat_config_string (ptr_trigger->options[TRIGGER_OPTION_CONDITIONS])); + weechat_log_printf (" regex . . . . . . . . . : '%s'", + weechat_config_string (ptr_trigger->options[TRIGGER_OPTION_REGEX])); + weechat_log_printf (" command . . . . . . . . : '%s'", + weechat_config_string (ptr_trigger->options[TRIGGER_OPTION_COMMAND])); + weechat_log_printf (" return_code . . . . . . : %d ('%s')", + weechat_config_integer (ptr_trigger->options[TRIGGER_OPTION_RETURN_CODE]), + trigger_return_code_string[weechat_config_integer (ptr_trigger->options[TRIGGER_OPTION_RETURN_CODE])]); + weechat_log_printf (" regex_count . . . . . . : %d", ptr_trigger->regex_count); + weechat_log_printf (" regex . . . . . . . . . : 0x%lx", ptr_trigger->regex); + for (i = 0; i < ptr_trigger->regex_count; i++) + { + weechat_log_printf (" regex[%03d].regex. . . : 0x%lx", + i, ptr_trigger->regex[i].regex); + weechat_log_printf (" regex[%03d].replace. . : '%s'", + i, ptr_trigger->regex[i].replace); + } + weechat_log_printf (" hooks_count . . . . . . : %d", ptr_trigger->hooks_count); + weechat_log_printf (" hooks . . . . . . . . . : 0x%lx", ptr_trigger->hooks); + for (i = 0; i < ptr_trigger->hooks_count; i++) + { + weechat_log_printf (" hooks[%03d]. . . . . . : 0x%lx", + i, ptr_trigger->hooks[i]); + } + weechat_log_printf (" hook_count_cb . . . . . : %lu", ptr_trigger->hook_count_cb); + weechat_log_printf (" hook_count_cmd. . . . . : %lu", ptr_trigger->hook_count_cmd); + weechat_log_printf (" hook_running. . . . . . : %d", ptr_trigger->hook_running); + weechat_log_printf (" hook_print_buffers. . . : '%s'", ptr_trigger->hook_print_buffers); + weechat_log_printf (" prev_trigger. . . . . . : 0x%lx", ptr_trigger->prev_trigger); + weechat_log_printf (" next_trigger. . . . . . : 0x%lx", ptr_trigger->next_trigger); + } +} + +/* + * Callback for signal "debug_dump". + */ + +int +trigger_debug_dump_cb (void *data, const char *signal, const char *type_data, + void *signal_data) +{ + /* make C compiler happy */ + (void) data; + (void) signal; + (void) type_data; + + if (!signal_data + || (weechat_strcasecmp ((char *)signal_data, TRIGGER_PLUGIN_NAME) == 0)) + { + weechat_log_printf (""); + weechat_log_printf ("***** \"%s\" plugin dump *****", + weechat_plugin->name); + + trigger_print_log (); + + weechat_log_printf (""); + weechat_log_printf ("***** End of \"%s\" plugin dump *****", + weechat_plugin->name); + } + + return WEECHAT_RC_OK; +} + +/* + * Initializes trigger plugin. + */ + +int +weechat_plugin_init (struct t_weechat_plugin *plugin, int argc, char *argv[]) +{ + int i, upgrading; + + /* make C compiler happy */ + (void) argc; + (void) argv; + + weechat_plugin = plugin; + + trigger_callback_init (); + + trigger_command_init (); + + if (!trigger_config_init ()) + return WEECHAT_RC_ERROR; + + trigger_config_read (); + + /* hook some signals */ + weechat_hook_signal ("debug_dump", &trigger_debug_dump_cb, NULL); + + /* hook completions */ + trigger_completion_init (); + + /* look at arguments */ + upgrading = 0; + for (i = 0; i < argc; i++) + { + if (weechat_strcasecmp (argv[i], "--upgrade") == 0) + { + upgrading = 1; + } + } + + if (upgrading) + trigger_buffer_set_callbacks (); + + return WEECHAT_RC_OK; +} + +/* + * Ends trigger plugin. + */ + +int +weechat_plugin_end (struct t_weechat_plugin *plugin) +{ + /* make C compiler happy */ + (void) plugin; + + trigger_config_write (); + trigger_config_free (); + + trigger_callback_end (); + + return WEECHAT_RC_OK; +} diff --git a/src/plugins/trigger/trigger.h b/src/plugins/trigger/trigger.h new file mode 100644 index 000000000..5ddc81e8c --- /dev/null +++ b/src/plugins/trigger/trigger.h @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2014 Sébastien Helleu <flashcode@flashtux.org> + * + * This file is part of WeeChat, the extensible chat client. + * + * WeeChat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * WeeChat is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with WeeChat. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __WEECHAT_TRIGGER_H +#define __WEECHAT_TRIGGER_H 1 + +#include <regex.h> + +#define weechat_plugin weechat_trigger_plugin +#define TRIGGER_PLUGIN_NAME "trigger" + +enum t_trigger_option +{ + TRIGGER_OPTION_ENABLED = 0, /* true if trigger is enabled */ + TRIGGER_OPTION_HOOK, /* hook (signal, modifier, ...) */ + TRIGGER_OPTION_ARGUMENTS, /* arguments for hook */ + TRIGGER_OPTION_CONDITIONS, /* conditions for trigger */ + TRIGGER_OPTION_REGEX, /* replace text with 1 or more regex */ + TRIGGER_OPTION_COMMAND, /* command run if conditions are OK */ + TRIGGER_OPTION_RETURN_CODE, /* return code for hook callback */ + /* number of trigger options */ + TRIGGER_NUM_OPTIONS, +}; + +enum t_trigger_hook_type +{ + TRIGGER_HOOK_SIGNAL = 0, + TRIGGER_HOOK_HSIGNAL, + TRIGGER_HOOK_MODIFIER, + TRIGGER_HOOK_PRINT, + TRIGGER_HOOK_TIMER, + /* number of hook types */ + TRIGGER_NUM_HOOK_TYPES, +}; + +enum t_trigger_return_code +{ + TRIGGER_RC_OK = 0, + TRIGGER_RC_OK_EAT, + TRIGGER_RC_ERROR, + /* number of return codes */ + TRIGGER_NUM_RETURN_CODES, +}; + +struct t_trigger_regex +{ + char *str_regex; /* regex to search for replacement */ + regex_t *regex; /* compiled regex */ + char *replace; /* replacement text */ + char *replace_eval; /* evaluatued replacement text */ +}; + +struct t_trigger +{ + /* user choices */ + char *name; /* trigger name */ + struct t_config_option *options[TRIGGER_NUM_OPTIONS]; + + /* internal vars */ + + /* regular expressions with their replacement text */ + int regex_count; /* number of regex/replacement */ + struct t_trigger_regex *regex; /* array of regex/replacement */ + + /* hooks */ + int hooks_count; /* number of hooks */ + struct t_hook **hooks; /* array of hooks (signal, ...) */ + unsigned long hook_count_cb; /* number of calls made to callback */ + unsigned long hook_count_cmd; /* number of commands run in callback*/ + int hook_running; /* 1 if one hook callback is running */ + char *hook_print_buffers; /* buffers (for hook_print only) */ + + /* links to other triggers */ + struct t_trigger *prev_trigger; /* link to previous trigger */ + struct t_trigger *next_trigger; /* link to next trigger */ +}; + +extern struct t_weechat_plugin *weechat_trigger_plugin; +extern char *trigger_option_string[]; +extern char *trigger_option_default[]; +extern char *trigger_hook_type_string[]; +extern int trigger_return_code[]; +extern struct t_trigger *triggers; +extern struct t_trigger *last_trigger; +extern int triggers_count; +extern struct t_trigger *triggers_temp; +extern struct t_trigger *last_trigger_temp; + +extern int trigger_search_option (const char *option_name); +extern int trigger_search_hook_type (const char *type); +extern struct t_trigger *trigger_search (const char *name); +extern struct t_trigger *trigger_search_with_option (struct t_config_option *option); +extern void trigger_set_regex (struct t_trigger *trigger); +extern void trigger_unhook (struct t_trigger *trigger); +extern void trigger_hook (struct t_trigger *trigger); +extern struct t_trigger *trigger_alloc (const char *name); +extern void trigger_add (struct t_trigger *trigger, struct t_trigger **triggers, + struct t_trigger **last_trigger); +extern struct t_trigger *trigger_new_with_options (const char *name, + struct t_config_option **options); +extern struct t_trigger *trigger_new (const char *name, + const char *enabled, + const char *hook, + const char *arguments, + const char *conditions, + const char *replace, + const char *command, + const char *return_code); +extern void trigger_rename (struct t_trigger *trigger, const char *name); +extern void trigger_free (struct t_trigger *trigger); +extern void trigger_free_all (); + +#endif /* __WEECHAT_TRIGGER_H */ diff --git a/src/plugins/weechat-plugin.h b/src/plugins/weechat-plugin.h index 87df384aa..9eaccb9ba 100644 --- a/src/plugins/weechat-plugin.h +++ b/src/plugins/weechat-plugin.h @@ -28,6 +28,7 @@ extern "C" { #include <sys/types.h> #include <sys/socket.h> +#include <regex.h> /* some systems like GNU/Hurd do not define PATH_MAX */ #ifndef PATH_MAX @@ -57,7 +58,7 @@ struct timeval; * please change the date with current one; for a second change at same * date, increment the 01, otherwise please keep 01. */ -#define WEECHAT_PLUGIN_API_VERSION "20140122-01" +#define WEECHAT_PLUGIN_API_VERSION "20140125-01" /* macros for defining plugin infos */ #define WEECHAT_PLUGIN_NAME(__name) \ @@ -243,10 +244,12 @@ struct t_weechat_plugin char *(*string_mask_to_regex) (const char *mask); const char *(*string_regex_flags) (const char *regex, int default_flags, int *flags); - int (*string_regcomp) (void *preg, const char *regex, int default_flags); + int (*string_regcomp) (regex_t *preg, const char *regex, int default_flags); int (*string_has_highlight) (const char *string, const char *highlight_words); int (*string_has_highlight_regex) (const char *string, const char *regex); + char *(*string_replace_regex) (const char *string, regex_t *regex, + const char *replace); char **(*string_split) (const char *string, const char *separators, int keep_eol, int num_items_max, int *num_items); void (*string_free_split) (char **split_string); @@ -1008,6 +1011,8 @@ extern int weechat_plugin_end (struct t_weechat_plugin *plugin); weechat_plugin->string_has_highlight(__string, __highlight_words) #define weechat_string_has_highlight_regex(__string, __regex) \ weechat_plugin->string_has_highlight_regex(__string, __regex) +#define weechat_string_replace_regex(__string, __regex, __replace) \ + weechat_plugin->string_replace_regex(__string, __regex, __replace) #define weechat_string_split(__string, __separator, __eol, __max, \ __num_items) \ weechat_plugin->string_split(__string, __separator, __eol, \ |