#!/bin/bash
# 
# bashrc provides:
#   functions for commonly used operations
#   dynamic prompt for changing system state
#
# XXX http://www.bash-hackers.org/wiki/doku.php/howto/getopts_tutorial

###
# load definitions & functions shared by non/interactive shells

# for vixie cron; % means newline... unescapably
export PERCENT='%'
export PATH=${PATH}:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/usr/X11/bin:/opt/local/bin/:${HOME}/bin
export PATH=$(for path in ${PATH//:/ }; do echo ${path}; done | sort | 
	grep -v '^$' | uniq | tr /\\n/ /:/ )
export VERSION_CONTROL="numbered" # gnu cp,mv obey this

is_command() {
	command -v "$1" >/dev/null 2>&1 && return 0
	which "$1"      >/dev/null 2>&1 && return 1
	return 0
}
requires() {
	for i; do
		is_command ${i} || echo "can't find ${i}"
	done
}

car() {
	cut -d' ' -f 1 <<< "$*"
}

# from http://mediakey.dk/~cc/bash-shell-object-oriented/
alias alias.new='alias'
alias alias.delete='unalias'
alias alias.properties="alias | grep "
alias alias.list='alias'
alias alias.list.terse='alias | cut -d " " -f 2- | cut -d "=" -f1'

directory.new()           { mkdir $* ; }
directory.delete()        { rmdir $* ; }
directory.properties()    { stat $* ; }
directory.contents()      { ls $* ; }
directory.contents.long() { ls -al $* ; }
directory.remove()        { rmdir $* ; }
directory.rename()        { mv $* ; }
directory.change()        { cd "$1" ; }
directory.current()       { pwd ; }
directory.walk_to_root()  {
	requires 'dirname' || { echo 'requires dirname'; return 1; }
	dir="$1"
	[[ -d "$dir" ]] && echo "$dir"
	while [[ "$dir" != "/" ]]; do 
		dir=$(dirname "$dir")
		echo "$dir"
	done
}

environment.has() {
	declare -p $1 >/dev/null 2>&1
	return $?
}

path.has() {
	local file="$1"
	which "${file}" >/dev/null 2>&1
	return $?
}

system.date() { date '+%F' ; }
system.name() { hostname ; }
system.system_info() { uname ; }
system.processes() { ps faxwww ; }
system.time.epoch() { date '+%s' ; }
system.time.from_unix() {
    perl -e 'use POSIX; printf "%s\n", POSIX::strftime("%Y-%m-%d %H:%M:%S", localtime(<STDIN>))' <<< $1
}

    
system.is_macintosh() {
	if [[ "$( uname -m )" = "ppc" ]] && [[ $( uname -s ) = "Linux" ]] ; then  
		return 0
	fi
	if [[ "$(uname -s)" = "Darwin" ]]; then
		return 0
	fi
	return 1
}
system.is_linux() {
	[[ "$(uname -s)" = "Linux" ]] && return 0
	return 1
}
	
system.gateway() {
	system.is_linux && {
		route -n | awk '/^0.0.0.0/ { print $2 }'
		return
	}
	system.is_macintosh && {
		netstat -rn | awk '/^default/ { print $2 }'
		return
	}
	return 1
}

file.new() { touch ; }
file.delete() { rm $* ; }
file.properties() { stat $* ; }
file.exists() { test -f $* ; }
file.contents() { cat $* ; }
file.contents.backwards() { tac $* ; }
file.contents.pager() { less $* ; }
file.contents.reverse() { rev $* ; }
file.rename() { mv $* ; }
file.exists() { test -f ; }
file.as_manual.view() { groff -m man -T ascii $1 | $PAGER ; }
file.time.created() { stat --printf='%Z' $* ; }
file.time.age() {
	# return file age (ctime) in seconds
	file="$1"
	echo $(( $(system.time.epoch) - $(file.time.created "${file}") ))
}
file.open() {
	type="$1"
	file="$2"
	system.is_macintosh && { 
		open -W "${file}"
		return 0
	}

	system.is_linux && {	
		declare -A linux
		linux["pdf"]="xpdf"
		linux["odt"]="oowriter2"
		linux["ods"]="oocalc2"
		${linux[$type]} "${file}"
	}
}

filter.url() {
	grep -ioE "([a-z]*://[^\'\"> \t]*|href *=[ \"\']*[^\"\'\>]*)" | sed -e "s/href[ =\'\"]*//"
}

alias function.new='function'
alias function.delete='unset -f'
alias function.edit="$EDITOR ~/.functions.bash"
alias function.list.detailed='declare -f'
alias function.list.terse='declare -F | cut -d "=" -f2- '
alias function.reload='. ~/.functions.bash'
alias function.save='declare -f > ~/.functions.bash '

net.ip.to_hostname() {
	for i in $*; do 
		host $i | grep -o 'pointer .*' | sed -e 's/pointer //'
	done
}
net.hostname.to_ip() {
	for i in $*; do
		host $i | grep -oE '(address .*|NXDOMAIN)' | sed -e 's/address //' 
	done
}

terminal.is() {
	[[ "${TERM}" = "$1" ]] && return 0
	return 1
}
terminal.has_color_256() {
	environment.has TERM_PROGRAM && return 1

	terminal.is 'rxvt-unicode' && return 0
	terminal.is 'color-xterm' && return 0
	terminal.is 'xterm-color' && return 0
	terminal.is 'screen' && return 0
	environment.has "COLORTERM" && return 0
	return 1
}

url.contents() { wget -q -O - $* ; }
url.get() { wget $* ; }
url.get.slow() { wget --limit=5k $* ; }
url.list() {
	for i in $*; do 
		url.contents ${i} | filter.url
	done
}

user.delete() { userdel $* ; }
user.list() { awk -F: '{ print $1 }'  /etc/passwd | sort ; }
user.new() { useradd $* ; }
user.properties() { id $* ; }

variable.new() { declare $* ; }
variable.delete() { unset $* ; }
variable.exported.list() { declare -x $* ; }
variable.list() { set $* ; }

git.deploy() { git reset --hard HEAD ; }

[[ "$(uname -s)" = "Darwin" ]] && {
	directory.contents.wide() { ls -C "$@" ; }
	system.processes() { ps ax; }
	file.time.created() { stat -f '%B' "$@" ; }
}

nice() {
	path.has ionice && ionice -c 3 -p $1
	path.has nice && command nice -n 19 $1
}

rrm() { command rm "$@"; }
export RM_ARCHIVE="/var/tmp/${USER}"
rm() {
	local secs=$( system.time.epoch )	
	local archive="${RM_ARCHIVE}/${secs}"
	[[ ! -d ${archive} ]] && mkdir -p "${archive}"
	[[ ! -d ${archive} ]] && { echo "rm: unable to mkdir ${archive}" ; return 1; }
	for i; do
		[[ -S "$i" ]] && command rm "$i"
		[[ -e "$i" ]] && command mv "$i" ${archive}/.
	done
}

# reverse() ? 

# curl + cat = wcat -- the liger of bash
wcat() { 
	which curl >/dev/null 2>&1 && curl -s "$1"
	if [[ $? != 0 ]]; then
		which wget >/dev/null 2>&1 && wget -q -O - "$1"
	fi
	return $?
}

pack() {
	require perl || return 1
	if [[ "$#" = "0" ]]; then 
		perl -e '$/="";while(<>){s/\n/!/g;print}'| perl -e 'while(<>){ s/!!([^!])/!\n$1/g; print}'
	else
		var=$1
		perl -e '$/="";while(<>){s/\n/!/g; s/'${var}'/\n/g; print}'
	fi
}
unpack() {
	requires perl || return 1
	if [[ "$#" = "0" ]]; then
		# replace two or more \n's with \n
		perl -e '$/="";while(<>){s/!/\n/g; print}'
	else	
		var=$1
		perl -e '$/="";while(<>){s/'${var}'/\n/g; s/!/\n/g; print}'
	fi
}

# borrowing a word to wise from ruby, note that xargs can't do blocks 
each() { 
	local line=""

	if [[ "$1" = "para" ]]; then
		local para
		shift
		while read line; do
			if [[ "$line" = "" ]]; then
				eval "${@//\{\}/\"${para}\"}" 
				para=""
			else
				# XXX this is dumm
				[[ "$para" != "" ]] && para="${para}
${line}"
				[[ "$para" =  "" ]] && para="${line}"
			fi
		done
		if [[ "$para" != "" ]]; then
			eval "${@//\{\}/\"${para}\"}" 
		fi
	fi

	if [[ "$1" = "word" ]]; then
		shift
		while read line; do
			for word in ${line}; do
				eval "${@/\{\}/\"${word}\"}" 
			done
		done
	fi

	while read line; do
		# $@ =~ s/ {} / $line /
		eval "${@//\{\}/\"${line}\"}" 
	done
}

# terminate command after N seconds
run_for(){
	requires sleep kill || return 1
	cmd="$1"
	duration="$2"

	${cmd} &
	sleep ${duration}
	kill %1  # safe as subprocesses do not see jobs of parent
}

has_disk_space() {
	requires df grep awk sed || return 1
	file_or_dir="$1"
	too_much=90

	used=$(df ${PWD} | awk ' /[0-9][0-9]/ { split($5, A, /%/); print A[1] }' )
	if [[ ${used} -lt ${too_much} ]]; then
		return 0
	fi
	return 1
}

field() {
	awk '{ print $'$1' }'
}

map() {
	requires xargs || return 1
	xargs -i $*
}

reduce() {
	requires awk || return 1
	op=$1
	case ${op} in 
		+)
			init=0
			;;
		'*')
			init=1
			;;
		*)
			init=0
			;;
	esac
	awk 'BEGIN {t='$init'} {t '$op'= $1} END {print t}'
}

repeat() {
	until $*; do
		sleep 1
	done
}


[[ -f "${HOME}/.bashrc.local" ]] && source ${HOME}/.bashrc.local

# non-interactive? bail!
[[ -z "$PS1" ]] && return

### 
# interactive definitions and functions

[[ -z "$COLUMNS" ]] && kill -SIGWINCH $$
shopt -s checkwinsize

# first security
export SSH_AUTH_SOCK="${HOME}/.ssh/agent"
umask 002
ssh_agent_check() {
	ssh_agent=$(ssh-add -l 2>&1)
	no_identities="The agent has no identities."
	no_agent="Could not open a connection to your authentication agent."
	[[ "${ssh_agent}" = "$no_agent" ]] && { 
		file.exists ${SSH_AUTH_SOCK} && { 
			killall -9 ssh-agent 2>/dev/null
			command rm ${SSH_AUTH_SOCK} 2>/dev/null
		}
		ssh-agent -a ${SSH_AUTH_SOCK} >/dev/null
	}
	ssh_agent=$(ssh-add -l 2>&1)
	[[ "${ssh_agent}" = "${no_identities}" ]] && { 
		ssh-add
	}
}

export GPG_TTY=$(tty)

shopt -s execfail
#set -C # dont clobber files on redirect
shopt -s checkjobs 2>/dev/null # new in bash 4

# set -o nounset # error on unset variables, use inside functions

# then comfort
export i=0 # always have a counter lying around
export EDITOR=$( which vim 2>/dev/null || which vi )
export LANG=
export GREP_OPTIONS="--color=never"
export FIND_OPTIONS=" -name .svn -prune -o -name .git -prune -o -name .hg -prune -o -name .cvs -prune -o -print"

set -o physical
shopt -s interactive_comments
shopt -s dirspell 2>/dev/null
shopt -s gnu_errfmt 
shopt -s cdspell 
shopt -s cdable_vars
shopt -s extglob progcomp

if which less >/dev/null 2>&1 ; then 
	export PAGER=less
	export LESS="-iRsX"
fi


# command history
shopt -s cmdhist                    # make a multi-line cmd into one line in history
shopt -s histappend 	            # never delete, only append
shopt -s histreedit 	            # let me retry failed history expansions
shopt -s histverify                 # expand and then re-edit
export HISTFILESIZE= 	            # keep my command history dammit
export HISTSIZE= 	                # keep my command history dammit
# ignore previous command, space-prefixed commands, etc.
export HISTIGNORE="&:ls:[bf]g:exit:mutt:encfs:fusermount:history:mplayer *:bt *:*porn*"
export HISTCONTROL=ignorespace
#export HISTTIMEFORMAT="%Y-%m-%d %H:%M:%S "
unset HISTTIMEFORMAT
# see PROMPT_COMMAND for sync'ing to disk for screen

unalias ls 2>/dev/null # default colors suck
alias ll="command ls -altrh" 
alias mutt="mutt && reset"

intercept_pid_io() { 
	requires strace
	strace -ff -e trace=write -e write=1,2 -p $1 
}

mplayer() {
	xset s off
	path.has xidletime && killall -STOP xidletime
	command mplayer "$@"
	path.has xidletime && killall -CONT xidletime
	xset s on
}

screen_resume() {
	exec ssh-agent -a ${SSH_AUTH_SOCK} screen -R
}
screen_show_in_shelltitle() { 
	# bash \\ bug means use \134
	#printf "\ek$*\e\134"
	system.processes | grep $PPID | grep -q vim && return
	printf "\ek$*\e\134" 1>&2 
}
screen_run() { 
	# pretty print $1 as needed
	# XXX ${FUNCNAME} usable here?
	if [[ ! -z "$STY" ]]; then
		string_to_show=$( sed -e 's/\..*//' <<< $1 )
		screen_show_in_shelltitle ${string_to_show}
	fi
	shift
	[[ "$1" = "exec" ]] && builtin $*
	command $*
}
ssh() { ssh_agent_check; screen_run $1 ssh "$@" ; }
scp() { ssh_agent_check; command scp "$@"; }
sshfs() { ssh_agent_check; command sshfs "$@"; }
telnet() { 
	local cmd="telnet"
	path.has expect && {
		for p in $( cat  ${HOME}/etc/bash/hosts-bs-del ); do
			[[ "$1" =~ $p ]] && cmd="kbdfix.exp telnet"
		done
	}
	screen_run $1 $cmd $1  
}
sudo()   { screen_run $1 sudo   $@ ; }
exec()   { screen_run $1 exec $@ ; }

ps() {
	if [[ ! -z "$1" ]]; then
		command ps $*
		return
	fi
	system.processes
}

man() {
	[[ ! -f "$1" ]] && { command man $*; return $?; }

	file.as_manual.view ${1}
}

cvs_stat() {
	cvs stat 2>/dev/null | grep -E '^File:|^\? ' 
}

cvs_() {
	cvs_stat | grep -v 'Status: Up-to-date' 
}

tmpfile() { echo "/tmp/$(basename $0).$$.tmp"; }

twit() {
	twitfile="${HOME}/.twits"
	url="https://twitter.com/statuses/update.xml"
	__add() { 
		grep -q ^$1\$ ${twitfile} || echo $1 >> ${twitfile}
	}
	for word in $*; do
		[ '@' = "${word/[^@]*/}" ] && __add ${word}
	done
	output=$(curl -v -n -d status="$*" ${url} 2>&1)
	[ $? != 0 ] && echo ${output}
}
complete_twit() {
	local cur=${COMP_WORDS[COMP_CWORD]}
	COMPREPLY=( $( grep -i ${cur} ${HOME}/.twits ) )
}
complete -F complete_twit twit

blog_image() {
	requires scp ssh mv mkdir || return 1
	local local_dir="${HOME}/var/media/pics/"
	local remote_dir="/var/www/www.haller.ws/pics/blog/"

	for img in $*; do
		img_name=$(basename "${img}" )
		ssh haller.ws "ls ${remote_dir}${img_name} >/dev/null 2>&1"
		if [[ $? == 0 ]]; then 
			echo "image already exists on server: ${remote_dir}${img_name}"
			return 1
		fi
		scp "${img}" haller.ws:${remote_dir}
		ssh haller.ws "chmod 644 ${remote_dir}${img_name}"
		[[ ! -d ${local_dir} ]] && mkdir -p ${local_dir}
		mv "${img}" ${local_dir}
	done
}

image_info() {
	# i haz the forgets
	requires identify
	identify $*
}
blog_image_scale() {
	image="$1"
	name="$2"
	[[ -z "$name" ]] && { echo "scale image new_name"; return; }
	local geom=( $(image_info "$image" | 
		awk '{ split($3,A,"x"); printf("%s %s\n", A[1], A[2]) }' ) )
	local scale=""
	if [[ ${geom[0]} -gt ${geom[1]} ]];  then
		scale="200x$(( ${geom[1]} * 200 / ${geom[0]} ))"
	else
		scale="$(( ${geom[0]} * 200 / ${geom[1]} ))x200"
	fi
	convert -scale ${scale} "${image}" "${name}"
}

whats_playing() {
	requires ps grep awk ls grep || return 1
	ps | awk '/mpla[y]er/ { print $1 }' | each ll /proc/{}/fd | grep -Ei 'flv|mp3|mp4|m4a'
}

bt_blocklist_update() {
	trans="${HOME}/.config/transmission/"
	[[ ! -d "$trans" ]] && trans="${HOME}/.config/transmissioncli/"
	[[ ! -d "$trans" ]] && { echo "no ~/.config/transmission* config dir found"; return 1; }
	level1="${trans}/blocklists/level1"
	site="http://update.transmissionbt.com/level1.gz"

	if [[ ! -f ${level1} ]]; then
		wcat ${site} | gunzip > ${level1}.tmp
		[[ "$?" != "0" ]] && return 1
		mv ${level1}.tmp ${level1}
	fi
	if [[ $( file.time.age ${level1} ) -gt $((3 * 86400)) ]]; then
		command rm ${level1}
		wcat ${site} | gunzip > ${level1}.tmp
		[[ "$?" != "0" ]] && return 1
		mv ${level1}.tmp ${level1}
	fi
}
bt_already() {
	echo -n "blocklist update..."
	bt_blocklist_update || {
		echo blocklist failed. exiting...
		return 1
	}
	echo
	transmission-cli -b -w ${HOME}/tmp/downloads/. -f ${HOME}/bin/transmission-end -u 10  "$1"
}
bt() {
	requires wget transmissioncli nice
	echo -n "blocklist update..."
	bt_blocklist_update || {
		echo blocklist failed. exiting...
		return 1
	}
	echo
	old_dir=${PWD}
	media_dir="${HOME}/tmp/downloads"
	[[ ! -d ${media_dir} ]] && mkdir -p ${media_dir}
	cd $media_dir
	torrent=$$.torrent
	if [ -e ${torrent} ]; then
		echo remove ${torrent}
		exit 0
	fi
	trap "rm ${torrent}; echo torrent ${torrent} removed" TERM 

	wcat $1 > ${torrent} 
	bt_already ${torrent}
	rm ${torrent}
	cd ${old_dir}
}
v() { mplayer -loop 0 "$@" ; }
a() { mplayer -novideo -loop 0 "$@" ; }

# drop to a pastebin
pastebin() {
	requires curl se || return 1
	text=""
	if [[ $# = 1 ]]; then
		[[ -f $1 ]] && text=$(cat $1)
	else
		while read line; do
			text="${text}${line}"
		done
	fi
	key="uvXpK+9/H/MBmc9kH7vs3jfOTIgdHrl2"
	curl -s -d "content=${text}" -d "type=1" -d "expiry=1 day" http://pastebin.ca/quiet-paste.php?api=${key} | sed 's/SUCCESS:/http:\/\/pastebin.ca\//'
	echo
}

# NAG_FROM NAG_TO in ~/.bashrc.local
nag() { 
	requires ssh || return 1
	msg="From: ${NAG_FROM}
To: ${NAG_TO}
Subject: $@

$@
"
	ssh haller.ws /usr/sbin/sendmail -oem -oi -t <<EOF &
${msg}
EOF
}

myip() {
	requires grep || return 1
	wcat http://haller.ws/test/printenv.cgi | grep REMOTE_ADDR | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+'
}

mib_view() {
	# XXX needs to actually parse the file....
	local input="cat"
	[[ -f "$1" ]] && input="cat $1"
	${input} | grep -E '(^[a-z]|::=)' | grep -vE '(IDENTIFIER|NOTIFICATION|^--)' \
	| perl -ne ' /^[a-z]/ and chop,chop; print'  | grep OBJECT-TYPE | sed 's/OBJECT-TYPE//' \
	| grep -E '^[a-z]' | sed 's/::=//'
}

comment_strip() {
	perl -ne 'm/^([^#][^\s=]+)\s*(=.*|)/ && printf("%-35s%s\n", $1, $2)' 
}

git_history() {
	git log "$1" |  grep commit | awk ' { print $2 }' | while read c ; do 
		git diff $c "$1" 
	done
}
git_all_child_count_inc() {
	export GIT_ALL_CHILD_COUNT=$(( $GIT_ALL_CHILD_COUNT + 1))
}
git_all_child_count_dec() {
	export GIT_ALL_CHILD_COUNT=$(( $GIT_ALL_CHILD_COUNT - 1))
}
git_all() {
	local max=5
	export GIT_ALL_CHILD_COUNT=0
	trap git_all_child_count_dec SIGCHLD
	ssh_agent_check

	for dir in $HOME/* $HOME/projects/* ; do 
		[[ ! -d ${dir}/.git ]] && continue
		echo -n "$dir "
		( builtin cd $dir 
			command git branch | grep -q '^* master' && command git pull -q 
		) &  
		disown $!
		git_all_child_count_inc
		while [[ $GIT_ALL_CHILD_COUNT -gt $max ]]; do git_all_child_count_inc; sleep 0.25; done
	done
	wait
	trap "" SIGCHLD
}

git_project_client_setup() {
	project="$1"
	[[ "${project}" == "" ]] && { echo "need a project name" ; return 1; }
	builtin cd ${HOME}/projects
	git pull
	git submodule update --init ${project}
    cd ${project}
	git checkout master
}

git_project_server_setup() {
	project="$1"
	[[ "${project}" == "" ]] && { echo "need a project name" ; return 1; }
	cd /var/www/git.haller.ws/projects
	mkdir ${project} && ( 
		cd ${project}
		git init
		echo "module ${project}" > init.txt
		git add init.txt
		git commit -m "Initial commit: module ${project}" 
	)

	git submodule add ssh://git.haller.ws/var/www/git.haller.ws/projects/${project} 
	git commit -m "Added submodule ${project}"
}

cd() {
	dir="$1"
	[[ "${dir}" = "" ]] && dir="${HOME}"
	[[ $dir != '-' && ! -d "${dir}" ]] && {
		requires dirname || echo "no dirname available?"
		dir="$(dirname "${dir}" )"
	}
	builtin cd "${dir}"
	# pretty print an initial directory listing
	requires ls head || return 1
	(ls -C -w $(( ${COLUMNS} - 1)) 2>/dev/null || ls -C )  | head 
}

# verify file changes by printing out the effects
file_change() {
	"$@"
	shift
	for f in "$@" ; do
		[[ ! -f "$f" ]] && continue
		command ls -alh "${f}"
	done
}

mv()    { file_change command mv    "$@"; }
ln()    { file_change command ln    "$@"; }
chmod() { file_change command chmod "$@"; }
chown() { file_change command chown "$@"; }
touch() { file_change command touch "$@"; }

# does a function exist
has_function() {
	type "$1" | grep -q 'shell function'
}

prompt_email() {
	local inbox="${HOME}/.prompt_inbox"
	local count=$( wc -l < $inbox 2> /dev/null | grep -Eo '[0-9]+' )
	[[ $count -gt 0 ]] && echo "${count}email"
	#[[ ! -f $inbox || $( file.time.age $inbox ) -gt 60 ]] && {
    false && {
		( 
        # bg'd subshell, this function still takes 3+ seconds to return. XXX
            sleep 3 
            wcat http://haller.ws/email_inbox.txt > ${inbox},tmp 
            [[ $? = 0 ]] && command mv ${inbox},tmp ${inbox} 
        ) &
        disown $!
	}
}
	
prompt_wifi() {
	wi_stats='/proc/net/wireless'
	[[ ! -e ${wi_stats} ]] && return
    awk '/:/ { if ($3 < 35) printf("Wifi %d%%", $3 )}' ${wi_stats}
}
prompt_error_code() {
	local err=${PROMPT_LAST_CMD_EXIT_CODE}
	[[ $err != 0 ]] && echo "Err ${err}"
}
prompt_hostname() { 
	# show the current machine's name when not local
	[[ -z "${SSH_CONNECTION}" ]] && return
	[[ ! -z "${PROMPT_HOSTNAME}" ]] && echo ${PROMPT_HOSTNAME} && return
	export PROMPT_HOSTNAME=$( hostname 2>/dev/null | sed 's/\..*//' )
	echo ${PROMPT_HOSTNAME} 
}
prompt_datetime() { 
	local hms=$(date +%T 2>/dev/null)
	[[ ! -z "${hms}" ]] && echo ${hms}
}
prompt_dir_has_git() {
	for dir in $(directory.walk_to_root "$PWD"); do 
		[[ -d "${dir}/.git" ]] && return 0
	done
	return 1
}
prompt_repos() {
	# any versioning systems with changes?
	changes=0
	repo=""
	branch=""
	[[ -d "${PWD}/.svn" ]] && {
		changes=$(svn stat | wc -l)
	}
	[[ -f "${PWD}/CVS/Entries" ]] && {
		changes=$(cvs_ | wc -l)
	}
	prompt_dir_has_git && {
		changes=$(git status | grep -c modified: )
		branch=$(git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/(\1)/')
	}

	[[ ${changes} -gt 0 ]] && { 
		repo="${changes} uncommitted"
	}
	prompt_dir_has_git && { 
		repo="${repo} ${branch}"
	}
	echo ${repo}
}	
export MAX_DIRECTORY_SIZE=$(( 25 * 1024 * 1024))
prompt_directory_size() {
	 ls -al | awk '{total+=$5} END{ if (total > ENVIRON["MAX_DIRECTORY_SIZE"] ){printf("%.1fM", total/(1024*1024))} }'
}
prompt_uid_sigil() {
	local uid_sigil='>'
	[ $UID = 0 -o $EUID = 0 ] && uid_sigil='#'
	echo ${uid_sigil}
}
prompt_screen_shelltitle() { # DO NOT OUTPUT TO PS[1-3]!!!
	local screenfix=""
	if [ ! -z "$STY" -a -z "${SSH_CONNECTION}" ]; then
        screen_show_in_shelltitle
	fi
}
prompt_pwd() {
	local wd=${PWD}
	wd=${wd/${HOME}/\~}
	echo ${wd}
}
prompt_network_outage() {
	local outage="${HOME}/.outage"
	if [[ "$1" = "ls" ]]; then
		test -f ${outage} 
		return $?
	fi
	# toggle between outage
	if [[ -f ${outage} ]]; then
		command rm ${outage}
		return
	fi	
	touch ${outage}
}

prompt_network() {
	# test connectivity, shared between a user's logins
	local server="$( system.gateway )"
	local logfile="${HOME}/.prompt_network_test.log"
	local pidfile="${HOME}/.prompt_network_test.pid"
	local pid=""
	local loss=""

	[[ -e ${pidfile} ]] && pid=$( < ${pidfile} )

	export PROMPT_NETWORK_LOSS=""
	prompt_network_outage ls && { # stop!
		export PROMPT_NETWORK_LOSS="* "
		return
	}

	[[ "$1" == "" ]] && { # full stop!!!
		[[ "${pid}" != "" ]] && command ps ${pid} >/dev/null && kill -9 ${pid}
		command rm -f ${pidfile} ${logfile} 2>/dev/null
		return
	}

	# sanity check
	[[ ${pid} != "" ]] && ! command ps ${pid} | grep -q ping && [[ -e ${logfile} ]] && [[ "$( < ${logfile} )" = "" ]] && {
		command rm -f ${pidfile} ${logfile} 2>/dev/null
	}

	[[ ! -e ${logfile} ]] && { # go!go!go! 
		ping -c 5 ${server} > ${logfile} 2>/dev/null &
		pid=$!
		disown ${pid} # don't tell us about it starting or ending
		command rm -f ${pidfile} 2>/dev/null
		echo ${pid} > ${pidfile}
		! command ps ${pid} >/dev/null && {
			command rm -f ${logfile} 2>/dev/null
			export PROMPT_NETWORK_LOSS="No Net"
			return
		}
	}

	grep -q -i loss ${logfile} && { # report
		loss=$( grep -Eo '[0-9]+% packet loss' ${logfile} | sed -e 's/[^0-9]//g' )
		[[ "$loss" > 0 ]] && export PROMPT_NETWORK_LOSS="Pkt Loss ${loss}%"
		command rm -f ${logfile} 2>/dev/null
	}
}
trap prompt_network EXIT
prompt_network_loss() {
	echo "${PROMPT_NETWORK_LOSS}"
}

prompt_disk_usage(){
	df "${PWD}" | awk ' /[0-9][0-9]/ && $5 ~ /9[0-9]%/ { printf("Disk %s", $5) }' 
}
export PROMPT_LOAD_MAX=3
export PROMPT_LOAD_NOTIFY=1
cpu_usage_as_int() {
	uptime | awk '{ split($0,A,"s:");split(A[2],B," "); printf("%d", B[1]); }'
}
prompt_cpu_usage_as_int() {
    local load=$(cpu_usage_as_int)
	[[ ${load} -gt ${PROMPT_LOAD_NOTIFY} ]] && echo "load ${load}"
}
prompt_time_since_last_command() {
	echo $(( ${SECONDS} - ${PROMPT_PREVIOUS_TIME} ))
}
export PROMPT_DNS_INTERVAL_MAX=30
prompt_dns_ping() {
	if [[ $(prompt_time_since_last_command) -lt ${PROMPT_DNS_INTERVAL_MAX} ]]; then
		[[ $(( ${HISTCMD} % 5 )) != 0 ]] && return
	fi
	host x.org >/dev/null 2>&1 &
	[[ $? != 0 ]] && echo "dns"
}
prompt_pad() {
	local color=$1
	shift
	ret=$( $* )
	[[ "${ret}" = "" ]] && return 
	echo -n "${color}${ret} ${PROMPT_COLOR}"
}
export PROMPT_KILL_FILE="${HOME}/.prompt_kill"
prompt_kill() {
	touch ${PROMPT_KILL_FILE}
}
prompt_kill_resume() {
	rm -f ${PROMPT_KILL_FILE}
}

# XXX constantly updating prompt? 
# XXX bash namespaces like VAX MAIL>
export PROMPT_MAX_LENGTH=100 # color escapes count in this....

export TEXT_BLACK=''
export  TEXT_BLUE=''
export   TEXT_RED=''
export  TEXT_GREY=''
# got 256 colors? use 'em http://www.frexx.de/xterm-256-notes/
terminal.has_color_256 && {
	export TEXT_BLACK='\[\e[0:30m\]'
	export  TEXT_BLUE='\[\e[0:34m\]'
	export   TEXT_RED='\[\e[0:31m\]'
	export  TEXT_GREY='\[\e[38;5;242m\]'
}
terminal.is "rxvt-unicode" && {
	export  TEXT_GREY='\[\e[38;5;84m\]'
}
export PROMPT_COLOR=${TEXT_GREY}

sane_prompt() {
	export PROMPT_LAST_CMD_EXIT_CODE=$? # must be first
	#local pipestatus=${PIPESTATUS[*]}  # doesn't actually work yet

    # disabled by operator?
	[[ -e ${PROMPT_KILL_FILE} ]] && return

    # box too busy, be nice, needs FP math
	local load=$( cpu_usage_as_int )
	if [[ ${load} -gt ${PROMPT_LOAD_MAX} ]]; then 
		echo CPU LOAD ABOVE ${PROMPT_LOAD_MAX} 
		return	
	fi

	# sync history to disk asynch
	history -a 

	prompt_screen_shelltitle 
	prompt_network run
	local pkts=$( prompt_pad ${TEXT_RED} prompt_network_loss )
	local wifi=$( prompt_pad ${TEXT_BLUE} prompt_wifi )
	local hms=$( prompt_pad ${PROMPT_COLOR} prompt_datetime )
	local box=$( prompt_pad ${PROMPT_COLOR} prompt_hostname )
	local dirsize=$( prompt_pad ${TEXT_BLUE} prompt_directory_size )
	local repo=$( prompt_pad ${TEXT_BLUE} prompt_repos )
	local disk=$( prompt_pad ${TEXT_RED} prompt_disk_usage )
	local cpu=$( prompt_pad ${TEXT_BLUE} prompt_cpu_usage_as_int )
	local email=$( prompt_pad ${TEXT_BLUE}  prompt_email  )
	local err=$( prompt_pad ${TEXT_RED}  prompt_error_code  )
	local wd=$( echo -n ${PROMPT_COLOR}; prompt_pwd )
	local sigil=$( prompt_uid_sigil )
	PS1="${hms}${box}${disk}${cpu}${wifi}${pkts}${dirsize}${email}${repo}${err}${wd}"
	[[ ${#PS1} -gt ${PROMPT_MAX_LENGTH} ]] && PS1="${PS1}\n"
	export PS1="${PS1}${sigil}${TEXT_BLACK} "
	export PROMPT_PREVIOUS_TIME=${SECONDS}
    #shopt -s extdebug
}
export PROMPT_COMMAND=sane_prompt

# setup precmd and postcmd like other real shells have
# borrowed from http://www.twistedmatrix.com/users/glyph/preexec.bash.txt
pre_post_cmd_handler() {
    # in completion
	[[ -n "${COMP_LINE}" ]] && return 
    # in second DEBUG call after command completes
	if [[ "${BASH_COMMAND}" = "${PROMPT_COMMAND}" ]]; then
		#shopt -u extdebug
		postcmd 
		return 0
	fi	

    # in first DEBUG call before command is run
	precmd "${BASH_COMMAND}"
}
#trap pre_post_cmd_handler DEBUG
precmd() {
	local cmd="$1"
    [[ "${cmd}" =~ '=' ]] && return 0
	
	[[ ${BASH_VERSINFO[0]} -lt 4 ]] && return 0
	if ! is_command $( car ${cmd} ); then
		# we think this is going to bomb, re-edit
		trap "echo;exec bash" INT
		local oldcmd="${cmd}"
		read -p "error> " -e -i "${cmd}" cmd
		${cmd}
		trap - INT
		return 1
	fi
	return 0
}
postcmd() {
	echo -n ''
}

command_not_found_handle() {
	local cmd="$*"
	[[ ${BASHVERSINFO[0]} -lt 4 ]] && {
		echo "no such command" #$cmd
		return
	}
	read -p "errors> " -e -i "${cmd}" cmd
	${cmd}
}

dvd_burn() {
	pushd "${PWD}"
	dir="${HOME}/tmp/dvd_burn_${RANDOM}"
	[[ -e ${dir} ]] && return 1
	mkdir -p ${dir}
	cd ${dir}

	[[ -z "$1" ]] && echo needs chapter count && return 1
	[[ -z "$2" ]] && echo needs hours:minutes:seconds duration && return 1
	__hms() {
		echo $(( $1 * 60 * 60 + $2 * 60 + $3 ))
	}
	total_time=$( __hms ${2//:/ })
	echo movie time in seconds: ${total_time}

	# mplayer uses kbits as default units = 1000 bits	
	local audio_rate=112
	local audio_size=$(( ${total_time} * ${audio_rate} ))
	local max_size=$((700 * 1000 * 8))  # 700mb in kbits
	local free_size=$(( ${max_size} - ${audio_size} ))
	local video_rate=$(( ${free_size} / ${total_time} ))
	echo video rate = ${video_rate}

	echo
	echo RIPPING AUDIO:
	echo ==============
	time mencoder dvd://2 -quiet -ovc frameno -o frameno.avi -oac mp3lame -lameopts abr:br=${audio_rate}
	echo 
	echo GENERATING VIDEO STATISTICS FILE:
	echo =================================
	time mencoder dvd://2 -quiet -nosound -oac copy -o /dev/null -ovc lavc -lavcopts \
		vcodec=mpeg4:vbitrate=${video_rate}:vhq:vpass=1:vqmin=1:vqmax=31 \
		-vf scale -zoom -xy 640 
	echo
	echo RIPPING VIDEO:
	echo ==============
	time mencoder dvd://2 -quiet -oac copy -o file.avi -ovc lavc -lavcopts \
		vcodec=mpeg4:vbitrate=${video_rate}:vhq:vpass=2:vqmin=1:vqmax=31 \
		-vf scale -zoom -xy 640 
	echo
	echo FINISHED DVD IN $PWD
	echo ====================
	popd
}






##### completions follow
complete -A hostname ping traceroute nmap telnet
complete -d cd
complete -f less
complete -A stopped -P '%' bg
complete -j -P '%' fg jobs disown
complete -v readonly unset
complete -A setopt set
complete -A shopt shopt
complete -A helptopic help
complete -a unalias
complete -A binding bind
complete -c command type which
complete -b builtin

complete_sudo() {
	if [[ ${COMP_WORDS[1]} == "pacman" ]]; then 
		complete_pacman
		return
	fi
	local cur=${COMP_WORDS[COMP_CWORD]}
	if [[ ${COMP_CWORD} = 1 ]]; then
		COMPREPLY=( $( compgen -c -- ${cur} ) )
		return
	fi
	COMPREPLY=( $( compgen -f -- ${cur} ) )
}
complete -F complete_sudo -o filenames sudo

complete_ssh() {
	local cur=${COMP_WORDS[COMP_CWORD]}
	#local hosts=$( grep -i '^Host\b'"[ \t]*${cur}" ${HOME}/.ssh/config  | sed 's/^[Hh]ost//' )
	local hosts=$( grep -io "^${cur}[^ ,]*" ${HOME}/.ssh/known_hosts )
	[[ "StrictHostKeyChecking" =~ ${cur} ]] && hosts="${hosts} StrictHostKeyChecking=no"
	COMPREPLY=( ${hosts} )
}
complete -A hostname -F complete_ssh ssh ping
complete -A hostname -F complete_ssh -f scp

complete_pacman() {
	#find balks easilly on a find /foo/*/* type dir, especially one like
	#   /var/lib/pacman/*/*
	# This little change-up removes the find *and* only uses enabled repos
	local available_pkgs
	local enabled_repos
	cur=${COMP_WORDS[COMP_CWORD]}
	enabled_repos=$( grep '\[' /etc/pacman.conf | grep -v -e 'options' -e '^#' | tr -d '[]' )
	available_pkgs=$( for r in $enabled_repos; do echo /var/lib/pacman/sync/$r/*; done )
	COMPREPLY=( $( compgen -W "$( for i in $available_pkgs; do j=${i##*/}; echo ${j%-*-*}; done )" -- $cur ) )
}

complete_makefile() {
	cur=${COMP_WORDS[COMP_CWORD]}
	makef_dir="."
	# before we check for makefiles, see if a path was specified with -C
	for (( i=0; i < ${#COMP_WORDS[@]}; i++ )); do
		if [[ ${COMP_WORDS[i]} == -C ]]; then
			# eval for tilde expansion
			eval makef_dir=${COMP_WORDS[i+1]}
			break
		fi
	done

	# make reads `GNUmakefile', then `makefile', then `Makefile'
	if [[ -f ${makef_dir}/GNUmakefile ]]; then
		makef=${makef_dir}/GNUmakefile
	elif [[ -f ${makef_dir}/makefile ]]; then
		makef=${makef_dir}/makefile
	elif [[ -f ${makef_dir}/Makefile ]]; then
		makef=${makef_dir}/Makefile
	else
		makef=${makef_dir}/*.mk        # local convention
	fi

	# before we scan for targets, see if a Makefile name was specified with -f
	for (( i=0; i < ${#COMP_WORDS[@]}; i++ )); do
		if [[ ${COMP_WORDS[i]} == -f ]]; then
			# eval for tilde expansion
			eval makef=${COMP_WORDS[i+1]}
			break
		fi
	done

	[[ ! -f $makef ]] && return 0

	# deal with included Makefiles
	makef_inc=$( grep -E '^-?include' $makef | sed -e "s,^.* ,"$makef_dir"/," )

	for file in $makef_inc etc/Makefile.$(system.system | tr A-Z a-z); do
		[[ -f $file ]] && makef="$makef $file"
	done

	COMPREPLY=( $( awk -F':' '/^[a-zA-Z0-9][^$#\/\t=]*:([^=]|$)/ \
		{split($1,A,/ /);for(i in A)print A[i]}' \
		$makef 2>/dev/null | command grep "^$cur" ))
}
complete -F complete_makefile make

#complete -f -X '!*.?(t)bz?(2)' bunzip2 bzcat bzcmp bzdiff bzegrep bzfgrep bzgrep
# can't complete directory names = foo


# archive
#test -d /prec/asound && test -f /usr/lib/libaoss.so && export LD_PRELOAD=libaoss.so
