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,