#!/bin/bash
# cmddiff	Command Difference
# 	stores a command output in RCS file,
#	shows a diff with previous version,
#	if field # given calculates changes' speed
#	if maximal value given calculates when it will be reached
# Copyright (C) 1999 Rafał Maszkowski <rzm@icm.edu.pl>
# Licence: GNU GPL rev 3+
# 19990205 written; rcs, line.field syntax
# 19990504 check if there is any command given
#	   -h option
# 19990506 check if t1-t0 or speed ==0
# 19990512 cat first version
# 2000-05-26 more flexible fields selection [?]
# 2011-08-12 more precise -p description; limit numbers accuracy on output
# 2012-12-06 licence: GPL 2 -> 3+; script encoding: latin2 -> utf8; help corrections; [ ] std test -> [[ ]] bash test
# 2021-04-01 no path appended by default, -p left for compatibility, -P - append path; multiple  -f  options; appropriate multiple  -e  options, can be omitted by  -e ""
# 2021-04-02 local time in diff header; 04-03 %#.3g with # to not to loose the trailing zeroes; +P in while pattern; 04-04 dynamic ends, e.g. -e f6.4
# 2022-06-07 checking needed commands and proposing package(s) installation
# 2025-03-20 -[fepPrh] → ^-[fepPrh] to not to match cmd options; 10-13 -q option; 10-24 print not only final date but also time diff.; 11-05 negative $span; 11-30 toend==.5 becomes empty so make it 0

# TODO
# -r options other than 0, -1, x.y would mean time span to make comparison in, in seconds
# options cache for given command
# remove spaces and such and make lower case before md5summing ( tr A-Z a-z|tr -dc 'a-z0-9' ?)
# maximal value error checking
# curve fitting to the field value
# -n option for not to add next entry into RCS
# div. by 0
# initialize option
# options to use suffixes like K, M, G… (-h?) or k, M, G… (-H?)
unset LANG
path=""
rev=0
nend=0
tmp=~/.cmddiff
declare -A ends

nocmd=0
for cmd in rcs ci co rcsdiff rlog cat date md5sum printf awk; do
	if ! which $cmd > /dev/null; then
		if [[ $cmd =~ rcs|ci|co|rcsdiff|rlog ]]; then
			if [ "$wasrcs" != 1 ]; then pkgs="$pkgs rcs"; nopkg=$(( $nopkg + 1 )); fi
			wasrcs=1
		fi
		if [[ $cmd =~ cat|date|md5sum|printf ]]; then
			if [ "$wascoreutuils" != 1 ]; then pkgs="$pkgs coreutils"; nopkg=$(( $nopkg + 1 )); fi
			wascoreutuils=1
		fi
		if [[ $cmd =~ awk ]]; then
			if [ "$wasgawk" != 1 ]; then pkgs="$pkgs gawk"; nopkg=$(( $nopkg + 1 )); fi
			wasgawk=1
		fi
		cmds="$cmds $cmd"
		nocmd=$(( $nocmd + 1 ))
	fi
done
if [ "$nocmd" != 0 ]; then
	pluralcmd=""; pluralpkg=""
	if [ $nocmd -gt 1 ]; then pluralcmd=s; fi
	if [ $nopkg -gt 1 ]; then pluralpkg=s; fi
	echo "ERROR: Missing command$pluralcmd$cmds. Instaling package$pluralpkg like$pkgs may help. Exiting…"
	exit 2
fi

while [[ "$1" =~ ^-[fepPqrh] ]]; do
	case "$1" in
		"-f") shift; fields="$fields $1"; shift;;
		"-e") shift; nend=$(( $nend + 1 )); ends[$nend]="$1"; shift;;
		"-p") shift; path="";;
		"-P") shift; path=`pwd`;;
		"-q") shift; quiet=1;;
		"-r") shift; rev="$1"; shift;;
		"-h") shift; cat <<EOT
cmddiff - command output tracing

example: cmddiff -f 2 -e 137000 -p -r 1.6 'wc -l addr.resolved'
-f 2          compare the 2nd field in 1st (default) line; general form: [line.]field ,
              can be specified many times
-e 137000     calculate when the field is going to reach this value, can be specified
              many times for appropriate fields, use "" to omit specifying some end
-P            do append path of the current directory to the characteristic command string
-p            do not append, default
-q            output shortened to the prognose line only
-r 1.6        compare with revision 1.6, default: 0 == 1.1, -1 means the previous one
'wc ...'      the command
EOT
			exit;;
	esac
done

if [ "$1" = "" ]; then echo "WARNING: Missing command argument, use -h option to see help."; exit 1; fi

if [ ! -d "$tmp" ]; then mkdir "$tmp"; fi

file=`echo "$path$@" | md5sum | awk '{print $1}'`
tmpf="$tmp/$file"
tmprcs="$tmp/$file,v"

if [ ! -e "$tmprcs" ]; then
	name="$@"
fi

case $rev in
	0) rev=1.1;;
	-1) if [ -e "$tmprcs" ]; then
		rev=`rlog -b "$tmprcs" | awk '/^revision / { print $2; exit }'`
	else
		rev=1.1
	fi
	;;
esac
# else given, e.g. 1.6

sh -c "$@" 2>&1 > "$tmpf"

cd "$tmp"
if [ -e "$tmprcs" ]; then
	if [ ! "$quiet" ]; then
		rlog -t "$tmprcs" | tail -2 | head -1
		rcsdiff -q -r$rev -u -zLT "$tmprcs"
	fi
else
	echo First version.
	if [ ! "$quiet" ]; then cat "$tmpf"; fi
fi

echo "$name" | ci -d -q -f "$tmpf"
rcs -U -q "$tmprcs"

nend=0
for field in $fields; do
	toendstr=""; nend=$(( $nend + 1 )); end=${ends[$nend]}
	co -r$rev -q -M -f -u "$tmprcs"
	t0=`date -r "$tmpf" +%s`
	v0=`cat "$tmpf" | awk --assign=field="$field" 'BEGIN { n=split(field, lnfd, "."); if (n==1) { lnfd[2]=lnfd[1]; lnfd[1]=1 } } { i++; if (i==lnfd[1]) { print $lnfd[2]; exit} }'`
	revcurr=`co -M -f -u "$tmprcs" 2>&1 | awk '/revision/ { print $2 }'`
	t1=`date -r "$tmpf" +%s`
	v1=`cat "$tmpf" | awk --assign=field="$field" 'BEGIN { n=split(field, lnfd, "."); if (n==1) { lnfd[2]=lnfd[1]; lnfd[1]=1 } } { i++; if (i==lnfd[1]) { print $lnfd[2]; exit} }'`
	if [[ $end =~ ^f ]]; then
		end=`cat "$tmpf" | awk --assign=field="$end" 'BEGIN { gsub("^f","",field); n=split (field, lnfd, "."); if (n==1) { lnfd[2]=lnfd[1]; lnfd[1]=1 } } { i++; if (i==lnfd[1]) { print $lnfd[2]; exit} }'`
	fi
	tdiff=`echo "$t1-$t0" | bc -l`
	if [ "$tdiff" != 0 ]; then
		speed=`echo "($v1-$v0)/($tdiff)" | bc -l`
		if [ ! -z "$end" -a "$speed" != 0 ]; then
			toend=`echo "($end-$v1)/$speed + 0.5" | bc -l | sed 's/\..*//'`; if [ "$toend" == "" ]; then toend=0; fi
			sign=""; if [ "$toend" -lt 0 ]; then toend=$(( -"$toend" )); sign=-; fi
			span=`echo "$toend" | awk '{
				d=int($1/86400); rest=$1-86400*d; h=int(rest/3600); rest=rest-3600*h; m=int(rest/60); s=rest-60*m
				if (d >= 2)		{ printf "%d days %02d:%02d:%02d", d, h, m, s
				} else if (d >= 1)	{ printf "%d day %02d:%02d:%02d", d, h, m, s
				} else			{ printf "%02d:%02d:%02d", h, m, s }
			}'`
			atend=`date -d "now + $toend seconds"`
			toendstr=`echo , gets to $end in $sign$span on $atend`
		fi
		printf "$rev-$revcurr, field $field, change: %#.3g/s%s\n" "$speed" "$toendstr"
	fi
done
