rc.c revision 224014
1/*
2 *  $Id: rc.c,v 1.47 2011/06/20 22:30:04 tom Exp $
3 *
4 *  rc.c -- routines for processing the configuration file
5 *
6 *  Copyright 2000-2010,2011	Thomas E. Dickey
7 *
8 *  This program is free software; you can redistribute it and/or modify
9 *  it under the terms of the GNU Lesser General Public License, version 2.1
10 *  as published by the Free Software Foundation.
11 *
12 *  This program is distributed in the hope that it will be useful, but
13 *  WITHOUT ANY WARRANTY; without even the implied warranty of
14 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 *  Lesser General Public License for more details.
16 *
17 *  You should have received a copy of the GNU Lesser General Public
18 *  License along with this program; if not, write to
19 *	Free Software Foundation, Inc.
20 *	51 Franklin St., Fifth Floor
21 *	Boston, MA 02110, USA.
22 *
23 *  An earlier version of this program lists as authors
24 *	Savio Lam (lam836@cs.cuhk.hk)
25 */
26
27#include <dialog.h>
28
29#include <dlg_keys.h>
30
31#ifdef HAVE_COLOR
32#include <dlg_colors.h>
33
34/*
35 * For matching color names with color values
36 */
37static const color_names_st color_names[] =
38{
39#ifdef HAVE_USE_DEFAULT_COLORS
40    {"DEFAULT", -1},
41#endif
42    {"BLACK", COLOR_BLACK},
43    {"RED", COLOR_RED},
44    {"GREEN", COLOR_GREEN},
45    {"YELLOW", COLOR_YELLOW},
46    {"BLUE", COLOR_BLUE},
47    {"MAGENTA", COLOR_MAGENTA},
48    {"CYAN", COLOR_CYAN},
49    {"WHITE", COLOR_WHITE},
50};				/* color names */
51#define COLOR_COUNT	(sizeof(color_names) / sizeof(color_names[0]))
52#endif /* HAVE_COLOR */
53
54#define GLOBALRC "/etc/dialogrc"
55#define DIALOGRC ".dialogrc"
56
57/* Types of values */
58#define VAL_INT  0
59#define VAL_STR  1
60#define VAL_BOOL 2
61
62/* Type of line in configuration file */
63typedef enum {
64    LINE_ERROR = -1,
65    LINE_EQUALS,
66    LINE_EMPTY
67} PARSE_LINE;
68
69/* number of configuration variables */
70#define VAR_COUNT        (sizeof(vars) / sizeof(vars_st))
71
72/* check if character is white space */
73#define whitespace(c)    (c == ' ' || c == TAB)
74
75/* check if character is string quoting characters */
76#define isquote(c)       (c == '"' || c == '\'')
77
78/* get last character of string */
79#define lastch(str)      str[strlen(str)-1]
80
81/*
82 * Configuration variables
83 */
84typedef struct {
85    const char *name;		/* name of configuration variable as in DIALOGRC */
86    void *var;			/* address of actual variable to change */
87    int type;			/* type of value */
88    const char *comment;	/* comment to put in "rc" file */
89} vars_st;
90
91/*
92 * This table should contain only references to dialog_state, since dialog_vars
93 * is reset specially in dialog.c before each widget.
94 */
95static const vars_st vars[] =
96{
97    {"aspect",
98     &dialog_state.aspect_ratio,
99     VAL_INT,
100     "Set aspect-ration."},
101
102    {"separate_widget",
103     &dialog_state.separate_str,
104     VAL_STR,
105     "Set separator (for multiple widgets output)."},
106
107    {"tab_len",
108     &dialog_state.tab_len,
109     VAL_INT,
110     "Set tab-length (for textbox tab-conversion)."},
111
112    {"visit_items",
113     &dialog_state.visit_items,
114     VAL_BOOL,
115     "Make tab-traversal for checklist, etc., include the list."},
116
117#ifdef HAVE_COLOR
118    {"use_shadow",
119     &dialog_state.use_shadow,
120     VAL_BOOL,
121     "Shadow dialog boxes? This also turns on color."},
122
123    {"use_colors",
124     &dialog_state.use_colors,
125     VAL_BOOL,
126     "Turn color support ON or OFF"},
127#endif				/* HAVE_COLOR */
128};				/* vars */
129
130static int
131skip_whitespace(char *str, int n)
132{
133    while (whitespace(str[n]) && str[n] != '\0')
134	n++;
135    return n;
136}
137
138static int
139skip_keyword(char *str, int n)
140{
141    while (isalnum(UCH(str[n])) && str[n] != '\0')
142	n++;
143    return n;
144}
145
146static int
147find_vars(char *name)
148{
149    int result = -1;
150    unsigned i;
151
152    for (i = 0; i < VAR_COUNT; i++) {
153	if (dlg_strcmp(vars[i].name, name) == 0) {
154	    result = (int) i;
155	    break;
156	}
157    }
158    return result;
159}
160
161#ifdef HAVE_COLOR
162static int
163find_color(char *name)
164{
165    int result = -1;
166    int i;
167    int limit = dlg_color_count();
168
169    for (i = 0; i < limit; i++) {
170	if (dlg_strcmp(dlg_color_table[i].name, name) == 0) {
171	    result = i;
172	    break;
173	}
174    }
175    return result;
176}
177
178/*
179 * Convert an attribute to a string representation like this:
180 *
181 * "(foreground,background,highlight)"
182 */
183static char *
184attr_to_str(char *str, int fg, int bg, int hl)
185{
186    int i;
187
188    strcpy(str, "(");
189    /* foreground */
190    for (i = 0; fg != color_names[i].value; i++) ;
191    strcat(str, color_names[i].name);
192    strcat(str, ",");
193
194    /* background */
195    for (i = 0; bg != color_names[i].value; i++) ;
196    strcat(str, color_names[i].name);
197
198    /* highlight */
199    strcat(str, hl ? ",ON)" : ",OFF)");
200
201    return str;
202}
203
204/*
205 * Extract the foreground, background and highlight values from an attribute
206 * represented as a string in this form:
207 *
208 * "(foreground,background,highlight)"
209 */
210static int
211str_to_attr(char *str, int *fg, int *bg, int *hl)
212{
213    int i = 0, get_fg = 1;
214    unsigned j;
215    char tempstr[MAX_LEN + 1], *part;
216
217    if (str[0] != '(' || lastch(str) != ')')
218	return -1;		/* invalid representation */
219
220    /* remove the parenthesis */
221    strcpy(tempstr, str + 1);
222    lastch(tempstr) = '\0';
223
224    /* get foreground and background */
225
226    while (1) {
227	/* skip white space before fg/bg string */
228	i = skip_whitespace(tempstr, i);
229	if (tempstr[i] == '\0')
230	    return -1;		/* invalid representation */
231	part = tempstr + i;	/* set 'part' to start of fg/bg string */
232
233	/* find end of fg/bg string */
234	while (!whitespace(tempstr[i]) && tempstr[i] != ','
235	       && tempstr[i] != '\0')
236	    i++;
237
238	if (tempstr[i] == '\0')
239	    return -1;		/* invalid representation */
240	else if (whitespace(tempstr[i])) {	/* not yet ',' */
241	    tempstr[i++] = '\0';
242
243	    /* skip white space before ',' */
244	    i = skip_whitespace(tempstr, i);
245	    if (tempstr[i] != ',')
246		return -1;	/* invalid representation */
247	}
248	tempstr[i++] = '\0';	/* skip the ',' */
249	for (j = 0; j < COLOR_COUNT && dlg_strcmp(part, color_names[j].name);
250	     j++) ;
251	if (j == COLOR_COUNT)	/* invalid color name */
252	    return -1;
253	if (get_fg) {
254	    *fg = color_names[j].value;
255	    get_fg = 0;		/* next we have to get the background */
256	} else {
257	    *bg = color_names[j].value;
258	    break;
259	}
260    }				/* got foreground and background */
261
262    /* get highlight */
263
264    /* skip white space before highlight string */
265    i = skip_whitespace(tempstr, i);
266    if (tempstr[i] == '\0')
267	return -1;		/* invalid representation */
268    part = tempstr + i;		/* set 'part' to start of highlight string */
269
270    /* trim trailing white space from highlight string */
271    i = (int) strlen(part) - 1;
272    while (whitespace(part[i]) && i > 0)
273	i--;
274    part[i + 1] = '\0';
275
276    if (!dlg_strcmp(part, "ON"))
277	*hl = TRUE;
278    else if (!dlg_strcmp(part, "OFF"))
279	*hl = FALSE;
280    else
281	return -1;		/* invalid highlight value */
282
283    return 0;
284}
285#endif /* HAVE_COLOR */
286
287/*
288 * Check if the line begins with a special keyword; if so, return true while
289 * pointing params to its parameters.
290 */
291static int
292begins_with(char *line, const char *keyword, char **params)
293{
294    int i = skip_whitespace(line, 0);
295    int j = skip_keyword(line, i);
296
297    if ((j - i) == (int) strlen(keyword)) {
298	char save = line[j];
299	line[j] = 0;
300	if (!dlg_strcmp(keyword, line + i)) {
301	    *params = line + skip_whitespace(line, j + 1);
302	    return 1;
303	}
304	line[j] = save;
305    }
306
307    return 0;
308}
309
310/*
311 * Parse a line in the configuration file
312 *
313 * Each line is of the form:  "variable = value". On exit, 'var' will contain
314 * the variable name, and 'value' will contain the value string.
315 *
316 * Return values:
317 *
318 * LINE_EMPTY   - line is blank or comment
319 * LINE_EQUALS  - line contains "variable = value"
320 * LINE_ERROR   - syntax error in line
321 */
322static PARSE_LINE
323parse_line(char *line, char **var, char **value)
324{
325    int i = 0;
326
327    /* ignore white space at beginning of line */
328    i = skip_whitespace(line, i);
329
330    if (line[i] == '\0')	/* line is blank */
331	return LINE_EMPTY;
332    else if (line[i] == '#')	/* line is comment */
333	return LINE_EMPTY;
334    else if (line[i] == '=')	/* variable names cannot start with a '=' */
335	return LINE_ERROR;
336
337    /* set 'var' to variable name */
338    *var = line + i++;		/* skip to next character */
339
340    /* find end of variable name */
341    while (!whitespace(line[i]) && line[i] != '=' && line[i] != '\0')
342	i++;
343
344    if (line[i] == '\0')	/* syntax error */
345	return LINE_ERROR;
346    else if (line[i] == '=')
347	line[i++] = '\0';
348    else {
349	line[i++] = '\0';
350
351	/* skip white space before '=' */
352	i = skip_whitespace(line, i);
353
354	if (line[i] != '=')	/* syntax error */
355	    return LINE_ERROR;
356	else
357	    i++;		/* skip the '=' */
358    }
359
360    /* skip white space after '=' */
361    i = skip_whitespace(line, i);
362
363    if (line[i] == '\0')
364	return LINE_ERROR;
365    else
366	*value = line + i;	/* set 'value' to value string */
367
368    /* trim trailing white space from 'value' */
369    i = (int) strlen(*value) - 1;
370    while (whitespace((*value)[i]) && i > 0)
371	i--;
372    (*value)[i + 1] = '\0';
373
374    return LINE_EQUALS;		/* no syntax error in line */
375}
376
377/*
378 * Create the configuration file
379 */
380void
381dlg_create_rc(const char *filename)
382{
383    unsigned i;
384    FILE *rc_file;
385
386    if ((rc_file = fopen(filename, "wt")) == NULL)
387	dlg_exiterr("Error opening file for writing in dlg_create_rc().");
388
389    fprintf(rc_file, "#\n\
390# Run-time configuration file for dialog\n\
391#\n\
392# Automatically generated by \"dialog --create-rc <file>\"\n\
393#\n\
394#\n\
395# Types of values:\n\
396#\n\
397# Number     -  <number>\n\
398# String     -  \"string\"\n\
399# Boolean    -  <ON|OFF>\n"
400#ifdef HAVE_COLOR
401	    "\
402# Attribute  -  (foreground,background,highlight?)\n"
403#endif
404	);
405
406    /* Print an entry for each configuration variable */
407    for (i = 0; i < VAR_COUNT; i++) {
408	fprintf(rc_file, "\n# %s\n", vars[i].comment);
409	switch (vars[i].type) {
410	case VAL_INT:
411	    fprintf(rc_file, "%s = %d\n", vars[i].name,
412		    *((int *) vars[i].var));
413	    break;
414	case VAL_STR:
415	    fprintf(rc_file, "%s = \"%s\"\n", vars[i].name,
416		    (char *) vars[i].var);
417	    break;
418	case VAL_BOOL:
419	    fprintf(rc_file, "%s = %s\n", vars[i].name,
420		    *((bool *) vars[i].var) ? "ON" : "OFF");
421	    break;
422	}
423    }
424#ifdef HAVE_COLOR
425    for (i = 0; i < (unsigned) dlg_color_count(); ++i) {
426	char buffer[MAX_LEN + 1];
427
428	fprintf(rc_file, "\n# %s\n", dlg_color_table[i].comment);
429	fprintf(rc_file, "%s = %s\n", dlg_color_table[i].name,
430		attr_to_str(buffer,
431			    dlg_color_table[i].fg,
432			    dlg_color_table[i].bg,
433			    dlg_color_table[i].hilite));
434    }
435#endif /* HAVE_COLOR */
436    dlg_dump_keys(rc_file);
437
438    (void) fclose(rc_file);
439}
440
441/*
442 * Parse the configuration file and set up variables
443 */
444int
445dlg_parse_rc(void)
446{
447    int i;
448    int l = 1;
449    PARSE_LINE parse;
450    char str[MAX_LEN + 1];
451    char *var;
452    char *value;
453    char *tempptr;
454    int result = 0;
455    FILE *rc_file = 0;
456    char *params;
457
458    /*
459     *  At startup, dialog determines the settings to use as follows:
460     *
461     *  a) if the environment variable $DIALOGRC is set, its value determines
462     *     the name of the configuration file.
463     *
464     *  b) if the file in (a) can't be found, use the file $HOME/.dialogrc
465     *     as the configuration file.
466     *
467     *  c) if the file in (b) can't be found, try using the GLOBALRC file.
468     *     Usually this will be /etc/dialogrc.
469     *
470     *  d) if the file in (c) cannot be found, use the compiled-in defaults.
471     */
472
473    /* try step (a) */
474    if ((tempptr = getenv("DIALOGRC")) != NULL)
475	rc_file = fopen(tempptr, "rt");
476
477    if (rc_file == NULL) {	/* step (a) failed? */
478	/* try step (b) */
479	if ((tempptr = getenv("HOME")) != NULL
480	    && strlen(tempptr) < MAX_LEN - (sizeof(DIALOGRC) + 3)) {
481	    if (tempptr[0] == '\0' || lastch(tempptr) == '/')
482		sprintf(str, "%s%s", tempptr, DIALOGRC);
483	    else
484		sprintf(str, "%s/%s", tempptr, DIALOGRC);
485	    rc_file = fopen(tempptr = str, "rt");
486	}
487    }
488
489    if (rc_file == NULL) {	/* step (b) failed? */
490	/* try step (c) */
491	strcpy(str, GLOBALRC);
492	if ((rc_file = fopen(tempptr = str, "rt")) == NULL)
493	    return 0;		/* step (c) failed, use default values */
494    }
495
496    DLG_TRACE(("opened rc file \"%s\"\n", tempptr));
497    /* Scan each line and set variables */
498    while ((result == 0) && (fgets(str, MAX_LEN, rc_file) != NULL)) {
499	DLG_TRACE(("rc:%s", str));
500	if (*str == '\0' || lastch(str) != '\n') {
501	    /* ignore rest of file if line too long */
502	    fprintf(stderr, "\nParse error: line %d of configuration"
503		    " file too long.\n", l);
504	    result = -1;	/* parse aborted */
505	    break;
506	}
507
508	lastch(str) = '\0';
509	if (begins_with(str, "bindkey", &params)) {
510	    dlg_parse_bindkey(params);
511	    continue;
512	}
513	parse = parse_line(str, &var, &value);	/* parse current line */
514
515	switch (parse) {
516	case LINE_EMPTY:	/* ignore blank lines and comments */
517	    break;
518	case LINE_EQUALS:
519	    /* search table for matching config variable name */
520	    if ((i = find_vars(var)) >= 0) {
521		switch (vars[i].type) {
522		case VAL_INT:
523		    *((int *) vars[i].var) = atoi(value);
524		    break;
525		case VAL_STR:
526		    if (!isquote(value[0]) || !isquote(lastch(value))
527			|| strlen(value) < 2) {
528			fprintf(stderr, "\nParse error: string value "
529				"expected at line %d of configuration "
530				"file.\n", l);
531			result = -1;	/* parse aborted */
532		    } else {
533			/* remove the (") quotes */
534			value++;
535			lastch(value) = '\0';
536			strcpy((char *) vars[i].var, value);
537		    }
538		    break;
539		case VAL_BOOL:
540		    if (!dlg_strcmp(value, "ON"))
541			*((bool *) vars[i].var) = TRUE;
542		    else if (!dlg_strcmp(value, "OFF"))
543			*((bool *) vars[i].var) = FALSE;
544		    else {
545			fprintf(stderr, "\nParse error: boolean value "
546				"expected at line %d of configuration "
547				"file (found %s).\n", l, value);
548			result = -1;	/* parse aborted */
549		    }
550		    break;
551		}
552#ifdef HAVE_COLOR
553	    } else if ((i = find_color(var)) >= 0) {
554		int fg = 0;
555		int bg = 0;
556		int hl = 0;
557		if (str_to_attr(value, &fg, &bg, &hl) == -1) {
558		    fprintf(stderr, "\nParse error: attribute "
559			    "value expected at line %d of configuration "
560			    "file.\n", l);
561		    result = -1;	/* parse aborted */
562		} else {
563		    dlg_color_table[i].fg = fg;
564		    dlg_color_table[i].bg = bg;
565		    dlg_color_table[i].hilite = hl;
566		}
567	    } else {
568#endif /* HAVE_COLOR */
569		fprintf(stderr, "\nParse error: unknown variable "
570			"at line %d of configuration file:\n\t%s\n", l, var);
571		result = -1;	/* parse aborted */
572	    }
573	    break;
574	case LINE_ERROR:
575	    fprintf(stderr, "\nParse error: syntax error at line %d of "
576		    "configuration file.\n", l);
577	    result = -1;	/* parse aborted */
578	    break;
579	}
580	l++;			/* next line */
581    }
582
583    (void) fclose(rc_file);
584    return result;
585}
586