diff options
Diffstat (limited to 'src/lib-config')
-rw-r--r-- | src/lib-config/Makefile.am | 12 | ||||
-rw-r--r-- | src/lib-config/get.c | 256 | ||||
-rw-r--r-- | src/lib-config/iconfig.h | 136 | ||||
-rw-r--r-- | src/lib-config/irssi-config.c | 189 | ||||
-rw-r--r-- | src/lib-config/irssi-config.h | 25 | ||||
-rw-r--r-- | src/lib-config/module.h | 6 | ||||
-rw-r--r-- | src/lib-config/parse.c | 335 | ||||
-rw-r--r-- | src/lib-config/set.c | 122 | ||||
-rw-r--r-- | src/lib-config/write.c | 336 |
9 files changed, 1200 insertions, 217 deletions
diff --git a/src/lib-config/Makefile.am b/src/lib-config/Makefile.am index 7de63122..00969218 100644 --- a/src/lib-config/Makefile.am +++ b/src/lib-config/Makefile.am @@ -1,9 +1,15 @@ noinst_LTLIBRARIES = libirssi_config.la -INCLUDES = $(GLIB_CFLAGS) +INCLUDES = \ + $(GLIB_CFLAGS) \ + -I$(top_srcdir)/src libirssi_config_la_SOURCES = \ - irssi-config.c + get.c \ + set.c \ + parse.c \ + write.c noinst_HEADERS = \ - irssi-config.h + iconfig.h \ + module.h diff --git a/src/lib-config/get.c b/src/lib-config/get.c new file mode 100644 index 00000000..e29df699 --- /dev/null +++ b/src/lib-config/get.c @@ -0,0 +1,256 @@ +/* + get.c : irssi configuration - get settings from memory + + Copyright (C) 1999 Timo Sirainen + + 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. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" + +CONFIG_NODE *config_node_find(CONFIG_NODE *node, const char *key) +{ + GSList *tmp; + + g_return_val_if_fail(node != NULL, NULL); + g_return_val_if_fail(key != NULL, NULL); + g_return_val_if_fail(is_node_list(node), NULL); + + for (tmp = node->value; tmp != NULL; tmp = tmp->next) { + CONFIG_NODE *node = tmp->data; + + if (node->key != NULL && g_strcasecmp(node->key, key) == 0) + return node; + } + + return NULL; +} + +/* find the section from node - if not found create it unless new_type is -1. + you can also specify in new_type if it's NODE_TYPE_LIST or NODE_TYPE_BLOCK */ +CONFIG_NODE *config_node_section(CONFIG_REC *rec, CONFIG_NODE *parent, const char *key, int new_type) +{ + CONFIG_NODE *node; + + g_return_val_if_fail(rec != NULL, NULL); + g_return_val_if_fail(parent != NULL, NULL); + g_return_val_if_fail(is_node_list(parent), NULL); + + node = key == NULL ? NULL : config_node_find(parent, key); + if (node != NULL) { + g_return_val_if_fail(new_type == -1 || new_type == node->type, NULL); + return node; + } + + if (new_type == -1) + return NULL; + + node = g_new0(CONFIG_NODE, 1); + parent->value = g_slist_append(parent->value, node); + + node->type = new_type; + node->key = key == NULL ? NULL : g_strdup(key); + + return node; +} + +/* find the section with the whole path. + create the path if necessary `create' is TRUE. */ +CONFIG_NODE *config_node_traverse(CONFIG_REC *rec, const char *section, int create) +{ + CONFIG_NODE *node; + char **list, **tmp; + int is_list, new_type; + + g_return_val_if_fail(rec != NULL, NULL); + + if (section == NULL || *section == '\0') + return rec->mainnode; + + /* check if it already exists in cache */ + node = g_hash_table_lookup(rec->cache, section); + if (node != NULL) return node; + + new_type = -1; + + node = rec->mainnode; + list = g_strsplit(section, "/", -1); + for (tmp = list; *tmp != NULL; tmp++) { + is_list = **tmp == '('; + if (create) new_type = is_list ? NODE_TYPE_LIST : NODE_TYPE_BLOCK; + + node = config_node_section(rec, node, *tmp + is_list, new_type); + if (node == NULL) return NULL; + } + g_strfreev(list); + + /* save to cache */ + g_hash_table_insert(rec->cache, g_strdup(section), node); + return node; +} + +char *config_get_str(CONFIG_REC *rec, const char *section, const char *key, const char *def) +{ + CONFIG_NODE *parent, *node; + char *path; + + g_return_val_if_fail(rec != NULL, (char *) def); + g_return_val_if_fail(section != NULL, (char *) def); + g_return_val_if_fail(key != NULL, (char *) def); + + /* check if it already exists in cache */ + path = g_strconcat(section, "/", key, NULL); + node = g_hash_table_lookup(rec->cache, path); + + if (node != NULL) + g_free(path); + else { + parent = config_node_traverse(rec, section, FALSE); + node = parent == NULL ? NULL : + config_node_find(parent, key); + + /* save to cache */ + if (node != NULL) + g_hash_table_insert(rec->cache, path, node); + else + g_free(path); + } + + return (node == NULL || !has_node_value(node)) ? (char *) def : node->value; +} + +int config_get_int(CONFIG_REC *rec, const char *section, const char *key, int def) +{ + char *str; + + str = config_get_str(rec, section, key, NULL); + if (str == NULL) return def; + + return atoi(str); +} + +int config_get_bool(CONFIG_REC *rec, const char *section, const char *key, int def) +{ + char *str; + + str = config_get_str(rec, section, key, NULL); + if (str == NULL) return def; + + return toupper(*str) == 'T' || toupper(*str) == 'Y'; +} + +/* Return value of key `value_key' from list item where `key' is `value' */ +const char *config_list_find(CONFIG_REC *rec, const char *section, const char *key, const char *value, const char *value_key) +{ + CONFIG_NODE *node; + + node = config_list_find_node(rec, section, key, value, value_key); + return node != NULL && node->type == NODE_TYPE_KEY ? + node->value : NULL; +} + +/* Like config_list_find(), but return node instead of it's value */ +CONFIG_NODE *config_list_find_node(CONFIG_REC *rec, const char *section, const char *key, const char *value, const char *value_key) +{ + CONFIG_NODE *node, *keynode; + GSList *tmp; + + g_return_val_if_fail(rec != NULL, NULL); + g_return_val_if_fail(section != NULL, NULL); + g_return_val_if_fail(key != NULL, NULL); + g_return_val_if_fail(value_key != NULL, NULL); + + node = config_node_traverse(rec, section, FALSE); + if (node == NULL || !is_node_list(node)) return NULL; + + for (tmp = node->value; tmp != NULL; tmp = tmp->next) { + node = tmp->data; + + if (node->type != NODE_TYPE_BLOCK) + continue; + + /* key matches value? */ + keynode = config_node_find(node, key); + if (keynode == NULL || keynode->type != NODE_TYPE_KEY || + g_strcasecmp(keynode->value, value) != 0) continue; + + return config_node_find(node, value_key); + } + + return NULL; +} + +char *config_node_get_str(CONFIG_NODE *parent, const char *key, const char *def) +{ + CONFIG_NODE *node; + + node = config_node_find(parent, key); + return (node == NULL || !has_node_value(node)) ? def : node->value; +} + +int config_node_get_int(CONFIG_NODE *parent, const char *key, int def) +{ + char *str; + + str = config_node_get_str(parent, key, NULL); + if (str == NULL) return def; + + return atoi(str); +} + +int config_node_get_bool(CONFIG_NODE *parent, const char *key, int def) +{ + char *str; + + str = config_node_get_str(parent, key, NULL); + if (str == NULL) return def; + + return toupper(*str) == 'T' || toupper(*str) == 'Y' || + (toupper(*str) == 'O' && toupper(str[1]) == 'N'); +} + +/* Get the value of keys `key' and `key_value' and put them to + `ret_key' and `ret_value'. Returns -1 if not found. */ +int config_node_get_keyvalue(CONFIG_NODE *node, const char *key, const char *value_key, char **ret_key, char **ret_value) +{ + CONFIG_NODE *keynode, *valuenode; + GSList *tmp; + + g_return_val_if_fail(node != NULL, -1); + g_return_val_if_fail(key != NULL, -1); + g_return_val_if_fail(value_key != NULL, -1); + g_return_val_if_fail(ret_key != NULL, -1); + g_return_val_if_fail(ret_value != NULL, -1); + + for (tmp = node->value; tmp != NULL; tmp = tmp->next) { + node = tmp->data; + + if (node->type != NODE_TYPE_BLOCK) + continue; + + keynode = config_node_find(node, key); + if (keynode == NULL || keynode->type != NODE_TYPE_KEY) + continue; + + valuenode = config_node_find(node, value_key); + + *ret_key = keynode->key; + *ret_value = valuenode != NULL && valuenode->type == NODE_TYPE_KEY ? + valuenode->value : NULL; + return 0; + } + + return -1; +} diff --git a/src/lib-config/iconfig.h b/src/lib-config/iconfig.h new file mode 100644 index 00000000..bbee0f6e --- /dev/null +++ b/src/lib-config/iconfig.h @@ -0,0 +1,136 @@ +#ifndef __ICONFIG_H +#define __ICONFIG_H + +enum { + NODE_TYPE_KEY, + NODE_TYPE_VALUE, + NODE_TYPE_BLOCK, + NODE_TYPE_LIST, + NODE_TYPE_COMMENT, +}; + +#define has_node_value(a) \ + ((a)->type == NODE_TYPE_KEY || (a)->type == NODE_TYPE_VALUE) +#define is_node_list(a) \ + ((a)->type == NODE_TYPE_BLOCK || (a)->type == NODE_TYPE_LIST) + +typedef struct { + int type; + char *key; + void *value; +} CONFIG_NODE; + +/* a = { x=y; y=z; } + + node1: type = NODE_TYPE_BLOCK, key = "a", value = (GSList *) nodes + nodes: (node2, node3) + node2: type = NODE_TYPE_KEY, key = "x", value = (char *) "y" + node3: type = NODE_TYPE_KEY, key = "y", value = (char *) "z" + + b = ( a, { b=c; d=e; } ) + + node1: type = NODE_TYPE_LIST, key = "b", value = (GSList *) nodes + nodes: (node2, node3) + node2: type = NODE_TYPE_VALUE, key = NULL, value = (char *) "a" + node4: type = NODE_TYPE_BLOCK, key = NULL, value = (GSList *) nodes2 + nodes2: (node4, node5) + node4: type = NODE_TYPE_KEY, key = "b", value = (char *) "c" + node5: type = NODE_TYPE_KEY, key = "d", value = (char *) "e" + + Comments node has key=NULL and value is the comment line. Empty lines are + also in comments so they won't be forgotten when the config file is + written. + +*/ + +struct _config_rec { + char *fname; + int handle; + int create_mode; + + char *last_error; + CONFIG_NODE *mainnode; + GHashTable *cache; + + GScanner *scanner; + + /* while writing to configuration file.. */ + int tmp_indent_level; /* indentation position */ + int tmp_last_lf; /* last character was a line feed */ +}; + +typedef struct _config_rec CONFIG_REC; + +/* Open configuration. The file is created if it doesn't exist, unless + `create_mode' is -1. `fname' can be NULL if you just want to use + config_parse_data() */ +CONFIG_REC *config_open(const char *fname, int create_mode); +/* Release all memory used by configuration */ +void config_close(CONFIG_REC *rec); +/* Change file name of config file */ +void config_change_file_name(CONFIG_REC *rec, const char *fname, int create_mode); + +/* Parse configuration file */ +int config_parse(CONFIG_REC *rec); +/* Parse configuration found from `data'. `input_name' is specifies the + "configuration name" which is displayed in error messages. */ +int config_parse_data(CONFIG_REC *rec, const char *data, const char *input_name); + +/* Write configuration file. Write to `fname' if it's not NULL. + If `create_mode' is -1, use the one that was given to config_open(). */ +int config_write(CONFIG_REC *rec, const char *fname, int create_mode); + +#define config_last_error(rec) \ + (rec)->last_error + +/* Getting values + + `section' is something like "maingroup/key/subkey", or with lists + "maingroup/(list/subkey" + + `def' is returned if the value is not found. */ +char *config_get_str(CONFIG_REC *rec, const char *section, const char *key, const char *def); +int config_get_int(CONFIG_REC *rec, const char *section, const char *key, int def); +int config_get_bool(CONFIG_REC *rec, const char *section, const char *key, int def); + +/* Return value of key `value_key' from list item where `key' is `value' */ +const char *config_list_find(CONFIG_REC *rec, const char *section, const char *key, const char *value, const char *value_key); +/* Like config_list_find(), but return node instead of it's value */ +CONFIG_NODE *config_list_find_node(CONFIG_REC *rec, const char *section, const char *key, const char *value, const char *value_key); + +/* Setting values */ +int config_set_str(CONFIG_REC *rec, const char *section, const char *key, const char *value); +int config_set_int(CONFIG_REC *rec, const char *section, const char *key, int value); +int config_set_bool(CONFIG_REC *rec, const char *section, const char *key, int value); + +/* Handling the configuration directly with nodes - + useful when you need to read all values in a block/list. */ +CONFIG_NODE *config_node_find(CONFIG_NODE *node, const char *key); +/* Find the section from node - if not found create it unless new_type is -1. + You can also specify in new_type if it's NODE_TYPE_LIST or NODE_TYPE_BLOCK */ +CONFIG_NODE *config_node_section(CONFIG_REC *rec, CONFIG_NODE *parent, const char *key, int new_type); +/* Find the section with the whole path. + Create the path if necessary `create' is TRUE. */ +CONFIG_NODE *config_node_traverse(CONFIG_REC *rec, const char *section, int create); +/* Get the value of keys `key' and `key_value' and put them to + `ret_key' and `ret_value'. Returns -1 if not found. */ +int config_node_get_keyvalue(CONFIG_NODE *node, const char *key, const char *value_key, char **ret_key, char **ret_value); + +char *config_node_get_str(CONFIG_NODE *parent, const char *key, const char *def); +int config_node_get_int(CONFIG_NODE *parent, const char *key, int def); +int config_node_get_bool(CONFIG_NODE *parent, const char *key, int def); + +void config_node_set_str(CONFIG_NODE *parent, const char *key, const char *value); +void config_node_set_int(CONFIG_NODE *parent, const char *key, int value); +void config_node_set_bool(CONFIG_NODE *parent, const char *key, int value); + +/* add/change the value of the `key' */ +void config_node_set_str(CONFIG_NODE *parent, const char *key, const char *value); +/* remove one node from block/list. + ..set_str() with value = NULL does the same. */ +void config_node_remove(CONFIG_NODE *parent, CONFIG_NODE *node); + +/* clear the entire configuration */ +void config_nodes_remove_all(CONFIG_REC *rec); + +#endif diff --git a/src/lib-config/irssi-config.c b/src/lib-config/irssi-config.c deleted file mode 100644 index 2750397c..00000000 --- a/src/lib-config/irssi-config.c +++ /dev/null @@ -1,189 +0,0 @@ -/* - config.c : Functions for reading onfiguration file - - Copyright (C) 1999 Timo Sirainen - - 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. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include "../common.h" -#include "../irc-base/irc-base.h" - -proplist_t cprop = NULL; - -gboolean config_get_bool(proplist_t prop, gchar *key, gboolean def) -{ - proplist_t pkey, pvalue; - gchar *value; - - if (prop == NULL) - return def; - - pkey = PLMakeString(key); - pvalue = PLGetDictionaryEntry(prop, pkey); - PLRelease(pkey); - if (pvalue == NULL) return def; - - value = PLGetString(pvalue); - return toupper(*value) == 'T' || toupper(*value) == 'Y'; -} - -gint config_get_int(proplist_t prop, gchar *key, gint def) -{ - proplist_t pkey, pvalue; - gint num; - - if (prop == NULL) - return def; - - pkey = PLMakeString(key); - pvalue = PLGetDictionaryEntry(prop, pkey); - PLRelease(pkey); - if (pvalue == NULL) return def; - - return sscanf(PLGetString(pvalue), "%d", &num) != 1 ? def : num; -} - -gchar *config_get_str(proplist_t prop, gchar *key, gchar *def) -{ - proplist_t pkey, pvalue; - - if (prop == NULL) - return def; - - pkey = PLMakeString(key); - pvalue = PLGetDictionaryEntry(prop, pkey); - PLRelease(pkey); - - return pvalue == NULL ? def : PLGetString(pvalue); -} - -proplist_t config_get_prop(proplist_t prop, gchar *key) -{ - proplist_t ret, pkey; - - pkey = PLMakeString(key); - ret = PLGetDictionaryEntry(prop, pkey); - PLRelease(pkey); - - return ret; -} - -proplist_t config_make_dict(proplist_t prop, gchar *section) -{ - proplist_t psect, pkey; - - pkey = PLMakeString(section); - psect = PLMakeDictionaryFromEntries(NULL, NULL); - prop = PLInsertDictionaryEntry(prop, pkey, psect); - return prop; -} - -proplist_t config_set_str(proplist_t prop, gchar *key, gchar *value) -{ - proplist_t pkey, pvalue; - - pkey = PLMakeString(key); pvalue = PLMakeString(value); - prop = PLInsertDictionaryEntry(prop, pkey, pvalue); - PLRelease(pkey); PLRelease(pvalue); - return prop; -} - -proplist_t config_set_int(proplist_t prop, gchar *key, gint value) -{ - proplist_t pkey, pvalue; - gchar *strval; - - strval = g_strdup_printf("%d", value); - pkey = PLMakeString(key); pvalue = PLMakeString(strval); - prop = PLInsertDictionaryEntry(prop, pkey, pvalue); - PLRelease(pkey); PLRelease(pvalue); - g_free(strval); - return prop; -} - -proplist_t config_set_bool(proplist_t prop, gchar *key, gboolean value) -{ - proplist_t pkey, pvalue; - - pkey = PLMakeString(key); pvalue = PLMakeString(value ? "Yes" : "No"); - prop = PLInsertDictionaryEntry(prop, pkey, pvalue); - PLRelease(pkey); PLRelease(pvalue); - return prop; -} - -proplist_t config_clean_key(proplist_t prop, gchar *key) -{ - proplist_t pkey; - - pkey = PLMakeString(key); - PLRemoveDictionaryEntry(prop, pkey); - PLRelease(pkey); - return prop; -} - -proplist_t config_section(proplist_t *prop, gchar *section) -{ - proplist_t ret, pkey; - - pkey = PLMakeString(section); - ret = PLGetDictionaryEntry(*prop, pkey); - if (ret == NULL) - { - ret = PLMakeDictionaryFromEntries(NULL, NULL); - *prop = PLInsertDictionaryEntry(*prop, pkey, ret); - } - PLRelease(pkey); - - return ret; -} - -proplist_t config_list_section(proplist_t *prop, gchar *section) -{ - proplist_t ret, pkey; - - pkey = PLMakeString(section); - ret = PLGetDictionaryEntry(*prop, pkey); - if (ret == NULL) - { - ret = PLMakeArrayFromElements(NULL); - *prop = PLInsertDictionaryEntry(*prop, pkey, ret); - } - PLRelease(pkey); - - return ret; -} - -gint config_list_find(proplist_t prop, gchar *key, gchar *value) -{ - proplist_t item; - gint num, max; - gchar *ret; - - if (prop == NULL) - return -1; - - max = PLGetNumberOfElements(prop); - for (num = 0; num < max; num++) - { - item = PLGetArrayElement(prop, num); - ret = config_get_str(item, key, NULL); - if (ret != NULL && g_strcasecmp(ret, value) == 0) - return num; - } - - return -1; -} - diff --git a/src/lib-config/irssi-config.h b/src/lib-config/irssi-config.h deleted file mode 100644 index 2dc06c5e..00000000 --- a/src/lib-config/irssi-config.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef __IRSSI_CONFIG_H -#define __IRSSI_CONFIG_H - -#include <proplist.h> - -extern proplist_t cprop; - -/* make proplist handling easier */ -gchar *config_get_str(proplist_t prop, gchar *key, gchar *def); -gint config_get_int(proplist_t prop, gchar *key, gint def); -gboolean config_get_bool(proplist_t prop, gchar *key, gboolean def); -proplist_t config_get_prop(proplist_t prop, gchar *key); - -proplist_t config_set_str(proplist_t prop, gchar *key, gchar *value); -proplist_t config_set_int(proplist_t prop, gchar *key, gint value); -proplist_t config_set_bool(proplist_t prop, gchar *key, gboolean value); - -proplist_t config_section(proplist_t *prop, gchar *section); -proplist_t config_list_section(proplist_t *prop, gchar *section); -proplist_t config_make_dict(proplist_t prop, gchar *section); -proplist_t config_clean_key(proplist_t prop, gchar *key); - -gint config_list_find(proplist_t prop, gchar *key, gchar *value); - -#endif diff --git a/src/lib-config/module.h b/src/lib-config/module.h new file mode 100644 index 00000000..22e0e7c7 --- /dev/null +++ b/src/lib-config/module.h @@ -0,0 +1,6 @@ +#include "common.h" +#include "iconfig.h" + +/* private */ +int config_error(CONFIG_REC *rec, const char *msg); + diff --git a/src/lib-config/parse.c b/src/lib-config/parse.c new file mode 100644 index 00000000..5171161c --- /dev/null +++ b/src/lib-config/parse.c @@ -0,0 +1,335 @@ +/* + parse.c : irssi configuration - parse configuration file + + Copyright (C) 1999 Timo Sirainen + + 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. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" + +int config_error(CONFIG_REC *rec, const char *msg) +{ + g_free_and_null(rec->last_error); + rec->last_error = g_strdup(msg); + return -1; +} + +static int node_add_comment(CONFIG_NODE *parent, const char *str) +{ + CONFIG_NODE *node; + + g_return_val_if_fail(parent != NULL, -1); + + if (!is_node_list(parent)) + return -1; + + node = g_new0(CONFIG_NODE, 1); + node->type = NODE_TYPE_COMMENT; + node->value = str == NULL ? NULL : g_strdup(str); + + parent->value = g_slist_append(parent->value, node); + return 0; +} + +/* same as g_scanner_get_next_token() except skips and reads the comments */ +static void config_parse_get_token(GScanner *scanner, CONFIG_NODE *node) +{ + int prev_empty = FALSE; + + for (;;) { + g_scanner_get_next_token(scanner); + + if (scanner->token == G_TOKEN_COMMENT_SINGLE) + node_add_comment(node, scanner->value.v_string); + else if (scanner->token == '\n') { + if (prev_empty) node_add_comment(node, NULL); + } else { + if (scanner->token == G_TOKEN_INT) { + scanner->token = G_TOKEN_STRING; +#undef g_strdup_printf /* This is free'd by GLib itself */ + scanner->value.v_string = g_strdup_printf("%lu", scanner->value.v_int); +#ifdef MEM_DEBUG +#define g_strdup_printf ig_strdup_printf +#endif + } + break; + } + + prev_empty = TRUE; + } +} + +/* same as g_scanner_peek_next_token() except skips and reads the comments */ +static void config_parse_peek_token(GScanner *scanner, CONFIG_NODE *node) +{ + int prev_empty = FALSE; + + for (;;) { + g_scanner_peek_next_token(scanner); + + if (scanner->next_token == G_TOKEN_COMMENT_SINGLE) + node_add_comment(node, scanner->next_value.v_string); + else if (scanner->next_token == '\n') { + if (prev_empty) node_add_comment(node, NULL); + } else + break; + + prev_empty = TRUE; + g_scanner_get_next_token(scanner); + } +} + +/* get optional token, optionally warn if it's missing */ +static void config_parse_warn_missing(CONFIG_REC *rec, CONFIG_NODE *node, int expected_token, int print_warning) +{ + config_parse_peek_token(rec->scanner, node); + if (rec->scanner->next_token == expected_token) { + g_scanner_get_next_token(rec->scanner); + return; + } + + if (print_warning) + g_scanner_warn(rec->scanner, "Warning: missing '%c'", expected_token); +} + +static void config_parse_loop(CONFIG_REC *rec, CONFIG_NODE *node, int expect); + +static int config_parse_symbol(CONFIG_REC *rec, CONFIG_NODE *node) +{ + CONFIG_NODE *newnode; + int print_warning; + char *key, last_char; + + g_return_val_if_fail(rec != NULL, G_TOKEN_ERROR); + g_return_val_if_fail(node != NULL, G_TOKEN_ERROR); + + config_parse_get_token(rec->scanner, node); + + last_char = node->type == NODE_TYPE_LIST ? ',' : ';'; + + /* key */ + key = NULL; + if (node->type != NODE_TYPE_LIST && + (rec->scanner->token == G_TOKEN_STRING)) { + key = g_strdup(rec->scanner->value.v_string); + + config_parse_get_token(rec->scanner, node); + if (rec->scanner->token != '=') + return '='; + + config_parse_get_token(rec->scanner, node); + } + + switch (rec->scanner->token) { + case G_TOKEN_STRING: + /* value */ + config_node_set_str(node, key, rec->scanner->value.v_string); + g_free_not_null(key); + + print_warning = TRUE; + if (node->type == NODE_TYPE_LIST) { + /* if it's last item it doesn't need comma */ + config_parse_peek_token(rec->scanner, node); + if (rec->scanner->next_token == ')') + print_warning = FALSE; + } + + config_parse_warn_missing(rec, node, last_char, print_warning); + break; + + case '{': + /* block */ + if (key == NULL && node->type != NODE_TYPE_LIST) + return G_TOKEN_ERROR; + + newnode = config_node_section(rec, node, key, NODE_TYPE_BLOCK); + config_parse_loop(rec, newnode, '}'); + g_free_not_null(key); + + config_parse_get_token(rec->scanner, node); + if (rec->scanner->token != '}') + return '}'; + + config_parse_warn_missing(rec, node, last_char, FALSE); + break; + + case '(': + /* list */ + if (key == NULL) + return G_TOKEN_ERROR; + newnode = config_node_section(rec, node, key, NODE_TYPE_LIST); + config_parse_loop(rec, newnode, ')'); + g_free_not_null(key); + + config_parse_get_token(rec->scanner, node); + if (rec->scanner->token != ')') + return ')'; + + config_parse_warn_missing(rec, node, last_char, FALSE); + break; + + default: + /* error */ + g_free_not_null(key); + return G_TOKEN_STRING; + } + + return G_TOKEN_NONE; +} + +static void config_parse_loop(CONFIG_REC *rec, CONFIG_NODE *node, int expect) +{ + int expected_token; + + g_return_if_fail(rec != NULL); + g_return_if_fail(node != NULL); + + do { + expected_token = config_parse_symbol(rec, node); + if (expected_token != G_TOKEN_NONE) { + if (expected_token == G_TOKEN_ERROR) + expected_token = G_TOKEN_NONE; + g_scanner_unexp_token(rec->scanner, expected_token, NULL, "symbol", NULL, NULL, TRUE); + } + + config_parse_peek_token(rec->scanner, node); + } while (rec->scanner->next_token != expect && + rec->scanner->next_token != G_TOKEN_EOF); +} + +static void config_parse_error_func(GScanner *scanner, char *message, int is_error) +{ + CONFIG_REC *rec = scanner->user_data; + char *old; + + old = rec->last_error; + rec->last_error = g_strdup_printf("%s%s:%d: %s%s\n", + old == NULL ? "" : old, + scanner->input_name, scanner->line, + is_error ? "error: " : "", + message); + g_free_not_null(old); +} + +void config_parse_init(CONFIG_REC *rec, const char *name) +{ + GScanner *scanner; + + g_free_and_null(rec->last_error); + config_nodes_remove_all(rec); + + rec->scanner = scanner = g_scanner_new(NULL); + scanner->config->skip_comment_single = FALSE; + scanner->config->cset_skip_characters = " \t"; + scanner->config->scan_binary = FALSE; + scanner->config->scan_octal = FALSE; + scanner->config->scan_float = FALSE; + scanner->config->scan_string_sq = TRUE; + scanner->config->scan_string_dq = TRUE; + scanner->config->scan_identifier_1char = TRUE; + scanner->config->identifier_2_string = TRUE; + + scanner->user_data = rec; + scanner->input_name = name; + scanner->msg_handler = (GScannerMsgFunc) config_parse_error_func; +} + +/* Parse configuration file */ +int config_parse(CONFIG_REC *rec) +{ + g_return_val_if_fail(rec != NULL, -1); + g_return_val_if_fail(rec->fname != NULL, -1); + + rec->handle = open(rec->fname, O_RDONLY); + if (rec->handle == -1) + return config_error(rec, g_strerror(errno)); + + config_parse_init(rec, rec->fname); + g_scanner_input_file(rec->scanner, rec->handle); + config_parse_loop(rec, rec->mainnode, G_TOKEN_EOF); + g_scanner_destroy(rec->scanner); + + close(rec->handle); + rec->handle = -1; + + return rec->last_error == NULL ? 0 : -1; +} + +/* Parse configuration found from `data'. `input_name' is specifies the + "configuration name" which is displayed in error messages. */ +int config_parse_data(CONFIG_REC *rec, const char *data, const char *input_name) +{ + config_parse_init(rec, input_name); + g_scanner_input_text(rec->scanner, data, strlen(data)); + config_parse_loop(rec, rec->mainnode, G_TOKEN_EOF); + g_scanner_destroy(rec->scanner); + + return rec->last_error == NULL ? 0 : -1; +} + +/* Open configuration. The file is created if it doesn't exist, unless + `create_mode' is -1. `fname' can be NULL if you just want to use + config_parse_data() */ +CONFIG_REC *config_open(const char *fname, int create_mode) +{ + CONFIG_REC *rec; + int f; + + if (fname != NULL) { + f = open(fname, O_RDONLY | (create_mode != -1 ? O_CREAT : 0), create_mode); + if (f == -1) return NULL; + close(f); + } + + rec = g_new0(CONFIG_REC, 1); + rec->fname = fname == NULL ? NULL : g_strdup(fname); + rec->handle = -1; + rec->create_mode = create_mode; + rec->mainnode = g_new0(CONFIG_NODE, 1); + rec->mainnode->type = NODE_TYPE_BLOCK; + rec->cache = g_hash_table_new((GHashFunc) g_str_hash, (GCompareFunc) g_str_equal); + + return rec; +} + +/* Release all memory used by configuration */ +void config_close(CONFIG_REC *rec) +{ + g_return_if_fail(rec != NULL); + + config_nodes_remove_all(rec); + g_free(rec->mainnode); + + if (rec->handle != -1) close(rec->handle); + g_hash_table_foreach(rec->cache, (GHFunc) g_free, NULL); + g_hash_table_destroy(rec->cache); + g_free_not_null(rec->last_error); + g_free(rec->fname); + g_free(rec); +} + +/* Change file name of config file */ +void config_change_file_name(CONFIG_REC *rec, const char *fname, int create_mode) +{ + g_return_if_fail(rec != NULL); + g_return_if_fail(fname != NULL); + + g_free_not_null(rec->fname); + rec->fname = g_strdup(fname); + + if (create_mode != -1) + rec->create_mode = create_mode; +} diff --git a/src/lib-config/set.c b/src/lib-config/set.c new file mode 100644 index 00000000..49457576 --- /dev/null +++ b/src/lib-config/set.c @@ -0,0 +1,122 @@ +/* + set.c : irssi configuration - change settings in memory + + Copyright (C) 1999 Timo Sirainen + + 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. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" + +void config_node_remove(CONFIG_NODE *parent, CONFIG_NODE *node) +{ + g_return_if_fail(parent != NULL); + g_return_if_fail(node != NULL); + + parent->value = g_slist_remove(parent->value, node); + + switch (node->type) { + case NODE_TYPE_KEY: + case NODE_TYPE_VALUE: + case NODE_TYPE_COMMENT: + g_free_not_null(node->value); + break; + case NODE_TYPE_BLOCK: + case NODE_TYPE_LIST: + while (node->value != NULL) + config_node_remove(node, ((GSList *) node->value)->data); + break; + } + g_free_not_null(node->key); + g_free(node); +} + +void config_nodes_remove_all(CONFIG_REC *rec) +{ + g_return_if_fail(rec != NULL); + + while (rec->mainnode->value != NULL) + config_node_remove(rec->mainnode, ((GSList *) rec->mainnode->value)->data); +} + + +void config_node_set_str(CONFIG_NODE *parent, const char *key, const char *value) +{ + CONFIG_NODE *node; + int no_key; + + g_return_if_fail(parent != NULL); + + no_key = key == NULL; + node = no_key ? NULL : config_node_find(parent, key); + + if (value == NULL) { + /* remove the key */ + if (node != NULL) config_node_remove(parent, node); + return; + } + + if (node != NULL) + g_free(node->value); + else { + node = g_new0(CONFIG_NODE, 1); + parent->value = g_slist_append(parent->value, node); + + node->type = no_key ? NODE_TYPE_VALUE : NODE_TYPE_KEY; + node->key = no_key ? NULL : g_strdup(key); + } + + node->value = g_strdup(value); +} + +void config_node_set_int(CONFIG_NODE *parent, const char *key, int value) +{ + char str[MAX_INT_STRLEN]; + + g_snprintf(str, sizeof(str), "%d", value); + return config_node_set_str(parent, key, str); +} + +void config_node_set_bool(CONFIG_NODE *parent, const char *key, int value) +{ + return config_node_set_str(parent, key, value ? "yes" : "no"); +} + +int config_set_str(CONFIG_REC *rec, const char *section, const char *key, const char *value) +{ + CONFIG_NODE *parent; + + g_return_val_if_fail(rec != NULL, -1); + g_return_val_if_fail(section != NULL, -1); + + parent = config_node_traverse(rec, section, TRUE); + if (parent == NULL) return -1; + + config_node_set_str(parent, key, value); + return 0; +} + +int config_set_int(CONFIG_REC *rec, const char *section, const char *key, int value) +{ + char str[MAX_INT_STRLEN]; + + g_snprintf(str, sizeof(str), "%d", value); + return config_set_str(rec, section, key, str); +} + +int config_set_bool(CONFIG_REC *rec, const char *section, const char *key, int value) +{ + return config_set_str(rec, section, key, value ? "yes" : "no"); +} diff --git a/src/lib-config/write.c b/src/lib-config/write.c new file mode 100644 index 00000000..30a41fd4 --- /dev/null +++ b/src/lib-config/write.c @@ -0,0 +1,336 @@ +/* + write.c : irssi configuration - write configuration file + + Copyright (C) 1999 Timo Sirainen + + 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. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" + +/* maximum length of lines in config file before splitting them to multiple lines */ +#define MAX_CHARS_IN_LINE 70 + +#define CONFIG_INDENT_SIZE 2 +static const char *indent_block = " "; /* needs to be the same size as CONFIG_INDENT_SIZE! */ + +/* write needed amount of indentation to the start of the line */ +static int config_write_indent(CONFIG_REC *rec) +{ + int n; + + for (n = 0; n < rec->tmp_indent_level/CONFIG_INDENT_SIZE; n++) { + if (write(rec->handle, indent_block, CONFIG_INDENT_SIZE) == -1) + return -1; + } + + return 0; +} + +static int config_write_str(CONFIG_REC *rec, const char *str) +{ + const char *strpos, *p; + + g_return_val_if_fail(rec != NULL, -1); + g_return_val_if_fail(str != NULL, -1); + + strpos = str; + while (*strpos != '\0') { + /* fill the indentation */ + if (rec->tmp_last_lf && rec->tmp_indent_level > 0) { + if (config_write_indent(rec) == -1) + return -1; + } + + p = strchr(strpos, '\n'); + if (p == NULL) { + if (write(rec->handle, strpos, strlen(strpos)) == -1) + return -1; + strpos = ""; + rec->tmp_last_lf = FALSE; + } else { + if (write(rec->handle, strpos, (int) (p-strpos)+1) == -1) + return -1; + strpos = p+1; + rec->tmp_last_lf = TRUE; + } + } + + return 0; +} + +static int config_has_specials(const char *text) +{ + g_return_val_if_fail(text != NULL, FALSE); + + while (*text != '\0') { + if ((unsigned char) *text <= 32 || *text == '"' || *text == '\\') + return TRUE; + text++; + } + + return FALSE; +} + +static int get_octal(int decimal) +{ + int octal, pos; + + octal = 0; pos = 0; + while (decimal > 0) { + octal += (decimal & 7)*(pos == 0 ? 1 : pos); + decimal /= 8; + pos += 10; + } + + return octal; +} + +static char *config_escape_string(const char *text) +{ + GString *str; + char *ret; + + g_return_val_if_fail(text != NULL, NULL); + + str = g_string_new("\""); + while (*text != '\0') { + if (*text == '\\' || *text == '"') + g_string_sprintfa(str, "\\%c", *text); + else if ((unsigned char) *text < 32) + g_string_sprintfa(str, "\\%03d", get_octal(*text)); + else + g_string_append_c(str, *text); + text++; + } + + g_string_append_c(str, '"'); + + ret = str->str; + g_string_free(str, FALSE); + return ret; +} + +static int config_write_word(CONFIG_REC *rec, const char *word, int string) +{ + char *str; + int ret; + + g_return_val_if_fail(rec != NULL, -1); + g_return_val_if_fail(word != NULL, -1); + + if (!string && !config_has_specials(word)) + return config_write_str(rec, word); + + str = config_escape_string(word); + ret = config_write_str(rec, str); + g_free(str); + + return ret; +} + +static int config_write_block(CONFIG_REC *rec, CONFIG_NODE *node, int list, int line_feeds); + +static int config_write_node(CONFIG_REC *rec, CONFIG_NODE *node, int line_feeds) +{ + g_return_val_if_fail(rec != NULL, -1); + g_return_val_if_fail(node != NULL, -1); + + switch (node->type) { + case NODE_TYPE_KEY: + if (config_write_word(rec, node->key, FALSE) == -1 || + config_write_str(rec, " = ") == -1 || + config_write_word(rec, node->value, TRUE) == -1) + return -1; + break; + case NODE_TYPE_VALUE: + if (config_write_word(rec, node->value, TRUE) == -1) + return -1; + break; + case NODE_TYPE_BLOCK: + /* key = { */ + if (node->key != NULL) { + if (config_write_str(rec, node->key) == -1 || + config_write_str(rec, " = ") == -1) + return -1; + } + if (config_write_str(rec, line_feeds ? "{\n" : "{ ") == -1) + return -1; + + /* ..block.. */ + rec->tmp_indent_level += CONFIG_INDENT_SIZE; + if (config_write_block(rec, node, FALSE, line_feeds) == -1) + return -1; + rec->tmp_indent_level -= CONFIG_INDENT_SIZE; + + /* }; */ + if (config_write_str(rec, "}") == -1) + return -1; + break; + case NODE_TYPE_LIST: + /* key = ( */ + if (node->key != NULL) { + if (config_write_str(rec, node->key) == -1 || + config_write_str(rec, " = ") == -1) + return -1; + } + if (config_write_str(rec, line_feeds ? "(\n" : "( ") == -1) + return -1; + + /* ..list.. */ + rec->tmp_indent_level += CONFIG_INDENT_SIZE; + if (config_write_block(rec, node, TRUE, line_feeds) == -1) + return -1; + rec->tmp_indent_level -= CONFIG_INDENT_SIZE; + + /* ); */ + if (config_write_str(rec, ")") == -1) + return -1; + break; + case NODE_TYPE_COMMENT: + if (node->value == NULL) + break; + + if (config_write_str(rec, "#") == -1 || + config_write_str(rec, node->value) == -1) + return -1; + break; + } + + return 0; +} + +static int config_block_get_length(CONFIG_REC *rec, CONFIG_NODE *node); + +static int config_node_get_length(CONFIG_REC *rec, CONFIG_NODE *node) +{ + int len; + + switch (node->type) { + case NODE_TYPE_KEY: + /* "key = value; " */ + len = 5 + strlen(node->key) + strlen(node->value); + break; + case NODE_TYPE_VALUE: + /* "value, " */ + len = 2 + strlen(node->value); + break; + case NODE_TYPE_BLOCK: + case NODE_TYPE_LIST: + /* "{ list }; " */ + len = 6; + if (node->key != NULL) len += strlen(node->key); + len += config_block_get_length(rec, node); + break; + default: + /* comments always split the line */ + len = 1000; + break; + } + + return len; +} + +/* return the number of characters `node' and it's subnodes take + if written to file */ +static int config_block_get_length(CONFIG_REC *rec, CONFIG_NODE *node) +{ + GSList *tmp; + int len; + + len = 0; + for (tmp = node->value; tmp != NULL; tmp = tmp->next) { + CONFIG_NODE *subnode = tmp->data; + + len += config_node_get_length(rec, subnode); + if (len > MAX_CHARS_IN_LINE) return len; + } + + return len; +} + +/* check if `node' and it's subnodes fit in one line in the config file */ +static int config_block_fit_one_line(CONFIG_REC *rec, CONFIG_NODE *node) +{ + g_return_val_if_fail(rec != NULL, 0); + g_return_val_if_fail(node != NULL, 0); + + return rec->tmp_indent_level + + config_node_get_length(rec, node) <= MAX_CHARS_IN_LINE; +} + +static int config_write_block(CONFIG_REC *rec, CONFIG_NODE *node, int list, int line_feeds) +{ + GSList *tmp; + int list_line_feeds, node_line_feeds; + + g_return_val_if_fail(rec != NULL, -1); + g_return_val_if_fail(node != NULL, -1); + g_return_val_if_fail(is_node_list(node), -1); + + list_line_feeds = !config_block_fit_one_line(rec, node); + + if (!line_feeds && list_line_feeds) + config_write_str(rec, "\n"); + + for (tmp = node->value; tmp != NULL; tmp = tmp->next) { + CONFIG_NODE *subnode = tmp->data; + + node_line_feeds = !line_feeds ? FALSE : !config_block_fit_one_line(rec, subnode); + if (config_write_node(rec, subnode, node_line_feeds) == -1) + return -1; + + if (subnode->type == NODE_TYPE_COMMENT) + config_write_str(rec, "\n"); + else if (list) { + if (tmp->next != NULL) + config_write_str(rec, list_line_feeds ? ",\n" : ", "); + else + config_write_str(rec, list_line_feeds ? "\n" : " "); + } else { + config_write_str(rec, list_line_feeds ? ";\n" : "; "); + } + } + + return 0; +} + +/* Write configuration file. Write to `fname' if it's not NULL. */ +int config_write(CONFIG_REC *rec, const char *fname, int create_mode) +{ + g_return_val_if_fail(rec != NULL, -1); + g_return_val_if_fail(fname != NULL || rec->fname != NULL, -1); + g_return_val_if_fail(create_mode != -1 || rec->create_mode != -1, -1); + + rec->handle = open(fname != NULL ? fname : rec->fname, + O_WRONLY | O_TRUNC | O_CREAT, + create_mode != -1 ? create_mode : rec->create_mode); + if (rec->handle == -1) + return config_error(rec, g_strerror(errno)); + + rec->tmp_indent_level = 0; + rec->tmp_last_lf = TRUE; + if (config_write_block(rec, rec->mainnode, FALSE, TRUE) == -1) { + /* write error */ + config_error(rec, errno == 0 ? "bug" : g_strerror(errno)); + return -1; + } + write(rec->handle, "\n", 1); + + close(rec->handle); + rec->handle = -1; + + return 0; +} |