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