sbrs

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

sbrs (9148B)


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