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 --- readme.md | 20 ++++++ st.sh | 47 +++++++++++++ subtext.awk | 230 ++++++++++++++++++++++-------------------------------------- test2.st | 23 ++++++ 4 files changed, 174 insertions(+), 146 deletions(-) create mode 100644 readme.md create mode 100644 st.sh create mode 100644 test2.st diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..46c5943 --- /dev/null +++ b/readme.md @@ -0,0 +1,20 @@ +# subtext +## a layered roffish markup .. thing + +subtext is a way to write text using shell templating and roff-like aesthetics for fun. confused? I don't know how to describe it really. just look at the syntax, i guess. + +## syntax + +subtext's syntax is inspired by roff mostly, with some other line-oriented markups in there as well. its semantics are mostly inspired by this one program i read about reading files as shell here-docs. i kinda combined those ideas together to make an awk script that generates a shell script that you can execute to generate markup. i will *eventually* have html and gemtext macrofiles in this repo, so you can generate html and gemtext from the same source file. + +### text lines + +### shell substitution + +### dotted lines + +### hashed lines + +## what's a macrofile? + +note: i should probably change this name diff --git a/st.sh b/st.sh new file mode 100644 index 0000000..8274526 --- /dev/null +++ b/st.sh @@ -0,0 +1,47 @@ +### st.sh + +# utilities +echo()(printf '%s\n' "$*") + +handle_input() { # handle_input "$@" + # handle input --- args or stdin + test -n "$1" && printf '%s' "$*" + if read -r first_line + then echo "$first_line"; cat + else echo + fi +} + +# authoring commands +nb() { # comment mechanism + : +} + +# html +attr(){ # attr [NAME|VALUE] ATTR_STRING + case "$1" in + (name) echo "${2%%=*}" ;; + (value) echo "${2#*=}" ;; + esac +} + +html_el(){ # el TAG [ATTRS...] [TEXT...] [< INPUT] + tag="$1"; attrs= ; text= + shift + for arg + do + case "$arg" in + (*=*) attrs="$attrs ${arg%%=*}=${arg#*=}"; shift ;; + (*) break ;; + esac + done + + printf '<%s%s>%s' "$tag" "$attrs" + handle_input "$@" | sed '$s,$,,' +} + +alias p='html_el p' +alias a='html_el a' +alias h1='html_el h1' +alias blockquote='html_el blockquote' +alias bq=blockquote 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 } diff --git a/test2.st b/test2.st new file mode 100644 index 0000000..02cd2f9 --- /dev/null +++ b/test2.st @@ -0,0 +1,23 @@ +#so st.sh +#alias verse='sed "s/^/| /"' +here is a document.. it's a document, yep + +.h1 here's a header + +..verse +here's a poem +or something + +i don't know +.. + +#echo and oh yeah, a dang shell thing +#title=test + +here's more paragraphs. +it's a trip, right? +meh, whatever. + +
+don't forget regular html +
\ No newline at end of file -- cgit 1.4.1-21-gabe81