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 20 : 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 20 : parser(std::string const & f, variable::vector_t & variables)
73 : : f_input(f)
74 20 : , f_variables(variables)
75 : {
76 20 : }
77 :
78 999 : int getc()
79 : {
80 999 : if(f_pos >= f_input.length())
81 : {
82 20 : return EOF;
83 : }
84 :
85 979 : int const c(f_input[f_pos]);
86 979 : ++f_pos;
87 979 : return c;
88 : }
89 :
90 75 : void ungetc(int c)
91 : {
92 75 : if(f_pos == 0)
93 : {
94 0 : throw logger_logic_error("ungetc() called too many times.");
95 : }
96 75 : --f_pos;
97 75 : if(f_input[f_pos] != c)
98 : {
99 0 : throw logger_logic_error("ungetc() called with the wrong character.");
100 : }
101 75 : }
102 :
103 158 : token_t get_token()
104 : {
105 0 : for(;;)
106 : {
107 158 : int c(getc());
108 158 : if(c == EOF)
109 : {
110 0 : return token_t::TOKEN_EOF;
111 : }
112 158 : switch(c)
113 : {
114 : case ' ':
115 : case '\t':
116 : case '\n':
117 : case '\r':
118 : // ignore spaces
119 0 : break;
120 :
121 : case ':':
122 20 : return token_t::TOKEN_COLON;
123 :
124 : case '=':
125 15 : return token_t::TOKEN_EQUAL;
126 :
127 : case '}':
128 44 : 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 7 : f_integer = c - '0';
169 8 : for(;;)
170 : {
171 15 : c = getc();
172 15 : if(c < '0' || c > '9')
173 : {
174 7 : ungetc(c);
175 7 : break;
176 : }
177 8 : f_integer *= 10;
178 8 : f_integer += c - '0';
179 : }
180 7 : return token_t::TOKEN_INTEGER;
181 : }
182 : break;
183 :
184 : default:
185 68 : if((c >= 'a' && c <= 'z')
186 0 : || (c >= 'A' && c <= 'Z')
187 0 : || c == '_')
188 : {
189 68 : f_text.clear();
190 68 : f_text += c;
191 391 : for(;;)
192 : {
193 459 : c = getc();
194 459 : if((c < 'a' || c > 'z')
195 78 : && (c < 'A' || c > 'Z')
196 78 : && (c < '0' || c > '9')
197 78 : && c != '_')
198 : {
199 68 : ungetc(c);
200 68 : break;
201 : }
202 391 : f_text += c;
203 : }
204 68 : return token_t::TOKEN_IDENTIFIER;
205 : }
206 : else
207 : {
208 0 : throw invalid_variable("unexpected character in format variable.");
209 : }
210 : break;
211 :
212 : }
213 : }
214 : }
215 :
216 44 : void parse_variable()
217 : {
218 44 : token_t tok(get_token());
219 44 : if(tok != token_t::TOKEN_IDENTIFIER)
220 : {
221 0 : throw invalid_variable("expected a token as the variable name.");
222 : }
223 88 : variable::pointer_t var(get_variable(f_text));
224 44 : if(var == nullptr)
225 : {
226 0 : throw invalid_variable("unknown variable \"" + f_text + "\".");
227 : }
228 44 : f_variables.push_back(var);
229 :
230 44 : tok = get_token();
231 20 : for(;;)
232 : {
233 64 : if(tok == token_t::TOKEN_END)
234 : {
235 44 : break;
236 : }
237 20 : if(tok != token_t::TOKEN_COLON)
238 : {
239 0 : throw invalid_variable("variable parameters must be delimited by colon (:) characters.");
240 : }
241 20 : tok = get_token();
242 20 : if(tok != token_t::TOKEN_IDENTIFIER)
243 : {
244 0 : throw invalid_variable("variable parameters must be given a name (an identifier).");
245 : }
246 40 : param::pointer_t p(std::make_shared<param>(f_text));
247 20 : var->add_param(p);
248 :
249 20 : tok = get_token();
250 20 : if(tok == token_t::TOKEN_EQUAL)
251 : {
252 : // the token is followed by a value
253 : //
254 15 : tok = get_token();
255 15 : if(tok == token_t::TOKEN_STRING
256 11 : || tok == token_t::TOKEN_IDENTIFIER)
257 : {
258 8 : p->set_value(f_text);
259 : }
260 7 : else if(tok == token_t::TOKEN_INTEGER)
261 : {
262 7 : p->set_integer(f_integer);
263 : }
264 : else
265 : {
266 0 : throw invalid_variable("unexpected token for a parameter value.");
267 : }
268 :
269 15 : tok = get_token();
270 : }
271 : }
272 44 : }
273 :
274 20 : void parse()
275 : {
276 64 : auto add_text = [this](std::string const & text)
277 33 : {
278 64 : if(!text.empty())
279 : {
280 66 : variable::pointer_t var(get_variable("direct"));
281 33 : if(var == nullptr)
282 : {
283 0 : throw logger_logic_error("variable type \"direct\" not registered?.");
284 : }
285 66 : param::pointer_t p(std::make_shared<param>("msg"));
286 33 : var->add_param(p);
287 33 : p->set_value(text);
288 33 : f_variables.push_back(var);
289 : }
290 84 : };
291 :
292 40 : std::string text;
293 :
294 291 : for(;;)
295 : {
296 311 : int c(getc());
297 311 : if(c == EOF)
298 : {
299 20 : break;
300 : }
301 291 : if(c == '$')
302 : {
303 44 : c = getc();
304 44 : if(c == '{')
305 : {
306 : // we found a variable definition
307 : //
308 44 : add_text(text);
309 44 : text.clear();
310 :
311 44 : parse_variable();
312 : }
313 : else
314 : {
315 0 : text += '$';
316 0 : text += c;
317 : }
318 : }
319 : else
320 : {
321 247 : text += c;
322 : }
323 : }
324 20 : add_text(text);
325 20 : }
326 :
327 : private:
328 : std::string const f_input;
329 : size_t f_pos = 0;
330 : variable::vector_t & f_variables;
331 : std::string f_text = std::string();
332 : std::int64_t f_integer = 0;
333 : };
334 :
335 :
336 :
337 : }
338 : // no name namespace
339 :
340 :
341 20 : format::format(std::string const & f)
342 : {
343 40 : parser p(f, f_variables);
344 20 : p.parse();
345 20 : }
346 :
347 :
348 45551 : std::string format::process_message(message const & msg)
349 : {
350 : return std::accumulate(
351 : f_variables.begin()
352 : , f_variables.end()
353 : , std::string()
354 45587 : , [&msg](std::string const & r, variable::pointer_t v)
355 45587 : {
356 91172 : return r + v->get_value(msg);
357 136721 : });
358 : }
359 :
360 :
361 :
362 :
363 :
364 6 : } // snaplogger namespace
365 : // vim: ts=4 sw=4 et
|