ohmu
·
2026-10-15
1#include "fastfetch.h"
2#include "common/format.h"
3#include "common/parsing.h"
4#include "common/textModifier.h"
5#include "common/strutil.h"
6#include "common/library.h"
7
8#include <inttypes.h>
9
10void ffFormatAppendFormatArg(FFstrbuf* buffer, const FFformatarg* formatarg) {
11 switch (formatarg->type) {
12 case FF_ARG_TYPE_INT:
13 ffStrbufAppendSInt(buffer, *(int32_t*) formatarg->value);
14 break;
15 case FF_ARG_TYPE_UINT:
16 ffStrbufAppendUInt(buffer, *(uint32_t*) formatarg->value);
17 break;
18 case FF_ARG_TYPE_UINT64:
19 ffStrbufAppendUInt(buffer, *(uint64_t*) formatarg->value);
20 break;
21 case FF_ARG_TYPE_UINT16:
22 ffStrbufAppendUInt(buffer, *(uint16_t*) formatarg->value);
23 break;
24 case FF_ARG_TYPE_UINT8:
25 ffStrbufAppendUInt(buffer, *(uint8_t*) formatarg->value);
26 break;
27 case FF_ARG_TYPE_STRING:
28 ffStrbufAppendS(buffer, (const char*) formatarg->value);
29 break;
30 case FF_ARG_TYPE_STRBUF:
31 ffStrbufAppend(buffer, (const FFstrbuf*) formatarg->value);
32 break;
33 case FF_ARG_TYPE_FLOAT:
34 ffStrbufAppendDouble(buffer, *(float*) formatarg->value, instance.config.display.fractionNdigits, instance.config.display.fractionTrailingZeros != FF_FRACTION_TRAILING_ZEROS_TYPE_NEVER);
35 break;
36 case FF_ARG_TYPE_DOUBLE:
37 ffStrbufAppendDouble(buffer, *(double*) formatarg->value, instance.config.display.fractionNdigits, instance.config.display.fractionTrailingZeros != FF_FRACTION_TRAILING_ZEROS_TYPE_NEVER);
38 break;
39 case FF_ARG_TYPE_BOOL:
40 ffStrbufAppendS(buffer, *(bool*) formatarg->value ? "true" : "false");
41 break;
42 case FF_ARG_TYPE_LIST: {
43 const FFlist* list = (const FFlist*) formatarg->value;
44 for (uint32_t i = 0; i < list->length; i++) {
45 ffStrbufAppend(buffer, FF_LIST_GET(FFstrbuf, *list, i));
46 if (i < list->length - 1) {
47 ffStrbufAppendS(buffer, ", ");
48 }
49 }
50 break;
51 }
52 case FF_ARG_TYPE_BUFFER: {
53 // Placeholder for binary data, just print the size for now
54 const FFArgBuffer* argBuffer = (const FFArgBuffer*) formatarg->value;
55 ffStrbufAppendF(buffer, "buffer(%u bytes)", argBuffer->length);
56 break;
57 }
58 default:
59 if (formatarg->type != FF_ARG_TYPE_NULL) {
60 fprintf(stderr, "Error: format string \"%s\": argument is not implemented: %i\n", buffer->chars, formatarg->type);
61 }
62 break;
63 }
64}
65
66/**
67 * @brief parses a string to a uint32_t
68 *
69 * If the string can't be parsed, or is < 1, uint32_t max is returned.
70 *
71 * @param placeholderValue the string to parse
72 * @return uint32_t the parsed value
73 */
74static uint32_t getArgumentIndex(const char* placeholderValue, uint32_t numArgs, const FFformatarg* arguments) {
75 char firstChar = placeholderValue[0];
76 if (firstChar == '\0') {
77 return 0; // use arg counter
78 }
79
80 if (firstChar >= '0' && firstChar <= '9') {
81 char* pEnd = nullptr;
82 uint32_t result = (uint32_t) strtoul(placeholderValue, &pEnd, 10);
83 if (result > numArgs) {
84 return UINT32_MAX;
85 }
86 if (*pEnd != '\0') {
87 return UINT32_MAX;
88 }
89 return result;
90 } else if (ffCharIsEnglishAlphabet(firstChar)) {
91 for (uint32_t i = 0; i < numArgs; ++i) {
92 const FFformatarg* arg = &arguments[i];
93 if (arg->name && ffStrEqualsIgnCase(placeholderValue, arg->name)) {
94 return i + 1;
95 }
96 }
97 }
98
99 return UINT32_MAX;
100}
101
102static inline void appendInvalidPlaceholder(FFstrbuf* buffer, const char* start, const FFstrbuf* placeholderValue, uint32_t index, uint32_t formatStringLength) {
103 ffStrbufAppendS(buffer, start);
104 ffStrbufAppend(buffer, placeholderValue);
105
106 if (index < formatStringLength) {
107 ffStrbufAppendC(buffer, '}');
108 }
109}
110
111static inline bool formatArgSet(const FFformatarg* arg) {
112 // clang-format off
113 return arg->value != nullptr && (
114 (arg->type == FF_ARG_TYPE_DOUBLE && *(double*) arg->value > 0.0) ||
115 (arg->type == FF_ARG_TYPE_FLOAT && *(float*) arg->value > 0.0) ||
116 (arg->type == FF_ARG_TYPE_INT && *(int32_t*) arg->value > 0) ||
117 (arg->type == FF_ARG_TYPE_STRBUF && ((FFstrbuf*) arg->value)->length > 0) ||
118 (arg->type == FF_ARG_TYPE_STRING && ffStrSet((char*) arg->value)) ||
119 (arg->type == FF_ARG_TYPE_UINT8 && *(uint8_t*) arg->value > 0) ||
120 (arg->type == FF_ARG_TYPE_UINT16 && *(uint16_t*) arg->value > 0) ||
121 (arg->type == FF_ARG_TYPE_UINT && *(uint32_t*) arg->value > 0) ||
122 (arg->type == FF_ARG_TYPE_UINT64 && *(uint64_t*) arg->value > 0) ||
123 (arg->type == FF_ARG_TYPE_BOOL && *(bool*) arg->value) ||
124 (arg->type == FF_ARG_TYPE_LIST && ((FFlist*) arg->value)->length > 0)
125 );
126 // clang-format on
127}
128
129[[maybe_unused]] static inline void normalizeArgName(FFstrbuf* dst, const char* src) {
130 ffStrbufClear(dst);
131 bool flag = false;
132 for (const char* p = src; *p; ++p) {
133 if (*p == '-') {
134 flag = true;
135 } else if (flag) {
136 ffStrbufAppendC(dst, (char) toupper((unsigned char) *p));
137 flag = false;
138 } else {
139 ffStrbufAppendC(dst, *p);
140 }
141 }
142}
143
144#if FF_HAVE_LUA
145 #include "common/lua.h"
146
147static void appendLuaError(FFstrbuf* buffer, const char* prefix, lua_State* L) {
148 const char* err = lua_tolstring(L, -1, nullptr);
149 if (err) {
150 // Drop the position Lua puts in front of an error and keep the message only. Both places
151 // that build one -- `luaG_addinfo` and `luaL_where` -- format it as `%s:%d: %s`, so a
152 // position is a colon, a line number, a colon and a space:
153 // general.preload.lua:1: <name> expected near '=' -> <name> expected near '='
154 // [string ""]:1: x -> x
155 // Not every error has one, and cutting at the first colon mangled those. `luaL_loadfilex`
156 // reports `cannot open <path>: <strerror>`, where that colon is the one after the path, so
157 // the path -- the only part naming the problem -- was dropped and `No such file or
158 // directory` was left on its own. A colon followed by a line number does not identify a
159 // position either, because a path can contain one: `cannot open /tmp/x:12:y/nope.lua: ...`
160 // has to survive whole. The trailing space is what separates the two, and a real position
161 // always has it.
162 for (const char* colon = strchr(err, ':'); colon; colon = strchr(colon + 1, ':')) {
163 const char* line = colon + 1;
164 while (*line >= '0' && *line <= '9') {
165 ++line;
166 }
167 if (line > colon + 1 && line[0] == ':' && line[1] == ' ') {
168 err = line + 2;
169 break;
170 }
171 }
172 }
173 ffStrbufAppendF(buffer, "%s: %s", prefix, err ? err : "unknown");
174}
175
176static bool parseLuaString(FFstrbuf* buffer, const char* script, uint32_t scriptLen, uint32_t numArgs, const FFformatarg* arguments) {
177 const char* err = ffLuaLoadState();
178 if (err) {
179 ffStrbufAppendF(buffer, "Lua init error: %s", err);
180 return false;
181 }
182
183 FF_STRBUF_AUTO_DESTROY argNameBuf = ffStrbufCreate();
184 bool ret = false;
185
186 lua_State* L = luaData.L;
187 // Clear stack and load chunk
188 lua_settop(L, 0);
189 if (luaL_loadbuffer(L, script, scriptLen, "") != LUA_OK) {
190 appendLuaError(buffer, "Lua load error", L);
191 } else {
192 // Build args table for name lookup only.
193 lua_createtable(L, 0, 0);
194
195 for (uint32_t i = 0; i < numArgs; ++i) {
196 const FFformatarg* arg = &arguments[i];
197 switch (arg->type) {
198 case FF_ARG_TYPE_INT:
199 lua_pushinteger(L, (lua_Integer) * (int32_t*) arg->value);
200 break;
201 case FF_ARG_TYPE_UINT:
202 lua_pushinteger(L, (lua_Integer) * (uint32_t*) arg->value);
203 break;
204 case FF_ARG_TYPE_UINT64:
205 lua_pushinteger(L, (lua_Integer) * (uint64_t*) arg->value);
206 break;
207 case FF_ARG_TYPE_UINT16:
208 lua_pushinteger(L, (lua_Integer) * (uint16_t*) arg->value);
209 break;
210 case FF_ARG_TYPE_UINT8:
211 lua_pushinteger(L, (lua_Integer) * (uint8_t*) arg->value);
212 break;
213 case FF_ARG_TYPE_FLOAT:
214 lua_pushnumber(L, (lua_Number) * (float*) arg->value);
215 break;
216 case FF_ARG_TYPE_DOUBLE:
217 lua_pushnumber(L, (lua_Number) * (double*) arg->value);
218 break;
219 case FF_ARG_TYPE_BOOL:
220 lua_pushboolean(L, *(bool*) arg->value);
221 break;
222 case FF_ARG_TYPE_STRING:
223 if (!arg->value) {
224 lua_pushlstring(L, "", 0);
225 } else {
226 lua_pushlstring(L, (const char*) arg->value, strlen((const char*) arg->value));
227 }
228 break;
229 case FF_ARG_TYPE_STRBUF: {
230 const FFstrbuf* sb = (const FFstrbuf*) arg->value;
231 lua_pushlstring(L, sb->chars, sb->length);
232 break;
233 }
234 case FF_ARG_TYPE_LIST: {
235 const FFlist* list = (const FFlist*) arg->value;
236 lua_createtable(L, 0, 0);
237 for (uint32_t li = 0; li < list->length; ++li) {
238 const FFstrbuf* item = FF_LIST_GET(FFstrbuf, *list, li);
239 lua_pushlstring(L, item->chars, item->length);
240 lua_seti(L, -2, (lua_Integer) (li + 1));
241 }
242 break;
243 }
244 default:
245 lua_pushnil(L);
246 break;
247 }
248 if (arg->name && arg->name[0]) {
249 normalizeArgName(&argNameBuf, arg->name);
250 } else {
251 ffStrbufSetF(&argNameBuf, "arg%" PRIu32, i + 1);
252 }
253 lua_setfield(L, -2, argNameBuf.chars);
254 }
255
256 if (lua_pcall(L, 1, LUA_MULTRET, 0) != LUA_OK) {
257 appendLuaError(buffer, "Lua runtime error", L);
258 } else {
259 int nresults = lua_gettop(L);
260 if (nresults == 0) {
261 ffStrbufAppendS(buffer, "Lua result error: no result");
262 } else {
263 // Convert first result to string
264 const char* res = lua_tolstring(L, 1, nullptr);
265 if (res) {
266 ffStrbufAppendS(buffer, res);
267 } else {
268 luaL_tolstring(L, 1, nullptr);
269 const char* sval = lua_tolstring(L, -1, nullptr);
270 if (sval) {
271 ffStrbufAppendS(buffer, sval);
272 }
273 }
274 ret = true;
275 }
276 }
277 }
278 lua_settop(L, 0);
279 return ret;
280}
281#endif
282
283// Returns the length of the ANSI CSI sequence at `str`, or 0 if there is none.
284// A CSI sequence is `ESC [` + parameter bytes (0x30-0x3F) + intermediate bytes (0x20-0x2F) + final byte (0x40-0x7E)
285static uint32_t ansiEscapeLength(const char* str) {
286 if (str[0] != '\e' || str[1] != '[') {
287 return 0;
288 }
289
290 const char* p = str + 2;
291 while (*p >= '0' && *p <= '?') {
292 ++p;
293 }
294 while (*p >= ' ' && *p <= '/') {
295 ++p;
296 }
297 if (*p < '@' || *p > '~') {
298 return 0; // not a complete CSI sequence
299 }
300 return (uint32_t) (p - str) + 1;
301}
302
303static bool skipAnsiEscape(FFstrbuf* in, FFstrbuf* out, FFstrbuf* trailingEscape) {
304 // Skip all ANSI escape codes at the start of the string, so that they don't count
305 // towards the truncation / padding length. A value may carry more than one of them,
306 // e.g. `\e[31m\e[1m`.
307 uint32_t prefixLen = 0;
308 while (in->length - prefixLen >= 2) {
309 uint32_t len = ansiEscapeLength(in->chars + prefixLen);
310 if (len == 0) {
311 break;
312 }
313 prefixLen += len;
314 }
315
316 if (prefixLen == 0) {
317 return false;
318 }
319
320 ffStrbufAppendNS(out, prefixLen, in->chars);
321 ffStrbufSubstrAfter(in, prefixLen - 1);
322
323 if (trailingEscape) {
324 // likely have one or more `CSI m` resets at the end of the string
325 uint32_t end = in->length;
326 while (end >= 3) {
327 uint32_t start = end - 1;
328 while (start > 0 && in->chars[start] != '\e') {
329 --start;
330 }
331 uint32_t len = ansiEscapeLength(in->chars + start);
332 if (len == 0 || start + len != end) {
333 break;
334 }
335 end = start;
336 }
337
338 if (end < in->length) {
339 ffStrbufSetNS(trailingEscape, in->length - end, in->chars + end);
340 ffStrbufSubstrBefore(in, end);
341 }
342 }
343 return true;
344}
345
346static bool parseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, uint32_t numArgs, const FFformatarg* arguments) {
347 uint32_t argCounter = 0;
348
349 uint32_t numOpenIfs = 0;
350 uint32_t numOpenNotIfs = 0;
351
352 FF_STRBUF_AUTO_DESTROY placeholderValue = ffStrbufCreate();
353
354 for (uint32_t i = 0; i < formatstr->length; ++i) {
355 // if we don't have a placeholder start just copy the chars over to output buffer
356 if (formatstr->chars[i] != '{') {
357 ffStrbufAppendC(buffer, formatstr->chars[i]);
358 continue;
359 }
360
361 // jump to next char, the start of the placeholder value
362 ++i;
363
364 // unmatched trailing '{'
365 if (i >= formatstr->length) {
366 ffStrbufAppendC(buffer, '{');
367 break;
368 }
369
370 // double {{ elvaluates to a single { and doesn't count as start
371 if (formatstr->chars[i] == '{') {
372 ffStrbufAppendC(buffer, '{');
373 continue;
374 }
375
376 ffStrbufClear(&placeholderValue);
377
378 {
379 uint32_t iEnd = ffStrbufNextIndexC(formatstr, i, '}');
380 ffStrbufAppendNS(&placeholderValue, iEnd - i, &formatstr->chars[i]);
381 i = iEnd;
382 }
383
384 char firstChar = placeholderValue.chars[0];
385
386 if (placeholderValue.length == 1) {
387 // test if for stop, if so break the loop
388 if (firstChar == '-') {
389 break;
390 }
391
392 // test for end of an if, if so do nothing
393 if (firstChar == '?') {
394 if (numOpenIfs == 0) {
395 appendInvalidPlaceholder(buffer, "{", &placeholderValue, i, formatstr->length);
396 } else {
397 --numOpenIfs;
398 }
399
400 continue;
401 }
402
403 // test for end of a not if, if so do nothing
404 if (firstChar == '/') {
405 if (numOpenNotIfs == 0) {
406 appendInvalidPlaceholder(buffer, "{", &placeholderValue, i, formatstr->length);
407 } else {
408 --numOpenNotIfs;
409 }
410
411 continue;
412 }
413
414 // test for end of a color, if so do nothing
415 if (firstChar == '#') {
416 if (!instance.config.display.pipe) {
417 ffStrbufAppendS(buffer, FASTFETCH_TEXT_MODIFIER_RESET);
418 }
419
420 continue;
421 }
422 }
423
424 // test for if, if so evaluate it
425 if (firstChar == '?') {
426 ffStrbufSubstrAfter(&placeholderValue, 0);
427
428 uint32_t index = getArgumentIndex(placeholderValue.chars, numArgs, arguments);
429
430 // testing for an invalid index
431 if (index > numArgs || index < 1) {
432 appendInvalidPlaceholder(buffer, "{?", &placeholderValue, i, formatstr->length);
433 continue;
434 }
435
436 // continue normally if an format arg is set and the value is > 0
437 if (formatArgSet(&arguments[index - 1])) {
438 ++numOpenIfs;
439 continue;
440 }
441
442 // fastforward to the end of the if without printing the in between
443 i = ffStrbufNextIndexS(formatstr, i, "{?}") + 2; // 2 is the length of "{?}" - 1 because the loop will increment it again directly after continue
444 continue;
445 }
446
447 // test for not if, if so evaluate it
448 if (firstChar == '/') {
449 ffStrbufSubstrAfter(&placeholderValue, 0);
450
451 uint32_t index = getArgumentIndex(placeholderValue.chars, numArgs, arguments);
452
453 // testing for an invalid index
454 if (index > numArgs || index < 1) {
455 appendInvalidPlaceholder(buffer, "{/", &placeholderValue, i, formatstr->length);
456 continue;
457 }
458
459 // continue normally if an format arg is not set or the value is 0
460 if (!formatArgSet(&arguments[index - 1])) {
461 ++numOpenNotIfs;
462 continue;
463 }
464
465 // fastforward to the end of the if without printing the in between
466 i = ffStrbufNextIndexS(formatstr, i, "{/}") + 2; // 2 is the length of "{/}" - 1 because the loop will increment it again directly after continue
467 continue;
468 }
469
470 // test for color, if so evaluate it
471 if (firstChar == '#') {
472 if (!instance.config.display.pipe) {
473 ffStrbufAppendS(buffer, "\e[");
474 ffOptionParseColorNoClear(placeholderValue.chars + 1, buffer);
475 ffStrbufAppendC(buffer, 'm');
476 }
477 continue;
478 }
479
480 // test for constant or env var, if so evaluate it
481 if (firstChar == '$') {
482 char* pend = nullptr;
483 int32_t indexSigned = (int32_t) strtol(placeholderValue.chars + 1, &pend, 10);
484 if (pend == placeholderValue.chars + 1) {
485 // treat placeholder as an environment variable
486 char* envValue = getenv(placeholderValue.chars + 1);
487 if (envValue) {
488 ffStrbufAppendS(buffer, envValue);
489 } else {
490 appendInvalidPlaceholder(buffer, "{", &placeholderValue, i, formatstr->length);
491 }
492 } else {
493 // treat placeholder as a constant
494 uint32_t index = (uint32_t) (indexSigned < 0 ? (int32_t) instance.config.display.constants.length + indexSigned : indexSigned - 1);
495
496 if (*pend != '\0' || instance.config.display.constants.length <= index) {
497 appendInvalidPlaceholder(buffer, "{", &placeholderValue, i, formatstr->length);
498 } else {
499 FFstrbuf* item = FF_LIST_GET(FFstrbuf, instance.config.display.constants, index);
500 ffStrbufAppend(buffer, item);
501 }
502 }
503 continue;
504 }
505
506 char* pSep = placeholderValue.chars;
507 char cSep = '\0';
508 while (*pSep && *pSep != ':' && *pSep != '<' && *pSep != '>' && *pSep != '|' && *pSep != '~') {
509 ++pSep;
510 }
511 if (*pSep) {
512 cSep = *pSep;
513 *pSep = '\0';
514 } else {
515 pSep = nullptr;
516 }
517
518 uint32_t index = getArgumentIndex(placeholderValue.chars, numArgs, arguments);
519
520 // test for invalid index
521 if (index == 0) {
522 index = ++argCounter;
523 }
524
525 if (index > numArgs) {
526 if (pSep) {
527 *pSep = cSep;
528 }
529 appendInvalidPlaceholder(buffer, "{", &placeholderValue, i, formatstr->length);
530 continue;
531 }
532
533 if (!cSep) {
534 ffFormatAppendFormatArg(buffer, &arguments[index - 1]);
535 } else if (cSep == '~') {
536 FF_STRBUF_AUTO_DESTROY tempString = ffStrbufCreate();
537 ffFormatAppendFormatArg(&tempString, &arguments[index - 1]);
538 FF_STRBUF_AUTO_DESTROY trailingEscape = ffStrbufCreate();
539 skipAnsiEscape(&tempString, buffer, &trailingEscape);
540
541 char* pEnd = nullptr;
542 int32_t start = (int32_t) strtol(pSep + 1, &pEnd, 10);
543 if (start < 0) {
544 start = (int32_t) tempString.length + start;
545 }
546 if (start >= 0 && (uint32_t) start < tempString.length) {
547 if (*pEnd == '\0') {
548 ffStrbufAppendNS(buffer, tempString.length - (uint32_t) start, &tempString.chars[start]);
549 } else if (*pEnd == ',') {
550 int32_t end = (int32_t) strtol(pEnd + 1, &pEnd, 10);
551 if (!*pEnd) {
552 if (end < 0) {
553 end = (int32_t) tempString.length + end;
554 }
555 if ((uint32_t) end > tempString.length) {
556 end = (int32_t) tempString.length;
557 }
558 if (end > start) {
559 ffStrbufAppendNS(buffer, (uint32_t) (end - start), &tempString.chars[start]);
560 }
561 }
562 }
563 }
564
565 if (trailingEscape.length > 0) {
566 ffStrbufAppend(buffer, &trailingEscape);
567 }
568
569 if (*pEnd) {
570 *pSep = cSep;
571 appendInvalidPlaceholder(buffer, "{", &placeholderValue, i, formatstr->length);
572 continue;
573 }
574 } else {
575 char* pEnd = nullptr;
576 int32_t truncLength = (int32_t) strtol(pSep + 1, &pEnd, 10);
577 if (*pEnd != '\0') {
578 *pSep = cSep;
579 appendInvalidPlaceholder(buffer, "{", &placeholderValue, i, formatstr->length);
580 continue;
581 }
582
583 bool ellipsis = false;
584 if (truncLength < 0) {
585 ellipsis = true;
586 truncLength = -truncLength;
587 }
588
589 FF_STRBUF_AUTO_DESTROY tempString = ffStrbufCreate();
590 ffFormatAppendFormatArg(&tempString, &arguments[index - 1]);
591 FF_STRBUF_AUTO_DESTROY trailingEscape = ffStrbufCreate();
592 skipAnsiEscape(&tempString, buffer, &trailingEscape);
593
594 if (tempString.length == (uint32_t) truncLength) {
595 ffStrbufAppend(buffer, &tempString);
596 } else if (tempString.length > (uint32_t) truncLength) {
597 if (cSep == ':') {
598 ffStrbufSubstrBefore(&tempString, (uint32_t) truncLength);
599 ffStrbufTrimRightSpace(&tempString);
600 } else {
601 ffStrbufSubstrBefore(&tempString, (uint32_t) (!ellipsis ? truncLength : truncLength - 1));
602 }
603 ffStrbufAppend(buffer, &tempString);
604
605 if (ellipsis) {
606 ffStrbufAppendS(buffer, "…");
607 }
608 } else if (cSep == ':') {
609 ffStrbufAppend(buffer, &tempString);
610 } else {
611 if (cSep == '<') { // left align
612 ffStrbufAppend(buffer, &tempString);
613 ffStrbufAppendNC(buffer, (uint32_t) truncLength - tempString.length, ' ');
614 } else if (cSep == '>') { // right align
615 ffStrbufAppendNC(buffer, (uint32_t) truncLength - tempString.length, ' ');
616 ffStrbufAppend(buffer, &tempString);
617 } else if (cSep == '|') { // center align
618 uint32_t padding = ((uint32_t) truncLength - tempString.length) / 2;
619 ffStrbufAppendNC(buffer, padding, ' ');
620 ffStrbufAppend(buffer, &tempString);
621 ffStrbufAppendNC(buffer, (uint32_t) truncLength - tempString.length - padding, ' ');
622 }
623 }
624
625 if (trailingEscape.length > 0) {
626 ffStrbufAppend(buffer, &trailingEscape);
627 }
628 }
629 }
630
631 if (!instance.config.display.pipe) {
632 ffStrbufAppendS(buffer, FASTFETCH_TEXT_MODIFIER_RESET);
633 }
634
635 return true;
636}
637
638bool ffParseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, uint32_t numArgs, const FFformatarg* arguments) {
639#if FF_HAVE_LUA
640 if (ffStrbufStartsWithS(formatstr, "lua:")) {
641 // If outputFormat starts with "lua:", treat the rest as a Lua script
642 return parseLuaString(buffer, formatstr->chars + 4, formatstr->length - 4, numArgs, arguments);
643 }
644#endif
645
646 return parseFormatString(buffer, formatstr, numArgs, arguments);
647}