├── .gitignore
├── README
├── shlog
└── shlog.conf.def
/.gitignore:
--------------------------------------------------------------------------------
1 | shlog.conf
2 | README.old
3 |
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | SHLOG(1) SHLOG(1)
2 | NAME
3 | shlog - a WIP static blog generator in /bin/sh
4 |
5 | SYNOPSIS
6 | shlog [subcommand]
7 |
8 | DESCRIPTION
9 | Note: This projects has been abandoned in favour of shite:
10 | https://git.zakaria.org/shite/
11 |
12 | Generate simple static sites. shlog is geared towards blogs.
13 |
14 | Warn: This project is no longer intended for any use other than my own.
15 | I will provide little to no support for anyone attempting to use this
16 | monstrosity - use at your own risk.
17 |
18 | The subcommands are as follows:
19 |
20 | i|index Generate the post index page. This page lists all posts and
21 | links to them.
22 |
23 | r|rss Generate the RSS feed. RSS feed location is configurable in the
24 | config file.
25 |
26 | u|update Find articles that haven't been converted to HTML and
27 | convert them.
28 |
29 | shlog defaults to u/update if no arguments are given.
30 |
31 | EXAMPLE
32 | To use shlog setup your site root as so:
33 |
34 | . # $SITE_ROOT
35 | |- posts/ # $POSTS_DIR
36 | | |- index.html # list of posts in chronological order
37 | | |- 2020-09-08-post.md # markdown post source
38 | | |- 2020-09-08-post.html # 'compiled' HTML post source
39 | |- html/ # dir containing html
40 | | |- footer.html # HTML added to the end of each post
41 | | |- head.html # HTML
added to every post
42 | | |- nav.html # HTML added to the start of each post
43 | | |- posts.html # HTML added to $POSTS_DIR/index.html
44 | |- style.css # style (not required)
45 | |- index.html # index/homepage (not required)
46 |
47 | After running `shlog update` Markdown in /posts/ following the filename
48 | format DD-MM-YYYY-shortitle.md will be run through lowdown and converted
49 | into HTML. Additional HTML will be added from the html/head.html,
50 | html/nav.html and html/footer.html.
51 |
52 | To generate a post index file (located at /posts/index.html) which lists
53 | posts in order of the date in it's filename (most recent first) and links
54 | to them, run `shlog index`.
55 |
56 | DEPENDENCIES
57 | shlog depends on the following programs:
58 |
59 | /bin/sh Obviously.
60 |
61 | lowdown Markdown to HTML converter (https://kristaps.bsd.lv/lowdown/).
62 |
63 | GOALS
64 | - Be a shell script.
65 | - Minimal external dependencies.
66 | - Be fast.
67 | - Be cross-platform (POSIX?).
68 |
69 | NON-GOALS
70 | - JavaScript (though it should be easy to add it yourself).
71 | - Post tags/categories.
72 |
73 | CONFIGURATION
74 | The main configuration file is located at:
75 | ${XDG_CONFIG_HOME}/shlog/shlog.conf.
76 | This config file is just a regular shell script that the shlog script
77 | sources on startup.
78 |
79 | A default/reference config is included in shlog.conf.def.
80 |
81 | PLANS
82 | - Better configuration syntax.
83 | - Better docs.
84 | - Better usablity.
85 |
--------------------------------------------------------------------------------
/shlog:
--------------------------------------------------------------------------------
1 | #
2 |
3 | usage() {
4 | cat</dev/null; then
67 | echo "$1" | tac
68 | else
69 | echo "$1" | tail -r
70 | fi
71 | }
72 |
73 | # log
74 | # $1 - message
75 | # $2 - (optional) log 'label'
76 | log() {
77 | message="$1"
78 | label="$2"
79 |
80 | if [ -z "$label" ]; then
81 | printf "%s: %s\\n" "$(bname "$0")" "$message" >&1
82 | return
83 | fi
84 |
85 | printf "%s: %s: %s\\n" "$(bname "$0")" "$label" "$message" >&1
86 | }
87 |
88 | #
89 | die() {
90 | message="$1"
91 |
92 | if [ -n "$message" ]; then
93 | log "$message" "error" >&2
94 | fi
95 |
96 | printf 'exiting.\n' >&2
97 | exit 1
98 | }
99 |
100 | # get markdown article title by extracting the first h1.
101 | # requires the first line of the markdown file to be the h1.
102 | # $1 - markdown file path
103 | get_md_title() {
104 | md_file="$1"
105 | head -n 1 "$md_file" | sed -e 's/^#\ \(.*\)$/\1/g' -e 's/\`//g'
106 | }
107 |
108 | # get date from post filename.
109 | # requires filename to be YYYY-MM-DD-blahblah-blah.md
110 | # $1 - markdown file path
111 | get_post_date() {
112 | md_filename="$(bname "$1")"
113 | echo "$md_filename" | sed -e 's/^\([0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]\)-.*$/\1/g'
114 | }
115 |
116 | # get list of all posts
117 | get_post_list() {
118 | posts=''
119 | for post in ${site_root}/${posts_dir}/*.html; do
120 | if [ "$(bname "$post")" = "index.html" ]; then
121 | continue
122 | fi
123 | posts="${posts}\\n${post}"
124 | done
125 | reverse "$posts"
126 | }
127 |
128 | # get list of unposted posts (.md files without their .html counterparts)
129 | get_unpost_list() {
130 | posts=''
131 | for post in ${site_root}/${posts_dir}/*.md; do
132 | # if the .html exists, it has already been compiled
133 | if [ -f "${post%%.*}.html" ]; then
134 | continue
135 | fi
136 | if [ "$(bname "$post")" = "index.html" ]; then
137 | continue
138 | fi
139 | posts="${posts}\\n${post}"
140 | done
141 | reverse "$posts"
142 | }
143 |
144 | # generate navgation/header html
145 | gen_html_nav() {
146 | printf '\n'
147 | printf '\n'
157 | printf '\n'
158 | }
159 |
160 | # generate html head
161 | # $1 - html title (optional)
162 | # $2 - canonical url of page (optional, only needed for opengraph)
163 | gen_html_head() {
164 | html_title="$1"
165 | page_url="$2"
166 |
167 | printf '\n'
168 | if [ -n "$head_content" ]; then
169 | if [ -f "$head_content" ]; then
170 | cat "$head_content"
171 | else
172 | die "head_content file '${head_content}' does not exist."
173 | fi
174 | fi
175 | if [ -n "$html_title" ]; then
176 | printf '%s - %s\n' "$html_title" "$site_title"
177 | else
178 | printf '%s\n' "$site_title"
179 | fi
180 | if is_yes "$add_og_tags"; then
181 | printf '\n' "$html_title"
182 | printf '\n' "$base_url" "$page_url"
183 | printf '\n'
184 | fi
185 | printf '\n'
186 | }
187 |
188 | # generate footer
189 | # $1 - page plaintext link (optional)
190 | gen_html_footer() {
191 | page_pt="$1"
192 |
193 | printf '\n'
199 | }
200 |
201 | # generate post info
202 | # $1 - date posted
203 | # $2 - post last modified
204 | gen_html_postinfo() {
205 | post_date="$1"
206 | post_modified="$2"
207 |
208 | printf '
for each post
270 | for post in $(get_post_list); do
271 | post_md="${post%%.*}.md"
272 | post_date="$(get_post_date "$post")"
273 | post_title="$(get_md_title "$post_md")"
274 | post_bname="$(bname "$post")"
275 |
276 | # use full paths, or relative paths ?
277 | if is_yes "$use_relative_paths"; then
278 | post_url="/${posts_dir}/${post_bname%%.*}"
279 | else
280 | post_url="${base_url}/${posts_dir}/${post_bname%%.*}"
281 | fi
282 |
283 | # add .html to the end of links ?
284 | if is_yes "$add_html_to_links"; then
285 | post_url="${post_url}.html"
286 | fi
287 |
288 | # construct
289 | # TODO: find a better way to do this
290 | fmt="$(echo "$post_list_format" | sed \
291 | -e "s/%d/${post_date}/g" \
292 | -e "s,%t,${post_title},g" \
293 | -e "s,%l,${post_url},g")"
294 |
295 | printf '
%s
\n' "$fmt"
296 | done
297 |
298 | printf '
\n'
299 | printf '\n'
300 | printf '\n'
301 | printf '\n'
302 | }
303 |
304 | # generate rss feed
305 | gen_rss() {
306 | printf '\n' "${base_url}"
307 | printf '\n'
308 | printf '%s\n' "${rss_title}"
309 | printf '\n'
310 | printf '%s\n' "${rss_link}"
311 |
312 | # add an for each post
313 | for post in $(get_post_list); do
314 | post_md="${post%%.*}.md"
315 | post_date="$(get_post_date "$post")"
316 | post_title="$(get_md_title "$post_md")"
317 | post_bname="$(bname "$post")"
318 |
319 | printf '\n'
320 | printf '%s/%s/%s\n' "${base_url}" "$posts_dir" "${post_bname%%.*}"
321 | printf '%s\n' "$post_title"
322 |
323 | # include post content ?
324 | if is_yes "$rss_include_html"; then
325 | printf '\n'
326 | run_lowdown "$post_md"
327 | printf '\n'
328 | fi
329 |
330 | printf '%s\n' "$(date_to_rfc2822 "$post_date" '%F')"
331 | printf '\n'
332 | done
333 |
334 | printf '\n'
335 | printf '\n'
336 | }
337 |
338 | # generate a blank page
339 | # $1 - page title
340 | # $2 - page file
341 | gen_page() {
342 | page_title="$1"
343 | page_file="$2"
344 | page_pt="$(bname "$page_file")"
345 |
346 | printf '\n'
347 | printf '\n'
348 | gen_html_head "${page_title}"
349 | printf '\n'
350 | gen_html_nav
351 | printf '\n'
352 | run_lowdown "$page_file"
353 | printf '\n'
354 | gen_html_footer "$page_pt"
355 | printf '\n'
356 | printf '\n'
357 | }
358 |
359 | # compile all .md files without .html couterparts
360 | compile_posts() {
361 | posts="$(get_unpost_list)"
362 |
363 | # return if there are no posts to update
364 | if [ -z "$posts" ]; then
365 | log "no posts to update"
366 | return
367 | fi
368 |
369 | # for each .md file without a .html counterpart...
370 | for post in $posts; do
371 | if [ -z "$post" ]; then
372 | continue
373 | fi
374 |
375 | log "adding $(bname "$post")..."
376 |
377 | # md
378 | post_md="${post%%.*}.md"
379 |
380 | # get post date
381 | post_date="$(get_post_date "$post")"
382 |
383 | # the current date is the last modified date
384 | post_modified="$(date '+%F')"
385 |
386 | # get post title
387 | post_title="$(get_md_title "$post_md")"
388 |
389 | # generate html from markdown
390 | post_html="$(run_lowdown "$post")"
391 |
392 | # generate footer
393 | post_foot="$(gen_html_footer "$post_md")"
394 |
395 | # generate info
396 | post_info="$(gen_html_postinfo "$post_date" "$post_modified")"
397 |
398 | # generate the post's html
399 | gen_html_post "$post" "$post_title" "$post_html" "$post_foot" "$post_info" > "${post%%.*}.html"
400 | done
401 |
402 | }
403 |
404 | main() {
405 | # load config
406 | DEF_CONFIG="${XDG_CONFIG_HOME:-${HOME}/.config}/shlog/shlog.conf"
407 | if [ -f "${DEF_CONFIG}" ]; then
408 | # shellcheck source=./shlog.conf.def
409 | . "${DEF_CONFIG}"
410 | else
411 | die "cannot load config."
412 | fi
413 |
414 | #
415 | case "$1" in
416 | h|-h|help) usage ;;
417 | ""|u|update) compile_posts ;;
418 | i|index) gen_index > "${site_root}/${posts_index}" ;;
419 | r|rss) gen_rss > "$rss_feed" ;;
420 | p|page) gen_page "$3" "$2" > "$4" ;;
421 | *) log "unsupported command $1" "error" ;;
422 | esac
423 | }
424 |
425 | main "$@"
--------------------------------------------------------------------------------
/shlog.conf.def:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # shlog.conf.def - example shlog config
3 |
4 | # FQDN
5 | fqdn=example.com #DOMAIN=example.com
6 |
7 | # base website url
8 | base_url="https://${DOMAIN}"
9 |
10 | # site title, used in
11 | site_title="example's website"
12 |
13 | # root site source
14 | site_root=${HOME}/src/blog
15 |
16 | # directory in the site_root containing posts
17 | posts_dir=posts
18 |
19 | # post list/index file location
20 | posts_index=${posts_dir}/index.html
21 |
22 | # pass additional options to lowdown
23 | lowdown_opts=
24 |
25 |
26 | # #
27 | # Y\N FLAGS #
28 | # #
29 |
30 | # use relative paths?
31 | use_relative_paths=y
32 |
33 | # add .html to the end of post links?
34 | add_html_to_links=y
35 |
36 | # add OpenGraph tags to posts
37 | add_og_tags=n
38 |
39 |
40 | # #
41 | # RSS #
42 | # #
43 |
44 | # location of rss feed xml
45 | rss_feed=${site_root}/rss.xml
46 | # rss feed
47 | rss_title=${site_title}
48 | # rss feed
49 | rss_link=${base_url}
50 |
51 | # include HTML of article in rss item?
52 | # when enabled the raw html of the article will be placed in the RSS item's
53 | # tag.
54 | rss_include_html=y
55 |
56 |
57 | # #
58 | # CONTENT #
59 | # #
60 |
61 | html_dir=${site_root}/html
62 |
63 | # file to add to the end of the HTML of all pages.
64 | # leave blank to add nothing.
65 | head_content=${html_dir}/head.html
66 |
67 | # content to add to the header of each page.
68 | # leave blank to add nothing.
69 | nav_content=${html_dir}/nav.html
70 |
71 | # content to add to the end of each post
72 | # after the metadata
73 | footer_content=${html_dir}/footer.html
74 |
75 | # content to add to the beginning of the post index.
76 | # leave blank to add nothing.
77 | postindex_content=${html_dir}/posts.html
78 |
79 | # format posts are listed in. (can include html)
80 | # '%d' = post date
81 | # '%t' = post title
82 | # '%l' = post url
83 | post_list_format="%d — %t"
84 |
--------------------------------------------------------------------------------