GCC Code Coverage Report


Directory: include/tts/
File: include/tts/tools/text.hpp
Date: 2026-10-14 16:28:14
Exec Total Coverage
Lines: 99 99 100.0%
Functions: 83 88 94.3%
Branches: 22 31 71.0%

Line Branch Exec Source
1 //======================================================================================================================
2 //! @file
3 /*
4 TTS - Tiny Test System
5 Copyright : TTS Contributors & Maintainers
6 SPDX-License-Identifier: BSL-1.0
7 */
8 //======================================================================================================================
9 #pragma once
10
11 #include <tts/tools/concepts.hpp>
12 #include <tts/tools/preprocessor.hpp>
13 #include <cstdarg>
14
15 TTS_DISABLE_WARNING_PUSH
16 TTS_DISABLE_WARNING_CRT_SECURE
17
18 namespace tts
19 {
20 //====================================================================================================================
21 /**
22 @ingroup tools-text
23 @brief Lightweight string-like object
24
25 `tts::text` is a simple string-like object that can be used to represent and manipulate text
26 data in TTS. It provides basic functionalities such as construction from formatted strings,
27 concatenation, and stream output support
28
29 @groupheader{Example}
30 @snippet doc/text.cpp snippet
31
32 @see tts::as_text
33 **/
34 //====================================================================================================================
35 struct text
36 {
37 /// Default constructor
38 12002 text() = default;
39
40 /// Construct from C-style string
41 4078 explicit text(char const* ptr)
42 4078 : text()
43 {
44
1/2
✓ Branch 0 taken 4078 times.
✗ Branch 1 not taken.
4078 if(ptr)
45 {
46 4078 std::size_t len = strlen(ptr);
47
2/2
✓ Branch 0 taken 3948 times.
✓ Branch 1 taken 130 times.
4078 if(len > 0)
48 {
49 3948 data_ = reinterpret_cast<char*>(malloc(len + 1)); // NOSONAR
50
1/2
✓ Branch 0 taken 3948 times.
✗ Branch 1 not taken.
3948 if(data_)
51 {
52 3948 size_ = len;
53 3948 memcpy(data_, ptr, size_);
54 3948 data_[ size_ ] = '\0';
55 }
56 }
57 }
58 4078 }
59
60 /**
61 @brief Construct from formatted string
62
63 tts::text is constructible from a C-style format string and a variable number of arguments.
64
65 @param format C-style format string
66 @param args Arguments to format according to `format`
67 **/
68 template<typename... Args>
69 8792 explicit text(char const* format, Args... args)
70 8792 : text()
71 {
72 8792 assign(format, args...);
73 8792 }
74
75 /// Copy constructor
76 832 text(text const& other)
77 832 : text()
78 {
79 // Strictly check for positive size and valid pointer to prevent allocation errors
80
2/4
✓ Branch 0 taken 832 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 832 times.
✗ Branch 3 not taken.
832 if(other.size_ > 0 && other.data_)
81 {
82 832 data_ = reinterpret_cast<char*>(malloc(other.size_ + 1)); // NOSONAR
83
1/2
✓ Branch 0 taken 832 times.
✗ Branch 1 not taken.
832 if(data_)
84 {
85 832 size_ = other.size_;
86 832 memcpy(data_, other.data_, size_);
87 832 data_[ size_ ] = '\0';
88 }
89 }
90 832 }
91
92 /// Move constructor
93 896 text(text&& other) noexcept
94 896 : text()
95 {
96 896 swap(other);
97 896 }
98
99 /// Copy assignment operator
100 1 text& operator=(text const& other)
101 {
102 1 text local(other);
103 1 swap(local);
104 2 return *this;
105 1 }
106
107 /// Move assignment operator
108 361 text& operator=(text&& other) noexcept
109 {
110 361 text local(std::move(other));
111 361 swap(local);
112 722 return *this;
113 361 }
114
115 /// Destructor
116 12786 ~text()
117 {
118
2/2
✓ Branch 0 taken 9176 times.
✓ Branch 1 taken 3610 times.
12786 if(data_) free(data_); // NOSONAR
119 12786 }
120
121 /// Swap contents with another text instance
122 2815 void swap(text& o) noexcept
123 {
124 2815 std::swap(o.data_, data_);
125 2815 std::swap(o.size_, size_);
126 2815 }
127
128 /// Concatenate another text instance
129 933 text& operator+=(text const& other)
130 {
131 933 text local {"%.*s%.*s", size_, data(), other.size_, other.data()};
132 933 swap(local);
133 1866 return *this;
134 933 }
135
136 /// Concatenate a C-style string
137 624 text& operator+=(char const* other)
138 {
139
1/2
✓ Branch 0 taken 624 times.
✗ Branch 1 not taken.
624 if(other)
140 {
141 624 text local {"%.*s%s", size_, data(), other};
142 624 swap(local);
143 624 }
144 624 return *this;
145 }
146
147 /// Concatenate and return a new text instance
148 8 text operator+(text const& other) const
149 {
150 8 text local(*this);
151
1/1
✓ Branch 1 taken 8 times.
16 return local += other;
152 8 }
153
154 /// Stream output operator
155 template<_::stream OS> friend OS& operator<<(OS& os, text const& t)
156 {
157 if(t.data_)
158 {
159 for(size_t i = 0; i < t.size_; ++i)
160 os << t.data_[ i ];
161 }
162 return os;
163 }
164
165 /// Check if the text is empty
166 3682 bool is_empty() const
167 {
168 3682 return size_ == 0;
169 }
170
171 /// Get the size of the text
172 size_t size() const
173 {
174 return size_;
175 }
176
177 /// Get a pointer to the constant underlying character data
178 /// Returns an empty string literal if data_ is null to prevent %s crashes
179 7225 [[nodiscard]] char const* data() const
180 {
181
2/2
✓ Branch 0 taken 6986 times.
✓ Branch 1 taken 239 times.
7225 return data_ ? data_ : "";
182 }
183
184 /// Get a const iterator to the beginning of the text
185 8 decltype(auto) begin() const
186 {
187 8 return data_;
188 }
189
190 /// Get an iterator to the beginning of the text
191 decltype(auto) begin()
192 {
193 return data_;
194 }
195
196 /// Get a const iterator to the end of the text
197 8 decltype(auto) end() const
198 {
199 8 return data_ + size_;
200 }
201
202 /// Get an iterator to the end of the text
203 decltype(auto) end()
204 {
205 return data_ + size_;
206 }
207
208 /// Equality comparison operator
209 31 friend bool operator==(text const& a, text const& b) noexcept
210 {
211
2/2
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 10 times.
31 if(a.size_ != b.size_) return false;
212
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
10 if(a.is_empty()) return true;
213 10 return strcmp(a.data_, b.data_) == 0;
214 }
215
216 /// Equality comparison operator with C-style string
217 1076 template<std::size_t N> friend bool operator==(text const& a, char const (&b)[ N ]) noexcept
218 {
219
2/2
✓ Branch 1 taken 490 times.
✓ Branch 2 taken 48 times.
1076 if(a.is_empty()) return N == 1;
220 96 return strcmp(a.data_, &b[ 0 ]) == 0;
221 }
222
223 /// Three-way comparison operator with other text instance
224 friend std::strong_ordering operator<=>(text const& a, text const& b) noexcept
225 {
226 if(a.is_empty() && b.is_empty()) return std::strong_ordering::equal;
227 if(a.is_empty()) return std::strong_ordering::less;
228 if(b.is_empty()) return std::strong_ordering::greater;
229
230 size_t const size = a.size_ < b.size_ ? a.size_ : b.size_;
231 int const cmp = strncmp(a.data_, b.data_, size);
232
233 if(cmp != 0) return cmp <=> 0;
234 return a.size_ <=> b.size_;
235 }
236
237 /// Three-way comparison operator with C-style string
238 template<std::size_t N> friend auto operator<=>(text const& a, char const (&b)[ N ]) noexcept
239 {
240 return a <=> text {b};
241 }
242
243 private:
244 4396 void assign(char const* format, ...) // NOSONAR - one body for every argument list
245 {
246 va_list args;
247 va_list probe;
248 4396 va_start(args, format);
249 4396 va_copy(probe, args);
250
251 // Store result in local int to check for errors/negatives
252 4396 int len = vsnprintf(nullptr, 0, format, probe);
253 4396 va_end(probe);
254
1/2
✓ Branch 0 taken 4396 times.
✗ Branch 1 not taken.
4396 if(len > 0)
255 {
256 4396 auto sz = static_cast<std::size_t>(len);
257 4396 data_ = reinterpret_cast<char*>(malloc(sz + 1)); // NOSONAR
258
1/2
✓ Branch 0 taken 4396 times.
✗ Branch 1 not taken.
4396 if(data_)
259 {
260 4396 size_ = sz; // Only assign size if alloc succeeds
261 4396 vsnprintf(data_, size_ + 1, format, args);
262 }
263 }
264 4396 va_end(args);
265 4396 }
266
267 char* data_ = nullptr;
268 size_t size_ = 0;
269 };
270
271 /// Concatenate text with C-style string
272 402 inline text operator+(text const& lhs, char const* rhs)
273 {
274 402 text that(lhs);
275
1/1
✓ Branch 1 taken 402 times.
804 return that += rhs;
276 402 }
277
278 /// Concatenate text with C-style string
279 10 inline text operator+(char const* lhs, text const& rhs)
280 {
281 10 text that(lhs);
282
1/1
✓ Branch 1 taken 10 times.
20 return that += rhs;
283 10 }
284
285 inline namespace literals
286 {
287 1 inline auto operator""_txt(char const* ptr, std::size_t sz)
288 {
289 1 return text("%.*s", sz, ptr);
290 }
291 }
292 }
293
294 TTS_DISABLE_WARNING_POP
295