#!/bin/bash
# $Id: dir_array,v 1.34 2005/09/02 00:25:19 jlarsen Exp $
# This file has functions for manipulating an array of directories to make
# it easy to switch between directories.  It handles dirnames with spaces in them.
#
# Call this file from within your .bashrc so that the functions are loaded when your shell is started.
# Make sure it is called after $HISTIGNORE is set in .bashrc because dir_array appends to $HISTIGNORE.

# Initialize required variables
declare -a DIR_ARRAY
DIR_ARRAY[1]=`pwd`
ARRAY_TOP=1
ARRAY_IDX=1
PREV_ARRAY_IDX=1
FILE_DIR=~/.dir_array
DEFAULT_FILE="default"
AUTO_ADD=off

# Add all the dir_array function calls to $HISTIGNORE so that they don't fill up the history.
# Append to $HISTIGNORE if it exists and create it if it doesn't.  For this to work, dir_array must be
# called after $HISTIGNORE is set in the user's .bashrc otherwise it will get overwritten.
if [ "$HISTIGNORE" != "" ]; then
	HISTIGNORE="$HISTIGNORE:a .:c:u:j:s:d:s l:s w:s df:s lf:s wf:s a:s af:s ?:b:f:t:j 1:j 2:j 3:j 4:j 5:j 6:j 7:j 8:j 9:j 10:j 11:j 12:j 13:j 14:j 15:j 16:j 17:j 18:j 19"
else
	HISTIGNORE="a .:c:u:j:s:d:s l:s w:s df:s lf:s wf:s a:s af:s ?:b:f:t:j 1:j 2:j 3:j 4:j 5:j 6:j 7:j 8:j 9:j 10:j 11:j 12:j 13:j 14:j 15:j 16:j 17:j 18:j 19"
fi

# Initialize the version
DIR_ARRAY_VER="v`echo '$Id: dir_array,v 1.34 2005/09/02 00:25:19 jlarsen Exp $' | awk '{print $3}'`"
DIR_ARRAY_DATE="`echo '$Id: dir_array,v 1.34 2005/09/02 00:25:19 jlarsen Exp $' | awk '{print $4}'`"


#-----------------------------------------------------------------------------
# dir_array_help - Function that displays help text
function dir_array_help () {
cat << EOF
 dir_array command summary:
 a dir - Append directory to end of list (Accepts ".")
 b     - Backup one directory in list with wrap around
 c     - Clear directory list and put \`pwd\` as entry 1 (See "u" below)
 d [n] - Delete entry from list. (Accepts "." for current ==> position.)
 f     - Forward one directory in list with wrap around
 i dir - Insert directory before current directory (Accepts ".")
 j [n] - Jump to directory in list ("." is a no-op ) (Calls "a" if not in list)
 s     - Show directory list
          ?   - Show this command summary
          a   - Append default file "$DEFAULT_FILE" to directory list
          af  - Append a file from "$FILE_DIR" to directory list
          df  - Delete a file in "$FILE_DIR"
          l   - Load directory list from default file "$DEFAULT_FILE"
          lf  - Load directory list from file in "$FILE_DIR"
          off - auto_add mode disabled (default startup mode)
          on  - auto_add mode enabled
          s   - Sort the directory list
          w   - Write directory list to default file "$DEFAULT_FILE"
          wf  - Write directory list to file in "$FILE_DIR"
 t     - Toggle back to previous directory
 u     - Undo "c"
 Note: ==> n    points to current directory
           n <  points to previous directory
EOF
}

#-----------------------------------------------------------------------------
# a - Function to add a directory to the list
# $1 is the directory to add
# $2 if not null then the call to "s" is suppressed
function a () {
   # Exit if nothing entered for $1
	if [ ! "$1" ]; then
		echo "Enter valid directory path or \".\""
		return
	fi

	local entry="$1"

   # Only do this if the directory exists
	if [ ! -d "$entry" ]; then
		echo "Invalid dir: $entry"
		return
	fi

   # Expand "." to full path name
	if [ "$entry" = "." ]; then 
		entry=`pwd`
	fi

   # If directory is relative pathname then cd first to get full pathname from pwd
	if [ "${entry:0:1}" != "/" ]; then
		builtin cd "$entry"
		entry=`pwd`
	fi

   # Strip off a trailing "/".  Solaris puts them in there when using file completion.
	entry=`echo "$entry" | sed -e 's/\/$//'`

   # If entry is now null then directory was root "/" so put the slash back in
	if [ ${#entry} -eq 0 ]; then
		entry="/"
	fi

   # Only add this to the array if it isn't already in the array. Otherwise, cd to it.
	local index=1
	while [ $index -le $ARRAY_TOP ]; do
		if [ "$entry" = "${DIR_ARRAY[$index]}" ]; then
			builtin cd "${DIR_ARRAY[$index]}"
			ARRAY_IDX=$index
			if [ "$2" == "" ]; then
				s
			fi
			return
		fi
		let index=index+1
	done

   # Directory exists and isn't in the array so add it and cd to the array
	let ARRAY_TOP=ARRAY_TOP+1
	DIR_ARRAY[$ARRAY_TOP]="$entry"
	PREV_ARRAY_IDX=$ARRAY_IDX
	ARRAY_IDX=$ARRAY_TOP
	builtin cd "$entry"
	if [ "$2" == "" ]; then
		s
	fi

} # a


#-----------------------------------------------------------------------------
# b - Function to move backward one position in the array
function b () {
	PREV_ARRAY_IDX=$ARRAY_IDX
	let ARRAY_IDX=ARRAY_IDX-1
	if [ $ARRAY_IDX -le 0 ]; then
		ARRAY_IDX=$ARRAY_TOP
	fi
	builtin cd "${DIR_ARRAY[$ARRAY_IDX]}"
	#s
} # b


#-----------------------------------------------------------------------------
# c - Function that clears the entire array
function c () {
	OLD_ARRAY_TOP=$ARRAY_TOP
	ARRAY_TOP=1
	OLD_PREV_ARRAY_IDX=$PREV_ARRAY_IDX
	PREV_ARRAY_IDX=1
	OLD_ARRAY_IDX=$ARRAY_IDX
	ARRAY_IDX=1
	OLD_DIR_ARRAY_1=${DIR_ARRAY[1]}
	DIR_ARRAY[1]=`pwd`
	s
} # c


#-----------------------------------------------------------------------------
# cd - Function used to auto add directories when autoadd is enabled
function cd () {
   if [ "$AUTO_ADD" == "off" ]; then
      if [[ "$1" == "" ]]; then
         builtin cd ~
      else
         builtin cd "$*"
      fi
   else
      if [[ "$1" == "" ]]; then
         a ~ no_show
      else
         a "$1" no_show
      fi
   fi
} # cd


#-----------------------------------------------------------------------------
# d - Function to delete a directory from the array
function d () {

   # Don't delete the only entry left
	if [ $ARRAY_TOP -le 1 ]; then return; fi

   # Display list if number not on the command line.  Accept "." as current directory entry.
	if [ $1 ]; then
		if [ "$1" = "." ]; then
			local entry=$ARRAY_IDX
		else
			if [ $1 -lt 1 -o $1 -gt $ARRAY_TOP ]; then
				echo "Error: Number out of range"
				return
			fi
			local entry=$1
		fi
	else
		s
		local response=""
		while [ "$response" = "" ]; do
			read -a response -p "Delete entry (CR to exit): "
         # Exit if no number was entered
			if [ "$response" = "" ]; then
				return
			fi
         # Use current pointer value if "." entered
			if [ "$response" = "." ]; then
				local entry=$ARRAY_IDX
			else
				if [ $response -lt 1 -o $response -gt $ARRAY_TOP ]; then
					echo "Error: Number out of range"
					return
				fi
				local entry=$response
			fi
		done
	fi

   # Setup to shift array contents down
	local index1="$entry"
	local index2="$entry"
	let index2=index2+1
   # Adjust ARRAY_IDX down one if its value is higher or equal to the entry to be deleted
	if [ $ARRAY_IDX -ge $index1 ]; then
		let ARRAY_IDX=ARRAY_IDX-1
		if [ $ARRAY_IDX -le 0 ]; then 
			ARRAY_IDX=1
		fi
	fi

   # Adjust PREV_ARRAY_IDX down one if its value is higher or equal to the entry to be deleted
	if [ $PREV_ARRAY_IDX -ge $index1 ]; then
		let PREV_ARRAY_IDX=PREV_ARRAY_IDX-1
		if [ $PREV_ARRAY_IDX -le 0 ]; then 
			PREV_ARRAY_IDX=1
		fi
	fi

   # Make the move
	while [ $index2 -le $ARRAY_TOP ]; do
		DIR_ARRAY[$index1]=${DIR_ARRAY[$index2]}
		let index1=index1+1
		let index2=index2+1
	done
   # Adjust the top of the array down
	let ARRAY_TOP=ARRAY_TOP-1
   # cd to the array entry pointed to by ARRAY_IDX in case it changed
	builtin cd "${DIR_ARRAY[$ARRAY_IDX]}"
	s
} # d


#-----------------------------------------------------------------------------
# f - Function to move forward one position in the array
function f () {
	PREV_ARRAY_IDX=$ARRAY_IDX
	let ARRAY_IDX=ARRAY_IDX+1
	if [ $ARRAY_IDX -gt $ARRAY_TOP ]; then
		ARRAY_IDX=1
	fi
	builtin cd "${DIR_ARRAY[$ARRAY_IDX]}"
	#s
} # f


#-----------------------------------------------------------------------------
# i - Function to inserts a directory at the current location in the array
function i () {
   # Exit if nothing entered for $1
	if [ ! "$1" ]; then
		echo "Enter valid directory path or \".\""
		return
	fi

	local entry="$1"

   # Only do this if the directory exists
	if [ ! -d "$entry" ]; then
		echo "Invalid dir: $entry"
		return
	fi

   # Expand "." to full path name
	if [ "$entry" = "." ]; then 
		entry=`pwd`
	fi

   # If directory is relative pathname then cd first to get full pathname from pwd
	if [ "${entry:0:1}" != "/" ]; then
		builtin cd "$entry"
		entry=`pwd`
	fi

   # Strip off a trailing "/".  Solaris puts them in there when using file completion.
	entry=`echo "$entry" | sed -e 's/\/$//'`

   # If entry is now null then directory was root "/" so put the slash back in
	if [ ${#entry} -eq 0 ]; then
		entry="/"
	fi

   # Only add this to the array if it isn't already in the array
	local index=1
	while [ $index -le $ARRAY_TOP ]; do
		if [ "$entry" = "${DIR_ARRAY[$index]}" ]; then
			builtin cd "${DIR_ARRAY[$index]}"
			ARRAY_IDX=$index
			s
			return
		fi
		let index=index+1
	done

   # Directory exists and isn't in the array so add it and CD to the array
   # First move all array entries up one to open a spot for new directory
	local index1=$ARRAY_TOP
	let ARRAY_TOP=ARRAY_TOP+1
	local index2=$ARRAY_TOP
	while [ $index1 -ge $ARRAY_IDX ]; do
		DIR_ARRAY[$index2]=${DIR_ARRAY[$index1]}
		let index1=index1-1
		let index2=index2-1
	done
		  
   # Save previous ARRAY_IDX for use by "t"
	let PREV_ARRAY_IDX=ARRAY_IDX+1

   # Now put the new directory in the array
	DIR_ARRAY[$ARRAY_IDX]="$entry"
	builtin cd "$entry"
	s
} # i


#-----------------------------------------------------------------------------
# j - Function to jump to a directory
function j () {
	if [ $1 ]; then
      # Handle the "." case by calling "a" with current directory
		if [ "$1" = "." ]; then
			a `pwd`
			return
		fi

      # Look for alpha characters and call "a" if found
		local num_test=`echo $1 | sed -e 's/^[1-9].*/NUM/p'`
		if [ "${num_test:0:3}" != "NUM" ]; then
			a "$1"
			return
		fi

      # Number entered so try using it
		if [ $1 -lt 1 -o $1 -gt $ARRAY_TOP ]; then
			s
			echo "Error: Number out of range"
			return
		fi

      # Only need to update and jump if $1 isn't the same as ARRAY_IDX
		if [ $1 -ne $ARRAY_IDX ]; then
			PREV_ARRAY_IDX=$ARRAY_IDX
			let ARRAY_IDX=$1
			builtin cd "${DIR_ARRAY[$ARRAY_IDX]}"
		else
         # If pwd doesn't equal DIR_ARRAY[$ARRAY_IDX] then need to jump back
			if [ "`pwd`" != "${DIR_ARRAY[$ARRAY_IDX]}" ]; then
				builtin cd "${DIR_ARRAY[$ARRAY_IDX]}"
			fi
		fi
		#s
	else
      # Nothing entered in $1 so show the list and ask where to jump
		s
		local response=""
		while [ "$response" = "" ]; do
			read -a response -p "Select directory: "
			if [ "$response" = "." -o "$response" = "" ]; then
				return
			else
				if [ $response -lt 1 -o $response -gt $ARRAY_TOP ]; then
					echo "Error: Number out of range"
					return
				fi
			fi
		done

      # Only need to update and jump if $response isn't the same as ARRAY_IDX
		if [ $response -ne $ARRAY_IDX ]; then
			PREV_ARRAY_IDX=$ARRAY_IDX
			ARRAY_IDX=$response
			builtin cd "${DIR_ARRAY[$ARRAY_IDX]}"
		else
         # If pwd doesn't equal DIR_ARRAY[$ARRAY_IDX] then need to jump back
			if [ "`pwd`" != "${DIR_ARRAY[$ARRAY_IDX]}" ]; then
				builtin cd "${DIR_ARRAY[$ARRAY_IDX]}"
			fi
		fi
		#s
	fi
} # j


#-----------------------------------------------------------------------------
# s - Function to show contents of the directory array or help if $1 isn't null
function s () {
	if [ $1 ]; then
		case $1 in
			a) # Append default $DEFAULT_FILE file to $DIR_ARRAY
				append_dir_array $FILE_DIR/$DEFAULT_FILE
				return
				;;

			af) # Append file from $FILE_DIR to $DIR_ARRAY
				append_dir_array_file
				return
				;;

			df) # Delete file from $FILE_DIR 
				delete_dir_array_file
				return
				;;

			l) # Load $DIR_ARRAY from default file $DEFAULT_FILE
				load_dir_array $FILE_DIR/$DEFAULT_FILE
				return
				;;

			lf) # Load $DIR_ARRAY from file in $FILE_DIR
				load_dir_array_file
				return
				;;

			off) # Disable auto add mode
				AUTO_ADD=off
            echo "auto_add mode \"off\""
				return
				;;

			on) # Enable auto add mode
				AUTO_ADD=on
            echo "auto_add mode \"on\""
				return
				;;

			s) # Sort directory list
				sort_dir_array
				return
				;;

			w) # Write DIR_ARRAY to default file $DEFAULT_FILE
				write_dir_array $FILE_DIR/$DEFAULT_FILE
				return
				;;

			wf) # Write DIR_ARRAY to file in $FILE_DIR
				write_dir_array_file
				return
				;;

			*) # Display command line reference
				dir_array_help
				;;
		esac

	else
		local index=1
		local current_dir=`pwd`
		while [ $index -le $ARRAY_TOP ]; do
			if [ $index -eq $ARRAY_IDX  -a  "$current_dir" = "${DIR_ARRAY[$index]}" ]; then
				if [ $index -eq $PREV_ARRAY_IDX ]; then
					if [ $index -lt 10 ]; then
						printf "==> %d.< %s\n" $index "${DIR_ARRAY[$index]}"
					else
						printf "==>%d.< %s\n" $index "${DIR_ARRAY[$index]}"
					fi
				else
					if [ $index -lt 10 ]; then
						printf "==> %d.  %s\n" $index "${DIR_ARRAY[$index]}"
					else
						printf "==>%d.  %s\n" $index "${DIR_ARRAY[$index]}"
					fi
				fi
			else
				if [ $index -eq $PREV_ARRAY_IDX ]; then
						if [ $index -lt 10 ]; then
							printf "    %d.< %s\n" $index "${DIR_ARRAY[$index]}"
						else
							printf "   %d.< %s\n" $index "${DIR_ARRAY[$index]}"
						fi
					else
						if [ $index -lt 10 ]; then
							printf "    %d.  %s\n" $index "${DIR_ARRAY[$index]}"
						else
							printf "   %d.  %s\n" $index "${DIR_ARRAY[$index]}"
						fi
					fi
			fi
			let index=index+1
		done
	fi
	echo "-----------------(auto_add: $AUTO_ADD)--( help: s ? )-- dir_array $DIR_ARRAY_VER $DIR_ARRAY_DATE ----"
} # s


#-----------------------------------------------------------------------------
# t - Function to toggle back to previously selected directory
function t () {
	if [ "${DIR_ARRAY[$ARRAY_IDX]}" != "`pwd`" ]; then
		a "`pwd`" no_show
	fi
	local temp=$ARRAY_IDX
	ARRAY_IDX=$PREV_ARRAY_IDX
	PREV_ARRAY_IDX=$temp
	builtin cd "${DIR_ARRAY[$ARRAY_IDX]}"
	#s
} # t


#-----------------------------------------------------------------------------
# u - Function that undoes the "c" command 
function u () {
	ARRAY_TOP=$OLD_ARRAY_TOP
   ARRAY_IDX=$OLD_ARRAY_IDX
	PREV_ARRAY_IDX=$OLD_PREV_ARRAY_IDX
	DIR_ARRAY[1]=$OLD_DIR_ARRAY_1
	builtin cd "${DIR_ARRAY[$ARRAY_IDX]}"
	s
} # u


#-----------------------------------------------------------------------------
# sort_dir_array - Function that sorts the array in alphabetical order
function sort_dir_array () {
   # Only do this if array has more than one entry
	if [ $ARRAY_TOP -eq 1 ]; then
		return
	fi

   # Sort the list
	local change=YES
	while [ "$change" == "YES" ]; do
		local index1=1
		local index2=2
		change=NO
		while [ $index2 -le $ARRAY_TOP ]; do
			if [[ "${DIR_ARRAY[$index1]}" > "${DIR_ARRAY[$index2]}" ]]; then
				change=YES
				local temp=${DIR_ARRAY[$index1]}
				DIR_ARRAY[$index1]=${DIR_ARRAY[$index2]}
				DIR_ARRAY[$index2]=$temp
            # Make sure the ARRAY_IDX follows the sorting
				if [ $ARRAY_IDX -eq $index2 ]; then
					ARRAY_IDX=$index1
				else
					if [ $ARRAY_IDX -eq $index1 ]; then
						ARRAY_IDX=$index2
					fi
				fi
            # Make sure the PREV_ARRAY_IDX follows the sorting
				if [ $PREV_ARRAY_IDX -eq $index2 ]; then
					PREV_ARRAY_IDX=$index1
				else
					if [ $PREV_ARRAY_IDX -eq $index1 ]; then
						PREV_ARRAY_IDX=$index2
					fi
				fi
			fi
			let index1=index1+1
			let index2=index2+1
		done
	done
	s
} # sort_dir_array


#-----------------------------------------------------------------------------
# write_dir_array - Function that writes the DIR_ARRAY to a file $1
# $1 is name of file to write
function write_dir_array () {
	# Create the directory if it doesn't exist
	if [ ! -d $FILE_DIR ]; then
		mkdir $FILE_DIR
	fi

	# Remove the file if it already exists
	if [ -e "$1" ]; then
		rm -f "$1"
	fi

	# Write to the file
	local index=1
	while [ $index -le $ARRAY_TOP ]; do
		echo ${DIR_ARRAY[$index]} >> "$1"
		let index=index+1
	done
} # write_dir_array



#-----------------------------------------------------------------------------
# append_dir_array - Function that appends the file passed in $1 to DIR_ARRAY
# $1 is the file to append
function append_dir_array () {
	if [ -e "$1" ]; then
		local lines=`wc -l < "$1"`
		local line_cnt=1
		while [ $line_cnt -le $lines ]; do
			# Create a sed script to isolate one line from the file then read it in
         # 040605 jrl - This sed method required to handle dirnames with spaces in them
			echo "${line_cnt}p" >| /tmp/$$.sed
			#DIR_ARRAY[$line_cnt]=`sed -n -f /tmp/$$.sed < "$1"`
			local entry=`sed -n -f /tmp/$$.sed < "$1"`
			rm -f /tmp/$$.sed

			# Check if entry is already in the current DIR_ARRAY
			local index=1
			local found=FALSE
			while [ $index -le $ARRAY_TOP ]; do
				if [ "$entry" = "${DIR_ARRAY[$index]}" ]; then
					found=TRUE
				fi
				let index=index+1
			done

         # Add the entry to the top of DIR_ARRAY if it isn't already in it
			if [ $found = FALSE ]; then
				let ARRAY_TOP=ARRAY_TOP+1
				DIR_ARRAY[$ARRAY_TOP]="$entry"
			fi

			# Point to the next line in the file
			let line_cnt=line_cnt+1
		done
		s
	fi
} # append_dir_array




#-----------------------------------------------------------------------------
# load_dir_array - Function that loads the DIR_ARRAY from a file
# $1 is the file to load
function load_dir_array () {
	if [ -e "$1" ]; then
		local lines=`wc -l < "$1"`
		local line_cnt=1
		while [ $line_cnt -le $lines ]; do

			# Create a sed script to isolate one line from the file then read it in
         # 040605 jrl - This sed method required to handle dirnames with spaces in them
			echo "${line_cnt}p" >| /tmp/$$.sed
			DIR_ARRAY[$line_cnt]=`sed -n -f /tmp/$$.sed < "$1"`
			rm -f /tmp/$$.sed

			# Point to the next line in the file
			let line_cnt=line_cnt+1

		done

      # Initialize the ARRAY_TOP to be the number of lines in $1
		ARRAY_TOP=$lines
		i `pwd`
	fi
} # load_dir_array


#-----------------------------------------------------------------------------
# load_dir_array_file - Function that loads the DIR_ARRAY from a file in $FILE_DIR
function load_dir_array_file () {
	if [ -d $FILE_DIR ]; then
		contents=`ls $FILE_DIR`
		PS3="Choose file (. to exit): "
		select file_name in $contents; do
			if [ "$REPLY" = "." ]; then
				return
			fi

			if [ "$file_name" != "" ]; then
				load_dir_array $FILE_DIR/$file_name
				return
			fi
		done
	else
		echo "Directory $FILE_DIR does not exist"
	fi
} # load_dir_array_file


#-----------------------------------------------------------------------------
# append_dir_array_file - Function that appends the DIR_ARRAY from a file in $FILE_DIR
function append_dir_array_file () {
	if [ -d $FILE_DIR ]; then
		contents=`ls $FILE_DIR`
		PS3="Choose file (. to exit): "
		select file_name in $contents; do
			if [ "$REPLY" = "." ]; then
				return
			fi

			if [ "$file_name" != "" ]; then
				append_dir_array $FILE_DIR/$file_name
				return
			fi
		done
	else
		echo "Directory $FILE_DIR does not exist"
	fi
} # append_dir_array_file


#-----------------------------------------------------------------------------
# write_dir_array_file - Function that writes the DIR_ARRAY to a file in $FILE_DIR
function write_dir_array_file () {
	if [ -d $FILE_DIR ]; then
		contents=`ls $FILE_DIR`
		PS3="Choose file or enter new name (. to exit): "
		select file_name in $contents; do
			if [ "$REPLY" = "." ]; then
				return
			fi

			if [ "$file_name" != "" ]; then
				write_dir_array $FILE_DIR/$file_name
				return
			fi

			if [ "$file_name" = "" -a "$REPLY" != "" ]; then
				write_dir_array $FILE_DIR/$REPLY
				return
			fi
		done
	else
		echo "Directory $FILE_DIR does not exist"
	fi
} # write_dir_array_file


#-----------------------------------------------------------------------------
# delete_dir_array_file - Function that deletes a file from $FILE_DIR
function delete_dir_array_file () {
	if [ -d $FILE_DIR ]; then
		contents=`ls $FILE_DIR`
		PS3="Choose file to delete (. to exit): "
		select file_name in $contents; do
			if [ "$REPLY" = "." ]; then
				return
			fi

			# Don't allow $DEFAULT_FILE to be removed
			if [ "$file_name" = "$DEFAULT_FILE" ]; then
				echo "Can't remove \"$DEFAULT_FILE\""
				return
			fi

			# Remove the file if $file_name isn't null
			if [ "$file_name" != "" ]; then
				rm -f $FILE_DIR/$file_name
				return
			fi
		done
	else
		echo "Directory $FILE_DIR does not exist"
	fi
} # delete_dir_array_file



