/************************************************************************/

/*	flist.c		file listing utility v2.0			*/

/*	Usage:		flist [file1 [file2...]] [-b -c -h -l -n -p]	*/

/*	History:	furiously hacked together	July 12, 1986	*/
/*			4 parameters added		July 15, 1986	*/
/*			2 new parameters, bugs "fixed"	July 30, 1986	*/
/*			semi-AmigaDOS-ized (header)	Aug  10, 1986	*/
/*			Amiga style wildcards added	Aug  11, 1986	*/

/*	Copyright 1986	Anson Mah					*/
/*			2735 E. 2nd Ave.,				*/
/*			Vancouver, B.C., Canada				*/
/*			V5M 1E2  (604) 254-6849				*/

/*	Compile using:	Lattice C v3.03 (need conversion to Aztec C)	*/

/*	Notes:		- TABs are 8 spaces in size.  This can be	*/
/*			  changed below.				*/
/*			- >3 blanks lines will force a new_page		*/
/*			- maximum physical line width is 128, due to	*/
/*			  limitiations with some printers (for		*/
/*			  instance, mine can do 136)			*/
/*			- line width should be kept as a multiple of	*/
/*			  TABSIZE for proper output handling		*/
/*			- will be AmigaDOS-ized and operate through	*/
/*			  the printer.device in the (distant?) future	*/
/*			- Amiga style wildcards now supported, so must	*/
/*			  link with wildexp.o and PatMatch.o		*/

/*	Listen:		"Please let me know about any improvements.	*/
/*			 Many are needed, especially in the code."	*/
/*			"Don't you dare sell this without at least	*/
/*			 my permission!!!  Besides, I don't think	*/
/*			 anyone in his right mind would pay for this."	*/

/*			Placed in the Public domain; distribute freely,	*/
/*			  but keep this header in place.		*/

/************************************************************************/


#include <exec/types.h>
#include <exec/memory.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <stdio.h>



#define PAGELENGTH	66			/* page size (lines)	*/
#define TOPMARGIN	2			/* begin print line 3	*/
#define BOTTOMMARGIN	4			/* stop after line 62	*/
#define CONDENSED	'\017'			/* condensed (~17 cpi)	*/
#define DEFAULTWIDTH	128			/* condensed text width	*/
#define NARROWWIDTH	80			/* normal text width	*/
#define TOPOFFORM	'\f'			/* formfeed code	*/
#define NEWLINE		'\n'			/* newline code		*/
#define TAB		'\t'			/* tab character	*/
#define TABSIZE		8			/* tab width (chars)	*/
#define LINENUMFORM	"  %4d: "		/* line number format	*/
#define LINENUMSIZE	8			/* line number width	*/
#define CREATED		"Created: "		/* created string	*/
#define PRINTED		"Printed: "		/* printed string	*/
#define PAGE		"Page "			/* page string		*/
						/* header formats	*/
#define DEFHEADERFORM	"%-30s%19s%9s%10s%13s%9s%10s%25s%3d"
#define NARHEADERFORM	"%-30s%15s%9s%10s%13s%3d"
#define MAXFILES	128			/* enough??		*/
#define ALLFILES	"#?"			/* all files wildcard	*/


/* External references							*/

extern BOOL Chk_Abort();			/* they should be BOOLs	*/
extern BOOL Enable_Abort;			/*	^^^^^^		*/
extern struct FileLock *Lock();			/* lock file		*/
extern BOOL Examine();				/* examine file		*/
extern BOOL iswild();				/* check for wildcard	*/
extern BOOL wildexp();				/* expand wildcard	*/

/* Forward references							*/

VOID	process_args(), file_loop(), list_file(), rid_path(),
	usage(), print_header(), print_linenum(), form_feed(),
	next_line(), next_page(), get_dates(), date(), time();

BOOL	is_dir();


/* Global variables							*/

WORD	curpage = 0,				/* current page		*/
	curline,				/* current line		*/
	curcol,					/* current column	*/
	fileline,				/* current line in file	*/
	width   = DEFAULTWIDTH - LINENUMSIZE,	/* width of line	*/
	nfiles;					/* no. files to print	*/

BOOL	linenum = TRUE,				/* line number flag	*/
	blkfeed = TRUE,				/* formfeed test flag	*/
	printer = TRUE,				/* printer output flag	*/
	deftext = TRUE,				/* default text flag	*/
	header  = TRUE,				/* print header flag	*/
	pagenum_reset = TRUE;			/* reset page no. flag	*/

UBYTE	cdate[10], ctime[10];			/* create date/time	*/
UBYTE	pdate[10], ptime[10];			/* print  date/time	*/
UBYTE	*filename;				/* name of current file	*/
FILE	*outp;					/* ptr to output stream	*/

struct FileLock *flock;				/* file lock		*/
struct FileInfoBlock *finfo;			/* file info block	*/



main(argc, argv)
WORD argc;
BYTE **argv;
{
	WORD	i,
		nfok;			/* number of readable files	*/
	UBYTE	dir[80],		/* directory string		*/
		*largv[MAXFILES];	/* ptrs to filenames		*/

	Enable_Abort = FALSE;	/* allow for ^C/^D termination, but	*/
				/* exit thru my routine, not C's	*/

	if (argc < 2)			/* if only "flist" typed	*/
		usage();

	process_args(argc, argv);

	if (!deftext)				/* set maximum width	*/
		width = NARROWWIDTH - LINENUMSIZE;
	if (!linenum && !deftext)
		width += LINENUMSIZE;

	nfok = nfiles;			/* supposed no. of good files	*/

	/* allocate memory for LONG-aligned FileInfoBlock structure	*/
	finfo = (struct FileInfoBlock *)
		AllocMem(sizeof(struct FileInfoBlock), MEMF_PUBLIC);
	if (finfo == NULL) {
		fputs("\nError: can't allocate memory for FileInfoBlock.\n\n", stderr);
		exit(1);
	}

	for (i = 1; i <= nfiles; i++)		/* check if files exist	*/
		if ((iswild(argv[i])) || (is_dir(argv[i])))
			/* i.e. not a file, but a wildcard or directory	*/
			continue;
		else if ((flock = Lock(argv[i], ACCESS_READ)) == NULL) {
			fprintf(stdout, "Warning: can't open %s\n", argv[i]);
			argv[i] = NULL;
			nfok--;
			}
		else
			UnLock(flock);

	if (!nfok) {		/* exit if no files are available	*/
		fputs("\nError:  no files selected.\n\n", stderr);
		FreeMem(finfo, sizeof(struct FileInfoBlock));
		exit(1);
	}

	if (printer)			/* open stream to printer	*/
		if ((outp = fopen("PRT:", "w")) == NULL) {
			fputs("\nError: printer not ready.\n\n", stderr);
			FreeMem(finfo, sizeof(struct FileInfoBlock));
			exit(1);
		}
	fputc(CONDENSED, outp);	/* set printer to condensed (17 cpi)	*/

	for (i = 1; i <= nfok; i++)		/* print each "ok" file	*/
		if (argv[i] != NULL)
			if (is_dir(argv[i])) {
				strcpy(dir, argv[i]);
				if ((dir[strlen(argv[i])-1] != ':') && (dir[strlen(argv[i])-1] != '/'))
					strcat(dir, "/");
				strcat(dir, ALLFILES);
				if (wildexp(dir, largv, MAXFILES))
					file_loop(largv);
				else
					fprintf(stderr, "Warning: directory %s seems to be empty.\n", argv[i]);
			}
			else if (iswild(argv[i]))
				if (wildexp(argv[i], largv, MAXFILES))
					file_loop(largv);
				else
					fprintf(stderr, "Warning: bad wildcard %s\n", argv[i]);
			else {
				filename = argv[i];
				list_file();
			}

	FreeMem(finfo, sizeof(struct FileInfoBlock));
	fclose(outp);
}



/* process_args	process argument list supplied by the user		*/

/* SETUP:	WORD	argc;						*/
/*		BYTE	**argv;						*/

/* CALL:	process_args(argc, argv);				*/

/* WHERE:	argc is the number of arguments				*/
/*		argv is an array of pointers to the arguments		*/

VOID process_args(argc, argv)
WORD	argc;
BYTE	**argv;
{
	WORD	i = 1;

	while ((*argv[i] != '-') && (i < argc))
		i++;
	if (!(nfiles = i - 1))			/* if no files entered	*/
		usage();

	if (i != argc) {			/* if there are parms.	*/
		i = nfiles + 1;
		while ((*argv[i] == '-') && (i <= argc))
			i++;
	}
	if (i != argc) {	/* if illegal parameters, i.e. no '-'	*/
		fputs("\nError:  bad parameters.\n", stderr);
		usage();
	}

	for (i = nfiles + 1; i < argc; i++)	/* interpret arguments	*/
		switch (*++argv[i]) {
			case 'b':
				blkfeed = FALSE;
				break;
			case 'c':
				printer = FALSE;
				outp = stdout;
				break;
			case 'h':
				header  = FALSE;
				break;
			case 'l':
				linenum = FALSE;
				break;
			case 'n':
				deftext = FALSE;
				break;
			case 'p':
				pagenum_reset = FALSE;
				break;
			default :
				fprintf(stderr, "\nError:  bad parameter -%s\n", argv[i]);
				usage();
		} /* switch */
}



/* file_loop	print files contained in largv				*/


VOID file_loop(names)
BYTE	**names;
{
	WORD	i;

	for (i = 0; i <= MAXFILES && names[i] != NULL; i++) {
		filename = names[i];
		list_file();
	}
}


/* is_dir	check to see if supplied file name is a directory	*/

/* SETUP:	BYTE	*name;						*/
/*		BOOL	is_dir();					*/

/* CALL:	is_dir(name);						*/

/* RETURN:	1 if directory, 0 if not				*/

BOOL is_dir(name)
BYTE	*name;
{
	WORD	result = 0;

	flock = Lock(name, ACCESS_READ);

	if (flock) {
		if (Examine(flock, finfo))
			if (finfo->fib_DirEntryType > 0L)
				result = 1;
		UnLock(flock);
	}

	return(result);
}



/* list_file	print contents of file in filename			*/


VOID list_file()
{
	BYTE	ch;		/* must be BYTE, or won't read EOF (-1)	*/
	WORD	i, t,			/* counters...			*/
		nlfeeds;		/* number of line feeds		*/
	FILE	*fp;			/* file pointer			*/

	flock = Lock(filename, ACCESS_READ);
	Examine(flock, finfo);
	fp = fopen(filename, "r");			/* open file	*/

	get_dates();	/* get creation and print dates	and times	*/

	if (printer && (nfiles != 1))
		/* no need to print this if listing only one file	*/
		/* and output is going to the screen!			*/
		fprintf(stdout, "Listing %s ...\n", filename);

	if (pagenum_reset)
		curpage = 1;
	else	curpage++;

	curline = TOPMARGIN + 1;
	curcol = 1;
	fileline = 1;

	rid_path();			/* extract name from path	*/
	print_header();
	print_linenum();

	while ((ch = fgetc(fp)) != EOF) {
		if (Chk_Abort()) {		/* check for ^C/^D	*/
			form_feed();
			UnLock(flock);
			fclose(fp);
			fclose(outp);
			FreeMem(finfo, sizeof(struct FileInfoBlock));
			fputs("*** BREAK - flist\n", stderr);
			exit(1);
		}
		switch(ch) {
			case TAB:
				t = ((((curcol - 1) / TABSIZE) + 1) * TABSIZE) - curcol + 1;
				for (i = 0; i < t; i++) {
					fputc(' ', outp);
					curcol++;
				}
				break;
			case NEWLINE:
				nlfeeds = 1;
				while ((ch = fgetc(fp)) == NEWLINE)
					nlfeeds++;

				/* formfeed on 3 or more blank lines	*/
				if ((nlfeeds >= 4) && blkfeed) {
					fileline += nlfeeds;
					next_page();
					nlfeeds = 0;
					print_linenum();
				} else {
					while (nlfeeds--) {
						fileline++;
						next_line();
						/* if last character of	*/
						/* file is a newline,	*/
						/* don't print line no.	*/
						if (ch != EOF)
							print_linenum();
					} /* while */
				} /* else */

				/* last character was not a NEWLINE,	*/
				/* so put it back			*/
				ungetc(ch, fp);
				curcol = 1;
				break;
			default:
				fputc(ch, outp);
				if (++curcol > width) {
					next_line();
					curcol = 1;
					if (linenum)
						for (i = 0; i < LINENUMSIZE; i++)
							fputc(' ', outp);
				} /* if */
		} /* switch */
	} /* while */

	form_feed();
	fclose(fp);
	UnLock(flock);
}



/* rid_path	extract the filename from the supplied path (if any)	*/


VOID rid_path()
{
	WORD i;

	i = strlen(filename) - 1;

	while ((filename[i] != '/') && (filename[i] != ':') && (i >= 0))
		i--;

	if (i < 0)  return;			/* no path supplied	*/

	for ( ; i >= 0; i--)		/* advance pointer beyond path	*/
		filename++;
}


/* usage	print to console syntax required			*/


VOID usage()
{
	fputs("\n                       F L I S T  v2.0\n\n", stderr);
	fputs("Usage:  flist [file1 [file2 ...]] [-b][-c][-h][-l][-n][-p]\n\n", stderr);
	fputs("        file may be a directory or contain AmigaDOS wildcards\n\n", stderr);
	fputs(" Args:  -b   pass blank lines through  (default: forced paging )\n", stderr);
	fputs("        -c   output to console         (default: printer output)\n", stderr);
	fputs("        -h   suppress header           (default: with header   )\n", stderr);
	fputs("        -l   suppress line numbers     (default: with line nos.)\n", stderr);
	fputs("        -n   narrow (72 column) output (default: 120 columns   )\n", stderr);
	fputs("             w/o line numbers: 80 columns (default: 128 columns)\n", stderr);
	fputs("        -p   suppress number reset     (default: reset per file)\n\n", stderr);

	exit(TRUE);
}


/* next_line	move output to next line, or next page if required	*/


VOID next_line()
{
	if (++curline > (PAGELENGTH - BOTTOMMARGIN)) {
		next_page();
		return;
	}

	fputc(NEWLINE, outp);
}



/* next_page	advance to next physical page and print header		*/


VOID next_page()
{
	curpage++;
	curline = TOPMARGIN + 1;
	form_feed();
	print_header();
}


/* form_feed	advances to next physical page without printing header	*/


VOID form_feed()
{
	fputc(TOPOFFORM, outp);
}


/* print_header		print header					*/


VOID print_header()
{
	WORD i;

	if (header)
		if (deftext)
			fprintf(outp, DEFHEADERFORM, filename, CREATED, cdate,
				ctime, PRINTED, pdate, ptime, PAGE, curpage);
		else
			fprintf(outp, NARHEADERFORM, filename, CREATED,
				cdate, ctime, PAGE, curpage);

	for (i = 0; i < 3; i++)
		next_line();
}


/* print_linenum	print line number in the form "number: "	*/


VOID print_linenum()
{
	if (linenum)
		fprintf(outp, LINENUMFORM, fileline);
}



/* get_dates	get dates (and times) of file creation and print	*/


VOID get_dates()
{
	struct DateStamp print_date;

	DateStamp(&print_date);			/* current time/date	*/
	date(pdate, &print_date);
	time(ptime, &print_date);

	date(cdate, &(finfo->fib_Date));	/* creation time/date	*/
	time(ctime, &(finfo->fib_Date));
}



UBYTE *months[12] = {
	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};

UBYTE dayspermonth[12] = {			/* non-leap years	*/
	31, 28, 31, 30, 31, 30,
	31, 31, 30, 31, 30, 31
};


/* date		get date and make it into a printable string		*/

/* SETUP:	BYTE	buffer[SIZE];					*/
/*		struct DateStamp ds;					*/

/* CALL:	date(buffer, &ds);					*/

/* WHERE:	buffer is a pointer to a storage buffer of adequate	*/
/*		  SIZE to store the date string (at least 9 BYTEs)	*/
/*		&ds is a pointer to a DateStamp structure		*/

/* NOTE:	slightly modified form of MyCLI's dates(); others are	*/
/*		  similar, I'm sure.    (no need to reinvent the wheel)	*/
/*		upon return, buffer is in the form  dd-mmm-yy		*/
/*		good until 2045, I guess				*/

VOID date(bufp, dsp)
BYTE	*bufp;
struct DateStamp *dsp;
{
	LONG	day, month, year;		/* self-explanatory	*/
	LONG	temp;

	day = (long)dsp->ds_Days;
	if (day < 0) {				/* < 0 around year 2045	*/
		sprintf(bufp, "Bad date!");
		return;
	}
	for (year = 78; (temp = 365 + (((year & 3) == 0) ? 1 : 0)) <= day; ) {
		year++;
		day -= temp;
	}
	for (month = 0; day >= dayspermonth[month]; month++) {
		day -= dayspermonth[month];
		if ((month == 1) && ((year & 3) == 0)) {
			day--;			/* leap year		*/
			if (day < 0) {
				day = 28;
				break;
			}
		}
	}

	sprintf(bufp, "%2ld-%3s-%02ld", day + 1, months[month], year);
}



/* time		get time and make it into a printable string		*/

/* SETUP:	BYTE	buffer[SIZE];					*/
/*		struct DateStamp ds;					*/

/* CALL:	time(buffer, &ds);					*/

/* WHERE:	buffer is a pointer to a storage buffer of adequate	*/
/*		  SIZE to store the date string (at least 9 BYTEs)	*/
/*		&ds is a pointer to a DateStamp structure		*/

/* NOTE:	bug in  MyCLI's times(); fixed here			*/

VOID time(bufp, dsp)
BYTE	*bufp;
struct DateStamp *dsp;
{
	LONG	hours, minutes, seconds;

	seconds = dsp->ds_Tick / 50L;
	seconds %= 60L;
	minutes = dsp->ds_Minute;
	hours = minutes / 60L;
	minutes %= 60L;

	if (hours == 0)
		hours = 12L;	/* was hours = 24L, and not correct	*/

	sprintf(bufp, "%2ld:%02ld:%02ld", (hours>12)?hours-12:hours, minutes, seconds);

	if (hours <= 12)
		strcat(bufp, "a");		/* a.m.	*/
	else
		strcat(bufp, "p");		/* p.m.	*/
}
