diff options
Diffstat (limited to 'bin/wag')
| -rwxr-xr-x | bin/wag | 288 |
1 files changed, 288 insertions, 0 deletions
@@ -0,0 +1,288 @@ +#!/bin/sh + +## wag is a tool to generate static web sites + +unset CDPATH +export LC_ALL=C IFS='
+' + +cmd=$(command -v "$0") + +## help prints this program documentation +help() { awk '/^## / {print substr($0, 4)}' "$cmd"; } + +lipsum='Lorem ipsum dolor sit amet, consectetur adipiscing elit, +sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris +nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in +reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla +pariatur. Excepteur sint occaecat cupidatat non proident, sunt in +culpa qui officia deserunt mollit anim id est laborum.' + +# An overly simplified http request parser for static web sites. +http_request() { + read -r cmd uri proto + case $uri in */) uri="${uri}index.html" ;; esac + while true; do + read -r line || break + [ ${#line} = 0 ] && break + done + printf 'HTTP/1.1 200 OK\n\n' && cat "${uri#/}" +} + +md2h() { want="$2" got=$(echo "$1" | md2html -); } + +md2html() { + tmp=$(mktemp -u) + trap "rm -f '$tmp'" EXIT + { + cat "${1:--}" | tee "$tmp" | awk '/^[ ]*\[[^]]+\]:/' + cat "$tmp" + } | + awk ' + function newblock(nblock) { + if (text) + print "<" block ">" text "</" block ">" + text = "" + out = 1 + block = nblock ? nblock : "p" + } + + function subinline(tgl, inl) { + while (match($0, tgl)) { + if (inline[ni] == inl) + ni -= sub(tgl, "</" inl ">") + else if (sub(tgl, "<" inl ">")) + inline[++ni] = inl + } + } + + function dolink(href, lnk) { + # Undo escaped html in uris + gsub(/&/, "\\&", href) + gsub(/</, "<", href) + gsub(/>/, ">", href) + # & can be tricky, and not standard: + gsub(/&/, "\\\\\\&", href) + gsub(/&/, "\\\\\\&", lnk) + return "<a href=\"" href "\">" lnk "</a>" + } + + BEGIN { + ni = 0 # inlines + nl = 0 # nested lists + out = 0 # 0 if no output so far + text = "" + block = "p" + } + + # Skip front matter. + out == 0 && $0 == "---" { + do + getline + while ($0 != "---") + next + } + + # Escape HTML. + esc != "false" { + gsub("&", "\\&") + gsub("<", "\\<") + gsub(">", "\\>") + } + + # Internal references. + match($0, /^[ ]*\[[^]]+\]:/) > 0 { + k = substr($0, RSTART+1, RLENGTH-3) + v = substr($0, RLENGTH+1) + sub(/^[ ]/, "", v) + sub(/[ ]$/, "", v) + ref[substr($0, RSTART+1, RLENGTH-3)] = v + next + } + + # Horizontal rules. + /^[ ]*([-*_] ?)+[ ]*$/ && text == "" { + print "<hr>" + next + } + + # Tables. Syntax: + # Right Align| Center Align |Left Align + /([ ]\|)|(\|[ ])/ { + if (block != "table") + newblock("table") + nc = split($0, cells, "|") + $0 = "<tr>\n" + for (i = 1; i <= nc; i++) { + align = "left" + if (sub(/^[ ]+/, "", cells[i])) { + if (sub(/[ ]+$/, "", cells[i])) + align = "center" + else + align = "right" + } + sub(/[ ]+$/, "", cells[i]) + $0 = $0 "<td align=\"" align "\">" cells[i] "</td>\n" + } + $0 = $0 "</tr>" + } + + # Ordered and unordered (possibly nested) lists. + /^[ ]*([*+-]|(([0-9]+[.-]?)+))[ ]/ { + newblock("li") + nnl = 1 + while (match($0, /^[ ]/)) { + sub(/^[ ]/, "") + nnl++ + } + while (nl > nnl) + print "</" list[nl--] ">" + while (nl < nnl) { + list[++nl] = "ol" + if (match($0, /^[*+-]/)) + list[nl] = "ul" + print "<" list[nl] ">" + } + sub(/^([*+-]|(([0-9]+[.-]?)+))[ ]/, "") + } + + # Multi line list items. + block == "li" { + sub(/^( *)|( *)/, "") + } + + # Code blocks. + /^( | )/ { + if (block != "code") + newblock("code") + sub(/^( | )/, "") + text = text $0 "\n" + next + } + + # Paragraphs. + /^$/ { + newblock() + while (nl > 0) + print "</" list[nl--] ">" + } + + # Headers. + /^#+ / { + newblock() + match($0, /#+/) + n = RLENGTH + if (n > 6) + n = 6 + text = substr($0, RLENGTH + 1) + sub(/^ */, "", text) + block = "h" n + next + } + + # Alternate headers (underlined). + /^=+$/ { + block = "h" 1 + next + } + + /^-+$/ { + block = "h" 2 + next + } + + { + # Images. + while (match($0, /!\[[^]]+\]\([^)]+\)/)) { + split(substr($0, RSTART, RLENGTH), a, /(!\[)|\)|(\]\()/) + sub(/!\[[^]]+\]\([^)]+\)/, "<img src=\"" a[3] "\" alt=\"" a[2] "\">") + } + # Links. + while (match($0, /\[[^]]+\]\([^)]+\)/)) { + split(substr($0, RSTART, RLENGTH), a, /[[)]|(\]\()/) + sub(/\[[^]]+\]\([^)]+\)/, dolink(a[3], a[2])) + } + # Internal references. + while (match($0, /\[[^]]+\]/)) { + k = substr($0, RSTART+1, RLENGTH-2) + sub(/\[[^]]+\]/, dolink(ref[k], k)) + } + # Auto links (uri matching is poor). + na = split($0, a, /(^\()|[ ]|([,.)]([ ]|$))/) + for (i = 1; i <= na; i++) + if (match(a[i], /^(((https?|ftp|file|news|irc):\/\/)|(mailto:)).+$/)) + sub(a[i], dolink(a[i], a[i])) + # Inline. + subinline("(\\*\\*)|(__)", "strong") + subinline("\\*", "em") + subinline("`", "code") + text = text (text ? " " : "") $0 + } + + END { + while (ni > 0) + text = text "</" inline[ni--] ">" + newblock() + while (nl > 0) + print "</" list[nl--] ">" + }' +} + +serve() { while true; do nc -l -p 1500 -e "$cmd http_request"; done; } + +test() { + fail=0 pass=0 skip=0 tfilter="$*" + + test_run md2h 'abc __def__ ghi' '<p>abc <strong>def</strong> ghi</p>' + test_run md2h 'abc **def** ghi' '<p>abc <strong>def</strong> ghi</p>' + test_run md2h 'abc *def* ghi' '<p>abc <em>def</em> ghi</p>' + test_run md2h 'abc ***def*** ghi' '<p>abc <strong><em>def</strong></em> ghi</p>' + test_run md2h 'abc `def` ghi' '<p>abc <code>def</code> ghi</p>' + test_run md2h '# h1' '<h1>h1</h1>' + test_run md2h '## h2' '<h2>h2</h2>' + test_run md2h 'h1 +==' '<h1>h1</h1>' + test_run md2h 'h2 +--' '<h2>h2</h2>' + test_run md2h 'abc [github] def' '<p>abc <a href="">github</a> def</p>' + test_run md2h 'abc [github](https://github.com) def' '<p>abc <a href="https://github.com">github</a> def</p>' + test_run md2h 'abc [github] def + +[github]: https://github.com' '<p>abc <a href="https://github.com">github</a> def</p>' + test_run md2h '--- +Title: front matter test +--- + +Hello [world]. + +--- +Bye. + +[world]: http://example.com' '<p>Hello <a href="http://example.com">world</a>.</p> +<hr> +<p>Bye.</p>' + + echo "Total: $((pass + fail + skip)), Passed: $pass, Failed: $fail, Skip: $skip" + return "$fail" +} + +test_run() { + eval "test_$1=\$((test_$1 + 1)); ti=\"\$test_$1\"" + [ "$tfilter" ] && + case "$1#$ti" in + $tfilter) ;; + *) skip=$((skip + 1)); return 0 ;; + esac + "$@" + [ "$got" = "$want" ] && { + pass=$((pass + 1)) + return 0 + } + fail=$((fail + 1)) + printf "%s FAIL\n Got: %s\n Want: %s\n" "$1#$ti" "$got" "$want" >&2 + return 1 +} + +# Execute command line +[ "$1" ] || help && "$@" |
