diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/binds.c | 16 | ||||
-rw-r--r-- | src/imv.c | 186 | ||||
-rw-r--r-- | src/ini.c | 398 | ||||
-rw-r--r-- | src/ini.h | 111 |
4 files changed, 458 insertions, 253 deletions
diff --git a/src/binds.c b/src/binds.c index 9de63d7..e0426e6 100644 --- a/src/binds.c +++ b/src/binds.c @@ -189,15 +189,18 @@ static int print_event(char *buf, size_t len, const SDL_Event *event) /* Try plain old character input */ const char *keyname = SDL_GetKeyName(kevent->keysym.sym); - /* Because '<' and '>' have special meaning in our syntax, and '=' is - * restricted within ini files, we rename these. - */ + /* Because '<' and '>' have special meaning in our syntax, and '=', '[', and + * ']' are restricted within ini files, we rename these. */ if(!strcmp(keyname, "<")) { keyname = "Less"; } else if(!strcmp(keyname, ">")) { keyname = "Greater"; } else if(!strcmp(keyname, "=")) { keyname = "Equals"; + } else if(!strcmp(keyname, "[")) { + keyname = "LeftSquareBracket"; + } else if(!strcmp(keyname, "]")) { + keyname = "RightSquareBracket"; } return snprintf(buf, len, "%s%s", prefix, keyname); @@ -263,6 +266,13 @@ struct list *imv_bind_parse_keys(const char *keys) list_deep_free(list); return NULL; } + } else { + /* Just a regular character */ + char *item = malloc(2); + item[0] = *keys; + item[1] = 0; + list_append(list, item); + ++keys; } } @@ -86,11 +86,6 @@ struct imv { } current_image; }; -enum config_section { - CFG_OPTIONS, - CFG_BINDS -}; - void command_quit(struct list *args, const char *argstr, void *data); void command_pan(struct list *args, const char *argstr, void *data); void command_select_rel(struct list *args, const char *argstr, void *data); @@ -111,14 +106,26 @@ static bool setup_window(struct imv *imv); static void handle_event(struct imv *imv, SDL_Event *event); static void render_window(struct imv *imv); -static void add_bind(struct imv *imv, const char *keys, const char *command) +static const char *add_bind(struct imv *imv, const char *keys, const char *command) { struct list *list = imv_bind_parse_keys(keys); if(!list) { - return; + return "Invalid key combination"; + } + + enum bind_result result = imv_binds_add(imv->binds, list, command); + + if (result == BIND_SUCCESS) { + return NULL; + } else if (result == BIND_INVALID_KEYS) { + return "Invalid keys to bind to"; + } else if (result == BIND_INVALID_COMMAND) { + return "No command given to bind to"; + } else if (result == BIND_CONFLICTS) { + return "Key combination conflicts with existing bind"; } - imv_binds_add(imv->binds, list, command); + return NULL; } struct imv *imv_create(void) @@ -860,80 +867,115 @@ static bool parse_bool(const char *str) ); } -bool imv_load_config(struct imv *imv) +static int handle_ini_value(void *user, const char *section, const char *name, + const char *value) { - const char *path = get_config_path(); - if(!path) { - return true; - } + struct imv *imv = user; - FILE *f = fopen(path, "r"); - if (!f) { - fprintf(stderr, "Unable to open config file: %s\n", path); - return false; + if (!strcmp(section, "binds")) { + const char *err = add_bind(imv, name, value); + if (err) { + fprintf(stderr, "Config error: %s\n", err); + return 0; + } + return 1; } - enum config_section sect = CFG_OPTIONS; - int type; - do { - char key[128], value[128]; - type = parse_ini_file(f, &key[0], sizeof(key), &value[0], sizeof(value)); + if (!strcmp(section, "options")) { - if(type == INI_SECTION) { - if(!strcmp(key, "binds")) { - sect = CFG_BINDS; - } else { - fprintf(stderr, "Unknown config section: %s\n", key); + if(!strcmp(name, "fullscreen")) { + imv->fullscreen = parse_bool(value); + return 1; + } + + if(!strcmp(name, "overlay")) { + imv->overlay_enabled = parse_bool(value); + return 1; + } + + if(!strcmp(name, "sampling")) { + imv->nearest_neighbour = !strcmp(value, "nearest_neighbour"); + return 1; + } + + if(!strcmp(name, "recursive")) { + imv->recursive_load = parse_bool(value); + return 1; + } + + if(!strcmp(name, "cycle_input")) { + imv->cycle_input = parse_bool(value); + return 1; + } + + if(!strcmp(name, "list_at_exit")) { + imv->list_at_exit = parse_bool(value); + return 1; + } + + if(!strcmp(name, "scaling")) { + if(!strcmp(value, "none")) { + imv->scaling_mode = SCALING_NONE; + } else if(!strcmp(value, "shrink")) { + imv->scaling_mode = SCALING_DOWN; + } else if(!strcmp(value, "full")) { + imv->scaling_mode = SCALING_FULL; + } + return 1; + } + + if(!strcmp(name, "background")) { + if(!parse_bg(imv, value)) { return false; } - } else if(type == INI_VALUE) { - if(sect == CFG_OPTIONS) { - if(!strcmp(key, "fullscreen")) { - imv->fullscreen = parse_bool(value); - } else if(!strcmp(key, "overlay")) { - imv->overlay_enabled = parse_bool(value); - } else if(!strcmp(key, "sampling")) { - imv->nearest_neighbour = !strcmp(value, "nearest_neighbour"); - } else if(!strcmp(key, "recursive")) { - imv->recursive_load = parse_bool(value); - } else if(!strcmp(key, "cycle_input")) { - imv->cycle_input = parse_bool(value); - } else if(!strcmp(key, "list_at_exit")) { - imv->list_at_exit = parse_bool(value); - } else if(!strcmp(key, "scaling")) { - if(!strcmp(value, "none")) { - imv->scaling_mode = SCALING_NONE; - } else if(!strcmp(value, "shrink")) { - imv->scaling_mode = SCALING_DOWN; - } else if(!strcmp(value, "full")) { - imv->scaling_mode = SCALING_FULL; - } - } else if(!strcmp(key, "background")) { - if(!parse_bg(imv, value)) { - return false; - } - } else if(!strcmp(key, "slideshow")) { - if(!parse_slideshow_duration(imv, value)) { - return false; - } - } else if(!strcmp(key, "overlay_font")) { - free(imv->font_name); - imv->font_name = strdup(value); - } else if(!strcmp(key, "default_binds")) { - const bool default_binds = parse_bool(value); - if(!default_binds) { - /* clear out any default binds if requested */ - imv_binds_clear(imv->binds); - } - } else { - fprintf(stderr, "Ignoring unknown option: %s\n", key); - } - } else if(sect == CFG_BINDS) { - add_bind(imv, key, value); + return 1; + } + + if(!strcmp(name, "slideshow")) { + if(!parse_slideshow_duration(imv, value)) { + return false; } + return 1; + } + + if(!strcmp(name, "overlay_font")) { + free(imv->font_name); + imv->font_name = strdup(value); + return 1; } - } while(type); + if(!strcmp(name, "default_binds")) { + const bool default_binds = parse_bool(value); + if(!default_binds) { + /* clear out any default binds if requested */ + imv_binds_clear(imv->binds); + } + return 1; + } + + /* No matches so far */ + fprintf(stderr, "Ignoring unknown option: %s\n", name); + return 1; + } + return 0; +} + +bool imv_load_config(struct imv *imv) +{ + const char *path = get_config_path(); + if(!path) { + /* no config, no problem - we have defaults */ + return true; + } + + const int err = ini_parse(path, handle_ini_value, imv); + if (err == -1) { + fprintf(stderr, "Unable to open config file: %s\n", path); + return false; + } else if (err > 0) { + fprintf(stderr, "Error in config file: %s:%d\n", path, err); + return false; + } return true; } @@ -1,188 +1,244 @@ -#include "ini.h" +/* inih -- simple .INI file parser + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include <stdio.h> #include <ctype.h> -#include <stdlib.h> #include <string.h> -enum parse_state { - START, - READING_KEY, - SEEKING_EQ, - SEEKING_VALUE, - READING_VALUE, - READING_SECTION_KEY, - SEEKING_SECTION_VALUE, - SEEKING_END_SECTION, - READING_SECTION_VALUE, - ACCEPT, - REJECT -}; - -int parse_ini_file(FILE* f, char *out_key, size_t key_size, char *out_value, size_t value_size) +#include "ini.h" + +#if !INI_USE_STACK +#include <stdlib.h> +#endif + +#define MAX_SECTION 50 +#define MAX_NAME 50 + +/* Used by ini_parse_string() to keep track of string parsing state. */ +typedef struct { + const char* ptr; + size_t num_left; +} ini_parse_string_ctx; + +/* Strip whitespace chars off end of given string, in place. Return s. */ +static char* rstrip(char* s) { - char buf[512]; - while(fgets(&buf[0], sizeof(buf), f)) { - int type = parse_ini_str(&buf[0], out_key, key_size, out_value, value_size); - if(type != 0) { - return type; + char* p = s + strlen(s); + while (p > s && isspace((unsigned char)(*--p))) + *p = '\0'; + return s; +} + +/* Return pointer to first non-whitespace char in given string. */ +static char* lskip(const char* s) +{ + while (*s && isspace((unsigned char)(*s))) + s++; + return (char*)s; +} + +/* Return pointer to first char (of chars) or inline comment in given string, + or pointer to null at end of string if neither found. Inline comment must + be prefixed by a whitespace character to register as a comment. */ +static char* find_chars_or_comment(const char* s, const char* chars) +{ +#if INI_ALLOW_INLINE_COMMENTS + int was_space = 0; + while (*s && (!chars || !strchr(chars, *s)) && + !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { + was_space = isspace((unsigned char)(*s)); + s++; + } +#else + while (*s && (!chars || !strchr(chars, *s))) { + s++; } - } - return 0; +#endif + return (char*)s; } -int parse_ini_str(const char* str, char *out_key, size_t key_size, char *out_value, size_t value_size) +/* Version of strncpy that ensures dest (size bytes) is null-terminated. */ +static char* strncpy0(char* dest, const char* src, size_t size) { - /* ignore comments */ - if(*str == ';') { - out_key[0] = 0; - out_value[0] = 0; - return INI_UNKNOWN; - } - - const char *key_start = 0; - size_t key_len = 0; - const char *value_start = 0; - size_t value_len = 0; - - int type = INI_UNKNOWN; - enum parse_state state = START; - char quote = 0; - char c = 0; - - do { - c = *str; - switch(state) { - case START: - if(c == '[') { - type = INI_SECTION; - state = READING_SECTION_KEY; - } else if(c != '=') { - type = INI_VALUE; - state = READING_KEY; - key_start = str; - ++key_len; - } else { - state = REJECT; - } - break; - case READING_KEY: - if(isspace(c)) { - state = SEEKING_EQ; - } else if(c == '=') { - state = SEEKING_VALUE; - } else { - ++key_len; - } - break; - case SEEKING_EQ: - if(c == '=') { - state = SEEKING_VALUE; - } else if(!isspace(c)) { - state = REJECT; - } - break; - case SEEKING_VALUE: - if(c == '"' || c == '\'') { - if(!value_start) { - quote = c; - state = READING_VALUE; - } else { - state = REJECT; - } - } else if(!isspace(c)) { - value_start = str; - ++value_len; - state = READING_VALUE; - } - break; - case READING_VALUE: - if(quote == 0 && isspace(c)) { - state = ACCEPT; - } else if(quote && c == quote) { - state = ACCEPT; - } else if(c == 0) { - state = ACCEPT; - } else if(c) { - if(!value_start) { - value_start = str; - } - ++value_len; - } else { - state = REJECT; - } - break; - case READING_SECTION_KEY: - if(isspace(c)) { - state = SEEKING_SECTION_VALUE; - } else if(c == ']') { - state = ACCEPT; - } else { - if(!key_start) { - key_start = str; - } - ++key_len; + strncpy(dest, src, size); + dest[size - 1] = '\0'; + return dest; +} + +/* See documentation in header file. */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user) +{ + /* Uses a fair bit of stack (use heap instead if you need to) */ +#if INI_USE_STACK + char line[INI_MAX_LINE]; +#else + char* line; +#endif + char section[MAX_SECTION] = ""; + char prev_name[MAX_NAME] = ""; + + char* start; + char* end; + char* name; + char* value; + int lineno = 0; + int error = 0; + +#if !INI_USE_STACK + line = (char*)malloc(INI_MAX_LINE); + if (!line) { + return -2; + } +#endif + +#if INI_HANDLER_LINENO +#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno) +#else +#define HANDLER(u, s, n, v) handler(u, s, n, v) +#endif + + /* Scan through stream line by line */ + while (reader(line, INI_MAX_LINE, stream) != NULL) { + lineno++; + + start = line; +#if INI_ALLOW_BOM + if (lineno == 1 && (unsigned char)start[0] == 0xEF && + (unsigned char)start[1] == 0xBB && + (unsigned char)start[2] == 0xBF) { + start += 3; } - break; - case SEEKING_SECTION_VALUE: - if(c == '"' || c == '\'') { - quote = c; - state = READING_SECTION_VALUE; - } else if(c == ']') { - state = ACCEPT; - } else if(!isspace(c)) { - state = REJECT; +#endif + start = lskip(rstrip(start)); + + if (*start == ';' || *start == '#') { + /* Per Python configparser, allow both ; and # comments at the + start of a line */ } - break; - case READING_SECTION_VALUE: - if(quote == 0 && isspace(c)) { - state = SEEKING_END_SECTION; - } else if(quote && c == quote) { - state = SEEKING_END_SECTION; - } else if(c == ']') { - state = ACCEPT; - } else if(c) { - if(!value_start) { - value_start = str; - } - ++value_len; - } else { - state = REJECT; +#if INI_ALLOW_MULTILINE + else if (*prev_name && *start && start > line) { + /* Non-blank line with leading whitespace, treat as continuation + of previous name's value (as per Python configparser). */ + if (!HANDLER(user, section, prev_name, start) && !error) + error = lineno; } - break; - case SEEKING_END_SECTION: - if(c == ']') { - state = ACCEPT; - } else if(!isspace(c)) { - state = REJECT; +#endif + else if (*start == '[') { + /* A "[section]" line */ + end = find_chars_or_comment(start + 1, "]"); + if (*end == ']') { + *end = '\0'; + strncpy0(section, start + 1, sizeof(section)); + *prev_name = '\0'; + } + else if (!error) { + /* No ']' found on section line */ + error = lineno; + } } - break; - case ACCEPT: - if(c != 0 && !isspace(c)) { - state = REJECT; + else if (*start) { + /* Not a comment, must be a name[=:]value pair */ + end = find_chars_or_comment(start, "=:"); + if (*end == '=' || *end == ':') { + *end = '\0'; + name = rstrip(start); + value = end + 1; +#if INI_ALLOW_INLINE_COMMENTS + end = find_chars_or_comment(value, NULL); + if (*end) + *end = '\0'; +#endif + value = lskip(value); + rstrip(value); + + /* Valid name[=:]value pair found, call handler */ + strncpy0(prev_name, name, sizeof(prev_name)); + if (!HANDLER(user, section, name, value) && !error) + error = lineno; + } + else if (!error) { + /* No '=' or ':' found on name[=:]value line */ + error = lineno; + } } - break; - case REJECT: - return 0; - break; - } - ++str; - } while(c != 0 && state != REJECT); - if(state == ACCEPT) { - if(key_len >= key_size) { - key_len = key_size - 1; +#if INI_STOP_ON_FIRST_ERROR + if (error) + break; +#endif } - if(value_len >= value_size) { - value_len = value_size - 1; + +#if !INI_USE_STACK + free(line); +#endif + + return error; +} + +/* See documentation in header file. */ +int ini_parse_file(FILE* file, ini_handler handler, void* user) +{ + return ini_parse_stream((ini_reader)fgets, file, handler, user); +} + +/* See documentation in header file. */ +int ini_parse(const char* filename, ini_handler handler, void* user) +{ + FILE* file; + int error; + + file = fopen(filename, "r"); + if (!file) + return -1; + error = ini_parse_file(file, handler, user); + fclose(file); + return error; +} + +/* An ini_reader function to read the next line from a string buffer. This + is the fgets() equivalent used by ini_parse_string(). */ +static char* ini_reader_string(char* str, int num, void* stream) { + ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream; + const char* ctx_ptr = ctx->ptr; + size_t ctx_num_left = ctx->num_left; + char* strp = str; + char c; + + if (ctx_num_left == 0 || num < 2) + return NULL; + + while (num > 1 && ctx_num_left != 0) { + c = *ctx_ptr++; + ctx_num_left--; + *strp++ = c; + if (c == '\n') + break; + num--; } - memcpy(out_key, key_start, key_len); - memcpy(out_value, value_start, value_len); - out_key[key_len] = 0; - out_value[value_len] = 0; - return type; - } else { - out_key[0] = 0; - out_value[0] = 0; - } - - return INI_UNKNOWN; + + *strp = '\0'; + ctx->ptr = ctx_ptr; + ctx->num_left = ctx_num_left; + return str; +} + +/* See documentation in header file. */ +int ini_parse_string(const char* string, ini_handler handler, void* user) { + ini_parse_string_ctx ctx; + + ctx.ptr = string; + ctx.num_left = strlen(string); + return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler, + user); } @@ -1,13 +1,110 @@ -#ifndef INI_H -#define INI_H +/* inih -- simple .INI file parser + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#ifndef __INI_H__ +#define __INI_H__ + +/* Make this header file easier to include in C++ code */ +#ifdef __cplusplus +extern "C" { +#endif #include <stdio.h> -#define INI_UNKNOWN 0 -#define INI_VALUE 1 -#define INI_SECTION 2 +/* Nonzero if ini_handler callback should accept lineno parameter. */ +#ifndef INI_HANDLER_LINENO +#define INI_HANDLER_LINENO 0 +#endif + +/* Typedef for prototype of handler function. */ +#if INI_HANDLER_LINENO +typedef int (*ini_handler)(void* user, const char* section, + const char* name, const char* value, + int lineno); +#else +typedef int (*ini_handler)(void* user, const char* section, + const char* name, const char* value); +#endif + +/* Typedef for prototype of fgets-style reader function. */ +typedef char* (*ini_reader)(char* str, int num, void* stream); + +/* Parse given INI-style file. May have [section]s, name=value pairs + (whitespace stripped), and comments starting with ';' (semicolon). Section + is "" if name=value pair parsed before any section heading. name:value + pairs are also supported as a concession to Python's configparser. + + For each name=value pair parsed, call handler function with given user + pointer as well as section, name, and value (data only valid for duration + of handler call). Handler should return nonzero on success, zero on error. + + Returns 0 on success, line number of first error on parse error (doesn't + stop on first error), -1 on file open error, or -2 on memory allocation + error (only when INI_USE_STACK is zero). +*/ +int ini_parse(const char* filename, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't + close the file when it's finished -- the caller must do that. */ +int ini_parse_file(FILE* file, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes an ini_reader function pointer instead of + filename. Used for implementing custom or string-based I/O (see also + ini_parse_string). */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user); -int parse_ini_file(FILE* f, char *out_key, size_t key_size, char *out_value, size_t value_size); -int parse_ini_str(const char* str, char *out_key, size_t key_size, char *out_value, size_t value_size); +/* Same as ini_parse(), but takes a zero-terminated string with the INI data +instead of a file. Useful for parsing INI data from a network socket or +already in memory. */ +int ini_parse_string(const char* string, ini_handler handler, void* user); +/* Nonzero to allow multi-line value parsing, in the style of Python's + configparser. If allowed, ini_parse() will call the handler with the same + name for each subsequent line parsed. */ +#ifndef INI_ALLOW_MULTILINE +#define INI_ALLOW_MULTILINE 1 #endif + +/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of + the file. See http://code.google.com/p/inih/issues/detail?id=21 */ +#ifndef INI_ALLOW_BOM +#define INI_ALLOW_BOM 1 +#endif + +/* Nonzero to allow inline comments (with valid inline comment characters + specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match + Python 3.2+ configparser behaviour. */ +#ifndef INI_ALLOW_INLINE_COMMENTS +#define INI_ALLOW_INLINE_COMMENTS 1 +#endif +#ifndef INI_INLINE_COMMENT_PREFIXES +#define INI_INLINE_COMMENT_PREFIXES ";" +#endif + +/* Nonzero to use stack, zero to use heap (malloc/free). */ +#ifndef INI_USE_STACK +#define INI_USE_STACK 1 +#endif + +/* Stop parsing on first error (default is to keep parsing). */ +#ifndef INI_STOP_ON_FIRST_ERROR +#define INI_STOP_ON_FIRST_ERROR 0 +#endif + +/* Maximum line length for any line in INI file. */ +#ifndef INI_MAX_LINE +#define INI_MAX_LINE 200 +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* __INI_H__ */ |