sbrs

Simple blog and RSS system
git clone git://git.margiolis.net/sbrs.git
Log | Files | Refs | README | LICENSE

sbrs (8528B)


      1 #!/bin/sh
      2 # See LICENSE file for copyright and license details.
      3 
      4 WEBSITE="https://margiolis.net"
      5 AUTHOR="Christos Margiolis"
      6 EMAIL="christos@margiolis.net"
      7 BLOGDIR="w"
      8 # TODO: support more indexes
      9 INDEX="w/index.html"
     10 RSSFILE="rss.xml"
     11 TEMPLATE="template.html"
     12 DRAFTDIR=".drafts"
     13 FORMAL_DATE="+%B %d, %Y"
     14 DATEFMT="+%Y.%m.%d"
     15 test -z "${EDITOR}" && EDITOR="vi"
     16 
     17 main()
     18 {
     19         test -f "${TEMPLATE}"   || err "${TEMPLATE}: missing file"
     20         test -f "${INDEX}"      || err "${INDEX}: missing file"
     21         test -f "${RSSFILE}"    || err "${RSSFILE}: missing file"
     22 
     23         if ! test -d "${BLOGDIR}"; then
     24                 confirmact "Blog directory doesn't exist. Intialize it here (y/n)? " "y"
     25                 mkdir -pv "${BLOGDIR}"
     26         fi
     27         # FIXME: ADD QUOTES INSIDE DATE ID
     28 
     29         case ${1} in
     30                 -n) newpost ;;
     31                 -p) listposts "${DRAFTDIR}" && publish ;;
     32                 -e) listposts "${DRAFTDIR}" && ${EDITOR} "${DRAFTDIR}/${blogpost}.html" ;;
     33                 -v) listposts "${DRAFTDIR}" && view ;;
     34                 -t) listposts "${DRAFTDIR}" && delete "${DRAFTDIR}" ;;
     35                 #-r) listposts "${BLOGDIR}"  && revise ;;
     36                 -c) listposts "${BLOGDIR}"  && titlechange ;;
     37                 -o) listposts "${BLOGDIR}"  && ${BROWSER} "${BLOGDIR}/${blogpost}.html" ;;
     38                 -d) listposts "${BLOGDIR}"  && delete "${BLOGDIR}" ;;
     39                 -l) listposts "${BLOGDIR}" ;;
     40                 *) usage ;;
     41         esac
     42 }
     43 
     44 err()
     45 {
     46         echo "${0##*/}: $@" 1>&2
     47 	exit 1
     48 }
     49 
     50 # Could just use `read -erp` but the -e and -p options don't work
     51 # or exist in all shells (e.g OpenBSD's ksh).
     52 xread()
     53 {
     54         printf "%s" "${1}" && read -r ${2}
     55 }
     56 
     57 # This is a POSIX compliant version of `sed -i` since non-GNU
     58 # implementations require a backup file with the -i option.
     59 # In this case, we make the backup files and immidiately delete
     60 # them afterwards because we probably don't need them.
     61 # We're using the extension `.bak` since we want to delete
     62 # only the files affected by `sedi` and nothing else, and that's
     63 # an easy way to make sure.
     64 sedi()
     65 {
     66         sed -i.bak "$@" && rm *.bak ${BLOGDIR}/*.bak 2>/dev/null
     67 }
     68 
     69 confirmact()
     70 {
     71         xread "${1}" act && test "${act}" = "${2}" || exit 1
     72 }
     73 
     74 # TODO: sort by date
     75 list()
     76 {
     77         find "${1}" -type f -name '*\.html' ! -name '*\.final*' 2>/dev/null |
     78         awk -F/ '{print $NF}'
     79 }
     80 
     81 # Create a proper file name for the post (e.g Hello world! -> hello-world).
     82 titlefmt()
     83 {
     84         echo "${1}" | iconv -cf UTF-8 -t ASCII//TRANSLIT |
     85         tr -d '[:punct:]' | tr '[:upper:]' '[:lower:]' | tr ' ' '-'
     86 }
     87 
     88 titleget()
     89 {
     90         grep "<title>" "${1}" | sed "s/<title>//;s/<\/title>//;s/ *//;"
     91 }
     92 
     93 titlecheck()
     94 {
     95         # XXX: maybe should not have `BLOGDIR` hardcoded
     96         test -f "${BLOGDIR}/${1}.html" && err "file exists already"
     97         test -z "${1}" && err "empty title"
     98 }
     99 
    100 rmcontents()
    101 {
    102         ls "${1}" | grep -x "${blogpost}\...*" | sed "s/^/${1}\//" | xargs -r rm
    103 }
    104 
    105 # -l option
    106 listposts()
    107 {
    108         dir="${1}"
    109         nposts=$(expr $(list "${dir}" | wc -l))
    110 
    111         test ${nposts} -eq 0 && err "no posts available in: ${dir}"
    112         printf "Listing posts in: %s (total: %d)\n" "${dir}" "${nposts}"
    113         list "${dir}" | nl
    114 
    115         xread "Choose a post to by number: " num
    116 
    117         if ! test -z "$(echo ${num} | grep -E "^[0-9]+$" | grep -v "^0")"; then
    118                 test $(expr ${num}) -gt ${nposts} && err "no post selected"
    119                 blogpost=$(list "${dir}" | nl | grep -w "${num}" | awk '{print $2}')
    120                 blogpost=${blogpost%.*}
    121         else
    122                 err "no post selected"
    123         fi
    124 }
    125 
    126 # -n option
    127 newpost()
    128 {
    129         mkdir -p "${DRAFTDIR}"
    130         xread "Title: " title
    131         test -z "${title}" && err "please specify a title"
    132 
    133         blogpost=$(titlefmt "${title}")
    134         titlecheck ${blogpost}
    135         $EDITOR "${DRAFTDIR}/${blogpost}.html"
    136 
    137         sed "s/HEADER/${title}/g;s/AUTHOR/${AUTHOR}/g;s/EMAIL/${EMAIL}/g;s/FORMAL_DATE/${FORMAL_DATE}/g;" "${TEMPLATE}" \
    138         > "${DRAFTDIR}/${blogpost}.final.html"
    139 }
    140 
    141 # -c option
    142 # TODO: replace ALL files
    143 titlechange()
    144 {
    145         xread "New title: " newtitle
    146         confirmact "Are you sure (y/N)? " "y"
    147 
    148         oldtitle=$(titleget "${BLOGDIR}/${blogpost}.html")
    149         newtitlefmt=$(titlefmt "${newtitle}")
    150         titlecheck ${newtitlefmt}
    151 
    152         # Adding `.html` so that it doesn't edit every single
    153         # instance of the title, because in case the title is
    154         # something very generic like 'and', it'll change every
    155         # single instance of `and` in the file, and we don't want that. :-)
    156         # The < and > serve the same purpose, as `oldtitle` is
    157         # probably going to be inside an <a> or <title> tag.
    158         sedi "s/${blogpost}.html/${newtitlefmt}.html/g;s/>${oldtitle}</>${newtitle}</g" \
    159         "${BLOGDIR}/${blogpost}.html" "${INDEX}" "${RSSFILE}" &&
    160         mv "${BLOGDIR}/${blogpost}.html" "${BLOGDIR}/${newtitlefmt}.html" &&
    161         echo "Title changed successfully: ${oldtitle} -> ${newtitle}"
    162 }
    163 
    164 # -v option
    165 view()
    166 {
    167         cat "${DRAFTDIR}/${blogpost}.final.html" > "${DRAFTDIR}/${blogpost}.final-view.html"
    168         sedi "/<\!--SBRS-->/r ${DRAFTDIR}/${blogpost}.html" "${DRAFTDIR}/${blogpost}.final-view.html"
    169         ${BROWSER} "${DRAFTDIR}/${blogpost}.final-view.html"
    170 }
    171 
    172 # -d and -t options
    173 delete()
    174 {
    175         dir="${1}"
    176 
    177         confirmact "Are you sure you want to delete \"${blogpost}\" (y/N)? " "y"
    178         if test "${dir}" = "${BLOGDIR}"; then
    179                 sedi "/${blogpost}/d" "${INDEX}"
    180                 sedi "/<\!--BEGIN ${blogpost}-->/,/<\!--END ${blogpost}-->/d" "${RSSFILE}"
    181         fi
    182         rmcontents "${dir}" && echo "Removed ${blogpost}."
    183 }
    184 
    185 # -p option
    186 # XXX: needs a good cleanup
    187 publish()
    188 {
    189         confirmact "Publish post (y/N)? " "y"
    190         title=$(titleget "${DRAFTDIR}/${blogpost}.final.html")
    191 
    192         # Make the final HTML file.
    193         sedi "/<\!--SBRS-->/r ${DRAFTDIR}/${blogpost}.html" "${DRAFTDIR}/${blogpost}.final.html"
    194 
    195         # Convert HTML tags to XML format. We're also wrapping all <pre> tags
    196         # in a CDATA block so that RSS readers can render it properly.
    197         sed "s/</\&lt;/g;s/>/\&gt;/g;s/\&lt;pre\&gt;/\&lt;pre\&gt;<\![CDATA[/g;s/\&lt;\/pre\&gt;/]]>\&lt;\/pre\&gt;/g" \
    198         "${DRAFTDIR}/${blogpost}.html" > "${DRAFTDIR}/${blogpost}.xml"
    199 
    200         cp "${DRAFTDIR}/${blogpost}.final.html" "${BLOGDIR}/${blogpost}.html"
    201 
    202         # Prepare the blog entry for the index files.
    203         printf "\t%s <a href=\"%s\">%s</a><br>\n" \
    204         "$(date ${DATEFMT})" "${BLOGDIR}/${blogpost}.html" "${title}" |
    205         expand -t8 > "${DRAFTDIR}/${blogpost}.final-htmlentry"
    206 
    207         # Prepare the RSS entry.
    208         rsscreate | expand -t8 > "${DRAFTDIR}/${blogpost}.final-rssentry"
    209 
    210         # Using || because of `sedi`.
    211         sedi "/<\!--SBRS-->/r ${DRAFTDIR}/${blogpost}.final-htmlentry" "${INDEX}" || echo "index: ok"
    212         sedi "/<\!--SBRS-->/r ${DRAFTDIR}/${blogpost}.final-rssentry" "${RSSFILE}" || echo "rss: ok"
    213         
    214         rmcontents "${DRAFTDIR}" && echo "clean up ${DRAFTDIR}: ok"
    215         echo "Published ${blogpost}."
    216 }
    217 
    218 # -r option
    219 #revise()
    220 #{
    221         #${EDITOR} "${BLOGDIR}/${blogpost}.html"
    222         #sedi "/<\!--BEGIN ${blogpost}-->/,/<\!--END ${blogpost}-->/d" "${RSSFILE}"
    223 #}
    224 
    225 rsscreate()
    226 {
    227         printf "<!--BEGIN %s-->\n" "${blogpost}"
    228         printf "<item>\n"
    229         printf "<title>%s</title>\n" "${title}"
    230         printf "<author>%s</author>\n" "${AUTHOR}"
    231         printf "<guid>%s</guid>\n" "${WEBSITE}/${BLOGDIR}/${blogpost}.html"
    232         printf "<pubDate>%s</pubDate>\n" "$(date '+%a, %d %b %Y %T %z')" 
    233         printf "<description>\n"
    234         printf "%s\n" "$(cat "${DRAFTDIR}/${blogpost}.xml")"
    235         printf "</description>\n"
    236         printf "</item>\n"
    237         printf "<!--END %s-->\n" "${blogpost}"
    238 }
    239 
    240 usage()
    241 {
    242         printf "usage: ${0##*/} {-n | -p | -e | -v | -t | -r | -c | -o | -d | -l}\n\n" 1>&2
    243         printf "options:\n" 1>&2
    244         printf "  -n\tnew post\n" 1>&2
    245         printf "  -p\tpublish draft post\n" 1>&2
    246         printf "  -e\tedit draft post\n" 1>&2
    247         printf "  -v\tview draft post in browser\n" 1>&2
    248         printf "  -t\tdelete draft post\n" 1>&2
    249         printf "  -r\trevise published post\n" 1>&2
    250         printf "  -c\tchange title\n" 1>&2
    251         printf "  -o\tview published post in browser\n" 1>&2
    252         printf "  -d\tdelete published post\n" 1>&2
    253         printf "  -l\tlist all published posts\n" 1>&2
    254         exit 1
    255 }
    256 
    257 main "${1}"