Remove dependency to CPP: support for #include directive
[wmaker-crm.git] / WINGs / menuparser.c
blobe021215444c8854debc680462b5e4512d368ffb6
1 /*
2 * Window Maker window manager
4 * Copyright (c) 2012 Christophe Curis
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.
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.
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.
20 #include "wconfig.h"
22 #include <stdio.h>
23 #include <string.h>
24 #include <ctype.h>
25 #include <stdarg.h>
27 #include <WINGs/WUtil.h>
29 #include "menuparser.h"
31 static WMenuParser menu_parser_create_new(const char *file_name, void *file,
32 const char *include_default_paths);
33 static char *menu_parser_isolate_token(WMenuParser parser);
34 static void menu_parser_get_directive(WMenuParser parser);
35 static Bool menu_parser_include_file(WMenuParser parser);
38 /***** Constructor and Destructor for the Menu Parser object *****/
39 WMenuParser WMenuParserCreate(const char *file_name, void *file,
40 const char *include_default_paths)
42 WMenuParser parser;
44 parser = menu_parser_create_new(file_name, file, include_default_paths);
45 return parser;
48 void WMenuParserDelete(WMenuParser parser)
50 if (parser->include_file) {
51 /* Trick: the top parser's data are not wmalloc'd, we point on the
52 provided data so we do not wfree it; however for include files
53 we did wmalloc them.
54 This code should not be used as the wfree is done when we reach
55 the end of an include file; however this may not happen when an
56 early exit occurs (typically when 'readMenuFile' does not find
57 its expected command). */
58 fclose(parser->include_file->file_handle);
59 wfree((char *) parser->include_file->file_name);
60 WMenuParserDelete(parser->include_file);
62 wfree(parser);
65 static WMenuParser menu_parser_create_new(const char *file_name, void *file,
66 const char *include_default_paths)
68 WMenuParser parser;
70 parser = wmalloc(sizeof(*parser));
71 parser->include_default_paths = include_default_paths;
72 parser->file_name = file_name;
73 parser->file_handle = file;
74 parser->rd = parser->line_buffer;
76 return parser;
79 /***** To report helpfull messages to user *****/
80 const char *WMenuParserGetFilename(WMenuParser parser)
82 return parser->file_name;
85 void WMenuParserError(WMenuParser parser, const char *msg, ...)
87 char buf[MAXLINE];
88 va_list args;
89 WMenuParser parent;
91 while (parser->include_file)
92 parser = parser->include_file;
94 va_start(args, msg);
95 vsnprintf(buf, sizeof(buf), msg, args);
96 va_end(args);
97 __wmessage("WMenuParser", parser->file_name, parser->line_number, WMESSAGE_TYPE_WARNING, buf);
99 for (parent = parser->parent_file; parent != NULL; parent = parent->parent_file)
100 __wmessage("WMenuParser", parser->file_name, parser->line_number, WMESSAGE_TYPE_WARNING,
101 _(" included from file \"%s\" at line %d"),
102 parent->file_name, parent->line_number);
105 /***** Read one line from file and split content *****/
106 /* The function returns False when the end of file is reached */
107 Bool WMenuParserGetLine(WMenuParser top_parser, char **title, char **command, char **parameter, char **shortcut)
109 WMenuParser cur_parser;
110 enum { GET_TITLE, GET_COMMAND, GET_PARAMETERS, GET_SHORTCUT } scanmode;
111 char *token;
112 char lineparam[MAXLINE];
113 char *params = NULL;
115 lineparam[0] = '\0';
116 *title = NULL;
117 *command = NULL;
118 *parameter = NULL;
119 *shortcut = NULL;
120 scanmode = GET_TITLE;
122 read_next_line_with_filechange:
123 cur_parser = top_parser;
124 while (cur_parser->include_file)
125 cur_parser = cur_parser->include_file;
127 read_next_line:
128 if (fgets(cur_parser->line_buffer, sizeof(cur_parser->line_buffer), cur_parser->file_handle) == NULL) {
129 if (cur_parser->parent_file == NULL)
130 /* Not inside an included file -> we have reached the end */
131 return False;
133 /* We have only reached the end of an included file -> go back to calling file */
134 fclose(cur_parser->file_handle);
135 wfree((char *) cur_parser->file_name);
136 cur_parser = cur_parser->parent_file;
137 wfree(cur_parser->include_file);
138 cur_parser->include_file = NULL;
139 goto read_next_line_with_filechange;
141 cur_parser->line_number++;
142 cur_parser->rd = cur_parser->line_buffer;
144 for (;;) {
145 if (!menu_parser_skip_spaces_and_comments(cur_parser)) {
146 /* We reached the end of line */
147 if (scanmode == GET_TITLE)
148 goto read_next_line; // Empty line -> skip
149 else
150 break; // Finished reading current line -> return it to caller
152 if ((scanmode == GET_TITLE) && (*cur_parser->rd == '#')) {
153 cur_parser->rd++;
154 menu_parser_get_directive(cur_parser);
155 goto read_next_line_with_filechange;
158 /* Found a word */
159 token = menu_parser_isolate_token(cur_parser);
160 switch (scanmode) {
161 case GET_TITLE:
162 *title = token;
163 scanmode = GET_COMMAND;
164 break;
166 case GET_COMMAND:
167 if (strcmp(token, "SHORTCUT") == 0) {
168 scanmode = GET_SHORTCUT;
169 wfree(token);
170 } else {
171 *command = token;
172 scanmode = GET_PARAMETERS;
174 break;
176 case GET_SHORTCUT:
177 if (*shortcut != NULL) {
178 WMenuParserError(top_parser, _("multiple SHORTCUT definition not valid") );
179 wfree(*shortcut);
181 *shortcut = token;
182 scanmode = GET_COMMAND;
183 break;
185 case GET_PARAMETERS:
187 char *src;
189 if (params == NULL) {
190 params = lineparam;
191 } else {
192 if ((params - lineparam) < sizeof(lineparam)-1)
193 *params++ = ' ';
195 src = token;
196 while ((params - lineparam) < sizeof(lineparam)-1)
197 if ( (*params = *src++) == '\0')
198 break;
199 else
200 params++;
201 wfree(token);
203 break;
207 if (params != NULL) {
208 lineparam[sizeof(lineparam) - 1] = '\0';
209 *parameter = wstrdup(lineparam);
212 return True;
215 /* Return False when there's nothing left on the line,
216 otherwise increment parser's pointer to next token */
217 Bool menu_parser_skip_spaces_and_comments(WMenuParser parser)
219 for (;;) {
220 while (isspace(*parser->rd))
221 parser->rd++;
223 if (*parser->rd == '\0')
224 return False; // Found the end of current line
226 else if ((parser->rd[0] == '\\') &&
227 (parser->rd[1] == '\n') &&
228 (parser->rd[2] == '\0')) {
229 // Means that the current line is expected to be continued on next line
230 if (fgets(parser->line_buffer, sizeof(parser->line_buffer), parser->file_handle) == NULL) {
231 WMenuParserError(parser, _("premature end of file while expecting a new line after '\\'") );
232 return False;
234 parser->line_number++;
235 parser->rd = parser->line_buffer;
237 } else if (parser->rd[0] == '/') {
238 if (parser->rd[1] == '/') // Single line C comment
239 return False; // Won't find anything more on this line
240 if (parser->rd[1] == '*') {
241 int start_line;
243 start_line = parser->line_number;
244 parser->rd += 2;
245 for (;;) {
246 /* Search end-of-comment marker */
247 while (*parser->rd != '\0') {
248 if (parser->rd[0] == '*')
249 if (parser->rd[1] == '/')
250 goto found_end_of_comment;
251 parser->rd++;
254 /* Marker not found -> load next line */
255 if (fgets(parser->line_buffer, sizeof(parser->line_buffer), parser->file_handle) == NULL) {
256 WMenuParserError(parser, _("reached end of file while searching '*/' for comment started at line %d"), start_line);
257 return False;
259 parser->line_number++;
260 parser->rd = parser->line_buffer;
263 found_end_of_comment:
264 parser->rd += 2; // Skip closing mark
265 continue; // Because there may be spaces after the comment
267 return True; // the '/' was not a comment, treat it as user data
268 } else
269 return True; // Found some data
273 /* read a token (non-spaces suite of characters)
274 the result os wmalloc's, so it needs to be free'd */
275 static char *menu_parser_isolate_token(WMenuParser parser)
277 char *start;
278 char *token;
280 start = parser->rd;
282 while (*parser->rd != '\0')
283 if (isspace(*parser->rd))
284 break;
285 else if ((parser->rd[0] == '/') &&
286 ((parser->rd[1] == '*') || (parser->rd[1] == '/')))
287 break;
288 else if ((parser->rd[0] == '\\') && (parser->rd[1] == '\n'))
289 break;
290 else if ((*parser->rd == '"' ) || (*parser->rd == '\'')) {
291 char eot = *parser->rd++;
292 while ((*parser->rd != '\0') && (*parser->rd != '\n'))
293 if (*parser->rd++ == eot)
294 goto found_end_quote;
295 WMenuParserError(parser, _("missing closing quote or double-quote before end-of-line") );
296 found_end_quote:
298 } else
299 parser->rd++;
301 token = wmalloc(parser->rd - start + 1);
302 strncpy(token, start, parser->rd - start);
303 token[parser->rd - start] = '\0';
305 return token;
308 /***** Processing of special # directives *****/
309 static void menu_parser_get_directive(WMenuParser parser)
311 char *command;
313 /* Isolate the command */
314 while (isspace(*parser->rd))
315 parser->rd++;
316 command = parser->rd;
317 while (*parser->rd)
318 if (isspace(*parser->rd)) {
319 *parser->rd++ = '\0';
320 break;
321 } else parser->rd++;
323 if (strcmp(command, "include") == 0) {
324 if (!menu_parser_include_file(parser)) return;
326 } else {
327 WMenuParserError(parser, _("unknow directive '#%s'"), command);
328 return;
331 if (menu_parser_skip_spaces_and_comments(parser))
332 WMenuParserError(parser, _("extra text after '#' command is ignored: \"%.16s...\""),
333 parser->rd);
336 /* Extract the file name, search for it in known directories
337 and create a sub-parser to handle it.
338 Returns False if the file could not be found */
339 static Bool menu_parser_include_file(WMenuParser parser)
341 char buffer[MAXLINE];
342 char *req_filename, *fullfilename, *p;
343 char eot;
344 FILE *fh;
346 if (!menu_parser_skip_spaces_and_comments(parser)) {
347 WMenuParserError(parser, _("no file name found for #include") );
348 return False;
350 switch (*parser->rd++) {
351 case '<': eot = '>'; break;
352 case '"': eot = '"'; break;
353 default:
354 WMenuParserError(parser, _("file name must be enclosed in brackets or double-quotes for #define") );
355 return False;
357 req_filename = parser->rd;
358 while (*parser->rd)
359 if (*parser->rd == eot) {
360 *parser->rd++ = '\0';
361 goto found_end_define_fname;
362 } else parser->rd++;
363 WMenuParserError(parser, _("missing closing '%c' in filename specification"), eot);
364 return False;
365 found_end_define_fname:
367 { /* Check we are not nesting too many includes */
368 WMenuParser p;
369 int count;
371 count = 0;
372 for (p = parser; p->parent_file; p = p->parent_file)
373 count++;
374 if (count > MAX_NESTED_INCLUDES) {
375 WMenuParserError(parser, _("too many nested includes") );
376 return False;
380 /* Absolute paths */
381 fullfilename = req_filename;
382 if (req_filename[0] != '/') {
383 /* Search first in the same directory as the current file */
384 p = strrchr(parser->file_name, '/');
385 if (p != NULL) {
386 int len;
388 len = p - parser->file_name + 1;
389 if (len > sizeof(buffer) - 1) len = sizeof(buffer) - 1;
390 strncpy(buffer, parser->file_name, len);
391 strncpy(buffer+len, req_filename, sizeof(buffer) - len - 1);
392 buffer[sizeof(buffer) - 1] = '\0';
393 fullfilename = buffer;
396 fh = fopen(fullfilename, "rb");
398 /* Not found? Search in wmaker's known places */
399 if (fh == NULL) {
400 if (req_filename[0] != '/') {
401 const char *src;
403 fullfilename = buffer;
404 src = parser->include_default_paths;
405 while (*src != '\0') {
406 p = buffer;
407 if (*src == '~') {
408 char *home = wgethomedir();
409 while (*home != '\0')
410 *p++ = *home++;
411 src++;
413 while ((*src != '\0') && (*src != ':'))
414 *p++ = *src++;
415 *p++ = '/';
416 strncpy(p, req_filename, sizeof(buffer) - (p - buffer - 1));
417 buffer[sizeof(buffer) - 1] = '\0';
419 fh = fopen(fullfilename, "rb");
420 if (fh != NULL) goto found_valid_file;
423 WMenuParserError(parser, _("could not find file \"%s\" for include"), req_filename);
424 return False;
427 /* Found the file, make it our new source */
428 found_valid_file:
429 parser->include_file = menu_parser_create_new(wstrdup(req_filename), fh, parser->include_default_paths);
430 parser->include_file->parent_file = parser;
431 return True;