#!/bin/bash
# pactree : a simple dependency tree viewer
#
# Copyright (C) 2008 Carlo "carlocci" Bersani <carlocci@gmail.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# Original http://carlocci.ngi.it/arch/pactree
# Credit to scj for the graphviz idea

# set the colors
branch1_color="\033[0;33m"    #Brown
branch2_color="\033[0;37m"    #Gray
leaf_color="\033[1;32m"       #Light green
leaf2_color="\033[0;32m"      #Green

# set the separators
separator="   "
branch_tip1="|--"
branch_tip2="+--"
provides="provides "

# set the graphviz options
# http://www.graphviz.org/doc/info/output.html for available output formats
# http://www.graphviz.org/doc/info/colors.html for available colors
gformat="png"                 #output format
start_color="red"             #START color
nodes_color="green"           #color of the nodes
arrow1_color="chocolate4"     #color of the normal arrow
arrow2_color="grey"           #color of the "provided by" headless arrow

readonly prog_name="pactree"
readonly prog_ver="0.3"

_usage(){
	echo "This program generates the dependency tree of an installed package"
	echo "Usage:   $prog_name [OPTIONS] <installed packages>"
	echo
	echo " OPTIONS:"
	echo "  -c, --color                Enable color output"
	echo "  -d, --depth INT            Limit the shown dependencies depth"
	echo "  -g, --graph                Use graphviz to make an image of the tree"
	echo "  -l, --linear               Enable linear output"
	echo "  -r, --reversed             Show reversed dependancies"
	echo "  -s, --silent               Shh, let me hear those errors!"
	echo "  -u, --unique               Print the dependency list with no duplicates"
	echo
	echo "  -h, --help                 Print this help message"
	echo "  -v, --version              Print the program name and version"
	echo
	echo "Example: $prog_name -c -d2 readline"
}

_version(){
	echo "$prog_name version $prog_ver"
	echo "Copyright (C) 2008 Carlo \"carlocci\" Bersani <carlocci@gmail.com>"
}
# end of the friendliness


# grab a field from the database: $1=path/to/file, $2=field to grab
_grabfield(){
	for line in $(cat "$1" 2>/dev/null ); do
		if [ -z "$line" ]; then
			continue;
		fi;
		if [[ "$line" =~ %[A-Z]*% ]]; then
			current="$line"
			continue;
		fi;
		if [ "$current" = "$2" ]; then
			echo "$line"
		fi;
	done
}


# find a dep in the db: $1=dep, $2=field, $3=dbfile, ret=file list
_finddep(){
	for line in $(awk 'BEGIN{RS=""}
	                   {
	                   if ($1=="'"$2"'"){
	                     for (i=2 ; i<=NF ; ++i){
	                       if ($i ~ /^'"$1"'([<>=]+.*|)$/ ){
	                         print FILENAME}
	                       }
	                     }
	                   }' $(find $pac_db -name $3)); do
		echo "${line%/*}"
	done
}


# Recursive function: does all of the work, pays all of the taxes     #
_tree(){
	pkg_name="$1"
	pkg_dirs="$(echo $pac_db/$pkg_name-[0-9]*)"

	# Is $pkg_name real or provided?
	[ ! -d "$pkg_dirs" ] && pkg_dirs="$(_finddep $pkg_name %PROVIDES% depends)"

	for pkg_dir in $pkg_dirs ; do
		spaces="$2"
		unset provided
		branch_tip="$branch_tip1"
		branch_color="$branch1_color"
		pkg_name="$(_grabfield "$pkg_dir/desc" %NAME%)"
		if [ ! "$pkg_name" = "$1" ]; then
			provided="$leaf2_color $provides$leaf_color$1"
			branch_tip="$branch_tip2"
			branch_color="$branch2_color"
			if [ $graphviz -eq 1 ] && [[ ! "${dep_list[@]}" =~ _$1_ ]] && [ $spaces -ne $((max_depth+1)) ]; then
				echo "\"$1\" -> \"$pkg_name\" [arrowhead=none, color=$arrow2_color];"
				dep_list=( "${dep_list[@]}" "_$1_" )
				_tree "$pkg_name" $((spaces+1))
				continue
			fi
		fi

		# Generate the spacer
		spacer=""
		for each in $(seq 1 $spaces); do
			spacer="$spacer$separator"
		done
		spacer="$spacer$branch_tip"

		[ $silent -ne 1 ] &&	echo -e "$branch_color$spacer$leaf_color$pkg_name$provided"

		[ ! -d "$pkg_dir" ] && echo "No $pkg_name in the database (inconsistent database?)" >&2

		if [[ ! " ${dep_list[@]} " =~ " $pkg_name " ]] && [ $spaces -ne $max_depth ]; then
			dep_list=( "${dep_list[@]}" "$pkg_name" )
			if [ $reversed_dep -eq 0 ]; then
				deps_pkg="$(_grabfield "$pkg_dir/depends" %DEPENDS%)"
			else
				reqs_pkg_dir="$(_finddep "$pkg_name" %DEPENDS% depends)"
				unset deps_pkg
				for req_pkg_dir in $reqs_pkg_dir; do
					deps_pkg=$(echo "$deps_pkg" "$(_grabfield "$req_pkg_dir/desc" %NAME%)")
				done
			fi
			for dep_pkg in $deps_pkg; do
				spaces=$2		#Bash scoping ;_;
				if [ $graphviz -eq 1 ]; then
					echo "\"$1\" -> \"${dep_pkg%%[<>=]*}\" [color=$arrow1_color];"
				fi
				_tree "${dep_pkg%%[<>=]*}" $((spaces+1))
			done
		fi
	done
}


# Main program: gets all of the money, pays none of the taxes

# Command line parameters parser
if [ $# -eq 0 ]; then
	_usage
	exit 1
fi

options=( "$@" )
len_options=${#options[@]}
for (( n=0 ; n < $len_options ; n++ )); do
	if [ "${options[$n]}" = "--" ]; then
		unset options[$n]
		break
	fi
	if [ "${options[$n]}" = "-h" -o "${options[$n]}" = "--help" ]; then
		_usage
		exit 0
	fi

	if [ "${options[$n]}" = "-v" -o "${options[$n]}" = "--version" ]; then
		_version
		exit 0
	fi

	if [ "${options[$n]}" = "-l" -o "${options[$n]}" = "--linear" ]; then
		unset options[$n]
		linear=1
		continue
	fi

	if [ "${options[$n]}" = "-s" -o "${options[$n]}" = "--silent" ]; then
		unset options[$n]
		silent=1
		continue
	fi

	if [ "${options[$n]}" = "-u" -o "${options[$n]}" = "--unique" ]; then
		unset options[$n]
		silent=1
		nodup=1
		continue
	fi

	if [ "${options[$n]}" = "-g" -o "${options[$n]}" = "--graph" ]; then
		unset options[$n]
		graphviz=1
		continue
	fi

	if [ "${options[$n]}" = "-c" -o "${options[$n]}" = "--color" ]; then
		unset options[$n]
		colored=1
		continue
	fi

	if [ "${options[$n]}" = "-r" -o "${options[$n]}" = "--reversed" ]; then
		unset options[$n]
		reversed_dep=1
		continue
	fi

	if [[ "${options[$n]}" =~ -d[[:digit:]]+ || "${options[$n]}" == "--depth" ]]; then
		if [[ "${options[$n]#-d}" =~ [[:digit:]]+ ]]; then
			max_depth="${options[$n]#-d}"
		elif [[ ${options[$((n+1))]} =~ [[:digit:]]+ ]]; then
			max_depth="${options[$((n+1))]}"
			unset options[$((n+1))]
			((++n))
		fi
		unset options[$n]
		continue
	fi
done
# End of the dumb command line parser

# Env
colored=${colored:-0}
max_depth=${max_depth:--10}
linear=${linear:-0}
silent=${silent:-0}
nodup=${nodup:-0}
graphviz=${graphviz:-0}
reversed_dep=${reversed_dep:-0}

if [ $colored -ne 1 ]; then
	unset branch1_color
	unset leaf_color
	unset leaf2_color
	unset branch2_color
fi

if [ $linear -eq 1 ]; then
	unset separator
	unset branch_tip1
	unset branch_tip2
	unset provides
fi

if [ $graphviz -eq 1 ]; then
	silent=1
	nodup=0
	if [ ! -f /usr/bin/dot ]; then
		echo "ERROR: package graphviz is not installed"
		echo "       Run pacman -S graphviz to install it"
		exit 1
	fi
fi

if [ ! -r /etc/pacman.conf ]; then
	echo "ERROR: unable to read /etc/pacman.conf"
	exit 1
else
	eval $(awk '/DBPath/ {print $1$2$3}' /etc/pacman.conf)
fi

pac_db="${DBPath:-/var/lib/pacman}/local"

if [ ! -d "$pac_db" ] ; then
	echo "ERROR: pacman database directory ${pac_db} not found"
	exit 1
fi
# Env End


# Program starts
_main(){
	for pkg_name in ${options[@]} ; do
		[ $graphviz -eq 1 ] && echo -e "\"START\" -> \"$pkg_name\" ;"
		_tree "$pkg_name" 0
		if [ $nodup -eq 1 ]; then
			for pkg_tree in ${dep_list[@]} ; do
				echo "$pkg_tree"
			done
		fi
	done
	if [ $silent -eq 0 ]; then
		echo -ne '\033[0m' # return colors to normal?
		echo -ne '\033[?25h' #return cursor to normal?
	fi
}


if [ $graphviz -eq 1 ]; then
	root_pkgs="${options[@]}"
	# Uncomment for the "generated by pactree" node in graphviz
	#advert="xyz [height=0.07, fontsize=8.0, label=\"GENERATED WITH PACTREE\",shape=box,color="black",style=filled,fontcolor="white"];\n"
	if [ $reversed_dep -eq 0 ]; then
		file_extension="deps.$gformat"
	else
		file_extension="reqs.$gformat"
	fi
	echo -e "digraph G { START [color=$start_color, style=filled];\n node [style=filled, color=$nodes_color];\n$(_main)\n$advert}" | dot -T$gformat -o "${root_pkgs// /_}.$file_extension"
else _main
fi

# vim: set ts=2 sw=2 noet: