aboutsummaryrefslogtreecommitdiff
path: root/src/zencore/string.cpp
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2026-04-23 18:16:57 +0200
committerStefan Boberg <[email protected]>2026-04-23 18:16:57 +0200
commit0232b991cd7d8e3a2114ea30e4591dd3e7b65c36 (patch)
tree94730e7594fd09ae1fa820391ce311f6daf13905 /src/zencore/string.cpp
parentFix forward declaration order for s_GotSigWinch and SigWinchHandler (diff)
parenttrace: declare Region event name fields as AnsiString (#1012) (diff)
downloadarchived-zen-sb/zen-help.tar.xz
archived-zen-sb/zen-help.zip
Merge branch 'main' into sb/zen-helpsb/zen-help
- Combine HelpCommand (this branch) with HistoryCommand (main) in zen CLI dispatcher - Keep filter-aware TuiPickOne rewrite; adopt main's ASCII arrow glyphs in doc comment
Diffstat (limited to 'src/zencore/string.cpp')
-rw-r--r--src/zencore/string.cpp154
1 files changed, 150 insertions, 4 deletions
diff --git a/src/zencore/string.cpp b/src/zencore/string.cpp
index 358722b0b..34519b83b 100644
--- a/src/zencore/string.cpp
+++ b/src/zencore/string.cpp
@@ -9,6 +9,7 @@
#include <inttypes.h>
#include <math.h>
#include <stdio.h>
+#include <charconv>
#include <exception>
#include <ostream>
#include <stdexcept>
@@ -54,15 +55,23 @@ namespace zen {
bool
ToString(std::span<char> Buffer, uint64_t Num)
{
- snprintf(Buffer.data(), Buffer.size(), "%" PRIu64, Num);
-
+ auto [Ptr, Ec] = std::to_chars(Buffer.data(), Buffer.data() + Buffer.size(), Num);
+ if (Ec != std::errc{} || Ptr == Buffer.data() + Buffer.size())
+ {
+ return false;
+ }
+ *Ptr = '\0';
return true;
}
bool
ToString(std::span<char> Buffer, int64_t Num)
{
- snprintf(Buffer.data(), Buffer.size(), "%" PRId64, Num);
-
+ auto [Ptr, Ec] = std::to_chars(Buffer.data(), Buffer.data() + Buffer.size(), Num);
+ if (Ec != std::errc{} || Ptr == Buffer.data() + Buffer.size())
+ {
+ return false;
+ }
+ *Ptr = '\0';
return true;
}
@@ -381,6 +390,34 @@ NiceNumGeneral(uint64_t Num, std::span<char> Buffer, NicenumFormat Format)
}
size_t
+ThousandsToBuffer(uint64_t Num, std::span<char> Buffer)
+{
+ // Format into a temporary buffer without separators
+ char Tmp[24];
+ int Len = snprintf(Tmp, sizeof(Tmp), "%llu", (unsigned long long)Num);
+
+ // Insert comma separators
+ int SepCount = (Len - 1) / 3;
+ int TotalLen = Len + SepCount;
+ ZEN_ASSERT(TotalLen < (int)Buffer.size());
+
+ int Src = Len - 1;
+ int Dst = TotalLen;
+ Buffer[Dst--] = '\0';
+
+ for (int i = 0; Src >= 0; i++)
+ {
+ if (i > 0 && i % 3 == 0)
+ {
+ Buffer[Dst--] = ',';
+ }
+ Buffer[Dst--] = Tmp[Src--];
+ }
+
+ return TotalLen;
+}
+
+size_t
NiceNumToBuffer(uint64_t Num, std::span<char> Buffer)
{
return NiceNumGeneral(Num, Buffer, kNicenum1024);
@@ -515,6 +552,40 @@ template class StringBuilderImpl<wchar_t>;
//////////////////////////////////////////////////////////////////////////
void
+StringBuilderBase::AppendPaddedInt(int64_t Value, int MinWidth)
+{
+ char Buf[24];
+ char* End = Buf + sizeof(Buf);
+ char* Ptr = End;
+ bool Negative = Value < 0;
+ uint64_t Abs = Negative ? uint64_t(-Value) : uint64_t(Value);
+ do
+ {
+ *--Ptr = '0' + char(Abs % 10);
+ Abs /= 10;
+ } while (Abs > 0);
+ while ((End - Ptr) < MinWidth)
+ {
+ *--Ptr = '0';
+ }
+ if (Negative)
+ {
+ *--Ptr = '-';
+ }
+ AppendRange(Ptr, End);
+}
+
+void
+StringBuilderBase::AppendFill(char C, size_t Count)
+{
+ EnsureCapacity(Count);
+ std::memset(m_CurPos, C, Count);
+ m_CurPos += Count;
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+void
UrlDecode(std::string_view InUrl, StringBuilderBase& OutUrl)
{
std::string_view::size_type i = 0;
@@ -1279,6 +1350,81 @@ TEST_CASE("hidesensitivestring")
CHECK_EQ(HideSensitiveString("1234567890123456789"sv), "1234XXXX..."sv);
}
+TEST_CASE("CompactString.default")
+{
+ CompactString S;
+ CHECK(S.IsEmpty());
+ CHECK(S.Size() == 0);
+ CHECK(S.ToView() == std::string_view());
+ CHECK(S.c_str()[0] == '\0');
+}
+
+TEST_CASE("CompactString.empty")
+{
+ CompactString S(std::string_view(""));
+ CHECK(S.IsEmpty());
+ CHECK(S.Size() == 0);
+}
+
+TEST_CASE("CompactString.short")
+{
+ CompactString S(std::string_view("hello"));
+ CHECK(!S.IsEmpty());
+ CHECK(S.Size() == 5);
+ CHECK(S.ToView() == std::string_view("hello"));
+}
+
+TEST_CASE("CompactString.sentinel_boundary")
+{
+ // 254 chars — largest value that fits in the prefix byte
+ std::string Str254(254, 'x');
+ std::string_view View254(Str254);
+ CompactString S(View254);
+ CHECK(S.Size() == 254);
+ CHECK(S.ToView() == View254);
+}
+
+TEST_CASE("CompactString.sentinel_exact")
+{
+ // 255 chars — hits the 0xFF sentinel, falls back to strlen
+ std::string Str255(255, 'y');
+ std::string_view View255(Str255);
+ CompactString S(View255);
+ CHECK(S.Size() == 255);
+ CHECK(S.ToView() == View255);
+}
+
+TEST_CASE("CompactString.long")
+{
+ // Well beyond the sentinel
+ std::string Str512(512, 'z');
+ std::string_view View512(Str512);
+ CompactString S(View512);
+ CHECK(S.Size() == 512);
+ CHECK(S.ToView() == View512);
+}
+
+TEST_CASE("CompactString.move")
+{
+ CompactString A(std::string_view("test"));
+ CompactString B(std::move(A));
+ CHECK(A.IsEmpty());
+ CHECK(B.ToView() == std::string_view("test"));
+
+ CompactString C(std::string_view("first"));
+ CompactString D(std::string_view("second"));
+ D = std::move(C);
+ CHECK(C.IsEmpty());
+ CHECK(D.ToView() == std::string_view("first"));
+}
+
+TEST_CASE("CompactString.implicit_conversion")
+{
+ CompactString S(std::string_view("view"));
+ std::string_view V = S;
+ CHECK(V == std::string_view("view"));
+}
+
TEST_SUITE_END();
void