replace strbuf_expand() with strbuf_expand_step()
Avoid the overhead of passing context to a callback function of strbuf_expand() by using strbuf_expand_step() in a loop instead. It requires explicit handling of %% and unrecognized placeholders, but is simpler, more direct and avoids void pointers. Signed-off-by: René Scharfe <l.s.r@web.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>
René Scharfe committed
Jun 17, 2023 at 22:43 UTC
6f1e2d52797161c7effe4d1388765de3dbd80bbf
7 files changed
+169
-272
builtin/cat-file.c
+17
-18
@@ -309,10 +309,8 @@ static int is_atom(const char *atom, const char *s, int slen)
309
}
310
311
static void expand_atom(struct strbuf *sb, const char *atom, int len,
312
- void *vdata)
312
+ struct expand_data *data)
313
{
314
- struct expand_data *data = vdata;
315
-
314
if (is_atom("objectname", atom, len)) {
315
if (!data->mark_query)
316
strbuf_addstr(sb, oid_to_hex(&data->oid));
@@ -346,19 +344,21 @@ static void expand_atom(struct strbuf *sb, const char *atom, int len,
344
die("unknown format element: %.*s", len, atom);
345
}
346
349
-static size_t expand_format(struct strbuf *sb, const char *start, void *data)
347
+static void expand_format(struct strbuf *sb, const char *start,
348
+ struct expand_data *data)
349
{
351
- const char *end;
352
-
353
- if (*start != '(')
354
- return 0;
355
- end = strchr(start + 1, ')');
356
- if (!end)
357
- die("format element '%s' does not end in ')'", start);
358
-
359
- expand_atom(sb, start + 1, end - start - 1, data);
360
-
361
- return end - start + 1;
350
+ while (strbuf_expand_step(sb, &start)) {
351
+ const char *end;
352
+
353
+ if (skip_prefix(start, "%", &start) || *start != '(')
354
+ strbuf_addch(sb, '%');
355
+ else if (!(end = strchr(start + 1, ')')))
356
+ die("format element '%s' does not end in ')'", start);
357
+ else {
358
+ expand_atom(sb, start + 1, end - start - 1, data);
359
+ start = end + 1;
360
+ }
361
+ }
362
}
363
364
static void batch_write(struct batch_options *opt, const void *data, int len)
@@ -494,7 +494,7 @@ static void batch_object_write(const char *obj_name,
494
if (!opt->format) {
495
print_default_format(scratch, data);
496
} else {
497
- strbuf_expand(scratch, opt->format, expand_format, data);
497
+ expand_format(scratch, opt->format, data);
498
strbuf_addch(scratch, '\n');
499
}
500
@@ -777,9 +777,8 @@ static int batch_objects(struct batch_options *opt)
777
*/
778
memset(&data, 0, sizeof(data));
779
data.mark_query = 1;
780
- strbuf_expand(&output,
780
+ expand_format(&output,
781
opt->format ? opt->format : DEFAULT_FORMAT,
782
- expand_format,
782
&data);
783
data.mark_query = 0;
784
strbuf_release(&output);
builtin/ls-files.c
+46
-63
@@ -262,74 +262,57 @@ static void expand_objectsize(struct strbuf *line, const struct object_id *oid,
262
strbuf_addstr(line, "-");
263
}
264
}
265
-struct show_index_data {
266
- const char *pathname;
267
- struct index_state *istate;
268
- const struct cache_entry *ce;
269
-};
270
-
271
-static size_t expand_show_index(struct strbuf *sb, const char *start,
272
- void *context)
273
-{
274
- struct show_index_data *data = context;
275
- const char *end;
276
- const char *p;
277
- size_t len = strbuf_expand_literal_cb(sb, start, NULL);
278
- struct stat st;
279
-
280
- if (len)
281
- return len;
282
- if (*start != '(')
283
- die(_("bad ls-files format: element '%s' "
284
- "does not start with '('"), start);
285
-
286
- end = strchr(start + 1, ')');
287
- if (!end)
288
- die(_("bad ls-files format: element '%s' "
289
- "does not end in ')'"), start);
290
-
291
- len = end - start + 1;
292
- if (skip_prefix(start, "(objectmode)", &p))
293
- strbuf_addf(sb, "%06o", data->ce->ce_mode);
294
- else if (skip_prefix(start, "(objectname)", &p))
295
- strbuf_add_unique_abbrev(sb, &data->ce->oid, abbrev);
296
- else if (skip_prefix(start, "(objecttype)", &p))
297
- strbuf_addstr(sb, type_name(object_type(data->ce->ce_mode)));
298
- else if (skip_prefix(start, "(objectsize:padded)", &p))
299
- expand_objectsize(sb, &data->ce->oid, object_type(data->ce->ce_mode), 1);
300
- else if (skip_prefix(start, "(objectsize)", &p))
301
- expand_objectsize(sb, &data->ce->oid, object_type(data->ce->ce_mode), 0);
302
- else if (skip_prefix(start, "(stage)", &p))
303
- strbuf_addf(sb, "%d", ce_stage(data->ce));
304
- else if (skip_prefix(start, "(eolinfo:index)", &p))
305
- strbuf_addstr(sb, S_ISREG(data->ce->ce_mode) ?
306
- get_cached_convert_stats_ascii(data->istate,
307
- data->ce->name) : "");
308
- else if (skip_prefix(start, "(eolinfo:worktree)", &p))
309
- strbuf_addstr(sb, !lstat(data->pathname, &st) &&
310
- S_ISREG(st.st_mode) ?
311
- get_wt_convert_stats_ascii(data->pathname) : "");
312
- else if (skip_prefix(start, "(eolattr)", &p))
313
- strbuf_addstr(sb, get_convert_attr_ascii(data->istate,
314
- data->pathname));
315
- else if (skip_prefix(start, "(path)", &p))
316
- write_name_to_buf(sb, data->pathname);
317
- else
318
- die(_("bad ls-files format: %%%.*s"), (int)len, start);
319
-
320
- return len;
321
-}
265
266
static void show_ce_fmt(struct repository *repo, const struct cache_entry *ce,
267
const char *format, const char *fullname) {
325
- struct show_index_data data = {
326
- .pathname = fullname,
327
- .istate = repo->index,
328
- .ce = ce,
329
- };
268
struct strbuf sb = STRBUF_INIT;
269
332
- strbuf_expand(&sb, format, expand_show_index, &data);
270
+ while (strbuf_expand_step(&sb, &format)) {
271
+ const char *end;
272
+ size_t len;
273
+ struct stat st;
274
+
275
+ if (skip_prefix(format, "%", &format))
276
+ strbuf_addch(&sb, '%');
277
+ else if ((len = strbuf_expand_literal_cb(&sb, format, NULL)))
278
+ format += len;
279
+ else if (*format != '(')
280
+ die(_("bad ls-files format: element '%s' "
281
+ "does not start with '('"), format);
282
+ else if (!(end = strchr(format + 1, ')')))
283
+ die(_("bad ls-files format: element '%s' "
284
+ "does not end in ')'"), format);
285
+ else if (skip_prefix(format, "(objectmode)", &format))
286
+ strbuf_addf(&sb, "%06o", ce->ce_mode);
287
+ else if (skip_prefix(format, "(objectname)", &format))
288
+ strbuf_add_unique_abbrev(&sb, &ce->oid, abbrev);
289
+ else if (skip_prefix(format, "(objecttype)", &format))
290
+ strbuf_addstr(&sb, type_name(object_type(ce->ce_mode)));
291
+ else if (skip_prefix(format, "(objectsize:padded)", &format))
292
+ expand_objectsize(&sb, &ce->oid,
293
+ object_type(ce->ce_mode), 1);
294
+ else if (skip_prefix(format, "(objectsize)", &format))
295
+ expand_objectsize(&sb, &ce->oid,
296
+ object_type(ce->ce_mode), 0);
297
+ else if (skip_prefix(format, "(stage)", &format))
298
+ strbuf_addf(&sb, "%d", ce_stage(ce));
299
+ else if (skip_prefix(format, "(eolinfo:index)", &format))
300
+ strbuf_addstr(&sb, S_ISREG(ce->ce_mode) ?
301
+ get_cached_convert_stats_ascii(repo->index,
302
+ ce->name) : "");
303
+ else if (skip_prefix(format, "(eolinfo:worktree)", &format))
304
+ strbuf_addstr(&sb, !lstat(fullname, &st) &&
305
+ S_ISREG(st.st_mode) ?
306
+ get_wt_convert_stats_ascii(fullname) : "");
307
+ else if (skip_prefix(format, "(eolattr)", &format))
308
+ strbuf_addstr(&sb, get_convert_attr_ascii(repo->index,
309
+ fullname));
310
+ else if (skip_prefix(format, "(path)", &format))
311
+ write_name_to_buf(&sb, fullname);
312
+ else
313
+ die(_("bad ls-files format: %%%.*s"),
314
+ (int)(end - format + 1), format);
315
+ }
316
strbuf_addch(&sb, line_terminator);
317
fwrite(sb.buf, sb.len, 1, stdout);
318
strbuf_release(&sb);
builtin/ls-tree.c
+41
-66
@@ -55,63 +55,6 @@ struct ls_tree_options {
55
const char *format;
56
};
57
58
-struct show_tree_data {
59
- struct ls_tree_options *options;
60
- unsigned mode;
61
- enum object_type type;
62
- const struct object_id *oid;
63
- const char *pathname;
64
- struct strbuf *base;
65
-};
66
-
67
-static size_t expand_show_tree(struct strbuf *sb, const char *start,
68
- void *context)
69
-{
70
- struct show_tree_data *data = context;
71
- struct ls_tree_options *options = data->options;
72
- const char *end;
73
- const char *p;
74
- unsigned int errlen;
75
- size_t len = strbuf_expand_literal_cb(sb, start, NULL);
76
-
77
- if (len)
78
- return len;
79
- if (*start != '(')
80
- die(_("bad ls-tree format: element '%s' does not start with '('"), start);
81
-
82
- end = strchr(start + 1, ')');
83
- if (!end)
84
- die(_("bad ls-tree format: element '%s' does not end in ')'"), start);
85
-
86
- len = end - start + 1;
87
- if (skip_prefix(start, "(objectmode)", &p)) {
88
- strbuf_addf(sb, "%06o", data->mode);
89
- } else if (skip_prefix(start, "(objecttype)", &p)) {
90
- strbuf_addstr(sb, type_name(data->type));
91
- } else if (skip_prefix(start, "(objectsize:padded)", &p)) {
92
- expand_objectsize(sb, data->oid, data->type, 1);
93
- } else if (skip_prefix(start, "(objectsize)", &p)) {
94
- expand_objectsize(sb, data->oid, data->type, 0);
95
- } else if (skip_prefix(start, "(objectname)", &p)) {
96
- strbuf_add_unique_abbrev(sb, data->oid, options->abbrev);
97
- } else if (skip_prefix(start, "(path)", &p)) {
98
- const char *name = data->base->buf;
99
- const char *prefix = options->chomp_prefix ? options->ls_tree_prefix : NULL;
100
- struct strbuf sbuf = STRBUF_INIT;
101
- size_t baselen = data->base->len;
102
-
103
- strbuf_addstr(data->base, data->pathname);
104
- name = relative_path(data->base->buf, prefix, &sbuf);
105
- quote_c_style(name, sb, NULL, 0);
106
- strbuf_setlen(data->base, baselen);
107
- strbuf_release(&sbuf);
108
- } else {
109
- errlen = (unsigned long)len;
110
- die(_("bad ls-tree format: %%%.*s"), errlen, start);
111
- }
112
- return len;
113
-}
114
-
58
static int show_recursive(struct ls_tree_options *options, const char *base,
59
size_t baselen, const char *pathname)
60
{
@@ -150,14 +93,7 @@ static int show_tree_fmt(const struct object_id *oid, struct strbuf *base,
93
int recurse = 0;
94
struct strbuf sb = STRBUF_INIT;
95
enum object_type type = object_type(mode);
153
- struct show_tree_data cb_data = {
154
- .options = options,
155
- .mode = mode,
156
- .type = type,
157
- .oid = oid,
158
- .pathname = pathname,
159
- .base = base,
160
- };
96
+ const char *format = options->format;
97
98
if (type == OBJ_TREE && show_recursive(options, base->buf, base->len, pathname))
99
recurse = READ_TREE_RECURSIVE;
@@ -166,7 +102,46 @@ static int show_tree_fmt(const struct object_id *oid, struct strbuf *base,
102
if (type == OBJ_BLOB && (options->ls_options & LS_TREE_ONLY))
103
return 0;
104
169
- strbuf_expand(&sb, options->format, expand_show_tree, &cb_data);
105
+ while (strbuf_expand_step(&sb, &format)) {
106
+ const char *end;
107
+ size_t len;
108
+
109
+ if (skip_prefix(format, "%", &format))
110
+ strbuf_addch(&sb, '%');
111
+ else if ((len = strbuf_expand_literal_cb(&sb, format, NULL)))
112
+ format += len;
113
+ else if (*format != '(')
114
+ die(_("bad ls-tree format: element '%s' "
115
+ "does not start with '('"), format);
116
+ else if (!(end = strchr(format + 1, ')')))
117
+ die(_("bad ls-tree format: element '%s' "
118
+ "does not end in ')'"), format);
119
+ else if (skip_prefix(format, "(objectmode)", &format))
120
+ strbuf_addf(&sb, "%06o", mode);
121
+ else if (skip_prefix(format, "(objecttype)", &format))
122
+ strbuf_addstr(&sb, type_name(type));
123
+ else if (skip_prefix(format, "(objectsize:padded)", &format))
124
+ expand_objectsize(&sb, oid, type, 1);
125
+ else if (skip_prefix(format, "(objectsize)", &format))
126
+ expand_objectsize(&sb, oid, type, 0);
127
+ else if (skip_prefix(format, "(objectname)", &format))
128
+ strbuf_add_unique_abbrev(&sb, oid, options->abbrev);
129
+ else if (skip_prefix(format, "(path)", &format)) {
130
+ const char *name;
131
+ const char *prefix = options->chomp_prefix ?
132
+ options->ls_tree_prefix : NULL;
133
+ struct strbuf sbuf = STRBUF_INIT;
134
+ size_t baselen = base->len;
135
+
136
+ strbuf_addstr(base, pathname);
137
+ name = relative_path(base->buf, prefix, &sbuf);
138
+ quote_c_style(name, &sb, NULL, 0);
139
+ strbuf_setlen(base, baselen);
140
+ strbuf_release(&sbuf);
141
+ } else
142
+ die(_("bad ls-tree format: %%%.*s"),
143
+ (int)(end - format + 1), format);
144
+ }
145
strbuf_addch(&sb, options->null_termination ? '\0' : '\n');
146
fwrite(sb.buf, sb.len, 1, stdout);
147
strbuf_release(&sb);
daemon.c
+19
-42
@@ -144,42 +144,6 @@ static void NORETURN daemon_die(const char *err, va_list params)
144
exit(1);
145
}
146
147
-struct expand_path_context {
148
- const char *directory;
149
- struct hostinfo *hostinfo;
150
-};
151
-
152
-static size_t expand_path(struct strbuf *sb, const char *placeholder, void *ctx)
153
-{
154
- struct expand_path_context *context = ctx;
155
- struct hostinfo *hi = context->hostinfo;
156
-
157
- switch (placeholder[0]) {
158
- case 'H':
159
- strbuf_addbuf(sb, &hi->hostname);
160
- return 1;
161
- case 'C':
162
- if (placeholder[1] == 'H') {
163
- strbuf_addstr(sb, get_canon_hostname(hi));
164
- return 2;
165
- }
166
- break;
167
- case 'I':
168
- if (placeholder[1] == 'P') {
169
- strbuf_addstr(sb, get_ip_address(hi));
170
- return 2;
171
- }
172
- break;
173
- case 'P':
174
- strbuf_addbuf(sb, &hi->tcp_port);
175
- return 1;
176
- case 'D':
177
- strbuf_addstr(sb, context->directory);
178
- return 1;
179
- }
180
- return 0;
181
-}
182
-
147
static const char *path_ok(const char *directory, struct hostinfo *hi)
148
{
149
static char rpath[PATH_MAX];
@@ -223,10 +187,7 @@ static const char *path_ok(const char *directory, struct hostinfo *hi)
187
}
188
else if (interpolated_path && hi->saw_extended_args) {
189
struct strbuf expanded_path = STRBUF_INIT;
226
- struct expand_path_context context;
227
-
228
- context.directory = directory;
229
- context.hostinfo = hi;
190
+ const char *format = interpolated_path;
191
192
if (*dir != '/') {
193
/* Allow only absolute */
@@ -234,8 +195,24 @@ static const char *path_ok(const char *directory, struct hostinfo *hi)
195
return NULL;
196
}
197
237
- strbuf_expand(&expanded_path, interpolated_path,
238
- expand_path, &context);
198
+ while (strbuf_expand_step(&expanded_path, &format)) {
199
+ if (skip_prefix(format, "%", &format))
200
+ strbuf_addch(&expanded_path, '%');
201
+ else if (skip_prefix(format, "H", &format))
202
+ strbuf_addbuf(&expanded_path, &hi->hostname);
203
+ else if (skip_prefix(format, "CH", &format))
204
+ strbuf_addstr(&expanded_path,
205
+ get_canon_hostname(hi));
206
+ else if (skip_prefix(format, "IP", &format))
207
+ strbuf_addstr(&expanded_path,
208
+ get_ip_address(hi));
209
+ else if (skip_prefix(format, "P", &format))
210
+ strbuf_addbuf(&expanded_path, &hi->tcp_port);
211
+ else if (skip_prefix(format, "D", &format))
212
+ strbuf_addstr(&expanded_path, directory);
213
+ else
214
+ strbuf_addch(&expanded_path, '%');
215
+ }
216
217
rlen = strlcpy(interp_path, expanded_path.buf,
218
sizeof(interp_path));
pretty.c
+43
-29
@@ -1254,9 +1254,19 @@ static struct strbuf *expand_separator(struct strbuf *sb,
1254
const char *argval, size_t arglen)
1255
{
1256
char *fmt = xstrndup(argval, arglen);
1257
+ const char *format = fmt;
1258
1259
strbuf_reset(sb);
1259
- strbuf_expand(sb, fmt, strbuf_expand_literal_cb, NULL);
1260
+ while (strbuf_expand_step(sb, &format)) {
1261
+ size_t len;
1262
+
1263
+ if (skip_prefix(format, "%", &format))
1264
+ strbuf_addch(sb, '%');
1265
+ else if ((len = strbuf_expand_literal_cb(sb, format, NULL)))
1266
+ format += len;
1267
+ else
1268
+ strbuf_addch(sb, '%');
1269
+ }
1270
free(fmt);
1271
return sb;
1272
}
@@ -1803,7 +1813,7 @@ static size_t format_and_pad_commit(struct strbuf *sb, /* in UTF-8 */
1813
1814
static size_t format_commit_item(struct strbuf *sb, /* in UTF-8 */
1815
const char *placeholder,
1806
- void *context)
1816
+ struct format_commit_context *context)
1817
{
1818
size_t consumed, orig_len;
1819
enum {
@@ -1842,7 +1852,7 @@ static size_t format_commit_item(struct strbuf *sb, /* in UTF-8 */
1852
}
1853
1854
orig_len = sb->len;
1845
- if (((struct format_commit_context *)context)->flush_type != no_flush)
1855
+ if ((context)->flush_type != no_flush)
1856
consumed = format_and_pad_commit(sb, placeholder, context);
1857
else
1858
consumed = format_commit_one(sb, placeholder, context);
@@ -1861,30 +1871,6 @@ static size_t format_commit_item(struct strbuf *sb, /* in UTF-8 */
1871
return consumed + 1;
1872
}
1873
1864
-static size_t userformat_want_item(struct strbuf *sb UNUSED,
1865
- const char *placeholder,
1866
- void *context)
1867
-{
1868
- struct userformat_want *w = context;
1869
-
1870
- if (*placeholder == '+' || *placeholder == '-' || *placeholder == ' ')
1871
- placeholder++;
1872
-
1873
- switch (*placeholder) {
1874
- case 'N':
1875
- w->notes = 1;
1876
- break;
1877
- case 'S':
1878
- w->source = 1;
1879
- break;
1880
- case 'd':
1881
- case 'D':
1882
- w->decorate = 1;
1883
- break;
1884
- }
1885
- return 0;
1886
-}
1887
-
1874
void userformat_find_requirements(const char *fmt, struct userformat_want *w)
1875
{
1876
struct strbuf dummy = STRBUF_INIT;
@@ -1894,7 +1880,26 @@ void userformat_find_requirements(const char *fmt, struct userformat_want *w)
1880
return;
1881
fmt = user_format;
1882
}
1897
- strbuf_expand(&dummy, fmt, userformat_want_item, w);
1883
+ while (strbuf_expand_step(&dummy, &fmt)) {
1884
+ if (skip_prefix(fmt, "%", &fmt))
1885
+ continue;
1886
+
1887
+ if (*fmt == '+' || *fmt == '-' || *fmt == ' ')
1888
+ fmt++;
1889
+
1890
+ switch (*fmt) {
1891
+ case 'N':
1892
+ w->notes = 1;
1893
+ break;
1894
+ case 'S':
1895
+ w->source = 1;
1896
+ break;
1897
+ case 'd':
1898
+ case 'D':
1899
+ w->decorate = 1;
1900
+ break;
1901
+ }
1902
+ }
1903
strbuf_release(&dummy);
1904
}
1905
@@ -1912,7 +1917,16 @@ void repo_format_commit_message(struct repository *r,
1917
const char *output_enc = pretty_ctx->output_encoding;
1918
const char *utf8 = "UTF-8";
1919
1915
- strbuf_expand(sb, format, format_commit_item, &context);
1920
+ while (strbuf_expand_step(sb, &format)) {
1921
+ size_t len;
1922
+
1923
+ if (skip_prefix(format, "%", &format))
1924
+ strbuf_addch(sb, '%');
1925
+ else if ((len = format_commit_item(sb, format, &context)))
1926
+ format += len;
1927
+ else
1928
+ strbuf_addch(sb, '%');
1929
+ }
1930
rewrap_message_tail(sb, &context, 0, 0, 0);
1931
1932
/*
strbuf.c
-20
@@ -427,26 +427,6 @@ int strbuf_expand_step(struct strbuf *sb, const char **formatp)
427
return 1;
428
}
429
430
-void strbuf_expand(struct strbuf *sb, const char *format, expand_fn_t fn,
431
- void *context)
432
-{
433
- while (strbuf_expand_step(sb, &format)) {
434
- size_t consumed;
435
-
436
- if (*format == '%') {
437
- strbuf_addch(sb, '%');
438
- format++;
439
- continue;
440
- }
441
-
442
- consumed = fn(sb, format, context);
443
- if (consumed)
444
- format += consumed;
445
- else
446
- strbuf_addch(sb, '%');
447
- }
448
-}
449
-
430
size_t strbuf_expand_literal_cb(struct strbuf *sb,
431
const char *placeholder,
432
void *context UNUSED)
strbuf.h
+3
-34
@@ -318,40 +318,9 @@ const char *strbuf_join_argv(struct strbuf *buf, int argc,
318
const char **argv, char delim);
319
320
/**
321
- * This function can be used to expand a format string containing
322
- * placeholders. To that end, it parses the string and calls the specified
323
- * function for every percent sign found.
324
- *
325
- * The callback function is given a pointer to the character after the `%`
326
- * and a pointer to the struct strbuf. It is expected to add the expanded
327
- * version of the placeholder to the strbuf, e.g. to add a newline
328
- * character if the letter `n` appears after a `%`. The function returns
329
- * the length of the placeholder recognized and `strbuf_expand()` skips
330
- * over it.
331
- *
332
- * The format `%%` is automatically expanded to a single `%` as a quoting
333
- * mechanism; callers do not need to handle the `%` placeholder themselves,
334
- * and the callback function will not be invoked for this placeholder.
335
- *
336
- * All other characters (non-percent and not skipped ones) are copied
337
- * verbatim to the strbuf. If the callback returned zero, meaning that the
338
- * placeholder is unknown, then the percent sign is copied, too.
339
- *
340
- * In order to facilitate caching and to make it possible to give
341
- * parameters to the callback, `strbuf_expand()` passes a context
342
- * pointer with any kind of data.
343
- */
344
-typedef size_t (*expand_fn_t) (struct strbuf *sb,
345
- const char *placeholder,
346
- void *context);
347
-void strbuf_expand(struct strbuf *sb,
348
- const char *format,
349
- expand_fn_t fn,
350
- void *context);
351
-
352
-/**
353
- * Used as callback for `strbuf_expand` to only expand literals
354
- * (i.e. %n and %xNN). The context argument is ignored.
321
+ * Used with `strbuf_expand_step` to expand the literals %n and %x
322
+ * followed by two hexadecimal digits. Returns the number of recognized
323
+ * characters. The context argument is ignored.
324
*/
325
size_t strbuf_expand_literal_cb(struct strbuf *sb,
326
const char *placeholder,