#!/usr/bin/env bash version="v1.3.0" shopt -s extglob configfiles="$HOME/.config/burrow/config $HOME/.config/burrow $HOME/.burrow" editor=${EDITOR:-vi} # vars from config config_dir_gopher="$HOME/gopher/" config_dir_recipebox="recipebox" config_dir_phlog="phlog" config_dir_topics="topics" config_gopher_server="sdf.org" config_gopher_port="70" config_gopher_root="/users/username/" config_phlog_gophermap=true config_phlog_usedate=true config_recipebox_gophermap=false config_recipebox_usedate=false config_topics_gophermap=true config_topics_usedate=false config_git_commit=false config_git_push=false config_autoindent=true # vars from flags flag_debug=0 flag_version=0 flag_help=0 flag_noautoindent=0 # vars from args arg_options="hvd" arg_longoptions="help,version,debug,noautoindent" arg_shortlist=0 arg_recipe=0 arg_phlog=0 arg_topic=0 arg_create_config=0 arg_edit_config=0 arg_update_git=0 arg_update_burrow=0 # silence directory movements pushd () { command pushd "$@" 2>/dev/null 1>&2 } popd () { command popd 2>/dev/null 1>&2 } function show_help { cat > /dev/stdout << END burrow [options] [commands] COMMANDS: phlog Create new phlog entry recipe Add new recipe to box topic Add or update a topic create-config Create a default configuration file edit-config Edit your configuration file update-git Silently pulls gopher git repo if it exists update-burrow Auto-update this application in its local folder OPTIONAL FLAGS: -h, --help Show this help -v, --version Show current version info -d, --debug Debug mode END } function parse_input { getopt -T > /dev/null if [ $? -eq 4 ]; then # GNU enhanced getopt is available parsed=$(getopt \ --options=$arg_options \ --longoptions=$arg_longoptions \ --name "$0" \ -- "$@") else # Original getopt is available parsed=$(getopt $arg_options "$@") fi if [[ $? -ne 0 ]]; then die "Invalid input" 2 fi eval set -- "$parsed" while true; do case "$1" in -h|--help) flag_help=1 shift ;; -v|--version) flag_version=1 shift ;; -d|--debug) flag_debug=1 shift ;; --noautoindent) flag_noautoindent=1 shift ;; --) shift break ;; *) die "Internal error: $1" 3 ;; esac done for arg in "$@"; do argc=${arg,,} case $argc in "shortlist") arg_shortlist=1 ;; "phlog") arg_phlog=1 ;; "recipe") arg_recipe=1 ;; "topic") arg_topic=1 ;; "create-config") arg_create_config=1 ;; "edit-config") arg_edit_config=1 ;; "update-git") arg_update_git=1 ;; "update-burrow") arg_update_burrow=1 ;; *) echo "Unknown command: $arg";; esac done } function day_suffix { case $(date +%d) in 01|1|21|31) echo "st";; 02|2|22) echo "nd";; 03|3|23) echo "rd";; *) echo "th";; esac } function update_gopher_root { newdate=$(date +"%B %d$(day_suffix), %Y") sed -i "s/.*Last\ Updated:.*/i ==== Last Updated: $newdate ====/" \ "${config_dir_gopher}/gophermap" } function check_directory { if [[ ! -d "$1" ]]; then die "Missing directory: $1 aborting." 1 fi } function die { msg="$1" code="$2" # exit code defaults to 1 if [[ "$code" =~ /^[0-9]+$/ ]] ; then code=1 fi # output message to stdout or stderr based on code if [[ ! -z "$msg" ]]; then if [[ "$code" == 0 ]]; then echo "$msg" else echo "$msg" >&2 fi fi exit "$code" } function finish { if [[ -f "$temp_gophermap" ]]; then rm "$temp_gophermap" fi if [[ -f "$temp_post" ]]; then rm "$temp_post" fi } trap finish EXIT function make_post_git { if [[ $config_git_commit != false ]]; then pushd "$config_dir_gopher" git add "${post_dir}/gophermap" "${post_file}" "$type_gophermap" if $update_root; then git add "${config_dir_gopher}/gophermap" fi git commit -m "$post_type: $title" if [[ $config_git_push != false ]]; then git pull git push fi popd fi } function make_post_process_formatting { # If using gophermap, prefix all post lines with "i" except links if $use_gophermap; then if [[ $config_autoindent ]] && [[ $flag_noautoindent == 0 ]]; then sed -i -e '/\t/! s/^/i/' "$post_file" fi fi } function make_post_gophermap { # Create temporary gophermap for new post temp_gophermap=$(mktemp -t "$(basename "$0").gophermap.XXXXXXX") || \ die "Failed to create temporary file" 1 # Add appropriate link into temp gophermap if $use_gophermap; then if $use_date; then # if using gophermap and date echo -e "1$(date +%Y-%m-%d) - $title\t${post_file_path}\t${config_gopher_server}\t${config_gopher_port}" > "$temp_gophermap" else # if using gophermap but not date echo -e "1$title\t${post_file_path}\t${config_gopher_server}\t${config_gopher_port}" > "$temp_gophermap" fi else if $use_date; then # if not using gophermap but using date echo -e "0$(date +%Y-%m-%d) - $title\t${post_file_path}\t${config_gopher_server}\t${config_gopher_port}" > "$temp_gophermap" else # if not using gophermap or date echo -e "0$title\t${post_file_path}\t${config_gopher_server}\t${config_gopher_port}" > "$temp_gophermap" fi fi cat "$type_gophermap" >> "$temp_gophermap" cat "$temp_gophermap" > "$type_gophermap" rm "$temp_gophermap" # Sort gophermap alphabetically if not using date if [[ $use_gophermap ]] && [[ ! $use_date ]]; then sort -fo "$type_gophermap" "$type_gophermap" fi } function make_post_unprocess { if [[ $config_autoindent ]] && [[ $flag_noautoindent == 0 ]]; then temp_post=$(mktemp -t "$(basename "$0").post.XXXXXXX") || \ die "Failed to create temporary file" 1 # copy existing post to tempfile cp "$post_file" "${temp_post}" # If using gophermaps, unformat it for editing if $use_gophermap; then sed -i -e '/\t/! s/^i//' "${temp_post}" fi fi # Get timestamp of tempfile temp_post_time=$(stat -c %Y "$temp_post") # Edit tempfile $EDITOR "$temp_post" # Verify that timestamp changed after editing, or abort temp_post_time_check=$(stat -c %Y "$temp_post") if [[ "$temp_post_time" != "$temp_post_time_check" ]] ; then cp "${temp_post}" "$post_file" else return 1 fi } function make_post_temp { # Create a tempfile to do our work temp_post=$(mktemp -t "$(basename "$0").post.XXXXXXX") || \ die "Failed to create temporary file" 1 # Populate tempfile with either template or default template if [[ -f "${post_dir}/.template" ]]; then cat "${post_dir}/.template" > "$temp_post" else { echo "----------------------------------------" echo "$title" if $use_date; then date +"%B %d$(day_suffix), %Y" fi echo "----------------------------------------" echo "" echo "" } > "$temp_post" fi # Check timestamp before editing file temp_post_time=$(stat -c %Y "$temp_post") $EDITOR "$temp_post" # Verify that timestamp changed after editing, or abort temp_post_time_check=$(stat -c %Y "$temp_post") # If we saved a change, create the new file if [[ "$temp_post_time" != "$temp_post_time_check" ]] ; then mkdir -p "${post_dir}" cp "${temp_post}" "$post_file" rm "${temp_post}" else return 1 fi } function make_post_paths { type_gophermap="${config_dir_gopher}${post_type}/gophermap" if $use_gophermap; then title_slug=$(echo "${title}" | \ sed -E -e 's/[^[:alnum:]]/-/g' -e 's/^-+|-+$//g' | \ tr -s '-' | \ tr '[:upper:]' '[:lower:]') if $use_date; then post_dir="${config_dir_gopher}${post_type}/$(date +%Y%m%d)-$title_slug" post_path="${config_gopher_root}${post_type}/$(date +%Y%m%d)-$title_slug" else post_dir="${config_dir_gopher}${post_type}/$title_slug" post_path="${config_gopher_root}${post_type}/$title_slug" fi post_file="${post_dir}/gophermap" post_file_path="${post_path}" else post_dir="${config_dir_gopher}${post_type}" post_path="${config_gopher_root}${post_type}" title_slug=$(echo "${title}" | \ sed -E -e 's/[^[:alnum:]]/-/g' -e 's/^-+|-+$//g' | \ tr -s '-' | \ tr '[:upper:]' '[:lower:]') if $use_date; then title_slug="$(date +%Y%m%d)-${title_slug}" fi post_file="${post_dir}/${title_slug}.txt" post_file_path="${post_path}/${title_slug}.txt" fi } function make_post { query="$1" post_type="$2" use_gophermap="$3" use_date="$4" update_root="$5" check_directory "$config_dir_gopher" check_directory "${config_dir_gopher}${post_type}" read -r -e -p "$query" title if [[ $title == "" ]]; then die "Cancelled." 0 fi make_post_paths if [[ -f $post_file ]]; then make_post_unprocess if [[ "$?" == 1 ]]; then die "Aborted edit" 0 fi make_post_process_formatting else make_post_temp if [[ "$?" == 1 ]]; then die "Aborted post" 0 fi make_post_process_formatting make_post_gophermap fi if $update_root; then update_gopher_root fi make_post_git } function edit_config { if [[ -f "$HOME/.config/burrow/config" ]]; then $EDITOR "$HOME/.config/burrow/config" elif [[ -f "$HOME/.config/burrow" ]]; then $EDITOR "$HOME/.config/burrow" elif [[ -f "$HOME/.burrow" ]]; then $EDITOR "$HOME/.burrow" else die "No configuration found" 0 fi } function create_config { if [[ ! -f "$HOME/.config/burrow/config" ]] && \ [[ ! -f "$HOME/.config/burrow" ]] && \ [[ ! -f "$HOME/.burrow" ]]; then config="$HOME/.config/burrow/config" mkdir -p "$(dirname "$config")" { echo "config_dir_gopher=\"$HOME/gopher/\"" echo "" echo "config_gopher_server=\"sdf.org\"" echo "config_gopher_port=\"70\"" echo "config_gopher_root=\"/users/username/\"" echo "" echo "config_dir_phlog=\"phlog\"" echo "config_phlog_gophermap=true" echo "config_phlog_usedate=true" echo "" echo "config_dir_recipebox=\"recipebox\"" echo "config_recipebox_gophermap=false" echo "config_recipebox_usedate=false" echo "" echo "config_dir_topics=\"topics\"" echo "config_topics_gophermap=true" echo "config_topics_usedate=false" echo "" echo "config_git_commit=false" echo "config_git_push=false" echo "" echo "config_autoindent=true" } >> "$config" else echo "Configuration already exists." fi } function update_git { pushd "$config_dir_gopher" if [[ $(git rev-parse --is-inside-work-tree 2> /dev/null) ]]; then git pull -q fi popd } function update_burrow { burrow_src="${BASH_SOURCE[0]}" while [ -h "${burrow_src}" ]; do burrow_dir="$( cd -P "$( dirname "${burrow_src}" )" && pwd )" burrow_src="$(readlink "${burrow_src}")" [[ ${burrow_src} != /* ]] && burrow_src="${burrow_dir}/${burrow_src}" done burrow_dir="$( cd -P "$( dirname "${burrow_src}" )" && pwd )" pushd "${burrow_dir}" if [[ $(git rev-parse --is-inside-work-tree 2> /dev/null) ]]; then read -r -p "Do you want to update your burrow git repository? [y/N] " response if [[ "$response" =~ ^([yY][eE][sS]|[yY])+$ ]] then git pull -q else die "Aborting." 0 fi else github_src="https://raw.githubusercontent.com/jamestomasino/burrow/master/" read -r -p "Do you want to fetch the latest burrow executable? [y/N] " response if [[ "$response" =~ ^([yY][eE][sS]|[yY])+$ ]] then for filename in ./burrow*; do curl -sfLO "${github_src}${filename}" done else die "Aborting." 0 fi fi popd } function main { parse_input "$@" if [[ $arg_shortlist -gt 0 ]]; then out="phlog topic recipe edit-config create-config update-burrow update-git -v -h -d" # include long options only if using gnu getopt getopt -T > /dev/null if [ $? -eq 4 ]; then out="${out} --version --help --debug --noautointent" fi die "${out}" 0 fi if [[ $flag_version -gt 0 ]]; then echo "$version" fi if [[ $flag_help -gt 0 ]]; then show_help fi if [[ $flag_debug -gt 0 ]]; then set -x fi if [[ $arg_create_config -gt 0 ]]; then create_config fi if [[ $arg_edit_config -gt 0 ]]; then edit_config fi for configfile in $configfiles; do if [[ -f $configfile ]]; then source "$configfile" fi done if [[ ${arg_update_burrow} -gt 0 ]]; then update_burrow fi if [[ ${arg_update_git} -gt 0 ]]; then update_git fi if [[ ${arg_recipe} -gt 0 ]]; then make_post "What is the name of your recipe? " \ "$config_dir_recipebox" \ "$config_recipebox_gophermap" \ "$config_recipebox_usedate" \ true fi if [[ ${arg_topic} -gt 0 ]]; then make_post "What is the name of your topic? " \ "$config_dir_topics" \ "$config_topics_gophermap" \ "$config_topics_usedate" \ true fi if [[ ${arg_phlog} -gt 0 ]]; then make_post "Enter a title for your post: " \ "$config_dir_phlog" \ "$config_phlog_gophermap" \ "$config_phlog_usedate" \ true fi } main "$@"