--- /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;
+ }
+