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.
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
, WParserMacro
*list_macros
);
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
)
44 parser
= menu_parser_create_new(file_name
, file
, include_default_paths
);
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
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
);
64 menu_parser_free_macros(parser
);
69 static WMenuParser
menu_parser_create_new(const char *file_name
, void *file
,
70 const char *include_default_paths
)
74 parser
= wmalloc(sizeof(*parser
));
75 parser
->include_default_paths
= include_default_paths
;
76 parser
->file_name
= file_name
;
77 parser
->file_handle
= file
;
78 parser
->rd
= parser
->line_buffer
;
83 /***** To report helpfull messages to user *****/
84 const char *WMenuParserGetFilename(WMenuParser parser
)
86 return parser
->file_name
;
89 void WMenuParserError(WMenuParser parser
, const char *msg
, ...)
95 while (parser
->include_file
)
96 parser
= parser
->include_file
;
99 vsnprintf(buf
, sizeof(buf
), msg
, args
);
101 __wmessage("WMenuParser", parser
->file_name
, parser
->line_number
, WMESSAGE_TYPE_WARNING
, buf
);
103 for (parent
= parser
->parent_file
; parent
!= NULL
; parent
= parent
->parent_file
)
104 __wmessage("WMenuParser", parser
->file_name
, parser
->line_number
, WMESSAGE_TYPE_WARNING
,
105 _(" included from file \"%s\" at line %d"),
106 parent
->file_name
, parent
->line_number
);
109 /***** Read one line from file and split content *****/
110 /* The function returns False when the end of file is reached */
111 Bool
WMenuParserGetLine(WMenuParser top_parser
, char **title
, char **command
, char **parameter
, char **shortcut
)
113 WMenuParser cur_parser
;
114 enum { GET_TITLE
, GET_COMMAND
, GET_PARAMETERS
, GET_SHORTCUT
} scanmode
;
116 char lineparam
[MAXLINE
];
124 scanmode
= GET_TITLE
;
126 read_next_line_with_filechange
:
127 cur_parser
= top_parser
;
128 while (cur_parser
->include_file
)
129 cur_parser
= cur_parser
->include_file
;
132 if (fgets(cur_parser
->line_buffer
, sizeof(cur_parser
->line_buffer
), cur_parser
->file_handle
) == NULL
) {
133 if (cur_parser
->parent_file
== NULL
)
134 /* Not inside an included file -> we have reached the end */
137 /* We have only reached the end of an included file -> go back to calling file */
138 fclose(cur_parser
->file_handle
);
139 wfree((char *) cur_parser
->file_name
);
140 cur_parser
= cur_parser
->parent_file
;
141 wfree(cur_parser
->include_file
);
142 cur_parser
->include_file
= NULL
;
143 goto read_next_line_with_filechange
;
145 cur_parser
->line_number
++;
146 cur_parser
->rd
= cur_parser
->line_buffer
;
149 if (!menu_parser_skip_spaces_and_comments(cur_parser
)) {
150 /* We reached the end of line */
151 if (scanmode
== GET_TITLE
)
152 goto read_next_line
; // Empty line -> skip
154 break; // Finished reading current line -> return it to caller
156 if ((scanmode
== GET_TITLE
) && (*cur_parser
->rd
== '#')) {
158 menu_parser_get_directive(cur_parser
);
159 goto read_next_line_with_filechange
;
163 token
= menu_parser_isolate_token(cur_parser
, top_parser
->macros
);
167 scanmode
= GET_COMMAND
;
171 if (strcmp(token
, "SHORTCUT") == 0) {
172 scanmode
= GET_SHORTCUT
;
176 scanmode
= GET_PARAMETERS
;
181 if (*shortcut
!= NULL
) {
182 WMenuParserError(top_parser
, _("multiple SHORTCUT definition not valid") );
186 scanmode
= GET_COMMAND
;
193 if (params
== NULL
) {
196 if ((params
- lineparam
) < sizeof(lineparam
)-1)
200 while ((params
- lineparam
) < sizeof(lineparam
)-1)
201 if ( (*params
= *src
++) == '\0')
211 if (params
!= NULL
) {
212 lineparam
[sizeof(lineparam
) - 1] = '\0';
213 *parameter
= wstrdup(lineparam
);
219 /* Return False when there's nothing left on the line,
220 otherwise increment parser's pointer to next token */
221 Bool
menu_parser_skip_spaces_and_comments(WMenuParser parser
)
224 while (isspace(*parser
->rd
))
227 if (*parser
->rd
== '\0')
228 return False
; // Found the end of current line
230 else if ((parser
->rd
[0] == '\\') &&
231 (parser
->rd
[1] == '\n') &&
232 (parser
->rd
[2] == '\0')) {
233 // Means that the current line is expected to be continued on next line
234 if (fgets(parser
->line_buffer
, sizeof(parser
->line_buffer
), parser
->file_handle
) == NULL
) {
235 WMenuParserError(parser
, _("premature end of file while expecting a new line after '\\'") );
238 parser
->line_number
++;
239 parser
->rd
= parser
->line_buffer
;
241 } else if (parser
->rd
[0] == '/') {
242 if (parser
->rd
[1] == '/') // Single line C comment
243 return False
; // Won't find anything more on this line
244 if (parser
->rd
[1] == '*') {
247 start_line
= parser
->line_number
;
250 /* Search end-of-comment marker */
251 while (*parser
->rd
!= '\0') {
252 if (parser
->rd
[0] == '*')
253 if (parser
->rd
[1] == '/')
254 goto found_end_of_comment
;
258 /* Marker not found -> load next line */
259 if (fgets(parser
->line_buffer
, sizeof(parser
->line_buffer
), parser
->file_handle
) == NULL
) {
260 WMenuParserError(parser
, _("reached end of file while searching '*/' for comment started at line %d"), start_line
);
263 parser
->line_number
++;
264 parser
->rd
= parser
->line_buffer
;
267 found_end_of_comment
:
268 parser
->rd
+= 2; // Skip closing mark
269 continue; // Because there may be spaces after the comment
271 return True
; // the '/' was not a comment, treat it as user data
273 return True
; // Found some data
277 /* read a token (non-spaces suite of characters)
278 the result os wmalloc's, so it needs to be free'd */
279 static char *menu_parser_isolate_token(WMenuParser parser
, WParserMacro
*list_macros
)
283 int limit
= MAX_NESTED_MACROS
;
288 while (*parser
->rd
!= '\0')
289 if (isspace(*parser
->rd
))
291 else if ((parser
->rd
[0] == '/') &&
292 ((parser
->rd
[1] == '*') || (parser
->rd
[1] == '/')))
294 else if ((parser
->rd
[0] == '\\') && (parser
->rd
[1] == '\n'))
296 else if ((*parser
->rd
== '"' ) || (*parser
->rd
== '\'')) {
297 char eot
= *parser
->rd
++;
298 while ((*parser
->rd
!= '\0') && (*parser
->rd
!= '\n'))
299 if (*parser
->rd
++ == eot
)
300 goto found_end_quote
;
301 WMenuParserError(parser
, _("missing closing quote or double-quote before end-of-line") );
304 } else if (isnamechr(*parser
->rd
)) {
308 start_macro
= parser
->rd
;
309 while (isnamechr(*parser
->rd
))
312 macro
= menu_parser_find_macro(parser
, start_macro
);
316 /* Copy the chars before the macro to the beginning of the buffer to
317 leave as much room as possible for expansion */
318 expand_there
= parser
->line_buffer
;
319 if (start
!= parser
->line_buffer
)
320 while (start
< start_macro
)
321 *expand_there
++ = *start
++;
322 start
= parser
->line_buffer
;
324 /* Macro expansion will take care to keep the rest of the line after
325 the macro to the end of the line for future token extraction */
326 menu_parser_expand_macro(parser
, macro
, expand_there
,
327 sizeof(parser
->line_buffer
) - (start
- parser
->line_buffer
) );
329 /* Restart parsing to allow expansion of sub macro calls */
330 parser
->rd
= expand_there
;
332 goto restart_token_split
;
333 WMenuParserError(parser
, _("too many nested macro expansion, breaking loop") );
334 while (isnamechr(*parser
->rd
))
338 // else: the text was passed over so it will be counted in the token from 'start'
342 token
= wmalloc(parser
->rd
- start
+ 1);
343 strncpy(token
, start
, parser
->rd
- start
);
344 token
[parser
->rd
- start
] = '\0';
349 /***** Processing of special # directives *****/
350 static void menu_parser_get_directive(WMenuParser parser
)
354 /* Isolate the command */
355 while (isspace(*parser
->rd
))
357 command
= parser
->rd
;
359 if (isspace(*parser
->rd
)) {
360 *parser
->rd
++ = '\0';
364 if (strcmp(command
, "include") == 0) {
365 if (!menu_parser_include_file(parser
)) return;
367 } else if (strcmp(command
, "define") == 0) {
368 menu_parser_define_macro(parser
);
371 WMenuParserError(parser
, _("unknow directive '#%s'"), command
);
375 if (menu_parser_skip_spaces_and_comments(parser
))
376 WMenuParserError(parser
, _("extra text after '#' command is ignored: \"%.16s...\""),
380 /* Extract the file name, search for it in known directories
381 and create a sub-parser to handle it.
382 Returns False if the file could not be found */
383 static Bool
menu_parser_include_file(WMenuParser parser
)
385 char buffer
[MAXLINE
];
386 char *req_filename
, *fullfilename
, *p
;
390 if (!menu_parser_skip_spaces_and_comments(parser
)) {
391 WMenuParserError(parser
, _("no file name found for #include") );
394 switch (*parser
->rd
++) {
395 case '<': eot
= '>'; break;
396 case '"': eot
= '"'; break;
398 WMenuParserError(parser
, _("file name must be enclosed in brackets or double-quotes for #define") );
401 req_filename
= parser
->rd
;
403 if (*parser
->rd
== eot
) {
404 *parser
->rd
++ = '\0';
405 goto found_end_define_fname
;
407 WMenuParserError(parser
, _("missing closing '%c' in filename specification"), eot
);
409 found_end_define_fname
:
411 { /* Check we are not nesting too many includes */
416 for (p
= parser
; p
->parent_file
; p
= p
->parent_file
)
418 if (count
> MAX_NESTED_INCLUDES
) {
419 WMenuParserError(parser
, _("too many nested includes") );
425 fullfilename
= req_filename
;
426 if (req_filename
[0] != '/') {
427 /* Search first in the same directory as the current file */
428 p
= strrchr(parser
->file_name
, '/');
432 len
= p
- parser
->file_name
+ 1;
433 if (len
> sizeof(buffer
) - 1) len
= sizeof(buffer
) - 1;
434 strncpy(buffer
, parser
->file_name
, len
);
435 strncpy(buffer
+len
, req_filename
, sizeof(buffer
) - len
- 1);
436 buffer
[sizeof(buffer
) - 1] = '\0';
437 fullfilename
= buffer
;
440 fh
= fopen(fullfilename
, "rb");
442 /* Not found? Search in wmaker's known places */
444 if (req_filename
[0] != '/') {
447 fullfilename
= buffer
;
448 src
= parser
->include_default_paths
;
449 while (*src
!= '\0') {
452 char *home
= wgethomedir();
453 while (*home
!= '\0')
457 while ((*src
!= '\0') && (*src
!= ':'))
460 strncpy(p
, req_filename
, sizeof(buffer
) - (p
- buffer
- 1));
461 buffer
[sizeof(buffer
) - 1] = '\0';
463 fh
= fopen(fullfilename
, "rb");
464 if (fh
!= NULL
) goto found_valid_file
;
467 WMenuParserError(parser
, _("could not find file \"%s\" for include"), req_filename
);
471 /* Found the file, make it our new source */
473 parser
->include_file
= menu_parser_create_new(wstrdup(req_filename
), fh
, parser
->include_default_paths
);
474 parser
->include_file
->parent_file
= parser
;