summaryrefslogtreecommitdiffstats
path: root/src/common/impl/format.c
diff options
context:
space:
mode:
authorsumuel <samuel@yakubos.org>2026-08-17 20:44:55 +0000
committersumuel <samuel@yakubos.org>2026-08-17 20:44:55 +0000
commit76424950e373d3b04ac3dd13019151bfba3e8423 (patch)
tree4c3cfdbda039e592b9186be3e28f8d7cfd439e8a /src/common/impl/format.c
Add the files
Diffstat (limited to 'src/common/impl/format.c')
-rw-r--r--src/common/impl/format.c761
1 files changed, 761 insertions, 0 deletions
diff --git a/src/common/impl/format.c b/src/common/impl/format.c
new file mode 100644
index 0000000..db414bc
--- /dev/null
+++ b/src/common/impl/format.c
@@ -0,0 +1,761 @@
+#include "fastfetch.h"
+#include "common/format.h"
+#include "common/parsing.h"
+#include "common/textModifier.h"
+#include "common/strutil.h"
+#include "common/library.h"
+
+#include <inttypes.h>
+
+void ffFormatAppendFormatArg(FFstrbuf* buffer, const FFformatarg* formatarg) {
+ switch (formatarg->type) {
+ case FF_ARG_TYPE_INT:
+ ffStrbufAppendSInt(buffer, *(int32_t*) formatarg->value);
+ break;
+ case FF_ARG_TYPE_UINT:
+ ffStrbufAppendUInt(buffer, *(uint32_t*) formatarg->value);
+ break;
+ case FF_ARG_TYPE_UINT64:
+ ffStrbufAppendUInt(buffer, *(uint64_t*) formatarg->value);
+ break;
+ case FF_ARG_TYPE_UINT16:
+ ffStrbufAppendUInt(buffer, *(uint16_t*) formatarg->value);
+ break;
+ case FF_ARG_TYPE_UINT8:
+ ffStrbufAppendUInt(buffer, *(uint8_t*) formatarg->value);
+ break;
+ case FF_ARG_TYPE_STRING:
+ ffStrbufAppendS(buffer, (const char*) formatarg->value);
+ break;
+ case FF_ARG_TYPE_STRBUF:
+ ffStrbufAppend(buffer, (const FFstrbuf*) formatarg->value);
+ break;
+ case FF_ARG_TYPE_FLOAT:
+ ffStrbufAppendDouble(buffer, *(float*) formatarg->value, instance.config.display.fractionNdigits, instance.config.display.fractionTrailingZeros != FF_FRACTION_TRAILING_ZEROS_TYPE_NEVER);
+ break;
+ case FF_ARG_TYPE_DOUBLE:
+ ffStrbufAppendDouble(buffer, *(double*) formatarg->value, instance.config.display.fractionNdigits, instance.config.display.fractionTrailingZeros != FF_FRACTION_TRAILING_ZEROS_TYPE_NEVER);
+ break;
+ case FF_ARG_TYPE_BOOL:
+ ffStrbufAppendS(buffer, *(bool*) formatarg->value ? "true" : "false");
+ break;
+ case FF_ARG_TYPE_LIST: {
+ const FFlist* list = (const FFlist*) formatarg->value;
+ for (uint32_t i = 0; i < list->length; i++) {
+ ffStrbufAppend(buffer, FF_LIST_GET(FFstrbuf, *list, i));
+ if (i < list->length - 1) {
+ ffStrbufAppendS(buffer, ", ");
+ }
+ }
+ break;
+ }
+ case FF_ARG_TYPE_BUFFER: {
+ // Placeholder for binary data, just print the size for now
+ const FFArgBuffer* argBuffer = (const FFArgBuffer*) formatarg->value;
+ ffStrbufAppendF(buffer, "buffer(%u bytes)", argBuffer->length);
+ break;
+ }
+ default:
+ if (formatarg->type != FF_ARG_TYPE_NULL) {
+ fprintf(stderr, "Error: format string \"%s\": argument is not implemented: %i\n", buffer->chars, formatarg->type);
+ }
+ break;
+ }
+}
+
+/**
+ * @brief parses a string to a uint32_t
+ *
+ * If the string can't be parsed, or is < 1, uint32_t max is returned.
+ *
+ * @param placeholderValue the string to parse
+ * @return uint32_t the parsed value
+ */
+static uint32_t getArgumentIndex(const char* placeholderValue, uint32_t numArgs, const FFformatarg* arguments) {
+ char firstChar = placeholderValue[0];
+ if (firstChar == '\0') {
+ return 0; // use arg counter
+ }
+
+ if (firstChar >= '0' && firstChar <= '9') {
+ char* pEnd = NULL;
+ uint32_t result = (uint32_t) strtoul(placeholderValue, &pEnd, 10);
+ if (result > numArgs) {
+ return UINT32_MAX;
+ }
+ if (*pEnd != '\0') {
+ return UINT32_MAX;
+ }
+ return result;
+ } else if (ffCharIsEnglishAlphabet(firstChar)) {
+ for (uint32_t i = 0; i < numArgs; ++i) {
+ const FFformatarg* arg = &arguments[i];
+ if (arg->name && ffStrEqualsIgnCase(placeholderValue, arg->name)) {
+ return i + 1;
+ }
+ }
+ }
+
+ return UINT32_MAX;
+}
+
+static inline void appendInvalidPlaceholder(FFstrbuf* buffer, const char* start, const FFstrbuf* placeholderValue, uint32_t index, uint32_t formatStringLength) {
+ ffStrbufAppendS(buffer, start);
+ ffStrbufAppend(buffer, placeholderValue);
+
+ if (index < formatStringLength) {
+ ffStrbufAppendC(buffer, '}');
+ }
+}
+
+static inline bool formatArgSet(const FFformatarg* arg) {
+ return arg->value != NULL && ((arg->type == FF_ARG_TYPE_DOUBLE && *(double*) arg->value > 0.0) || (arg->type == FF_ARG_TYPE_FLOAT && *(float*) arg->value > 0.0) || (arg->type == FF_ARG_TYPE_INT && *(int32_t*) arg->value > 0) || (arg->type == FF_ARG_TYPE_STRBUF && ((FFstrbuf*) arg->value)->length > 0) || (arg->type == FF_ARG_TYPE_STRING && ffStrSet((char*) arg->value)) || (arg->type == FF_ARG_TYPE_UINT8 && *(uint8_t*) arg->value > 0) || (arg->type == FF_ARG_TYPE_UINT16 && *(uint16_t*) arg->value > 0) || (arg->type == FF_ARG_TYPE_UINT && *(uint32_t*) arg->value > 0) || (arg->type == FF_ARG_TYPE_UINT64 && *(uint64_t*) arg->value > 0) || (arg->type == FF_ARG_TYPE_BOOL && *(bool*) arg->value) || (arg->type == FF_ARG_TYPE_LIST && ((FFlist*) arg->value)->length > 0));
+}
+
+FF_A_UNUSED static inline void normalizeArgName(FFstrbuf* dst, const char* src) {
+ ffStrbufClear(dst);
+ bool flag = false;
+ for (const char* p = src; *p; ++p) {
+ if (*p == '-') {
+ flag = true;
+ } else if (flag) {
+ ffStrbufAppendC(dst, (char) toupper((unsigned char) *p));
+ flag = false;
+ } else {
+ ffStrbufAppendC(dst, *p);
+ }
+ }
+}
+
+#if FF_HAVE_LUA
+ #include "common/lua.h"
+
+static void appendLuaError(FFstrbuf* buffer, const char* prefix, lua_State* L) {
+ const char* err = lua_tolstring(L, -1, NULL);
+ if (err) {
+ const char* tmp = strchr(err, ':');
+ if (tmp) {
+ err = tmp + 1;
+ while (*err == ' ') {
+ ++err;
+ }
+ }
+ }
+ ffStrbufAppendF(buffer, "%s: %s", prefix, err ? err : "unknown");
+}
+
+static bool parseLuaString(FFstrbuf* buffer, const char* script, uint32_t scriptLen, uint32_t numArgs, const FFformatarg* arguments) {
+ const char* err = ffLuaLoadState();
+ if (err) {
+ ffStrbufAppendF(buffer, "Lua init error: %s", err);
+ return false;
+ }
+
+ FF_STRBUF_AUTO_DESTROY argNameBuf = ffStrbufCreate();
+ bool ret = false;
+
+ lua_State* L = luaData.L;
+ // Clear stack and load chunk
+ lua_settop(L, 0);
+ if (luaL_loadbuffer(L, script, scriptLen, "") != LUA_OK) {
+ appendLuaError(buffer, "Lua load error", L);
+ } else {
+ // Build args table for name lookup only.
+ lua_createtable(L, 0, 0);
+
+ for (uint32_t i = 0; i < numArgs; ++i) {
+ const FFformatarg* arg = &arguments[i];
+ switch (arg->type) {
+ case FF_ARG_TYPE_INT:
+ lua_pushinteger(L, (lua_Integer) * (int32_t*) arg->value);
+ break;
+ case FF_ARG_TYPE_UINT:
+ lua_pushinteger(L, (lua_Integer) * (uint32_t*) arg->value);
+ break;
+ case FF_ARG_TYPE_UINT64:
+ lua_pushinteger(L, (lua_Integer) * (uint64_t*) arg->value);
+ break;
+ case FF_ARG_TYPE_UINT16:
+ lua_pushinteger(L, (lua_Integer) * (uint16_t*) arg->value);
+ break;
+ case FF_ARG_TYPE_UINT8:
+ lua_pushinteger(L, (lua_Integer) * (uint8_t*) arg->value);
+ break;
+ case FF_ARG_TYPE_FLOAT:
+ lua_pushnumber(L, (lua_Number) * (float*) arg->value);
+ break;
+ case FF_ARG_TYPE_DOUBLE:
+ lua_pushnumber(L, (lua_Number) * (double*) arg->value);
+ break;
+ case FF_ARG_TYPE_BOOL:
+ lua_pushboolean(L, *(bool*) arg->value);
+ break;
+ case FF_ARG_TYPE_STRING:
+ lua_pushlstring(L, (const char*) arg->value, strlen((const char*) arg->value));
+ break;
+ case FF_ARG_TYPE_STRBUF: {
+ const FFstrbuf* sb = (const FFstrbuf*) arg->value;
+ lua_pushlstring(L, sb->chars, sb->length);
+ break;
+ }
+ case FF_ARG_TYPE_LIST: {
+ const FFlist* list = (const FFlist*) arg->value;
+ lua_createtable(L, 0, 0);
+ for (uint32_t li = 0; li < list->length; ++li) {
+ const FFstrbuf* item = FF_LIST_GET(FFstrbuf, *list, li);
+ lua_pushlstring(L, item->chars, item->length);
+ lua_seti(L, -2, (lua_Integer) (li + 1));
+ }
+ break;
+ }
+ default:
+ lua_pushnil(L);
+ break;
+ }
+ if (arg->name && arg->name[0]) {
+ normalizeArgName(&argNameBuf, arg->name);
+ } else {
+ ffStrbufSetF(&argNameBuf, "arg%" PRIu32, i + 1);
+ }
+ lua_setfield(L, -2, argNameBuf.chars);
+ }
+
+ if (lua_pcall(L, 1, LUA_MULTRET, 0) != LUA_OK) {
+ appendLuaError(buffer, "Lua runtime error", L);
+ } else {
+ int nresults = lua_gettop(L);
+ if (nresults == 0) {
+ ffStrbufAppendS(buffer, "Lua result error: no result");
+ } else {
+ // Convert first result to string
+ const char* res = lua_tolstring(L, 1, NULL);
+ if (res) {
+ ffStrbufAppendS(buffer, res);
+ } else {
+ luaL_tolstring(L, 1, NULL);
+ const char* sval = lua_tolstring(L, -1, NULL);
+ if (sval) {
+ ffStrbufAppendS(buffer, sval);
+ }
+ }
+ ret = true;
+ }
+ }
+ }
+ lua_settop(L, 0);
+ return ret;
+}
+#endif
+
+#if FF_HAVE_QUICKJS
+ #include <quickjs.h>
+
+struct FFQuickJSData {
+ FF_LIBRARY_SYMBOL(JS_NewRuntime)
+ FF_LIBRARY_SYMBOL(JS_NewContext)
+ FF_LIBRARY_SYMBOL(JS_FreeRuntime)
+ FF_LIBRARY_SYMBOL(JS_EvalThis)
+ FF_LIBRARY_SYMBOL(JS_GetException)
+ FF_LIBRARY_SYMBOL(JS_ToCStringLen2)
+ FF_LIBRARY_SYMBOL(JS_FreeCString)
+ FF_LIBRARY_SYMBOL(JS_NewStringLen)
+ FF_LIBRARY_SYMBOL(JS_NewArray)
+ FF_LIBRARY_SYMBOL(JS_NewBigUint64)
+ FF_LIBRARY_SYMBOL(JS_SetPropertyUint32)
+ FF_LIBRARY_SYMBOL(JS_NewObject)
+ FF_LIBRARY_SYMBOL(JS_SetPropertyStr)
+ FF_LIBRARY_SYMBOL(JS_FreeValue)
+
+ JSRuntime* rt;
+ JSContext* ctx;
+ bool inited;
+} qjsData;
+
+static const char* loadQuickJSState(void) {
+ if (qjsData.inited) {
+ if (qjsData.ctx == NULL) {
+ return "QuickJS is not available";
+ }
+ return NULL;
+ }
+
+ qjsData.inited = true;
+ #ifdef _WIN32
+ FF_LIBRARY_LOAD_MESSAGE(libqjs, "libqjs-0" FF_LIBRARY_EXTENSION, 0)
+ #else
+ FF_LIBRARY_LOAD_MESSAGE(libqjs, "libqjs" FF_LIBRARY_EXTENSION, 0)
+ #endif
+
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_NewRuntime)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_NewContext)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_FreeRuntime)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_EvalThis)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_GetException)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_ToCStringLen2)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_FreeCString)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_NewStringLen)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_NewArray)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_SetPropertyUint32)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_NewObject)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_SetPropertyStr)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_FreeValue)
+
+ qjsData.rt = qjsData.ffJS_NewRuntime();
+ if (qjsData.rt == NULL) {
+ return "JS_NewRuntime() failed";
+ }
+
+ qjsData.ctx = qjsData.ffJS_NewContext(qjsData.rt);
+ if (qjsData.ctx == NULL) {
+ qjsData.ffJS_FreeRuntime(qjsData.rt);
+ qjsData.rt = NULL;
+ return "JS_NewContext() failed";
+ }
+
+ libqjs = NULL; // don't close quickjs
+
+ return NULL;
+}
+
+static bool parseQuickJSString(FFstrbuf* buffer, const char* script, uint32_t scriptLen, uint32_t numArgs, const FFformatarg* arguments) {
+ const char* err = loadQuickJSState();
+ if (err) {
+ ffStrbufAppendF(buffer, "Qjs init error: %s", err);
+ return false;
+ }
+ JSContext* ctx = qjsData.ctx;
+ JSValue argsObj = qjsData.ffJS_NewObject(ctx);
+ FF_STRBUF_AUTO_DESTROY argNameBuf = ffStrbufCreate();
+
+ for (uint32_t i = 0; i < numArgs; ++i) {
+ const FFformatarg* arg = &arguments[i];
+
+ JSValue value;
+ switch (arg->type) {
+ case FF_ARG_TYPE_INT:
+ value = JS_NewInt32(ctx, *(int32_t*) arg->value);
+ break;
+ case FF_ARG_TYPE_UINT:
+ value = JS_NewUint32(ctx, *(uint32_t*) arg->value);
+ break;
+ case FF_ARG_TYPE_UINT64: {
+ uint64_t val = *(uint64_t*) arg->value;
+ if (val <= INT32_MAX) {
+ value = JS_NewInt32(ctx, (int32_t) val);
+ } else {
+ value = JS_NewFloat64(ctx, (double) val);
+ }
+ break;
+ }
+ case FF_ARG_TYPE_UINT16:
+ value = JS_NewUint32(ctx, *(uint16_t*) arg->value);
+ break;
+ case FF_ARG_TYPE_UINT8:
+ value = JS_NewUint32(ctx, *(uint8_t*) arg->value);
+ break;
+ case FF_ARG_TYPE_FLOAT:
+ value = JS_NewFloat64(ctx, *(float*) arg->value);
+ break;
+ case FF_ARG_TYPE_DOUBLE:
+ value = JS_NewFloat64(ctx, *(double*) arg->value);
+ break;
+ case FF_ARG_TYPE_BOOL:
+ value = JS_NewBool(ctx, *(bool*) arg->value);
+ break;
+ case FF_ARG_TYPE_STRING:
+ value = qjsData.ffJS_NewStringLen(ctx, (const char*) arg->value, strlen((const char*) arg->value));
+ break;
+ case FF_ARG_TYPE_STRBUF: {
+ const FFstrbuf* sb = (const FFstrbuf*) arg->value;
+ value = qjsData.ffJS_NewStringLen(ctx, sb->chars, sb->length);
+ break;
+ }
+ case FF_ARG_TYPE_LIST: {
+ const FFlist* list = (const FFlist*) arg->value;
+ JSValue arr = qjsData.ffJS_NewArray(ctx);
+ for (uint32_t li = 0; li < list->length; ++li) {
+ const FFstrbuf* item = FF_LIST_GET(FFstrbuf, *list, li);
+ JSValue itemValue = qjsData.ffJS_NewStringLen(ctx, item->chars, item->length);
+ qjsData.ffJS_SetPropertyUint32(ctx, arr, li, itemValue);
+ }
+
+ value = arr;
+ break;
+ }
+ default:
+ value = JS_UNDEFINED;
+ break;
+ }
+
+ if (arg->name && arg->name[0]) {
+ normalizeArgName(&argNameBuf, arg->name);
+ } else {
+ ffStrbufSetF(&argNameBuf, "arg%" PRIu32, i + 1);
+ }
+ qjsData.ffJS_SetPropertyStr(ctx, argsObj, argNameBuf.chars, value);
+ }
+ JSValue result = qjsData.ffJS_EvalThis(ctx, argsObj, script, scriptLen, "", JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_STRICT);
+
+ qjsData.ffJS_FreeValue(ctx, argsObj);
+
+ bool ret = false;
+ if (JS_IsException(result)) {
+ JSValue exc = qjsData.ffJS_GetException(ctx);
+ const char* message = qjsData.ffJS_ToCStringLen2(ctx, NULL, exc, false);
+ qjsData.ffJS_FreeValue(ctx, exc);
+ ffStrbufAppendF(buffer, "Qjs runtime error: %s", message ?: "unknown");
+ if (message) {
+ qjsData.ffJS_FreeCString(ctx, message);
+ }
+ } else if (JS_IsUndefined(result)) {
+ ffStrbufAppendS(buffer, "Qjs result error: undefined result");
+ } else {
+ size_t len;
+ const char* res = qjsData.ffJS_ToCStringLen2(ctx, &len, result, false);
+ if (res) {
+ ffStrbufAppendNS(buffer, (uint32_t) len, res);
+ qjsData.ffJS_FreeCString(ctx, res);
+ }
+ ret = true;
+ }
+
+ qjsData.ffJS_FreeValue(ctx, result);
+ return ret;
+}
+#endif
+
+static bool skipAnsiEscape(FFstrbuf* in, FFstrbuf* out, FFstrbuf* trailingEscape) {
+ if (__builtin_expect(in->chars[0] == '\e' && in->chars[1] == '[', false)) {
+ // skip ANSI escape codes at the start of the string for truncation
+ const char* p = in->chars + 2;
+ while (*p && (ffCharIsDigit(*p) || *p == ';')) {
+ ++p;
+ }
+ if (*p && isascii(*p)) {
+ ++p;
+ }
+ uint32_t prefixLen = (uint32_t) (p - in->chars);
+ ffStrbufAppendNS(out, prefixLen, in->chars);
+ ffStrbufSubstrAfter(in, prefixLen - 1);
+
+ if (trailingEscape) {
+ // likely have a `CSI m` reset at the end of the string
+ uint32_t iLastEscape = ffStrbufLastIndexC(in, '\e');
+ if (iLastEscape != in->length) {
+ ffStrbufSetNS(trailingEscape, in->length - iLastEscape, in->chars + iLastEscape);
+ ffStrbufSubstrBefore(in, iLastEscape);
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+static bool parseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, uint32_t numArgs, const FFformatarg* arguments) {
+ uint32_t argCounter = 0;
+
+ uint32_t numOpenIfs = 0;
+ uint32_t numOpenNotIfs = 0;
+
+ FF_STRBUF_AUTO_DESTROY placeholderValue = ffStrbufCreate();
+
+ for (uint32_t i = 0; i < formatstr->length; ++i) {
+ // if we don't have a placeholder start just copy the chars over to output buffer
+ if (formatstr->chars[i] != '{') {
+ ffStrbufAppendC(buffer, formatstr->chars[i]);
+ continue;
+ }
+
+ // jump to next char, the start of the placeholder value
+ ++i;
+
+ // unmatched trailing '{'
+ if (i >= formatstr->length) {
+ ffStrbufAppendC(buffer, '{');
+ break;
+ }
+
+ // double {{ elvaluates to a single { and doesn't count as start
+ if (formatstr->chars[i] == '{') {
+ ffStrbufAppendC(buffer, '{');
+ continue;
+ }
+
+ ffStrbufClear(&placeholderValue);
+
+ {
+ uint32_t iEnd = ffStrbufNextIndexC(formatstr, i, '}');
+ ffStrbufAppendNS(&placeholderValue, iEnd - i, &formatstr->chars[i]);
+ i = iEnd;
+ }
+
+ char firstChar = placeholderValue.chars[0];
+
+ if (placeholderValue.length == 1) {
+ // test if for stop, if so break the loop
+ if (firstChar == '-') {
+ break;
+ }
+
+ // test for end of an if, if so do nothing
+ if (firstChar == '?') {
+ if (numOpenIfs == 0) {
+ appendInvalidPlaceholder(buffer, "{", &placeholderValue, i, formatstr->length);
+ } else {
+ --numOpenIfs;
+ }
+
+ continue;
+ }
+
+ // test for end of a not if, if so do nothing
+ if (firstChar == '/') {
+ if (numOpenNotIfs == 0) {
+ appendInvalidPlaceholder(buffer, "{", &placeholderValue, i, formatstr->length);
+ } else {
+ --numOpenNotIfs;
+ }
+
+ continue;
+ }
+
+ // test for end of a color, if so do nothing
+ if (firstChar == '#') {
+ if (!instance.config.display.pipe) {
+ ffStrbufAppendS(buffer, FASTFETCH_TEXT_MODIFIER_RESET);
+ }
+
+ continue;
+ }
+ }
+
+ // test for if, if so evaluate it
+ if (firstChar == '?') {
+ ffStrbufSubstrAfter(&placeholderValue, 0);
+
+ uint32_t index = getArgumentIndex(placeholderValue.chars, numArgs, arguments);
+
+ // testing for an invalid index
+ if (index > numArgs || index < 1) {
+ appendInvalidPlaceholder(buffer, "{?", &placeholderValue, i, formatstr->length);
+ continue;
+ }
+
+ // continue normally if an format arg is set and the value is > 0
+ if (formatArgSet(&arguments[index - 1])) {
+ ++numOpenIfs;
+ continue;
+ }
+
+ // fastforward to the end of the if without printing the in between
+ i = ffStrbufNextIndexS(formatstr, i, "{?}") + 2; // 2 is the length of "{?}" - 1 because the loop will increment it again directly after continue
+ continue;
+ }
+
+ // test for not if, if so evaluate it
+ if (firstChar == '/') {
+ ffStrbufSubstrAfter(&placeholderValue, 0);
+
+ uint32_t index = getArgumentIndex(placeholderValue.chars, numArgs, arguments);
+
+ // testing for an invalid index
+ if (index > numArgs || index < 1) {
+ appendInvalidPlaceholder(buffer, "{/", &placeholderValue, i, formatstr->length);
+ continue;
+ }
+
+ // continue normally if an format arg is not set or the value is 0
+ if (!formatArgSet(&arguments[index - 1])) {
+ ++numOpenNotIfs;
+ continue;
+ }
+
+ // fastforward to the end of the if without printing the in between
+ i = ffStrbufNextIndexS(formatstr, i, "{/}") + 2; // 2 is the length of "{/}" - 1 because the loop will increment it again directly after continue
+ continue;
+ }
+
+ // test for color, if so evaluate it
+ if (firstChar == '#') {
+ if (!instance.config.display.pipe) {
+ ffStrbufAppendS(buffer, "\e[");
+ ffOptionParseColorNoClear(placeholderValue.chars + 1, buffer);
+ ffStrbufAppendC(buffer, 'm');
+ }
+ continue;
+ }
+
+ // test for constant or env var, if so evaluate it
+ if (firstChar == '$') {
+ char* pend = NULL;
+ int32_t indexSigned = (int32_t) strtol(placeholderValue.chars + 1, &pend, 10);
+ if (pend == placeholderValue.chars + 1) {
+ // treat placeholder as an environment variable
+ char* envValue = getenv(placeholderValue.chars + 1);
+ if (envValue) {
+ ffStrbufAppendS(buffer, envValue);
+ } else {
+ appendInvalidPlaceholder(buffer, "{", &placeholderValue, i, formatstr->length);
+ }
+ } else {
+ // treat placeholder as a constant
+ uint32_t index = (uint32_t) (indexSigned < 0 ? (int32_t) instance.config.display.constants.length + indexSigned : indexSigned - 1);
+
+ if (*pend != '\0' || instance.config.display.constants.length <= index) {
+ appendInvalidPlaceholder(buffer, "{", &placeholderValue, i, formatstr->length);
+ } else {
+ FFstrbuf* item = FF_LIST_GET(FFstrbuf, instance.config.display.constants, index);
+ ffStrbufAppend(buffer, item);
+ }
+ }
+ continue;
+ }
+
+ char* pSep = placeholderValue.chars;
+ char cSep = '\0';
+ while (*pSep && *pSep != ':' && *pSep != '<' && *pSep != '>' && *pSep != '|' && *pSep != '~') {
+ ++pSep;
+ }
+ if (*pSep) {
+ cSep = *pSep;
+ *pSep = '\0';
+ } else {
+ pSep = NULL;
+ }
+
+ uint32_t index = getArgumentIndex(placeholderValue.chars, numArgs, arguments);
+
+ // test for invalid index
+ if (index == 0) {
+ index = ++argCounter;
+ }
+
+ if (index > numArgs) {
+ if (pSep) {
+ *pSep = cSep;
+ }
+ appendInvalidPlaceholder(buffer, "{", &placeholderValue, i, formatstr->length);
+ continue;
+ }
+
+ if (!cSep) {
+ ffFormatAppendFormatArg(buffer, &arguments[index - 1]);
+ } else if (cSep == '~') {
+ FF_STRBUF_AUTO_DESTROY tempString = ffStrbufCreate();
+ ffFormatAppendFormatArg(&tempString, &arguments[index - 1]);
+ FF_STRBUF_AUTO_DESTROY trailingEscape = ffStrbufCreate();
+ skipAnsiEscape(&tempString, buffer, &trailingEscape);
+
+ char* pEnd = NULL;
+ int32_t start = (int32_t) strtol(pSep + 1, &pEnd, 10);
+ if (start < 0) {
+ start = (int32_t) tempString.length + start;
+ }
+ if (start >= 0 && (uint32_t) start < tempString.length) {
+ if (*pEnd == '\0') {
+ ffStrbufAppendNS(buffer, tempString.length - (uint32_t) start, &tempString.chars[start]);
+ } else if (*pEnd == ',') {
+ int32_t end = (int32_t) strtol(pEnd + 1, &pEnd, 10);
+ if (!*pEnd) {
+ if (end < 0) {
+ end = (int32_t) tempString.length + end;
+ }
+ if ((uint32_t) end > tempString.length) {
+ end = (int32_t) tempString.length;
+ }
+ if (end > start) {
+ ffStrbufAppendNS(buffer, (uint32_t) (end - start), &tempString.chars[start]);
+ }
+ }
+ }
+ }
+
+ if (trailingEscape.length > 0) {
+ ffStrbufAppend(buffer, &trailingEscape);
+ }
+
+ if (*pEnd) {
+ *pSep = cSep;
+ appendInvalidPlaceholder(buffer, "{", &placeholderValue, i, formatstr->length);
+ continue;
+ }
+ } else {
+ char* pEnd = NULL;
+ int32_t truncLength = (int32_t) strtol(pSep + 1, &pEnd, 10);
+ if (*pEnd != '\0') {
+ *pSep = cSep;
+ appendInvalidPlaceholder(buffer, "{", &placeholderValue, i, formatstr->length);
+ continue;
+ }
+
+ bool ellipsis = false;
+ if (truncLength < 0) {
+ ellipsis = true;
+ truncLength = -truncLength;
+ }
+
+ FF_STRBUF_AUTO_DESTROY tempString = ffStrbufCreate();
+ ffFormatAppendFormatArg(&tempString, &arguments[index - 1]);
+ FF_STRBUF_AUTO_DESTROY trailingEscape = ffStrbufCreate();
+ skipAnsiEscape(&tempString, buffer, &trailingEscape);
+
+ if (tempString.length == (uint32_t) truncLength) {
+ ffStrbufAppend(buffer, &tempString);
+ } else if (tempString.length > (uint32_t) truncLength) {
+ if (cSep == ':') {
+ ffStrbufSubstrBefore(&tempString, (uint32_t) truncLength);
+ ffStrbufTrimRightSpace(&tempString);
+ } else {
+ ffStrbufSubstrBefore(&tempString, (uint32_t) (!ellipsis ? truncLength : truncLength - 1));
+ }
+ ffStrbufAppend(buffer, &tempString);
+
+ if (ellipsis) {
+ ffStrbufAppendS(buffer, "…");
+ }
+ } else if (cSep == ':') {
+ ffStrbufAppend(buffer, &tempString);
+ } else {
+ if (cSep == '<') { // left align
+ ffStrbufAppend(buffer, &tempString);
+ ffStrbufAppendNC(buffer, (uint32_t) truncLength - tempString.length, ' ');
+ } else if (cSep == '>') { // right align
+ ffStrbufAppendNC(buffer, (uint32_t) truncLength - tempString.length, ' ');
+ ffStrbufAppend(buffer, &tempString);
+ } else if (cSep == '|') { // center align
+ uint32_t padding = ((uint32_t) truncLength - tempString.length) / 2;
+ ffStrbufAppendNC(buffer, padding, ' ');
+ ffStrbufAppend(buffer, &tempString);
+ ffStrbufAppendNC(buffer, (uint32_t) truncLength - tempString.length - padding, ' ');
+ }
+ }
+
+ if (trailingEscape.length > 0) {
+ ffStrbufAppend(buffer, &trailingEscape);
+ }
+ }
+ }
+
+ if (!instance.config.display.pipe) {
+ ffStrbufAppendS(buffer, FASTFETCH_TEXT_MODIFIER_RESET);
+ }
+
+ return true;
+}
+
+bool ffParseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, uint32_t numArgs, const FFformatarg* arguments) {
+#if FF_HAVE_QUICKJS
+ if (ffStrbufStartsWithS(formatstr, "qjs:")) {
+ return parseQuickJSString(buffer, formatstr->chars + 4, formatstr->length - 4, numArgs, arguments);
+ }
+#endif
+
+#if FF_HAVE_LUA
+ if (ffStrbufStartsWithS(formatstr, "lua:")) {
+ // If outputFormat starts with "lua:", treat the rest as a Lua script
+ return parseLuaString(buffer, formatstr->chars + 4, formatstr->length - 4, numArgs, arguments);
+ }
+#endif
+
+ return parseFormatString(buffer, formatstr, numArgs, arguments);
+}