Line data Source code
1 : // Copyright (c) 2013-2021 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 3290 : int getc()
77 : {
78 3290 : if(f_pos >= f_input.length())
79 : {
80 58 : return EOF;
81 : }
82 :
83 3232 : int const c(f_input[f_pos]);
84 3232 : ++f_pos;
85 3232 : return c;
86 : }
87 :
88 293 : void ungetc(int c)
89 : {
90 293 : if(f_pos == 0)
91 : {
92 : throw logger_logic_error("ungetc() called too many times."); // LCOV_EXCL_LINE
93 : }
94 293 : --f_pos;
95 293 : if(f_input[f_pos] != c)
96 : {
97 : throw logger_logic_error("ungetc() called with the wrong character."); // LCOV_EXCL_LINE
98 : }
99 293 : }
100 :
101 600 : token_t get_token()
102 : {
103 : for(;;)
104 : {
105 600 : int c(getc());
106 600 : if(c == EOF)
107 : {
108 0 : return token_t::TOKEN_EOF;
109 : }
110 600 : 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 119 : case '}':
126 119 : 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 30 : 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 30 : f_integer = c - '0';
167 : for(;;)
168 : {
169 88 : c = getc();
170 59 : if(c < '0' || c > '9')
171 : {
172 30 : ungetc(c);
173 30 : break;
174 : }
175 29 : f_integer *= 10;
176 29 : f_integer += c - '0';
177 : }
178 30 : return token_t::TOKEN_INTEGER;
179 : }
180 : break;
181 :
182 247 : default:
183 247 : if((c >= 'a' && c <= 'z')
184 0 : || (c >= 'A' && c <= 'Z')
185 0 : || c == '_')
186 : {
187 247 : f_text.clear();
188 247 : f_text += c;
189 : for(;;)
190 : {
191 3227 : c = getc();
192 1737 : if((c < 'a' || c > 'z')
193 285 : && (c < 'A' || c > 'Z')
194 285 : && (c < '0' || c > '9')
195 285 : && c != '_')
196 : {
197 247 : ungetc(c);
198 247 : break;
199 : }
200 1490 : f_text += c;
201 : }
202 247 : 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 119 : void parse_variable()
222 : {
223 119 : token_t tok(get_token());
224 119 : if(tok != token_t::TOKEN_IDENTIFIER)
225 : {
226 0 : throw invalid_variable("expected a token as the variable name.");
227 : }
228 238 : variable::pointer_t var(get_variable(f_text));
229 119 : if(var == nullptr)
230 : {
231 0 : throw invalid_variable("unknown variable \"" + f_text + "\".");
232 : }
233 119 : f_variables.push_back(var);
234 :
235 119 : tok = get_token();
236 : for(;;)
237 : {
238 214 : if(tok == token_t::TOKEN_END)
239 : {
240 119 : 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 56 : p->set_value(f_text);
264 : }
265 30 : else if(tok == token_t::TOKEN_INTEGER)
266 : {
267 30 : 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 119 : }
278 :
279 58 : void parse()
280 : {
281 177 : auto add_text = [this](std::string const & text)
282 98 : {
283 177 : if(!text.empty())
284 : {
285 196 : variable::pointer_t var(get_variable("direct"));
286 98 : if(var == nullptr)
287 : {
288 0 : throw logger_logic_error("variable type \"direct\" not registered?");
289 : }
290 196 : param::pointer_t p(std::make_shared<param>("msg"));
291 98 : var->add_param(p);
292 98 : p->set_value(text);
293 98 : f_variables.push_back(var);
294 : }
295 235 : };
296 :
297 116 : std::string text;
298 :
299 : for(;;)
300 : {
301 689 : int c(getc());
302 689 : if(c == EOF)
303 : {
304 58 : break;
305 : }
306 631 : if(c == '$')
307 : {
308 135 : c = getc();
309 135 : if(c == '{')
310 : {
311 : // we found a variable definition
312 : //
313 119 : add_text(text);
314 119 : text.clear();
315 :
316 119 : parse_variable();
317 : }
318 : else
319 : {
320 16 : text += '$';
321 16 : ungetc(c);
322 : }
323 : }
324 : else
325 : {
326 496 : text += c;
327 : }
328 631 : }
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 45389 : 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 90778 : , std::string()
359 45591 : , [&msg, &ignore_on_no_repeat](std::string const & r, variable::pointer_t v)
360 91182 : {
361 91182 : if(ignore_on_no_repeat
362 45591 : && 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 91182 : return r + v->get_value(msg);
370 136162 : });
371 : }
372 :
373 :
374 :
375 :
376 :
377 6 : } // snaplogger namespace
378 : // vim: ts=4 sw=4 et
|