# HG changeset patch
# Parent 00b93fdbb1de8e77b6992cf799715c1da3e61746
[work-in-progress] Use templates for statusbars
diff -r 00b93fdbb1de mcabber/CMakeLists.txt
--- a/mcabber/CMakeLists.txt Thu Aug 09 00:54:37 2012 +0300
+++ b/mcabber/CMakeLists.txt Tue Aug 28 20:37:28 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 00b93fdbb1de mcabber/mcabber/hooks.c
--- a/mcabber/mcabber/hooks.c Thu Aug 09 00:54:37 2012 +0300
+++ b/mcabber/mcabber/hooks.c Tue Aug 28 20:37:28 2012 +0300
@@ -612,6 +612,10 @@
ns[0] = imstatus2char[new_status];
hk_run_handlers(HOOK_MY_STATUS_CHANGE, args);
+
+ // FIXME not when modules_enable, but when templates_enable
+ settings_set(SETTINGS_TYPE_OPTION, "my_status", ns);
+ settings_tmpl_set("my_message", msg);
}
#endif
diff -r 00b93fdbb1de mcabber/mcabber/parser.c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/parser.c Tue Aug 28 20:37:28 2012 +0300
@@ -0,0 +1,1116 @@
+
+/* 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"
+
+//
+// Common
+//
+
+// XXX one for all 'mcabber' quark? to utils?
+GQuark tmpl_gerror_quark ( void )
+{
+ return g_quark_from_static_string ( "tmpl_gerror_quark" );
+}
+
+//
+// Globs
+//
+
+// match tmpl_glob ( string, pattern, greedy, right-to-left )
+// Performs matching of given pattern against given string,
+// returns first substring in input, that matched. In pattern,
+// 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 );
+}
+
+//
+// Simple math expressions with variables
+//
+
+typedef enum {
+ no_op,
+ noempty_op,
+ paren_op,
+ plus_op,
+ minus_op,
+ remainder_op,
+ division_op,
+ multiplication_op,
+} math_operator_t;
+
+typedef enum {
+ in_preargument,
+ in_hexoctzero,
+ in_number,
+ in_variable,
+ in_postparen,
+ in_op,
+} math_parser_state_t;
+
+// - + * / % ( )
+static gssize tmpl_math_expand_internal ( const char *str, gsize length, tmpl_variable_callback_t get_val, gpointer udata, GError **error, math_operator_t bound, gsize *proc_len )
+{
+ g_assert ( error != NULL && *error == NULL );
+
+ gssize result = 0;
+ const char * p = str;
+ const char * const e = str + length;
+ math_parser_state_t state = in_preargument;
+ const char * varname = NULL; // variable name start
+ gsize base = 10; // number base
+ gboolean invert = FALSE; // number change sign
+ 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 ) {
+ math_operator_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;
+}
+
+//
+// Shell-like variable substitution
+//
+
+typedef enum {
+ in_string,
+ in_escape,
+ in_var,
+ in_plainvarstart,
+ in_plainvar,
+ in_prevarname,
+ in_varnamestart,
+ in_varname,
+ in_varchop,
+ in_varstrip,
+ in_varstripchop,
+ in_varslash,
+ in_varmatch,
+ in_varpresubst,
+ in_varsubst,
+ in_varcolon,
+ in_varminus,
+ in_varplus,
+ in_varpos,
+ in_postvarpos,
+ in_varlen,
+ in_varsubstring,
+ in_varend,
+ in_evalstart,
+ in_mathexpand,
+ in_dblparen_eol,
+ in_varif,
+ in_postvarif,
+ in_varelse,
+ nostate,
+} var_parser_state_t;
+
+const static char * const var_parser_statenames [ ] = {
+ "in string",
+ "in escape",
+ "in var",
+ "in plainvarstart",
+ "in plainvar",
+ "in prevarname",
+ "in varnamestart",
+ "in varname",
+ "in varchop",
+ "in varstrip",
+ "in varstripchop",
+ "in varslash",
+ "in varmatch",
+ "in varpresubst",
+ "in varsubst",
+ "in varcolon",
+ "in varminus",
+ "in varplus",
+ "in varpos",
+ "in postvarpos",
+ "in varlen",
+ "in varsubstring",
+ "in varend",
+ "in evalstart",
+ "in mathexpand",
+ "in dblparen eol",
+ "in_varif",
+ "in_postvarif",
+ "in_varelse",
+ "wrong state",
+};
+
+typedef enum {
+ no_eol = 0x00,
+ brace_eol = 0x01,
+ slash_eol = 0x02,
+ colon_eol = 0x04,
+ paren_eol = 0x08,
+ dblparen_eol = 0x10,
+ balance_parens = 0x20,
+} var_parser_flags_t;
+
+typedef enum {
+ no_operation,
+ expand_var,
+ value_length,
+} var_operation_t;
+
+// expansion tmpl_expand ( template, callback )
+// Parse template, substitute shell-like expressions:
+// * $var ${var}
+// * ${var##pat} ${var#pat} ${var%%pat} ${var%pat}
+// * ${var/pat} ${var/pat/repl} ${var//pat/repl} ${var/%pat/repl} ${var/#pat/repl}
+// * ${var:+tmpl} ${var:-tmpl} ${var?tmpl:tmpl}
+// * ${var:math} ${var:math:math}
+// * ${#var} ${!var} ${!var[operation]}
+// * $(( math ))
+// * \n \t \e \$ \\ \X
+// Callback will be called to obtain variable values.
+// Variable name should be: /^[[:alnum:]][[:alnum:]_-]*$/
+// For pattern rules see tmpl_glob ().
+// For math rules see tmpl_math_expand ().
+// TODO:
+// * validate variable name in ${!var} expression
+// * de-hardcode variable name validation rules (or allow pure-numeric names)
+static gchar *tmpl_var_expand_internal ( const char *template, const char * const e, tmpl_variable_callback_t get_val, gpointer udata, gsize *ret_len, GError **error, var_parser_flags_t flags, gsize *proc_len )
+{
+ g_assert ( error != NULL && *error == NULL );
+
+ if ( template == NULL || len == 0 )
+ return NULL;
+
+ GString *result = g_string_new ( NULL );
+ const char *p = template;
+ var_parser_state_t state = in_string;
+ const char *a1s = p; // string, varname
+ const gchar *value = NULL; // variable value
+ gsize vlen = 0; // variable value length
+ 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 if ( *p == '?' ) { // conditional expression
+ state = in_varif;
+ 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_var_expand_internal ( p, e, 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_var_expand_internal ( p, e, get_val, udata, &plen, error, brace_eol | slash_eol, &processed );
+ p += processed;
+ state = in_varpresubst;
+ } else if ( state == in_varpresubst ) { // skip slash
+ if ( *p == '/' )
+ p ++;
+ state = in_varsubst;
+ } else if ( state == in_varsubst ) { // replace expression
+ gsize processed = 0;
+ gsize slen = 0;
+ gchar *subst = tmpl_var_expand_internal ( p, e, get_val, udata, &slen, error, brace_eol, &processed );
+ if ( *error == NULL ) {
+ const char * start = value;
+ const char * const ve = value + vlen;
+ 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_var_expand_internal ( p, e, get_val, udata, &elen, error, brace_eol, &processed );
+ if ( *error == NULL ) {
+ if ( ( vlen == 0 && state == in_varminus ) || ( vlen > 0 && state == in_varplus ) )
+ g_string_append_len ( result, expansion, elen );
+ 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_var_expand_internal ( p, e, get_val, udata, &elen, error, colon_eol | brace_eol | balance_parens, &processed );
+ if ( *error == NULL ) {
+ subsoffset = tmpl_math_expand_internal ( expansion, elen, get_val, udata, error, no_op, NULL );
+ if ( subsoffset < 0 )
+ 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_var_expand_internal ( p, e, get_val, udata, &elen, error, brace_eol | balance_parens, &processed );
+ if ( *error == NULL ) {
+ subslength = tmpl_math_expand_internal ( expansion, elen, get_val, udata, error, no_op, NULL );
+ if ( subslength < 0 )
+ 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_varif ) {
+ gsize elen = 0;
+ gsize processed = 0;
+ gchar *expansion = tmpl_var_expand_internal ( p, e, get_val, udata, &elen, error, colon_eol | brace_eol, &processed );
+ if ( *error == NULL && vlen != 0 )
+ g_string_append_len ( result, expansion, elen );
+ g_free ( expansion );
+ p += processed;
+ state = in_postvarif;
+ } else if ( state == in_postvarif ) {
+ if ( *p == ':' ) {
+ state = in_varelse;
+ p ++;
+ } else if ( *p == '}' )
+ state = in_varend;
+ else
+ g_assert_not_reached ();
+ } else if ( state == in_varelse ) {
+ gsize elen = 0;
+ gsize processed = 0;
+ gchar *expansion = tmpl_var_expand_internal ( p, e, get_val, udata, &elen, error, brace_eol, &processed );
+ if ( *error == NULL && vlen == 0 )
+ g_string_append_len ( result, expansion, elen );
+ g_free ( expansion );
+ p += processed;
+ state = in_varend;
+ } else if ( state == in_varend ) { // end of enclosed variable
+ if ( *p == '}' ) {
+ p ++;
+ 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_var_expand_internal ( p, e, get_val, udata, &elen, error, dblparen_eol | balance_parens, &processed );
+ if ( *error == NULL ) {
+ gssize value = tmpl_math_expand_internal ( expansion, elen, get_val, udata, error, no_op, NULL );
+ if ( *error == NULL )
+ 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'", var_parser_statenames [ state < nostate ? state : nostate ] );
+ }
+
+ 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 wrapper
+gchar *tmpl_var_expand ( const char *template, gsize len, tmpl_variable_callback_t get_val, gpointer udata, gsize *ret_len, GError **ret_err )
+{
+ g_return_val_if_fail ( ret_err == NULL || *ret_err == NULL, NULL );
+
+ GError * error = NULL;
+
+ gchar * result = tmpl_var_expand_internal ( template, template + len, get_val, udata, ret_len, &error, no_eol, NULL );
+
+ if ( error != NULL ) {
+ g_propagate_error ( ret_err, error );
+ g_free ( result );
+ return NULL;
+ }
+
+ return result;
+}
+
+//
+// Simple markup language
+//
+
+/*
+typedef enum {
+ ml_color,
+ ml_fill_right,
+ ml_fill_left,
+ ml_fill_both,
+} markup_link_type_t;
+
+typedef guint markup_color_t;
+
+typedef struct {
+ markup_link_type_t type;
+ markup_color_t color;
+ const char * s;
+ gssize len;
+ gboolean permanent;
+ guint width;
+} markup_link_t;
+*/
+
+typedef enum {
+ mp_in_string,
+ mp_in_percent,
+ mp_in_color,
+ mp_nostate,
+} markup_parser_state_t;
+
+const static char * const markup_parser_statenames [ ] = {
+ "in string",
+ "in percent",
+ "in color",
+ "wrong state",
+};
+
+void tmpl_markup_free ( markup_link_t * link )
+{
+ markup_link_t * current = link;
+ while ( current != NULL ) {
+ markup_link_t * tmp = current -> next;
+ if ( link -> permanent )
+ g_free ( link -> s );
+ g_slice_free ( markup_link_t, current );
+ current = tmp;
+ }
+}
+
+markup_link_t * link tmpl_markup_permanentize ( markup_link_t * link )
+{
+ markup_link_t * current;
+ for ( current = link; current -> next != NULL; current = current -> next ) {
+ current -> s = g_strndup ( current -> s, current -> s - current -> e );
+ current -> permanent = TRUE;
+ }
+ return link;
+}
+
+static tmpl_markup_link_t * tmpl_append_markup_link ( markup_link_t *last, markup_link_type_t type, markup_color_t color, const gchar * s, const gchar * e )
+{
+ if ( type == ml_color ) { // it is not splitter
+ if ( last -> s == NULL || last -> len == 0 ) { // append to empty string -- overwrite color & string
+ last -> s = s;
+ last -> len = e - s;
+ last -> color = color;
+ return last;
+ } else if ( last -> color == color ) { // color unchanged
+ if ( s == e ) // appending empty string - do nothing
+ return last;
+ else if ( last -> s + last -> len == s ) { // appending continuation of current string
+ last -> len += e - s;
+ return last;
+ }
+ }
+ }
+
+ tmpl_markup_link_t * link = g_slice_new ( markup_link_t );
+
+ link -> type = type;
+ link -> color = color;
+ link -> s = s;
+ link -> len = e - s;
+ link -> permanent = FALSE;
+ link -> next = last -> next; // allow insertion
+
+ last -> next = link;
+
+ return link;
+}
+
+static markup_link_t * tmpl_markup_expand_internal ( markup_link_t * chain, const char * template, const char * const e, tmpl_shortcut_callback_t get_shortcut, gpointer udata, GError **error )
+{
+ tmpl_markup_link_t * last = chain;
+ markup_parser_state_t state = mp_in_string;
+ const char * p = template;
+ const char * s = template;
+ markup_color_t color = 0;
+
+ while ( p < e && *error == NULL ) {
+ if ( state == mp_in_string ) {
+ if ( *p == '%' ) {
+ last = tmpl_append_markup_link ( last, ml_color, last -> color, s, p - s );
+ state = mp_in_percent;
+ }
+ p ++;
+ } else if ( state == mp_in_percent ) {
+ if ( *p == '%' ) {
+ s = p;
+ state = mp_in_string
+ p ++;
+ } else if ( isalpha ( *p ) ) {
+ gsize slen = 0;
+ const char * shortcut = get_shortcut ( *p, &slen, udata );
+ last = tmpl_markup_expand_internal ( last, shortcut, shortcut + slen, get_shortcut, udata, error );
+ state = mp_in_string;
+ p ++;
+ } else if ( isdigit ( *p ) ) {
+ color = 0;
+ state = mp_in_color;
+ } else if ( *p == '<' ) {
+ p ++;
+ last = tmpl_append_markup_link ( last, ml_fill_left, last -> color, NULL, 0 );
+ s = p;
+ state = mp_in_string;
+ } else if ( *p == '|' ) {
+ p ++;
+ last = tmpl_append_markup_link ( last, ml_fill_both, last -> color, NULL, 0 );
+ s = p;
+ state = mp_in_string;
+ } else if ( *p == '>' ) {
+ p ++;
+ last = tmpl_append_markup_link ( last, ml_fill_right, last -> color, NULL, 0 );
+ s = p;
+ state = mp_in_string;
+ } else
+ g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EWRONGMARKUP, "Wrong symbol in markup expression: '%c'", *p );
+ } else if ( state == mp_in_color ) {
+ short d = -1;
+ if ( *p == '0' )
+ d = 0;
+ else if ( *p == '1' )
+ d = 1;
+ else if ( *p == '2' )
+ d = 2;
+ else if ( *p == '3' )
+ d = 3;
+ else if ( *p == '4' )
+ d = 4;
+ else if ( *p == '5' )
+ d = 5;
+ else if ( *p == '6' )
+ d = 6;
+ else if ( *p == '7' )
+ d = 7;
+ else if ( *p == '8' )
+ d = 8;
+ else if ( *p == '9' )
+ d = 9;
+ if ( d >= 0 ) {
+ color = color * 10 + d;
+ p ++;
+ } else {
+ last = tmpl_append_markup_link ( last, ml_color, color, NULL, 0 );
+ s = p;
+ state = mp_in_string;
+ }
+ } else
+ g_assert_not_reached ();
+ }
+
+ if ( error == NULL ) {
+ if ( state == mp_in_string )
+ last = tmpl_append_markup_chunk ( last, ml_color, last -> color, s, e );
+ else if ( state == mp_in_color )
+ last = tmpl_append_markup_link ( last, ml_color, color, NULL, 0 );
+ else
+ g_set_error ( error, TMPL_GERROR_QUARK, TMPL_EWRONGMARKUP,
+ "Markup parser ended in a wrong state '%s'",
+ markup_parser_statenames [ state < mp_nostate ? state : mp_nostate ] );
+ }
+
+ return last;
+}
+
+// public wrapper
+markup_link_t * tmpl_markup_expand ( const char *template, gsize len, tmpl_chunk_callback_t get_chunk, gpointer udata, GError **ret_err )
+{
+ g_return_val_if_fail ( ret_err == NULL || *ret_err == NULL, NULL );
+
+ GError * error = NULL;
+ markup_link_t * result = g_slice_new ( tmpl_markup_link_t );
+
+ result -> type = ml_color;
+ result -> color = 0;
+ result -> s = NULL;
+ result -> len = 0;
+ result -> permanent = FALSE;
+ result -> next = NULL;
+
+ tmpl_markup_expand_internal ( result, template, template + len, get_chunk, udata, &error );
+
+ if ( error != NULL ) {
+ g_propagate_error ( ret_err, error );
+ tmpl_markup_free ( result );
+ return NULL;
+ }
+
+ return result;
+}
+
+/* vim: se ts=4 sw=4: */
diff -r 00b93fdbb1de mcabber/mcabber/parser.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/parser.h Tue Aug 28 20:37:28 2012 +0300
@@ -0,0 +1,154 @@
+
+#ifndef MCABBER_PARSER_H
+#define MCABBER_PARSER_H 1
+
+/* 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>
+
+//
+// Design notes:
+//
+// Please, keep this part of code free of direct mcabber internals references,
+// as a sort of in-tree library. This way it is much easier to test and debug.
+//
+
+//
+// Typedefs
+//
+
+#define TMPL_GERROR_QUARK tmpl_gerror_quark ()
+
+#define TMPL_EWRONGVAREXP ( 0x01 )
+#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 )
+
+#define TMPL_EWRONGMARKUP ( 0x0A )
+
+typedef const char *(*tmpl_variable_callback_t) ( const gchar *name, gsize len, gpointer udata, gsize *ret_len );
+typedef const char *(*tmpl_shortcut_callback_t) ( const gchar name, gpointer udata, gsize *ret_len );
+
+typedef enum {
+ ml_color,
+ ml_fill_right,
+ ml_fill_left,
+ ml_fill_both,
+} markup_link_type_t;
+
+typedef guint markup_color_t;
+
+typedef struct {
+ markup_link_type_t type;
+ markup_color_t color;
+ const char * s;
+ gsize len;
+ gboolean permanent;
+ gsize width;
+} markup_link_t;
+
+//
+// Common
+//
+
+GQuark tmpl_gerror_quark ( void );
+
+//
+// Simple glob matching
+//
+
+// match tmpl_glob ( string, pattern, right-to-left )
+// Performs matching of given pattern against given string,
+// returns substring, that matched. In pattern, there are currently
+// two tokens recognized: '*' and '?'. Two flags determine, if
+// '*' should be greedy and which end of string must match the
+// pattern (i.e. have anchor).
+const char * tmpl_glob ( const char * str, gsize strlen, const char * pat, gsize patlen, gboolean rtl, gsize * ret_len );
+
+// 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 );
+
+//
+// Math expansion
+//
+
+// result tmpl_math_expand ( string, callback )
+// Performs mathematical expansion of given string.
+// Note, that $var expressions are not recognized, you have to
+// supply already expanded string here.
+// Supported operators:
+// ( ) + - * / %
+gssize tmpl_math_expand ( const char * str, gsize len, tmpl_variable_callback_t get_val, gpointer udata, GError ** error );
+
+//
+// Variable expansion
+//
+
+// expansion tmpl_expand ( template, callback )
+// Parse template, substitute shell-like expressions:
+// * $var ${var}
+// * ${var##pat} ${var#pat} ${var%%pat} ${var%pat}
+// * ${var/pat} ${var/pat/repl} ${var//pat/repl} ${var/%pat/repl} ${var/#pat/repl}
+// * ${var:+tmpl} ${var:-tmpl}
+// * ${var:mathexp} ${var:mathexp:mathexp}
+// * ${#var} ${!var} ${!var[operation]}
+// * $(( mathexp ))
+// * \n \t \e \$ \\ \X
+// Callback will be called to obtain variable values.
+// Variable name should be: /^[[:alnum:]][[:alnum:]_-]*$/
+// For pattern rules see tmpl_glob ().
+// For math expansion rules see tmpl_math_expand ().
+// Returned value should be g_free'd.
+gchar *tmpl_var_expand ( const char * str, gsize len, tmpl_variable_callback_t get_val, gpointer udata, gsize *ret_len, GError ** error );
+
+//
+// Simple markup parsing
+//
+
+// tmpl_markup_link_free ( chain )
+// Frees markup links chain (and associated strings, if permanentized).
+void tmpl_markup_free ( markup_link_t * chain );
+
+// chain tmpl_markup_permanentize ( chain )
+// Strdups strings, used in markup to make them independent of chunk expansions used.
+markup_link_t * tmpl_markup_permanentize ( markup_link_t * chain );
+
+// chain tmpl_markup_expand ( template, callback )
+// Parses simple markup language:
+// * %% - '%' symbol
+// * %a - %Z - insert shortcut with this name (expand it as well)
+// * %123 - text after this will be marked with color 123
+// * %> %< %| - text padding/trimming marks
+// Returns a chain of markup_link_t structs.
+// String pointers in links can be NULL.
+// String pointers in links point to parts of template or returned by shortcut callback values,
+// so, take care not to modify/free any of them, while using chain. Alternatively, you can
+// tmpl_markup_permanentize () markup.
+// Width fields are reserved for user, they are not set.
+// User have to free returned markup chain with tmpl_markup_free ().
+markup_link_t * tmpl_markup_expand ( const char * str, gsize len, tmpl_shortcut_callback_t get_chunk, gpointer udata, GError ** error );
+
+#endif
+
diff -r 00b93fdbb1de mcabber/mcabber/settings.c
--- a/mcabber/mcabber/settings.c Thu Aug 09 00:54:37 2012 +0300
+++ b/mcabber/mcabber/settings.c Tue Aug 28 20:37:28 2012 +0300
@@ -363,6 +363,32 @@
return 0;
}
+// settings_tmpl_set(option, value)
+// Sets option, escaping it for use in templates.
+// Could be more efficient with notifiers instead of guards.
+void settings_tmpl_set(const gchar *key, const gchar *value)
+{
+ if (value && *value) {
+ const gchar *p = strchr(value, '%');
+ if (p) {
+ GString *result = g_string_new(NULL);
+ const gchar *s = value;
+ while (p) {
+ g_string_append_len(result, s, p - s + 1);
+ g_string_append_c(result, '%');
+ s = ++ p;
+ p = strchr(s, '%');
+ }
+ if (*s)
+ g_string_append(result, s);
+ settings_set(SETTNIGS_TYPE_OPTION, key, result -> str);
+ g_string_free(result, TRUE);
+ return;
+ }
+ }
+ settings_set ( SETTNIGS_TYPE_OPTION, key, value );
+}
+
// settings_get_status_msg(status)
// Return a string with the current status message:
// - if there is a user-defined message ("message" option),
diff -r 00b93fdbb1de mcabber/mcabber/settings.h
--- a/mcabber/mcabber/settings.h Thu Aug 09 00:54:37 2012 +0300
+++ b/mcabber/mcabber/settings.h Tue Aug 28 20:37:28 2012 +0300
@@ -37,6 +37,7 @@
void settings_opt_set_raw(const gchar *key, const gchar *value);
void settings_set(guint type, const gchar *key, const gchar *value);
void settings_del(guint type, const gchar *key);
+void settings_tmpl_set(const gchar *key, const gchar *value);
const gchar *settings_get(guint type, const gchar *key);
int settings_get_int(guint type, const gchar *key);
const gchar *settings_get_status_msg(enum imstatus status);
diff -r 00b93fdbb1de mcabber/mcabber/templates.c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/templates.c Tue Aug 28 20:37:28 2012 +0300
@@ -0,0 +1,467 @@
+
+/* Copyright 2012 Myhailo Danylenko
+ *
+ * This file is part of mcabber
+ *
+ * mcabber is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <glib.h>
+
+#include <string.h> // strlen
+
+#include "logprint.h"
+#include "settings.h"
+#include "main.h" // main_context
+#include "templates.h"
+#include "parser.h"
+
+//
+// globals
+//
+
+typedef enum {
+ markup_template, // fully processed $ and % templates
+ shortcut_template, // no-callback $-only but %-propagating templates
+ var_template, // $-only templates with callback
+} template_type_t;
+
+typedef union {
+ tmpl_var_callback_t var;
+ tmpl_markup_callback_t markup;
+} template_callback_t;
+
+typedef struct {
+ gchar * name; // variable name
+ template_type_t type; // can be only one (for simplicity)
+ template_callback_t callback; //
+ gpointer userdata; // userdata for callback
+ GSList * guards; // var-guards
+ gboolean in_use; // to mark markup & var templates for drop
+ gboolean var_changed; // var-reevaluate flag
+ gboolean markup_changed; // markup-reevaluate flag
+ gboolean run_callback; // rerun callback flag
+ gchar * var_expansion; // var-expansion of template
+ GSList * templates; // markup templates, that use this shortcut template
+ GSList * shortcuts; // shortcut templates in use by this markup template
+} template_t;
+
+typedef struct {
+ gchar * name;
+ GSList * templates;
+} tmpl_guard_t;
+
+static GHashTable * tmpl_templates = NULL;
+static GHashTable * tmpl_guards = NULL;
+static guint tmpl_attached_id = 0;
+
+//
+// predeclarations
+//
+
+static gchar *tmpl_guard ( const gchar *key, const gchar *new_value );
+
+//
+// code
+//
+
+// [cb] drops template from guard's 'templates' list
+static void tmpl_unguard ( gpointer data, gpointer udata )
+{
+ tmpl_guard_t *guard = data;
+ template_t *template = udata;
+ guard -> templates = g_slist_remove ( guard -> templates, template );
+}
+
+// [cb] drops template from shortcut's 'templates' list
+static void tmpl_unshortcut ( gpointer data, gpointer udata )
+{
+ template_t * shortcut = data;
+ template_t * template = udata;
+ shortcut -> templates = g_slist_remove ( shortcut -> templates, template );
+}
+
+// [destructor cb] releases guard hash table entry
+static void tmpl_free_guard ( gpointer data )
+{
+ tmpl_guard_t * guard = data;
+ settings_del_guard ( guard -> name );
+ g_slist_free ( guard -> templates );
+ g_free ( guard -> name );
+ g_slice_free ( tmpl_guard_t, guard );
+}
+
+// [destructor cb] releases taken guards and frees command
+static void tmpl_free_template ( gpointer data )
+{
+ template_t * template = data;
+ // not running unshortcut, as it was done earlier
+ // or it is global destruction and chunk may no longer exist
+ g_slist_free ( template -> shortcuts );
+ g_slist_free ( template -> templates );
+ g_slist_foreach ( template -> guards, tmpl_unguard, template );
+ g_slist_free ( template -> guards );
+ g_free ( template -> name );
+ g_free ( template -> var_expansion );
+ g_slice_free ( template_t, template );
+}
+
+// [cb] sets var-changed flag on template
+static void template_set_var_changed ( gpointer data, gpointer udata )
+{
+ template_t * template = data;
+ template -> var_changed = TRUE;
+}
+
+// [cb] sets markup-changed flag on template
+static void template_set_markup_changed ( gpointer data, gpointer udata )
+{
+ template_t * template = data;
+ template -> markup_changed = TRUE;
+}
+
+// install guard (name must be glib-allocated string)
+// XXX we are always using the same callback?
+static void tmpl_install_guard ( gchar *name, template_t *template, settings_guard_t callback )
+{
+ tmpl_guard_t *guard = g_hash_table_lookup ( tmpl_guards, name );
+ if ( guard == NULL ) {
+ if ( ! settings_set_guard ( name, callback ) ) {
+ scr_log_print ( LPRINT_LOGNORM, "Error: Unable to install guard for variable '%s' for template '%s'.", name, template -> name );
+ g_free ( name );
+ } else {
+ guard = g_slice_new ( tmpl_guard_t );
+ guard -> name = name;
+ guard -> templates = NULL;
+ g_hash_table_replace ( tmpl_guards, guard -> name, guard ); // to be sure
+ }
+ } else
+ g_free ( name );
+ if ( ! g_slist_find ( template -> guards, guard ) ) {
+ template -> guards = g_slist_prepend ( template -> guards, guard );
+ guard -> templates = g_slist_prepend ( guard -> templates, template );
+ }
+}
+
+// [parser cb] provides mcabber option values & reinstalls guards
+static const char *tmpl_get_var ( const gchar *name, gsize len, gpointer udata, gsize *ret_len )
+{
+ const char *result = NULL;
+ if ( name != NULL && len > 0 ) {
+ template_t * template = udata;
+ gchar * var = g_strndup ( name, len );
+ result = settings_opt_get ( var );
+ // consumes var
+ tmpl_install_guard ( var, template, tmpl_guard );
+ }
+ if ( ret_len != NULL ) {
+ if ( result != NULL )
+ *ret_len = strlen ( result );
+ else
+ *ret_len = 0;
+ }
+ return result;
+}
+
+// [parser cb] create & evaluate shortcut template, add to dependencies, return expansion
+static const char *tmpl_get_shortcut ( const gchar name, gpointer udata, gsize *ret_len )
+{
+ gchar optname [ 7 ] = "tmpl_X\0";
+ template_t * template = udata;
+ template_t * shortcut;
+ optname [ 5 ] = name;
+ shortcut = g_hash_table_lookup ( templates, optname );
+ if ( shortcut != NULL ) {
+ if ( shortcut -> type != shortcut_template ) {
+ scr_log_print ( LPRINT_LOGNORM, "Error: Conflicting type of template for shortcut '%c'.", name );
+ return NULL;
+ }
+ // link as dependency
+ if ( ! g_slist_find ( template -> shortcuts, shortcut ) ) {
+ shortcut -> templates = g_slist_prepend ( shortcut -> templates, template );
+ template -> shortcuts = g_slist_prepend ( template -> shortcuts, shortcut );
+ }
+ } else {
+ // create new
+ shortcut = template_new ( name );
+ if ( shortcut == NULL ) {
+ scr_log_print ( LPRINT_LOGNORM, "Error: Unable to create template for shortcut '%c'.", name );
+ return NULL;
+ }
+ shortcut -> type = shortcut_template;
+ shortcut -> markup_changed = FALSE;
+ // consumes optname
+ tmpl_install_guard ( g_strndup ( optname, 6 ), shortcut, tmpl_guard );
+ // link as dependency
+ shortcut -> templates = g_slist_prepend ( shortcut -> templates, template );
+ template -> shortcuts = g_slist_prepend ( template -> shortcuts, shortcut );
+ // update expansion
+ if ( shortcut -> var_changed ) {
+ GError *error = NULL;
+ shortcut -> var_changed = FALSE;
+ shortcut -> var_expansion = tmpl_var_expand ( expression, strlen ( expression ), tmpl_get_var, template, NULL, &error );
+ if ( error != NULL ) {
+ scr_log_print ( LPRINT_LOGNORM, "Error: Expansion error on shortcut '%c': %s", name, error -> message );
+ g_error_free ( error );
+ return NULL;
+ }
+ }
+ }
+ return shortcut -> var_expansion;
+}
+
+// [cb]
+// update expansion of all $-changed templates,
+// reinstall guards,
+// mark dependencies as %-changed,
+// call $-callbacks
+static void evaluate_template1 ( gpointer key, gpointer value, gpointer udata )
+{
+ template_t * template = value;
+
+ if ( template -> changed && template -> in_use ) {
+ const gchar * expression = settings_opt_get ( template -> name );
+ gchar * expansion = NULL;
+ // release guards (but do not free them)
+ g_slist_foreach ( template -> guards, tmpl_unguard, template );
+ g_slist_free ( template -> guards );
+ template -> guards = NULL;
+ // re-install guards & get updated expansion
+ if ( expression != NULL ) {
+ GError *error = NULL;
+ expansion = tmpl_var_expand ( expression, strlen ( expression ), tmpl_get_var, template, NULL, &error );
+ if ( error != NULL ) {
+ scr_log_print ( LPRINT_LOGNORM, "Error: Expansion error on template '%s': %s", template -> name, error -> message );
+ g_error_free ( error );
+ }
+ }
+ // re-install guard on template itself
+ tmpl_install_guard ( g_strdup ( template -> name ), template, tmpl_guard );
+ template -> changed = FALSE;
+ // check, if expansion has changed
+ if ( g_strcmp0 ( expansion, template -> var_expansion ) ||
+ ( template -> type == var_template && template -> run_callback ) ) {
+ g_free ( template -> var_expansion );
+ template -> var_expansion = expansion;
+ if ( template -> type == var_template ) {
+ // pass result to callback
+ template -> run_callback = FALSE;
+ template -> callback.var ( expansion, template -> userdata );
+ } else if ( template -> type == shortcut_template )
+ // mark %-dependencies as %-changed
+ g_slist_foreach ( template -> templates, tmpl_set_markup_changed, NULL );
+ else if ( template -> type == markup_template )
+ // mark self as %-changed
+ template -> markup_changed = TRUE;
+ else
+ g_assert_not_reached ();
+ } else
+ g_free ( expansion );
+ }
+}
+
+// [cb]
+// drop dependencies for unused root templates,
+// evaluate %-changed templates,
+// rebuild dependencies, call %-callbacks
+static void evaluate_template2 ( gpointer key, gpointer value, gpointer udata )
+{
+ template_t * template = value;
+
+ if ( template -> type == markup_template ) {
+ if ( ! template -> in_use ) {
+ // release chunks
+ g_slist_foreach ( template -> shortcuts, tmpl_unshortcut, template );
+ g_slist_free ( template -> shortcuts );
+ template -> shortcuts = NULL;
+ } else if ( template -> markup_changed || template -> run_callback ) {
+ // release chunks
+ g_slist_foreach ( template -> shortcuts, tmpl_unshortcut, template );
+ g_slist_free ( template -> shortcuts );
+ template -> shortcuts = NULL;
+ // evaluate template
+ GError * error = NULL;
+ markup_link_t * expansion = tmpl_markup_expand ( template -> var_expansion, strlen ( template -> var_expansion ), tmpl_get_shortcut, template, NULL, &error );
+ if ( error != NULL ) {
+ scr_log_print ( LPRINT_LOGNORM, "Error: Markup error on template '%s': %s.", template -> name, error -> message );
+ g_error_free ( error );
+ }
+ template -> run_callback = FALSE;
+ template -> callback.markup ( expansion, template -> userdata );
+ if ( expansion != NULL )
+ tmpl_markup_free ( expansion ); // TODO cache / check / ...
+ }
+ }
+}
+
+// [cb] mark deleted templates for removal
+static gboolean tmpl_drop_unused_templates ( gpointer key, gpointer value, gpointer udata )
+{
+ template_t * template = value;
+ if ( template -> type == shortcut_template )
+ // drop chunk templates with no dependent root templates
+ return template -> templates == NULL;
+ else
+ // drop user templates, marked as unused
+ return ! template -> in_use;
+}
+
+// [cb] mark unused guards for removal
+static gboolean tmpl_drop_unused_guards ( gpointer key, gpointer value, gpointer udata )
+{
+ tmpl_guard_t * guard = value;
+ if ( guard -> templates == NULL )
+ return TRUE;
+ return FALSE;
+}
+
+// [idle cb] update commands/guards & call cbs when necessary
+static gboolean reevaluate_templates ( gpointer data )
+{
+ // allow reschedule in a process of reevaluation
+ tmpl_attached_id = 0;
+ // update expansion of var-changed templates, call var-callbacks
+ g_hash_table_foreach ( tmpl_templates, evaluate_template1, NULL );
+ // release shortcuts of unused markup templates, reevaluate markup-changed templates, call markup-callbacks
+ g_hash_table_foreach ( tmpl_templates, evaluate_template2, NULL );
+ // drop removed shortcut- and unused markup- and var- templates
+ g_hash_table_foreach_remove ( tmpl_templates, tmpl_drop_unused_templates, NULL );
+ // free unused guards TODO do only when needed
+ g_hash_table_foreach_remove ( tmpl_guards, tmpl_drop_unused_guards, NULL );
+ // always return false, this is oneshot idle call
+ return FALSE;
+}
+
+// schedule templates reevaluation
+static void tmpl_schedule_rerun ( void )
+{
+ if ( tmpl_attached_id == 0 ) {
+ GSource * source = g_idle_source_new ();
+ g_source_set_callback ( source, reevaluate_templates, NULL, NULL );
+ tmpl_attached_id = g_source_attach ( source, main_context );
+ g_source_unref ( source );
+ }
+}
+
+// [guard] generic guard for variable
+static gchar *tmpl_guard ( const gchar *key, const gchar *new_value )
+{
+ if ( g_strcmp0 ( new_value, settings_opt_get ( key ) ) ) {
+ // mark dependent commands as modified
+ tmpl_guard_t *guard = g_hash_table_lookup ( tmpl_guards, key );
+ g_slist_foreach ( guard -> templates, template_set_var_changed, NULL );
+ // schedule changed templates reevaluation
+ tmpl_schedule_rerun ();
+ }
+ return g_strdup ( new_value );
+}
+
+// allocates new template and sets generic values
+static template_t *template_new ( const gchar * name )
+{
+ g_assert ( name != NULL );
+ // check for existing template
+ template_t * template = g_hash_table_lookup ( tmpl_templates, name );
+ if ( template != NULL )
+ return NULL;
+ // create new
+ template = g_slice_new ( template_t );
+ template -> name = g_strdup ( name );
+ template -> userdata = udata;
+ template -> in_use = TRUE;
+ template -> guards = NULL;
+ template -> var_changed = TRUE;
+ template -> markup_changed = TRUE;
+ template -> run_callback = FALSE; // XXX
+ template -> var_expansion = NULL;
+ template -> templates = NULL;
+ template -> shortcuts = NULL;
+ return template;
+}
+
+// public
+gboolean markup_template_add ( const gchar * name, tmpl_markup_callback_t callback, gpointer udata )
+{
+ g_assert ( callback != NULL );
+
+ template_t * template = template_new ( name );
+ if ( template == NULL )
+ return FALSE;
+
+ // fill specific fields
+ template -> type = markup_template;
+ template -> callback.markup = callback;
+ template -> userdata = udata;
+ template -> markup_changed = TRUE;
+ // schedule new template evaluation
+ tmpl_schedule_rerun ();
+ return TRUE;
+}
+
+// public
+gboolean var_template_add ( const gchar * name, tmpl_var_callback_t callback, gpointer udata )
+{
+ g_assert ( callback != NULL );
+
+ template_t * template = template_new ( name );
+ if ( template == NULL )
+ return FALSE;
+
+ // fill specific fields
+ template -> type = var_template;
+ template -> callback.var = callback;
+ template -> userdata = udata;
+ template -> markup_changed = FALSE;
+ // schedule new template evaluation
+ tmpl_schedule_rerun ();
+ return TRUE;
+}
+
+// public
+void template_set_in_use ( const gchar * name, gboolean in_use )
+{
+ g_assert ( name != NULL );
+ template_t * template = g_hash_table_lookup ( tmpl_templates, name );
+ g_assert ( template != NULL ); // XXX
+ template -> in_use = in_use;
+ tmpl_schedule_rerun (); // XXX
+}
+
+// public
+void template_rerun_callback ( const gchar * name )
+{
+ g_assert ( name != NULL );
+ template_t * template = g_hash_table_lookup ( tmpl_templates, name );
+ g_assert ( template != NULL && template -> type != shortcut_template ); // XXX
+ template -> run_callback = TRUE;
+ tmpl_schedule_rerun ();
+}
+
+// private
+void templates_init ( void )
+{
+ // the key will be freed by destruction cb
+ tmpl_guards = g_hash_table_new_full ( g_str_hash, g_str_equal, NULL, tmpl_free_guard );
+ tmpl_templates = g_hash_table_new_full ( g_str_hash, g_str_equal, NULL, tmpl_free_template );
+}
+
+// private
+void templates_uninit ( void )
+{
+ if ( tmpl_attached_id != 0 )
+ g_source_remove ( tmpl_attached_id );
+ g_hash_table_destroy ( tmpl_templates );
+ g_hash_table_destroy ( tmpl_guards );
+}
+
+/* vim: se ts=4 sw=4: */
diff -r 00b93fdbb1de mcabber/mcabber/templates.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/templates.h Tue Aug 28 20:37:28 2012 +0300
@@ -0,0 +1,54 @@
+
+#ifndef MCABBER_TEMPLATES_H
+#define MCABBER_TEMPLATES_H 1
+
+/* Copyright 2012 Myhailo Danylenko
+ *
+ * This file is part of mcabber
+ *
+ * mcabber is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <glib.h>
+
+#include <mcabber/parser.h>
+
+// Type for template callback
+typedef void (*tmpl_var_callback_t) ( const gchar * expansion, gpointer udata );
+typedef void (*tmpl_markup_callback_t) ( const markup_link_t * expansion, gpointer udata );
+
+// success template_add ( option name, cb, cb udata )
+// Adds given mcabber option to list of watched templates.
+// If any option, used in that template (or template itself) will change,
+// callback will be called with new expansion of template.
+gboolean var_template_add ( const gchar * name, tmpl_var_callback_t callback, gpointer udata );
+gboolean markup_template_add ( const gchar * name, tmpl_markup_callback_t callback, gpointer udata );
+
+// template_set_in_use ( option name, used flag )
+// Marks template as (un)used.
+// Note: Template will be actually removed only on next evaluation run,
+// though call to this function schedules such run. This way, you can
+// mark a bunch of templates as unused and then mark some of them as used.
+void template_set_in_use ( const gchar * name, gboolean in_use );
+void template_rerun_callback ( const gchar * name );
+
+// XXX do we need this?
+// void tmpl_schedule_rerun ( void );
+
+// private
+void templates_init ( void );
+void templates_uninit ( void );
+
+#endif
+
+/* vim: se ts=4 sw=4: */