tmpl

Line-based key-value templator
git clone https://git.sinitax.com/sinitax/tmpl
Log | Files | Refs | README | LICENSE | sfeed.txt

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}