about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rwxr-xr-xvienna428
1 files changed, 215 insertions, 213 deletions
diff --git a/vienna b/vienna index 8c3c92f..c7b4ffe 100755 --- a/vienna +++ b/vienna
@@ -6,7 +6,7 @@
6### Entry point 6### Entry point
7 7
8usage() { 8usage() {
9 cat <<EOF 9 cat <<EOF
10VIENNA: a tiny, tasty ssg 10VIENNA: a tiny, tasty ssg
11by C. Duckworth <acdw@acdw.net> 11by C. Duckworth <acdw@acdw.net>
12 12
@@ -37,131 +37,132 @@ which by default is ./.vienna.sh. vienna uses heredoc-inspired templating, so
37you can include shell snippets and variables by doubling the dollar signs. 37you can include shell snippets and variables by doubling the dollar signs.
38 38
39EOF 39EOF
40 exit "${1:-0}" 40 exit "${1:-0}"
41} 41}
42 42
43configure() { 43configure() {
44 ## Set up environment 44 ## Set up environment
45 DOMAIN="${VIENNA_DOMAIN:-https://www.example.com}" 45 DOMAIN="${VIENNA_DOMAIN:-https://www.example.com}"
46 TMPD="${VIENNA_TMPD:-/tmp/vienna}" 46 TMPD="${VIENNA_TMPD:-/tmp/vienna}"
47 WORKD="${VIENNA_WORKD:-$PWD}" 47 WORKD="${VIENNA_WORKD:-$PWD}"
48 OUTD="${VIENNA_OUTD:-out}" 48 OUTD="${VIENNA_OUTD:-out}"
49 CONFIG="${VIENNA_CONFIG:-./.vienna.sh}" 49 CONFIG="${VIENNA_CONFIG:-./.vienna.sh}"
50 # Templates 50 # Templates
51 PAGE_TEMPLATE="${VIENNA_PAGE_TEMPLATE:-.page.tmpl.html}" 51 PAGE_TEMPLATE="${VIENNA_PAGE_TEMPLATE:-.page.tmpl.html}"
52 INDEX_TEMPLATE="${VIENNA_INDEX_TEMPLATE:-.index.tmpl.html}" 52 INDEX_TEMPLATE="${VIENNA_INDEX_TEMPLATE:-.index.tmpl.html}"
53 FEED_TEMPLATE="${VIENNA_FEED_TEMPLATE:-.feed.tmpl.xml}" 53 FEED_TEMPLATE="${VIENNA_FEED_TEMPLATE:-.feed.tmpl.xml}"
54 # File extensions 54 # File extensions
55 PAGE_RAW_EXT="${VIENNA_PAGE_RAW_EXT:-htm}" 55 PAGE_RAW_EXT="${VIENNA_PAGE_RAW_EXT:-htm}"
56 # Logging 56 # Logging
57 LOG=true 57 LOG=true
58 ## Parse command line arguments 58 ## Parse command line arguments
59 while getopts C:c:d:ho:q opt; do 59 while getopts C:c:d:ho:q opt; do
60 case "$opt" in 60 case "$opt" in
61 C) WORKD="$OPTARG" ;; 61 C) WORKD="$OPTARG" ;;
62 c) 62 c)
63 CONFIG="$OPTARG" 63 CONFIG="$OPTARG"
64 # To error later if a config is specified on the command 64 # To error later if a config is specified on the command
65 # line but doesn't exist. 65 # line but doesn't exist.
66 CONFIG_ARG=1 66 CONFIG_ARG=1
67 ;; 67 ;;
68 d) DOMAIN="$OPTARG" ;; 68 d) DOMAIN="$OPTARG" ;;
69 h) usage 0 ;; 69 h) usage 0 ;;
70 o) OUTD="$OPTARG" ;; 70 o) OUTD="$OPTARG" ;;
71 q) LOG=false ;; 71 q) LOG=false ;;
72 *) exit 1 ;; 72 *) exit 1 ;;
73 esac 73 esac
74 done 74 done
75 ## Log configuration variables 75 ## Log configuration variables
76 log config "domain: $DOMAIN" 76 log config "domain: $DOMAIN"
77 log config "workdir: $WORKD" 77 log config "workdir: $WORKD"
78 log config "output: $OUTD" 78 log config "output: $OUTD"
79 ## Initialize state 79 ## Initialize state
80 FILE= 80 FILE=
81 ## Cleanup after we're done 81 ## Cleanup after we're done
82 trap cleanup INT QUIT 82 trap cleanup INT QUIT
83} 83}
84 84
85main() { 85main() {
86 # State predicates 86 # State predicates
87 alias pagep=false indexp=false feedp=false 87 alias pagep=false indexp=false feedp=false
88 # Convenience aliases 88 # Convenience aliases
89 alias body=cat title='meta title' pubdate='meta date' 89 alias body=cat title='meta title' pubdate='meta date'
90 # Configure 90 # Configure
91 configure "$@" 91 configure "$@"
92 shift "$((OPTIND - 1))" 92 shift "$((OPTIND - 1))"
93 # Further argument processing --- pre-build 93 # Further argument processing --- pre-build
94 preprocess "$@" 94 preprocess "$@"
95 # Prepare 95 # Prepare
96 cd "$WORKD" || exit 2 96 cd "$WORKD" || exit 2
97 if test -f "$CONFIG"; then 97 if test -f "$CONFIG"; then
98 # Source ./.vienna.sh, if it exists. 98 # Source ./.vienna.sh, if it exists.
99 . "$CONFIG" 99 . "$CONFIG"
100 elif test -n "$CONFIG_ARG"; then 100 elif test -n "$CONFIG_ARG"; then
101 # If a -c option was passed on the command line but the file 101 # If a -c option was passed on the command line but the file
102 # doesn't exist, that's an error. If we're just looking for the 102 # doesn't exist, that's an error. If we're just looking for the
103 # default file, however, there is no error---the user might want 103 # default file, however, there is no error---the user might want
104 # to use the default configuration. 104 # to use the default configuration.
105 log "Can't find configuration \`$CONFIG'." 105 log "Can't find configuration \`$CONFIG'."
106 exit 2 106 exit 2
107 fi 107 fi
108 mkdir -p "$OUTD" || exit 2 108 mkdir -p "$OUTD" || exit 2
109 mkdir -p "$TMPD" || exit 2 109 mkdir -p "$TMPD" || exit 2
110 # Build pages 110 # Build pages
111 alias pagep=true 111 alias pagep=true
112 genpage *."$PAGE_RAW_EXT" || exit 2 112 genpage *."$PAGE_RAW_EXT" || exit 2
113 alias pagep=false 113 alias pagep=false
114 # Build index 114 # Build index
115 alias indexp=true 115 alias indexp=true
116 genlist index_item "$INDEX_TEMPLATE" *."$PAGE_RAW_EXT" >"$OUTD/index.html" || exit 2 116 genlist index_item "$INDEX_TEMPLATE" *."$PAGE_RAW_EXT" >"$OUTD/index.html" || exit 2
117 alias indexp=false 117 alias indexp=false
118 # Build feed 118 # Build feed
119 alias feedp=true 119 alias feedp=true
120 genlist feed_item "$FEED_TEMPLATE" *."$PAGE_RAW_EXT" >"$OUTD/feed.xml" || exit 2 120 genlist feed_item "$FEED_TEMPLATE" *."$PAGE_RAW_EXT" >"$OUTD/feed.xml" || exit 2
121 alias feedp=false 121 alias feedp=false
122 # Copy static files 122 # Copy static files
123 static * || exit 2 123 static * || exit 2
124 # Further argument processing --- post-build 124 # Further argument processing --- post-build
125 postprocess "$@" 125 postprocess "$@"
126} 126}
127 127
128preprocess() { 128preprocess() {
129 case "${1:-ok}" in 129 case "${1:-ok}" in
130 ok) ;; 130 ok) ;;
131 clean) 131 clean)
132 log vienna "clean" 132 log vienna "clean"
133 rm -r "$OUTD" 133 rm -r "$OUTD"
134 exit 134 cleanup
135 ;; 135 exit
136 esac 136 ;;
137 esac
137} 138}
138 139
139postprocess() { 140postprocess() {
140 case "${1:-ok}" in 141 case "${1:-ok}" in
141 ok) ;; 142 ok) ;;
142 publish) 143 publish)
143 log vienna "publish" 144 log vienna "publish"
144 publish "$OUTD" 145 publish "$OUTD"
145 ;; 146 ;;
146 preview) 147 preview)
147 log vienna "preview" 148 log vienna "preview"
148 preview "$OUTD" 149 preview "$OUTD"
149 ;; 150 ;;
150 *) 151 *)
151 log vienna "Don't know command \`$1'." 152 log vienna "Don't know command \`$1'."
152 exit 1 153 exit 1
153 ;; 154 ;;
154 esac 155 esac
155} 156}
156 157
157cleanup() { 158cleanup() {
158 test -z "$DEBUG" && 159 test -z "$DEBUG" &&
159 test -z "$NODEP_RM" && 160 test -z "$NODEP_RM" &&
160 rm -r "$TMPD" 161 rm -r "$TMPD"
161} 162}
162 163
163publish() { 164publish() {
164 cat <<EOF >&2 165 cat <<EOF >&2
165 166
166I want to publish your website but I don't know how. 167I want to publish your website but I don't know how.
167Make a $CONFIG file in this directory and write a \`publish' 168Make a $CONFIG file in this directory and write a \`publish'
@@ -169,11 +170,11 @@ function telling me what to do. I recommend using \`rsync',
169but you live your life." 170but you live your life."
170 171
171EOF 172EOF
172 exit 3 173 exit 3
173} 174}
174 175
175preview() { 176preview() {
176 cat <<EOF >&2 177 cat <<EOF >&2
177 178
178I want to show you a preview of your website but I don't 179I want to show you a preview of your website but I don't
179know how. Make a $CONFIG file in this directory and write 180know how. Make a $CONFIG file in this directory and write
@@ -182,17 +183,17 @@ using something like \`python -m http.server', but you live
182your life." 183your life."
183 184
184EOF 185EOF
185 exit 3 186 exit 3
186} 187}
187 188
188### Utility 189### Utility
189 190
190log() { 191log() {
191 if "$LOG"; then 192 if "$LOG"; then
192 t="$1" 193 t="$1"
193 shift 194 shift
194 echo >&2 "[$t]" "$@" 195 echo >&2 "[$t]" "$@"
195 fi 196 fi
196} 197}
197 198
198### File processing 199### File processing
@@ -200,140 +201,141 @@ log() {
200## Building block functions 201## Building block functions
201 202
202shellfix() { # shellfix FILE... 203shellfix() { # shellfix FILE...
203 ## Replace ` with \`, $ with \$, and $$ with $ 204 ## Replace ` with \`, $ with \$, and $$ with $
204 # shellcheck disable=2016 205 # shellcheck disable=2016
205 sed -E \ 206 sed -E \
206 -e 's/`/\\`/g' \ 207 -e 's/`/\\`/g' \
207 -e 's/(^|[^\$])\$([^\$]|$)/\1\\$\2/g' \ 208 -e 's/(^|[^\$])\$([^\$]|$)/\1\\$\2/g' \
208 -e 's/\$\$/$/g' \ 209 -e 's/\$\$/$/g' \
209 "$@" 210 "$@"
210} 211}
211 212
212expand() { # expand TEMPLATE... < INPUT 213expand() { # expand TEMPLATE... < INPUT
213 ## Print TEMPLATE to stdout, expanding shell constructs. 214 ## Print TEMPLATE to stdout, expanding shell constructs.
214 end="expand_:_${count:=0}_:_end" 215 end="expand_:_${count:=0}_:_end"
215 eval "$( 216 eval "$(
216 echo "cat<<$end" 217 echo "cat<<$end"
217 shellfix "$@" 218 shellfix "$@"
218 echo 219 echo
219 echo "$end" 220 echo "$end"
220 )" && count=$((count + 1)) 221 )" && count=$((count + 1))
221} 222}
222 223
223phtml() { # phtml < INPUT 224phtml() { # phtml < INPUT
224 ## Output HTML, pretty much. 225 ## Output HTML, pretty much.
225 # Paragraphs unadorned with html tags will be wrapped in <p> tags, and 226 # Paragraphs unadorned with html tags will be wrapped in <p> tags, and
226 # &, <, > will be escaped unless prepended with \. Paragraphs where the 227 # &, <, > will be escaped unless prepended with \. Paragraphs where the
227 # first character is < will be left as-is, excepting indentation on the 228 # first character is < will be left as-is, excepting indentation on the
228 # first line (an implementation detail). 229 # first line (an implementation detail).
229 sed -E \ 230 sed -E \
230 '/./{H;1h;$!d;}; x; 231 '/./{H;1h;$!d;}; x;
231 s#^[ \n\t]+[^<].*#&#; 232 s#^[ \n\t]+[^<].*#&#;
232 t par; b; 233 t par; b;
233 :par; 234 :par;
234 s#([^\\])&#\1\&amp;#g; s#\\&#\&#g; 235 s#([^\\])&#\1\&amp;#g; s#\\&#\&#g;
235 s#([^\\])<#\1\&lt;#g; s#\\<#<#g; 236 s#([^\\])<#\1\&lt;#g; s#\\<#<#g;
236 s#([^\\])>#\1\&gt;#g; s#\\>#>#g;' 237 s#([^\\])>#\1\&gt;#g; s#\\>#>#g;'
237} 238}
238 239
239meta() { # meta FIELD [FILE] < INPUT 240meta() { # meta FIELD [FILE] < INPUT
240 ## Extract metadata FIELDS from INPUT. 241 ## Extract metadata FIELDS from INPUT.
241 # FILE gives the filename to save metadata to in the $WORKD. It 242 # FILE gives the filename to save metadata to in the $WORKD. It
242 # defaults to the current value for $FILE. 243 # defaults to the current value for $FILE.
243 # 244 #
244 # Metadata should exist as colon-separated data in an HTML comment at 245 # Metadata should exist as colon-separated data in an HTML comment at
245 # the beginning of an input file. 246 # the beginning of an input file.
246 field="$1" 247 field="$1"
247 file="${2:-$FILE}" 248 file="${2:-$FILE}"
248 metafile="$TMPD/${file}.meta" 249 metafile="${TMPD:=.}/${file}.meta"
249 test -f "$metafile" || 250 test -f "$metafile" ||
250 sed '/<!--/n;/-->/q' >"$metafile" 251 sed '/<!--/!q;/<!--/n;/-->/q' >"$metafile"
251 sed -n "s/^[ \t]*$field:[ \t]*//p" <"$metafile" 252 sed -n "s/^[ \t]*$field:[ \t]*//p" <"$metafile"
252} 253}
253 254
254## Customizable bits 255## Customizable bits
255 256
256filters() { # filters < INPUT 257filters() { # filters < INPUT
257 ## The filters to run input through. 258 ## The filters to run input through.
258 # This is a good candidate for customization in .vienna.sh. 259 # This is a good candidate for customization in .vienna.sh.
259 expand | phtml 260 expand | phtml
260} 261}
261 262
262### Site building 263### Site building
263 264
264genpage() { # genpage PAGE... 265genpage() { # genpage PAGE...
265 ## Compile PAGE(s) into $OUTD for publication. 266 ## Compile PAGE(s) into $OUTD for publication.
266 # Outputs a file of the format $OUTD/<PAGE>/index.html. 267 # Outputs a file of the format $OUTD/<PAGE>/index.html.
267 test -f "$PAGE_TEMPLATE" || return 1 268 test -f "$PAGE_TEMPLATE" || return 1
268 for FILE; do 269 for FILE; do
269 log genpage "$FILE" 270 log genpage "$FILE"
270 outd="$OUTD/${FILE%.$PAGE_RAW_EXT}" 271 outd="$OUTD/${FILE%.$PAGE_RAW_EXT}"
271 outf="$outd/index.html" 272 outf="$outd/index.html"
272 tmpf="$TMPD/$FILE.tmp" 273 tmpf="$TMPD/$FILE.tmp"
273 mkdir -p "$outd" 274 mkdir -p "$outd"
274 filters <"$FILE" >"$tmpf" 275 filters <"$FILE" >"$tmpf"
275 expand "$PAGE_TEMPLATE" <"$tmpf" >"$outf" 276 expand "$PAGE_TEMPLATE" <"$tmpf" >"$outf"
276 done 277 done
277} 278}
278 279
279genlist() { # genlist PERITEM_FUNC TEMPLATE_FILE PAGE... 280genlist() { # genlist PERITEM_FUNC TEMPLATE_FILE PAGE...
280 peritem_func="$1" 281 peritem_func="$1"
281 template_file="$2" 282 template_file="$2"
282 shift 2 || return 2 283 shift 2 || return 2
283 test -f "$template_file" || return 1 284 test -f "$template_file" || return 1
284 for FILE; do 285 for FILE; do
285 log genlist "$peritem_func/$template_file: $FILE" 286 log genlist "$peritem_func: $template_file: $FILE"
286 LINK="$DOMAIN${DOMAIN:+/}${1%.PAGE_RAW_EXT}" 287
287 done | expand "$template_file" 288 LINK="$DOMAIN${DOMAIN:+/}${1%.PAGE_RAW_EXT}"
289 done | expand "$template_file"
288} 290}
289 291
290index_item() { # index_item PAGE 292index_item() { # index_item PAGE
291 ## Construct a single item in an index.html. 293 ## Construct a single item in an index.html.
292 echo "<li><a href=\"$LINK\">$(meta title "$1")</a></li>" 294 echo "<li><a href=\"$LINK\">$(meta title "$1")</a></li>"
293} 295}
294 296
295feed_item() { # feed_item PAGE 297feed_item() { # feed_item PAGE
296 ## Construct a single item in an RSS feed. 298 ## Construct a single item in an RSS feed.
297 date="$(pubdate "$1")" 299 date="$(pubdate "$1")"
298 echo "<item>" 300 echo "<item>"
299 echo "<title>$(meta title "$1")</title>" 301 echo "<title>$(meta title "$1")</title>"
300 echo "<link>$LINK</link>" 302 echo "<link>$LINK</link>"
301 echo "<guid>$LINK</guid>" 303 echo "<guid>$LINK</guid>"
302 test -n "$date" && echo "<pubDate>$date</pubDate>" 304 test -n "$date" && echo "<pubDate>$date</pubDate>"
303 echo "</item>" 305 echo "</item>"
304} 306}
305 307
306index() { # index PAGE... 308index() { # index PAGE...
307 ## Build a site index from all PAGE(s) passed to it. 309 ## Build a site index from all PAGE(s) passed to it.
308 # Wraps each PAGE in a <li><a> structure. 310 # Wraps each PAGE in a <li><a> structure.
309 test -f "$INDEX_TEMPLATE" || return 1 311 test -f "$INDEX_TEMPLATE" || return 1
310 for FILE; do 312 for FILE; do
311 log index "$FILE" 313 log index "$FILE"
312 index_item "$FILE" 314 index_item "$FILE"
313 done | expand "$INDEX_TEMPLATE" >"$OUTD/index.html" 315 done | expand "$INDEX_TEMPLATE" >"$OUTD/index.html"
314} 316}
315 317
316feed() { # feed PAGE... 318feed() { # feed PAGE...
317 ## Build an RSS 2.0 feed from PAGE(s). 319 ## Build an RSS 2.0 feed from PAGE(s).
318 test -f "$FEED_TEMPLATE" || return 1 320 test -f "$FEED_TEMPLATE" || return 1
319 for FILE; do 321 for FILE; do
320 log feed "$FILE" 322 log feed "$FILE"
321 feed_item "$FILE" 323 feed_item "$FILE"
322 done | expand "$FEED_TEMPLATE" >"$OUTD/feed.xml" 324 done | expand "$FEED_TEMPLATE" >"$OUTD/feed.xml"
323} 325}
324 326
325static() { # static FILE... 327static() { # static FILE...
326 ## Copy static FILE(s) to $OUTD as-is. 328 ## Copy static FILE(s) to $OUTD as-is.
327 # Performs a simple heuristic to determine whether to copy a file or 329 # Performs a simple heuristic to determine whether to copy a file or
328 # not. 330 # not.
329 for FILE; do 331 for FILE; do
330 case "$FILE" in 332 case "$FILE" in
331 .*) continue ;; 333 .*) continue ;;
332 *.htm) continue ;; 334 *.htm) continue ;;
333 "$OUTD") continue ;; 335 "$OUTD") continue ;;
334 *) cp -r "$FILE" "$OUTD/" ;; 336 *) cp -r "$FILE" "$OUTD/" ;;
335 esac 337 esac
336 done 338 done
337} 339}
338 340
339### Do the thing! 341### Do the thing!