From 54e07f822b463c8d461fef254213f83c59d82810 Mon Sep 17 00:00:00 2001 From: Case Duckworth Date: Thu, 1 Feb 2024 23:52:57 -0600 Subject: Refactor for interleaved shell commands and text --- subtext.awk | 230 ++++++++++++++++++++++-------------------------------------- 1 file changed, 84 insertions(+), 146 deletions(-) (limited to 'subtext.awk') diff --git a/subtext.awk b/subtext.awk index ee04238..c45aae4 100644 --- a/subtext.awk +++ b/subtext.awk @@ -1,194 +1,132 @@ -# subtext -- text substitutor -*- awk -*- +# 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 { - true = 1; false = 0 ## Tuneables - runbody = bool(runbody ? runbody : true) - postproc = postproc ? postproc : "shexpand" - bodyfunc = bodyfunc ? bodyfunc : "body" - sopath = get_value(sopath, ".:"ENVIRON["HOME"]"/.subtext") - ## Globals - ## Wrap the text in a function - pretext = "### begin text\n"bodyfunc"(){\n" - posttext = "}\n### end text" - ## Prelude function strings + dryrun = dryrun ? dryrun : 0 + sopath = sopath ? sopath : ".:" ENVIRON["HOME"] "/.subtext" + bodyend = bodyend ? bodyend : "_@end@_" + ## Kernel # 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'" shxwrap = " -e 's/^/:/'" - htmlfix = "sed -E" \ - " -e 's#([^\\\\]|^)&#\\1\\&#g'" \ - " -e 's#([^\\\\]|^)<#\\1\\<#g'" \ - " -e 's#([^\\\\]|^)>#\\1\\>#g'" \ - " -e 's#\\\\([&<>])#\\1#g'"; - ## Prelude - par = "#!/bin/sh\n### kernel\n" \ - "preface()(sed \"s/^/$1/\")\n" \ - "unpreface()(sed \"s/^$1//\")\n" \ - "shexpand()(eval \"$( (echo 'cat<<.';preface +;echo .)" \ - " | unpreface + )\")\n" \ - "### library\n" \ + kernel = "quote()(sed \"s/^/$1/\")\n" \ + "unquote()(sed \"s/^$1//\")\n" \ + "shexpand()(eval " \ + "\"$(echo 'cat<<" bodyend "';cat;echo " bodyend ")\")\n" \ "shellfix()(" shellfix ")\n" \ - "htmlfix()(" htmlfix ")\n" \ - "### variables\n" \ - "ST_POSTPROC=" postproc "\n" \ - "ST_BODYFUNC=" bodyfunc "\n" \ - "ST_SOPATH=" sopath "\n" \ - "### header\n" + "ST_SOPATH=" sopath } -### End a block - -end[endn] && $0 == end[endn] { - pushpar(end[endn--] "\n)", true) - printpar() - subdocp = false - next -} - -### Line continuation - -/\\$/ { +/\\$/ { # line continuation getline nl - sub(/\\$/,"") + sub(/\\$/, "") $0 = $0 " " nl } -### Special commands -## These call subtext-internal functions - -/^#so/ { # Insert $2 verbatim (if in sopath), else error - pushpar($0) +/^#so/ { # source a file + pushpar() source($2) next } -/^###$/ { # Delimit document - printpar() - docp = !docp - if (docp) { - print pretext "unpreface ':'<<'_'|eval \"$ST_POSTPROC\"" - } else { - end_text() - } +/^#/ { # head lines + sub(/^#+[ ]*/,"") + head = head (head?"\n":"") $0 next } -### Escape sequences - -/^\.\./ && docp { # Begin a heredoc - # ..[] [] [<2 ? " | " substr($1,3) " " quote(2) : "") \ + slurp("..") \ + "..\n)" + next } -/^\./ && docp { # One-line command - # . [] - ## wraps the line in $( ... ), basically (also quotes) - specialp = 2 - gsub(/"/, "\\\\&", $0) - ln = "$$(" substr($1, 2) - for (f=2; f<=NF; f++) { - ln = ln " \"" $f "\"" - } - ln = ln ")" - $0 = ln +/^\./ { # line + body = body (body?"\n":"") \ + "$$(" substr($1, 2) " " quote(2) ")" + next } -/^\\/ && docp { # \ at the beginning of a line escapes the next character - $0 = substr($0, 2) +/^$/ { # line break + if (!pushpar()) next } -### Book-keeping - -/^$/ { - if (!par) - next - printpar() -} - -{ - pushpar($0) - if (--specialp < 0) - specialp = 0 +{ # regular text + par = par (par?"\n":"") $0 } END { - if (dead) - exit dead - if (par) - printpar() - while (endn > 0) - print "\n" end[endn--] "\n)" - if (docp) - end_text() - if (runbody) - print bodyfunc -} - -function end_text() { - print "_\n" posttext + if (dead) exit dead + + print "#!/bin/sh" + print "### generated with subtext" + print kernel + print "### head" + print head + print "### body" + print "body(){ shexpand << \\_" + pushpar() + print body | shellfix + close(shellfix) + print "_" + print "}" + if (!dryrun) print "body" +} + +function quote(begin, end, out) { + if (!begin) begin = 1 + if (!end) end = NF + for (i=begin; i<=NF; i++) { + gsub(/"/, "\\\"", $i) + out = out (out?" ":"") "\"" $i "\"" + } + return out } -function pushpar(text, force_newline) { - par = par ((par || force_newline) ? "\n" : "") text +function slurp(to, out, nl) { + while (nl != to) { + getline nl + out = out "\n+" nl + } + sub("\\+"to"$", "", out) + return out } -function printpar() { - specialp = specialp || (match(par, /^[ ]*" par "

" - shx = shellfix shxwrap - print par | shx - close(shx) - } else - print par +function pushpar() { + if (!par) return 0 + if (!match(par, /^[ ]*" par "

" + body = body (body?"\n":"") par par = "" + return 1 } -function get_value(var, env_var, default) { - if (var) - return var - else - return default -} - -function bool(var, default) { - if (var=="false" || var=="no" || var=="0") - return false - else if (var=="true" || var=="yes" || var=="1") - return true - else - return var || default -} - -function source(name) { - found = false - sp = "" +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 = true - pushpar(ln) + found = 1 + head = head (head?"\n":"") ln } - if (found) - break + if (found) break + } + if (!found) { + printf "Couldn't source %s; looked in:\n\t%s", name, sp + dead = 127 + exit } - if (!found) - die("Couldn't source " name "; looked in:\n\t" sp, 9) -} - -function die(message, code) { - dead = code - print "!!" FILENAME ":" NR ": " message > "/dev/stderr" - exit } -- cgit 1.4.1-21-gabe81