# subtext -*- awk -*- # (C) C Duckworth ## Subtext is an awk program that converts roff-like input to a shell script ## that can output markup. Lines prefixed with . are wrapped in $(...) ## constructs within the `body' function, but lines prefixed with # are ## transported to the top of the file, where they're executed within the shell ## environment. BEGIN { ## Tuneables dryrun = dryrun ? dryrun : 0 sopath = sopath ? sopath : "." sofile = sofile ? sofile : "" shxend = shxend ? shxend : "%%end" ## Shellfix: escape ` and $ in input # Ask sed to do these b/c awk has no capture groups ;_; shellfix = "sed -E" \ " -e 's/`/\\\\`/g'" \ " -e 's/(^|[^\\$])\\$([^\\$]|$)/\\1\\\\$\\2/g'" \ " -e 's/(^|[^\\$])\\$(\\$+)([^\\$]|$)/\\1\\2\\3/g'" } /\\$/ { # line continuation getline nl sub(/\\$/, "") $0 = $0 " " nl } ### AWK layer /^%so/ { # source a file pushpar() source($2) next } /^%/ { next } # comments ### Shell layer /^#/ { # head lines sub(/^#+[ ]*/,"") head = head (head?"\n":"") $0 next } ### Text layer end[endn] && $0 == end[endn] { par = par "\n" end[endn--] "\n)" next } /^\.\./ { # block if (match($0, /[ ]*<<[ ]*/)) end[++endn] = substr($0, RSTART+RLENGTH) else end[++endn] = ".." $0 = substr($0, 3, RSTART?RSTART-3:length) par = par (par?"\n":"") \ "$$(" ($1 ? $1 " " shquote(2) : "") " << " end[endn] next } /^\.$/ { next } # continuation line /^\./ { # line par = par (body?"\n":"") \ "$$(" substr($1, 2) " " shquote(2) ")" next } /^\\/ { # special line escape $0 = substr($0, 2) } /^$/ { # blank line if (!pushpar()) next } { # regular text par = par (par?"\n":"") $0 } END { if (dead) exit dead print "#!/bin/sh" print "### generated with subtext" print "quote()(sed \"s/^/$1/\")" print "unquote()(sed \"s/^$1//\")" printf "shexpand()(eval \"$(echo 'cat<<%s';cat;echo '%s')\")\n", \ shxend, shxend printf "shellfix()(%s)\n", shellfix print "echo()(printf '%s\\n' \"$@\")" print "handle_input()("\ "test -n \"$1\" && printf '%s' \"$*\";"\ "if read -r first_line;"\ "then echo \"$first_line\";cat;return 0;"\ "else echo;return 1;fi)" printf "ST_SOPATH=%s\n", sopath if (sofile) source(sofile) print "### head" print head print "### body" print "body(){ unquote : << \\_ | shexpand" pushpar() shfq = shellfix " -e 's/^/:/'" print body | shfq close(shfq) print "_" print "}" if (!dryrun) print "body" } function shquote(begin, end, quoted, out) { if (!begin) begin = 1 if (!end) end = NF for (i=begin; i<=NF; i++) { if ($i ~ /"/) quoted = 1 if ($i ~ /^\$/) { quoted = 1 $i = "\"" $i } if ((($i ~ /\)$/) || ($i ~ /"$/)) && quoted) { quoted = 0 if ($i !~ /"$/) $i = $i "\"" out = out (out?" ":"") $i continue } if (!quoted) { gsub(/"/, "\\\"", $i) out = out (out?" ":"") "\"" $i "\"" } else out = out (out?" ":"") $i } return out } function slurp(to, out, nl) { while (nl != to) { getline nl out = out "\n+" nl } sub("\\+"to"$", "", out) return out } function pushpar() { if (!par) return 0 if (!match(par, /^[ \t\n]*[<$]/)) { par = "$$(p<<../p\n" par "\n../p\n)" } body = body (body?"\n":"") par par = "" return 1 } function source(name, found, sp) { split(sopath, asopath, ":") for (dir in asopath) { fn = asopath[dir] "/" name sp = asopath[dir] "\n\t" sp while ((getline ln < fn) > 0) { found = 1 head = head (head?"\n":"") ln } close(fn) if (found) break } if (!found) { printf "Couldn't source %s; looked in:\n\t%s", name, sp dead = 127 exit } }