# 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);