angel: some basic structs, code not ready yet (config parser "works")
parent
d153b12ed2
commit
72136b4d03
@ -0,0 +1,27 @@
|
||||
#ifndef _LIGHTTPD_ANGEL_BASE_H_
|
||||
#define _LIGHTTPD_ANGEL_BASE_H_
|
||||
|
||||
#ifdef _LIGHTTPD_BASE_H_
|
||||
#error Do not mix lighty with angel code
|
||||
#endif
|
||||
|
||||
#include <lighttpd/settings.h>
|
||||
|
||||
#include <lighttpd/module.h>
|
||||
|
||||
/* angel_server.h */
|
||||
|
||||
struct server;
|
||||
typedef struct server server;
|
||||
|
||||
struct instance;
|
||||
typedef struct instance instance;
|
||||
|
||||
|
||||
#include <lighttpd/angel_value.h>
|
||||
#include <lighttpd/angel_data.h>
|
||||
#include <lighttpd/angel_connection.h>
|
||||
#include <lighttpd/angel_server.h>
|
||||
#include <lighttpd/angel_plugin.h>
|
||||
|
||||
#endif
|
@ -0,0 +1,14 @@
|
||||
#ifndef _LIGHTTPD_ANGEL_CONIG_PARSER_H_
|
||||
#define _LIGHTTPD_ANGEL_CONIG_PARSER_H_
|
||||
|
||||
/* error handling */
|
||||
#define ANGEL_CONFIG_PARSER_ERROR angel_config_parser_error_quark()
|
||||
LI_API GQuark angel_config_parser_error_quark();
|
||||
|
||||
typedef enum {
|
||||
ANGEL_CONFIG_PARSER_ERROR_PARSE, /* parse error */
|
||||
} AngelConfigParserError;
|
||||
|
||||
LI_API gboolean angel_config_parse_file(const gchar *filename, GError **err);
|
||||
|
||||
#endif
|
@ -0,0 +1,135 @@
|
||||
#ifndef _LIGHTTPD_ANGEL_CONNECTION_H_
|
||||
#define _LIGHTTPD_ANGEL_CONNECTION_H_
|
||||
|
||||
struct angel_connection;
|
||||
typedef struct angel_connection angel_connection;
|
||||
|
||||
struct angel_call;
|
||||
typedef struct angel_call angel_call;
|
||||
|
||||
typedef void (*AngelCallback)(angel_call *acall, gboolean timeout, GString *error, GString *data, GArray *fds);
|
||||
|
||||
|
||||
struct angel_connection {
|
||||
GStaticMutex mutex; /* angel itself has no threads */
|
||||
struct ev_loop *loop;
|
||||
int fd;
|
||||
};
|
||||
|
||||
/* with multi-threading you should protect the structure
|
||||
* containing the angel_call with a lock
|
||||
*/
|
||||
struct angel_call {
|
||||
gpointer context;
|
||||
AngelCallback callback;
|
||||
/* internal data */
|
||||
gint32 id; /* id is -1 if there is no call pending (the callback may still be running) */
|
||||
guint timeout;
|
||||
ev_timer timeout_watcher;
|
||||
ev_io fd_watcher;
|
||||
};
|
||||
|
||||
/* error handling */
|
||||
#define ANGEL_CALL_ERROR angel_call_error_quark()
|
||||
LI_API GQuark angel_call_error_quark();
|
||||
|
||||
typedef enum {
|
||||
ANGEL_CALL_ALREADY_RUNNING /* the angel_call struct is already in use for a call */
|
||||
} AngelCallError;
|
||||
|
||||
/* create connection */
|
||||
LI_API angel_connection* angel_connection_create(int fd);
|
||||
|
||||
|
||||
/* calls */
|
||||
/* the GString* parameters get stolen by the angel call (moved to chunkqueue) */
|
||||
LI_API void angel_call_init(angel_call *call);
|
||||
|
||||
LI_API gboolean angel_send_simple_call(
|
||||
angel_connection *acon,
|
||||
const gchar *module, gsize module_len, const gchar *action, gsize action_len,
|
||||
GString *data,
|
||||
GError **err);
|
||||
|
||||
LI_API gboolean angel_send_call(
|
||||
angel_connection *acon,
|
||||
const gchar *module, gsize module_len, const gchar *action, gsize action_len,
|
||||
angel_call *call, guint timeout,
|
||||
GString *data,
|
||||
GError **err);
|
||||
|
||||
LI_API gboolean angel_send_result(
|
||||
angel_connection *acon,
|
||||
const gchar *module, gsize module_len, const gchar *action, gsize action_len,
|
||||
angel_call *call, guint timeout,
|
||||
GString *error, GString *data, GArray *fds,
|
||||
GError **err);
|
||||
|
||||
LI_API gboolean angel_cancel_call(angel_connection *acon, angel_call *call);
|
||||
|
||||
/* Usage */
|
||||
#if 0
|
||||
void init() {
|
||||
/* ... init ctx... */
|
||||
angel_call_init(&ctx->call);
|
||||
ctx->call.context = ctx;
|
||||
ctx->call.callback = my_callback;
|
||||
}
|
||||
|
||||
gboolean start_call(curctx) {
|
||||
GError *err = NULL;
|
||||
GString *data;
|
||||
lock();
|
||||
ctx = get_ctx();
|
||||
/* another thread may have already called angel, depending on the task (e.g. fastcgi spawn) */
|
||||
if (!angel_call_is_needed(ctx)) {
|
||||
unlock();
|
||||
return TRUE;
|
||||
}
|
||||
data = build_call_data();
|
||||
if (!angel_send_call(acon, CONST_STR_LEN("mymod"), CONST_STR_LEN("myaction"), &ctx->call, 10, data, &err)) {
|
||||
unlock();
|
||||
report_error(&err);
|
||||
return FALSE;
|
||||
}
|
||||
/* add current context (e.g. vrequest) to a wakeup list */
|
||||
push_to_queue(ctx->waitqueue, curctx);
|
||||
|
||||
unlock();
|
||||
return TRUE; /* at this point the callback may be already finished */
|
||||
}
|
||||
|
||||
void my_callback(angel_call *acall, gboolean timeout, GString *error, GString *data, GArray *fds) {
|
||||
lock();
|
||||
|
||||
handle_error();
|
||||
parse_data();
|
||||
/* ... */
|
||||
|
||||
done:
|
||||
wakeup(acall->ctx->waitqueue);
|
||||
if (error) g_string_free(error, TRUE);
|
||||
if (data) g_string_free(error, TRUE);
|
||||
/* destroy fd-array? */
|
||||
|
||||
perhaps_free_ctx(acall->ctx);
|
||||
|
||||
unlock();
|
||||
}
|
||||
|
||||
void stop_call() {
|
||||
lock();
|
||||
ctx = get_ctx();
|
||||
if (!angel_cancel_call(acon, ctx)) {
|
||||
/* callback either is already done or just to be called */
|
||||
/* do _not_ destroy the context */
|
||||
unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
perhaps_free_ctx(ctx);
|
||||
unlock();
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
@ -0,0 +1,58 @@
|
||||
#ifndef _LIGHTTPD_ANGEL_DATA_H_
|
||||
#define _LIGHTTPD_ANGEL_DATA_H_
|
||||
|
||||
/* write/read data from/to a buffer (GString) (binary)
|
||||
* this is not meant to be the most performant way to do this,
|
||||
* as communication with the angel shouldn't happen to often anyway.
|
||||
*
|
||||
* Please never send "user" data to the angel (i.e. do not implement
|
||||
* something like a mod_cgi via sending the request data to the angel;
|
||||
* instead use the angel to spawn a fastcgi backend (or something similar)
|
||||
* and send the request via a socket to the backend directly.
|
||||
*/
|
||||
|
||||
/* angel obviously doesn't work across platforms, so we don't need
|
||||
* to care about endianess
|
||||
*/
|
||||
|
||||
/* The buffer may be bigger of course, but a single string should not
|
||||
* exceed this length: */
|
||||
#define ANGEL_DATA_MAX_STR_LEN 1024 /* must fit into a gint32 */
|
||||
|
||||
/* Needed for reading data */
|
||||
struct angel_buffer;
|
||||
typedef struct angel_buffer angel_buffer;
|
||||
struct angel_buffer {
|
||||
GString *data;
|
||||
gsize pos;
|
||||
};
|
||||
|
||||
/* error handling */
|
||||
#define ANGEL_DATA_ERROR angel_data_error_quark()
|
||||
LI_API GQuark angel_data_error_quark();
|
||||
|
||||
typedef enum {
|
||||
ANGEL_DATA_ERROR_EOF, /* not enough data to read value */
|
||||
ANGEL_DATA_ERROR_INVALID_STRING_LENGTH, /* invalid string length read from buffer (< 0 || > max-str-len) */
|
||||
ANGEL_DATA_ERROR_STRING_TOO_LONG /* string too long (len > max-str-len) */
|
||||
} AngelDataError;
|
||||
|
||||
/* write */
|
||||
LI_API gboolean angel_data_write_int32(GString *buf, gint32 i, GError **err);
|
||||
LI_API gboolean angel_data_write_int64(GString *buf, gint64 i, GError **err);
|
||||
LI_API gboolean angel_data_write_char (GString *buf, gchar c, GError **err);
|
||||
LI_API gboolean angel_data_write_str (GString *buf, const GString *str, GError **err);
|
||||
LI_API gboolean angel_data_write_cstr (GString *buf, const gchar *str, gsize len, GError **err);
|
||||
|
||||
/* read:
|
||||
* - if the val pointer is NULL, the data will be discarded
|
||||
* - reading strings: if *val != NULL *val will be reused;
|
||||
* otherwise a new GString* will be created
|
||||
* - *val will only be modified if no error is returned
|
||||
*/
|
||||
LI_API gboolean angel_data_read_int32(angel_buffer *buf, gint32 *val, GError **err);
|
||||
LI_API gboolean angel_data_read_int64(angel_buffer *buf, gint64 *val, GError **err);
|
||||
LI_API gboolean angel_data_read_char (angel_buffer *buf, gchar *val, GError **err);
|
||||
LI_API gboolean angel_data_read_str (angel_buffer *buf, GString **val, GError **err);
|
||||
|
||||
#endif
|
@ -0,0 +1,11 @@
|
||||
#ifndef _LIGHTTPD_ANGEL_PLUGIN_H_
|
||||
#define _LIGHTTPD_ANGEL_PLUGIN_H_
|
||||
|
||||
#ifndef _LIGHTTPD_ANGEL_BASE_H_
|
||||
#error Please include <lighttpd/angel_base.h> instead of this file
|
||||
#endif
|
||||
|
||||
|
||||
LI_API gboolean plugins_handle_item(server *srv, value *hash);
|
||||
|
||||
#endif
|
@ -0,0 +1,36 @@
|
||||
#ifndef _LIGHTTPD_ANGEL_SERVER_H_
|
||||
#define _LIGHTTPD_ANGEL_SERVER_H_
|
||||
|
||||
#ifndef _LIGHTTPD_ANGEL_BASE_H_
|
||||
#error Please include <lighttpd/angel_base.h> instead of this file
|
||||
#endif
|
||||
|
||||
#ifndef LIGHTTPD_ANGEL_MAGIC
|
||||
#define LIGHTTPD_ANGEL_MAGIC ((guint)0x3e14ac65)
|
||||
#endif
|
||||
|
||||
struct instance {
|
||||
pid_t pid;
|
||||
};
|
||||
|
||||
struct server {
|
||||
guint32 magic; /** server magic version, check against LIGHTTPD_ANGEL_MAGIC in plugins */
|
||||
|
||||
struct ev_loop *loop;
|
||||
ev_signal
|
||||
sig_w_INT,
|
||||
sig_w_TERM,
|
||||
sig_w_PIPE;
|
||||
|
||||
struct modules *modules;
|
||||
|
||||
GHashTable *plugins; /**< const gchar* => (plugin*) */
|
||||
struct plugin *core_plugin;
|
||||
|
||||
ev_tstamp started;
|
||||
};
|
||||
|
||||
LI_API server* server_new(const gchar *module_dir);
|
||||
LI_API void server_free(server* srv);
|
||||
|
||||
#endif
|
@ -0,0 +1,60 @@
|
||||
#ifndef _LIGHTTPD_ANGEL_VALUE_H_
|
||||
#define _LIGHTTPD_ANGEL_VALUE_H_
|
||||
|
||||
#ifndef _LIGHTTPD_ANGEL_BASE_H_
|
||||
#error Please include <lighttpd/angel_base.h> instead of this file
|
||||
#endif
|
||||
|
||||
struct value;
|
||||
typedef struct value value;
|
||||
|
||||
struct value_range;
|
||||
typedef struct value_range value_range;
|
||||
|
||||
typedef enum {
|
||||
VALUE_NONE,
|
||||
/* primitive types */
|
||||
VALUE_BOOLEAN,
|
||||
VALUE_NUMBER,
|
||||
VALUE_STRING,
|
||||
VALUE_RANGE,
|
||||
|
||||
/* container */
|
||||
VALUE_LIST,
|
||||
VALUE_HASH
|
||||
} value_type;
|
||||
|
||||
struct value_range {
|
||||
guint64 from, to;
|
||||
};
|
||||
|
||||
struct value {
|
||||
value_type type;
|
||||
union {
|
||||
gboolean boolean;
|
||||
gint64 number;
|
||||
GString *string;
|
||||
value_range range;
|
||||
/* array of (value*) */
|
||||
GPtrArray *list;
|
||||
/* hash GString => value */
|
||||
GHashTable *hash;
|
||||
} data;
|
||||
};
|
||||
|
||||
LI_API value* value_new_none();
|
||||
LI_API value* value_new_bool(gboolean val);
|
||||
LI_API value* value_new_number(gint64 val);
|
||||
LI_API value* value_new_string(GString *val);
|
||||
LI_API value* value_new_range(value_range val);
|
||||
LI_API value* value_new_list();
|
||||
LI_API value* value_new_hash();
|
||||
|
||||
LI_API value* value_copy(value* val);
|
||||
LI_API void value_free(value* val);
|
||||
|
||||
LI_API const char* value_type_string(value_type type);
|
||||
|
||||
LI_API GString *value_to_string(value *val);
|
||||
|
||||
#endif
|
@ -0,0 +1,428 @@
|
||||
#include <lighttpd/angel_base.h>
|
||||
#include <lighttpd/angel_config_parser.h>
|
||||
|
||||
typedef struct {
|
||||
GArray *g_stack;
|
||||
int *stack;
|
||||
int cs, top;
|
||||
|
||||
GString *token;
|
||||
gboolean readingtoken;
|
||||
char hexchar;
|
||||
gint64 number, number2;
|
||||
gint sign;
|
||||
|
||||
/* item */
|
||||
GString *itemname;
|
||||
value *itemvalue;
|
||||
|
||||
GPtrArray *valuestack;
|
||||
value *curvalue;
|
||||
|
||||
gchar buf[8];
|
||||
} pcontext;
|
||||
|
||||
typedef struct {
|
||||
const gchar *filename;
|
||||
int line, column;
|
||||
} filecontext;
|
||||
|
||||
GQuark angel_config_parser_error_quark() {
|
||||
return g_quark_from_static_string("angel-config-parser-error-quark");
|
||||
}
|
||||
|
||||
static gchar *format_char(pcontext *ctx, gchar c) {
|
||||
if (g_ascii_isprint(c)) {
|
||||
ctx->buf[0] = c;
|
||||
ctx->buf[1] = '\0';
|
||||
} else switch (c) {
|
||||
case '\n': ctx->buf[0] = '\\'; ctx->buf[1] = 'n'; ctx->buf[2] = '\0'; break;
|
||||
case '\r': ctx->buf[0] = '\\'; ctx->buf[1] = 'r'; ctx->buf[2] = '\0'; break;
|
||||
case '\t': ctx->buf[0] = '\\'; ctx->buf[1] = 't'; ctx->buf[2] = '\0'; break;
|
||||
default: g_snprintf(ctx->buf, 8, "\\x%02X", (unsigned int) (unsigned char) c); break;
|
||||
}
|
||||
return ctx->buf;
|
||||
}
|
||||
|
||||
#define PARSE_ERROR_FMT(fmt, ...) do { \
|
||||
g_set_error(err, \
|
||||
ANGEL_CONFIG_PARSER_ERROR, \
|
||||
ANGEL_CONFIG_PARSER_ERROR_PARSE, \
|
||||
"Parsing failed in '%s:%i,%i': " fmt, \
|
||||
fctx->filename, fctx->line, fctx->column, \
|
||||
__VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
#define PARSE_ERROR(msg) PARSE_ERROR_FMT("%s", msg)
|
||||
|
||||
#define UPDATE_COLUMN() do { \
|
||||
fctx->column += p - linestart; \
|
||||
linestart = p; \
|
||||
} while (0)
|
||||
|
||||
%%{
|
||||
machine angel_config_parser;
|
||||
|
||||
access ctx->;
|
||||
|
||||
prepush { update_stack(ctx); }
|
||||
postpop { update_stack(ctx); }
|
||||
|
||||
action starttoken {
|
||||
g_string_truncate(ctx->token, 0);
|
||||
tokenstart = fpc;
|
||||
ctx->readingtoken = TRUE;
|
||||
}
|
||||
action endtoken {
|
||||
g_string_append_len(ctx->token, tokenstart, fpc - tokenstart);
|
||||
tokenstart = NULL;
|
||||
ctx->readingtoken = FALSE;
|
||||
}
|
||||
|
||||
action newline {
|
||||
fctx->line++;
|
||||
fctx->column = 1;
|
||||
linestart = fpc+1;
|
||||
}
|
||||
|
||||
action startitem {
|
||||
ctx->itemname = ctx->token;
|
||||
ctx->token = g_string_sized_new(0);
|
||||
ctx->itemvalue = value_new_hash();
|
||||
}
|
||||
|
||||
action enditem {
|
||||
GString *tmp = value_to_string(ctx->itemvalue);
|
||||
g_printerr("Item '%s': %s\n", ctx->itemname->str, tmp->str);
|
||||
g_string_free(tmp, TRUE);
|
||||
g_string_free(ctx->itemname, TRUE);
|
||||
ctx->itemname = NULL;
|
||||
value_free(ctx->itemvalue);
|
||||
ctx->itemvalue = NULL;
|
||||
}
|
||||
|
||||
action error_unknown {
|
||||
UPDATE_COLUMN();
|
||||
PARSE_ERROR("internal parse error");
|
||||
ctx->cs = angel_config_parser_error; fbreak;
|
||||
}
|
||||
|
||||
action error_unexpected_char {
|
||||
UPDATE_COLUMN();
|
||||
PARSE_ERROR_FMT("unexpected character '%s'", format_char(ctx, fc));
|
||||
ctx->cs = angel_config_parser_error; fbreak;
|
||||
}
|
||||
|
||||
action error_expected_block {
|
||||
UPDATE_COLUMN();
|
||||
PARSE_ERROR_FMT("unexpected character '%s', expected block ('{...}' or ';') for item '%s'", format_char(ctx, fc), ctx->itemname->str);
|
||||
ctx->cs = angel_config_parser_error; fbreak;
|
||||
}
|
||||
|
||||
action error_expected_xdigit {
|
||||
UPDATE_COLUMN();
|
||||
PARSE_ERROR_FMT("unexpected character '%s', expected hexdigit", format_char(ctx, fc));
|
||||
ctx->cs = angel_config_parser_error; fbreak;
|
||||
}
|
||||
|
||||
action error_forbidden_character {
|
||||
UPDATE_COLUMN();
|
||||
PARSE_ERROR_FMT("forbidden character '%s' in string", format_char(ctx, fc));
|
||||
ctx->cs = angel_config_parser_error; fbreak;
|
||||
}
|
||||
|
||||
action error_expected_semicolon {
|
||||
UPDATE_COLUMN();
|
||||
PARSE_ERROR_FMT("unexpected character '%s', expected ';'", format_char(ctx, fc));
|
||||
ctx->cs = angel_config_parser_error; fbreak;
|
||||
}
|
||||
|
||||
action error_expected_colon {
|
||||
UPDATE_COLUMN();
|
||||
PARSE_ERROR_FMT("unexpected character '%s', expected ':'", format_char(ctx, fc));
|
||||
ctx->cs = angel_config_parser_error; fbreak;
|
||||
}
|
||||
|
||||
action error_expected_string {
|
||||
UPDATE_COLUMN();
|
||||
PARSE_ERROR_FMT("unexpected character '%s', expected a string (\"...\")", format_char(ctx, fc));
|
||||
ctx->cs = angel_config_parser_error; fbreak;
|
||||
}
|
||||
|
||||
action error_expected_value {
|
||||
UPDATE_COLUMN();
|
||||
PARSE_ERROR_FMT("unexpected character '%s', expected a value", format_char(ctx, fc));
|
||||
ctx->cs = angel_config_parser_error; fbreak;
|
||||
}
|
||||
|
||||
action error_expected_hash_end {
|
||||
UPDATE_COLUMN();
|
||||
PARSE_ERROR_FMT("unexpected character '%s', expected ']'", format_char(ctx, fc));
|
||||
ctx->cs = angel_config_parser_error; fbreak;
|
||||
}
|
||||
|
||||
|
||||
|
||||
action string_start {
|
||||
g_string_truncate(ctx->token, 0);
|
||||
}
|
||||
|
||||
action string_append {
|
||||
g_string_append_len(ctx->token, fpc, 1);
|
||||
}
|
||||
|
||||
|
||||
action value_true {
|
||||
ctx->curvalue = value_new_bool(TRUE);
|
||||
}
|
||||
|
||||
action value_false {
|
||||
ctx->curvalue = value_new_bool(FALSE);
|
||||
}
|
||||
|
||||
action value_number {
|
||||
ctx->curvalue = value_new_number(ctx->number);
|
||||
}
|
||||
|
||||
action value_range {
|
||||
value_range vr = { ctx->number2, ctx->number };
|
||||
ctx->curvalue = value_new_range(vr);
|
||||
}
|
||||
|
||||
action value_string {
|
||||
ctx->curvalue = value_new_string(ctx->token);
|
||||
ctx->token = g_string_sized_new(0);
|
||||
}
|
||||
|
||||
action value_list_start {
|
||||
g_ptr_array_add(ctx->valuestack, value_new_list());
|
||||
ctx->curvalue = NULL;
|
||||
fcall value_list_sub;
|
||||
}
|
||||
action value_list_push {
|
||||
value *vlist = g_ptr_array_index(ctx->valuestack, ctx->valuestack->len-1);
|
||||
g_ptr_array_add(vlist->data.list, ctx->curvalue);
|
||||
ctx->curvalue = NULL;
|
||||
}
|
||||
action value_list_end {
|
||||
guint ndx = ctx->valuestack->len - 1;
|
||||
ctx->curvalue = g_ptr_array_index(ctx->valuestack, ndx);
|
||||
g_ptr_array_set_size(ctx->valuestack, ndx);
|
||||
fret;
|
||||
}
|
||||
|
||||
action value_hash_start {
|
||||
g_ptr_array_add(ctx->valuestack, value_new_hash());
|
||||
ctx->curvalue = NULL;
|
||||
fcall value_hash_sub;
|
||||
}
|
||||
action value_hash_push_name {
|
||||
g_ptr_array_add(ctx->valuestack, ctx->curvalue);
|
||||
ctx->curvalue = NULL;
|
||||
}
|
||||
action value_hash_push_value {
|
||||
guint ndx = ctx->valuestack->len-1;
|
||||
value *vname = g_ptr_array_index(ctx->valuestack, ndx);
|
||||
value *vhash = g_ptr_array_index(ctx->valuestack, ndx-1);
|
||||
g_ptr_array_set_size(ctx->valuestack, ndx);
|
||||
g_hash_table_insert(vhash->data.hash, vname->data.string, ctx->curvalue);
|
||||
ctx->curvalue = NULL;
|
||||
vname->type = VALUE_NONE;
|
||||
value_free(vname);
|
||||
}
|
||||
action value_hash_end {
|
||||
guint ndx = ctx->valuestack->len - 1;
|
||||
ctx->curvalue = g_ptr_array_index(ctx->valuestack, ndx);
|
||||
g_ptr_array_set_size(ctx->valuestack, ndx);
|
||||
fret;
|
||||
}
|
||||
|
||||
action option_start {
|
||||
g_ptr_array_add(ctx->valuestack, value_new_string(ctx->token));
|
||||
ctx->token = g_string_sized_new(0);
|
||||
ctx->curvalue= NULL;
|
||||
}
|
||||
action option_push {
|
||||
guint ndx = ctx->valuestack->len-1;
|
||||
value *vname = g_ptr_array_index(ctx->valuestack, ndx);
|
||||
g_ptr_array_set_size(ctx->valuestack, ndx);
|
||||
if (!ctx->curvalue) ctx->curvalue = value_new_none();
|
||||
g_hash_table_insert(ctx->itemvalue->data.hash, vname->data.string, ctx->curvalue);
|
||||
vname->type = VALUE_NONE;
|
||||
value_free(vname);
|
||||
}
|
||||
|
||||
line_sane = ( '\n' ) @newline;
|
||||
line_weird = ( '\r' ) @newline;
|
||||
line_insane = ( '\r\n' ) @{ fctx->line--; };
|
||||
line = ( line_sane | line_weird | line_insane );
|
||||
# line = ( '\n' | '\r' '\n'? <: '' ) %newline;
|
||||
|
||||
ws = [\t\v\f ];
|
||||
comment = '#' (any - line)* line;
|
||||
noise = ( ws | line | comment )+;
|
||||
# FIXME for viewing the statemachine
|
||||
# noise = ( ws | [\r\n] )+;
|
||||
|
||||
id = (alpha (alnum | '.' | '-' | '_')** @err(error_unexpected_char) ) >starttoken %endtoken;
|
||||
|
||||
special_chars = '\\' (
|
||||
'n' @{ g_string_append(ctx->token, "\n"); }
|
||||
| 't' @{ g_string_append(ctx->token, "\t"); }
|
||||
| 'r' @{ g_string_append(ctx->token, "\r"); }
|
||||
| 'x'
|
||||
(xdigit @{ ctx->hexchar = 16*g_ascii_xdigit_value(fc); }
|
||||
xdigit @{ ctx->hexchar += g_ascii_xdigit_value(fc);
|
||||
g_string_append_len(ctx->token, &ctx->hexchar, 1);
|
||||
} ) @err(error_expected_xdigit)
|
||||
| (any - [ntrx] - cntrl) @string_append
|
||||
) ;
|
||||
string = ('"' @string_start ( (any - ["\\] - cntrl)@string_append | special_chars )* '"' ) <>err(error_forbidden_character);
|
||||
# FIXME for viewing the statemachine
|
||||
# string = '"' @string_start '"';
|
||||
|
||||
action number_digit {
|
||||
ctx->number = ctx->number*10 + ctx->sign * (fc - '0');
|
||||
}
|
||||
number = (('-'@{ctx->sign = -1;})? (digit digit**) $number_digit) >{ ctx->number = 0; ctx->sign = 1; };
|
||||
|
||||
value_bool = ('true'i | 'enabled'i) %value_true | ('false'i | 'disabled'i) %value_false;
|
||||
value_number = number noise** ('-'@{ ctx->number2 = ctx->number; } noise* number %value_range | '' %value_number);
|
||||
value_string = string @value_string;
|
||||
value_list = '(' @value_list_start;
|
||||
value_hash = '[' @value_hash_start;
|
||||
value = (value_bool | value_number | value_string | value_list | value_hash) >err(error_expected_value);
|
||||
# FIXME for viewing the statemachine
|
||||
# value = "v";
|
||||
|
||||
value_list_sub_item = value %value_list_push;
|
||||
value_list_sub := ((noise | value_list_sub_item noise* ',')* value_list_sub_item? (noise*) ')' @value_list_end) $err(error_unexpected_char);
|
||||
|
||||
value_hash_sub_item = (value_string %value_hash_push_name) $err(error_expected_string) (noise*) $err(error_expected_colon) ':' noise* value %value_hash_push_value;
|
||||
value_hash_sub := (noise | value_hash_sub_item noise* ',')* value_hash_sub_item? (noise*) $err(error_expected_hash_end) ']' @value_hash_end;
|
||||
|
||||
option = id %option_start <: noise* value? (noise*) $err(error_expected_semicolon) ';' @option_push ;
|
||||
optionlist = (noise | option)*;
|
||||
item = (id %startitem) noise* ( ('{' optionlist '}') | ';' ) >err(error_expected_block) @enditem ;
|
||||
|
||||
main := ((noise | item) >err(error_unexpected_char) )*;
|
||||
}%%
|
||||
|
||||
%% write data;
|
||||
|
||||
static void update_stack(pcontext *ctx) {
|
||||
g_array_set_size(ctx->g_stack, ctx->top + 1);
|
||||
ctx->stack = (int*) ctx->g_stack->data;
|
||||
}
|
||||
|
||||
static int angel_config_parser_has_error(pcontext *ctx) {
|
||||
return ctx->cs == angel_config_parser_error;
|
||||
}
|
||||
|
||||
static int angel_config_parser_is_finished(pcontext *ctx) {
|
||||
return ctx->cs >= angel_config_parser_first_final;
|
||||
}
|
||||
|
||||
static pcontext* angel_config_parser_new() {
|
||||
pcontext *ctx = g_slice_new0(pcontext);
|
||||
ctx->g_stack = g_array_sized_new(FALSE, FALSE, sizeof(int), 8);
|
||||
ctx->top = 0;
|
||||
g_array_set_size(ctx->g_stack, ctx->top + 1);
|
||||
ctx->stack = (int*) ctx->g_stack->data;
|
||||
ctx->token = g_string_sized_new(0);
|
||||
ctx->valuestack = g_ptr_array_new();
|
||||
|
||||
%% write init;
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
static void angel_config_parser_free(pcontext *ctx) {
|
||||
if (!ctx) return;
|
||||
|
||||
g_array_free(ctx->g_stack, TRUE);
|
||||
g_string_free(ctx->token, TRUE);
|
||||
for (guint i = 0; i < ctx->valuestack->len; i++) {
|
||||
value_free(g_ptr_array_index(ctx->valuestack, i));
|
||||
}
|
||||
g_ptr_array_free(ctx->valuestack, TRUE);
|
||||
if (ctx->itemname) g_string_free(ctx->itemname, TRUE);
|
||||
value_free(ctx->itemvalue);
|
||||
value_free(ctx->curvalue);
|
||||
g_slice_free(pcontext, ctx);
|
||||
}
|
||||
|
||||
static gboolean angel_config_parser_finalize(pcontext *ctx, filecontext *fctx, GError **err) {
|
||||
/*
|
||||
gchar c;
|
||||
gchar *p = &c, *pe = p, *eof = p, *linestart = p, *tokenstart = NULL;
|
||||
if (ctx->readingtoken) tokenstart = p;
|
||||
|
||||
%% write exec;
|
||||
|
||||
if (ctx->readingtoken) g_string_append_len(ctx->token, tokenstart, p - tokenstart);
|
||||
|
||||
UPDATE_COLUMN();
|
||||
|
||||
if (err && *err) return FALSE;
|
||||
|
||||
if (angel_config_parser_has_error(ctx)) {
|
||||
PARSE_ERROR("unknown parser error");
|
||||
return FALSE;
|
||||
}
|
||||
*/
|
||||
|
||||
if (!angel_config_parser_is_finished(ctx)) {
|
||||
PARSE_ERROR("unexpected end of file");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean angel_config_parse_data(pcontext *ctx, filecontext *fctx, gchar *data, gsize len, GError **err) {
|
||||
gchar *p = data, *pe = p+len, *eof = NULL, *linestart = p, *tokenstart = NULL;
|
||||
if (ctx->readingtoken) tokenstart = p;
|
||||
|
||||
%% write exec;
|
||||
|
||||
if (ctx->readingtoken) g_string_append_len(ctx->token, tokenstart, p - tokenstart);
|
||||
|
||||
UPDATE_COLUMN();
|
||||
fctx->column--;
|
||||
|
||||
if (err && *err) return FALSE;
|
||||
|
||||
if (angel_config_parser_has_error(ctx)) {
|
||||
PARSE_ERROR("unknown parser error");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean angel_config_parse_file(const gchar *filename, GError **err) {
|
||||
char *data = NULL;
|
||||
gsize len = 0;
|
||||
filecontext sfctx, *fctx = &sfctx;
|
||||
pcontext *ctx = angel_config_parser_new();
|
||||
|
||||
if (err && *err) goto error;
|
||||
|
||||
if (!g_file_get_contents(filename, &data, &len, err)) goto error;
|
||||
|
||||
sfctx.filename = filename;
|
||||
sfctx.line = 1;
|
||||
sfctx.column = 1;
|
||||
if (!angel_config_parse_data(ctx, fctx, data, len, err)) goto error;
|
||||
if (!angel_config_parser_finalize(ctx, fctx, err)) goto error;
|
||||
|
||||
if (data) g_free(data);
|
||||
angel_config_parser_free(ctx);
|
||||
return TRUE;
|
||||
|
||||
error:
|
||||
if (data) g_free(data);
|
||||
angel_config_parser_free(ctx);
|
||||
return FALSE;
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
|
||||
#include <lighttpd/base.h>
|
||||
#include <lighttpd/angel_data.h>
|
||||
|
||||
/* error handling */
|
||||
GQuark angel_data_error_quark() {
|
||||
return g_quark_from_static_string("angel-data-error-quark");
|
||||
}
|
||||
|
||||
static gboolean error_eof(GError **err, const gchar *info) {
|
||||
g_set_error(err,
|
||||
ANGEL_DATA_ERROR,
|
||||
ANGEL_DATA_ERROR_EOF,
|
||||
"Not enough data to read value '%s'", info);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* write */
|
||||
|
||||
gboolean angel_data_write_int32(GString *buf, gint32 i, GError **err) {
|
||||
g_return_val_if_fail(err == NULL || *err == NULL, FALSE);
|
||||
g_string_append_len(buf, (const gchar*) &i, sizeof(i));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean angel_data_write_int64(GString *buf, gint64 i, GError **err) {
|
||||
g_return_val_if_fail(err == NULL || *err == NULL, FALSE);
|
||||
g_string_append_len(buf, (const gchar*) &i, sizeof(i));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean angel_data_write_char (GString *buf, gchar c, GError **err) {
|
||||
g_return_val_if_fail(err == NULL || *err == NULL, FALSE);
|
||||
g_string_append_len(buf, &c, sizeof(c));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean angel_data_write_str (GString *buf, const GString *str, GError **err) {
|
||||
g_return_val_if_fail(err == NULL || *err == NULL, FALSE);
|
||||
if (str->len > ANGEL_DATA_MAX_STR_LEN) {
|
||||
g_set_error(err,
|
||||
ANGEL_DATA_ERROR,
|
||||
ANGEL_DATA_ERROR_STRING_TOO_LONG,
|
||||
"String too long (len: %" G_GSIZE_FORMAT "): '%s'", str->len, str->str);
|
||||
return FALSE;
|
||||
}
|
||||
if (!angel_data_write_int32(buf, str->len, err)) return FALSE;
|
||||
g_string_append_len(buf, GSTR_LEN(str));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean angel_data_write_cstr (GString *buf, const gchar *str, gsize len, GError **err) {
|
||||
const GString tmps = { (gchar*) str, len, 0 }; /* fake const GString */
|
||||
return angel_data_write_str(buf, &tmps, err);
|
||||
}
|
||||
|
||||
/* read */
|
||||
|
||||
gboolean angel_data_read_int32(angel_buffer *buf, gint32 *val, GError **err) {
|
||||
g_return_val_if_fail(err == NULL || *err == NULL, FALSE);
|
||||
if (buf->data->len - buf->pos < sizeof(gint32)) {
|
||||
return error_eof(err, "int32");
|
||||
}
|
||||
if (val) {
|
||||
memcpy(val, buf->data->str + buf->pos, sizeof(gint32));
|
||||
}
|
||||
buf->pos += sizeof(gint32);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean angel_data_read_int64(angel_buffer *buf, gint64 *val, GError **err) {
|
||||
g_return_val_if_fail(err == NULL || *err == NULL, FALSE);
|
||||
if (buf->data->len - buf->pos < sizeof(gint64)) {
|
||||
return error_eof(err, "int64");
|
||||
}
|
||||
if (val) {
|
||||
memcpy(val, buf->data->str + buf->pos, sizeof(gint64));
|
||||
}
|
||||
buf->pos += sizeof(gint64);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean angel_data_read_char (angel_buffer *buf, gchar *val, GError **err) {
|
||||
g_return_val_if_fail(err == NULL || *err == NULL, FALSE);
|
||||
if (buf->data->len - buf->pos < sizeof(gchar)) {
|
||||
return error_eof(err, "char");
|
||||
}
|
||||
if (val) {
|
||||
*val = buf->data->str[buf->pos];
|
||||
}
|
||||
buf->pos += sizeof(gchar);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean angel_data_read_str (angel_buffer *buf, GString **val, GError **err) {
|
||||
gint32 ilen;
|
||||
gsize len;
|
||||
GString *s;
|
||||
g_return_val_if_fail(err == NULL || *err == NULL, FALSE);
|
||||
|
||||
if (buf->data->len - buf->pos < sizeof(gint32)) {
|
||||
return error_eof(err, "string-length");
|
||||
}
|
||||
memcpy(&ilen, buf->data->str + buf->pos, sizeof(len));
|
||||
buf->pos += sizeof(gint32);
|
||||
if (ilen < 0 || ilen > ANGEL_DATA_MAX_STR_LEN) {
|
||||
g_set_error(err,
|
||||
ANGEL_DATA_ERROR,
|
||||
ANGEL_DATA_ERROR_INVALID_STRING_LENGTH,
|
||||
"String length in buffer invalid: %i", (gint) ilen);
|
||||
return FALSE;
|
||||
}
|
||||
len = (gsize) ilen;
|
||||
if (buf->data->len - buf->pos < len) {
|
||||
return error_eof(err, "string-data");
|
||||
}
|
||||
s = *val;
|
||||
if (!s) {
|
||||
*val = s = g_string_sized_new(len);
|
||||
} else {
|
||||
g_string_truncate(s, 0);
|
||||
}
|
||||
g_string_append_len(s, buf->data->str + buf->pos, len);
|
||||
buf->pos += len;
|
||||
return TRUE;
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
|
||||
#include <lighttpd/angel_base.h>
|
||||
#include <lighttpd/angel_config_parser.h>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
GError *error = NULL;
|
||||
GOptionContext *context;
|
||||
|
||||
/* options */
|
||||
gboolean show_version = FALSE, no_fork = FALSE;
|
||||
gchar const *const def_module_dir = "/usr/local/lib"; /* TODO: configure module-dir with make-system */
|
||||
gchar const *module_dir = def_module_dir;
|
||||
gchar const *config_path = NULL, *pidfile = NULL;
|
||||
|
||||
gboolean res;
|
||||
int result = 0;
|
||||
|
||||
GOptionEntry entries[] = {
|
||||
{ "config", 'c', 0, G_OPTION_ARG_FILENAME, &config_path, "filename/path of the config", "PATH" },
|
||||
{ "module-dir", 'm', 0, G_OPTION_ARG_STRING, &module_dir, "module directory", "PATH" },
|
||||
{ "no-daemon", 'n', 0, G_OPTION_ARG_NONE, &no_fork, "Don't fork (for daemontools)", NULL },
|
||||
{ "pid-file", 0, 0, G_OPTION_ARG_STRING, &pidfile, "Location of the pid file (only valid in daemon mode)", "PATH" },
|
||||
{ "version", 'v', 0, G_OPTION_ARG_NONE, &show_version, "show version and exit", NULL },
|
||||
{ NULL, 0, 0, 0, NULL, NULL, NULL }
|
||||
};
|
||||
|
||||
/* parse commandline options */
|
||||
context = g_option_context_new("- fast and lightweight webserver");
|
||||
g_option_context_add_main_entries(context, entries, NULL);
|
||||
|
||||
res = g_option_context_parse(context, &argc, &argv, &error);
|
||||
|
||||
g_option_context_free(context);
|
||||
|
||||
if (!res) {
|
||||
g_printerr("failed to parse command line arguments: %s\n", error->message);
|
||||
g_error_free(error);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (show_version) {
|
||||
g_print("%s %s - a fast and lightweight webserver\n", PACKAGE_NAME "-angel", PACKAGE_VERSION);
|
||||
g_print("Build date: %s\n", PACKAGE_BUILD_DATE);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!angel_config_parse_file(config_path, &error)) {
|
||||
g_printerr("failed to parse config file: %s\n", error->message);
|
||||
g_error_free(error);
|
||||
result = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
g_printerr("Parsed config file\n");
|
||||
|
||||
cleanup:
|
||||
if (config_path) g_free((gchar*) config_path);
|
||||
if (module_dir != def_module_dir) g_free((gchar*) module_dir);
|
||||
|
||||
return 0;
|
||||
}
|
@ -0,0 +1,210 @@
|
||||
#include <lighttpd/angel_base.h>
|
||||
|
||||
value* value_new_none() {
|
||||
value *v = g_slice_new0(value);
|
||||
v->type = VALUE_NONE;
|
||||
return v;
|
||||
}
|
||||
|
||||
value* value_new_bool(gboolean val) {
|
||||
value *v = g_slice_new0(value);
|
||||
v->data.boolean = val;
|
||||
v->type = VALUE_BOOLEAN;
|
||||
return v;
|
||||
}
|
||||
|
||||
value* value_new_number(gint64 val) {
|
||||
value *v = g_slice_new0(value);
|
||||
v->data.number = val;
|
||||
v->type = VALUE_NUMBER;
|
||||
return v;
|
||||
}
|
||||
|
||||
value* value_new_string(GString *val) {
|
||||
value *v = g_slice_new0(value);
|
||||
v->data.string = val;
|
||||
v->type = VALUE_STRING;
|
||||
return v;
|
||||
}
|
||||
|
||||
value* value_new_range(value_range val) {
|
||||
value *v = g_slice_new0(value);
|
||||
v->data.range = val;
|
||||
v->type = VALUE_RANGE;
|
||||
return v;
|
||||
}
|
||||
|
||||
value* value_new_list() {
|
||||
value *v = g_slice_new0(value);
|
||||
v->data.list = g_ptr_array_new();
|
||||
v->type = VALUE_LIST;
|
||||
return v;
|
||||
}
|
||||
|
||||
static void _value_hash_free_key(gpointer data) {
|
||||
g_string_free((GString*) data, TRUE);
|
||||
}
|
||||
|
||||
static void _value_hash_free_value(gpointer data) {
|
||||
value_free((value*) data);
|
||||
}
|
||||
|
||||
value* value_new_hash() {
|
||||
value *v = g_slice_new0(value);
|
||||
v->data.hash = g_hash_table_new_full(
|
||||
(GHashFunc) g_string_hash, (GEqualFunc) g_string_equal,
|
||||
_value_hash_free_key, _value_hash_free_value);
|
||||
v->type = VALUE_HASH;
|
||||
return v;
|
||||
}
|
||||
|
||||
value* value_copy(value* val) {
|
||||
value *n;
|
||||
|
||||
switch (val->type) {
|
||||
case VALUE_NONE: n = value_new_bool(FALSE); n->type = VALUE_NONE; return n; /* hack */
|
||||
case VALUE_BOOLEAN: return value_new_bool(val->data.boolean);
|
||||
case VALUE_NUMBER: return value_new_number(val->data.number);
|
||||
case VALUE_STRING: return value_new_string(g_string_new_len(GSTR_LEN(val->data.string)));
|
||||
case VALUE_RANGE: return value_new_range(val->data.range);
|
||||
/* list: we have to copy every value in the list! */
|
||||
case VALUE_LIST:
|
||||
n = value_new_list();
|
||||
g_ptr_array_set_size(n->data.list, val->data.list->len);
|
||||
for (guint i = 0; i < val->data.list->len; i++) {
|
||||
g_ptr_array_index(n->data.list, i) = value_copy(g_ptr_array_index(val->data.list, i));
|
||||
}
|
||||
return n;
|
||||
/* hash: iterate over hashtable, clone each value */
|
||||
case VALUE_HASH:
|
||||
n = value_new_hash();
|
||||
{
|
||||
GHashTableIter iter;
|
||||
gpointer k, v;
|
||||
g_hash_table_iter_init(&iter, val->data.hash);
|
||||
while (g_hash_table_iter_next(&iter, &k, &v))
|
||||
g_hash_table_insert(n->data.hash, g_string_new_len(GSTR_LEN((GString*)k)), value_copy((value*)v));
|
||||
}
|
||||
return n;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void value_list_free(GPtrArray *vallist) {
|
||||
if (!vallist) return;
|
||||
for (gsize i = 0; i < vallist->len; i++) {
|
||||
value_free(g_ptr_array_index(vallist, i));
|
||||
}
|
||||
g_ptr_array_free(vallist, TRUE);
|
||||
}
|
||||
|
||||
void value_free(value* val) {
|
||||
if (!val) return;
|
||||
|
||||
switch (val->type) {
|
||||
case VALUE_NONE:
|
||||
case VALUE_BOOLEAN:
|
||||
case VALUE_NUMBER:
|
||||
/* Nothing to free */
|
||||
break;
|
||||
case VALUE_STRING:
|
||||
g_string_free(val->data.string, TRUE);
|
||||
break;
|
||||
case VALUE_RANGE:
|
||||
break;
|
||||
case VALUE_LIST:
|
||||
value_list_free(val->data.list);
|
||||
break;
|
||||
case VALUE_HASH:
|
||||
g_hash_table_destroy(val->data.hash);
|
||||
break;
|
||||
}
|
||||
val->type = VALUE_NONE;
|
||||
g_slice_free(value, val);
|
||||
}
|
||||
|
||||
const char* value_type_string(value_type type) {
|
||||
switch(type) {
|
||||
case VALUE_NONE:
|
||||
return "none";
|
||||
case VALUE_BOOLEAN:
|
||||
return "boolean";
|
||||
case VALUE_NUMBER:
|
||||
return "number";
|
||||
case VALUE_STRING:
|
||||
return "string";
|
||||
case VALUE_RANGE:
|
||||
return "range";
|
||||
case VALUE_LIST:
|
||||
return "list";
|
||||
case VALUE_HASH:
|
||||
return "hash";
|
||||
}
|
||||
return "<unknown>";
|
||||
}
|
||||
|
||||
GString *value_to_string(value *val) {
|
||||
GString *str = NULL;
|
||||
|
||||
switch (val->type) {
|
||||
case VALUE_NONE:
|
||||
return NULL;
|
||||
case VALUE_BOOLEAN:
|
||||
str = g_string_new(val->data.boolean ? "true" : "false");
|
||||
break;
|
||||
case VALUE_NUMBER:
|
||||
str = g_string_sized_new(0);
|
||||
g_string_printf(str, "%" G_GINT64_FORMAT, val->data.number);
|
||||
break;
|
||||
case VALUE_STRING:
|
||||
str = g_string_new_len(CONST_STR_LEN("\""));
|
||||
g_string_append_len(str, GSTR_LEN(val->data.string));
|
||||
g_string_append_c(str, '"');
|
||||
break;
|
||||
case VALUE_RANGE:
|
||||
str = g_string_sized_new(0);
|
||||
g_string_printf(str, "%" G_GINT64_FORMAT "-%" G_GINT64_FORMAT, val->data.range.from, val->data.range.to);
|
||||
break;
|
||||
case VALUE_LIST:
|
||||
str = g_string_new_len(CONST_STR_LEN("("));
|
||||
if (val->data.list->len) {
|
||||
GString *tmp = value_to_string(g_ptr_array_index(val->data.list, 0));
|
||||
g_string_append(str, tmp->str);
|
||||
g_string_free(tmp, TRUE);
|
||||
for (guint i = 1; i < val->data.list->len; i++) {
|
||||
tmp = value_to_string(g_ptr_array_index(val->data.list, i));
|
||||
g_string_append_len(str, CONST_STR_LEN(", "));
|
||||
g_string_append(str, tmp->str);
|
||||
g_string_free(tmp, TRUE);
|
||||
}
|
||||
}
|
||||
g_string_append_c(str, ')');
|
||||
break;
|
||||
case VALUE_HASH:
|
||||
{
|
||||
GHashTableIter iter;
|
||||
gpointer k, v;
|
||||
GString *tmp;
|
||||
guint i = 0;
|
||||
|
||||
str = g_string_new_len(CONST_STR_LEN("["));
|
||||
|
||||
g_hash_table_iter_init(&iter, val->data.hash);
|
||||
while (g_hash_table_iter_next(&iter, &k, &v)) {
|
||||
if (i)
|
||||
g_string_append_len(str, CONST_STR_LEN(", "));
|
||||
tmp = value_to_string((value*)v);
|
||||
g_string_append_len(str, GSTR_LEN((GString*)k));
|
||||
g_string_append_len(str, CONST_STR_LEN(": "));
|
||||
g_string_append_len(str, GSTR_LEN(tmp));
|
||||
g_string_free(tmp, TRUE);
|
||||
i++;
|
||||
}
|
||||
|
||||
g_string_append_c(str, ']');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
Loading…
Reference in New Issue