From 76424950e373d3b04ac3dd13019151bfba3e8423 Mon Sep 17 00:00:00 2001 From: sumuel Date: Mon, 17 Aug 2026 20:44:55 +0000 Subject: Add the files --- src/common/impl/format.c | 761 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 761 insertions(+) create mode 100644 src/common/impl/format.c (limited to 'src/common/impl/format.c') 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 + +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 + +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); +} -- cgit v1.2.3