Skip to content

Instantly share code, notes, and snippets.

@darkuranium
Last active August 28, 2025 12:13
Show Gist options
  • Save darkuranium/4f4f571f9e50240436ab to your computer and use it in GitHub Desktop.
Save darkuranium/4f4f571f9e50240436ab to your computer and use it in GitHub Desktop.
Macro language processor
#!/usr/bin/awk -f
# Usage: ./mproc.awk \
# KEY1=VAL1 KEY2=VAL2 ... \
# /path/to/config1.conf.in /path/to/config2.conf.in > /path/to/output.conf
function logCOLOR(col, action, str) {
printf "\033[%sm> %s\033[0m %s\n", col, action, str > "/dev/stderr";
}
function logVERBOSE(col, action, str) {
#logCOLOR(col, action, str);
}
function errFATAL(str) {
logCOLOR("31", "ERROR", str);
exit 1;
}
BEGIN {
# BRANCH STATE
btop = 0;
bstack_p[btop] = 1; # should we print the contents?
bstack_h[btop] = 1; # did we print at any point in the past?
for(i = 1; i < ARGC; i++)
{
eq = index(ARGV[i], "=");
if(eq)
{
key = substr(ARGV[i], 1, eq - 1);
val = substr(ARGV[i], eq + 1);
vars[key] = val;
ARGV[i] = "";
}
else
process_file(ARGV[i]);
}
# don't process anything else
ARGC = 1;
}
function includefile(fname, raw, line) {
logVERBOSE("32", "INCLUDE" (raw ? " RAW" : ""), fname);
if(raw)
{
while((getline line < fname) > 0)
printf "%s\n", line;
}
else
process_file(fname);
}
function isfalse(str) {
return (tolower(str) ~ /^(0|no|off|false)?$/);
}
function istrue(str) {
return !isfalse(str);
}
function istrue_var(var) {
return (var ~ /^\!/) ? isfalse(vars[var]) : istrue(vars[var]);
}
function err_stray(kw) {
errFATAL(sprintf("Stray `%s` in input (missing `@if`?)", kw));
}
function expand_vars(src, dst, end, pattern, var, rstart, rlength) {
end = 1;
dst = "";
while (match(src, /@[[:alpha:]_][[:alnum:]_]*@/)) {
rstart = RSTART;
rlength = RLENGTH;
pattern = substr(src, rstart + 1, rlength - 2);
if(!(pattern in vars))
printf "Warning: Variable `%s` not set!\n", pattern > "/dev/stderr";
var = vars[pattern];
if(macros[pattern])
var = expand_vars(var);
dst = dst substr(src, 1, rstart - 1) var;
end = rstart + rlength;
src = substr(src, end);
}
dst = dst src;
return dst;
}
function cmd_get_name(cmd) {
match(cmd, /^[[:space:]]*@[[:alpha:]_.][[:alnum:]_.]*[[:space:]]/);
return substr(cmd, RSTART + 1, RLENGTH - 2);
}
function cmd_get_body(cmd, ret) {
# trim leading spaces
match(cmd, /^[[:space:]]*@[[:alpha:]_.][[:alnum:]_.]*[[:space:]]+/);
ret = substr(cmd, RSTART + RLENGTH);
#trim trailing spaces
match(ret, /[[:space:]]*$/);
return substr(ret, 0, RSTART);
}
# we have to use a separate function because of recursion!
function process_file(fname, pname)
{
logVERBOSE("36", "PROCESS BEGIN", fname);
pname = vars["MP_SRC_FILENAME"];
vars["MP_SRC_FILENAME"] = fname;
while((getline < fname) > 0)
{
if($0 ~ /^[[:space:]]*@include(\.raw)?[[:space:]]+.+$/) {
cmd = cmd_get_name($0);
file = cmd_get_body($0);
includefile(file, cmd == "include.raw");
continue;
}
if($0 ~ /^[[:space:]]*@#.*$/) { # comments
continue;
}
if($0 ~ /^[[:space:]]*@[[:space:]]*$/) { # blank `@` lines
continue;
}
if($0 ~ /^[[:space:]]*@define[[:space:]]+[[:alpha:]_][[:alnum:]_]*[:?]?=.*?$/) {
if(!bstack_p[btop]) continue;
eq = index($0, "=");
split($2, spl, "=");
key = spl[1];
val = substr($0, eq+1);
end = substr(key, length(key));
nkey = substr(key, 1, length(key)-1);
if(end == "?") # ignore if set
{
key = nkey;
if(key in vars)
continue;
}
else if(end == ":") # expand now
{
key = nkey;
val = expand_vars(val);
macros[key] = 0;
}
else
macros[key] = 1;
vars[key] = val;
#printf "DEFINE %s => %s %s\n", key, val, end;
continue;
}
if($0 ~ /^[[:space:]]*@undef[[:space:]]+[[:alpha:]_][[:alnum:]_]*[[:space:]]*$/) {
if(!bstack_p[btop]) continue;
key = $2;
delete vars[$2];
delete macros[$2];
#printf "UNDEF %s\n", $2;
continue;
}
if($0 ~ /^[[:space:]]*@if[[:space:]]+\!?[[:alpha:]_][[:alnum:]_]*[[:space:]]*$/) {
btop++;
bstack_p[btop] = istrue_var($2);
bstack_h[btop] = bstack_p[btop];
#printf "IF %s => bstack_p[%s] = %s\n", $2, btop, bstack_p[btop];
continue;
}
if($0 ~ /^[[:space:]]*@elif[[:space:]]+\!?[[:alpha:]_][[:alnum:]_]*[[:space:]]*$/) {
if(!btop)
err_stray($1);
if(bstack_h[btop] == 2)
printf "Warning: `@elif` after `@else`!\n" > "/dev/stderr";
if(bstack_h[btop])
bstack_p[btop] = 0;
else
{
bstack_p[btop] = istrue_var($2);
bstack_h[btop] = bstack_p[btop];
}
#printf "ELIF %s => bstack_p[%s] = %s\n", $2, btop, bstack_p[btop];
continue;
}
if($0 ~ /^[[:space:]]*@else[[:space:]]*$/) {
if(!btop)
err_stray($1);
bstack_p[btop] = !bstack_h[btop];
bstack_h[btop] = 2;
#printf "ELSE %s\n", btop;
continue;
}
if($0 ~ /^[[:space:]]*@endif[[:space:]]*$/) {
if(!btop)
err_stray($1);
btop--;
#printf "ENDIF %s\n", btop;
continue;
}
if($0 ~ /^[[:space:]]*@/) {
errFATAL(sprintf("Unknown command `%s`", $0));
}
if(!bstack_p[btop]) continue;
print expand_vars($0);
}
close(fname);
vars["MP_SRC_FILENAME"] = pname;
logVERBOSE("36", "PROCESS END", fname);
}
@# output a warning into output
# !!! AUTOGENERATED FILE - DO NOT EDIT !!!
# Generated from @MP_SRC_FILENAME@
@include other.conf.in
@include.raw other.conf.in
@define INDEXES?=index.html index.htm
@if ENABLE_PHP
@define INDEXES:=@INDEXES@ index.php
@endif
server {
root /home/@USER@/public_html;
server_name @DOMAIN@ @[email protected];
index @INDEXES@;
@if ENABLE_PHP
location ~ \.php$ {
fastcgi_pass /var/run/php5/@[email protected];
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
@endif
}
# this is included from `other.conf.in` for @USER@!
@define DOMAIN:=@DOMAIN@ whatever.com
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment