GCC Code Coverage Report


Directory: ./
File: include/na64util/str-fmt.hh
Date: 2025-09-01 06:19:01
Exec Total Coverage
Lines: 9 29 31.0%
Functions: 3 11 27.3%
Branches: 0 2 0.0%

Line Branch Exec Source
1 #pragma once
2
3 #include "na64util/exception.hh"
4
5 #include <string>
6 #include <stdexcept>
7 #include <map>
8 #include <vector>
9 #include <algorithm>
10 #include <functional>
11 #include <unordered_set>
12 #include <sstream>
13
14 namespace na64dp {
15 namespace util {
16
17 /// A C++ bastard of printf() function
18 std::string format(const char *fmt, ...) throw();
19
20 /// String substitution dictionary type
21 typedef std::map<std::string, std::string> StrSubstDict;
22
23 extern const struct StringSubstFormat {
24 char bgnMarker[4], endMarker[4];
25 } gDefaultStrFormat;
26
27 /**\brief Renders string template wrt given context dictionary
28 *
29 * This function performs substitution in string expression `pattern` using the
30 * set of key+value pairs provided by `context`.
31 *
32 * Produces output relying on format string and a map of key-value pairs.
33 * E.g. for string template (1st argument) `"Hi, {user}!"` and pair
34 * `"user" -> "Joe"` the output string will be `"Hi, Joe!"`. This function is
35 * used across the library to "render" various "string templates" producing
36 * unique string for various detector types, messages, etc.
37 *
38 * If `requireCompleteness` is invokes `assert_str_has_no_fillers()` on result.
39 *
40 * \todo Recusrsive subst (e.g. "{kin}" -> "{kin}{statNum}") leads to
41 * infinite loop. Add exception for this.
42 */
43 std::string str_subst( const std::string & pattern
44 , const StrSubstDict & context
45 , bool requireCompleteness=true
46 , const StringSubstFormat * fmt=&gDefaultStrFormat );
47
48 /// Returns if the string has no template fillers
49 bool str_has_no_fillers( const std::string &, const StringSubstFormat * fmt=&gDefaultStrFormat );
50
51 /// Raises an exception if string has template fillers
52 void assert_str_has_no_fillers( const std::string &, const StringSubstFormat * fmt=&gDefaultStrFormat );
53
54 /// Retrieves list of fillers from string
55 ///
56 /// For instance, input "clusters/{kin@det}/{number@det}/clusters" will result
57 /// in two elements: "kin@det" and "numer@det".
58 std::unordered_set<std::string> get_fillers(const std::string & expr);
59
60 /// A generic utility function splitting space-separated tokens with
61 /// single-leveled commas expression support.
62 std::vector<std::string> tokenize_quoted_expression(const std::string &);
63
64 // trim from start (in place)
65 static inline void ltrim(std::string &s) {
66 s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
67 return !std::isspace(ch);
68 }));
69 }
70
71 // trim from end (in place)
72 static inline void rtrim(std::string &s) {
73 s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
74 return !std::isspace(ch);
75 }).base(), s.end());
76 }
77
78 // trim from both ends (in place)
79 static inline void trim(std::string &s) {
80 ltrim(s);
81 rtrim(s);
82 }
83
84 // trim from start (copying)
85 static inline std::string ltrim_copy(std::string s) {
86 ltrim(s);
87 return s;
88 }
89
90 // trim from end (copying)
91 static inline std::string rtrim_copy(std::string s) {
92 rtrim(s);
93 return s;
94 }
95
96 // trim from both ends (copying)
97 static inline std::string trim_copy(std::string s) {
98 trim(s);
99 return s;
100 }
101
102 /// Traits for context substitution; shall provide `append_dict(dct, val)`
103 template<typename T> struct StringSubstContextTraits;
104
105 /// Performs substitution of the string with placeholders with respect to
106 /// context dictionary taken from traits
107 template<typename T> std::string
108 str_subst_val( const T & val
109 , const std::string & pattern
110 , bool requireCompleteness=true
111 , const StringSubstFormat * fmt=&gDefaultStrFormat
112 ) {
113 StrSubstDict dct;
114 StringSubstContextTraits<T>::append_dict(dct, val);
115 return str_subst(pattern, dct, requireCompleteness, fmt);
116 }
117
118 /// Replaces phrase in a string
119 std::string
120 str_replace( std::string src
121 , const std::string & search
122 , const std::string & replace
123 );
124
125 /**\brief Converts string expression to boolean option
126 *
127 * Kind of similar to `lexical_cast<bool>()` proposed by many libs around:
128 * converts string option that is supposed to be boolean option.
129 * Case-insensitive.
130 * Variants yielding `true`: "1", "yes", "true", "enable", "on".
131 * Variants yielding `false`: null ptr, "0", "no", "false", "disable", "off", empty string.
132 * All other strings lead to `errors::InvalidOptionExpression` error.
133 * */
134 bool str_to_bool(std::string);
135
136 /**\brief Concatenates string-convertible items into delimited string
137 *
138 * This templated function comes in hand at many places to produce
139 * human-friendly form of comma-separated strings, mostly from STL-compatible
140 * containers. */
141 template<typename IteratorT> std::string
142 str_join( IteratorT itemsBgn
143 , IteratorT itemsEnd
144 , const std::string & delimiter=", "
145 , std::function<std::string(IteratorT)> f=[](IteratorT tokIt){return *tokIt;}
146 ) {
147 std::ostringstream oss;
148 bool isFirst = true;
149 for(IteratorT it = itemsBgn; it != itemsEnd; ++it) {
150 if(isFirst) isFirst = false; else oss << delimiter;
151 oss << f(it);
152 }
153 return oss.str();
154 }
155
156 /**\brief Time string formatting function
157 *
158 * Converts real value which is assumed to be rough estimation of seconds
159 * into human-readable string:
160 *
161 * [<hours>h][<minutes>m]<seconds>.<mseconds>
162 *
163 * Time formatted this way is widely used for logging as it seems to be a good
164 * tradeoff between precision, simplicity and a kind of compact form for
165 * typical application lifetime (from few seconds up to few hours). */
166 size_t seconds_to_hmsm(float seconds, char * dest, size_t destLen);
167
168 /**\brief Parses time+date string expression of somewhat standard form
169 *
170 * Generally, this function is to fix certain time+date standard format across
171 * NA64sw framework.
172 *
173 * Examples: "2018-05-14-20:14:57", "2016-07-09-18:50:42", i.e. corresponding
174 * `strptime()`-format is `"%s-%m-%d-%H:%M:%S"`.
175 *
176 * Symmetric to `str_format_timedate()`.
177 *
178 * \note technically, returned value is a real timestamp only at current
179 * timezone
180 * */
181 time_t
182 parse_timedate_strexpr( const char * c );
183
184 /**\brief Returns time+date string expression of given `time_t` struct
185 *
186 * Symmetric to `parse_timedate_strexpr()`.
187 *
188 * \note technically, returned value is a real timestamp only at current
189 * timezone
190 * */
191 std::string
192 str_format_timedate(const time_t &);
193
194 } // namespace ::na64dp::util
195
196 namespace errors {
197
198 class NoDataInEventError : public GenericRuntimeError {
199 public:
200 NoDataInEventError( const char * s ) throw() : GenericRuntimeError( s ) {}
201 };
202
203 class StringIncomplete : public std::runtime_error {
204 private:
205 std::string _tok;
206 std::string _s;
207 public:
208 1 StringIncomplete( const std::string & s, const std::string & tok ) throw()
209 2 : std::runtime_error( util::format( "String \"%s\" has template markup"
210 " after substitution.", s.c_str() ) )
211 1 , _tok(tok)
212 3 , _s(s)
213 1 {}
214 1 const std::string & incomplete_string() const throw() {
215 1 return _s;
216 }
217
218 2 const std::string & token() const throw() {
219 2 return _tok;
220 }
221 }; // class StringIncomplete
222
223 class InvalidOptionExpression : public GenericRuntimeError {
224 public:
225 InvalidOptionExpression( const std::string & expr ) throw()
226 : GenericRuntimeError( util::format( "Invalid option expression: \"%s\"", expr.c_str() ).c_str() )
227 {}
228 };
229
230 } // namespace na64dp::errors
231 } // namespace na64dp
232
233 #ifndef NA64DP_RUNTIME_ERROR
234 #define NA64DP_RUNTIME_ERROR( fmt, ... ) { \
235 throw ::na64dp::errors::GenericRuntimeError(::na64dp::util::format( \
236 "at %s:%d: " fmt, __FILE__, __LINE__, ##__VA_ARGS__ \
237 ).c_str() ); \
238 }
239 #endif
240
241 #ifndef NA64DP_ASSERT_EVENT_DATA
242 #define NA64DP_ASSERT_EVENT_DATA( data, fmt, ... ) if(!data){ \
243 throw ::na64dp::errors::NoDataInEventError(::na64dp::util::format( \
244 "at %s:%d: requested data is not set/does not exist in the" \
245 " event (" fmt ").", __FILE__, __LINE__, ##__VA_ARGS__ \
246 ).c_str()); \
247 }
248 #endif
249