#!/bin/sh
# conform-port [-h] [-q] [-u userlist] <portsdir> -- quality of the ports of a tree, read from files, no build.
# One stderr line per fault, "<pkgpath>: <fault>"; stdout: the summary. rc 0 clean, 1 faults,
# 2 usage. A port of ours (DISTFILES empty) owes what an OpenBSD port owes:
#   - one PLIST and one DESCR per subpackage (PLIST-<sub>, DESCR-<sub>; PLIST, DESCR alone)
#   - PERMIT_PACKAGE set
#   - a man page, unless CONFORM_DOC = No
#   - every file of share/examples/ named by a PLIST is named by a test of the port (files/test*), or the examples directory is
#   - every @newuser / @newgroup name in the user.list of the tree or of -u (default: the ports
#     tree of the system, which owns the accounts of upstream ports)
# CONFORM_DOC = No says the port is not ours and has no page: refused on a port of ours, and any
# value but Yes or No is refused. REVISION bump needs history, not read here.
PATH=/bin:/sbin:/usr/bin:/usr/sbin
set -u

usage() { echo "usage: conform-port [-h] [-q] [-u userlist] portsdir" >&2; }
quiet=0
ulist=/usr/ports/infrastructure/db/user.list
while getopts hqu: o; do
	case $o in
	h) usage; exit 0 ;;
	q) quiet=1 ;;
	u) ulist=$OPTARG ;;
	*) usage; exit 2 ;;
	esac
done
shift $((OPTIND - 1))
[ $# -eq 1 ] && [ -d "$1" ] || { usage; exit 2; }
root=${1%/}
here=$(dirname "$0")

faults=0
bad() { echo "$1: $2" >&2; faults=$((faults + 1)); }

# the subpackage names of a Makefile: lines of MULTI_PACKAGES, each -name
subnames() {
	awk '/^MULTI_PACKAGES[ \t]*[?+]?=/ { on = 1 }
		on { n = split($0, w, /[ \t\\]+/); for (i = 1; i <= n; i++) if (w[i] ~ /^-[A-Za-z0-9_.+]+$/) print substr(w[i], 2)
			if ($0 !~ /\\[ \t]*$/) on = 0 }' "$1"
}

count=0
tmp=$(mktemp "${TMPDIR:-/tmp}/conform-port.XXXXXXXXXX") || exit 2
trap 'rm -f "$tmp"' EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM
sh "$here/conform-port-survey.sh" "$root" < /dev/null > "$tmp" || exit 2
while read -r pkgpath src multi subs plist descr man rev user rc confdoc permit; do
	count=$((count + 1))
	d=$root/$pkgpath
	src=${src#src=}; man=${man#man=}; user=${user#user=}; permit=${permit#permit=}; confdoc=${confdoc#confdoc=}
	if [ "$multi" = multi=yes ]; then
		for s in $(subnames "$d/Makefile"); do
			[ -f "$d/pkg/PLIST-$s" ] || bad "$pkgpath" "no pkg/PLIST-$s"
			[ -f "$d/pkg/DESCR-$s" ] || bad "$pkgpath" "no pkg/DESCR-$s"
		done
	else
		[ -f "$d/pkg/PLIST" ] || bad "$pkgpath" "no pkg/PLIST"
		[ -f "$d/pkg/DESCR" ] || bad "$pkgpath" "no pkg/DESCR"
	fi
	[ "$permit" != unset ] || bad "$pkgpath" "PERMIT_PACKAGE not set"
	case $confdoc in
	Yes) [ "$src" != ours ] || [ "$man" = yes ] || bad "$pkgpath" "no man page (ours; set CONFORM_DOC = No only for a port that is not ours)" ;;
	No) [ "$src" != ours ] || bad "$pkgpath" "CONFORM_DOC = No on a port of ours (DISTFILES is empty)" ;;
	*) bad "$pkgpath" "CONFORM_DOC is '$confdoc', not Yes or No" ;;
	esac
	if [ "$src" = ours ]; then
		for ex in $(sed -n 's,^share/examples/[^ ]*/\([^/ ]*[^/ ]\)$,\1,p' "$d"/pkg/PLIST* 2>/dev/null | sort -u); do
			grep -q -F -- "$ex" "$d"/files/test* "$d"/files/tests/* 2>/dev/null || grep -q -i -E -- '(files|share)/examples' "$d"/files/test* "$d"/files/tests/* 2>/dev/null || bad "$pkgpath" "example $ex is named by no test of the port"
		done
	fi
	if [ "$user" = yes ]; then
		for a in $(sed -n -e 's/^@newuser \([^:]*\):.*/\1/p' -e 's/^@newgroup \([^:]*\):.*/\1/p' "$d"/pkg/PLIST* | sort -u); do
			{ grep -q -E "(^|[ 	])$a([ 	]|\$)" "$root/user.list" || { [ -r "$ulist" ] && grep -q -E "(^|[ 	])$a([ 	]|\$)" "$ulist"; }; } || bad "$pkgpath" "account $a not in user.list"
		done
	fi
done < "$tmp"

[ "$quiet" -eq 1 ] || echo "conform-port: $count ports, $faults faults"
[ "$faults" -eq 0 ]
