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>

Johannes Schindelin committed Apr 9, 2018 at 10:32 UTC 8032cc4462b8af268fe457c2a0431473334dfd18
2 files changed +114 -12
config.c
+89 -12
@@ -653,7 +653,45 @@ static int get_base_var(struct strbuf *name)
653 }
654 }
655
656 -static int git_parse_source(config_fn_t fn, void *data)
656 +struct parse_event_data {
657 + enum config_event_t previous_type;
658 + size_t previous_offset;
659 + const struct config_options *opts;
660 +};
661 +
662 +static int do_event(enum config_event_t type, struct parse_event_data *data)
663 +{
664 + size_t offset;
665 +
666 + if (!data->opts || !data->opts->event_fn)
667 + return 0;
668 +
669 + if (type == CONFIG_EVENT_WHITESPACE &&
670 + data->previous_type == type)
671 + return 0;
672 +
673 + offset = cf->do_ftell(cf);
674 + /*
675 + * At EOF, the parser always "inserts" an extra '\n', therefore
676 + * the end offset of the event is the current file position, otherwise
677 + * we will already have advanced to the next event.
678 + */
679 + if (type != CONFIG_EVENT_EOF)
680 + offset--;
681 +
682 + if (data->previous_type != CONFIG_EVENT_EOF &&
683 + data->opts->event_fn(data->previous_type, data->previous_offset,
684 + offset, data->opts->event_fn_data) < 0)
685 + return -1;
686 +
687 + data->previous_type = type;
688 + data->previous_offset = offset;
689 +
690 + return 0;
691 +}
692 +
693 +static int git_parse_source(config_fn_t fn, void *data,
694 + const struct config_options *opts)
695 {
696 int comment = 0;
697 int baselen = 0;
@@ -664,8 +702,15 @@ static int git_parse_source(config_fn_t fn, void *data)
702 /* U+FEFF Byte Order Mark in UTF8 */
703 const char *bomptr = utf8_bom;
704
705 + /* For the parser event callback */
706 + struct parse_event_data event_data = {
707 + CONFIG_EVENT_EOF, 0, opts
708 + };
709 +
710 for (;;) {
668 - int c = get_next_char();
711 + int c;
712 +
713 + c = get_next_char();
714 if (bomptr && *bomptr) {
715 /* We are at the file beginning; skip UTF8-encoded BOM
716 * 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)
727 }
728 }
729 if (c == '\n') {
685 - if (cf->eof)
730 + if (cf->eof) {
731 + if (do_event(CONFIG_EVENT_EOF, &event_data) < 0)
732 + return -1;
733 return 0;
734 + }
735 + if (do_event(CONFIG_EVENT_WHITESPACE, &event_data) < 0)
736 + return -1;
737 comment = 0;
738 continue;
739 }
690 - if (comment || isspace(c))
740 + if (comment)
741 continue;
742 + if (isspace(c)) {
743 + if (do_event(CONFIG_EVENT_WHITESPACE, &event_data) < 0)
744 + return -1;
745 + continue;
746 + }
747 if (c == '#' || c == ';') {
748 + if (do_event(CONFIG_EVENT_COMMENT, &event_data) < 0)
749 + return -1;
750 comment = 1;
751 continue;
752 }
753 if (c == '[') {
754 + if (do_event(CONFIG_EVENT_SECTION, &event_data) < 0)
755 + return -1;
756 +
757 /* Reset prior to determining a new stem */
758 strbuf_reset(var);
759 if (get_base_var(var) < 0 || var->len < 1)
@@ -704,6 +764,10 @@ static int git_parse_source(config_fn_t fn, void *data)
764 }
765 if (!isalpha(c))
766 break;
767 +
768 + if (do_event(CONFIG_EVENT_ENTRY, &event_data) < 0)
769 + return -1;
770 +
771 /*
772 * Truncate the var name back to the section header
773 * 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)
779 break;
780 }
781
782 + if (do_event(CONFIG_EVENT_ERROR, &event_data) < 0)
783 + return -1;
784 +
785 switch (cf->origin_type) {
786 case CONFIG_ORIGIN_BLOB:
787 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)
1457 * fgetc, ungetc, ftell of top need to be initialized before calling
1458 * this function.
1459 */
1393 -static int do_config_from(struct config_source *top, config_fn_t fn, void *data)
1460 +static int do_config_from(struct config_source *top, config_fn_t fn, void *data,
1461 + const struct config_options *opts)
1462 {
1463 int ret;
1464
@@ -1402,7 +1470,7 @@ static int do_config_from(struct config_source *top, config_fn_t fn, void *data)
1470 strbuf_init(&top->var, 1024);
1471 cf = top;
1472
1405 - ret = git_parse_source(fn, data);
1473 + ret = git_parse_source(fn, data, opts);
1474
1475 /* pop config-file parsing state stack */
1476 strbuf_release(&top->value);
@@ -1415,7 +1483,7 @@ static int do_config_from(struct config_source *top, config_fn_t fn, void *data)
1483 static int do_config_from_file(config_fn_t fn,
1484 const enum config_origin_type origin_type,
1485 const char *name, const char *path, FILE *f,
1418 - void *data)
1486 + void *data, const struct config_options *opts)
1487 {
1488 struct config_source top;
1489
@@ -1428,15 +1496,18 @@ static int do_config_from_file(config_fn_t fn,
1496 top.do_ungetc = config_file_ungetc;
1497 top.do_ftell = config_file_ftell;
1498
1431 - return do_config_from(&top, fn, data);
1499 + return do_config_from(&top, fn, data, opts);
1500 }
1501
1502 static int git_config_from_stdin(config_fn_t fn, void *data)
1503 {
1436 - return do_config_from_file(fn, CONFIG_ORIGIN_STDIN, "", NULL, stdin, data);
1504 + return do_config_from_file(fn, CONFIG_ORIGIN_STDIN, "", NULL, stdin,
1505 + data, NULL);
1506 }
1507
1439 -int git_config_from_file(config_fn_t fn, const char *filename, void *data)
1508 +int git_config_from_file_with_options(config_fn_t fn, const char *filename,
1509 + void *data,
1510 + const struct config_options *opts)
1511 {
1512 int ret = -1;
1513 FILE *f;
@@ -1444,13 +1515,19 @@ int git_config_from_file(config_fn_t fn, const char *filename, void *data)
1515 f = fopen_or_warn(filename, "r");
1516 if (f) {
1517 flockfile(f);
1447 - ret = do_config_from_file(fn, CONFIG_ORIGIN_FILE, filename, filename, f, data);
1518 + ret = do_config_from_file(fn, CONFIG_ORIGIN_FILE, filename,
1519 + filename, f, data, opts);
1520 funlockfile(f);
1521 fclose(f);
1522 }
1523 return ret;
1524 }
1525
1526 +int git_config_from_file(config_fn_t fn, const char *filename, void *data)
1527 +{
1528 + return git_config_from_file_with_options(fn, filename, data, NULL);
1529 +}
1530 +
1531 int git_config_from_mem(config_fn_t fn, const enum config_origin_type origin_type,
1532 const char *name, const char *buf, size_t len, void *data)
1533 {
@@ -1467,7 +1544,7 @@ int git_config_from_mem(config_fn_t fn, const enum config_origin_type origin_typ
1544 top.do_ungetc = config_buf_ungetc;
1545 top.do_ftell = config_buf_ftell;
1546
1470 - return do_config_from(&top, fn, data);
1547 + return do_config_from(&top, fn, data, NULL);
1548 }
1549
1550 int git_config_from_blob_oid(config_fn_t fn,
config.h
+25
@@ -28,15 +28,40 @@ enum config_origin_type {
28 CONFIG_ORIGIN_CMDLINE
29 };
30
31 +enum config_event_t {
32 + CONFIG_EVENT_SECTION,
33 + CONFIG_EVENT_ENTRY,
34 + CONFIG_EVENT_WHITESPACE,
35 + CONFIG_EVENT_COMMENT,
36 + CONFIG_EVENT_EOF,
37 + CONFIG_EVENT_ERROR
38 +};
39 +
40 +/*
41 + * The parser event function (if not NULL) is called with the event type and
42 + * the begin/end offsets of the parsed elements.
43 + *
44 + * Note: for CONFIG_EVENT_ENTRY (i.e. config variables), the trailing newline
45 + * character is considered part of the element.
46 + */
47 +typedef int (*config_parser_event_fn_t)(enum config_event_t type,
48 + size_t begin_offset, size_t end_offset,
49 + void *event_fn_data);
50 +
51 struct config_options {
52 unsigned int respect_includes : 1;
53 const char *commondir;
54 const char *git_dir;
55 + config_parser_event_fn_t event_fn;
56 + void *event_fn_data;
57 };
58
59 typedef int (*config_fn_t)(const char *, const char *, void *);
60 extern int git_default_config(const char *, const char *, void *);
61 extern int git_config_from_file(config_fn_t fn, const char *, void *);
62 +extern int git_config_from_file_with_options(config_fn_t fn, const char *,
63 + void *,
64 + const struct config_options *);
65 extern int git_config_from_mem(config_fn_t fn, const enum config_origin_type,
66 const char *name, const char *buf, size_t len, void *data);
67 extern int git_config_from_blob_oid(config_fn_t fn, const char *name,