1
0
mirror of https://github.com/gryf/wmaker.git synced 2026-01-07 14:24:14 +01:00
Files
wmaker/WINGs/menuparser.c
Christophe CURIS 88a82ab8df Remove dependency to CPP: support for #include directive
The parser is prepared to handle '#' directives, starting with file
inclusion. The search path for the file are taken from what was
actually given to CPP. There is an arbitrary limit to the inclusion
nesting, which is actually not a design limitation but a security
to avoid infinite include loops.
2012-07-14 20:22:20 +02:00

433 lines
12 KiB
C

/*
* Window Maker window manager
*
* Copyright (c) 2012 Christophe Curis
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "wconfig.h"
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdarg.h>
#include <WINGs/WUtil.h>
#include "menuparser.h"
static WMenuParser menu_parser_create_new(const char *file_name, void *file,
const char *include_default_paths);
static char *menu_parser_isolate_token(WMenuParser parser);
static void menu_parser_get_directive(WMenuParser parser);
static Bool menu_parser_include_file(WMenuParser parser);
/***** Constructor and Destructor for the Menu Parser object *****/
WMenuParser WMenuParserCreate(const char *file_name, void *file,
const char *include_default_paths)
{
WMenuParser parser;
parser = menu_parser_create_new(file_name, file, include_default_paths);
return parser;
}
void WMenuParserDelete(WMenuParser parser)
{
if (parser->include_file) {
/* Trick: the top parser's data are not wmalloc'd, we point on the
provided data so we do not wfree it; however for include files
we did wmalloc them.
This code should not be used as the wfree is done when we reach
the end of an include file; however this may not happen when an
early exit occurs (typically when 'readMenuFile' does not find
its expected command). */
fclose(parser->include_file->file_handle);
wfree((char *) parser->include_file->file_name);
WMenuParserDelete(parser->include_file);
}
wfree(parser);
}
static WMenuParser menu_parser_create_new(const char *file_name, void *file,
const char *include_default_paths)
{
WMenuParser parser;
parser = wmalloc(sizeof(*parser));
parser->include_default_paths = include_default_paths;
parser->file_name = file_name;
parser->file_handle = file;
parser->rd = parser->line_buffer;
return parser;
}
/***** To report helpfull messages to user *****/
const char *WMenuParserGetFilename(WMenuParser parser)
{
return parser->file_name;
}
void WMenuParserError(WMenuParser parser, const char *msg, ...)
{
char buf[MAXLINE];
va_list args;
WMenuParser parent;
while (parser->include_file)
parser = parser->include_file;
va_start(args, msg);
vsnprintf(buf, sizeof(buf), msg, args);
va_end(args);
__wmessage("WMenuParser", parser->file_name, parser->line_number, WMESSAGE_TYPE_WARNING, buf);
for (parent = parser->parent_file; parent != NULL; parent = parent->parent_file)
__wmessage("WMenuParser", parser->file_name, parser->line_number, WMESSAGE_TYPE_WARNING,
_(" included from file \"%s\" at line %d"),
parent->file_name, parent->line_number);
}
/***** Read one line from file and split content *****/
/* The function returns False when the end of file is reached */
Bool WMenuParserGetLine(WMenuParser top_parser, char **title, char **command, char **parameter, char **shortcut)
{
WMenuParser cur_parser;
enum { GET_TITLE, GET_COMMAND, GET_PARAMETERS, GET_SHORTCUT } scanmode;
char *token;
char lineparam[MAXLINE];
char *params = NULL;
lineparam[0] = '\0';
*title = NULL;
*command = NULL;
*parameter = NULL;
*shortcut = NULL;
scanmode = GET_TITLE;
read_next_line_with_filechange:
cur_parser = top_parser;
while (cur_parser->include_file)
cur_parser = cur_parser->include_file;
read_next_line:
if (fgets(cur_parser->line_buffer, sizeof(cur_parser->line_buffer), cur_parser->file_handle) == NULL) {
if (cur_parser->parent_file == NULL)
/* Not inside an included file -> we have reached the end */
return False;
/* We have only reached the end of an included file -> go back to calling file */
fclose(cur_parser->file_handle);
wfree((char *) cur_parser->file_name);
cur_parser = cur_parser->parent_file;
wfree(cur_parser->include_file);
cur_parser->include_file = NULL;
goto read_next_line_with_filechange;
}
cur_parser->line_number++;
cur_parser->rd = cur_parser->line_buffer;
for (;;) {
if (!menu_parser_skip_spaces_and_comments(cur_parser)) {
/* We reached the end of line */
if (scanmode == GET_TITLE)
goto read_next_line; // Empty line -> skip
else
break; // Finished reading current line -> return it to caller
}
if ((scanmode == GET_TITLE) && (*cur_parser->rd == '#')) {
cur_parser->rd++;
menu_parser_get_directive(cur_parser);
goto read_next_line_with_filechange;
}
/* Found a word */
token = menu_parser_isolate_token(cur_parser);
switch (scanmode) {
case GET_TITLE:
*title = token;
scanmode = GET_COMMAND;
break;
case GET_COMMAND:
if (strcmp(token, "SHORTCUT") == 0) {
scanmode = GET_SHORTCUT;
wfree(token);
} else {
*command = token;
scanmode = GET_PARAMETERS;
}
break;
case GET_SHORTCUT:
if (*shortcut != NULL) {
WMenuParserError(top_parser, _("multiple SHORTCUT definition not valid") );
wfree(*shortcut);
}
*shortcut = token;
scanmode = GET_COMMAND;
break;
case GET_PARAMETERS:
{
char *src;
if (params == NULL) {
params = lineparam;
} else {
if ((params - lineparam) < sizeof(lineparam)-1)
*params++ = ' ';
}
src = token;
while ((params - lineparam) < sizeof(lineparam)-1)
if ( (*params = *src++) == '\0')
break;
else
params++;
wfree(token);
}
break;
}
}
if (params != NULL) {
lineparam[sizeof(lineparam) - 1] = '\0';
*parameter = wstrdup(lineparam);
}
return True;
}
/* Return False when there's nothing left on the line,
otherwise increment parser's pointer to next token */
Bool menu_parser_skip_spaces_and_comments(WMenuParser parser)
{
for (;;) {
while (isspace(*parser->rd))
parser->rd++;
if (*parser->rd == '\0')
return False; // Found the end of current line
else if ((parser->rd[0] == '\\') &&
(parser->rd[1] == '\n') &&
(parser->rd[2] == '\0')) {
// Means that the current line is expected to be continued on next line
if (fgets(parser->line_buffer, sizeof(parser->line_buffer), parser->file_handle) == NULL) {
WMenuParserError(parser, _("premature end of file while expecting a new line after '\\'") );
return False;
}
parser->line_number++;
parser->rd = parser->line_buffer;
} else if (parser->rd[0] == '/') {
if (parser->rd[1] == '/') // Single line C comment
return False; // Won't find anything more on this line
if (parser->rd[1] == '*') {
int start_line;
start_line = parser->line_number;
parser->rd += 2;
for (;;) {
/* Search end-of-comment marker */
while (*parser->rd != '\0') {
if (parser->rd[0] == '*')
if (parser->rd[1] == '/')
goto found_end_of_comment;
parser->rd++;
}
/* Marker not found -> load next line */
if (fgets(parser->line_buffer, sizeof(parser->line_buffer), parser->file_handle) == NULL) {
WMenuParserError(parser, _("reached end of file while searching '*/' for comment started at line %d"), start_line);
return False;
}
parser->line_number++;
parser->rd = parser->line_buffer;
}
found_end_of_comment:
parser->rd += 2; // Skip closing mark
continue; // Because there may be spaces after the comment
}
return True; // the '/' was not a comment, treat it as user data
} else
return True; // Found some data
}
}
/* read a token (non-spaces suite of characters)
the result os wmalloc's, so it needs to be free'd */
static char *menu_parser_isolate_token(WMenuParser parser)
{
char *start;
char *token;
start = parser->rd;
while (*parser->rd != '\0')
if (isspace(*parser->rd))
break;
else if ((parser->rd[0] == '/') &&
((parser->rd[1] == '*') || (parser->rd[1] == '/')))
break;
else if ((parser->rd[0] == '\\') && (parser->rd[1] == '\n'))
break;
else if ((*parser->rd == '"' ) || (*parser->rd == '\'')) {
char eot = *parser->rd++;
while ((*parser->rd != '\0') && (*parser->rd != '\n'))
if (*parser->rd++ == eot)
goto found_end_quote;
WMenuParserError(parser, _("missing closing quote or double-quote before end-of-line") );
found_end_quote:
;
} else
parser->rd++;
token = wmalloc(parser->rd - start + 1);
strncpy(token, start, parser->rd - start);
token[parser->rd - start] = '\0';
return token;
}
/***** Processing of special # directives *****/
static void menu_parser_get_directive(WMenuParser parser)
{
char *command;
/* Isolate the command */
while (isspace(*parser->rd))
parser->rd++;
command = parser->rd;
while (*parser->rd)
if (isspace(*parser->rd)) {
*parser->rd++ = '\0';
break;
} else parser->rd++;
if (strcmp(command, "include") == 0) {
if (!menu_parser_include_file(parser)) return;
} else {
WMenuParserError(parser, _("unknow directive '#%s'"), command);
return;
}
if (menu_parser_skip_spaces_and_comments(parser))
WMenuParserError(parser, _("extra text after '#' command is ignored: \"%.16s...\""),
parser->rd);
}
/* Extract the file name, search for it in known directories
and create a sub-parser to handle it.
Returns False if the file could not be found */
static Bool menu_parser_include_file(WMenuParser parser)
{
char buffer[MAXLINE];
char *req_filename, *fullfilename, *p;
char eot;
FILE *fh;
if (!menu_parser_skip_spaces_and_comments(parser)) {
WMenuParserError(parser, _("no file name found for #include") );
return False;
}
switch (*parser->rd++) {
case '<': eot = '>'; break;
case '"': eot = '"'; break;
default:
WMenuParserError(parser, _("file name must be enclosed in brackets or double-quotes for #define") );
return False;
}
req_filename = parser->rd;
while (*parser->rd)
if (*parser->rd == eot) {
*parser->rd++ = '\0';
goto found_end_define_fname;
} else parser->rd++;
WMenuParserError(parser, _("missing closing '%c' in filename specification"), eot);
return False;
found_end_define_fname:
{ /* Check we are not nesting too many includes */
WMenuParser p;
int count;
count = 0;
for (p = parser; p->parent_file; p = p->parent_file)
count++;
if (count > MAX_NESTED_INCLUDES) {
WMenuParserError(parser, _("too many nested includes") );
return False;
}
}
/* Absolute paths */
fullfilename = req_filename;
if (req_filename[0] != '/') {
/* Search first in the same directory as the current file */
p = strrchr(parser->file_name, '/');
if (p != NULL) {
int len;
len = p - parser->file_name + 1;
if (len > sizeof(buffer) - 1) len = sizeof(buffer) - 1;
strncpy(buffer, parser->file_name, len);
strncpy(buffer+len, req_filename, sizeof(buffer) - len - 1);
buffer[sizeof(buffer) - 1] = '\0';
fullfilename = buffer;
}
}
fh = fopen(fullfilename, "rb");
/* Not found? Search in wmaker's known places */
if (fh == NULL) {
if (req_filename[0] != '/') {
const char *src;
fullfilename = buffer;
src = parser->include_default_paths;
while (*src != '\0') {
p = buffer;
if (*src == '~') {
char *home = wgethomedir();
while (*home != '\0')
*p++ = *home++;
src++;
}
while ((*src != '\0') && (*src != ':'))
*p++ = *src++;
*p++ = '/';
strncpy(p, req_filename, sizeof(buffer) - (p - buffer - 1));
buffer[sizeof(buffer) - 1] = '\0';
fh = fopen(fullfilename, "rb");
if (fh != NULL) goto found_valid_file;
}
}
WMenuParserError(parser, _("could not find file \"%s\" for include"), req_filename);
return False;
}
/* Found the file, make it our new source */
found_valid_file:
parser->include_file = menu_parser_create_new(wstrdup(req_filename), fh, parser->include_default_paths);
parser->include_file->parent_file = parser;
return True;
}