config: introduce an optional event stream while parsing

This extends our config parser so that it can optionally produce an event
stream via callback function, where it reports e.g. when a comment was
parsed, or a section header, etc.

This parser will be used subsequently to handle the scenarios better where
removing config entries would make sections empty, or where a new entry
could be added to an already-existing, empty section.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Johannes Schindelin 2018-04-09 10:32:05 +02:00 committed by Junio C Hamano
parent b73bdc34c0
commit 8032cc4462
2 changed files with 114 additions and 12 deletions

101
config.c
View File

@ -653,7 +653,45 @@ static int get_base_var(struct strbuf *name)
} }
} }
static int git_parse_source(config_fn_t fn, void *data) struct parse_event_data {
enum config_event_t previous_type;
size_t previous_offset;
const struct config_options *opts;
};
static int do_event(enum config_event_t type, struct parse_event_data *data)
{
size_t offset;
if (!data->opts || !data->opts->event_fn)
return 0;
if (type == CONFIG_EVENT_WHITESPACE &&
data->previous_type == type)
return 0;
offset = cf->do_ftell(cf);
/*
* At EOF, the parser always "inserts" an extra '\n', therefore
* the end offset of the event is the current file position, otherwise
* we will already have advanced to the next event.
*/
if (type != CONFIG_EVENT_EOF)
offset--;
if (data->previous_type != CONFIG_EVENT_EOF &&
data->opts->event_fn(data->previous_type, data->previous_offset,
offset, data->opts->event_fn_data) < 0)
return -1;
data->previous_type = type;
data->previous_offset = offset;
return 0;
}
static int git_parse_source(config_fn_t fn, void *data,
const struct config_options *opts)
{ {
int comment = 0; int comment = 0;
int baselen = 0; int baselen = 0;
@ -664,8 +702,15 @@ static int git_parse_source(config_fn_t fn, void *data)
/* U+FEFF Byte Order Mark in UTF8 */ /* U+FEFF Byte Order Mark in UTF8 */
const char *bomptr = utf8_bom; const char *bomptr = utf8_bom;
/* For the parser event callback */
struct parse_event_data event_data = {
CONFIG_EVENT_EOF, 0, opts
};
for (;;) { for (;;) {
int c = get_next_char(); int c;
c = get_next_char();
if (bomptr && *bomptr) { if (bomptr && *bomptr) {
/* We are at the file beginning; skip UTF8-encoded BOM /* We are at the file beginning; skip UTF8-encoded BOM
* if present. Sane editors won't put this in on their * if present. Sane editors won't put this in on their
@ -682,18 +727,33 @@ static int git_parse_source(config_fn_t fn, void *data)
} }
} }
if (c == '\n') { if (c == '\n') {
if (cf->eof) if (cf->eof) {
if (do_event(CONFIG_EVENT_EOF, &event_data) < 0)
return -1;
return 0; return 0;
}
if (do_event(CONFIG_EVENT_WHITESPACE, &event_data) < 0)
return -1;
comment = 0; comment = 0;
continue; continue;
} }
if (comment || isspace(c)) if (comment)
continue; continue;
if (isspace(c)) {
if (do_event(CONFIG_EVENT_WHITESPACE, &event_data) < 0)
return -1;
continue;
}
if (c == '#' || c == ';') { if (c == '#' || c == ';') {
if (do_event(CONFIG_EVENT_COMMENT, &event_data) < 0)
return -1;
comment = 1; comment = 1;
continue; continue;
} }
if (c == '[') { if (c == '[') {
if (do_event(CONFIG_EVENT_SECTION, &event_data) < 0)
return -1;
/* Reset prior to determining a new stem */ /* Reset prior to determining a new stem */
strbuf_reset(var); strbuf_reset(var);
if (get_base_var(var) < 0 || var->len < 1) if (get_base_var(var) < 0 || var->len < 1)
@ -704,6 +764,10 @@ static int git_parse_source(config_fn_t fn, void *data)
} }
if (!isalpha(c)) if (!isalpha(c))
break; break;
if (do_event(CONFIG_EVENT_ENTRY, &event_data) < 0)
return -1;
/* /*
* Truncate the var name back to the section header * Truncate the var name back to the section header
* stem prior to grabbing the suffix part of the name * stem prior to grabbing the suffix part of the name
@ -715,6 +779,9 @@ static int git_parse_source(config_fn_t fn, void *data)
break; break;
} }
if (do_event(CONFIG_EVENT_ERROR, &event_data) < 0)
return -1;
switch (cf->origin_type) { switch (cf->origin_type) {
case CONFIG_ORIGIN_BLOB: case CONFIG_ORIGIN_BLOB:
error_msg = xstrfmt(_("bad config line %d in blob %s"), error_msg = xstrfmt(_("bad config line %d in blob %s"),
@ -1390,7 +1457,8 @@ int git_default_config(const char *var, const char *value, void *dummy)
* fgetc, ungetc, ftell of top need to be initialized before calling * fgetc, ungetc, ftell of top need to be initialized before calling
* this function. * this function.
*/ */
static int do_config_from(struct config_source *top, config_fn_t fn, void *data) static int do_config_from(struct config_source *top, config_fn_t fn, void *data,
const struct config_options *opts)
{ {
int ret; int ret;
@ -1402,7 +1470,7 @@ static int do_config_from(struct config_source *top, config_fn_t fn, void *data)
strbuf_init(&top->var, 1024); strbuf_init(&top->var, 1024);
cf = top; cf = top;
ret = git_parse_source(fn, data); ret = git_parse_source(fn, data, opts);
/* pop config-file parsing state stack */ /* pop config-file parsing state stack */
strbuf_release(&top->value); strbuf_release(&top->value);
@ -1415,7 +1483,7 @@ static int do_config_from(struct config_source *top, config_fn_t fn, void *data)
static int do_config_from_file(config_fn_t fn, static int do_config_from_file(config_fn_t fn,
const enum config_origin_type origin_type, const enum config_origin_type origin_type,
const char *name, const char *path, FILE *f, const char *name, const char *path, FILE *f,
void *data) void *data, const struct config_options *opts)
{ {
struct config_source top; struct config_source top;
@ -1428,15 +1496,18 @@ static int do_config_from_file(config_fn_t fn,
top.do_ungetc = config_file_ungetc; top.do_ungetc = config_file_ungetc;
top.do_ftell = config_file_ftell; top.do_ftell = config_file_ftell;
return do_config_from(&top, fn, data); return do_config_from(&top, fn, data, opts);
} }
static int git_config_from_stdin(config_fn_t fn, void *data) static int git_config_from_stdin(config_fn_t fn, void *data)
{ {
return do_config_from_file(fn, CONFIG_ORIGIN_STDIN, "", NULL, stdin, data); return do_config_from_file(fn, CONFIG_ORIGIN_STDIN, "", NULL, stdin,
data, NULL);
} }
int git_config_from_file(config_fn_t fn, const char *filename, void *data) int git_config_from_file_with_options(config_fn_t fn, const char *filename,
void *data,
const struct config_options *opts)
{ {
int ret = -1; int ret = -1;
FILE *f; FILE *f;
@ -1444,13 +1515,19 @@ int git_config_from_file(config_fn_t fn, const char *filename, void *data)
f = fopen_or_warn(filename, "r"); f = fopen_or_warn(filename, "r");
if (f) { if (f) {
flockfile(f); flockfile(f);
ret = do_config_from_file(fn, CONFIG_ORIGIN_FILE, filename, filename, f, data); ret = do_config_from_file(fn, CONFIG_ORIGIN_FILE, filename,
filename, f, data, opts);
funlockfile(f); funlockfile(f);
fclose(f); fclose(f);
} }
return ret; return ret;
} }
int git_config_from_file(config_fn_t fn, const char *filename, void *data)
{
return git_config_from_file_with_options(fn, filename, data, NULL);
}
int git_config_from_mem(config_fn_t fn, const enum config_origin_type origin_type, int git_config_from_mem(config_fn_t fn, const enum config_origin_type origin_type,
const char *name, const char *buf, size_t len, void *data) const char *name, const char *buf, size_t len, void *data)
{ {
@ -1467,7 +1544,7 @@ int git_config_from_mem(config_fn_t fn, const enum config_origin_type origin_typ
top.do_ungetc = config_buf_ungetc; top.do_ungetc = config_buf_ungetc;
top.do_ftell = config_buf_ftell; top.do_ftell = config_buf_ftell;
return do_config_from(&top, fn, data); return do_config_from(&top, fn, data, NULL);
} }
int git_config_from_blob_oid(config_fn_t fn, int git_config_from_blob_oid(config_fn_t fn,

View File

@ -28,15 +28,40 @@ enum config_origin_type {
CONFIG_ORIGIN_CMDLINE CONFIG_ORIGIN_CMDLINE
}; };
enum config_event_t {
CONFIG_EVENT_SECTION,
CONFIG_EVENT_ENTRY,
CONFIG_EVENT_WHITESPACE,
CONFIG_EVENT_COMMENT,
CONFIG_EVENT_EOF,
CONFIG_EVENT_ERROR
};
/*
* The parser event function (if not NULL) is called with the event type and
* the begin/end offsets of the parsed elements.
*
* Note: for CONFIG_EVENT_ENTRY (i.e. config variables), the trailing newline
* character is considered part of the element.
*/
typedef int (*config_parser_event_fn_t)(enum config_event_t type,
size_t begin_offset, size_t end_offset,
void *event_fn_data);
struct config_options { struct config_options {
unsigned int respect_includes : 1; unsigned int respect_includes : 1;
const char *commondir; const char *commondir;
const char *git_dir; const char *git_dir;
config_parser_event_fn_t event_fn;
void *event_fn_data;
}; };
typedef int (*config_fn_t)(const char *, const char *, void *); typedef int (*config_fn_t)(const char *, const char *, void *);
extern int git_default_config(const char *, const char *, void *); extern int git_default_config(const char *, const char *, void *);
extern int git_config_from_file(config_fn_t fn, const char *, void *); extern int git_config_from_file(config_fn_t fn, const char *, void *);
extern int git_config_from_file_with_options(config_fn_t fn, const char *,
void *,
const struct config_options *);
extern int git_config_from_mem(config_fn_t fn, const enum config_origin_type, extern int git_config_from_mem(config_fn_t fn, const enum config_origin_type,
const char *name, const char *buf, size_t len, void *data); const char *name, const char *buf, size_t len, void *data);
extern int git_config_from_blob_oid(config_fn_t fn, const char *name, extern int git_config_from_blob_oid(config_fn_t fn, const char *name,