tmpl

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

commit 110975105e860a4530f9d5b1595d063b8356ccd6
Author: Louis Burda <quent.burda@gmail.com>
Date:   Fri, 26 May 2023 20:44:50 +0200

Add initial version

Diffstat:
A.gitignore | 2++
AMakefile | 17+++++++++++++++++
Atest | 10++++++++++
Atmpl.c | 238+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 267 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,2 @@ +tmpl +.gdb_history diff --git a/Makefile b/Makefile @@ -0,0 +1,17 @@ +PREFIX ?= /usr/local +BINDIR ?= /bin + +all: tmpl + +clean: + rm -f tmpl + +tmp: tmpl.c + +install: + install -m755 tmpl -t "$(DESTDIR)$(PREFIX)$(BINDIR)" + +uninstall: + rm -f "$(DESTDIR)$(PREFIX)$(BINDIR)/tmpl" + +.PHONY: all clean install uninstall diff --git a/test b/test @@ -0,0 +1,10 @@ +#default PREFIX /usr/local +#default BINDIR /bin + +#ifdef DEBUG +yes debug +#else +no debug +#endif + +install -m755 "#{TARGET}" -t "#{DESTDIR}#{PREFIX}#{BINDIR}" diff --git a/tmpl.c b/tmpl.c @@ -0,0 +1,238 @@ +#include <err.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +enum { + IFEQ, + IFDEF, + ELSE +}; + +struct block { + int active; + int type; +}; + +struct var { + const char *name; + const char *value; + struct var *next; +}; + +struct var *vars, **vars_end; + +struct var * +getvar(const char *name) +{ + struct var *var; + + for (var = vars; var; var = var->next) { + if (!strcmp(var->name, name)) + return var; + } + + return NULL; +} + +void +assign(char *line) +{ + char *name, *value, *sep; + struct var *var; + + sep = strchr(line, '='); + if (!sep) err(1, "invalid assign: '%s'", line); + *sep = '\0'; + + var = malloc(sizeof(struct var)); + if (!var) err(1, "malloc"); + var->name = line; + var->value = sep + 1; + var->next = NULL; + + *vars_end = var; + vars_end = &var->next; +} + +void +template(char *line) +{ + static struct block stack[32] = { 0 }; + static int stack_top = 0; + static size_t lineno = 0; + struct var *var; + char *open, *close; + char *sep; + + lineno++; + + if (!strncmp(line, "#ifdef ", 7)) { + if (stack_top == 32) errx(1, "too much nesting"); + stack[stack_top].active = 0; + stack[stack_top].type = IFDEF; + if (!stack_top || stack[stack_top-1].active) { + if (getvar(line + 7)) + stack[stack_top].active = 1; + } + stack_top++; + } else if (!strncmp(line, "#ifeq ", 6)) { + if (stack_top == 32) errx(1, "too much nesting"); + stack[stack_top].active = 0; + stack[stack_top].type = IFEQ; + if (!stack_top || stack[stack_top-1].active) { + var = getvar(line + 6); + sep = strchr(line + 6, ' '); + if (!sep) err(1, "invalid #ifeq: %lu:%s", lineno, line); + if (!strcmp(var->value, sep + 1)) { + stack[stack_top].active = 1; + } + } + stack_top++; + } else if (!strcmp(line, "#else")) { + if (!stack_top || stack[stack_top-1].type != IFDEF + && stack[stack_top-1].type != IFEQ) + err(1, "invalid #else: %lu:%s", lineno, line); + stack[stack_top-1].active ^= 1; + stack[stack_top-1].type = ELSE; + } else if (!strcmp(line, "#endif")) { + if (!stack_top) err(1, "invalid #endif: %lu:%s", lineno, line); + stack_top--; + } else if (!strncmp(line, "#define ", 8)) { + if (stack_top && !stack[stack_top-1].active) + return; + + sep = strchr(line + 8, ' '); + if (!sep) err(1, "invalid #default: %lu:%s", lineno, line); + *sep = '='; + assign(line + 8); + } else if (!strncmp(line, "#default ", 9)) { + if (stack_top && !stack[stack_top-1].active) + return; + + sep = strchr(line + 9, ' '); + if (!sep) err(1, "invalid #default: %lu:%s", lineno, line); + *sep = '\0'; + var = getvar(line + 9); + if (!var) { + *sep = '='; + assign(line + 9); + } + } else { + if (stack_top && !stack[stack_top-1].active) + return; + + while (1) { + open = strstr(line, "#{"); + if (!open) break; + + if (open != line && open[-1] == '\\') { + open[-1] = '\0'; + fputs(line, stdout); + fputs("#{", stdout); + line = open + 2; + continue; + } + + close = strchr(open, '}'); + if (!close) break; + + *open = '\0'; + fputs(line, stdout); + + *close = '\0'; + var = getvar(open + 2); + if (var) fputs(var->value, stdout); + + line = close + 1; + } + + puts(line); + } +} + +void +perline(char *contents, int nonempty, void (*process)(char *)) +{ + char *line, *sep, *end; + + line = contents; + do { + sep = strchr(line, '\n'); + end = sep ? sep : line + strlen(line); + *end = '\0'; + if (!nonempty || end > line + 1) + process(line); + line = end + 1; + } while (sep); +} + +char * +readall(const char *path) +{ + char buf[BUFSIZ]; + char *contents; + ssize_t ret; + size_t len, cap; + int fd; + + fd = open(path, O_RDONLY); + if (fd < 0) err(1, "open '%s'", path); + + cap = BUFSIZ + 1; + contents = malloc(cap); + if (!contents) err(1, "malloc"); + + len = 0; + while ((ret = read(fd, buf, BUFSIZ)) > 0) { + if (len + ret >= cap) { + cap *= 2; + contents = realloc(contents, cap); + if (!contents) err(1, "realloc"); + } + memcpy(contents + len, buf, ret); + len += ret; + } + contents[len] = '\0'; + + return contents; +} + +int +main(int argc, const char **argv) +{ + const char **arg; + const char *path; + char *str, *contents; + int fd; + + vars = NULL; + vars_end = &vars; + + if (!argc) return 1; + for (arg = argv + 1; *arg; arg++) { + if (!strcmp(*arg, "-h") || !strcmp(*arg, "--help")) { + fprintf(stderr, "Usage: tmpl [-C CONFIG].. [-D NAME=VALUE].. FILE..\n"); + return 1; + } else if (!strcmp(*arg, "-D") || !strcmp(*arg, "--define")) { + str = strdup(*++arg); + if (!str) err(1, "strdup"); + assign(str); + } else if (!strcmp(*arg, "-C") || !strcmp(*arg, "--config")) { + contents = readall(*++arg); + perline(contents, 1, assign); + free(contents); + } else { + break; + } + } + + for (; *arg; arg++) { + path = !strcmp(*arg, "-") ? "/dev/stdin" : *arg; + contents = readall(path); + perline(contents, 0, template); + free(contents); + } +}