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