all repos — mgba @ 67e31c9666531c99aa7625c2ef009b39ae4bb49e

mGBA Game Boy Advance Emulator

src/third-party/inih/ini.c (view raw)

  1/* inih -- simple .INI file parser
  2
  3inih is released under the New BSD license (see LICENSE.txt). Go to the project
  4home page for more info:
  5
  6http://code.google.com/p/inih/
  7
  8*/
  9
 10#include <stdio.h>
 11#include <ctype.h>
 12#include <string.h>
 13
 14#include "ini.h"
 15
 16#if !INI_USE_STACK
 17#include <stdlib.h>
 18#endif
 19
 20#define MAX_SECTION 50
 21#define MAX_NAME 50
 22
 23/* Strip whitespace chars off end of given string, in place. Return s. */
 24static char* rstrip(char* s)
 25{
 26    char* p = s + strlen(s);
 27    while (p > s && isspace((unsigned char)(*--p)))
 28        *p = '\0';
 29    return s;
 30}
 31
 32/* Return pointer to first non-whitespace char in given string. */
 33static char* lskip(const char* s)
 34{
 35    while (*s && isspace((unsigned char)(*s)))
 36        s++;
 37    return (char*)s;
 38}
 39
 40/* Return pointer to first char c or ';' comment in given string, or pointer to
 41   null at end of string if neither found. ';' must be prefixed by a whitespace
 42   character to register as a comment. */
 43static char* find_char_or_comment(const char* s, char c)
 44{
 45    int was_whitespace = 0;
 46    while (*s && *s != c && !(was_whitespace && *s == ';')) {
 47        was_whitespace = isspace((unsigned char)(*s));
 48        s++;
 49    }
 50    return (char*)s;
 51}
 52
 53/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
 54static char* strncpy0(char* dest, const char* src, size_t size)
 55{
 56    strncpy(dest, src, size);
 57    dest[size - 1] = '\0';
 58    return dest;
 59}
 60
 61/* See documentation in header file. */
 62int ini_parse_file(FILE* file,
 63                   int (*handler)(void*, const char*, const char*,
 64                                  const char*),
 65                   void* user)
 66{
 67    /* Uses a fair bit of stack (use heap instead if you need to) */
 68#if INI_USE_STACK
 69    char line[INI_MAX_LINE];
 70#else
 71    char* line;
 72#endif
 73    char section[MAX_SECTION] = "";
 74    char prev_name[MAX_NAME] = "";
 75
 76    char* start;
 77    char* end;
 78    char* name;
 79    char* value;
 80    int lineno = 0;
 81    int error = 0;
 82
 83#if !INI_USE_STACK
 84    line = (char*)malloc(INI_MAX_LINE);
 85    if (!line) {
 86        return -2;
 87    }
 88#endif
 89
 90    /* Scan through file line by line */
 91    while (fgets(line, INI_MAX_LINE, file) != NULL) {
 92        lineno++;
 93
 94        start = line;
 95#if INI_ALLOW_BOM
 96        if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
 97                           (unsigned char)start[1] == 0xBB &&
 98                           (unsigned char)start[2] == 0xBF) {
 99            start += 3;
100        }
101#endif
102        start = lskip(rstrip(start));
103
104        if (*start == ';' || *start == '#') {
105            /* Per Python ConfigParser, allow '#' comments at start of line */
106        }
107#if INI_ALLOW_MULTILINE
108        else if (*prev_name && *start && start > line) {
109            /* Non-black line with leading whitespace, treat as continuation
110               of previous name's value (as per Python ConfigParser). */
111            if (!handler(user, section, prev_name, start) && !error)
112                error = lineno;
113        }
114#endif
115        else if (*start == '[') {
116            /* A "[section]" line */
117            end = find_char_or_comment(start + 1, ']');
118            if (*end == ']') {
119                *end = '\0';
120                strncpy0(section, start + 1, sizeof(section));
121                *prev_name = '\0';
122            }
123            else if (!error) {
124                /* No ']' found on section line */
125                error = lineno;
126            }
127        }
128        else if (*start && *start != ';') {
129            /* Not a comment, must be a name[=:]value pair */
130            end = find_char_or_comment(start, '=');
131            if (*end != '=') {
132                end = find_char_or_comment(start, ':');
133            }
134            if (*end == '=' || *end == ':') {
135                *end = '\0';
136                name = rstrip(start);
137                value = lskip(end + 1);
138                end = find_char_or_comment(value, '\0');
139                if (*end == ';')
140                    *end = '\0';
141                rstrip(value);
142
143                /* Valid name[=:]value pair found, call handler */
144                strncpy0(prev_name, name, sizeof(prev_name));
145                if (!handler(user, section, name, value) && !error)
146                    error = lineno;
147            }
148            else if (!error) {
149                /* No '=' or ':' found on name[=:]value line */
150                error = lineno;
151            }
152        }
153
154#if INI_STOP_ON_FIRST_ERROR
155        if (error)
156            break;
157#endif
158    }
159
160#if !INI_USE_STACK
161    free(line);
162#endif
163
164    return error;
165}
166
167/* See documentation in header file. */
168int ini_parse(const char* filename,
169              int (*handler)(void*, const char*, const char*, const char*),
170              void* user)
171{
172    FILE* file;
173    int error;
174
175    file = fopen(filename, "r");
176    if (!file)
177        return -1;
178    error = ini_parse_file(file, handler, user);
179    fclose(file);
180    return error;
181}