1 /*
2  * CvsGraph graphical representation generator of brances and revisions
3  * of a file in cvs/rcs.
4  *
5  * Copyright (C) 2001,2002  B. Stultiens
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  */
21 
22 /*#define DEBUG	1*/
23 
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <ctype.h>
28 #include <assert.h>
29 
30 #include <gd.h>
31 #include <gdfontt.h>
32 #include <gdfonts.h>
33 #include <gdfontmb.h>
34 #include <gdfontl.h>
35 #include <gdfontg.h>
36 
37 #include "utils.h"
38 #include "cvsgraph.h"
39 #include "readconf.h"
40 
41 int line_number;
42 
43 typedef struct
44 {
45 	const char	*keyword;
46 	int		type;
47 	union {
48 		void	*v;	/* join of other values */
49 		int	*i;
50 		font_t	*f;
51 		char	**s;
52 		color_t	*c;
53 		double	*d;
54 		stringlist_t *sl;
55 		condstring_t *cs;
56 		colorlist_t *cl;
57 		int	val;
58 	} confref;
59 } keyword_t;
60 
61 typedef union {
62 	keyword_t	*kw;
63 	int		i;
64 	double		d;
65 	char		*str;
66 } YYSTYPE;
67 
68 static YYSTYPE yylval;
69 
70 static int nstacked_opts;
71 static char **stacked_opts;
72 
73 static keyword_t keywords[] = {
74 	{ "branch_bgcolor",	TYPE_COLOR,	{ &conf.branch_bgcolor } },
75 	{ "branch_bspace",	TYPE_NUMBER,	{ &conf.branch_bspace } },
76 	{ "branch_color",	TYPE_COLOR,	{ &conf.branch_color } },
77 	{ "branch_font",	TYPE_FONT,	{ &conf.branch_font.gdfont } },
78 	{ "branch_ttfont",	TYPE_STRING,	{ &conf.branch_font.ttfont } },
79 	{ "branch_ttsize",	TYPE_DOUBLE,	{ &conf.branch_font.ttsize } },
80 	{ "branch_tag_color",	TYPE_COLOR,	{ &conf.branch_tag_color } },
81 	{ "branch_tag_font",	TYPE_FONT,	{ &conf.branch_tag_font.gdfont } },
82 	{ "branch_tag_ttfont",	TYPE_STRING,	{ &conf.branch_tag_font.ttfont } },
83 	{ "branch_tag_ttsize",	TYPE_DOUBLE,	{ &conf.branch_tag_font.ttsize } },
84 	{ "branch_lspace",	TYPE_NUMBER,	{ &conf.branch_lspace } },
85 	{ "branch_rspace",	TYPE_NUMBER,	{ &conf.branch_rspace } },
86 	{ "branch_tspace",	TYPE_NUMBER,	{ &conf.branch_tspace } },
87 	{ "branch_connect",	TYPE_NUMBER,	{ &conf.branch_connect } },
88 	{ "branch_margin",	TYPE_NUMBER,	{ &conf.branch_margin } },
89 	{ "branch_dupbox",	TYPE_BOOLEAN,	{ &conf.branch_dupbox } },
90 	{ "branch_fold",	TYPE_BOOLEAN,	{ &conf.branch_fold } },
91 	{ "branch_foldall",	TYPE_BOOLEAN,	{ &conf.branch_foldall } },
92 	{ "branch_resort",	TYPE_BOOLEAN,	{ &conf.branch_resort } },
93 	{ "branch_subtree",	TYPE_STRING,	{ &conf.branch_subtree } },
94 	{ "upside_down",	TYPE_BOOLEAN,	{ &conf.upside_down } },
95 	{ "left_right",		TYPE_BOOLEAN,	{ &conf.left_right } },
96 	{ "auto_stretch",	TYPE_BOOLEAN,	{ &conf.auto_stretch } },
97 	{ "color_bg",		TYPE_COLOR,	{ &conf.color_bg } },
98 	{ "transparent_bg",	TYPE_BOOLEAN,	{ &conf.transparent_bg } },
99 	{ "cvsmodule",		TYPE_STRING,	{ &conf.cvsmodule } },
100 	{ "cvsroot",		TYPE_STRING,	{ &conf.cvsroot } },
101 	{ "date_format",	TYPE_STRING,	{ &conf.date_format } },
102 	{ "box_shadow",		TYPE_BOOLEAN,	{ &conf.box_shadow } },
103 	{ "strip_untagged",	TYPE_BOOLEAN,	{ &conf.strip_untagged } },
104 	{ "strip_first_rev",	TYPE_BOOLEAN,	{ &conf.strip_first_rev } },
105 	{ "anti_alias",		TYPE_BOOLEAN,	{ &conf.anti_alias } },
106 	{ "use_ttf",		TYPE_BOOLEAN,	{ &conf.use_ttf } },
107 	{ "parse_logs",		TYPE_BOOLEAN,	{ &conf.parse_logs } },
108 	{ "html_level",		TYPE_NUMBER,	{ &conf.html_level } },
109 	{ "thick_lines",	TYPE_NUMBER,	{ &conf.thick_lines } },
110 	{ "msg_color",		TYPE_COLOR,	{ &conf.msg_color } },
111 	{ "msg_font",		TYPE_FONT,	{ &conf.msg_font.gdfont } },
112 	{ "msg_ttfont",		TYPE_STRING,	{ &conf.msg_font.ttfont } },
113 	{ "msg_ttsize",		TYPE_DOUBLE,	{ &conf.msg_font.ttsize } },
114 	{ "rev_hidenumber",	TYPE_BOOLEAN,	{ &conf.rev_hidenumber } },
115 	{ "rev_color",		TYPE_COLOR,	{ &conf.rev_color } },
116 	{ "rev_bgcolor",	TYPE_COLOR,	{ &conf.rev_bgcolor } },
117 	{ "rev_font",		TYPE_FONT,	{ &conf.rev_font.gdfont } },
118 	{ "rev_ttfont",		TYPE_STRING,	{ &conf.rev_font.ttfont } },
119 	{ "rev_ttsize",		TYPE_DOUBLE,	{ &conf.rev_font.ttsize } },
120 	{ "rev_separator",	TYPE_NUMBER,	{ &conf.rev_separator } },
121 	{ "rev_minline",	TYPE_NUMBER,	{ &conf.rev_minline } },
122 	{ "rev_maxline",	TYPE_NUMBER,	{ &conf.rev_maxline } },
123 	{ "rev_lspace",		TYPE_NUMBER,	{ &conf.rev_lspace } },
124 	{ "rev_rspace",		TYPE_NUMBER,	{ &conf.rev_rspace } },
125 	{ "rev_tspace",		TYPE_NUMBER,	{ &conf.rev_tspace } },
126 	{ "rev_bspace",		TYPE_NUMBER,	{ &conf.rev_bspace } },
127 	{ "rev_text",		TYPE_CSTRING,	{ &conf.rev_text } },
128 	{ "rev_idtext",		TYPE_CSTRING,	{ &conf.rev_idtext } },
129 	{ "rev_text_color",	TYPE_COLOR,	{ &conf.rev_text_color } },
130 	{ "rev_text_font",	TYPE_FONT,	{ &conf.rev_text_font.gdfont } },
131 	{ "rev_text_ttfont",	TYPE_STRING,	{ &conf.rev_text_font.ttfont } },
132 	{ "rev_text_ttsize",	TYPE_DOUBLE,	{ &conf.rev_text_font.ttsize } },
133 	{ "rev_maxtags",	TYPE_NUMBER,	{ &conf.rev_maxtags } },
134 	{ "merge_color",	TYPE_COLORLIST,	{ &conf.merge_color } },
135 	{ "merge_from",		TYPE_STRINGLIST,	{ &conf.merge_from } },
136 	{ "merge_to",		TYPE_STRINGLIST,	{ &conf.merge_to } },
137 	{ "merge_findall",	TYPE_BOOLEAN,	{ &conf.merge_findall } },
138 	{ "merge_front",	TYPE_BOOLEAN,	{ &conf.merge_front } },
139 	{ "merge_nocase",	TYPE_BOOLEAN,	{ &conf.merge_nocase } },
140 	{ "merge_arrows",	TYPE_BOOLEAN,	{ &conf.merge_arrows } },
141 	{ "merge_cvsnt",	TYPE_BOOLEAN,	{ &conf.merge_cvsnt } },
142 	{ "merge_cvsnt_color",	TYPE_COLOR,	{ &conf.merge_cvsnt_color } },
143 	{ "merge_on_tag",	TYPE_BOOLEAN,	{ &conf.merge_on_tag } },
144 	{ "arrow_width",	TYPE_NUMBER,	{ &conf.arrow_width } },
145 	{ "arrow_length",	TYPE_NUMBER,	{ &conf.arrow_length } },
146 	{ "tag_color",		TYPE_COLOR,	{ &conf.tag_color } },
147 	{ "tag_font",		TYPE_FONT,	{ &conf.tag_font.gdfont } },
148 	{ "tag_ttfont",		TYPE_STRING,	{ &conf.tag_font.ttfont } },
149 	{ "tag_ttsize",		TYPE_DOUBLE,	{ &conf.tag_font.ttsize } },
150 	{ "tag_ignore",		TYPE_STRING,	{ &conf.tag_ignore } },
151 	{ "tag_ignore_merge",	TYPE_BOOLEAN,	{ &conf.tag_ignore_merge } },
152 	{ "tag_nocase",		TYPE_BOOLEAN,	{ &conf.tag_nocase } },
153 	{ "tag_negate",		TYPE_BOOLEAN,	{ &conf.tag_negate } },
154 	{ "title",		TYPE_STRING,	{ &conf.title } },
155 	{ "title_x",		TYPE_NUMBER,	{ &conf.title_x } },
156 	{ "title_y",		TYPE_NUMBER,	{ &conf.title_y } },
157 	{ "title_font",		TYPE_FONT,	{ &conf.title_font.gdfont } },
158 	{ "title_ttfont",	TYPE_STRING,	{ &conf.title_font.ttfont } },
159 	{ "title_ttsize",	TYPE_DOUBLE,	{ &conf.title_font.ttsize } },
160 	{ "title_align",	TYPE_NUMBER,	{ &conf.title_align } },
161 	{ "title_color",	TYPE_COLOR,	{ &conf.title_color } },
162 	{ "margin_top",		TYPE_NUMBER,	{ &conf.margin_top } },
163 	{ "margin_bottom",	TYPE_NUMBER,	{ &conf.margin_bottom } },
164 	{ "margin_left",	TYPE_NUMBER,	{ &conf.margin_left } },
165 	{ "margin_right",	TYPE_NUMBER,	{ &conf.margin_right } },
166 	{ "image_type",		TYPE_NUMBER,	{ &conf.image_type } },
167 	{ "image_quality",	TYPE_NUMBER,	{ &conf.image_quality } },
168 	{ "image_compress",	TYPE_NUMBER,	{ &conf.image_compress } },
169 	{ "image_interlace",	TYPE_BOOLEAN,	{ &conf.image_interlace } },
170 	{ "map_name",		TYPE_STRING,	{ &conf.map_name } },
171 	{ "map_branch_href",	TYPE_STRING,	{ &conf.map_branch_href } },
172 	{ "map_branch_alt",	TYPE_STRING,	{ &conf.map_branch_alt } },
173 	{ "map_rev_href",	TYPE_STRING,	{ &conf.map_rev_href } },
174 	{ "map_rev_alt",	TYPE_STRING,	{ &conf.map_rev_alt } },
175 	{ "map_diff_href",	TYPE_STRING,	{ &conf.map_diff_href } },
176 	{ "map_diff_alt",	TYPE_STRING,	{ &conf.map_diff_alt } },
177 	{ "map_merge_href",	TYPE_STRING,	{ &conf.map_merge_href } },
178 	{ "map_merge_alt",	TYPE_STRING,	{ &conf.map_merge_alt } },
179 	{ "jpeg",		TYPE_VALUE,	{ val: IMAGE_JPEG } },
180 	{ "png",		TYPE_VALUE,	{ val: IMAGE_PNG } },
181 	{ "gif",		TYPE_VALUE,	{ val: IMAGE_GIF } },
182 	{ "true",		TYPE_VALUE,	{ val: 1 } },
183 	{ "false",		TYPE_VALUE,	{ val: 0 } },
184 	{ "not",		TYPE_VALUE,	{ val: -1 } },
185 	{ "left",		TYPE_VALUE,	{ val: 0 } },
186 	{ "center",		TYPE_VALUE,	{ val: 1 } },
187 	{ "right",		TYPE_VALUE,	{ val: 2 } },
188 	{ "tiny",		TYPE_VALUE,	{ val: 0 } },
189 	{ "small",		TYPE_VALUE,	{ val: 1 } },
190 	{ "medium",		TYPE_VALUE,	{ val: 2 } },
191 	{ "large",		TYPE_VALUE,	{ val: 3 } },
192 	{ "giant",		TYPE_VALUE,	{ val: 4 } },
193 	{ "HTML3",		TYPE_VALUE,	{ val: 1 } },
194 	{ "HTML4",		TYPE_VALUE,	{ val: 2 } },
195 	{ "XHTML",		TYPE_VALUE,	{ val: 3 } },
196 };
197 
198 #define NKEYWORDS	(sizeof(keywords) / sizeof(keywords[0]))
199 
cmp_kw(const void * k1,const void * k2)200 static int cmp_kw(const void *k1, const void *k2)
201 {
202 	return strcmp(((keyword_t *)k1)->keyword, ((keyword_t *)k2)->keyword);
203 }
204 
205 /*
206  **************************************************************************
207  * Debug routines
208  **************************************************************************
209  */
210 #ifdef DEBUG
211 #define DEBUGSTREAM stdout
debug_pname(const char * n)212 static void debug_pname(const char *n)
213 {
214 	fprintf(DEBUGSTREAM, "%-16s: ", n);
215 }
216 
debug_pstring(const char * n,const char * a)217 static void debug_pstring(const char *n, const char *a)
218 {
219 	debug_pname(n);
220 	if(!a)
221 		fprintf(DEBUGSTREAM, "<not-set>\n");
222 	else
223 	{
224 		fputc('\'', DEBUGSTREAM);
225 		for(; *a; a++)
226 		{
227 			if(isprint(*a))
228 				fputc(*a, DEBUGSTREAM);
229 			else
230 			{
231 				fputc('\\', DEBUGSTREAM);
232 				switch(*a)
233 				{
234 				case '\a': fputc('a', DEBUGSTREAM); break;
235 				case '\b': fputc('b', DEBUGSTREAM); break;
236 				case '\f': fputc('f', DEBUGSTREAM); break;
237 				case '\n': fputc('n', DEBUGSTREAM); break;
238 				case '\r': fputc('r', DEBUGSTREAM); break;
239 				case '\t': fputc('t', DEBUGSTREAM); break;
240 				case '\v': fputc('v', DEBUGSTREAM); break;
241 				default:
242 					fprintf(DEBUGSTREAM, "x%02x", (unsigned char)*a);
243 				}
244 			}
245 		}
246 		fprintf(DEBUGSTREAM, "'\n");
247 	}
248 }
249 
debug_pbool(const char * n,int b)250 static void debug_pbool(const char *n, int b)
251 {
252 	debug_pname(n);
253 	fprintf(DEBUGSTREAM, "%s\n", b ? "true" : "false");
254 }
255 
debug_pint(const char * n,int i)256 static void debug_pint(const char *n, int i)
257 {
258 	debug_pname(n);
259 	fprintf(DEBUGSTREAM, "%i\n", i);
260 }
261 
debug_pdouble(const char * n,double d)262 static void debug_pdouble(const char *n, double d)
263 {
264 	debug_pname(n);
265 	fprintf(DEBUGSTREAM, "%g\n", d);
266 }
267 
debug_pfont(const char * n,gdFontPtr f)268 static void debug_pfont(const char *n, gdFontPtr f)
269 {
270 	const char *s = "<Unknown font>";
271 	debug_pname(n);
272 	if(f == gdFontTiny)
273 		s = "gdFontTiny";
274 	else if(f == gdFontSmall)
275 		s = "gdFontSmall";
276 	else if(f == gdFontMediumBold)
277 		s = "gdFontMediumBold";
278 	else if(f == gdFontLarge)
279 		s = "gdFontLarge";
280 	else if(f == gdFontGiant)
281 		s = "gdFontGiant";
282 	fprintf(DEBUGSTREAM, "%s\n", s);
283 }
284 
285 static char *debug_op[] = { "=~", "=*", "!~", "!*", "==", "!_", ">=", ">", "<=", "<" };
286 
debug_pcolornode(node_t * n)287 static void debug_pcolornode(node_t *n)
288 {
289 	if(!n)
290 		return;
291 	if(n->op == TYPE_STRING)
292 		fprintf(DEBUGSTREAM, "%s ", n->value.str);
293 	else if(n->op == TYPE_COLOR)
294 		fprintf(DEBUGSTREAM, "#%02x%02x%02x ", n->value.clr.r, n->value.clr.g, n->value.clr.b);
295 	else
296 	{
297 		char *key;
298 		fprintf(DEBUGSTREAM, "[ ");
299 		switch(n->key)
300 		{
301 		case KEY_STATE:		key = "state"; break;
302 		case KEY_AUTHOR:	key = "author"; break;
303 		case KEY_TAG:		key = "tag"; break;
304 		case KEY_DATE:		key = "date"; break;
305 		case KEY_REV:		key = "rev"; break;
306 		case KEY_UNKNOWN:	key = "unknown"; break;
307 		default:		key = "<undefined>"; break;
308 		}
309 		fprintf(DEBUGSTREAM, "%s ", key);
310 		if(n->op > OP_FIRST && n->op < OP_LAST)
311 			fprintf(DEBUGSTREAM, "%s ", debug_op[n->op - OP_FIRST - 1]);
312 		else
313 			fprintf(DEBUGSTREAM, "op(-?-) ");
314 		fprintf(DEBUGSTREAM, "%s ", n->content);
315 		debug_pcolornode(n->tcase);
316 		debug_pcolornode(n->fcase);
317 		fprintf(DEBUGSTREAM, "]");
318 	}
319 }
320 
debug_pcolor(const char * n,color_t * c)321 static void debug_pcolor(const char *n, color_t *c)
322 {
323 	debug_pname(n);
324 	if(c->node)
325 	{
326 		debug_pcolornode(c->node);
327 		fprintf(DEBUGSTREAM, "\n");
328 	}
329 	else
330 		fprintf(DEBUGSTREAM, "#%02x%02x%02x\n", c->r, c->g, c->b);
331 }
332 
debug_pcolorlist(const char * n,colorlist_t * cl)333 static void debug_pcolorlist(const char *n, colorlist_t *cl)
334 {
335 	int i;
336 	char buf[128];
337 	for(i = 0; i < cl->n; i++)
338 	{
339 		sprintf(buf, "%s{%d}", n, i);
340 		debug_pcolor(buf, &cl->clrs[i]);
341 	}
342 }
343 
debug_pstringlist(const char * n,stringlist_t * sl)344 static void debug_pstringlist(const char *n, stringlist_t *sl)
345 {
346 	int i;
347 	char buf[128];
348 	for(i = 0; i < sl->n; i++)
349 	{
350 		sprintf(buf, "%s{%d}", n, i);
351 		debug_pstring(buf, sl->strs[i]);
352 	}
353 }
354 
dump_config(void)355 void dump_config(void)
356 {
357 	debug_pstring("cvsroot", conf.cvsroot);
358 	debug_pstring("cvsmodule", conf.cvsmodule);
359 	debug_pstring("date_format", conf.date_format);
360 
361 	debug_pcolor("color_bg", &conf.color_bg);
362 	debug_pbool("box_shadow", conf.box_shadow);
363 	debug_pbool("upside_down", conf.upside_down);
364 	debug_pbool("left_right", conf.left_right);
365 	debug_pbool("strip_untagged", conf.strip_untagged);
366 	debug_pbool("strip_first_rev", conf.strip_first_rev);
367 	debug_pbool("auto_stretch", conf.auto_stretch);
368 	debug_pbool("anti_alias", conf.anti_alias);
369 	debug_pbool("use_ttf", conf.use_ttf);
370 	debug_pint("thick_lines", conf.thick_lines);
371 	debug_pint("html_level", conf.html_level);
372 	debug_pbool("parse_logs", conf.parse_logs);
373 	debug_pbool("transparent_bg", conf.transparent_bg);
374 
375 	debug_pint("arrow_length", conf.arrow_length);
376 	debug_pint("arrow_width", conf.arrow_width);
377 
378 	debug_pbool("merge_arrows", conf.merge_arrows);
379 	debug_pcolor("merge_cvsnt_color", &conf.merge_cvsnt_color);
380 	debug_pbool("merge_cvsnt", conf.merge_cvsnt);
381 	debug_pbool("merge_findall", conf.merge_findall);
382 	debug_pcolorlist("merge_color", &conf.merge_color);
383 	debug_pstringlist("merge_from", &conf.merge_from);
384 	debug_pstringlist("merge_to", &conf.merge_to);
385 	debug_pbool("merge_front", conf.merge_front);
386 	debug_pbool("merge_nocase", conf.merge_nocase);
387 	debug_pbool("merge_on_tag", conf.merge_on_tag);
388 
389 	debug_pcolor("msg_color", &conf.msg_color);
390 	debug_pfont("msg_font", conf.msg_font.gdfont);
391 	debug_pstring("msg_ttfont", conf.msg_font.ttfont);
392 	debug_pdouble("msg_ttsize", conf.msg_font.ttsize);
393 
394 	debug_pfont("tag_font", conf.tag_font.gdfont);
395 	debug_pstring("tag_ttfont", conf.tag_font.ttfont);
396 	debug_pdouble("tag_ttsize", conf.tag_font.ttsize);
397 	debug_pcolor("tag_color", &conf.tag_color);
398 	debug_pbool("tag_ignore_merge", conf.tag_ignore_merge);
399 	debug_pstring("tag_ignore", conf.tag_ignore);
400 	debug_pbool("tag_negate", conf.tag_negate);
401 	debug_pbool("tag_nocase", conf.tag_nocase);
402 
403 	debug_pfont("rev_font", conf.rev_font.gdfont);
404 	debug_pstring("rev_ttfont", conf.rev_font.ttfont);
405 	debug_pdouble("rev_ttsize", conf.rev_font.ttsize);
406 	debug_pcolor("rev_color", &conf.rev_color);
407 	debug_pcolor("rev_bgcolor", &conf.rev_bgcolor);
408 	debug_pint("rev_separator", conf.rev_separator);
409 	debug_pint("rev_minline", conf.rev_minline);
410 	debug_pint("rev_maxline", conf.rev_maxline);
411 	debug_pint("rev_maxtags", conf.rev_maxtags);
412 	debug_pint("rev_lspace", conf.rev_lspace);
413 	debug_pint("rev_rspace", conf.rev_rspace);
414 	debug_pint("rev_tspace", conf.rev_tspace);
415 	debug_pint("rev_bspace", conf.rev_bspace);
416 	debug_pcstring("rev_text", conf.rev_text);
417 	debug_pcolor("rev_text_color", &conf.rev_text_color);
418 	debug_pfont("rev_text_font", conf.rev_text_font.gdfont);
419 	debug_pstring("rev_text_ttfont", conf.rev_text_font.ttfont);
420 	debug_pdouble("rev_text_ttsize", conf.rev_text_font.ttsize);
421 	debug_pbool("rev_hidenumber", conf.rev_hidenumber);
422 
423 	debug_pfont("branch_font", conf.branch_font.gdfont);
424 	debug_pstring("branch_ttfont", conf.branch_font.ttfont);
425 	debug_pdouble("branch_ttsize", conf.branch_font.ttsize);
426 	debug_pcolor("branch_color", &conf.branch_color);
427 	debug_pfont("branch_tag_font", conf.branch_tag_font.gdfont);
428 	debug_pstring("branch_tag_ttfont", conf.branch_tag_font.ttfont);
429 	debug_pdouble("branch_tag_ttsize", conf.branch_tag_font.ttsize);
430 	debug_pcolor("branch_tag_color", &conf.branch_tag_color);
431 	debug_pcolor("branch_bgcolor", &conf.branch_bgcolor);
432 	debug_pint("branch_lspace", conf.branch_lspace);
433 	debug_pint("branch_rspace", conf.branch_rspace);
434 	debug_pint("branch_tspace", conf.branch_tspace);
435 	debug_pint("branch_bspace", conf.branch_bspace);
436 	debug_pint("branch_connect", conf.branch_connect);
437 	debug_pint("branch_margin", conf.branch_margin);
438 	debug_pint("branch_dupbox", conf.branch_dupbox);
439 	debug_pbool("branch_fold", conf.branch_fold);
440 	debug_pbool("branch_foldall", conf.branch_foldall);
441 	debug_pbool("branch_resort", conf.branch_resort);
442 	debug_pstring("branch_subtree", conf.branch_subtree);
443 
444 	debug_pstring("title", conf.title);
445 	debug_pint("title_x", conf.title_x);
446 	debug_pint("title_y", conf.title_y);
447 	debug_pfont("title_font", conf.title_font.gdfont);
448 	debug_pstring("title_ttfont", conf.title_font.ttfont);
449 	debug_pdouble("title_ttsize", conf.title_font.ttsize);
450 	debug_pint("title_align", conf.title_align);
451 	debug_pcolor("title_color", &conf.title_color);
452 
453 	debug_pint("margin_top", conf.margin_top);
454 	debug_pint("margin_bottom", conf.margin_bottom);
455 	debug_pint("margin_left", conf.margin_left);
456 	debug_pint("margin_right", conf.margin_right);
457 
458 	debug_pint("image_type", conf.image_type);
459 	debug_pint("image_quality", conf.image_quality);
460 	debug_pint("image_compress", conf.image_compress);
461 	debug_pbool("image_interlace", conf.image_interlace);
462 
463 	debug_pstring("map_name", conf.map_name);
464 	debug_pstring("map_branch_href", conf.map_branch_href);
465 	debug_pstring("map_branch_alt", conf.map_branch_alt);
466 	debug_pstring("map_rev_href", conf.map_rev_href);
467 	debug_pstring("map_rev_alt", conf.map_rev_alt);
468 	debug_pstring("map_diff_href", conf.map_diff_href);
469 	debug_pstring("map_diff_alt", conf.map_diff_alt);
470 	debug_pstring("map_merge_href", conf.map_merge_href);
471 	debug_pstring("map_merge_alt", conf.map_merge_alt);
472 
473 	debug_pstring("expand[0]", conf.expand[0]);
474 	debug_pstring("expand[1]", conf.expand[1]);
475 	debug_pstring("expand[2]", conf.expand[2]);
476 	debug_pstring("expand[3]", conf.expand[3]);
477 	debug_pstring("expand[4]", conf.expand[4]);
478 	debug_pstring("expand[5]", conf.expand[5]);
479 	debug_pstring("expand[6]", conf.expand[6]);
480 	debug_pstring("expand[7]", conf.expand[7]);
481 	debug_pstring("expand[8]", conf.expand[8]);
482 	debug_pstring("expand[9]", conf.expand[9]);
483 }
484 #endif
485 
486 /*
487  **************************************************************************
488  * String collection routines
489  **************************************************************************
490  */
491 #define STRALLOCSIZE	128
492 static char *str;
493 static int nstr;
494 static int nastr;
495 
reset_str(void)496 static void reset_str(void)
497 {
498 	nstr = 0;
499 }
500 
add_str(int c)501 static void add_str(int c)
502 {
503 	if(nstr + 1 + 1 > nastr)
504 	{
505 		str = xrealloc(str, nastr+STRALLOCSIZE);
506 		nastr += STRALLOCSIZE;
507 	}
508 	str[nstr++] = c;
509 }
510 
get_str(void)511 static char *get_str(void)
512 {
513 	if(!str)
514 		return xstrdup("");
515 
516 	str[nstr] = '\0';
517 	return xstrdup(str);
518 }
519 
520 /*
521  **************************************************************************
522  * Input routines
523  **************************************************************************
524  */
525 static char *buf = NULL;
526 static int bufsize = 0;
527 static int bufalloc = 0;
528 static int bufpos = 0;
529 static int bufunput = -1;
530 static FILE *buffp;
531 
set_input(FILE * fp,char * s)532 static void set_input(FILE *fp, char *s)
533 {
534 	assert((fp == NULL && s != NULL) || (fp != NULL && s == NULL));
535 	buffp = fp;
536 	bufsize = bufpos = 0;
537 	if(s)
538 	{
539 		if(!buf)
540 		{
541 			bufalloc = 8192;
542 			buf = xmalloc(bufalloc * sizeof(*buf));
543 		}
544 		bufsize = strlen(s);
545 		assert(bufsize < bufalloc);
546 		strcpy(buf, s);
547 	}
548 }
549 
get_input(void)550 static int get_input(void)
551 {
552 	if(bufunput != -1)
553 	{
554 		int c = bufunput;
555 		bufunput = -1;
556 		return c;
557 	}
558 
559 	if(bufpos < bufsize)
560 	{
561 		assert(buf != NULL);
562 retry_input:
563 		return (int)((unsigned char)buf[bufpos++]);
564 	}
565 
566 	if(!buf)
567 	{
568 		bufalloc = 8192;
569 		buf = xmalloc(bufalloc * sizeof(*buf));
570 		bufsize = bufpos = 0;
571 	}
572 	if(buffp)
573 	{
574 		bufsize = fread(buf, 1, bufalloc, buffp);
575 		bufpos = 0;
576 		if(!bufsize)
577 			return EOF;
578 		goto retry_input;
579 	}
580 	return EOF;
581 }
582 
unget_input(int c)583 static void unget_input(int c)
584 {
585 	bufunput = c;
586 }
587 
588 /*
589  **************************************************************************
590  * Lexical scanner
591  **************************************************************************
592  */
config_lex(void)593 static int config_lex(void)
594 {
595 	int ch;
596 	while(1)
597 	{
598 		ch = get_input();
599 		if(ch == '\n')
600 			line_number++;
601 
602 		if(isspace(ch))
603 			continue;
604 
605 		switch(ch)
606 		{
607 		case '=':
608 			ch = get_input();
609 			switch(ch)
610 			{
611 			case '~': return OP_CONTAINED;
612 			case '*': return OP_CONTAINEDI;
613 			case '=': return OP_EQ;
614 			default:
615 				unget_input(ch);
616 				return '=';
617 			}
618 			break;
619 
620 		case '!':
621 			ch = get_input();
622 			switch(ch)
623 			{
624 			case '~': return OP_NCONTAINED;
625 			case '*': return OP_NCONTAINEDI;
626 			case '=': return OP_NE;
627 			default:
628 				stack_msg(MSG_ERR, "config: %d: Invalid operator", line_number);
629 				unget_input(ch);
630 				return '!';
631 			}
632 			break;
633 
634 		case '>':
635 			ch = get_input();
636 			if(ch == '=')
637 				return OP_GE;
638 			unget_input(ch);
639 			return OP_GT;
640 
641 		case '<':
642 			ch = get_input();
643 			if(ch == '=')
644 				return OP_LE;
645 			unget_input(ch);
646 			return OP_LT;
647 
648 		case EOF:
649 		case ';':
650 		case '[':
651 		case ']':
652 			return ch;
653 
654 		case '#':	/* Comment */
655 			while((ch = get_input()) != '\n' && ch != EOF)
656 				;
657 			if(ch != EOF)
658 				unget_input(ch);
659 			break;
660 
661 		case '"':
662 			reset_str();
663 			while(1)
664 			{
665 				char c[4];
666 				ch = get_input();
667 				switch(ch)
668 				{
669 				case '\\': /* Start an escape sequence */
670 					switch(ch = get_input())
671 					{
672 					default: /* This includes '\\', '"' and embedded newlines */
673 						add_str(ch);
674 						break;
675 					case 'a': add_str('\a'); break;
676 					case 'b': add_str('\b'); break;
677 					case 'f': add_str('\f'); break;
678 					case 'n': add_str('\n'); break;
679 					case 'r': add_str('\r'); break;
680 					case 't': add_str('\t'); break;
681 					case 'v': add_str('\v'); break;
682 					case 'x':
683 					case 'X': /* Hex escape */
684 						c[0] = get_input();
685 						c[1] = get_input();
686 						c[2] = '\0';
687 						if(!isxdigit((int)(unsigned char)c[0]) || !isxdigit((int)(unsigned char)c[1]))
688 							stack_msg(MSG_ERR, "config: %d: Invalid hex escape", line_number);
689 						add_str((int)strtol(c, NULL, 16));
690 						break;
691 					case '0':
692 					case '1':
693 					case '2': /* Octal escape */
694 						c[0] = ch;
695 						c[1] = c[2] = c[3] = '\0';
696 						if((ch = get_input()) >= '0' && ch <= '7')
697 							c[1] = ch;
698 						else
699 							unget_input(ch);
700 						if((ch = get_input()) >= '0' && ch <= '7')
701 							c[2] = ch;
702 						else
703 							unget_input(ch);
704 						add_str((int)strtol(c, NULL, 8));
705 						break;
706 					case EOF:
707 						yyerror("Unexpected EOF in escape");
708 						break;
709 					}
710 					break;
711 				case '"':
712 					yylval.str = get_str();
713 					return TYPE_STRING;
714 				case '\n':
715 					yyerror("Newline in string");
716 					break;
717 				case EOF:
718 					yyerror("Unexpected EOF in string");
719 					break;
720 				default:
721 					add_str(ch);
722 					break;
723 				}
724 			}
725 			break;
726 
727 		default:
728 			if(isalpha(ch) || ch == '_')
729 			{
730 				keyword_t skw;
731 				keyword_t *kw;
732 				/* Collect keyword */
733 				reset_str();
734 				add_str(ch);
735 				while(1)
736 				{
737 					ch = get_input();
738 					if(isalnum(ch) || ch == '_')
739 						add_str(ch);
740 					else
741 					{
742 						unget_input(ch);
743 						break;
744 					}
745 				}
746 				skw.keyword = get_str();
747 				kw = bsearch(&skw, keywords, NKEYWORDS, sizeof(keywords[0]), cmp_kw);
748 				if(!kw)
749 				{
750 					stack_msg(MSG_ERR, "config: %d: Unknown keyword '%s'", line_number, skw.keyword);
751 					yylval.kw = NULL;
752 					return TYPE_KEYWORD;
753 				}
754 				xfree((void *)skw.keyword);
755 				if(kw->type == TYPE_VALUE)
756 				{
757 					yylval.i = (int)kw->confref.val;
758 					return TYPE_NUMBER;
759 				}
760 				yylval.kw = kw;
761 				return TYPE_KEYWORD;
762 			}
763 			else if(isdigit(ch) || ch == '+' || ch == '-')
764 			{
765 				char *s;
766 				char *eptr;
767 				int type = TYPE_NUMBER;
768 				/* Collect number */
769 				reset_str();
770 				add_str(ch);
771 				while(1)
772 				{
773 					ch = get_input();
774 					if(isxdigit(ch) || ch == 'x' || ch == 'X' || ch == '.') /* Not exact, but close enough */
775 						add_str(ch);
776 					else
777 					{
778 						unget_input(ch);
779 						break;
780 					}
781 					if(ch == '.')
782 						type = TYPE_DOUBLE;
783 				}
784 				s = get_str();
785 				if(type == TYPE_DOUBLE)
786 				{
787 					yylval.d = strtod(s, &eptr);
788 					if(*eptr)
789 						stack_msg(MSG_ERR, "config: %d: Invalid floating point number", line_number);
790 				}
791 				else
792 				{
793 					yylval.i = strtol(s, &eptr, 0);
794 					if(*eptr)
795 						stack_msg(MSG_ERR, "config: %d: Invalid number", line_number);
796 				}
797 				xfree(s);
798 				return type;
799 			}
800 			else
801 				yyerror("Unmatched text '%c' (0x%02x)", isprint(ch) ? ch : ' ', ch);
802 			break;
803 		}
804 	}
805 }
806 
set_color(color_t * c,char * s)807 static void set_color(color_t *c, char *s)
808 {
809 	char *cptr;
810 	c->node = NULL;
811 	if(*s != '#' || strlen(s) != 7)
812 	{
813 colorerror:
814 		stack_msg(MSG_ERR, "config: %d: Invalid color value '%s'", line_number, s);
815 		return;
816 	}
817 	c->b = strtol(s+5, &cptr, 16);
818 	if(*cptr)
819 		goto colorerror;
820 	s[5] = '\0';
821 	c->g = strtol(s+3, &cptr, 16);
822 	if(*cptr)
823 		goto colorerror;
824 	s[3] = '\0';
825 	c->r = strtol(s+1, &cptr, 16);
826 	if(*cptr)
827 		goto colorerror;
828 }
829 
get_font(int id)830 static gdFontPtr get_font(int id)
831 {
832 	switch(id)
833 	{
834 	case 0:	return gdFontTiny;
835 	case 1:	return gdFontSmall;
836 	default:
837 	case 2:	return gdFontMediumBold;
838 	case 3:	return gdFontLarge;
839 	case 4:	return gdFontGiant;
840 	}
841 }
842 
843 /*
844  **************************************************************************
845  * The config parser
846  * Grammar:
847  *	file	: <Empty>
848  *		| lines
849  *		;
850  *
851  *	lines	: line
852  *		| lines line
853  *		;
854  *
855  *	line	: <keyword> '=' <value> ';'
856  *		| <keyword> '=' expr ';'
857  *		| ';'
858  *
859  *	expr	: '[' <kw> <op> <value> expr expr ']'
860  *		| <value>
861  *		;
862  **************************************************************************
863  */
skip_to_semicolon(int state)864 static int skip_to_semicolon(int state)
865 {
866 	int token;
867 	while(1)
868 	{
869 		token = config_lex();
870 		if(token == ';')
871 			return 0;
872 		else if(token == EOF)
873 			return state;
874 	}
875 }
876 
new_node(char * val)877 static node_t *new_node(char *val)
878 {
879 	node_t *n = xmalloc(sizeof(*n));
880 	if(!strcmp(val, "state"))
881 		n->key = KEY_STATE;
882 	else if(!strcmp(val, "author"))
883 		n->key = KEY_AUTHOR;
884 	else if(!strcmp(val, "tag"))
885 		n->key = KEY_TAG;
886 	else if(!strcmp(val, "date"))
887 		n->key = KEY_DATE;
888 	else if(!strcmp(val, "rev"))
889 		n->key = KEY_REV;
890 	else
891 	{
892 		n->key = KEY_UNKNOWN;
893 		stack_msg(MSG_ERR, "config: %d: Unknown key '%s'", line_number, val);
894 	}
895 	return n;
896 }
897 
898 typedef struct __statestack_t
899 {
900 	int	state;
901 	node_t	*node;
902 } statestack_t;
903 
904 static int nstatestack = 0;
905 static int nastatestack;
906 static statestack_t *statestack = NULL;
907 
push_state(node_t * node,int state)908 static void push_state(node_t *node, int state)
909 {
910 	if(!statestack)
911 	{
912 		statestack = xmalloc(4 * sizeof(*statestack));
913 		nastatestack = 4;
914 		nstatestack = 0;
915 	}
916 	else if(nstatestack >= nastatestack)
917 	{
918 		nastatestack *= 2;
919 		statestack = xrealloc(statestack, nastatestack * sizeof(*statestack));
920 	}
921 	statestack[nstatestack].node = node;
922 	statestack[nstatestack].state = state;
923 	nstatestack++;
924 }
925 
pop_state(node_t ** node,int * state)926 static int pop_state(node_t **node, int *state)
927 {
928 	if(nstatestack <= 0)
929 	{
930 		*state = 3;
931 		return 0;
932 	}
933 	assert(*node != NULL);
934 	if(statestack[nstatestack-1].state == 8)
935 		statestack[nstatestack-1].node->tcase = *node;
936 	else if(statestack[nstatestack-1].state == 9)
937 		statestack[nstatestack-1].node->fcase = *node;
938 	*state = statestack[nstatestack-1].state;
939 	*node = statestack[nstatestack-1].node;
940 	return nstatestack--;
941 }
942 
943 /* YYYY.MM.DD.hh.mm.ss */
944 /* 0123456789012345678 */
945 /*           111111111 */
fixup_date(const char * str)946 static char *fixup_date(const char *str)
947 {
948 	int i;
949 	int y=1970, m=1, d=1, h=0, mi=0, s=0;
950 	int l = strlen(str);
951 	char date[6*16]; /* Should be wildly enough to hold 19 chars from 6 numbers with all possible errors */
952 	if(l < 4 || l > 19)
953 	{
954 date_err:
955 		stack_msg(MSG_ERR, "config: %d: Invalid date string '%s'", line_number, str);
956 		return "1970.01.01.00.00.00";
957 	}
958 	for(i = 0; i < l; i++)
959 	{
960 		if(!strchr("0123456789.", str[i]))
961 			goto date_err;
962 	}
963 	i = sscanf(str, "%d.%d.%d.%d.%d.%d", &y, &m, &d, &h, &mi, &s);
964 	if(i == EOF || i < 0 || i > 6)
965 		goto date_err;
966 	if(i >= 1 && (y < 1970 || y > 2037))
967 		goto date_err;
968 	if(i >= 2 && (m < 1 || m > 12))
969 		goto date_err;
970 	if(i >= 3 && (d < 1 || d > 31))
971 		goto date_err;
972 	if(i >= 4 && (h < 0 || h > 23))
973 		goto date_err;
974 	if(i >= 5 && (mi < 0 || mi > 59))
975 		goto date_err;
976 	if(i >= 6 && (s < 0 || s > 59))
977 		goto date_err;
978 	sprintf(date, "%04d.%02d.%02d.%02d.%02d.%02d", y, m, d, h, mi, s);
979 	return strdup(date);
980 }
981 
config_parse(void)982 static void config_parse(void)
983 {
984 	int state = 0;
985 	int token;
986 	int t;
987 	keyword_t *kw = NULL;
988 	node_t *node = NULL;
989 
990 	while(1)
991 	{
992 		token = config_lex();
993 		if(token == EOF)
994 		{
995 			if(state)
996 				stack_msg(MSG_ERR, "config: %d: Unexpected EOF", line_number);
997 			break;
998 		}
999 
1000 		switch(state)
1001 		{
1002 		case 0:
1003 			if(token == TYPE_KEYWORD)
1004 			{
1005 				kw = yylval.kw;
1006 				state = 1;
1007 			}
1008 			else if(token != ';')
1009 				stack_msg(MSG_ERR, "config: %d: Keyword expected", line_number);
1010 			break;
1011 		case 1:
1012 			if(token != '=')
1013 			{
1014 				stack_msg(MSG_ERR, "config: %d: '=' expected", line_number);
1015 				state = skip_to_semicolon(state);
1016 				break;
1017 			}
1018 			else
1019 				state = 2;
1020 			break;
1021 		case 2:
1022 			if(!kw)
1023 			{
1024 				/* Error recovery of failed keyword */
1025 				state = 3;
1026 				break;
1027 			}
1028 			if(token == '[')
1029 			{
1030 				if(kw->type != TYPE_COLOR && kw->type != TYPE_CSTRING)
1031 				{
1032 					stack_msg(MSG_ERR, "config: %d: Conditional expression not allowed for keyword", line_number);
1033 					state = skip_to_semicolon(state);
1034 					break;
1035 				}
1036 				state = 4;
1037 				break;
1038 			}
1039 			if(kw->type == TYPE_FONT || kw->type == TYPE_BOOLEAN)
1040 				t = TYPE_NUMBER;
1041 			else if(kw->type == TYPE_COLOR || kw->type == TYPE_COLORLIST || kw->type == TYPE_STRINGLIST || kw->type == TYPE_CSTRING)
1042 				t = TYPE_STRING;
1043 			else
1044 				t = kw->type;
1045 
1046 			if(token == TYPE_NUMBER && kw->type == TYPE_DOUBLE)
1047 			{
1048 				/* Auto promote numbers to doubles if required */
1049 				yylval.d = (double)yylval.i;
1050 				token = TYPE_DOUBLE;
1051 			}
1052 
1053 			if(token != t)
1054 			{
1055 				char *e;
1056 				switch(kw->type)
1057 				{
1058 				case TYPE_STRING:	e = "String"; yylval.str = xstrdup("error recovery"); break;
1059 				case TYPE_STRINGLIST:	e = "StringL"; yylval.str = xstrdup("error recovery"); break;
1060 				case TYPE_CSTRING:	e = "CString"; yylval.str = xstrdup("error recovery"); break;
1061 				case TYPE_NUMBER:	e = "Number"; yylval.i = 0; break;
1062 				case TYPE_COLOR:	e = "Color"; yylval.str = xstrdup("#123456"); break;
1063 				case TYPE_COLORLIST:	e = "ColorL"; yylval.str = xstrdup("#123456"); break;
1064 				case TYPE_FONT:		e = "Font"; yylval.i = 0; break;
1065 				case TYPE_BOOLEAN:	e = "Boolean"; yylval.i = 0; break;
1066 				case TYPE_DOUBLE:	e = "Double"; yylval.d = 0.0; break;
1067 				default:		e = "Internal error: Unknown type"; yylval.i = 0; break;
1068 				}
1069 				stack_msg(MSG_ERR, "config: %d: %s expected", line_number, e);
1070 			}
1071 #ifdef DEBUG
1072 			printf("processing: '%s'\n", kw->keyword);
1073 #endif
1074 			switch(kw->type)
1075 			{
1076 				case TYPE_STRING:
1077 					*kw->confref.s = yylval.str;
1078 					break;
1079 				case TYPE_STRINGLIST:
1080 					kw->confref.sl->strs = xrealloc(kw->confref.sl->strs, sizeof(*kw->confref.sl->strs) * (kw->confref.sl->n + 1));
1081 					kw->confref.sl->strs[kw->confref.sl->n] = yylval.str;
1082 					kw->confref.sl->n++;
1083 					break;
1084 				case TYPE_CSTRING:
1085 					kw->confref.cs->str = yylval.str;
1086 					break;
1087 				case TYPE_NUMBER:
1088 					*kw->confref.i = yylval.i;
1089 					break;
1090 				case TYPE_BOOLEAN:
1091 					if(yylval.i == -1)
1092 						*kw->confref.i = !*kw->confref.i;
1093 					else
1094 						*kw->confref.i = yylval.i != 0;
1095 					break;
1096 				case TYPE_COLOR:
1097 					set_color(kw->confref.c, yylval.str);
1098 					break;
1099 				case TYPE_COLORLIST:
1100 					kw->confref.cl->clrs = xrealloc(kw->confref.cl->clrs, sizeof(*kw->confref.cl->clrs) * (kw->confref.cl->n + 1));
1101 					set_color(&kw->confref.cl->clrs[kw->confref.cl->n], yylval.str);
1102 					kw->confref.cl->n++;
1103 					break;
1104 				case TYPE_FONT:
1105 					kw->confref.f->gdfont = get_font(yylval.i);
1106 					break;
1107 				case TYPE_DOUBLE:
1108 					*kw->confref.d = yylval.d;
1109 					break;
1110 				default:
1111 					yyerror("Internal error: Unknown type passed %d", kw->type);
1112 					break;
1113 			}
1114 			kw = NULL;
1115 			state = 3;
1116 			break;
1117 		case 3:
1118 			if(token != ';')
1119 				stack_msg(MSG_ERR, "config: %d: ';' expected", line_number);
1120 			state = 0;
1121 			break;
1122 		case 4:
1123 			if(token != TYPE_STRING)
1124 			{
1125 				stack_msg(MSG_ERR, "config: %d: String expected (condition key)", line_number);
1126 				state = skip_to_semicolon(state);
1127 				break;
1128 			}
1129 			node = new_node(yylval.str);
1130 			state = 5;
1131 			break;
1132 		case 5:
1133 			if(token <= OP_FIRST || token >= OP_LAST)
1134 			{
1135 				stack_msg(MSG_ERR, "config: %d: Operator expected", line_number);
1136 				state = skip_to_semicolon(state);
1137 				break;
1138 			}
1139 			node->op = token;
1140 			state = 6;
1141 			break;
1142 		case 6:
1143 			if(token != TYPE_STRING)
1144 			{
1145 				stack_msg(MSG_ERR, "config: %d: String expected (condition)", line_number);
1146 				state = skip_to_semicolon(state);
1147 				break;
1148 			}
1149 			if(node->key == KEY_DATE)
1150 				node->content = fixup_date(yylval.str);
1151 			else
1152 				node->content = yylval.str;
1153 			state = 7;
1154 			break;
1155 		case 7:
1156 			if(token == '[')
1157 			{
1158 				push_state(node, 8);
1159 				node = NULL;
1160 				state = 4;
1161 				break;
1162 			}
1163 			if(token != TYPE_STRING)
1164 			{
1165 				stack_msg(MSG_ERR, "config: %d: String or '[' expected (true case)", line_number);
1166 				state = skip_to_semicolon(state);
1167 				break;
1168 			}
1169 			node->tcase = xmalloc(sizeof(*node->tcase));
1170 			if(kw->type == TYPE_COLOR || kw->type == TYPE_COLORLIST)
1171 			{
1172 				node->tcase->key = TYPE_COLOR;
1173 				set_color(&node->tcase->value.clr, yylval.str);
1174 			}
1175 			else
1176 			{
1177 				node->tcase->key = TYPE_STRING;
1178 				node->tcase->value.str = yylval.str;
1179 			}
1180 			state = 8;
1181 			break;
1182 		case 8:
1183 			if(token == '[')
1184 			{
1185 				push_state(node, 9);
1186 				node = NULL;
1187 				state = 4;
1188 				break;
1189 			}
1190 			if(token != TYPE_STRING)
1191 			{
1192 				stack_msg(MSG_ERR, "config: %d: String or '[' expected (false case)", line_number);
1193 				state = skip_to_semicolon(state);
1194 				break;
1195 			}
1196 			node->fcase = xmalloc(sizeof(*node->fcase));
1197 			if(kw->type == TYPE_COLOR || kw->type == TYPE_COLORLIST)
1198 			{
1199 				node->fcase->key = TYPE_COLOR;
1200 				set_color(&node->fcase->value.clr, yylval.str);
1201 			}
1202 			else
1203 			{
1204 				node->fcase->key = TYPE_STRING;
1205 				node->fcase->value.str = yylval.str;
1206 			}
1207 			state = 9;
1208 			break;
1209 		case 9:
1210 			if(token != ']')
1211 			{
1212 				stack_msg(MSG_ERR, "config: %d: ']' expected", line_number);
1213 				state = skip_to_semicolon(state);
1214 				break;
1215 			}
1216 			if(!pop_state(&node, &state))
1217 			{
1218 				if(kw->type == TYPE_COLOR)
1219 					kw->confref.c->node = node;
1220 				else if(kw->type == TYPE_CSTRING)
1221 					kw->confref.cs->node = node;
1222 				else
1223 					stack_msg(MSG_ERR, "config: %d: Color or conditional string keyword expected", line_number);
1224 				node = NULL;
1225 				kw = NULL;
1226 			}
1227 			break;
1228 		default:
1229 			yyerror("Internal error: invalid state %d", state);
1230 			break;
1231 		}
1232 	}
1233 }
1234 
1235 /*
1236  **************************************************************************
1237  * Configuration
1238  **************************************************************************
1239  */
stack_option(const char * opt)1240 void stack_option(const char *opt)
1241 {
1242 	stacked_opts = xrealloc(stacked_opts, sizeof(*stacked_opts) * (nstacked_opts + 1));
1243 	stacked_opts[nstacked_opts] = xmalloc(strlen(opt) + 2);
1244 	strcpy(stacked_opts[nstacked_opts], opt);
1245 	strcat(stacked_opts[nstacked_opts], ";");
1246 	nstacked_opts++;
1247 #ifdef DEBUG
1248 	printf("stacking option: '%s'\n", stacked_opts[nstacked_opts-1]);
1249 #endif
1250 }
1251 
read_config(const char * path)1252 void read_config(const char *path)
1253 {
1254 	FILE *fp;
1255 
1256 	/* Make sure we have them sorted for bsearch */
1257 	qsort(keywords, NKEYWORDS, sizeof(keywords[0]), cmp_kw);
1258 
1259 	if(path)
1260 	{
1261 		if((fp = fopen(path, "r")) != NULL)
1262 			input_file = path;
1263 	}
1264 	else
1265 	{
1266 		if((fp = fopen("./" CONFFILENAME, "r")) == NULL)
1267 		{
1268 			if((fp = fopen(ETCDIR "/" CONFFILENAME, "r")) != NULL)
1269 				input_file = ETCDIR "/" CONFFILENAME;
1270 		}
1271 		else
1272 			input_file = "./" CONFFILENAME;
1273 	}
1274 
1275 	if(fp)
1276 	{
1277 		line_number = 1;
1278 		set_input(fp, NULL);
1279 		config_parse();
1280 		fclose(fp);
1281 		input_file = NULL;
1282 	}
1283 
1284 	if(nstacked_opts)
1285 	{
1286 		int i;
1287 		for(i = 0; i < nstacked_opts; i++)
1288 		{
1289 			line_number = 0;
1290 			set_input(NULL, stacked_opts[i]);
1291 			input_file = stacked_opts[i];
1292 #ifdef DEBUG
1293 			printf("parsing stacked option: '%s'\n", stacked_opts[i]);
1294 #endif
1295 			config_parse();
1296 		}
1297 		input_file = NULL;
1298 	}
1299 
1300 	if(conf.merge_from.n != conf.merge_to.n)
1301 	{
1302 		int x = conf.merge_from.n < conf.merge_to.n ? conf.merge_from.n : conf.merge_to.n;
1303 		stack_msg(MSG_ERR, "config: merge_from(n=%d) does not match merge_to(n=%d)", conf.merge_from.n, conf.merge_to.n);
1304 		conf.merge_from.n = x;
1305 		conf.merge_to.n = x;
1306 	}
1307 	if(conf.merge_color.n < conf.merge_from.n)
1308 	{
1309 		/* Silently extend missing merge_color statements with black */
1310 		int x;
1311 		char c[] = "#000000";
1312 		for(x = conf.merge_color.n; x < conf.merge_from.n; x++)
1313 		{
1314 			conf.merge_color.clrs = xrealloc(conf.merge_color.clrs, sizeof(*conf.merge_color.clrs) * (conf.merge_color.n + 1));
1315 			set_color(&conf.merge_color.clrs[conf.merge_color.n], c);
1316 			conf.merge_color.n++;
1317 		}
1318 	}
1319 
1320 #ifdef DEBUG
1321 	dump_config();
1322 #endif
1323 }
1324 
1325 /*
1326  **************************************************************************
1327  * Color reference by name for late-binding color allocation
1328  **************************************************************************
1329  */
get_colorref(const char * confcolor,int idx)1330 color_t *get_colorref(const char *confcolor, int idx)
1331 {
1332 	keyword_t skw;
1333 	keyword_t *kw;
1334 
1335 	if(!confcolor)
1336 		return NULL;
1337 
1338 	skw.keyword = confcolor;
1339 	kw = bsearch(&skw, keywords, NKEYWORDS, sizeof(keywords[0]), cmp_kw);
1340 	if(!kw || (kw->type != TYPE_COLOR && kw->type != TYPE_COLORLIST))
1341 		return NULL;
1342 	if(kw->type == TYPE_COLORLIST)
1343 	{
1344 		if(idx >= kw->confref.cl->n)
1345 			return NULL;
1346 		return &kw->confref.cl->clrs[idx];
1347 	}
1348 	return kw->confref.c;
1349 }
1350