#!/bin/bash
# setcd bash functions: interpret this in your current shell by sourcing it

# ------------------------------------------------------------------------------

function chdir ()
  {
  # a bare-bones cd: does nothing paticular
  command cd "$@"
  }

# ------------------------------------------------------------------------------

function cd_basic ()
  {
  local oldCWD=`pwd`
  local newCWD=`command cd "$@" > /dev/null && pwd`

  if [[ "$oldCWD" == "$newCWD" ]]; then
    return 0                # destination is already current directory
  fi
  if [[ -z "$newCWD" ]]; then
    return 1                # failed to change to destination directory
  fi

  # execute leaving script if any
  if [[ -r "$oldCWD/.bashcd_old" ]]; then
    source "$oldCWD/.bashcd_old"
  fi

  # change directory
  command pushd "$@" > /dev/null

  # execute entering script if any
  if [[ -r "$newCWD/.bashcd" ]]; then
    source "$newCWD/.bashcd"
  fi
  }

# ------------------------------------------------------------------------------

function cd ()
  {
  # cd should accept:
  # "-"                     revert to previous directory (popd)
  # path                    push path onto the dirs stack, go there
  # path:path:path n        use n to index the path list; cd there
  # path path path n        use n to index the path list; cd there
  # path1 path2 path3       go to path1
  # n                       go to $DIRSTACK[n] if n is not a directory

  # cd will also source the following files if it can change directory:
  # old pwd's .bashcd_old   - before changing directories
  # new pwd's .bashcd       - after changing directories

  if [[ -z "$setcdProc" ]]; then
    setcd
  fi

  local    argc=$#
  local -a argv=( "$@" )
  local    last
  local    num
  local    dir

  if [[ $argc -gt 0 ]]; then
    last=${argv[argc - 1]}
  else
    last=
  fi

  if [[ $argc -eq 0 ]]; then
    # default directory - go home
    set -- ~
  elif [[ $argc -eq 1 ]]; then
    case "$last" in
      -)
        popcd
        return $?
        ;;
      [0-9]*)
        num=`echo "$last" | sed -e 's/[^0-9]//g'`
        if [[ "$num" == "$last" ]]; then
          # completely numeric
          if [[ -d $num || "$CDPATH" == *:${num}:* ]]; then
            dir="$num"
            echo dir="$num"
          elif [[ "$num" -lt ${#DIRSTACK[*]} && "$num" -ge 0 ]]; then
            dir=`command dirs -l -p | awk "NR==$num + 1 "'{ print $0 }'`
            # dir=`echo ${DIRSTACK[num]}` # doesn't work if dir contains '~'
            # echo num=$num "DIRSTACK[$num]"="${DIRSTACK[num]}" dir="$dir"
          else
            echo "${0}: ${FUNCNAME}: directory stack index out of range" 1>&2
            return 1
          fi
          set -- "$dir"
        else
          # leave alone
          :
        fi
        ;;
      *)
        ;;
    esac
  else # [[ $argc -gt 1 ]]; then
    num=`echo "$last" | sed -e 's/[^0-9]//g'`
    if [[ "$num" == "$last" ]]; then
      unset argv[$argc-1]
      dir=$(
        IFS=:
        echo "${argv[*]}" | awk -F: '{i='"$num"' + 1; if (NF >= i) print $i }'
      )
      if [[ -n "$dir" ]]; then
        set -- $dir
      fi
    else
      set -- "$1"
    fi
  fi
  cd_basic "$@"
  }

# ------------------------------------------------------------------------------

function lcd ()
  {
  command dirs -l -v
  }

# ------------------------------------------------------------------------------

function popcd_basic ()
  {
  local    oldCWD=`pwd`
  local    newCWD=`pwd`
  local -a stack=( `dirs -l` )
  local    depth=${#DIRSTACK[*]}
  local    i

  case "x$1" in
    x+*)        command popd "$1" > /dev/null ; return $? ;;
    x-*)        command popd "$1" > /dev/null ; return $? ;;
    x[0-9]*)    i=$((0 + $1)) ;;
    x*)         i=0 ;;
  esac

  [[ $i -ge $depth ]] && i=$depth
  [[ $i -le 0 ]] && i=1
  newCWD="${stack[$(( (i > 0) ? i : 0 ))]}"

  while [[ ( ! -d "$newCWD" ) && ( $i -lt $depth ) ]]; do
    echo "$i - directory $newCWD does not exist: dropping" 1>&2
    i=$((i + 1))
    newCWD="${stack[$i]}"
  done

  if [[ $i == $depth ]]; then
    newCWD=~
  fi

  if [[ "$oldCWD" != "$newCWD" ]]; then
    # execute leaving script if any
    if [[ -r "$oldCWD/.bashcd_old" ]]; then
      source "$oldCWD/.bashcd_old"
    fi
  fi

  if [[ $i == $depth ]]; then
    # echo "$1 - beyond depth ${depth}: clearing stack" 1>&2
    dirs -c
    command pushd ~ -n > /dev/null
    command popd +1 > /dev/null
  else
    # get rid of the intermediate levels
    while [[ $i -gt 1 ]]; do
      command popd +0 -n > /dev/null
      i=$((i - 1))
    done
  fi

  # change directory
  command popd > /dev/null

  # execute entering script if any
  if [[ -r "$newCWD/.bashcd" ]]; then
    source "$newCWD/.bashcd"
  fi
  }

# ------------------------------------------------------------------------------

function popcd ()
  {
  # popcd should accept
  # +n                      calls popd +n (removing the nth entry)
  # -n                      calls popd -n (removing the (depth - n)th entry)
  # n                       pops the stack until n etries have been removed
  popcd_basic "$@"
  }

# ------------------------------------------------------------------------------

function - ()
  {
  popcd "$@"
  }

# ------------------------------------------------------------------------------

function _ ()
  {
  popcd "$@"
  }

# ------------------------------------------------------------------------------

function setcd_usage ()
  {
  local u='call setcd as follows:
	setcd [-nuUtThixXfb][-c|-C <c-factor>]
		-n:	add node host name to prompt (N: only for xterm title)
		-u:	add user name to prompt
		-U:	add user name to xterm title
		-t:	add terminal name to prompt
		-T:	add terminal name to xterm title
		-h:	add history number to prompt
		-c:	shorten the path if too long - equivalent to -C4
		-C:	shorten the path if too long -
			  if <c-factor> is > 0: max len = columns / <c-factor>
			  else show last -<c-factor> path elements
		-i:	apply C shortening for icon (else last part of path)
		-x:	use n, u options and display directory in xterm titles
		-X:	display directory in xterm icons
		-f:	add n, u values to end of directory in xterm icons
		-b:	add n, u values to start of directory in xterm icons'
  echo "$u" 1>&2
  }

# ------------------------------------------------------------------------------

function setcd ()
  {
  if [[ -z "$setcdProc" ]]; then
    setcdProc=$$
    export cdDepth=$((cdDepth + 1))
    [[ $cdDepth -gt 1 ]] && export cdDepthS="<`basename ${SHELL}`:$cdDepth>"
  fi
  # see setcd_usage for usage
  local onode='' oXnode=''
  local ouser='' oXuser=''
  local oterm='' oXterm=''
  local ohist='' ocutN=0 oXcut=''
  local oXnu='' oXdir='' oXnupos=''
  OPTIND=1

  if [[ ( $# -gt 0 ) && ( x"$1" != x-* ) ]]; then
    local arg1="$1"
    shift
    set -- "-$arg1" "$@"
  fi

  while getopts nNuUtThcC:iIxXfb arg; do
    case $arg in
        n)      onode=y ;;
        N)      oXnode=y ;;
        u)      ouser=y ;;
        U)      oXuser=y ;;
        t)      oterm=y ;;
        T)      oXterm=y ;;
        h)      ohist=y ;;
        c)      ocutN=4 ;;
        C)      if ( echo "$OPTARG" | grep -q -E -e '^-?[0-9]+$' ) || \
                   [ "$OPTARG" -eq 0 ]; then
                  ocutN=$OPTARG
                else
                  echo "${0}: setcd: bad divisor number for -C option:"\
                       "$OPTARG" 1>&2
                  setcd_usage
                  return 1
                fi ;;
        i | I)  oXcut=y ;;
        x)      oXnu=y ;;
        X)      oXdir=y ;;
        D)      oXdir= ;;
        f)      oXnupos=f ;;
        b)      oXnupos=b ;;
        \? | :) setcd_usage
                return 1 ;;
    esac
  done
  shift `expr $OPTIND - 1`
  OPTIND=1
  if [[ $# -gt 0 ]]; then
    echo "${0}: setcd: does not take non-option arguments" 1>&2
    setcd_usage
    return 1
  fi

  # set up prompt
  local prefix= suffix= cutcmd=
  [[ -n "$onode" ]] && prefix="`tput bold; tput smso; hostname`:"
# [[ -n "$ouser" ]] && prefix="`tput bold; tput smso; whoami`@$prefix"
  [[ -n "$ouser" ]] && prefix="`tput bold; tput smso; `\u@$prefix"
  [[ -n "$oterm" ]] && prefix="`tput bold`[`tty | sed -e s./dev/..`]$prefix"
# [[ -n "$oterm" ]] && prefix="`tput bold`\][\l]\[$prefix"
  [[ -n "$ohist" ]] && prefix="\]!\!!\[$prefix"

  prefix="`tput bold; tput smso`$cdDepthS`tput sgr0`$prefix"
  prefix="\[$prefix`tput sgr0; tput rev`\]"
  suffix="\[`tput sgr0`\]"

  if [[ -n "$ocutN" && "$ocutN" -ne 0 ]]; then
    if [[ "$ocutN" -lt 0 ]]; then
      cutcmd='CWDL=$((0 - '"$ocutN"'));
              if [[ ( ${#CWD} -gt $CWDL ) && ( $CWDL > 0 ) ]];
              then
                CWDP="...";
                CWD=${CWD:${#CWD}-${CWDL}};
              else
                CWDP="";
              fi;'
    else # $ocutN -gt 0
      cutcmd='CWDL=$((('"$ocutN"' > 0) ? (COLUMNS / '"$ocutN"') : 0));
              if [[ ( ${#CWD} -gt $CWDL ) && ( $CWDL > 0 ) ]];
              then
                CWDP="...";
                CWD=${CWD:${#CWD}-${CWDL}};
              else
                CWDP="";
              fi;'
    fi
  fi
  # set up prompt command string
  PROMPT_COMMAND='CWD=`pwd`; CWDP='';
                  '"$cutcmd"'
                  PS1="'"$prefix"'$CWDP$CWD'"$suffix"' "'
  }

# ------------------------------------------------------------------------------

function addcd ()
  {
  # call addcd as follows:
  #     addcd <name>
  # this adds <name> as a "nickname" of the current directory path
  :
  }

# ------------------------------------------------------------------------------

function autoaddcd ()
  {
  :
  }

# ------------------------------------------------------------------------------

function remcd ()
  {
  :
  }

# ------------------------------------------------------------------------------

function resetcd ()
  {
  :
  }
