about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rwxr-xr-xvienna210
1 files changed, 210 insertions, 0 deletions
diff --git a/vienna b/vienna new file mode 100755 index 0000000..f7daabc --- /dev/null +++ b/vienna
@@ -0,0 +1,210 @@
1#!/bin/sh
2# vienna --- a tiny, tasty ssg
3# by C. Duckworth <acdw@acdw.net>
4
5### Entry point
6
7configure() { # configure ARG...
8 ## Set up environment
9 DOMAIN="${VIENNA_DOMAIN:-https://www.example.com}"
10 TMPD="${VIENNA_TMPD:-/tmp/vienna}"
11 WORKD="${VIENNA_WORKD:-$PWD}"
12 OUTD="${VIENNA_OUTD:-out}"
13 CONFIG="${VIENNA_CONFIG:-./.vienna.sh}"
14 # Templates
15 PAGE_TEMPLATE="${VIENNA_PAGE_TEMPLATE:-.page.tmpl.html}"
16 INDEX_TEMPLATE="${VIENNA_INDEX_TEMPLATE:-.index.tmpl.html}"
17 FEED_TEMPLATE="${VIENNA_FEED_TEMPLATE:-.feed.tmpl.xml}"
18 # File extensions
19 PAGE_RAW_EXT="${VIENNA_PAGE_RAW_EXT:-htm}"
20 ## Source ./.vienna.sh, if it exists.
21 # Sourcing it here lets it override any environment variables, and it to
22 # be overridden by command-line flags.
23 test -f "$CONFIG" && . "$CONFIG"
24 ## Parse command line arguments
25 while getopts d:C:o: opt; do
26 case "$opt" in
27 C) WORKD="$OPTARG" ;;
28 d) DOMAIN="$OPTARG" ;;
29 o) OUTD="$OPTARG" ;;
30 *) exit 1 ;;
31 esac
32 done
33 ## Initialize state
34 FILE=
35 ## Cleanup after we're done
36 trap cleanup INT KILL
37}
38
39main() {
40 # State predicates
41 alias pagep=false indexp=false feedp=false
42 # Convenience aliases
43 alias body=cat title='meta title' pubdate='meta date'
44 # Configure
45 configure "$@"
46 shift "$((OPTIND - 1))"
47 # Prepare
48 cd "$WORKD" || exit 2
49 mkdir -p "$OUTD" || exit 2
50 mkdir -p "$TMPD" || exit 2
51 # Build pages
52 alias pagep=true
53 build *."$PAGE_RAW_EXT" || exit 2
54 alias pagep=false
55 # Build index
56 alias indexp=true
57 index *."$PAGE_RAW_EXT" || exit 2
58 alias indexp=false
59 # Build feed
60 alias feedp=true
61 feed *."$PAGE_RAW_EXT" || exit 2
62 alias feedp=false
63 # Copy static files
64 static * || exit 2
65 # If $1 is 'publish', yeet the out/ directory somewhere
66 if test "x$1" = xpublish; then
67 publish "$OUTD"
68 fi
69}
70
71cleanup() {
72 test -z "$DEBUG" &&
73 test -z "$NODEP_RM" &&
74 rm -r "$TMPD"
75}
76
77publish() {
78 echo >&2 "I want to publish your website but I don't know how."
79 echo >&2 "Make a $CONFIG file in this directory and write a"
80 echo >&2 "\`publish' function telling me what to do. I rec-"
81 echo >&2 "ommend using \`rsync', but you live your life."
82 exit 3
83}
84
85### File processing
86
87## Building block functions
88
89shellfix() { # shellfix FILE...
90 ## Replace ` with \`, $ with \$, and $$ with $
91 sed -E \
92 -e 's/`/\\`/g' \
93 -e 's/(^|[^\$])\$([^\$]|$)/\1\\$\2/g' \
94 -e 's/\$\$/$/g' \
95 "$@"
96}
97
98expand() { # expand TEMPLATE... < INPUT
99 ## Print TEMPLATE to stdout, expanding shell constructs.
100 end="expand_:_${count:=0}_:_end"
101 eval "$(
102 echo "cat<<$end"
103 shellfix "$@"
104 echo
105 echo "$end"
106 )" && count=$((count + 1))
107}
108
109phtml() { # phtml < INPUT
110 ## Output HTML, pretty much.
111 # Paragraphs unadorned with html tags will be wrapped in <p> tags, and
112 # &, <, > will be escaped unless prepended with \. Paragraphs where the
113 # first character is < will be left as-is, excepting indentation on the
114 # first line (an implementation detail).
115 sed -E \
116 '/./{H;1h;$!d;}; x;
117 s#^[ \n\t]\+##;
118 t ok; :ok;
119 s#^[^<].*#&#;
120 t par; b;
121 :par;
122 s#([^\\])&#\1\&amp;#g; s#\\&#\&#g;
123 s#([^\\])<#\1\&lt;#g; s#\\<#<#g;
124 s#([^\\])>#\1\&gt;#g; s#\\>#>#g;'
125}
126
127meta() { # meta FIELD [FILE] < INPUT
128 ## Extract metadata FIELDS from INPUT.
129 # FILE gives the filename to save metadata to in the $WORKD. It
130 # defaults to the current value for $FILE.
131 #
132 # Metadata should exist as colon-separated data in an HTML comment at
133 # the beginning of an input file.
134 field="$1"
135 file="${2:-$FILE}"
136 metafile="$TMPD/${file}.meta"
137 test -f "$metafile" ||
138 sed '/<!--/n;/-->/q' >"$metafile"
139 sed -n "s/^[ \t]*$1:[ \t]*//p" <"$metafile"
140}
141
142## Customizable bits
143
144filters() { # filters < INPUT
145 ## The filters to run input through.
146 # This is a good candidate for customization in .vienna.sh.
147 expand | phtml
148}
149
150### Site building
151
152build() { # build PAGE...
153 ## Compile PAGE(s) into $OUTD for publication.
154 # Outputs a file of the format $OUTD/<PAGE>/index.html.
155 test -f "$PAGE_TEMPLATE" || return 1
156 for FILE; do
157 echo >&2 "[build] $FILE"
158 outd="$OUTD/${FILE%.$PAGE_RAW_EXT}"
159 outf="$outd/index.html"
160 tmpf="$TMPD/$FILE.tmp"
161 mkdir -p "$outd"
162 filters <"$FILE" >"$tmpf"
163 expand "$PAGE_TEMPLATE" <"$tmpf" >"$outf"
164 done
165}
166
167index() { # index PAGE...
168 ## Build a site index from all PAGE(s) passed to it.
169 # Wraps each PAGE in a <li><a> structure.
170 test -f "$INDEX_TEMPLATE" || return 1
171 for FILE; do
172 echo >&2 "[index] $FILE"
173 link="$DOMAIN${DOMAIN:+/}${FILE%.$PAGE_RAW_EXT}"
174 echo "<li><a href=\"$link\">$(meta title "$FILE")</a></li>"
175 done | expand "$INDEX_TEMPLATE" >"$OUTD/index.html"
176}
177
178feed() { # feed PAGE...
179 ## Build an RSS 2.0 feed from PAGE(s).
180 test -f "$FEED_TEMPLATE" || return 1
181 for FILE; do
182 echo >&2 "[feed] $FILE"
183 link="$DOMAIN${DOMAIN:+/}${FILE%.$PAGE_RAW_EXT}"
184 date="$(meta pubdate "$FILE")"
185 echo "<item>"
186 echo "<title>$(meta title "$FILE")</title>"
187 echo "<link>$link</link>"
188 echo "<guid>$link</guid>"
189 test -n "$date" && echo "<pubDate>$date</pubDate>"
190 echo "</item>"
191 done | expand "$FEED_TEMPLATE" >"$OUTD/feed.xml"
192}
193
194static() { # static FILE...
195 ## Copy static FILE(s) to $OUTD as-is.
196 # Performs a simple heuristic to determine whether to copy a file or
197 # not.
198 for FILE; do
199 case "$FILE" in
200 .*) continue ;;
201 "$OUTD") continue ;;
202 *) cp -r "$FILE" "$OUTD/" ;;
203 esac
204 done
205}
206
207### Do the thing!
208
209test -n "$DEBUG" && set -x
210test -n "$SOURCE" || main "$@"