#!/bin/ksh
# "Compile" ksh scripts by inlining autoload functions.
# Author: Perette Barella
# Copyright 2018 Devious Fish.  All rights reserved.
VERSION='$Id: kshcompile 89 2022-11-24 17:33:47Z perette $'

arg0=$(basename "$0")
USAGE='nvap'
if [[ $(getopts '[-][12:abc]' flag --abc; print -- 0$flag) != "012" ]]
then
	print "$arg0: Outdated Korn shell." 1>&2
	exit 1
fi      

USAGE=$'
[-1?'$VERSION$']
[+NAME?'$arg0$' - assemble Korn shell script with autoloads into autonomous script]
[+DESCRIPTION?\b'$arg0$'\b reads a Korn shell script, expanding \vautoload\v commands
with the corresponding function.  Functions are found by searching $FPATH for a file
with a matching name.]
[+?Replacing is done recursively, so depended-on functions may depend on other
autoloadable functions.  Functions are only inlined once; if requested a second time,
it is ignored.]
[d:dependencies?List dependencies without compiling anything.]
[o:output?Output file.]:[file]
[p:permissions?File permissions for output file.]:[chmod-string]
[t:tagged?Tag autoloaded components in output with start/end market lines.]
[+ENVIRONMENT VARIABLES?\vFPATH\v provides the function search path.]
[+EXIT STATUS?0 on success, non-0 on error.]
[+SEE ALSO?\bnetbackup\b(1)]

script

[-author?Perette Barella <perette@deviousfish.com>]
'



# Initialize fpath with $FPATH, one per index
typeset -a fpath
integer i=0
typeset piece
while true
do
	piece=$(print -- "$FPATH:" | cut -d: -s -f$((i + 1)) )
	[[ -z "$piece" ]] && break
	fpath[$i]="$piece"
	let i++
done
unset i piece

typeset -A IMPORTED
function process_autoload {
	typeset file="$1" func path
	integer status=0 line="$2"
	shift 2
	for func
	do
		typeset found=false
		for path in $fpath
		do
			typeset target="$path/$func"
			if [ -f "$target" ]
			then
				found=true
				# Don't import it if it's already imported
				if [[ ! -v IMPORTED[$target] ]]
				then
					IMPORTED[$target]=yes
					$DEPENDENCIES && print -- "$target"
					$TAGGED && print -- "##### Start of autoload: $target"
					import "$target" < "$target" || status=1
					$TAGGED && print -- "##### End of autoload: $target"
				fi
				break;
			fi
		done
		if ! $found
		then
			print "$file:$line: $func: not found." 1>&2
			COMPILE=false
			status=1
		fi
	done
	return $status
}

function import {
	typeset file="$1" command parameters
	integer line_number=0
	while IFS="" read -r line
	do
		let line_number++
		print -- "$line" | read command parameters
		if [[ $command == "autoload" ]]
		then
			process_autoload "$file" $line_number $parameters || return $?
		else
			$COMPILE && print -r -- "$line"
		fi
	done
	return 0
}


DEPENDENCIES=false
COMPILE=true
TAGGED=false
OUTPUT=""
PERMISSIONS=u+x

while getopts -a "$arg0" "$USAGE" option
do
	case "$option" in
	    d)
		DEPENDENCIES=true
		COMPILE=false
		;;
	    p)
		PERMISSIONS="$OPTARG"
		;;
	    o)
		OUTPUT="$OPTARG"
		;;
	    t)
		TAGGED=true
		;;
	esac
done
shift $((OPTIND - 1))

if [[ ! -z "$OUTPUT" ]]
then
	exec > "$OUTPUT" || exit 1
fi

status=0
if [ $# -eq 0 ]
then
	print -- "$arg0: Reading script from stdin." 1>&2
	import "stdin" || status=$?
else
	for file
	do
		import "$file" < "$file" || status=$?
	done
fi

# If something went wrong, remove the output file.
if (( $status != 0 ))
then
	[[ ! -z "$OUTPUT" ]] && rm -f "$OUTPUT"
else
	if [[ ! -z "$OUTPUT" ]]
	then
		chmod "$PERMISSIONS" "$OUTPUT" || status=$?
	fi
fi

return $status

