3 # Copyright (C) 2018 Amin Bandali <amin@aminb.org>
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <https://www.gnu.org/licenses/>.
18 # ssng is a fork of Roman Zolotarev's ssg. See end of file for ssg's
21 : "${WEBSITE_TITLE:=Roman Zolotarev}"
22 : "${SERVER_NAME:=www.romanzolotarev.com}"
23 : "${SERVER_PROTO:=https}"
24 : "${RSS_AUTHOR:=hi@romanzolotarev.com (Roman Zolotarev)}"
25 : "${RSS_DESCRIPTION:=Personal website}"
26 : "${COPYRIGHT_YEAR:=2016}"
28 ##########################################################################
30 [ -n "$DOCS" ] ||
{ echo "export DOCS <target_directory>"; exit 1; }
31 DOCUMENT_ROOT
=$
(readlink
-fn "$DOCS")
33 # shellcheck disable=SC2064
35 trap exit HUP INT TERM
36 [ "$2" = '--clean' ] && RSYNC_FLAGS
='--delete-excluded' || RSYNC_FLAGS
=''
38 INDEX_HTML_FILE
="$TEMP_DIR/index.html"
39 CSS_FILE
="$TEMP_DIR/styles.css"
40 RSS_FILE
="$TEMP_DIR/rss.xml"
41 RSS_URL
="$SERVER_PROTO://$SERVER_NAME/rss.xml"
42 SITEMAP
="$TEMP_DIR/sitemap.xml"
44 ANNOUNCEMENT_FILE
="$PWD/announcement.html"
45 FOOTER_FILE
="$PWD/footer.html"
46 HEADER_FILE
="$PWD/header.html"
47 [ -f "$ANNOUNCEMENT_FILE" ] &&
48 ANNOUNCEMENT_TEXT
=$
(cat "$ANNOUNCEMENT_FILE")
49 [ -f "$HEADER_FILE" ] &&
50 HEADER
=$
(cat "$HEADER_FILE") ||
52 <a href="/">Home</a> -
53 <a href="/twitter.html">Twitter</a>
56 [ -f "$FOOTER_FILE" ] &&
57 FOOTER
=$
(cat "$FOOTER_FILE") ||
59 Copyright $COPYRIGHT_YEAR–$(date +%Y)
60 <a href="/about.html">$WEBSITE_TITLE</a>
64 ##########################################################################
67 echo 'usage: DOCS=<target_directory>'
69 echo ' ssg build [--clean]'
70 echo ' | watch [--clean]'
75 rsync
-a --delete-excluded \
81 copy_to_document_root
() {
82 [ "$(dirname "$DOCUMENT_ROOT")" = "$PWD" ] &&
83 self
="/$(basename "$DOCUMENT_ROOT")/" ||
85 rsync
-a $RSYNC_FLAGS \
89 "$TEMP_DIR/" "$DOCUMENT_ROOT"
93 find "$TEMP_DIR" -type f
-name '*.md'|
94 while read -r file; do
95 lowdown
-D html-skiphtml
-d metadata \
96 "$file" > "${file%\.md}.html" &&
102 # filter first 20 lines with links and link titles (dates)
103 # shellcheck disable=SC2016
104 fst_h1
='/<[h1]*( id=".*")?>/{gsub(/<[^>]*>/,"");print($0);exit;}'
105 a
='^<li><a href="\(.*\)" title="\([^<]*\)">[^<]*<\/a>.*<\/li>.*'
108 url
=$
(echo "$line"|
sed "s/$a/\\1/g")
109 date=$
(echo "$line"|
sed "s/$a/\\2/g")
110 file="${TEMP_DIR}${url}"
111 [ ! -f "$file" ] && return
113 title
="$(awk "$fst_h1" "$file")"
114 # replace relative URIs with absolute URIs
115 article
=$
(sed "s/\\([hrefsc]*\\)=\"\\//\\1=\"$prefix/g" "$file")
118 <title>$title</title>
119 <guid>$SERVER_PROTO://${SERVER_NAME}$url</guid>
120 <link>$SERVER_PROTO://${SERVER_NAME}$url</link>
121 <pubDate>$date 00:00:00 +0000</pubDate>
122 <description><![CDATA[$article]]></description>
125 )|
sed 's/\ /\ /'>>"$RSS_FILE"
129 date_rfc_822
=$
(date "+%a, %d %b %Y %H:%M:%S %z")
130 cat > "$RSS_FILE" << EOF
131 <?xml version="1.0" encoding="utf-8"?>
132 <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
134 <atom:link href="$RSS_URL" rel="self" type="application/rss+xml" />
135 <title>$WEBSITE_TITLE</title>
136 <description>$RSS_DESCRIPTION</description>
137 <link>$SERVER_PROTO://$SERVER_NAME/</link>
138 <lastBuildDate>$date_rfc_822</lastBuildDate>
139 <managingEditor>$RSS_AUTHOR</managingEditor>
142 prefix
="$SERVER_PROTO:\\/\\/$SERVER_NAME\\/"
143 grep "$a" "$INDEX_HTML_FILE" |
145 while read -r line
; do line_to_rss_item
"$line"; done
146 echo '</channel></rss>' >> "$RSS_FILE"
150 # generate sorted sitemap
151 find_h1_tag
='/<[h1]*( id=".*")?>/'
152 # shellcheck disable=SC2016
153 tag_content
='{gsub(/<[^>]*>/,"");print(FILENAME"===="$0);exit;}'
155 find "$TEMP_DIR" -type f -name '*.html'|
156 while read -r file; do
157 awk "${find_h1_tag}${tag_content}" "$file"
161 # save sitemap in html and xml formats
162 date=$
(date +%Y-
%m-
%dT
%H
:%M
:%S
%z
)
163 cat > "$SITEMAP" << EOF
164 <?xml version="1.0" encoding="UTF-8"?>
166 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
167 xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9
168 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"
169 xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
171 echo "$sitemap"|
while read -r line
; do
173 url
=${page#$TEMP_DIR}
175 /index.html
) title
='Home';;
176 *) title
="${line#*====}";;
178 cat >> "$SITEMAP" << EOF
180 <loc>$SERVER_PROTO://${SERVER_NAME}$url</loc>
181 <lastmod>$date</lastmod>
182 <priority>1.0</priority>
186 echo '</urlset>' >> "$SITEMAP"
187 # generate html pages
188 styles
=$
(cat "$CSS_FILE")
189 [ -n "$ANNOUNCEMENT_TEXT" ] &&
190 announcement
="$(cat << EOF
191 <div class="announcement
">
192 <div class="announcement__text
">$ANNOUNCEMENT_TEXT</div>
197 while read -r line
; do
199 url
=${page#$TEMP_DIR}
200 article
=$
(cat "$page")
204 head_title
="$WEBSITE_TITLE"
208 title
="${line#*====}"
209 head_title
="$title - $WEBSITE_TITLE"
210 header__home
="$HEADER"
213 # merge page with html template
215 <!DOCTYPE html><html lang="en">
216 <head><title>$head_title</title>
217 <meta charset="utf-8">
218 <meta name="viewport" content="width=device-width, initial-scale=1">
219 <link rel="alternate" type="application/atom+xml" href="/rss.xml">
220 <link rel="icon" type="image/png" href="/favicon.png">
221 <style>$styles</style>
226 t.addEventListener('DOMContentLoaded', function () {
227 var l = t.querySelector('#light-off');
228 if (l === null) { console.log('Lights-out...'); }
230 l.checked = t.cookie.match(/lightOff=true/) !== null;
231 l.addEventListener('change', function () {
232 t.cookie = 'lightOff=' + JSON.stringify(l.checked) + ';path=/';
238 <input class="light-off" type="checkbox" id="light-off">
242 <div class="header__left">$header__home</div>
243 <div class="header__right">
244 <label for="light-off" class="light-off-button"></label>
247 <div class="article">$article</div>
248 <div class="footer">$FOOTER</div>
254 echo "$date $(echo "$sitemap"|wc -l|tr -d ' ')pp"
257 clean_up
() { rm -rf "$TEMP_DIR"; }
259 ##########################################################################
264 ls index.
* >/dev
/null
2>&1 ||
265 { echo 'no index.* found in the directory'; exit 1; }
266 [ ! -x "$(which rsync)" ] &&
267 { echo 'rsync(1) should be installed'; exit 1; }
268 [ ! -x "$(which lowdown)" ] &&
269 { echo 'lowdown(1) should be installed'; exit 1; }
270 printf 'building %s %s ' "$DOCUMENT_ROOT" "$2"
275 copy_to_document_root
280 cmd
="entr -d env DOCS=$DOCS $(basename "$0") build $2"
281 pgrep
-qf "$cmd" && { echo "already watching $DOCS"; exit 1; }
283 [ ! -x "$(which entr)" ] &&
284 { echo 'entr(1) should be installed'; exit 1; }
286 find "$PWD" -type f \
287 \
( -name "$(basename "$0")" \
293 -or -name '*.png' \
)\
296 ! -path "${DOCUMENT_ROOT}*" |
308 # https://www.romanzolotarev.com/bin/ssg
309 # Copyright 2018 Roman Zolotarev <hi@romanzolotarev.com>
311 # Permission to use, copy, modify, and/or distribute this software for any
312 # purpose with or without fee is hereby granted, provided that the above
313 # copyright notice and this permission notice appear in all copies.
315 # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
316 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
317 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
318 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
319 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
320 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
321 # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.