Skip to content

Commit

Permalink
config: implement include directive
Browse files Browse the repository at this point in the history
Interpret the "include" property as a directive to parse the
configuration file described as the value. This mechanism is similar
in behavior to the preprocessor #include directive.

For this to work, the config parsing code changes its working directory
to the directory containing the configuration file being included,
and stores its path in a new "workdir" property, whenever a "command"
property is being read. This allows the block code to change directory
when a command is spawned, and thus allow command scripts relative
to their included configuration fragment to work as expected.

Note that include loop is detected, in which case an error is thrown.

If the include path starts with "~/", tilde is replaced with the
value of the HOME environment variable, if defined.

Below is an example of config file using the "include" directive:

    # ~/.config/i3blocks/config
    separator=true
    separator_block_width=15

    include=contrib/greetings/config

    # ~/.config/i3blocks/contrib/greetings/config
    [greetings]
    command=echo "Hello, $USER!"
    interval=once

Refs #453
  • Loading branch information
vivien committed Jan 13, 2023
1 parent fde6d3c commit 1bf9649
Showing 1 changed file with 124 additions and 71 deletions.
195 changes: 124 additions & 71 deletions config.c
Expand Up @@ -20,6 +20,7 @@
#include <limits.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>

#include "config.h"
#include "ini.h"
Expand All @@ -32,120 +33,177 @@
#endif

struct config {
struct map *section;
struct map *global;
struct map *includes;
struct map *defaults;
struct map *instance;
config_cb_t *cb;
void *data;
char workdir[PATH_MAX];
};

static int config_finalize(struct config *conf)
static int config_include(struct config *conf, const char *path);

static int config_ini_section_cb(char *section, void *data)
{
struct config *conf = data;
int err;

if (conf->section) {
/* flush previous section */
if (!map_empty(conf->instance)) {
if (conf->cb) {
err = conf->cb(conf->section, conf->data);
err = conf->cb(conf->instance, conf->data);
if (err)
return err;
}

conf->section = NULL;
map_clear(conf->instance);
}

return 0;
err = map_copy(conf->instance, conf->defaults);
if (err)
return err;

return map_set(conf->instance, "name", section);
}

static int config_reset(struct config *conf)
static int config_ini_property_cb(char *key, char *value, void *data)
{
conf->section = map_create();
if (!conf->section)
return -ENOMEM;

if (conf->global)
return map_copy(conf->section, conf->global);
struct config *conf = data;
char cwd[PATH_MAX];
struct map *map;
int err;

return 0;
}
if (strcmp(key, "include") == 0) {
err = config_include(conf, value);
if (err == -ENOENT)
err = -EINVAL;

static int config_set(struct config *conf, const char *key, const char *value)
{
struct map *map = conf->section;
return err;
}

if (!map) {
if (!conf->global) {
conf->global = map_create();
if (!conf->global)
return -ENOMEM;
}
map = map_empty(conf->instance) ? conf->defaults : conf->instance;

map = conf->global;
if (strcmp(key, "command") == 0) {
err = map_set(map, "workdir", conf->workdir);
if (err)
return err;
}

return map_set(map, key, value);
}

static int config_ini_section_cb(char *section, void *data)
static int config_include(struct config *conf, const char *path)
{
const char * const home = sys_getenv("HOME");
char includepath[PATH_MAX];
char basepath[PATH_MAX];
char dirpath[PATH_MAX];
char cwd[PATH_MAX];
char *base;
char *dir;
int err;
int fd;

err = config_finalize(data);
err = sys_getcwd(cwd, sizeof(cwd));
if (err)
return err;

err = config_reset(data);
if (strncmp(path, "~/", 2) == 0 && home)
snprintf(includepath, sizeof(includepath), "%s/%s", home, path + 2);
else
snprintf(includepath, sizeof(includepath), "%s", path);

strcpy(dirpath, includepath);
dir = dirname(dirpath);

strcpy(basepath, includepath);
base = basename(basepath);

err = sys_chdir(dir);
if (err)
return err;

return config_set(data, "name", section);
}
err = sys_getcwd(conf->workdir, sizeof(conf->workdir));
if (err)
return err;

static int config_ini_property_cb(char *key, char *value, void *data)
{
return config_set(data, key, value);
}
snprintf(includepath, sizeof(includepath), "%s/%s", conf->workdir, base);

static int config_read(struct config *conf, int fd)
{
int err;
if (map_get(conf->includes, includepath)) {
error("include loop detected for %s", includepath);
return -ELOOP;
}

err = map_set(conf->includes, includepath, includepath);
if (err)
return err;

debug("including %s", includepath);

err = sys_open(base, &fd);
if (err)
return err;

err = ini_read(fd, -1, config_ini_section_cb, config_ini_property_cb, conf);

sys_close(fd);

err = ini_read(fd, -1, config_ini_section_cb, config_ini_property_cb,
conf);
if (err && err != -EAGAIN)
return err;

return config_finalize(conf);
err = map_set(conf->includes, includepath, NULL);
if (err)
return err;

err = sys_chdir(cwd);
if (err)
return err;

strcpy(conf->workdir, cwd);

return 0;
}

static int config_open(struct config *conf, const char *path)
static int config_parse(const char *path, config_cb_t *cb, void *data)
{
size_t plen = strlen(path);
char pname[plen + 1];
char *dname;
struct config conf = { 0 };
int err;
int fd;

debug("try file %s", path);
conf.cb = cb;
conf.data = data;

err = sys_open(path, &fd);
if (err)
return err;
conf.includes = map_create();
if (!conf.includes)
return -ENOMEM;

strcpy(pname, path);
dname = dirname(pname);
err = sys_chdir(dname);
if (err) {
error("failed to change directory to %s", dname);
return err;
conf.defaults = map_create();
if (!conf.defaults) {
map_destroy(conf.includes);
return -ENOMEM;
}

debug("changed directory to %s", dname);
conf.instance = map_create();
if (!conf.instance) {
map_destroy(conf.includes);
map_destroy(conf.defaults);
return -ENOMEM;
}

err = config_read(conf, fd);
sys_close(fd);
err = config_include(&conf, path);
if (err) {
map_destroy(conf.includes);
map_destroy(conf.defaults);
map_destroy(conf.instance);
return err;
}

if (conf->global)
map_destroy(conf->global);
/* flush last section */
if (!map_empty(conf.instance) && conf.cb)
err = conf.cb(conf.instance, conf.data);

map_destroy(conf.includes);
map_destroy(conf.defaults);
map_destroy(conf.instance);
return err;
}

Expand All @@ -154,30 +212,25 @@ int config_load(const char *path, config_cb_t *cb, void *data)
const char * const home = sys_getenv("HOME");
const char * const xdg_home = sys_getenv("XDG_CONFIG_HOME");
const char * const xdg_dirs = sys_getenv("XDG_CONFIG_DIRS");
struct config conf = {
.data = data,
.cb = cb,
};
char buf[PATH_MAX];
int err;


/* command line config file? */
if (path)
return config_open(&conf, path);
return config_parse(path, cb, data);

/* user config file? */
if (home) {
if (xdg_home)
snprintf(buf, sizeof(buf), "%s/i3blocks/config", xdg_home);
else
snprintf(buf, sizeof(buf), "%s/.config/i3blocks/config", home);
err = config_open(&conf, buf);
err = config_parse(buf, cb, data);
if (err != -ENOENT)
return err;

snprintf(buf, sizeof(buf), "%s/.i3blocks.conf", home);
err = config_open(&conf, buf);
err = config_parse(buf, cb, data);
if (err != -ENOENT)
return err;
}
Expand All @@ -187,10 +240,10 @@ int config_load(const char *path, config_cb_t *cb, void *data)
snprintf(buf, sizeof(buf), "%s/i3blocks/config", xdg_dirs);
else
snprintf(buf, sizeof(buf), "%s/xdg/i3blocks/config", SYSCONFDIR);
err = config_open(&conf, buf);
err = config_parse(buf, cb, data);
if (err != -ENOENT)
return err;

snprintf(buf, sizeof(buf), "%s/i3blocks.conf", SYSCONFDIR);
return config_open(&conf, buf);
return config_parse(buf, cb, data);
}

0 comments on commit 1bf9649

Please sign in to comment.