diff options
author | Case Duckworth | 2024-02-01 23:52:57 -0600 |
---|---|---|
committer | Case Duckworth | 2024-02-01 23:52:57 -0600 |
commit | 54e07f822b463c8d461fef254213f83c59d82810 (patch) | |
tree | f5f8d7e6375653113b6aaef2e12905b4a7d26334 /subtext.awk | |
parent | uhh (diff) | |
download | subtext-54e07f822b463c8d461fef254213f83c59d82810.tar.gz subtext-54e07f822b463c8d461fef254213f83c59d82810.zip |
Refactor for interleaved shell commands and text
Diffstat (limited to 'subtext.awk')
-rw-r--r-- | subtext.awk | 230 |
1 files changed, 84 insertions, 146 deletions
diff --git a/subtext.awk b/subtext.awk index ee04238..c45aae4 100644 --- a/subtext.awk +++ b/subtext.awk | |||
@@ -1,194 +1,132 @@ | |||
1 | # subtext -- text substitutor -*- awk -*- | 1 | # subtext -*- awk -*- |
2 | # (C) C Duckworth <acdw@acdw.net> | 2 | # (C) C Duckworth <acdw@acdw.net> |
3 | ## Subtext is an awk program that converts roff-like input to a shell script | ||
4 | ## that can output markup. Lines prefixed with . are wrapped in $(...) | ||
5 | ## constructs within the `body' function, but lines prefixed with # are | ||
6 | ## transported to the top of the file, where they're executed within the shell | ||
7 | ## environment. | ||
3 | 8 | ||
4 | BEGIN { | 9 | BEGIN { |
5 | true = 1; false = 0 | ||
6 | ## Tuneables | 10 | ## Tuneables |
7 | runbody = bool(runbody ? runbody : true) | 11 | dryrun = dryrun ? dryrun : 0 |
8 | postproc = postproc ? postproc : "shexpand" | 12 | sopath = sopath ? sopath : ".:" ENVIRON["HOME"] "/.subtext" |
9 | bodyfunc = bodyfunc ? bodyfunc : "body" | 13 | bodyend = bodyend ? bodyend : "_@end@_" |
10 | sopath = get_value(sopath, ".:"ENVIRON["HOME"]"/.subtext") | 14 | ## Kernel |
11 | ## Globals | ||
12 | ## Wrap the text in a function | ||
13 | pretext = "### begin text\n"bodyfunc"(){\n" | ||
14 | posttext = "}\n### end text" | ||
15 | ## Prelude function strings | ||
16 | # Ask sed to do these b/c awk has no capture groups ;_; | 15 | # Ask sed to do these b/c awk has no capture groups ;_; |
17 | shellfix = "sed -E" \ | 16 | shellfix = "sed -E" \ |
18 | " -e 's/`/\\\\`/g'" \ | 17 | " -e 's/`/\\\\`/g'" \ |
19 | " -e 's/(^|[^\\$])\\$([^\\$]|$)/\\1\\\\$\\2/g'" \ | 18 | " -e 's/(^|[^\\$])\\$([^\\$]|$)/\\1\\\\$\\2/g'" \ |
20 | " -e 's/(^|[^\\$])\\$(\\$+)([^\\$]|$)/\\1\\2\\3/g'" | 19 | " -e 's/(^|[^\\$])\\$(\\$+)([^\\$]|$)/\\1\\2\\3/g'" |
21 | shxwrap = " -e 's/^/:/'" | 20 | shxwrap = " -e 's/^/:/'" |
22 | htmlfix = "sed -E" \ | 21 | kernel = "quote()(sed \"s/^/$1/\")\n" \ |
23 | " -e 's#([^\\\\]|^)&#\\1\\&#g'" \ | 22 | "unquote()(sed \"s/^$1//\")\n" \ |
24 | " -e 's#([^\\\\]|^)<#\\1\\<#g'" \ | 23 | "shexpand()(eval " \ |
25 | " -e 's#([^\\\\]|^)>#\\1\\>#g'" \ | 24 | "\"$(echo 'cat<<" bodyend "';cat;echo " bodyend ")\")\n" \ |
26 | " -e 's#\\\\([&<>])#\\1#g'"; | ||
27 | ## Prelude | ||
28 | par = "#!/bin/sh\n### kernel\n" \ | ||
29 | "preface()(sed \"s/^/$1/\")\n" \ | ||
30 | "unpreface()(sed \"s/^$1//\")\n" \ | ||
31 | "shexpand()(eval \"$( (echo 'cat<<.';preface +;echo .)" \ | ||
32 | " | unpreface + )\")\n" \ | ||
33 | "### library\n" \ | ||
34 | "shellfix()(" shellfix ")\n" \ | 25 | "shellfix()(" shellfix ")\n" \ |
35 | "htmlfix()(" htmlfix ")\n" \ | 26 | "ST_SOPATH=" sopath |
36 | "### variables\n" \ | ||
37 | "ST_POSTPROC=" postproc "\n" \ | ||
38 | "ST_BODYFUNC=" bodyfunc "\n" \ | ||
39 | "ST_SOPATH=" sopath "\n" \ | ||
40 | "### header\n" | ||
41 | } | 27 | } |
42 | 28 | ||
43 | ### End a block | 29 | /\\$/ { # line continuation |
44 | |||
45 | end[endn] && $0 == end[endn] { | ||
46 | pushpar(end[endn--] "\n)", true) | ||
47 | printpar() | ||
48 | subdocp = false | ||
49 | next | ||
50 | } | ||
51 | |||
52 | ### Line continuation | ||
53 | |||
54 | /\\$/ { | ||
55 | getline nl | 30 | getline nl |
56 | sub(/\\$/,"") | 31 | sub(/\\$/, "") |
57 | $0 = $0 " " nl | 32 | $0 = $0 " " nl |
58 | } | 33 | } |
59 | 34 | ||
60 | ### Special commands | 35 | /^#so/ { # source a file |
61 | ## These call subtext-internal functions | 36 | pushpar() |
62 | |||
63 | /^#so/ { # Insert $2 verbatim (if in sopath), else error | ||
64 | pushpar($0) | ||
65 | source($2) | 37 | source($2) |
66 | next | 38 | next |
67 | } | 39 | } |
68 | 40 | ||
69 | /^###$/ { # Delimit document | 41 | /^#/ { # head lines |
70 | printpar() | 42 | sub(/^#+[ ]*/,"") |
71 | docp = !docp | 43 | head = head (head?"\n":"") $0 |
72 | if (docp) { | ||
73 | print pretext "unpreface ':'<<'_'|eval \"$ST_POSTPROC\"" | ||
74 | } else { | ||
75 | end_text() | ||
76 | } | ||
77 | next | 44 | next |
78 | } | 45 | } |
79 | 46 | ||
80 | ### Escape sequences | 47 | /^\.\./ { # block |
81 | 48 | body = body (body?"\n":"") \ | |
82 | /^\.\./ && docp { # Begin a heredoc | 49 | "$$(unquote + << .." \ |
83 | # ..[<command>] [<options>] [<<delim] | 50 | (length>2 ? " | " substr($1,3) " " quote(2) : "") \ |
84 | ## ends with DELIM or '..' | 51 | slurp("..") \ |
85 | subdocp = true | 52 | "..\n)" |
86 | end[++endn] = (match($0, "<<") ? substr($0, RSTART + RLENGTH) : "..") | 53 | next |
87 | command = substr($0, 3, RSTART ? RSTART - 2 : length($0)) | ||
88 | $0 = "$$(" (command ? command : "cat") "<<" end[endn] | ||
89 | } | 54 | } |
90 | 55 | ||
91 | /^\./ && docp { # One-line command | 56 | /^\./ { # line |
92 | # .<command> [<parameters>] | 57 | body = body (body?"\n":"") \ |
93 | ## wraps the line in $( ... ), basically (also quotes) | 58 | "$$(" substr($1, 2) " " quote(2) ")" |
94 | specialp = 2 | 59 | next |
95 | gsub(/"/, "\\\\&", $0) | ||
96 | ln = "$$(" substr($1, 2) | ||
97 | for (f=2; f<=NF; f++) { | ||
98 | ln = ln " \"" $f "\"" | ||
99 | } | ||
100 | ln = ln ")" | ||
101 | $0 = ln | ||
102 | } | 60 | } |
103 | 61 | ||
104 | /^\\/ && docp { # \ at the beginning of a line escapes the next character | 62 | /^$/ { # line break |
105 | $0 = substr($0, 2) | 63 | if (!pushpar()) next |
106 | } | 64 | } |
107 | 65 | ||
108 | ### Book-keeping | 66 | { # regular text |
109 | 67 | par = par (par?"\n":"") $0 | |
110 | /^$/ { | ||
111 | if (!par) | ||
112 | next | ||
113 | printpar() | ||
114 | } | ||
115 | |||
116 | { | ||
117 | pushpar($0) | ||
118 | if (--specialp < 0) | ||
119 | specialp = 0 | ||
120 | } | 68 | } |
121 | 69 | ||
122 | END { | 70 | END { |
123 | if (dead) | 71 | if (dead) exit dead |
124 | exit dead | 72 | |
125 | if (par) | 73 | print "#!/bin/sh" |
126 | printpar() | 74 | print "### generated with subtext" |
127 | while (endn > 0) | 75 | print kernel |
128 | print "\n" end[endn--] "\n)" | 76 | print "### head" |
129 | if (docp) | 77 | print head |
130 | end_text() | 78 | print "### body" |
131 | if (runbody) | 79 | print "body(){ shexpand << \\_" |
132 | print bodyfunc | 80 | pushpar() |
133 | } | 81 | print body | shellfix |
134 | 82 | close(shellfix) | |
135 | function end_text() { | 83 | print "_" |
136 | print "_\n" posttext | 84 | print "}" |
85 | if (!dryrun) print "body" | ||
86 | } | ||
87 | |||
88 | function quote(begin, end, out) { | ||
89 | if (!begin) begin = 1 | ||
90 | if (!end) end = NF | ||
91 | for (i=begin; i<=NF; i++) { | ||
92 | gsub(/"/, "\\\"", $i) | ||
93 | out = out (out?" ":"") "\"" $i "\"" | ||
94 | } | ||
95 | return out | ||
137 | } | 96 | } |
138 | 97 | ||
139 | function pushpar(text, force_newline) { | 98 | function slurp(to, out, nl) { |
140 | par = par ((par || force_newline) ? "\n" : "") text | 99 | while (nl != to) { |
100 | getline nl | ||
101 | out = out "\n+" nl | ||
102 | } | ||
103 | sub("\\+"to"$", "", out) | ||
104 | return out | ||
141 | } | 105 | } |
142 | 106 | ||
143 | function printpar() { | 107 | function pushpar() { |
144 | specialp = specialp || (match(par, /^[ ]*</)) | 108 | if (!par) return 0 |
145 | if (docp) { | 109 | if (!match(par, /^[ ]*</)) |
146 | if (!subdocp && !specialp) | 110 | par = "<p>" par "</p>" |
147 | par = "<p>" par "</p>" | 111 | body = body (body?"\n":"") par |
148 | shx = shellfix shxwrap | ||
149 | print par | shx | ||
150 | close(shx) | ||
151 | } else | ||
152 | print par | ||
153 | par = "" | 112 | par = "" |
113 | return 1 | ||
154 | } | 114 | } |
155 | 115 | ||
156 | function get_value(var, env_var, default) { | 116 | function source(name, found, sp) { |
157 | if (var) | ||
158 | return var | ||
159 | else | ||
160 | return default | ||
161 | } | ||
162 | |||
163 | function bool(var, default) { | ||
164 | if (var=="false" || var=="no" || var=="0") | ||
165 | return false | ||
166 | else if (var=="true" || var=="yes" || var=="1") | ||
167 | return true | ||
168 | else | ||
169 | return var || default | ||
170 | } | ||
171 | |||
172 | function source(name) { | ||
173 | found = false | ||
174 | sp = "" | ||
175 | split(sopath, asopath, ":") | 117 | split(sopath, asopath, ":") |
176 | for (dir in asopath) { | 118 | for (dir in asopath) { |
177 | fn = asopath[dir] "/" name | 119 | fn = asopath[dir] "/" name |
178 | sp = asopath[dir] "\n\t" sp | 120 | sp = asopath[dir] "\n\t" sp |
179 | while ((getline ln < fn) > 0) { | 121 | while ((getline ln < fn) > 0) { |
180 | found = true | 122 | found = 1 |
181 | pushpar(ln) | 123 | head = head (head?"\n":"") ln |
182 | } | 124 | } |
183 | if (found) | 125 | if (found) break |
184 | break | 126 | } |
127 | if (!found) { | ||
128 | printf "Couldn't source %s; looked in:\n\t%s", name, sp | ||
129 | dead = 127 | ||
130 | exit | ||
185 | } | 131 | } |
186 | if (!found) | ||
187 | die("Couldn't source " name "; looked in:\n\t" sp, 9) | ||
188 | } | ||
189 | |||
190 | function die(message, code) { | ||
191 | dead = code | ||
192 | print "!!" FILENAME ":" NR ": " message > "/dev/stderr" | ||
193 | exit | ||
194 | } | 132 | } |