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.
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.
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.description "A few stories told by Dscho"
36 # $ git config blog.background background.jpg
37 # $ git config blog.branch blog
39 # Now you can start writing posts, by creating a file called "new", and
40 # calling ./upload.sh to commit the post together with the images and
43 # Note that no file names may contain spaces.
45 # TODO: document the "syntax" of the source-*.txt files
48 # make sure we're in the correct working directory
51 GITWEBURL
="$(git config gitweb.url)"
52 test -z "$GITWEBURL" && {
55 git config
-l -f .gitconfig |
60 test -z "$(git config "$key")" &&
61 git config
"$key" "$value"
64 echo "Please set gitweb.url in the Git config first!" >&2
70 value
=$
(git config blog.
$1)
71 test -z "$value" && value
=$2
75 BACKGROUNDIMG
=$
(get_config background paper.jpg
)
76 TITLE
=$
(get_config title
"Dscho's Git log")
77 MAXENTRIES
=$
(get_config maxPostsPerPage
10)
78 BRANCH
=$
(get_config branch blog
)
79 DESC
=$
(get_config description
"A few stories told by Dscho")
81 URLPREFIX
="$(dirname "$GITWEBURL")"/
82 REMOTEREPOSITORY
="$(basename "$GITWEBURL")"
84 *'?'*) BLOBPLAIN
="$REMOTEREPOSITORY;a=blob_plain";;
85 */) URLPREFIX
=$GITWEBURL; BLOBPLAIN
="a=blob_plain";;
86 *) BLOBPLAIN
="$REMOTEREPOSITORY?a=blob_plain";;
88 URL
="$BLOBPLAIN;hb=$BRANCH;f="
99 move_new_entry_back
() {
100 test -f source-
$now.txt
&&
101 mv source-
$now.txt
$NEW &&
102 git
rm --cached -f source-
$now.txt
116 strip_prefix
"${1%$3}" "$2"
120 # add illogical suffix
122 *1?|
*[04-9]) echo "$1th";;
129 make_chinese_hour
() {
132 01|
02) echo Buffalo
;;
140 17|
18) echo Rooster
;;
160 make_roman_number
() {
163 ?
) digit_to_roman
$1 I V X
;;
164 ??
) echo $
(digit_to_roman
${1%?} X L C
)$
(make_roman_number
${1#?});;
165 ???
) echo $
(digit_to_roman
${1%??} C D M
)$
(make_roman_number
${1#?});;
166 ????
) echo $
(digit_to_roman
${1%???} M
)$
(make_roman_number
${1#?});;
171 printf "%s, %s of %s, Anno Domini %s, at the hour of the %s\n" \
173 $
(nth $
(date +%e
-d @
$1)) \
175 $
(make_roman_number $
(date +%Y
-d @
$1)) \
176 $
(make_chinese_hour $
(date +%H
-d @
$1))
179 # make an argument for sed, to replace $1..$1 by <$2>..</$2>
180 markup_substitution
() {
181 # sed does not know minimal match with .*, only greedy one
186 while test ! -z "$delim"
188 right_no
=$
(($right_no+1))
189 test $right_no -gt 9 &&
190 die
"Invalid markup pattern: $1"
191 first
=$
(expr "$delim" : '\(.\)')
192 delim
=${delim#$first}
193 test -z "$delim" && {
194 middle
="$middle\\([^$first]\\|$first[A-Za-z0-9]\\)"
197 middle
="$middle\\([^$first]\\|$first"
198 middleend
="$middleend\\)"
201 left
="\\(^\\|[^A-Za-z0-9]\\)$1\\($middle$middleend*\\)"
202 right
="$1\\($\\|[^A-Za-z0-9]\\)"
203 # work around stupid dash interpreting backslashes when expanding vars
205 test "\\" = "$(echo "$bs")" || bs
="\\"
206 echo "s/$left$right/${bs}1<$2>${bs}2<\/$2>$bs$right_no/g"
210 space80
="$space80$space80 "
211 # transform markup in stdin to HTML
214 *invert-bash
*) bash_bg
=white
; bash_fg
=black
;;
215 *) bash_bg
=black
; bash_fg
=white
;;
217 sed -e 's!^$!</p><p>!' \
218 -e "$(markup_substitution "''" i)" \
219 -e "$(markup_substitution "_
" u)" \
220 -e 's!IMHO!in my humble opinion!g' \
221 -e 's!BTW!By the way,!g' \
222 -e 's!\([^/]\)repo.or.cz!\1<a href=http://&>&</a>!g' \
223 -e 's!:-)!\☺!g' \
224 -e "s!\\[\\[\(Image\|SVG\):.*!$THIS handle &!e" \
226 border=1 bgcolor='$bash_bg'>\
227 <tr><td bgcolor=lightblue colspan=3>\
228 <pre>'"$space80"'</pre>\
231 <table cellspacing=5 border=0\
232 style="color:'$bash_fg';">\
235 -e 's!</bash>!</pre>\
243 # output lines containing <timestamp> <filename> <title>
244 get_blog_entries
() {
245 for file in $
(ls -r source-
*.txt
)
247 timestamp
=$
(chomp
$file source- .txt
)
248 title
="$(sed 1q < $file | markup)"
249 echo "$timestamp $file $title"
253 get_last_removed_entry
() {
254 git log
--pretty=format
: --name-only --diff-filter=D HEAD |
258 source-
*.txt
) file=$line;;
259 '') test -z "$file" ||
{
269 test $box_count = 0 ||
echo "<br>"
270 echo "<table width=$toc_width bgcolor=#e0e0e0 border=0>"
271 echo "<tr><th>$1</th></tr>"
276 echo "</td></tr></table>"
277 box_count
=$
(($box_count+1))
282 body_style
="width:800px"
283 body_style
="$body_style;background-image:url($URL$BACKGROUNDIMG)"
284 body_style
="$body_style;background-repeat:repeat-y"
285 body_style
="$body_style;background-attachment:scroll"
286 body_style
="$body_style;padding:0px;"
287 text_style
="width:610px"
288 text_style
="$text_style;margin-left:120px"
289 text_style
="$text_style;margin-top:50px"
290 text_style
="$text_style;align:left"
291 text_style
="$text_style;vertical-align:top;"
292 text_style
="$text_style;font-family: Arial, sans-serif;"
296 <title>$TITLE</title>
297 <meta http-equiv="Content-Type"
298 content="text/html; charset=UTF-8"/>
300 <body style="$body_style">
301 <div style="$text_style">
308 toc_style
="position:absolute;top:50px;left:810px;width=$toc_width"
310 echo "<div style=\"$toc_style\">"
311 begin_box
"Table of contents:"
314 while read timestamp filename title
316 date="$(date +"%d
%b
%Y
" -d @$timestamp)"
317 echo "<li><a href=#$timestamp>$date $title</a>"
321 last_removed_entry
=$
(get_last_removed_entry
)
322 test -z "$last_removed_entry" ||
{
323 commit
=$
(git log
--pretty=format
:%H
--diff-filter=AM \
324 -- $last_removed_entry |
326 previous
="$BLOBPLAIN;hb=$commit"
327 echo "<a href=$previous;f=index.html>Older posts</a>"
332 rss_style
="background-color:orange;text-decoration:none"
333 rss_style
="$rss_style;color:white;font-family:sans-serif;"
335 echo '<div style="text-align:right;">'
336 echo "<a href=\"$ORIGURL$RSS\""
337 echo ' title="Subscribe to my RSS feed"'
338 echo ' class="rss" rel="nofollow"'
339 echo " style=\"$rss_style\">RSS</a>"
343 test -f about.html
&& {
344 begin_box
"About this blog:"
350 test -f links.html
&& {
357 test -z "$DRYRUN" && test -f google.adsense
&& {
358 begin_box
"Google Ads:"
364 } |
sed -s "s/^/$indent/"
367 # timestamps will not need padding to sort correctly, for some time...
369 while read timestamp filename title
371 echo "<h6>$(make_date $timestamp)</h6>"
372 echo "<a name=$timestamp>"
373 echo "<h2>$title</h2>"
376 sed 1d
< $filename | markup
379 sed -e "s/^./$indent&/" \
380 -e "/<pre>/,/<\/pre>/s/^$indent//"
390 echo '<?xml version="1.0" encoding="utf-8"?>'
391 echo '<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">'
393 echo "<title>$TITLE</title>"
394 echo "<link>$URLPREFIX${URL}index.html</link>"
395 self
="$URLPREFIX$ORIGURL$RSS"
396 selfattribs
='rel="self" type="application/rss+xml"'
397 echo "<atom:link href=\"$self\" $selfattribs/>"
398 echo "<description>$DESC</description>"
399 echo "<lastBuildDate>$(date --rfc-2822)</lastBuildDate>"
400 echo '<language>en-us</language>'
403 while read timestamp filename title
406 title
=$
(echo "$title" |
sed 's/<[^>]*>//g')
408 echo "<title>$title</title>"
409 echo "<link>$URLPREFIX${URL}index.html#$timestamp</link>"
410 echo "<guid>$URLPREFIX${URL}index.html#$timestamp</guid>"
411 echo "<pubDate>$(date --rfc-2822 -d @$timestamp)</pubDate>"
412 description
="$(cat < $filename | markup invert-bash)"
413 echo "<description><![CDATA[$description]]></description>"
423 grep -v '\.\(rss\|html\|gitignore\|in\|sh\|txt\|adsense\)$'
426 remove_old_entries
() {
427 count
=$
(ls source-
*.txt |
wc -l)
428 test $MAXENTRIES -ge $count && return
430 for file in source-
*.txt
432 test $MAXENTRIES -lt $count ||
break
433 git
rm $file > /dev
/null ||
return 1
438 # remove no-longer referenced images
439 image_files
=$
(get_image_files
)
440 referenced_files
="$(cat source-*.txt |
442 sed -ne 's/\[\[\(Image\|SVG\)://p') $BACKGROUNDIMG"
443 for file in $
(echo $image_files $referenced_files $referenced_files |
444 tr ' ' '\n' |
sort |
uniq -u)
446 git
rm $file > /dev
/null ||
return 1
451 # never, ever have spaces in the file names
452 commit_new_images
() {
453 files
="$(remove_old_entries) $RSS $BACKGROUNDIMG" ||
454 die
"Could not remove old entries"
457 generate_rss
> $RSS &&
459 die
"Could not generate $RSS"
461 for image
in $
(cat source-
* |
463 sed -n 's/.*\[\[\(Image\|SVG\)://p' |
467 git add
$image || die
"Could not git add image $image"
468 files
="$files $image"
471 git update-index
--refresh &&
472 git diff-files
--quiet -- $files &&
473 git
diff --cached --quiet HEAD
-- $files ||
474 git commit
-s -m "Housekeeping on $(make_date $now)" $files
478 test ! -z "$DRYRUN" && echo "$1" && return
479 rev=$
(git rev-list
-1 HEAD
-- $1)
480 test -z "$rev" && die
"No revision found for $1"
481 echo "$BLOBPLAIN;hb=$rev;f=$1"
485 # for some reason, Firefox adds scrollbars, so nudge the width a bit
486 width
=$
(sed -ne 's/.* width="\([^"]*\).*/\1/p' -e '/<metadata/q' < "$1")
487 test -z "$width" || width
=" width=$(($width+5))"
488 url
=$
(get_image_url
"$1")
494 <embed type="image/svg+xml"
508 handle_image_file
() {
509 echo "<center><img src=$(get_image_url "${1%% *}") ${1#* }></center>"
514 # parse command line option
516 *dry
*|
*preview
*) DRYRUN
=1; export DRYRUN
; shift;;
517 *show
*) xdg-open
"$(pwd)"/$TEST; exit;;
518 *remote
*) xdg-open
$URLPREFIX$URL$OUTPUT; exit;;
522 "[[SVG:"*) handle_svg_file
"$(chomp "$
*" '\[\[SVG:' '\]\]')";;
523 "[[Image:"*) handle_image_file
"$(chomp "$
*" '\[\[Image:' '\]\]')";;
535 remote show on the remote
536 upload (default) upload
539 *) die
"Unknown command: $1";;
543 die
"Usage: $0 [--dry-run]"
545 # make sure we're on the correct branch
546 test refs
/heads
/$BRANCH = $
(git symbolic-ref HEAD
) ||
547 die
"Not on branch $BRANCH"
549 # make sure there are no uncommitted changes
550 git update-index
--refresh &&
551 git diff-files
--quiet ||
552 die
"Have unstaged changes!"
554 # rename the new blog entry if it exists
557 mv -i $NEW source-
$now.txt
&&
558 git add source-
$now.txt
560 die
"Could not rename source.txt"
562 # commit the images that are referenced and not yet committed
563 test ! -z "$DRYRUN" ||
565 die
"Could not commit new images"
567 # to find the images reliably, we have to use the commit name, not the branch
568 # we use the latest commit touching an image file.
569 IMAGEFILES
="$(get_image_files)"
570 REV
=$
(git rev-list
-1 HEAD
-- $IMAGEFILES)
571 test -z "$REV" && REV
=$BRANCH
572 URL
="$BLOBPLAIN;hb=$REV;f="
574 if test ! -z "$DRYRUN"
576 # Output to test.html and have local links into the current directory
581 make_html
> $OUTPUT || die
"Could not write $OUTPUT"
583 test ! -z "$DRYRUN" && {
589 git commit
-s -m "Update $(make_date $now)" &&
590 git push origin
+$BRANCH