#!/bin/bash

###########################################################################
# Copyright (c) 2004-2009 Hai Zaar and Gil Ran                            #
#                                                                         #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of version 3 of the GNU General Public License as       #
# published by the Free Software Foundation.                              #
#                                                                         #
###########################################################################


#
# Dynamic loader for bash libraries
# NOTE: The script can't load libraries if their file name starts with '-'.
#       (If someone gives a file a name that starts with a '-' he deserves it!)
#

# The prefix of internal functions
INTERNAL_PREFIX='__${Lib}_'

prefix=@prefix@
exec_prefix=@exec_prefix@

# The directory that contains the bash libraries
LD_BASH_PATH=@libdir@/bash


# LD_BASH_CACHE - The ldbash cache file
LD_BASH_CACHE=@sysconfdir@/ldbash.cache

# EXPORTS and REQUIREMENTS definitions
source $LD_BASH_CACHE || exit 1

# We want to use getopt_long...
source $LD_BASH_PATH/getopts.sh
source $LD_BASH_PATH/hashstash.sh

#############################################################
###################       FUNCTIONS      ####################
#############################################################

# Prints the script's usage.
usage()
{
	echo "usage: ldbash"
	echo "               [-h|--help]"
	echo "               [-l|--list]"
	echo "               [-L|--load <lib,[lib[...]]>]"
	echo "               [-U|--unload <lib,[lib[...]]>]"
	echo "               [-e|--externlist <lib,[lib[...]]>]"
	echo "               [--externlist-all]"
	echo "               [-i|--internlist <lib,[lib[...]]>]"
	echo "               [--internlist-all]"
	echo "				 [-v|--version]"
	exit
}

# Lists all the available libraries.
list()
{
	retval=`(cd $LD_BASH_PATH ; ls *.sh | sed "s/\.sh$//g")`
}

#
# $retval findLibExternalFuncs <lib[,lib[...]]>
#
#	Finds all exported functions of given libraries.
#
#	Parameters: 
#		lib		- A name of a bash library
#	Return value:
#		A list of external (exported) functions of the given library
findLibExternalFuncs()
{
	retval=""

	# for each lib, find it's external functions
	local Lib=
	for Lib in ${*//,/ } ; do
		eval LibInternalPrefix=$INTERNAL_PREFIX
		
		# Add to the retval the name of the lib
		retval="$retval $Lib: "

		# Step by step:
		# Grep functions declarations from the code of given library. 
		# Take just the declaration (looks like: "funcName<white_space>()")
		# Remove the '()'
		# Now throw away the lines that define internal functions
		retval="$retval"`grep ^"[ |\t]*[a-zA-Z_][0-9a-zA-Z_]*[ |\t]*()" ${LD_BASH_PATH}/${Lib}.sh 2> /dev/null | \
						 cut -d : -f 2 | \
						 sed -e "s/()//g" -e "s/[ |\t]//g" 2> /dev/null | \
						 grep -v ^"[ |\t]*${LibInternalPrefix}" 2> /dev/null`
	done
}

#
# $retval findLibInternalFuncs <lib[,lib[...]]>
#
#	Finds all internal (not-to-be-exported) functions of given libraries.
#
#	Parameters: 
#		lib		- A name of a bash library
# 	Return value:
#		A list of internal functions of the given library
findLibInternalFuncs()
{
	retval=""
	
	# for each lib, find it's internal functions
	local Lib=
	for Lib in ${*//,/ } ; do
		eval LibInternalPrefix=$INTERNAL_PREFIX
		
		# Add to the retval the name of the lib
		retval="$retval $Lib: "

		# Step by step:
		# Grep internal functions declarations from the code of given library. Step by step:
		# Take just the declaration (looks like: "funcName<whitespace>()")
		# Remove the '()'
		retval="$retval"`grep ^"[ |\t]*$LibInternalPrefix[0-9a-zA-Z_]*[ |\t]*()" ${LD_BASH_PATH}/${Lib}.sh 2> /dev/null | \
						 cut -d : -f 2 2> /dev/null | 
						 sed -e "s/()//g" -e "s/[ |\t]//g" 2> /dev/null`
	done
}

#
# ldbash_load load <lib[,lib[...]]>
#
#	See description of return value.
#	
#	Parameters:
#		lib		- A list of libraries to load.
#	Return value:
#		A string that contains a command that will load the libraries.
#		This string should be evaled (eval $retval).
load()
{
	local LoadString=""
	
	# LibsRequests is a list of requests for libraries to be loaded. Once a 
	# request is dealt with, it is removed from the list, and the appropriate 
	# library is added to LibsAdded (if it's not already there).
	# LibsAdded is the list which aggregates the names of libraries to 
	# eventually load.
	local LibsRequests="${*//,/ } "
	local LibsAdded=
	
	# Create the list of libs to load (with dependencies)
	local Lib=
	while [[ $LibsRequests ]]; do
		
		# Handling the request first on the list (and removing it)
		Lib=${LibsRequests%% *}
		LibsRequests=${LibsRequests#* }
		
		# Requests for libraries which have already been encountered are ignored.
		[[ $LibsAdded == $Lib\ * ]] || [[ $LibsAdded == *\ $Lib\ * ]] && continue
		LibsAdded="$LibsAdded$Lib "
		
		# Get the names of the needed functions
		eval LibRequire='${'${Lib}_REQUIRE'}'
			
		# Find out, for each function, who exports it
		local Func=
		for Func in $LibRequire ; do
			# This finds the name of the library that defines the function
			# It does it by greping "EXPORT" string and the function name from the cache file
			# and then taking only the lib name from the line.
			# NeededLib is not local so it can be treeted as an array.
			NeededLib=(`grep EXPORT $LD_BASH_CACHE | \
						grep " $Func " | \
						awk -F= '{print $1}' | \
						sed -e 's/_EXPORT//g'`)
			
			LibsRequests="$LibsRequests$NeededLib "
			unset NeededLib
		done
	done

	# Build the command that loads the libs
	for Lib in $LibsAdded ; do
		LoadString="${LoadString} source ${LD_BASH_PATH}/${Lib}.sh;"
	done

	retval=$LoadString
}

#
# ldbash_unload unload <lib,[lib[...]]>
#
#	See description of return value.
#	
#	Parameters:
#		lib		- A list of libraries to unload.
#	Return value
#		A string that contains a command that will unloaded the libraries.
#		This string should be evaled (\e eval \e retval)
unload()
{
	local UnloadString=""
	
	local Lib=
	
	# Build the command that unloads the libs
	#
	# we've sources cache file that defines ${lib}_EXPORT
	# ${lib}_EXPORT contains list of all functions that we've defined in load()
	# by running eval 'unset ${lib}_EXPORT' we'll unset them all
	#
	for Lib in ${*//,/ } ; do
		eval LibExports='${'${Lib}_EXPORT'}'
		UnloadString="${UnloadString} unset $LibExports;"
	done

	retval=$UnloadString
}

##############################################################
##################          MAIN          ####################
##############################################################

if ! [[ $* ]] ; then
	usage
fi

# parsing parameters
# 
# -e will set Extern to its argument
# BUT
# --externlist-all will set Extern to '-all'
#
getopt_long '-l|--list->List
			 -L|--load->Load:
			 -U|--unload->Unload:
			 -e|--externlist->Extern:
			 -i|--internlist->Intern:
			 -v|--version->Version
			 -h|--help->Help' "$@"
eval "$retval"

# Do we need to show usage message?
[[ $Help ]] && usage

if [[ $Version ]] ; then
	echo "@PACKAGE_STRING@"
	echo "Writen by Gil Ran and Hai Zaar"
	exit 0
fi

# Do we need to show a list of libraries?
if [[ $List ]] ; then
	echo "Libraries:"
	list
	for LibName in $retval ; do
		echo "	$LibName"
	done
fi

# Do we need to show a list of external functions?
if [[ $Extern ]] ; then
	# Check if the parameter is -all
	if [[ "$Extern" = '-all' ]] ; then
		# Run the function for all the available libraries
		list
		findLibExternalFuncs "${retval// /,}"
	else
		findLibExternalFuncs "$Extern"
	fi
	
	echo "External:"
	for FuncName in $retval ; do
		# Check if it is the library's name
		if [[ "$FuncName" = *: ]] ; then
			echo "	$FuncName"
		else # It must be a function name
			echo "		$FuncName"
		fi
	done
fi

# Do we need to show a list of internal functions?
if [[ $Intern ]] ; then
	# Check if the parameter is -all
	if [[ "$Intern" = '-all' ]] ; then
		# Run the function for all the available libraries
		list
		findLibInternalFuncs "${retval// /,}"
	else
		findLibInternalFuncs "$Intern"
	fi

	echo "Internal:"
	for FuncName in $retval ; do
		# Check if it is the library's name
		if [[ "$FuncName" = *: ]] ; then
			echo "	$FuncName"
		else # It must be a function name
			echo "		$FuncName"
		fi
	done
fi

# If any on the listing options were set, do not preform load/unload
[[ $List ]] || [[ $Extern ]] || [[ $Intern ]] && exit 0

# Do we need to load any libraries?
if [[ $Load ]] ; then
	load "$Load"
	echo $retval
fi

# Do we need to unload any libraries?
if [[ $Unload ]] ; then
	unload "$Unload"
	echo $retval
fi
