NeoMutt  2025-12-11-58-g09398d
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
node_expando.c
Go to the documentation of this file.
1
23
29
30#include "config.h"
31#include <limits.h>
32#include <stdio.h>
33#include "mutt/lib.h"
34#include "node_expando.h"
35#include "color/lib.h"
36#include "definition.h"
37#include "format.h"
38#include "helpers.h"
39#include "mutt_thread.h"
40#include "node.h"
41#include "parse.h"
42#include "render.h"
43
49{
51
52 // NOTE(g0mb4): Expando definition should contain this
53 priv->color = -1;
54
55 return priv;
56}
57
63{
64 if (!ptr || !*ptr)
65 return;
66
67 FREE(ptr);
68}
69
77struct ExpandoNode *node_expando_new(struct ExpandoFormat *fmt, int did, int uid)
78{
79 struct ExpandoNode *node = node_new();
80
81 node->type = ENT_EXPANDO;
82 node->did = did;
83 node->uid = uid;
85
86 node->format = fmt;
87
90
91 return node;
92}
93
99void node_expando_set_color(const struct ExpandoNode *node, int cid)
100{
101 if (!node || (node->type != ENT_EXPANDO) || !node->ndata)
102 return;
103
104 struct NodeExpandoPrivate *priv = node->ndata;
105
106 priv->color = cid;
107}
108
114void node_expando_set_has_tree(const struct ExpandoNode *node, bool has_tree)
115{
116 if (!node || (node->type != ENT_EXPANDO) || !node->ndata)
117 return;
118
119 struct NodeExpandoPrivate *priv = node->ndata;
120
121 priv->has_tree = has_tree;
122}
123
135struct ExpandoFormat *parse_format(const char *str, const char **parsed_until,
136 struct ExpandoParseError *err)
137{
138 if (!str || !parsed_until || !err)
139 return NULL;
140
141 const char *start = str;
142
143 struct ExpandoFormat *fmt = MUTT_MEM_CALLOC(1, struct ExpandoFormat);
144
145 fmt->leader = ' ';
147 fmt->min_cols = 0;
148 fmt->max_cols = -1;
149
150 if (*str == '-')
151 {
153 str++;
154 }
155 else if (*str == '=')
156 {
158 str++;
159 }
160
161 if (*str == '0')
162 {
163 // Ignore '0' with left-justification
164 if (fmt->justification != JUSTIFY_LEFT)
165 fmt->leader = '0';
166 str++;
167 }
168
169 // Parse the width (min_cols)
170 if (mutt_isdigit(*str))
171 {
172 unsigned short number = 0;
173 const char *end_ptr = mutt_str_atous(str, &number);
174
175 if (!end_ptr || (number == USHRT_MAX))
176 {
177 err->position = str;
178 snprintf(err->message, sizeof(err->message), _("Invalid number: %s"), str);
179 FREE(&fmt);
180 return NULL;
181 }
182
183 fmt->min_cols = number;
184 str = end_ptr;
185 }
186
187 // Parse the precision (max_cols)
188 if (*str == '.')
189 {
190 str++;
191
192 unsigned short number = 1;
193
194 if (mutt_isdigit(*str))
195 {
196 const char *end_ptr = mutt_str_atous(str, &number);
197
198 if (!end_ptr || (number == USHRT_MAX))
199 {
200 err->position = str;
201 snprintf(err->message, sizeof(err->message), _("Invalid number: %s"), str);
202 FREE(&fmt);
203 return NULL;
204 }
205
206 str = end_ptr;
207 }
208 else
209 {
210 number = 0;
211 }
212
213 fmt->leader = (number == 0) ? ' ' : '0';
214 fmt->max_cols = number;
215 }
216
217 // A modifier of '_' before the letter means force lower case
218 if (*str == '_')
219 {
220 fmt->lower = true;
221 str++;
222 }
223
224 if (str == start) // Failed to parse anything
225 FREE(&fmt);
226
227 if (fmt && (fmt->min_cols == 0) && (fmt->max_cols == -1) && !fmt->lower)
228 FREE(&fmt);
229
230 *parsed_until = str;
231 return fmt;
232}
233
244struct ExpandoNode *parse_short_name(const char *str, const struct ExpandoDefinition *defs,
245 ExpandoParserFlags flags,
246 struct ExpandoFormat *fmt, const char **parsed_until,
247 struct ExpandoParseError *err)
248{
249 if (!str || !defs)
250 return NULL;
251
252 const struct ExpandoDefinition *def = defs;
253 for (; def && (def->short_name || def->long_name); def++)
254 {
255 size_t len = mutt_str_len(def->short_name);
256
257 if (mutt_strn_equal(def->short_name, str, len))
258 {
259 if (def->parse)
260 {
261 return def->parse(str, fmt, def->did, def->uid, flags, parsed_until, err);
262 }
263 else
264 {
265 *parsed_until = str + len;
266 return node_expando_new(fmt, def->did, def->uid);
267 }
268 }
269 }
270
271 return NULL;
272}
273
284struct ExpandoNode *parse_long_name(const char *str, const struct ExpandoDefinition *defs,
285 ExpandoParserFlags flags,
286 struct ExpandoFormat *fmt, const char **parsed_until,
287 struct ExpandoParseError *err)
288{
289 if (!str || !defs)
290 return NULL;
291
292 const struct ExpandoDefinition *def = defs;
293 for (; def && (def->short_name || def->long_name); def++)
294 {
295 if (!def->long_name)
296 continue;
297
298 size_t len = mutt_str_len(def->long_name);
299
300 if (mutt_strn_equal(def->long_name, str, len))
301 {
302 *parsed_until = str + len;
303 if (def->parse)
304 {
305 struct ExpandoNode *node = def->parse(str, fmt, def->did, def->uid,
306 flags, parsed_until, err);
307 if (node || (err->message[0] != '\0'))
308 return node;
309 }
310 else
311 {
312 if (str[len] != '}') // Not an exact match
313 continue;
314
315 return node_expando_new(fmt, def->did, def->uid);
316 }
317 }
318 }
319
320 return NULL;
321}
322
332struct ExpandoNode *node_expando_parse(const char *str, const struct ExpandoDefinition *defs,
333 ExpandoParserFlags flags, const char **parsed_until,
334 struct ExpandoParseError *err)
335{
336 ASSERT(str[0] == '%');
337 str++;
338
339 struct ExpandoFormat *fmt = parse_format(str, parsed_until, err);
340 if (err->position)
341 {
342 FREE(&fmt);
343 return NULL;
344 }
345
346 str = *parsed_until;
347
348 struct ExpandoNode *node = parse_short_name(str, defs, flags, fmt, parsed_until, err);
349 if (node)
350 return node;
351
352 if (!err->position)
353 {
354 err->position = *parsed_until;
355 // L10N: e.g. "Unknown expando: %Q"
356 snprintf(err->message, sizeof(err->message), _("Unknown expando: %%%.1s"), *parsed_until);
357 }
358
359 FREE(&fmt);
360 return NULL;
361}
362
372struct ExpandoNode *node_expando_parse_name(const char *str,
373 const struct ExpandoDefinition *defs,
374 ExpandoParserFlags flags, const char **parsed_until,
375 struct ExpandoParseError *err)
376{
377 ASSERT(str[0] == '%');
378 str++;
379
380 struct ExpandoFormat *fmt = parse_format(str, parsed_until, err);
381 if (err->position)
382 goto fail;
383
384 str = *parsed_until;
385
386 if (str[0] != '{')
387 goto fail;
388
389 str++;
390
391 struct ExpandoNode *node = parse_long_name(str, defs, flags, fmt, parsed_until, err);
392 if (!node)
393 goto fail;
394
395 fmt = NULL; // owned by the node, now
396
397 if ((*parsed_until)[0] == '}')
398 {
399 (*parsed_until)++;
400 return node;
401 }
402
403 node_free(&node);
404
405fail:
406 FREE(&fmt);
407 return NULL;
408}
409
416const char *skip_until_ch(const char *str, char terminator)
417{
418 while (str[0] != '\0')
419 {
420 if (*str == terminator)
421 break;
422
423 if (str[0] == '\\') // Literal character
424 {
425 if (str[1] == '\0')
426 return str + 1;
427
428 str++;
429 }
430
431 str++;
432 }
433
434 return str;
435}
436
448struct ExpandoNode *node_expando_parse_enclosure(const char *str, int did,
449 int uid, char terminator,
450 struct ExpandoFormat *fmt,
451 const char **parsed_until,
452 struct ExpandoParseError *err)
453
454{
455 str++; // skip opening char
456
457 const char *expando_end = skip_until_ch(str, terminator);
458
459 if (*expando_end != terminator)
460 {
461 err->position = expando_end;
462 snprintf(err->message, sizeof(err->message),
463 // L10N: Expando is missing a terminator character
464 // e.g. "%[..." is missing the final ']'
465 _("Expando is missing terminator: '%c'"), terminator);
466 return NULL;
467 }
468
469 *parsed_until = expando_end + 1;
470
471 struct ExpandoNode *node = node_expando_new(fmt, did, uid);
472
473 struct Buffer *buf = buf_pool_get();
474 for (; str < expando_end; str++)
475 {
476 if (str[0] == '\\')
477 continue;
478 buf_addch(buf, str[0]);
479 }
480
481 node->text = buf_strdup(buf);
482 buf_pool_release(&buf);
483
484 return node;
485}
486
492void add_color(struct Buffer *buf, enum ColorId cid)
493{
494 ASSERT(cid < MT_COLOR_MAX);
495
497 buf_addch(buf, cid);
498}
499
503int node_expando_render(const struct ExpandoNode *node,
504 const struct ExpandoRenderCallback *erc, struct Buffer *buf,
505 int max_cols, void *data, MuttFormatFlags flags)
506{
507 ASSERT(node->type == ENT_EXPANDO);
508
509 struct Buffer *buf_expando = buf_pool_get();
510 struct Buffer *buf_format = buf_pool_get();
511
512 const struct ExpandoFormat *fmt = node->format;
513 const struct NodeExpandoPrivate *priv = node->ndata;
514
515 // ---------------------------------------------------------------------------
516 // Numbers and strings get treated slightly differently. We prefer strings.
517 // This allows dates to be stored as 1729850182, but displayed as "2024-10-25".
518
519 const struct ExpandoRenderCallback *erc_match = find_get_string(erc, node->did, node->uid);
520 if (erc_match)
521 {
522 erc_match->get_string(node, data, flags, buf_expando);
523
524 if (fmt && fmt->lower)
525 buf_lower_special(buf_expando);
526 }
527 else
528 {
529 erc_match = find_get_number(erc, node->did, node->uid);
530 ASSERT(erc_match && "Unknown UID");
531
532 const long num = erc_match->get_number(node, data, flags);
533
534 int precision = 1;
535
536 if (fmt)
537 {
538 precision = fmt->max_cols;
539 if ((precision < 0) && (fmt->leader == '0'))
540 precision = fmt->min_cols;
541 }
542
543 if (num < 0)
544 precision--; // Allow space for the '-' sign
545
546 buf_printf(buf_expando, "%.*ld", precision, num);
547 }
548
549 // ---------------------------------------------------------------------------
550
551 int max = max_cols;
552 int min = 0;
553
554 if (fmt)
555 {
556 min = fmt->min_cols;
557 if (fmt->max_cols > 0)
558 max = MIN(max_cols, fmt->max_cols);
559 }
560
561 const enum FormatJustify just = fmt ? fmt->justification : JUSTIFY_LEFT;
562
563 int total_cols = format_string(buf_format, min, max, just, ' ', buf_string(buf_expando),
564 buf_len(buf_expando), priv->has_tree);
565
566 if (!buf_is_empty(buf_format))
567 {
568 if (priv->color > -1)
569 add_color(buf, priv->color);
570
571 buf_addstr(buf, buf_string(buf_format));
572
573 if (priv->color > -1)
575 }
576
577 buf_pool_release(&buf_format);
578 buf_pool_release(&buf_expando);
579
580 return total_cols;
581}
const char * mutt_str_atous(const char *str, unsigned short *dst)
Convert ASCII string to an unsigned short.
Definition atoi.c:269
int buf_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition buffer.c:161
size_t buf_len(const struct Buffer *buf)
Calculate the length of a Buffer.
Definition buffer.c:491
bool buf_is_empty(const struct Buffer *buf)
Is the Buffer empty?
Definition buffer.c:291
size_t buf_addch(struct Buffer *buf, char c)
Add a single character to a Buffer.
Definition buffer.c:241
size_t buf_addstr(struct Buffer *buf, const char *s)
Add a string to a Buffer.
Definition buffer.c:226
char * buf_strdup(const struct Buffer *buf)
Copy a Buffer's string.
Definition buffer.c:571
static const char * buf_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition buffer.h:96
Color and attribute parsing.
ColorId
List of all coloured objects.
Definition color.h:36
@ MT_COLOR_MAX
Definition color.h:98
@ MT_COLOR_INDEX
Index: default colour.
Definition color.h:87
bool mutt_isdigit(int arg)
Wrapper for isdigit(3)
Definition ctype.c:65
Define an Expando format string.
uint8_t ExpandoParserFlags
Flags for expando_parse(), e.g. EP_CONDITIONAL.
Definition definition.h:33
const struct ExpandoRenderCallback * find_get_string(const struct ExpandoRenderCallback *erc, int did, int uid)
Find a get_string() callback function.
Definition helpers.c:68
void buf_lower_special(struct Buffer *buf)
Convert to lowercase, excluding special characters.
Definition helpers.c:91
const struct ExpandoRenderCallback * find_get_number(const struct ExpandoRenderCallback *erc, int did, int uid)
Find a get_number() callback function.
Definition helpers.c:44
Shared code.
Expando Parsing.
int format_string(struct Buffer *buf, int min_cols, int max_cols, enum FormatJustify justify, char pad_char, const char *str, size_t n, bool arboreal)
Format a string, like snprintf()
Definition format.c:108
Simple string formatting.
FormatJustify
Alignment for format_string()
Definition format.h:33
@ JUSTIFY_RIGHT
Right justify the text.
Definition format.h:36
@ JUSTIFY_LEFT
Left justify the text.
Definition format.h:34
@ JUSTIFY_CENTER
Centre the text.
Definition format.h:35
int node_expando_render(const struct ExpandoNode *node, const struct ExpandoRenderCallback *erc, struct Buffer *buf, int max_cols, void *data, MuttFormatFlags flags)
Render an Expando Node - Implements ExpandoNode::render() -.
#define FREE(x)
Definition memory.h:62
#define MIN(a, b)
Definition memory.h:37
#define MUTT_MEM_CALLOC(n, type)
Definition memory.h:47
Convenience wrapper for the library headers.
#define _(a)
Definition message.h:28
bool mutt_strn_equal(const char *a, const char *b, size_t num)
Check for equality of two strings (to a maximum), safely.
Definition string.c:427
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition string.c:498
Create/manipulate threading in emails.
@ MUTT_SPECIAL_INDEX
Colour indicator.
Definition mutt_thread.h:72
struct ExpandoNode * node_new(void)
Create a new empty ExpandoNode.
Definition node.c:39
void node_free(struct ExpandoNode **ptr)
Free an ExpandoNode and its private data.
Definition node.c:48
Basic Expando Node.
@ ENT_EXPANDO
Expando, e.g. 'n'.
Definition node.h:39
struct ExpandoNode * node_expando_parse(const char *str, const struct ExpandoDefinition *defs, ExpandoParserFlags flags, const char **parsed_until, struct ExpandoParseError *err)
Parse an Expando format string.
void node_expando_private_free(void **ptr)
Free Expando private data - Implements ExpandoNode::ndata_free()
void node_expando_set_color(const struct ExpandoNode *node, int cid)
Set the colour for an Expando.
void node_expando_set_has_tree(const struct ExpandoNode *node, bool has_tree)
Set the has_tree flag for an Expando.
struct ExpandoNode * parse_long_name(const char *str, const struct ExpandoDefinition *defs, ExpandoParserFlags flags, struct ExpandoFormat *fmt, const char **parsed_until, struct ExpandoParseError *err)
Create an expando by its long name.
void add_color(struct Buffer *buf, enum ColorId cid)
Add a colour code to a buffer.
struct NodeExpandoPrivate * node_expando_private_new(void)
Create new Expando private data.
struct ExpandoNode * node_expando_new(struct ExpandoFormat *fmt, int did, int uid)
Create a new Expando ExpandoNode.
struct ExpandoNode * node_expando_parse_enclosure(const char *str, int did, int uid, char terminator, struct ExpandoFormat *fmt, const char **parsed_until, struct ExpandoParseError *err)
Parse an enclosed Expando.
struct ExpandoNode * node_expando_parse_name(const char *str, const struct ExpandoDefinition *defs, ExpandoParserFlags flags, const char **parsed_until, struct ExpandoParseError *err)
Parse an Expando format string.
const char * skip_until_ch(const char *str, char terminator)
Search a string for a terminator character.
struct ExpandoNode * parse_short_name(const char *str, const struct ExpandoDefinition *defs, ExpandoParserFlags flags, struct ExpandoFormat *fmt, const char **parsed_until, struct ExpandoParseError *err)
Create an expando by its short name.
struct ExpandoFormat * parse_format(const char *str, const char **parsed_until, struct ExpandoParseError *err)
Parse a format string.
Expando Node for an Expando.
struct Buffer * buf_pool_get(void)
Get a Buffer from the pool.
Definition pool.c:82
void buf_pool_release(struct Buffer **ptr)
Return a Buffer to the pool.
Definition pool.c:96
Render Expandos using Data.
uint8_t MuttFormatFlags
Flags for expando_render(), e.g. MUTT_FORMAT_FORCESUBJ.
Definition render.h:32
#define ASSERT(COND)
Definition signal2.h:60
String manipulation buffer.
Definition buffer.h:36
char * data
Pointer to data.
Definition buffer.h:37
Definition of a format string.
Definition definition.h:43
short uid
Unique ID in domain.
Definition definition.h:47
struct ExpandoNode *(* parse)(const char *str, struct ExpandoFormat *fmt, int did, int uid, ExpandoParserFlags flags, const char **parsed_until, struct ExpandoParseError *err)
Definition definition.h:62
const char * long_name
Long Expando name, e.g. "name".
Definition definition.h:45
short did
Domain ID.
Definition definition.h:46
const char * short_name
Short Expando name, e.g. "n".
Definition definition.h:44
Formatting information for an Expando.
Definition node.h:53
char leader
Leader character, 0 or space.
Definition node.h:57
enum FormatJustify justification
Justification: left, centre, right.
Definition node.h:56
int min_cols
Minimum number of screen columns.
Definition node.h:54
int max_cols
Maximum number of screen columns.
Definition node.h:55
bool lower
Display in lower case.
Definition node.h:58
Basic Expando Node.
Definition node.h:67
int uid
Unique ID, e.g. ED_EMA_SIZE.
Definition node.h:70
void * ndata
Private node data.
Definition node.h:77
struct ExpandoFormat * format
Formatting info.
Definition node.h:72
int(* render)(const struct ExpandoNode *node, const struct ExpandoRenderCallback *erc, struct Buffer *buf, int max_cols, void *data, MuttFormatFlags flags)
Definition node.h:92
int did
Domain ID, e.g. ED_EMAIL.
Definition node.h:69
const char * text
Node-specific text.
Definition node.h:73
enum ExpandoNodeType type
Type of Node, e.g. ENT_EXPANDO.
Definition node.h:68
void(* ndata_free)(void **ptr)
Function to free the private node data.
Definition node.h:78
Buffer for parsing errors.
Definition parse.h:37
char message[1024]
Error message.
Definition parse.h:38
const char * position
Position of error in original string.
Definition parse.h:39
get_string_t get_string
Definition render.h:80
get_number_t get_number
Definition render.h:81
Private data for an Expando -.
int color
ColorId to use.
bool has_tree
Contains tree characters, used in $index_format's s.