#! /usr/bin/awk -f # # Markdown processor in AWK. It is simplified from d.awk # https://github.com/wernsey/d.awk # # (c) 2016 Werner Stoop # Slight modifications (c) 2022 Case Duckworth # Copying and distribution of this file, with or without modification, # are permitted in any medium without royalty provided the copyright # notice and this notice are preserved. This file is offered as-is, # without any warranty. BEGIN { # Configuration options if (Title == "") { Title = "Documentation" } if (Theme == "") { Theme = 1 } if (HideToCLevel == "") { HideToCLevel = 3 } if (Lang == "") { Lang = "en" } if (Tables == "") { Tables = 1 #TopLinks = 1; #classic_underscore = 1; } if (MaxWidth == "") { MaxWidth = "1080px" } if (NumberHeadings == "") { NumberHeadings = 1 } if (NumberH1s == "") { NumberH1s = 0 } Mode = "p" ToC = "" ToCLevel = 1 CSS = init_css(Theme) for (i = 0; i < 128; i++) { _ord[sprintf("%c", i)] = i } srand() } { gsub(/\r/, "", $0) } { Out = Out filter($0) } END { if (Mode == "ul" || Mode == "ol") { while (ListLevel > 1) { Buf = Buf "\n" Open[ListLevel--] ">" } Out = Out tag(Mode, Buf "\n") } else if (Mode == "pre") { while (ListLevel > 1) { Buf = Buf "\n" Open[ListLevel--] ">" } Out = Out tag(Mode, Buf "\n") } else if (Mode == "table") { Out = Out end_table() } else { Buf = trim(scrub(Buf)) if (Buf) { Out = Out tag(Mode, Buf) } } if (ToC && match(Out, /!\[toc[-+]?\]/)) { print "" } if (Out) { Out = fix_footnotes(Out) Out = fix_links(Out) Out = fix_abbrs(Out) Out = make_toc(Out) print trim(Out) if (footnotes) { footnotes = fix_links(footnotes) print "
" } else if (match(st, /^[[:space:]]*>/)) { Buf = Buf "\n" scrub(trim(substr(st, RSTART + RLENGTH))) } else if (match(st, /^[[:space:]]*$/)) { res = tag("blockquote", tag("p", trim(Buf))) pop() res = res filter(st) } else { Buf = Buf st } } else if (Mode == "table") { if (match(st, /.*\|(.*\|)+/)) { process_table_row(st) } else { res = end_table() pop() res = res filter(st) } } else if (Mode == "pre") { if (! Preterm && match(st, /^(( )| *\t)/) || Preterm && ! match(st, /^[[:space:]]*```+/)) { Buf = Buf ((Buf) ? "\n" : "") substr(st, RSTART + RLENGTH) } else { gsub(/\t/, " ", Buf) if (length(trim(Buf)) > 0) { plang = "" mmaid = 0 if (match(Preterm, /^[[:space:]]*```+/)) { plang = trim(substr(Preterm, RSTART + RLENGTH)) if (plang) { HasPretty = 1 if (plang == "auto") { plang = "class=\"prettyprint\"" } else { plang = "class=\"prettyprint lang-" plang "\"" } } } if (mmaid && Mermaid) { res = tag("div", Buf, "class=\"mermaid\"") } else { res = tag("pre", tag("code", escape(Buf), plang)) } } pop() if (Preterm) { sub(/^[[:space:]]*```+[[:alnum:]]*/, "", st) } res = res filter(st) } } else if (Mode == "ul" || Mode == "ol") { if (ListLevel == 0 || match(st, /^[[:space:]]*$/) && (RLENGTH <= indent[1])) { while (ListLevel > 1) { Buf = Buf "\n" Open[ListLevel--] ">" } res = tag(Mode, "\n" Buf "\n") pop() } else if (match(st, /^[[:space:]]*([*+-]|[[:digit:]]+\.)/)) { tmp = substr(st, RLENGTH + 1) match(st, /^[[:space:]]*/) if (RLENGTH > indent[ListLevel]) { indent[++ListLevel] = RLENGTH if (match(st, /^[[:space:]]*[*+-]/)) { Open[ListLevel] = "ul" } else { Open[ListLevel] = "ol" } Buf = Buf "\n<" Open[ListLevel] ">" } else { while (RLENGTH < indent[ListLevel]) { Buf = Buf "\n" Open[ListLevel--] ">" } } if (match(tmp, /^[[:space:]]*\[[xX[:space:]]\]/)) { st = substr(tmp, RLENGTH + 1) tmp = tolower(substr(tmp, RSTART, RLENGTH)) Buf = Buf "
or blocks
pos = match(st, /\[[^\]]+\]/)
if (! pos) {
break
}
if (pre && pre < pos) {
match(st, /<\/(pre|code)>/)
res = res substr(st, 1, RSTART + RLENGTH)
st = substr(st, RSTART + RLENGTH + 1)
continue
}
img = substr(st, RSTART - 1, 1) == "!"
if (substr(st, RSTART - (img ? 2 : 1), 1) == "\\") {
res = res substr(st, 1, RSTART - (img ? 3 : 2))
if (img && substr(st, RSTART, RLENGTH) == "[toc]") {
res = res "\\"
}
res = res substr(st, RSTART - (img ? 1 : 0), RLENGTH + (img ? 1 : 0))
st = substr(st, RSTART + RLENGTH)
continue
}
res = res substr(st, 1, RSTART - (img ? 2 : 1))
rx = substr(st, RSTART, RLENGTH)
st = substr(st, RSTART + RLENGTH)
if (match(st, /^[[:space:]]*\([^)]+\)/)) {
lt = substr(rx, 2, length(rx) - 2)
match(st, /\([^)]+\)/)
url = substr(st, RSTART + 1, RLENGTH - 2)
st = substr(st, RSTART + RLENGTH)
ld = ""
if (match(url, /[[:space:]]+["']/)) {
ld = url
url = substr(url, 1, RSTART - 1)
match(ld, /["']/)
delim = substr(ld, RSTART, 1)
if (match(ld, delim ".*" delim)) {
ld = substr(ld, RSTART + 1, RLENGTH - 2)
}
} else {
ld = ""
}
if (img) {
res = res ""
} else {
res = res "" lt ""
}
} else if (match(st, /^[[:space:]]*\[[^\]]*\]/)) {
lt = substr(rx, 2, length(rx) - 2)
match(st, /\[[^\]]*\]/)
lr = trim(tolower(substr(st, RSTART + 1, RLENGTH - 2)))
if (! lr) {
lr = tolower(trim(lt))
if (LinkDescs[lr]) {
lt = LinkDescs[lr]
}
}
st = substr(st, RSTART + RLENGTH)
url = LinkUrls[lr]
ld = LinkDescs[lr]
if (img) {
res = res ""
} else if (url) {
res = res "" lt ""
} else {
res = res "[" lt "][" lr "]"
}
} else {
res = res (img ? "!" : "") rx
}
} while (pos > 0)
return (res st)
}
function heading(level, st, res, href, u, text, svg)
{
if (level > 6) {
level = 6
}
st = trim(st)
href = tolower(st)
href = strip_tags(href)
gsub(/[^-_ [:alnum:]]+/, "", href)
gsub(/[[:space:]]/, "-", href)
if (TitleUrls[href]) {
for (u = 1; TitleUrls[href "-" u]; u++) {
}
href = href "-" u
}
TitleUrls[href] = "#" href
svg = ""
text = "" st " " svg "" (TopLinks ? " ↑ Top" : "")
res = tag("h" level, text, "id=\"" href "\"")
for (; ToCLevel < level; ToCLevel++) {
ToC_ID++
if (ToCLevel < HideToCLevel) {
ToC = ToC "▼"
ToC = ToC ""
} else {
ToC = ToC "►"
ToC = ToC "- " st "\n"
ToCLevel = level
return res
}
function init_css(Theme, css, ss, hr, c1, c2, c3, c4, c5, bg1, bg2, bg3, bg4, ff, fs, i)
{
if (Theme == "0") {
return ""
}
css["body"] = "color:%color1%;font-family:%font-family%;font-size:%font-size%;line-height:1.5em;" "padding:1em 2em;width:80%;max-width:%maxwidth%;margin:0 auto;min-height:100%;float:none;"
css["h1"] = "border-bottom:1px solid %color1%;padding:0.3em 0.1em;"
css["h1 a"] = "color:%color1%;"
css["h2"] = "color:%color2%;border-bottom:1px solid %color2%;padding:0.2em 0.1em;"
css["h2 a"] = "color:%color2%;"
css["h3"] = "color:%color3%;border-bottom:1px solid %color3%;padding:0.1em 0.1em;"
css["h3 a"] = "color:%color3%;"
css["h4,h5,h6"] = "padding:0.1em 0.1em;"
css["h4 a,h5 a,h6 a"] = "color:%color4%;"
css["h1,h2,h3,h4,h5,h6"] = "font-weight:bolder;line-height:1.2em;"
css["h4"] = "border-bottom:1px solid %color4%"
css["p"] = "margin:0.5em 0.1em;"
css["hr"] = "background:%color1%;height:1px;border:0;"
css["a.normal, a.toc"] = "color:%color2%;"
#css["a.normal:visited"] = "color:%color2%;";
#css["a.normal:active"] = "color:%color4%;";
css["a.normal:hover, a.toc:hover"] = "color:%color4%;"
css["a.top"] = "font-size:x-small;text-decoration:initial;float:right;"
css["a.header svg"] = "opacity:0;"
css["a.header:hover svg"] = "opacity:1;"
css["a.header"] = "text-decoration: none;"
css["strong,b"] = "color:%color1%"
css["code"] = "color:%color2%;font-weight:bold;"
css["blockquote"] = "margin-left:1em;color:%color2%;border-left:0.2em solid %color3%;padding:0.25em 0.5em;overflow-x:auto;"
css["pre"] = "color:%color2%;background:%color5%;border:1px solid;border-radius:2px;line-height:1.25em;margin:0.25em 0.5em;padding:0.75em;overflow-x:auto;"
css["table.dawk-ex"] = "border-collapse:collapse;margin:0.5em;"
css["th.dawk-ex,td.dawk-ex"] = "padding:0.5em 0.75em;border:1px solid %color4%;"
css["th.dawk-ex"] = "color:%color2%;border:1px solid %color3%;border-bottom:2px solid %color3%;"
css["tr.dawk-ex:nth-child(odd)"] = "background-color:%color5%;"
css["table.da"] = "border-collapse:collapse;margin:0.5em;"
css["table.da th,td"] = "padding:0.5em 0.75em;border:1px solid %color4%;"
css["table.da th"] = "color:%color2%;border:1px solid %color3%;border-bottom:2px solid %color3%;"
css["table.da tr:nth-child(odd)"] = "background-color:%color5%;"
css["div.dawk-ex"] = "padding:0.5em;"
css["caption.dawk-ex"] = "padding:0.5em;font-style:italic;"
css["dl.dawk-ex"] = "margin:0.5em;"
css["dt.dawk-ex"] = "font-weight:bold;"
css["dd.dawk-ex"] = "padding:0.3em;"
css["mark.dawk-ex"] = "color:%color5%;background-color:%color4%;"
css["del.dawk-ex,s.dawk-ex"] = "color:%color4%;"
css["a.toc-button"] = "color:%color2%;cursor:pointer;font-size:small;padding: 0.3em 0.5em 0.5em 0.5em;font-family:monospace;border-radius:3px;"
css["a.toc-button:hover"] = "color:%color4%;background:%color5%;"
css["div#table-of-contents"] = "padding:0;font-size:smaller;"
css["abbr"] = "cursor:help;"
css["ol.footnotes"] = "font-size:small;color:%color4%"
css["a.footnote"] = "font-size:smaller;text-decoration:initial;"
css["a.footnote-back"] = "text-decoration:initial;font-size:x-small;"
css[".fade"] = "color:%color5%;"
css[".highlight"] = "color:%color2%;background-color:%color5%;"
css["summary"] = "cursor:pointer;"
css["ul.toc"] = "list-style-type:none;"
if (NumberHeadings) {
if (NumberH1s) {
css["body"] = css["body"] "counter-reset: h1 toc1;"
css["h1"] = css["h1"] "counter-reset: h2 h3 h4;"
css["h2"] = css["h2"] "counter-reset: h3 h4;"
css["h3"] = css["h3"] "counter-reset: h4;"
css["h1::before"] = "content: counter(h1) \" \"; counter-increment: h1; margin-right: 10px;"
css["h2::before"] = "content: counter(h1) \".\"counter(h2) \" \";counter-increment: h2; margin-right: 10px;"
css["h3::before"] = "content: counter(h1) \".\"counter(h2) \".\"counter(h3) \" \";counter-increment: h3; margin-right: 10px;"
css["h4::before"] = "content: counter(h1) \".\"counter(h2) \".\"counter(h3)\".\"counter(h4) \" \";counter-increment: h4; margin-right: 10px;"
css["li.toc-1"] = "counter-reset: toc2 toc3 toc4;"
css["li.toc-2"] = "counter-reset: toc3 toc4;"
css["li.toc-3"] = "counter-reset: toc4;"
css["a.toc-1::before"] = "content: counter(h1) \" \";counter-increment: toc1;"
css["a.toc-2::before"] = "content: counter(h1) \".\" counter(toc2) \" \";counter-increment: toc2;"
css["a.toc-3::before"] = "content: counter(h1) \".\" counter(toc2) \".\" counter(toc3) \" \";counter-increment: toc3;"
css["a.toc-4::before"] = "content: counter(h1) \".\" counter(toc2) \".\" counter(toc3) \".\" counter(toc4) \" \";counter-increment: toc4;"
} else {
css["h1"] = css["h1"] "counter-reset: h2 h3 h4;"
css["h2"] = css["h2"] "counter-reset: h3 h4;"
css["h3"] = css["h3"] "counter-reset: h4;"
css["h2::before"] = "content: counter(h2) \" \";counter-increment: h2; margin-right: 10px;"
css["h3::before"] = "content: counter(h2) \".\"counter(h3) \" \";counter-increment: h3; margin-right: 10px;"
css["h4::before"] = "content: counter(h2) \".\"counter(h3)\".\"counter(h4) \" \";counter-increment: h4; margin-right: 10px;"
css["li.toc-1"] = "counter-reset: toc2 toc3 toc4;"
css["li.toc-2"] = "counter-reset: toc3 toc4;"
css["li.toc-3"] = "counter-reset: toc4;"
css["a.toc-2::before"] = "content: counter(toc2) \" \";counter-increment: toc2;"
css["a.toc-3::before"] = "content: counter(toc2) \".\" counter(toc3) \" \";counter-increment: toc3;"
css["a.toc-4::before"] = "content: counter(toc2) \".\" counter(toc3) \".\" counter(toc4) \" \";counter-increment: toc4;"
}
}
# Colors:
#c1="#314070";c2="#465DA6";c3="#6676A8";c4="#A88C3F";c5="#E8E4D9";
c1 = "#314070"
c2 = "#384877"
c3 = "#6676A8"
c4 = "#738FD0"
c5 = "#FBFCFF"
# Font Family:
ff = "sans-serif"
fs = "11pt"
# Alternative color scheme suggestions:
#c1="#303F9F";c2="#0449CC";c3="#2162FA";c4="#4B80FB";c5="#EDF2FF";
#ff="\"Trebuchet MS\", Helvetica, sans-serif";
#c1="#430005";c2="#740009";c3="#A6373F";c4="#c55158";c5="#fbf2f2";
#ff="Verdana, Geneva, sans-serif";
#c1="#083900";c2="#0D6300";c3="#3C8D2F";c4="#50be3f";c5="#f2faf1";
#ff="Georgia, serif";
#c1="#35305D";c2="#646379";c3="#7A74A5";c4="#646392";c5="#fafafa";
for (i = 0; i <= 255; i++) {
_hex[sprintf("%02X", i)] = i
}
for (k in css) {
ss = ss "\n" k "{" css[k] "}"
}
gsub(/%maxwidth%/, MaxWidth, ss)
gsub(/%color1%/, c1, ss)
gsub(/%color2%/, c2, ss)
gsub(/%color3%/, c3, ss)
gsub(/%color4%/, c4, ss)
gsub(/%color5%/, c5, ss)
gsub(/%font-family%/, ff, ss)
gsub(/%font-size%/, fs, ss)
gsub(/%hr%/, hr, ss)
return ss
}
function itag(t, body)
{
return ("<" t ">" body "" t ">")
}
function make_toc(st, r, p, dis, t, n)
{
if (! ToC) {
return st
}
for (; ToCLevel > 1; ToCLevel--) {
ToC = ToC "
"
}
p = match(st, /!\[toc[-+]?\]/)
while (p) {
if (substr(st, RSTART - 1, 1) == "\\") {
r = r substr(st, 1, RSTART - 2) substr(st, RSTART, RLENGTH)
st = substr(st, RSTART + RLENGTH)
p = match(st, /!\[toc[-+]?\]/)
continue
}
++n
dis = index(substr(st, RSTART, RLENGTH), "+")
t = " "
}
ToC = ToC "\n" (dis ? "▼" : "►") " Contents\n" "\n" ToC "
\n\n"
r = r substr(st, 1, RSTART - 1)
r = r t
st = substr(st, RSTART + RLENGTH)
p = match(st, /!\[toc[-+]?\]/)
}
return (r st)
}
function obfuscate(e, r, i, t, o)
{
for (i = 1; i <= length(e); i++) {
t = substr(e, i, 1)
r = int(rand() * 100)
if (r > 50) {
o = o sprintf("%02X;", _ord[t])
} else if (r > 10) {
o = o sprintf("%d;", _ord[t])
} else {
o = o t
}
}
return o
}
function pop()
{
Mode = Stack[--StackTop]
Buf = ""
return Mode
}
function process_table_row(st, cols, i)
{
if (match(st, /^[[:space:]]*\|/)) {
st = substr(st, RSTART + RLENGTH)
}
if (match(st, /\|[[:space:]]*$/)) {
st = substr(st, 1, RSTART - 1)
}
st = trim(st)
if (match(st, /^([[:space:]:|]|---+)*$/)) {
IsHeaders[Row - 1] = 1
cols = split(st, A, /[[:space:]]*\|[[:space:]]*/)
for (i = 1; i <= cols; i++) {
if (match(A[i], /^:-*:$/)) {
Align[i] = "center"
} else if (match(A[i], /^-*:$/)) {
Align[i] = "right"
} else if (match(A[i], /^:-*$/)) {
Align[i] = "left"
}
}
return
}
cols = split(st, A, /[[:space:]]*\|[[:space:]]*/)
for (i = 1; i <= cols; i++) {
Table[Row, i] = A[i]
}
NCols[Row] = cols
if (cols > MaxCols) {
MaxCols = cols
}
IsHeaders[Row] = 0
Row++
}
function push(newmode)
{
Stack[StackTop++] = Mode
Mode = newmode
}
function scrub(st, mp, ms, me, r, p, tg, a)
{
sub(/ $/, "
\n", st)
gsub(/( |[[:space:]]+\\)\n/, "
\n", st)
gsub(/( |[[:space:]]+\\)$/, "
\n", st)
while (match(st, /(__?|\*\*?|~~|`+|[&><\\])/)) {
a = substr(st, 1, RSTART - 1)
mp = substr(st, RSTART, RLENGTH)
ms = substr(st, RSTART - 1, 1)
me = substr(st, RSTART + RLENGTH, 1)
p = RSTART + RLENGTH
if (! classic_underscore && match(mp, /_+/)) {
if (match(ms, /[[:alnum:]]/) && match(me, /[[:alnum:]]/)) {
tg = substr(st, 1, index(st, mp))
r = r tg
st = substr(st, index(st, mp) + 1)
continue
}
}
st = substr(st, p)
r = r a
ms = ""
if (mp == "\\") {
if (match(st, /^!?\[/)) {
r = r "\\" substr(st, RSTART, RLENGTH)
st = substr(st, 2)
} else if (match(st, /^(\*\*|__|~~|`+)/)) {
r = r substr(st, 1, RLENGTH)
st = substr(st, RLENGTH + 1)
} else {
r = r substr(st, 1, 1)
st = substr(st, 2)
}
continue
} else if (mp == "_" || mp == "*") {
if (match(me, /[[:space:]]/)) {
r = r mp
continue
}
p = index(st, mp)
while (p && match(substr(st, p - 1, 1), /[\\[:space:]]/)) {
ms = ms substr(st, 1, p - 1) mp
st = substr(st, p + length(mp))
p = index(st, mp)
}
if (! p) {
r = r mp ms
continue
}
ms = ms substr(st, 1, p - 1)
r = r itag("em", scrub(ms))
st = substr(st, p + length(mp))
} else if (mp == "__" || mp == "**") {
if (match(me, /[[:space:]]/)) {
r = r mp
continue
}
p = index(st, mp)
while (p && match(substr(st, p - 1, 1), /[\\[:space:]]/)) {
ms = ms substr(st, 1, p - 1) mp
st = substr(st, p + length(mp))
p = index(st, mp)
}
if (! p) {
r = r mp ms
continue
}
ms = ms substr(st, 1, p - 1)
r = r itag("strong", scrub(ms))
st = substr(st, p + length(mp))
} else if (mp == "~~") {
p = index(st, mp)
if (! p) {
r = r mp
continue
}
while (p && substr(st, p - 1, 1) == "\\") {
ms = ms substr(st, 1, p - 1) mp
st = substr(st, p + length(mp))
p = index(st, mp)
}
ms = ms substr(st, 1, p - 1)
r = r itag("del", scrub(ms))
st = substr(st, p + length(mp))
} else if (match(mp, /`+/)) {
p = index(st, mp)
if (! p) {
r = r mp
continue
}
ms = substr(st, 1, p - 1)
r = r itag("code", escape(ms))
st = substr(st, p + length(mp))
} else if (mp == ">") {
r = r ">"
} else if (mp == "<") {
p = index(st, ">")
if (! p) {
r = r "<"
continue
}
tg = substr(st, 1, p - 1)
if (match(tg, /^[[:alpha:]]+[[:space:]]/)) {
a = trim(substr(tg, RSTART + RLENGTH - 1))
tg = substr(tg, 1, RLENGTH - 1)
} else {
a = ""
}
if (match(tolower(tg), "^/?(a|abbr|div|span|blockquote|pre|img|code|p|em|strong|sup|sub|del|ins|s|u|b|i|br|hr|ul|ol|li|table|thead|tfoot|tbody|tr|th|td|caption|column|col|colgroup|figure|figcaption|dl|dd|dt|mark|cite|q|var|samp|small|details|summary)$")) {
if (! match(tg, /\//)) {
if (match(a, /class="/)) {
sub(/class="/, "class=\"dawk-ex ", a)
} else if (a) {
a = a " class=\"dawk-ex\""
} else {
a = "class=\"dawk-ex\""
}
r = r "<" tg " " a ">"
} else {
r = r "<" tg ">"
}
} else if (match(tg, "^[[:alpha:]]+://[[:graph:]]+$")) {
if (! a) {
a = tg
}
r = r "" a ""
} else if (match(tg, "^[[:graph:]]+@[[:graph:]]+$")) {
if (! a) {
a = tg
}
r = r "" obfuscate(a) ""
} else {
r = r "<"
continue
}
st = substr(st, p + 1)
} else if (mp == "&") {
if (match(st, /^[#[:alnum:]]+;/)) {
r = r "&" substr(st, 1, RLENGTH)
st = substr(st, RLENGTH + 1)
} else {
r = r "&"
}
}
}
return (r st)
}
function strip_tags(st)
{
gsub(/<\/?[^>]+>/, "", st)
return st
}
function tag(t, body, attr)
{
if (attr) {
attr = " " trim(attr)
# https://www.w3.org/TR/html5/grouping-content.html#the-p-element
}
if (t == "p" && (match(body, /<\/?(div|table|blockquote|dl|ol|ul|h[[:digit:]]|hr|pre)[>[:space:]]/)) || (match(body, /!\[toc\]/) && substr(body, RSTART - 1, 1) != "\\")) {
return ("<" t attr ">" body "\n")
} else {
return ("<" t attr ">" body "" t ">\n")
}
}
function trim(st)
{
sub(/^[[:space:]]+/, "", st)
sub(/[[:space:]]+$/, "", st)
return st
}