/* * CvsGraph graphical representation generator of brances and revisions * of a file in cvs/rcs. * * Copyright (C) 2001,2002 B. Stultiens * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /*#define DEBUG 1*/ #include #include #include #include #include #include #include #include #include #include #include #include "utils.h" #include "cvsgraph.h" #include "readconf.h" int line_number; typedef struct { const char *keyword; int type; union { void *v; /* join of other values */ int *i; font_t *f; char **s; color_t *c; double *d; stringlist_t *sl; condstring_t *cs; colorlist_t *cl; INTTYPE val; } confref; } keyword_t; typedef union { keyword_t *kw; int i; double d; char *str; } YYSTYPE; static YYSTYPE yylval; static int nstacked_opts; static char **stacked_opts; static keyword_t keywords[] = { { "branch_bgcolor", TYPE_COLOR, { &conf.branch_bgcolor } }, { "branch_bspace", TYPE_NUMBER, { &conf.branch_bspace } }, { "branch_color", TYPE_COLOR, { &conf.branch_color } }, { "branch_font", TYPE_FONT, { &conf.branch_font.gdfont } }, { "branch_ttfont", TYPE_STRING, { &conf.branch_font.ttfont } }, { "branch_ttsize", TYPE_DOUBLE, { &conf.branch_font.ttsize } }, { "branch_tag_color", TYPE_COLOR, { &conf.branch_tag_color } }, { "branch_tag_font", TYPE_FONT, { &conf.branch_tag_font.gdfont } }, { "branch_tag_ttfont", TYPE_STRING, { &conf.branch_tag_font.ttfont } }, { "branch_tag_ttsize", TYPE_DOUBLE, { &conf.branch_tag_font.ttsize } }, { "branch_lspace", TYPE_NUMBER, { &conf.branch_lspace } }, { "branch_rspace", TYPE_NUMBER, { &conf.branch_rspace } }, { "branch_tspace", TYPE_NUMBER, { &conf.branch_tspace } }, { "branch_connect", TYPE_NUMBER, { &conf.branch_connect } }, { "branch_margin", TYPE_NUMBER, { &conf.branch_margin } }, { "branch_dupbox", TYPE_BOOLEAN, { &conf.branch_dupbox } }, { "branch_fold", TYPE_BOOLEAN, { &conf.branch_fold } }, { "branch_foldall", TYPE_BOOLEAN, { &conf.branch_foldall } }, { "branch_resort", TYPE_BOOLEAN, { &conf.branch_resort } }, { "branch_subtree", TYPE_STRING, { &conf.branch_subtree } }, { "upside_down", TYPE_BOOLEAN, { &conf.upside_down } }, { "left_right", TYPE_BOOLEAN, { &conf.left_right } }, { "auto_stretch", TYPE_BOOLEAN, { &conf.auto_stretch } }, { "color_bg", TYPE_COLOR, { &conf.color_bg } }, { "transparent_bg", TYPE_BOOLEAN, { &conf.transparent_bg } }, { "cvsmodule", TYPE_STRING, { &conf.cvsmodule } }, { "cvsroot", TYPE_STRING, { &conf.cvsroot } }, { "date_format", TYPE_STRING, { &conf.date_format } }, { "box_shadow", TYPE_BOOLEAN, { &conf.box_shadow } }, { "strip_untagged", TYPE_BOOLEAN, { &conf.strip_untagged } }, { "strip_first_rev", TYPE_BOOLEAN, { &conf.strip_first_rev } }, { "anti_alias", TYPE_BOOLEAN, { &conf.anti_alias } }, { "use_ttf", TYPE_BOOLEAN, { &conf.use_ttf } }, { "parse_logs", TYPE_BOOLEAN, { &conf.parse_logs } }, { "html_level", TYPE_NUMBER, { &conf.html_level } }, { "thick_lines", TYPE_NUMBER, { &conf.thick_lines } }, { "msg_color", TYPE_COLOR, { &conf.msg_color } }, { "msg_font", TYPE_FONT, { &conf.msg_font.gdfont } }, { "msg_ttfont", TYPE_STRING, { &conf.msg_font.ttfont } }, { "msg_ttsize", TYPE_DOUBLE, { &conf.msg_font.ttsize } }, { "rev_hidenumber", TYPE_BOOLEAN, { &conf.rev_hidenumber } }, { "rev_color", TYPE_COLOR, { &conf.rev_color } }, { "rev_bgcolor", TYPE_COLOR, { &conf.rev_bgcolor } }, { "rev_font", TYPE_FONT, { &conf.rev_font.gdfont } }, { "rev_ttfont", TYPE_STRING, { &conf.rev_font.ttfont } }, { "rev_ttsize", TYPE_DOUBLE, { &conf.rev_font.ttsize } }, { "rev_separator", TYPE_NUMBER, { &conf.rev_separator } }, { "rev_minline", TYPE_NUMBER, { &conf.rev_minline } }, { "rev_maxline", TYPE_NUMBER, { &conf.rev_maxline } }, { "rev_lspace", TYPE_NUMBER, { &conf.rev_lspace } }, { "rev_rspace", TYPE_NUMBER, { &conf.rev_rspace } }, { "rev_tspace", TYPE_NUMBER, { &conf.rev_tspace } }, { "rev_bspace", TYPE_NUMBER, { &conf.rev_bspace } }, { "rev_text", TYPE_CSTRING, { &conf.rev_text } }, { "rev_idtext", TYPE_CSTRING, { &conf.rev_idtext } }, { "rev_text_color", TYPE_COLOR, { &conf.rev_text_color } }, { "rev_text_font", TYPE_FONT, { &conf.rev_text_font.gdfont } }, { "rev_text_ttfont", TYPE_STRING, { &conf.rev_text_font.ttfont } }, { "rev_text_ttsize", TYPE_DOUBLE, { &conf.rev_text_font.ttsize } }, { "rev_maxtags", TYPE_NUMBER, { &conf.rev_maxtags } }, { "merge_color", TYPE_COLORLIST, { &conf.merge_color } }, { "merge_from", TYPE_STRINGLIST, { &conf.merge_from } }, { "merge_to", TYPE_STRINGLIST, { &conf.merge_to } }, { "merge_findall", TYPE_BOOLEAN, { &conf.merge_findall } }, { "merge_front", TYPE_BOOLEAN, { &conf.merge_front } }, { "merge_nocase", TYPE_BOOLEAN, { &conf.merge_nocase } }, { "merge_arrows", TYPE_BOOLEAN, { &conf.merge_arrows } }, { "merge_cvsnt", TYPE_BOOLEAN, { &conf.merge_cvsnt } }, { "merge_cvsnt_color", TYPE_COLOR, { &conf.merge_cvsnt_color } }, { "arrow_width", TYPE_NUMBER, { &conf.arrow_width } }, { "arrow_length", TYPE_NUMBER, { &conf.arrow_length } }, { "tag_color", TYPE_COLOR, { &conf.tag_color } }, { "tag_font", TYPE_FONT, { &conf.tag_font.gdfont } }, { "tag_ttfont", TYPE_STRING, { &conf.tag_font.ttfont } }, { "tag_ttsize", TYPE_DOUBLE, { &conf.tag_font.ttsize } }, { "tag_ignore", TYPE_STRING, { &conf.tag_ignore } }, { "tag_ignore_merge", TYPE_BOOLEAN, { &conf.tag_ignore_merge } }, { "tag_nocase", TYPE_BOOLEAN, { &conf.tag_nocase } }, { "tag_negate", TYPE_BOOLEAN, { &conf.tag_negate } }, { "title", TYPE_STRING, { &conf.title } }, { "title_x", TYPE_NUMBER, { &conf.title_x } }, { "title_y", TYPE_NUMBER, { &conf.title_y } }, { "title_font", TYPE_FONT, { &conf.title_font.gdfont } }, { "title_ttfont", TYPE_STRING, { &conf.title_font.ttfont } }, { "title_ttsize", TYPE_DOUBLE, { &conf.title_font.ttsize } }, { "title_align", TYPE_NUMBER, { &conf.title_align } }, { "title_color", TYPE_COLOR, { &conf.title_color } }, { "margin_top", TYPE_NUMBER, { &conf.margin_top } }, { "margin_bottom", TYPE_NUMBER, { &conf.margin_bottom } }, { "margin_left", TYPE_NUMBER, { &conf.margin_left } }, { "margin_right", TYPE_NUMBER, { &conf.margin_right } }, { "image_type", TYPE_NUMBER, { &conf.image_type } }, { "image_quality", TYPE_NUMBER, { &conf.image_quality } }, { "image_compress", TYPE_NUMBER, { &conf.image_compress } }, { "image_interlace", TYPE_BOOLEAN, { &conf.image_interlace } }, { "map_name", TYPE_STRING, { &conf.map_name } }, { "map_branch_href", TYPE_STRING, { &conf.map_branch_href } }, { "map_branch_alt", TYPE_STRING, { &conf.map_branch_alt } }, { "map_rev_href", TYPE_STRING, { &conf.map_rev_href } }, { "map_rev_alt", TYPE_STRING, { &conf.map_rev_alt } }, { "map_diff_href", TYPE_STRING, { &conf.map_diff_href } }, { "map_diff_alt", TYPE_STRING, { &conf.map_diff_alt } }, { "map_merge_href", TYPE_STRING, { &conf.map_merge_href } }, { "map_merge_alt", TYPE_STRING, { &conf.map_merge_alt } }, { "jpeg", TYPE_VALUE, { (void *)IMAGE_JPEG } }, { "png", TYPE_VALUE, { (void *)IMAGE_PNG } }, { "gif", TYPE_VALUE, { (void *)IMAGE_GIF } }, { "true", TYPE_VALUE, { (void *)1 } }, { "false", TYPE_VALUE, { (void *)0 } }, { "not", TYPE_VALUE, { (void *)-1 } }, { "left", TYPE_VALUE, { (void *)0 } }, { "center", TYPE_VALUE, { (void *)1 } }, { "right", TYPE_VALUE, { (void *)2 } }, { "tiny", TYPE_VALUE, { (void *)0 } }, { "small", TYPE_VALUE, { (void *)1 } }, { "medium", TYPE_VALUE, { (void *)2 } }, { "large", TYPE_VALUE, { (void *)3 } }, { "giant", TYPE_VALUE, { (void *)4 } }, { "HTML3", TYPE_VALUE, { (void *)1 } }, { "HTML4", TYPE_VALUE, { (void *)2 } }, { "XHTML", TYPE_VALUE, { (void *)3 } }, }; #define NKEYWORDS (sizeof(keywords) / sizeof(keywords[0])) static int cmp_kw(const void *k1, const void *k2) { return strcmp(((keyword_t *)k1)->keyword, ((keyword_t *)k2)->keyword); } /* ************************************************************************** * Debug routines ************************************************************************** */ #ifdef DEBUG #define DEBUGSTREAM stdout static void debug_pname(const char *n) { fprintf(DEBUGSTREAM, "%-16s: ", n); } static void debug_pstring(const char *n, const char *a) { debug_pname(n); if(!a) fprintf(DEBUGSTREAM, "\n"); else { fputc('\'', DEBUGSTREAM); for(; *a; a++) { if(isprint(*a)) fputc(*a, DEBUGSTREAM); else { fputc('\\', DEBUGSTREAM); switch(*a) { case '\a': fputc('a', DEBUGSTREAM); break; case '\b': fputc('b', DEBUGSTREAM); break; case '\f': fputc('f', DEBUGSTREAM); break; case '\n': fputc('n', DEBUGSTREAM); break; case '\r': fputc('r', DEBUGSTREAM); break; case '\t': fputc('t', DEBUGSTREAM); break; case '\v': fputc('v', DEBUGSTREAM); break; default: fprintf(DEBUGSTREAM, "x%02x", (unsigned char)*a); } } } fprintf(DEBUGSTREAM, "'\n"); } } static void debug_pbool(const char *n, int b) { debug_pname(n); fprintf(DEBUGSTREAM, "%s\n", b ? "true" : "false"); } static void debug_pint(const char *n, int i) { debug_pname(n); fprintf(DEBUGSTREAM, "%i\n", i); } static void debug_pdouble(const char *n, double d) { debug_pname(n); fprintf(DEBUGSTREAM, "%g\n", d); } static void debug_pfont(const char *n, gdFontPtr f) { const char *s = ""; debug_pname(n); if(f == gdFontTiny) s = "gdFontTiny"; else if(f == gdFontSmall) s = "gdFontSmall"; else if(f == gdFontMediumBold) s = "gdFontMediumBold"; else if(f == gdFontLarge) s = "gdFontLarge"; else if(f == gdFontGiant) s = "gdFontGiant"; fprintf(DEBUGSTREAM, "%s\n", s); } static char *debug_op[] = { "=~", "=*", "!~", "!*", "==", "!_", ">=", ">", "<=", "<" }; static void debug_pcolornode(node_t *n) { if(!n) return; if(n->op == TYPE_STRING) fprintf(DEBUGSTREAM, "%s ", n->value.str); else if(n->op == TYPE_COLOR) fprintf(DEBUGSTREAM, "#%02x%02x%02x ", n->value.clr.r, n->value.clr.g, n->value.clr.b); else { char *key; fprintf(DEBUGSTREAM, "[ "); switch(n->key) { case KEY_STATE: key = "state"; break; case KEY_AUTHOR: key = "author"; break; case KEY_TAG: key = "tag"; break; case KEY_DATE: key = "date"; break; case KEY_REV: key = "rev"; break; case KEY_UNKNOWN: key = "unknown"; break; default: key = ""; break; } fprintf(DEBUGSTREAM, "%s ", key); if(n->op > OP_FIRST && n->op < OP_LAST) fprintf(DEBUGSTREAM, "%s ", debug_op[n->op - OP_FIRST - 1]); else fprintf(DEBUGSTREAM, "op(-?-) "); fprintf(DEBUGSTREAM, "%s ", n->content); debug_pcolornode(n->tcase); debug_pcolornode(n->fcase); fprintf(DEBUGSTREAM, "]"); } } static void debug_pcolor(const char *n, color_t *c) { debug_pname(n); if(c->node) { debug_pcolornode(c->node); fprintf(DEBUGSTREAM, "\n"); } else fprintf(DEBUGSTREAM, "#%02x%02x%02x\n", c->r, c->g, c->b); } static void debug_pcolorlist(const char *n, colorlist_t *cl) { int i; char buf[128]; for(i = 0; i < cl->n; i++) { sprintf(buf, "%s{%d}", n, i); debug_pcolor(buf, &cl->clrs[i]); } } static void debug_pstringlist(const char *n, stringlist_t *sl) { int i; char buf[128]; for(i = 0; i < sl->n; i++) { sprintf(buf, "%s{%d}", n, i); debug_pstring(buf, sl->strs[i]); } } void dump_config(void) { debug_pstring("cvsroot", conf.cvsroot); debug_pstring("cvsmodule", conf.cvsmodule); debug_pstring("date_format", conf.date_format); debug_pcolor("color_bg", &conf.color_bg); debug_pbool("box_shadow", conf.box_shadow); debug_pbool("upside_down", conf.upside_down); debug_pbool("left_right", conf.left_right); debug_pbool("strip_untagged", conf.strip_untagged); debug_pbool("strip_first_rev", conf.strip_first_rev); debug_pbool("auto_stretch", conf.auto_stretch); debug_pbool("anti_alias", conf.anti_alias); debug_pbool("use_ttf", conf.use_ttf); debug_pint("thick_lines", conf.thick_lines); debug_pint("html_level", conf.html_level); debug_pbool("parse_logs", conf.parse_logs); debug_pbool("transparent_bg", conf.transparent_bg); debug_pint("arrow_length", conf.arrow_length); debug_pint("arrow_width", conf.arrow_width); debug_pbool("merge_arrows", conf.merge_arrows); debug_pcolor("merge_cvsnt_color", &conf.merge_cvsnt_color); debug_pbool("merge_cvsnt", conf.merge_cvsnt); debug_pbool("merge_findall", conf.merge_findall); debug_pcolorlist("merge_color", &conf.merge_color); debug_pstringlist("merge_from", &conf.merge_from); debug_pstringlist("merge_to", &conf.merge_to); debug_pbool("merge_front", conf.merge_front); debug_pbool("merge_nocase", conf.merge_nocase); debug_pcolor("msg_color", &conf.msg_color); debug_pfont("msg_font", conf.msg_font.gdfont); debug_pstring("msg_ttfont", conf.msg_font.ttfont); debug_pdouble("msg_ttsize", conf.msg_font.ttsize); debug_pfont("tag_font", conf.tag_font.gdfont); debug_pstring("tag_ttfont", conf.tag_font.ttfont); debug_pdouble("tag_ttsize", conf.tag_font.ttsize); debug_pcolor("tag_color", &conf.tag_color); debug_pbool("tag_ignore_merge", conf.tag_ignore_merge); debug_pstring("tag_ignore", conf.tag_ignore); debug_pbool("tag_negate", conf.tag_negate); debug_pbool("tag_nocase", conf.tag_nocase); debug_pfont("rev_font", conf.rev_font.gdfont); debug_pstring("rev_ttfont", conf.rev_font.ttfont); debug_pdouble("rev_ttsize", conf.rev_font.ttsize); debug_pcolor("rev_color", &conf.rev_color); debug_pcolor("rev_bgcolor", &conf.rev_bgcolor); debug_pint("rev_separator", conf.rev_separator); debug_pint("rev_minline", conf.rev_minline); debug_pint("rev_maxline", conf.rev_maxline); debug_pint("rev_maxtags", conf.rev_maxtags); debug_pint("rev_lspace", conf.rev_lspace); debug_pint("rev_rspace", conf.rev_rspace); debug_pint("rev_tspace", conf.rev_tspace); debug_pint("rev_bspace", conf.rev_bspace); debug_pcstring("rev_text", conf.rev_text); debug_pcolor("rev_text_color", &conf.rev_text_color); debug_pfont("rev_text_font", conf.rev_text_font.gdfont); debug_pstring("rev_text_ttfont", conf.rev_text_font.ttfont); debug_pdouble("rev_text_ttsize", conf.rev_text_font.ttsize); debug_pbool("rev_hidenumber", conf.rev_hidenumber); debug_pfont("branch_font", conf.branch_font.gdfont); debug_pstring("branch_ttfont", conf.branch_font.ttfont); debug_pdouble("branch_ttsize", conf.branch_font.ttsize); debug_pcolor("branch_color", &conf.branch_color); debug_pfont("branch_tag_font", conf.branch_tag_font.gdfont); debug_pstring("branch_tag_ttfont", conf.branch_tag_font.ttfont); debug_pdouble("branch_tag_ttsize", conf.branch_tag_font.ttsize); debug_pcolor("branch_tag_color", &conf.branch_tag_color); debug_pcolor("branch_bgcolor", &conf.branch_bgcolor); debug_pint("branch_lspace", conf.branch_lspace); debug_pint("branch_rspace", conf.branch_rspace); debug_pint("branch_tspace", conf.branch_tspace); debug_pint("branch_bspace", conf.branch_bspace); debug_pint("branch_connect", conf.branch_connect); debug_pint("branch_margin", conf.branch_margin); debug_pint("branch_dupbox", conf.branch_dupbox); debug_pbool("branch_fold", conf.branch_fold); debug_pbool("branch_foldall", conf.branch_foldall); debug_pbool("branch_resort", conf.branch_resort); debug_pstring("branch_subtree", conf.branch_subtree); debug_pstring("title", conf.title); debug_pint("title_x", conf.title_x); debug_pint("title_y", conf.title_y); debug_pfont("title_font", conf.title_font.gdfont); debug_pstring("title_ttfont", conf.title_font.ttfont); debug_pdouble("title_ttsize", conf.title_font.ttsize); debug_pint("title_align", conf.title_align); debug_pcolor("title_color", &conf.title_color); debug_pint("margin_top", conf.margin_top); debug_pint("margin_bottom", conf.margin_bottom); debug_pint("margin_left", conf.margin_left); debug_pint("margin_right", conf.margin_right); debug_pint("image_type", conf.image_type); debug_pint("image_quality", conf.image_quality); debug_pint("image_compress", conf.image_compress); debug_pbool("image_interlace", conf.image_interlace); debug_pstring("map_name", conf.map_name); debug_pstring("map_branch_href", conf.map_branch_href); debug_pstring("map_branch_alt", conf.map_branch_alt); debug_pstring("map_rev_href", conf.map_rev_href); debug_pstring("map_rev_alt", conf.map_rev_alt); debug_pstring("map_diff_href", conf.map_diff_href); debug_pstring("map_diff_alt", conf.map_diff_alt); debug_pstring("map_merge_href", conf.map_merge_href); debug_pstring("map_merge_alt", conf.map_merge_alt); debug_pstring("expand[0]", conf.expand[0]); debug_pstring("expand[1]", conf.expand[1]); debug_pstring("expand[2]", conf.expand[2]); debug_pstring("expand[3]", conf.expand[3]); debug_pstring("expand[4]", conf.expand[4]); debug_pstring("expand[5]", conf.expand[5]); debug_pstring("expand[6]", conf.expand[6]); debug_pstring("expand[7]", conf.expand[7]); debug_pstring("expand[8]", conf.expand[8]); debug_pstring("expand[9]", conf.expand[9]); } #endif /* ************************************************************************** * String collection routines ************************************************************************** */ #define STRALLOCSIZE 128 static char *str; static int nstr; static int nastr; static void reset_str(void) { nstr = 0; } static void add_str(int c) { if(nstr + 1 + 1 > nastr) { str = xrealloc(str, nastr+STRALLOCSIZE); nastr += STRALLOCSIZE; } str[nstr++] = c; } static char *get_str(void) { if(!str) return xstrdup(""); str[nstr] = '\0'; return xstrdup(str); } /* ************************************************************************** * Input routines ************************************************************************** */ static char *buf = NULL; static int bufsize = 0; static int bufalloc = 0; static int bufpos = 0; static int bufunput = -1; static FILE *buffp; static void set_input(FILE *fp, char *s) { assert((fp == NULL && s != NULL) || (fp != NULL && s == NULL)); buffp = fp; bufsize = bufpos = 0; if(s) { if(!buf) { bufalloc = 8192; buf = xmalloc(bufalloc * sizeof(*buf)); } bufsize = strlen(s); assert(bufsize < bufalloc); strcpy(buf, s); } } static int get_input(void) { if(bufunput != -1) { int c = bufunput; bufunput = -1; return c; } if(bufpos < bufsize) { assert(buf != NULL); retry_input: return (int)((unsigned char)buf[bufpos++]); } if(!buf) { bufalloc = 8192; buf = xmalloc(bufalloc * sizeof(*buf)); bufsize = bufpos = 0; } if(buffp) { bufsize = fread(buf, 1, bufalloc, buffp); bufpos = 0; if(!bufsize) return EOF; goto retry_input; } return EOF; } static void unget_input(int c) { bufunput = c; } /* ************************************************************************** * Lexical scanner ************************************************************************** */ static int config_lex(void) { int ch; while(1) { ch = get_input(); if(ch == '\n') line_number++; if(isspace(ch)) continue; switch(ch) { case '=': ch = get_input(); switch(ch) { case '~': return OP_CONTAINED; case '*': return OP_CONTAINEDI; case '=': return OP_EQ; default: unget_input(ch); return '='; } break; case '!': ch = get_input(); switch(ch) { case '~': return OP_NCONTAINED; case '*': return OP_NCONTAINEDI; case '=': return OP_NE; default: stack_msg(MSG_ERR, "config: %d: Invalid operator", line_number); unget_input(ch); return '!'; } break; case '>': ch = get_input(); if(ch == '=') return OP_GE; unget_input(ch); return OP_GT; case '<': ch = get_input(); if(ch == '=') return OP_LE; unget_input(ch); return OP_LT; case EOF: case ';': case '[': case ']': return ch; case '#': /* Comment */ while((ch = get_input()) != '\n' && ch != EOF) ; if(ch != EOF) unget_input(ch); break; case '"': reset_str(); while(1) { char c[4]; ch = get_input(); switch(ch) { case '\\': /* Start an escape sequence */ switch(ch = get_input()) { default: /* This includes '\\', '"' and embedded newlines */ add_str(ch); break; case 'a': add_str('\a'); break; case 'b': add_str('\b'); break; case 'f': add_str('\f'); break; case 'n': add_str('\n'); break; case 'r': add_str('\r'); break; case 't': add_str('\t'); break; case 'v': add_str('\v'); break; case 'x': case 'X': /* Hex escape */ c[0] = get_input(); c[1] = get_input(); c[2] = '\0'; if(!isxdigit((int)(unsigned char)c[0]) || !isxdigit((int)(unsigned char)c[1])) stack_msg(MSG_ERR, "config: %d: Invalid hex escape", line_number); add_str((int)strtol(c, NULL, 16)); break; case '0': case '1': case '2': /* Octal escape */ c[0] = ch; c[1] = c[2] = c[3] = '\0'; if((ch = get_input()) >= '0' && ch <= '7') c[1] = ch; else unget_input(ch); if((ch = get_input()) >= '0' && ch <= '7') c[2] = ch; else unget_input(ch); add_str((int)strtol(c, NULL, 8)); break; case EOF: yyerror("Unexpected EOF in escape"); break; } break; case '"': yylval.str = get_str(); return TYPE_STRING; case '\n': yyerror("Newline in string"); break; case EOF: yyerror("Unexpected EOF in string"); break; default: add_str(ch); break; } } break; default: if(isalpha(ch) || ch == '_') { keyword_t skw; keyword_t *kw; /* Collect keyword */ reset_str(); add_str(ch); while(1) { ch = get_input(); if(isalnum(ch) || ch == '_') add_str(ch); else { unget_input(ch); break; } } skw.keyword = get_str(); kw = bsearch(&skw, keywords, NKEYWORDS, sizeof(keywords[0]), cmp_kw); if(!kw) { stack_msg(MSG_ERR, "config: %d: Unknown keyword '%s'", line_number, skw.keyword); yylval.kw = NULL; return TYPE_KEYWORD; } xfree((void *)skw.keyword); if(kw->type == TYPE_VALUE) { yylval.i = (int)kw->confref.val; return TYPE_NUMBER; } yylval.kw = kw; return TYPE_KEYWORD; } else if(isdigit(ch) || ch == '+' || ch == '-') { char *s; char *eptr; int type = TYPE_NUMBER; /* Collect number */ reset_str(); add_str(ch); while(1) { ch = get_input(); if(isxdigit(ch) || ch == 'x' || ch == 'X' || ch == '.') /* Not exact, but close enough */ add_str(ch); else { unget_input(ch); break; } if(ch == '.') type = TYPE_DOUBLE; } s = get_str(); if(type == TYPE_DOUBLE) { yylval.d = strtod(s, &eptr); if(*eptr) stack_msg(MSG_ERR, "config: %d: Invalid floating point number", line_number); } else { yylval.i = strtol(s, &eptr, 0); if(*eptr) stack_msg(MSG_ERR, "config: %d: Invalid number", line_number); } xfree(s); return type; } else yyerror("Unmatched text '%c' (0x%02x)", isprint(ch) ? ch : ' ', ch); break; } } } static void set_color(color_t *c, char *s) { char *cptr; c->node = NULL; if(*s != '#' || strlen(s) != 7) { colorerror: stack_msg(MSG_ERR, "config: %d: Invalid color value '%s'", line_number, s); return; } c->b = strtol(s+5, &cptr, 16); if(*cptr) goto colorerror; s[5] = '\0'; c->g = strtol(s+3, &cptr, 16); if(*cptr) goto colorerror; s[3] = '\0'; c->r = strtol(s+1, &cptr, 16); if(*cptr) goto colorerror; } static gdFontPtr get_font(int id) { switch(id) { case 0: return gdFontTiny; case 1: return gdFontSmall; default: case 2: return gdFontMediumBold; case 3: return gdFontLarge; case 4: return gdFontGiant; } } /* ************************************************************************** * The config parser * Grammar: * file : * | lines * ; * * lines : line * | lines line * ; * * line : '=' ';' * | '=' expr ';' * | ';' * * expr : '[' expr expr ']' * | * ; ************************************************************************** */ static int skip_to_semicolon(int state) { int token; while(1) { token = config_lex(); if(token == ';') return 0; else if(token == EOF) return state; } } static node_t *new_node(char *val) { node_t *n = xmalloc(sizeof(*n)); if(!strcmp(val, "state")) n->key = KEY_STATE; else if(!strcmp(val, "author")) n->key = KEY_AUTHOR; else if(!strcmp(val, "tag")) n->key = KEY_TAG; else if(!strcmp(val, "date")) n->key = KEY_DATE; else if(!strcmp(val, "rev")) n->key = KEY_REV; else { n->key = KEY_UNKNOWN; stack_msg(MSG_ERR, "config: %d: Unknown key '%s'", line_number, val); } return n; } typedef struct __statestack_t { int state; node_t *node; } statestack_t; static int nstatestack = 0; static int nastatestack; static statestack_t *statestack = NULL; static void push_state(node_t *node, int state) { if(!statestack) { statestack = xmalloc(4 * sizeof(*statestack)); nastatestack = 4; nstatestack = 0; } else if(nstatestack >= nastatestack) { nastatestack *= 2; statestack = xrealloc(statestack, nastatestack * sizeof(*statestack)); } statestack[nstatestack].node = node; statestack[nstatestack].state = state; nstatestack++; } static int pop_state(node_t **node, int *state) { if(nstatestack <= 0) { *state = 3; return 0; } assert(*node != NULL); if(statestack[nstatestack-1].state == 8) statestack[nstatestack-1].node->tcase = *node; else if(statestack[nstatestack-1].state == 9) statestack[nstatestack-1].node->fcase = *node; *state = statestack[nstatestack-1].state; *node = statestack[nstatestack-1].node; return nstatestack--; } /* YYYY.MM.DD.hh.mm.ss */ /* 0123456789012345678 */ /* 111111111 */ static char *fixup_date(const char *str) { int i; int y=1970, m=1, d=1, h=0, mi=0, s=0; int l = strlen(str); char date[6*16]; /* Should be wildly enough to hold 19 chars from 6 numbers with all possible errors */ if(l < 4 || l > 19) { date_err: stack_msg(MSG_ERR, "config: %d: Invalid date string '%s'", line_number, str); return "1970.01.01.00.00.00"; } for(i = 0; i < l; i++) { if(!strchr("0123456789.", str[i])) goto date_err; } i = sscanf(str, "%d.%d.%d.%d.%d.%d", &y, &m, &d, &h, &mi, &s); if(i == EOF || i < 0 || i > 6) goto date_err; if(i >= 1 && (y < 1970 || y > 2037)) goto date_err; if(i >= 2 && (m < 1 || m > 12)) goto date_err; if(i >= 3 && (d < 1 || d > 31)) goto date_err; if(i >= 4 && (h < 0 || h > 23)) goto date_err; if(i >= 5 && (mi < 0 || mi > 59)) goto date_err; if(i >= 6 && (s < 0 || s > 59)) goto date_err; sprintf(date, "%04d.%02d.%02d.%02d.%02d.%02d", y, m, d, h, mi, s); return strdup(date); } static void config_parse(void) { int state = 0; int token; int t; keyword_t *kw = NULL; node_t *node = NULL; while(1) { token = config_lex(); if(token == EOF) { if(state) stack_msg(MSG_ERR, "config: %d: Unexpected EOF", line_number); break; } switch(state) { case 0: if(token == TYPE_KEYWORD) { kw = yylval.kw; state = 1; } else if(token != ';') stack_msg(MSG_ERR, "config: %d: Keyword expected", line_number); break; case 1: if(token != '=') { stack_msg(MSG_ERR, "config: %d: '=' expected", line_number); state = skip_to_semicolon(state); break; } else state = 2; break; case 2: if(!kw) { /* Error recovery of failed keyword */ state = 3; break; } if(token == '[') { if(kw->type != TYPE_COLOR && kw->type != TYPE_CSTRING) { stack_msg(MSG_ERR, "config: %d: Conditional expression not allowed for keyword", line_number); state = skip_to_semicolon(state); break; } state = 4; break; } if(kw->type == TYPE_FONT || kw->type == TYPE_BOOLEAN) t = TYPE_NUMBER; else if(kw->type == TYPE_COLOR || kw->type == TYPE_COLORLIST || kw->type == TYPE_STRINGLIST || kw->type == TYPE_CSTRING) t = TYPE_STRING; else t = kw->type; if(token == TYPE_NUMBER && kw->type == TYPE_DOUBLE) { /* Auto promote numbers to doubles if required */ yylval.d = (double)yylval.i; token = TYPE_DOUBLE; } if(token != t) { char *e; switch(kw->type) { case TYPE_STRING: e = "String"; yylval.str = xstrdup("error recovery"); break; case TYPE_STRINGLIST: e = "StringL"; yylval.str = xstrdup("error recovery"); break; case TYPE_CSTRING: e = "CString"; yylval.str = xstrdup("error recovery"); break; case TYPE_NUMBER: e = "Number"; yylval.i = 0; break; case TYPE_COLOR: e = "Color"; yylval.str = xstrdup("#123456"); break; case TYPE_COLORLIST: e = "ColorL"; yylval.str = xstrdup("#123456"); break; case TYPE_FONT: e = "Font"; yylval.i = 0; break; case TYPE_BOOLEAN: e = "Boolean"; yylval.i = 0; break; case TYPE_DOUBLE: e = "Double"; yylval.d = 0.0; break; default: e = "Internal error: Unknown type"; yylval.i = 0; break; } stack_msg(MSG_ERR, "config: %d: %s expected", line_number, e); } #ifdef DEBUG printf("processing: '%s'\n", kw->keyword); #endif switch(kw->type) { case TYPE_STRING: *kw->confref.s = yylval.str; break; case TYPE_STRINGLIST: kw->confref.sl->strs = xrealloc(kw->confref.sl->strs, sizeof(*kw->confref.sl->strs) * (kw->confref.sl->n + 1)); kw->confref.sl->strs[kw->confref.sl->n] = yylval.str; kw->confref.sl->n++; break; case TYPE_CSTRING: kw->confref.cs->str = yylval.str; break; case TYPE_NUMBER: *kw->confref.i = yylval.i; break; case TYPE_BOOLEAN: if(yylval.i == -1) *kw->confref.i = !*kw->confref.i; else *kw->confref.i = yylval.i != 0; break; case TYPE_COLOR: set_color(kw->confref.c, yylval.str); break; case TYPE_COLORLIST: kw->confref.cl->clrs = xrealloc(kw->confref.cl->clrs, sizeof(*kw->confref.cl->clrs) * (kw->confref.cl->n + 1)); set_color(&kw->confref.cl->clrs[kw->confref.cl->n], yylval.str); kw->confref.cl->n++; break; case TYPE_FONT: kw->confref.f->gdfont = get_font(yylval.i); break; case TYPE_DOUBLE: *kw->confref.d = yylval.d; break; default: yyerror("Internal error: Unknown type passed %d", kw->type); break; } kw = NULL; state = 3; break; case 3: if(token != ';') stack_msg(MSG_ERR, "config: %d: ';' expected", line_number); state = 0; break; case 4: if(token != TYPE_STRING) { stack_msg(MSG_ERR, "config: %d: String expected (condition key)", line_number); state = skip_to_semicolon(state); break; } node = new_node(yylval.str); state = 5; break; case 5: if(token <= OP_FIRST || token >= OP_LAST) { stack_msg(MSG_ERR, "config: %d: Operator expected", line_number); state = skip_to_semicolon(state); break; } node->op = token; state = 6; break; case 6: if(token != TYPE_STRING) { stack_msg(MSG_ERR, "config: %d: String expected (condition)", line_number); state = skip_to_semicolon(state); break; } if(node->key == KEY_DATE) node->content = fixup_date(yylval.str); else node->content = yylval.str; state = 7; break; case 7: if(token == '[') { push_state(node, 8); node = NULL; state = 4; break; } if(token != TYPE_STRING) { stack_msg(MSG_ERR, "config: %d: String or '[' expected (true case)", line_number); state = skip_to_semicolon(state); break; } node->tcase = xmalloc(sizeof(*node->tcase)); if(kw->type == TYPE_COLOR || kw->type == TYPE_COLORLIST) { node->tcase->key = TYPE_COLOR; set_color(&node->tcase->value.clr, yylval.str); } else { node->tcase->key = TYPE_STRING; node->tcase->value.str = yylval.str; } state = 8; break; case 8: if(token == '[') { push_state(node, 9); node = NULL; state = 4; break; } if(token != TYPE_STRING) { stack_msg(MSG_ERR, "config: %d: String or '[' expected (false case)", line_number); state = skip_to_semicolon(state); break; } node->fcase = xmalloc(sizeof(*node->fcase)); if(kw->type == TYPE_COLOR || kw->type == TYPE_COLORLIST) { node->fcase->key = TYPE_COLOR; set_color(&node->fcase->value.clr, yylval.str); } else { node->fcase->key = TYPE_STRING; node->fcase->value.str = yylval.str; } state = 9; break; case 9: if(token != ']') { stack_msg(MSG_ERR, "config: %d: ']' expected", line_number); state = skip_to_semicolon(state); break; } if(!pop_state(&node, &state)) { if(kw->type == TYPE_COLOR) kw->confref.c->node = node; else if(kw->type == TYPE_CSTRING) kw->confref.cs->node = node; else stack_msg(MSG_ERR, "config: %d: Color or conditional string keyword expected", line_number); node = NULL; kw = NULL; } break; default: yyerror("Internal error: invalid state %d", state); break; } } } /* ************************************************************************** * Configuration ************************************************************************** */ void stack_option(const char *opt) { stacked_opts = xrealloc(stacked_opts, sizeof(*stacked_opts) * (nstacked_opts + 1)); stacked_opts[nstacked_opts] = xmalloc(strlen(opt) + 2); strcpy(stacked_opts[nstacked_opts], opt); strcat(stacked_opts[nstacked_opts], ";"); nstacked_opts++; #ifdef DEBUG printf("stacking option: '%s'\n", stacked_opts[nstacked_opts-1]); #endif } void read_config(const char *path) { FILE *fp; /* Make sure we have them sorted for bsearch */ qsort(keywords, NKEYWORDS, sizeof(keywords[0]), cmp_kw); if(path) { if((fp = fopen(path, "r")) != NULL) input_file = path; } else { if((fp = fopen("./" CONFFILENAME, "r")) == NULL) { if((fp = fopen(ETCDIR "/" CONFFILENAME, "r")) != NULL) input_file = ETCDIR "/" CONFFILENAME; } else input_file = "./" CONFFILENAME; } if(fp) { line_number = 1; set_input(fp, NULL); config_parse(); fclose(fp); input_file = NULL; } if(nstacked_opts) { int i; for(i = 0; i < nstacked_opts; i++) { line_number = 0; set_input(NULL, stacked_opts[i]); input_file = stacked_opts[i]; #ifdef DEBUG printf("parsing stacked option: '%s'\n", stacked_opts[i]); #endif config_parse(); } input_file = NULL; } if(conf.merge_from.n != conf.merge_to.n) { int x = conf.merge_from.n < conf.merge_to.n ? conf.merge_from.n : conf.merge_to.n; stack_msg(MSG_ERR, "config: merge_from(n=%d) does not match merge_to(n=%d)", conf.merge_from.n, conf.merge_to.n); conf.merge_from.n = x; conf.merge_to.n = x; } if(conf.merge_color.n < conf.merge_from.n) { /* Silently extend missing merge_color statements with black */ int x; char c[] = "#000000"; for(x = conf.merge_color.n; x < conf.merge_from.n; x++) { conf.merge_color.clrs = xrealloc(conf.merge_color.clrs, sizeof(*conf.merge_color.clrs) * (conf.merge_color.n + 1)); set_color(&conf.merge_color.clrs[conf.merge_color.n], c); conf.merge_color.n++; } } #ifdef DEBUG dump_config(); #endif } /* ************************************************************************** * Color reference by name for late-binding color allocation ************************************************************************** */ color_t *get_colorref(const char *confcolor, int idx) { keyword_t skw; keyword_t *kw; if(!confcolor) return NULL; skw.keyword = confcolor; kw = bsearch(&skw, keywords, NKEYWORDS, sizeof(keywords[0]), cmp_kw); if(!kw || (kw->type != TYPE_COLOR && kw->type != TYPE_COLORLIST)) return NULL; if(kw->type == TYPE_COLORLIST) { if(idx >= kw->confref.cl->n) return NULL; return &kw->confref.cl->clrs[idx]; } return kw->confref.c; }