Housekeeping on Thursday, 12th of February, Anno Domini MMIX, at the hour of the...
[git/dscho.git] / upload.sh
blobeb6b398c53cb5958fba1eff42822d6e77f59c3de
1 #!/bin/sh
3 # This is a simple script that will produce my blog on repo.or.cz
5 # The idea is to have source-<timestamp>.txt files as input, having the
6 # stories, and this script turning them into nice HTML, committing
7 # everything, and then pushing it to my repository.
9 # The blog will then be served using gitweb.
11 # To make it easier on me, if a file "new" exists, it is automatically
12 # renamed using the current timestamp.
14 # How to use:
16 # $ mkdir my-blog
17 # $ cd my-blog
18 # $ git init
20 # Then symlink or copy this file (upload.sh); you can track it or add it
21 # to .gitignore, does not matter.
23 # Add a remote "origin" (you might want to track only the appropriate branch
24 # if the repository contains other branches, too), add a background image,
25 # and then set the config variables gitweb.url, blog.title, blog.background
26 # and blog.branch appropriately.
28 # Example:
30 # $ git remote add -t blog repo.or.cz:/srv/git/git/dscho.git/
31 # $ git symbolic-ref HEAD refs/heads/blog
32 # $ cp ~/images/background.jpg ./
33 # $ git config gitweb.url http://repo.or.cz/w/git/dscho.git
34 # $ git config blog.title "Dscho's blog"
35 # $ git config blog.background background.jpg
36 # $ git config blog.branch blog
38 # Now you can start writing posts, by creating a file called "new", and
39 # calling ./upload.sh to commit the post together with the images and
40 # push all.
42 # Note that no file names may contain spaces.
44 # TODO: document the "syntax" of the source-*.txt files
47 # make sure we're in the correct working directory
48 cd "$(dirname "$0")"
50 GITWEBURL="$(git config gitweb.url)"
51 test -z "$GITWEBURL" && {
52 echo "Please set gitweb.url in the Git config first!" >&2
53 exit 1
56 get_config () {
57 value=$(git config blog.$1)
58 test -z "$value" && value=$2
59 echo $value
62 BACKGROUNDIMG=$(get_config background paper.jpg)
63 TITLE=$(get_config title "Dscho's Git log")
64 MAXENTRIES=$(get_config maxPostsPerPage 10)
65 BRANCH=$(get_config branch blog)
67 URLPREFIX="$(dirname "$GITWEBURL")"/
68 REMOTEREPOSITORY="$(basename "$GITWEBURL")"
69 case "$GITWEBURL" in
70 *'?'*) BLOBPLAIN="$REMOTEREPOSITORY;a=blob_plain";;
71 */) URLPREFIX=$GITWEBURL; BLOBPLAIN="a=blob_plain";;
72 *) BLOBPLAIN="$REMOTEREPOSITORY?a=blob_plain";;
73 esac
74 URL="$BLOBPLAIN;hb=$BRANCH;f="
75 ORIGURL=$URL
76 NEW=new
77 OUTPUT=index.html
78 RSS=blog.rss
79 TEST=test.html
80 THIS=$0
82 LC_ALL=C
83 export LC_ALL
85 move_new_entry_back () {
86 test -f source-$now.txt &&
87 mv source-$now.txt $NEW &&
88 git rm --cached -f source-$now.txt
91 die () {
92 move_new_entry_back
93 echo "$*" >&2
94 exit 1
97 strip_prefix () {
98 echo "${1#$2}"
101 chomp () {
102 strip_prefix "${1%$3}" "$2"
105 nth () {
106 # add illogical suffix
107 case "$1" in
108 *1?|*[04-9]) echo "$1th";;
109 *1) echo "$1st";;
110 *2) echo "$1nd";;
111 *3) echo "$1rd";;
112 esac
115 make_chinese_hour () {
116 case $1 in
117 23|00) echo Rat;;
118 01|02) echo Buffalo;;
119 03|04) echo Tiger;;
120 05|06) echo Rabbit;;
121 07|08) echo Dragon;;
122 09|10) echo Snake;;
123 11|12) echo Horse;;
124 13|14) echo Goat;;
125 15|16) echo Monkey;;
126 17|18) echo Rooster;;
127 19|20) echo Dog;;
128 21|22) echo Pig;;
129 esac
132 digit_to_roman () {
133 case $1 in
134 1) echo $2;;
135 2) echo $2$2;;
136 3) echo $2$2$2;;
137 4) echo $2$3;;
138 5) echo $3;;
139 6) echo $3$2;;
140 7) echo $3$2$2;;
141 8) echo $3$2$2$2;;
142 9) echo $2$4;;
143 esac
146 make_roman_number () {
147 case $1 in
148 '') ;;
149 ?) digit_to_roman $1 I V X;;
150 ??) echo $(digit_to_roman ${1%?} X L C)$(make_roman_number ${1#?});;
151 ???) echo $(digit_to_roman ${1%??} C D M)$(make_roman_number ${1#?});;
152 ????) echo $(digit_to_roman ${1%???} M)$(make_roman_number ${1#?});;
153 esac
156 make_date () {
157 printf "%s, %s of %s, Anno Domini %s, at the hour of the %s\n" \
158 $(date +%A -d @$1) \
159 $(nth $(date +%e -d @$1)) \
160 $(date +%B -d @$1) \
161 $(make_roman_number $(date +%Y -d @$1)) \
162 $(make_chinese_hour $(date +%H -d @$1))
165 # make an argument for sed, to replace $1..$1 by <$2>..</$2>
166 markup_substitution () {
167 # sed does not know minimal match with .*, only greedy one
168 middle=
169 middleend=
170 delim=$1
171 right_no=3
172 while test ! -z "$delim"
174 right_no=$(($right_no+1))
175 test $right_no -gt 9 &&
176 die "Invalid markup pattern: $1"
177 first=$(expr "$delim" : '\(.\)')
178 delim=${delim#$first}
179 test -z "$delim" && {
180 middle="$middle\\([^$first]\\|$first[A-Za-z0-9]\\)"
181 break
183 middle="$middle\\([^$first]\\|$first"
184 middleend="$middleend\\)"
185 done
187 left="\\(^\\|[^A-Za-z0-9]\\)$1\\($middle$middleend*\\)"
188 right="$1\\($\\|[^A-Za-z0-9]\\)"
189 echo "s/$left$right/\\\\1<$2>\\\\2<\/$2>\\\\$right_no/g"
192 space80=' '
193 space80="$space80$space80$space80 "
194 # transform markup in stdin to HTML
195 markup () {
196 case "$*" in
197 *invert-bash*) bash_bg=white; bash_fg=black;;
198 *) bash_bg=black; bash_fg=white;;
199 esac
200 sed -e 's!^$!</p><p>!' \
201 -e "$(markup_substitution "''" i)" \
202 -e "$(markup_substitution "_" u)" \
203 -e 's!IMHO!in my humble opinion!g' \
204 -e 's!BTW!By the way,!g' \
205 -e 's!repo.or.cz!<a href=http://&>&</a>!g' \
206 -e 's!:-)!\&#x263a;!g' \
207 -e "s!\\[\\[\(Image\|SVG\):.*!$THIS handle &!e" \
208 -e 's!<bash>!<table\
209 border=1 bgcolor='$bash_bg'>\
210 <tr><td bgcolor=lightblue colspan=3>\
211 <pre>'"$space80"'</pre>\
212 </td></tr>\
213 <tr><td>\
214 <table cellspacing=5 border=0\
215 style="color:'$bash_fg';">\
216 <tr><td>\
217 <pre>!' \
218 -e 's!</bash>!</pre>\
219 </td></tr>\
220 </table>\
221 </td></tr>\
222 </table>!' \
226 # output lines containing <timestamp> <filename> <title>
227 get_blog_entries () {
228 for file in $(ls -r source-*.txt)
230 timestamp=$(chomp $file source- .txt)
231 title="$(sed 1q < $file | markup)"
232 echo "$timestamp $file $title"
233 done
236 get_last_removed_entry () {
237 git log --pretty=format: --name-only --diff-filter=D HEAD |
238 while read line
240 case "$line" in
241 source-*.txt) file=$line;;
242 '') test -z "$file" || {
243 echo "$file"
244 break
246 esac
247 done
250 box_count=0
251 begin_box () {
252 test $box_count = 0 || echo "<br>"
253 echo "<table width=$toc_width bgcolor=#e0e0e0 border=1>"
254 echo "<tr><th>$1</th></tr>"
255 echo "<tr><td>"
258 end_box () {
259 echo "</td></tr></table>"
260 box_count=$(($box_count+1))
263 # make HTML page
264 make_html () {
265 body_style="width:800px"
266 body_style="$body_style;background-image:url($URL$BACKGROUNDIMG)"
267 body_style="$body_style;background-repeat:repeat-y"
268 body_style="$body_style;background-attachment:scroll"
269 body_style="$body_style;padding:0px;"
270 text_style="width:610px"
271 text_style="$text_style;margin-left:120px"
272 text_style="$text_style;margin-top:50px"
273 text_style="$text_style;align:left"
274 text_style="$text_style;vertical-align:top;"
275 cat << EOF
276 <html>
277 <head>
278 <title>$TITLE</title>
279 <meta http-equiv="Content-Type"
280 content="text/html; charset=UTF-8"/>
281 </head>
282 <body style="$body_style">
283 <div style="$text_style">
284 <h1>$TITLE</h1>
286 indent='\t\t\t'
288 # make toc
289 toc_width=400px
290 toc_style="position:absolute;top:50px;left:810px;width=$toc_width"
292 echo "<div style=\"$toc_style\">"
293 begin_box "Table of contents:"
294 echo '<p><ul>'
295 get_blog_entries |
296 while read timestamp filename title
298 date="$(date +"%d %b %Y" -d @$timestamp)"
299 echo "<li><a href=#$timestamp>$date $title</a>"
300 done
301 echo '</ul></p>'
302 file=
303 last_removed_entry=$(get_last_removed_entry)
304 test -z "$last_removed_entry" || {
305 commit=$(git log --pretty=format:%H --diff-filter=AM \
306 -- $last_removed_entry |
307 head -n 1)
308 previous="$BLOBPLAIN;hb=$commit"
309 echo "<a href=$previous;f=index.html>Older posts</a>"
311 end_box
313 # RSS feed
314 rss_style="background-color:orange;text-decoration:none"
315 rss_style="$rss_style;color:white;font-family:sans-serif;"
316 echo '<br>'
317 echo '<div style="text-align:right;">'
318 echo "<a href=\"$ORIGURL$RSS\""
319 echo ' title="Subscribe to my RSS feed"'
320 echo ' class="rss" rel="nofollow"'
321 echo " style=\"$rss_style\">RSS</a>"
322 echo '</div>'
324 # About
325 test -f about.html && {
326 begin_box "About this blog:"
327 cat about.html
328 end_box
331 # Links
332 test -f links.html && {
333 begin_box "Links:"
334 cat links.html
335 end_box
338 # Google AdSense
339 test -z "$DRYRUN" && test -f google.adsense && {
340 begin_box "Google Ads:"
341 cat google.adsense
342 end_box
345 echo '</div>'
346 } | sed -s "s/^/$indent/"
349 # timestamps will not need padding to sort correctly, for some time...
350 get_blog_entries |
351 while read timestamp filename title
353 echo "<h6>$(make_date $timestamp)</h6>"
354 echo "<a name=$timestamp>"
355 echo "<h2>$title</h2>"
356 echo ""
357 echo "<p>"
358 sed 1d < $filename | markup
359 echo "</p>"
360 done |
361 sed -e "s/^./$indent&/" \
362 -e "/<pre>/,/<\/pre>/s/^$indent//"
364 cat << EOF
365 </div>
366 </body>
367 </html>
371 generate_rss () {
372 echo '<?xml version="1.0" encoding="utf-8"?>'
373 echo '<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">'
374 echo '<channel>'
375 echo "<title>Dscho's blog</title>"
376 echo "<link>$URLPREFIX${URL}index.html</link>"
377 self="$URLPREFIX$ORIGURL$RSS"
378 selfattribs='rel="self" type="application/rss+xml"'
379 echo "<atom:link href=\"$self\" $selfattribs/>"
380 echo '<description>A few stories told by Dscho</description>'
381 echo "<lastBuildDate>$(date --rfc-2822)</lastBuildDate>"
382 echo '<language>en-us</language>'
384 get_blog_entries |
385 while read timestamp filename title
387 # remove all tags
388 title=$(echo "$title" | sed 's/<[^>]*>//g')
389 echo '<item>'
390 echo "<title>$title</title>"
391 echo "<link>$URLPREFIX${URL}index.html#$timestamp</link>"
392 echo "<guid>$URLPREFIX${URL}index.html#$timestamp</guid>"
393 echo "<pubDate>$(date --rfc-2822 -d @$timestamp)</pubDate>"
394 description="$(cat < $filename | markup invert-bash)"
395 echo "<description><![CDATA[$description]]></description>"
396 echo "</item>"
397 done
399 echo '</channel>'
400 echo '</rss>'
403 get_image_files () {
404 git ls-files |
405 grep -v '\.\(rss\|html\|gitignore\|in\|sh\|txt\|adsense\)$'
408 remove_old_entries () {
409 count=$(ls source-*.txt | wc -l)
410 test $MAXENTRIES -ge $count && return
412 for file in source-*.txt
414 test $MAXENTRIES -lt $count || break
415 git rm $file > /dev/null || return 1
416 count=$(($count-1))
417 echo $file
418 done
420 # remove no-longer referenced images
421 image_files=$(get_image_files)
422 referenced_files="$(cat source-*.txt |
423 tr ']|' '\n' |
424 sed -ne 's/\[\[\(Image\|SVG\)://p') $BACKGROUNDIMG"
425 for file in $(echo $image_files $referenced_files $referenced_files |
426 tr ' ' '\n' | sort | uniq -u)
428 git rm $file > /dev/null || return 1
429 echo $file
430 done
433 # never, ever have spaces in the file names
434 commit_new_images () {
435 files="$(remove_old_entries) $RSS $BACKGROUNDIMG" ||
436 die "Could not remove old entries"
439 generate_rss > $RSS &&
440 git add $RSS ||
441 die "Could not generate $RSS"
443 for image in $(cat source-* |
444 tr ' ]|' '\n' |
445 sed -n 's/.*\[\[\(Image\|SVG\)://p' |
446 sort |
447 uniq)
449 git add $image || die "Could not git add image $image"
450 files="$files $image"
451 done
453 git update-index --refresh &&
454 git diff-files --quiet -- $files &&
455 git diff --cached --quiet HEAD -- $files ||
456 git commit -s -m "Housekeeping on $(make_date $now)" $files
459 get_image_url () {
460 test ! -z "$DRYRUN" && echo "$1" && return
461 rev=$(git rev-list -1 HEAD -- $1)
462 test -z "$rev" && die "No revision found for $1"
463 echo "$BLOBPLAIN;hb=$rev;f=$1"
466 handle_svg_file () {
467 # for some reason, Firefox adds scrollbars, so nudge the width a bit
468 width=$(sed -ne 's/.* width="\([^"]*\).*/\1/p' -e '/<metadata/q' < "$1")
469 test -z "$width" || width=" width=$(($width+5))"
470 url=$(get_image_url "$1")
471 cat << EOF
472 <center>
473 <table border=0>
474 <tr>
475 <td align=center>
476 <embed type="image/svg+xml"
477 src="$url"$width />
478 </td>
479 </tr>
480 <tr>
481 <td align=center>
482 <a href=$url>$1</a>
483 </td>
484 </tr>
485 </table>
486 </center>
490 handle_image_file () {
491 echo "<center><img src=$(get_image_url "${1%% *}") ${1#* }></center>"
496 # parse command line option
497 case "$1" in
498 *dry*) DRYRUN=1; export DRYRUN; shift;;
499 *show*) firefox "$(pwd)"/$TEST; exit;;
500 *remote*) firefox $URLPREFIX$URL$OUTPUT; exit;;
501 handle)
502 shift
503 case "$1" in
504 "[[SVG:"*) handle_svg_file "$(chomp "$*" '\[\[SVG:' '\]\]')";;
505 "[[Image:"*) handle_image_file "$(chomp "$*" '\[\[Image:' '\]\]')";;
506 esac
507 exit
509 '') ;;
510 *) die "Unknown command: $1";;
511 esac
513 test "$#" = 0 ||
514 die "Usage: $0 [--dry-run]"
516 # make sure we're on the correct branch
517 test refs/heads/$BRANCH = $(git symbolic-ref HEAD) ||
518 die "Not on branch $BRANCH"
520 # make sure there are no uncommitted changes
521 git update-index --refresh &&
522 git diff-files --quiet ||
523 die "Have unstaged changes!"
525 # rename the new blog entry if it exists
526 now=$(date +%s)
527 test ! -f $NEW || {
528 mv -i $NEW source-$now.txt &&
529 git add source-$now.txt
530 } ||
531 die "Could not rename source.txt"
533 # commit the images that are referenced and not yet committed
534 test ! -z "$DRYRUN" ||
535 commit_new_images ||
536 die "Could not commit new images"
538 # to find the images reliably, we have to use the commit name, not the branch
539 # we use the latest commit touching an image file.
540 IMAGEFILES="$(get_image_files)"
541 REV=$(git rev-list -1 HEAD -- $IMAGEFILES)
542 test -z "$REV" && REV=$BRANCH
543 URL="$BLOBPLAIN;hb=$REV;f="
545 if test ! -z "$DRYRUN"
546 then
547 # Output to test.html and have local links into the current directory
548 OUTPUT=$TEST
549 URL=
552 make_html > $OUTPUT || die "Could not write $OUTPUT"
554 test ! -z "$DRYRUN" && {
555 move_new_entry_back
556 exit
559 git add $OUTPUT &&
560 git commit -s -m "Update $(make_date $now)" &&
561 git push origin +$BRANCH