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/</\</g;s/>/\>/g;s/\<pre\>/\<pre\><\![CDATA[/g;s/\<\/pre\>/]]>\<\/pre\>/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}"