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 60 : 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 60 : parser(std::string const & f, variable::vector_t & variables)
71 60 : : f_input(f)
72 60 : , f_variables(variables)
73 : {
74 60 : }
75 :
76 3450 : int getc()
77 : {
78 3450 : if(f_pos >= f_input.length())
79 : {
80 60 : return EOF;
81 : }
82 :
83 3390 : int const c(f_input[f_pos]);
84 3390 : ++f_pos;
85 3390 : return c;
86 : }
87 :
88 309 : void ungetc(int c)
89 : {
90 309 : if(f_pos == 0)
91 : {
92 : throw logger_logic_error("ungetc() called too many times."); // LCOV_EXCL_LINE
93 : }
94 309 : --f_pos;
95 309 : if(f_input[f_pos] != c)
96 : {
97 : throw logger_logic_error("ungetc() called with the wrong character."); // LCOV_EXCL_LINE
98 : }
99 309 : }
100 :
101 632 : token_t get_token()
102 : {
103 : for(;;)
104 : {
105 632 : int c(getc());
106 632 : if(c == EOF)
107 : {
108 0 : return token_t::TOKEN_EOF;
109 : }
110 632 : switch(c)
111 : {
112 0 : case ' ':
113 : case '\t':
114 : case '\n':
115 : case '\r':
116 : // ignore spaces
117 0 : break;
118 :
119 100 : case ':':
120 100 : return token_t::TOKEN_COLON;
121 :
122 91 : case '=':
123 91 : return token_t::TOKEN_EQUAL;
124 :
125 125 : case '}':
126 125 : 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 263 : default:
183 263 : if((c >= 'a' && c <= 'z')
184 0 : || (c >= 'A' && c <= 'Z')
185 0 : || c == '_')
186 : {
187 263 : f_text.clear();
188 263 : f_text += c;
189 : for(;;)
190 : {
191 3423 : c = getc();
192 1843 : if((c < 'a' || c > 'z')
193 301 : && (c < 'A' || c > 'Z')
194 301 : && (c < '0' || c > '9')
195 301 : && c != '_')
196 : {
197 263 : ungetc(c);
198 263 : break;
199 : }
200 1580 : f_text += c;
201 : }
202 263 : 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 125 : void parse_variable()
222 : {
223 125 : token_t tok(get_token());
224 125 : if(tok != token_t::TOKEN_IDENTIFIER)
225 : {
226 0 : throw invalid_variable("expected a token as the variable name.");
227 : }
228 250 : variable::pointer_t var(get_variable(f_text));
229 125 : if(var == nullptr)
230 : {
231 0 : throw invalid_variable("unknown variable \"" + f_text + "\".");
232 : }
233 125 : f_variables.push_back(var);
234 :
235 125 : tok = get_token();
236 : for(;;)
237 : {
238 225 : if(tok == token_t::TOKEN_END)
239 : {
240 125 : break;
241 : }
242 100 : if(tok != token_t::TOKEN_COLON)
243 : {
244 0 : throw invalid_variable("variable parameters must be delimited by colon (:) characters.");
245 : }
246 100 : tok = get_token();
247 100 : if(tok != token_t::TOKEN_IDENTIFIER)
248 : {
249 0 : throw invalid_variable("variable parameters must be given a name (an identifier).");
250 : }
251 200 : param::pointer_t p(std::make_shared<param>(f_text));
252 100 : var->add_param(p);
253 :
254 100 : tok = get_token();
255 100 : if(tok == token_t::TOKEN_EQUAL)
256 : {
257 : // the token is followed by a value
258 : //
259 91 : tok = get_token();
260 91 : if(tok == token_t::TOKEN_STRING
261 68 : || tok == token_t::TOKEN_IDENTIFIER)
262 : {
263 61 : 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 91 : tok = get_token();
275 : }
276 100 : }
277 125 : }
278 :
279 60 : void parse()
280 : {
281 245 : auto add_text = [this](std::string const & text)
282 104 : {
283 185 : if(!text.empty())
284 : {
285 208 : variable::pointer_t var(get_variable("direct"));
286 104 : if(var == nullptr)
287 : {
288 0 : throw logger_logic_error("variable type \"direct\" not registered?");
289 : }
290 208 : param::pointer_t p(std::make_shared<param>("msg"));
291 104 : var->add_param(p);
292 104 : p->set_value(text);
293 104 : f_variables.push_back(var);
294 : }
295 245 : };
296 :
297 120 : std::string text;
298 :
299 : for(;;)
300 : {
301 705 : int c(getc());
302 705 : if(c == EOF)
303 : {
304 60 : break;
305 : }
306 645 : if(c == '$')
307 : {
308 141 : c = getc();
309 141 : if(c == '{')
310 : {
311 : // we found a variable definition
312 : //
313 125 : add_text(text);
314 125 : text.clear();
315 :
316 125 : parse_variable();
317 : }
318 : else
319 : {
320 16 : text += '$';
321 16 : ungetc(c);
322 : }
323 : }
324 : else
325 : {
326 504 : text += c;
327 : }
328 645 : }
329 60 : add_text(text);
330 60 : }
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 60 : format::format(std::string const & f)
347 : {
348 120 : parser p(f, f_variables);
349 60 : p.parse();
350 60 : }
351 :
352 :
353 48903 : 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 97806 : , std::string()
359 49186 : , [&msg, &ignore_on_no_repeat](std::string const & r, variable::pointer_t v)
360 98372 : {
361 98372 : if(ignore_on_no_repeat
362 49186 : && 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 98372 : return r + v->get_value(msg);
370 146703 : });
371 : }
372 :
373 :
374 :
375 :
376 :
377 6 : } // snaplogger namespace
378 : // vim: ts=4 sw=4 et
|