[net.micro.6809] columnar output hack

jejones@ea.UUCP (12/21/84)

Yet another possibly useful tool: this one splits out fields from input
lines and writes them out in columnar fashion. Admittedly this one, like
the sort that preceded it, isn't particularly OS-9 or 6809-specific, but
then there are places that lock up net.sources, and I want the OS-9 users
out there to get these tools.

						James Jones

------------------------------TIARA HERE------------------------------
/*
 * column -- print with fields justified and columnized
 *
 * usage:	column [<options>] [<field-spec> ... ] [<pathname> ...]
 *		where <field-spec> has the form
 *			<field #>/<max width>[<justify>]
 *		and <justify> can be either 'l' or 'r'.
 *		(Upper or lower case will do.)
 *
 * options:	-c=<character>	use the <character> as a field separator
 *		-l<length>	assume the output lines have maximum length
 *				<length>
 *
 * semantics:	reads files with the specified pathnames and prints the
 *		fields selected from each line so that they fit on a line
 *		at most <length> characters long, with each field lined up 
 *		in columnar fashion. Fields longer than their <max width>
 *		are truncated. Spaces left over are divided evenly
 *		between columns.
 *
 * defaults:	field separator defaults to space, <length> to 80.
 *		If no fields are specified, then 1/<length>l is assumed.
 *		Justification defaults to left-justification.
 *		If no pathnames are specified, standard input is read.
 *
 * limits:	Lines are truncated at MAXLINE characters.
 *		We can output at most MAXFIELDS fields, and read
 *		at most MAXFILES files.
 *		We are currently stupid about tabs and backspaces
 *		when we figure out what fits on a line.
 */

#include <stdio.h>
#include <ctype.h>
#include <bool.h>

#define MAXLINE		256
#define MAXFILES	20
#define MAXFIELDS	10
#define error(msg)	{fprintf(stderr, "column: %s\n", (msg)); exit(1);}
#define Space(size)	{int i; for (i = 0; i < (size); i++) putchar(' ');}

#define DELIM		'c'
#define LENGTH		'l'
#define LJUST		'L'
#define RJUST		'R'

typedef struct {
	int	f_no;
	int	f_maxlen;
	char	f_justify;
	int	f_gutter;
	char	*f_pos;
	int	f_trulen;
} FldDesc;

FldDesc	LineFields[MAXFIELDS],
	*FldSeq[MAXFIELDS];
int	NFields;

char	*Input[MAXFILES];
int	NInputs, CurrPath;
FILE	*CurrSource;

char	Delim = ' ';
int	Length = 80;

main(argc, argv)
int	argc;
char	*argv[];
{
	int	i;
	char	Line[MAXLINE];
	char	*InLine();
	bool	OK, DoOpt(), DoFile(), InitLayout(), StartRead();

	OK = TRUE;

	for (i = 1; i < argc; i++) {
		if (*argv[i] == '-')
			OK &= DoOpt(argv[i]);
		else if (isdigit(*argv[i]))
			OK &= DoFld(argv[i]);
		else
			OK &= DoFile(argv[i]);
	}

	if (!OK || !InitLayout() || !StartRead())
		exit(1);

	while (InLine(Line) != NULL)
		Columnize(Line);

}

bool
DoOpt(OpString)
char	*OpString;
{

	switch (*(OpString + 1)) {
	case DELIM:
		if (*(OpString + 2) != '=')
			error("invalid delimiter specification");
		Delim = *(OpString + 3);
		break;
	case LENGTH:
		if ((Length = atoi(OpString + 2)) <= 0)
			error("invalid line length");
		break;
	default:
		fprintf(stderr, "column: invalid option %c\n", *(OpString + 1));
		return(FALSE);
	}

	return(TRUE);

}
bool
DoFld(FldString)
char	*FldString;
{
	FldDesc	*NewFld;

	FldSeq[NFields] = NewFld = &LineFields[NFields];

	if ((NewFld->f_no = atoi(FldString)) <= 0)
		error("invalid field number");

	while (isdigit(*(FldString++)))
		;
	if (*(FldString - 1) != '/')
		error("invalid field specification");

	if ((NewFld->f_maxlen = atoi(FldString)) <= 0)
		error("invalid field width");

	while (isdigit(*(FldString++)))
		;
	switch (toupper(*(FldString - 1))) {
	case RJUST:
		NewFld->f_justify = RJUST;
		break;
	case '\0':
	case LJUST:
		NewFld->f_justify = LJUST;
		break;
	default:
		error("invalid justification");
	}

	NFields++;
	return(TRUE);

}

/*
 * routines to hide the multiplicity of input files from the main
 * algorithm -- DoFile, StartRead, and InLine.
 */

bool
DoFile(PathName)
char	*PathName;
{
	if (NInputs >= MAXFILES)
		error("too many input files");
	Input[NInputs++] = PathName;
	return(TRUE);
}

bool
StartRead()
{
	if (NInputs > 0) {
		if ((CurrSource = fopen(Input[0], "r")) == NULL) {
			fprintf(stderr, "sort: can't open %s\n", Input[0]);
			return(FALSE);
		}
	} else
		CurrSource = stdin;
	return(TRUE);
}

char *
InLine(Buffer)
char	*Buffer;
{
	char	*result;

	while ((result = fgets(Buffer, MAXLINE, CurrSource)) == NULL) {
		fclose(CurrSource);
		if (++CurrPath >= NInputs)
			return(NULL);
		if ((CurrSource = fopen(Input[CurrPath], "r")) == NULL) {
			fprintf(stderr, "sort: can't open %s\n",
				Input[CurrPath]);
			exit(1);
		}
	}

	return(result);

}

bool
InitLayout()
{
	int	i, totlen, gap, leftover;
	int	Precede();

	if (NFields == 0) {
		FldSeq[0] = &LineFields[0];
		LineFields[0].f_no = 1;
		LineFields[0].f_maxlen = Length;
		LineFields[0].f_justify = LJUST;
	} else if (NFields > 1) {
		totlen = 0;
		for (i = 0; i < NFields; i++)
			totlen += LineFields[i].f_maxlen;
		if ((gap = (Length - totlen) / (NFields - 1)) < 1)
			error("fields too big for output line");
		leftover = (Length - totlen) % (NFields - 1);
		for (i = 0; i < NFields - 1; i++)
			LineFields[i].f_gutter = gap + (i < leftover);
		qsort(FldSeq, NFields, sizeof(FldDesc *), Precede);
	}
	LineFields[NFields - 1].f_gutter = 0;

	return(TRUE);

}

int
Precede(f1, f2)
FldDesc	**f1, **f2;
{
	return ((*f1)->f_no - (*f2)->f_no);
}

Columnize(Line)
char	*Line;
{
	char	*Field;
	int	i, CurrField, FScan, FLen;
	bool	ShouldOutput;

	Field = Line;
	Field[strlen(Field) - 1] = Delim;

	for (CurrField = 1, FScan = 0; FScan < NFields; CurrField++) {
		ShouldOutput = CurrField >= FldSeq[FScan]->f_no;
		if (ShouldOutput)
			FldSeq[FScan]->f_pos = Field;
		FLen = 0;
		if (*Field != '\0') {
			while (*(Field++) != Delim)
				FLen++;
			*(Field - 1) = '\0';
		}
		if (ShouldOutput) {
			FldSeq[FScan]->f_trulen = FLen;
			FScan++;
		}
	}

	for (FScan = 0; FScan < NFields; FScan++)
		PrintField(&LineFields[FScan]);
	putchar('\n');

}

PrintField(RefFld)
FldDesc	*RefFld;
{
	if (RefFld->f_trulen > RefFld->f_maxlen) {
		(RefFld->f_pos)[RefFld->f_maxlen] = '\0';
		RefFld->f_trulen = RefFld->f_maxlen;
	}

	switch (RefFld->f_justify) {
	case RJUST:
		Space(RefFld->f_maxlen - RefFld->f_trulen);
		fputs(RefFld->f_pos, stdout);
		break;
	case LJUST:
		fputs(RefFld->f_pos, stdout);
		Space(RefFld->f_maxlen - RefFld->f_trulen);
		break;
	}

	Space(RefFld->f_gutter);

}