about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--README.html203
-rwxr-xr-xdoc.awk213
-rw-r--r--footer.html2
-rw-r--r--header.html4
-rwxr-xr-xhtmlsafe.awk8
-rwxr-xr-xmdown.awk903
-rw-r--r--style.css31
7 files changed, 1364 insertions, 0 deletions
diff --git a/README.html b/README.html new file mode 100644 index 0000000..94c9c2d --- /dev/null +++ b/README.html
@@ -0,0 +1,203 @@
1<!DOCTYPE html>
2<title>doc.awk</title>
3<link type="text/css" rel="stylesheet" href="style.css" />
4<body>
5<h1 id="doc-awk"><a href="#doc-awk" class="header">DOC AWK&nbsp;<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>
6<p>A quick-and-dirty literate-programming-style documentation generator
7inspired by <a class="normal" href="https://ashkenas.com/docco/" title="">docco</a>.</p>
8<p>by Case Duckworth <a class="normal" href="&#x6D;a&#105;&#108;&#x74;&#x6F;&#58;a&#x63;&#x64;&#x77;&#x40;&#97;&#x63;&#x64;&#x77;&#x2E;&#x6E;&#x65;&#x74;">&#97;&#x63;&#x64;w&#x40;&#97;&#x63;&#100;&#119;&#x2E;&#110;&#101;t</a></p>
9<p>Source available under the <a class="normal" href="https://acdw.casa/gcl" title="">Good Choices License</a>.</p>
10<p>There's a lot of quick-and-dirty "literate programming tools" out there, many
11of which were inspired by, and also borrowed from, docco. I was particularly
12interested in <a class="normal" href="https://rtomayko.github.io/shocco/" title="">shocco</a>, written in POSIX shell (of which I am a fan).</p>
13<p>Notably missing, however, was a converter of some kind written in AWK. Thus,
14DOC AWK was born.</p>
15<p>This page is the result of DOC AWK working on itself. Not bad for &lt; 250 lines
16including 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
17git repository</a> to use it yourself.</p>
18<h2 id="code"><a href="#code" class="header">Code&nbsp;<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>
19<pre><code>BEGIN {
20</code></pre>
21<p>All the best awk scripts start with a <code>BEGIN</code> block. In this one, we
22set a few variables from the environment, with defaults. I use the
23convenience function <code>getenv</code>, further down this script, to make it
24easier.</p>
25<p>First, the comment regex. This regex detects a comment <em>line</em>, not an
26inline comment. By default, it's set up for awk, shell, and other
27languages that use <code>#</code> as a comment delimiter, but you can make it
28whatever you want.</p>
29<pre><code> COMMENT = getenv("DOCAWK_COMMENT", COMMENT, "^[ \t]*#+[ \t]*")
30</code></pre>
31<p>You can set <code>DOCAWK_TEXTPROC</code> to any text processor you want, but the
32default is the vendored <code>mdown.awk</code> script in this repo. It's from
33<a class="normal" href="https://github.com/wernsey/d.awk" title="">d.awk</a>.</p>
34<pre><code> TEXTPROC = getenv("DOCAWK_TEXTPROC", TEXTPROC, "./mdown.awk")
35</code></pre>
36<p>You can also set the processor for code sections of the source file;
37the included <code>htmlsafe.awk</code> simply escapes &lt;, &amp;, and &gt;.</p>
38<pre><code> CODEPROC = getenv("DOCAWK_CODEPROC", CODEPROC, "./htmlsafe.awk")
39</code></pre>
40<p>Usually, a file header and footer are enough for most documents. The
41defaults here are the included header.html and footer.html, since the
42default output type is html.</p>
43<p>Each of these documents are actually <em>templates</em>, with keys that can
44expand to variables inside of <code>@@VARIABLE@@</code>. This is mostly
45for title expansion.</p>
46<pre><code> HEADER = getenv("DOCAWK_HEADER", HEADER, "./header.html")
47 FOOTER = getenv("DOCAWK_FOOTER", FOOTER, "./footer.html")
48}
49</code></pre>
50<p>Because <code>FILENAME</code> is unset during <code>BEGIN</code>, template expansion that attempts
51to view the filename doesn't work. Thus, I need a state variable to track
52whether we've started or not (so that I don't print a header with every new
53file).</p>
54<pre><code>! begun {
55</code></pre>
56<p>The template array is initialized with the document's title.</p>
57<pre><code> TV["TITLE"] = get_title()
58</code></pre>
59<p>Print the header here, since if multiple files are passed to DOC AWK
60they'll all be concatenated anyway.</p>
61<pre><code> file_print(HEADER)
62}
63</code></pre>
64<p><code>doc.awk</code> is multi-file aware. It also removes the shebang line from the
65script if it exists, because you probably don't want that in the output.</p>
66<p>It wouldn't be a <em>bad</em> idea to make a heuristic for determining the type of
67source file we're converting here.</p>
68<pre><code>FNR == 1 {
69 begun = 1
70 if ($0 ~ COMMENT) {
71 lt = "text"
72 } else {
73 lt = "code"
74 }
75 if ($0 !~ /^#!/) {
76 bufadd(lt)
77 }
78 next
79}
80</code></pre>
81<p>The main logic is quite simple: if a given line is a comment as defined by
82<code>DOCAWK_COMMENT</code>, it's in a text block and should be treated as such;
83otherwise, it's in a code block. Accumulate each part in a dedicated buffer,
84and on a switch-over between code and text, print the buffer and reset.</p>
85<pre><code>$0 !~ COMMENT {
86 lt = "code"
87 bufprint("text")
88}
89
90$0 ~ COMMENT {
91 lt = "text"
92 bufprint("code")
93 sub(COMMENT, "", $0)
94}
95
96{
97 bufadd(lt)
98}
99</code></pre>
100<p>Of course, at the end there might be something in either buffer, so print that
101out too. I've decided to put text last for the possibility of ending commentary.</p>
102<pre><code>END {
103 bufprint("code")
104 bufprint("text")
105 file_print(FOOTER)
106}
107</code></pre>
108<h2 id="functions"><a href="#functions" class="header">Functions&nbsp;<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>
109<p><em>bufadd</em>: Add a STR to buffer TYPE. STR defaults to $0, the input record.</p>
110<pre><code>function bufadd(type, str)
111{
112 buf[type] = buf[type] (str ? str : $0) "\n"
113}
114</code></pre>
115<p><em>bufprint</em>: Print a buffer of TYPE. Automatically wrap the code blocks in a
116little HTML code block. I could maybe have a DOCAWK_CODE_PRE/POST and maybe
117even one for text too, to make it more extensible (to other markup languages,
118for example).</p>
119<pre><code>function bufprint(type)
120{
121 buf[type] = trim(buf[type])
122 if (buf[type]) {
123 if (type == "code") {
124 printf "&lt;pre&gt;&lt;code&gt;"
125 printf(buf[type]) | CODEPROC
126 close(CODEPROC)
127 print "&lt;/code&gt;&lt;/pre&gt;"
128 } else if (type == "text") {
129 print(buf[type]) | TEXTPROC
130 close(TEXTPROC)
131 }
132 buf[type] = ""
133 }
134}
135</code></pre>
136<p><em>file_print</em>: Print FILE line-by-line. The <code>&gt; 0</code> check here ensures that it
137bails on error (-1).</p>
138<pre><code>function file_print(file)
139{
140 if (file) {
141 while ((getline l &lt; file) &gt; 0) {
142 print template_expand(l)
143 }
144 close(file)
145 }
146}
147</code></pre>
148<p><em>get_title</em>: get the title of the current script, for the expanded document.
149If variables are set, use those; otherwise try to figure out the title from
150the document's basename.</p>
151<pre><code>function get_title()
152{
153 title = getenv("DOCAWK_TITLE", TITLE)
154 if (! title) {
155 title = FILENAME
156 sub(/.*\//, "", title)
157 }
158 return title
159}
160</code></pre>
161<p><em>getenv</em>: a convenience function for pulling values out of the environment.
162If an environment variable ENV isn't found, test if VAR is set (i.e., <code>doc.awk
163-v var=foo</code>.) and return it if it's set. Otherwise, return the default value
164DEF.</p>
165<pre><code>function getenv(env, var, def)
166{
167 if (ENVIRON[env]) {
168 return ENVIRON[env]
169 } else if (var) {
170 return var
171 } else {
172 return def
173 }
174}
175</code></pre>
176<p><em>template_expand</em>: expand templates of the form <code>@@template@@</code> in the text.
177Currently it only does variables, and works by line.</p>
178<p>Due to the way awk works, template variables need to live in their own special
179array, <code>TV</code>. I'd love it if awk had some kind of <code>eval</code> functionality, but at
180least POSIX awk doesn't.</p>
181<pre><code>function template_expand(text)
182{
183 if (match(text, /@@[^@]*@@/)) {
184 var = substr(text, RSTART + 2, RLENGTH - 4)
185 new = substr(text, 1, RSTART - 1)
186 new = new TV[var]
187 new = new substr(text, RSTART + RLENGTH)
188 } else {
189 new = text
190 }
191 return new
192}
193</code></pre>
194<p><em>trim</em>: remove whitespace from either end of a string.</p>
195<pre><code>function trim(str)
196{
197 sub(/^[ \n]*/, "", str)
198 sub(/[ \n]*$/, "", str)
199 return str
200}
201</code></pre>
202</body>
203</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 @@
1#!/bin/awk -f
2# DOC AWK
3# ======
4#
5# A quick-and-dirty literate-programming-style documentation generator
6# inspired by [docco][].
7#
8# by Case Duckworth <acdw@acdw.net>
9#
10# Source available under the [Good Choices License][gcl].
11#
12# [gcl]: https://acdw.casa/gcl Good Choices License
13#
14# There's a lot of quick-and-dirty "literate programming tools" out there, many
15# of which were inspired by, and also borrowed from, docco. I was particularly
16# interested in [shocco][], written in POSIX shell (of which I am a fan).
17#
18# Notably missing, however, was a converter of some kind written in AWK. Thus,
19# DOC AWK was born.
20#
21# This page is the result of DOC AWK working on itself. Not bad for < 250 lines
22# including commentary! You can pick up the raw source code of doc.awk [in its
23# git repository][git] to use it yourself.
24#
25# [docco]: https://ashkenas.com/docco/
26# [shocco]: https://rtomayko.github.io/shocco/
27# [git]: https://git.acdw.net/docawk
28#
29# Code
30# ----
31BEGIN {
32 # All the best awk scripts start with a `BEGIN` block. In this one, we
33 # set a few variables from the environment, with defaults. I use the
34 # convenience function `getenv`, further down this script, to make it
35 # easier.
36 #
37 # First, the comment regex. This regex detects a comment *line*, not an
38 # inline comment. By default, it's set up for awk, shell, and other
39 # languages that use `#` as a comment delimiter, but you can make it
40 # whatever you want.
41 COMMENT = getenv("DOCAWK_COMMENT", COMMENT, "^[ \t]*#+[ \t]*")
42 # You can set `DOCAWK_TEXTPROC` to any text processor you want, but the
43 # default is the vendored `mdown.awk` script in this repo. It's from
44 # [d.awk](https://github.com/wernsey/d.awk).
45 TEXTPROC = getenv("DOCAWK_TEXTPROC", TEXTPROC, "./mdown.awk")
46 # You can also set the processor for code sections of the source file;
47 # the included `htmlsafe.awk` simply escapes <, &, and >.
48 CODEPROC = getenv("DOCAWK_CODEPROC", CODEPROC, "./htmlsafe.awk")
49 # Usually, a file header and footer are enough for most documents. The
50 # defaults here are the included header.html and footer.html, since the
51 # default output type is html.
52 #
53 # Each of these documents are actually *templates*, with keys that can
54 # expand to variables inside of `@@VARIABLE@@`. This is mostly
55 # for title expansion.
56 HEADER = getenv("DOCAWK_HEADER", HEADER, "./header.html")
57 FOOTER = getenv("DOCAWK_FOOTER", FOOTER, "./footer.html")
58}
59
60# Because `FILENAME` is unset during `BEGIN`, template expansion that attempts
61# to view the filename doesn't work. Thus, I need a state variable to track
62# whether we've started or not (so that I don't print a header with every new
63# file).
64! begun {
65 # The template array is initialized with the document's title.
66 TV["TITLE"] = get_title()
67 # Print the header here, since if multiple files are passed to DOC AWK
68 # they'll all be concatenated anyway.
69 file_print(HEADER)
70}
71
72# `doc.awk` is multi-file aware. It also removes the shebang line from the
73# script if it exists, because you probably don't want that in the output.
74#
75# It wouldn't be a *bad* idea to make a heuristic for determining the type of
76# source file we're converting here.
77FNR == 1 {
78 begun = 1
79 if ($0 ~ COMMENT) {
80 lt = "text"
81 } else {
82 lt = "code"
83 }
84 if ($0 !~ /^#!/) {
85 bufadd(lt)
86 }
87 next
88}
89
90# The main logic is quite simple: if a given line is a comment as defined by
91# `DOCAWK_COMMENT`, it's in a text block and should be treated as such;
92# otherwise, it's in a code block. Accumulate each part in a dedicated buffer,
93# and on a switch-over between code and text, print the buffer and reset.
94$0 !~ COMMENT {
95 lt = "code"
96 bufprint("text")
97}
98
99$0 ~ COMMENT {
100 lt = "text"
101 bufprint("code")
102 sub(COMMENT, "", $0)
103}
104
105{
106 bufadd(lt)
107}
108
109# Of course, at the end there might be something in either buffer, so print that
110# out too. I've decided to put text last for the possibility of ending commentary.
111END {
112 bufprint("code")
113 bufprint("text")
114 file_print(FOOTER)
115}
116
117
118# Functions
119# ---------
120#
121# *bufadd*: Add a STR to buffer TYPE. STR defaults to $0, the input record.
122function bufadd(type, str)
123{
124 buf[type] = buf[type] (str ? str : $0) "\n"
125}
126
127# *bufprint*: Print a buffer of TYPE. Automatically wrap the code blocks in a
128# little HTML code block. I could maybe have a DOCAWK_CODE_PRE/POST and maybe
129# even one for text too, to make it more extensible (to other markup languages,
130# for example).
131function bufprint(type)
132{
133 buf[type] = trim(buf[type])
134 if (buf[type]) {
135 if (type == "code") {
136 printf "<pre><code>"
137 printf(buf[type]) | CODEPROC
138 close(CODEPROC)
139 print "</code></pre>"
140 } else if (type == "text") {
141 print(buf[type]) | TEXTPROC
142 close(TEXTPROC)
143 }
144 buf[type] = ""
145 }
146}
147
148# *file_print*: Print FILE line-by-line. The `> 0` check here ensures that it
149# bails on error (-1).
150function file_print(file)
151{
152 if (file) {
153 while ((getline l < file) > 0) {
154 print template_expand(l)
155 }
156 close(file)
157 }
158}
159
160# *get_title*: get the title of the current script, for the expanded document.
161# If variables are set, use those; otherwise try to figure out the title from
162# the document's basename.
163function get_title()
164{
165 title = getenv("DOCAWK_TITLE", TITLE)
166 if (! title) {
167 title = FILENAME
168 sub(/.*\//, "", title)
169 }
170 return title
171}
172
173# *getenv*: a convenience function for pulling values out of the environment.
174# If an environment variable ENV isn't found, test if VAR is set (i.e., `doc.awk
175# -v var=foo`.) and return it if it's set. Otherwise, return the default value
176# DEF.
177function getenv(env, var, def)
178{
179 if (ENVIRON[env]) {
180 return ENVIRON[env]
181 } else if (var) {
182 return var
183 } else {
184 return def
185 }
186}
187
188# *template_expand*: expand templates of the form `@@template@@` in the text.
189# Currently it only does variables, and works by line.
190#
191# Due to the way awk works, template variables need to live in their own special
192# array, `TV`. I'd love it if awk had some kind of `eval` functionality, but at
193# least POSIX awk doesn't.
194function template_expand(text)
195{
196 if (match(text, /@@[^@]*@@/)) {
197 var = substr(text, RSTART + 2, RLENGTH - 4)
198 new = substr(text, 1, RSTART - 1)
199 new = new TV[var]
200 new = new substr(text, RSTART + RLENGTH)
201 } else {
202 new = text
203 }
204 return new
205}
206
207# *trim*: remove whitespace from either end of a string.
208function trim(str)
209{
210 sub(/^[ \n]*/, "", str)
211 sub(/[ \n]*$/, "", str)
212 return str
213}
diff --git a/footer.html b/footer.html new file mode 100644 index 0000000..308b1d0 --- /dev/null +++ b/footer.html
@@ -0,0 +1,2 @@
1</body>
2</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 @@
1<!DOCTYPE html>
2<title>@@TITLE@@</title>
3<link type="text/css" rel="stylesheet" href="style.css" />
4<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 @@
1#!/bin/awk -f
2{
3 gsub(/&/, "\\&amp;", $0)
4 gsub(/</, "\\&lt;", $0)
5 gsub(/>/, "\\&gt;", $0)
6 print
7}
8
diff --git a/mdown.awk b/mdown.awk new file mode 100755 index 0000000..ca39b09 --- /dev/null +++ b/mdown.awk
@@ -0,0 +1,903 @@
1#! /usr/bin/awk -f
2#
3# Markdown processor in AWK. It is simplified from d.awk
4# https://github.com/wernsey/d.awk
5#
6# (c) 2016 Werner Stoop
7# Slight modifications (c) 2022 Case Duckworth
8# Copying and distribution of this file, with or without modification,
9# are permitted in any medium without royalty provided the copyright
10# notice and this notice are preserved. This file is offered as-is,
11# without any warranty.
12BEGIN {
13 # Configuration options
14 if (Title == "") {
15 Title = "Documentation"
16 }
17 if (Theme == "") {
18 Theme = 1
19 }
20 if (HideToCLevel == "") {
21 HideToCLevel = 3
22 }
23 if (Lang == "") {
24 Lang = "en"
25 }
26 if (Tables == "") {
27 Tables = 1
28 #TopLinks = 1;
29 #classic_underscore = 1;
30 }
31 if (MaxWidth == "") {
32 MaxWidth = "1080px"
33 }
34 if (NumberHeadings == "") {
35 NumberHeadings = 1
36 }
37 if (NumberH1s == "") {
38 NumberH1s = 0
39 }
40 Mode = "p"
41 ToC = ""
42 ToCLevel = 1
43 CSS = init_css(Theme)
44 for (i = 0; i < 128; i++) {
45 _ord[sprintf("%c", i)] = i
46 }
47 srand()
48}
49
50{
51 gsub(/\r/, "", $0)
52}
53
54{
55 Out = Out filter($0)
56}
57
58END {
59 if (Mode == "ul" || Mode == "ol") {
60 while (ListLevel > 1) {
61 Buf = Buf "\n</" Open[ListLevel--] ">"
62 }
63 Out = Out tag(Mode, Buf "\n")
64 } else if (Mode == "pre") {
65 while (ListLevel > 1) {
66 Buf = Buf "\n</" Open[ListLevel--] ">"
67 }
68 Out = Out tag(Mode, Buf "\n")
69 } else if (Mode == "table") {
70 Out = Out end_table()
71 } else {
72 Buf = trim(scrub(Buf))
73 if (Buf) {
74 Out = Out tag(Mode, Buf)
75 }
76 }
77 if (ToC && match(Out, /!\[toc[-+]?\]/)) {
78 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')?'&#x25BA;':'&#x25BC;';\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')?'&#x25BA;':'&#x25BC;';\n" " }\n" "}\n" "//-->\n</script>"
79 }
80 if (Out) {
81 Out = fix_footnotes(Out)
82 Out = fix_links(Out)
83 Out = fix_abbrs(Out)
84 Out = make_toc(Out)
85 print trim(Out)
86 if (footnotes) {
87 footnotes = fix_links(footnotes)
88 print "<hr><ol class=\"footnotes\">\n" footnotes "</ol>"
89 }
90 }
91}
92
93
94function end_table(r, c, t, a, s)
95{
96 for (r = 1; r < Row; r++) {
97 t = IsHeaders[r] ? "th" : "td"
98 s = s "<tr>"
99 for (c = 1; c <= NCols[r]; c++) {
100 a = Align[c]
101 if (a) {
102 s = s "<" t " align=\"" a "\">" scrub(Table[r, c]) "</" t ">"
103 } else {
104 s = s "<" t ">" scrub(Table[r, c]) "</" t ">"
105 }
106 }
107 s = s "</tr>\n"
108 }
109 return tag("table", s, "class=\"da\"")
110}
111
112function escape(st)
113{
114 gsub(/&/, "\\&amp;", st)
115 gsub(/</, "\\&lt;", st)
116 gsub(/>/, "\\&gt;", st)
117 return st
118}
119
120function filter(st, res, tmp, linkdesc, url, delim, edelim, name, def, plang, mmaid, cols, i)
121{
122 if (Mode == "p") {
123 if (match(st, /^[[:space:]]*\[[-._[:alnum:][:space:]]+\]:/)) {
124 linkdesc = ""
125 LastLink = 0
126 match(st, /\[.*\]/)
127 LinkRef = tolower(substr(st, RSTART + 1, RLENGTH - 2))
128 st = substr(st, RSTART + RLENGTH + 2)
129 match(st, /[^[:space:]]+/)
130 url = substr(st, RSTART, RLENGTH)
131 st = substr(st, RSTART + RLENGTH + 1)
132 if (match(url, /^<.*>/)) {
133 url = substr(url, RSTART + 1, RLENGTH - 2)
134 }
135 if (match(st, /["'(]/)) {
136 delim = substr(st, RSTART, 1)
137 edelim = (delim == "(") ? ")" : delim
138 if (match(st, delim ".*" edelim)) {
139 linkdesc = substr(st, RSTART + 1, RLENGTH - 2)
140 }
141 }
142 LinkUrls[LinkRef] = escape(url)
143 if (! linkdesc) {
144 LastLink = 1
145 }
146 LinkDescs[LinkRef] = escape(linkdesc)
147 return
148 } else if (LastLink && match(st, /^[[:space:]]*["'(]/)) {
149 match(st, /["'(]/)
150 delim = substr(st, RSTART, 1)
151 edelim = (delim == "(") ? ")" : delim
152 st = substr(st, RSTART)
153 if (match(st, delim ".*" edelim)) {
154 LinkDescs[LinkRef] = escape(substr(st, RSTART + 1, RLENGTH - 2))
155 }
156 LastLink = 0
157 return
158 } else if (match(st, /^[[:space:]]*\[\^[-._[:alnum:][:space:]]+\]:[[:space:]]*/)) {
159 match(st, /\[\^[[:alnum:]]+\]:/)
160 name = substr(st, RSTART + 2, RLENGTH - 4)
161 def = substr(st, RSTART + RLENGTH + 1)
162 Footnote[tolower(name)] = scrub(def)
163 return
164 } else if (match(st, /^[[:space:]]*\*\[[[:alnum:]]+\]:[[:space:]]*/)) {
165 match(st, /\[[[:alnum:]]+\]/)
166 name = substr(st, RSTART + 1, RLENGTH - 2)
167 def = substr(st, RSTART + RLENGTH + 2)
168 Abbrs[toupper(name)] = def
169 return
170 } else if (match(st, /^(( )| *\t)/) || match(st, /^[[:space:]]*```+[[:alnum:]]*/)) {
171 Preterm = trim(substr(st, RSTART, RLENGTH))
172 st = substr(st, RSTART + RLENGTH)
173 if (Buf) {
174 res = tag("p", scrub(Buf))
175 }
176 Buf = st
177 push("pre")
178 } else if (! trim(Prev) && match(st, /^[[:space:]]*[*-][[:space:]]*[*-][[:space:]]*[*-][-*[:space:]]*$/)) {
179 if (Buf) {
180 res = tag("p", scrub(Buf))
181 }
182 Buf = ""
183 res = res "<hr>\n"
184 } else if (match(st, /^[[:space:]]*===+[[:space:]]*$/)) {
185 Buf = trim(substr(Buf, 1, length(Buf) - length(Prev) - 1))
186 if (Buf) {
187 res = tag("p", scrub(Buf))
188 }
189 if (Prev) {
190 res = res heading(1, scrub(Prev))
191 }
192 Buf = ""
193 } else if (match(st, /^[[:space:]]*---+[[:space:]]*$/)) {
194 Buf = trim(substr(Buf, 1, length(Buf) - length(Prev) - 1))
195 if (Buf) {
196 res = tag("p", scrub(Buf))
197 }
198 if (Prev) {
199 res = res heading(2, scrub(Prev))
200 }
201 Buf = ""
202 } else if (match(st, /^[[:space:]]*#+/)) {
203 sub(/#+[[:space:]]*$/, "", st)
204 match(st, /#+/)
205 ListLevel = RLENGTH
206 tmp = substr(st, RSTART + RLENGTH)
207 if (Buf) {
208 res = tag("p", scrub(Buf))
209 }
210 res = res heading(ListLevel, scrub(trim(tmp)))
211 Buf = ""
212 } else if (match(st, /^[[:space:]]*>/)) {
213 if (Buf) {
214 res = tag("p", scrub(Buf))
215 }
216 Buf = scrub(trim(substr(st, RSTART + RLENGTH)))
217 push("blockquote")
218 } else if (Tables && match(st, /.*\|(.*\|)+/)) {
219 if (Buf) {
220 res = tag("p", scrub(Buf))
221 }
222 Row = 1
223 for (i = 1; i <= MaxCols; i++) {
224 Align[i] = ""
225 }
226 process_table_row(st)
227 push("table")
228 } else if (match(st, /^[[:space:]]*([*+-]|[[:digit:]]+\.)[[:space:]]/)) {
229 if (Buf) {
230 res = tag("p", scrub(Buf))
231 }
232 Buf = ""
233 match(st, /^[[:space:]]*/)
234 ListLevel = 1
235 indent[ListLevel] = RLENGTH
236 Open[ListLevel] = match(st, /^[[:space:]]*[*+-][[:space:]]*/) ? "ul" : "ol"
237 push(Open[ListLevel])
238 res = res filter(st)
239 } else if (match(st, /^[[:space:]]*$/)) {
240 if (trim(Buf)) {
241 res = tag("p", scrub(trim(Buf)))
242 Buf = ""
243 }
244 } else {
245 Buf = Buf st "\n"
246 }
247 LastLink = 0
248 } else if (Mode == "blockquote") {
249 if (match(st, /^[[:space:]]*>[[:space:]]*$/)) {
250 Buf = Buf "\n</p><p>"
251 } else if (match(st, /^[[:space:]]*>/)) {
252 Buf = Buf "\n" scrub(trim(substr(st, RSTART + RLENGTH)))
253 } else if (match(st, /^[[:space:]]*$/)) {
254 res = tag("blockquote", tag("p", trim(Buf)))
255 pop()
256 res = res filter(st)
257 } else {
258 Buf = Buf st
259 }
260 } else if (Mode == "table") {
261 if (match(st, /.*\|(.*\|)+/)) {
262 process_table_row(st)
263 } else {
264 res = end_table()
265 pop()
266 res = res filter(st)
267 }
268 } else if (Mode == "pre") {
269 if (! Preterm && match(st, /^(( )| *\t)/) || Preterm && ! match(st, /^[[:space:]]*```+/)) {
270 Buf = Buf ((Buf) ? "\n" : "") substr(st, RSTART + RLENGTH)
271 } else {
272 gsub(/\t/, " ", Buf)
273 if (length(trim(Buf)) > 0) {
274 plang = ""
275 mmaid = 0
276 if (match(Preterm, /^[[:space:]]*```+/)) {
277 plang = trim(substr(Preterm, RSTART + RLENGTH))
278 if (plang) {
279 HasPretty = 1
280 if (plang == "auto") {
281 plang = "class=\"prettyprint\""
282 } else {
283 plang = "class=\"prettyprint lang-" plang "\""
284 }
285 }
286 }
287 if (mmaid && Mermaid) {
288 res = tag("div", Buf, "class=\"mermaid\"")
289 } else {
290 res = tag("pre", tag("code", escape(Buf), plang))
291 }
292 }
293 pop()
294 if (Preterm) {
295 sub(/^[[:space:]]*```+[[:alnum:]]*/, "", st)
296 }
297 res = res filter(st)
298 }
299 } else if (Mode == "ul" || Mode == "ol") {
300 if (ListLevel == 0 || match(st, /^[[:space:]]*$/) && (RLENGTH <= indent[1])) {
301 while (ListLevel > 1) {
302 Buf = Buf "\n</" Open[ListLevel--] ">"
303 }
304 res = tag(Mode, "\n" Buf "\n")
305 pop()
306 } else if (match(st, /^[[:space:]]*([*+-]|[[:digit:]]+\.)/)) {
307 tmp = substr(st, RLENGTH + 1)
308 match(st, /^[[:space:]]*/)
309 if (RLENGTH > indent[ListLevel]) {
310 indent[++ListLevel] = RLENGTH
311 if (match(st, /^[[:space:]]*[*+-]/)) {
312 Open[ListLevel] = "ul"
313 } else {
314 Open[ListLevel] = "ol"
315 }
316 Buf = Buf "\n<" Open[ListLevel] ">"
317 } else {
318 while (RLENGTH < indent[ListLevel]) {
319 Buf = Buf "\n</" Open[ListLevel--] ">"
320 }
321 }
322 if (match(tmp, /^[[:space:]]*\[[xX[:space:]]\]/)) {
323 st = substr(tmp, RLENGTH + 1)
324 tmp = tolower(substr(tmp, RSTART, RLENGTH))
325 Buf = Buf "<li><input type=\"checkbox\" " (index(tmp, "x") ? "checked" : "") " disabled>" scrub(st)
326 } else {
327 Buf = Buf "<li>" scrub(tmp)
328 }
329 } else if (match(st, /^[[:space:]]*$/)) {
330 Buf = Buf "<br>\n"
331 } else {
332 sub(/^[[:space:]]+/, "", st)
333 Buf = Buf "\n" scrub(st)
334 }
335 }
336 Prev = st
337 return res
338}
339
340function fix_abbrs(str, st, k, r, p)
341{
342 for (k in Abbrs) {
343 r = ""
344 st = str
345 t = escape(Abbrs[toupper(k)])
346 gsub(/&/, "\\&", t)
347 p = match(st, "[^[:alnum:]]" k "[^[:alnum:]]")
348 while (p) {
349 r = r substr(st, 1, RSTART)
350 r = r "<abbr title=\"" t "\">" k "</abbr>"
351 st = substr(st, RSTART + RLENGTH - 1)
352 p = match(st, "[^[:alnum:]]" k "[^[:alnum:]]")
353 }
354 str = r st
355 }
356 return str
357}
358
359function fix_footnotes(st, r, p, n, i, d, fn, fc)
360{
361 p = match(st, /\[\^[^\]]+\]/)
362 while (p) {
363 if (substr(st, RSTART - 2, 1) == "\\") {
364 r = r substr(st, 1, RSTART - 3) substr(st, RSTART, RLENGTH)
365 st = substr(st, RSTART + RLENGTH)
366 p = match(st, /\[\^[^\]]+\]/)
367 continue
368 }
369 r = r substr(st, 1, RSTART - 1)
370 d = substr(st, RSTART + 2, RLENGTH - 3)
371 n = tolower(d)
372 st = substr(st, RSTART + RLENGTH)
373 if (Footnote[tolower(n)]) {
374 if (! fn[n]) {
375 fn[n] = ++fc
376 }
377 d = Footnote[n]
378 } else {
379 Footnote[n] = scrub(d)
380 if (! fn[n]) {
381 fn[n] = ++fc
382 }
383 }
384 footname[fc] = n
385 d = strip_tags(d)
386 if (length(d) > 20) {
387 d = substr(d, 1, 20) "&hellip;"
388 }
389 r = r "<sup title=\"" d "\"><a href=\"#footnote-" fn[n] "\" id=\"footnote-pos-" fn[n] "\" class=\"footnote\">[" fn[n] "]</a></sup>"
390 p = match(st, /\[\^[^\]]+\]/)
391 }
392 for (i = 1; i <= fc; i++) {
393 footnotes = footnotes "<li id=\"footnote-" i "\">" Footnote[footname[i]] "<a title=\"Return to Document\" class=\"footnote-back\" href=\"#footnote-pos-" i "\">&nbsp;&nbsp;&#8630;&nbsp;Back</a></li>\n"
394 }
395 return (r st)
396}
397
398function fix_links(st, lt, ld, lr, url, img, res, rx, pos, pre)
399{
400 do {
401 pre = match(st, /<(pre|code)>/) # Don't substitute in <pre> or <code> blocks
402 pos = match(st, /\[[^\]]+\]/)
403 if (! pos) {
404 break
405 }
406 if (pre && pre < pos) {
407 match(st, /<\/(pre|code)>/)
408 res = res substr(st, 1, RSTART + RLENGTH)
409 st = substr(st, RSTART + RLENGTH + 1)
410 continue
411 }
412 img = substr(st, RSTART - 1, 1) == "!"
413 if (substr(st, RSTART - (img ? 2 : 1), 1) == "\\") {
414 res = res substr(st, 1, RSTART - (img ? 3 : 2))
415 if (img && substr(st, RSTART, RLENGTH) == "[toc]") {
416 res = res "\\"
417 }
418 res = res substr(st, RSTART - (img ? 1 : 0), RLENGTH + (img ? 1 : 0))
419 st = substr(st, RSTART + RLENGTH)
420 continue
421 }
422 res = res substr(st, 1, RSTART - (img ? 2 : 1))
423 rx = substr(st, RSTART, RLENGTH)
424 st = substr(st, RSTART + RLENGTH)
425 if (match(st, /^[[:space:]]*\([^)]+\)/)) {
426 lt = substr(rx, 2, length(rx) - 2)
427 match(st, /\([^)]+\)/)
428 url = substr(st, RSTART + 1, RLENGTH - 2)
429 st = substr(st, RSTART + RLENGTH)
430 ld = ""
431 if (match(url, /[[:space:]]+["']/)) {
432 ld = url
433 url = substr(url, 1, RSTART - 1)
434 match(ld, /["']/)
435 delim = substr(ld, RSTART, 1)
436 if (match(ld, delim ".*" delim)) {
437 ld = substr(ld, RSTART + 1, RLENGTH - 2)
438 }
439 } else {
440 ld = ""
441 }
442 if (img) {
443 res = res "<img src=\"" url "\" title=\"" ld "\" alt=\"" lt "\">"
444 } else {
445 res = res "<a class=\"normal\" href=\"" url "\" title=\"" ld "\">" lt "</a>"
446 }
447 } else if (match(st, /^[[:space:]]*\[[^\]]*\]/)) {
448 lt = substr(rx, 2, length(rx) - 2)
449 match(st, /\[[^\]]*\]/)
450 lr = trim(tolower(substr(st, RSTART + 1, RLENGTH - 2)))
451 if (! lr) {
452 lr = tolower(trim(lt))
453 if (LinkDescs[lr]) {
454 lt = LinkDescs[lr]
455 }
456 }
457 st = substr(st, RSTART + RLENGTH)
458 url = LinkUrls[lr]
459 ld = LinkDescs[lr]
460 if (img) {
461 res = res "<img src=\"" url "\" title=\"" ld "\" alt=\"" lt "\">"
462 } else if (url) {
463 res = res "<a class=\"normal\" href=\"" url "\" title=\"" ld "\">" lt "</a>"
464 } else {
465 res = res "[" lt "][" lr "]"
466 }
467 } else {
468 res = res (img ? "!" : "") rx
469 }
470 } while (pos > 0)
471 return (res st)
472}
473
474function heading(level, st, res, href, u, text, svg)
475{
476 if (level > 6) {
477 level = 6
478 }
479 st = trim(st)
480 href = tolower(st)
481 href = strip_tags(href)
482 gsub(/[^-_ [:alnum:]]+/, "", href)
483 gsub(/[[:space:]]/, "-", href)
484 if (TitleUrls[href]) {
485 for (u = 1; TitleUrls[href "-" u]; u++) {
486 }
487 href = href "-" u
488 }
489 TitleUrls[href] = "#" href
490 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>"
491 text = "<a href=\"#" href "\" class=\"header\">" st "&nbsp;" svg "</a>" (TopLinks ? "&nbsp;&nbsp;<a class=\"top\" title=\"Return to top\" href=\"#\">&#8593;&nbsp;Top</a>" : "")
492 res = tag("h" level, text, "id=\"" href "\"")
493 for (; ToCLevel < level; ToCLevel++) {
494 ToC_ID++
495 if (ToCLevel < HideToCLevel) {
496 ToC = ToC "<a class=\"toc-button\" id=\"toc-btn-" ToC_ID "\" onclick=\"toggle_toc_ul('" ToC_ID "')\">&#x25BC;</a>"
497 ToC = ToC "<ul class=\"toc toc-" ToCLevel "\" id=\"toc-ul-" ToC_ID "\">"
498 } else {
499 ToC = ToC "<a class=\"toc toc-button\" id=\"toc-btn-" ToC_ID "\" onclick=\"toggle_toc_ul('" ToC_ID "')\">&#x25BA;</a>"
500 ToC = ToC "<ul style=\"display:none;\" class=\"toc toc-" ToCLevel "\" id=\"toc-ul-" ToC_ID "\">"
501 }
502 }
503 for (; ToCLevel > level; ToCLevel--) {
504 ToC = ToC "</ul>"
505 }
506 ToC = ToC "<li class=\"toc-" level "\"><a class=\"toc toc-" level "\" href=\"#" href "\">" st "</a>\n"
507 ToCLevel = level
508 return res
509}
510
511function init_css(Theme, css, ss, hr, c1, c2, c3, c4, c5, bg1, bg2, bg3, bg4, ff, fs, i)
512{
513 if (Theme == "0") {
514 return ""
515 }
516 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;"
517 css["h1"] = "border-bottom:1px solid %color1%;padding:0.3em 0.1em;"
518 css["h1 a"] = "color:%color1%;"
519 css["h2"] = "color:%color2%;border-bottom:1px solid %color2%;padding:0.2em 0.1em;"
520 css["h2 a"] = "color:%color2%;"
521 css["h3"] = "color:%color3%;border-bottom:1px solid %color3%;padding:0.1em 0.1em;"
522 css["h3 a"] = "color:%color3%;"
523 css["h4,h5,h6"] = "padding:0.1em 0.1em;"
524 css["h4 a,h5 a,h6 a"] = "color:%color4%;"
525 css["h1,h2,h3,h4,h5,h6"] = "font-weight:bolder;line-height:1.2em;"
526 css["h4"] = "border-bottom:1px solid %color4%"
527 css["p"] = "margin:0.5em 0.1em;"
528 css["hr"] = "background:%color1%;height:1px;border:0;"
529 css["a.normal, a.toc"] = "color:%color2%;"
530 #css["a.normal:visited"] = "color:%color2%;";
531 #css["a.normal:active"] = "color:%color4%;";
532 css["a.normal:hover, a.toc:hover"] = "color:%color4%;"
533 css["a.top"] = "font-size:x-small;text-decoration:initial;float:right;"
534 css["a.header svg"] = "opacity:0;"
535 css["a.header:hover svg"] = "opacity:1;"
536 css["a.header"] = "text-decoration: none;"
537 css["strong,b"] = "color:%color1%"
538 css["code"] = "color:%color2%;font-weight:bold;"
539 css["blockquote"] = "margin-left:1em;color:%color2%;border-left:0.2em solid %color3%;padding:0.25em 0.5em;overflow-x:auto;"
540 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;"
541 css["table.dawk-ex"] = "border-collapse:collapse;margin:0.5em;"
542 css["th.dawk-ex,td.dawk-ex"] = "padding:0.5em 0.75em;border:1px solid %color4%;"
543 css["th.dawk-ex"] = "color:%color2%;border:1px solid %color3%;border-bottom:2px solid %color3%;"
544 css["tr.dawk-ex:nth-child(odd)"] = "background-color:%color5%;"
545 css["table.da"] = "border-collapse:collapse;margin:0.5em;"
546 css["table.da th,td"] = "padding:0.5em 0.75em;border:1px solid %color4%;"
547 css["table.da th"] = "color:%color2%;border:1px solid %color3%;border-bottom:2px solid %color3%;"
548 css["table.da tr:nth-child(odd)"] = "background-color:%color5%;"
549 css["div.dawk-ex"] = "padding:0.5em;"
550 css["caption.dawk-ex"] = "padding:0.5em;font-style:italic;"
551 css["dl.dawk-ex"] = "margin:0.5em;"
552 css["dt.dawk-ex"] = "font-weight:bold;"
553 css["dd.dawk-ex"] = "padding:0.3em;"
554 css["mark.dawk-ex"] = "color:%color5%;background-color:%color4%;"
555 css["del.dawk-ex,s.dawk-ex"] = "color:%color4%;"
556 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;"
557 css["a.toc-button:hover"] = "color:%color4%;background:%color5%;"
558 css["div#table-of-contents"] = "padding:0;font-size:smaller;"
559 css["abbr"] = "cursor:help;"
560 css["ol.footnotes"] = "font-size:small;color:%color4%"
561 css["a.footnote"] = "font-size:smaller;text-decoration:initial;"
562 css["a.footnote-back"] = "text-decoration:initial;font-size:x-small;"
563 css[".fade"] = "color:%color5%;"
564 css[".highlight"] = "color:%color2%;background-color:%color5%;"
565 css["summary"] = "cursor:pointer;"
566 css["ul.toc"] = "list-style-type:none;"
567 if (NumberHeadings) {
568 if (NumberH1s) {
569 css["body"] = css["body"] "counter-reset: h1 toc1;"
570 css["h1"] = css["h1"] "counter-reset: h2 h3 h4;"
571 css["h2"] = css["h2"] "counter-reset: h3 h4;"
572 css["h3"] = css["h3"] "counter-reset: h4;"
573 css["h1::before"] = "content: counter(h1) \" \"; counter-increment: h1; margin-right: 10px;"
574 css["h2::before"] = "content: counter(h1) \".\"counter(h2) \" \";counter-increment: h2; margin-right: 10px;"
575 css["h3::before"] = "content: counter(h1) \".\"counter(h2) \".\"counter(h3) \" \";counter-increment: h3; margin-right: 10px;"
576 css["h4::before"] = "content: counter(h1) \".\"counter(h2) \".\"counter(h3)\".\"counter(h4) \" \";counter-increment: h4; margin-right: 10px;"
577 css["li.toc-1"] = "counter-reset: toc2 toc3 toc4;"
578 css["li.toc-2"] = "counter-reset: toc3 toc4;"
579 css["li.toc-3"] = "counter-reset: toc4;"
580 css["a.toc-1::before"] = "content: counter(h1) \" \";counter-increment: toc1;"
581 css["a.toc-2::before"] = "content: counter(h1) \".\" counter(toc2) \" \";counter-increment: toc2;"
582 css["a.toc-3::before"] = "content: counter(h1) \".\" counter(toc2) \".\" counter(toc3) \" \";counter-increment: toc3;"
583 css["a.toc-4::before"] = "content: counter(h1) \".\" counter(toc2) \".\" counter(toc3) \".\" counter(toc4) \" \";counter-increment: toc4;"
584 } else {
585 css["h1"] = css["h1"] "counter-reset: h2 h3 h4;"
586 css["h2"] = css["h2"] "counter-reset: h3 h4;"
587 css["h3"] = css["h3"] "counter-reset: h4;"
588 css["h2::before"] = "content: counter(h2) \" \";counter-increment: h2; margin-right: 10px;"
589 css["h3::before"] = "content: counter(h2) \".\"counter(h3) \" \";counter-increment: h3; margin-right: 10px;"
590 css["h4::before"] = "content: counter(h2) \".\"counter(h3)\".\"counter(h4) \" \";counter-increment: h4; margin-right: 10px;"
591 css["li.toc-1"] = "counter-reset: toc2 toc3 toc4;"
592 css["li.toc-2"] = "counter-reset: toc3 toc4;"
593 css["li.toc-3"] = "counter-reset: toc4;"
594 css["a.toc-2::before"] = "content: counter(toc2) \" \";counter-increment: toc2;"
595 css["a.toc-3::before"] = "content: counter(toc2) \".\" counter(toc3) \" \";counter-increment: toc3;"
596 css["a.toc-4::before"] = "content: counter(toc2) \".\" counter(toc3) \".\" counter(toc4) \" \";counter-increment: toc4;"
597 }
598 }
599 # Colors:
600 #c1="#314070";c2="#465DA6";c3="#6676A8";c4="#A88C3F";c5="#E8E4D9";
601 c1 = "#314070"
602 c2 = "#384877"
603 c3 = "#6676A8"
604 c4 = "#738FD0"
605 c5 = "#FBFCFF"
606 # Font Family:
607 ff = "sans-serif"
608 fs = "11pt"
609 # Alternative color scheme suggestions:
610 #c1="#303F9F";c2="#0449CC";c3="#2162FA";c4="#4B80FB";c5="#EDF2FF";
611 #ff="\"Trebuchet MS\", Helvetica, sans-serif";
612 #c1="#430005";c2="#740009";c3="#A6373F";c4="#c55158";c5="#fbf2f2";
613 #ff="Verdana, Geneva, sans-serif";
614 #c1="#083900";c2="#0D6300";c3="#3C8D2F";c4="#50be3f";c5="#f2faf1";
615 #ff="Georgia, serif";
616 #c1="#35305D";c2="#646379";c3="#7A74A5";c4="#646392";c5="#fafafa";
617 for (i = 0; i <= 255; i++) {
618 _hex[sprintf("%02X", i)] = i
619 }
620 for (k in css) {
621 ss = ss "\n" k "{" css[k] "}"
622 }
623 gsub(/%maxwidth%/, MaxWidth, ss)
624 gsub(/%color1%/, c1, ss)
625 gsub(/%color2%/, c2, ss)
626 gsub(/%color3%/, c3, ss)
627 gsub(/%color4%/, c4, ss)
628 gsub(/%color5%/, c5, ss)
629 gsub(/%font-family%/, ff, ss)
630 gsub(/%font-size%/, fs, ss)
631 gsub(/%hr%/, hr, ss)
632 return ss
633}
634
635function itag(t, body)
636{
637 return ("<" t ">" body "</" t ">")
638}
639
640function make_toc(st, r, p, dis, t, n)
641{
642 if (! ToC) {
643 return st
644 }
645 for (; ToCLevel > 1; ToCLevel--) {
646 ToC = ToC "</ul>"
647 }
648 p = match(st, /!\[toc[-+]?\]/)
649 while (p) {
650 if (substr(st, RSTART - 1, 1) == "\\") {
651 r = r substr(st, 1, RSTART - 2) substr(st, RSTART, RLENGTH)
652 st = substr(st, RSTART + RLENGTH)
653 p = match(st, /!\[toc[-+]?\]/)
654 continue
655 }
656 ++n
657 dis = index(substr(st, RSTART, RLENGTH), "+")
658 t = "<div>\n<a id=\"toc-button-" n "\" class=\"toc-button\" onclick=\"toggle_toc(" n ")\"><span id=\"btn-text-" n "\">" (dis ? "&#x25BC;" : "&#x25BA;") "</span>&nbsp;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>"
659 r = r substr(st, 1, RSTART - 1)
660 r = r t
661 st = substr(st, RSTART + RLENGTH)
662 p = match(st, /!\[toc[-+]?\]/)
663 }
664 return (r st)
665}
666
667function obfuscate(e, r, i, t, o)
668{
669 for (i = 1; i <= length(e); i++) {
670 t = substr(e, i, 1)
671 r = int(rand() * 100)
672 if (r > 50) {
673 o = o sprintf("&#x%02X;", _ord[t])
674 } else if (r > 10) {
675 o = o sprintf("&#%d;", _ord[t])
676 } else {
677 o = o t
678 }
679 }
680 return o
681}
682
683function pop()
684{
685 Mode = Stack[--StackTop]
686 Buf = ""
687 return Mode
688}
689
690function process_table_row(st, cols, i)
691{
692 if (match(st, /^[[:space:]]*\|/)) {
693 st = substr(st, RSTART + RLENGTH)
694 }
695 if (match(st, /\|[[:space:]]*$/)) {
696 st = substr(st, 1, RSTART - 1)
697 }
698 st = trim(st)
699 if (match(st, /^([[:space:]:|]|---+)*$/)) {
700 IsHeaders[Row - 1] = 1
701 cols = split(st, A, /[[:space:]]*\|[[:space:]]*/)
702 for (i = 1; i <= cols; i++) {
703 if (match(A[i], /^:-*:$/)) {
704 Align[i] = "center"
705 } else if (match(A[i], /^-*:$/)) {
706 Align[i] = "right"
707 } else if (match(A[i], /^:-*$/)) {
708 Align[i] = "left"
709 }
710 }
711 return
712 }
713 cols = split(st, A, /[[:space:]]*\|[[:space:]]*/)
714 for (i = 1; i <= cols; i++) {
715 Table[Row, i] = A[i]
716 }
717 NCols[Row] = cols
718 if (cols > MaxCols) {
719 MaxCols = cols
720 }
721 IsHeaders[Row] = 0
722 Row++
723}
724
725function push(newmode)
726{
727 Stack[StackTop++] = Mode
728 Mode = newmode
729}
730
731function scrub(st, mp, ms, me, r, p, tg, a)
732{
733 sub(/ $/, "<br>\n", st)
734 gsub(/( |[[:space:]]+\\)\n/, "<br>\n", st)
735 gsub(/( |[[:space:]]+\\)$/, "<br>\n", st)
736 while (match(st, /(__?|\*\*?|~~|`+|[&><\\])/)) {
737 a = substr(st, 1, RSTART - 1)
738 mp = substr(st, RSTART, RLENGTH)
739 ms = substr(st, RSTART - 1, 1)
740 me = substr(st, RSTART + RLENGTH, 1)
741 p = RSTART + RLENGTH
742 if (! classic_underscore && match(mp, /_+/)) {
743 if (match(ms, /[[:alnum:]]/) && match(me, /[[:alnum:]]/)) {
744 tg = substr(st, 1, index(st, mp))
745 r = r tg
746 st = substr(st, index(st, mp) + 1)
747 continue
748 }
749 }
750 st = substr(st, p)
751 r = r a
752 ms = ""
753 if (mp == "\\") {
754 if (match(st, /^!?\[/)) {
755 r = r "\\" substr(st, RSTART, RLENGTH)
756 st = substr(st, 2)
757 } else if (match(st, /^(\*\*|__|~~|`+)/)) {
758 r = r substr(st, 1, RLENGTH)
759 st = substr(st, RLENGTH + 1)
760 } else {
761 r = r substr(st, 1, 1)
762 st = substr(st, 2)
763 }
764 continue
765 } else if (mp == "_" || mp == "*") {
766 if (match(me, /[[:space:]]/)) {
767 r = r mp
768 continue
769 }
770 p = index(st, mp)
771 while (p && match(substr(st, p - 1, 1), /[\\[:space:]]/)) {
772 ms = ms substr(st, 1, p - 1) mp
773 st = substr(st, p + length(mp))
774 p = index(st, mp)
775 }
776 if (! p) {
777 r = r mp ms
778 continue
779 }
780 ms = ms substr(st, 1, p - 1)
781 r = r itag("em", scrub(ms))
782 st = substr(st, p + length(mp))
783 } else if (mp == "__" || mp == "**") {
784 if (match(me, /[[:space:]]/)) {
785 r = r mp
786 continue
787 }
788 p = index(st, mp)
789 while (p && match(substr(st, p - 1, 1), /[\\[:space:]]/)) {
790 ms = ms substr(st, 1, p - 1) mp
791 st = substr(st, p + length(mp))
792 p = index(st, mp)
793 }
794 if (! p) {
795 r = r mp ms
796 continue
797 }
798 ms = ms substr(st, 1, p - 1)
799 r = r itag("strong", scrub(ms))
800 st = substr(st, p + length(mp))
801 } else if (mp == "~~") {
802 p = index(st, mp)
803 if (! p) {
804 r = r mp
805 continue
806 }
807 while (p && substr(st, p - 1, 1) == "\\") {
808 ms = ms substr(st, 1, p - 1) mp
809 st = substr(st, p + length(mp))
810 p = index(st, mp)
811 }
812 ms = ms substr(st, 1, p - 1)
813 r = r itag("del", scrub(ms))
814 st = substr(st, p + length(mp))
815 } else if (match(mp, /`+/)) {
816 p = index(st, mp)
817 if (! p) {
818 r = r mp
819 continue
820 }
821 ms = substr(st, 1, p - 1)
822 r = r itag("code", escape(ms))
823 st = substr(st, p + length(mp))
824 } else if (mp == ">") {
825 r = r "&gt;"
826 } else if (mp == "<") {
827 p = index(st, ">")
828 if (! p) {
829 r = r "&lt;"
830 continue
831 }
832 tg = substr(st, 1, p - 1)
833 if (match(tg, /^[[:alpha:]]+[[:space:]]/)) {
834 a = trim(substr(tg, RSTART + RLENGTH - 1))
835 tg = substr(tg, 1, RLENGTH - 1)
836 } else {
837 a = ""
838 }
839 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)$")) {
840 if (! match(tg, /\//)) {
841 if (match(a, /class="/)) {
842 sub(/class="/, "class=\"dawk-ex ", a)
843 } else if (a) {
844 a = a " class=\"dawk-ex\""
845 } else {
846 a = "class=\"dawk-ex\""
847 }
848 r = r "<" tg " " a ">"
849 } else {
850 r = r "<" tg ">"
851 }
852 } else if (match(tg, "^[[:alpha:]]+://[[:graph:]]+$")) {
853 if (! a) {
854 a = tg
855 }
856 r = r "<a class=\"normal\" href=\"" tg "\">" a "</a>"
857 } else if (match(tg, "^[[:graph:]]+@[[:graph:]]+$")) {
858 if (! a) {
859 a = tg
860 }
861 r = r "<a class=\"normal\" href=\"" obfuscate("mailto:" tg) "\">" obfuscate(a) "</a>"
862 } else {
863 r = r "&lt;"
864 continue
865 }
866 st = substr(st, p + 1)
867 } else if (mp == "&") {
868 if (match(st, /^[#[:alnum:]]+;/)) {
869 r = r "&" substr(st, 1, RLENGTH)
870 st = substr(st, RLENGTH + 1)
871 } else {
872 r = r "&amp;"
873 }
874 }
875 }
876 return (r st)
877}
878
879function strip_tags(st)
880{
881 gsub(/<\/?[^>]+>/, "", st)
882 return st
883}
884
885function tag(t, body, attr)
886{
887 if (attr) {
888 attr = " " trim(attr)
889 # https://www.w3.org/TR/html5/grouping-content.html#the-p-element
890 }
891 if (t == "p" && (match(body, /<\/?(div|table|blockquote|dl|ol|ul|h[[:digit:]]|hr|pre)[>[:space:]]/)) || (match(body, /!\[toc\]/) && substr(body, RSTART - 1, 1) != "\\")) {
892 return ("<" t attr ">" body "\n")
893 } else {
894 return ("<" t attr ">" body "</" t ">\n")
895 }
896}
897
898function trim(st)
899{
900 sub(/^[[:space:]]+/, "", st)
901 sub(/[[:space:]]+$/, "", st)
902 return st
903}
diff --git a/style.css b/style.css new file mode 100644 index 0000000..eb059d9 --- /dev/null +++ b/style.css
@@ -0,0 +1,31 @@
1body {
2 margin: auto;
3 font: 18px/1.4 serif;
4 max-width: 70ch;
5 background-color: papayawhip;
6 color: navy;
7}
8code {
9 background-color: #ffe;
10 color: black;
11}
12pre > code {
13 display: block;
14}
15img {
16 max-width: 100%;
17}
18a:link {
19 text-decoration: none;
20 background: #eef;
21 color: inherit;
22}
23a:visted {
24 color: inherit;
25}
26a:active {
27 border-bottom: 1px solid;
28}
29a:hover {
30 border-bottom: 1px solid;
31}