#!/bin/sh
############################################################ IDENT(1)
#
# $Title: Script to search p4d log entries $
#
############################################################ CONFIGURATION

#
# Local perforce server settings
# NB: Taken from rc.conf(5) on FreeBSD
#
P4D_ROOT=$( sysrc -n p4d_root 2> /dev/null )
P4D_LOG=$( sysrc -n p4d_log 2> /dev/null )

#
# Sensible defaults (i.e., Linux)
#
: ${P4D_ROOT:=/perforce}
: ${P4D_LOG:=$P4D_ROOT/logs/p4log}

unset P4D_ROOT

############################################################ GLOBALS

pgm="${0##*/}" # Program basename

#
# Global exit status
#
SUCCESS=0
FAILURE=1

#
# Command-line options
#
AFTER=0		# -A NUM
BEFOR=0		# -B NUM
COLOR=1		# -n (disable) or -c (always enabled even to non-terminal)
LIMIT=0		# -NUM
LOGFILE=	# -f file

#
# Miscellaneous
#
REGEX=  # first non-flag argument

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

usage()
{
	local fmt="$1"
	exec >&2
	if [ "$fmt" ]; then
		shift 1 # fmt
		printf "%s: $fmt\n" "$pgm" "$@"
	fi
	local optfmt="\t%-8s %s\n"
	printf "Usage: %s [OPTIONS] regex\n" "$pgm"
	printf "OPTIONS:\n"
	printf "$optfmt" "regex" \
		"awk(1) regular expression for matching log entries."
	printf "$optfmt" "-NUM" \
		"Limit output to at-most NUM mathing entries."
	printf "$optfmt" "-A NUM" \
		"Show NUM lines of context following matched entries."
	printf "$optfmt" "-B NUM" \
		"Show NUM lines of context leading up to matched entries."
	printf "$optfmt" "-c" \
		"Always enable color, even to non-terminals (e.g., pipes)."
	printf "$optfmt" "-f file" \
		"Read file (\`-' for stdin; Default $P4D_LOG)."
	printf "$optfmt" "-n" \
		"Disable the use of color highlighting on terminals."
	exit $FAILURE
}

setopt()
{
	local __var_to_set="$1" __value="$2" __retval=0 # NB: Caller sets $arg
	case "$arg" in
	-??*) __retval=1 arg="-${arg#-?}" ;;
	  -?) __retval=0 arg= ;;
	   *) return $FAILURE
	esac
	eval $__var_to_set='"$__value"'
	return $__retval # caller: 0 = shift (-X); 1 = continue (-Xv => -v)
}

setarg()
{
	local __var_to_set="$1" __value="$2" __retval=0 # NB: Caller sets $arg
	case "$arg" in
	-??*) eval __retval=0 $__var_to_set='${arg#-?}' ;;
	  -?) eval __retval=1 $__var_to_set='"$__value"' ;;
	esac
	arg=
	return $__retval # caller: 0 = shift 1 (-Xarg); 1 = shift 2 (-X arg)
}

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

#
# Process command-line arguments
#
while [ $# -gt 0 ]; do
	: ${arg:="$1"}
	case "$arg" in
	-[0-9]*)
		LIMIT="${arg#-}"
		LIMIT="${LIMIT%%[!0-9]*}"
		arg="${arg#-$LIMIT}"
		arg="${arg:+-}$arg"
		[ "$arg" ] && continue
		;;
	-A*) setarg AFTER "$2" || shift ;;
	-B*) setarg BEFOR "$2" || shift ;;
	-c*) setopt COLOR 2 || continue ;;
	-f*) setarg LOGFILE "$2" || shift ;;
	-n*) setopt COLOR 0 || continue ;;
	-*) usage "invalid option -- %s" "${1#-}" ;;
	 *) break # non-option argument encountered
	esac
	shift
done
REGEX="$1"

#
# Validate command-line arguments
#
[ "$REGEX" ] || usage
[ "$BEFOR" = "${BEFOR%%[!0-9]*}" ] || usage "invalid \`-B NUM' argument"
[ "$LIMIT" = "${LIMIT%%[!0-9]*}" ] || usage "invalid \`-NUM' argument"
[ "$AFTER" = "${AFTER%%[!0-9]*}" ] || usage "invalid \`-A NUM' argument"
P4D_LOG="${LOGFILE:-$P4D_LOG}" # set after last-call to usage()

#
# For `-n' (COLOR=0) and `-c' (COLOR=2), keep the value of COLOR as-is. In the
# default case (COLOR=1), reset COLOR to zero if stdout is a non-terminal
# (e.g., when stdout has been redirected to a file/pipe).
#
[ $COLOR -ne 1 ] || [ -t 1 ] || COLOR=0

#
# Wield awk(1) to display matching paragraphs from the p4d log file
#
awk -v REGEX="$REGEX" -v COLOR=$COLOR \
	-v LIMIT=$LIMIT -v BEFOR=$BEFOR -v AFTER=$AFTER '
	######################################## AWK(1) FUNCTIONS

	function buf_print() { print buf }

	function color_print()
	{
		pbuf = buf
		if (COLOR) {
			cbuf = pbuf
			pbuf = ""
			while (match(cbuf, REGEX)) {
				pbuf = sprintf("%s%s%c[43;30m%s%c[49;39m",
					pbuf, substr(cbuf, 0, RSTART - 1), 27,
					substr(cbuf, RSTART, RLENGTH), 27)
				cbuf = substr(cbuf, RSTART + RLENGTH)
			}
			pbuf = pbuf cbuf
			if (BEFOR || AFTER)
				pbuf = sprintf("%c[7m%s%c[27m", 27, pbuf, 27)
		}
		print pbuf
	}

	function bstack_add()
	{
		if (A) { # Print/Skip when consuming AFTER-elements
			buf_print()
			if (!--A && !BEFOR && N) print "--"
			return
		}
		if (BEFOR <= 0) # feature not enabled
			return
		if (BEFOR == 1) # only one slot (easy)
			bstack[B = 1] = buf
		else { # multiple slots (tricky)
			if (B < BEFOR) B++
			for (n = B; n > 1; n--) # make room
				bstack[n] = bstack[n-1]
			bstack[n] = buf
		}
	}

	function bufhandler()
	{
		if (!buf) return
		if (buf ~ REGEX && (LIMIT ? N < LIMIT : 1)) {
			if (B && N) print "--" # print hr before bstack items
			while (B) # print the bstack and reset B to zero
				print bstack[B--]
			delete bstack	# erase the bstack
			A = AFTER	# reset A to high watermark
			color_print()	# dump matching buffer
			N++		# increment printed matches
		} else bstack_add()
		buf = "" # reset buffer
	}

	######################################## AWK(1) MAIN

	/^[^[:space:]]/ { # got a line beginning with non-whitespace
		bufhandler()
		if (LIMIT > 0 && N >= LIMIT && !A) exit
		buf = sprintf(">>> ENTRY#%09u %s:%u: %s",
			C++, FILENAME, FNR, $0)
		next
	}

	# NOTREACHED unless line begins with whitespace
	{ buf = buf "\n" $0 }

	# NOTREACHED until EOF or awk(1) exit
	END { bufhandler() }

	######################################## AWK(1) END
' "$P4D_LOG"

################################################################################
# END
################################################################################
#
# $Copyright: 2015 Devin Teske. All rights reserved. $
#
# $Header: //guest/freebsdfrau/p4t/libexec/server_log#1 $
#
################################################################################