Add use-gslice and templates
authorMyhailo Danylenko <isbear@ukrpost.net>
Fri, 20 Jul 2012 17:55:10 +0300
changeset 33 ce47dc7fc6c0
parent 32 b257e55b48fc
child 34 94b6e4aa74a3
Add use-gslice and templates
add-cmake
series
templates
use-gslice
--- a/add-cmake	Thu Jul 19 12:13:16 2012 +0300
+++ b/add-cmake	Fri Jul 20 17:55:10 2012 +0300
@@ -1,19 +1,19 @@
 # HG changeset patch
-# Parent e2ba0b74584c4965e2a42466b6c7da4d85e47548
-[in development] Add possibility to build with CMake
+# Parent 9dbc874e6cf72a133e8568ae28fd254c6028d60a
+[work-in-progress] Add possibility to build with CMake
 
-diff -r e2ba0b74584c .hgignore
---- a/.hgignore	Thu Jul 19 08:06:20 2012 +0300
-+++ b/.hgignore	Thu Jul 19 12:09:29 2012 +0300
+diff -r 9dbc874e6cf7 .hgignore
+--- a/.hgignore	Fri Jul 20 17:29:26 2012 +0300
++++ b/.hgignore	Fri Jul 20 17:29:52 2012 +0300
 @@ -34,3 +34,5 @@
  tags
  mcabber/ptodo
  mcabber/ppatches
 +
 +mcabber/build
-diff -r e2ba0b74584c mcabber/CMakeLists.txt
+diff -r 9dbc874e6cf7 mcabber/CMakeLists.txt
 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-+++ b/mcabber/CMakeLists.txt	Thu Jul 19 12:09:29 2012 +0300
++++ b/mcabber/CMakeLists.txt	Fri Jul 20 17:29:52 2012 +0300
 @@ -0,0 +1,296 @@
 +## Copyright 2010-2012 Myhailo Danylenko
 +# This file is part of mcabber.
@@ -311,9 +311,9 @@
 +endif ()
 +
 +## The End ## vim: se ts=4 sw=4: ##
-diff -r e2ba0b74584c mcabber/config.h.in
+diff -r 9dbc874e6cf7 mcabber/config.h.in
 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-+++ b/mcabber/config.h.in	Thu Jul 19 12:09:29 2012 +0300
++++ b/mcabber/config.h.in	Fri Jul 20 17:29:52 2012 +0300
 @@ -0,0 +1,49 @@
 +/* help @help.c */
 +#define DATA_DIR                      "@DATA_DIR@"
@@ -364,9 +364,9 @@
 +/* option, also XEP0085? */
 +#cmakedefine XEP0022                  @XEP0022@
 +/* end */
-diff -r e2ba0b74584c mcabber/configure.ac
---- a/mcabber/configure.ac	Thu Jul 19 08:06:20 2012 +0300
-+++ b/mcabber/configure.ac	Thu Jul 19 12:09:29 2012 +0300
+diff -r 9dbc874e6cf7 mcabber/configure.ac
+--- a/mcabber/configure.ac	Fri Jul 20 17:29:26 2012 +0300
++++ b/mcabber/configure.ac	Fri Jul 20 17:29:52 2012 +0300
 @@ -36,9 +36,8 @@
  
  # Checks for header files.
@@ -392,22 +392,22 @@
  
  
  AC_CHECK_DECLS([strptime],,,
-diff -r e2ba0b74584c mcabber/hgcset.h.in
+diff -r 9dbc874e6cf7 mcabber/hgcset.h.in
 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-+++ b/mcabber/hgcset.h.in	Thu Jul 19 12:09:29 2012 +0300
++++ b/mcabber/hgcset.h.in	Fri Jul 20 17:29:52 2012 +0300
 @@ -0,0 +1,3 @@
 +/* this can go to config.h */
 +#define HGCSET "@HGCSET@"
 +/* end */
-diff -r e2ba0b74584c mcabber/mcabber.menu.in
+diff -r 9dbc874e6cf7 mcabber/mcabber.menu.in
 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-+++ b/mcabber/mcabber.menu.in	Thu Jul 19 12:09:29 2012 +0300
++++ b/mcabber/mcabber.menu.in	Fri Jul 20 17:29:52 2012 +0300
 @@ -0,0 +1,2 @@
 +?package(mcabber):needs="text" section="Applications/Network/Communication"\
 +  title="mcabber" command="@CMAKE_INSTALL_PREFIX@/bin/mcabber"
-diff -r e2ba0b74584c mcabber/mcabber.pc.in.in
+diff -r 9dbc874e6cf7 mcabber/mcabber.pc.in.in
 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-+++ b/mcabber/mcabber.pc.in.in	Thu Jul 19 12:09:29 2012 +0300
++++ b/mcabber/mcabber.pc.in.in	Fri Jul 20 17:29:52 2012 +0300
 @@ -0,0 +1,14 @@
 +prefix=${CMAKE_INSTALL_PREFIX}
 +exec_prefix=$${EMPTY}{prefix}
--- a/series	Thu Jul 19 12:13:16 2012 +0300
+++ b/series	Fri Jul 20 17:55:10 2012 +0300
@@ -8,3 +8,5 @@
 guard-xmpp-password
 roster-state-colors
 add-cmake
+use-gslice
+templates
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/templates	Fri Jul 20 17:55:10 2012 +0300
@@ -0,0 +1,1346 @@
+# HG changeset patch
+# Parent 456d832740146c5c9a0428c9ca0a1a0c431481b0
+[work-in-progress] Use templates for statusbars
+
+diff -r 456d83274014 mcabber/CMakeLists.txt
+--- a/mcabber/CMakeLists.txt	Fri Jul 20 17:29:53 2012 +0300
++++ b/mcabber/CMakeLists.txt	Fri Jul 20 17:30:20 2012 +0300
+@@ -154,8 +154,8 @@
+ 
+ ## Define targets
+ set ( mcabber_SUBSYSTEMS
+-	caps commands compl events hbuf help histolog hooks
+-	modules nohtml otr pgp roster screen settings utf8 utils
++	caps commands compl events hbuf help histolog hooks modules
++	nohtml otr parser pgp roster screen settings templates utf8 utils
+ 	xmpp xmpp_helper xmpp_iq xmpp_iqrequest xmpp_muc xmpp_s10n )
+ if ( NOT MODULES_ENABLE )
+ 	list ( APPEND mcabber_SUBSYSTEMS extcmd fifo )
+diff -r 456d83274014 mcabber/mcabber/parser.c
+--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
++++ b/mcabber/mcabber/parser.c	Fri Jul 20 17:30:20 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 "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
++//
++
++GQuark tmpl_gerror_quark ( void )
++{
++	return g_quark_from_static_string ( "tmpl_gerror_quark" );
++}
++
++// 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,
++// there are currently two tokens recognized: '*' and '?'.
++const gchar *tmpl_glob ( const char *str, gsize length, const char *pat, gsize patlen, gboolean rtl, gsize *ret_len )
++{
++	*ret_len = 0;
++	if ( rtl ) { // rtl
++		const char *p           = pat + patlen - 1;
++		const char *s           = str + length - 1;
++		const char *matchend    = NULL;
++		const char *patrollback = p;
++		const char *strrollback = s;
++		while ( p >= pat && s >= str ) {
++			if ( *p == '*' ) {
++				if ( matchend == NULL && patrollback != NULL )
++					matchend = strrollback;
++				patrollback = NULL;
++				strrollback = NULL;
++				p --;
++			} else if ( *p == '?' || *p == *s ) {
++				if ( patrollback == NULL ) {
++					patrollback = p;
++					strrollback = s;
++				}
++				p --;
++				s --;
++			} else if ( patrollback != NULL ) {
++				p =    patrollback;
++				s = -- strrollback;
++			} else {
++				s --;
++			}
++		}
++		// now s, p or both have finished run
++		if ( s >= str && patrollback == NULL ) // s remains and final star
++			s = str - 1; // value at the end of loop
++		if ( p >= pat ) { // p remains
++			while ( *p == '*' && p >= pat ) // ignore empty stars
++				p --;
++			if ( p >= pat ) // symbols remain in pattern, no match
++				return NULL;
++		}
++		if ( matchend == NULL && patrollback != NULL ) // no stars
++			matchend = strrollback;
++		if ( matchend != NULL ) {
++			*ret_len = matchend - s;
++			return s + 1;
++		} else
++			return NULL;
++	} else { // ltr
++		const char * const  pe          = pat + patlen;
++		const char * const  se          = str + length;
++		const char         *p           = pat;
++		const char         *s           = str;
++		const char         *matchstart  = NULL;
++		const char         *patrollback = p;
++		const char         *strrollback = s;
++		while ( p < pe && s < se ) {
++			if ( *p == '*' ) {
++				if ( matchstart == NULL && patrollback != NULL )
++					matchstart = strrollback;
++				patrollback = NULL;
++				strrollback = NULL;
++				p ++;
++			} else if ( *p == '?' || *p == *s ) {
++				if ( patrollback == NULL ) {
++					patrollback = p;
++					strrollback = s;
++				}
++				p ++;
++				s ++;
++			} else if ( patrollback != NULL ) { // start/nomatch-recovery eat
++				p =    patrollback;
++				s = ++ strrollback;
++			} else { // star-eat
++				s ++;
++			}
++		}
++		// now s, p or both have finished run
++		if ( s < se && patrollback == NULL ) // s remains and final star
++				s = se;
++		if ( p < pe ) { // p remains
++			while ( *p == '*' && p < pe ) // ignore empty stars
++				p ++;
++			if ( p < pe ) // symbols remain in pattern, no match
++				return NULL;
++		}
++		if ( matchstart == NULL && patrollback != NULL ) // no stars
++			matchstart = strrollback;
++		if ( matchstart != NULL )
++			*ret_len = s - matchstart;
++		return matchstart;
++	}
++}
++
++// match tmpl_greedy_glob ( string, pattern, right-to-left )
++// The same as above, but greedy.
++const gchar *tmpl_greedy_glob ( const char *str, gsize length, const char *pat, gsize patlen, gboolean rtl, gsize *ret_len )
++{
++	const char * const  pe = pat + patlen;
++	const char         *p  = pat;
++	while ( p < pe ) {
++		if ( *p == '*' ) {
++			gsize       llen = 0;
++			const char *left = tmpl_glob ( str, length, pat, p - pat, FALSE, &llen );
++			if ( left != NULL ) {
++				gsize       rlen  = 0;
++				const char *right = tmpl_glob ( left + llen, str + length - ( left + llen ), p + 1, pe - p - 1, TRUE, &rlen );
++				if ( right != NULL ) {
++					*ret_len = right + rlen - left;
++					return left;
++				}
++			}
++			return NULL;
++		}
++		p ++;
++	}
++	return tmpl_glob ( str, length, pat, patlen, rtl, ret_len );
++}
++
++// - + * / % ( )
++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 )
++{
++	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;
++	const char *       varname  = NULL;  // variable name start
++	gsize              base     = 10;    // number base
++	gboolean           invert   = FALSE; // number change sign
++	gboolean           errstate = bound >= noempty_op; // indicates erroneous state
++
++	// process first argument
++	while ( p < e && *error == NULL ) {
++		if ( state == in_preargument ) { // initial space-skipping state
++			if ( isspace ( *p ) )
++				p ++;
++			else if ( *p == '-' ) {
++				invert = ! invert;
++				errstate = TRUE;
++				p ++;
++			} else if ( *p == '+' ) {
++				errstate = TRUE;
++				p ++;
++			} else if ( *p == '(' ) {
++				gsize processed = 0;
++				p ++;
++				result = tmpl_math_expand_internal ( p, e - p, get_val, udata, error, paren_op, &processed );
++				if ( *error == NULL )
++					state = in_postparen;
++				errstate  = TRUE;
++				p        += processed;
++			} else if ( *p == '0' ) {
++				result   = 0;
++				errstate = FALSE;
++				state    = in_hexoctzero;
++				p ++;
++			} else if ( isdigit ( *p ) ) {
++				result   = 0;
++				base     = 10;
++				state    = in_number;
++			} else if ( isalpha ( *p ) || *p == '_' ) {
++				state    = in_variable;
++				errstate = FALSE;
++				varname  = p;
++				p ++;
++			} else
++				g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EMATHWRONGARG, "Wrong symbol in math argument" );
++		} else if ( state == in_hexoctzero ) {
++			if ( *p == 'x' ) {
++				errstate = TRUE;
++				base     = 16;
++				p ++;
++			} else
++				base = 8;
++			state  = in_number;
++		} else if ( state == in_number ) {
++			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 ( base <= 8 )
++				;
++			else if ( *p == '8' )
++				d = 8;
++			else if ( *p == '9' )
++				d = 9;
++			else if ( base <= 10 )
++				;
++			else if ( *p == 'a' || *p == 'A' )
++				d = 10;
++			else if ( *p == 'b' || *p == 'B' )
++				d = 11;
++			else if ( *p == 'c' || *p == 'C' )
++				d = 12;
++			else if ( *p == 'd' || *p == 'D' )
++				d = 13;
++			else if ( *p == 'e' || *p == 'E' )
++				d = 14;
++			else if ( *p == 'f' || *p == 'F' )
++				d = 15;
++			if ( d >= 0 ) {
++				errstate = FALSE;
++				result   = result * base + d;
++				p ++;
++			} else if ( ! errstate )
++				state = in_op;
++			else
++				g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EMATHWRONGNUMBER, "Too short math number" );
++		} else if ( state == in_variable ) {
++			if ( ! ( isalnum ( *p ) || *p == '_' ) ) { // XXX vars with '-' have to be expanded earlier
++				gsize        vlen  = 0;
++				const char * value = get_val ( varname, p - varname, udata, &vlen );
++				// TODO control recursion level
++				result = tmpl_math_expand_internal ( value, vlen, get_val, udata, error, no_op, NULL );
++				if ( *error == NULL )
++					state = in_op;
++			} else
++				p ++;
++		} else if ( state == in_postparen ) {
++			if ( *p == ')' ) {
++				errstate = FALSE;
++				p ++;
++				state    = in_op;
++			} else
++				g_assert_not_reached ();
++		} else if ( state == in_op )
++			break;
++		else
++			g_assert_not_reached ();
++	}
++
++	if ( *error == NULL ) {
++		// in_preargument - empty argument, possibly +-+- - 0 / errstate
++		// in_hexoctzero  - 0                             - 0
++		// in_number      - 0, 0x, 01, 0x1, 1             - failure at 0x (errstate), invert
++		// in_variable    - varname                       - get var, invert
++		// in_postparen   - eof                           - failure (errstate)
++		// in_op          - ok                            - invert, continue parsing
++		if ( errstate )
++			g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EMATHWRONGSTATE, "Wrong state at the end of first math argument parsing" );
++		else {
++			if ( state == in_op ) {
++				if ( invert )
++					result = - result;
++			
++				// process operator+second argument pairs
++				while ( p < e && *error == NULL ) {
++					tmpl_math_op_t op = no_op;
++					if ( isspace ( *p ) )
++						p ++;
++					else if ( *p == '+' ) // set op
++						op = plus_op;
++					else if ( *p == '-' )
++						op = minus_op;
++					else if ( *p == '/' )
++						op = division_op;
++					else if ( *p == '*' )
++						op = multiplication_op;
++					else if ( *p == '%' )
++						op = remainder_op;
++					else if ( *p == ')' ) {
++						if ( bound < paren_op )
++							g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EMATHUNBALPAREN, "Unbalanced parens in math expression" );
++						else
++							break;
++					} else
++						g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EMATHWRONGOP, "Wrong math operator '%c'", *p );
++
++					if ( op != no_op ) {
++						// lower priority operator, returning result
++						if ( op < bound )
++							break;
++
++						p ++;
++						gsize  processed = 0;
++						gssize arg       = tmpl_math_expand_internal ( p, e - p, get_val, udata, error, op, &processed );
++						if ( *error == NULL ) {
++							if ( op == plus_op )
++								result += arg;
++							else if ( op == minus_op )
++								result -= arg;
++							else if ( op == division_op )
++								result /= arg;
++							else if ( op == multiplication_op )
++								result *= arg;
++							else if ( op == remainder_op )
++								result %= arg;
++							else
++								g_assert_not_reached ();
++						}
++						p += processed;
++					}
++				}
++			} else if ( state == in_variable ) {
++				gsize        vlen  = 0;
++				const char * value = get_val ( varname, p - varname, udata, &vlen );
++				result = tmpl_math_expand_internal ( value, vlen, get_val, udata, error, no_op, NULL );
++				if ( *error == NULL && invert )
++					result = - result;
++			} else if ( invert )
++				result = - result;
++			// XXX may add long conditional with g_assert here, but I'm lazy
++		}
++	}
++
++	// return result
++	if ( proc_len != NULL )
++		*proc_len = p - str;
++	return result;
++}
++
++// result tmpl_math_expand ( plainexpanded string, callback )
++// Performs mathematical expansion on given string.
++// Note, that it does not perform variable/quote/etc expansion,
++// if you want it, you should do that first.
++gssize tmpl_math_expand ( const char *str, gsize length, tmpl_variable_callback_t get_val, gpointer udata, GError **ret_err )
++{
++	g_return_val_if_fail ( ret_err == NULL || *ret_err == NULL, -1 ); // XXX
++
++	GError * error  = NULL;
++	gssize   result = tmpl_math_expand_internal ( str, length, get_val, udata, &error, no_op, NULL );
++	if ( error != NULL )
++		g_propagate_error ( ret_err, error );
++	return result;
++}
++
++// 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
++// 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 )
++{
++	g_assert ( error != NULL && *error == NULL );
++
++	if ( template == NULL || len == 0 )
++		return NULL;
++
++	GString            *result     = g_string_new ( NULL );
++	const char         *p          = template;
++	const char * const  e          = template + len;
++	tmpl_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;
++	gchar              *pattern    = NULL;
++	gsize               plen       = 0;
++	gboolean            rtl        = FALSE; // strip/chop
++	gboolean            greedy     = FALSE; // strip/chop
++	gboolean            multiglob  = FALSE; // replace
++	gsize               parencount = 0;
++	gssize              subsoffset = -1;
++	gssize              subslength = -1;
++
++	while ( p < e && *error == NULL ) {
++		if ( state == in_string ) { // nothing special
++			if ( *p == '\\' ) { // escape next char
++				g_string_append_len ( result, a1s, p - a1s );
++				state = in_escape;
++			} else if ( *p == '$' ) { // start variable
++				g_string_append_len ( result, a1s, p - a1s );
++				state = in_var;
++			} else if ( flags & balance_parens && *p == '(' ) {
++				parencount ++;
++			} else if ( *p == ')' ) {
++				if ( flags & balance_parens && parencount > 0 ) {
++					parencount --;
++				} else if ( flags & dblparen_eol ) {
++					g_string_append_len ( result, a1s, p - a1s );
++					state = in_dblparen_eol;
++				} else if ( flags & balance_parens )
++					g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EUNBALPAREN, "Unbalanced parentheses" );
++			} else if ( ( flags & brace_eol && *p == '}' ) ||
++			            ( flags & slash_eol && *p == '/' ) ||
++						( flags & colon_eol && parencount == 0 && *p == ':' ) )
++				break;
++			p ++;
++		} else if ( state == in_escape ) { // escape (on escaped char)
++			if ( *p == 'n' )
++				g_string_append_c ( result, '\n' );
++			else if ( *p == 't' )
++				g_string_append_c ( result, '\t' );
++			else if ( *p == 'e' ) // for some experimentation with colors...
++				g_string_append_c ( result, '\033' );
++			else
++				g_string_append_c ( result, *p );
++			p ++;
++			state = in_string;
++			a1s   = p;
++		} else if ( state == in_var ) { // some variable
++			if ( *p == '{' ) { // enclosed variable
++				state = in_prevarname;
++				p ++;
++			} else if ( *p == '(' ) {
++				state = in_evalstart;
++				p ++;
++			} else // unenclosed variable
++				state = in_plainvarstart;
++		} else if ( state == in_plainvarstart ) {
++			if ( isalpha ( *p ) || *p == '_' ) {
++				state = in_plainvar;
++				a1s   = p;
++				p ++;
++			} else
++				g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EWRONGVARNAME, "Wrong symbol at the start of variable name" );
++		} else if ( state == in_plainvar ) { // unenclosed variable
++			if ( ! ( isalnum ( *p ) || *p == '-' || *p == '_' ) ) {
++				gsize        vlen  = 0;
++				const gchar *value = get_val ( a1s, p - a1s, udata, &vlen );
++				g_string_append_len ( result, value, vlen );
++				state = in_string;
++				a1s   = p;
++			} else
++				p ++;
++		} else if ( state == in_prevarname ) { // allow ! and # at varname start
++			if ( *p == '!' ) {
++				operation = expand_var;
++				p++;
++			} else if ( *p == '#' ) {
++				operation = value_length;
++				p++;
++			} else
++				operation = no_operation;
++			state = in_varnamestart;
++		} else if ( state == in_varnamestart ) {
++			if ( isalpha ( *p ) || *p == '_' ) {
++				state = in_varname;
++				a1s   = p;
++				p ++;
++			} else
++				g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EWRONGVARNAME, "Wrong symbol at start of variable name" );
++		} else if ( state == in_varname ) { // enclosed variable name
++			if ( ! ( isalnum ( *p ) || *p == '-' || *p == '_' ) ) {
++				value = get_val ( a1s, p - a1s, udata, &vlen );
++				if ( operation == value_length ) {
++					if ( *p == '}' ) {
++						g_string_append_printf ( result, "%lu", vlen );
++						state = in_varend;
++					} else
++						g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EWRONGVAREXP, "Wrong variable expansion expression" );
++				} else {
++					if ( operation == expand_var && value != NULL && vlen > 0 )
++						value = get_val ( value, vlen, udata, &vlen );
++					if ( *p == '}' ) { // end of variable
++						g_string_append_len ( result, value, vlen );
++						state = in_varend;
++					} else if ( *p == '#' ) { // strip expression
++						state = in_varstrip;
++						p ++;
++					} else if ( *p == '%' ) { // chop expression
++						state = in_varchop;
++						p ++;
++					} else if ( *p == '/' ) { // replace expression
++						state = in_varslash;
++						p ++;
++					} else if ( *p == ':' ) { // substring expression
++						state = in_varcolon;
++						p ++;
++					} else // wrong symbols
++						g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EWRONGVAREXP, "Wrong variable expansion expression" );
++				}
++			} else
++				p ++;
++		} else if ( state == in_varstrip ) { // one of strip expressions
++			if ( *p == '#' ) {
++				greedy = TRUE;
++				p ++;
++			}
++			rtl   = FALSE;
++			state = in_varstripchop;
++		} else if ( state == in_varchop ) { // one of chop expressions
++			if ( *p == '%' ) {
++				greedy = TRUE;
++				p ++;
++			}
++			rtl   = TRUE;
++			state = in_varstripchop;
++		} 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 );
++			if ( *error == NULL ) {
++				gsize       mlen  = 0;
++				const char *match = NULL;
++				if ( greedy )
++					match = tmpl_greedy_glob ( value, vlen, expansion, elen, rtl, &mlen );
++				else
++					match = tmpl_glob ( value, vlen, expansion, elen, rtl, &mlen );
++				if ( mlen > 0 ) {
++					if ( rtl ) { // % and %%
++						if ( match + mlen == value + vlen )
++							vlen -= mlen;
++					} else if ( match == value ) { // # and ##
++						value  = match + mlen;
++						vlen  -= mlen;
++					}
++				}
++				g_string_append_len ( result, value, vlen );
++			}
++			g_free ( expansion );
++			p     += processed;
++			state  = in_varend;
++		} else if ( state == in_varslash ) { // replace expression
++			multiglob = FALSE;
++			rtl       = FALSE;
++			if ( *p == '#' )
++				p ++;
++			else if ( *p == '%' ) {
++				rtl = TRUE;
++				p ++;
++			} else if ( *p == '/' ) {
++				multiglob = TRUE;
++				p ++;
++			}
++			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 );
++			p               += processed;
++			state            = in_varpresubst;
++		} else if ( state == in_varpresubst ) { // skip slash
++			if ( *p == '/' )
++				p ++;
++			state = in_varsubst;
++		} 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 );
++			if ( *error == NULL ) {
++				const char       * start = value;
++				const char * const ve    = value + vlen;
++				while ( start < ve ) {
++					gsize       mlen  = 0;
++					const char *match = tmpl_glob ( start, ve - start, pattern, plen, rtl, &mlen );
++					if ( mlen > 0 ) {
++						g_string_append_len ( result, start, match - start );
++						g_string_append_len ( result, subst, slen );
++						start = match + mlen;
++					} else
++						break;
++					if ( ! multiglob )
++						break;
++				}
++				g_string_append_len ( result, start, ve - start );
++			}
++			g_free ( subst );
++			g_free ( pattern );
++			pattern  = NULL;
++			p       += processed;
++			state    = in_varend;
++		} else if ( state == in_varcolon ) { // substring or substitution expression
++			if ( *p == '-' ) {
++				state = in_varminus;
++				p ++;
++			} else if ( *p == '+' ) {
++				state = in_varplus;
++				p ++;
++			} else {
++				subsoffset = -1;
++				subslength = -1;
++				state      = in_varpos;
++			}
++		} 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 );
++			if ( *error == NULL ) {
++				if ( ( vlen == 0 && state == in_varminus ) || ( vlen > 0 && state == in_varplus ) )
++					g_string_append_len ( result, expansion, elen );
++				else if ( vlen > 0 && state == in_varminus )
++					g_string_append_len ( result, value, vlen );
++			}
++			g_free ( expansion );
++			p     += processed;
++			state  = in_varend;
++		} 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 );
++			if ( *error == NULL ) {
++				subsoffset = tmpl_math_expand_internal ( expansion, elen, get_val, udata, error, no_op, NULL );
++				if ( subsoffset < 0 )
++					subsoffset = 0;
++			}
++			g_free ( expansion );
++			p     += processed;
++			state  = in_postvarpos;
++		} else if ( state == in_postvarpos ) {
++			if ( *p == ':' ) {
++				state = in_varlen;
++				p ++;
++			} else if ( *p == '}' )
++				state = in_varsubstring;
++			else
++				g_assert_not_reached ();
++		} 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 );
++			if ( *error == NULL ) {
++				subslength = tmpl_math_expand_internal ( expansion, elen, get_val, udata, error, no_op, NULL );
++				if ( subslength < 0 )
++					subslength = 0;
++			}
++			g_free ( expansion );
++			p     += processed;
++			state  = in_varsubstring;
++		} else if ( state == in_varsubstring ) {
++			g_assert ( subsoffset >= 0 );
++			if ( subslength == -1 )
++				subslength = vlen;
++			if ( subsoffset < vlen ) {
++				if ( subsoffset + subslength > vlen )
++					g_string_append_len ( result, value + subsoffset, vlen - subsoffset );
++				else
++					g_string_append_len ( result, value + subsoffset, subslength );
++			}
++			state = in_varend;
++		} else if ( state == in_varend ) { // end of enclosed variable
++			if ( *p == '}' ) {
++				p ++;
++				state = in_string;
++				a1s   = p;
++			} else
++				g_assert_not_reached (); // this state must be used only on '}' or EOL
++		} else if ( state == in_evalstart ) {
++			if ( *p == '(' ) {
++				p ++;
++				state = in_mathexpand;
++			} else
++				g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EUNBALPAREN, "Command evaluation is not supported" );
++		} 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 );
++			if ( *error == NULL ) {
++				gssize value = tmpl_math_expand_internal ( expansion, elen, get_val, udata, error, no_op, NULL );
++				if ( *error == NULL )
++					g_string_append_printf ( result, "%li", value );
++			}
++			g_free ( expansion );
++			p     += processed;
++			state  = in_string;
++			a1s    = p;
++		} else if ( state == in_dblparen_eol ) {
++			if ( *p == ')' ) {
++				p ++;
++				break;
++			} else
++				g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EUNBALPAREN, "Unbalanced parentheses" );
++		} else
++			g_assert_not_reached ();
++	}
++
++	if ( *error == NULL ) {
++		if ( state == in_plainvar ) { // plain variable at the end of template
++			gsize        vlen  = 0;
++			const gchar *value = get_val ( a1s, p - a1s, udata, &vlen );
++			g_string_append_len ( result, value, vlen );
++		} 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" );
++	}
++
++	if ( pattern != NULL )
++		g_free ( pattern );
++
++	if ( proc_len != NULL )
++		*proc_len = p - template;
++	if ( ret_len != NULL )
++		*ret_len = result -> len;
++	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 )
++{
++	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 )
++		g_propagate_error ( ret_err, error );
++	return result;
++}
++
++/* vim: se ts=4 sw=4: */
+diff -r 456d83274014 mcabber/mcabber/parser.h
+--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
++++ b/mcabber/mcabber/parser.h	Fri Jul 20 17:30:20 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 456d83274014 mcabber/mcabber/templates.c
+--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
++++ b/mcabber/mcabber/templates.c	Fri Jul 20 17:30:20 2012 +0300
+@@ -0,0 +1,277 @@
++
++/* 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 struct {
++	gchar           * name;
++	tmpl_callback_t   callback;
++	gpointer          userdata;
++	gboolean          in_use;
++	GSList          * guards;
++	gboolean          changed;
++	gchar           * prev_expansion;
++} 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 );
++}
++
++// [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;
++	g_slist_foreach ( template -> guards, tmpl_unguard, template );
++	g_slist_free ( template -> guards );
++	g_free ( template -> name );
++	g_free ( template -> prev_expansion );
++	g_slice_free ( template_t, template );
++}
++
++// 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;
++}
++
++// [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;
++}
++
++// [cb] mark deleted templates for removal, reevaluate changed
++static gboolean reevaluate_template ( gpointer key, gpointer value, gpointer udata )
++{
++	template_t * template = value;
++
++	if ( ! template -> in_use )
++		return TRUE;
++
++	if ( template -> changed ) {
++		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;
++		// pass result to callback
++		if ( g_strcmp0 ( expansion, template -> prev_expansion ) ) {
++			g_free ( template -> prev_expansion );
++			template -> prev_expansion = expansion;
++			template -> callback ( expansion, template -> userdata );
++		} else
++			g_free ( expansion );
++	}
++
++	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;
++	// drop removed & reevaluate changed templates
++	g_hash_table_foreach_remove ( tmpl_templates, reevaluate_template, 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 );
++	}
++}
++
++// [cb] sets changed flag on template
++static void template_set_changed ( gpointer data, gpointer udata )
++{
++	template_t * template = data;
++	template -> changed   = TRUE;
++}
++
++// [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 456d83274014 mcabber/mcabber/templates.h
+--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
++++ b/mcabber/mcabber/templates.h	Fri Jul 20 17:30:20 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 456d83274014 mcabber/mcabber/utils.c
+--- a/mcabber/mcabber/utils.c	Fri Jul 20 17:29:53 2012 +0300
++++ b/mcabber/mcabber/utils.c	Fri Jul 20 17:30:20 2012 +0300
+@@ -650,6 +650,42 @@
+   g_free(arglst);
+ }
+ 
++//  parse_list(arg, cb, udata)
++// Calls cb for every element in space/semicolon/comma-separated list.
++// Designed to work in-place, so, no escapes, quoting etc.
++// Terminates parsing if callback returns false.
++void parse_list(const char *arg, parse_list_cb_t cb, void *udata)
++{
++  const char *p, *start;
++  enum {
++    in_space,
++    in_string,
++  } state;
++
++  if (!arg) 
++    return;
++
++  state = in_space;
++  while ( *p ) {
++    if ( *p == ' ' || *p == ';' || *p == ',' ) {
++      if ( state == in_string ) {
++        if ( ! cb ( start, p, udata ) )
++          return;
++        state = in_space;
++      }
++    } else {
++      if ( state == in_space ) {
++        start = p;
++        state = in_string;
++      }
++    }
++    p ++;
++  }
++
++  if ( state == in_string )
++    cb ( start, p, udata );
++}
++
+ //  replace_nl_with_dots(bufstr)
+ // Replace '\n' with "(...)" (or with a NUL if the string is too short)
+ void replace_nl_with_dots(char *bufstr)
+diff -r 456d83274014 mcabber/mcabber/utils.h
+--- a/mcabber/mcabber/utils.h	Fri Jul 20 17:29:53 2012 +0300
++++ b/mcabber/mcabber/utils.h	Fri Jul 20 17:30:20 2012 +0300
+@@ -43,6 +43,11 @@
+ char **split_arg(const char *arg, unsigned int n, int dontstriplast);
+ void free_arg_lst(char **arglst);
+ 
++/* fast in-place string split on space/semicolon/comma
++ * stops processing if callback returns false value */
++typedef int (*parse_list_cb_t)(const char *start, const char *end, void *udata);
++void parse_list(const char *arg, parse_list_cb_t cb, void *udata);
++
+ void replace_nl_with_dots(char *bufstr);
+ char *ut_expand_tabs(const char *text);
+ 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/use-gslice	Fri Jul 20 17:55:10 2012 +0300
@@ -0,0 +1,434 @@
+# HG changeset patch
+# Parent 70b1f1918050fa1126a21f4772d5d644799af32b
+[work-in-progress] Use glib slices to allocate constant-size blocks
+
+diff -r 70b1f1918050 mcabber/mcabber/caps.c
+--- a/mcabber/mcabber/caps.c	Fri Jul 20 17:41:15 2012 +0300
++++ b/mcabber/mcabber/caps.c	Fri Jul 20 17:51:21 2012 +0300
+@@ -52,7 +52,7 @@
+   g_hash_table_destroy(c->identities);
+   g_hash_table_destroy(c->features);
+   g_hash_table_destroy(c->forms);
+-  g_free(c);
++  g_slice_free(caps, c);
+ }
+ 
+ void identity_destroy(gpointer data)
+@@ -61,14 +61,14 @@
+   g_free(i->category);
+   g_free(i->type);
+   g_free(i->name);
+-  g_free(i);
++  g_slice_free(identity, i);
+ }
+ 
+ void form_destroy(gpointer data)
+ {
+   dataform *f = data;
+   g_hash_table_destroy(f->fields);
+-  g_free(f);
++  g_slice_free(dataform, f);
+ }
+ 
+ void field_destroy(gpointer data)
+@@ -97,7 +97,7 @@
+ {
+   if (!hash)
+     return;
+-  caps *c = g_new0(caps, 1);
++  caps *c = g_slice_new(caps);
+   c->features = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+   c->identities = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, identity_destroy);
+   c->forms = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, form_destroy);
+@@ -158,7 +158,7 @@
+ 
+   c = g_hash_table_lookup(caps_cache, hash);
+   if (c) {
+-    identity *i = g_new0(identity, 1);
++    identity *i = g_slice_new(identity);
+ 
+     i->category = g_strdup(category);
+     i->name = g_strdup(name);
+@@ -182,7 +182,7 @@
+     return;
+   c = g_hash_table_lookup(caps_cache, hash);
+   if (c) {
+-    dataform *d = g_new0(dataform, 1);
++    dataform *d = g_slice_new(dataform);
+     char *f = g_strdup(formtype);
+ 
+     d->fields = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, field_destroy);
+diff -r 70b1f1918050 mcabber/mcabber/commands.c
+--- a/mcabber/mcabber/commands.c	Fri Jul 20 17:41:15 2012 +0300
++++ b/mcabber/mcabber/commands.c	Fri Jul 20 17:51:21 2012 +0300
+@@ -114,7 +114,7 @@
+       gpointer userdata = command->userdata;
+       Commands = g_slist_delete_link(Commands, sl_cmd);
+       compl_del_category_word(COMPL_CMD, command->name);
+-      g_free(command);
++      g_slice_free(cmd, command);
+       return userdata;
+     }
+   return NULL;
+@@ -126,7 +126,7 @@
+ gpointer cmd_add(const char *name, const char *help, guint flags_row1,
+                  guint flags_row2, void (*f)(char*), gpointer userdata)
+ {
+-  cmd *n_cmd = g_new0(cmd, 1);
++  cmd *n_cmd = g_slice_new0(cmd);
+   strncpy(n_cmd->name, name, 32-1);
+   n_cmd->help = help;
+   n_cmd->completion_flags[0] = flags_row1;
+@@ -1579,7 +1579,7 @@
+     return NULL;
+   }
+ 
+-  msgbuf = g_new0(char, HBB_BLOCKSIZE);
++  msgbuf = g_slice_alloc0(HBB_BLOCKSIZE);
+   len = fread(msgbuf, 1, HBB_BLOCKSIZE-1, fd);
+   fclose(fd);
+ 
+@@ -1603,7 +1603,7 @@
+   if (*p || (size_t)(p-msgbuf) != len) { // We're not at the End Of Line...
+     scr_LogPrint(LPRINT_LOGNORM, "Message file contains "
+                  "invalid characters (%s)", filename);
+-    g_free(msgbuf);
++    g_slice_free1(HBB_BLOCKSIZE, msgbuf);
+     return NULL;
+   }
+ 
+@@ -1617,7 +1617,7 @@
+   // It could be empty, once the trailing newlines are gone
+   if (p == msgbuf && *p == '\n') {
+     scr_LogPrint(LPRINT_LOGNORM, "Message file is empty (%s)", filename);
+-    g_free(msgbuf);
++    g_slice_free1(HBB_BLOCKSIZE, msgbuf);
+     return NULL;
+   }
+ 
+@@ -1626,7 +1626,7 @@
+   if (!msgbuf_utf8 && msgbuf)
+     scr_LogPrint(LPRINT_LOGNORM, "Message file charset conversion error (%s)",
+                  filename);
+-  g_free(msgbuf);
++  g_slice_free1(HBB_BLOCKSIZE, msgbuf);
+   return msgbuf_utf8;
+ }
+ 
+@@ -1898,7 +1898,7 @@
+   esub   = buddy_getsubscription(bud);
+   on_srv = buddy_getonserverflag(bud);
+ 
+-  buffer = g_new(char, 4096);
++  buffer = g_slice_alloc(4096);
+ 
+   if (bjid) {
+     GSList *resources, *p_res;
+@@ -1997,7 +1997,7 @@
+                  type == ROSTER_TYPE_GROUP ? "group" :
+                  (type == ROSTER_TYPE_SPECIAL ? "special" : "unknown"));
+   }
+-  g_free(buffer);
++  g_slice_free1(4096, buffer);
+ 
+   // Tell the user if this item has an annotation.
+   if (type == ROSTER_TYPE_USER ||
+@@ -2045,7 +2045,7 @@
+ 
+   bjid = buddy_getjid(bud);
+ 
+-  buffer = g_new(char, 4096);
++  buffer = g_slice_alloc(4096);
+   strncpy(buffer, "Room members:", 127);
+   scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0);
+ 
+@@ -2100,7 +2100,7 @@
+     g_free(p_res->data);
+   }
+   g_slist_free(resources);
+-  g_free(buffer);
++  g_slice_free1(4096, buffer);
+ }
+ 
+ static void move_group_member(gpointer bud, void *groupnamedata)
+@@ -3091,7 +3091,7 @@
+   affil = buddy_getaffil(bud, nick);
+   realjid = buddy_getrjid(bud, nick);
+ 
+-  buffer = g_new(char, 4096);
++  buffer = g_slice_alloc(4096);
+ 
+   snprintf(buffer, 4095, "Whois [%s]", nick);
+   scr_WriteIncomingMessage(bjid, buffer, 0, msg_flag, 0);
+@@ -3121,7 +3121,7 @@
+ 
+   scr_WriteIncomingMessage(bjid, "End of WHOIS", 0, msg_flag, 0);
+ 
+-  g_free(buffer);
++  g_slice_free1(4096, buffer);
+   g_free(nick);
+   if (paramlst)
+     free_arg_lst(paramlst);
+diff -r 70b1f1918050 mcabber/mcabber/events.c
+--- a/mcabber/mcabber/events.c	Fri Jul 20 17:41:15 2012 +0300
++++ b/mcabber/mcabber/events.c	Fri Jul 20 17:51:21 2012 +0300
+@@ -83,7 +83,7 @@
+     return NULL;
+   }
+ 
+-  event = g_new(evs_t, 1);
++  event = g_slice_new(evs_t);
+ 
+   event->id          = stridn;
+   event->description = g_strdup(desc);
+@@ -134,7 +134,7 @@
+   evs_list = g_slist_remove(evs_list, event);
+   g_free(event->id);
+   g_free(event->description);
+-  g_free(event);
++  g_slice_free(evs_t, event);
+ 
+   return 0; // Ok, deleted
+ }
+@@ -213,7 +213,7 @@
+     evs_list = g_slist_remove(evs_list, event);
+     g_free(event->id);
+     g_free(event->description);
+-    g_free(event);
++    g_slice_free(evs_t, event);
+   }
+   g_slist_free(evs_list);
+   evs_list = NULL;
+diff -r 70b1f1918050 mcabber/mcabber/hbuf.c
+--- a/mcabber/mcabber/hbuf.c	Fri Jul 20 17:41:15 2012 +0300
++++ b/mcabber/mcabber/hbuf.c	Fri Jul 20 17:51:21 2012 +0300
+@@ -29,7 +29,6 @@
+ #include "utf8.h"
+ #include "screen.h"
+ 
+-
+ /* This is a private structure type */
+ 
+ typedef struct {
+@@ -59,7 +58,7 @@
+ {
+   GList *curr_elt = first_hbuf_elt;
+ 
+-  // Let's add non-persistent blocs if necessary
++  // Let's add non-persistent blocks if necessary
+   // - If there are '\n' in the string
+   // - If length > width (and width != 0)
+   while (curr_elt) {
+@@ -95,17 +94,21 @@
+       end = hbuf_b_curr->ptr_end;
+       hbuf_b_curr->ptr_end = br;
+       // Create another block
+-      hbuf_b_curr = g_new0(hbuf_block, 1);
++      hbuf_b_curr = g_slice_new(hbuf_block);
+       // The block must be persistent after a CR
+       if (cr) {
+         hbuf_b_curr->ptr    = hbuf_b_prev->ptr_end + 1; // == cr+1
+         hbuf_b_curr->flags  = HBB_FLAG_PERSISTENT;
+       } else {
+         hbuf_b_curr->ptr    = hbuf_b_prev->ptr_end; // == br
+-        hbuf_b_curr->flags    = 0;
++        hbuf_b_curr->flags  = 0;
+       }
+-      hbuf_b_curr->ptr_end  = end;
++      hbuf_b_curr->ptr_end       = end;
+       hbuf_b_curr->ptr_end_alloc = hbuf_b_prev->ptr_end_alloc;
++      hbuf_b_curr->prefix.timestamp  = 0;
++      hbuf_b_curr->prefix.flags      = 0;
++      hbuf_b_curr->prefix.mucnicklen = 0;
++      hbuf_b_curr->prefix.xep184     = NULL;
+       // This is OK because insert_before(NULL) == append():
+       *p_hbuf = g_list_insert_before(*p_hbuf, curr_elt->next, hbuf_b_curr);
+     }
+@@ -138,7 +141,7 @@
+   textlen = strlen(text);
+   hbb_blocksize = MAX(textlen+1, HBB_BLOCKSIZE);
+ 
+-  hbuf_block_elt = g_new0(hbuf_block, 1);
++  hbuf_block_elt = g_slice_new(hbuf_block);
+   hbuf_block_elt->prefix.timestamp  = timestamp;
+   hbuf_block_elt->prefix.flags      = prefix_flags;
+   hbuf_block_elt->prefix.mucnicklen = mucnicklen;
+@@ -146,7 +149,7 @@
+   if (!*p_hbuf) {
+     hbuf_block_elt->ptr  = g_new(char, hbb_blocksize);
+     if (!hbuf_block_elt->ptr) {
+-      g_free(hbuf_block_elt);
++      g_slice_free(hbuf_block, hbuf_block_elt);
+       return;
+     }
+     hbuf_block_elt->flags  = HBB_FLAG_ALLOC | HBB_FLAG_PERSISTENT;
+@@ -208,7 +211,7 @@
+                 g_free(hbuf_b_elt->ptr);
+               }
+             }
+-            g_free(hbuf_b_elt);
++            g_slice_free(hbuf_block, hbuf_b_elt);
+             hbuf_head = *p_hbuf = g_list_delete_link(hbuf_head, hbuf_elt);
+           }
+           n--;
+@@ -245,7 +248,7 @@
+     if (hbuf_b_elt->flags & HBB_FLAG_ALLOC) {
+       g_free(hbuf_b_elt->ptr);
+     }
+-    g_free(hbuf_b_elt);
++    g_slice_free(hbuf_block, hbuf_b_elt);
+   }
+ 
+   g_list_free(first_elt);
+@@ -275,7 +278,7 @@
+     // Is next line not-persistent?
+     if (!(hbuf_b_next->flags & HBB_FLAG_PERSISTENT)) {
+       hbuf_b_curr->ptr_end = hbuf_b_next->ptr_end;
+-      g_free(hbuf_b_next);
++      g_slice_free(hbuf_block, hbuf_b_next);
+       curr_elt = g_list_delete_link(curr_elt, next_elt);
+     } else
+       curr_elt = next_elt;
+@@ -309,8 +312,8 @@
+ //  hbuf_get_lines(hbuf, n)
+ // Returns an array of n hbb_line pointers
+ // (The first line will be the line currently pointed by hbuf)
+-// Note: The caller should free the array, the hbb_line pointers and the
+-// text pointers after use.
++// Note: The caller should g_free the array, g_slice_free hbb_line pointers
++// and g_free text pointers after use.
+ hbb_line **hbuf_get_lines(GList *hbuf, unsigned int n)
+ {
+   unsigned int i;
+@@ -349,7 +352,7 @@
+ 
+       blk = (hbuf_block*)(hbuf->data);
+       maxlen = blk->ptr_end - blk->ptr;
+-      *array_elt = (hbb_line*)g_new(hbb_line, 1);
++      *array_elt = (hbb_line*)g_slice_new(hbb_line);
+       (*array_elt)->timestamp  = blk->prefix.timestamp;
+       (*array_elt)->flags      = blk->prefix.flags;
+       (*array_elt)->mucnicklen = blk->prefix.mucnicklen;
+diff -r 70b1f1918050 mcabber/mcabber/hooks.c
+--- a/mcabber/mcabber/hooks.c	Fri Jul 20 17:41:15 2012 +0300
++++ b/mcabber/mcabber/hooks.c	Fri Jul 20 17:51:21 2012 +0300
+@@ -99,7 +99,7 @@
+                      gint priority, gpointer userdata)
+ {
+   GSList **hqueue = NULL;
+-  hook_list_data_t *h = g_new(hook_list_data_t, 1);
++  hook_list_data_t *h = g_slice_new(hook_list_data_t);
+ 
+   h->handler  = handler;
+   h->priority = priority;
+@@ -148,7 +148,7 @@
+   el = g_slist_find_custom(*hqueue, &hid,
+                            (GCompareFunc)_hk_queue_search_cb);
+   if (el) {
+-    g_free(el->data);
++    g_slice_free(hook_list_data_t, el->data);
+     *hqueue = g_slist_delete_link(*hqueue, el);
+     // Remove hook hash table entry if the hook queue is empty
+     if (!*hqueue)
+diff -r 70b1f1918050 mcabber/mcabber/modules.c
+--- a/mcabber/mcabber/modules.c	Fri Jul 20 17:41:15 2012 +0300
++++ b/mcabber/mcabber/modules.c	Fri Jul 20 17:51:21 2012 +0300
+@@ -176,7 +176,7 @@
+   }
+ 
+   { // Register module
+-    loaded_module_t *module = g_new(loaded_module_t, 1);
++    loaded_module_t *module = g_slice_new(loaded_module_t);
+ 
+     module->refcount     = 1;
+     module->locked       = manual;
+@@ -267,7 +267,7 @@
+   // Output this here, as arg may point to module->name
+   scr_LogPrint(LPRINT_LOGNORM, "Unloaded module %s.", module->name);
+   g_free(module->name);
+-  g_free(module);
++  g_slice_free(loaded_module_t, module);
+ 
+   return NULL;
+ }
+diff -r 70b1f1918050 mcabber/mcabber/screen.c
+--- a/mcabber/mcabber/screen.c	Fri Jul 20 17:41:15 2012 +0300
++++ b/mcabber/mcabber/screen.c	Fri Jul 20 17:51:21 2012 +0300
+@@ -1305,7 +1305,7 @@
+         wattrset(win_entry->win, get_color(COLOR_GENERAL));
+ 
+       g_free(line->text);
+-      g_free(line);
++      g_slice_free(hbb_line, line);
+     } else {
+       wclrtobot(win_entry->win);
+       break;
+diff -r 70b1f1918050 mcabber/mcabber/settings.c
+--- a/mcabber/mcabber/settings.c	Fri Jul 20 17:41:15 2012 +0300
++++ b/mcabber/mcabber/settings.c	Fri Jul 20 17:51:21 2012 +0300
+@@ -453,7 +453,7 @@
+     // If value is 0, we do not need to create a structure (that's
+     // the default value).
+     if (value) {
+-      pgpdata = g_new0(T_pgpopt, 1);
++      pgpdata = g_slice_new0(T_pgpopt);
+       pgpdata->pgp_disabled = value;
+       g_hash_table_insert(pgpopt, g_strdup(bjid), pgpdata);
+     }
+@@ -493,7 +493,7 @@
+     // If value is 0, we do not need to create a structure (that's
+     // the default value).
+     if (value) {
+-      pgpdata = g_new0(T_pgpopt, 1);
++      pgpdata = g_slice_new0(T_pgpopt);
+       pgpdata->pgp_force = value;
+       g_hash_table_insert(pgpopt, g_strdup(bjid), pgpdata);
+     }
+@@ -533,7 +533,7 @@
+     // If keyid is NULL, we do not need to create a structure (that's
+     // the default value).
+     if (keyid) {
+-      pgpdata = g_new0(T_pgpopt, 1);
++      pgpdata = g_slice_new0(T_pgpopt);
+       pgpdata->pgp_keyid = g_strdup(keyid);
+       g_hash_table_insert(pgpopt, g_strdup(bjid), pgpdata);
+     }
+diff -r 70b1f1918050 mcabber/mcabber/xmpp_iq.c
+--- a/mcabber/mcabber/xmpp_iq.c	Fri Jul 20 17:41:15 2012 +0300
++++ b/mcabber/mcabber/xmpp_iq.c	Fri Jul 20 17:51:21 2012 +0300
+@@ -751,7 +751,7 @@
+                  lm_message_get_from(m));
+   }
+ 
+-  buf = g_new0(char, 512);
++  buf = g_slice_alloc(512);
+ 
+   r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);
+   query = lm_message_node_add_child(r->node, "query", NULL);
+@@ -778,7 +778,7 @@
+ 
+   lm_connection_send(c, r, NULL);
+   lm_message_unref(r);
+-  g_free(buf);
++  g_slice_free1(512, buf);
+   return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+ }
+ 
+@@ -801,7 +801,7 @@
+                  lm_message_get_from(m));
+   }
+ 
+-  buf = g_new0(char, 512);
++  buf = g_slice_alloc(512);
+ 
+   r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);
+   query = lm_message_node_add_child(r->node, "time", NULL);
+@@ -838,7 +838,7 @@
+ 
+   lm_connection_send(c, r, NULL);
+   lm_message_unref(r);
+-  g_free(buf);
++  g_slice_free1(512, buf);
+   return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+ }
+