Line data Source code
1 : // Copyright (c) 2013-2022 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++ lib
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 58 : 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 :
70 58 : parser(std::string const & f, variable::vector_t & variables)
71 58 : : f_input(f)
72 58 : , f_variables(variables)
73 : {
74 58 : }
75 :
76 3349 : int getc()
77 : {
78 3349 : if(f_pos >= f_input.length())
79 : {
80 58 : return EOF;
81 : }
82 :
83 3291 : int const c(f_input[f_pos]);
84 3291 : ++f_pos;
85 3291 : return c;
86 : }
87 :
88 290 : void ungetc(int c)
89 : {
90 290 : if(f_pos == 0)
91 : {
92 : throw logger_logic_error("ungetc() called too many times."); // LCOV_EXCL_LINE
93 : }
94 290 : --f_pos;
95 290 : if(f_input[f_pos] != c)
96 : {
97 : throw logger_logic_error("ungetc() called with the wrong character."); // LCOV_EXCL_LINE
98 : }
99 290 : }
100 :
101 610 : token_t get_token()
102 : {
103 : for(;;)
104 : {
105 610 : int c(getc());
106 610 : if(c == EOF)
107 : {
108 0 : return token_t::TOKEN_EOF;
109 : }
110 610 : switch(c)
111 : {
112 0 : case ' ':
113 : case '\t':
114 : case '\n':
115 : case '\r':
116 : // ignore spaces
117 0 : break;
118 :
119 95 : case ':':
120 95 : return token_t::TOKEN_COLON;
121 :
122 86 : case '=':
123 86 : return token_t::TOKEN_EQUAL;
124 :
125 124 : case '}':
126 124 : return token_t::TOKEN_END;
127 :
128 23 : case '"':
129 : case '\'':
130 : case '`':
131 : {
132 23 : f_text.clear();
133 23 : int quote(c);
134 : for(;;)
135 : {
136 115 : c = getc();
137 69 : if(c == '\\')
138 : {
139 1 : c = getc();
140 : }
141 68 : else if(c == quote)
142 : {
143 23 : break;
144 : }
145 46 : if(c == EOF)
146 : {
147 0 : throw invalid_variable("unterminated string in format variable.");
148 : }
149 46 : f_text += c;
150 : }
151 23 : return token_t::TOKEN_STRING;
152 : }
153 : break;
154 :
155 26 : case '0':
156 : case '1':
157 : case '2':
158 : case '3':
159 : case '4':
160 : case '5':
161 : case '6':
162 : case '7':
163 : case '8':
164 : case '9':
165 : {
166 26 : f_integer = c - '0';
167 : for(;;)
168 : {
169 78 : c = getc();
170 52 : if(c < '0' || c > '9')
171 : {
172 26 : ungetc(c);
173 26 : break;
174 : }
175 26 : f_integer *= 10;
176 26 : f_integer += c - '0';
177 : }
178 26 : return token_t::TOKEN_INTEGER;
179 : }
180 : break;
181 :
182 256 : default:
183 256 : if((c >= 'a' && c <= 'z')
184 0 : || (c >= 'A' && c <= 'Z')
185 0 : || c == '_')
186 : {
187 256 : f_text.clear();
188 256 : f_text += c;
189 : for(;;)
190 : {
191 3320 : c = getc();
192 1788 : if((c < 'a' || c > 'z')
193 291 : && (c < 'A' || c > 'Z')
194 291 : && (c < '0' || c > '9')
195 291 : && c != '_')
196 : {
197 256 : ungetc(c);
198 256 : break;
199 : }
200 1532 : f_text += c;
201 : }
202 256 : return token_t::TOKEN_IDENTIFIER;
203 : }
204 : else
205 : {
206 0 : std::stringstream ss;
207 :
208 0 : ss << "unexpected character '\\x"
209 0 : << std::hex
210 : << c
211 0 : << "' in format variable.";
212 :
213 0 : throw invalid_variable(ss.str());
214 : }
215 : break;
216 :
217 : }
218 0 : }
219 : }
220 :
221 124 : void parse_variable()
222 : {
223 124 : token_t tok(get_token());
224 124 : if(tok != token_t::TOKEN_IDENTIFIER)
225 : {
226 0 : throw invalid_variable("expected a token as the variable name.");
227 : }
228 248 : variable::pointer_t var(get_variable(f_text));
229 124 : if(var == nullptr)
230 : {
231 0 : throw invalid_variable("unknown variable \"" + f_text + "\".");
232 : }
233 124 : f_variables.push_back(var);
234 :
235 124 : tok = get_token();
236 : for(;;)
237 : {
238 219 : if(tok == token_t::TOKEN_END)
239 : {
240 124 : break;
241 : }
242 95 : if(tok != token_t::TOKEN_COLON)
243 : {
244 0 : throw invalid_variable("variable parameters must be delimited by colon (:) characters.");
245 : }
246 95 : tok = get_token();
247 95 : if(tok != token_t::TOKEN_IDENTIFIER)
248 : {
249 0 : throw invalid_variable("variable parameters must be given a name (an identifier).");
250 : }
251 190 : param::pointer_t p(std::make_shared<param>(f_text));
252 95 : var->add_param(p);
253 :
254 95 : tok = get_token();
255 95 : if(tok == token_t::TOKEN_EQUAL)
256 : {
257 : // the token is followed by a value
258 : //
259 86 : tok = get_token();
260 86 : if(tok == token_t::TOKEN_STRING
261 63 : || tok == token_t::TOKEN_IDENTIFIER)
262 : {
263 60 : p->set_value(f_text);
264 : }
265 26 : else if(tok == token_t::TOKEN_INTEGER)
266 : {
267 26 : p->set_integer(f_integer);
268 : }
269 : else
270 : {
271 0 : throw invalid_variable("unexpected token for a parameter value.");
272 : }
273 :
274 86 : tok = get_token();
275 : }
276 95 : }
277 124 : }
278 :
279 58 : void parse()
280 : {
281 240 : auto add_text = [this](std::string const & text)
282 96 : {
283 182 : if(!text.empty())
284 : {
285 192 : variable::pointer_t var(get_variable("direct"));
286 96 : if(var == nullptr)
287 : {
288 0 : throw logger_logic_error("variable type \"direct\" not registered?");
289 : }
290 192 : param::pointer_t p(std::make_shared<param>("msg"));
291 96 : var->add_param(p);
292 96 : p->set_value(text);
293 96 : f_variables.push_back(var);
294 : }
295 240 : };
296 :
297 116 : std::string text;
298 :
299 : for(;;)
300 : {
301 697 : int c(getc());
302 697 : if(c == EOF)
303 : {
304 58 : break;
305 : }
306 639 : if(c == '$')
307 : {
308 132 : c = getc();
309 132 : if(c == '{')
310 : {
311 : // we found a variable definition
312 : //
313 124 : add_text(text);
314 124 : text.clear();
315 :
316 124 : parse_variable();
317 : }
318 : else
319 : {
320 8 : text += '$';
321 8 : ungetc(c);
322 : }
323 : }
324 : else
325 : {
326 507 : text += c;
327 : }
328 639 : }
329 58 : add_text(text);
330 58 : }
331 :
332 : private:
333 : std::string const f_input;
334 : size_t f_pos = 0;
335 : variable::vector_t & f_variables;
336 : std::string f_text = std::string();
337 : std::int64_t f_integer = 0;
338 : };
339 :
340 :
341 :
342 : }
343 : // no name namespace
344 :
345 :
346 58 : format::format(std::string const & f)
347 : {
348 116 : parser p(f, f_variables);
349 58 : p.parse();
350 58 : }
351 :
352 :
353 46615 : std::string format::process_message(message const & msg, bool ignore_on_no_repeat)
354 : {
355 : return std::accumulate(
356 : f_variables.begin()
357 : , f_variables.end()
358 93230 : , std::string()
359 46878 : , [&msg, &ignore_on_no_repeat](std::string const & r, variable::pointer_t v)
360 93756 : {
361 93756 : if(ignore_on_no_repeat
362 46878 : && v->ignore_on_no_repeat())
363 : {
364 : // do not include this variable to generate the "no-repeat"
365 : // message (i.e. probably a time base message)
366 : //
367 0 : return r;
368 : }
369 93756 : return r + v->get_value(msg);
370 139839 : });
371 : }
372 :
373 :
374 :
375 :
376 :
377 6 : } // snaplogger namespace
378 : // vim: ts=4 sw=4 et
|