mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-09-13 02:42:09 +02:00
format string more like template language
This commit is contained in:
+190
-48
@@ -17,87 +17,229 @@ void ffFormatAppendFormatArg(FFstrbuf* buffer, const FFformatarg* formatarg)
|
||||
else if(formatarg->type != FF_FORMAT_ARG_TYPE_NULL)
|
||||
{
|
||||
fprintf(stderr, "Error: format string \"%s\": argument is not implemented: %i\n", buffer->chars, formatarg->type);
|
||||
ffStrbufDestroy(buffer);
|
||||
exit(806);
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool placeholderValueIsForError(const FFstrbuf* placeholderValue)
|
||||
{
|
||||
return
|
||||
ffStrbufIgnCaseCompS(placeholderValue, "e") == 0 ||
|
||||
ffStrbufIgnCaseCompS(placeholderValue, "error") == 0 ||
|
||||
ffStrbufIgnCaseCompS(placeholderValue, "0") == 0
|
||||
;
|
||||
}
|
||||
|
||||
static inline uint32_t getArgumentIndex(const FFstrbuf* placeholderValue)
|
||||
{
|
||||
uint32_t result = UINT32_MAX;
|
||||
|
||||
if(placeholderValue->chars[0] != '-')
|
||||
sscanf(placeholderValue->chars, "%u", &result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
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 void appendEmptyPlaceholder(FFstrbuf* buffer, const char* placeholder, uint32_t* argCounter, uint32_t numArgs, const FFformatarg* arguments)
|
||||
{
|
||||
if(*argCounter > numArgs)
|
||||
ffStrbufAppendS(buffer, placeholder);
|
||||
else
|
||||
ffFormatAppendFormatArg(buffer, &arguments[(*argCounter)++]);
|
||||
}
|
||||
|
||||
static inline bool formatArgSet(const FFformatarg* arg)
|
||||
{
|
||||
return arg->value != NULL && (
|
||||
(arg->type == FF_FORMAT_ARG_TYPE_DOUBLE && *(double*)arg->value > 0) ||
|
||||
(arg->type == FF_FORMAT_ARG_TYPE_INT && *(int*)arg->value > 0) ||
|
||||
(arg->type == FF_FORMAT_ARG_TYPE_STRBUF && ((FFstrbuf*)arg->value)->length > 0) ||
|
||||
(arg->type == FF_FORMAT_ARG_TYPE_STRING && *(const char*)arg->value != '\0') ||
|
||||
(arg->type == FF_FORMAT_ARG_TYPE_UINT8 && *(uint8_t*)arg->value > 0) ||
|
||||
(arg->type == FF_FORMAT_ARG_TYPE_UINT && *(uint32_t*)arg->value > 0)
|
||||
);
|
||||
}
|
||||
|
||||
void ffParseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, const FFstrbuf* error, uint32_t numArgs, const FFformatarg* arguments)
|
||||
{
|
||||
uint32_t argCounter = 1; //First arg is 1 in fomat string
|
||||
uint32_t argCounter = 0;
|
||||
|
||||
uint32_t numOpenIfs = 0;
|
||||
uint32_t numOpenNotIfs = 0;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// if we have an { at the end handle it as {}
|
||||
if(i == formatstr->length - 1)
|
||||
{
|
||||
ffStrbufAppendC(buffer, '{');
|
||||
continue; //This will always stop the loop
|
||||
appendEmptyPlaceholder(buffer, "{", &argCounter, numArgs, arguments);
|
||||
continue;
|
||||
}
|
||||
|
||||
// jump to next char, the start of the placeholder value
|
||||
++i;
|
||||
|
||||
// double {{ elvaluates to a single { and doesn't count as start
|
||||
if(formatstr->chars[i] == '{')
|
||||
{
|
||||
ffStrbufAppendC(buffer, '{');
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t argIndex;
|
||||
|
||||
// placeholder is {}
|
||||
if(formatstr->chars[i] == '}')
|
||||
{
|
||||
if(argCounter > numArgs)
|
||||
{
|
||||
ffStrbufAppendS(buffer, "{}");
|
||||
continue;
|
||||
}
|
||||
|
||||
argIndex = argCounter++;
|
||||
appendEmptyPlaceholder(buffer, "{}", &argCounter, numArgs, arguments);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
|
||||
FFstrbuf placeholderValue;
|
||||
ffStrbufInit(&placeholderValue);
|
||||
|
||||
while(formatstr->chars[i] != '}' && i < formatstr->length)
|
||||
ffStrbufAppendC(&placeholderValue, formatstr->chars[i++]);
|
||||
|
||||
// test for error, if so print it
|
||||
if(placeholderValueIsForError(&placeholderValue)) {
|
||||
ffStrbufAppend(buffer, error);
|
||||
ffStrbufDestroy(&placeholderValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
// test if for stop, if so break the loop
|
||||
if(placeholderValue.length == 1 && placeholderValue.chars[0] == '-')
|
||||
{
|
||||
FFstrbuf argnumstr;
|
||||
ffStrbufInit(&argnumstr);
|
||||
|
||||
while(formatstr->chars[i] != '}' && i < formatstr->length)
|
||||
ffStrbufAppendC(&argnumstr, formatstr->chars[i++]);
|
||||
|
||||
if(
|
||||
ffStrbufIgnCaseCompS(&argnumstr, "e") == 0 ||
|
||||
ffStrbufIgnCaseCompS(&argnumstr, "error") == 0 ||
|
||||
ffStrbufIgnCaseCompS(&argnumstr, "0") == 0
|
||||
) {
|
||||
ffStrbufAppend(buffer, error);
|
||||
ffStrbufDestroy(&argnumstr);
|
||||
continue;
|
||||
}
|
||||
|
||||
//Test if argnumstr is valid
|
||||
if(
|
||||
argnumstr.length == 0 ||
|
||||
ffStrbufGetC(&argnumstr, 0) == '-' ||
|
||||
sscanf(argnumstr.chars, "%u", &argIndex) != 1 ||
|
||||
argIndex > numArgs
|
||||
) {
|
||||
//Not valid
|
||||
ffStrbufAppendC(buffer, '{');
|
||||
ffStrbufAppend(buffer, &argnumstr);
|
||||
if(formatstr->chars[i] == '}') // We dont have a closing { when ending because whole format string is over
|
||||
ffStrbufAppendC(buffer, '}');
|
||||
ffStrbufDestroy(&argnumstr);
|
||||
continue;
|
||||
}
|
||||
|
||||
ffStrbufDestroy(&argnumstr);
|
||||
ffStrbufDestroy(&placeholderValue);
|
||||
break;
|
||||
}
|
||||
|
||||
ffFormatAppendFormatArg(buffer, &arguments[argIndex - 1]);
|
||||
// test for end of an if, if so do nothing
|
||||
if(placeholderValue.length == 1 && placeholderValue.chars[0] == '?')
|
||||
{
|
||||
if(numOpenIfs == 0)
|
||||
appendInvalidPlaceholder(buffer, "{", &placeholderValue, i, formatstr->length);
|
||||
else
|
||||
--numOpenIfs;
|
||||
|
||||
ffStrbufDestroy(&placeholderValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
// test for end of a not if, if so do nothing
|
||||
if(placeholderValue.length == 1 && placeholderValue.chars[0] == '/')
|
||||
{
|
||||
if(numOpenNotIfs == 0)
|
||||
appendInvalidPlaceholder(buffer, "{", &placeholderValue, i, formatstr->length);
|
||||
else
|
||||
--numOpenNotIfs;
|
||||
|
||||
ffStrbufDestroy(&placeholderValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
// test for if, if so evaluate it
|
||||
if(placeholderValue.chars[0] == '?')
|
||||
{
|
||||
ffStrbufSubstrAfter(&placeholderValue, 0);
|
||||
|
||||
// continue if an error is set
|
||||
if(placeholderValueIsForError(&placeholderValue) && error != NULL && error->length > 0)
|
||||
{
|
||||
++numOpenIfs;
|
||||
ffStrbufDestroy(&placeholderValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t index = getArgumentIndex(&placeholderValue);
|
||||
|
||||
// testing for an invalid index
|
||||
if(index > numArgs)
|
||||
{
|
||||
appendInvalidPlaceholder(buffer, "{?", &placeholderValue, i, formatstr->length);
|
||||
ffStrbufDestroy(&placeholderValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
// continue normally if an format arg is set and the value is > 0
|
||||
if(formatArgSet(&arguments[index - 1]))
|
||||
{
|
||||
++numOpenIfs;
|
||||
ffStrbufDestroy(&placeholderValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
// fastforward to the end of the if without printing the in between
|
||||
i = ffStrbufFirstIndexAfterS(formatstr, i, "{?}") + 2; // 2 is the length of "{?}" -1 because the loop will increament it again directly after continue
|
||||
ffStrbufDestroy(&placeholderValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
// test for not if, if so evaluate it
|
||||
if(placeholderValue.chars[0] == '/')
|
||||
{
|
||||
ffStrbufSubstrAfter(&placeholderValue, 0);
|
||||
|
||||
//continue if an error os not set
|
||||
if(placeholderValueIsForError(&placeholderValue) && (error == NULL || error->length == 0))
|
||||
{
|
||||
++numOpenNotIfs;
|
||||
ffStrbufDestroy(&placeholderValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t index = getArgumentIndex(&placeholderValue);
|
||||
|
||||
// testing for an invalid index
|
||||
if(index > numArgs)
|
||||
{
|
||||
appendInvalidPlaceholder(buffer, "{/", &placeholderValue, i, formatstr->length);
|
||||
ffStrbufDestroy(&placeholderValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
//continue normally if an format arg is not set or the value is 0
|
||||
if(!formatArgSet(&arguments[index - 1]))
|
||||
{
|
||||
++numOpenNotIfs;
|
||||
ffStrbufDestroy(&placeholderValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
// fastforward to the end of the if without printing the in between
|
||||
i = ffStrbufFirstIndexAfterS(formatstr, i, "{/}") + 2; // 2 is the length of "{/}" -1 because the loop will increament it again directly after continue
|
||||
ffStrbufDestroy(&placeholderValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t index = getArgumentIndex(&placeholderValue);
|
||||
|
||||
// test for invalid index
|
||||
if(index > numArgs)
|
||||
{
|
||||
appendInvalidPlaceholder(buffer, "{", &placeholderValue, i, formatstr->length);
|
||||
ffStrbufDestroy(&placeholderValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
ffFormatAppendFormatArg(buffer, &arguments[index - 1]);
|
||||
|
||||
ffStrbufDestroy(&placeholderValue);
|
||||
}
|
||||
|
||||
ffStrbufTrimRight(buffer, ' ');
|
||||
|
||||
+5
-7
@@ -55,15 +55,12 @@ void ffPrintError(FFinstance* instance, const char* moduleName, uint8_t moduleIn
|
||||
FF_STRBUF_CREATE(error);
|
||||
ffStrbufAppendVF(&error, message, arguments);
|
||||
|
||||
FFformatarg nullArgs[numFormatArgs];
|
||||
for(uint32_t i = 0; i < numFormatArgs; i++)
|
||||
{
|
||||
nullArgs[i].type = FF_FORMAT_ARG_TYPE_NULL;
|
||||
nullArgs[i].value = NULL;
|
||||
}
|
||||
// calloc sets all to 0 and FF_FORMAT_ARG_TYPE_NULL also has value 0 so we don't need to explictly set it
|
||||
FFformatarg* nullArgs = calloc(numFormatArgs, sizeof(FFformatarg));
|
||||
|
||||
ffPrintFormatString(instance, moduleName, moduleIndex, customKeyFormat, formatString, &error, numFormatArgs, nullArgs);
|
||||
|
||||
free(nullArgs);
|
||||
ffStrbufDestroy(&error);
|
||||
}
|
||||
|
||||
@@ -195,7 +192,7 @@ static bool printCachedFormat(FFinstance* instance, const char* moduleName, cons
|
||||
|
||||
uint8_t moduleCounter = 1;
|
||||
|
||||
FFformatarg arguments[numArgs];
|
||||
FFformatarg* arguments = calloc(numArgs, sizeof(FFformatarg));
|
||||
uint32_t argumentCounter = 0;
|
||||
|
||||
uint32_t lastIndex = 0;
|
||||
@@ -218,6 +215,7 @@ static bool printCachedFormat(FFinstance* instance, const char* moduleName, cons
|
||||
lastIndex = nullByteIndex + 1;
|
||||
}
|
||||
|
||||
free(arguments);
|
||||
ffStrbufDestroy(&content);
|
||||
|
||||
return moduleCounter > 1;
|
||||
|
||||
+15
-1
@@ -154,11 +154,25 @@ static inline void printCommandHelpFormat()
|
||||
"\n"
|
||||
"To make formatting easier, a double open curly brace (\"{{\") will be printed as a single open curly brace and not counted as the beginn of a placeholder.\n"
|
||||
"If a value index is missformatted or wants a non existing value, it will be printed as is, with the curly braces around it.\n"
|
||||
"If the last placeholder isn't closed, it will be printed as is, with the open curly braces at the start.\n"
|
||||
"If the last placeholder isn't closed, it will be treated like it was at the end of the format string.\n"
|
||||
"\n"
|
||||
"To only print something if a variable is set use \"{?<index>} ... {?}\".\n"
|
||||
"For example to only print a second value if it is set use \"{?2} Second value: {2}{?}\".\n"
|
||||
"If a \"{?}\" is found without an opener, it is printed as is.\n"
|
||||
"\n"
|
||||
"To only print something if a variable is not set, do the same as with if, just replace every '?' with a '!'.\n"
|
||||
"For example to print a fallback for a second value if it is not set use \"{?2}{2}{?}{/2}Second value fallback{/}\".\n"
|
||||
"\n"
|
||||
"There is a special variable set if an error occured during detection, you can access it with \"{e}\", \"{error}\" or \"{0}\".\n"
|
||||
"You can use ifs and not ifs with it like with an index, for example use \"{?e}some text{?}\" to print an text if an error occurred.\n"
|
||||
"\n"
|
||||
"To stop formating at any point in the format string, use \"{-}\".\n"
|
||||
"For example to print an error instead of the normal output if it occured, prefix the format string with \"{?e}{e}{-}{?}...\".\n"
|
||||
"\n"
|
||||
"If an format string evaluates to an empty value, the whole line in the output will be discarded.\n"
|
||||
"You can therefore use --host-format \" \" to disable host output.\n"
|
||||
"Note that --host-format \"\" would evaluate as not set, and therefore use the built in host format\n"
|
||||
"This can be used to print nothing if an error occured: prefix the format string with \"{?e}{-}{?}...\".\n"
|
||||
"\n"
|
||||
"Format string is also the way to go to set a fixed value, just use one without placeholders.\n"
|
||||
"For example when running in headless mode you could use \"--resolution-format \"Preferred\"."
|
||||
|
||||
+1
-1
@@ -164,7 +164,7 @@ typedef struct FFinstance
|
||||
|
||||
typedef enum FFformatargtype
|
||||
{
|
||||
FF_FORMAT_ARG_TYPE_NULL,
|
||||
FF_FORMAT_ARG_TYPE_NULL = 0,
|
||||
FF_FORMAT_ARG_TYPE_UINT,
|
||||
FF_FORMAT_ARG_TYPE_UINT8,
|
||||
FF_FORMAT_ARG_TYPE_INT,
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
|
||||
typedef struct FFlist
|
||||
{
|
||||
void* data;
|
||||
char* data;
|
||||
uint32_t elementSize;
|
||||
uint32_t length;
|
||||
uint32_t capacity;
|
||||
|
||||
+32
-4
@@ -142,6 +142,8 @@ void ffStrbufSetVF(FFstrbuf* strbuf, const char* format, va_list arguments)
|
||||
|
||||
void ffStrbufAppend(FFstrbuf* strbuf, const FFstrbuf* value)
|
||||
{
|
||||
if(value == NULL)
|
||||
return;
|
||||
ffStrbufAppendNS(strbuf, value->length, value->chars);
|
||||
}
|
||||
|
||||
@@ -338,11 +340,13 @@ void ffStrbufRemoveStringsA(FFstrbuf* strbuf, uint32_t numStrings, const char* s
|
||||
|
||||
void ffStrbufRemoveStringsV(FFstrbuf* strbuf, uint32_t numStrings, va_list arguments)
|
||||
{
|
||||
const char* strings[numStrings];
|
||||
const char** strings = calloc(numStrings, sizeof(const char*));
|
||||
for(uint32_t i = 0; i < numStrings; i++)
|
||||
strings[i] = va_arg(arguments, const char*);
|
||||
|
||||
ffStrbufRemoveStringsA(strbuf, numStrings, strings);
|
||||
|
||||
free(strings);
|
||||
}
|
||||
|
||||
void ffStrbufRemoveStrings(FFstrbuf* strbuf, uint32_t numStrings, ...)
|
||||
@@ -353,7 +357,7 @@ void ffStrbufRemoveStrings(FFstrbuf* strbuf, uint32_t numStrings, ...)
|
||||
va_end(argp);
|
||||
}
|
||||
|
||||
uint32_t ffStrbufFirstIndexAfterC(FFstrbuf* strbuf, uint32_t start, const char c)
|
||||
uint32_t ffStrbufFirstIndexAfterC(const FFstrbuf* strbuf, uint32_t start, const char c)
|
||||
{
|
||||
for(uint32_t i = start; i < strbuf->length; i++)
|
||||
{
|
||||
@@ -363,12 +367,36 @@ uint32_t ffStrbufFirstIndexAfterC(FFstrbuf* strbuf, uint32_t start, const char c
|
||||
return strbuf->length;
|
||||
}
|
||||
|
||||
uint32_t ffStrbufFirstIndexC(FFstrbuf* strbuf, const char c)
|
||||
uint32_t ffStrbufFirstIndexC(const FFstrbuf* strbuf, const char c)
|
||||
{
|
||||
return ffStrbufFirstIndexAfterC(strbuf, 0, c);
|
||||
}
|
||||
|
||||
uint32_t ffStrbufLastIndexC(FFstrbuf* strbuf, const char c)
|
||||
uint32_t ffStrbufFirstIndexAfterS(const FFstrbuf* strbuf, uint32_t start, const char* str)
|
||||
{
|
||||
for(uint32_t i = start + 1; i < strbuf->length; i++)
|
||||
{
|
||||
bool found = true;
|
||||
|
||||
for(uint32_t k = 0; str[k] != '\0'; k++)
|
||||
{
|
||||
if(i + k == strbuf->length)
|
||||
return strbuf->length;
|
||||
|
||||
if(strbuf->chars[i + k] != str[k])
|
||||
{
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(found)
|
||||
return i;
|
||||
}
|
||||
return strbuf->length;
|
||||
}
|
||||
|
||||
uint32_t ffStrbufLastIndexC(const FFstrbuf* strbuf, const char c)
|
||||
{
|
||||
//We need to loop one higher than the actual index, because uint32_t is guranteed to be >= 0, so this statement would always be true
|
||||
for(uint32_t i = strbuf->length; i > 0; i--)
|
||||
|
||||
+4
-3
@@ -72,9 +72,10 @@ void ffStrbufRemoveStringsA(FFstrbuf* strbuf, uint32_t numStrings, const char* s
|
||||
void ffStrbufRemoveStringsV(FFstrbuf* strbuf, uint32_t numStrings, va_list arguments);
|
||||
void ffStrbufRemoveStrings(FFstrbuf* strbuf, uint32_t numStrings, ...);
|
||||
|
||||
uint32_t ffStrbufFirstIndexAfterC(FFstrbuf* strbuf, uint32_t start, const char c);
|
||||
uint32_t ffStrbufFirstIndexC(FFstrbuf* strbuf, const char c);
|
||||
uint32_t ffStrbufLastIndexC(FFstrbuf* strbuf, const char c);
|
||||
uint32_t ffStrbufFirstIndexAfterC(const FFstrbuf* strbuf, uint32_t start, const char c);
|
||||
uint32_t ffStrbufFirstIndexC(const FFstrbuf* strbuf, const char c);
|
||||
uint32_t ffStrbufFirstIndexAfterS(const FFstrbuf* strbuf, uint32_t start, const char* str);
|
||||
uint32_t ffStrbufLastIndexC(const FFstrbuf* strbuf, const char c);
|
||||
|
||||
void ffStrbufSubstrBefore(FFstrbuf* strbuf, uint32_t index);
|
||||
void ffStrbufSubstrBeforeFirstC(FFstrbuf* strbuf, const char c);
|
||||
|
||||
Reference in New Issue
Block a user