# dirs -- Directory stack manipulation functions for sh, ksh, et al
#	Brian Katzung	19 January 1993
#
# Csh compatible:
# dirs [-l]		Stack listing (long)
# popd			Cd to top of stack and pop
# popd +n		Pop the nth item from the stack
# pushd			Swap cwd and top of stack
# pushd +n		Move cwd and 1..n-1 to end; cd top and pop
# pushd directory	Push cwd and cd to directory
#
# Extensions:
# dirs [-muv]		Stack listing (implement move, implement unique,
#			list vertically)
# sd			pushd -mn
# pd			pushd -n
# pushd . pattern	Push the first directory containing pattern
# pushd -n +n		Push cwd and cd to former nth item
# pushd oldpat newpat	Ksh only - push cwd and cd oldpath newpat

#DIRS=
case $HOME in
/)	# Home is /; no ~ substitution
	DIRSMAP="-e
;" ;;
*)	# Replace $HOME with ~ by default
	DIRSMAP="-e
s^$HOME~" ;;
esac
export DIRSMAP

_d_fields=`echo +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 \
+11 +12 +13 +14 +15 +16 +17 +18 +19 +20 |
tr ' ' '\012'`

# Utility functions to avoid having to exec cut, expr, sed, etc.

# Return the first n ($1) items from the remaining arguments
_d_first ()
{
	_which="$1"
	shift
	for _field in $_d_fields
	do
		# Stop if out of arguments
		case $1 in
		"")	return 1 ;;
		esac

		echo "$1"

		# Stop after printing first n arguments
		case $_field in
		$_which)
			return 0 ;;
		esac
		shift
	done
	return 2
}

# Return arguments starting after the nth ($1) one
_d_skip ()
{
	_which="$1"
	shift
	for _field in $_d_fields
	do
		# Stop if out of arguments
		case $1 in
		"")	return 1 ;;
		esac

		shift

		# Print arguments after the nth one
		case $_field in
		$_which)
			for _dir
			do
				echo "$_dir"
			done
			return 0
			;;
		esac
	done
	return 2
}

# Return only the nth ($1) item from the remaining arguments
_d_item ()
{
	_which="$1"
	shift
	for _field in $_d_fields
	do
		# Stop if out of arguments
		case $1 in
		"")	return 1 ;;
		esac

		# Print and return when we get to the nth argument
		case $_field in
		$_which)
			echo "$1"
			return 0
			;;
		esac

		shift
	done
	return 2
}

# Return all but the nth ($1) item from the remaining arguments
_d_not ()
{
	_which="$1"
	shift
	for _field in $_d_fields
	do
		# Return if out of arguments
		case $1 in
		"")	return 0 ;;
		esac

		# Print if not at the nth argument
		case $_field in
		$_which)
			;;
		*)	echo "$1"
		esac
		shift
	done
	return 0
}

# Return arguments not matching the first argument
_d_except ()
{
	_which="$1"
	shift
	for _dir
	do
		# Print if it doesn't match
		case $_dir in
		$_which)
			;;
		*)	echo "$_dir" ;;
		esac
	done
	return 0
}

# Returns arguments except the first match
_d_notfirst ()
{
	_which="$1"
	shift
	for _dir
	do
		case $_dir in
		# At the first match, print everything after the match
		$_which)
			shift
			for _dir
			do
				echo "$_dir"
			done
			return 0
			;;
		esac

		# Print each item before the first match
		echo "$_dir"
		shift
	done
	return 0
}

# Display the directory stack
dirs ()
{
	# Accumulate flags
	_d_flags=
	for _dir
	do
		case $_dir in
		-*)	_d_flags="$_d_flags$_dir"
			shift
			;;
		*)	break ;;
		esac
	done

	_ifs="$IFS"
	IFS="
"

	# Implement -m (move mode) and -u (unique mode)
	case $_d_flags in
	*u*)	DIRS=`_d_except $PWD $DIRS` ;;
	*m*)	DIRS=`_d_notfirst $PWD $DIRS` ;;
	esac

	# Set up for short or long format
	case $_d_flags in
	*l*)	# Long listing (no ~ substitution)
		set -- $PWD $DIRS
		;;
	*)	# Shortened listing
		set -- `echo "$PWD
$DIRS" | sed ${DIRSMAP}`
		;;
	esac

	# Print in horizontal or vertical format
	case $_d_flags in
	*v*)	# Vertical listing (1/line)
		for _dir
		do echo "$_dir"
		done
		;;
	*)	# Horizontal
		echo "$*"
		;;
	esac
	IFS="$_ifs"
	return 0
}

# Push directory or rearrange the directory stack
pushd ()
{
	# Accumulate flags
	_d_flags=
	for _dir
	do
		case $_dir in
		-*)	_d_flags="$_d_flags$_dir"
			shift
			;;
		*)	break ;;
		esac
	done

	case ${3-} in
	?*)
		echo "Too many arguments to pushd."
		return 1
		;;
	esac

	_pwd="$PWD"
	_ifs="$IFS"
	IFS="
"
	case ${1-} in
	"")	# Swap top two directories
		shift $#
		set -- $DIRS
		case $# in
		0)	echo "No other directory."
			IFS="$_ifs"
			return 1
			;;
		esac

		if cd "${1-}"
		then
			# New location off stack and old one on
			shift
			DIRS="$_pwd
$@"
		fi
		;;

	+[0-9]*)# Rearrange nth or nth..last item(s) on the stack
		if _dir=`_d_item $1 $DIRS`
		then	:
		else	echo "Directory stack not that deep."
			IFS="$_ifs"
			return 1
		fi

		if cd "$_dir"
		then
			case $_d_flags in
			*n*)	# New behavior - just push nth item
				DIRS="$_pwd
$DIRS"
				;;
			*)	# Move cwd and 1..n-1 to end
				DIRS="`_d_skip $1 $DIRS`
`_d_first $1 $_pwd $DIRS'"
				;;
			esac
		fi
		;;

	.)	case ${2-} in
		"")	# pushd . (backward compatibility)
			DIRS="$_pwd
$DIRS"
			;;
		*)	# Push first stack item containing $2
			for _dir in $DIRS
			do
				case $_dir in
				*"$2"*)	_pwd="$PWD"
					if cd "$_dir"
					then
						DIRS="$_pwd
$DIRS"
					fi
					break
					;;
				esac
			done
			;;
		esac
		;;

	*)	# Push new directory
		_pwd="$PWD"
		if cd "$1" ${2+"$2"}
		then
			DIRS="$_pwd
$DIRS"
		fi
		;;
	esac

	IFS="$_ifs"
	dirs $_d_flags
	return 0
}

# Remove the top directory or a middle directory from the directory stack
popd ()
{
	_d_flags=
	for _dir
	do
		case $_dir in
		-*)	_d_flags="$_d_flags$_dir"
			shift
			;;
		*)	break ;;
		esac
	done

	case ${2-} in
	?*)
		echo "Too many arguments to popd."
		return 1
		;;
	esac

	_ifs="$IFS"
	IFS="
"
	case ${1-} in
	"")	# Pop the top directory
		set -- $DIRS
		case ${1-} in
		"")
			echo "Directory stack empty."
			IFS="$_ifs"
			return 1
			;;
		esac
		if cd "$1"
		then
			shift
			DIRS="$@"
		fi
		;;

	+[0-9]*)# Discard the nth item from the stack
		_dir=`_d_item "$1" $DIRS`
		case ${_dir-} in
		"")
			echo "Directory stack not that deep."
			IFS="$_ifs"
			return 1
			;;
		esac
		DIRS=`_d_not "$1" $DIRS`
		;;
	esac

	IFS="$_ifs"
	dirs _d_flags
	return 0
}

# Shorthand functions

sd ()
{
	pushd -mn ${1+"$@"}
	return $?
}

pd ()
{
	pushd -n ${1+"$@"}
	return $?
}
