#!/bin/ksh
######################################################################
# Purpose:	This script analyzes dependencies in a web page
#		development hierarchy and writes make recipes
#		to build the page into '.dependencies', and to
#		recurse into subdirectories.
# Copyright:	Copyright 1997-2026 Perette Barella.
#		All rights reserved.
######################################################################
VERSION='$Id: makedepend 336 2026-03-28 15:43:51Z perette $'

# Validate that ksh supports the modern/extended getopts format.
# Author: Perette Barella 
# Copyright 2018 Devious Fish.  All rights reserved.
# $Id: modern_ksh_check 19 2018-07-28 23:40:39Z perette $  


function modern_ksh_check {
	if [[ $(getopts '[-][12:abc]' flag --abc; print -- 0$flag) != "012" ]]
	then
		print -- "$arg0${arg0+: }Outdated Korn shell." 1>&2
		exit 1
	fi
}
# Korn shell/zsh require
# Author: Perette Barella
# Copyright 2018 Devious Fish.  All rights reserved.
# $Id: require 61 2021-08-09 18:49:07Z perette $

function require {
	typeset requirement
	integer result=0
	for requirement
	do
		if ! whence -p "${requirement%:*}" >/dev/null 2>&1
		then
			print -- "$arg0${arg0+: }${requirement#*:} not found; please install." 1>&2
			result=1
		fi
	done
	(( $result != 0 )) && exit $result
	return 0
}

# base_name - basename implemented as a shell function.
# Author: Perette Barella
# Copyright 2020 Devious Fish.  All rights reserved.
# Why: In testing, performed >> 10x faster than external program.
# $Id$

function base_name {
        typeset base=${1##*/}
        (( $# == 2 )) && eval "base=\${base%$2}"
        print -r -- "$base"
}

# Check that the WEBBASE environment variable is set and sane,
# and that current directory resides within it.
# Side effects: Sets the DIR_TOP, DIR_OFFSET and DIR_INCLUDE
# based on $WEBBASE and current directory.
# $Id: check_webbase 229 2026-02-08 22:38:36Z perette $
function check_webbase {
	if [[ -z "$WEBBASE" ]]
	then
		print -- "$arg0: WEBBASE undefined." 1>&2
		exit 1
	fi

	if [[ ! -d "$WEBBASE" ]]
	then
		print -- "$arg0: WEBBASE does not point to a valid directory." 1>&2
		exit 1
	fi

	typeset dir_to_check="${PWD:0:${#WEBBASE}}"
	if [ "$dir_to_check" != "$WEBBASE" ]
	then
		here=$(/bin/pwd)
		dir_to_check="${here:0:${#WEBBASE}}"
		if [ "$dir_to_check" != "$WEBBASE" ]
		then
			print -- "$arg0: $PWD not in $WEBBASE hierarchy" 1>&2
			exit 1
		fi
	fi
}


# Calculate the paths we need to pass to M4.
# $Id: calculate_web_paths 239 2026-02-12 03:52:29Z perette $
function calculate_web_paths {
	DIR_OFFSET=${PWD:${#WEBBASE}}
	[[ "${DIR_OFFSET:0:1}" == "/" ]] && DIR_OFFSET="${DIR_OFFSET:1}"

	if [[ -z "$DIR_OFFSET" ]]
	then
		DIR_OFFSET="."
		DIR_TOP="."
	else
		DIR_TOP=".."
		typeset dirwork=$(dirname "$DIR_OFFSET")
		while [[ "$dirwork" != "." ]]
		do
			DIR_TOP="../$DIR_TOP"
			dirwork=$(dirname "$dirwork")
		done

	fi
	DIR_INCLUDE="$DIR_TOP/Include"
}


arg0=$(base_name "$0")

modern_ksh_check
check_webbase
calculate_web_paths

M4="${M4:-m4}"
M4_OPTS="-P"
TEMP="${TEMP:-/var/tmp}"

require m4

USAGE=$'
[-1?'$VERSION$']
[+NAME?'$arg0$' - collect dependencies for .web files and subdirectories.]
[+DESCRIPTION?\b'$arg0$'\b analyzes all .web files in a directory, running M4 to collect a list of their dependencies, including files included via \bm4_include\b, linking macros, and image and media macros.  Discovered dependencies are written as \bmake\b(1) recipes to to \b.dependencies\b.  If no \bMakefile\b exists, the file is created with a default template that references this file.]
[i:ignore?A space-separated list of files or directories for which no recipes or dependencies should be generated.]:[files]
[s:subdirectories?Also create dependencies for immediate subdirectories.]
[r:recurse?Recurse into subdirectories.]
[+FILES?\b'$arg0$'\b writes the following files:]
{
  [+.dependencies?File into which dependency recipes are written.]
  [+Makefile?Makefile that is created if it does not already exist.]
}
[+EXIT STATUS?0 on success, non-0 on error.]
[+SEE ALSO?\bmd2web\b(1), \bweb2html\b(1)]



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


function valid_directory {
	typeset name="$1"
	# Ignore hidden directories
	[[ "${name:0:1}" == "." ]] && return 1
	# Ignore source directories
	[[ "$name" == "SCCS" || "$name" == "RCS" ]] && return 1
	# Ignore Thumbnails directories created by macro package
	[[ "$name" == "Thumbnails" ]] && return 1
	# Legacy: Ignore directories marked with .nomake
	[[ -e "$name/.nomake" ]] && return 1
	return 0
}

function existing_makefile {
	typeset path="$1"
	[[ -e "$path/Makefile" || -e "$path/makefile" || -e "$path/GNUmakefile" ]]
}

IGNORE=""
INCLUDE=${WEBBASE}/Include
RECURSE=false
RECURSE_OPTION=
RECURSION_MODE=depend

# Process command line options
while getopts -a "$arg0" "$USAGE" option
do
	case "$option" in
		i)	IGNORE=" $OPTARG "
			;;
		r)	RECURSE=true
			RECURSE_OPTION=-r
			RECURSION_MODE="builddepend"
			;;
		s)	RECURSE=true
			RECURSE_OPTION=
			RECURSION_MODE=depend
			;;
	esac
done

shift $((OPTIND - 1))

# Make a list of .web files for which we will make dependencies
files=""
for file in $(find -L . -maxdepth 1 -name '*.web' -type f -printf "%f\n")
do
	if [[ $IGNORE != *" $file "* ]]
	then
		files="${files}${files:+ }$file"
	fi
done

# Make a list of subdirectories
dirs=""
for file in $(find -L . -maxdepth 1 -type d -printf "%f\n")
do
	if valid_directory "$file" && [[ $IGNORE != *" $file "* ]]
	then
		dirs="${dirs}${dirs:+ }$file"
	fi
done

# Make a list of .web files that are created automatically from other things
# such as .md
webfiles=""
mdfiles=""
for ext in md
do
	for file in $(find -L . -maxdepth 1 -name "*.$ext" -type f -printf "%f\n")
	do
		[[ $IGNORE == *" $file "* ]] && continue
		webfile="$(base_name "$file" .$ext).web"
		[[ $IGNORE == *" $webfile "* ]] && continue
		# If a .web file exists, it will already be in $files.
		# If not, add it to that now.
		[[ ! -e "$webfile" ]] && files="${files}${files:+ }$webfile"
		mdfiles="${mdfiles}${mdfiles:+ }$file"
		webfiles="${webfiles}${webfiles:+ }$webfile"
	done
done

# Create a Makefile if there isn't an existing Makefile.
if ! existing_makefile .
then
	cat << 'EOF' > Makefile
# List any HTML files for which there are recipes that
# should be build for "all" and deleted during cleaning.
HTMLTARGETS=

# List any other files for which there are recipes that
# should be built for "all" and deleted during cleaning.
FILETARGETS=

# Phony targets.  These are built for "all" but excluded
# from deletion during cleaning.
PHONYTARGETS=

# A list of files or directories that should be ignored.
# They should not be built or recursed into.
IGNORE=

include ${WEBBASE}/Include/Makefile.inc

# Tell Tidy to ignore proprietary attributes (needed for
# some social media integrations).
# TIDY_OPTIONS=--warn-proprietary-attributes no

# Set TIDY_TOLERATE_WARNINGS to 1 to ignore HTML Tidy warnings
# TIDY_TOLERATE_WARNINGS=1

# Set VALIDATE to false to disable Validator.Nu validation,
# or ignore to run the validator but ignore the result.
# VALIDATE=ignore

# Add custom recipes here:
EOF
fi

# Write the dependencies.
cat > .dependencies << EOF
####################################################
# THIS FILE IS AUTOMATICALLY GENERATED AND UPDATED.
# Use 'make depend' to rebuild this file.
# CHANGES WILL BE OVERWRITTEN.
####################################################

# Subdirectories to build
SUBDIRS=$dirs

# HTML targets that makedepend sees
AUTOHTMLTARGETS=$(print -- ${files//.web/.html})

# .web intermediates for .md files that makedepend sees
AUTOWEBTARGETS=$webfiles

# Combine automatic lists with any user-provided ones.
TARGETS=\$(AUTOHTMLTARGETS) \$(AUTOWEBTARGETS) \\
	\$(FILETARGETS) \$(HTMLTARGETS) \$(PHONYTARGETS)
CLEANFILES=\$(AUTOHTMLTARGETS) \$(AUTOWEBTARGETS) \\
	\$(FILETARGETS) \$(HTMLTARGETS)

WEBBASE=${WEBBASE}

# If asked by parent to build this directory by name, make all.
$(basename "$PWD"): all

# .md files dependencies:
EOF

# Write dependencies for .md files
for file in $mdfiles
do
	webfile="$(base_name "$file" .md).web"
	print -- "$webfile:	$file"
done >> .dependencies

# Extract dependencies for .web files
side_dependencies="$(mktemp "${TEMP}/$arg0.XXXXXX")"
trap "rm -f '$side_dependencies'" EXIT

print >> .dependencies
print "# .web files:" >> .dependencies
if [[ -n "$files" ]]
then
	# Create intermediate .webs from .mds so we can read their dependencies.
	make --no-print-directory webs 1>&2
	for file in $files
	do
		name=$(base_name $file .web)
		if [[ -e "$file" ]]
		then
			depends=$(${M4} ${M4_OPTS} -s -D__EXTRACTING=true \
				-DFILE_SOURCE="$file" \
				-DFILE_DEST="$name.html" \
				-DDIR_TOP="$DIR_TOP" \
				-DDIR_INCLUDE="$DIR_INCLUDE" \
				-DDIR_HERE="$DIR_OFFSET" \
				-D_DEPENDENCY=$'m4_divert(1)\n#line 00 "$1"\nm4_divert(-1)' \
				-D_SIDE_DEPENDENCY=$'m4_divert(1)\n#side 00 |$1|$2|$3|\nm4_divert(-1)' \
				$file | tee -a "$side_dependencies" | grep '^#line' | cut -d'"' -f2 -s | sort -u)
		else
			# If the .web file doesn't exist yet, give it standard dependencies.
			depends="$file \${WEBBASE}/Include/html.m4"
		fi
		print -- "${name}.html:	" $depends
		print
	done
fi >> .dependencies

print >> .dependencies
print "# Side-car dependencies" >> .dependencies
grep "^#side" "$side_dependencies" | while IFS="|" read -r junk source destination action
do
	typeset order_only_prerequisite=""
	# For normal link (ln -f is effectively ln --clobber), symlink
	# and copy --noclobber, use an order-only prerequisite.
	# These check only for file existence, without comparing file dates.
	[[ $action == "ln" || $action == "ln -s" ||
	   $action == "cp -n" || $action == "cp -np" ]] && order_only_rerequisite=" |"
	print -r -- "$destination:$order_only_prerequisite $source"
	print -r -- "	${action:-ln} $source $destination"
	print
done >> .dependencies

[[ -z "$dirs" ]] && exit 0

# Recurse into subdirectories if needed
if $RECURSE
then
	for dir in $dirs
	do
		if existing_makefile "$dir"
		then
			# Run make depend on the existing makefile to pick up IGNOREs.
			make -C "$dir" $RECURSION_MODE
		else
			print -- "$arg0: Initializing dependencies in $dir"
			cd "$dir"
			${INCLUDE}/bin/makedepend $RECURSE_OPTION
			cd ..
		fi
	done
fi

