tmpl.c (5242B)
1#define _XOPEN_SOURCE 500 2 3#include <unistd.h> 4#include <fcntl.h> 5#include <stdio.h> 6#include <string.h> 7#include <stdarg.h> 8#include <stdbool.h> 9#include <stdlib.h> 10 11enum { 12 IFEQ, 13 IFDEF, 14 ELSE 15}; 16 17struct block { 18 int active; 19 int type; 20}; 21 22struct var { 23 const char *name; 24 const char *value; 25 struct var *next; 26}; 27 28static struct var *vars = NULL; 29static struct var **vars_end = &vars; 30 31static struct block stack[32] = { 0 }; 32static int stack_top = 0; 33static size_t lineno = 0; 34 35static bool fail_missing = false; 36 37static const char *prefix = "#{"; 38 39static void 40die(const char *fmt, ...) 41{ 42 va_list ap; 43 44 va_start(ap, fmt); 45 fputs("tmpl: ", stderr); 46 vfprintf(stderr, fmt, ap); 47 if (*fmt && fmt[strlen(fmt)-1] == ':') { 48 fputc(' ', stderr); 49 perror(NULL); 50 } else { 51 fputc('\n', stderr); 52 } 53 va_end(ap); 54 55 exit(1); 56} 57 58static const char * 59getvar(const char *name) 60{ 61 const char *env; 62 struct var *var; 63 64 for (var = vars; var; var = var->next) { 65 if (!strcmp(var->name, name)) 66 return var->value; 67 } 68 69 env = getenv(name); 70 if (!env && fail_missing) 71 die("not set: '%s'", name); 72 73 return env; 74} 75 76static void 77assign(char *line) 78{ 79 char *sep; 80 struct var *var; 81 82 sep = strchr(line, '='); 83 if (!sep) die("invalid assign '%s'", line); 84 85 line = strdup(line); 86 if (!line) die("strdup:"); 87 sep = strchr(line, '='); 88 *sep = '\0'; 89 90 var = malloc(sizeof(struct var)); 91 if (!var) die("malloc:"); 92 var->name = line; 93 var->value = sep + 1; 94 var->next = NULL; 95 96 *vars_end = var; 97 vars_end = &var->next; 98} 99 100static void 101template(char *line) 102{ 103 const char *value; 104 char *open, *close; 105 char *sep; 106 107 lineno++; 108 109 if (!strncmp(line, "#ifdef ", 7)) { 110 if (stack_top == 32) die("too deeply nested"); 111 stack[stack_top].active = 0; 112 stack[stack_top].type = IFDEF; 113 if (!stack_top || stack[stack_top-1].active) { 114 if (getvar(line + 7)) 115 stack[stack_top].active = 1; 116 } 117 stack_top++; 118 } else if (!strncmp(line, "#ifeq ", 6)) { 119 if (stack_top == 32) die("too deeply nested"); 120 stack[stack_top].active = 0; 121 stack[stack_top].type = IFEQ; 122 if (!stack_top || stack[stack_top-1].active) { 123 sep = strchr(line + 6, ' '); 124 if (!sep) die("invalid #ifeq\n%lu: %s", lineno, line); 125 *sep = '\0'; 126 value = getvar(line + 6); 127 if (value && !strcmp(value, sep + 1)) 128 stack[stack_top].active = 1; 129 } 130 stack_top++; 131 } else if (!strcmp(line, "#else")) { 132 if (!stack_top || (stack[stack_top-1].type != IFDEF 133 && stack[stack_top-1].type != IFEQ)) 134 die("invalid #else\n%lu: %s", lineno, line); 135 stack[stack_top-1].active ^= 1; 136 stack[stack_top-1].type = ELSE; 137 } else if (!strcmp(line, "#endif")) { 138 if (!stack_top) die("invalid #endif\n%lu: %s", lineno, line); 139 stack_top--; 140 } else if (!strncmp(line, "#define ", 8)) { 141 if (stack_top && !stack[stack_top-1].active) 142 return; 143 144 sep = strchr(line + 8, ' '); 145 if (!sep) die("invalid #default\n%lu: %s", lineno, line); 146 *sep = '='; 147 assign(line + 8); 148 } else if (!strncmp(line, "#default ", 9)) { 149 if (stack_top && !stack[stack_top-1].active) 150 return; 151 152 sep = strchr(line + 9, ' '); 153 if (!sep) die("invalid #default\n%lu: %s", lineno, line); 154 *sep = '\0'; 155 value = getvar(line + 9); 156 if (!value) { 157 *sep = '='; 158 assign(line + 9); 159 } 160 } else if (!strncmp(line, "\\#", 2)) { 161 puts(line+1); /* escaped */ 162 } else if (!strncmp(line, "#-- ", 4)) { 163 return; /* comment */ 164 } else { 165 if (stack_top && !stack[stack_top-1].active) 166 return; 167 168 while (1) { 169 open = strstr(line, prefix); 170 if (!open) break; 171 172 if (open != line && open[-1] == '\\') { 173 open[-1] = '\0'; 174 fputs(line, stdout); 175 fputs(prefix, stdout); 176 line = open + 2; 177 continue; 178 } 179 180 close = strchr(open, '}'); 181 if (!close) break; 182 183 *open = '\0'; 184 fputs(line, stdout); 185 186 *close = '\0'; 187 value = getvar(open + 2); 188 if (value) fputs(value, stdout); 189 190 line = close + 1; 191 } 192 193 puts(line); 194 } 195} 196 197static void 198perline(const char *path, int nonempty, void (*process)(char *)) 199{ 200 char linebuf[4096]; 201 size_t len; 202 FILE *f; 203 204 f = fopen(path, "r"); 205 if (!f) die("fopen '%s':", path); 206 while (fgets(linebuf, 4096, f)) { 207 len = strlen(linebuf); 208 if (len && linebuf[len - 1] == '\n') { 209 linebuf[--len] = '\0'; 210 } else if (len && !feof(f)) { 211 die("fgets '%s': line too long", path); 212 } 213 if (len || !nonempty) 214 process(linebuf); 215 } 216 fclose(f); 217} 218 219int 220main(int argc, char **argv) 221{ 222 const char *path; 223 char **arg, **dst; 224 225 if (!argc) return 1; 226 227 for (dst = arg = argv + 1; *arg; arg++) { 228 if (!strcmp(*arg, "-e")) { 229 fail_missing = true; 230 } else if (!strcmp(*arg, "-p")) { 231 prefix = *++arg; 232 if (!prefix || !*prefix) die("invalid prefix"); 233 } else { 234 *dst++ = *arg; 235 } 236 } 237 *dst = NULL; 238 239 for (arg = argv + 1; *arg; arg++) { 240 if (!strcmp(*arg, "-h") || !strcmp(*arg, "--help")) { 241 fprintf(stderr, "Usage: tmpl [-e] [-c CONFIG].. " 242 "[-D NAME=VALUE].. FILE..\n"); 243 return 1; 244 } else if (!strcmp(*arg, "-D") || !strcmp(*arg, "--define")) { 245 if (!*++arg) die("missing argument"); 246 assign(*arg); 247 } else if (!strcmp(*arg, "-c") || !strcmp(*arg, "--config")) { 248 if (!*++arg) die("missing argument"); 249 perline(*arg, 1, assign); 250 } else { 251 break; 252 } 253 } 254 255 for (; *arg; arg++) { 256 path = !strcmp(*arg, "-") ? "/dev/stdin" : *arg; 257 perline(path, 0, template); 258 } 259}