From dc541ac7814f4fc30063237faf511ecd4fb9f5e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Wed, 11 Sep 2024 11:07:00 +0800 Subject: [PATCH] Formatting: support syntax of sub string Fix #1256; Ref #1255 --- src/common/format.c | 132 ++++++++++++++++++++++++--------------- src/data/help_format.txt | 4 ++ tests/format.c | 38 +++++++++++ 3 files changed, 122 insertions(+), 52 deletions(-) diff --git a/src/common/format.c b/src/common/format.c index 9815848ad..1e8c92702 100644 --- a/src/common/format.c +++ b/src/common/format.c @@ -280,34 +280,18 @@ void ffParseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, uint32_t n continue; } - int32_t truncLength = 0; - char align = '\0'; - char* pSep = memchr(placeholderValue.chars, ':', placeholderValue.length); - if (pSep) - align = ':'; + char* pSep = placeholderValue.chars; + char cSep = '\0'; + while (*pSep && *pSep != ':' && *pSep != '<' && *pSep != '>' && *pSep != '~') + ++pSep; + if (*pSep) + { + cSep = *pSep; + *pSep = '\0'; + } 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(pSep + 1, &pEnd, 10); - if (*pEnd != '\0') - { - appendInvalidPlaceholder(buffer, "{", &placeholderValue, i, formatstr->length); - continue; - } - *pSep = '\0'; + pSep = NULL; } uint32_t index = getArgumentIndex(placeholderValue.chars, numArgs, arguments); @@ -318,53 +302,97 @@ void ffParseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, uint32_t n if (index > numArgs) { - if (pSep) *pSep = align; + if (pSep) *pSep = cSep; appendInvalidPlaceholder(buffer, "{", &placeholderValue, i, formatstr->length); continue; } - bool ellipsis = false; - if (truncLength < 0) - { - ellipsis = true; - truncLength = -truncLength; - } - - if (!align) + if (!cSep) ffFormatAppendFormatArg(buffer, &arguments[index - 1]); + else if (cSep == '~') + { + FF_STRBUF_AUTO_DESTROY tempString = ffStrbufCreate(); + ffFormatAppendFormatArg(&tempString, &arguments[index - 1]); + + 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 (*pEnd) + { + *pSep = cSep; + appendInvalidPlaceholder(buffer, "{", &placeholderValue, i, formatstr->length); + continue; + } + } else { - ffStrbufClear(&placeholderValue); - ffFormatAppendFormatArg(&placeholderValue, &arguments[index - 1]); - if (placeholderValue.length == (uint32_t) truncLength) - ffStrbufAppend(buffer, &placeholderValue); - else if (placeholderValue.length > (uint32_t) truncLength) + char* pEnd = NULL; + int32_t truncLength = (int32_t) strtol(pSep + 1, &pEnd, 10); + if (*pEnd != '\0') { - if (align == ':') + *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]); + if (tempString.length == (uint32_t) truncLength) + ffStrbufAppend(buffer, &tempString); + else if (tempString.length > (uint32_t) truncLength) + { + if (cSep == ':') { - ffStrbufSubstrBefore(&placeholderValue, (uint32_t) truncLength); - ffStrbufTrimRightSpace(&placeholderValue); + ffStrbufSubstrBefore(&tempString, (uint32_t) truncLength); + ffStrbufTrimRightSpace(&tempString); } else - ffStrbufSubstrBefore(&placeholderValue, (uint32_t) (!ellipsis? truncLength : truncLength - 1)); - ffStrbufAppend(buffer, &placeholderValue); + ffStrbufSubstrBefore(&tempString, (uint32_t) (!ellipsis? truncLength : truncLength - 1)); + ffStrbufAppend(buffer, &tempString); if (ellipsis) ffStrbufAppendS(buffer, "…"); } - else if (align == ':') - ffStrbufAppend(buffer, &placeholderValue); + else if (cSep == ':') + ffStrbufAppend(buffer, &tempString); else { - if (align == '<') + if (cSep == '<') { - ffStrbufAppend(buffer, &placeholderValue); - ffStrbufAppendNC(buffer, (uint32_t) truncLength - placeholderValue.length, ' '); + ffStrbufAppend(buffer, &tempString); + ffStrbufAppendNC(buffer, (uint32_t) truncLength - tempString.length, ' '); } else { - ffStrbufAppendNC(buffer, (uint32_t) truncLength - placeholderValue.length, ' '); - ffStrbufAppend(buffer, &placeholderValue); + ffStrbufAppendNC(buffer, (uint32_t) truncLength - tempString.length, ' '); + ffStrbufAppend(buffer, &tempString); } } } diff --git a/src/data/help_format.txt b/src/data/help_format.txt index 9d84e6138..0c77ba863 100644 --- a/src/data/help_format.txt +++ b/src/data/help_format.txt @@ -17,6 +17,10 @@ In 2.23.0 or newer, `<` or `>` can be specified instead of `:` to set a left or For example: "--title-format '{user-name<20}'" will generate ` `; "--title-format '{user-name>20}'" will generate ` ` +In 2.24.0 or newer, `{~startIndex,endIndex}` can be specified to slice a string. Negative index counts back from the end of the string. +If an index is omitted, 0 is used. For example, both `{~,0}` `{~0,}` and `{~,}` are same as `{~0,0}` and will always generate a empty string. +If `,endIndex` is omitted or greater than the length of the string, the length of string is used. + 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 index 173a5a298..6b7d0a727 100644 --- a/tests/format.c +++ b/tests/format.c @@ -69,10 +69,48 @@ int main(void) VERIFY("output({1n>20})", "12345 67890", "output({1n>20})"); VERIFY("output({120})", "12345 67890", "output({120})"); VERIFY("output({1:11})", "", "output()"); + VERIFY("output({2:2})", "", "output({2:2})"); + } + + { + VERIFY("output({1~0})", "12345 67890", "output(12345 67890)"); + VERIFY("output({1~1})", "12345 67890", "output(2345 67890)"); + VERIFY("output({1~10})", "12345 67890", "output(0)"); + VERIFY("output({1~20})", "12345 67890", "output()"); + VERIFY("output({1~-5})", "12345 67890", "output(67890)"); + VERIFY("output({1~-50})", "12345 67890", "output()"); + VERIFY("output({1~0,1})", "12345 67890", "output(1)"); + VERIFY("output({1~0,6})", "12345 67890", "output(12345 )"); + VERIFY("output({1~0,10})", "12345 67890", "output(12345 6789)"); + VERIFY("output({1~5,10})", "12345 67890", "output( 6789)"); + VERIFY("output({1~5,100})", "12345 67890", "output( 67890)"); + VERIFY("output({1~10,10})", "12345 67890", "output()"); + VERIFY("output({1~10,5})", "12345 67890", "output()"); + VERIFY("output({1~3,-3})", "12345 67890", "output(45 67)"); + VERIFY("output({1~-5,-3})", "12345 67890", "output(67)"); + VERIFY("output({1~-0,10})", "12345 67890", "output(12345 6789)"); + VERIFY("output({1~-3,-5})", "12345 67890", "output()"); + VERIFY("output({1~})", "12345 67890", "output(12345 67890)"); // Same as {1~0} + VERIFY("output({1~-0})", "12345 67890", "output(12345 67890)"); // Same as {1~0} + VERIFY("output({1~,-1})", "12345 67890", "output(12345 6789)"); // Same as {1~0,-1} + VERIFY("output({1~,})", "12345 67890", "output()"); // Same as {1~0,0} + } + + { + VERIFY("output({1n~0})", "12345 67890", "output({1n~0})"); + VERIFY("output({1~<0})", "12345 67890", "output({1~<0})"); + VERIFY("output({1~-})", "12345 67890", "output({1~-})"); + VERIFY("output({1~-,1})", "12345 67890", "output({1~-,1})"); + VERIFY("output({1~0,,1})", "12345 67890", "output({1~0,,1})"); + VERIFY("output({1~0,1,})", "12345 67890", "output({1~0,1,})"); + VERIFY("output({1~,0,1})", "12345 67890", "output({1~,0,1})"); + VERIFY("output({2,0})", "12345 67890", "output({2,0})"); } { VERIFY("output({1:20}{1<20}{1>20})", "12345 67890", "output(12345 6789012345 67890 12345 67890)"); + VERIFY("output({?1}OK{?}{/1}NOT OK{/})", "12345 67890", "output(OK)"); + VERIFY("output({?1}OK{?}{/1}NOT OK{/})", "", "output(NOT OK)"); } //Success