Audacious  $Id:Doxyfile42802007-03-2104:39:00Znenolod$
tuple_formatter.c
Go to the documentation of this file.
00001 /*
00002  * Audacious
00003  * Copyright (c) 2007 William Pitcock
00004  *
00005  * This program is free software; you can redistribute it and/or modify
00006  * it under the terms of the GNU General Public License as published by
00007  * the Free Software Foundation; under version 3 of the License.
00008  *
00009  * This program is distributed in the hope that it will be useful,
00010  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  * GNU General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU General Public License
00015  * along with this program.  If not, see <http://www.gnu.org/licenses>.
00016  *
00017  * The Audacious team does not consider modular code linking to
00018  * Audacious or using our public API to be a derived work.
00019  */
00020 
00021 #include <glib.h>
00022 #include <mowgli.h>
00023 
00024 #include "config.h"
00025 #include "tuple.h"
00026 #include "tuple_formatter.h"
00027 #include "audstrings.h"
00028 
00029 /*
00030  * TUPLE_USE_COMPILER:
00031  *  Undefine this to disable usage of the Tuplez compiler implementation.
00032  *  This may be useful for prototyping new features of the language.
00033  */
00034 #define TUPLE_USE_COMPILER
00035 
00036 /*
00037  * TUPLE_COMPILER_DEBUG:
00038  *  Define this to debug the execution process of the Tuplez compiled
00039  *  bytecode. This may be useful if bugs creep in.
00040  */
00041 #undef TUPLE_COMPILER_DEBUG
00042 
00043 #ifdef TUPLE_USE_COMPILER
00044 # include "tuple_compiler.h"
00045 static GStaticMutex tuplec_mutex = G_STATIC_MUTEX_INIT;
00046 #endif
00047 
00048 #ifdef _DEBUG
00049 # define _TRACE(fmt, ...) g_print("[tuple-fmt] %s(%d) " fmt "\n", __FILE__, __LINE__, __VA_ARGS__);
00050 #else
00051 # define _TRACE(fmt, ...)
00052 #endif
00053 
00054 /*
00055  * the tuple formatter:
00056  *
00057  * this is a data-driven meta-language which eventually hopes to be
00058  * turing complete.
00059  *
00060  * language constructs follow the following basic rules:
00061  *   - begin with ${
00062  *   - end with }
00063  *
00064  * language constructs:
00065  *   - ${field}: prints a field
00066  *   - ${?field:expr}: evaluates expr if field exists
00067  *   - ${=field,"value"}: defines field in the currently iterated
00068  *                        tuple as string value of "value"
00069  *   - ${=field,value}: defines field in the currently iterated
00070  *                      tuple as integer value of "value"
00071  *   - ${==field,field:expr}: evaluates expr if both fields are the same
00072  *   - ${!=field,field:expr}: evaluates expr if both fields are not the same
00073  *   - ${(empty)?field:expr}: evaluates expr if field is empty or does not exist
00074  *   - %{function:args,arg2,...}: runs function and inserts the result.
00075  *
00076  * everything else is treated as raw text.
00077  * additionally, plugins can add additional instructions and functions!
00078  */
00079 
00080 typedef struct {
00081     Tuple *tuple;
00082     GString *str;
00083 } TupleFormatterContext;
00084 
00085 /* processes a construct, e.g. "${?artist:artist is defined}" would
00086    return "artist is defined" if artist is defined. */
00087 gchar *
00088 tuple_formatter_process_construct(Tuple *tuple, const gchar *string)
00089 {
00090     TupleFormatterContext *ctx;
00091     const gchar *iter;
00092     gchar *out;
00093     gint level = 0;
00094 
00095     g_return_val_if_fail(tuple != NULL, NULL);
00096     g_return_val_if_fail(string != NULL, NULL);
00097 
00098     ctx = g_new0(TupleFormatterContext, 1);
00099     ctx->str = g_string_new("");
00100 
00101     _TRACE("parsing <%s>", string);
00102 
00103     /* parsers are ugly */
00104     for (iter = string; *iter != '\0'; iter++)
00105     {
00106         /* if it's raw text, just copy the byte */
00107         if (*iter != '$' && *iter != '%' && *iter != '}' )
00108         {
00109             g_string_append_c(ctx->str, *iter);
00110         }
00111         else if (*iter == '}' && level > 0)
00112         {
00113             level--;
00114         }
00115         else if (g_str_has_prefix(iter, "${") == TRUE)
00116         {
00117             GString *expression = g_string_new("");
00118             GString *argument = g_string_new("");
00119             GString *sel = expression;
00120             gchar *result;
00121             level++;
00122 
00123             for (iter += 2; *iter != '\0'; iter++)
00124             {
00125                 if (*iter == ':')
00126                 {
00127                     if (sel != argument)
00128                     {
00129                         sel = argument;
00130                         continue;
00131                     }
00132                     else
00133                         g_string_append_c(sel, *iter);
00134                     continue;
00135                 }
00136 
00137                 if (g_str_has_prefix(iter, "${") == TRUE || g_str_has_prefix(iter, "%{") == TRUE)
00138                 {
00139                     if (sel == argument)
00140                     {
00141                         g_string_append_c(sel, *iter);
00142                         level++;
00143                     }
00144                 }
00145                 else if (*iter == '}')
00146                 {
00147                     level--;
00148                     if (sel == argument)
00149                     {
00150                         if (level == 0)
00151                             break;
00152                         else
00153                             g_string_append_c(sel, *iter);
00154                     }
00155                     else
00156                         break;
00157                 }
00158                 else
00159                     g_string_append_c(sel, *iter);
00160             }
00161 
00162             if (expression->len == 0)
00163             {
00164                 g_string_free(expression, TRUE);
00165                 g_string_free(argument, TRUE);
00166                 continue;
00167             }
00168 
00169             result = tuple_formatter_process_expr(tuple, expression->str, argument->len ? argument->str : NULL);
00170             if (result != NULL)
00171             {
00172                 g_string_append(ctx->str, result);
00173                 g_free(result);
00174             }
00175 
00176             g_string_free(expression, TRUE);
00177             g_string_free(argument, TRUE);
00178 
00179             if (*iter == '\0')
00180                 break;
00181         }
00182         else if (g_str_has_prefix(iter, "%{") == TRUE)
00183         {
00184             GString *expression = g_string_new("");
00185             GString *argument = g_string_new("");
00186             GString *sel = expression;
00187             gchar *result;
00188             level++;
00189 
00190             for (iter += 2; *iter != '\0'; iter++)
00191             {
00192                 if (*iter == ':')
00193                 {
00194                     if (sel != argument)
00195                     {
00196                         sel = argument;
00197                         continue;
00198                     }
00199                     else
00200                         g_string_append_c(sel, *iter);
00201                     continue;
00202                 }
00203 
00204                 if (g_str_has_prefix(iter, "${") == TRUE || g_str_has_prefix(iter, "%{") == TRUE)
00205                 {
00206                     if (sel == argument)
00207                     {
00208                         g_string_append_c(sel, *iter);
00209                         level++;
00210                     }
00211                 }
00212                 else if (*iter == '}')
00213                 {
00214                     level--;
00215                     if (sel == argument)
00216                     {
00217                         if (level == 0)
00218                             break;
00219                         else
00220                             g_string_append_c(sel, *iter);
00221                     }
00222                     else
00223                         break;
00224                 }
00225                 else
00226                     g_string_append_c(sel, *iter);
00227             }
00228 
00229             if (expression->len == 0)
00230             {
00231                 g_string_free(expression, TRUE);
00232                 g_string_free(argument, TRUE);
00233                 continue;
00234             }
00235 
00236             result = tuple_formatter_process_function(tuple, expression->str, argument->len ? argument->str : NULL);
00237             if (result != NULL)
00238             {
00239                 g_string_append(ctx->str, result);
00240                 g_free(result);
00241             }
00242 
00243             g_string_free(expression, TRUE);
00244             g_string_free(argument, TRUE);
00245 
00246             if (*iter == '\0')
00247                 break;
00248         }
00249     }
00250 
00251     out = g_strdup(ctx->str->str);
00252     g_string_free(ctx->str, TRUE);
00253     g_free(ctx);
00254 
00255     _TRACE("parsed <%s> as <%s>", string, out);
00256 
00257     return out;
00258 }
00259 
00260 static GList *tuple_formatter_expr_list = NULL;
00261 
00262 typedef struct {
00263     const gchar *name;
00264     gboolean (*func)(Tuple *tuple, const gchar *expression);
00265 } TupleFormatterExpression;
00266 
00267 /* processes an expression and optional argument pair. */
00268 gchar *
00269 tuple_formatter_process_expr(Tuple *tuple, const gchar *expression,
00270     const gchar *argument)
00271 {
00272     TupleFormatterExpression *expr = NULL;
00273     GList *iter;
00274 
00275     g_return_val_if_fail(tuple != NULL, NULL);
00276     g_return_val_if_fail(expression != NULL, NULL);
00277 
00278     for (iter = tuple_formatter_expr_list; iter != NULL; iter = iter->next)
00279     {
00280         TupleFormatterExpression *tmp = (TupleFormatterExpression *) iter->data;
00281 
00282         if (g_str_has_prefix(expression, tmp->name) == TRUE)
00283         {
00284             expr = tmp;
00285             expression += strlen(tmp->name);
00286         }
00287     }
00288 
00289     /* ${artist} */
00290     if (expr == NULL && argument == NULL)
00291     {
00292         TupleValueType type = tuple_get_value_type(tuple, -1, expression);
00293 
00294         switch(type)
00295         {
00296         case TUPLE_STRING:
00297              return g_strdup(tuple_get_string(tuple, -1, expression));
00298              break;
00299         case TUPLE_INT:
00300              return g_strdup_printf("%d", tuple_get_int(tuple, -1, expression));
00301              break;
00302         case TUPLE_UNKNOWN:
00303         default:
00304              return NULL;
00305         }
00306     }
00307     else if (expr != NULL)
00308     {
00309         if (expr->func(tuple, expression) == TRUE && argument != NULL)
00310             return tuple_formatter_process_construct(tuple, argument);
00311     }
00312 
00313     return NULL;
00314 }
00315 
00316 static GList *tuple_formatter_func_list = NULL;
00317 
00318 typedef struct {
00319     const gchar *name;
00320     gchar *(*func)(Tuple *tuple, gchar **args);
00321 } TupleFormatterFunction;
00322 
00323 /* processes a function */
00324 gchar *
00325 tuple_formatter_process_function(Tuple *tuple, const gchar *expression,
00326     const gchar *argument)
00327 {
00328     TupleFormatterFunction *expr = NULL;
00329     GList *iter;
00330 
00331     g_return_val_if_fail(tuple != NULL, NULL);
00332     g_return_val_if_fail(expression != NULL, NULL);
00333 
00334     for (iter = tuple_formatter_func_list; iter != NULL; iter = iter->next)
00335     {
00336         TupleFormatterFunction *tmp = (TupleFormatterFunction *) iter->data;
00337 
00338         if (g_str_has_prefix(expression, tmp->name) == TRUE)
00339         {
00340             expr = tmp;
00341             expression += strlen(tmp->name);
00342         }
00343     }
00344 
00345     if (expr != NULL)
00346     {
00347         gchar **args;
00348         gchar *ret;
00349 
00350         if (argument)
00351             args = g_strsplit(argument, ",", 10);
00352         else
00353             args = NULL;
00354 
00355         ret = expr->func(tuple, args);
00356 
00357         if (args)
00358             g_strfreev(args);
00359 
00360         return ret;
00361     }
00362 
00363     return NULL;
00364 }
00365 
00366 /* registers a formatter */
00367 void
00368 tuple_formatter_register_expression(const gchar *keyword,
00369         gboolean (*func)(Tuple *tuple, const gchar *argument))
00370 {
00371     TupleFormatterExpression *expr;
00372 
00373     g_return_if_fail(keyword != NULL);
00374     g_return_if_fail(func != NULL);
00375 
00376     expr = g_new0(TupleFormatterExpression, 1);
00377     expr->name = keyword;
00378     expr->func = func;
00379 
00380     tuple_formatter_expr_list = g_list_append(tuple_formatter_expr_list, expr);
00381 }
00382 
00383 /* registers a function */
00384 void
00385 tuple_formatter_register_function(const gchar *keyword,
00386         gchar *(*func)(Tuple *tuple, gchar **argument))
00387 {
00388     TupleFormatterFunction *expr;
00389 
00390     g_return_if_fail(keyword != NULL);
00391     g_return_if_fail(func != NULL);
00392 
00393     expr = g_new0(TupleFormatterFunction, 1);
00394     expr->name = keyword;
00395     expr->func = func;
00396 
00397     tuple_formatter_func_list = g_list_append(tuple_formatter_func_list, expr);
00398 }
00399 
00400 /* builtin-keyword: ${==arg1,arg2}, returns TRUE if <arg1> and <arg2> match.
00401    <arg1> and <arg2> can also be raw text, which should be enclosed in "double quotes". */
00402 static gboolean
00403 tuple_formatter_expression_match(Tuple *tuple, const gchar *expression)
00404 {
00405     gchar **args = g_strsplit(expression, ",", 2);
00406     gchar *arg1 = NULL, *arg2 = NULL;
00407     gint ret;
00408 
00409     if (args[0][0] == '\"') /* check if arg1 is "raw text" */
00410     {
00411         if ( strlen(args[0]) > 1 )
00412         {
00413             args[0][strlen(args[0]) - 1] = '\0';
00414             arg1 = g_strdup(&(args[0][1]));
00415             args[0][strlen(args[0]) - 1] = '\"';
00416         }
00417         else /* bad formatted arg */
00418             return FALSE;
00419     }
00420     else if (tuple_get_value_type(tuple, -1, args[0]) == TUPLE_UNKNOWN)
00421     {
00422         g_strfreev(args);
00423         return FALSE;
00424     }
00425 
00426     if (args[1][0] == '\"') /* check if arg2 is "raw text" */
00427     {
00428         if ( strlen(args[1]) > 1 )
00429         {
00430             args[1][strlen(args[1]) - 1] = '\0';
00431             arg2 = g_strdup(&(args[1][1]));
00432             args[1][strlen(args[1]) - 1] = '\"';
00433         }
00434         else /* bad formatted arg */
00435             return FALSE;
00436     }
00437     else if (tuple_get_value_type(tuple, -1, args[1]) == TUPLE_UNKNOWN)
00438     {
00439         g_strfreev(args);
00440         return FALSE;
00441     }
00442 
00443     if (!arg1) /* if arg1 is not "raw text", get the tuple value */
00444     {
00445         if (tuple_get_value_type(tuple, -1, args[0]) == TUPLE_STRING)
00446             arg1 = g_strdup(tuple_get_string(tuple, -1, args[0]));
00447         else
00448             arg1 = g_strdup_printf("%d", tuple_get_int(tuple, -1, args[0]));
00449     }
00450 
00451     if (!arg2) /* if arg2 is not "raw text", get the tuple value */
00452     {
00453         if (tuple_get_value_type(tuple, -1, args[1]) == TUPLE_STRING)
00454             arg2 = g_strdup(tuple_get_string(tuple, -1, args[1]));
00455         else
00456             arg2 = g_strdup_printf("%d", tuple_get_int(tuple, -1, args[1]));
00457     }
00458 
00459     ret = g_ascii_strcasecmp(arg1, arg2);
00460     g_free(arg1);
00461     g_free(arg2);
00462     g_strfreev(args);
00463 
00464     return ret ? FALSE : TRUE;
00465 }
00466 
00467 /* builtin-keyword: ${!=arg1,arg2}. returns TRUE if <arg1> and <arg2> don't match.
00468    <arg1> and <arg2> can also be raw text, which should be enclosed in "double quotes". */
00469 static gboolean
00470 tuple_formatter_expression_nonmatch(Tuple *tuple, const gchar *expression)
00471 {
00472     return tuple_formatter_expression_match(tuple, expression) ^ 1;
00473 }
00474 
00475 /* builtin-keyword: ${(empty)?}. returns TRUE if <arg> is empty. */
00476 static gboolean
00477 tuple_formatter_expression_empty(Tuple *tuple, const gchar *expression)
00478 {
00479     gboolean ret = TRUE;
00480     const gchar *iter;
00481     TupleValueType type = tuple_get_value_type(tuple, -1, expression);
00482 
00483     if (type == TUPLE_UNKNOWN)
00484         return TRUE;
00485 
00486     if (type == TUPLE_INT)
00487         return (tuple_get_int(tuple, -1, expression) == 0);
00488 
00489     iter = tuple_get_string(tuple, -1, expression);
00490     if (!iter)
00491         return TRUE;
00492 
00493     while (ret && *iter != '\0')
00494     {
00495         if (*iter == ' ')
00496             iter++;
00497         else
00498             ret = FALSE;
00499     }
00500 
00501     return ret;
00502 }
00503 
00504 /* builtin-keyword: ${?arg}, returns TRUE if <arg> exists. */
00505 static gboolean
00506 tuple_formatter_expression_exists(Tuple *tuple, const gchar *expression)
00507 {
00508     return !tuple_formatter_expression_empty(tuple, expression);
00509 }
00510 
00511 /* builtin function: %{audacious-version} */
00512 static gchar *
00513 tuple_formatter_function_version(Tuple *tuple, gchar **args)
00514 {
00515     return g_strdup(PACKAGE_NAME " " PACKAGE_VERSION);
00516 }
00517 
00518 /*
00519  * Compile a tuplez string and cache the result.
00520  * This caches the result for the last string, so that
00521  * successive calls are sped up.
00522  *
00523  * TODO/1.5: Implement a more efficient use of the compiler.
00524  */
00525 gchar * tuple_formatter_process_string (const Tuple * tuple, const gchar * string)
00526 {
00527     static gboolean initialized = FALSE;
00528     static gchar *last_string = NULL;
00529 #ifdef TUPLE_USE_COMPILER
00530     static TupleEvalContext *last_ctx = NULL;
00531     static TupleEvalNode *last_ev = NULL;
00532     gchar *result = NULL;
00533 #endif
00534 
00535     if (initialized == FALSE)
00536     {
00537         tuple_formatter_register_expression("?", tuple_formatter_expression_exists);
00538         tuple_formatter_register_expression("==", tuple_formatter_expression_match);
00539         tuple_formatter_register_expression("!=", tuple_formatter_expression_nonmatch);
00540         tuple_formatter_register_expression("(empty)?", tuple_formatter_expression_empty);
00541 
00542         tuple_formatter_register_function("audacious-version", tuple_formatter_function_version);
00543         initialized = TRUE;
00544     }
00545 
00546 #ifdef TUPLE_USE_COMPILER
00547     g_static_mutex_lock (& tuplec_mutex);
00548 
00549     if (last_string == NULL ||
00550         (last_string != NULL && strcmp(last_string, string)))
00551     {
00552         g_free(last_string);
00553 
00554         if (last_ctx != NULL)
00555         {
00556             tuple_evalctx_free(last_ctx);
00557             tuple_evalnode_free(last_ev);
00558         }
00559 
00560         last_ctx = tuple_evalctx_new();
00561         last_string = g_strdup(string);
00562         last_ev = tuple_formatter_compile(last_ctx, last_string);
00563         if (last_ctx->iserror) {
00564             g_warning("[TuplezCC]: %s", last_ctx->errmsg);
00565         }
00566         if (!last_ev) {
00567             g_warning("[TuplezCC]: Compilation failed!\n");
00568         }
00569     }
00570 
00571 #ifdef TUPLE_COMPILER_DEBUG
00572     {
00573         gint level = 0;
00574 
00575         tuple_formatter_print(stderr, &level, last_ctx, last_ev);
00576     }
00577 #endif
00578 
00579     tuple_evalctx_reset(last_ctx);
00580 
00581     result = tuple_formatter_eval(last_ctx, last_ev, tuple);
00582     if (last_ctx->iserror) {
00583         g_warning("[TuplezEV]: %s", last_ctx->errmsg);
00584     }
00585 
00586     g_static_mutex_unlock (& tuplec_mutex);
00587 
00588     return result;
00589 #else
00590     return tuple_formatter_process_construct(tuple, string);
00591 #endif
00592 }
00593 
00594 gchar * tuple_formatter_make_title_string (const Tuple * tuple, const gchar *
00595  string)
00596 {
00597     gchar *title = tuple_formatter_process_string(tuple, string);
00598 
00599     if (title == NULL || !title[0])
00600     {
00601         const char *filename = tuple_get_string(tuple, FIELD_FILE_NAME, NULL);
00602 
00603         g_free(title);
00604         title = g_strdup((filename != NULL) ? filename : "");
00605         string_cut_extension(title);
00606     }
00607 
00608     return title;
00609 }