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