#!/bin/bash

###############################################################################
# Copyright 2003 Thomas de Grenier de Latour (TGL) <degrenier@easyconnect.fr>
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License version 2, as 
#    published by the Free Software Foundation.

# TODO:
# - Submit to Gentoo forum for comments.
# - Skip wellknown useless messages? (like "Applying something.patch",
#  "Installing GNOME 2 GConf Schemas", etc.)
# - Anything useful to do with "emerge.log"? (Would probably be a good idea to 
#  merge some features of this script in some already existing "emerge.log" 
#  parser. Remember to contact Giorgio Mandolfo <giorgio@pollycoke.org>, the 
#  "genlop" tool author)
# - Yes, things could be optimized. I don't really care, tho...
# - Add an option to force PORT_LOGDIR.
# - Optionaly display emerge steps (unpack, compile, etc.)?


###############################################################################
# Seetings, etc.

# Meta
PROG=`basename ${0}`
VERSION="0.3.1"

# Uncomment for debugging. 
#debug=yes

# What messages to show if no option?
default_show="errors warnings infos \
	okaycolor failedcolor \
	okaymono failedmono \
	unknown portage"

# Change this if you want to force PORT_LOGDIR
PORT_LOGDIR="$(/usr/lib/portage/bin/portageq envvar PORT_LOGDIR)"

###############################################################################
# Checks and warnings

# PORT_LOGDIR?
if [ ! -d "${PORT_LOGDIR}" ] ; then
	echo "PORT_LOGDIR not found."
	exit 1 
fi

# NOCOLOR?
NOCOLOR="$(/usr/lib/portage/bin/portageq envvar NOCOLOR)"
if [ "${NOCOLOR}" == "yes" ] ; then
	echo "Warning: NOCOLOR is set in your /etc/make.conf. \
I won't be able to guess message types in monochrome logs..." >&2
fi

###############################################################################
# Parse args

parse_args() {
	local a since

	# Command line options
	a="${1}"
	while [ -n "${a}" ] ; do
		case "${a}" in
		-h|--help)
			usage=y ; break ;;
		-e|--error|--errors)
			show="errors failedcolor ${show}" ;; 
		-w|--warning|--warnings)
			show="warnings ${show}" ;;
		-i|--info|--infos)
			show="infos okaycolor ${show}" ;;
		-u|--unknown)
			show="unknown okaymono failedmono ${show}" ;;
		-p|--portage-message|--portage-messages)
			show="portage ${show}" ;;
		-m|--monochrome)
			monochrome=y ;;
		-s|--since)
			shift ; since="${1}" ;;
		--since=*)
			since="${a#--since=}" ;;
		-n|--nomatch)
			showall=y ;;
		--)
			shift ; a="${1}" ; break
			;;
		-*)
			usageerror='${YL}Invalid option ${RD}'"${a}"' 1>&2'
			usage=y ; break ;;
		*)
			break ;;
		esac
		shift ; a="${1}"
	done
	
	# Now, get patterns. We disable some regexp special chars.
	while [ -n "${a}" ] ; do
		a="${a//./\\.}"
		a="${a//-/\\-}"
		a="${a//\\*/.*}"
		patterns="${a} ${patterns}"
		shift ; a="${1}"
	done

	# Parse the "since" value, produce "cmin" (in minutes)
	[ -n "${since}" ] && \
		if ( echo ${since} | egrep -q "[0-9]+d") ; then
			cmin=${since%d}
			let cmin*=60*24
		elif ( echo ${since} | egrep -q "[0-9]+h") ; then
			cmin=${since%h}
			let cmin*=60
		elif ( echo ${since} | egrep -q "[0-9]+m") ; then
			cmin=${since%m}
		else
			usageerror='${YL}Invalid time parameter: ${RD}'"${since}"' 1>&2'
			usage=y
		fi
	
	# Set default values
	[ -z "${patterns}" ] && patterns='.*'
	[ -z "${show}" ] && show="${default_show}"
}

###############################################################################
# Setup output colors

set_colors() {
	NO="\x1b[0;0m"
	BR="\x1b[0;01m"
	CY="\x1b[36;01m"
	RD="\x1b[31;01m"
	GR="\x1b[32;01m"
	YL="\x1b[33;01m"
	BL="\x1b[34;01m"
}

###############################################################################
# Display help message

display_help() {
	if [ -n "${usageerror}" ]; then
		eval echo -e '${CY}${PROG}${NO}:${YL}' ${usageerror}
	else 
		echo -e "${CY}${PROG} v. ${VERSION}${NO}

${BR}${PROG}${NO} is a tool to retrieve informations in per packages emerge logs.
You have to set ${BR}PORT_LOGDIR${NO} in ${BR}/etc/make.conf${NO} to use it.

By default, all messages are displayed. If you want only some of this 
informations, specify them in options."
	fi

	echo -e "
${BR}Usage:
${CY}${PROG}${NO} [${BR}options${NO}] [${YL}pattern1${NO} [${YL}pattern2${NO}]...]"

	if [ -z "${usageerror}" ]; then
		echo -e "
${YL}Patterns${NO} are interpreted as substrings to match either on ${YL}package-name${NO} 
or on ${YL}package-name-version${NO}. Some ${BR}special chars${NO} are supported:
  ${YL}^${NO} to match the beginning of the string,
  ${YL}\$${NO} to match the end of the string,
  ${YL}*${NO} to any substring (need to be single-quoted or escaped).
(Note: categories are not supported since they don't appear in logs)"
	fi
	
	echo -e "
${BR}Available options:${NO}
${BL}-e${NO}, ${BL}--errors${NO}      search for eerror messages   (\" ${RD}*${NO} \")
${BL}-w${NO}, ${BL}--warnings${NO}    search for ewarn messages    (\" ${YL}*${NO} \")
${BL}-i${NO}, ${BL}--infos${NO}       search for einfos messages   (\" ${GR}*${NO} \")
${BL}-u${NO}, ${BL}--unknown${NO}     search for unknown messages  (\" * \", for monochrome logs)
${BL}-p${NO}, ${BL}--portage${NO}     search for portage messages  (\"!!!\", aka \"die\" messages)
${BL}-s${NO}, ${BL}--since=<Xu>${NO}  only read logs modified in the last ${BR}X u${NO} 
                  (where ${BR}X${NO} is an integer and ${BR}u${NO} a time unit:
                  ${BR}d${NO} for days, ${BR}h${NO} for hours and ${BR}m${NO} for minutes)
${BL}-n${NO}, ${BL}--nomatch${NO}     also display packages that contain no matching message
${BL}-m${NO}, ${BL}--monochrome${NO}  don't use colors for ${BR}${PROG}${NO} output
${BL}-h${NO}, ${BL}--help${NO}        display this message and exit"
	
	if [ -n "${usageerror}" ]; then
		exit 1
	fi
}

###############################################################################
# List matching logs files:
#  - list log files that are recent enough,
#  - filter the list with patterns (trying both with and without "-version"),
#  - prefix results with mtimes, sort, and remove mtime prefix.

find_logs() {
	local cminopt p f
	[ -n "${cmin}" ] && cminopt="-cmin -${cmin}"
	for f in $( find ${PORT_LOGDIR} -maxdepth 1 -name "*.log" -type f ${cminopt} ) ; do
		for p in $1 ; do
			if ( echo "$f" \
					| sed -e's:\(/.*/[0-9]*\-\)\|\(\.log\)::g' \
					| grep -q "$p" ) || \
				( echo "$f" \
					| sed -e's:\(/.*/[0-9]*\-\)\|\(\-[0-9].*\.log\)::g' \
					| grep -q "$p" ); then
				echo "`stat -c%Y $f` $f"
				break
			fi
		done
	done | sort -n | sed 's:^[0-9]* ::'
}

###############################################################################
# Build the search regexp

build_sed_regexp() {
	
	local INFO='\o033\[32;01m'
	local WARNING='\o033\[33;01m'
	local ERROR='\o033\[31;01m'
	local NORMAL='\o033\[0m'
	local BRACKET='\o033\[34;01m'
	
	local a outexp
	while [ ${#} -gt 0 ] ; do
		outexp="${outexp}\\("
		a="$1"
		shift
		case "${a}" in 
			errors)
				outexp="${outexp}^ $ERROR\\*$NORMAL " ;;
			warnings)
				outexp="${outexp}^ $WARNING\\*$NORMAL " ;;
			infos)
				outexp="${outexp}^ $INFO\\*$NORMAL " ;;
			unknown)
				outexp="${outexp}^ \\* " ;;
			portage)
				outexp="${outexp}^!!! " ;;
			okaycolor)
				outexp="${outexp}${BRACKET}\\[ ${INFO}ok${BRACKET} \\]$NORMAL" ;;
			failedcolor)
				outexp="${outexp}${BRACKET}\\[ ${ERROR}!!${BRACKET} \\]$NORMAL" ;;
			okaymono)
				outexp="${outexp}\\[ ok \\]" ;;
			failedmono)
				outexp="${outexp}\\[ !! \\]" ;;
			*)
				echo "Unknown message type ${a}" ; exit 1 ;;
		esac
		outexp="${outexp}\\)"
		[ ${#} -gt 0 ] && outexp="${outexp}\\|"
	done
	echo "${outexp}"
}

###############################################################################
# Extract interresting messages. We group them as they are in the file.

log_sed() {
	sed -n -e "
/$sed_exp/{
	i...
	p
	n
	:block
	/$sed_exp/{
		p
		n
		b block
	}
}
\$i..." ${1}
}

###############################################################################
# Display a pretty title for this section of the result.

log_title() {
	# Infos to be displayed
	local logfile="${1}"
	local logsize=$(du -h "${logfile}" | awk '{ print $1 }' )
	local package="`echo ${logfile} | sed -e 's:\(^/.*/[0-9]*\-\)\|\(\.log$\)::g'`"
	local mtime="`stat -c%y ${logfile} | sed 's/:[0-9][0-9]\..*$//'`"
	
	# Let's have fun with block justification
	local middle="=="
	local side="="
	local firstline=" === ${mtime} ${middle} ${package} === "
	local secondline=" ${side} ${logfile} (${logsize}) ${side} "
	while [ ${#firstline} -gt ${#secondline} ]; do
		side="=${side}"
		secondline=" ${side} ${logfile} ${side} "
	done
	while [ ${#firstline} -lt ${#secondline} ]; do
		middle="=${middle}"
		firstline=" === ${mtime} ${middle} ${package} === "
	done
	
	# Okay, now we can display it, with colors
	echo -e "${CY} === ${YL}${mtime}${CY} ${middle} ${BL}${package}${CY} === ${NO}"
	echo -e "${CY} ${side} ${NO}${logfile}${YL} (${logsize})${CY} ${side} ${NO}"
}

###############################################################################
# Main

# First, we parse arguments, display help if needed, etc.
parse_args ${*}
[ -z "${monochrome}" ] && set_colors
[ -n "${usage}" ] && display_help && exit 0
[ -n "${debug}" ] && echo "-> Portage log directory: ${PORT_LOGDIR}" >&2
[ -n "${debug}" ] && echo "-> Packages to search: ${patterns}" >&2
[ -n "${debug}" ] && echo "-> Messages to search: ${show}" >&2
[ -n "${debug}" ] && [ -n "${cmin}" ] \
	&& echo "-> Only logs modified in the last ${cmin} minutes" >&2

# Next, we search for logfiles that may interest us
logs=`find_logs "$patterns"`
[ -n "${debug}" ] && echo -e "-> Matching log files: \n${logs}" >&2

# And we prepare a nice piece of regexp depending on the options
sed_exp=`build_sed_regexp $show`
[ -n "${debug}" ] && echo -e "-> Sed regexp to search: ${sed_exp}" >&2

# Finally, we can parse the logs
for logfile in $logs ; do
	[ -n "${debug}" ] && echo -e "-> Working on ${logfile}" >&2
	messages=`log_sed $logfile`
	if [[ "${messages}" != "..." || -n "${showall}" || -n ${debug} ]] ; then
		log_title ${logfile}
		echo -e "${messages}\n"
	fi
done

# Arrivederci !
[ -n "${debug}" ] && echo "-> All done." >&2