#!/bin/sh version="v1.7.9" configsystem="/etc/burrow/config" if [ -n "$XDG_CONFIG_HOME" ]; then configfiles="${XDG_CONFIG_HOME}/burrow/config" else configfiles="$HOME/.config/burrow/config $HOME/.config/burrow $HOME/.burrow" fi # vars from config config_dir_gopher="$HOME/public_gopher/" config_dir_phlog="phlog" config_gopher_server="envs.net" config_gopher_port="70" config_gopher_root="/~$USER/" config_phlog_gophermap=true config_phlog_usedate=true config_git_commit=false config_git_push=false config_autoindent=true config_file_rss="rss.xml" config_gopher_name="$USER's gopherhole" config_gopher_desc="this is my gopherhole" config_rss_num_entries="10" config_phlog_autorss=false config_gophermap_omitserver=false config_gophernicus=true # coreutils function wrappers stat_func () { return 0 } # vars from flags flag_debug=0 flag_version=0 flag_help=0 # vars from args arg_options="hvd" arg_shortlist=0 arg_gopherdir=0 arg_phlog=0 arg_quickstart=0 arg_edit_config=0 arg_update_git=0 arg_rss=0 arg_gophermap="" # silence directory movements push_d () { cd "$@" > /dev/null 2> /dev/null || exit } pop_d () { cd - > /dev/null 2> /dev/null || exit } show_help () { cat << END burrow [options] [commands] COMMANDS: phlog Create new phlog entry gophermap Edit a gophermap rss Generate an rss feed of recent phlog entries quickstart Create a new configuration file from prompts edit-config Edit your configuration file update-git Silently pulls gopher git repo if it exists OPTIONAL FLAGS: -h Show this help -v Show current version info -d Debug mode END } check_coreutils () { if stat -c"%U" /dev/null >/dev/null 2>/dev/null ; then # GNU environment stat_func () { stat -c %Y "$1" } date_func () { date -d "$1" +"%a, %d %b %Y %T %z" 2>/dev/null } else # BSD environment stat_func () { stat -Lnqr "$1" | awk '{print $10}' } date_func () { # $1 will be in YYYYMMDD format printf "%s" "$1" | awk 'BEGIN {n = split("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec",month)} { y=substr($0, 0, 4); m=substr($0,5,2); gsub ("^0*", "", m); d=substr($0,7,2); print d, month[m], y, "00:00:00 +0000"}' 2>/dev/null } fi } parse_input () { if ! parsed=$(getopt $arg_options "$@"); then die "Invalid input" 2 fi eval set -- "$parsed" while true; do case "$1" in -h) flag_help=1 shift ;; -v) flag_version=1 shift ;; -d) flag_debug=1 shift ;; --) shift break ;; *) die "Internal error: $1" 3 ;; esac done while test $# -gt 0 do argc=$(printf "%s" "$1" | tr '[:upper:]' '[:lower:]') case $argc in "quickstart") arg_quickstart=1 ;; "shortlist") arg_shortlist=1 ;; "gophermap") if [ $# -gt 1 ]; then shift arg_gophermap="${1%/}/" else arg_gophermap="/" fi ;; "phlog") arg_phlog=1 ;; "gopherdir") arg_gopherdir=1 ;; "edit-config") arg_edit_config=1 ;; "update-git") arg_update_git=1 ;; "rss") arg_rss=1 ;; *) printf "Unknown command: %s\\n" "$argc";; esac shift done } day_suffix () { case $(date +%d) in 01|1|21|31) printf "st";; 02|2|22) printf "nd";; 03|3|23) printf "rd";; *) printf "th";; esac } update_gopher_root () { touch "${config_dir_gopher}gophermap" newdate=$(date +"%B %d$(day_suffix), %Y") newString=$(printf "i ==== Last Updated: %s ====\\t\\t%s\\t%s" \ "$newdate" \ "$config_gopher_server" \ "$config_gopher_port") if [ "$(uname)" = "Darwin" ]; then sed -i '' "s|.*Last\\ Updated:.*|${newString}|" "${config_dir_gopher}gophermap" else sed -i'' "s|.*Last\\ Updated:.*|${newString}|" "${config_dir_gopher}gophermap" fi } check_directory () { if [ ! -d "$1" ]; then die "Missing directory: $1 aborting." 1 fi } die () { msg="$1" code="$2" # exit code defaults to 1 if printf "%s" "$code" | grep -q '^[0-9]+$'; then code=1 fi # output message to stdout or stderr based on code if [ -n "$msg" ]; then if [ "$code" -eq 0 ]; then printf "%s\\n" "$msg" else printf "%s\\n" "$msg" >&2 fi fi exit "$code" } finish () { if [ -f "$temp_gophermap" ]; then rm "$temp_gophermap" fi if [ -f "$temp_post" ]; then rm "$temp_post" fi } trap finish EXIT make_post_git () { if $config_git_commit; then push_d "$config_dir_gopher" git add "${post_dir}/gophermap" "${post_file}" "$type_gophermap" if $update_root; then git add "${config_dir_gopher}/gophermap" fi if $config_phlog_autorss; then git add "${config_dir_gopher}${config_file_rss}" fi git commit -m "$post_type: $title" if $config_git_push; then git pull git push fi pop_d fi } make_post_process_formatting () { # If using gophermap, prefix all post lines with "i" except links if $use_gophermap; then if $config_autoindent; then temp_post=$(mktemp -t "$(basename "$0").post.XXXXXXX") || \ die "Failed to create temporary file" 1 if $config_gophernicus; then awk -v server="${config_gopher_server}" -v port="${config_gopher_port}" '/(^[0-9cdghisGIT;\+].*\t|^[=\*\%\#\!\:\~\.].*)/ {print $0; next} {print "i" $0 "\t\t" server "\t" port}' "$post_file" > "${temp_post}" else awk -v server="${config_gopher_server}" -v port="${config_gopher_port}" '/^[0-9cdghisGIT;\+].*\t/ {print $0; next} {print "i" $0 "\t\t" server "\t" port}' "$post_file" > "${temp_post}" fi # create post file normally to respect umask touch "${post_file}" cat "${temp_post}" > "${post_file}" rm "${temp_post}" fi fi } 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 $config_gophermap_omitserver; then # if using gophermap and date, no server printf "1%s - %s\\t%s\\n" \ "$(date +%Y-%m-%d)" \ "$title" \ "$post_file_path" > "$temp_gophermap" else # if using gophermap and date printf "1%s - %s\\t%s\\t%s\\t%s\\n" \ "$(date +%Y-%m-%d)" \ "$title" \ "$post_file_path" \ "$config_gopher_server" \ "$config_gopher_port" > "$temp_gophermap" fi else if $config_gophermap_omitserver; then # if using gophermap but not date, no server printf "1%s\\t%s\\n" \ "$title" \ "$post_file_path" > "$temp_gophermap" else # if using gophermap but not date printf "1%s\\t%s\\t%s\\t%s\\n" \ "$title" \ "$post_file_path" \ "$config_gopher_server" \ "$config_gopher_port" > "$temp_gophermap" fi fi else if $use_date; then if $config_gophermap_omitserver; then # if not using gophermap but using date, no server printf "0%s - %s\\t%s\\n" \ "$(date +%Y-%m-%d)" \ "$title" \ "$post_file_path" > "$temp_gophermap" else # if not using gophermap but using date printf "0%s - %s\\t%s\\t%s\\t%s\\n" \ "$(date +%Y-%m-%d)" \ "$title" \ "$post_file_path" \ "$config_gopher_server" \ "$config_gopher_port" > "$temp_gophermap" fi else if $config_gophermap_omitserver; then # if not using gophermap or date, no server printf "0%s\\t%s\\n" \ "$title" \ "$post_file_path" > "$temp_gophermap" else # if not using gophermap or date printf "0%s\\t%s\\t%s\\t%s\\n" \ "$title" \ "$post_file_path" \ "$config_gopher_server" \ "$config_gopher_port" > "$temp_gophermap" fi fi fi if [ -f "$type_gophermap" ]; then cat "$type_gophermap" >> "$temp_gophermap" fi if ! $use_date; then sort -fk 1.2 "$temp_gophermap" -o "$temp_gophermap" fi touch "$type_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 } make_post_unprocess () { if [ $config_autoindent ]; then temp_post=$(mktemp -t "$(basename "$0").post.XXXXXXX") || \ die "Failed to create temporary file" 1 # If using gophermaps, unformat it for editing if $use_gophermap; then awk -F"\\t" '/^[0-9h\+GIThs].*\t/ {print $0; next} {sub(/^i/, "", $1);print $1}' "$post_file" > "${temp_post}" else # copy existing post to tempfile cp "$post_file" "${temp_post}" fi fi # Get timestamp of tempfile temp_post_time=$(stat_func "$temp_post") # Edit tempfile ${EDITOR:-vim} "$temp_post" # Verify that timestamp changed after editing, or abort temp_post_time_check=$(stat_func "$temp_post") if [ "$temp_post_time" -ne "$temp_post_time_check" ] ; then touch "${post_file}" cat "${temp_post}" > "${post_file}" rm "${temp_post}" else rm "${temp_post}" return 1 fi } 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 if [ -n "$title" ]; then { printf "%s\\n%s\\n" "----------------------------------------" "$title" if $use_date; then date +"%B %d$(day_suffix), %Y" fi printf "%s\\n\\n\\n" "----------------------------------------" } > "$temp_post" else { printf "%s\\n%s\\n" "----------------------------------------" "$post_type" printf "%s\\n\\n\\n" "----------------------------------------" } > "$temp_post" fi # Check timestamp before editing file temp_post_time=$(stat_func "$temp_post") ${EDITOR:-vim} "$temp_post" # Verify that timestamp changed after editing, or abort temp_post_time_check=$(stat_func "$temp_post") # If we saved a change, create the new file if [ "$temp_post_time" -ne "$temp_post_time_check" ] ; then mkdir -p "${post_dir}" touch "${post_file}" cat "${temp_post}" > "${post_file}" rm "${temp_post}" else rm "${temp_post}" return 1 fi } make_post_paths () { type_gophermap="${config_dir_gopher}${post_type}/gophermap" if $use_gophermap; then title_slug=$(printf "%s" "$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 if [ -z "$title_slug" ]; then post_dir="${config_dir_gopher}${post_type}" post_path="${config_gopher_root}${post_type}" else post_dir="${config_dir_gopher}${post_type}/$title_slug" post_path="${config_gopher_root}${post_type}/$title_slug" fi 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=$(printf "%s" "$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 } make_post () { query="$1" post_type="$2" use_gophermap="$3" use_date="$4" update_root="$5" create_rss="$6" create_gophermap="$7" check_directory "$config_dir_gopher" if [ ! -d "${config_dir_gopher}${post_type}" ]; then mkdir -p "${config_dir_gopher}${post_type}" fi if [ -n "$query" ]; then printf "%s" "$query" read -r title if [ -z "$title" ]; then die "Cancelled." 0 fi else title="" fi make_post_paths if [ -f "$post_file" ]; then make_post_unprocess if [ "$?" -eq 1 ]; then die "Aborted edit" 0 fi make_post_process_formatting else make_post_temp if [ "$?" -eq 1 ]; then die "Aborted post" 0 fi make_post_process_formatting if $create_gophermap; then make_post_gophermap fi fi if $update_root; then update_gopher_root fi if $create_rss; then make_rss fi make_post_git } make_rss () { push_d "${config_dir_gopher}${config_dir_phlog}" search_list=$(find . \ -mindepth 1 \ -type f \ -print | \ grep -v "^\\./gophermap$" | sort -r | \ head -n "${config_rss_num_entries}") { printf '' printf "\\n%s\\n" "$config_gopher_name" printf "gopher://%s%s\\n" "$config_gopher_server" "$config_gopher_root" printf "%s\\n" "$config_gopher_desc" } > "${config_dir_gopher}${config_file_rss}" for f in $search_list; do filename="$(printf "%s" "$f" | sed "s|${config_dir_gopher}${config_dir_phlog}/||" | sed "s|^\\./||")" if printf "%s" "$filename" | grep -q "/gophermap"; then item_type=1 filename="$( printf "%s" "$filename" | sed "s|gophermap$||")" else item_type=0 fi date="$(printf "%s" "$filename" | sed 's|^\./||' | awk 'BEGIN { FS="-" } { print $1; }')" title="$(printf "%s" "$filename" | awk 'BEGIN { FS="-" } { $1=""; print $0; }' | sed "s|/gophermap||" | sed 's/^\ //' | sed 's|/$||' | awk '{for(i=1;i<=NF;i++){ $i=toupper(substr($i,1,1)) substr($i,2) }}1')" dateval=$(date_func "$date") if [ -n "$dateval" ]; then { printf "\\n" printf " %s\\n" "$title" printf " gopher://%s/%s%s%s/%s\\n" "$config_gopher_server" "$item_type" "$config_gopher_root" "$config_dir_phlog" "$filename" printf " %s\\n" "$dateval" printf " \\n" if [ "$item_type" -eq 1 ] then awk -v server="${config_gopher_server}" -v port="${config_gopher_port}" -F"\\t" '/^[2-9\+GITs].*\t/ {print $0; next} /^h.*\t/ { l=substr($1, 2, length($1)); print l "\n " substr($2, 5, length($2)); next } /^[0-1].*\t/ { l=substr($1, 2, length($1)); t=substr($1,1,1); (!$3) ? s=server : s=$3; print l "\n gopher://" s "/" t $2; next } {sub(/^i/, "", $1);print $1}' "$f" else cat "$f" fi printf "]]>\\n" printf "\\n" } >> "${config_dir_gopher}${config_file_rss}" fi done pop_d printf "\\n" >> "${config_dir_gopher}${config_file_rss}" printf "\\n" >> "${config_dir_gopher}${config_file_rss}" } edit_config () { for configfile in $configfiles; do if [ -f "$configfile" ] && [ -w "$configfile" ]; then ${EDITOR:-vim} "$configfile" fi done } yesno () { printf "%s " "$1" read -r yn case $yn in [Yy]* ) result=0 ;; * ) result=1 ;; esac return $result } quickstart () { printf "Location of your local gopher directory? " read -r config_dir_gopher # add trailing slash if missing config_dir_gopher="${config_dir_gopher%/}/" printf "Hostname of your gopher server? " read -r config_gopher_server printf "Port of your gopher server? " read -r config_gopher_port printf "Root directory of your gopher site? " read -r config_gopher_root if yesno "Is your gopher server 'gophernicus'? "; then config_gophernicus=true else config_gophernicus=false fi # add trailing slash if missing config_gopher_root="${config_gopher_root%/}/" if yesno "Create a phlog? "; then printf "Phlog directory? (relative to root of gopher site) " read -r config_dir_phlog # remove leading slash if present config_dir_phlog="${config_dir_phlog#/}" if yesno "Include gophermap? "; then config_phlog_gophermap=true else config_phlog_gophermap=false fi if yesno "Include the date in your phlog posts? "; then config_phlog_usedate=true else config_phlog_usedate=false fi if yesno "Auto-commit to git repo? "; then config_git_commit=true else config_git_commit=false fi if yesno "Auto-push to git remote? "; then config_git_push=true else config_git_push=false fi if yesno "Auto-indent posts? "; then config_autoindent=true else config_autoindent=false fi if yesno "Auto-generate phlog RSS? "; then config_phlog_autorss=true printf "Name of gopher hole? " read -r config_gopher_name printf "Description of gopher hole? " read -r config_gopher_desc printf "RSS filename? " read -r config_file_rss printf "Number of RSS entries? " read -r config_rss_num_entries else config_phlog_autorss=false fi fi } create_config () { match=0 for configfile in $configfiles; do if [ -f "$configfile" ]; then match=1 fi done if [ $match -eq 0 ]; then if [ -n "$XDG_CONFIG_HOME" ]; then config="$XDC_CONFIG_HOME/burrow/config" else config="$HOME/.config/burrow/config" fi mkdir -p "$(dirname "$config")" { printf "config_dir_gopher=\"%s\"\\n" "$config_dir_gopher" printf "\\n" printf "config_gopher_server=\"%s\"\\n" "$config_gopher_server" printf "config_gopher_port=\"%s\"\\n" "$config_gopher_port" printf "config_gopher_root=\"%s\"\\n" "$config_gopher_root" printf "\\n" printf "config_dir_phlog=\"%s\"\\n" "$config_dir_phlog" printf "config_phlog_gophermap=%s\\n" "$config_phlog_gophermap" printf "config_phlog_usedate=%s\\n" "$config_phlog_usedate" printf "config_phlog_autorss=%s\\n" "$config_phlog_autorss" printf "\\n" printf "config_git_commit=%s\\n" "$config_git_commit" printf "config_git_push=%s\\n" "$config_git_push" printf "\\n" printf "config_autoindent=%s\\n" "$config_autoindent" printf "config_gophernicus=%s\\n" "$config_gophernicus" printf "\\n" printf "config_file_rss=\"%s\"\\n" "$config_file_rss" printf "config_gopher_name=\"%s\"\\n" "$config_gopher_name" printf "config_gopher_desc=\"%s\"\\n" "$config_gopher_desc" printf "config_rss_num_entries=\"%s\"\\n" "$config_rss_num_entries" } >> "$config" fi } update_git () { push_d "$config_dir_gopher" if [ "$(git rev-parse --is-inside-work-tree 2> /dev/null)" ]; then git pull -q > /dev/null 2> /dev/null fi pop_d } main () { check_coreutils "$@" parse_input "$@" if [ $arg_shortlist -gt 0 ]; then out="phlog gophermap rss edit-config update-git" die "${out}" 0 fi if [ $flag_version -gt 0 ]; then printf "%s\\n" "$version" fi if [ $flag_help -gt 0 ]; then show_help fi if [ $flag_debug -gt 0 ]; then set -x fi if [ $arg_quickstart -gt 0 ]; then quickstart create_config fi if [ $arg_edit_config -gt 0 ]; then create_config edit_config fi # load system config first if [ -f "$configsystem" ]; then # shellcheck disable=SC1090 . "$configsystem" fi # load any user configuration files in order for configfile in $configfiles; do if [ -f "$configfile" ]; then # shellcheck disable=SC1090 . "$configfile" fi done if [ $arg_gopherdir -gt 0 ]; then die "${config_dir_gopher}" 0 fi if [ $arg_update_git -gt 0 ]; then update_git fi if [ "$arg_gophermap" != "" ]; then make_post "" \ "${arg_gophermap}" \ true \ false \ true \ false \ false 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 \ ${config_phlog_autorss} \ true fi if [ $arg_rss -gt 0 ]; then make_rss fi } main "$@"