0
0
mirror of https://github.com/mpv-player/mpv.git synced 2024-09-20 12:02:23 +02:00
mpv/options/parse_configfile.c
wm4 9ea0590371 options: rewrite config file parser
The format doesn't change. Some details are different, though. For
example, it will now accept option values with spaces even if they're
not quoted. (I see no reason why the user should be forced to add
quotes.)

The code is now smaller and should be much easier to extend. It also
can load config from in-memory buffers, which might be helpful in the
future.

read_file() should eventually be replaced with stream_read_complete().
But since the latter function may access options under various
circumstances, and also needs access to the mpv_global struct, there
is a separate implementation for now.
2015-04-04 01:04:04 +02:00

207 lines
6.5 KiB
C

/*
* This file is part of MPlayer.
*
* MPlayer is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* MPlayer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with MPlayer; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include "osdep/io.h"
#include "parse_configfile.h"
#include "common/common.h"
#include "common/msg.h"
#include "misc/ctype.h"
#include "m_option.h"
#include "m_config.h"
// Skip whitespace and comments (assuming there are no line breaks)
static bool skip_ws(bstr *s)
{
*s = bstr_lstrip(*s);
if (bstr_startswith0(*s, "#"))
s->len = 0;
return s->len;
}
static int m_config_parse(m_config_t *config, const char *location, bstr data,
char *initial_section, int flags)
{
m_profile_t *profile = m_config_add_profile(config, initial_section);
void *tmp = talloc_new(NULL);
int line_no = 0;
int errors = 0;
bstr_eatstart0(&data, "\xEF\xBB\xBF"); // skip BOM
while (data.len) {
talloc_free_children(tmp);
bool ok = false;
line_no++;
char loc[512];
snprintf(loc, sizeof(loc), "%s:%d:", location, line_no);
bstr line = bstr_strip_linebreaks(bstr_getline(data, &data));
if (!skip_ws(&line))
continue;
// Profile declaration
if (bstr_eatstart0(&line, "[")) {
bstr profilename;
if (!bstr_split_tok(line, "]", &profilename, &line)) {
MP_ERR(config, "%s missing closing ]\n", loc);
goto error;
}
if (skip_ws(&line)) {
MP_ERR(config, "%s unparseable extra characters: '%.*s'\n",
loc, BSTR_P(line));
goto error;
}
profile = m_config_add_profile(config, bstrto0(tmp, profilename));
continue;
}
bstr_eatstart0(&line, "--");
bstr option = line;
while (line.len && (mp_isalnum(line.start[0]) || line.start[0] == '_' ||
line.start[0] == '-'))
line = bstr_cut(line, 1);
option.len = option.len - line.len;
skip_ws(&line);
bstr value = {0};
if (bstr_eatstart0(&line, "=")) {
skip_ws(&line);
if (line.len && (line.start[0] == '"' || line.start[0] == '\'')) {
// Simple quoting, like "value"
char term[2] = {line.start[0], 0};
line = bstr_cut(line, 1);
if (!bstr_split_tok(line, term, &value, &line)) {
MP_ERR(config, "%s unterminated quote\n", loc);
goto error;
}
} else if (bstr_eatstart0(&line, "%")) {
// Quoting with length, like %5%value
bstr rest;
long long len = bstrtoll(line, &rest, 10);
if (rest.len == line.len || !bstr_eatstart0(&rest, "%") ||
len > rest.len)
{
MP_ERR(config, "%s broken escaping with '%%'\n", loc);
goto error;
}
value = bstr_splice(line, 0, len);
line = bstr_cut(line, len);
} else {
// No quoting; take everything until the comment or end of line
int end = bstrchr(line, '#');
value = bstr_strip(end < 0 ? line : bstr_splice(line, 0, end));
line.len = 0;
}
}
if (skip_ws(&line)) {
MP_ERR(config, "%s unparseable extra characters: '%.*s'\n",
loc, BSTR_P(line));
goto error;
}
int res;
if (profile) {
if (bstr_equals0(option, "profile-desc")) {
m_profile_set_desc(profile, value);
res = 0;
} else {
res = m_config_set_profile_option(config, profile, option, value);
}
} else {
res = m_config_set_option_ext(config, option, value, flags);
}
if (res < 0) {
MP_ERR(config, "%s setting option %.*s='%.*s' failed.\n",
loc, BSTR_P(option), BSTR_P(value));
goto error;
}
ok = true;
error:
if (!ok)
errors++;
if (errors > 16) {
MP_ERR(config, "%s: too many errors, stopping.\n", location);
break;
}
}
talloc_free(tmp);
return 1;
}
static bstr read_file(struct mp_log *log, const char *filename)
{
FILE *f = fopen(filename, "rb");
if (!f) {
mp_verbose(log, "Can't open config file: %s\n", mp_strerror(errno));
return (bstr){0};
}
char *data = talloc_array(NULL, char, 0);
size_t size = 0;
while (1) {
size_t left = talloc_get_size(data) - size;
if (!left) {
MP_TARRAY_GROW(NULL, data, size + 1);
continue;
}
size_t s = fread(data + size, 1, left, f);
if (!s) {
if (ferror(f))
mp_err(log, "Error reading config file.\n");
fclose(f);
MP_TARRAY_APPEND(NULL, data, size, 0);
return (bstr){data, size - 1};
}
size += s;
}
assert(0);
}
// Load options and profiles from from a config file.
// conffile: path to the config file
// initial_section: default section where to add normal options
// flags: M_SETOPT_* bits
// returns: 1 on success, -1 on error, 0 if file not accessible.
int m_config_parse_config_file(m_config_t *config, const char *conffile,
char *initial_section, int flags)
{
flags = flags | M_SETOPT_FROM_CONFIG_FILE;
MP_VERBOSE(config, "Reading config file %s\n", conffile);
bstr data = read_file(config->log, conffile);
if (!data.start)
return 0;
int r = m_config_parse(config, conffile, data, initial_section, flags);
talloc_free(data.start);
return r;
}