diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b60ddaf9..6971e647a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1342,9 +1342,17 @@ if (BUILD_TESTS) PRIVATE libfastfetch ) + add_executable(fastfetch-test-format + tests/format.c + ) + target_link_libraries(fastfetch-test-format + PRIVATE libfastfetch + ) + enable_testing() add_test(NAME test-strbuf COMMAND fastfetch-test-strbuf) add_test(NAME test-list COMMAND fastfetch-test-list) + add_test(NAME test-format COMMAND fastfetch-test-format) endif() ################## diff --git a/src/common/format.c b/src/common/format.c index f6b9b562f..74f59d865 100644 --- a/src/common/format.c +++ b/src/common/format.c @@ -265,18 +265,34 @@ void ffParseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, uint32_t n continue; } - int32_t truncLength = INT32_MAX; - char* pColon = memchr(placeholderValue.chars, ':', placeholderValue.length); - if (pColon != NULL) + int32_t truncLength = 0; + char align = '\0'; + char* pSep = memchr(placeholderValue.chars, ':', placeholderValue.length); + if (pSep) + align = ':'; + else + { + pSep = memchr(placeholderValue.chars, '<', placeholderValue.length); + if (pSep) + align = '<'; + else + { + pSep = memchr(placeholderValue.chars, '>', placeholderValue.length); + if (pSep) + align = '>'; + } + } + + if (pSep) { char* pEnd = NULL; - truncLength = (int32_t) strtol(pColon + 1, &pEnd, 10); + truncLength = (int32_t) strtol(pSep + 1, &pEnd, 10); if (*pEnd != '\0') { appendInvalidPlaceholder(buffer, "{", &placeholderValue, i, formatstr->length); continue; } - *pColon = '\0'; + *pSep = '\0'; } uint32_t index = getArgumentIndex(placeholderValue.chars, numArgs, arguments); @@ -287,7 +303,7 @@ void ffParseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, uint32_t n if (index > numArgs) { - if (pColon) *pColon = ':'; + if (pSep) *pSep = align; appendInvalidPlaceholder(buffer, "{", &placeholderValue, i, formatstr->length); continue; } @@ -299,14 +315,43 @@ void ffParseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, uint32_t n truncLength = -truncLength; } - uint32_t oldLength = buffer->length; - ffFormatAppendFormatArg(buffer, &arguments[index - 1]); - if (buffer->length - oldLength > (uint32_t) truncLength) + if (!align) + ffFormatAppendFormatArg(buffer, &arguments[index - 1]); + else { - ffStrbufSubstrBefore(buffer, oldLength + (uint32_t) truncLength); - ffStrbufTrimRightSpace(buffer); - if (ellipsis) - ffStrbufAppendS(buffer, "…"); + ffStrbufClear(&placeholderValue); + ffFormatAppendFormatArg(&placeholderValue, &arguments[index - 1]); + if (placeholderValue.length == (uint32_t) truncLength) + ffStrbufAppend(buffer, &placeholderValue); + else if (placeholderValue.length > (uint32_t) truncLength) + { + if (align == ':') + { + ffStrbufSubstrBefore(&placeholderValue, (uint32_t) truncLength); + ffStrbufTrimRightSpace(&placeholderValue); + } + else + ffStrbufSubstrBefore(&placeholderValue, (uint32_t) (!ellipsis? truncLength : truncLength - 1)); + ffStrbufAppend(buffer, &placeholderValue); + + if (ellipsis) + ffStrbufAppendS(buffer, "…"); + } + else if (align == ':') + ffStrbufAppend(buffer, &placeholderValue); + else + { + if (align == '<') + { + ffStrbufAppend(buffer, &placeholderValue); + ffStrbufAppendNC(buffer, (uint32_t) truncLength - placeholderValue.length, ' '); + } + else + { + ffStrbufAppendNC(buffer, (uint32_t) truncLength - placeholderValue.length, ' '); + ffStrbufAppend(buffer, &placeholderValue); + } + } } } diff --git a/src/data/help_format.txt b/src/data/help_format.txt index 4474e8694..9d84e6138 100644 --- a/src/data/help_format.txt +++ b/src/data/help_format.txt @@ -13,6 +13,10 @@ For example: "--title-format '{user-name:5}'" will truncate user name into 5-len If '' is negative, an ellipsis … will be appended at the end when the original string is truncated. Note: The string length is counted in raw bytes. Multi-byte unicode characters and ANSI escape codes are not taken into account. +In 2.23.0 or newer, `<` or `>` can be specified instead of `:` to set a left or right padding. +For example: "--title-format '{user-name<20}'" will generate ` `; +"--title-format '{user-name>20}'" will generate ` ` + If the value index is missing, meaning the placeholder is "{}", an internal counter sets the value index. This means that the format string "Values: {1} ({2})" is equivalent to "Values: {} ({})". Note that this counter only counts empty placeholders, so the format string "{2} {} {}" will contain the second value, then the first, and then the second again. diff --git a/tests/format.c b/tests/format.c new file mode 100644 index 000000000..173a5a298 --- /dev/null +++ b/tests/format.c @@ -0,0 +1,80 @@ +#include "common/format.h" +#include "util/textModifier.h" +#include "fastfetch.h" + +static void verify(const char* format, const char* arg, const char* expected, int lineNo) +{ + FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate(); + FF_STRBUF_AUTO_DESTROY formatter = ffStrbufCreateStatic(format); + const FFformatarg arguments[] = { + { .type = FF_FORMAT_ARG_TYPE_STRING, arg } + }; + ffParseFormatString(&result, &formatter, 1, arguments); + if (!ffStrbufEqualS(&result, expected)) + { + fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] %s: expected \"%s\", got \"%s\"\n" FASTFETCH_TEXT_MODIFIER_RESET, lineNo, format, expected, result.chars); + exit(1); + } +} + +#define VERIFY(format, argument, expected) verify((format), (argument), (expected), __LINE__) + +int main(void) +{ + instance.config.display.pipe = true; + + { + VERIFY("output({})", "12345 67890", "output(12345 67890)"); + VERIFY("output({1})", "12345 67890", "output(12345 67890)"); + VERIFY("output({})", "", "output()"); + VERIFY("output({1})", "", "output()"); + } + + { + VERIFY("output({1:20})", "12345 67890", "output(12345 67890)"); + VERIFY("output({1:11})", "12345 67890", "output(12345 67890)"); + VERIFY("output({1:-11})", "12345 67890", "output(12345 67890)"); + VERIFY("output({1:6})", "12345 67890", "output(12345)"); + VERIFY("output({:6})", "12345 67890", "output(12345)"); + VERIFY("output({:-6})", "12345 67890", "output(12345…)"); + VERIFY("output({:0})", "12345 67890", "output()"); + VERIFY("output({:})", "12345 67890", "output()"); + } + + { + VERIFY("output({1<20})", "12345 67890", "output(12345 67890 )"); + VERIFY("output({1<-20})", "12345 67890", "output(12345 67890 )"); + VERIFY("output({1<11})", "12345 67890", "output(12345 67890)"); + VERIFY("output({1<-11})", "12345 67890", "output(12345 67890)"); + VERIFY("output({1<6})", "12345 67890", "output(12345 )"); + VERIFY("output({<6})", "12345 67890", "output(12345 )"); + VERIFY("output({<-6})", "12345 67890", "output(12345…)"); + VERIFY("output({<0})", "12345 67890", "output()"); + VERIFY("output({<})", "12345 67890", "output()"); + } + + { + VERIFY("output({1>20})", "12345 67890", "output( 12345 67890)"); + VERIFY("output({1>-20})", "12345 67890", "output( 12345 67890)"); + VERIFY("output({1>11})", "12345 67890", "output(12345 67890)"); + VERIFY("output({1>-11})", "12345 67890", "output(12345 67890)"); + VERIFY("output({1>6})", "12345 67890", "output(12345 )"); + VERIFY("output({>6})", "12345 67890", "output(12345 )"); + VERIFY("output({>-6})", "12345 67890", "output(12345…)"); + VERIFY("output({>0})", "12345 67890", "output()"); + VERIFY("output({>})", "12345 67890", "output()"); + } + + { + VERIFY("output({1n>20})", "12345 67890", "output({1n>20})"); + VERIFY("output({120})", "12345 67890", "output({120})"); + VERIFY("output({1:11})", "", "output()"); + } + + { + VERIFY("output({1:20}{1<20}{1>20})", "12345 67890", "output(12345 6789012345 67890 12345 67890)"); + } + + //Success + puts("\033[32mAll tests passed!" FASTFETCH_TEXT_MODIFIER_RESET); +}