| 1 | #!/usr/bin/make -f |
| 2 | # Source: https://pedantic.software/git/blogit/ |
| 3 | BLOG := $(MAKE) -f $(lastword $(MAKEFILE_LIST)) --no-print-directory |
| 4 | ifneq ($(filter-out help,$(MAKECMDGOALS)),) |
| 5 | include config |
| 6 | endif |
| 7 | |
| 8 | # The following can be configured in config |
| 9 | BLOG_DATE_FORMAT_INDEX ?= %x |
| 10 | BLOG_DATE_FORMAT ?= %x %X |
| 11 | BLOG_TITLE ?= blog |
| 12 | BLOG_DESCRIPTION ?= blog |
| 13 | BLOG_URL_ROOT ?= http://localhost/blog |
| 14 | BLOG_FEED_MAX ?= 20 |
| 15 | BLOG_FEEDS ?= rss atom |
| 16 | BLOG_SRC ?= articles |
| 17 | |
| 18 | |
| 19 | .PHONY: help init build deploy clean |
| 20 | |
| 21 | ARTICLES = $(shell git ls-tree HEAD --name-only -- $(BLOG_SRC)/ 2>/dev/null) |
| 22 | TAGFILES = $(patsubst $(BLOG_SRC)/%.md,tags/%,$(ARTICLES)) |
| 23 | |
| 24 | help: |
| 25 | $(info blogit init|build|deploy|clean) |
| 26 | |
| 27 | init: |
| 28 | mkdir -p $(BLOG_SRC) data templates |
| 29 | printf '<!DOCTYPE html><html><head><title>$$TITLE</title></head><body>' > templates/header.html |
| 30 | printf '</body></html>' > templates/footer.html |
| 31 | printf '' > templates/index_header.html |
| 32 | printf '<p>Tags:' > templates/tag_list_header.html |
| 33 | printf '<a href="$$URL">$$NAME</a>' > templates/tag_entry.html |
| 34 | printf ', ' > templates/tag_separator.html |
| 35 | printf '</p>' > templates/tag_list_footer.html |
| 36 | printf '<h2>Articles</h2><ul>' > templates/article_list_header.html |
| 37 | printf '<li><a href="$$URL">$$DATE $$TITLE</a></li>' > templates/article_entry.html |
| 38 | printf '' > templates/article_separator.html |
| 39 | printf '</ul>' > templates/article_list_footer.html |
| 40 | printf '' > templates/index_footer.html |
| 41 | printf '' > templates/tag_index_header.html |
| 42 | printf '' > templates/tag_index_footer.html |
| 43 | printf '<h1>$$TITLE</h1>' > templates/article_header.html |
| 44 | printf '' > templates/article_footer.html |
| 45 | printf 'blog\n' > .git/info/exclude |
| 46 | |
| 47 | build: blog/index.html tagpages $(patsubst $(BLOG_SRC)/%.md,blog/%.html,$(ARTICLES)) $(patsubst %,blog/%.xml,$(BLOG_FEEDS)) |
| 48 | |
| 49 | deploy: build |
| 50 | rsync -rLtvz $(BLOG_RSYNC_OPTS) blog/ data/ $(BLOG_REMOTE) |
| 51 | |
| 52 | clean: |
| 53 | rm -rf blog tags |
| 54 | |
| 55 | config: |
| 56 | printf 'BLOG_REMOTE:=%s\n' \ |
| 57 | '$(shell printf "Blog remote (eg: host:/var/www/html): ">/dev/tty; head -n1)' \ |
| 58 | > $@ |
| 59 | |
| 60 | tags/%: $(BLOG_SRC)/%.md |
| 61 | mkdir -p tags |
| 62 | grep -i '^; *tags:' "$<" | cut -d: -f2- | sed 's/ */\n/g' | sed '/^$$/d' | sort -u > $@ |
| 63 | |
| 64 | blog/index.html: $(ARTICLES) $(TAGFILES) $(addprefix templates/,$(addsuffix .html,header index_header tag_list_header tag_entry tag_separator tag_list_footer article_list_header article_entry article_separator article_list_footer index_footer footer)) |
| 65 | mkdir -p blog |
| 66 | TITLE="$(BLOG_TITLE)"; \ |
| 67 | export TITLE; \ |
| 68 | envsubst < templates/header.html > $@; \ |
| 69 | envsubst < templates/index_header.html >> $@; \ |
| 70 | envsubst < templates/tag_list_header.html >> $@; \ |
| 71 | first=true; \ |
| 72 | for t in $(shell cat $(TAGFILES) | sort -u); do \ |
| 73 | "$$first" || envsubst < templates/tag_separator.html; \ |
| 74 | NAME="$$t" \ |
| 75 | URL="@$$t.html" \ |
| 76 | envsubst < templates/tag_entry.html; \ |
| 77 | first=false; \ |
| 78 | done >> $@; \ |
| 79 | envsubst < templates/tag_list_footer.html >> $@; \ |
| 80 | envsubst < templates/article_list_header.html >> $@; \ |
| 81 | first=true; \ |
| 82 | for f in $(ARTICLES); do \ |
| 83 | printf '%s ' "$$f"; \ |
| 84 | git log --diff-filter=A --date="format:%s $(BLOG_DATE_FORMAT_INDEX)" --pretty=format:'%ad%n' -- "$$f"; \ |
| 85 | done | sort -k2nr | cut -d" " -f1,3- | while IFS=" " read -r FILE DATE; do \ |
| 86 | "$$first" || envsubst < templates/article_separator.html; \ |
| 87 | URL="`printf '%s' "\$$FILE" | sed 's,^$(BLOG_SRC)/\(.*\).md,\1,'`.html" \ |
| 88 | DATE="$$DATE" \ |
| 89 | TITLE="`head -n1 "\$$FILE" | sed -e 's/^# //g'`" \ |
| 90 | envsubst < templates/article_entry.html; \ |
| 91 | first=false; \ |
| 92 | done >> $@; \ |
| 93 | envsubst < templates/article_list_footer.html >> $@; \ |
| 94 | envsubst < templates/index_footer.html >> $@; \ |
| 95 | envsubst < templates/footer.html >> $@; \ |
| 96 | |
| 97 | |
| 98 | blog/tag/%.html: $(ARTICLES) $(addprefix templates/,$(addsuffix .html,header tag_header index_entry tag_footer footer)) |
| 99 | |
| 100 | .PHONY: tagpages |
| 101 | tagpages: $(TAGFILES) |
| 102 | +$(BLOG) $(patsubst %,blog/@%.html,$(shell cat $(TAGFILES) | sort -u)) |
| 103 | |
| 104 | blog/@%.html: $(TAGFILES) $(addprefix templates/,$(addsuffix .html,header tag_index_header tag_list_header tag_entry tag_separator tag_list_footer article_list_header article_entry article_separator article_list_footer tag_index_footer footer)) |
| 105 | mkdir -p blog |
| 106 | TITLE="Articles tagged $*"; \ |
| 107 | TAGS="$*"; \ |
| 108 | export TITLE; \ |
| 109 | export TAGS; \ |
| 110 | envsubst < templates/header.html > $@; \ |
| 111 | envsubst < templates/tag_index_header.html >> $@; \ |
| 112 | envsubst < templates/article_list_header.html >> $@; \ |
| 113 | first=true; \ |
| 114 | for f in $(shell grep -FH '$*' $(TAGFILES) | sed 's,^tags/\([^:]*\):.*,$(BLOG_SRC)/\1.md,'); do \ |
| 115 | printf '%s ' "$$f"; \ |
| 116 | git log --diff-filter=A --date="format:%s $(BLOG_DATE_FORMAT_INDEX)" --pretty=format:'%ad%n' -- "$$f"; \ |
| 117 | done | sort -k2nr | cut -d" " -f1,3- | while IFS=" " read -r FILE DATE; do \ |
| 118 | "$$first" || envsubst < templates/article_separator.html; \ |
| 119 | URL="`printf '%s' "\$$FILE" | sed 's,^$(BLOG_SRC)/\(.*\).md,\1,'`.html" \ |
| 120 | DATE="$$DATE" \ |
| 121 | TITLE="`head -n1 "\$$FILE" | sed -e 's/^# //g'`" \ |
| 122 | envsubst < templates/article_entry.html; \ |
| 123 | first=false; \ |
| 124 | done >> $@; \ |
| 125 | envsubst < templates/article_list_footer.html >> $@; \ |
| 126 | envsubst < templates/tag_index_footer.html >> $@; \ |
| 127 | envsubst < templates/footer.html >> $@; \ |
| 128 | |
| 129 | |
| 130 | blog/%.html: $(BLOG_SRC)/%.md $(addprefix templates/,$(addsuffix .html,header article_header article_footer footer)) |
| 131 | mkdir -p blog |
| 132 | TITLE="$(shell head -n1 $<)"; \ |
| 133 | export TITLE; \ |
| 134 | AUTHOR="$(shell git log -n 1 --reverse --format="%cn" -- "$<")"; \ |
| 135 | export AUTHOR; \ |
| 136 | DATE_POSTED="$(shell git log --diff-filter=A --date="format:$(BLOG_DATE_FORMAT)" --pretty=format:'%ad' -- "$<")"; \ |
| 137 | export DATE_POSTED; \ |
| 138 | DATE_EDITED="$(shell git log -n 1 --date="format:$(BLOG_DATE_FORMAT)" --pretty=format:'%ad' -- "$<")"; \ |
| 139 | export DATE_EDITED; \ |
| 140 | TAGS="$(shell grep -i '^; *tags:' "$<" | cut -d: -f2- | paste -sd ',')"; \ |
| 141 | export TAGS; \ |
| 142 | DESCRIPTION="$(shell grep -i '^; *description:' "$<" | cut -d: -f2-)"; \ |
| 143 | export DESCRIPTION; \ |
| 144 | envsubst < templates/header.html > $@; \ |
| 145 | envsubst < templates/article_header.html >> $@; \ |
| 146 | sed -e 1d \ |
| 147 | -e '/^;/d' \ |
| 148 | -e 's/&/\&/g' \ |
| 149 | -e 's/</\</g' \ |
| 150 | -e 's/>/\>/g' \ |
| 151 | -e '/^```$$/{s,.*,,;x;p;/^<\/code>/d;s,.*,<pre><code>,;bT}' \ |
| 152 | -e 'x;/<\/code>/{x;s,\$$,\$,g;$$G;p;d};x' \ |
| 153 | -e 's,\\\$$,\$,g' \ |
| 154 | -e '/^####/{s,^####,<h4>,;s,$$,</h4>,;H;s,.*,,;x;p;d}' \ |
| 155 | -e '/^###/{s,^###,<h3>,;s,$$,</h3>,;H;s,.*,,;x;p;d}' \ |
| 156 | -e '/^##/{s,^##,<h2>,;s,$$,</h2>,;H;s,.*,,;x;p;d}' \ |
| 157 | -e '/^#/{s,^#,<h1>,;s,$$,</h1>,;H;s,.*,,;x;p;d}' \ |
| 158 | -e 's,`\([^`]*\)`,<code>\1</code>,g' \ |
| 159 | -e 's,\*\*\(\([^*<>][^*<>]*\*\?\)*\)\*\*,<b>\1</b>,g' \ |
| 160 | -e 's,\*\([^*<>][^*<>]*\)\*,<i>\1</i>,g' \ |
| 161 | -e 's,!\[\([^]]*\)\](\([^)]*\)),<img src="\2" alt="\1"/>,g' \ |
| 162 | -e 's,\[\([^]]*\)\](\([^)]*\)),<a href="\2">\1</a>,g' \ |
| 163 | -e '/^- /{s,^- ,<li>,;s,$$,</li>,;x;/^<\/ul>/{x;bL};p;s,.*,<ul>,;bT}' \ |
| 164 | -e '/^[1-9][0-9]*\. /{s,^[0-9]*\. ,<li>,;s,$$,</li>,;x;/^<\/ol>/{x;bL};p;s,.*,<ol>,;bT}' \ |
| 165 | -e '/^$$/{x;/^$$/d;p;d}' \ |
| 166 | -e 'x;/^$$/{s,.*,<p>,;bT};x' \ |
| 167 | -e ':L;$$G;p;d' \ |
| 168 | -e ':T;p;:t;s,<\([^/>][^>]*\)>\(\(<[^/>][^>]*>\)*\),\2</\1>,;/<[^\/>]/bt;x;/^$$/{$${x;p};d};bL' \ |
| 169 | "$<" | envsubst >> $@; \ |
| 170 | envsubst < templates/article_footer.html >> $@; \ |
| 171 | envsubst < templates/footer.html >> $@; \ |
| 172 | |
| 173 | blog/rss.xml: $(ARTICLES) |
| 174 | printf '<?xml version="1.0" encoding="UTF-8"?>\n<rss version="2.0">\n<channel>\n<title>%s</title>\n<link>%s</link>\n<description>%s</description>\n' \ |
| 175 | "$(BLOG_TITLE)" "$(BLOG_URL_ROOT)" "$(BLOG_DESCRIPTION)" > $@ |
| 176 | for f in $(ARTICLES); do \ |
| 177 | printf '%s ' "$$f"; \ |
| 178 | git log --diff-filter=A --date="format:%s %a, %d %b %Y %H:%M:%S %z" --pretty=format:'%ad%n' -- "$$f"; \ |
| 179 | done | sort -k2nr | head -n $(BLOG_FEED_MAX) | cut -d" " -f1,3- | while IFS=" " read -r FILE DATE; do \ |
| 180 | printf '<item>\n<title>%s</title>\n<link>%s</link>\n<guid>%s</guid>\n<pubDate>%s</pubDate>\n<description>%s</description>\n</item>\n' \ |
| 181 | "`head -n 1 $$FILE | sed 's/&/&/'`" \ |
| 182 | "$(BLOG_URL_ROOT)/`basename $$FILE .md`.html" \ |
| 183 | "$(BLOG_URL_ROOT)/`basename $$FILE .md`.html" \ |
| 184 | "$$DATE" \ |
| 185 | "`sed -n 's/&/&/;1d;/^$$/{2{d;b};q};p' < $$FILE`"; \ |
| 186 | done >> $@ |
| 187 | printf '</channel>\n</rss>\n' >> $@ |
| 188 | |
| 189 | blog/atom.xml: $(ARTICLES) |
| 190 | printf '<?xml version="1.0" encoding="UTF-8"?>\n<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">\n<title type="text">%s</title>\n<subtitle type="text">%s</subtitle>\n<updated>%s</updated>\n<link rel="alternate" type="text/html" href="%s"/>\n<id>%s</id>\n<link rel="self" type="application/atom+xml" href="%s"/>\n' \ |
| 191 | "$(BLOG_TITLE)" "$(BLOG_DESCRIPTION)" "$(shell date +%Y-%m-%dT%H:%M:%SZ)" "$(BLOG_URL_ROOT)" "$(BLOG_URL_ROOT)/atom.xml" "$(BLOG_URL_ROOT)/atom.xml" > $@ |
| 192 | for f in $(ARTICLES); do \ |
| 193 | printf '%s ' "$$f"; \ |
| 194 | git log --diff-filter=A --date="format:%s %Y-%m-%dT%H:%M:%SZ" --pretty=format:'%ad %aN%n' -- "$$f"; \ |
| 195 | done | sort -k2nr | head -n $(BLOG_FEED_MAX) | cut -d" " -f1,3- | while IFS=" " read -r FILE DATE AUTHOR; do \ |
| 196 | printf '<entry>\n<title type="text">%s</title>\n<link rel="alternate" type="text/html" href="%s"/>\n<id>%s</id>\n<published>%s</published>\n<updated>%s</updated>\n<author><name>%s</name></author>\n<summary type="text">%s</summary>\n</entry>\n' \ |
| 197 | "`head -n 1 $$FILE | sed 's/&/&/'`" \ |
| 198 | "$(BLOG_URL_ROOT)/`basename $$FILE .md`.html" \ |
| 199 | "$(BLOG_URL_ROOT)/`basename $$FILE .md`.html" \ |
| 200 | "$$DATE" \ |
| 201 | "`git log -n 1 --date="format:%Y-%m-%dT%H:%M:%SZ" --pretty=format:'%ad' -- "$$FILE"`" \ |
| 202 | "$$AUTHOR" \ |
| 203 | "`sed -n 's/&/&/;1d;/^$$/{2{d;b};q};p' < $$FILE`"; \ |
| 204 | done >> $@ |
| 205 | printf '</feed>\n' >> $@ |