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 66 : parser(std::string const & f, variable::vector_t & variables)
72 66 : : f_input(f)
73 66 : , f_variables(variables)
74 : {
75 66 : }
76 :
77 3700 : int getc()
78 : {
79 3700 : if(f_pos >= f_input.length())
80 : {
81 66 : return EOF;
82 : }
83 :
84 3634 : int const c(f_input[f_pos]);
85 3634 : ++f_pos;
86 3634 : return c;
87 : }
88 :
89 321 : void ungetc(int c)
90 : {
91 321 : if(f_pos == 0)
92 : {
93 : throw logger_logic_error("ungetc() called too many times."); // LCOV_EXCL_LINE
94 : }
95 321 : --f_pos;
96 321 : 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 321 : }
108 :
109 672 : token_t get_token()
110 : {
111 : for(;;)
112 : {
113 672 : int c(getc());
114 672 : if(c == EOF)
115 : {
116 0 : return token_t::TOKEN_EOF;
117 : }
118 672 : switch(c)
119 : {
120 0 : case ' ':
121 : case '\t':
122 : case '\n':
123 : case '\r':
124 : // ignore spaces
125 0 : break;
126 :
127 104 : case ':':
128 104 : return token_t::TOKEN_COLON;
129 :
130 91 : case '=':
131 91 : return token_t::TOKEN_EQUAL;
132 :
133 141 : case '}':
134 141 : 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 287 : default:
191 287 : if((c >= 'a' && c <= 'z')
192 0 : || (c >= 'A' && c <= 'Z')
193 0 : || c == '_')
194 : {
195 287 : f_text.clear();
196 287 : f_text += c;
197 : for(;;)
198 : {
199 1987 : c = getc();
200 1987 : if((c < 'a' || c > 'z')
201 324 : && (c < 'A' || c > 'Z')
202 324 : && (c < '0' || c > '9')
203 324 : && c != '_')
204 : {
205 287 : ungetc(c);
206 287 : break;
207 : }
208 1700 : f_text += c;
209 : }
210 287 : 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 141 : bool parse_variable()
223 : {
224 141 : token_t tok(get_token());
225 141 : if(tok != token_t::TOKEN_IDENTIFIER)
226 : {
227 0 : return false;
228 : }
229 141 : variable::pointer_t var(get_variable(f_text));
230 141 : if(var == nullptr)
231 : {
232 0 : return false;
233 : }
234 141 : f_variables.push_back(var);
235 :
236 141 : tok = get_token();
237 : for(;;)
238 : {
239 245 : if(tok == token_t::TOKEN_END)
240 : {
241 141 : break;
242 : }
243 104 : if(tok != token_t::TOKEN_COLON)
244 : {
245 0 : return false;
246 : }
247 104 : tok = get_token();
248 104 : if(tok != token_t::TOKEN_IDENTIFIER)
249 : {
250 0 : return false;
251 : }
252 104 : param::pointer_t p(std::make_shared<param>(f_text));
253 104 : var->add_param(p);
254 :
255 104 : tok = get_token();
256 104 : 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 208 : }
278 :
279 141 : return true;
280 141 : }
281 :
282 66 : void parse()
283 : {
284 66 : auto add_text = [this](std::string const & text)
285 : {
286 207 : if(!text.empty())
287 : {
288 336 : variable::pointer_t var(get_variable("direct"));
289 112 : if(var == nullptr)
290 : {
291 0 : throw logger_logic_error("variable type \"direct\" not registered?");
292 : }
293 112 : param::pointer_t p(std::make_shared<param>("msg"));
294 112 : var->add_param(p);
295 112 : p->set_value(text);
296 112 : f_variables.push_back(var);
297 112 : }
298 207 : };
299 :
300 66 : std::string text;
301 :
302 : for(;;)
303 : {
304 770 : int c(getc());
305 770 : if(c == EOF)
306 : {
307 66 : break;
308 : }
309 704 : if(c == '$')
310 : {
311 149 : c = getc();
312 149 : if(c == '{')
313 : {
314 : // we found what looks like a variable definition
315 : //
316 141 : add_text(text);
317 141 : 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 141 : auto const save_pos(f_pos);
324 141 : 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 555 : text += c;
339 : }
340 704 : }
341 66 : add_text(text);
342 132 : }
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 66 : format::format(std::string const & f)
359 66 : : f_format(f)
360 : {
361 66 : parser p(f, f_variables);
362 66 : p.parse();
363 132 : }
364 :
365 :
366 0 : std::string format::get_format() const
367 : {
368 0 : return f_format;
369 : }
370 :
371 :
372 41374 : std::string format::process_message(message const & msg, bool ignore_on_no_repeat)
373 : {
374 41374 : return std::accumulate(
375 : f_variables.begin()
376 : , f_variables.end()
377 41380 : , std::string()
378 41695 : , [&msg, &ignore_on_no_repeat](std::string const & r, variable::pointer_t v)
379 : {
380 83390 : if(ignore_on_no_repeat
381 41695 : && 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 2 : return r;
387 : }
388 83380 : return r + v->get_value(msg);
389 124116 : });
390 : }
391 :
392 :
393 :
394 :
395 :
396 : } // snaplogger namespace
397 : // vim: ts=4 sw=4 et
|