format string more like template language

This commit is contained in:
Linus Dierheimer
2021-04-24 21:42:48 +02:00
parent 36a4d095ed
commit d469120e9b
7 changed files with 248 additions and 65 deletions
+190 -48
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -9,7 +9,7 @@
typedef struct FFlist
{
void* data;
char* data;
uint32_t elementSize;
uint32_t length;
uint32_t capacity;
+32 -4
View File
@@ -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
View File
@@ -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);