#!/bin/bash # # QaD script to wrap blog posting with IkiWiki # # TODO: get some settings from ikiwiki.setup? # TODO: split RcsSubmit. add files to the RCS always (catches drafts) # Settings AUTHOR="Daniel Quinlan" WIKI_SRC=$HOME/src/wiki POST_SUFFIX=.mdwn DRAFT_PREFIX=DRAFT # Don't mess with these SETUP_FILE=ikiwiki.setup.$(hostname) POST_BASE="wiki-src/blog/posts" TAG_BASE="wiki-src/blog/tags" DRAFT_BASE="drafts" POST_LAYOUT="%Y/%m/%d" DIR_PATTERN="$POST_BASE/$POST_LAYOUT" DATE=$(date "+%Y-%m-%d %H:%M:%S") OP=POST COMMIT_FILES= EDIT_FILE= DEBUG= function EditFileCheckForChanges() { # {{{ # edit a file, return true if it was changed md5sum=$(md5sum $1) # edit tempfile ${EDITOR:-vim} $1 if [ "$md5sum" == "$(md5sum $1)" ] ; then return 1 else return 0 fi } # }}} function InitialCase() { # {{{ LOWER='abcdefghijklmnopqrstuvwxyz' UPPER='ABCDEFGHIJKLMNOPQRSTUVWXYZ' echo "$@" | sed -n ' # if first char is not a lowercase letter # jump to end and print /^[^a-z]/bEND # save word h # keep just the first letter s/^\([a-z]\).*/\1/ # translate to upper case y/'$LOWER'/'$UPPER'/ # swap hold/pattern space x # append hold space to pattern space G # replace old first letter with new first letter s/^[a-z]\(.*\)\n\([A-Z]\)/\2\1/p t : END p ' } # }}} function CreateTagsIfNotExist() { # {{{ # $1 - file to search for tags local TAGS TAG_FILE tag TAG_TITLE TAGS=$(grep '\[\[tag' $1 | sed -n 's/^.*\[\[tag *\([^]]*\)\]\].*$/\1/p') for tag in $TAGS ; do TAG_FILE=$TAG_BASE/$(echo "$tag$POST_SUFFIX" | tr '[A-Z]' '[a-z]') TAG_FILE_ABS=$WIKI_SRC/$TAG_FILE TAG_TITLE=$(InitialCase "$tag") if [ ! -e $TAG_FILE_ABS ] ; then if [ -z "$DEBUG" ] ; then cat < $TAG_FILE_ABS This feed contains pages with the tag "$TAG_TITLE". [[inline pages="link($tag) and *blog/posts/* and !*/Discussion" show="10"]] EOF fi echo ${TAG_FILE} fi done } # }}} function GetMetaDataFromPost() { # {{{ # $1 = path to post file # $2 = meta data to get if [ ! -r "$1" ] ; then echo "Error: $1 not found" >&2 exit 1 fi grep "\[\[meta $2=" $1 | sed -e 's/^\[\[meta [a-z]*="\([^"]*\)".*$/\1/' } # }}} function MakeSlugFromTitle() { # {{{ echo "$1" | tr -dc '[a-zA-Z0-9 _\-]' | sed -e 's/ / /g' | tr '[A-Z] ' '[a-z]-' } # }}} function NewPost() { # {{{ local TEMP_FILE=$1 local NEW_FILE=false if [ -z "$TEMP_FILE" ] ; then TEMP_FILE=$(tempfile -p$DRAFT_PREFIX -d$WIKI_SRC/$DRAFT_BASE) NEW_FILE=true # cat post template into tempfile cat <<__END__ > $TEMP_FILE [[meta title="I_FORGOT_TO_ADD_A_TITLE"]] [[meta date="$DATE"]] [[meta author="$AUTHOR"]] drivel goes here [[tag uncategorised ]] __END__ fi echo $TEMP_FILE if ! EditFileCheckForChanges $TEMP_FILE ; then echo $TEMP_FILE not modified, aborting... [ "$NEW_FILE" == "true" ] && rm $TEMP_FILE exit 0 fi # parse tempfile for title POST_TITLE="$(GetMetaDataFromPost $TEMP_FILE title)" if [ -z "$POST_TITLE" ] ; then # TODO: add some logic for a default title (based on date?) echo no title found in $TEMP_FILE >&2 exit 1 fi # parse tempfile for date POST_DATE="$(GetMetaDataFromPost $TEMP_FILE date)" POST_DATE_DIR=$(date "+$DIR_PATTERN" -d "$POST_DATE") # create slug from title SLUG=$(MakeSlugFromTitle "$POST_TITLE") POST_FILE=$WIKI_SRC/$POST_DATE_DIR/$SLUG$POST_SUFFIX # check if post already exists if [ -e $POST_FILE ] ; then echo $POST_FILE exists while : ; do read -p 'Edit existing file, Merge, Overwrite or Quit? [emoq]' case $REPLY in e|E) EditPost $POST_FILE exit 0 ;; m|M) echo Doing a simple merge... cat $TEMP_FILE >> $POST_FILE EditPost $POST_FILE exit 0 ;; o|O) break ;; q|Q) exit 0 ;; esac done fi # makepath and # make any missing year/month/day indexes CREATED_FILES=$(MakePath $WIKI_SRC/$POST_BASE $POST_LAYOUT $(date "+%Y/%m/%d" -d "$POST_DATE")) # create files for any new tags CREATED_FILES="$CREATED_FILES $(CreateTagsIfNotExist $TEMP_FILE)" # move to wikisrc $DEBUG mv -v $TEMP_FILE $POST_FILE && CREATED_FILES="$CREATED_FILES ${POST_FILE##$WIKI_SRC/}" while read -p 'Commit post? [yn] ' ; do case $REPLY in Y|y) break ;; N|n) echo Created files: echo $CREATED_FILES | tr ' ' '\n' exit 0 ;; esac done RcsSubmit "$CREATED_FILES" "Blog post on $DATE" } # }}} function MakePath() { #{{{ # $1 = POST_BASE # $2 = POST_LAYOUT # $3 = optional, date -d local DATE CREATED_FILES ABS_PATH [ -n "$3" ] && DATE="-d $3" cd $1 || return 1 for part in $(echo $2 | tr / ' ') ; do dir=$(date "+$part" $DATE) case $part in %Y) pages="./$dir/*/*/*" title=$dir ;; %m) pages="./$dir/*/*" title="$(date '+%B %Y' $DATE)" ;; %d) pages="./*" title="$(date '+%A %B %d %Y' $DATE)" ;; esac [ -d $dir ] || mkdir $dir ABS_PATH=$(pwd)/${dir}$POST_SUFFIX CREATED_FILES="$CREATED_FILES ${ABS_PATH##$WIKI_SRC/}" if [ -n "$DEBUG" ] ; then echo "Creating $ABS_PATH" >&2 else cat <<__END__ > $ABS_PATH [[meta title="$title"]] Here is a full list of posts for $title [[inline pages="$pages and !*/Discussion" archive="yes"]] __END__ fi cd $dir done cd $WIKI_SRC echo $CREATED_FILES } # }}} function EditPost() { # {{{ cd $WIKI_SRC if EditFileCheckForChanges $1 ; then RcsSubmit $1 "Edit post $(basename -- $1)" fi } # }}} function RcsSubmit() { # {{{ # $1 list of files to submit # $2 commit message cd $WIKI_SRC && \ $DEBUG git add $1 && \ $DEBUG git commit -m "$2" -o $1 } # }}} function UpdateIndexes() { # {{{ local CREATED_FILES # TODO: fix the grep pattern in next 90 or so years find $WIKI_SRC/$POST_BASE -type d | grep '20../../..$' | while read dir ; do echo "$(MakePath $WIKI_SRC/$POST_BASE $POST_LAYOUT ${dir##$WIKI_SRC/$POST_BASE/})" cd $WIKI_SRC done } # }}} function SetupBlog() { # {{{ if [ -z "$DEBUG" -a -r $WIKI_SRC ] ; then echo "$WIKI_SRC exists. giving up" >&2 exit 1 fi $DEBUG mkdir -p $WIKI_SRC/plugins $DEBUG mkdir -p $WIKI_SRC/templates $DEBUG mkdir -p $WIKI_SRC/$DRAFT_BASE $DEBUG mkdir -p $WIKI_SRC/$POST_BASE $DEBUG mkdir -p $WIKI_SRC/$TAG_BASE } # }}} function IkiwikiBuild() { # {{{ cd $WIKI_SRC if [ "$1" == "REFRESH" ] ; then REFRESH=--refresh fi if [ -r $SETUP_FILE ] ; then $DEBUG ikiwiki --setup $SETUP_FILE $REFRESH else echo $SETUP_FILE not found >&2 exit 1 fi } # }}} function usage() { # {{{ cat <&2 Usage: `basename -- $0` [option] [operation] Operations: --post|-p [file] create/update a blog post (Default) if file is specified update that post should be relative to $WIKI_SRC --list-tags|-t list existing blog tags --update-indexes update blog year/month/day indexes --build|-b run 'ikiwiki --setup ikiwiki.$(hostname)' --refresh|-r run 'ikiwiki --setup ikiwiki.$(hostname) --refresh' --setup setup a new blog Options: --debug Debug: don't actually run commands which would change state, just show what would have been done. --help|-h Help: show this help message EOF exit 1 } # }}} # process options {{{ RAW_ARGS=`getopt -o h,p::,t,b,r \ --long help,debug,post::,list-tags,update-indexes,setup,build,refresh \ -n "$(basename $0)" -- "$@"` [ $? != 0 ] && usage eval set -- "$RAW_ARGS" while true ; do case "$1" in -p|--post) OP=POST ; EDIT_FILE=$2 ; shift 2;; -t|--list-tags) OP=TAGS ; shift ;; -r|--refresh) OP=REFRESH ; shift ;; -b|--build) OP=BUILD ; shift ;; --update-indexes) OP=UPDATE ; shift ;; --setup) OP=SETUP ; shift ;; --debug) DEBUG=echo ; shift ;; -h|--help) usage ;; --) shift ; break ;; *) echo "unknown option ($1)" ; usage ;; esac done if [ "$OP" == "POST" ] ; then # handle a space separating -p and the filename if [ -z "$EDIT_FILE" -a -n "$1" -a -w "$WIKI_SRC/$1" ] ; then EDIT_FILE=$1 fi fi if [ -n "$DEBUG" ] ; then echo "Vars: $@" echo "op: $OP" [ "$OP" == "POST" ] && echo "Edit file: x${EDIT_FILE}x" fi # }}} case $OP in POST) if [ -n "$1" -a -e $WIKI_SRC/$1 ] ; then if basename -- "$1" | grep -q ^$DRAFT_PREFIX ; then NewPost $1 else EditPost $1 fi else NewPost fi ;; TAGS) cd $WIKI_SRC/$TAG_BASE for tag in *$POST_SUFFIX ; do echo ${tag%%$POST_SUFFIX} done | fmt -80 ;; UPDATE) echo Created files: UpdateIndexes ;; SETUP) SetupBlog "$1" ;; BUILD|REFRESH) IkiwikiBuild "$OP" ;; *) echo unknown operation >&2 echo >&2 usage ;; esac # vim:set foldmethod=marker: