Line data Source code
1 : // Copyright (c) 2013-2025 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/snaplogger
4 : // contact@m2osw.com
5 : //
6 : // This program is free software: you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation, either version 3 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program. If not, see <https://www.gnu.org/licenses/>.
18 :
19 : /** \file
20 : * \brief Format a message.
21 : *
22 : * This file declares the format class used to transform a message with
23 : * the administrator defined format.
24 : */
25 :
26 :
27 : // self
28 : //
29 : #include "snaplogger/format.h"
30 :
31 : #include "snaplogger/exception.h"
32 :
33 :
34 : // C++
35 : //
36 : #include <numeric>
37 : #include <iostream>
38 :
39 :
40 : // last include
41 : //
42 : #include <snapdev/poison.h>
43 :
44 :
45 :
46 : namespace snaplogger
47 : {
48 :
49 :
50 : namespace
51 : {
52 :
53 :
54 :
55 : class parser
56 : {
57 : public:
58 : enum class token_t
59 : {
60 : TOKEN_EOF,
61 : TOKEN_COLON,
62 : TOKEN_EQUAL,
63 : TOKEN_STRING,
64 : TOKEN_INTEGER,
65 : TOKEN_IDENTIFIER,
66 : TOKEN_END,
67 :
68 : TOKEN_ERROR, // mark an error
69 : };
70 :
71 63 : parser(std::string const & f, variable::vector_t & variables)
72 63 : : f_input(f)
73 63 : , f_variables(variables)
74 : {
75 63 : }
76 :
77 3600 : int getc()
78 : {
79 3600 : if(f_pos >= f_input.length())
80 : {
81 63 : return EOF;
82 : }
83 :
84 3537 : int const c(f_input[f_pos]);
85 3537 : ++f_pos;
86 3537 : return c;
87 : }
88 :
89 312 : void ungetc(int c)
90 : {
91 312 : if(f_pos == 0)
92 : {
93 : throw logger_logic_error("ungetc() called too many times."); // LCOV_EXCL_LINE
94 : }
95 312 : --f_pos;
96 312 : if(f_input[f_pos] != c)
97 : {
98 : // LCOV_EXCL_START
99 : throw logger_logic_error(
100 : std::string("ungetc() called with the wrong character (expected '")
101 : + static_cast<char>(f_input[f_pos])
102 : + "', got '"
103 : + static_cast<char>(c)
104 : + "').");
105 : // LCOV_EXCL_STOP
106 : }
107 312 : }
108 :
109 654 : token_t get_token()
110 : {
111 : for(;;)
112 : {
113 654 : int c(getc());
114 654 : if(c == EOF)
115 : {
116 0 : return token_t::TOKEN_EOF;
117 : }
118 654 : switch(c)
119 : {
120 0 : case ' ':
121 : case '\t':
122 : case '\n':
123 : case '\r':
124 : // ignore spaces
125 0 : break;
126 :
127 100 : case ':':
128 100 : return token_t::TOKEN_COLON;
129 :
130 91 : case '=':
131 91 : return token_t::TOKEN_EQUAL;
132 :
133 136 : case '}':
134 136 : return token_t::TOKEN_END;
135 :
136 23 : case '"':
137 : case '\'':
138 : case '`':
139 : {
140 23 : f_text.clear();
141 23 : int quote(c);
142 : for(;;)
143 : {
144 69 : c = getc();
145 69 : if(c == '\\')
146 : {
147 1 : c = getc();
148 : }
149 68 : else if(c == quote)
150 : {
151 23 : break;
152 : }
153 46 : if(c == EOF)
154 : {
155 0 : return token_t::TOKEN_ERROR;
156 : }
157 46 : f_text += c;
158 : }
159 23 : return token_t::TOKEN_STRING;
160 : }
161 : break;
162 :
163 26 : case '0':
164 : case '1':
165 : case '2':
166 : case '3':
167 : case '4':
168 : case '5':
169 : case '6':
170 : case '7':
171 : case '8':
172 : case '9':
173 : {
174 26 : f_integer = c - '0';
175 : for(;;)
176 : {
177 52 : c = getc();
178 52 : if(c < '0' || c > '9')
179 : {
180 26 : ungetc(c);
181 26 : break;
182 : }
183 26 : f_integer *= 10;
184 26 : f_integer += c - '0';
185 : }
186 26 : return token_t::TOKEN_INTEGER;
187 : }
188 : break;
189 :
190 278 : default:
191 278 : if((c >= 'a' && c <= 'z')
192 0 : || (c >= 'A' && c <= 'Z')
193 0 : || c == '_')
194 : {
195 278 : f_text.clear();
196 278 : f_text += c;
197 : for(;;)
198 : {
199 1929 : c = getc();
200 1929 : if((c < 'a' || c > 'z')
201 314 : && (c < 'A' || c > 'Z')
202 314 : && (c < '0' || c > '9')
203 314 : && c != '_')
204 : {
205 278 : ungetc(c);
206 278 : break;
207 : }
208 1651 : f_text += c;
209 : }
210 278 : return token_t::TOKEN_IDENTIFIER;
211 : }
212 : else
213 : {
214 0 : return token_t::TOKEN_ERROR;
215 : }
216 : break;
217 :
218 : }
219 0 : }
220 : }
221 :
222 136 : bool parse_variable()
223 : {
224 136 : token_t tok(get_token());
225 136 : if(tok != token_t::TOKEN_IDENTIFIER)
226 : {
227 0 : return false;
228 : }
229 136 : variable::pointer_t var(get_variable(f_text));
230 136 : if(var == nullptr)
231 : {
232 0 : return false;
233 : }
234 136 : f_variables.push_back(var);
235 :
236 136 : tok = get_token();
237 : for(;;)
238 : {
239 236 : if(tok == token_t::TOKEN_END)
240 : {
241 136 : break;
242 : }
243 100 : if(tok != token_t::TOKEN_COLON)
244 : {
245 0 : return false;
246 : }
247 100 : tok = get_token();
248 100 : if(tok != token_t::TOKEN_IDENTIFIER)
249 : {
250 0 : return false;
251 : }
252 100 : param::pointer_t p(std::make_shared<param>(f_text));
253 100 : var->add_param(p);
254 :
255 100 : tok = get_token();
256 100 : if(tok == token_t::TOKEN_EQUAL)
257 : {
258 : // the token is followed by a value
259 : //
260 91 : tok = get_token();
261 91 : if(tok == token_t::TOKEN_STRING
262 68 : || tok == token_t::TOKEN_IDENTIFIER)
263 : {
264 65 : p->set_value(f_text);
265 : }
266 26 : else if(tok == token_t::TOKEN_INTEGER)
267 : {
268 26 : p->set_integer(f_integer);
269 : }
270 : else
271 : {
272 0 : return false;
273 : }
274 :
275 91 : tok = get_token();
276 : }
277 200 : }
278 :
279 136 : return true;
280 136 : }
281 :
282 63 : void parse()
283 : {
284 63 : auto add_text = [this](std::string const & text)
285 : {
286 199 : if(!text.empty())
287 : {
288 327 : variable::pointer_t var(get_variable("direct"));
289 109 : if(var == nullptr)
290 : {
291 0 : throw logger_logic_error("variable type \"direct\" not registered?");
292 : }
293 109 : param::pointer_t p(std::make_shared<param>("msg"));
294 109 : var->add_param(p);
295 109 : p->set_value(text);
296 109 : f_variables.push_back(var);
297 109 : }
298 199 : };
299 :
300 63 : std::string text;
301 :
302 : for(;;)
303 : {
304 751 : int c(getc());
305 751 : if(c == EOF)
306 : {
307 63 : break;
308 : }
309 688 : if(c == '$')
310 : {
311 144 : c = getc();
312 144 : if(c == '{')
313 : {
314 : // we found what looks like a variable definition
315 : //
316 136 : add_text(text);
317 136 : text.clear();
318 :
319 : // try parsing the variable, if that fails, ignore
320 : // the variable and instead add the "${" introducer
321 : // on its own
322 : //
323 136 : auto const save_pos(f_pos);
324 136 : if(!parse_variable())
325 : {
326 0 : text += "${";
327 0 : f_pos = save_pos;
328 : }
329 : }
330 : else
331 : {
332 8 : text += '$';
333 8 : ungetc(c);
334 : }
335 : }
336 : else
337 : {
338 544 : text += c;
339 : }
340 688 : }
341 63 : add_text(text);
342 126 : }
343 :
344 : private:
345 : std::string const f_input;
346 : size_t f_pos = 0;
347 : variable::vector_t & f_variables;
348 : std::string f_text = std::string();
349 : std::int64_t f_integer = 0;
350 : };
351 :
352 :
353 :
354 : }
355 : // no name namespace
356 :
357 :
358 63 : format::format(std::string const & f)
359 63 : : f_format(f)
360 : {
361 63 : parser p(f, f_variables);
362 63 : p.parse();
363 126 : }
364 :
365 :
366 0 : std::string format::get_format() const
367 : {
368 0 : return f_format;
369 : }
370 :
371 :
372 41128 : std::string format::process_message(message const & msg, bool ignore_on_no_repeat)
373 : {
374 41128 : return std::accumulate(
375 : f_variables.begin()
376 : , f_variables.end()
377 41134 : , std::string()
378 41484 : , [&msg, &ignore_on_no_repeat](std::string const & r, variable::pointer_t v)
379 : {
380 82968 : if(ignore_on_no_repeat
381 41484 : && v->ignore_on_no_repeat())
382 : {
383 : // do not include this variable to generate the "no-repeat"
384 : // message (i.e. probably a time base message)
385 : //
386 0 : return r;
387 : }
388 82962 : return r + v->get_value(msg);
389 123378 : });
390 : }
391 :
392 :
393 :
394 :
395 :
396 : } // snaplogger namespace
397 : // vim: ts=4 sw=4 et
|