--- a/templates.diff Thu Aug 09 00:58:48 2012 +0300
+++ b/templates.diff Thu Aug 16 00:25:08 2012 +0300
@@ -1,10 +1,10 @@
# HG changeset patch
-# Parent 5ccd3db13ba7f9636bb686af68df9f5b281d343d
+# Parent 00b93fdbb1de8e77b6992cf799715c1da3e61746
[work-in-progress] Use templates for statusbars
-diff -r 5ccd3db13ba7 mcabber/CMakeLists.txt
---- a/mcabber/CMakeLists.txt Wed Aug 08 21:10:27 2012 +0300
-+++ b/mcabber/CMakeLists.txt Thu Aug 09 00:11:13 2012 +0300
+diff -r 00b93fdbb1de mcabber/CMakeLists.txt
+--- a/mcabber/CMakeLists.txt Thu Aug 09 00:54:37 2012 +0300
++++ b/mcabber/CMakeLists.txt Thu Aug 16 00:23:26 2012 +0300
@@ -154,8 +154,8 @@
## Define targets
@@ -16,10 +16,10 @@
xmpp xmpp_helper xmpp_iq xmpp_iqrequest xmpp_muc xmpp_s10n )
if ( NOT MODULES_ENABLE )
list ( APPEND mcabber_SUBSYSTEMS extcmd fifo )
-diff -r 5ccd3db13ba7 mcabber/mcabber/markup_parser.c
+diff -r 00b93fdbb1de mcabber/mcabber/parser.c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
-+++ b/mcabber/mcabber/markup_parser.c Thu Aug 09 00:11:13 2012 +0300
-@@ -0,0 +1,45 @@
++++ b/mcabber/mcabber/parser.c Thu Aug 16 00:23:26 2012 +0300
+@@ -0,0 +1,1116 @@
+
+/* Copyright 2012 Myhailo Danylenko
+ *
@@ -48,734 +48,20 @@
+
+#include "config.h"
+
-+typedef gchar * ( * markup_chunk_callback_t ) ( gchar chunk, gpointer userdata );
-+
-+typedef struct markup_link_struct markup_link_t;
-+struct markup_link_struct {
-+ ...
-+};
-+
+//
-+// Private types
-+//
-+
-+
-+markup_link_t * markup_expand ( const gchar *expansion, markup_chunk_callback_t get_chunk, gpointer userdata, GError ** error )
-+{
-+}
-+
-+/* vim: se ts=4 sw=4: */
-diff -r 5ccd3db13ba7 mcabber/mcabber/markup_parser.h
---- /dev/null Thu Jan 01 00:00:00 1970 +0000
-+++ b/mcabber/mcabber/markup_parser.h Thu Aug 09 00:11:13 2012 +0300
-@@ -0,0 +1,77 @@
-+
-+#ifndef MCABBER_PARSER_H
-+#define MCABBER_PARSER_H
-+
-+/* Copyright 2012 Myhailo Danylenko
-+ *
-+ * This file is part of mcabber
-+ *
-+ * mcabber 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 2 of the License, or
-+ * (at your option) any later version.
-+ *
-+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */
-+
-+#include <glib.h>
-+
-+#define TMPL_GERROR_QUARK tmpl_gerror_quark ()
-+
-+#define TMPL_EWRONGVAREXP ( 0x01 )
-+#define TMPL_EWRONGVARNAME ( 0x03 )
-+#define TMPL_EUNBALPAREN ( 0x04 )
-+#define TMPL_EWRONGENDSTATE ( 0x02 )
-+
-+#define TMPL_EMATHWRONGARG ( 0x05 )
-+#define TMPL_EMATHWRONGNUMBER ( 0x06 )
-+#define TMPL_EMATHUNBALPAREN ( 0x07 )
-+#define TMPL_EMATHWRONGOP ( 0x08 )
-+#define TMPL_EMATHWRONGSTATE ( 0x09 )
-+
-+typedef const char *(*tmpl_variable_callback_t) ( const gchar *name, gsize len, gpointer udata, gsize *ret_len );
-+
-+GQuark tmpl_gerror_quark ( void );
-+
-+// match tmpl_glob ( string, pattern, right-to-left )
-+// Performs matching of given pattern against given string,
-+// returns substring, that matched. In pattern, there are currently
-+// two tokens recognized: '*' and '?'. Two flags determine, if
-+// '*' should be greedy and which end of string must match the
-+// pattern (i.e. have anchor).
-+const char *tmpl_glob ( const char *str, gsize strlen, const char *pat, gsize patlen, gboolean rtl, gsize *ret_len );
-+
-+// match tmpl_greedy_glob ( string, pattern, rigt-to-left )
-+// The same, as above, but greedy.
-+const char *tmpl_greedy_glob ( const char *str, gsize strlen, const char *pat, gsize patlen, gboolean rtl, gsize *ret_len );
-+
-+// result tmpl_math_expand ( string, callback )
-+// Performs mathematical expansion of given string.
-+// Note, that $var expressions are not recognized, you have to
-+// supply already expanded string here.
-+// Supported operators:
-+// ( ) + - * / %
-+gssize tmpl_math_expand ( const char *str, gsize len, tmpl_variable_callback_t get_val, gpointer udata, GError **error );
-+
-+// expansion tmpl_expand ( template, callback )
-+// Parse template, substitute shell-like expressions:
-+// * $var ${var}
-+// * ${var##pat} ${var#pat} ${var%%pat} ${var%pat}
-+// * ${var/pat} ${var/pat/repl} ${var//pat/repl} ${var/%pat/repl} ${var/#pat/repl}
-+// * ${var:+tmpl} ${var:-tmpl}
-+// * ${var:mathexp} ${var:mathexp:mathexp}
-+// * ${#var} ${!var} ${!var[operation]}
-+// * $(( mathexp ))
-+// * \n \t \e \$ \\ \X
-+// Callback will be called to obtain variable values.
-+// Variable name should be: /^[[:alnum:]][[:alnum:]_-]*$/
-+// For pattern rules see mms_glob ().
-+gchar *tmpl_expand ( const char *template, gsize len, tmpl_variable_callback_t get_val, gpointer udata, gsize *ret_len, GError **error );
-+
-+# endif
-+
-diff -r 5ccd3db13ba7 mcabber/mcabber/settings.c
---- a/mcabber/mcabber/settings.c Wed Aug 08 21:10:27 2012 +0300
-+++ b/mcabber/mcabber/settings.c Thu Aug 09 00:11:13 2012 +0300
-@@ -363,6 +363,32 @@
- return 0;
- }
-
-+// settings_tmpl_set(option, value)
-+// Sets option, escaping it for use in templates.
-+// Could be more efficient with notifiers instead of guards.
-+void settings_tmpl_set(const gchar *key, const gchar *value)
-+{
-+ if (value && *value) {
-+ const gchar *p = strchr(value, '%');
-+ if (p) {
-+ GString *result = g_string_new(NULL);
-+ const gchar *s = value;
-+ while (p) {
-+ g_string_append_len(result, s, p - s + 1);
-+ g_string_append_c(result, '%');
-+ s = ++ p;
-+ p = strchr(s, '%');
-+ }
-+ if (*s)
-+ g_string_append(result, s);
-+ settings_set(SETTNIGS_TYPE_OPTION, key, result -> str);
-+ g_string_free(result, TRUE);
-+ return;
-+ }
-+ }
-+ settings_set ( SETTNIGS_TYPE_OPTION, key, value );
-+}
-+
- // settings_get_status_msg(status)
- // Return a string with the current status message:
- // - if there is a user-defined message ("message" option),
-diff -r 5ccd3db13ba7 mcabber/mcabber/settings.h
---- a/mcabber/mcabber/settings.h Wed Aug 08 21:10:27 2012 +0300
-+++ b/mcabber/mcabber/settings.h Thu Aug 09 00:11:13 2012 +0300
-@@ -37,6 +37,7 @@
- void settings_opt_set_raw(const gchar *key, const gchar *value);
- void settings_set(guint type, const gchar *key, const gchar *value);
- void settings_del(guint type, const gchar *key);
-+void settings_tmpl_set(const gchar *key, const gchar *value);
- const gchar *settings_get(guint type, const gchar *key);
- int settings_get_int(guint type, const gchar *key);
- const gchar *settings_get_status_msg(enum imstatus status);
-diff -r 5ccd3db13ba7 mcabber/mcabber/templates.c
---- /dev/null Thu Jan 01 00:00:00 1970 +0000
-+++ b/mcabber/mcabber/templates.c Thu Aug 09 00:11:13 2012 +0300
-@@ -0,0 +1,390 @@
-+
-+/* Copyright 2012 Myhailo Danylenko
-+ *
-+ * This file is part of mcabber
-+ *
-+ * mcabber 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 2 of the License, or
-+ * (at your option) any later version.
-+ *
-+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */
-+
-+#include <glib.h>
-+
-+#include <string.h> // strlen
-+
-+#include "logprint.h"
-+#include "settings.h"
-+#include "main.h" // main_context
-+#include "templates.h"
-+#include "parser.h"
-+
-+//
-+// globals
-+//
-+
-+typedef void (*tmpl_callback_t) ( const gchar * expansion, gpointer udata );
-+typedef void (*tmpl_root_callback_t) ( const tmpl_link_struct_t * expansion, gpointer udata );
-+
-+typedef enum {
-+ root_template, // fully processed $ and % templates
-+ shortcut_template, // no-callback $-only but %-propagating templates
-+ user_template, // $-only templates with callback
-+} template_type_t;
-+
-+typedef struct {
-+ gchar * name; // variable name
-+ template_type_t type; // can be only one (for simplicity)
-+ tmpl_callback_t callback; // for root it is %-callback, for user it is $-callback
-+ gpointer userdata; // userdata for callback
-+ GSList * guards; // $-guards
-+ gboolean in_use; // to mark root templates for drop
-+ gboolean changed; // $-changed flag
-+ gboolean chunk_changed; // %-changed flag
-+ gchar * expansion; // expansion of template
-+ GSList * templates; // root templates, that use this chunk template // roots? :D
-+ GSList * chunks; // chunk templates in use by this root template
-+} template_t;
-+
-+typedef struct {
-+ gchar * name;
-+ GSList * templates;
-+} tmpl_guard_t;
-+
-+typedef struct tmpl_link_struct tmpl_link_t;
-+struct tmpl_link_struct {
-+ tmpl_link_type_t type;
-+ guint color;
-+ gchar * string;
-+ tmpl_link_t * next;
-+};
-+
-+static GHashTable * tmpl_templates = NULL;
-+static GHashTable * tmpl_guards = NULL;
-+static guint tmpl_attached_id = 0;
-+
-+//
-+// predeclarations
-+//
-+
-+static gchar *tmpl_guard ( const gchar *key, const gchar *new_value );
-+
-+//
-+// code
++// Common
+//
+
-+// [cb] drops template from guard's 'templates' list
-+static void tmpl_unguard ( gpointer data, gpointer udata )
-+{
-+ tmpl_guard_t *guard = data;
-+ template_t *template = udata;
-+ guard -> templates = g_slist_remove ( guard -> templates, template );
-+}
-+
-+// [cb] drops template from chunk's 'templates' list
-+static void tmpl_unchunk ( gpointer data, gpointer udata )
-+{
-+ template_t * chunk = data;
-+ template_t * template = udata;
-+ chunk -> templates = g_slist_remove ( chunk -> templates, template );
-+}
-+
-+// [destructor cb] releases guard hash table entry
-+static void tmpl_free_guard ( gpointer data )
-+{
-+ tmpl_guard_t * guard = data;
-+ settings_del_guard ( guard -> name );
-+ g_slist_free ( guard -> templates );
-+ g_free ( guard -> name );
-+ g_slice_free ( tmpl_guard_t, guard );
-+}
-+
-+// [destructor cb] releases taken guards and frees command
-+static void tmpl_free_template ( gpointer data )
-+{
-+ template_t * template = data;
-+ // not running unchunk, as it was done earlier
-+ // or it is global destruction and chunk may no longer exist
-+ g_slist_free ( template -> chunks );
-+ g_slist_free ( template -> templates );
-+ g_slist_foreach ( template -> guards, tmpl_unguard, template );
-+ g_slist_free ( template -> guards );
-+ g_free ( template -> name );
-+ g_free ( template -> expansion );
-+ g_slice_free ( template_t, template );
-+}
-+
-+// [cb] sets changed flag on template
-+static void template_set_changed ( gpointer data, gpointer udata )
-+{
-+ template_t * template = data;
-+ template -> changed = TRUE;
-+}
-+
-+// [cb] sets chunk_changed flag on template
-+static void template_set_chunk_changed ( gpointer data, gpointer udata )
-+{
-+ template_t * template = data;
-+ template -> chunk_changed = TRUE;
-+}
-+
-+// install guard (name must be glib-allocated string)
-+static void tmpl_install_guard ( gchar *name, template_t *template, settings_guard_t callback )
-+{
-+ tmpl_guard_t *guard = g_hash_table_lookup ( tmpl_guards, name );
-+ if ( guard == NULL ) {
-+ if ( ! settings_set_guard ( name, callback ) ) {
-+ scr_log_print ( LPRINT_LOGNORM, "Error: Unable to install guard for variable '%s' for template '%s'.", name, template -> name );
-+ g_free ( name );
-+ } else {
-+ guard = g_slice_new ( tmpl_guard_t );
-+ guard -> name = name;
-+ guard -> templates = NULL;
-+ g_hash_table_replace ( tmpl_guards, guard -> name, guard ); // to be sure
-+ }
-+ } else
-+ g_free ( name );
-+ if ( ! g_slist_find ( template -> guards, guard ) ) {
-+ template -> guards = g_slist_prepend ( template -> guards, guard );
-+ guard -> templates = g_slist_prepend ( guard -> templates, template );
-+ }
-+}
-+
-+// [parser cb] provides mcabber option values & reinstalls guards
-+static const char *tmpl_get_var ( const gchar *name, gsize len, gpointer udata, gsize *ret_len )
-+{
-+ const char *result = NULL;
-+ if ( name != NULL && len > 0 ) {
-+ template_t * template = udata;
-+ gchar * var = g_strndup ( name, len );
-+ result = settings_opt_get ( var );
-+ // consumes var
-+ tmpl_install_guard ( var, template, tmpl_guard );
-+ }
-+ if ( ret_len != NULL ) {
-+ if ( result != NULL )
-+ *ret_len = strlen ( result );
-+ else
-+ *ret_len = 0;
-+ }
-+ return result;
-+}
-+
-+// [parser cb] FIXME
-+static tmpl_link_t *tmpl_get_chunk ( const gchar name, gpointer udata )
-+{
-+ gchar * optname = g_strdup_printf ( "tmpl_%c", name );
-+ template_t * root = udata;
-+ template_t * chunk = g_hash_table_lookup ( templates, optname );
-+ if ( chunk ) {
-+ // check type
-+ // link as dep
-+ } else {
-+ // create new
-+ // consumes optname
-+ //tmpl_install_guard ( optname, chunk, tmpl_guard );
-+ // var_evaluate
-+ }
-+ // return value
-+}
-+
-+// [cb]
-+// drop dependencies for unused root templates,
-+// update expansion of all $-changed templates,
-+// reinstall guards,
-+// mark dependencies as %-changed,
-+// call $-callbacks
-+static void evaluate_template1 ( gpointer key, gpointer value, gpointer udata )
-+{
-+ template_t * template = value;
-+
-+ if ( template -> changed && template -> in_use ) {
-+ const gchar * expression = settings_opt_get ( template -> name );
-+ gchar * expansion = NULL;
-+ // release guards (but do not free them)
-+ g_slist_foreach ( template -> guards, tmpl_unguard, template );
-+ g_slist_free ( template -> guards );
-+ template -> guards = NULL;
-+ // re-install guards & get updated expansion
-+ if ( expression != NULL ) {
-+ GError *error = NULL;
-+ expansion = tmpl_expand ( expression, strlen ( expression ), tmpl_get_var, template, NULL, &error );
-+ if ( error != NULL ) {
-+ scr_log_print ( LPRINT_LOGNORM, "Error: Expansion error on template '%s': %s.\nExpansion stopped at: '%s'", template -> name, error -> message, expansion );
-+ g_error_free ( error );
-+ g_free ( expansion );
-+ expansion = NULL;
-+ }
-+ }
-+ // re-install guard on template itself
-+ tmpl_install_guard ( g_strdup ( template -> name ), template, tmpl_guard );
-+ template -> changed = FALSE;
-+ // check, if expansion has changed
-+ if ( g_strcmp0 ( expansion, template -> expansion ) ) {
-+ g_free ( template -> expansion );
-+ template -> expansion = expansion;
-+ if ( template -> type == user_template )
-+ // pass result to callback
-+ template -> callback ( expansion, template -> userdata );
-+ else if ( template -> type == chunk_template )
-+ // mark %-dependencies as %-changed
-+ g_slist_foreach ( template -> templates, tmpl_set_chunk_changed, NULL );
-+ else if ( template -> type == root_template )
-+ // mark self as %-changed
-+ template -> chunk_changed = TRUE;
-+ else
-+ g_assert_not_reached ();
-+ } else
-+ g_free ( expansion );
-+ }
-+}
-+
-+// [cb]
-+// evaluate %-changed templates,
-+// rebuild dependencies, call %-callbacks
-+static void evaluate_template2 ( gpointer key, gpointer value, gpointer udata )
-+{
-+ template_t * template = value;
-+
-+ if ( template -> type == root_template ) {
-+ if ( ! template -> in_use ) {
-+ // release chunks
-+ g_slist_foreach ( template -> chunks, tmpl_unchunk, template );
-+ g_slist_free ( template -> chunks );
-+ template -> chunks = NULL;
-+ } else if ( template -> chunk_changed ) {
-+ // release chunks
-+ g_slist_foreach ( template -> chunks, tmpl_unchunk, template );
-+ g_slist_free ( template -> chunks );
-+ template -> chunks = NULL;
-+ // evaluate template
-+ GError * error = NULL;
-+ template_link_t * expansion = tmpl_chunk_expand ( template -> expansion, tmpl_get_chunk, template, NULL, &error );
-+ if ( error != NULL ) {
-+ scr_log_print ( LPRINT_LOGNORM, "Error: Markup error on template '%s': %s.", template -> name, error -> message );
-+ g_error_free ( error );
-+ // XXX free result?
-+ }
-+ template -> callback ( result, template -> userdata );
-+ }
-+ }
-+}
-+
-+// [cb] mark deleted templates for removal
-+static gboolean tmpl_drop_unused_templates ( gpointer key, gpointer value, gpointer udata )
-+{
-+ template_t * template = value;
-+ if ( template -> type == chunk_template )
-+ // drop chunk templates with no dependent root templates
-+ return template -> templates == NULL;
-+ else
-+ // drop user templates, marked as unused
-+ return ! template -> in_use;
-+}
-+
-+// [cb] mark unused guards for removal
-+static gboolean tmpl_drop_unused_guards ( gpointer key, gpointer value, gpointer udata )
-+{
-+ tmpl_guard_t * guard = value;
-+ if ( guard -> templates == NULL )
-+ return TRUE;
-+ return FALSE;
-+}
-+
-+// [idle cb] update commands/guards & call cbs when necessary
-+static gboolean reevaluate_templates ( gpointer data )
-+{
-+ // allow reschedule in a process of reevaluation
-+ tmpl_attached_id = 0;
-+ // release chunks of unused root templates, update expansion of $-changed templates, call $-callbacks
-+ g_hash_table_foreach ( tmpl_templates, evaluate_template1, NULL );
-+ // reevaluate %-changed templates, call %-callbacks
-+ g_hash_table_foreach ( tmpl_templates, evaluate_template2, NULL );
-+ // drop removed chunk-templates
-+ g_hash_table_foreach_remove ( tmpl_templates, tmpl_drop_unused_templates, NULL );
-+ // free unused guards TODO do only when needed
-+ g_hash_table_foreach_remove ( tmpl_guards, tmpl_drop_unused_guards, NULL );
-+ // always return false, this is oneshot idle call
-+ return FALSE;
-+}
-+
-+// schedule templates reevaluation
-+static void tmpl_schedule_rerun ( void )
-+{
-+ if ( tmpl_attached_id == 0 ) {
-+ GSource * source = g_idle_source_new ();
-+ g_source_set_callback ( source, reevaluate_templates, NULL, NULL );
-+ tmpl_attached_id = g_source_attach ( source, main_context );
-+ g_source_unref ( source );
-+ }
-+}
-+
-+// [guard] generic guard for variable
-+static gchar *tmpl_guard ( const gchar *key, const gchar *new_value )
-+{
-+ if ( g_strcmp0 ( new_value, settings_opt_get ( key ) ) ) {
-+ // mark dependent commands as modified
-+ tmpl_guard_t *guard = g_hash_table_lookup ( tmpl_guards, key );
-+ g_slist_foreach ( guard -> templates, template_set_changed, NULL );
-+ // schedule execution of modified commands
-+ tmpl_schedule_rerun ();
-+ }
-+ return g_strdup ( new_value );
-+}
-+
-+// public
-+gboolean template_add ( const gchar * name, tmpl_callback_t callback, gpointer udata )
-+{
-+ g_assert ( name != NULL && callback != NULL );
-+ // check for existing template
-+ template_t * template = g_hash_table_lookup ( tmpl_templates, name );
-+ if ( template != NULL )
-+ return FALSE;
-+ // create new
-+ template = g_slice_new ( template_t );
-+ template -> name = g_strdup ( name );
-+ template -> callback = callback;
-+ template -> userdata = udata;
-+ template -> in_use = TRUE;
-+ template -> guards = NULL;
-+ template -> changed = TRUE;
-+ template -> prev_expansion = NULL;
-+ // schedule reevaluation
-+ tmpl_schedule_rerun ();
-+ return TRUE;
-+}
-+
-+// public
-+void template_set_in_use ( const gchar * name, gboolean in_use )
-+{
-+ g_assert ( name != NULL );
-+ template_t * template = g_hash_table_lookup ( tmpl_templates, name );
-+ g_assert ( template != NULL );
-+ template -> in_use = in_use;
-+}
-+
-+// private
-+void templates_init ( void )
-+{
-+ // the key will be freed by destruction cb
-+ tmpl_guards = g_hash_table_new_full ( g_str_hash, g_str_equal, NULL, tmpl_free_guard );
-+ tmpl_templates = g_hash_table_new_full ( g_str_hash, g_str_equal, NULL, tmpl_free_template );
-+}
-+
-+// private
-+void templates_uninit ( void )
-+{
-+ if ( tmpl_attached_id != 0 )
-+ g_source_remove ( tmpl_attached_id );
-+ g_hash_table_destroy ( tmpl_templates );
-+ g_hash_table_destroy ( tmpl_guards );
-+}
-+
-+/* vim: se ts=4 sw=4: */
-diff -r 5ccd3db13ba7 mcabber/mcabber/templates.h
---- /dev/null Thu Jan 01 00:00:00 1970 +0000
-+++ b/mcabber/mcabber/templates.h Thu Aug 09 00:11:13 2012 +0300
-@@ -0,0 +1,44 @@
-+
-+/* Copyright 2012 Myhailo Danylenko
-+ *
-+ * This file is part of mcabber
-+ *
-+ * mcabber 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 2 of the License, or
-+ * (at your option) any later version.
-+ *
-+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */
-+
-+#include <glib.h>
-+
-+// Type for template callback
-+typedef void (*tmpl_callback_t) ( const gchar * expansion, gpointer udata );
-+
-+// success template_add ( option name, cb, cb udata )
-+// Adds given mcabber option to list of watched templates.
-+// If any option, used in that template (or template itself) will change,
-+// callback will be called with new expansion of template.
-+gboolean template_add ( const gchar * name, tmpl_callback_t callback, gpointer udata );
-+
-+// template_set_in_use ( option name, used flag )
-+// Marks template as (un)used.
-+// Note: Template will be actually removed only on next evaluation run,
-+// though call to this function schedules such run. This way, you can
-+// mark a bunch of templates as unused and then mark some of them as used.
-+void template_set_in_use ( const gchar * name, gboolean in_use );
-+
-+// XXX do we need this?
-+// void tmpl_schedule_rerun ( void );
-+
-+// private
-+void templates_init ( void );
-+void templates_uninit ( void );
-+
-+/* vim: se ts=4 sw=4: */
-diff -r 5ccd3db13ba7 mcabber/mcabber/var_parser.c
---- /dev/null Thu Jan 01 00:00:00 1970 +0000
-+++ b/mcabber/mcabber/var_parser.c Thu Aug 09 00:11:13 2012 +0300
-@@ -0,0 +1,853 @@
-+
-+/* Copyright 2012 Myhailo Danylenko
-+ *
-+ * This file is part of mcabber
-+ *
-+ * mcabber 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 2 of the License, or
-+ * (at your option) any later version.
-+ *
-+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */
-+
-+#include <glib.h>
-+
-+#include <ctype.h> // is*
-+#include <stdlib.h> // strtol
-+#include <string.h> // strchr
-+
-+#include "var_parser.h"
-+
-+#include "config.h"
-+
-+//
-+// Private types
-+//
-+
-+typedef enum {
-+ in_string,
-+ in_escape,
-+ in_var,
-+ in_plainvarstart,
-+ in_plainvar,
-+ in_prevarname,
-+ in_varnamestart,
-+ in_varname,
-+ in_varchop,
-+ in_varstrip,
-+ in_varstripchop,
-+ in_varslash,
-+ in_varmatch,
-+ in_varpresubst,
-+ in_varsubst,
-+ in_varcolon,
-+ in_varminus,
-+ in_varplus,
-+ in_varpos,
-+ in_postvarpos,
-+ in_varlen,
-+ in_varsubstring,
-+ in_varend,
-+ in_evalstart,
-+ in_mathexpand,
-+ in_dblparen_eol,
-+ nostate,
-+} tmpl_parser_state_t;
-+
-+const static char * const tmpl_statenames [ ] = {
-+ "in string",
-+ "in escape",
-+ "in var",
-+ "in plainvarstart",
-+ "in plainvar",
-+ "in prevarname",
-+ "in varnamestart",
-+ "in varname",
-+ "in varchop",
-+ "in varstrip",
-+ "in varstripchop",
-+ "in varslash",
-+ "in varmatch",
-+ "in varpresubst",
-+ "in varsubst",
-+ "in varcolon",
-+ "in varminus",
-+ "in varplus",
-+ "in varpos",
-+ "in postvarpos",
-+ "in varlen",
-+ "in varsubstring",
-+ "in varend",
-+ "in evalstart",
-+ "in mathexpand",
-+ "in dblparen eol",
-+ "wrong state",
-+};
-+
-+typedef enum {
-+ no_eol = 0x0,
-+ brace_eol = 0x1,
-+ slash_eol = 0x2,
-+ colon_eol = 0x4,
-+ paren_eol = 0x8,
-+ dblparen_eol = 0x10,
-+ balance_parens = 0x20,
-+} tmpl_parser_flags_t;
-+
-+typedef enum {
-+ no_operation,
-+ expand_var,
-+ value_length,
-+} tmpl_var_operation_t;
-+
-+typedef enum {
-+ no_op,
-+ noempty_op,
-+ paren_op,
-+ plus_op,
-+ minus_op,
-+ remainder_op,
-+ division_op,
-+ multiplication_op,
-+} tmpl_math_op_t;
-+
-+typedef enum {
-+ in_preargument,
-+ in_hexoctzero,
-+ in_number,
-+ in_variable,
-+ in_postparen,
-+ in_op,
-+} tmpl_math_state_t;
-+
-+//
-+// Code
-+//
-+
++// XXX one for all 'mcabber' quark? to utils?
+GQuark tmpl_gerror_quark ( void )
+{
+ return g_quark_from_static_string ( "tmpl_gerror_quark" );
+}
+
++//
++// Globs
++//
++
+// match tmpl_glob ( string, pattern, greedy, right-to-left )
+// Performs matching of given pattern against given string,
+// returns first substring in input, that matched. In pattern,
@@ -897,15 +183,39 @@
+ return tmpl_glob ( str, length, pat, patlen, rtl, ret_len );
+}
+
++//
++// Simple math expressions with variables
++//
++
++typedef enum {
++ no_op,
++ noempty_op,
++ paren_op,
++ plus_op,
++ minus_op,
++ remainder_op,
++ division_op,
++ multiplication_op,
++} math_operator_t;
++
++typedef enum {
++ in_preargument,
++ in_hexoctzero,
++ in_number,
++ in_variable,
++ in_postparen,
++ in_op,
++} math_parser_state_t;
++
+// - + * / % ( )
-+static gssize tmpl_math_expand_internal ( const char *str, gsize length, tmpl_variable_callback_t get_val, gpointer udata, GError **error, tmpl_math_op_t bound, gsize *proc_len )
++static gssize tmpl_math_expand_internal ( const char *str, gsize length, tmpl_variable_callback_t get_val, gpointer udata, GError **error, math_operator_t bound, gsize *proc_len )
+{
+ g_assert ( error != NULL && *error == NULL );
+
+ gssize result = 0;
+ const char * p = str;
+ const char * const e = str + length;
-+ tmpl_math_state_t state = in_preargument;
++ math_parser_state_t state = in_preargument;
+ const char * varname = NULL; // variable name start
+ gsize base = 10; // number base
+ gboolean invert = FALSE; // number change sign
@@ -1040,7 +350,7 @@
+
+ // process operator+second argument pairs
+ while ( p < e && *error == NULL ) {
-+ tmpl_math_op_t op = no_op;
++ math_operator_t op = no_op;
+ if ( isspace ( *p ) )
+ p ++;
+ else if ( *p == '+' ) // set op
@@ -1119,19 +429,110 @@
+ return result;
+}
+
++//
++// Shell-like variable substitution
++//
++
++typedef enum {
++ in_string,
++ in_escape,
++ in_var,
++ in_plainvarstart,
++ in_plainvar,
++ in_prevarname,
++ in_varnamestart,
++ in_varname,
++ in_varchop,
++ in_varstrip,
++ in_varstripchop,
++ in_varslash,
++ in_varmatch,
++ in_varpresubst,
++ in_varsubst,
++ in_varcolon,
++ in_varminus,
++ in_varplus,
++ in_varpos,
++ in_postvarpos,
++ in_varlen,
++ in_varsubstring,
++ in_varend,
++ in_evalstart,
++ in_mathexpand,
++ in_dblparen_eol,
++ in_varif,
++ in_postvarif,
++ in_varelse,
++ nostate,
++} var_parser_state_t;
++
++const static char * const var_parser_statenames [ ] = {
++ "in string",
++ "in escape",
++ "in var",
++ "in plainvarstart",
++ "in plainvar",
++ "in prevarname",
++ "in varnamestart",
++ "in varname",
++ "in varchop",
++ "in varstrip",
++ "in varstripchop",
++ "in varslash",
++ "in varmatch",
++ "in varpresubst",
++ "in varsubst",
++ "in varcolon",
++ "in varminus",
++ "in varplus",
++ "in varpos",
++ "in postvarpos",
++ "in varlen",
++ "in varsubstring",
++ "in varend",
++ "in evalstart",
++ "in mathexpand",
++ "in dblparen eol",
++ "in_varif",
++ "in_postvarif",
++ "in_varelse",
++ "wrong state",
++};
++
++typedef enum {
++ no_eol = 0x00,
++ brace_eol = 0x01,
++ slash_eol = 0x02,
++ colon_eol = 0x04,
++ paren_eol = 0x08,
++ dblparen_eol = 0x10,
++ balance_parens = 0x20,
++} var_parser_flags_t;
++
++typedef enum {
++ no_operation,
++ expand_var,
++ value_length,
++} var_operation_t;
++
+// expansion tmpl_expand ( template, callback )
+// Parse template, substitute shell-like expressions:
-+// * $var ${var}
-+// * ${var##pat} ${var#pat} ${var%%pat} ${var%pat}
-+// * ${var/pat} ${var/pat/repl} ${var//pat/repl} ${var/%pat/repl} ${var/#pat/repl}
-+// * ${var:+tmpl} ${var:-tmpl}
-+// * ${var:uint} ${var:uint:uint}
-+// * ${#var} ${!var} ${!var[operation]}
-+// * \n \t \e \$ \\ \X
++// * $var ${var}
++// * ${var##pat} ${var#pat} ${var%%pat} ${var%pat}
++// * ${var/pat} ${var/pat/repl} ${var//pat/repl} ${var/%pat/repl} ${var/#pat/repl}
++// * ${var:+tmpl} ${var:-tmpl} ${var?tmpl:tmpl}
++// * ${var:math} ${var:math:math}
++// * ${#var} ${!var} ${!var[operation]}
++// * $(( math ))
++// * \n \t \e \$ \\ \X
+// Callback will be called to obtain variable values.
+// Variable name should be: /^[[:alnum:]][[:alnum:]_-]*$/
+// For pattern rules see tmpl_glob ().
-+static gchar *tmpl_expand_internal ( const char *template, gsize len, tmpl_variable_callback_t get_val, gpointer udata, gsize *ret_len, GError **error, tmpl_parser_flags_t flags, gsize *proc_len )
++// For math rules see tmpl_math_expand ().
++// TODO:
++// * validate variable name in ${!var} expression
++// * de-hardcode variable name validation rules (or allow pure-numeric names)
++static gchar *tmpl_var_expand_internal ( const char *template, const char * const e, tmpl_variable_callback_t get_val, gpointer udata, gsize *ret_len, GError **error, var_parser_flags_t flags, gsize *proc_len )
+{
+ g_assert ( error != NULL && *error == NULL );
+
@@ -1140,12 +541,11 @@
+
+ GString *result = g_string_new ( NULL );
+ const char *p = template;
-+ const char * const e = template + len;
-+ tmpl_parser_state_t state = in_string;
++ var_parser_state_t state = in_string;
+ const char *a1s = p; // string, varname
+ const gchar *value = NULL; // variable value
+ gsize vlen = 0; // variable value length
-+ tmpl_var_operation_t operation = no_operation;
++ var_operation_t operation = no_operation;
+ gchar *pattern = NULL;
+ gsize plen = 0;
+ gboolean rtl = FALSE; // strip/chop
@@ -1259,6 +659,9 @@
+ } else if ( *p == ':' ) { // substring expression
+ state = in_varcolon;
+ p ++;
++ } else if ( *p == '?' ) { // conditional expression
++ state = in_varif;
++ p ++;
+ } else // wrong symbols
+ g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EWRONGVAREXP, "Wrong variable expansion expression" );
+ }
@@ -1281,7 +684,7 @@
+ } else if ( state == in_varstripchop ) { // pattern expressions
+ gsize elen = 0;
+ gsize processed = 0;
-+ gchar *expansion = tmpl_expand_internal ( p, e - p, get_val, udata, &elen, error, brace_eol, &processed );
++ gchar *expansion = tmpl_var_expand_internal ( p, e, get_val, udata, &elen, error, brace_eol, &processed );
+ if ( *error == NULL ) {
+ gsize mlen = 0;
+ const char *match = NULL;
@@ -1318,7 +721,7 @@
+ state = in_varmatch;
+ } else if ( state == in_varmatch ) { // match part of replace expression
+ gsize processed = 0;
-+ pattern = tmpl_expand_internal ( p, e - p, get_val, udata, &plen, error, brace_eol | slash_eol, &processed );
++ pattern = tmpl_var_expand_internal ( p, e, get_val, udata, &plen, error, brace_eol | slash_eol, &processed );
+ p += processed;
+ state = in_varpresubst;
+ } else if ( state == in_varpresubst ) { // skip slash
@@ -1328,7 +731,7 @@
+ } else if ( state == in_varsubst ) { // replace expression
+ gsize processed = 0;
+ gsize slen = 0;
-+ gchar *subst = tmpl_expand_internal ( p, e - p, get_val, udata, &slen, error, brace_eol, &processed );
++ gchar *subst = tmpl_var_expand_internal ( p, e, get_val, udata, &slen, error, brace_eol, &processed );
+ if ( *error == NULL ) {
+ const char * start = value;
+ const char * const ve = value + vlen;
@@ -1366,7 +769,7 @@
+ } else if ( state == in_varminus || state == in_varplus ) { // zero and non-zero substitution
+ gsize elen = 0;
+ gsize processed = 0;
-+ gchar *expansion = tmpl_expand_internal ( p, e - p, get_val, udata, &elen, error, brace_eol, &processed );
++ gchar *expansion = tmpl_var_expand_internal ( p, e, get_val, udata, &elen, error, brace_eol, &processed );
+ if ( *error == NULL ) {
+ if ( ( vlen == 0 && state == in_varminus ) || ( vlen > 0 && state == in_varplus ) )
+ g_string_append_len ( result, expansion, elen );
@@ -1379,7 +782,7 @@
+ } else if ( state == in_varpos ) { // substring expression
+ gsize elen = 0;
+ gsize processed = 0;
-+ gchar *expansion = tmpl_expand_internal ( p, e - p, get_val, udata, &elen, error, colon_eol | brace_eol | balance_parens, &processed );
++ gchar *expansion = tmpl_var_expand_internal ( p, e, get_val, udata, &elen, error, colon_eol | brace_eol | balance_parens, &processed );
+ if ( *error == NULL ) {
+ subsoffset = tmpl_math_expand_internal ( expansion, elen, get_val, udata, error, no_op, NULL );
+ if ( subsoffset < 0 )
@@ -1399,7 +802,7 @@
+ } else if ( state == in_varlen ) {
+ gsize elen = 0;
+ gsize processed = 0;
-+ gchar *expansion = tmpl_expand_internal ( p, e - p, get_val, udata, &elen, error, brace_eol | balance_parens, &processed );
++ gchar *expansion = tmpl_var_expand_internal ( p, e, get_val, udata, &elen, error, brace_eol | balance_parens, &processed );
+ if ( *error == NULL ) {
+ subslength = tmpl_math_expand_internal ( expansion, elen, get_val, udata, error, no_op, NULL );
+ if ( subslength < 0 )
@@ -1419,6 +822,32 @@
+ g_string_append_len ( result, value + subsoffset, subslength );
+ }
+ state = in_varend;
++ } else if ( state == in_varif ) {
++ gsize elen = 0;
++ gsize processed = 0;
++ gchar *expansion = tmpl_var_expand_internal ( p, e, get_val, udata, &elen, error, colon_eol | brace_eol, &processed );
++ if ( *error == NULL && vlen != 0 )
++ g_string_append_len ( result, expansion, elen );
++ g_free ( expansion );
++ p += processed;
++ state = in_postvarif;
++ } else if ( state == in_postvarif ) {
++ if ( *p == ':' ) {
++ state = in_varelse;
++ p ++;
++ } else if ( *p == '}' )
++ state = in_varend;
++ else
++ g_assert_not_reached ();
++ } else if ( state == in_varelse ) {
++ gsize elen = 0;
++ gsize processed = 0;
++ gchar *expansion = tmpl_var_expand_internal ( p, e, get_val, udata, &elen, error, brace_eol, &processed );
++ if ( *error == NULL && vlen == 0 )
++ g_string_append_len ( result, expansion, elen );
++ g_free ( expansion );
++ p += processed;
++ state = in_varend;
+ } else if ( state == in_varend ) { // end of enclosed variable
+ if ( *p == '}' ) {
+ p ++;
@@ -1435,7 +864,7 @@
+ } else if ( state == in_mathexpand ) {
+ gsize elen = 0;
+ gsize processed = 0;
-+ gchar *expansion = tmpl_expand_internal ( p, e - p, get_val, udata, &elen, error, dblparen_eol | balance_parens, &processed );
++ gchar *expansion = tmpl_var_expand_internal ( p, e, get_val, udata, &elen, error, dblparen_eol | balance_parens, &processed );
+ if ( *error == NULL ) {
+ gssize value = tmpl_math_expand_internal ( expansion, elen, get_val, udata, error, no_op, NULL );
+ if ( *error == NULL )
@@ -1463,7 +892,7 @@
+ } else if ( state == in_string ) // end of string
+ g_string_append_len ( result, a1s, p - a1s );
+ else if ( state != in_dblparen_eol )
-+ g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EWRONGENDSTATE, "Parser stopped in incorrect state '%s'", state < nostate ? tmpl_statenames [ state ] : "wrong state" );
++ g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EWRONGENDSTATE, "Parser stopped in incorrect state '%s'", var_parser_statenames [ state < nostate ? state : nostate ] );
+ }
+
+ if ( pattern != NULL )
@@ -1476,29 +905,244 @@
+ return g_string_free ( result, FALSE );
+}
+
-+//
-+// public wrappers
-+//
-+
-+gchar *tmpl_expand ( const char *template, gsize len, tmpl_variable_callback_t get_val, gpointer udata, gsize *ret_len, GError **ret_err )
++// public wrapper
++gchar *tmpl_var_expand ( const char *template, gsize len, tmpl_variable_callback_t get_val, gpointer udata, gsize *ret_len, GError **ret_err )
+{
+ g_return_val_if_fail ( ret_err == NULL || *ret_err == NULL, NULL );
+
+ GError * error = NULL;
-+ gchar * result = tmpl_expand_internal ( template, len, get_val, udata, ret_len, &error, no_eol, NULL );
-+ if ( error != NULL )
++
++ gchar * result = tmpl_var_expand_internal ( template, template + len, get_val, udata, ret_len, &error, no_eol, NULL );
++
++ if ( error != NULL ) {
+ g_propagate_error ( ret_err, error );
++ g_free ( result );
++ return NULL;
++ }
++
++ return result;
++}
++
++//
++// Simple markup language
++//
++
++/*
++typedef enum {
++ ml_color,
++ ml_fill_right,
++ ml_fill_left,
++ ml_fill_both,
++} markup_link_type_t;
++
++typedef guint markup_color_t;
++
++typedef struct {
++ markup_link_type_t type;
++ markup_color_t color;
++ const char * s;
++ gssize len;
++ gboolean permanent;
++ guint width;
++} markup_link_t;
++*/
++
++typedef enum {
++ mp_in_string,
++ mp_in_percent,
++ mp_in_color,
++ mp_nostate,
++} markup_parser_state_t;
++
++const static char * const markup_parser_statenames [ ] = {
++ "in string",
++ "in percent",
++ "in color",
++ "wrong state",
++};
++
++void tmpl_markup_free ( markup_link_t * link )
++{
++ markup_link_t * current = link;
++ while ( current != NULL ) {
++ markup_link_t * tmp = current -> next;
++ if ( link -> permanent )
++ g_free ( link -> s );
++ g_slice_free ( markup_link_t, current );
++ current = tmp;
++ }
++}
++
++markup_link_t * link tmpl_markup_permanentize ( markup_link_t * link )
++{
++ markup_link_t * current;
++ for ( current = link; current -> next != NULL; current = current -> next ) {
++ current -> s = g_strndup ( current -> s, current -> s - current -> e );
++ current -> permanent = TRUE;
++ }
++ return link;
++}
++
++static tmpl_markup_link_t * tmpl_append_markup_link ( markup_link_t *last, markup_link_type_t type, markup_color_t color, const gchar * s, const gchar * e )
++{
++ if ( type == ml_color ) { // it is not splitter
++ if ( last -> s == NULL || last -> len == 0 ) { // append to empty string -- overwrite color & string
++ last -> s = s;
++ last -> len = e - s;
++ last -> color = color;
++ return last;
++ } else if ( last -> color == color ) { // color unchanged
++ if ( s == e ) // appending empty string - do nothing
++ return last;
++ else if ( last -> s + last -> len == s ) { // appending continuation of current string
++ last -> len += e - s;
++ return last;
++ }
++ }
++ }
++
++ tmpl_markup_link_t * link = g_slice_new ( markup_link_t );
++
++ link -> type = type;
++ link -> color = color;
++ link -> s = s;
++ link -> len = e - s;
++ link -> permanent = FALSE;
++ link -> next = last -> next; // allow insertion
++
++ last -> next = link;
++
++ return link;
++}
++
++static markup_link_t * tmpl_markup_expand_internal ( markup_link_t * chain, const char * template, const char * const e, tmpl_shortcut_callback_t get_shortcut, gpointer udata, GError **error )
++{
++ tmpl_markup_link_t * last = chain;
++ markup_parser_state_t state = mp_in_string;
++ const char * p = template;
++ const char * s = template;
++ markup_color_t color = 0;
++
++ while ( p < e && *error == NULL ) {
++ if ( state == mp_in_string ) {
++ if ( *p == '%' ) {
++ last = tmpl_append_markup_link ( last, ml_color, last -> color, s, p - s );
++ state = mp_in_percent;
++ }
++ p ++;
++ } else if ( state == mp_in_percent ) {
++ if ( *p == '%' ) {
++ s = p;
++ state = mp_in_string
++ p ++;
++ } else if ( isalpha ( *p ) ) {
++ gsize slen = 0;
++ const char * shortcut = get_shortcut ( *p, &slen, udata );
++ last = tmpl_markup_expand_internal ( last, shortcut, shortcut + slen, get_shortcut, udata, error );
++ state = mp_in_string;
++ p ++;
++ } else if ( isdigit ( *p ) ) {
++ color = 0;
++ state = mp_in_color;
++ } else if ( *p == '<' ) {
++ p ++;
++ last = tmpl_append_markup_link ( last, ml_fill_left, last -> color, NULL, 0 );
++ s = p;
++ state = mp_in_string;
++ } else if ( *p == '|' ) {
++ p ++;
++ last = tmpl_append_markup_link ( last, ml_fill_both, last -> color, NULL, 0 );
++ s = p;
++ state = mp_in_string;
++ } else if ( *p == '>' ) {
++ p ++;
++ last = tmpl_append_markup_link ( last, ml_fill_right, last -> color, NULL, 0 );
++ s = p;
++ state = mp_in_string;
++ } else
++ g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EWRONGMARKUP, "Wrong symbol in markup expression: '%c'", *p );
++ } else if ( state == mp_in_color ) {
++ short d = -1;
++ if ( *p == '0' )
++ d = 0;
++ else if ( *p == '1' )
++ d = 1;
++ else if ( *p == '2' )
++ d = 2;
++ else if ( *p == '3' )
++ d = 3;
++ else if ( *p == '4' )
++ d = 4;
++ else if ( *p == '5' )
++ d = 5;
++ else if ( *p == '6' )
++ d = 6;
++ else if ( *p == '7' )
++ d = 7;
++ else if ( *p == '8' )
++ d = 8;
++ else if ( *p == '9' )
++ d = 9;
++ if ( d >= 0 ) {
++ color = color * 10 + d;
++ p ++;
++ } else {
++ last = tmpl_append_markup_link ( last, ml_color, color, NULL, 0 );
++ s = p;
++ state = mp_in_string;
++ }
++ } else
++ g_assert_not_reached ();
++ }
++
++ if ( error == NULL ) {
++ if ( state == mp_in_string )
++ last = tmpl_append_markup_chunk ( last, ml_color, last -> color, s, e );
++ else if ( state == mp_in_color )
++ last = tmpl_append_markup_link ( last, ml_color, color, NULL, 0 );
++ else
++ g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EWRONGMARKUP,
++ "Markup parser ended in a wrong state '%s'",
++ markup_parser_statenames [ state < mp_nostate ? state : mp_nostate ] );
++ }
++
++ return last;
++}
++
++// public wrapper
++markup_link_t * tmpl_markup_expand ( const char *template, gsize len, tmpl_chunk_callback_t get_chunk, gpointer udata, GError **ret_err )
++{
++ g_return_val_if_fail ( ret_err == NULL || *ret_err == NULL, NULL );
++
++ GError * error = NULL;
++ markup_link_t * result = g_slice_new ( tmpl_markup_link_t );
++
++ result -> type = ml_color;
++ result -> color = 0;
++ result -> s = NULL;
++ result -> len = 0;
++ result -> permanent = FALSE;
++ result -> next = NULL;
++
++ tmpl_markup_expand_internal ( result, template, template + len, get_chunk, udata, &error );
++
++ if ( error != NULL ) {
++ g_propagate_error ( ret_err, error );
++ tmpl_markup_free ( result );
++ return NULL;
++ }
++
+ return result;
+}
+
+/* vim: se ts=4 sw=4: */
-diff -r 5ccd3db13ba7 mcabber/mcabber/var_parser.h
+diff -r 00b93fdbb1de mcabber/mcabber/parser.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
-+++ b/mcabber/mcabber/var_parser.h Thu Aug 09 00:11:13 2012 +0300
-@@ -0,0 +1,77 @@
++++ b/mcabber/mcabber/parser.h Thu Aug 16 00:23:26 2012 +0300
+@@ -0,0 +1,154 @@
+
+#ifndef MCABBER_PARSER_H
-+#define MCABBER_PARSER_H
++#define MCABBER_PARSER_H 1
+
+/* Copyright 2012 Myhailo Danylenko
+ *
@@ -1519,6 +1163,17 @@
+
+#include <glib.h>
+
++//
++// Design notes:
++//
++// Please, keep this part of code free of direct mcabber internals references,
++// as a sort of in-tree library. This way it is much easier to test and debug.
++//
++
++//
++// Typedefs
++//
++
+#define TMPL_GERROR_QUARK tmpl_gerror_quark ()
+
+#define TMPL_EWRONGVAREXP ( 0x01 )
@@ -1532,44 +1187,687 @@
+#define TMPL_EMATHWRONGOP ( 0x08 )
+#define TMPL_EMATHWRONGSTATE ( 0x09 )
+
-+typedef const char *(*tmpl_variable_callback_t) ( const gchar *name, gsize len, gpointer udata, gsize *ret_len );
++#define TMPL_EWRONGMARKUP ( 0x0A )
++
++typedef const char *(*tmpl_variable_callback_t) ( const gchar *name, gsize len, gpointer udata, gsize *ret_len );
++typedef const char *(*tmpl_shortcut_callback_t) ( const gchar name, gpointer udata, gsize *ret_len );
++
++typedef enum {
++ ml_color,
++ ml_fill_right,
++ ml_fill_left,
++ ml_fill_both,
++} markup_link_type_t;
++
++typedef guint markup_color_t;
++
++typedef struct {
++ markup_link_type_t type;
++ markup_color_t color;
++ const char * s;
++ gsize len;
++ gboolean permanent;
++ gsize width;
++} markup_link_t;
++
++//
++// Common
++//
+
+GQuark tmpl_gerror_quark ( void );
+
++//
++// Simple glob matching
++//
++
+// match tmpl_glob ( string, pattern, right-to-left )
+// Performs matching of given pattern against given string,
+// returns substring, that matched. In pattern, there are currently
+// two tokens recognized: '*' and '?'. Two flags determine, if
+// '*' should be greedy and which end of string must match the
+// pattern (i.e. have anchor).
-+const char *tmpl_glob ( const char *str, gsize strlen, const char *pat, gsize patlen, gboolean rtl, gsize *ret_len );
++const char * tmpl_glob ( const char * str, gsize strlen, const char * pat, gsize patlen, gboolean rtl, gsize * ret_len );
+
+// match tmpl_greedy_glob ( string, pattern, rigt-to-left )
+// The same, as above, but greedy.
-+const char *tmpl_greedy_glob ( const char *str, gsize strlen, const char *pat, gsize patlen, gboolean rtl, gsize *ret_len );
++const char * tmpl_greedy_glob ( const char * str, gsize strlen, const char * pat, gsize patlen, gboolean rtl, gsize * ret_len );
++
++//
++// Math expansion
++//
+
+// result tmpl_math_expand ( string, callback )
+// Performs mathematical expansion of given string.
+// Note, that $var expressions are not recognized, you have to
+// supply already expanded string here.
+// Supported operators:
-+// ( ) + - * / %
-+gssize tmpl_math_expand ( const char *str, gsize len, tmpl_variable_callback_t get_val, gpointer udata, GError **error );
++// ( ) + - * / %
++gssize tmpl_math_expand ( const char * str, gsize len, tmpl_variable_callback_t get_val, gpointer udata, GError ** error );
++
++//
++// Variable expansion
++//
+
+// expansion tmpl_expand ( template, callback )
+// Parse template, substitute shell-like expressions:
-+// * $var ${var}
-+// * ${var##pat} ${var#pat} ${var%%pat} ${var%pat}
-+// * ${var/pat} ${var/pat/repl} ${var//pat/repl} ${var/%pat/repl} ${var/#pat/repl}
-+// * ${var:+tmpl} ${var:-tmpl}
-+// * ${var:mathexp} ${var:mathexp:mathexp}
-+// * ${#var} ${!var} ${!var[operation]}
-+// * $(( mathexp ))
-+// * \n \t \e \$ \\ \X
++// * $var ${var}
++// * ${var##pat} ${var#pat} ${var%%pat} ${var%pat}
++// * ${var/pat} ${var/pat/repl} ${var//pat/repl} ${var/%pat/repl} ${var/#pat/repl}
++// * ${var:+tmpl} ${var:-tmpl}
++// * ${var:mathexp} ${var:mathexp:mathexp}
++// * ${#var} ${!var} ${!var[operation]}
++// * $(( mathexp ))
++// * \n \t \e \$ \\ \X
+// Callback will be called to obtain variable values.
+// Variable name should be: /^[[:alnum:]][[:alnum:]_-]*$/
-+// For pattern rules see mms_glob ().
-+gchar *tmpl_expand ( const char *template, gsize len, tmpl_variable_callback_t get_val, gpointer udata, gsize *ret_len, GError **error );
++// For pattern rules see tmpl_glob ().
++// For math expansion rules see tmpl_math_expand ().
++// Returned value should be g_free'd.
++gchar *tmpl_var_expand ( const char * str, gsize len, tmpl_variable_callback_t get_val, gpointer udata, gsize *ret_len, GError ** error );
++
++//
++// Simple markup parsing
++//
++
++// tmpl_markup_link_free ( chain )
++// Frees markup links chain (and associated strings, if permanentized).
++void tmpl_markup_free ( markup_link_t * chain );
++
++// chain tmpl_markup_permanentize ( chain )
++// Strdups strings, used in markup to make them independent of chunk expansions used.
++markup_link_t * tmpl_markup_permanentize ( markup_link_t * chain );
++
++// chain tmpl_markup_expand ( template, callback )
++// Parses simple markup language:
++// * %% - '%' symbol
++// * %a - %Z - insert shortcut with this name (expand it as well)
++// * %123 - text after this will be marked with color 123
++// * %> %< %| - text padding/trimming marks
++// Returns a chain of markup_link_t structs.
++// String pointers in links can be NULL.
++// String pointers in links point to parts of template or returned by shortcut callback values,
++// so, take care not to modify/free any of them, while using chain. Alternatively, you can
++// tmpl_markup_permanentize () markup.
++// Width fields are reserved for user, they are not set.
++// User have to free returned markup chain with tmpl_markup_free ().
++markup_link_t * tmpl_markup_expand ( const char * str, gsize len, tmpl_shortcut_callback_t get_chunk, gpointer udata, GError ** error );
++
++#endif
++
+diff -r 00b93fdbb1de mcabber/mcabber/settings.c
+--- a/mcabber/mcabber/settings.c Thu Aug 09 00:54:37 2012 +0300
++++ b/mcabber/mcabber/settings.c Thu Aug 16 00:23:26 2012 +0300
+@@ -363,6 +363,32 @@
+ return 0;
+ }
+
++// settings_tmpl_set(option, value)
++// Sets option, escaping it for use in templates.
++// Could be more efficient with notifiers instead of guards.
++void settings_tmpl_set(const gchar *key, const gchar *value)
++{
++ if (value && *value) {
++ const gchar *p = strchr(value, '%');
++ if (p) {
++ GString *result = g_string_new(NULL);
++ const gchar *s = value;
++ while (p) {
++ g_string_append_len(result, s, p - s + 1);
++ g_string_append_c(result, '%');
++ s = ++ p;
++ p = strchr(s, '%');
++ }
++ if (*s)
++ g_string_append(result, s);
++ settings_set(SETTNIGS_TYPE_OPTION, key, result -> str);
++ g_string_free(result, TRUE);
++ return;
++ }
++ }
++ settings_set ( SETTNIGS_TYPE_OPTION, key, value );
++}
++
+ // settings_get_status_msg(status)
+ // Return a string with the current status message:
+ // - if there is a user-defined message ("message" option),
+diff -r 00b93fdbb1de mcabber/mcabber/settings.h
+--- a/mcabber/mcabber/settings.h Thu Aug 09 00:54:37 2012 +0300
++++ b/mcabber/mcabber/settings.h Thu Aug 16 00:23:26 2012 +0300
+@@ -37,6 +37,7 @@
+ void settings_opt_set_raw(const gchar *key, const gchar *value);
+ void settings_set(guint type, const gchar *key, const gchar *value);
+ void settings_del(guint type, const gchar *key);
++void settings_tmpl_set(const gchar *key, const gchar *value);
+ const gchar *settings_get(guint type, const gchar *key);
+ int settings_get_int(guint type, const gchar *key);
+ const gchar *settings_get_status_msg(enum imstatus status);
+diff -r 00b93fdbb1de mcabber/mcabber/templates.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/mcabber/mcabber/templates.c Thu Aug 16 00:23:26 2012 +0300
+@@ -0,0 +1,468 @@
++
++/* Copyright 2012 Myhailo Danylenko
++ *
++ * This file is part of mcabber
++ *
++ * mcabber 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 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */
++
++#include <glib.h>
++
++#include <string.h> // strlen
++
++#include "logprint.h"
++#include "settings.h"
++#include "main.h" // main_context
++#include "templates.h"
++#include "parser.h"
++
++//
++// globals
++//
++
++typedef enum {
++ markup_template, // fully processed $ and % templates
++ shortcut_template, // no-callback $-only but %-propagating templates
++ var_template, // $-only templates with callback
++} template_type_t;
++
++typedef union {
++ tmpl_var_callback_t var;
++ tmpl_markup_callback_t markup;
++} template_callback_t;
++
++typedef struct {
++ gchar * name; // variable name
++ template_type_t type; // can be only one (for simplicity)
++ template_callback_t callback; //
++ gpointer userdata; // userdata for callback
++ GSList * guards; // var-guards
++ gboolean in_use; // to mark markup & var templates for drop
++ gboolean var_changed; // var-reevaluate flag
++ gboolean markup_changed; // markup-reevaluate flag
++ gboolean run_callback; // rerun callback flag
++ gchar * var_expansion; // var-expansion of template
++ GSList * templates; // markup templates, that use this shortcut template
++ GSList * shortcuts; // shortcut templates in use by this markup template
++} template_t;
++
++typedef struct {
++ gchar * name;
++ GSList * templates;
++} tmpl_guard_t;
++
++static GHashTable * tmpl_templates = NULL;
++static GHashTable * tmpl_guards = NULL;
++static guint tmpl_attached_id = 0;
++
++//
++// predeclarations
++//
++
++static gchar *tmpl_guard ( const gchar *key, const gchar *new_value );
++
++//
++// code
++//
++
++// [cb] drops template from guard's 'templates' list
++static void tmpl_unguard ( gpointer data, gpointer udata )
++{
++ tmpl_guard_t *guard = data;
++ template_t *template = udata;
++ guard -> templates = g_slist_remove ( guard -> templates, template );
++}
++
++// [cb] drops template from shortcut's 'templates' list
++static void tmpl_unshortcut ( gpointer data, gpointer udata )
++{
++ template_t * shortcut = data;
++ template_t * template = udata;
++ shortcut -> templates = g_slist_remove ( shortcut -> templates, template );
++}
++
++// [destructor cb] releases guard hash table entry
++static void tmpl_free_guard ( gpointer data )
++{
++ tmpl_guard_t * guard = data;
++ settings_del_guard ( guard -> name );
++ g_slist_free ( guard -> templates );
++ g_free ( guard -> name );
++ g_slice_free ( tmpl_guard_t, guard );
++}
++
++// [destructor cb] releases taken guards and frees command
++static void tmpl_free_template ( gpointer data )
++{
++ template_t * template = data;
++ // not running unshortcut, as it was done earlier
++ // or it is global destruction and chunk may no longer exist
++ g_slist_free ( template -> shortcuts );
++ g_slist_free ( template -> templates );
++ g_slist_foreach ( template -> guards, tmpl_unguard, template );
++ g_slist_free ( template -> guards );
++ g_free ( template -> name );
++ g_free ( template -> var_expansion );
++ g_slice_free ( template_t, template );
++}
++
++// [cb] sets var-changed flag on template
++static void template_set_var_changed ( gpointer data, gpointer udata )
++{
++ template_t * template = data;
++ template -> var_changed = TRUE;
++}
++
++// [cb] sets markup-changed flag on template
++static void template_set_markup_changed ( gpointer data, gpointer udata )
++{
++ template_t * template = data;
++ template -> markup_changed = TRUE;
++}
++
++// install guard (name must be glib-allocated string)
++// XXX we are always using the same callback?
++static void tmpl_install_guard ( gchar *name, template_t *template, settings_guard_t callback )
++{
++ tmpl_guard_t *guard = g_hash_table_lookup ( tmpl_guards, name );
++ if ( guard == NULL ) {
++ if ( ! settings_set_guard ( name, callback ) ) {
++ scr_log_print ( LPRINT_LOGNORM, "Error: Unable to install guard for variable '%s' for template '%s'.", name, template -> name );
++ g_free ( name );
++ } else {
++ guard = g_slice_new ( tmpl_guard_t );
++ guard -> name = name;
++ guard -> templates = NULL;
++ g_hash_table_replace ( tmpl_guards, guard -> name, guard ); // to be sure
++ }
++ } else
++ g_free ( name );
++ if ( ! g_slist_find ( template -> guards, guard ) ) {
++ template -> guards = g_slist_prepend ( template -> guards, guard );
++ guard -> templates = g_slist_prepend ( guard -> templates, template );
++ }
++}
++
++// [parser cb] provides mcabber option values & reinstalls guards
++static const char *tmpl_get_var ( const gchar *name, gsize len, gpointer udata, gsize *ret_len )
++{
++ const char *result = NULL;
++ if ( name != NULL && len > 0 ) {
++ template_t * template = udata;
++ gchar * var = g_strndup ( name, len );
++ result = settings_opt_get ( var );
++ // consumes var
++ tmpl_install_guard ( var, template, tmpl_guard );
++ }
++ if ( ret_len != NULL ) {
++ if ( result != NULL )
++ *ret_len = strlen ( result );
++ else
++ *ret_len = 0;
++ }
++ return result;
++}
+
-+# endif
++// [parser cb] create & evaluate shortcut template, add to dependencies, return expansion
++static const char *tmpl_get_shortcut ( const gchar name, gpointer udata, gsize *ret_len )
++{
++ gchar optname [ 7 ] = "tmpl_X\0";
++ template_t * template = udata;
++ template_t * shortcut;
++ optname [ 5 ] = name;
++ shortcut = g_hash_table_lookup ( templates, optname );
++ if ( shortcut != NULL ) {
++ if ( shortcut -> type != shortcut_template ) {
++ scr_log_print ( LPRINT_LOGNORM, "Error: Conflicting type of template for shortcut '%c'.", name );
++ return NULL;
++ }
++ // link as dependency
++ if ( ! g_slist_find ( template -> shortcuts, shortcut ) ) {
++ shortcut -> templates = g_slist_prepend ( shortcut -> templates, template );
++ template -> shortcuts = g_slist_prepend ( template -> shortcuts, shortcut );
++ }
++ } else {
++ // create new
++ shortcut = template_new ( name );
++ if ( shortcut == NULL ) {
++ scr_log_print ( LPRINT_LOGNORM, "Error: Unable to create template for shortcut '%c'.", name );
++ return NULL;
++ }
++ shortcut -> type = shortcut_template;
++ shortcut -> markup_changed = FALSE;
++ // consumes optname
++ tmpl_install_guard ( g_strndup ( optname, 6 ), shortcut, tmpl_guard );
++ // link as dependency
++ shortcut -> templates = g_slist_prepend ( shortcut -> templates, template );
++ template -> shortcuts = g_slist_prepend ( template -> shortcuts, shortcut );
++ // update expansion
++ if ( shortcut -> var_changed ) {
++ GError *error = NULL;
++ shortcut -> var_changed = FALSE;
++ shortcut -> var_expansion = tmpl_var_expand ( expression, strlen ( expression ), tmpl_get_var, template, NULL, &error );
++ if ( error != NULL ) {
++ scr_log_print ( LPRINT_LOGNORM, "Error: Expansion error on shortcut '%c': %s", name, error -> message );
++ g_error_free ( error );
++ return NULL;
++ }
++ }
++ }
++ return shortcut -> var_expansion;
++}
++
++// [cb]
++// update expansion of all $-changed templates,
++// reinstall guards,
++// mark dependencies as %-changed,
++// call $-callbacks
++static void evaluate_template1 ( gpointer key, gpointer value, gpointer udata )
++{
++ template_t * template = value;
++
++ if ( template -> changed && template -> in_use ) {
++ const gchar * expression = settings_opt_get ( template -> name );
++ gchar * expansion = NULL;
++ // release guards (but do not free them)
++ g_slist_foreach ( template -> guards, tmpl_unguard, template );
++ g_slist_free ( template -> guards );
++ template -> guards = NULL;
++ // re-install guards & get updated expansion
++ if ( expression != NULL ) {
++ GError *error = NULL;
++ expansion = tmpl_var_expand ( expression, strlen ( expression ), tmpl_get_var, template, NULL, &error );
++ if ( error != NULL ) {
++ scr_log_print ( LPRINT_LOGNORM, "Error: Expansion error on template '%s': %s.\nExpansion stopped at: '%s'", template -> name, error -> message, expansion );
++ g_error_free ( error );
++ expansion = NULL;
++ }
++ }
++ // re-install guard on template itself
++ tmpl_install_guard ( g_strdup ( template -> name ), template, tmpl_guard );
++ template -> changed = FALSE;
++ // check, if expansion has changed
++ if ( g_strcmp0 ( expansion, template -> var_expansion ) ||
++ ( template -> type == var_template && template -> run_callback ) ) {
++ g_free ( template -> var_expansion );
++ template -> var_expansion = expansion;
++ if ( template -> type == var_template ) {
++ // pass result to callback
++ template -> run_callback = FALSE;
++ template -> callback.var ( expansion, template -> userdata );
++ } else if ( template -> type == shortcut_template )
++ // mark %-dependencies as %-changed
++ g_slist_foreach ( template -> templates, tmpl_set_markup_changed, NULL );
++ else if ( template -> type == markup_template )
++ // mark self as %-changed
++ template -> markup_changed = TRUE;
++ else
++ g_assert_not_reached ();
++ } else
++ g_free ( expansion );
++ }
++}
++
++// [cb]
++// drop dependencies for unused root templates,
++// evaluate %-changed templates,
++// rebuild dependencies, call %-callbacks
++static void evaluate_template2 ( gpointer key, gpointer value, gpointer udata )
++{
++ template_t * template = value;
++
++ if ( template -> type == markup_template ) {
++ if ( ! template -> in_use ) {
++ // release chunks
++ g_slist_foreach ( template -> shortcuts, tmpl_unshortcut, template );
++ g_slist_free ( template -> shortcuts );
++ template -> shortcuts = NULL;
++ } else if ( template -> markup_changed || template -> run_callback ) {
++ // release chunks
++ g_slist_foreach ( template -> shortcuts, tmpl_unshortcut, template );
++ g_slist_free ( template -> shortcuts );
++ template -> shortcuts = NULL;
++ // evaluate template
++ GError * error = NULL;
++ markup_link_t * expansion = tmpl_markup_expand ( template -> var_expansion, strlen ( template -> var_expansion ), tmpl_get_shortcut, template, NULL, &error );
++ if ( error != NULL ) {
++ scr_log_print ( LPRINT_LOGNORM, "Error: Markup error on template '%s': %s.", template -> name, error -> message );
++ g_error_free ( error );
++ }
++ template -> run_callback = FALSE;
++ template -> callback.markup ( expansion, template -> userdata );
++ if ( expansion != NULL )
++ tmpl_markup_free ( expansion ); // TODO cache / check / ...
++ }
++ }
++}
++
++// [cb] mark deleted templates for removal
++static gboolean tmpl_drop_unused_templates ( gpointer key, gpointer value, gpointer udata )
++{
++ template_t * template = value;
++ if ( template -> type == shortcut_template )
++ // drop chunk templates with no dependent root templates
++ return template -> templates == NULL;
++ else
++ // drop user templates, marked as unused
++ return ! template -> in_use;
++}
++
++// [cb] mark unused guards for removal
++static gboolean tmpl_drop_unused_guards ( gpointer key, gpointer value, gpointer udata )
++{
++ tmpl_guard_t * guard = value;
++ if ( guard -> templates == NULL )
++ return TRUE;
++ return FALSE;
++}
++
++// [idle cb] update commands/guards & call cbs when necessary
++static gboolean reevaluate_templates ( gpointer data )
++{
++ // allow reschedule in a process of reevaluation
++ tmpl_attached_id = 0;
++ // update expansion of var-changed templates, call var-callbacks
++ g_hash_table_foreach ( tmpl_templates, evaluate_template1, NULL );
++ // release shortcuts of unused markup templates, reevaluate markup-changed templates, call markup-callbacks
++ g_hash_table_foreach ( tmpl_templates, evaluate_template2, NULL );
++ // drop removed shortcut- and unused markup- and var- templates
++ g_hash_table_foreach_remove ( tmpl_templates, tmpl_drop_unused_templates, NULL );
++ // free unused guards TODO do only when needed
++ g_hash_table_foreach_remove ( tmpl_guards, tmpl_drop_unused_guards, NULL );
++ // always return false, this is oneshot idle call
++ return FALSE;
++}
+
++// schedule templates reevaluation
++static void tmpl_schedule_rerun ( void )
++{
++ if ( tmpl_attached_id == 0 ) {
++ GSource * source = g_idle_source_new ();
++ g_source_set_callback ( source, reevaluate_templates, NULL, NULL );
++ tmpl_attached_id = g_source_attach ( source, main_context );
++ g_source_unref ( source );
++ }
++}
++
++// [guard] generic guard for variable
++static gchar *tmpl_guard ( const gchar *key, const gchar *new_value )
++{
++ if ( g_strcmp0 ( new_value, settings_opt_get ( key ) ) ) {
++ // mark dependent commands as modified
++ tmpl_guard_t *guard = g_hash_table_lookup ( tmpl_guards, key );
++ g_slist_foreach ( guard -> templates, template_set_var_changed, NULL );
++ // schedule changed templates reevaluation
++ tmpl_schedule_rerun ();
++ }
++ return g_strdup ( new_value );
++}
++
++// allocates new template and sets generic values
++static template_t *template_new ( const gchar * name )
++{
++ g_assert ( name != NULL );
++ // check for existing template
++ template_t * template = g_hash_table_lookup ( tmpl_templates, name );
++ if ( template != NULL )
++ return NULL;
++ // create new
++ template = g_slice_new ( template_t );
++ template -> name = g_strdup ( name );
++ template -> userdata = udata;
++ template -> in_use = TRUE;
++ template -> guards = NULL;
++ template -> var_changed = TRUE;
++ template -> markup_changed = TRUE;
++ template -> run_callback = FALSE; // XXX
++ template -> var_expansion = NULL;
++ template -> templates = NULL;
++ template -> shortcuts = NULL;
++ return template;
++}
++
++// public
++gboolean markup_template_add ( const gchar * name, tmpl_markup_callback_t callback, gpointer udata )
++{
++ g_assert ( callback != NULL );
++
++ template_t * template = template_new ( name );
++ if ( template == NULL )
++ return FALSE;
++
++ // fill specific fields
++ template -> type = markup_template;
++ template -> callback.markup = callback;
++ template -> userdata = udata;
++ template -> markup_changed = TRUE;
++ // schedule new template evaluation
++ tmpl_schedule_rerun ();
++ return TRUE;
++}
++
++// public
++gboolean var_template_add ( const gchar * name, tmpl_var_callback_t callback, gpointer udata )
++{
++ g_assert ( callback != NULL );
++
++ template_t * template = template_new ( name );
++ if ( template == NULL )
++ return FALSE;
++
++ // fill specific fields
++ template -> type = var_template;
++ template -> callback.var = callback;
++ template -> userdata = udata;
++ template -> markup_changed = FALSE;
++ // schedule new template evaluation
++ tmpl_schedule_rerun ();
++ return TRUE;
++}
++
++// public
++void template_set_in_use ( const gchar * name, gboolean in_use )
++{
++ g_assert ( name != NULL );
++ template_t * template = g_hash_table_lookup ( tmpl_templates, name );
++ g_assert ( template != NULL ); // XXX
++ template -> in_use = in_use;
++ tmpl_schedule_rerun (); // XXX
++}
++
++// public
++void template_rerun_callback ( const gchar * name )
++{
++ g_assert ( name != NULL );
++ template_t * template = g_hash_table_lookup ( tmpl_templates, name );
++ g_assert ( template != NULL && template -> type != shortcut_template ); // XXX
++ template -> run_callback = TRUE;
++ tmpl_schedule_rerun ();
++}
++
++// private
++void templates_init ( void )
++{
++ // the key will be freed by destruction cb
++ tmpl_guards = g_hash_table_new_full ( g_str_hash, g_str_equal, NULL, tmpl_free_guard );
++ tmpl_templates = g_hash_table_new_full ( g_str_hash, g_str_equal, NULL, tmpl_free_template );
++}
++
++// private
++void templates_uninit ( void )
++{
++ if ( tmpl_attached_id != 0 )
++ g_source_remove ( tmpl_attached_id );
++ g_hash_table_destroy ( tmpl_templates );
++ g_hash_table_destroy ( tmpl_guards );
++}
++
++/* vim: se ts=4 sw=4: */
+diff -r 00b93fdbb1de mcabber/mcabber/templates.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/mcabber/mcabber/templates.h Thu Aug 16 00:23:26 2012 +0300
+@@ -0,0 +1,54 @@
++
++#ifndef MCABBER_TEMPLATES_H
++#define MCABBER_TEMPLATES_H 1
++
++/* Copyright 2012 Myhailo Danylenko
++ *
++ * This file is part of mcabber
++ *
++ * mcabber 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 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */
++
++#include <glib.h>
++
++#include <mcabber/parser.h>
++
++// Type for template callback
++typedef void (*tmpl_var_callback_t) ( const gchar * expansion, gpointer udata );
++typedef void (*tmpl_markup_callback_t) ( const markup_link_t * expansion, gpointer udata );
++
++// success template_add ( option name, cb, cb udata )
++// Adds given mcabber option to list of watched templates.
++// If any option, used in that template (or template itself) will change,
++// callback will be called with new expansion of template.
++gboolean var_template_add ( const gchar * name, tmpl_var_callback_t callback, gpointer udata );
++gboolean markup_template_add ( const gchar * name, tmpl_markup_callback_t callback, gpointer udata );
++
++// template_set_in_use ( option name, used flag )
++// Marks template as (un)used.
++// Note: Template will be actually removed only on next evaluation run,
++// though call to this function schedules such run. This way, you can
++// mark a bunch of templates as unused and then mark some of them as used.
++void template_set_in_use ( const gchar * name, gboolean in_use );
++void template_rerun_callback ( const gchar * name );
++
++// XXX do we need this?
++// void tmpl_schedule_rerun ( void );
++
++// private
++void templates_init ( void );
++void templates_uninit ( void );
++
++#endif
++
++/* vim: se ts=4 sw=4: */