GCC Code Coverage Report


Directory: ./
File: src/util/str-fmt.cc
Date: 2025-09-01 06:19:01
Exec Total Coverage
Lines: 54 115 47.0%
Functions: 5 12 41.7%
Branches: 46 71 64.8%

Line Branch Exec Source
1 #include "na64util/str-fmt.hh"
2
3 #include <cstdarg>
4 #include <vector>
5 #include <map>
6 #include <unordered_set>
7 #include <cassert>
8 #include <cmath>
9 #include <cstring>
10 #include <cstdint>
11 #include <regex>
12
13 namespace na64dp {
14 namespace util {
15
16 std::string
17 str_replace( std::string src
18 , const std::string & search
19 , const std::string & replace
20 ) {
21 size_t pos = 0;
22 while((pos = src.find(search, pos)) != std::string::npos) {
23 src.replace(pos, search.length(), replace);
24 pos += replace.length();
25 }
26 return src;
27 }
28
29
30 const StringSubstFormat gDefaultStrFormat = { "{", "}" };
31
32 /** This function reproduces pure C `printf()` behaviour, accepting a format
33 * string and arbitrary number of arguments to produce output as an
34 * `std::string` instance.
35 *
36 * Although the output string is allocated on heap, initial buffer is restrcted
37 * by number defined by `NA64DP_STR_FMT_LENGTH` macro. It will be extended, but
38 * this fact may have importance for performance concerns.
39 * */
40 std::string
41 104 format(const char *fmt, ...) throw() {
42 va_list args;
43 104 va_start(args, fmt);
44 208 std::vector<char> v(NA64DP_STR_FMT_LENGTH);
45 while(true) {
46 va_list args2;
47 104 va_copy(args2, args);
48 104 int res = vsnprintf(v.data(), v.size(), fmt, args2);
49
3/6
✓ Branch 0 taken 104 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 104 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 104 times.
✗ Branch 6 not taken.
104 if ((res >= 0) && (res < static_cast<int>(v.size()))) {
50 104 va_end(args);
51 104 va_end(args2);
52 104 return std::string(v.data());
53 }
54 size_t size;
55 if (res < 0)
56 size = v.size() * 2;
57 else
58 size = static_cast<size_t>(res) + 1;
59 v.clear();
60 v.resize(size);
61 va_end(args2);
62 }
63 104 }
64
65 std::string
66 15 str_subst( const std::string & tmplt
67 , const std::map<std::string, std::string> & context
68 , bool requireCompleteness
69 , const StringSubstFormat * fmt
70 ) {
71 15 std::string r(tmplt);
72 // Iterate over all entries within a context (we assume context to be
73 // short).
74
3/3
✓ Branch 4 taken 76 times.
✓ Branch 8 taken 76 times.
✓ Branch 9 taken 15 times.
91 for( auto entry : context ) {
75 std::size_t pos;
76
2/2
✓ Branch 1 taken 76 times.
✓ Branch 4 taken 76 times.
76 const std::string key = fmt->bgnMarker + entry.first + fmt->endMarker
77 76 , & val = entry.second;
78 // Unil there is no more occurances of the string, perform search and
79 // substitution
80
2/2
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 76 times.
100 while( std::string::npos != (pos = r.find(key)) ) {
81
2/2
✓ Branch 2 taken 24 times.
✓ Branch 5 taken 24 times.
24 r = r.replace( pos, key.length(), val );
82 }
83 76 }
84
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
15 if( requireCompleteness ) {
85
1/1
✓ Branch 1 taken 14 times.
15 assert_str_has_no_fillers(r, fmt);
86 }
87 14 return r;
88 1 }
89
90 static const std::unordered_set<std::string>
91 _trueToks = { "true", "1", "yes", "enable", "on" },
92 _falseToks = { "false", "0", "no", "disable", "off" };
93
94 bool
95 str_to_bool(std::string expr) {
96 trim(expr);
97 std::transform(expr.begin(), expr.end(), expr.begin(),
98 [](unsigned char c){ return std::tolower(c); });
99 if( _trueToks.find(expr) != _trueToks.end() ) return true;
100 if( _falseToks.find(expr) != _falseToks.end() ) return false;
101 throw errors::InvalidOptionExpression(expr);
102 }
103
104 /** We consider any of curly brackets `{}` to be a remnant of template string
105 * markup and return `true` if any of them is found in string
106 *
107 * \todo shall we imply some advanced logic here?
108 * \todo Support `StringSubstFormat` (doesn't work for non-default)
109 * */
110 bool
111 15 str_has_no_fillers( const std::string & s
112 , const StringSubstFormat * fmt ) {
113 15 return std::string::npos == s.find_first_of("{}");
114 }
115
116 /** Uses `str_has_no_fillers()` to check if string is consistent and raises
117 * `StringIncomplete` exception if expectation is not fullfilled.
118 *
119 * \todo Support `StringSubstFormat` (doesn't work for non-default)
120 * */
121 void
122 15 assert_str_has_no_fillers( const std::string & s
123 , const StringSubstFormat * fmt ) {
124
2/2
✓ Branch 1 taken 14 times.
✓ Branch 2 taken 1 times.
15 if( str_has_no_fillers(s) ) return;
125 // find the first problematic token, if exists
126 1 size_t sPos = 0;
127 do {
128 1 size_t tokBgn = s.find('{', sPos);
129
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if(tokBgn == std::string::npos) return; // no open bracket
130
3/7
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 1 times.
1 if(tokBgn > 1 && s[tokBgn-1] == '\\' && tokBgn != s.size()) {
131 sPos = tokBgn + 1;
132 continue; // escaped open bracket
133 }
134 1 size_t tokEnd = s.find('}', tokBgn);
135
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if(tokEnd > tokBgn)
136
1/1
✓ Branch 2 taken 1 times.
1 throw errors::StringIncomplete(s, s.substr(tokBgn, tokEnd));
137 } while(false);
138 }
139
140 #if 0
141 std::unordered_set<std::string>
142 get_fillers(const std::string & expr) {
143 std::unordered_set<std::string> result;
144 std::regex pat(R"(\{([^\}]+)\})");
145 std::smatch match;
146 auto begin = expr.cbegin()
147 , end = expr.cend();
148 while( std::regex_search(begin, end, match, pat) ) {
149 result.emplace(match[1]);
150 begin = match.suffix().first;
151 }
152 return result;
153 }
154 #else
155 std::unordered_set<std::string>
156 get_fillers(const std::string & input) {
157 static const std::regex placeholderRegex(R"(\{([^{}]+)\})");
158 std::unordered_set<std::string> result;
159 auto begin = std::sregex_iterator(input.begin(), input.end(), placeholderRegex);
160 auto end = std::sregex_iterator();
161 for( auto it = begin; it != end; ++it ) {
162 result.insert((*it)[1]); // Group 1 inside {...}
163 }
164 return result;
165 }
166
167 #endif
168
169 /** Examples:
170 * "one two" -> ("one", "two")
171 * ""some another" vlah" -> ("some another", "blah")
172 * */
173 std::vector<std::string>
174 5 tokenize_quoted_expression(const std::string & strExpr) {
175 5 std::vector<std::string> tokens;
176 5 const char * tokBgn = nullptr;
177
2/2
✓ Branch 1 taken 34 times.
✓ Branch 2 taken 3 times.
37 for( const char *c = strExpr.c_str(); '\0' != *c; ++c ) {
178
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 23 times.
34 if( ' ' == *c ) {
179
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 4 times.
11 if( !tokBgn ) continue; // omit spaces between tokens
180 // otherwise, push token
181
2/2
✓ Branch 1 taken 4 times.
✓ Branch 4 taken 4 times.
4 tokens.push_back( std::string(tokBgn, c - tokBgn) );
182 4 tokBgn = nullptr;
183
3/4
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 17 times.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
23 } else if( '"' == *c && !tokBgn ) {
184 6 tokBgn = ++c;
185 // comma starts escaped sequence -- traverse for next comma
186
2/2
✓ Branch 0 taken 31 times.
✓ Branch 1 taken 4 times.
35 while( *c != '"' ) {
187
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 29 times.
31 if( *c == '\0' )
188
1/1
✓ Branch 2 taken 2 times.
2 throw std::runtime_error("Unbalanced quotes.");
189 29 ++c;
190 }
191 // Push token
192
2/2
✓ Branch 1 taken 4 times.
✓ Branch 4 taken 4 times.
4 tokens.push_back( std::string(tokBgn, c - tokBgn) );
193 4 tokBgn = nullptr;
194 } else {
195
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 11 times.
17 if( !tokBgn ) tokBgn = c;
196 }
197 }
198 // push back last token in string
199
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 if(tokBgn)
200
2/2
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 2 times.
4 tokens.push_back( tokBgn );
201 3 return tokens;
202 2 }
203
204 size_t
205 seconds_to_hmsm( float seconds
206 , char * dest , size_t destLen ) {
207 size_t nWritten = 0;
208 size_t intSecondsOverall = round(seconds)
209 , hh = intSecondsOverall/3600;
210 uint32_t mm = (intSecondsOverall%3600)/60
211 , ss = intSecondsOverall%60
212 , ms = size_t(seconds*1000)%1000
213 ;
214 char * c = dest;
215 if(hh) {
216 nWritten = snprintf(c, destLen, "%zuh", hh);
217 if(nWritten >= destLen) return nWritten;
218 c += nWritten;
219 }
220 if(mm) {
221 nWritten = snprintf( c, destLen - (c - dest)
222 , "%02u:", mm);
223 if(nWritten >= destLen) return nWritten;
224 c += nWritten;
225 }
226 nWritten = snprintf( c, destLen - (c - dest)
227 , "%02u.%03u", ss, ms );
228 if(nWritten >= destLen) return nWritten;
229 c += nWritten;
230 return c - dest;
231 }
232
233 time_t
234 parse_timedate_strexpr( const char * c ) {
235 // Parse timedate string into GNU POSIX ::tm instance
236 // Note, that these time strings are given in local CERN-Prevessin EHN1
237 // location's timezone (Europe/Paris). To get the true timestamp (UTC)
238 // one need to convert it using timezone info
239 ::tm tm;
240 memset(&tm, 0, sizeof(tm));
241 strptime(c, "%Y-%m-%d-%H:%M:%S", &tm); // TODO: configurable macro
242 //printf( "xxx %s\n", tm.tm_zone );
243 // Not set by strptime(); tells mktime() to determine whether daylight
244 // saving time is in effect:
245 tm.tm_isdst = -1;
246 // Force the measurement timezone
247 //tm.tm_zone = "Europe/Paris"; // does not work
248 // Convert to time_t
249 time_t t = mktime(&tm);
250 if(-1 == t) {
251 return 0;
252 }
253 //printf( "%s => year=%d, month=%d, date=%d, time=%d:%d:%d ; timestamp=%ld\n"
254 // , c
255 // , 1900 + tm.tm_year, 1 + tm.tm_mon, tm.tm_mday
256 // , tm.tm_hour, tm.tm_min, tm.tm_sec
257 // , t
258 // ); // xxx
259 return t;
260 }
261
262 std::string
263 str_format_timedate(const time_t & t) {
264 char bf[128];
265 struct tm tm;
266 gmtime_r(&t, &tm);
267 strftime( bf, sizeof(bf)
268 , "%Y-%m-%d-%H:%M:%S" // TODO: configurable macro
269 , &tm );
270 return bf;
271 }
272
273 } // namespace ::na64dp::util
274 } // namespace na64dp
275
276