#! /bin/bash source_me_please
# The above shbang is intended to cause an error if this script is executed,
# while still satisfying editors that use the shbang line to determine the
# file type.

# 
shopt -s extglob

# password <varname>: prompt for and read a password into varname.
password () {
	[[ -t 0 ]] || { echo "$FUNCNAME: stdin is not a tty"; return 2; }

	# We use the  var=$(foo) && eval  construct so that we can run foo
	# in a subshell but still get data back from it.
	# Since this variable would clash with the user-supplied variable
	# name in the eval, we pick something unlikely to be used.
	local __passwd_str=$(
		local ch='' str=''
		
		# Restore echo even if we die
		trap 'stty echo' EXIT

		# Just read -s is not enough, because characters might 
		# arrive between calls to read; we must keep echo disabled
		# the whole time.
		stty -echo
		printf "Password: " >&2
		while
			IFS='' read -r -s -n 1 ch || {
				exit 1;
			}
			# Is the character not a newline?
			[[ "$ch" ]]
		do
			str="$str$ch"
			printf "*" >&2
		done
		printf "%s" "$str"
		echo >&2
	) && eval "${1-REPLY}="'$__passwd_str';
}


# quote [ARGS...] - safely quote arguments for the shell.
#
# Each argument is printed, enclosed in single quotes and with embedded
# single quotes appropriately escaped.  Output is a space-separated list
# of the quoted strings, followed by a newline.
quote () {
	local arg fixed
	declare -i i=0
	for arg; do
		((i++)) && printf " ";
		# This expansion is misparsed inside double quotes :(
		fixed=${arg//"'"/"'\''"}
		printf "'%s'" "$fixed"
	done
	printf "\n"
}

# Like ssh, except arguments after '--' are not expanded on the remote end.
# Assumes the remote shell can handle arbitrary strings inside single quotes
# (except, of course, single quotes).  Note that, unlike with ssh, the
# hostname *must* precede the '--'.
scmd () 
{ 
    local arg quotehence ssh_args=() cmd=()
    for arg do
        if [[ $arg = -- ]]; then
            quotehence=1
        elif [[ $quotehence ]]; then
            cmd+=("$arg")
        else
            ssh_args+=("$arg")
        fi
    done
    ssh "${ssh_args[@]}" "$(quote "${cmd[@]}")"
}


# POSIX dirname(1) without fork/exec.
#
# "Step N" below refers to the steps in the POSIX specification for dirname:
#    http://www.opengroup.org/onlinepubs/009695399/utilities/dirname.html
dirname () {
	# // is not treated specially by this implementation, so steps
	# 1 and 6 are ignored.

	# Ignore a first argument of '--'
	[[ $1 = -- ]] && shift

	# Detect incorrect number of arguments.  We use string equality
	# instead of numeric for a slight speedup.
	if [[ $# != 1 ]]; then
		# Must use numeric comparison here
		if [[ $# -lt 1 ]]; then
			printf "%s: missing operand\n" "dirname" >&2
		else
			printf "%s: extra operand '%s'\n" "dirname" "$2" >&2
		fi
		return 1
	fi

	# Handle empty string immediately.  The next step converts all-slash
	# strings to the empty string, but we need to be able to distinguish
	# between the two.
	if [[ ! "$1" ]]; then
		echo .
		return
	fi

	# Remove trailing slashes (step 3).  If the string was all slashes,
	# this gives us "" and our ${fn:-/} yields /.  Thus steps 1 and 2 are
	# handled as well.
	local fn=${1%%+(/)}

	if [[ "$fn" = +([!/]) ]]; then
		# No slashes, and the string hadn't been all-slashes.  It's
		# in the current directory (step 4).
		fn=.
	else
		# Remove trailing non-slashes and the slashes that precede them
		# (steps 5, 7).  The string might actually be empty.
		fn=${fn%%+(/)*([!/])}
	fi
	# convert empty string to "/" (step 8).
	printf "%s\n" "${fn:-/}"
}


# POSIX basename(1) without fork/exec.
#
# "Step N" below refers to the steps in the POSIX specification for basename:
#    http://www.opengroup.org/onlinepubs/009695399/utilities/basename.html
basename () {
	# Ignore a first argument of '--'
	[[ $1 = -- ]] && shift

	if [[ $# -lt 1 || $# -gt 2 ]]; then
		# Test again; this way we don't have to do two separate
		# comparisons in the fast case.
		if [[ $# -lt 1 ]]; then
			printf "%s: missing operand\n" "basename" >&2
			return 1
		elif [[ $# -gt 2 ]]; then
			printf "%s: extra operand '%s'\n" "basename" "$3" >&2
			return 1
		fi
	fi

	# Handle empty string immediately (step 1).
	if [[ ! "$1" ]]; then
		echo .
		return
	fi

	# All slashes (step 3).
	if [[ "$1" = +(/) ]]; then
		echo /
		return
	fi

	# Remove trailing slashes (step 4)
	local fn=${1%%+(/)}

	# Remove everything up to the last remaining slash (step 5)
	fn=${fn##*/}

	# Remove the suffix
	[[ "$fn" != "$2" ]] && fn=${fn%"$2"}
	printf "%s\n" "$fn"
}


# pid_is_terminal PID - Test whether a PID is a terminal emulator.
#
# Return success if PID belongs to a terminal emulator program, failure if
# it does not.  This test is not foolproof.
pid_is_terminal() {
	local proc="$(ps -o comm= -p "$1")"
	[[ $proc = *term* || $proc = rxvt* || $proc = screen ]]
}


# dir_access_color [dir] - color based on directory permissions
#
# Output a terminal color code based on one's access rights to the named
# directory.  If no directory is specified, $PWD is used.  Colors are:
#  black:   the directory does not exist
#  blue:    the directory exists but we have no permissions on it
#  magenta: the directory is executable but not readable or writable
#  red:     the directory is readable, but not writable
#  green:   the directory is writable
#
# The bold version of each color is used, so that, with most terminal
# emulators, nonexistent directories yield dark grey, not black.
dir_access_color() {
	local dir="${1-$PWD}" color=0 # black (dk grey)
	[[ -e "$dir" ]] && color=4    # blue
	[[ -x "$dir" ]] && color=5    # magenta
	[[ -r "$dir" ]] && color=1    # red
	[[ -w "$dir" ]] && color=2    # green
	tput -S <<-EOT
		bold
		setaf $color
	EOT
}

# nfm_xterm_title - Set the title of an xterm to something informative.
nfm_xterm_title() {
	local status="$?"
	local host="${HOSTNAME%%.*}"
	local dir="$(dirs +0)"
	dir="${dir%/}/"

	local histline=$(HISTTIMEFORMAT='' history 1)
	histline=${histline#*  }

	# gnome-terminal doesn't mind, but xterm refuses to set the title if
	# it contains non-ASCII characters.  So, use x instead of ×.
	local TITLE="${XTTITLE-${LOGNAME}@${host}}: ${dir} [$$] ${COLUMNS}x${LINES}: $histline [$status]"
	local ICON="${XTTITLE-$LOGNAME}: ${dir}"
	TITLE=${TITLE//[[:cntrl:]]/?}
	ICON=${ICON//[[:cntrl:]]/?}

	printf "\e]2;%s\a\e]1;%s\a" "$TITLE" "$ICON"
}

# spin - run a command with a spinner
# Runs in a subshell to localize trap.
spin () (
	local spinchars='-\|/' delay=1 i size usage=0

	# TODO: make sure there is a tty.
	# TODO: option to send spinner to stderr rather than /dev/tty.

	# Parse arguments; sorry for the lack of getopts
	while (( $# )) && [[ "$1" = -* ]]; do
		if [[ "$1" = "-c" ]]; then
			spinchars=$2
			shift 2
		elif [[ "$1" = "-d" ]]; then
			# TODO: ensure that this is a valid argument to sleep
			delay=$2
			shift 2
		else
			echo "Invalid option: '$1'" >&2
			usage=1
			break
		fi
	done
	if (( usage )) || [[ "$#" -eq 0 ]]; then
		cat >&2 <<EOF
Usage: $0 [-d delay] [-c spinchars] command [args]

Print a spinner while command is running.  spinchars is the list of
characters to use for the spinner, default '-\\|/'.  delay is the delay
in seconds between animation frames, default 1.
EOF
		return 125
	fi
		

	# Spinner loop running in the background.  Output goes directly
	# to the tty.
	for ((i=0,size=${#spinchars};; i=(i+1)%size)); do
		printf '%s\r' "${spinchars:i:1}" >/dev/tty
		sleep "$delay"
	done &

	# Clean up afterwards
	trap 'kill '"$!"'; echo >/dev/tty;' 0 

	# Run the command
	"$@"
)

itime () {
	local precision=2
	local date= pow t s b itime
	local OPTARG OPTIND opt
	while getopts p:wWdDh opt; do
		case $opt in
			p) let precision=OPTARG ;;
			w) precision=0 ;;
			W) precision=2 ;;
			d) date=yes ;;
			D) date= ;;
			h|*) cat >&2 <<-"EOF"
				Usage: itime [-p PREC | -w | -W | -d | -D ]... [ TIME ]
				Print the time TIME (default now) in Swatch Interent Time.
				  -p PREC   print to PREC digits after the decimal point
				  -w        equivalent to -p 0
				  -W        equivalent to -p 2
				  -d/-D     show/do not show date (default -D)
			EOF
			[[ $opt = h ]]  # succeed if -h, fail otherwise
			return
			;;
		esac
	done
	shift $((OPTIND - 1))

	let t=$(date +%s -d "${1:-now}")
	let s=(t+3600)%86400
	if (( precision > 0 )); then
		let pow=10**(1+precision)

		let b=(s*pow)/864
		itime=@$(sed 's/[[:digit:]]\{'"$precision"'\}$/.&/' <<<"$b")
	else
		local i
		let pow=10**(-precision)

		let b=$(( (s*10)/(864*pow) ))
		itime=@$b
		for (( i=precision ; i < 0 && (i-precision < 2); i++ )); do
			itime+="0"
		done
	fi
	printf "%s" "$itime"
	if [[ $date ]]; then
		date -d "${1:-now}" +' d%d.%m.%y'
	else
		echo
	fi
}

song() {
	printf "%s\n\t%s\n%s %s / %s\n" \
	       "$(audtool current-song)" \
	       "$(audtool current-song-filename)" \
	       "$(audtool playback-status)" \
	       "$(audtool current-song-output-length)" \
	       "$(audtool current-song-length)" \
	       ;
}

### Tests for some of the above functions.

_TEST_dirname () {
	[[ $(dirname /)        = "/"   ]] || return 1
	[[ $(dirname //)       = /?(/) ]] || return 2
	[[ $(dirname /a/b/)    = "/a"  ]] || return 3
	[[ $(dirname //a//b//) = "//a" ]] || return 4
	[[ $(dirname a)        = "."   ]] || return 5
	[[ $(dirname "")       = "."   ]] || return 6
	[[ $(dirname /a)       = "/"   ]] || return 7
	[[ $(dirname /a/b)     = "/a"  ]] || return 8
	[[ $(dirname a/b)      = "a"   ]] || return 9
	[[ $(dirname -- /a/b)  = "/a"  ]] || return 10
}

_TEST_basename () {
	[[ $(basename /)          = "/"   ]] || return 1
	[[ $(basename //)         = /?(/) ]] || return 2
	[[ $(basename /a/b/)      = "b"   ]] || return 3
	[[ $(basename //a//b//)   = "b"   ]] || return 4
	[[ $(basename a)          = "a"   ]] || return 5
	[[ $(basename "")         = ?(.)  ]] || return 6
	[[ $(basename /a)         = "a"   ]] || return 7
	[[ $(basename /a/b)       = "b"   ]] || return 8
	[[ $(basename '/a/b/c\d') = 'c\d' ]] || return 9
	[[ $(basename a/b)        = "b"   ]] || return 10
	[[ $(basename a/ab b)     = "a"   ]] || return 11
	[[ $(basename a/ab ab)    = "ab"  ]] || return 12
	[[ $(basename a/ab c)     = "ab"  ]] || return 13
	[[ $(basename a/ab "*")   = "ab"  ]] || return 14
	[[ $(basename "a/a*" "*") = "a"   ]] || return 15
	[[ $(basename -- a/b)     = "b"   ]] || return 16
	[[ $(basename -- a/bcd d) = "bc"  ]] || return 17
}

_TEST_quote () {
	local tests=(
	  # test single- and double-quotes
	  "foo'bar" "'foo" "''\"'\\'" "bar'" "''"
	  # test spaces
	  "foo  bar" "  baz  " "fum  '" "  '  '  "
	  # test other characters
	  "'\"\$x &#{a,b}'"
	)
	local arg
	declare -i i=0

	for arg in "${tests[@]}"; do
		let i++
		local evaled="$(eval "printf '%s' $(quote "$arg")")"
		[[ "$evaled" = "$arg" ]] || {
			printf "<<%s>> != <<%s>>\n" "$arg" "$evaled" 
			return $i
		}
	done
	return 0
}