mirror of
https://github.com/irssi/irssi.git
synced 2026-08-22 18:12:12 +02:00
add support for including config files within config files.
you can include another file by using a path relative to the primary config location or an absolute path. the syntax is: include "file"; this is meant to provide a reasonble work around for issue #6, so that people can store and share their configuration file but exclude sensitive information.
This commit is contained in:
parent
308ee024f2
commit
a23ef4dc9c
6 changed files with 181 additions and 13 deletions
|
|
@ -33,6 +33,14 @@ CONFIG_NODE *config_node_find(CONFIG_NODE *parent, const char *key)
|
||||||
|
|
||||||
if (node->key != NULL && g_ascii_strcasecmp(node->key, key) == 0)
|
if (node->key != NULL && g_ascii_strcasecmp(node->key, key) == 0)
|
||||||
return node;
|
return node;
|
||||||
|
|
||||||
|
/* the primary children of an include node should also be searched */
|
||||||
|
if (node->type == NODE_TYPE_INCLUDE) {
|
||||||
|
CONFIG_INCLUDE *inc = node->value;
|
||||||
|
node = config_node_find(inc->rec->mainnode, key);
|
||||||
|
if (node)
|
||||||
|
return node;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,26 @@
|
||||||
#define __ICONFIG_H
|
#define __ICONFIG_H
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
NODE_TYPE_KEY,
|
NODE_TYPE_KEY,
|
||||||
NODE_TYPE_VALUE,
|
NODE_TYPE_VALUE,
|
||||||
NODE_TYPE_BLOCK,
|
NODE_TYPE_INCLUDE,
|
||||||
NODE_TYPE_LIST,
|
NODE_TYPE_BLOCK,
|
||||||
NODE_TYPE_COMMENT
|
NODE_TYPE_LIST,
|
||||||
|
NODE_TYPE_COMMENT,
|
||||||
|
|
||||||
|
NODE_TYPE_COUNT
|
||||||
};
|
};
|
||||||
|
|
||||||
#define has_node_value(a) \
|
#define has_node_value(a) \
|
||||||
((a)->type == NODE_TYPE_KEY || (a)->type == NODE_TYPE_VALUE)
|
((a)->type == NODE_TYPE_KEY || (a)->type == NODE_TYPE_VALUE || \
|
||||||
|
(a)->type == NODE_TYPE_INCLUDE)
|
||||||
|
|
||||||
#define is_node_list(a) \
|
#define is_node_list(a) \
|
||||||
((a)->type == NODE_TYPE_BLOCK || (a)->type == NODE_TYPE_LIST)
|
((a)->type == NODE_TYPE_BLOCK || (a)->type == NODE_TYPE_LIST)
|
||||||
|
|
||||||
typedef struct _CONFIG_NODE CONFIG_NODE;
|
typedef struct _CONFIG_NODE CONFIG_NODE;
|
||||||
typedef struct _CONFIG_REC CONFIG_REC;
|
typedef struct _CONFIG_REC CONFIG_REC;
|
||||||
|
typedef struct _CONFIG_INCLUDE CONFIG_INCLUDE;
|
||||||
|
|
||||||
struct _CONFIG_NODE {
|
struct _CONFIG_NODE {
|
||||||
int type;
|
int type;
|
||||||
|
|
@ -23,6 +29,13 @@ struct _CONFIG_NODE {
|
||||||
void *value;
|
void *value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct _CONFIG_INCLUDE {
|
||||||
|
/* the user probably specified a relative path from the root config, store
|
||||||
|
it for writing back, but set the rec->fname to the absolute path */
|
||||||
|
char *original_path;
|
||||||
|
CONFIG_REC *rec;
|
||||||
|
};
|
||||||
|
|
||||||
/* a = { x=y; y=z; }
|
/* a = { x=y; y=z; }
|
||||||
|
|
||||||
node1: type = NODE_TYPE_BLOCK, key = "a", value = (GSList *) nodes
|
node1: type = NODE_TYPE_BLOCK, key = "a", value = (GSList *) nodes
|
||||||
|
|
@ -44,12 +57,27 @@ struct _CONFIG_NODE {
|
||||||
also in comments so they won't be forgotten when the config file is
|
also in comments so they won't be forgotten when the config file is
|
||||||
written.
|
written.
|
||||||
|
|
||||||
|
Include nodes have key=NULL, the path value is stored in CONFIG_INCLUDE's
|
||||||
|
original_path member as it's usually converted to an absolute path. All of
|
||||||
|
the include node's modifications and caches are stored in the 'root' config
|
||||||
|
and a write of the root config causes all included configs to be written
|
||||||
|
whether a modification has been made in them or not. A list of included
|
||||||
|
file names is maintained in the root rec to prevent circular inclusions.
|
||||||
|
It could eventually be better to keep a list of GFiles and use g_file_equal
|
||||||
|
for the comparison, but right now it doesn't even cover symbolic links.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
struct _CONFIG_REC {
|
struct _CONFIG_REC {
|
||||||
|
/* used for updating the root config's modifycounter from included configs */
|
||||||
|
CONFIG_REC *root_rec;
|
||||||
|
/* this list is only maintained in the root_rec, it's to prevent circular
|
||||||
|
inclusions */
|
||||||
|
GHashTable *includes;
|
||||||
char *fname;
|
char *fname;
|
||||||
int create_mode;
|
int create_mode;
|
||||||
int modifycounter; /* increase every time something is changed */
|
/* use config_rec_increase_modifycounter every time something changes */
|
||||||
|
int modifycounter;
|
||||||
|
|
||||||
char *last_error;
|
char *last_error;
|
||||||
CONFIG_NODE *mainnode;
|
CONFIG_NODE *mainnode;
|
||||||
|
|
@ -144,6 +172,9 @@ int config_node_get_bool(CONFIG_NODE *parent, const char *key, int def);
|
||||||
void config_node_set_str(CONFIG_REC *rec, CONFIG_NODE *parent, const char *key, const char *value);
|
void config_node_set_str(CONFIG_REC *rec, CONFIG_NODE *parent, const char *key, const char *value);
|
||||||
void config_node_set_int(CONFIG_REC *rec, CONFIG_NODE *parent, const char *key, int value);
|
void config_node_set_int(CONFIG_REC *rec, CONFIG_NODE *parent, const char *key, int value);
|
||||||
void config_node_set_bool(CONFIG_REC *rec, CONFIG_NODE *parent, const char *key, int value);
|
void config_node_set_bool(CONFIG_REC *rec, CONFIG_NODE *parent, const char *key, int value);
|
||||||
|
/* if a relative path is specified then you can access the absolute path using
|
||||||
|
node->value->fname. the key is set to the specified include path */
|
||||||
|
void config_node_set_include(CONFIG_REC *rec, CONFIG_NODE *parent, const char *fname);
|
||||||
|
|
||||||
/* Remove one node from block/list. */
|
/* Remove one node from block/list. */
|
||||||
void config_node_remove(CONFIG_REC *rec, CONFIG_NODE *parent, CONFIG_NODE *node);
|
void config_node_remove(CONFIG_REC *rec, CONFIG_NODE *parent, CONFIG_NODE *node);
|
||||||
|
|
|
||||||
|
|
@ -2,5 +2,5 @@
|
||||||
#include "iconfig.h"
|
#include "iconfig.h"
|
||||||
|
|
||||||
/* private */
|
/* private */
|
||||||
int config_error(CONFIG_REC *rec, const char *msg);
|
int config_error(CONFIG_REC *rec, const char *msg, ...);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,10 +43,16 @@ static unsigned int g_istr_hash(gconstpointer v)
|
||||||
return h /* % M */;
|
return h /* % M */;
|
||||||
}
|
}
|
||||||
|
|
||||||
int config_error(CONFIG_REC *rec, const char *msg)
|
int config_error(CONFIG_REC *rec, const char *msg, ...)
|
||||||
{
|
{
|
||||||
|
va_list vl;
|
||||||
|
|
||||||
g_free_and_null(rec->last_error);
|
g_free_and_null(rec->last_error);
|
||||||
rec->last_error = g_strdup(msg);
|
|
||||||
|
va_start(vl, msg);
|
||||||
|
rec->last_error = g_strdup_vprintf(msg, vl);
|
||||||
|
va_end(vl);
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -133,6 +139,7 @@ static GTokenType config_parse_symbol(CONFIG_REC *rec, CONFIG_NODE *node)
|
||||||
GTokenType last_char;
|
GTokenType last_char;
|
||||||
int print_warning;
|
int print_warning;
|
||||||
char *key;
|
char *key;
|
||||||
|
int key_is_include = FALSE;
|
||||||
|
|
||||||
g_return_val_if_fail(rec != NULL, G_TOKEN_ERROR);
|
g_return_val_if_fail(rec != NULL, G_TOKEN_ERROR);
|
||||||
g_return_val_if_fail(node != NULL, G_TOKEN_ERROR);
|
g_return_val_if_fail(node != NULL, G_TOKEN_ERROR);
|
||||||
|
|
@ -147,12 +154,23 @@ static GTokenType config_parse_symbol(CONFIG_REC *rec, CONFIG_NODE *node)
|
||||||
(rec->scanner->token == G_TOKEN_STRING)) {
|
(rec->scanner->token == G_TOKEN_STRING)) {
|
||||||
key = g_strdup(rec->scanner->value.v_string);
|
key = g_strdup(rec->scanner->value.v_string);
|
||||||
|
|
||||||
config_parse_warn_missing(rec, node, '=', TRUE);
|
if (g_ascii_strcasecmp(key, "include") == 0)
|
||||||
|
key_is_include = TRUE;
|
||||||
|
else
|
||||||
|
config_parse_warn_missing(rec, node, '=', TRUE);
|
||||||
|
|
||||||
config_parse_get_token(rec->scanner, node);
|
config_parse_get_token(rec->scanner, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (rec->scanner->token) {
|
switch (rec->scanner->token) {
|
||||||
case G_TOKEN_STRING:
|
case G_TOKEN_STRING:
|
||||||
|
if (key_is_include) {
|
||||||
|
g_free_not_null(key);
|
||||||
|
config_node_set_include(rec, node, rec->scanner->value.v_string);
|
||||||
|
config_parse_warn_missing(rec, node, last_char, TRUE);
|
||||||
|
return G_TOKEN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
/* value */
|
/* value */
|
||||||
config_node_set_str(rec, node, key, rec->scanner->value.v_string);
|
config_node_set_str(rec, node, key, rec->scanner->value.v_string);
|
||||||
g_free_not_null(key);
|
g_free_not_null(key);
|
||||||
|
|
@ -332,6 +350,8 @@ void config_close(CONFIG_REC *rec)
|
||||||
g_hash_table_destroy(rec->cache_nodes);
|
g_hash_table_destroy(rec->cache_nodes);
|
||||||
g_free_not_null(rec->last_error);
|
g_free_not_null(rec->last_error);
|
||||||
g_free_not_null(rec->fname);
|
g_free_not_null(rec->fname);
|
||||||
|
if (rec->includes)
|
||||||
|
g_hash_table_destroy(rec->includes);
|
||||||
g_free(rec);
|
g_free(rec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,15 @@
|
||||||
|
|
||||||
#include "module.h"
|
#include "module.h"
|
||||||
|
|
||||||
|
static void config_rec_increase_modifycounter(CONFIG_REC *rec)
|
||||||
|
{
|
||||||
|
g_return_if_fail(rec != NULL);
|
||||||
|
|
||||||
|
/* handle CONFIG_RECs which are includes of other configs */
|
||||||
|
if (rec->root_rec)
|
||||||
|
rec->root_rec->modifycounter++;
|
||||||
|
}
|
||||||
|
|
||||||
static void cache_remove(CONFIG_REC *rec, CONFIG_NODE *node)
|
static void cache_remove(CONFIG_REC *rec, CONFIG_NODE *node)
|
||||||
{
|
{
|
||||||
char *path;
|
char *path;
|
||||||
|
|
@ -39,7 +48,7 @@ void config_node_remove(CONFIG_REC *rec, CONFIG_NODE *parent, CONFIG_NODE *node)
|
||||||
if (parent == NULL)
|
if (parent == NULL)
|
||||||
parent = rec->mainnode;
|
parent = rec->mainnode;
|
||||||
|
|
||||||
rec->modifycounter++;
|
config_rec_increase_modifycounter(rec);
|
||||||
cache_remove(rec, node);
|
cache_remove(rec, node);
|
||||||
parent->value = g_slist_remove(parent->value, node);
|
parent->value = g_slist_remove(parent->value, node);
|
||||||
|
|
||||||
|
|
@ -49,6 +58,16 @@ void config_node_remove(CONFIG_REC *rec, CONFIG_NODE *parent, CONFIG_NODE *node)
|
||||||
case NODE_TYPE_COMMENT:
|
case NODE_TYPE_COMMENT:
|
||||||
g_free_not_null(node->value);
|
g_free_not_null(node->value);
|
||||||
break;
|
break;
|
||||||
|
case NODE_TYPE_INCLUDE: {
|
||||||
|
CONFIG_INCLUDE *inc = node->value;
|
||||||
|
if (inc) {
|
||||||
|
g_hash_table_remove(inc->rec->root_rec->includes, inc->original_path);
|
||||||
|
config_close(inc->rec);
|
||||||
|
g_free_not_null(inc->original_path);
|
||||||
|
g_free(inc);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case NODE_TYPE_BLOCK:
|
case NODE_TYPE_BLOCK:
|
||||||
case NODE_TYPE_LIST:
|
case NODE_TYPE_LIST:
|
||||||
while (node->value != NULL)
|
while (node->value != NULL)
|
||||||
|
|
@ -87,6 +106,79 @@ void config_nodes_remove_all(CONFIG_REC *rec)
|
||||||
config_node_remove(rec, rec->mainnode, ((GSList *) rec->mainnode->value)->data);
|
config_node_remove(rec, rec->mainnode, ((GSList *) rec->mainnode->value)->data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void config_node_set_include(CONFIG_REC *rec, CONFIG_NODE *parent, const char *fname)
|
||||||
|
{
|
||||||
|
CONFIG_NODE *node;
|
||||||
|
CONFIG_INCLUDE *inc;
|
||||||
|
CONFIG_REC *root_rec;
|
||||||
|
char *full_path = NULL;
|
||||||
|
|
||||||
|
g_return_if_fail(rec != NULL);
|
||||||
|
g_return_if_fail(parent != NULL);
|
||||||
|
|
||||||
|
if (!fname || !*fname) {
|
||||||
|
config_error(rec, "Included filename is empty.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* if the parent rec has no root_rec, it is the root */
|
||||||
|
root_rec = rec->root_rec ? rec->root_rec : rec;
|
||||||
|
if (!root_rec->includes) {
|
||||||
|
root_rec->includes = g_hash_table_new_full(g_str_hash, g_str_equal,
|
||||||
|
g_free, NULL);
|
||||||
|
if (!root_rec->includes)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_hash_table_contains(root_rec->includes, fname)) {
|
||||||
|
config_error(rec, "'%s' has already been included.\n", fname);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!g_path_is_absolute(fname)) {
|
||||||
|
gchar *dirname = g_path_get_dirname(rec->fname);
|
||||||
|
if (!dirname) {
|
||||||
|
config_error(rec, "g_path_get_dirname failed.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
full_path = g_build_filename(dirname, fname, NULL);
|
||||||
|
g_free(dirname);
|
||||||
|
}
|
||||||
|
|
||||||
|
node = g_new0(CONFIG_NODE, 1);
|
||||||
|
if (!node) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
node->type = NODE_TYPE_INCLUDE;
|
||||||
|
|
||||||
|
inc = g_new0(CONFIG_INCLUDE, 1);
|
||||||
|
if (!inc) {
|
||||||
|
config_node_remove(rec, parent, node);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
node->value = inc;
|
||||||
|
inc->original_path = g_strdup(fname);
|
||||||
|
|
||||||
|
inc->rec = config_open(full_path ? full_path : fname, 0660);
|
||||||
|
if (!inc->rec) {
|
||||||
|
config_error(rec, "Unable to open '%s': %s.\n", fname,
|
||||||
|
strerror(errno));
|
||||||
|
config_node_remove(rec, parent, node);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
g_free_not_null(full_path);
|
||||||
|
inc->rec->root_rec = root_rec;
|
||||||
|
g_hash_table_add(root_rec->includes, g_strdup(fname));
|
||||||
|
|
||||||
|
if (config_parse(inc->rec) != 0) {
|
||||||
|
config_node_remove(rec, parent, node);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent->value = g_slist_append(parent->value, node);
|
||||||
|
config_rec_increase_modifycounter(rec);
|
||||||
|
}
|
||||||
|
|
||||||
void config_node_set_str(CONFIG_REC *rec, CONFIG_NODE *parent, const char *key, const char *value)
|
void config_node_set_str(CONFIG_REC *rec, CONFIG_NODE *parent, const char *key, const char *value)
|
||||||
{
|
{
|
||||||
CONFIG_NODE *node;
|
CONFIG_NODE *node;
|
||||||
|
|
@ -123,7 +215,7 @@ void config_node_set_str(CONFIG_REC *rec, CONFIG_NODE *parent, const char *key,
|
||||||
}
|
}
|
||||||
|
|
||||||
node->value = g_strdup(value);
|
node->value = g_strdup(value);
|
||||||
rec->modifycounter++;
|
config_rec_increase_modifycounter(rec);
|
||||||
}
|
}
|
||||||
|
|
||||||
void config_node_set_int(CONFIG_REC *rec, CONFIG_NODE *parent, const char *key, int value)
|
void config_node_set_int(CONFIG_REC *rec, CONFIG_NODE *parent, const char *key, int value)
|
||||||
|
|
|
||||||
|
|
@ -149,6 +149,18 @@ static int config_write_node(CONFIG_REC *rec, CONFIG_NODE *node, int line_feeds)
|
||||||
if (config_write_word(rec, node->value, TRUE) == -1)
|
if (config_write_word(rec, node->value, TRUE) == -1)
|
||||||
return -1;
|
return -1;
|
||||||
break;
|
break;
|
||||||
|
case NODE_TYPE_INCLUDE: {
|
||||||
|
CONFIG_INCLUDE *inc = node->value;
|
||||||
|
if (config_write_str(rec, "include ") == -1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (config_write_word(rec, inc->original_path, TRUE) == -1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
config_write(inc->rec, NULL, -1);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
case NODE_TYPE_BLOCK:
|
case NODE_TYPE_BLOCK:
|
||||||
/* key = { */
|
/* key = { */
|
||||||
if (node->key != NULL) {
|
if (node->key != NULL) {
|
||||||
|
|
@ -217,6 +229,11 @@ static int config_node_get_length(CONFIG_REC *rec, CONFIG_NODE *node)
|
||||||
/* "value, " */
|
/* "value, " */
|
||||||
len = 2 + strlen(node->value);
|
len = 2 + strlen(node->value);
|
||||||
break;
|
break;
|
||||||
|
case NODE_TYPE_INCLUDE: {
|
||||||
|
CONFIG_INCLUDE *inc = node->value;
|
||||||
|
len = strlen("include \"\"") + strlen(inc->rec->fname);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case NODE_TYPE_BLOCK:
|
case NODE_TYPE_BLOCK:
|
||||||
case NODE_TYPE_LIST:
|
case NODE_TYPE_LIST:
|
||||||
/* "{ list }; " */
|
/* "{ list }; " */
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue