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
);
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
);
65 static WMenuParser
menu_parser_create_new(const char *file_name
, void *file
,
66 const char *include_default_paths
)
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
;
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
, ...)
91 while (parser
->include_file
)
92 parser
= parser
->include_file
;
95 vsnprintf(buf
, sizeof(buf
), msg
, 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
;
112 char lineparam
[MAXLINE
];
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
;
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 */
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
;
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
150 break; // Finished reading current line -> return it to caller
152 if ((scanmode
== GET_TITLE
) && (*cur_parser
->rd
== '#')) {
154 menu_parser_get_directive(cur_parser
);
155 goto read_next_line_with_filechange
;
159 token
= menu_parser_isolate_token(cur_parser
);
163 scanmode
= GET_COMMAND
;
167 if (strcmp(token
, "SHORTCUT") == 0) {
168 scanmode
= GET_SHORTCUT
;
172 scanmode
= GET_PARAMETERS
;
177 if (*shortcut
!= NULL
) {
178 WMenuParserError(top_parser
, _("multiple SHORTCUT definition not valid") );
182 scanmode
= GET_COMMAND
;
189 if (params
== NULL
) {
192 if ((params
- lineparam
) < sizeof(lineparam
)-1)
196 while ((params
- lineparam
) < sizeof(lineparam
)-1)
197 if ( (*params
= *src
++) == '\0')
207 if (params
!= NULL
) {
208 lineparam
[sizeof(lineparam
) - 1] = '\0';
209 *parameter
= wstrdup(lineparam
);
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
)
220 while (isspace(*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 '\\'") );
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] == '*') {
243 start_line
= parser
->line_number
;
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
;
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
);
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
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
)
282 while (*parser
->rd
!= '\0')
283 if (isspace(*parser
->rd
))
285 else if ((parser
->rd
[0] == '/') &&
286 ((parser
->rd
[1] == '*') || (parser
->rd
[1] == '/')))
288 else if ((parser
->rd
[0] == '\\') && (parser
->rd
[1] == '\n'))
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") );
301 token
= wmalloc(parser
->rd
- start
+ 1);
302 strncpy(token
, start
, parser
->rd
- start
);
303 token
[parser
->rd
- start
] = '\0';
308 /***** Processing of special # directives *****/
309 static void menu_parser_get_directive(WMenuParser parser
)
313 /* Isolate the command */
314 while (isspace(*parser
->rd
))
316 command
= parser
->rd
;
318 if (isspace(*parser
->rd
)) {
319 *parser
->rd
++ = '\0';
323 if (strcmp(command
, "include") == 0) {
324 if (!menu_parser_include_file(parser
)) return;
327 WMenuParserError(parser
, _("unknow directive '#%s'"), command
);
331 if (menu_parser_skip_spaces_and_comments(parser
))
332 WMenuParserError(parser
, _("extra text after '#' command is ignored: \"%.16s...\""),
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
;
346 if (!menu_parser_skip_spaces_and_comments(parser
)) {
347 WMenuParserError(parser
, _("no file name found for #include") );
350 switch (*parser
->rd
++) {
351 case '<': eot
= '>'; break;
352 case '"': eot
= '"'; break;
354 WMenuParserError(parser
, _("file name must be enclosed in brackets or double-quotes for #define") );
357 req_filename
= parser
->rd
;
359 if (*parser
->rd
== eot
) {
360 *parser
->rd
++ = '\0';
361 goto found_end_define_fname
;
363 WMenuParserError(parser
, _("missing closing '%c' in filename specification"), eot
);
365 found_end_define_fname
:
367 { /* Check we are not nesting too many includes */
372 for (p
= parser
; p
->parent_file
; p
= p
->parent_file
)
374 if (count
> MAX_NESTED_INCLUDES
) {
375 WMenuParserError(parser
, _("too many nested includes") );
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
, '/');
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 */
400 if (req_filename
[0] != '/') {
403 fullfilename
= buffer
;
404 src
= parser
->include_default_paths
;
405 while (*src
!= '\0') {
408 char *home
= wgethomedir();
409 while (*home
!= '\0')
413 while ((*src
!= '\0') && (*src
!= ':'))
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
);
427 /* Found the file, make it our new source */
429 parser
->include_file
= menu_parser_create_new(wstrdup(req_filename
), fh
, parser
->include_default_paths
);
430 parser
->include_file
->parent_file
= parser
;