ssg5 (6421B)
1 #!/bin/sh -e 2 # 3 # https://rgz.ee/bin/ssg5 4 # Copyright 2018-2019 Roman Zolotarev <hi@romanzolotarev.com> 5 # 6 # Permission to use, copy, modify, and/or distribute this software for any 7 # purpose with or without fee is hereby granted, provided that the above 8 # copyright notice and this permission notice appear in all copies. 9 # 10 # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 # 18 19 main() { 20 test -n "$1" || usage 21 test -n "$2" || usage 22 test -n "$3" || usage 23 test -n "$4" || usage 24 test -d "$1" || no_dir "$1" 25 test -d "$2" || no_dir "$2" 26 27 src=$(readlink_f "$1") 28 dst=$(readlink_f "$2") 29 30 IGNORE=$( 31 if ! test -f "$src/.ssgignore" 32 then 33 printf ' ! -path "*/.*"' 34 return 35 fi 36 while read -r x 37 do 38 test -n "$x" || continue 39 printf ' ! -path "*/%s*"' "$x" 40 done < "$src/.ssgignore" 41 ) 42 43 # files 44 45 title="$3" 46 47 h_file="$src/_header.html" 48 h_index_file="$src/_header-index.html" 49 f_file="$src/_footer.html" 50 f_index_file="$src/_footer-index.html" 51 test -f "$f_file" && DEFFOOTER=$(cat "$f_file") && export DEFFOOTER 52 test -f "$f_index_file" && INDEXFOOTER=$(cat "$f_index_file") && export INDEXFOOTER 53 test -f "$h_file" && DEFHEADER=$(cat "$h_file") && export DEFHEADER 54 test -f "$h_index_file" && INDEXHEADER=$(cat "$h_index_file") && export INDEXHEADER 55 56 list_dirs "$src" | 57 (cd "$src" && cpio -pdu "$dst") 58 59 fs=$( 60 if test -f "$dst/.files" 61 then list_affected_files "$src" "$dst/.files" 62 else list_files "$1" 63 fi 64 ) 65 66 if test -n "$fs" 67 then 68 echo "$fs" | tee "$dst/.files" 69 70 if echo "$fs" | grep -q '\.md$' 71 then 72 if test -x "$(which lowdown 2> /dev/null)" 73 then 74 echo "$fs" | grep '\.md$' | 75 render_md_files_lowdown "$src" "$dst" "$title" 76 else 77 if test -x "$(which markdown 2> /dev/null)" 78 then 79 echo "$fs" | grep '\.md$' | 80 render_md_files_markdown "$src" "$dst" "$title" 81 else 82 echo "couldn't find lowdown nor markdown" 83 exit 3 84 fi 85 fi 86 fi 87 88 echo "$fs" | grep '\.html$' | 89 render_html_files "$src" "$dst" "$title" 90 91 echo "$fs" | grep -Ev '\.md$|\.html$' | 92 (cd "$src" && cpio -pu "$dst") 93 fi 94 95 printf '[ssg] ' >&2 96 print_status 'file, ' 'files, ' "$fs" >&2 97 98 99 # sitemap 100 101 base_url="$4" 102 date=$(date +%Y-%m-%d) 103 urls=$(list_pages "$src") 104 105 test -n "$urls" && 106 render_sitemap "$urls" "$base_url" "$date" > "$dst/sitemap.xml" 107 108 print_status 'url' 'urls' "$urls" >&2 109 echo >&2 110 } 111 112 113 readlink_f() { 114 file="$1" 115 cd "$(dirname "$file")" 116 file=$(basename "$file") 117 while test -L "$file" 118 do 119 file=$(readlink "$file") 120 cd "$(dirname "$file")" 121 file=$(basename "$file") 122 done 123 dir=$(pwd -P) 124 echo "$dir/$file" 125 } 126 127 128 print_status() { 129 test -z "$3" && printf 'no %s' "$2" && return 130 131 echo "$3" | awk -v singular="$1" -v plural="$2" ' 132 END { 133 if (NR==1) printf NR " " singular 134 if (NR>1) printf NR " " plural 135 }' 136 } 137 138 139 usage() { 140 echo "usage: ${0##*/} src dst title base_url" >&2 141 exit 1 142 } 143 144 145 no_dir() { 146 echo "${0##*/}: $1: No such directory" >&2 147 exit 2 148 } 149 150 list_dirs() { 151 cd "$1" && eval "find . -type d ! -name '.' ! -path '*/_*' $IGNORE" 152 } 153 154 155 list_files() { 156 cd "$1" && eval "find . -type f ! -name '.' ! -path '*/_*' $IGNORE" 157 } 158 159 160 list_dependant_files () { 161 e="\\( -name '*.html' -o -name '*.md' -o -name '*.css' -o -name '*.js' \\)" 162 cd "$1" && eval "find . -type f ! -name '.' ! -path '*/_*' $IGNORE $e" 163 } 164 165 list_newer_files() { 166 cd "$1" && eval "find . -type f ! -name '.' $IGNORE -newer $2" 167 } 168 169 170 has_partials() { 171 grep -qE '^./_.*\.html$|^./_.*\.js$|^./_.*\.css$' 172 } 173 174 175 list_affected_files() { 176 fs=$(list_newer_files "$1" "$2") 177 178 if echo "$fs" | has_partials 179 then list_dependant_files "$1" 180 else echo "$fs" 181 fi 182 } 183 184 185 render_html_files() { 186 while read -r f 187 do 188 if $(echo "$1/$f" | grep "index.html" > /dev/null); then 189 HEADER="$INDEXHEADER" && export HEADER 190 FOOTER="$INDEXFOOTER" && export FOOTER 191 else 192 HEADER="$DEFHEADER" && export HEADER 193 FOOTER="$DEFFOOTER" && export FOOTER 194 fi 195 render_html_file "$3" < "$1/$f" > "$2/$f" 196 done 197 } 198 199 200 render_md_files_lowdown() { 201 while read -r f 202 do 203 lowdown \ 204 -D html-skiphtml \ 205 -d metadata \ 206 -d autolink < "$1/$f" | 207 render_html_file "$3" \ 208 > "$2/${f%\.md}.html" 209 done 210 } 211 212 213 render_md_files_markdown() { 214 while read -r f 215 do 216 if $(echo "$1/$f" | grep "index.md" > /dev/null); then 217 HEADER="$INDEXHEADER" && export HEADER 218 FOOTER="$INDEXFOOTER" && export FOOTER 219 else 220 HEADER="$DEFHEADER" && export HEADER 221 FOOTER="$DEFFOOTER" && export FOOTER 222 fi 223 markdown < "$1/$f" | 224 render_html_file "$3" \ 225 > "$2/${f%\.md}.html" 226 done 227 } 228 229 230 render_html_file() { 231 # h/t Devin Teske 232 awk -v title="$1" ' 233 { body = body "\n" $0 } 234 END { 235 body = substr(body, 2) 236 if (body ~ /<[Hh][Tt][Mm][Ll]/) { 237 print body 238 exit 239 } 240 if (match(body, /<[[:space:]]*[Hh]1(>|[[:space:]][^>]*>)/)) { 241 t = substr(body, RSTART + RLENGTH) 242 sub("<[[:space:]]*/[[:space:]]*[Hh]1.*", "", t) 243 gsub(/^[[:space:]]*|[[:space:]]$/, "", t) 244 if (t) title = t " — " title 245 } 246 n = split(ENVIRON["HEADER"], header, /\n/) 247 for (i = 1; i <= n; i++) { 248 if (match(tolower(header[i]), "<title></title>")) { 249 head = substr(header[i], 1, RSTART - 1) 250 tail = substr(header[i], RSTART + RLENGTH) 251 print head "<title>" title "</title>" tail 252 } else print header[i] 253 } 254 print body 255 print ENVIRON["FOOTER"] 256 }' 257 } 258 259 260 list_pages() { 261 e="\\( -name '*.html' -o -name '*.md' \\)" 262 cd "$1" && eval "find . -type f ! -path '*/.*' ! -path '*/_*' $IGNORE $e" | 263 sed 's#^./##;s#.md$#.html#;s#/index.html$#/#' 264 } 265 266 267 render_sitemap() { 268 urls="$1" 269 base_url="$2" 270 date="$3" 271 272 echo '<?xml version="1.0" encoding="UTF-8"?>' 273 echo '<urlset' 274 echo 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' 275 echo 'xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9' 276 echo 'http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"' 277 echo 'xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' 278 echo "$urls" | 279 sed -E 's#^(.*)$#<url><loc>'"$base_url"'/\1</loc><lastmod>'\ 280 "$date"'</lastmod><priority>1.0</priority></url>#' 281 echo '</urlset>' 282 } 283 284 main "$@"