#!/bin/bash
# dep - library dependency support

set -e

lib_load 'dev/package/files'
lib_load 'doc/pdf'
lib_load 'doc/tool/graphviz'


######
# Config

dev_package_dep_config_init() {
	lib_setting_vars dep_extensions_show dep_extensions_expand
	dep_extensions_show=${dep_extensions_show:-false}
	dep_extensions_expand=${dep_extensions_expand:-false}

	lib_setting_vars dep_graph_runtime
	dep_graph_runtime=${dep_graph_runtime:-false}
}

dev_package_dep_config_check() {
	:
}


######
# Dependencies

lib_deps() {
	local file=$1
	grep -Pe '^[#\t ]*lib_load ' "$file" \
		| sed -e "s,^lib_load ['\"]*,!," \
			-e "s,^.*lib_load ['\"]*,," \
			-e "s,['\"] *$,,"
}


######
# Package Dependency CLI

package_dep_dispatch() { lib_cmd_dispatch package_dep "$@"; }

package_dep_usage() {
	cat <<USAGE
<cmd> ...
Dependency Commands:
	check [<file>+] 		Checks script dependencies
	list [<file>+] 			Prints script dependencies
	tree [<file>+]			Prints dependency tree(s)
	graph [<file>+]			Generates and views a dependency graph
USAGE
}

package_dep_list() {
	local -a files
	package_sources_or_args files "$@"

	# find only files that load libraries
	local -a links
	links=( $(grep -l 'lib_load' "${files[@]}" ) )

	for_each _package_dep_print "${links[@]}"
}

_package_dep_print() {
	local file=$1
	echo -en "$file: "
	lib_deps "$file" | xargs
}

package_dep_check() {
	min_args 1 "$@"
#	local -a files
#	package_sources_or_args files "$@"
	dep_check "$@" || error "dependency check failed"
}

package_dep_tree() {
	verbose=true package_dep_check "$@"
}


######
# Package Dependency Tree

dep_check() {
	local -a loaded visited

	_dep_check "$@"

	info "${#loaded[@]} libraries loaded"
	info "  ${loaded[*]}"

	local n=$(( ${#package_libs[@]} - ${#loaded[@]} ))
	if [ $n -gt 0 ]; then
		local -a pkgs
		for pkg in ${package_libs[@]}; do
			if ! in_list "$pkg" "${loaded[@]}"; then
				list_append pkgs "$pkg "
			fi
		done

		info "${#pkgs[@]} libraries NOT loaded:"
		info "  ${pkgs[@]}"
	fi
}

_dep_check() {
	local okay=true
	local file
	for file in "$@"; do
		local name
		name=$(basename "$file" .in)
		if [ ! -f "$file" ]; then
			warn "${visited[*]} -> $name: library not found"
			okay=false
			continue
		fi

		if in_list $name "${visited[@]}"; then
			error "cycle detected: ${visited[*]} $name"
		fi
		__dep_check "$file"
	done
	$okay
}

__dep_check() {
	local file=$1

	local name
	name=$(basename "$file" .in)

	local n_visited=${#visited[*]}
	local -a old=( "${visited[@]}" )
	local -a visited=( "${old[@]}" $name )

	local -a deps
	deps=( $(lib_deps "$file") )

	local dir
	dir=$(basename "$(dirname "$file")")
	if [ "$dir" = 'src' ]; then
		list_insert deps "!$runtime_name"
	fi

	local prefix='+-> '
	[ $n_visited -gt 0 ] || prefix=''

	local suffix=''
	in_list $name "${loaded[@]}" || suffix=' *'

	local indent_level=2
	local indent=$(( $n_visited * $indent_level ))
	info "$(printf "%${indent}s%s" '' "$prefix$name$suffix")"

	if ! in_list $name "${loaded[@]}"; then
		list_append loaded $name
	fi

	if ${dep_check_prune:-true} && [ -z "$suffix" ]; then
		return
	fi

	if [ -z "${deps[*]}" ]; then
		return
	fi

	local okay=true
	local dname
	local -a dfiles=()
	for dname in "${deps[@]}"; do
		dname=${dname#!}
		list_append dfiles "libs/$dname.in"
	done

	_dep_check "${dfiles[@]}"
}


######
# Dependency Graphs

package_dep_graph() { cmd_dispatch "$@"; }

package_dep_graph_usage() {
	cat <<USAGE
Dependency Graph Commands:
	all				Displays complete dependecy graph
	tools				Displays graph for all tools
	libs				Displays graphs for all libraries
	runtime				Displays graph for runtime libraries
	files <file>+			Displays graph(s) for select files
USAGE
}

# TODO: rename these symbols and eliminate these placeholders
package_dep_graph_all() { dep_graph_view all; }
package_dep_graph_tools() { dep_graph_view tools; }
package_dep_graph_libs() { dep_graph_view libraries; }
package_dep_graph_runtime() { dep_graph_view runtime; }
package_dep_graph_files() { dep_graph_view files "$@"; }


######
# Dependency Graphs

dep_graph_view() {
	local dotfile
	dotfile=$(cmd_tempfile)
	dep_graph_pdf "$dotfile" "$@"
	run_pdf_viewer "$dotfile.pdf"
}

dep_graph_all() {
	local dotfile=$1
	shift
	dep_graph "$dotfile.dot" "$@"
	[ -s "$dotfile.dot" ] || return 0

	local ext
	for ext in svg pdf; do
		graphviz_dot_$ext "$dotfile.dot" "$dotfile.$ext"
	done
}

dep_graph_pdf() { dep_graph_write pdf "$@"; }
dep_graph_svg() { dep_graph_write svg "$@"; }

dep_graph_write() {
	local ext=$1
	local dotfile=$2
	shift 2
	dep_graph "$dotfile.dot" "$@"
	[ -s "$dotfile.dot" ] || return 0
	graphviz_dot_$ext "$dotfile.dot" "$dotfile.$ext"
}

# dep_graph() - Generates graphviz source code in the named file ($1)
# that will produce the specified kind ($2) of package dependency graph.
# $1 - Output file that will contain the required 'dot' graphing commands
# $2 - Kind of package graph (all, tools, libs, runtime, or files)
dep_graph() {
	min_args 2 "$@"
	local out=$1
	shift
	file_mkdir "$out"
	dep_graph_kind "$@" >"$out"
}

dep_graph_kind() {
	local kind=$1
	shift

	run_pushd "$package_repodir"

	local -a files
	case "$kind" in
	(all)
		dep_extensions_show=true
		dep_extensions_expand=true
		files=( $(find src libs -name '*.in') )
		;;
	(tools)		files=( $(find src -name '*.in') ) ;;
	(libraries)	files=( $(find libs -name '*.in') ) ;;
	(runtime)	files=( "libs/$package_name.in" ) ;;
	(files)		files=( $(find "$@" -name '*.in') ) ;;
	(*)
		warn "'$kind' is not among ${dep_graph_kinds[*]}"
		error "invalid graph type: '$kind'"
		;;
	esac

	func_trace "${files[@]}"
	dot_digraph deps dep_graph_gen "${files[@]}"
	run_popd
}

dep_graph_gen() {
	local -a dep_graph_visited=()
	for_each dep_graph_file "$@"
}

dep_graph_file() {
	local file=$1

	# avoid graphs cycles
	! in_list "$file" "${dep_graph_visited[@]}" || return 0
	dep_graph_visited+=( "$file" )

	local src
	src=${file#*/}
	src=${src%.in}

	# display tool different
	if is_package_tool "$file"; then
		src="$src*"
		! $dep_extensions_show || dot_node_list "$src" "$package_name"
	fi

	# only display the include the runtime when indicated
	if $dep_extensions_show && in_list $src "${package_extensions[@]}"; then
		! $dep_extensions_expand || dep_graph_file "$runtime_file"
	fi

	local -A dep_arc_list=()
	local dep
	for dep in $(lib_deps "$file"); do
		local style
		if [ "${dep:0:1}" != '!' ]; then
			style=dashed
		else
			style=solid
		fi
		local dot_node_attr="[style=$style]"

		local dname=${dep#!}
		[ "${dep_arc_list[$dep]}" ] || dot_node_list "$src" "$dname"
		dep_arc_list[$dep]=1

		dep_graph_file "libs/$dname.in"
	done
}

View the Script Reference Index


Generated on Tue Apr 25 21:20:34 PDT 2017 by mcsh i7 v0.18.0.