Add a parser for the action command language.
authorDana Jansens <danakj@orodu.net>
Tue, 26 Jul 2011 22:06:42 +0000 (18:06 -0400)
committerDana Jansens <danakj@orodu.net>
Sun, 16 Oct 2011 22:54:05 +0000 (18:54 -0400)
The parser generates list of actions (complete with filters).  The parser isn't
called anywhere yet, and uses the incomplete actions_act_new() function.

TODO:
The filters now are just text string key:value pairs. But they should
  become intelligent objects like actions are.
The actions need to change their option parsing from XML to the new key:value
  pair lists.

Makefile.am
openbox/actions.c
openbox/actions.h
openbox/actions_list.c [new file with mode: 0644]
openbox/actions_list.h [new file with mode: 0644]
openbox/actions_parser.c [new file with mode: 0644]
openbox/actions_parser.h [new file with mode: 0644]

index 3967a727854abf34242f7dbbfb0f8e1c6e86b252..542a2474a48ecb611cb42f8c0f6431519fcd52b2 100644 (file)
@@ -238,6 +238,10 @@ openbox_openbox_SOURCES = \
        openbox/actions/unfocus.c \
        openbox/actions.c \
        openbox/actions.h \
+       openbox/actions_list.c \
+       openbox/actions_list.h \
+       openbox/actions_parser.c \
+       openbox/actions_parser.h \
        openbox/apps_menu.c \
        openbox/apps_menu.h \
        openbox/client.c \
index ee9d55f1bf8f42936c41594d263e0c2fab07d0a2..2288ea6d6bfbf90d0e0f1e872755a5c02aeccf32 100644 (file)
@@ -251,6 +251,33 @@ ObActionsAct* actions_parse(xmlNodePtr node)
     return act;
 }
 
+ObActionsAct* actions_act_new(const gchar *name, GList *keys, GList *values)
+{
+    ObActionsAct *act = NULL;
+
+    act = actions_build_act_from_string(name);
+    if (act) {
+        /* there is more stuff to parse here */
+        if (act->def->canbeinteractive) {
+            if (act->def->setup.i)
+//XXX                act->options = act->def->setup.i(keys, values,
+                act->options = act->def->setup.i(NULL,
+                                                 &act->i_pre,
+                                                 &act->i_input,
+                                                 &act->i_cancel,
+                                                 &act->i_post);
+        }
+        else {
+            if (act->def->setup.n)
+//XXX                act->options = act->def->setup.n(keys, values);
+                act->options = act->def->setup.n(NULL);
+        }
+    }
+    g_free(name);
+
+    return act;
+}
+
 gboolean actions_act_is_interactive(ObActionsAct *act)
 {
     return act->i_input != NULL;
index e03bc577742fd0eb92d03019c53061b1a3ea9393..16a48ca89fbf2da33688ef08454a0bbaf93f9c24 100644 (file)
@@ -88,6 +88,14 @@ ObActionsAct* actions_parse_string(const gchar *name);
 
 gboolean actions_act_is_interactive(ObActionsAct *act);
 
+/*! Create a new ObActionAct structure.
+  @name The name of the action.
+  @keys The names of the options passed to the action.
+  @values The values of the options passed to the action, paired with the
+    keys.  These are ObActionsListValue objects.
+*/
+ObActionsAct* actions_act_new(const gchar *name, GList *keys, GList *values);
+
 void actions_act_ref(ObActionsAct *act);
 void actions_act_unref(ObActionsAct *act);
 
diff --git a/openbox/actions_list.c b/openbox/actions_list.c
new file mode 100644 (file)
index 0000000..20f2b90
--- /dev/null
@@ -0,0 +1,159 @@
+/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
+
+   actions_list.c for the Openbox window manager
+   Copyright (c) 2011        Dana Jansens
+
+   This program 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.
+
+   See the COPYING file for a copy of the GNU General Public License.
+*/
+
+#include "actions_list.h"
+
+#include <glib.h>
+
+struct _ObActionsListValue {
+    gint ref;
+    enum {
+        OB_AL_STRING,
+        OB_AL_INTEGER,
+        OB_AL_ACTIONSLIST
+    } type;
+    union {
+        gchar *string;
+        guint integer;
+        ObActionsList *actions;
+    } v;
+};
+
+void actions_list_ref(ObActionsList *l)
+{
+    ++l->ref;
+}
+
+void actions_list_unref(ObActionsList *l)
+{
+    while (l && --l->ref < 1) {
+        ObActionsList *n = l->next;
+
+        if (l->isfilter) {
+            actions_list_test_destroy(l->u.f.test);
+            actions_list_unref(l->u.f.thendo);
+            actions_list_unref(l->u.f.elsedo);
+        }
+        else {
+            actions_act_unref(l->u.action);
+        }
+        g_slice_free(ObActionsList, l);
+        l = n;
+    }
+}
+
+void actions_list_test_destroy(ObActionsListTest *t)
+{
+    while (t) {
+        ObActionsListTest *n = t->next;
+
+        g_free(t->key);
+        actions_list_value_unref(t->value);
+        g_slice_free(ObActionsListTest, t);
+        t = n;
+    }
+}
+
+ObActionsListValue* actions_list_value_new_string(const gchar *s)
+{
+    return actions_list_value_new_string_steal(g_strdup(s));
+}
+
+ObActionsListValue* actions_list_value_new_string_steal(gchar *s)
+{
+    ObActionsListValue *v = g_slice_new(ObActionsListValue);
+    v->ref = 1;
+    v->type = OB_AL_STRING;
+    v->v.string = s;
+    return v;
+}
+
+ObActionsListValue* actions_list_value_new_int(gint i)
+{
+    ObActionsListValue *v = g_slice_new(ObActionsListValue);
+    v->ref = 1;
+    v->type = OB_AL_INTEGER;
+    v->v.integer = i;
+    return v;
+}
+
+ObActionsListValue* actions_list_value_new_actions_list(ObActionsList *al)
+{
+    ObActionsListValue *v = g_slice_new(ObActionsListValue);
+    v->ref = 1;
+    v->type = OB_AL_ACTIONSLIST;
+    v->v.actions = al;
+    actions_list_ref(al);
+    return v;
+}
+
+void actions_list_value_ref(ObActionsListValue *v)
+{
+    ++v->ref;
+}
+
+void actions_list_value_unref(ObActionsListValue *v)
+{
+    if (v && --v->ref < 1) {
+        switch (v->type) {
+        case OB_AL_STRING:
+            g_free(v->v.string);
+            break;
+        case OB_AL_ACTIONSLIST:
+            actions_list_unref(v->v.actions);
+            break;
+        case OB_AL_INTEGER:
+            break;
+        }
+        g_slice_free(ObActionsListValue, v);
+    }
+}
+
+gboolean actions_list_value_is_string(ObActionsListValue *v)
+{
+    return v->type == OB_AL_STRING;
+}
+
+gboolean actions_list_value_is_int(ObActionsListValue *v)
+{
+    return v->type == OB_AL_INTEGER;
+}
+
+gboolean actions_list_value_is_actions_list(ObActionsListValue *v)
+{
+    return v->type == OB_AL_ACTIONSLIST;
+}
+
+gchar* actions_list_value_string(ObActionsListValue *v)
+{
+    g_return_val_if_fail(v->type == OB_AL_STRING, NULL);
+    return v->v.string;
+}
+
+gint actions_list_value_int(ObActionsListValue *v)
+{
+    g_return_val_if_fail(v->type == OB_AL_INTEGER, 0);
+    return v->v.integer;
+}
+
+ObActionsList* actions_list_value_actions_list(ObActionsListValue *v)
+{
+    g_return_val_if_fail(v->type == OB_AL_ACTIONSLIST, NULL);
+    return v->v.actions;
+}
+
diff --git a/openbox/actions_list.h b/openbox/actions_list.h
new file mode 100644 (file)
index 0000000..f5710e3
--- /dev/null
@@ -0,0 +1,74 @@
+/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
+
+   actions_list.h for the Openbox window manager
+   Copyright (c) 2011        Dana Jansens
+
+   This program 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.
+
+   See the COPYING file for a copy of the GNU General Public License.
+*/
+
+#include "actions.h"
+
+#include <glib.h>
+
+typedef struct _ObActionsList      ObActionsList;
+typedef struct _ObActionsListTest  ObActionsListTest;
+typedef struct _ObActionsListValue ObActionsListValue;
+
+/*! Each node of the Actions list is an action itself (or a filter bound to
+  an action). */
+struct _ObActionsList {
+    gint ref;
+    gboolean isfilter;
+    union {
+        struct _ObActionsListFilter {
+            ObActionsListTest *test; /* can be null */
+            ObActionsList *thendo; /* can be null */
+            ObActionsList *elsedo; /* can be null */
+        } f;
+        ObActionsAct *action;
+    } u;
+    ObActionsList *next;
+};
+
+struct _ObActionsListTest {
+    gchar *key;
+    ObActionsListValue *value; /* can be null */
+    gboolean and;
+    ObActionsListTest *next;
+};
+
+void actions_list_ref(ObActionsList *l);
+void actions_list_unref(ObActionsList *l);
+
+void actions_list_test_destroy(ObActionsListTest *t);
+
+/*! Creates a new value by making a copy of the given string. */
+ObActionsListValue* actions_list_value_new_string(const gchar *s);
+/*! Creates a new value from a string, and steals ownership of the string. It
+  will be freed when then value is destroyed. */
+ObActionsListValue* actions_list_value_new_string_steal(gchar *s);
+/*! Creates a new value that holds an integer. */
+ObActionsListValue* actions_list_value_new_int(gint i);
+/*! Creates a new value with a given actions list. */
+ObActionsListValue* actions_list_value_new_actions_list(ObActionsList *al);
+
+void actions_list_value_ref(ObActionsListValue *v);
+void actions_list_value_unref(ObActionsListValue *v);
+
+gboolean actions_list_value_is_string(ObActionsListValue *v);
+gboolean actions_list_value_is_int(ObActionsListValue *v);
+gboolean actions_list_value_is_actions_list(ObActionsListValue *v);
+
+gchar* actions_list_value_string(ObActionsListValue *v);
+gint actions_list_value_int(ObActionsListValue *v);
+ObActionsList* actions_list_value_actions_list(ObActionsListValue *v);
diff --git a/openbox/actions_parser.c b/openbox/actions_parser.c
new file mode 100644 (file)
index 0000000..2ef8ee8
--- /dev/null
@@ -0,0 +1,436 @@
+/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
+
+   actions_parser.c for the Openbox window manager
+   Copyright (c) 2011        Dana Jansens
+
+   This program 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.
+
+   See the COPYING file for a copy of the GNU General Public License.
+*/
+
+#include "actions_parser.h"
+#include "gettext.h"
+
+#ifdef HAVE_STRING_H
+#  include <string.h>
+#endif
+
+#define SKIP " \t"
+#define IDENTIFIER_FIRST G_CSET_a_2_z G_CSET_A_2_Z
+#define IDENTIFIER_NTH G_CSET_a_2_z G_CSET_A_2_Z \
+    G_CSET_DIGITS G_CSET_LATINS G_CSET_LATINC "_-"
+#define ESCAPE_SEQS "\"()"
+
+ObActionsList* parse_list(ObActionsParser *p, GTokenType end, gboolean *e);
+ObActionsList* parse_action(ObActionsParser *p, gboolean *e);
+ObActionsList* parse_filter(ObActionsParser *p, gboolean *e);
+ObActionsListTest* parse_filter_test(ObActionsParser *p, gboolean *e);
+ObActionsListValue* parse_value(ObActionsParser *p,
+                                gboolean allow_actions,
+                                gboolean *e);
+gchar* parse_string(ObActionsParser *p, guchar end, gboolean *e);
+
+struct _ObActionsParser
+{
+    gint ref;
+    GScanner *scan;
+};
+
+ObActionsParser* actions_parser_new(void)
+{
+    ObActionsParser *p;
+    GScannerConfig config = {
+        .cset_skip_characters = SKIP,
+        .cset_identifier_first =  IDENTIFIER_FIRST,
+        .cset_identifier_nth = IDENTIFIER_NTH,
+        .cpair_comment_single = "#\n",
+        .case_sensitive = FALSE,
+        .skip_comment_multi = TRUE,
+        .skip_comment_single = TRUE,
+        .scan_comment_multi = FALSE,
+        .scan_identifier = TRUE,
+        .scan_identifier_1char = TRUE,
+        .scan_identifier_NULL = FALSE,
+        .scan_symbols = TRUE,
+        .scan_binary = FALSE,
+        .scan_octal = FALSE,
+        .scan_float = TRUE,
+        .scan_hex = FALSE,
+        .scan_hex_dollar = FALSE,
+        .scan_string_sq = FALSE,
+        .scan_string_dq = FALSE,
+        .numbers_2_int = TRUE,
+        .int_2_float = FALSE,
+        .identifier_2_string = FALSE,
+        .char_2_token = TRUE,
+        .symbol_2_token = FALSE,
+        .scope_0_fallback = FALSE,
+        .store_int64 = FALSE
+    };
+
+    p = g_slice_new(ObActionsParser);
+    p->ref = 1;
+    p->scan = g_scanner_new(&config);
+    return p;
+}
+
+void actions_parser_ref(ObActionsParser *p)
+{
+    ++p->ref;
+}
+
+void actions_parser_unref(ObActionsParser *p)
+{
+    if (p && --p->ref < 1) {
+        g_scanner_destroy(p->scan);
+        g_slice_free(ObActionsParser, p);
+    }
+}
+
+ObActionsList* actions_parser_read_string(ObActionsParser *p,
+                                          const gchar *text)
+{
+    gboolean e;
+
+    g_scanner_input_text(p->scan, text, strlen(text));
+    p->scan->input_name = "(console)";
+
+    return parse_list(p, G_TOKEN_EOF, &e);
+}
+
+ObActionsList* actions_parser_read_file(ObActionsParser *p,
+                                        const gchar *file,
+                                        GError **error)
+{
+    GIOChannel *ch;
+    gboolean e;
+
+    ch = g_io_channel_new_file(file, "r", error);
+    if (!ch) return NULL;
+
+    g_scanner_input_file(p->scan, g_io_channel_unix_get_fd(ch));
+    p->scan->input_name = file;
+
+    return parse_list(p, G_TOKEN_EOF, &e);
+}
+
+/*************  Parser functions.  The list is the entry point.  ************
+BNF for the language:
+
+TEST       := KEY=VALUE | KEY
+ACTION     := [FILTER] ACTION ACTIONELSE | ACTIONNAME ACTIONOPTS | {ACTIONLIST}
+ACTIONLIST := ACTION ACTIONLIST | ACTION
+ACTIONELSE := nil | \| ACTION
+FILTER     := FILTERORS
+FILTERORS  := FILTERANDS \| FILTERORS | FILTERANDS
+FILTERANDS := TEST, FILTERANDS | TEST
+ACTIONOPTS := ACTIONOPT ACTIONOPTS | ACTIONOPT
+ACTIONOPT  := ATTRIBUTE:WORD | ATTRIBUTE:STRING | ATTRIBUTE:{ACTIONLIST}
+WORD   := string of text without any spaces
+STRING := "TEXT" | (TEXT) |
+  where TEXT is a string, any occurance of the closing quote character must
+  be escaped by an backslash.
+  \\ \( \) and \" are all valid escaped characters.
+**************                                                   ************/
+
+gpointer parse_error(ObActionsParser *p, GTokenType exp, const gchar *message,
+                     gboolean *e)
+{
+    g_scanner_unexp_token(p->scan, exp, NULL, NULL, NULL, message, TRUE);
+    *e = TRUE;
+    return NULL;
+}
+
+ObActionsList* parse_list(ObActionsParser *p, GTokenType end, gboolean *e)
+{
+    ObActionsList *first, *last;
+    GTokenType t;
+
+    first = last = NULL;
+
+    t = g_scanner_peek_next_token(p->scan);
+    while (t != end && t != G_TOKEN_EOF) {
+        if (t == '\n')
+            g_scanner_get_next_token(p->scan); /* skip empty lines */
+        else {
+            ObActionsList *next;
+
+            /* parse the next action and stick it on the end of the list */
+            next = parse_action(p, e);
+            if (last) last->next = next;
+            if (!first) first = next;
+            last = next;
+
+            if (*e) break; /* don't parse any more after an error */
+        }
+
+        t = g_scanner_peek_next_token(p->scan);
+    }
+
+    /* eat the ending character */
+    g_scanner_get_next_token(p->scan);
+
+    return first;
+}
+
+ObActionsList* parse_action(ObActionsParser *p, gboolean *e)
+{
+    GTokenType t;
+    ObActionsList *al;
+    gchar *name;
+    GList *keys, *values;
+
+    t = g_scanner_get_next_token(p->scan);
+
+    if (t == '[') return parse_filter(p, e);
+    if (t == '{') return parse_list(p, '}', e);
+
+    /* check for a name */
+    if (t != G_TOKEN_IDENTIFIER)
+        return parse_error(p, G_TOKEN_NONE, _("Expected an action name"), e);
+
+    /* read the action's name */
+    name = g_strdup(p->scan->value.v_string);
+    keys = values = NULL;
+
+    /* read the action's options key:value pairs */
+    t = g_scanner_peek_next_token(p->scan);
+    while (t == G_TOKEN_IDENTIFIER) {
+        gchar *key;
+        ObActionsListValue *value;
+
+        g_scanner_get_next_token(p->scan); /* eat the key */
+        t = g_scanner_peek_next_token(p->scan); /* check for ':' */
+        if (t != ':') {
+            parse_error(p, ':', NULL, e);
+            break; /* don't read any more options */
+        }
+
+        /* save the key */
+        key = g_strdup(p->scan->value.v_string);
+        g_scanner_get_next_token(p->scan); /* eat the ':' */
+
+        /* read the value */
+        value = parse_value(p, TRUE, e);
+
+        /* check if we read a value (regardless of errors), and save
+           the key:value pair if we did. */
+        if (value) {
+            keys = g_list_prepend(keys, key);
+            values = g_list_prepend(values, value);
+        }
+        else
+            g_free(key); /* didn't read any value */
+
+        if (*e) break; /* don't parse any more if there was an error */
+    }
+
+    al = g_slice_new(ObActionsList);
+    al->ref = 1;
+    al->isfilter = FALSE;
+    al->u.action = actions_act_new(name, keys, values);
+    return al;
+}
+
+ObActionsList* parse_filter(ObActionsParser *p, gboolean *e)
+{
+    GTokenType t;
+    ObActionsList *al, *thendo, *elsedo;
+    ObActionsListTest *test;
+
+    /* read the filter tests */
+    test = parse_filter_test(p, e);
+    if (*e) {
+        actions_list_test_destroy(test);
+        return NULL;
+    }
+
+    /* read the action for the filter */
+    thendo = parse_action(p, e);
+    elsedo = NULL;
+
+    if (!*e) {
+        /* check for else case */
+        t = g_scanner_peek_next_token(p->scan);
+        if (t == '|') {
+            g_scanner_get_next_token(p->scan); /* eat it */
+
+            /* read the else action for the filter */
+            elsedo = parse_action(p, e);
+        }
+    }
+
+    al = g_slice_new(ObActionsList);
+    al->ref = 1;
+    al->isfilter = TRUE;
+    al->u.f.test = test;
+    al->u.f.thendo = thendo;
+    al->u.f.elsedo = elsedo;
+    return al;
+}
+
+ObActionsListTest* parse_filter_test(ObActionsParser *p, gboolean *e)
+{
+    GTokenType t;
+    gchar *key;
+    ObActionsListValue *value;
+    gboolean and;
+    ObActionsListTest *next;
+
+    t = g_scanner_get_next_token(p->scan);
+    if (t == ']') /* empty */
+        return NULL;
+
+    else if (t != G_TOKEN_IDENTIFIER)
+        return parse_error(p, G_TOKEN_NONE,
+                           _("Expected a filter test lvalue"), e);
+
+    /* got a key */
+    key = g_strdup(p->scan->value.v_string);
+    value = NULL;
+    and = FALSE;
+    next = NULL;
+
+    /* check if it has a value also */
+    t = g_scanner_peek_next_token(p->scan);
+    if (t == '=') {
+        g_scanner_get_next_token(p->scan); /* eat the = */
+        value = parse_value(p, FALSE, e);
+    }
+
+    /* don't allow any errors (but value can be null if not present) */
+    if (*e) {
+        g_free(key);
+        actions_list_value_unref(value);
+        return NULL;
+    }
+
+    /* check if there is another test and how we're connected */
+    t = g_scanner_get_next_token(p->scan);
+    switch (t) {
+    case ',': /* and */
+        and = TRUE;
+        next = parse_filter_test(p, e);
+        break;
+    case '|': /* or */
+        and = FALSE;
+        next = parse_filter_test(p, e);
+        break;
+    case ']': /* end of the filter */
+        break;
+    default:
+        parse_error(p, ']', NULL, e);
+    }
+
+    /* don't allow any errors */
+    if (*e) {
+        g_free(key);
+        actions_list_value_unref(value);
+        actions_list_test_destroy(next);
+        return NULL;
+    }
+    else {
+        ObActionsListTest *test;
+
+        test = g_slice_new(ObActionsListTest);
+        test->key = key;
+        test->value = value;
+        test->and = and;
+        test->next = next;
+        return test;
+    }
+}
+
+ObActionsListValue* parse_value(ObActionsParser *p,
+                                gboolean allow_actions,
+                                gboolean *e)
+{
+    GTokenType t;
+    ObActionsListValue *v;
+
+    v = NULL;
+    t = g_scanner_get_next_token(p->scan);
+    if (t == G_TOKEN_IDENTIFIER)
+        v = actions_list_value_new_string(p->scan->value.v_string);
+    else if (t == G_TOKEN_INT)
+        v = actions_list_value_new_int(p->scan->value.v_int);
+    else if (t == '"')
+        v = actions_list_value_new_string(parse_string(p, '"', e));
+    else if (t == '(')
+        v = actions_list_value_new_string(parse_string(p, ')', e));
+    else if (t == '{' && allow_actions) {
+        ObActionsList *l = parse_list(p, '}', e);
+        if (l) v = actions_list_value_new_actions_list(l);
+    }
+    else
+        parse_error(p, G_TOKEN_NONE, _("Expected an option value"), e);
+    return v;
+}
+
+
+gchar* parse_string(ObActionsParser *p, guchar end, gboolean *e)
+{
+    GString *buf;
+    GTokenType t;
+    const gchar *error_message = NULL;
+
+    /* inside a string we want everything to be parsed as text (identifiers) */
+    p->scan->config->cset_skip_characters = "";
+    p->scan->config->cset_identifier_first = IDENTIFIER_NTH " ";
+    p->scan->config->cset_identifier_nth = IDENTIFIER_NTH " ";
+
+    buf = g_string_new("");
+
+    t = g_scanner_get_next_token(p->scan);
+    while (t != end) {
+        switch (t) {
+        case G_TOKEN_IDENTIFIER:
+            g_string_append(buf, p->scan->value.v_string);
+            break;
+        case G_TOKEN_EOF:
+            error_message = _("Missing end of quoted string");
+            goto parse_string_error;
+        case G_TOKEN_NONE:
+            error_message = _("Unknown token in quoted string");
+            goto parse_string_error;
+        case '\\': /* escape sequence */
+            t = g_scanner_get_next_token(p->scan);
+            if (!strchr(ESCAPE_SEQS, t)) {
+                error_message = _("Unknown escape sequence");
+                goto parse_string_error;
+            }
+            g_string_append_c(buf, t);
+            break;
+        default: /* other single character */
+            g_string_append_c(buf, t);
+        }
+
+        t = g_scanner_get_next_token(p->scan);
+    }
+
+    /* reset to their default values */
+    p->scan->config->cset_skip_characters = SKIP;
+    p->scan->config->cset_identifier_first = IDENTIFIER_FIRST;
+    p->scan->config->cset_identifier_nth = IDENTIFIER_NTH;
+
+    {
+        gchar *v = buf->str;
+        g_string_free(buf, FALSE);
+        return v;
+    }
+
+parse_string_error:
+    g_scanner_unexp_token(p->scan, G_TOKEN_NONE, NULL, NULL, NULL,
+                          error_message, TRUE);
+    g_string_free(buf, TRUE);
+    *e = TRUE;
+    return NULL;
+}
diff --git a/openbox/actions_parser.h b/openbox/actions_parser.h
new file mode 100644 (file)
index 0000000..5d26a29
--- /dev/null
@@ -0,0 +1,34 @@
+/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
+
+   actions_parser.h for the Openbox window manager
+   Copyright (c) 2011        Dana Jansens
+
+   This program 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.
+
+   See the COPYING file for a copy of the GNU General Public License.
+*/
+
+#include "actions_list.h"
+
+#include <glib.h>
+
+typedef struct _ObActionsParser ObActionsParser;
+
+ObActionsParser* actions_parser_new(void);
+
+void actions_parser_ref(ObActionsParser *p);
+void actions_parser_unref(ObActionsParser *p);
+
+ObActionsList* actions_parser_read_string(ObActionsParser *p,
+                                          const gchar *text);
+ObActionsList* actions_parser_read_file(ObActionsParser *p,
+                                        const gchar *file,
+                                        GError **error);