Skip to content

Commit 90b8610

Browse files
rustyrussellsangbida
authored andcommitted
bkpr-report: enhance tag syntax from {tag:replacement} to {tag?if-set:if-not-set}
This is more powerful, but we need to do this before release. It lets us do things like: {credit?+{credit}:0} to only put a `+` in front of no-zero credit, and {txid?TXID {txid}:Outpoint {outpoint}} to print either `TXID xxx` or `Outpoint xxx`. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent bc93145 commit 90b8610

File tree

6 files changed

+286
-123
lines changed

6 files changed

+286
-123
lines changed

contrib/msggen/msggen/schema.json

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5160,15 +5160,16 @@
51605160
"{currencydebit}: debit amount converted into bkpr-currency",
51615161
"{currencycreditdebit}: +credit or -debit (or 0) in bkpr-currency",
51625162
"",
5163-
"If a field is unavailable, it expands to an empty string.",
5163+
"If a field is unavailable, it expands to an empty string (or 0 for credit, debit, fees and creditdebit).",
51645164
"",
5165-
"You can provide fallback with ?, which is used when the tag is not present (or zero, for credit, debit, fees and creditdebit). This fallback can include more tags:",
5166-
" * {outpoint?NONE}",
5167-
" * {payment_id?txid: {txid?UNKNOWN}}",
5168-
"The first one the outpoint, or NONE if that is not available. ",
5169-
"The second prints the payment_id, or if that is not available, the string 'txid: ' followed by the txid, or if that is not available, 'txid: UNKNOWN'.",
5170-
"",
5171-
"The text after ? is used only if that tag would otherwise be empty.",
5165+
"Tags support C-style conditional syntax: {tag[?if-set][:if-not-set]}",
5166+
" * if-set: text to use when the tag is present (and non-zero for credit, debit, fees and creditdebit). Default is the tag value itself.",
5167+
" * if-not-set: text to use when the tag is absent (or zero for amount fields). Default is empty string (or 0 for amount fields).",
5168+
"Either or both parts may be omitted, and each part can itself contain tags. For example:",
5169+
" * {outpoint:NONE}: the outpoint value, or 'NONE' if not available",
5170+
" * {credit:0.00}: the credit value, or '0.00' if zero",
5171+
" * {outpoint?[{outpoint}]:NONE}: '[<value>]' if outpoint is available, or 'NONE' if not",
5172+
" * {payment_id:{txid:UNKNOWN}}: the payment_id, or the txid if no payment_id, or 'UNKNOWN' if neither",
51725173
"",
51735174
"To include a literal {, write {{."
51745175
]

doc/schemas/bkpr-report.json

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,16 @@
4646
"{currencydebit}: debit amount converted into bkpr-currency",
4747
"{currencycreditdebit}: +credit or -debit (or 0) in bkpr-currency",
4848
"",
49-
"If a field is unavailable, it expands to an empty string.",
49+
"If a field is unavailable, it expands to an empty string (or 0 for credit, debit, fees and creditdebit).",
5050
"",
51-
"You can provide fallback with ?, which is used when the tag is not present (or zero, for credit, debit, fees and creditdebit). This fallback can include more tags:",
52-
" * {outpoint?NONE}",
53-
" * {payment_id?txid: {txid?UNKNOWN}}",
54-
"The first one the outpoint, or NONE if that is not available. ",
55-
"The second prints the payment_id, or if that is not available, the string 'txid: ' followed by the txid, or if that is not available, 'txid: UNKNOWN'.",
56-
"",
57-
"The text after ? is used only if that tag would otherwise be empty.",
51+
"Tags support C-style conditional syntax: {tag[?if-set][:if-not-set]}",
52+
" * if-set: text to use when the tag is present (and non-zero for credit, debit, fees and creditdebit). Default is the tag value itself.",
53+
" * if-not-set: text to use when the tag is absent (or zero for amount fields). Default is empty string (or 0 for amount fields).",
54+
"Either or both parts may be omitted, and each part can itself contain tags. For example:",
55+
" * {outpoint:NONE}: the outpoint value, or 'NONE' if not available",
56+
" * {credit:0.00}: the credit value, or '0.00' if zero",
57+
" * {outpoint?[{outpoint}]:NONE}: '[<value>]' if outpoint is available, or 'NONE' if not",
58+
" * {payment_id:{txid:UNKNOWN}}: the payment_id, or the txid if no payment_id, or 'UNKNOWN' if neither",
5859
"",
5960
"To include a literal {, write {{."
6061
]

plugins/bkpr/report.c

Lines changed: 54 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,10 @@ struct report_format {
220220
const struct bkpr *bkpr,
221221
const struct income_event *e);
222222
const char **str;
223-
/* If fmt returns NULL, evaluate these instead. */
224-
struct report_format **alt;
223+
/* If fmt returns non-NULL (and non-ZERO_AMOUNT), evaluate these instead of the value. */
224+
struct report_format **ifset;
225+
/* If fmt returns NULL (or ZERO_AMOUNT when either ifset/ifnotset is non-NULL), evaluate these. */
226+
struct report_format **ifnotset;
225227
};
226228

227229
static void add_literal(struct report_format *f,
@@ -231,15 +233,18 @@ static void add_literal(struct report_format *f,
231233
tal_arr_expand(&f->fmt, NULL);
232234
tal_arr_expand(&f->str,
233235
tal_strndup(f->str, *start, end - *start));
234-
tal_arr_expand(&f->alt, NULL);
236+
tal_arr_expand(&f->ifset, NULL);
237+
tal_arr_expand(&f->ifnotset, NULL);
235238
*start = end;
236239
}
237240
}
238241

242+
/* alt_term is a secondary loop terminator (in addition to term); '\0' means none. */
239243
static struct report_format *
240244
parse_report_format(const tal_t *ctx,
241245
const char **start,
242246
char term,
247+
char alt_term,
243248
const char **err)
244249
{
245250
const char *p;
@@ -248,11 +253,12 @@ parse_report_format(const tal_t *ctx,
248253
f = tal(ctx, struct report_format);
249254
f->fmt = tal_arr(f, typeof(*f->fmt), 0);
250255
f->str = tal_arr(f, const char *, 0);
251-
f->alt = tal_arr(f, struct report_format *, 0);
256+
f->ifset = tal_arr(f, struct report_format *, 0);
257+
f->ifnotset = tal_arr(f, struct report_format *, 0);
252258

253259
p = *start;
254-
while (*p != term) {
255-
struct report_format *alt;
260+
while (*p != term && !(alt_term && *p == alt_term)) {
261+
struct report_format *ifset, *ifnotset;
256262
const struct report_tag *rt;
257263

258264
if (*p == '\0') {
@@ -273,7 +279,8 @@ parse_report_format(const tal_t *ctx,
273279
lit = tal_strcat(tmpctx, take(lit), "{");
274280
tal_arr_expand(&f->fmt, NULL);
275281
tal_arr_expand(&f->str, lit);
276-
tal_arr_expand(&f->alt, NULL);
282+
tal_arr_expand(&f->ifset, NULL);
283+
tal_arr_expand(&f->ifnotset, NULL);
277284
p += 2;
278285
*start = p;
279286
continue;
@@ -282,7 +289,7 @@ parse_report_format(const tal_t *ctx,
282289
/* Emit preceding literal, if any. */
283290
add_literal(f, start, p);
284291

285-
const char *endtag = p + 1 + strcspn(p+1, "?}");
292+
const char *endtag = p + 1 + strcspn(p+1, "?:}");
286293
if (*endtag == '\0') {
287294
*err = tal_fmt(ctx, "Unterminated tag %s", p + 1);
288295
return tal_free(f);
@@ -296,25 +303,45 @@ parse_report_format(const tal_t *ctx,
296303
return tal_free(f);
297304
}
298305

306+
ifset = ifnotset = NULL;
299307
if (*endtag == '?') {
308+
/* Parse if-set, which ends at ':' or '}' */
300309
*start = endtag + 1;
301-
alt = parse_report_format(f, start, '}', err);
302-
if (!alt) {
303-
/* Steal error upwards! */
310+
ifset = parse_report_format(f, start, '}', ':', err);
311+
if (!ifset) {
304312
tal_steal(ctx, *err);
305313
return tal_free(f);
306314
}
307-
/* Consume final } */
315+
if (**start == ':') {
316+
/* Parse if-not-set */
317+
(*start)++;
318+
ifnotset = parse_report_format(f, start, '}', '\0', err);
319+
if (!ifnotset) {
320+
tal_steal(ctx, *err);
321+
return tal_free(f);
322+
}
323+
}
324+
/* Consume final '}' */
325+
(*start)++;
326+
} else if (*endtag == ':') {
327+
/* Only if-not-set */
328+
*start = endtag + 1;
329+
ifnotset = parse_report_format(f, start, '}', '\0', err);
330+
if (!ifnotset) {
331+
tal_steal(ctx, *err);
332+
return tal_free(f);
333+
}
334+
/* Consume final '}' */
308335
(*start)++;
309336
} else {
310337
assert(*endtag == '}');
311-
alt = NULL;
312338
*start = endtag + 1;
313339
}
314340

315341
tal_arr_expand(&f->fmt, rt->fmt);
316342
tal_arr_expand(&f->str, NULL);
317-
tal_arr_expand(&f->alt, alt);
343+
tal_arr_expand(&f->ifset, ifset);
344+
tal_arr_expand(&f->ifnotset, ifnotset);
318345

319346
p = *start;
320347
}
@@ -336,7 +363,7 @@ struct command_result *param_report_format(struct command *cmd,
336363
if (ret)
337364
return ret;
338365

339-
*format = parse_report_format(cmd, &start, '\0', &err);
366+
*format = parse_report_format(cmd, &start, '\0', '\0', &err);
340367
if (!*format)
341368
return command_fail_badparam(cmd, name, buffer, tok, err);
342369

@@ -420,19 +447,24 @@ static char *format_event(const tal_t *ctx,
420447
}
421448

422449
v = fmt->fmt[i](tmpctx, bkpr, e);
423-
/* If there's an alternative, we treat ZERO_AMOUNT as missing. */
424-
if (v == ZERO_AMOUNT && fmt->alt[i])
450+
/* Treat ZERO_AMOUNT as absent when there are conditionals. */
451+
if (v == ZERO_AMOUNT && (fmt->ifset[i] || fmt->ifnotset[i]))
425452
v = NULL;
426453

427454
if (v) {
428-
v = escape_value(tmpctx, v, esc);
429-
out = tal_strcat(ctx, take(out), v);
455+
if (fmt->ifset[i]) {
456+
out = tal_strcat(ctx, take(out),
457+
format_event(tmpctx, fmt->ifset[i], esc, bkpr, e));
458+
} else {
459+
out = tal_strcat(ctx, take(out),
460+
escape_value(tmpctx, v, esc));
461+
}
430462
continue;
431463
}
432464

433-
if (fmt->alt[i]) {
434-
char *alt = format_event(tmpctx, fmt->alt[i], esc, bkpr, e);
435-
out = tal_strcat(ctx, take(out), alt);
465+
if (fmt->ifnotset[i]) {
466+
out = tal_strcat(ctx, take(out),
467+
format_event(tmpctx, fmt->ifnotset[i], esc, bkpr, e));
436468
}
437469
}
438470

0 commit comments

Comments
 (0)