addcols.awk generates a tally

Dan & Dana Montagnese adagio@sprintmail.com
Sun, 16 Apr 2000 19:57:35 -0700


#! /usr/bin/awk -f

# addcols.awk generates a tally.
# It reads input lines and looks at the end of each line for a real 
# number preceded by an integer.  The integer is optional and 
# defaults to 1. 

# The output line consists of:
#	whatever preceded the numbers on the input line;
#	the integer;
#	the real number and
#	the product of the two numbers. 

# Input lines that are blank, contain only white space or that have "#" 
# as the first non-whitespace character are printed as they are.

# The grand total of the products of each line is displayed at the end.
#
# Any of the following are valid input lines ( without the "#" ):

# 56
# 4.78
# .78	
# 412 4.78
# 2 x 4			412 	4.78
# two-by-four's										412		4.78

#######################################################################


# Display error message and set exit status.
function Error(a_Field, ai_Line, as_Message)
{
	printf("\"%s\" in line %d %s\n\n", a_Field, ai_Line, as_Message)
	EXIT_STATUS_ = EXIT_FAILURE_
}


# Form the first part of the output line.
function FormLineStart(ai_CurrentField,
	ls_LineStart, ls_Tmp)
{
	ls_LineStart = ""
	ls_Tmp = ""
	
	while (ai_CurrentField > 0)
	{
		ls_Tmp = $ai_CurrentField " " ls_LineStart
		ls_LineStart = ls_Tmp
		ai_CurrentField--
	}	
	
	return (ls_LineStart)
}


# Check if argument is an integer.
function IsInteger(ai_Arg,
	li_TestResults)
{
	li_TestResults = TRUE_

	if ( ai_Arg !~ /^[0-9][0-9]*$/ )
	{
		li_TestResults = FALSE_
	}

	return (li_TestResults)
}


# Check if arg is a real number.
function IsReal(af_Amt,
	li_TestResults)
{
	li_TestResults = TRUE_

	# Test for:
	# At least one digit followed by an optional decimal point and optional 
	# digits; 

	# Optional digits followed by a decimal point and digits;
	if ( af_Amt !~ /^[0-9][0-9]*\.?[0-9]*$/ && af_Amt !~ /^[0-9]*\.[0-9][0-9]*$/ )
	{
		li_TestResults = FALSE_
	}

	return (li_TestResults)
}


# Check for blank lines, lines containing only white space and lines 
# beginning with #.
function NonDataLine(as_Line,
	li_TestResults)
{
	li_TestResults = TRUE_

	if ( $0 !~ /^[ \t]*$/ && $0 !~ /^[ \t]*#/ )
	{
		li_TestResults = FALSE_
	}

	return (li_TestResults)
}


# Print a separator line.
function Separator( \
	li_Count)
{

	for ( li_Count = 0; li_Count < WIDTH_LINE_; li_Count++)
	{
		printf("%s", FILL_CHAR_)
	}

	print ""
}


BEGIN {
	# Widths of fields for printf
	WIDTH_QTY_ 				= 5
	WIDTH_AMT_ 				= 12
	WIDTH_PRECISION_		= 2
	WIDTH_LINE_				= 78
	WIDTH_TOTAL_ 			= WIDTH_AMT_ + WIDTH_QTY_ 
	WIDTH_GRANDTOTAL_ 		= WIDTH_TOTAL_ + 3
	WIDTH_LINESTART_ 		= \
		WIDTH_LINE_ - WIDTH_QTY_ - WIDTH_AMT_ - WIDTH_TOTAL_
	WIDTH_LASTLINESTART_ 	= WIDTH_LINE_ - WIDTH_GRANDTOTAL_

	TRUE_					= 1
	FALSE_					= 0

	FILL_CHAR_				= "-"

	AMT_FIELD_ERR_ 			= " should be a real number."

	EXIT_SUCCESS_ 			= 0
	EXIT_FAILURE_ 			= 1
	EXIT_STATUS_ 			= EXIT_SUCCESS_

	lf_GrandTotal = 0.00

	# Print header line.
	printf("\n%-*s%*s%*s%*s\n", WIDTH_LINESTART_, "", 
		WIDTH_QTY_, "Qty", WIDTH_AMT_, "Amt", WIDTH_TOTAL_, "Total")
	Separator()
}


# Main
{
	lf_Amt	 		= 0.00
	li_Qty 			= 1
	lf_Total 		= 0.00
	ls_LineStart 	= ""
	li_CurrentField 	= NF

	if ( NonDataLine($0) )
	{
		print
		next
	}

	if ( !IsReal($li_CurrentField) )
	{
		Error( $li_CurrentField, NR, AMT_FIELD_ERR_)
		exit (EXIT_STATUS_)
	}

	lf_Amt = $li_CurrentField

	if ( --li_CurrentField > 0 )
	{

		if ( IsInteger($li_CurrentField) )
		{
			li_Qty = $li_CurrentField
			li_CurrentField--
		}
		
		if ( li_CurrentField > 0 )
		{
			ls_LineStart = FormLineStart(li_CurrentField)
		}

	}

	lf_Total = li_Qty * lf_Amt
	lf_GrandTotal += lf_Total
	printf("%-*s%*d%*.*f%*.*f\n", WIDTH_LINESTART_, ls_LineStart, 
		WIDTH_QTY_, li_Qty, WIDTH_AMT_, WIDTH_PRECISION_, lf_Amt, 
		WIDTH_TOTAL_, WIDTH_PRECISION_, lf_Total)
}


END {

	if (EXIT_STATUS_ == EXIT_SUCCESS_ )
	{
		Separator()
		printf("%*s%*.*f\n\n", WIDTH_LASTLINESTART_, "Total",
			WIDTH_GRANDTOTAL_, WIDTH_PRECISION_, lf_GrandTotal)	
	}

}