summary refs log tree commit diff stats
path: root/ht
diff options
context:
space:
mode:
Diffstat (limited to 'ht')
-rwxr-xr-xht287
1 files changed, 287 insertions, 0 deletions
diff --git a/ht b/ht new file mode 100755 index 0000000..c8cef5c --- /dev/null +++ b/ht
@@ -0,0 +1,287 @@
1#!/bin/sh
2
3### Code:
4
5usage() {
6 cat <<\EOF
7ht: Build a website
8Usage: ht -h
9 ht [OPTIONS...] FILE...
10
11FLAGS:
12 -h Show this help and exit.
13 -B Treat all targets as "new" (like in `make').
14
15OPTIONS:
16 -u BASEURL The base URL of the built site. Default: https://example.com
17 -o OUTD Output built files to OUTD. Default: ./out/
18 -w WORKD Use WORKD as the working directory. Default: /tmp/ht/
19 -p PROC Use PROC to process each input file. Default: ./ht.awk
20 -P TEMPLATE Use TEMPLATE as the page template. Default: ./page.tmpl.htm
21 -i ITEMFMT Format individual items with ITEMFMT in the site index.
22 -I TEMPLATE Use TEMPLATE as the index template. Default: ./index.tmpl.htm
23 -f ITEMFMT Format individual items with ITEMFMT in the site feed.
24 -F TEMPLATE Use TEMPLATE as the feed template. Default: ./feed.tmpl.xml
25 -s DIRECTORY The DIRECTORY holding static files (images, css, etc.).
26 Default: ./static/
27 -S DIRECTORY The DIRECTORY to copy static files to. Default: ./out/static.
28
29PARAMETERS:
30 FILE... The list of files to include in the website.
31EOF
32 exit "${1:-0}"
33}
34
35main() {
36 configure "$@"
37 shift $((OPTIND - 1))
38 prepare
39 static_copy
40
41 for input; do
42 page_write "$PAGE_TEMPLATE" "$input"
43 done
44
45 if [ -n "$INDEXEACH" ]; then
46 index_write "$INDEX_TEMPLATE" "$INDEXEACH" "$OUTD/$INDEXNAME"
47 fi
48 if [ -n "$FEEDEACH" ]; then
49 index_write "$FEED_TEMPLATE" "$FEEDEACH" "$OUTD/$FEEDNAME"
50 fi
51 printf 'Done. ' >&2
52 if test -f "$WORKD/ok"; then
53 eprint "No errors reported."
54 else
55 eprint "There were errors."
56 fi
57}
58
59configure() {
60 OUTD="${HT_OUT_DIR:-./out}"
61 WORKD="${HT_WORKD:-/tmp/ht}"
62 PROC="${HT_PROC:-./ht.awk}" # XXX: Needs better resolution
63 BASEURL="${HT_BASEURL:-https://example.com}"
64 PAGE_TEMPLATE="${HT_PAGE_TEMPLATE:-./page.tmpl.htm}"
65 INDEX_TEMPLATE="${HT_INDEX_TEMPLATE:-./index.tmpl.htm}"
66 FEED_TEMPLATE="${HT_FEED_TEMPLATE:-./feed.tmpl.xml}"
67 ALLNEW=false
68 STATICD="${HT_STATIC_DIR:-./static}"
69 STATICOUT="${HT_STATIC_OUTPUT_DIR:-${OUTD}/static}"
70
71 INDEXNAME="${HT_INDEX_NAME:-index.html}"
72 if [ -n "$HT_INDEX_ITEM_FORMAT" ]; then
73 INDEXEACH="$HT_INDEX_ITEM_FORMAT"
74 else
75 # shellcheck disable=2016
76 INDEXEACH='<li><a href=\"$BASEURL/${file}/\">${title}</a></li>'
77 fi
78
79 FEEDNAME="${HT_FEED_NAME:-feed.xml}"
80 if [ -n "$HT_FEED_ITEM_FORMAT" ]; then
81 FEEDEACH="$HT_FEED_ITEM_FORMAT"
82 else
83 # shellcheck disable=2016
84 FEEDEACH='<entry>
85 <id>$BASEURL/$file</id>
86 <link rel=\"alternate\" href=\"$BASEURL/$file/\" />
87 <title>${title}</title>
88 <updated>$(date -u -d "@${time}" -R)</updated>
89 <author>$AUTHOR</author>
90 <content type=\"text/html\">
91 <![CDATA[$(cat "$WORKD/${file}.body")]]>
92 </content>
93 </entry>'
94 fi
95
96 # shellcheck disable=2034 # BASEURL is used in templates
97 while getopts hBo:w:p:i:I:f:F:u:s:S: opt; do
98 case "$opt" in
99 h) usage ;;
100 o) OUTD="$OPTARG" ;;
101 w) WORKD="$OPTARG" ;;
102 p) PROC="$OPTARG" ;;
103 P) PAGE_TEMPLATE="$OPTARG" ;;
104 i) INDEXEACH="$OPTARG" ;;
105 I) INDEX_TEMPLATE="$OPTARG" ;;
106 f) FEEDEACH="$OPTARG" ;;
107 F) FEED_TEMPLATE="$OPTARG" ;;
108 u) BASEURL="$OPTARG" ;;
109 B) ALLNEW=true ;;
110 s) STATICD="$OPTARG" ;;
111 S) STATICOUT="$OPTARG" ;;
112 *) usage 1 ;;
113 esac
114 done
115
116 INDEX="$WORKD/index.txt"
117}
118
119prepare() {
120 test -n "$WORKD" && rm -rf "$WORKD"
121 mkdir -p "$OUTD" "$WORKD"
122 test -x "$PROC" || {
123 eprint "Can't find processor: $PROC"
124 exit 2
125 }
126 touch "$WORKD/ok"
127}
128
129### Utilities
130
131print() {
132 printf '%s\n' "$*"
133}
134
135eprint() {
136 print "$@" >&2
137}
138
139olderp() { # olderp REF OTHER
140 # Is REF older than OTHER ? Necessary b/c test -ot is not POSIX.
141 a="$1"
142 b="$2"
143
144 if a_age="$(stat -c%Y "$a" 2>/dev/null)"; then
145 a_exist=true
146 else
147 a_exist=false
148 fi
149 if b_age="$(stat -c%Y "$b" 2>/dev/null)"; then
150 b_exist=true
151 else
152 b_exist=false
153 fi
154
155 if $a_exist && ! $b_exist; then
156 # B doesn't exist -- so B is newer
157 # eprint "$a is older than $b (doesn't exist)"
158 return 0
159 elif ! $a_exist && $b_exist; then
160 # A doesn't exist -- so A is newer
161 # eprint "$a (doesn't exist) is newer than $b"
162 return 1
163 else
164 # A's age > B's age ?
165 # eprint "$a ($a_age) <= $b ($b_age)?"
166 test "$a_age" -le "$b_age"
167 fi
168}
169
170uptodate() { # uptodate FILE DEPENDENCIES... # && return
171 # Check if FILE is up-to-date with DEPENDENCIES.
172 $ALLNEW && return 1
173 uptodate=1
174 file="$1"
175 shift
176 for dep in "$@"; do
177 olderp "$dep" "$file" && uptodate=0
178 done
179 return $uptodate
180}
181
182### File conversion
183
184template_expand() { # template_expand TEMPLATES...
185 end="tmpl_$(date +%s)_${count:=0}"
186 eval "$(
187 print "cat <<$end"
188 cat "$@"
189 print
190 print "$end"
191 )"
192 count=$((count + 1))
193}
194
195meta_save() { # meta_save [-c COMMENTCH] [-m METACH] META_FILE < INPUT
196 COMMENTCH=';'
197 METACH='@'
198 while getopts c:m: opt; do
199 case "$opt" in
200 c) COMMENTCH="$OPTARG" ;;
201 m) METACH="$OPTARG" ;;
202 *) ;;
203 esac
204 done
205 shift $((OPTIND - 1))
206 sed -n "s/^${COMMENTCH}${COMMENTCH}${METACH}//p" 2>/dev/null | tee "$1"
207}
208
209meta_clear() { # meta_clear META_FILE
210 while read -r line; do
211 case "$line" in
212 *'()'*) unset -f "${line%()*}" ;;
213 *=*) unset -v "${line%=*}" ;;
214 *) ;;
215 esac
216 done <"$1" 2>/dev/null
217}
218
219page_write() { # html_write TEMPLATE INPUT
220 template="$1"
221 file="$2"
222
223 fn="${file##*/}"
224 fn="${fn%%.*}"
225 out="${OUTD}/${fn}/index.html"
226 mkdir -p "${out%/*}"
227 meta="$WORKD/${fn}.meta"
228
229 eval "$(meta_save "$meta" <"$file")"
230 stat -c "%Y ${fn} $meta" "$file" >>"$INDEX"
231
232 uptodate "$out" "$template" "$file" && return
233
234 eprint "Page: $file -> $out"
235 if
236 ! "$PROC" "$file" |
237 tee "$WORKD/${fn}.body" |
238 template_expand "$template" >"$out"
239 then
240 eprint "$file -> $out ... ERROR!"
241 rm "$WORKD/ok"
242 return
243 fi
244 meta_clear "$meta"
245}
246
247index_write() { # index_write TEMPLATE EACH OUTFILE
248 template="$1"
249 each="$2"
250 out="$3"
251
252 test -f "$INDEX" || return 1
253 uptodate "$out" "$template" "$INDEX" && return
254
255 eprint "Index: $out"
256 # shellcheck disable=2034 # file and time can be used in `each'
257 sort -k1,1 -nr "$INDEX" |
258 while IFS=' ' read -r time file meta; do
259 # shellcheck disable=1090
260 . "$meta"
261 if ! item="$(eval print "\"$each\"")"; then
262 eprint "ERROR: couldn't add '$file' to $out"
263 rm "$WORKD/ok"
264 fi
265 print "$item"
266 meta_clear "$meta"
267 done |
268 template_expand "$template" >"$out"
269}
270
271### Static files
272
273static_copy() { # static_copy
274 test -d "$STATICD" || return 1
275 if command -v rsync 2>/dev/null; then
276 eprint rsync -avz "$STATICD/" "$STATICOUT/"
277 rsync -avz "$STATICD/" "$STATICOUT/"
278 else
279 eprint cp -r "$STATICD"/* "$STATICOUT/"
280 cp -r "$STATICD"/* "$STATICOUT/"
281 fi
282}
283
284### Do the thing
285
286test "$DEBUG" && set -x
287test "$SOURCE" || main "$@"