From 0d81f5100640c7f961fe6d6e79a6b0d801b3289b Mon Sep 17 00:00:00 2001 From: Case Duckworth Date: Tue, 2 Aug 2022 09:25:42 -0500 Subject: Initial commit --- README.html | 203 ++++++++++++++ doc.awk | 213 ++++++++++++++ footer.html | 2 + header.html | 4 + htmlsafe.awk | 8 + mdown.awk | 903 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ style.css | 31 ++ 7 files changed, 1364 insertions(+) create mode 100644 README.html create mode 100755 doc.awk create mode 100644 footer.html create mode 100644 header.html create mode 100755 htmlsafe.awk create mode 100755 mdown.awk create mode 100644 style.css diff --git a/README.html b/README.html new file mode 100644 index 0000000..94c9c2d --- /dev/null +++ b/README.html @@ -0,0 +1,203 @@ +<!DOCTYPE html> +<title>doc.awk</title> +<link type="text/css" rel="stylesheet" href="style.css" /> +<body> +<h1 id="doc-awk"><a href="#doc-awk" class="header">DOC AWK <svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><g transform="rotate(-30, 8, 8)" stroke="#000000" opacity="0.25"><rect fill="none" height="6" width="8" x="2" y="6" rx="1.5"/><rect fill="none" height="6" width="8" x="6" y="4" rx="1.5"/></g></svg></a></h1> +<p>A quick-and-dirty literate-programming-style documentation generator +inspired by <a class="normal" href="https://ashkenas.com/docco/" title="">docco</a>.</p> +<p>by Case Duckworth <a class="normal" href="mailto:acdw@acdw.net">acdw@acdw.net</a></p> +<p>Source available under the <a class="normal" href="https://acdw.casa/gcl" title="">Good Choices License</a>.</p> +<p>There's a lot of quick-and-dirty "literate programming tools" out there, many +of which were inspired by, and also borrowed from, docco. I was particularly +interested in <a class="normal" href="https://rtomayko.github.io/shocco/" title="">shocco</a>, written in POSIX shell (of which I am a fan).</p> +<p>Notably missing, however, was a converter of some kind written in AWK. Thus, +DOC AWK was born.</p> +<p>This page is the result of DOC AWK working on itself. Not bad for < 250 lines +including commentary! You can pick up the raw source code of doc.awk <a class="normal" href="https://git.acdw.net/doc.awk" title="">in its +git repository</a> to use it yourself.</p> +<h2 id="code"><a href="#code" class="header">Code <svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><g transform="rotate(-30, 8, 8)" stroke="#000000" opacity="0.25"><rect fill="none" height="6" width="8" x="2" y="6" rx="1.5"/><rect fill="none" height="6" width="8" x="6" y="4" rx="1.5"/></g></svg></a></h2> +<pre><code>BEGIN { +</code></pre> +<p>All the best awk scripts start with a <code>BEGIN</code> block. In this one, we +set a few variables from the environment, with defaults. I use the +convenience function <code>getenv</code>, further down this script, to make it +easier.</p> +<p>First, the comment regex. This regex detects a comment <em>line</em>, not an +inline comment. By default, it's set up for awk, shell, and other +languages that use <code>#</code> as a comment delimiter, but you can make it +whatever you want.</p> +<pre><code> COMMENT = getenv("DOCAWK_COMMENT", COMMENT, "^[ \t]*#+[ \t]*") +</code></pre> +<p>You can set <code>DOCAWK_TEXTPROC</code> to any text processor you want, but the +default is the vendored <code>mdown.awk</code> script in this repo. It's from +<a class="normal" href="https://github.com/wernsey/d.awk" title="">d.awk</a>.</p> +<pre><code> TEXTPROC = getenv("DOCAWK_TEXTPROC", TEXTPROC, "./mdown.awk") +</code></pre> +<p>You can also set the processor for code sections of the source file; +the included <code>htmlsafe.awk</code> simply escapes <, &, and >.</p> +<pre><code> CODEPROC = getenv("DOCAWK_CODEPROC", CODEPROC, "./htmlsafe.awk") +</code></pre> +<p>Usually, a file header and footer are enough for most documents. The +defaults here are the included header.html and footer.html, since the +default output type is html.</p> +<p>Each of these documents are actually <em>templates</em>, with keys that can +expand to variables inside of <code>@@VARIABLE@@</code>. This is mostly +for title expansion.</p> +<pre><code> HEADER = getenv("DOCAWK_HEADER", HEADER, "./header.html") + FOOTER = getenv("DOCAWK_FOOTER", FOOTER, "./footer.html") +} +</code></pre> +<p>Because <code>FILENAME</code> is unset during <code>BEGIN</code>, template expansion that attempts +to view the filename doesn't work. Thus, I need a state variable to track +whether we've started or not (so that I don't print a header with every new +file).</p> +<pre><code>! begun { +</code></pre> +<p>The template array is initialized with the document's title.</p> +<pre><code> TV["TITLE"] = get_title() +</code></pre> +<p>Print the header here, since if multiple files are passed to DOC AWK +they'll all be concatenated anyway.</p> +<pre><code> file_print(HEADER) +} +</code></pre> +<p><code>doc.awk</code> is multi-file aware. It also removes the shebang line from the +script if it exists, because you probably don't want that in the output.</p> +<p>It wouldn't be a <em>bad</em> idea to make a heuristic for determining the type of +source file we're converting here.</p> +<pre><code>FNR == 1 { + begun = 1 + if ($0 ~ COMMENT) { + lt = "text" + } else { + lt = "code" + } + if ($0 !~ /^#!/) { + bufadd(lt) + } + next +} +</code></pre> +<p>The main logic is quite simple: if a given line is a comment as defined by +<code>DOCAWK_COMMENT</code>, it's in a text block and should be treated as such; +otherwise, it's in a code block. Accumulate each part in a dedicated buffer, +and on a switch-over between code and text, print the buffer and reset.</p> +<pre><code>$0 !~ COMMENT { + lt = "code" + bufprint("text") +} + +$0 ~ COMMENT { + lt = "text" + bufprint("code") + sub(COMMENT, "", $0) +} + +{ + bufadd(lt) +} +</code></pre> +<p>Of course, at the end there might be something in either buffer, so print that +out too. I've decided to put text last for the possibility of ending commentary.</p> +<pre><code>END { + bufprint("code") + bufprint("text") + file_print(FOOTER) +} +</code></pre> +<h2 id="functions"><a href="#functions" class="header">Functions <svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><g transform="rotate(-30, 8, 8)" stroke="#000000" opacity="0.25"><rect fill="none" height="6" width="8" x="2" y="6" rx="1.5"/><rect fill="none" height="6" width="8" x="6" y="4" rx="1.5"/></g></svg></a></h2> +<p><em>bufadd</em>: Add a STR to buffer TYPE. STR defaults to $0, the input record.</p> +<pre><code>function bufadd(type, str) +{ + buf[type] = buf[type] (str ? str : $0) "\n" +} +</code></pre> +<p><em>bufprint</em>: Print a buffer of TYPE. Automatically wrap the code blocks in a +little HTML code block. I could maybe have a DOCAWK_CODE_PRE/POST and maybe +even one for text too, to make it more extensible (to other markup languages, +for example).</p> +<pre><code>function bufprint(type) +{ + buf[type] = trim(buf[type]) + if (buf[type]) { + if (type == "code") { + printf "<pre><code>" + printf(buf[type]) | CODEPROC + close(CODEPROC) + print "</code></pre>" + } else if (type == "text") { + print(buf[type]) | TEXTPROC + close(TEXTPROC) + } + buf[type] = "" + } +} +</code></pre> +<p><em>file_print</em>: Print FILE line-by-line. The <code>> 0</code> check here ensures that it +bails on error (-1).</p> +<pre><code>function file_print(file) +{ + if (file) { + while ((getline l < file) > 0) { + print template_expand(l) + } + close(file) + } +} +</code></pre> +<p><em>get_title</em>: get the title of the current script, for the expanded document. +If variables are set, use those; otherwise try to figure out the title from +the document's basename.</p> +<pre><code>function get_title() +{ + title = getenv("DOCAWK_TITLE", TITLE) + if (! title) { + title = FILENAME + sub(/.*\//, "", title) + } + return title +} +</code></pre> +<p><em>getenv</em>: a convenience function for pulling values out of the environment. +If an environment variable ENV isn't found, test if VAR is set (i.e., <code>doc.awk +-v var=foo</code>.) and return it if it's set. Otherwise, return the default value +DEF.</p> +<pre><code>function getenv(env, var, def) +{ + if (ENVIRON[env]) { + return ENVIRON[env] + } else if (var) { + return var + } else { + return def + } +} +</code></pre> +<p><em>template_expand</em>: expand templates of the form <code>@@template@@</code> in the text. +Currently it only does variables, and works by line.</p> +<p>Due to the way awk works, template variables need to live in their own special +array, <code>TV</code>. I'd love it if awk had some kind of <code>eval</code> functionality, but at +least POSIX awk doesn't.</p> +<pre><code>function template_expand(text) +{ + if (match(text, /@@[^@]*@@/)) { + var = substr(text, RSTART + 2, RLENGTH - 4) + new = substr(text, 1, RSTART - 1) + new = new TV[var] + new = new substr(text, RSTART + RLENGTH) + } else { + new = text + } + return new +} +</code></pre> +<p><em>trim</em>: remove whitespace from either end of a string.</p> +<pre><code>function trim(str) +{ + sub(/^[ \n]*/, "", str) + sub(/[ \n]*$/, "", str) + return str +} +</code></pre> +</body> +</html> diff --git a/doc.awk b/doc.awk new file mode 100755 index 0000000..4735f9d --- /dev/null +++ b/doc.awk @@ -0,0 +1,213 @@ +#!/bin/awk -f +# DOC AWK +# ====== +# +# A quick-and-dirty literate-programming-style documentation generator +# inspired by [docco][]. +# +# by Case Duckworth <acdw@acdw.net> +# +# Source available under the [Good Choices License][gcl]. +# +# [gcl]: https://acdw.casa/gcl Good Choices License +# +# There's a lot of quick-and-dirty "literate programming tools" out there, many +# of which were inspired by, and also borrowed from, docco. I was particularly +# interested in [shocco][], written in POSIX shell (of which I am a fan). +# +# Notably missing, however, was a converter of some kind written in AWK. Thus, +# DOC AWK was born. +# +# This page is the result of DOC AWK working on itself. Not bad for < 250 lines +# including commentary! You can pick up the raw source code of doc.awk [in its +# git repository][git] to use it yourself. +# +# [docco]: https://ashkenas.com/docco/ +# [shocco]: https://rtomayko.github.io/shocco/ +# [git]: https://git.acdw.net/docawk +# +# Code +# ---- +BEGIN { + # All the best awk scripts start with a `BEGIN` block. In this one, we + # set a few variables from the environment, with defaults. I use the + # convenience function `getenv`, further down this script, to make it + # easier. + # + # First, the comment regex. This regex detects a comment *line*, not an + # inline comment. By default, it's set up for awk, shell, and other + # languages that use `#` as a comment delimiter, but you can make it + # whatever you want. + COMMENT = getenv("DOCAWK_COMMENT", COMMENT, "^[ \t]*#+[ \t]*") + # You can set `DOCAWK_TEXTPROC` to any text processor you want, but the + # default is the vendored `mdown.awk` script in this repo. It's from + # [d.awk](https://github.com/wernsey/d.awk). + TEXTPROC = getenv("DOCAWK_TEXTPROC", TEXTPROC, "./mdown.awk") + # You can also set the processor for code sections of the source file; + # the included `htmlsafe.awk` simply escapes <, &, and >. + CODEPROC = getenv("DOCAWK_CODEPROC", CODEPROC, "./htmlsafe.awk") + # Usually, a file header and footer are enough for most documents. The + # defaults here are the included header.html and footer.html, since the + # default output type is html. + # + # Each of these documents are actually *templates*, with keys that can + # expand to variables inside of `@@VARIABLE@@`. This is mostly + # for title expansion. + HEADER = getenv("DOCAWK_HEADER", HEADER, "./header.html") + FOOTER = getenv("DOCAWK_FOOTER", FOOTER, "./footer.html") +} + +# Because `FILENAME` is unset during `BEGIN`, template expansion that attempts +# to view the filename doesn't work. Thus, I need a state variable to track +# whether we've started or not (so that I don't print a header with every new +# file). +! begun { + # The template array is initialized with the document's title. + TV["TITLE"] = get_title() + # Print the header here, since if multiple files are passed to DOC AWK + # they'll all be concatenated anyway. + file_print(HEADER) +} + +# `doc.awk` is multi-file aware. It also removes the shebang line from the +# script if it exists, because you probably don't want that in the output. +# +# It wouldn't be a *bad* idea to make a heuristic for determining the type of +# source file we're converting here. +FNR == 1 { + begun = 1 + if ($0 ~ COMMENT) { + lt = "text" + } else { + lt = "code" + } + if ($0 !~ /^#!/) { + bufadd(lt) + } + next +} + +# The main logic is quite simple: if a given line is a comment as defined by +# `DOCAWK_COMMENT`, it's in a text block and should be treated as such; +# otherwise, it's in a code block. Accumulate each part in a dedicated buffer, +# and on a switch-over between code and text, print the buffer and reset. +$0 !~ COMMENT { + lt = "code" + bufprint("text") +} + +$0 ~ COMMENT { + lt = "text" + bufprint("code") + sub(COMMENT, "", $0) +} + +{ + bufadd(lt) +} + +# Of course, at the end there might be something in either buffer, so print that +# out too. I've decided to put text last for the possibility of ending commentary. +END { + bufprint("code") + bufprint("text") + file_print(FOOTER) +} + + +# Functions +# --------- +# +# *bufadd*: Add a STR to buffer TYPE. STR defaults to $0, the input record. +function bufadd(type, str) +{ + buf[type] = buf[type] (str ? str : $0) "\n" +} + +# *bufprint*: Print a buffer of TYPE. Automatically wrap the code blocks in a +# little HTML code block. I could maybe have a DOCAWK_CODE_PRE/POST and maybe +# even one for text too, to make it more extensible (to other markup languages, +# for example). +function bufprint(type) +{ + buf[type] = trim(buf[type]) + if (buf[type]) { + if (type == "code") { + printf "<pre><code>" + printf(buf[type]) | CODEPROC + close(CODEPROC) + print "</code></pre>" + } else if (type == "text") { + print(buf[type]) | TEXTPROC + close(TEXTPROC) + } + buf[type] = "" + } +} + +# *file_print*: Print FILE line-by-line. The `> 0` check here ensures that it +# bails on error (-1). +function file_print(file) +{ + if (file) { + while ((getline l < file) > 0) { + print template_expand(l) + } + close(file) + } +} + +# *get_title*: get the title of the current script, for the expanded document. +# If variables are set, use those; otherwise try to figure out the title from +# the document's basename. +function get_title() +{ + title = getenv("DOCAWK_TITLE", TITLE) + if (! title) { + title = FILENAME + sub(/.*\//, "", title) + } + return title +} + +# *getenv*: a convenience function for pulling values out of the environment. +# If an environment variable ENV isn't found, test if VAR is set (i.e., `doc.awk +# -v var=foo`.) and return it if it's set. Otherwise, return the default value +# DEF. +function getenv(env, var, def) +{ + if (ENVIRON[env]) { + return ENVIRON[env] + } else if (var) { + return var + } else { + return def + } +} + +# *template_expand*: expand templates of the form `@@template@@` in the text. +# Currently it only does variables, and works by line. +# +# Due to the way awk works, template variables need to live in their own special +# array, `TV`. I'd love it if awk had some kind of `eval` functionality, but at +# least POSIX awk doesn't. +function template_expand(text) +{ + if (match(text, /@@[^@]*@@/)) { + var = substr(text, RSTART + 2, RLENGTH - 4) + new = substr(text, 1, RSTART - 1) + new = new TV[var] + new = new substr(text, RSTART + RLENGTH) + } else { + new = text + } + return new +} + +# *trim*: remove whitespace from either end of a string. +function trim(str) +{ + sub(/^[ \n]*/, "", str) + sub(/[ \n]*$/, "", str) + return str +} diff --git a/footer.html b/footer.html new file mode 100644 index 0000000..308b1d0 --- /dev/null +++ b/footer.html @@ -0,0 +1,2 @@ +</body> +</html> diff --git a/header.html b/header.html new file mode 100644 index 0000000..38cba73 --- /dev/null +++ b/header.html @@ -0,0 +1,4 @@ +<!DOCTYPE html> +<title>@@TITLE@@</title> +<link type="text/css" rel="stylesheet" href="style.css" /> +<body> diff --git a/htmlsafe.awk b/htmlsafe.awk new file mode 100755 index 0000000..a49fe21 --- /dev/null +++ b/htmlsafe.awk @@ -0,0 +1,8 @@ +#!/bin/awk -f +{ + gsub(/&/, "\\&", $0) + gsub(/</, "\\<", $0) + gsub(/>/, "\\>", $0) + print +} + diff --git a/mdown.awk b/mdown.awk new file mode 100755 index 0000000..ca39b09 --- /dev/null +++ b/mdown.awk @@ -0,0 +1,903 @@ +#! /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 "<script><!--\n" "function toggle_toc(n) {\n" " var toc=document.getElementById('table-of-contents-' + n);\n" " var btn=document.getElementById('btn-text-' + n);\n" " toc.style.display=(toc.style.display=='none')?'block':'none';\n" " btn.innerHTML=(toc.style.display=='none')?'►':'▼';\n" "}\n" "function toggle_toc_ul(n) { \n" " var toc=document.getElementById('toc-ul-' + n); \n" " var btn=document.getElementById('toc-btn-' + n); \n" " if(toc) {\n" " toc.style.display=(toc.style.display=='none')?'block':'none'; \n" " btn.innerHTML=(toc.style.display=='none')?'►':'▼';\n" " }\n" "}\n" "//-->\n</script>" + } + 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 "<hr><ol class=\"footnotes\">\n" footnotes "</ol>" + } + } +} + + +function end_table(r, c, t, a, s) +{ + for (r = 1; r < Row; r++) { + t = IsHeaders[r] ? "th" : "td" + s = s "<tr>" + for (c = 1; c <= NCols[r]; c++) { + a = Align[c] + if (a) { + s = s "<" t " align=\"" a "\">" scrub(Table[r, c]) "</" t ">" + } else { + s = s "<" t ">" scrub(Table[r, c]) "</" t ">" + } + } + s = s "</tr>\n" + } + return tag("table", s, "class=\"da\"") +} + +function escape(st) +{ + gsub(/&/, "\\&", st) + gsub(/</, "\\<", st) + gsub(/>/, "\\>", st) + return st +} + +function filter(st, res, tmp, linkdesc, url, delim, edelim, name, def, plang, mmaid, cols, i) +{ + if (Mode == "p") { + if (match(st, /^[[:space:]]*\[[-._[:alnum:][:space:]]+\]:/)) { + linkdesc = "" + LastLink = 0 + match(st, /\[.*\]/) + LinkRef = tolower(substr(st, RSTART + 1, RLENGTH - 2)) + st = substr(st, RSTART + RLENGTH + 2) + match(st, /[^[:space:]]+/) + url = substr(st, RSTART, RLENGTH) + st = substr(st, RSTART + RLENGTH + 1) + if (match(url, /^<.*>/)) { + url = substr(url, RSTART + 1, RLENGTH - 2) + } + if (match(st, /["'(]/)) { + delim = substr(st, RSTART, 1) + edelim = (delim == "(") ? ")" : delim + if (match(st, delim ".*" edelim)) { + linkdesc = substr(st, RSTART + 1, RLENGTH - 2) + } + } + LinkUrls[LinkRef] = escape(url) + if (! linkdesc) { + LastLink = 1 + } + LinkDescs[LinkRef] = escape(linkdesc) + return + } else if (LastLink && match(st, /^[[:space:]]*["'(]/)) { + match(st, /["'(]/) + delim = substr(st, RSTART, 1) + edelim = (delim == "(") ? ")" : delim + st = substr(st, RSTART) + if (match(st, delim ".*" edelim)) { + LinkDescs[LinkRef] = escape(substr(st, RSTART + 1, RLENGTH - 2)) + } + LastLink = 0 + return + } else if (match(st, /^[[:space:]]*\[\^[-._[:alnum:][:space:]]+\]:[[:space:]]*/)) { + match(st, /\[\^[[:alnum:]]+\]:/) + name = substr(st, RSTART + 2, RLENGTH - 4) + def = substr(st, RSTART + RLENGTH + 1) + Footnote[tolower(name)] = scrub(def) + return + } else if (match(st, /^[[:space:]]*\*\[[[:alnum:]]+\]:[[:space:]]*/)) { + match(st, /\[[[:alnum:]]+\]/) + name = substr(st, RSTART + 1, RLENGTH - 2) + def = substr(st, RSTART + RLENGTH + 2) + Abbrs[toupper(name)] = def + return + } else if (match(st, /^(( )| *\t)/) || match(st, /^[[:space:]]*```+[[:alnum:]]*/)) { + Preterm = trim(substr(st, RSTART, RLENGTH)) + st = substr(st, RSTART + RLENGTH) + if (Buf) { + res = tag("p", scrub(Buf)) + } + Buf = st + push("pre") + } else if (! trim(Prev) && match(st, /^[[:space:]]*[*-][[:space:]]*[*-][[:space:]]*[*-][-*[:space:]]*$/)) { + if (Buf) { + res = tag("p", scrub(Buf)) + } + Buf = "" + res = res "<hr>\n" + } else if (match(st, /^[[:space:]]*===+[[:space:]]*$/)) { + Buf = trim(substr(Buf, 1, length(Buf) - length(Prev) - 1)) + if (Buf) { + res = tag("p", scrub(Buf)) + } + if (Prev) { + res = res heading(1, scrub(Prev)) + } + Buf = "" + } else if (match(st, /^[[:space:]]*---+[[:space:]]*$/)) { + Buf = trim(substr(Buf, 1, length(Buf) - length(Prev) - 1)) + if (Buf) { + res = tag("p", scrub(Buf)) + } + if (Prev) { + res = res heading(2, scrub(Prev)) + } + Buf = "" + } else if (match(st, /^[[:space:]]*#+/)) { + sub(/#+[[:space:]]*$/, "", st) + match(st, /#+/) + ListLevel = RLENGTH + tmp = substr(st, RSTART + RLENGTH) + if (Buf) { + res = tag("p", scrub(Buf)) + } + res = res heading(ListLevel, scrub(trim(tmp))) + Buf = "" + } else if (match(st, /^[[:space:]]*>/)) { + if (Buf) { + res = tag("p", scrub(Buf)) + } + Buf = scrub(trim(substr(st, RSTART + RLENGTH))) + push("blockquote") + } else if (Tables && match(st, /.*\|(.*\|)+/)) { + if (Buf) { + res = tag("p", scrub(Buf)) + } + Row = 1 + for (i = 1; i <= MaxCols; i++) { + Align[i] = "" + } + process_table_row(st) + push("table") + } else if (match(st, /^[[:space:]]*([*+-]|[[:digit:]]+\.)[[:space:]]/)) { + if (Buf) { + res = tag("p", scrub(Buf)) + } + Buf = "" + match(st, /^[[:space:]]*/) + ListLevel = 1 + indent[ListLevel] = RLENGTH + Open[ListLevel] = match(st, /^[[:space:]]*[*+-][[:space:]]*/) ? "ul" : "ol" + push(Open[ListLevel]) + res = res filter(st) + } else if (match(st, /^[[:space:]]*$/)) { + if (trim(Buf)) { + res = tag("p", scrub(trim(Buf))) + Buf = "" + } + } else { + Buf = Buf st "\n" + } + LastLink = 0 + } else if (Mode == "blockquote") { + if (match(st, /^[[:space:]]*>[[:space:]]*$/)) { + Buf = Buf "\n</p><p>" + } 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 "<li><input type=\"checkbox\" " (index(tmp, "x") ? "checked" : "") " disabled>" scrub(st) + } else { + Buf = Buf "<li>" scrub(tmp) + } + } else if (match(st, /^[[:space:]]*$/)) { + Buf = Buf "<br>\n" + } else { + sub(/^[[:space:]]+/, "", st) + Buf = Buf "\n" scrub(st) + } + } + Prev = st + return res +} + +function fix_abbrs(str, st, k, r, p) +{ + for (k in Abbrs) { + r = "" + st = str + t = escape(Abbrs[toupper(k)]) + gsub(/&/, "\\&", t) + p = match(st, "[^[:alnum:]]" k "[^[:alnum:]]") + while (p) { + r = r substr(st, 1, RSTART) + r = r "<abbr title=\"" t "\">" k "</abbr>" + st = substr(st, RSTART + RLENGTH - 1) + p = match(st, "[^[:alnum:]]" k "[^[:alnum:]]") + } + str = r st + } + return str +} + +function fix_footnotes(st, r, p, n, i, d, fn, fc) +{ + p = match(st, /\[\^[^\]]+\]/) + while (p) { + if (substr(st, RSTART - 2, 1) == "\\") { + r = r substr(st, 1, RSTART - 3) substr(st, RSTART, RLENGTH) + st = substr(st, RSTART + RLENGTH) + p = match(st, /\[\^[^\]]+\]/) + continue + } + r = r substr(st, 1, RSTART - 1) + d = substr(st, RSTART + 2, RLENGTH - 3) + n = tolower(d) + st = substr(st, RSTART + RLENGTH) + if (Footnote[tolower(n)]) { + if (! fn[n]) { + fn[n] = ++fc + } + d = Footnote[n] + } else { + Footnote[n] = scrub(d) + if (! fn[n]) { + fn[n] = ++fc + } + } + footname[fc] = n + d = strip_tags(d) + if (length(d) > 20) { + d = substr(d, 1, 20) "…" + } + r = r "<sup title=\"" d "\"><a href=\"#footnote-" fn[n] "\" id=\"footnote-pos-" fn[n] "\" class=\"footnote\">[" fn[n] "]</a></sup>" + p = match(st, /\[\^[^\]]+\]/) + } + for (i = 1; i <= fc; i++) { + footnotes = footnotes "<li id=\"footnote-" i "\">" Footnote[footname[i]] "<a title=\"Return to Document\" class=\"footnote-back\" href=\"#footnote-pos-" i "\"> ↶ Back</a></li>\n" + } + return (r st) +} + +function fix_links(st, lt, ld, lr, url, img, res, rx, pos, pre) +{ + do { + pre = match(st, /<(pre|code)>/) # Don't substitute in <pre> or <code> 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 "<img src=\"" url "\" title=\"" ld "\" alt=\"" lt "\">" + } else { + res = res "<a class=\"normal\" href=\"" url "\" title=\"" ld "\">" lt "</a>" + } + } 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 "<img src=\"" url "\" title=\"" ld "\" alt=\"" lt "\">" + } else if (url) { + res = res "<a class=\"normal\" href=\"" url "\" title=\"" ld "\">" lt "</a>" + } 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 = "<svg width=\"16\" height=\"16\" xmlns=\"http://www.w3.org/2000/svg\"><g transform=\"rotate(-30, 8, 8)\" stroke=\"#000000\" opacity=\"0.25\"><rect fill=\"none\" height=\"6\" width=\"8\" x=\"2\" y=\"6\" rx=\"1.5\"/><rect fill=\"none\" height=\"6\" width=\"8\" x=\"6\" y=\"4\" rx=\"1.5\"/></g></svg>" + text = "<a href=\"#" href "\" class=\"header\">" st " " svg "</a>" (TopLinks ? " <a class=\"top\" title=\"Return to top\" href=\"#\">↑ Top</a>" : "") + res = tag("h" level, text, "id=\"" href "\"") + for (; ToCLevel < level; ToCLevel++) { + ToC_ID++ + if (ToCLevel < HideToCLevel) { + ToC = ToC "<a class=\"toc-button\" id=\"toc-btn-" ToC_ID "\" onclick=\"toggle_toc_ul('" ToC_ID "')\">▼</a>" + ToC = ToC "<ul class=\"toc toc-" ToCLevel "\" id=\"toc-ul-" ToC_ID "\">" + } else { + ToC = ToC "<a class=\"toc toc-button\" id=\"toc-btn-" ToC_ID "\" onclick=\"toggle_toc_ul('" ToC_ID "')\">►</a>" + ToC = ToC "<ul style=\"display:none;\" class=\"toc toc-" ToCLevel "\" id=\"toc-ul-" ToC_ID "\">" + } + } + for (; ToCLevel > level; ToCLevel--) { + ToC = ToC "</ul>" + } + ToC = ToC "<li class=\"toc-" level "\"><a class=\"toc toc-" level "\" href=\"#" href "\">" st "</a>\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 "</ul>" + } + 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 = "<div>\n<a id=\"toc-button-" n "\" class=\"toc-button\" onclick=\"toggle_toc(" n ")\"><span id=\"btn-text-" n "\">" (dis ? "▼" : "►") "</span> Contents</a>\n" "<div id=\"table-of-contents-" n "\" style=\"display:" (dis ? "block" : "none") ";\">\n<ul class=\"toc toc-1\">" ToC "</ul>\n</div>\n</div>" + 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("&#x%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(/ $/, "<br>\n", st) + gsub(/( |[[:space:]]+\\)\n/, "<br>\n", st) + gsub(/( |[[:space:]]+\\)$/, "<br>\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 class=\"normal\" href=\"" tg "\">" a "</a>" + } else if (match(tg, "^[[:graph:]]+@[[:graph:]]+$")) { + if (! a) { + a = tg + } + r = r "<a class=\"normal\" href=\"" obfuscate("mailto:" tg) "\">" obfuscate(a) "</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 +} diff --git a/style.css b/style.css new file mode 100644 index 0000000..eb059d9 --- /dev/null +++ b/style.css @@ -0,0 +1,31 @@ +body { + margin: auto; + font: 18px/1.4 serif; + max-width: 70ch; + background-color: papayawhip; + color: navy; +} +code { + background-color: #ffe; + color: black; +} +pre > code { + display: block; +} +img { + max-width: 100%; +} +a:link { + text-decoration: none; + background: #eef; + color: inherit; +} +a:visted { + color: inherit; +} +a:active { + border-bottom: 1px solid; +} +a:hover { + border-bottom: 1px solid; +} -- cgit 1.4.1-21-gabe81