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 34 : 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 34 : parser(std::string const & f, variable::vector_t & variables)
73 : : f_input(f)
74 34 : , f_variables(variables)
75 : {
76 34 : }
77 :
78 1859 : int getc()
79 : {
80 1859 : if(f_pos >= f_input.length())
81 : {
82 34 : return EOF;
83 : }
84 :
85 1825 : int const c(f_input[f_pos]);
86 1825 : ++f_pos;
87 1825 : return c;
88 : }
89 :
90 159 : void ungetc(int c)
91 : {
92 159 : if(f_pos == 0)
93 : {
94 0 : throw logger_logic_error("ungetc() called too many times.");
95 : }
96 159 : --f_pos;
97 159 : if(f_input[f_pos] != c)
98 : {
99 0 : throw logger_logic_error("ungetc() called with the wrong character.");
100 : }
101 159 : }
102 :
103 294 : token_t get_token()
104 : {
105 0 : for(;;)
106 : {
107 294 : int c(getc());
108 294 : if(c == EOF)
109 : {
110 0 : return token_t::TOKEN_EOF;
111 : }
112 294 : switch(c)
113 : {
114 : case ' ':
115 : case '\t':
116 : case '\n':
117 : case '\r':
118 : // ignore spaces
119 0 : break;
120 :
121 : case ':':
122 38 : return token_t::TOKEN_COLON;
123 :
124 : case '=':
125 33 : return token_t::TOKEN_EQUAL;
126 :
127 : case '}':
128 76 : return token_t::TOKEN_END;
129 :
130 : case '"':
131 : case '\'':
132 : case '`':
133 : {
134 4 : f_text.clear();
135 4 : int quote(c);
136 7 : for(;;)
137 : {
138 11 : c = getc();
139 11 : if(c == '\\')
140 : {
141 1 : c = getc();
142 : }
143 10 : else if(c == quote)
144 : {
145 4 : break;
146 : }
147 7 : if(c == EOF)
148 : {
149 0 : throw invalid_variable("unterminated string in format variable.");
150 : }
151 7 : f_text += c;
152 : }
153 4 : return token_t::TOKEN_STRING;
154 : }
155 : break;
156 :
157 : 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 15 : f_integer = c - '0';
169 14 : for(;;)
170 : {
171 29 : c = getc();
172 29 : if(c < '0' || c > '9')
173 : {
174 15 : ungetc(c);
175 15 : break;
176 : }
177 14 : f_integer *= 10;
178 14 : f_integer += c - '0';
179 : }
180 15 : return token_t::TOKEN_INTEGER;
181 : }
182 : break;
183 :
184 : default:
185 128 : if((c >= 'a' && c <= 'z')
186 0 : || (c >= 'A' && c <= 'Z')
187 0 : || c == '_')
188 : {
189 128 : f_text.clear();
190 128 : f_text += c;
191 792 : for(;;)
192 : {
193 920 : c = getc();
194 920 : if((c < 'a' || c > 'z')
195 150 : && (c < 'A' || c > 'Z')
196 150 : && (c < '0' || c > '9')
197 150 : && c != '_')
198 : {
199 128 : ungetc(c);
200 128 : break;
201 : }
202 792 : f_text += c;
203 : }
204 128 : 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 0 : << c
213 0 : << "' in format variable.";
214 :
215 0 : throw invalid_variable(ss.str());
216 : }
217 : break;
218 :
219 : }
220 : }
221 : }
222 :
223 76 : void parse_variable()
224 : {
225 76 : token_t tok(get_token());
226 76 : if(tok != token_t::TOKEN_IDENTIFIER)
227 : {
228 0 : throw invalid_variable("expected a token as the variable name.");
229 : }
230 152 : variable::pointer_t var(get_variable(f_text));
231 76 : if(var == nullptr)
232 : {
233 0 : throw invalid_variable("unknown variable \"" + f_text + "\".");
234 : }
235 76 : f_variables.push_back(var);
236 :
237 76 : tok = get_token();
238 38 : for(;;)
239 : {
240 114 : if(tok == token_t::TOKEN_END)
241 : {
242 76 : break;
243 : }
244 38 : if(tok != token_t::TOKEN_COLON)
245 : {
246 0 : throw invalid_variable("variable parameters must be delimited by colon (:) characters.");
247 : }
248 38 : tok = get_token();
249 38 : if(tok != token_t::TOKEN_IDENTIFIER)
250 : {
251 0 : throw invalid_variable("variable parameters must be given a name (an identifier).");
252 : }
253 76 : param::pointer_t p(std::make_shared<param>(f_text));
254 38 : var->add_param(p);
255 :
256 38 : tok = get_token();
257 38 : if(tok == token_t::TOKEN_EQUAL)
258 : {
259 : // the token is followed by a value
260 : //
261 33 : tok = get_token();
262 33 : if(tok == token_t::TOKEN_STRING
263 29 : || tok == token_t::TOKEN_IDENTIFIER)
264 : {
265 18 : p->set_value(f_text);
266 : }
267 15 : else if(tok == token_t::TOKEN_INTEGER)
268 : {
269 15 : p->set_integer(f_integer);
270 : }
271 : else
272 : {
273 0 : throw invalid_variable("unexpected token for a parameter value.");
274 : }
275 :
276 33 : tok = get_token();
277 : }
278 : }
279 76 : }
280 :
281 34 : void parse()
282 : {
283 110 : auto add_text = [this](std::string const & text)
284 75 : {
285 110 : if(!text.empty())
286 : {
287 150 : variable::pointer_t var(get_variable("direct"));
288 75 : if(var == nullptr)
289 : {
290 0 : throw logger_logic_error("variable type \"direct\" not registered?.");
291 : }
292 150 : param::pointer_t p(std::make_shared<param>("msg"));
293 75 : var->add_param(p);
294 75 : p->set_value(text);
295 75 : f_variables.push_back(var);
296 : }
297 144 : };
298 :
299 68 : std::string text;
300 :
301 478 : for(;;)
302 : {
303 512 : int c(getc());
304 512 : if(c == EOF)
305 : {
306 34 : break;
307 : }
308 478 : if(c == '$')
309 : {
310 92 : c = getc();
311 92 : if(c == '{')
312 : {
313 : // we found a variable definition
314 : //
315 76 : add_text(text);
316 76 : text.clear();
317 :
318 76 : parse_variable();
319 : }
320 : else
321 : {
322 16 : text += '$';
323 16 : ungetc(c);
324 : }
325 : }
326 : else
327 : {
328 386 : text += c;
329 : }
330 : }
331 34 : add_text(text);
332 34 : }
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 34 : format::format(std::string const & f)
349 : {
350 68 : parser p(f, f_variables);
351 34 : p.parse();
352 34 : }
353 :
354 :
355 48743 : std::string format::process_message(message const & msg)
356 : {
357 : return std::accumulate(
358 : f_variables.begin()
359 : , f_variables.end()
360 : , std::string()
361 48901 : , [&msg](std::string const & r, variable::pointer_t v)
362 48901 : {
363 97800 : return r + v->get_value(msg);
364 146541 : });
365 : }
366 :
367 :
368 :
369 :
370 :
371 6 : } // snaplogger namespace
372 : // vim: ts=4 sw=4 et
|