You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
566 lines
16 KiB
566 lines
16 KiB
/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
|
|
|
|
#include "option_parser.h"
|
|
|
|
#include <glib.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "utils.h"
|
|
|
|
typedef struct _entry_t {
|
|
char *key;
|
|
char *value;
|
|
} entry_t;
|
|
|
|
typedef struct _section_t {
|
|
char *name;
|
|
int entry_count;
|
|
entry_t *entries;
|
|
} section_t;
|
|
|
|
static int section_count = 0;
|
|
static section_t *sections;
|
|
|
|
static section_t *new_section(const char *name);
|
|
static section_t *get_section(const char *name);
|
|
static void add_entry(const char *section_name, const char *key, const char *value);
|
|
static const char *get_value(const char *section, const char *key);
|
|
static char *clean_value(const char *value);
|
|
|
|
static int cmdline_argc;
|
|
static char **cmdline_argv;
|
|
|
|
static char *usage_str = NULL;
|
|
static void cmdline_usage_append(const char *key, const char *type, const char *description);
|
|
|
|
static int cmdline_find_option(const char *key);
|
|
|
|
section_t *new_section(const char *name)
|
|
{
|
|
for (int i = 0; i < section_count; i++) {
|
|
if (!strcmp(name, sections[i].name)) {
|
|
die("Duplicated section in dunstrc detected.\n", -1);
|
|
}
|
|
}
|
|
|
|
section_count++;
|
|
sections = g_realloc(sections, sizeof(section_t) * section_count);
|
|
sections[section_count - 1].name = g_strdup(name);
|
|
sections[section_count - 1].entries = NULL;
|
|
sections[section_count - 1].entry_count = 0;
|
|
return §ions[section_count - 1];
|
|
}
|
|
|
|
void free_ini(void)
|
|
{
|
|
for (int i = 0; i < section_count; i++) {
|
|
for (int j = 0; j < sections[i].entry_count; j++) {
|
|
g_free(sections[i].entries[j].key);
|
|
g_free(sections[i].entries[j].value);
|
|
}
|
|
g_free(sections[i].entries);
|
|
g_free(sections[i].name);
|
|
}
|
|
g_free(sections);
|
|
section_count = 0;
|
|
sections = NULL;
|
|
}
|
|
|
|
section_t *get_section(const char *name)
|
|
{
|
|
for (int i = 0; i < section_count; i++) {
|
|
if (strcmp(sections[i].name, name) == 0)
|
|
return §ions[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void add_entry(const char *section_name, const char *key, const char *value)
|
|
{
|
|
section_t *s = get_section(section_name);
|
|
if (s == NULL) {
|
|
s = new_section(section_name);
|
|
}
|
|
|
|
s->entry_count++;
|
|
int len = s->entry_count;
|
|
s->entries = g_realloc(s->entries, sizeof(entry_t) * len);
|
|
s->entries[s->entry_count - 1].key = g_strdup(key);
|
|
s->entries[s->entry_count - 1].value = clean_value(value);
|
|
}
|
|
|
|
const char *get_value(const char *section, const char *key)
|
|
{
|
|
section_t *s = get_section(section);
|
|
if (!s) {
|
|
return NULL;
|
|
}
|
|
|
|
for (int i = 0; i < s->entry_count; i++) {
|
|
if (strcmp(s->entries[i].key, key) == 0) {
|
|
return s->entries[i].value;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
char *ini_get_path(const char *section, const char *key, const char *def)
|
|
{
|
|
return string_to_path(ini_get_string(section, key, def));
|
|
}
|
|
|
|
char *ini_get_string(const char *section, const char *key, const char *def)
|
|
{
|
|
const char *value = get_value(section, key);
|
|
if (value)
|
|
return g_strdup(value);
|
|
|
|
return def ? g_strdup(def) : NULL;
|
|
}
|
|
|
|
gint64 ini_get_time(const char *section, const char *key, gint64 def)
|
|
{
|
|
const char *timestring = get_value(section, key);
|
|
gint64 val = def;
|
|
|
|
if (timestring) {
|
|
val = string_to_time(timestring);
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
int ini_get_int(const char *section, const char *key, int def)
|
|
{
|
|
const char *value = get_value(section, key);
|
|
if (value == NULL)
|
|
return def;
|
|
else
|
|
return atoi(value);
|
|
}
|
|
|
|
double ini_get_double(const char *section, const char *key, double def)
|
|
{
|
|
const char *value = get_value(section, key);
|
|
if (value == NULL)
|
|
return def;
|
|
else
|
|
return atof(value);
|
|
}
|
|
|
|
bool ini_is_set(const char *ini_section, const char *ini_key)
|
|
{
|
|
return get_value(ini_section, ini_key) != NULL;
|
|
}
|
|
|
|
const char *next_section(const char *section)
|
|
{
|
|
if (section_count == 0)
|
|
return NULL;
|
|
|
|
if (section == NULL) {
|
|
return sections[0].name;
|
|
}
|
|
|
|
for (int i = 0; i < section_count; i++) {
|
|
if (strcmp(section, sections[i].name) == 0) {
|
|
if (i + 1 >= section_count)
|
|
return NULL;
|
|
else
|
|
return sections[i + 1].name;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int ini_get_bool(const char *section, const char *key, int def)
|
|
{
|
|
const char *value = get_value(section, key);
|
|
if (value == NULL)
|
|
return def;
|
|
else {
|
|
switch (value[0]) {
|
|
case 'y':
|
|
case 'Y':
|
|
case 't':
|
|
case 'T':
|
|
case '1':
|
|
return true;
|
|
case 'n':
|
|
case 'N':
|
|
case 'f':
|
|
case 'F':
|
|
case '0':
|
|
return false;
|
|
default:
|
|
return def;
|
|
}
|
|
}
|
|
}
|
|
|
|
char *clean_value(const char *value)
|
|
{
|
|
char *s;
|
|
|
|
if (value[0] == '"')
|
|
s = g_strdup(value + 1);
|
|
else
|
|
s = g_strdup(value);
|
|
|
|
if (s[strlen(s) - 1] == '"')
|
|
s[strlen(s) - 1] = '\0';
|
|
|
|
return s;
|
|
}
|
|
|
|
int load_ini_file(FILE *fp)
|
|
{
|
|
if (!fp)
|
|
return 1;
|
|
|
|
char *line = NULL;
|
|
size_t line_len = 0;
|
|
|
|
int line_num = 0;
|
|
char *current_section = NULL;
|
|
while (getline(&line, &line_len, fp) != -1) {
|
|
line_num++;
|
|
|
|
char *start = g_strstrip(line);
|
|
|
|
if (*start == ';' || *start == '#' || strlen(start) == 0)
|
|
continue;
|
|
|
|
if (*start == '[') {
|
|
char *end = strchr(start + 1, ']');
|
|
if (!end) {
|
|
fprintf(stderr,
|
|
"Warning: invalid config file at line %d\n",
|
|
line_num);
|
|
fprintf(stderr, "Missing ']'\n");
|
|
continue;
|
|
}
|
|
|
|
*end = '\0';
|
|
|
|
g_free(current_section);
|
|
current_section = (g_strdup(start + 1));
|
|
new_section(current_section);
|
|
continue;
|
|
}
|
|
|
|
char *equal = strchr(start + 1, '=');
|
|
if (!equal) {
|
|
fprintf(stderr,
|
|
"Warning: invalid config file at line %d\n",
|
|
line_num);
|
|
fprintf(stderr, "Missing '='\n");
|
|
continue;
|
|
}
|
|
|
|
*equal = '\0';
|
|
char *key = g_strstrip(start);
|
|
char *value = g_strstrip(equal + 1);
|
|
|
|
char *quote = strchr(value, '"');
|
|
if (quote) {
|
|
char *closing_quote = strchr(quote + 1, '"');
|
|
if (!closing_quote) {
|
|
fprintf(stderr,
|
|
"Warning: invalid config file at line %d\n",
|
|
line_num);
|
|
fprintf(stderr, "Missing '\"'\n");
|
|
continue;
|
|
}
|
|
} else {
|
|
char *comment = strpbrk(value, "#;");
|
|
if (comment)
|
|
*comment = '\0';
|
|
}
|
|
value = g_strstrip(value);
|
|
|
|
if (!current_section) {
|
|
fprintf(stderr,
|
|
"Warning: invalid config file at line %d\n",
|
|
line_num);
|
|
fprintf(stderr, "Key value pair without a section\n");
|
|
continue;
|
|
}
|
|
|
|
add_entry(current_section, key, value);
|
|
}
|
|
free(line);
|
|
g_free(current_section);
|
|
return 0;
|
|
}
|
|
|
|
void cmdline_load(int argc, char *argv[])
|
|
{
|
|
cmdline_argc = argc;
|
|
cmdline_argv = argv;
|
|
}
|
|
|
|
int cmdline_find_option(const char *key)
|
|
{
|
|
if (!key) {
|
|
return -1;
|
|
}
|
|
char *key1 = g_strdup(key);
|
|
char *key2 = strchr(key1, '/');
|
|
|
|
if (key2) {
|
|
*key2 = '\0';
|
|
key2++;
|
|
}
|
|
|
|
/* look for first key */
|
|
for (int i = 0; i < cmdline_argc; i++) {
|
|
if (strcmp(key1, cmdline_argv[i]) == 0) {
|
|
g_free(key1);
|
|
return i;
|
|
}
|
|
}
|
|
|
|
/* look for second key if one was specified */
|
|
if (key2) {
|
|
for (int i = 0; i < cmdline_argc; i++) {
|
|
if (strcmp(key2, cmdline_argv[i]) == 0) {
|
|
g_free(key1);
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
|
|
g_free(key1);
|
|
return -1;
|
|
}
|
|
|
|
static const char *cmdline_get_value(const char *key)
|
|
{
|
|
int idx = cmdline_find_option(key);
|
|
if (idx < 0) {
|
|
return NULL;
|
|
}
|
|
|
|
if (idx + 1 >= cmdline_argc) {
|
|
/* the argument is missing */
|
|
fprintf(stderr, "Warning: %s, missing argument. Ignoring\n",
|
|
key);
|
|
return NULL;
|
|
}
|
|
return cmdline_argv[idx + 1];
|
|
}
|
|
|
|
char *cmdline_get_string(const char *key, const char *def, const char *description)
|
|
{
|
|
cmdline_usage_append(key, "string", description);
|
|
const char *str = cmdline_get_value(key);
|
|
|
|
if (str)
|
|
return g_strdup(str);
|
|
if (def == NULL)
|
|
return NULL;
|
|
else
|
|
return g_strdup(def);
|
|
}
|
|
|
|
char *cmdline_get_path(const char *key, const char *def, const char *description)
|
|
{
|
|
cmdline_usage_append(key, "string", description);
|
|
const char *str = cmdline_get_value(key);
|
|
|
|
if (str)
|
|
return string_to_path(g_strdup(str));
|
|
else
|
|
return string_to_path(g_strdup(def));
|
|
}
|
|
|
|
gint64 cmdline_get_time(const char *key, gint64 def, const char *description)
|
|
{
|
|
cmdline_usage_append(key, "time", description);
|
|
const char *timestring = cmdline_get_value(key);
|
|
gint64 val = def;
|
|
|
|
if (timestring) {
|
|
val = string_to_time(timestring);
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
int cmdline_get_int(const char *key, int def, const char *description)
|
|
{
|
|
cmdline_usage_append(key, "int", description);
|
|
const char *str = cmdline_get_value(key);
|
|
|
|
if (str == NULL)
|
|
return def;
|
|
else
|
|
return atoi(str);
|
|
}
|
|
|
|
double cmdline_get_double(const char *key, double def, const char *description)
|
|
{
|
|
cmdline_usage_append(key, "double", description);
|
|
const char *str = cmdline_get_value(key);
|
|
|
|
if (str == NULL)
|
|
return def;
|
|
else
|
|
return atof(str);
|
|
}
|
|
|
|
int cmdline_get_bool(const char *key, int def, const char *description)
|
|
{
|
|
cmdline_usage_append(key, "", description);
|
|
int idx = cmdline_find_option(key);
|
|
|
|
if (idx > 0)
|
|
return true;
|
|
else
|
|
return def;
|
|
}
|
|
|
|
bool cmdline_is_set(const char *key)
|
|
{
|
|
return cmdline_get_value(key) != NULL;
|
|
}
|
|
|
|
char *option_get_path(const char *ini_section,
|
|
const char *ini_key,
|
|
const char *cmdline_key,
|
|
const char *def,
|
|
const char *description)
|
|
{
|
|
char *val = NULL;
|
|
|
|
if (cmdline_key) {
|
|
val = cmdline_get_path(cmdline_key, NULL, description);
|
|
}
|
|
|
|
if (val) {
|
|
return val;
|
|
} else {
|
|
return ini_get_path(ini_section, ini_key, def);
|
|
}
|
|
}
|
|
|
|
char *option_get_string(const char *ini_section,
|
|
const char *ini_key,
|
|
const char *cmdline_key,
|
|
const char *def,
|
|
const char *description)
|
|
{
|
|
char *val = NULL;
|
|
|
|
if (cmdline_key) {
|
|
val = cmdline_get_string(cmdline_key, NULL, description);
|
|
}
|
|
|
|
if (val) {
|
|
return val;
|
|
} else {
|
|
return ini_get_string(ini_section, ini_key, def);
|
|
}
|
|
}
|
|
|
|
gint64 option_get_time(const char *ini_section,
|
|
const char *ini_key,
|
|
const char *cmdline_key,
|
|
gint64 def,
|
|
const char *description)
|
|
{
|
|
gint64 ini_val = ini_get_time(ini_section, ini_key, def);
|
|
return cmdline_get_time(cmdline_key, ini_val, description);
|
|
}
|
|
|
|
int option_get_int(const char *ini_section,
|
|
const char *ini_key,
|
|
const char *cmdline_key,
|
|
int def,
|
|
const char *description)
|
|
{
|
|
/* *str is only used to check wether the cmdline option is actually set. */
|
|
const char *str = cmdline_get_value(cmdline_key);
|
|
|
|
/* we call cmdline_get_int even when the option isn't set in order to
|
|
* add the usage info */
|
|
int val = cmdline_get_int(cmdline_key, def, description);
|
|
|
|
if (!str)
|
|
return ini_get_int(ini_section, ini_key, def);
|
|
else
|
|
return val;
|
|
}
|
|
|
|
double option_get_double(const char *ini_section,
|
|
const char *ini_key,
|
|
const char *cmdline_key,
|
|
double def,
|
|
const char *description)
|
|
{
|
|
const char *str = cmdline_get_value(cmdline_key);
|
|
double val = cmdline_get_double(cmdline_key, def, description);
|
|
|
|
if (!str)
|
|
return ini_get_double(ini_section, ini_key, def);
|
|
else
|
|
return val;
|
|
}
|
|
|
|
int option_get_bool(const char *ini_section,
|
|
const char *ini_key,
|
|
const char *cmdline_key,
|
|
int def,
|
|
const char *description)
|
|
{
|
|
int val = false;
|
|
|
|
if (cmdline_key)
|
|
val = cmdline_get_bool(cmdline_key, false, description);
|
|
|
|
if (cmdline_key && val) {
|
|
/* this can only be true if the value has been set,
|
|
* so we can return */
|
|
return true;
|
|
}
|
|
|
|
return ini_get_bool(ini_section, ini_key, def);
|
|
}
|
|
|
|
void cmdline_usage_append(const char *key, const char *type, const char *description)
|
|
{
|
|
char *key_type;
|
|
if (type && strlen(type) > 0)
|
|
key_type = g_strdup_printf("%s (%s)", key, type);
|
|
else
|
|
key_type = g_strdup(key);
|
|
|
|
if (!usage_str) {
|
|
usage_str =
|
|
g_strdup_printf("%-40s - %s\n", key_type, description);
|
|
g_free(key_type);
|
|
return;
|
|
}
|
|
|
|
char *tmp;
|
|
tmp =
|
|
g_strdup_printf("%s%-40s - %s\n", usage_str, key_type, description);
|
|
g_free(key_type);
|
|
|
|
g_free(usage_str);
|
|
usage_str = tmp;
|
|
|
|
}
|
|
|
|
const char *cmdline_create_usage(void)
|
|
{
|
|
return usage_str;
|
|
}
|
|
|
|
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
|