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 @@ + +doc.awk + + +

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.

+

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 to use it yourself.

+

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.

+
	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/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 +# +# 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 "
"
+			printf(buf[type]) | CODEPROC
+			close(CODEPROC)
+			print "
" + } 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 @@ + + diff --git a/header.html b/header.html new file mode 100644 index 0000000..38cba73 --- /dev/null +++ b/header.html @@ -0,0 +1,4 @@ + +@@TITLE@@ + + 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) + 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" + } + Out = Out tag(Mode, Buf "\n") + } else if (Mode == "pre") { + while (ListLevel > 1) { + Buf = Buf "\n" + } + 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 "
    \n" footnotes "
" + } + } +} + + +function end_table(r, c, t, a, s) +{ + for (r = 1; r < Row; r++) { + t = IsHeaders[r] ? "th" : "td" + s = s "" + for (c = 1; c <= NCols[r]; c++) { + a = Align[c] + if (a) { + s = s "<" t " align=\"" a "\">" scrub(Table[r, c]) "" + } else { + s = s "<" t ">" scrub(Table[r, c]) "" + } + } + s = s "\n" + } + return tag("table", s, "class=\"da\"") +} + +function escape(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 "
\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

" + } 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" + } + 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" + } + } + if (match(tmp, /^[[:space:]]*\[[xX[:space:]]\]/)) { + st = substr(tmp, RLENGTH + 1) + tmp = tolower(substr(tmp, RSTART, RLENGTH)) + Buf = Buf "

  • " scrub(st) + } else { + Buf = Buf "
  • " scrub(tmp) + } + } else if (match(st, /^[[:space:]]*$/)) { + Buf = Buf "
    \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 "" k "" + 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 "[" fn[n] "]" + p = match(st, /\[\^[^\]]+\]/) + } + for (i = 1; i <= fc; i++) { + footnotes = footnotes "
  • " Footnote[footname[i]] "  ↶ Back
  • \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
     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 "
        " + } + } + for (; ToCLevel > level; ToCLevel--) { + 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 "") +} + +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 = "
    \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("&#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(/ $/, "
    \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 "\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