Commit some images on Sunday, 25th of January, Anno Domini MMIX, at the hour of the...
[git/dscho.git] / upload.sh
blobc867ef37866073db7db68e756b1afc932aa56f1d
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 blog")
64 MAXENTRIES=$(get_config maxPostsPerPage 10)
65 BRANCH=$(get_config branch blog)
67 URLPREFIX="$(dirname "$GITWEBURL")"/
68 REMOTEREPOSITORY="$(basename "$GITWEBURL")"
69 URL="$REMOTEREPOSITORY?a=blob_plain;hb=$BRANCH;f="
70 ORIGURL=$URL
71 NEW=new
72 OUTPUT=index.html
73 RSS=blog.rss
74 TEST=test.html
75 THIS=$0
77 LC_ALL=C
78 export LC_ALL
80 move_new_entry_back () {
81 test -f source-$now.txt &&
82 mv source-$now.txt $NEW &&
83 git rm --cached -f source-$now.txt
86 die () {
87 move_new_entry_back
88 echo "$*" >&2
89 exit 1
92 strip_prefix () {
93 echo "${1#$2}"
96 chomp () {
97 strip_prefix "${1%$3}" "$2"
100 nth () {
101 # add illogical suffix
102 case "$1" in
103 *1?|*[04-9]) echo "$1th";;
104 *1) echo "$1st";;
105 *2) echo "$1nd";;
106 *3) echo "$1rd";;
107 esac
110 make_chinese_hour () {
111 case $1 in
112 23|00) echo Rat;;
113 01|02) echo Buffalo;;
114 03|04) echo Tiger;;
115 05|06) echo Rabbit;;
116 07|08) echo Dragon;;
117 09|10) echo Snake;;
118 11|12) echo Horse;;
119 13|14) echo Goat;;
120 15|16) echo Monkey;;
121 17|18) echo Rooster;;
122 19|20) echo Dog;;
123 21|22) echo Pig;;
124 esac
127 digit_to_roman () {
128 case $1 in
129 1) echo $2;;
130 2) echo $2$2;;
131 3) echo $2$2$2;;
132 4) echo $2$3;;
133 5) echo $3;;
134 6) echo $3$2;;
135 7) echo $3$2$2;;
136 8) echo $3$2$2$2;;
137 9) echo $2$4;;
138 esac
141 make_roman_number () {
142 case $1 in
143 '') ;;
144 ?) digit_to_roman $1 I V X;;
145 ??) echo $(digit_to_roman ${1%?} X L C)$(make_roman_number ${1#?});;
146 ???) echo $(digit_to_roman ${1%??} C D M)$(make_roman_number ${1#?});;
147 ????) echo $(digit_to_roman ${1%???} M)$(make_roman_number ${1#?});;
148 esac
151 make_date () {
152 printf "%s, %s of %s, Anno Domini %s, at the hour of the %s\n" \
153 $(date +%A -d @$1) \
154 $(nth $(date +%e -d @$1)) \
155 $(date +%B -d @$1) \
156 $(make_roman_number $(date +%Y -d @$1)) \
157 $(make_chinese_hour $(date +%H -d @$1))
160 # make an argument for sed, to replace $1..$1 by <$2>..</$2>
161 markup_substitution () {
162 case "$1" in
163 ?) echo "s/$1\\([^$1]*\\)$1/<$2>\\\\1<\/$2>/g";;
165 tmp="[^${1%?}]*"
166 tmp2="\\|${1%?}[^${1#?}]$tmp"
167 tmp3="\\($tmp\\($tmp2\\($tmp2\\($tmp2\\)\\)\\)\\)"
168 echo "s/$1$tmp3$1/<$2>\\\\1<\/$2>/g"
170 esac
173 # transform markup in stdin to HTML
174 markup () {
175 case "$*" in
176 *invert-bash*) bash_bg=white; bash_fg=black;;
177 *) bash_bg=black; bash_fg=white;;
178 esac
179 sed -e 's!^$!</p><p>!' \
180 -e "$(markup_substitution "''" i)" \
181 -e "$(markup_substitution "_" u)" \
182 -e 's!IMHO!in my humble opinion!g' \
183 -e 's!BTW!By the way,!g' \
184 -e 's!repo.or.cz!<a href=http://&>&</a>!g' \
185 -e 's!:-)!\&#x263a;!g' \
186 -e "s!\\[\\[\(Image\|SVG\):.*!$THIS handle &!e" \
187 -e 's!<bash>!<table\
188 border=1 bgcolor='$bash_bg'>\
189 <tr><td bgcolor=lightblue colspan=3>\
190 \&nbsp;\
191 </td></tr>\
192 <tr><td>\
193 <table cellspacing=5 border=0\
194 style="color:'$bash_fg';">\
195 <tr><td>\
196 <pre>!' \
197 -e 's!</bash>! </pre>\
198 </td></tr>\
199 </table>\
200 </td></tr>\
201 </table>!' \
205 # output lines containing <timestamp> <filename> <title>
206 get_blog_entries () {
207 for file in $(ls -r source-*.txt)
209 timestamp=$(chomp $file source- .txt)
210 title="$(sed 1q < $file | markup)"
211 echo "$timestamp $file $title"
212 done
215 get_last_removed_entry () {
216 git log --pretty=format: --name-only --diff-filter=D HEAD |
217 while read line
219 case "$line" in
220 source-*.txt) file=$line;;
221 '') test -z "$file" || {
222 echo "$file"
223 break
225 esac
226 done
229 box_count=0
230 begin_box () {
231 test $box_count = 0 || echo "<br>"
232 echo "<table width=$toc_width bgcolor=#e0e0e0 border=1>"
233 echo "<tr><th>$1</th></tr>"
234 echo "<tr><td>"
237 end_box () {
238 echo "</td></tr></table>"
239 box_count=$(($box_count+1))
242 # make HTML page
243 make_html () {
244 body_style="width:800px"
245 body_style="$body_style;background-image:url($URL$BACKGROUNDIMG)"
246 body_style="$body_style;background-repeat:repeat-y"
247 body_style="$body_style;background-attachment:scroll"
248 body_style="$body_style;padding:0px;"
249 text_style="width:610px"
250 text_style="$text_style;margin-left:120px"
251 text_style="$text_style;margin-top:50px"
252 text_style="$text_style;align:left"
253 text_style="$text_style;vertical-align:top;"
254 cat << EOF
255 <html>
256 <head>
257 <title>$TITLE</title>
258 <meta http-equiv="Content-Type"
259 content="text/html; charset=UTF-8"/>
260 </head>
261 <body style="$body_style">
262 <div style="$text_style">
263 <h1>$TITLE</h1>
265 indent='\t\t\t'
267 # make toc
268 toc_width=400px
269 toc_style="position:absolute;top:50px;left:810px;width=$toc_width"
271 echo "<div style=\"$toc_style\">"
272 begin_box "Table of contents:"
273 echo '<p><ul>'
274 get_blog_entries |
275 while read timestamp filename title
277 date="$(date +"%d %b %Y" -d @$timestamp)"
278 echo "<li><a href=#$timestamp>$date $title</a>"
279 done
280 echo '</ul></p>'
281 file=
282 last_removed_entry=$(get_last_removed_entry)
283 test -z "$last_removed_entry" || {
284 commit=$(git log --pretty=format:%H --diff-filter=AM \
285 -- $last_removed_entry |
286 head -n 1)
287 previous="$REMOTEREPOSITORY?a=blob_plain;hb=$commit"
288 echo "<a href=$previous;f=index.html>Older posts</a>"
290 end_box
292 # RSS feed
293 rss_style="background-color:orange;text-decoration:none"
294 rss_style="$rss_style;color:white;font-family:sans-serif;"
295 echo '<br>'
296 echo '<div style="text-align:right;">'
297 echo "<a href=\"$ORIGURL$RSS\""
298 echo ' title="Subscribe to my RSS feed"'
299 echo ' class="rss" rel="nofollow"'
300 echo " style=\"$rss_style\">RSS</a>"
301 echo '</div>'
303 # About
304 test -f about.html && {
305 begin_box "About this blog:"
306 cat about.html
307 end_box
310 # Links
311 test -f links.html && {
312 begin_box "Links:"
313 cat links.html
314 end_box
317 # Google AdSense
318 test -z "$DRYRUN" && test -f google.adsense && {
319 begin_box "Google Ads:"
320 cat google.adsense
321 end_box
324 echo '</div>'
325 } | sed -s "s/^/$indent/"
328 # timestamps will not need padding to sort correctly, for some time...
329 get_blog_entries |
330 while read timestamp filename title
332 echo "<h6>$(make_date $timestamp)</h6>"
333 echo "<a name=$timestamp>"
334 echo "<h2>$title</h2>"
335 echo ""
336 echo "<p>"
337 sed 1d < $filename | markup
338 echo "</p>"
339 done |
340 sed -e "s/^./$indent&/" \
341 -e "/<pre>/,/<\/pre>/s/^$indent//"
343 cat << EOF
344 </div>
345 </body>
346 </html>
350 generate_rss () {
351 echo '<?xml version="1.0" encoding="utf-8"?>'
352 echo '<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">'
353 echo '<channel>'
354 echo "<title>Dscho's blog</title>"
355 echo "<link>$URLPREFIX${URL}index.html</link>"
356 self="$URLPREFIX$ORIGURL$RSS"
357 selfattribs='rel="self" type="application/rss+xml"'
358 echo "<atom:link href=\"$self\" $selfattribs/>"
359 echo '<description>A few stories told by Dscho</description>'
360 echo "<lastBuildDate>$(date --rfc-2822)</lastBuildDate>"
361 echo '<language>en-us</language>'
363 get_blog_entries |
364 while read timestamp filename title
366 echo '<item>'
367 echo "<title>$title</title>"
368 echo "<link>$URLPREFIX${URL}index.html#$timestamp</link>"
369 echo "<guid>$URLPREFIX${URL}index.html#$timestamp</guid>"
370 echo "<pubDate>$(date --rfc-2822 -d @$timestamp)</pubDate>"
371 description="$(cat < $filename | markup invert-bash)"
372 echo "<description><![CDATA[$description]]></description>"
373 echo "</item>"
374 done
376 echo '</channel>'
377 echo '</rss>'
380 get_image_files () {
381 git ls-files |
382 grep -v '\.\(rss\|html\|gitignore\|in\|sh\|txt\|adsense\)$'
385 remove_old_entries () {
386 count=$(ls source-*.txt | wc -l)
387 test $MAXENTRIES -ge $count && return
389 for file in source-*.txt
391 test $MAXENTRIES -lt $count || break
392 git rm $file > /dev/null || return 1
393 count=$(($count-1))
394 echo $file
395 done
397 # remove no-longer referenced images
398 image_files=$(get_image_files)
399 referenced_files="$(cat source-*.txt |
400 tr ']|' '\n' |
401 sed -ne 's/\[\[\(Image\|SVG\)://p') $BACKGROUNDIMG"
402 for file in $(echo $image_files $referenced_files $referenced_files |
403 tr ' ' '\n' | sort | uniq -u)
405 git rm $file > /dev/null || return 1
406 echo $file
407 done
410 # never, ever have spaces in the file names
411 commit_new_images () {
412 files="$(remove_old_entries) $RSS $BACKGROUNDIMG" ||
413 die "Could not remove old entries"
416 generate_rss > $RSS &&
417 git add $RSS ||
418 die "Could not generate $RSS"
420 for image in $(cat source-* |
421 tr ' ]|' '\n' |
422 sed -n 's/.*\[\[\(Image\|SVG\)://p' |
423 sort |
424 uniq)
426 git add $image || die "Could not git add image $image"
427 files="$files $image"
428 done
430 git update-index --refresh &&
431 git diff-files --quiet -- $files &&
432 git diff --cached --quiet HEAD -- $files ||
433 git commit -s -m "Commit some images on $(make_date $now)" $files
436 get_image_url () {
437 test ! -z "$DRYRUN" && echo "$1" && return
438 rev=$(git rev-list -1 HEAD -- $1)
439 test -z "$rev" && die "No revision found for $1"
440 echo "$REMOTEREPOSITORY?a=blob_plain;hb=$rev;f=$1"
443 handle_svg_file () {
444 # for some reason, Firefox adds scrollbars, so nudge the width a bit
445 width=$(sed -ne 's/.* width="\([^"]*\).*/\1/p' -e '/<metadata/q' < "$1")
446 test -z "$width" || width=" width=$(($width+5))"
447 url=$(get_image_url "$1")
448 cat << EOF
449 <center>
450 <table border=0>
451 <tr>
452 <td align=center>
453 <embed type="image/svg+xml"
454 src="$url"$width />
455 </td>
456 </tr>
457 <tr>
458 <td align=center>
459 <a href=$url>$1</a>
460 </td>
461 </tr>
462 </table>
463 </center>
467 handle_image_file () {
468 echo "<center><img src=$(get_image_url "${1%% *}") ${1#* }></center>"
473 # parse command line option
474 case "$1" in
475 *dry*) DRYRUN=1; export DRYRUN; shift;;
476 *show*) firefox "$(pwd)"/$TEST; exit;;
477 *remote*) firefox $URLPREFIX$URL$OUTPUT; exit;;
478 handle)
479 shift
480 case "$1" in
481 "[[SVG:"*) handle_svg_file "$(chomp "$*" '\[\[SVG:' '\]\]')";;
482 "[[Image:"*) handle_image_file "$(chomp "$*" '\[\[Image:' '\]\]')";;
483 esac
484 exit
486 '') ;;
487 *) die "Unknown command: $1";;
488 esac
490 test "$#" = 0 ||
491 die "Usage: $0 [--dry-run]"
493 # make sure we're on the correct branch
494 test refs/heads/$BRANCH = $(git symbolic-ref HEAD) ||
495 die "Not on branch $BRANCH"
497 # make sure there are no uncommitted changes
498 git update-index --refresh &&
499 git diff-files --quiet ||
500 die "Have unstaged changes!"
502 # rename the new blog entry if it exists
503 now=$(date +%s)
504 test ! -f $NEW || {
505 mv -i $NEW source-$now.txt &&
506 git add source-$now.txt
507 } ||
508 die "Could not rename source.txt"
510 # commit the images that are referenced and not yet committed
511 test ! -z "$DRYRUN" ||
512 commit_new_images ||
513 die "Could not commit new images"
515 # to find the images reliably, we have to use the commit name, not the branch
516 # we use the latest commit touching an image file.
517 IMAGEFILES="$(get_image_files)"
518 REV=$(git rev-list -1 HEAD -- $IMAGEFILES)
519 test -z "$REV" && REV=$BRANCH
520 URL="$REMOTEREPOSITORY?a=blob_plain;hb=$REV;f="
522 if test ! -z "$DRYRUN"
523 then
524 # Output to test.html and have local links into the current directory
525 OUTPUT=$TEST
526 URL=
529 make_html > $OUTPUT || die "Could not write $OUTPUT"
531 test ! -z "$DRYRUN" && {
532 move_new_entry_back
533 exit
536 git add $OUTPUT &&
537 git commit -s -m "Update $(make_date $now)" &&
538 git push origin +$BRANCH