Some updates to templates
authorMyhailo Danylenko <isbear@ukrpost.net>
Thu, 16 Aug 2012 00:25:08 +0300
changeset 47 e935062a8180
parent 46 13edc1a9f1e2
child 48 307314cdbb39
Some updates to templates
templates.diff
--- 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: */