Line data Source code
1 : /*
2 : * Copyright (c) 2013-2019 Made to Order Software Corp. All Rights Reserved
3 : *
4 : * https://snapwebsites.org/project/snaplogger
5 : * contact@m2osw.com
6 : *
7 : * This program is free software; you can redistribute it and/or modify
8 : * it under the terms of the GNU General Public License as published by
9 : * the Free Software Foundation; either version 2 of the License, or
10 : * (at your option) any later version.
11 : *
12 : * This program is distributed in the hope that it will be useful,
13 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 : * GNU General Public License for more details.
16 : *
17 : * You should have received a copy of the GNU General Public License along
18 : * with this program; if not, write to the Free Software Foundation, Inc.,
19 : * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 : */
21 :
22 : /** \file
23 : * \brief Format a message.
24 : *
25 : * This file declares the format class used to transform a message with
26 : * the administrator defined format.
27 : */
28 :
29 :
30 : // self
31 : //
32 : #include "snaplogger/format.h"
33 :
34 : #include "snaplogger/exception.h"
35 :
36 :
37 : // C++ lib
38 : //
39 : #include <numeric>
40 : #include <iostream>
41 :
42 :
43 : // last include
44 : //
45 : #include <snapdev/poison.h>
46 :
47 :
48 :
49 : namespace snaplogger
50 : {
51 :
52 :
53 : namespace
54 : {
55 :
56 :
57 :
58 56 : class parser
59 : {
60 : public:
61 : enum class token_t
62 : {
63 : TOKEN_EOF,
64 : TOKEN_COLON,
65 : TOKEN_EQUAL,
66 : TOKEN_STRING,
67 : TOKEN_INTEGER,
68 : TOKEN_IDENTIFIER,
69 : TOKEN_END
70 : };
71 :
72 56 : parser(std::string const & f, variable::vector_t & variables)
73 56 : : f_input(f)
74 56 : , f_variables(variables)
75 : {
76 56 : }
77 :
78 3164 : int getc()
79 : {
80 3164 : if(f_pos >= f_input.length())
81 : {
82 56 : return EOF;
83 : }
84 :
85 3108 : int const c(f_input[f_pos]);
86 3108 : ++f_pos;
87 3108 : return c;
88 : }
89 :
90 289 : void ungetc(int c)
91 : {
92 289 : if(f_pos == 0)
93 : {
94 : throw logger_logic_error("ungetc() called too many times."); // LCOV_EXCL_LINE
95 : }
96 289 : --f_pos;
97 289 : if(f_input[f_pos] != c)
98 : {
99 : throw logger_logic_error("ungetc() called with the wrong character."); // LCOV_EXCL_LINE
100 : }
101 289 : }
102 :
103 592 : token_t get_token()
104 : {
105 : for(;;)
106 : {
107 592 : int c(getc());
108 592 : if(c == EOF)
109 : {
110 0 : return token_t::TOKEN_EOF;
111 : }
112 592 : switch(c)
113 : {
114 0 : case ' ':
115 : case '\t':
116 : case '\n':
117 : case '\r':
118 : // ignore spaces
119 0 : break;
120 :
121 94 : case ':':
122 94 : return token_t::TOKEN_COLON;
123 :
124 85 : case '=':
125 85 : return token_t::TOKEN_EQUAL;
126 :
127 117 : case '}':
128 117 : return token_t::TOKEN_END;
129 :
130 23 : case '"':
131 : case '\'':
132 : case '`':
133 : {
134 23 : f_text.clear();
135 23 : int quote(c);
136 : for(;;)
137 : {
138 115 : c = getc();
139 69 : if(c == '\\')
140 : {
141 1 : c = getc();
142 : }
143 68 : else if(c == quote)
144 : {
145 23 : break;
146 : }
147 46 : if(c == EOF)
148 : {
149 0 : throw invalid_variable("unterminated string in format variable.");
150 : }
151 46 : f_text += c;
152 : }
153 23 : return token_t::TOKEN_STRING;
154 : }
155 : break;
156 :
157 30 : case '0':
158 : case '1':
159 : case '2':
160 : case '3':
161 : case '4':
162 : case '5':
163 : case '6':
164 : case '7':
165 : case '8':
166 : case '9':
167 : {
168 30 : f_integer = c - '0';
169 : for(;;)
170 : {
171 88 : c = getc();
172 59 : if(c < '0' || c > '9')
173 : {
174 30 : ungetc(c);
175 30 : break;
176 : }
177 29 : f_integer *= 10;
178 29 : f_integer += c - '0';
179 : }
180 30 : return token_t::TOKEN_INTEGER;
181 : }
182 : break;
183 :
184 243 : default:
185 243 : if((c >= 'a' && c <= 'z')
186 0 : || (c >= 'A' && c <= 'Z')
187 0 : || c == '_')
188 : {
189 243 : f_text.clear();
190 243 : f_text += c;
191 : for(;;)
192 : {
193 3189 : c = getc();
194 1716 : if((c < 'a' || c > 'z')
195 281 : && (c < 'A' || c > 'Z')
196 281 : && (c < '0' || c > '9')
197 281 : && c != '_')
198 : {
199 243 : ungetc(c);
200 243 : break;
201 : }
202 1473 : f_text += c;
203 : }
204 243 : return token_t::TOKEN_IDENTIFIER;
205 : }
206 : else
207 : {
208 0 : std::stringstream ss;
209 :
210 0 : ss << "unexpected character '\\x"
211 0 : << std::hex
212 : << c
213 0 : << "' in format variable.";
214 :
215 0 : throw invalid_variable(ss.str());
216 : }
217 : break;
218 :
219 : }
220 0 : }
221 : }
222 :
223 117 : void parse_variable()
224 : {
225 117 : token_t tok(get_token());
226 117 : if(tok != token_t::TOKEN_IDENTIFIER)
227 : {
228 0 : throw invalid_variable("expected a token as the variable name.");
229 : }
230 234 : variable::pointer_t var(get_variable(f_text));
231 117 : if(var == nullptr)
232 : {
233 0 : throw invalid_variable("unknown variable \"" + f_text + "\".");
234 : }
235 117 : f_variables.push_back(var);
236 :
237 117 : tok = get_token();
238 : for(;;)
239 : {
240 211 : if(tok == token_t::TOKEN_END)
241 : {
242 117 : break;
243 : }
244 94 : if(tok != token_t::TOKEN_COLON)
245 : {
246 0 : throw invalid_variable("variable parameters must be delimited by colon (:) characters.");
247 : }
248 94 : tok = get_token();
249 94 : if(tok != token_t::TOKEN_IDENTIFIER)
250 : {
251 0 : throw invalid_variable("variable parameters must be given a name (an identifier).");
252 : }
253 188 : param::pointer_t p(std::make_shared<param>(f_text));
254 94 : var->add_param(p);
255 :
256 94 : tok = get_token();
257 94 : if(tok == token_t::TOKEN_EQUAL)
258 : {
259 : // the token is followed by a value
260 : //
261 85 : tok = get_token();
262 85 : if(tok == token_t::TOKEN_STRING
263 62 : || tok == token_t::TOKEN_IDENTIFIER)
264 : {
265 55 : p->set_value(f_text);
266 : }
267 30 : else if(tok == token_t::TOKEN_INTEGER)
268 : {
269 30 : p->set_integer(f_integer);
270 : }
271 : else
272 : {
273 0 : throw invalid_variable("unexpected token for a parameter value.");
274 : }
275 :
276 85 : tok = get_token();
277 : }
278 94 : }
279 117 : }
280 :
281 56 : void parse()
282 : {
283 173 : auto add_text = [this](std::string const & text)
284 94 : {
285 173 : if(!text.empty())
286 : {
287 188 : variable::pointer_t var(get_variable("direct"));
288 94 : if(var == nullptr)
289 : {
290 0 : throw logger_logic_error("variable type \"direct\" not registered?.");
291 : }
292 188 : param::pointer_t p(std::make_shared<param>("msg"));
293 94 : var->add_param(p);
294 94 : p->set_value(text);
295 94 : f_variables.push_back(var);
296 : }
297 229 : };
298 :
299 112 : std::string text;
300 :
301 : for(;;)
302 : {
303 594 : int c(getc());
304 594 : if(c == EOF)
305 : {
306 56 : break;
307 : }
308 538 : if(c == '$')
309 : {
310 133 : c = getc();
311 133 : if(c == '{')
312 : {
313 : // we found a variable definition
314 : //
315 117 : add_text(text);
316 117 : text.clear();
317 :
318 117 : parse_variable();
319 : }
320 : else
321 : {
322 16 : text += '$';
323 16 : ungetc(c);
324 : }
325 : }
326 : else
327 : {
328 405 : text += c;
329 : }
330 538 : }
331 56 : add_text(text);
332 56 : }
333 :
334 : private:
335 : std::string const f_input;
336 : size_t f_pos = 0;
337 : variable::vector_t & f_variables;
338 : std::string f_text = std::string();
339 : std::int64_t f_integer = 0;
340 : };
341 :
342 :
343 :
344 : }
345 : // no name namespace
346 :
347 :
348 56 : format::format(std::string const & f)
349 : {
350 112 : parser p(f, f_variables);
351 56 : p.parse();
352 56 : }
353 :
354 :
355 48170 : std::string format::process_message(message const & msg)
356 : {
357 : return std::accumulate(
358 : f_variables.begin()
359 : , f_variables.end()
360 96340 : , std::string()
361 48368 : , [&msg](std::string const & r, variable::pointer_t v)
362 48368 : {
363 145099 : return r + v->get_value(msg);
364 241231 : });
365 : }
366 :
367 :
368 :
369 :
370 :
371 6 : } // snaplogger namespace
372 : // vim: ts=4 sw=4 et
|