#!/bin/sh # htmp [-d] [-f] [TMPL] [VAR=VAL...] [--] [ARG...] - shell-based htemplate engine # -d: debug, print shell script instead of running it # -f: strip first line when shebang line (use #!/usr/bin/htmp -f) # # successfully tested with: mawk, gawk, nawk, 9awk, busybox awk # template syntax: # $ shell commands on own lines # ${VARIABLE_EXPANSION} # $(COMMAND_EXPANSION) # $# comments # $$ plain $ # trailing newlines are omitted with a final \ # restart as awk script, keep original stdin as fd 3 # NB: true + // doesnt work in mawk; exit $? is invalid regex in busybox awk false && /; exec 3>&0; awk -f "$0" -- "$@" | sh; eval exit $\?; / {} function shquot(s) { # does not add outer ' gsub("'", "'\\''", s) return s } BEGIN { split("", vars) split("", args) debug = 0 dashdash = 0 stripsh = 0 tfile = 0 for (i = 1; i < ARGC; i++) { if (!dashdash && ARGV[i] == "--") dashdash = 1 else if (!dashdash && ARGV[i] == "-d") debug = "__htmpHTMPhTmPhTMpHtmPhtmp__" else if (!dashdash && ARGV[i] == "-f") stripsh = 1 else if (!dashdash && match(ARGV[i], "=")) vars[substr(ARGV[i], 1, RSTART-1)] = \ substr(ARGV[i], RSTART+1) else if (!dashdash && !tfile) tfile = ARGV[i] else args[length(args)+1] = ARGV[i] } # fake single arg with template to awk ARGC = 2 ARGV[1] = tfile == 0 ? "-" : tfile if (debug) printf("cat <<'%s'\n", debug) for (v in vars) printf("export %s='%s'; ", v, shquot(vars[v])) printf("%s", "p() { printf '%s' \"$@\"; }; match() { case \"$1\" in $2) printf '%s\n' $3;; *) printf \"%s${4:+\n}\" \"${4:-}\"; esac }; template() { ") } NR == 1 && /^#!/ && stripsh { next } /^[\t ]*\$ / { # $ SHELL COMMAND sub("^[\t ]*\\$ ", "") print $0 next } { s = $0 if (match(s, "\\\\$")) { # NO NEWLINE \ nl = 0 s = substr(s, 1, length(s)-1) } else nl = 1 while (s != "") { if (match(s, "^\\$\\$")) { # $$ -> $ printf("p '$';") s = substr(s, RLENGTH+1) } else if (match(s, "^\\${[^}]*}")) { # ${VAR} or $VAR printf("p %s;", substr(s, 1, RLENGTH)) s = substr(s, RLENGTH+1) } else if (match(s, "^\\$\\([^\\)]*\\)")) { # $(COMMAND) printf("p \"$(%s)\";", substr(s, 3, RLENGTH-3)) s = substr(s, RLENGTH+1) } else if (match(s, "^[^\\$][^\\$]*") || match(s, "^\\$[0-9A-Za-z_][0-9A-Za-z_]*")) { # plain text printf("p '%s';", shquot(substr(s, 1, RLENGTH))) s = substr(s, RLENGTH+1) } else if (match(s, "^\\$#")) { # $# COMMENT break } else { if (!err) err="htmp: "FILENAME": can't parse line "NR": "s break } } if (nl) printf("printf '\\n';") printf("\n") } END { print "}" if (err) { printf("printf '%%s\\n' '%s' 1>&2; exit 111;\n", shquot(err)) } else { if (!debug) printf("%s", "exec 0>&3; exec 3>&-; ") printf("%s", "set -e -u; template") for (a in args) printf(" '%s'", shquot(args[a])) printf("\n") if (!debug) printf("exec 0>&-; \n") } if (debug) print debug }