about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--readme.md20
-rw-r--r--st.sh47
-rw-r--r--subtext.awk230
-rw-r--r--test2.st23
4 files changed, 174 insertions, 146 deletions
diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..46c5943 --- /dev/null +++ b/readme.md
@@ -0,0 +1,20 @@
1# subtext
2## a layered roffish markup .. thing
3
4subtext is a way to write text using shell templating and roff-like aesthetics for fun. confused? I don't know how to describe it really. just look at the syntax, i guess.
5
6## syntax
7
8subtext's syntax is inspired by roff mostly, with some other line-oriented markups in there as well. its semantics are mostly inspired by this one program i read about reading files as shell here-docs. i kinda combined those ideas together to make an awk script that generates a shell script that you can execute to generate markup. i will *eventually* have html and gemtext macrofiles in this repo, so you can generate html and gemtext from the same source file.
9
10### text lines
11
12### shell substitution
13
14### dotted lines
15
16### hashed lines
17
18## what's a macrofile?
19
20note: i should probably change this name
diff --git a/st.sh b/st.sh new file mode 100644 index 0000000..8274526 --- /dev/null +++ b/st.sh
@@ -0,0 +1,47 @@
1### st.sh
2
3# utilities
4echo()(printf '%s\n' "$*")
5
6handle_input() { # handle_input "$@"
7 # handle input --- args or stdin
8 test -n "$1" && printf '%s' "$*"
9 if read -r first_line
10 then echo "$first_line"; cat
11 else echo
12 fi
13}
14
15# authoring commands
16nb() { # comment mechanism
17 :
18}
19
20# html
21attr(){ # attr [NAME|VALUE] ATTR_STRING
22 case "$1" in
23 (name) echo "${2%%=*}" ;;
24 (value) echo "${2#*=}" ;;
25 esac
26}
27
28html_el(){ # el TAG [ATTRS...] [TEXT...] [< INPUT]
29 tag="$1"; attrs= ; text=
30 shift
31 for arg
32 do
33 case "$arg" in
34 (*=*) attrs="$attrs ${arg%%=*}=${arg#*=}"; shift ;;
35 (*) break ;;
36 esac
37 done
38
39 printf '<%s%s>%s' "$tag" "$attrs"
40 handle_input "$@" | sed '$s,$,</'"$tag"'>,'
41}
42
43alias p='html_el p'
44alias a='html_el a'
45alias h1='html_el h1'
46alias blockquote='html_el blockquote'
47alias bq=blockquote
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
4BEGIN { 9BEGIN {
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\\&amp;#g'" \ 22 "unquote()(sed \"s/^$1//\")\n" \
24 " -e 's#([^\\\\]|^)<#\\1\\&lt;#g'" \ 23 "shexpand()(eval " \
25 " -e 's#([^\\\\]|^)>#\\1\\&gt;#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
45end[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
122END { 70END {
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)
135function end_text() { 83 print "_"
136 print "_\n" posttext 84 print "}"
85 if (!dryrun) print "body"
86}
87
88function 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
139function pushpar(text, force_newline) { 98function 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
143function printpar() { 107function 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
156function get_value(var, env_var, default) { 116function source(name, found, sp) {
157 if (var)
158 return var
159 else
160 return default
161}
162
163function 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
172function 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
190function die(message, code) {
191 dead = code
192 print "!!" FILENAME ":" NR ": " message > "/dev/stderr"
193 exit
194} 132}
diff --git a/test2.st b/test2.st new file mode 100644 index 0000000..02cd2f9 --- /dev/null +++ b/test2.st
@@ -0,0 +1,23 @@
1#so st.sh
2#alias verse='sed "s/^/| /"'
3here is a document.. it's a document, yep
4
5.h1 here's a header
6
7..verse
8here's a poem
9or something
10
11i don't know
12..
13
14#echo and oh yeah, a dang shell thing
15#title=test
16
17here's more paragraphs.
18it's a trip, right?
19meh, whatever.
20
21<blockquote>
22don't forget regular html
23</blockquote> \ No newline at end of file