diff options
author | Case Duckworth | 2022-08-02 09:25:42 -0500 |
---|---|---|
committer | Case Duckworth | 2022-08-02 09:26:59 -0500 |
commit | 0d81f5100640c7f961fe6d6e79a6b0d801b3289b (patch) | |
tree | d5edcc746b7612e8ebebf2ea7407c6eabd53b9f7 | |
download | docawk-main.tar.gz docawk-main.zip |
-rw-r--r-- | README.html | 203 | ||||
-rwxr-xr-x | doc.awk | 213 | ||||
-rw-r--r-- | footer.html | 2 | ||||
-rw-r--r-- | header.html | 4 | ||||
-rwxr-xr-x | htmlsafe.awk | 8 | ||||
-rwxr-xr-x | mdown.awk | 903 | ||||
-rw-r--r-- | style.css | 31 |
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 <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 | ||
7 | inspired by <a class="normal" href="https://ashkenas.com/docco/" title="">docco</a>.</p> | ||
8 | <p>by Case Duckworth <a class="normal" href="mailto:acdw@acdw.net">acdw@acdw.net</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 | ||
11 | of which were inspired by, and also borrowed from, docco. I was particularly | ||
12 | 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> | ||
13 | <p>Notably missing, however, was a converter of some kind written in AWK. Thus, | ||
14 | DOC AWK was born.</p> | ||
15 | <p>This page is the result of DOC AWK working on itself. Not bad for < 250 lines | ||
16 | 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 | ||
17 | git repository</a> to use it yourself.</p> | ||
18 | <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> | ||
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 | ||
22 | set a few variables from the environment, with defaults. I use the | ||
23 | convenience function <code>getenv</code>, further down this script, to make it | ||
24 | easier.</p> | ||
25 | <p>First, the comment regex. This regex detects a comment <em>line</em>, not an | ||
26 | inline comment. By default, it's set up for awk, shell, and other | ||
27 | languages that use <code>#</code> as a comment delimiter, but you can make it | ||
28 | whatever 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 | ||
32 | default 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; | ||
37 | the included <code>htmlsafe.awk</code> simply escapes <, &, and >.</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 | ||
41 | defaults here are the included header.html and footer.html, since the | ||
42 | default output type is html.</p> | ||
43 | <p>Each of these documents are actually <em>templates</em>, with keys that can | ||
44 | expand to variables inside of <code>@@VARIABLE@@</code>. This is mostly | ||
45 | for 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 | ||
51 | to view the filename doesn't work. Thus, I need a state variable to track | ||
52 | whether we've started or not (so that I don't print a header with every new | ||
53 | file).</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 | ||
60 | they'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 | ||
65 | script 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 | ||
67 | source 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; | ||
83 | otherwise, it's in a code block. Accumulate each part in a dedicated buffer, | ||
84 | and 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 | ||
101 | out 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 <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 | ||
116 | little HTML code block. I could maybe have a DOCAWK_CODE_PRE/POST and maybe | ||
117 | even one for text too, to make it more extensible (to other markup languages, | ||
118 | for 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 "<pre><code>" | ||
125 | printf(buf[type]) | CODEPROC | ||
126 | close(CODEPROC) | ||
127 | print "</code></pre>" | ||
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>> 0</code> check here ensures that it | ||
137 | bails on error (-1).</p> | ||
138 | <pre><code>function file_print(file) | ||
139 | { | ||
140 | if (file) { | ||
141 | while ((getline l < file) > 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. | ||
149 | If variables are set, use those; otherwise try to figure out the title from | ||
150 | the 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. | ||
162 | If 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 | ||
164 | DEF.</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. | ||
177 | Currently 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 | ||
179 | array, <code>TV</code>. I'd love it if awk had some kind of <code>eval</code> functionality, but at | ||
180 | least 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 | # ---- | ||
31 | BEGIN { | ||
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. | ||
77 | FNR == 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. | ||
111 | END { | ||
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. | ||
122 | function 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). | ||
131 | function 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). | ||
150 | function 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. | ||
163 | function 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. | ||
177 | function 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. | ||
194 | function 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. | ||
208 | function 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(/&/, "\\&", $0) | ||
4 | gsub(/</, "\\<", $0) | ||
5 | gsub(/>/, "\\>", $0) | ||
6 | |||
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. | ||
12 | BEGIN { | ||
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 | |||
58 | END { | ||
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')?'►':'▼';\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>" | ||
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 | |||
94 | function 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 | |||
112 | function escape(st) | ||
113 | { | ||
114 | gsub(/&/, "\\&", st) | ||
115 | gsub(/</, "\\<", st) | ||
116 | gsub(/>/, "\\>", st) | ||
117 | return st | ||
118 | } | ||
119 | |||
120 | function 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 | |||
340 | function 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 | |||
359 | function 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) "…" | ||
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 "\"> ↶ Back</a></li>\n" | ||
394 | } | ||
395 | return (r st) | ||
396 | } | ||
397 | |||
398 | function 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 | |||
474 | function 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 " " svg "</a>" (TopLinks ? " <a class=\"top\" title=\"Return to top\" href=\"#\">↑ 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 "')\">▼</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 "')\">►</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 | |||
511 | function 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 | |||
635 | function itag(t, body) | ||
636 | { | ||
637 | return ("<" t ">" body "</" t ">") | ||
638 | } | ||
639 | |||
640 | function 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 ? "▼" : "►") "</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>" | ||
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 | |||
667 | function 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 | |||
683 | function pop() | ||
684 | { | ||
685 | Mode = Stack[--StackTop] | ||
686 | Buf = "" | ||
687 | return Mode | ||
688 | } | ||
689 | |||
690 | function 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 | |||
725 | function push(newmode) | ||
726 | { | ||
727 | Stack[StackTop++] = Mode | ||
728 | Mode = newmode | ||
729 | } | ||
730 | |||
731 | function 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 ">" | ||
826 | } else if (mp == "<") { | ||
827 | p = index(st, ">") | ||
828 | if (! p) { | ||
829 | r = r "<" | ||
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 "<" | ||
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 "&" | ||
873 | } | ||
874 | } | ||
875 | } | ||
876 | return (r st) | ||
877 | } | ||
878 | |||
879 | function strip_tags(st) | ||
880 | { | ||
881 | gsub(/<\/?[^>]+>/, "", st) | ||
882 | return st | ||
883 | } | ||
884 | |||
885 | function 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 | |||
898 | function 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 @@ | |||
1 | body { | ||
2 | margin: auto; | ||
3 | font: 18px/1.4 serif; | ||
4 | max-width: 70ch; | ||
5 | background-color: papayawhip; | ||
6 | color: navy; | ||
7 | } | ||
8 | code { | ||
9 | background-color: #ffe; | ||
10 | color: black; | ||
11 | } | ||
12 | pre > code { | ||
13 | display: block; | ||
14 | } | ||
15 | img { | ||
16 | max-width: 100%; | ||
17 | } | ||
18 | a:link { | ||
19 | text-decoration: none; | ||
20 | background: #eef; | ||
21 | color: inherit; | ||
22 | } | ||
23 | a:visted { | ||
24 | color: inherit; | ||
25 | } | ||
26 | a:active { | ||
27 | border-bottom: 1px solid; | ||
28 | } | ||
29 | a:hover { | ||
30 | border-bottom: 1px solid; | ||
31 | } | ||