[comp.sources.amiga] v90i042: BBSindex 1.0 - file database utility for BBS-PC!, Part02/03

Amiga-Request@cs.odu.edu (Amiga Sources/Binaries Moderator) (02/03/90)

Submitted-by: Eddy Carroll <ECARROLL%vax1.tcd.ie@CUNYVM.CUNY.EDU>
Posting-number: Volume 90, Issue 042
Archive-name: util/bbsindex-1.0/part02

#!/bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of archive 2 (of 3)."
# Contents:  src/bbsindex.c src/command.c src/format.c
# Wrapped by tadguy@xanth on Fri Feb  2 14:54:38 1990
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'src/bbsindex.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'src/bbsindex.c'\"
else
echo shar: Extracting \"'src/bbsindex.c'\" \(13333 characters\)
sed "s/^X//" >'src/bbsindex.c' <<'END_OF_FILE'
X/*
X * BBSindex                         (C) Copyright Eddy Carroll, August 1989
X *
X * This is a utility which will scan the file catalogue of BBS-PC! and
X * build lists of the files in various sections and directories.
X *
X * It is designed to be run automatically on a regular basis in the
X * background. It reads in instructions from an input file, and 
X * produces output files based on these instructions.
X *
X * Usage: BBSindex [options] [scriptfile] ..
X *
X * With no parameters, a short help message is displayed.
X *
X * The possible options are:
X *
X * -c <file>        This specifies the configuration file (CFGINFO.DAT)
X * -d <file>		This specifies the input file (default UDHEAD.DAT)
X * -f "string" 		This specifies the format string for the output
X * -h				Displays the help message
X * -n				Stops AmigaDOS putting up requesters
X * -t               Trace mode - display each line before execution
X *
X * scriptfile is a file containing commands to control what is output.
X * See the documentation for more info. Note that options and script files
X * may be freely interspersed.
X *
X */
X
X#define MAIN
X
X#ifndef LATTICE_50
X#include "system.h"
X#endif
X
X#include "bbsindex.h"
X
X/*
X *		Miscellaneous static global variables
X */
X
Xstatic struct IntuitionBase *IntuitionBase;
Xstatic BPTR infile, lock;
Xstatic struct Remember *memkey;
Xstatic char buffer[BUFSIZE];
Xstatic int bufpos = 0;
Xstatic APTR requeststat;	/* Ptr to window for AmigaDos requesters */
X
X/*
X *		print()
X *		-------
X *		Prints a string to standard output
X */
Xvoid print(s)
Xchar *s;
X{
X	Write(errorfile,s,strlen(s));
X}
X
X/*
X *		Cleanup()
X *		---------
X *		Releases all program resources, and exits. Note that because of
X *		the way it checks to see if a resource is allocated, you need to
X *		set pointers to NULL if you release the object associated with
X *		them. For example, when you close a file, remember to set the
X *		file handle to NULL immediately afterwards, otherwise Cleanup()
X *		will try to close it again.
X */
Xvoid Cleanup(code)
Xint code;
X{
X	static ctrlc;
X	struct Process *me = (struct Process *)FindTask(0L);
X	int i;
X
X	if (!ctrlc) {
X		ctrlc = 1;
X		me->pr_WindowPtr = requeststat;
X		if (script)
X			FreeMem(script, scriptsize);
X		for (i = 0; i < nummacros; i++)
X			FreeMem(macros[i], MACROSIZE + macros[i]->size);
X		for (i = 0; i < nestlevel; i++)
X			FreeMem(params[i], PARAMSIZE + params[i]->size);
X		if (memkey)
X			FreeRemember(&memkey, TRUE);
X		if (IntuitionBase)
X			CloseLibrary(IntuitionBase);
X		if (lock)
X			UnLock(lock);
X		if (dirlock)
X			UnLock(dirlock);
X		if (infile)
X			Close(infile);
X		if (outfile != Output())
X			Close(outfile);
X		if (errorfile)
X			Close(errorfile);
X		exit(code);
X	}
X}
X
X/*
X *		cxovf()
X *		-------
X *		The standard lattice "stack abort" code. This is called when the
X *		stack overflows (having first reset the stack pointer to something
X *		safe of course).
X */
Xvoid cxovf()
X{
X	print("\nBBSindex: Stack overflow! Use the STACK command "
X		  "to increase stack size\n");
X	Cleanup(32);
X}
X
X/*
X *		chkabort()
X *		----------
X *		My own version of chkabort(), called by the Lattice Library
X *		functions periodically to check for CTRL-C.
X *
X *		This version cleans up first (otherwise, after CTRL-C'ing a few times
X *		while  processing a 220K UDHEAD.DAT file, you find you don't have any
X *		memory left!).
X */
Xvoid chkabort()
X{
X	if (SetSignal(0,0) & SIGBREAKF_CTRL_C) {
X		SetSignal(0,0xffffffff);
X		print("^C\n");
X		Cleanup(5);
X	}
X}
X
X
X/*
X *		SafeAllocMem()
X *		--------------
X *		A revised AllocMem() that checks for out of memory.
X */
Xvoid *SafeAllocMem(size)
Xint size;
X{
X	void *ptr = AllocMem(size, 0);
X	if (!ptr) {
X		print("BBSindex: Out of memory!\n");
X		Cleanup(20);
X	}
X	return (ptr);
X}
X
X
X/*
X *		mymalloc()
X *		----------
X *		My own private malloc. No memory limits, and no library overhead.
X *		Easy to replace the call to AllocRemember with one to malloc for
X *		portability. To try and keep down memory fragmentation, requests
X *		for memory less than 200 bytes are allocated from a larger block
X *		which is allocated "on the fly".
X */
Xvoid *mymalloc(size)
Xint size;
X{
X	static void *bigblock;				/* Current large memory block */
X	static int curpos = FRAGBLOCK;		/* Current position in block  */
X	void *ptr;
X	
X	size = (size + 7) & 0xfffffff8;		/* Round up to 8 block boundary */
X	if (size < FRAGTHRESH) {
X		/*
X		 *		If we want a small chunk, allocate it from our intermediate
X		 *		block.
X		 */
X		if ((size + curpos) > FRAGBLOCK) {
X			bigblock = mymalloc(FRAGBLOCK);
X			curpos = 0;
X		}
X		ptr = (char *)bigblock + curpos;
X		curpos += size;
X	} else {
X		/*
X		 *		Otherwise, just allocate memory as normal.
X		 */
X		ptr = AllocRemember(&memkey, size, 0);
X		if (!ptr) {
X			print("BBSindex: Out of memory!\n");
X			Cleanup(20);
X		}
X	}
X	return (ptr);
X}
X
X
X/*
X *		dumpdata()
X *		----------
X *		Outputs raw data to a file, checking for write errors.
X */
Xvoid dumpdata(buf,len)
Xchar *buf;
Xint len;
X{
X	if (Write(outfile, buf, len) != len) {
X		print("BBSindex: Error writing output file (disk full?)\n");
X		Cleanup(10);
X	}
X}
X
X/*
X *		putstring()
X *		-----------
X *		This function outputs a string to the current output file. If the
X *		file is a tty, the string is output immediately, else it is
X *		buffered up and output when it is big enough.
X */
X
Xvoid putstring(s)
Xchar *s;
X{
X	int len = strlen(s);
X
X	if (toscreen) {
X		dumpdata(s, len);
X	} else {
X		if ( (bufpos + len) > BUFSIZE) {
X			dumpdata(buffer, bufpos);
X			bufpos = 0;
X		}
X		strcpy(buffer+bufpos, s);
X		bufpos += len;
X	}
X}
X
X/*
X *		flushout()
X *		----------
X *		Flushes output to disk, before closing a file.
X */
X
Xvoid flushout()
X{
X	if (bufpos > 0) {
X		dumpdata(buffer, bufpos);
X	}
X	bufpos = 0;
X}
X
X/*
X *		openfile()
X *		----------
X *		Opens a specified file for reading, and returns the length
X *		of the file. If an error occurs, aborts automatically. The
X *		caller has responsibility for calling Close(infile) when finished
X *		with the file.
X */
Xlong openfile(filename)
Xchar *filename;
X{
X	if ((lock = Lock(filename, ACCESS_READ)) == NULL) {
X		print3("BBSindex: Can't access file ",filename,"\n");
X		Cleanup(5);
X	}
X
X	if (!Examine(lock, fib)) {
X		print3("BBSindex: Can't get info for file ",filename,"\n");
X		Cleanup(5);
X	}
X	UnLock(lock); lock = NULL;
X
X	if ((infile = Open(filename, MODE_OLDFILE)) == NULL) {
X		print3("BBSindex: Can't open file ",filename," for input\n");
X		Cleanup(5);
X	}
X	return (fib->fib_Size);
X}
X
X/*
X *		readdatabase()
X *		--------------
X *		This function reads in the entire file database into memory,
X *		allocating memory as required. It sets up an array of pointers to
X *		the records in the database, which is used by qsort() among other
X *		things.
X *
X *		Note that the memory needed is allocated in a number of small
X *		chunks each BLOCKSIZE * UDSIZE bytes in size (~16K by default).
X *		This means there doesn't need to be a contigous block of memory
X *		big enough to hold the whole file database available - it is
X *		split into smaller chunks instead.
X */
X
Xvoid readdatabase(filename)
Xchar *filename;
X{
X	long	size;
X	long	bsize;
X	long	i,j;
X	UDHEAD	*block;		/* Pointer to block of headers	*/
X	UDHEAD	**p;
X
X	readfiles = TRUE;
X
X	size = openfile(filename);
X	if ((size % UDSIZE) || (size < (2 * UDSIZE))) {
X		print3("BBSindex: ",filename,
X			   " isn't a valid BBS-PC! file header file!\n");
X		Cleanup(5);
X	}
X
X	/*
X	 *		File successfully opened, now read in file data.
X	 *		Skip past unneeded data at start of BBS-PC! file
X	 */
X	Seek(infile, UDSIZE * 2, OFFSET_BEGINNING);
X	size		= size - (2 * UDSIZE);
X	numrecs		= size/UDSIZE;
X	ptrblock	= mymalloc(numrecs * sizeof(UDHEAD *));
X	p			= ptrblock;
X
X	/*
X	 *	Now allocate blocks to hold data, read in data, and setup initial
X	 *	pointers to point to the file headers.
X	 */
X	for (i = numrecs; i > 0; i = i - BLOCKSIZE) {
X
X		bsize = (i > BLOCKSIZE ? BLOCKSIZE : i);
X		block = mymalloc(bsize * UDSIZE);
X
X		chkabort();
X		if (Read(infile, block, bsize * UDSIZE) != (bsize * UDSIZE)) {
X			print3("BBSindex: Error reading from file",filename,"\n");
X			Cleanup(10);
X		}
X
X		/*
X		 *		Now initialise all the pointers for this block. Also
X		 *		null-terminate the catalogue filename, since BBS-PC!
X		 *		doesn't always save out the null termination byte.
X		 *		Also set Online and Valid files to 0, by default.
X		 *		These will be updated by CHECKFILES.
X		 */
X
X		for (j = 0; j < bsize; j++) {
X			block->cat_name[15] = '\0';
X			block->online = 0;
X			block->valid = 0;
X			block->dirnum = 0;
X			*p++ = block++;
X		}
X	}
X	Close(infile); infile = NULL;
X}
X
X/*
X *		readconfigfile()
X *		----------------
X *		This function reads in the configuration file (i.e. CONFIG.DAT),
X *		if present, and initialises the directory array with the
X *		directory names. If the file isn't present, readconfig is set
X *		to FALSE, so that CHECKFILES won't work unless directories are
X *		specified on the command line.
X */
Xvoid readconfigfile()
X{
X	int i;
X	long size;
X
X	size = openfile(configname);
X	if (size != CFGSIZE) {
X		print3("BBSindex: ", configname,
X			" is not a valid configuration file\n");
X		Cleanup(10);
X	}
X	if (Read(infile, config, CFGSIZE) != CFGSIZE) {
X		print3("BBSindex: Error reading from configuration file ",
X				configname, "\n");
X		Cleanup(10);
X	}
X	Close(infile);
X	infile = NULL;
X	for (i = 0; i < NUM_SECT; i++)
X		strcpy(dirnames[i], config->ud_alt[i]);
X}
X
X
X/*
X *		readscript()
X *		------------
X *		This function reads in the specified script file into memory.
X *		It allocates memory for the script file on the fly - this 
X *		memory should be released by the caller, either through calling
X *		Cleanup(), or explicitly by calling FreeMem(). If the latter,
X *		remember to set script = NULL immediately afterwards.
X *
X *		The script pointer is set to the start of the script buffer by
X *		this call, so readcommand() will start at the beginning of the file.
X */
X
Xvoid readscript(filename)
Xchar *filename;
X{
X	strcpy(scriptname, filename);
X	scriptsize = openfile(filename);
X	if (scriptsize == 0) {
X		print3("BBSindex: Script file ",filename," is empty.\n");
X		Cleanup(10);
X	}
X
X	script = SafeAllocMem(scriptsize);
X	if (Read(infile, script, scriptsize) != scriptsize) {
X		print3("BBSindex: Error reading script file ", filename, "\n");
X		Cleanup(10);
X	}
X	Close(infile); infile = NULL;
X	scriptpos = 0;
X	linenum = 1;
X}
X
X/*
X *		help()
X *		------
X *		Prints out a help screen for the program.
X */
Xvoid help()
X{
X	print("BBSindex V1.0 file utility for BBS-PC! "
X		  "Copyright (C) Eddy Carroll 1989.\n");
X	print("Usage: bbsindex {options} scriptfile ..\n\n");
X	print("Possible options are:\n\n");
X	print(" -c filename    Read configuration from filename (default ");
X	print2(configname, ")\n");
X	print(" -d filename    Read database from filename (default ");
X	print2(databasename, ")\n");
X	print(" -f \"string\"    Format output as string (default \""
X		  "%15n %w %-6x-%b{B,T}  %c\")\n");
X	print(" -h             Print this help screen\n");
X	print(" -n             Disable AmigaDos requesters\n");
X	print(" -t             Turn on trace mode\n\n");
X	print("(See documentation for a description of the script language.)\n");
X	Cleanup(5);
X}
X
X/*
X *		doscript()
X *		----------
X *		Reads in and executes the specified script file.
X */
Xvoid doscript(name)
Xchar *name;
X{
X	readscript(name);
X	execscript();
X	flushout();
X	FreeMem(script, scriptsize);
X	script = NULL;
X}
X
X
X/*
X *		Mainline
X *		--------
X */
X
Xvoid main(argc,argv)
Xint argc;
Xchar *argv[];
X{
X	struct Process *me = (struct Process *)FindTask(0L);
X	requeststat = me->pr_WindowPtr;
X
X	/*
X	 *		Setup defaults for command line etc.
X	 */
X
X	errorfile	= Open("*", MODE_NEWFILE);
X	outfile		= Output();
X	toscreen	= IsInteractive(outfile);
X	strcpy(formatstring, FORMAT);
X	strcpy(databasename, UDNAME);
X	strcpy(configname,   CFGNAME);
X	tree[0].field = E_ALL;		/* By default, select all files */
X
X
X	/*
X	 *		Open standard resources
X	 */
X	if ((IntuitionBase = OpenLibrary("intuition.library",33)) == NULL) {
X		print("BBSindex: Couldn't open intuition.library, sigh.\n");
X		Cleanup(5);
X	}
X
X	/*
X	 *		Allocate space for reading directory info into
X	 */
X	fib = mymalloc(sizeof(struct FileInfoBlock));
X
X
X	/*
X	 *		Check to see were we run with the name PROGSCRIPT. Only
X	 *		check the last part of the filename, in case we were run with
X	 *		a full path (like BBS:BBSCRIPT for example).
X	 */
X	if (!stricmp(argv[0] + strlen(argv[0]) - strlen(PROGSCRIPT), PROGSCRIPT)) {
X		doscript(DEFSCRIPT);
X		Cleanup(0);
X	}
X
X	/*
X	 *		Now parse the command line.
X	 */
X	if (argc < 2) {
X		help();
X		Cleanup(0);
X	}
X
X	while (argc > 1) {
X		if (argv[1][0] == '-') {
X			switch (argv[1][1]) {
X
X				case 'c':					/* Set config filename */
X					argv++, argc--;
X					strcpy(configname, argv[1]);
X					break;
X
X				case 'd':					/* Set database filename */
X					argv++; argc--;
X					strcpy(databasename, argv[1]);
X					break;
X
X				case 'f':					/* Format string */
X					argv++; argc--;
X					strcpy(formatstring, argv[1]);
X					strcat(formatstring, "\n");
X					break;
X
X				case 'h':					/* Display help */
X					help();
X					break;
X
X				case 'n':					/* Disable AmigaDos requesters */
X					com_norequest();
X					break;
X
X				case 't':
X					tracemode = TRUE;
X					break;
X
X				default:
X					print3("BBSindex: Unknown option ", argv[1], "ignored\n");
X					break;
X			}
X		} else		/* Not a command line option, so invoke script */
X			doscript(argv[1]);
X		argv++, argc--;
X	}
X	Cleanup(0);
X}
X
X
Xvoid __fpinit(){}
Xvoid __fpterm(){}
Xvoid MemCleanup(){}
END_OF_FILE
if test 13333 -ne `wc -c <'src/bbsindex.c'`; then
    echo shar: \"'src/bbsindex.c'\" unpacked with wrong size!
fi
# end of 'src/bbsindex.c'
fi
if test -f 'src/command.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'src/command.c'\"
else
echo shar: Extracting \"'src/command.c'\" \(22726 characters\)
sed "s/^X//" >'src/command.c' <<'END_OF_FILE'
X/*
X * COMMANDS.C
X *
X * This module contains all the command parsing stuff used to parse the
X * commands in the script file.
X *
X */
X
X#ifndef LATTICE_50
X#include "system.h"
X#endif
X
X#include "bbsindex.h"
X
X/*
X *		The list of command names and associated functions
X */
X
Xvoid com_append(), com_close(), com_config(), com_database(), com_echo(),
X	 com_exec(), com_format(), com_list(), com_macro(), com_msg(),
X	 com_open(), com_reset(), com_scan(), com_trace(), com_ignore();
X
Xstruct {
X	char *name;
X	void (*proc)();
X} com[] = {
X
X	{	"APPEND",		com_append		},
X	{	"CHECKFILES",	com_checkfiles	},
X	{	"CLOSE",		com_close		},
X	{	"CONFIG",		com_config		},
X	{	"DATABASE",		com_database	},
X	{	"ECHO",			com_echo		},
X	{	"EXEC",			com_exec		},
X	{	"FOREIGN",		com_foreign		},
X	{	"FORMAT",		com_format		},
X	{	"IGNORE",		com_ignore		},
X	{	"LIST",			com_list		},
X	{	"MACRO",		com_macro		},
X	{	"MSG",			com_msg			},
X	{	"NOREQUEST",	com_norequest	},
X	{	"OPEN",			com_open		},
X	{	"RESET",		com_reset		},
X	{	"SCAN",			com_scan		},
X	{	"SELECT",		com_select		},
X	{	"SORT",			com_sort		},
X	{	"TRACE",		com_trace		},
X	{	NULL,			NULL			} 
X};
X
Xstruct constval {
X	struct constval *next;
X	char *name;
X	char *value;
X};
Xtypedef struct constval CONST;
X
XCONST *firstconst = NULL;		/* Pointer to first constant on list	*/
X
Xstatic char line[MAXCOM];	/* Temporary line buffer */
X
X/*
X *		scripterror()
X *		-------------
X *		Prints an error message for the current script command; the filename
X *		and linenumber are automatically output, followed by the user
X *		specified message. The message should be terminated with a NL,
X *		unless the caller intends outputting any more info afterwards.
X */
Xvoid scripterror(s)
Xchar *s;
X{
X	print3("===> ", scriptname, " (");
X	print3(itoa(linenum), "): ",s);
X}
X
X/*
X *		addconst()
X *		--------
X *		Creates a new constant entry, and links it into the list of 
X *		existing constant definitions. The name of the constant is
X *		initialised appropriately. A pointer to the constant is returned.
X */
XCONST *addconst(name)
Xchar *name;
X{
X	CONST *tv;
X
X	tv			= mymalloc(sizeof(CONST));
X	tv->next	= firstconst;
X	firstconst	= tv;
X	tv->name	= mymalloc(strlen(name)+1);
X	strcpy(tv->name, name);
X	return (tv);
X}
X
X/*
X *		findconst()
X *		---------
X *		Searches constant table for specified constant, and returns a
X *		pointer to it if found, or NULL if not found.
X */
XCONST *findconst(name)
Xchar *name;
X{
X	CONST *tv;
X
X	for (tv = firstconst; tv && stricmp(name, tv->name); tv = tv->next)
X		;
X	return (tv);
X}
X
X
X/*
X *		readcommand()
X *		-------------
X *		This function reads a command from the script buffer into a
X *		specified command buffer. The following modifications are made
X *		to the original script text:
X *
X *		- Anything after a # to an end of line is ignored
X *		- Any line ending with \ is continued on the next line
X *		- Everything outside quotes is capitalised.
X *		- Any white space outside quotes gets reduced to a single space
X *		- White space surrounding command lines is removed.
X *		- Any commas on the line are removed, and replaced by spaces
X *
X *		The script command is terminated by either a semicolon or a newline.
X *		The command buffer is null-terminated on return. When the end of the
X *		script is reached, a 0 is returned. The maximum size of the command
X *		line is specified in max - if this is exceeded, then an error message
X *		is generated. Normally, the length of the command line read in is
X *		returned.
X *
X *		For the technically minded, a mini state machine is setup, to handle
X *		the different requirements.
X */
X
X#define STATE_START		1	/* Skip over space at start of command			*/
X#define STATE_SPACE		2	/* Replace multiple white space by single space	*/
X#define STATE_COPY		3	/* Copy normal text, capitalising				*/
X#define STATE_IGNORE	4	/* If next char is newline, then skip it		*/
X#define STATE_QUOTE		5	/* Copy text up until next quote				*/
X#define STATE_IGQUOTE	6	/* Like STATE_IGNORE, but between quotes		*/
X#define STATE_COMMENT	7	/* Ignore everything until the next newline		*/
X#define STATE_CONST_ST	8	/* Starting to expand a constant $(..)			*/
X#define STATE_CONST_CP  9	/* Copying constant name, and expanding it		*/
X
X/*
X *		Retrieve next character from script buffer, updating line counter
X *		and checking for end of buffer.
X */
X#define nextchar(ch) 									\
X	(	(ch) = script[scriptpos++],				 		\
X	 	((ch) == CHAR_NL ? linenum++ : 0),				\
X	 	((scriptpos > scriptsize) ? (loop = 0) : 0) 	\
X	)
X
X/*
X *		Add character to command line, checking for end of buffer and
X *		aborting if the buffer is overrun.
X */
X#define addchar(ch)										\
X	(	buf[pos++] = (ch),								\
X		((pos > max) ? (scripterror("line too long\n"), Cleanup(10)) : 0)	\
X	)
X
Xint readcommand(buf,max)
Xchar *buf;
Xint max;
X{
X	int pos = 0;
X	int state = STATE_START;
X	int laststate;
X	int loop = 1;
X	int ch;
X	CONST *var;
X	char varname[MAXCONST], *p;
X	char openquote;
X	int varpos;
X
X	if (scriptpos >= scriptsize)
X		return 0;
X
X	nextchar(ch);
X	while (loop) {
X		switch (state) {
X
X			case STATE_START:
X				switch (ch) {
X
X					case CHAR_SPACE:
X					case CHAR_TAB:
X					case CHAR_SEMI:
X					case CHAR_NL:
X					case CHAR_COMMA:
X						nextchar(ch);
X						break;
X
X					case CHAR_HASH:
X						state = STATE_COMMENT;
X						break;
X
X					default:
X						state = STATE_COPY;
X						break;
X				}
X				break;
X
X			case STATE_SPACE:
X				switch (ch) {
X
X					case CHAR_SPACE:
X					case CHAR_TAB:
X					case CHAR_COMMA:
X						nextchar(ch);
X						break;
X
X					case CHAR_NL:
X						linenum--;
X						/* Drop through */
X					case CHAR_SEMI:
X						scriptpos--;
X						loop = 0;
X						break;
X
X					default:
X						addchar(CHAR_SPACE);
X						state = STATE_COPY;
X						break;
X				}
X				break;
X
X			case STATE_COPY:
X				switch (ch) {
X
X					/*
X					 * If we get a # after a command, then we immediately
X					 * stop, so that the comment will get eaten the NEXT
X					 * time we call readcommand(). If we went into
X					 * STATE_COMMENT, then the following command would end
X					 * up getting read into the buffer as well.
X					 *
X					 */
X					case CHAR_HASH:
X						scriptpos--;
X						loop = 0;
X						break;
X
X					case CHAR_QUOTE:
X					case CHAR_QUOTES:
X						addchar(ch);
X						openquote = ch;
X						state = STATE_QUOTE;
X						nextchar(ch);
X						break;
X
X					case CHAR_DOLLAR:
X						laststate = state;	/* Save return state */
X						state = STATE_CONST_ST;
X						nextchar(ch);
X						break;
X
X					case CHAR_NL:
X						linenum--;
X						/* Drop through */
X
X					case CHAR_SEMI:
X						loop = 0;
X						scriptpos--;
X						break;
X
X					case CHAR_ESC:
X						nextchar(ch);
X						state = STATE_IGNORE;
X						break;
X
X					case CHAR_SPACE:
X					case CHAR_TAB:
X					case CHAR_COMMA:
X						state = STATE_SPACE;
X						break;
X
X					default:
X						addchar(toupper(ch));
X						nextchar(ch);
X						break;
X				}
X				break;
X
X			case STATE_IGNORE:
X				if (ch == CHAR_NL)
X					ch = CHAR_SPACE;
X				addchar(ch);
X				nextchar(ch);
X				state = STATE_COPY;
X				break;
X
X			case STATE_QUOTE:
X				switch (ch) {
X
X					case CHAR_NL:
X						scriptpos--;
X						linenum--;
X						loop = 0;
X						break;
X
X					case CHAR_ESC:
X						state = STATE_IGQUOTE;
X						nextchar(ch);
X						break;
X
X					case CHAR_DOLLAR:
X						laststate = state;
X						state = STATE_CONST_ST;
X						nextchar(ch);
X						break;
X
X					case CHAR_QUOTE:
X					case CHAR_QUOTES:
X						if (openquote == ch)
X							state = STATE_COPY;
X						/* Deliberate fall through to next switch */
X
X					default:
X						addchar(ch);
X						nextchar(ch);
X						break;
X				}
X				break;
X
X			case STATE_IGQUOTE:
X				if (ch != CHAR_NL) {
X					addchar(CHAR_ESC);
X					addchar(ch);
X				}
X				nextchar(ch);
X				state = STATE_QUOTE;
X				break;
X
X			case STATE_COMMENT:
X				if (ch == CHAR_NL)
X					state = STATE_START;
X				nextchar(ch);
X				break;
X
X			case STATE_CONST_ST:
X				if (ch != '(') { /* If not a constant usage, copy unchanged */
X					addchar(CHAR_DOLLAR);
X					state = laststate;
X					break;
X				}
X				varpos = 0;
X				nextchar(ch);
X				state = STATE_CONST_CP;
X				break;
X
X			case STATE_CONST_CP:
X				if (ch == ')') {
X					/*
X					 *		Variable name has been fully entered, so now
X					 *		expand it.
X					 */
X					varname[varpos] = CHAR_NULL;
X					var = findconst(varname);
X					if (!var) {
X						scripterror("unknown constant ");
X						print2(varname, ".\n");
X						Cleanup(10);
X					}
X					for (p = var->value; *p; p++)
X						addchar(*p);
X					state = laststate;
X				} else {
X					/*
X					 *		Else still gathering constant name, so keep
X					 *		copying into array.
X					 */
X					if (varpos >= MAXCONST) {
X						scripterror("constant name too long.\n");
X						Cleanup(10);
X					}
X					varname[varpos++] = ch;
X				}
X				nextchar(ch);
X				break;
X		}
X	}
X	addchar(CHAR_NULL);	/* Null terminate command string */
X	compos = 0;
X	comlen = strlen(buf);
X	return (comlen);
X}
X
X/*
X *		findmacro()
X *		-----------
X *		Searches the macro table for the specified macro. If found, returns
X *		pointer to the macro definition, else returns -1.
X */
Xint findmacro(name)
Xchar *name;
X{
X	int i;
X
X	for (i = 0; i < nummacros; i++)
X		if (!strcmp(macros[i]->name, name))
X			return (i);
X	return (-1);
X}
X
X/*
X *		getstring()
X *		-----------
X *		This function scans the command buffer starting at position
X *		'compos', and returns a pointer to the next identifier/string in
X *		the buffer. The string is null-terminated, and if it was enclosed
X *		in quotes, these are removed. compos is left pointing to just after
X *		the string.
X */
Xchar *getstring()
X{
X	char *s = combuf + compos;
X	char *p, openquote;
X
X	if (compos > comlen) {
X		scripterror("missing parameter\n");
X		Cleanup(10);
X	}
X	if (*s == CHAR_SPACE)
X		s++;
X
X	if (*s == CHAR_QUOTE || *s == CHAR_QUOTES) {
X		openquote = *s++;
X		for (p = s; *p && *p != openquote; p++) {
X			if (*p == CHAR_ESC)
X				p++;	/* Skip over possible escaped quotes */
X		}
X		*p = CHAR_NULL;
X		compos = (p - combuf) + 1;
X		return (s);
X	}
X
X	for (p = s; *p && *p != CHAR_SPACE; p++)
X		;
X	*p = CHAR_NULL;
X	compos = (p - combuf) + 1;
X	return (s);
X}
X
X
X/*
X *		execline()
X *		----------
X *		This function executes the current line in the command buffer,
X *		handling macro expansion as necessary.
X */
Xvoid execline()
X{
X	char *cmd;
X	char *p, *s;
X	char *equals;
X	MACRO *curmac;
X	CONST *var;
X	int macronum;
X	char *ps[10];
X	PARAM *par;
X	int length;
X	int i;
X
X	chkabort();
X	if (tracemode) {
X		if (nestlevel > 0) {
X			int i;
X			for (i = 0; i < nestlevel; i++)
X				print("    ");
X			print2(combuf, "\n");
X		} else {
X			print3(itoa(linenum), ":", scriptname);
X			print3(": ", combuf, "\n");
X		}
X	}
X	cmd = getstring();
X	for (i = 0; com[i].name && strcmp(cmd, com[i].name); i++)
X		;
X	if (com[i].name) {
X		/*
X		 *		A valid command was found, so execute it
X		 */
X		com[i].proc();
X	} else if ((macronum = findmacro(cmd)) == -1) {
X		/*
X		 *		Not a macro -- is it a constant definition?
X		 */
X		if (compos < comlen) {
X			/*
X			 *		There's a parameter after it; check is it an equals sign
X			 */
X			equals = getstring();
X			if (equals[0] != CHAR_EQUALS || equals[1] != CHAR_NULL) {
X				/*
X				 *		Wasn't an equals, so probably just a wrong command
X				 *		with some superfluous parameters.
X				 */
X				scripterror("unknown command ");
X				print2(cmd, "\n");
X				Cleanup(10);
X			}
X			/*
X			 *		Okay, it's a constant definition. If it's already been
X			 *		defined, print a warning but carry on anyway.
X			 */
X			var = findconst(cmd);
X			if (var) {
X				scripterror("constant ");
X				print2(cmd, " redefined.\n");
X			} else
X				var = addconst(cmd);
X			/*
X			 *		Now see was a value specified for the constant.
X			 *		If it was, copy it into constant, else just 
X			 *		setup a null definition.
X			 */
X			if (compos < comlen) {
X				/*
X				 *		Copy user's definition
X				 */
X				cmd = getstring();
X				if (strlen(cmd) >= MAXCONST) {
X					scripterror("constant name too long.\n");
X					Cleanup(10);
X				}
X				var->value = mymalloc(strlen(cmd)+1);
X				strcpy(var->value, cmd);
X			} else {
X				/*
X				 *		Setup null constant definition
X				 */
X				var->value = mymalloc(1);
X				strcpy(var->value, "");
X			}
X		} else {
X			/*
X			 *		Not a command, not a macro, not a constant definition.
X			 *		Therefore, must be an error.
X			 */
X			scripterror("unknown command ");
X			print2(cmd, "\n");
X			Cleanup(10);
X		}
X	} else {
X		/*
X		 *		It's a macro. Save a copy of the macro parameters into
X		 *		$0 to $9, and then execute each line of the macro definition,
X		 *		expanding parameter usages as necessary.
X		 */
X		curmac = macros[macronum];
X		length = 0;
X		if (nestlevel >= MAXNEST) {
X			scripterror("macros can only be nested ");
X			print2(MAXNEST, " deep\n");
X			Cleanup(10);
X		}
X		par = SafeAllocMem(PARAMSIZE + comlen + 1);
X		params[nestlevel++] = par;
X		par->size = comlen + 1;
X
X		/*
X		 *		Now initialise parameters $0 to $9
X		 */
X		ps[0] = par->params;
X		for (i = 1; i < 10; i++) {
X			if (compos < comlen)
X				/* Save parameter for later */
X				ps[i] = par->params + (getstring() - combuf);
X			else
X				ps[i] = NULL;
X		}
X		memcpy(par->params, combuf, comlen+1);
X
X		/*
X		 *		Now recursively call execline() with command buffer setup
X		 *		to be the current macro buffer line.
X		 */
X		length = 0;
X		s = curmac->text;
X		for (length = 0; length < curmac->size;
X						length += strlen(s) + 1, s = curmac->text + length) {
X			/* Copy macro line into buffer, doing expansion */
X			comlen = 0;
X			for (p = s; *p; p++) {
X				if (*p == '$') {
X					p++;
X					if (!*p) {
X						scripterror("'$' must be followed by something\n");
X						Cleanup(10);
X					}
X					if (isdigit(*p)) {
X						int num = *p - '0';
X
X						if (ps[num]) {
X							/* Copy macro definition */
X							if ((strlen(ps[num]) + comlen) >= MAXCOM - 10)
X								goto endcommand;	/* Eek! my first C Goto! */
X							strcpy(combuf+comlen, ps[num]);
X							comlen += strlen(ps[num]);
X						}
X						continue;
X
X					}
X				}
X				combuf[comlen++] = *p;
X			}
Xendcommand:
X			combuf[comlen] = CHAR_NULL;
X			compos = 0;
X			execline();
X		}
X		/*
X		 *		Now free memory allocated for macro parameters
X		 */
X		FreeMem(par, PARAMSIZE + par->size);
X		nestlevel--;
X	}
X}
X
X
X/*
X *		execscript()
X *		------------
X *		This function goes through all the commands in the script file,
X *		executing them one by one. Each command is parsed, and then
X *		the appropriate function called. Any errors that occur cause the
X *		script to be aborted, so if this call returns, the script was
X *		successfully executed.
X */
Xvoid execscript()
X{
X	while (readcommand(combuf, MAXCOM))
X		execline();
X}
X
X/*
X *		====> Now the actual commands follow <====
X */
X
X/*
X *		com_append()
X *		------------
X *		This file opens the specified file for appending. If there was
X *		already a file open, it is closed.
X */
X
Xvoid com_append()
X{
X	char *filename = getstring();
X	BPTR lock;
X
X	com_close();
X
X	if (lock = Lock(filename, ACCESS_READ)) {
X		UnLock(lock);
X		outfile = Open(filename, MODE_OLDFILE);
X	} else
X		outfile = Open(filename, MODE_NEWFILE);
X
X	if (!outfile) {
X		scripterror("error appending to file ");
X		print2(filename, "\n");
X		Cleanup(10);
X	}
X	Seek(outfile, 0, OFFSET_END);
X	toscreen = IsInteractive(outfile);
X}
X
X/*
X *		com_open()
X *		----------
X *		This command opens a file for output. The output of the ECHO,
X *		FOREIGN and LIST commands goes to this file. If an output file
X *		was already open, it is closed first.
X */
Xvoid com_open()
X{
X	char *filename = getstring();
X
X	com_close();
X	outfile = Open(filename, MODE_NEWFILE);
X	if (!outfile) {
X		scripterror("error opening file ");
X		print2(filename, "\n");
X		Cleanup(10);
X	}
X	toscreen = IsInteractive(outfile);
X}
X
X/*
X *		com_close()
X *		-----------
X *		Closes the current output file, if any. All future output goes to
X *		stdout.
X */
Xvoid com_close()
X{
X	if (outfile != Output()) {
X		flushout();
X		Close(outfile);
X		outfile = Output();
X		toscreen = IsInteractive(outfile);
X	}
X}
X
X/*
X *		com_config()
X *		------------
X *		This command sets the name of the configuration file used by
X *		CHECKFILES to get the names of the directories to scan from.
X */
Xvoid com_config()
X{
X	strcpy(configname, getstring());
X}
X
X/*
X *		com_database()
X *		--------------
X *		This command sets the name of the database file read in
X *		which contains the details of all the file headers.
X */
Xvoid com_database()
X{
X	strcpy(databasename, getstring());
X}
X
X/*
X *		com_norequest()
X *		---------------
X *		This command stops AmigaDos from putting up requesters when an
X *		error occurs, or a volume isn't mounted. 
X */
Xvoid com_norequest()
X{
X	struct Process *me = (struct Process *)FindTask(0L);
X	me->pr_WindowPtr = (APTR)-1L;
X}
X
X/*
X *		com_echo()
X *		----------
X *		This command echoes the specified string to the output file.
X *		A number of meta characters may be present in the string to be
X *		output. See the documentation for more info. If any other parameter
X *		is present on the line after the string, no newline is added
X *		to the output, otherwise a newline is added automatically.
X *
X */
Xvoid com_echo()
X{
X	strcpy(line, getstring());
X	if (compos >= comlen) 	/* Add NL if no second parameter */
X		strcat(line, "\n");
X	putstring(echoformat(out, MAXOUT, line));
X}
X
X/*
X *		com_exec()
X *		----------
X *		This command executes the indicated AmigaDOS command. The output
X *		from the command goes into the current output file, if any.
X */
Xvoid com_exec()
X{
X	flushout();
X	Execute(getstring(), NULL, outfile);
X}
X
X/*
X *		com_format()
X *		------------
X *		This command sets up the format string used by LIST and FOREIGN.
X *		For details of what the format may contain, see the documentation.
X */
Xvoid com_format()
X{
X	strcpy(formatstring, getstring());
X	if (compos >= comlen)				/* No second parameter */
X		strcat(formatstring, "\n");
X}
X
X/*
X *		com_list()
X *		----------
X *		This command is the biggy! It scans through the entire file
X *		database, selecting records as specified with SELECT, and printing
X *		them using the order specified with FORMAT. The order of printing
X *		is as specified with SORT.
X */
Xvoid com_list()
X{
X	int i;
X
X	CHECKDATABASE();
X	curbytes = 0;
X	curfiles = 0;
X    for (i = 0; i < numrecs; i++) {
X        if (ptrblock[i]->type == 0 && match(ptrblock[i], tree)) {
X            format(out, MAXOUT, formatstring, ptrblock[i], checkfiles);
X			putstring(out);
X			curfiles++;
X			curbytes += ptrblock[i]->length;
X		}
X		chkabort();
X	}
X	totalbytes += curbytes;
X	totalfiles += curfiles;
X}
X
X/*
X *		com_msg()
X *		---------
X *		This command is identical to the ECHO command, except that
X *		the output goes to the screen (i.e. stderr) instead of the
X *		current output file. It is intended for printing informational
X *		messages to let the user know of the program's progress, while
X *		processing a large script.
X */
Xvoid com_msg()
X{
X	strcpy(line, getstring());
X	if (compos >= comlen) 	/* Add NL if no second parameter */
X		strcat(line, "\n");
X	print(echoformat(out, MAXOUT, line));
X}
X
X/*
X *		com_reset()
X *		-----------
X *		This command resets the running totals which are maintained, 
X *		which give the total number of bytes and total number of files
X *		output so far.
X */
Xvoid com_reset()
X{
X	totalfiles = 0;
X	totalbytes = 0;
X}
X
X/*
X *		com_scan()
X *		----------
X *		This command is identical to com_list(), except that no output is
X *		produced. It is provided to allow counters to be updated, without
X *		producing output, so that for example, the total number of files
X *		in a listing can be output before the files themselves.
X */
X
Xvoid com_scan()
X{
X	int i;
X
X	CHECKDATABASE();
X	curbytes = 0;
X	curfiles = 0;
X    for (i = 0; i < numrecs; i++) {
X        if (ptrblock[i]->type == 0 && match(ptrblock[i], tree)) {
X			curfiles++;
X			curbytes += ptrblock[i]->length;
X		}
X		chkabort();
X	}
X	totalbytes += curbytes;
X	totalfiles += curfiles;
X}
X
X/*
X *		com_macro()
X *		-----------
X *		This command lets you define a macro. The syntax is:
X *
X *			MACRO name
X *				  ..
X *				commands
X *				  ..
X *			ENDM
X *
X *		The macro can contain $1 to $9, which will be substituted at
X *		run time by the appropriate parameters that were passed when
X *		the macro was invoked.
X */
Xvoid com_macro()
X{
X	static char macroname[MACROLEN];
X	int curline, curpos;	/* Saves current line # and position in script */
X	int length;				/* Length of macro script					   */
X	int macronum;
X	MACRO *curmac;
X	char *buf, *p;
X
X	if (nummacros >= MAXMACRO) {
X		scripterror("maximum of ");
X		print2(itoa(MAXMACRO), " macros allowed\n");
X		Cleanup(10);
X	}
X
X	curline = linenum;
X	curpos = scriptpos;
X	
X	p = getstring();
X	strncpy(macroname, p, MACROLEN-1);
X	macroname[MACROLEN-1] = CHAR_NULL;
X
X	/*
X	 *		First of all, find out how much space the macro occupies
X	 */
X	length = 0;
X	while (readcommand(combuf, MAXCOM) && strcmp(combuf, "ENDM"))
X		length += comlen + 1;	/* The extra 1 is for the terminating \0 */
X
X	/*
X	 *		Now, restore linenumber and script position so we can read
X	 *		in macro for real the second time.
X	 */
X	linenum = curline;
X	if (scriptpos >= scriptsize) {
X		scripterror("missing ENDM in macro definition\n");
X		Cleanup(10);
X	}
X
X	scriptpos = curpos;
X
X	if ((macronum = findmacro(macroname)) == -1) 		/* New macro */
X		macronum = nummacros++;
X	else
X		/* Macro redefinition, so release earlier definition */
X		FreeMem(macros[macronum], macros[macronum]->size + MACROSIZE);
X
X	curmac = SafeAllocMem(MACROSIZE + length);
X	macros[macronum] = curmac;
X	strcpy(curmac->name, macroname);
X	curmac->size = length;
X	buf = curmac->text;
X
X	/*
X	 *		Initialised macro definition, now copy macro text from script
X	 *		file into macro buffer.
X	 */
X	while (readcommand(combuf, MAXCOM) && strcmp("ENDM", combuf)) {
X		strcpy(buf, combuf);
X		buf += comlen + 1;
X	}
X}
X
X/*
X *		com_trace()
X *		-----------
X *		Turns on or off trace mode. When trace mode is on, every line that
X *		is executed is displayed first. This is handy when executing
X *		macros, to see exactly what is happening. Note that the -t option
X *		on the command line will enable tracing for the whole file,
X *		but if TRACE ON or OFF is encountered, this overrides -t.
X */
Xvoid com_trace()
X{
X	char *opt = getstring();
X
X	if (!strcmp(opt, "ON"))
X		tracemode = TRUE;
X	else if (!strcmp(opt, "OFF"))
X		tracemode = FALSE;
X	else {
X		scripterror("TRACE ON or TRACE OFF expected\n");
X		Cleanup(10);
X	}
X}
X
X/*
X *		com_ignore()
X *		------------
X *		This command takes a list of filenames. Each filename listed here
X *		is remembered, and when CHECKFILES is done, any filename listed
X *		here will be marked as valid, regardless of whether it really IS
X *		valid or not. This is useful if you have some files online which
X *		are updated by external programs (such as, for example, the output
X *		from BBSINFO), and hence have "wandering" file sizes.
X */
Xvoid com_ignore()
X{
X	IGNORE *ig;
X	char *p;
X
X	while (compos < comlen) {
X		ig = mymalloc(sizeof(IGNORE));
X		p = getstring();
X		if (strlen(p) >= CAT_LEN) {
X			scripterror("filename too long.\n");
X			Cleanup(10);
X		}
X		strcpy(ig->name, p);
X		ig->next = firstignore;
X		firstignore = ig;
X	}
X}
END_OF_FILE
if test 22726 -ne `wc -c <'src/command.c'`; then
    echo shar: \"'src/command.c'\" unpacked with wrong size!
fi
# end of 'src/command.c'
fi
if test -f 'src/format.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'src/format.c'\"
else
echo shar: Extracting \"'src/format.c'\" \(18069 characters\)
sed "s/^X//" >'src/format.c' <<'END_OF_FILE'
X/*
X *	FORMAT.C
X *
X *	This is a general purpose format routine for BBSINDEX. It takes
X *	as parameters a format string and a pointer to a file header and creates
X *	a string, based on the format string, but containing information from
X *	the file header.
X *
X *  The format string is similar to a printf format string. The following
X *  special sequences are translated into values associated with the
X *	fileheader.
X *
X *		%a		Number of accesses
X *		%b{}	File type {Binary,Text}
X *		%c		Comment
X *		%d		Disk filename
X *		%f		Full name of disk file
X *		%i{}	File status {Online,Offline}
X *		%k		File size in K
X *		%l{}	File origin {Local,Remote}
X *		%n		Filename
X *		%o		Uploader's name (owner)
X *		%p		Path name of disk file
X *		%r		Directory number
X *		%s		Section number or letter
X *		%ux		Output x n times, where n is length of last {sub}format cmd
X *		%v{}	File contents {Valid,Invalid}
X *		%w		Upload date (when)
X *		%x		File size
X *		%y		Disk directory number
X *		%{...}	Format substring in { }
X *	
X *	The following escape sequences are also recognised:
X *	
X *		\n		- End of line
X *		\e		- Escape
X *		\E		- CSI (0x9b)
X *		\t		- Tab
X *		\nnn	- Octal number with the appropriate value
X *	
X *	Any other characters after a \ are treated as normal. This can be used
X *	to escape certain characters that have special meaning, such as \ itself,
X *	%, and sometimes {, }, and comma.
X *	
X *	With the % flags, additional characters may appear between the % and the
X *	value. Using %c as an example, %20c means make the comment 20 characters
X *	wide, padding with spaces as necessary. The comment is aligned to the
X *	left. %-20c is similar, but aligns the comment to the right. This can
X *	be useful for padding. Items followed by {} are boolean flags, which
X *	can be either true or false. Inside the {} are two items, seperated by
X *	a comma. If the flag is true, the first value is used, else the second
X *	value. The expressions inside {} may not include %.
X *
X *	The exception to this is %m, which can contain a complete format
X *	string within the {}. In this case, the substring is formatted as
X *	normal, and then the resulting string is formatted according to
X *	the parameters between the % and m. For example %10m{(%s,%d)} formats
X *	the section and directory number in brackets, while ensuring that
X *	the total field width is 10 characters.
X *
X *	n.b. A file is said to be valid if its actual filesize is equivalent
X *  to the filesize in the file catalogue.
X *
X */
X
X#ifndef LATTICE_50
X#include "system.h"
X#endif
X
X#include "bbsindex.h"
X
X#define LEFT	0		/* Left alignment				*/
X#define RIGHT	1		/* Right alignment				*/
X#define MAXPATH	256		/* Maximum length of disk path	*/
X
X#define MIN(a,b) ((a) > (b) ? (b) : (a))
X
X#define	LEFTBR	'{'		/* Left bracket					*/
X#define RIGHTBR	'}'		/* Right bracket				*/
X#define BACKSLASH '\\'	/* Backslash					*/
X
Xstatic int len;			/* Length of format option			*/
Xstatic int align;		/* Text alignment - LEFT or RIGHT	*/
Xstatic int opos;		/* Position in output string		*/
Xstatic int omaxlen;		/* Maximum length of output string	*/
X
Xstatic char buf[256];
X
Xstatic char *sectnums[] = {
X	"0", "1", "2", "3", "4", "5", "6", "7",
X	"8", "9", "A", "B", "C", "D", "E", "F"
X};
X
Xstatic char *days[] = {
X	"Sunday",	"Monday",	"Tuesday",	"Wednesday",
X	"Thursday",	"Friday",	"Saturday"
X};
X
X/*
X *		doescape()
X *		----------
X *		This function parses the escape sequence passed as a string,
X *		and stores the character it corresponds to in the output character.
X *		it returns the number of characters taken up by the escape sequence.
X */
Xint doescape(out, seq)
Xunsigned char *out;
Xunsigned char *seq;
X{
X	int num = 0;
X	char *p;
X
X	if (*seq >= '0' && *seq <= '7') {
X		p = seq;
X		for (p = seq; *p >= '0' && *p <= '7'; p++)
X			num = (num * 8) + (*seq - '0');
X		*out = num;
X		return (p-seq);
X	}
X	switch (*seq) {
X
X		case 't':
X			*out = '\t';
X			break;
X
X		case 'n':
X			*out = '\n';
X			break;
X
X		case 'e':
X			*out = '\033';
X			break;
X
X		case 'E':
X			*out = '\233';
X			break;
X
X		default:
X			*out = *seq;
X			break;
X	}
X	return (1);
X}
X
X
X/*
X *		makedate()
X *		----------
X *		This function takes the passed day, month and year and returns
X *		a pointer to a string containing dd-mmm-yy (e.g. 23-Mar-89).
X */
X
X#define ITOA(c1,c2,x) ((c1) = ((x)/10 + '0'),(c2) = (((x) % 10) + '0'))
X
Xchar *makedate(day, month, year)
Xint day, month, year;
X{
X	static char buf[10];
X
X	ITOA(buf[0], buf[1], day);
X	buf[3] = months[month][0];
X	buf[4] = months[month][1];
X	buf[5] = months[month][2];
X	ITOA(buf[7], buf[8], year);
X	buf[2] = '-';
X	buf[6] = '-';
X	buf[9] = CHAR_NULL;
X	return (buf);
X}
X
X/*
X *		maketime()
X *		----------
X *		Returns a pointer to a string containing the specified time, in
X *		the form HH:MM:SS.
X */
Xchar *maketime(secs, mins, hours)
Xint secs, mins, hours;
X{
X	static char buf[9];
X	ITOA(buf[0], buf[1], hours);
X	ITOA(buf[3], buf[4], mins);
X	ITOA(buf[6], buf[7], secs);
X	buf[2] = ':';
X	buf[5] = ':';
X	buf[8] = CHAR_NULL;
X	return (buf);
X}
X
X/*
X *		itoa()
X *		------
X *		This function returns a pointer to a string containing the ascii
X *		representation of the number. The pointer is valid until the next
X *		time itoa() is called. Negative numbers are not handled.
X */
Xchar *itoa(n)
Xint n;
X{
X	static char buf[20];
X	int i = 18;
X
X	buf[19] = CHAR_NULL;
X	if (n == 0) {
X		buf[18] = '0';
X		return (buf + 18);
X	} else {
X		for ( ; n && (i > 0); i--) {
X			buf[i] = (n % 10) + '0';
X			n = n / 10;
X		}
X	}
X	return (buf+i+1);
X}
X
X
X/*
X *		addstring()
X *		-----------
X *		This functions adds string 's' to string 'out' starting at position
X *		(global) opos. If (global) len == 0, then no special formatting is
X *		done. Else, the string is formatted in a field of width 'len'
X *		characters, truncating or padding with spaces as appropriate. If
X *		(global) align == LEFT, then the string is aligned to the left
X *		of the field, else to the right.
X *
X *		If opos exceeds omaxlen at any point, then no more copying is done.
X */
X
Xvoid addstring(out, s)
Xchar *out;
Xchar *s;
X{
X	int oleft = omaxlen - opos;	/* Number of chars left in output string */
X	int slen = strlen(s);
X
X	/* Note: Be VERY careful the following hasn't got changed into tabs! */
X	static char pad[] = "\
X                                                                          \
X                                                                          \
X                                                                          ";
X	/* End of space definition! There should be ~200 spaces */
X
X	if (len == 0) {				/* No special alignment needed */
X		if (oleft > 0)
X			strncpy(out+opos,s,MIN(slen,oleft));
X		opos = opos + slen;
X	} else {					/* Do special alignment */
X		if (slen > len)
X			strncpy(out+opos, s, len);
X		else {
X			if (align == LEFT) {
X				if (oleft > 0)
X					/* Copy string into left of field */
X					strncpy(out+opos, s, MIN(slen,oleft));
X				oleft = oleft - slen;
X				if (oleft > 0 && len > slen)
X					/* Copy padding in to rest of field */
X					strncpy(out+opos+slen, pad, MIN(len-slen, oleft));
X			} else {	/* Align == RIGHT */
X				if (len > slen)
X					/* Copy padding into left of field */
X					strncpy(out+opos, pad, MIN(oleft, len-slen));
X				if (oleft > 0)
X					/* Copy string into right of field */
X					strncpy(out+opos+(len-slen), s, MIN(slen,oleft-slen));
X			}
X		}
X		opos = opos + len;
X	}
X}
X
X/*
X *		addnumber()
X *		-----------
X *		This macro adds the ascii representation of the number n
X *		to the output buffer 'out', starting at position opos (global).
X *		See addstring() for more details.
X */
X
X#define addnumber(out,n) addstring((out),itoa(n))
X
X/*
X *		format()
X *		--------
X *		This function converts the format string f and the file header into
X *		an output string. See above for valid options in the format string.
X *		A pointer to the output string is returned. Maxlen is the maximum
X *		length of the output string. Checkfiles is a boolean which is true
X *		if the dirnum, online and valid fields in the file header are valid.
X *		These are normally NOT valid, unless requested by the user.
X *
X *		The external variable dirnames is expected to exist. Dirnames is
X *		an array of disk dirctories (NOT BBS file directories).
X */
X
Xchar *format(out, maxlen, f, fhead, checkfiles)
Xchar *out;
Xint maxlen;
Xchar *f;
XUDHEAD *fhead;
Xint checkfiles;
X{
X	char subformat[MAXSUB];
X	static int lastlen;			/* Length of last {sub}format string	*/
X	int fpos = 0;				/* Position in format string			*/
X	int flen = strlen(f);
X	int bool, numchars;
X	char *p, *s, *rightbr;
X
X	omaxlen = maxlen-1;	/* Make this global for convenience */
X	opos = 0;
X
X	for (fpos = 0; fpos < flen && opos < maxlen; fpos++) {
X		if (f[fpos] == BACKSLASH) {
X			fpos += doescape(out+opos, f+fpos+1);
X			opos++;
X		} else if (f[fpos] != '%')
X			out[opos++] = f[fpos];
X		else {
X			align = LEFT;
X			if (f[++fpos] == '-') {
X				align = RIGHT;
X				fpos++;
X			}
X			len = 0;
X			while (isdigit(f[fpos]))
X				len = (len * 10) + f[fpos++] - '0';
X
X			switch (f[fpos]) {
X
X				case 'a':			/* Number of file accesses */
X					addnumber(out,fhead->accesses);
X					break;
X
X				case 'c':			/* File comment	*/
X					addstring(out,fhead->desc);
X					break;
X
X				case 'd':
X					addstring(out,fhead->disk_name);
X					break;
X
X				case 'f':
X					if (checkfiles) {
X						*buf = CHAR_NULL;
X						if (fhead->online) {
X							char ch;
X							strcat(buf, dirnames[fhead->dirnum]);
X							ch = buf[strlen(buf)-1];
X							if (ch != '/' && ch != ':')
X								strcat(buf, "/");
X						}
X						strcat(buf,fhead->disk_name);
X						addstring(out,buf);
X					} else
X						addstring(out,fhead->disk_name); /* Maintain formatting */
X					break;
X
X				/*
X				 *		Note that all the boolean-related options are grouped
X				 *		together, so that they can have common error handling.
X				 *		A bit nasty perhaps, but it works :-)
X				 *
X				 *		Note that 'i' and 'v' default to being online and
X				 *		valid respectively, if checkfiles hasn't been
X				 *		selected.
X				 */
X
X				case 'b':		/* True if file is binary			*/
X				case 'i':		/* True if file is online			*/
X				case 'l':		/* True if file uploaded locally	*/
X				case 'v':		/* True if file is valid			*/
X
X					switch (f[fpos]) {
X						case 'b':	bool = fhead->bin;
X									break;
X						case 'i':	bool = checkfiles ? fhead->online : 0; 
X									break;
X						case 'l':	bool = fhead->local;
X									break;
X						case 'v':	bool = checkfiles ? fhead->valid : 0;
X									break;
X					}
X					/* Make sure { and } surround arguments */
X					rightbr = strchr(f+fpos, RIGHTBR);
X					if (f[fpos+1] == LEFTBR && rightbr != NULL) {
X						p = f + (fpos+2);
X						s = buf;
X
X						if (bool) {		/* Use first argument */
X							/* Copy until comma reached */
X							/* Handle any escaped characters (\n, \t etc) */
X							while (*p && *p != ',' && *p != RIGHTBR) {
X								if (*p == BACKSLASH) {
X									p = p + doescape(s++, p+1) + 1;
X								} else
X									*s++ = *p++;
X							}
X						} else {
X							/* As above, but copy second argument */
X							p = strchr(p,',');
X							if (p) {
X								p++;
X								while (*p && *p != RIGHTBR) {
X									if (*p == BACKSLASH) {
X										p = p + doescape(s++, p+1) + 1;
X									} else
X										*s++ = *p++;
X								}
X							}
X						}
X						*s = CHAR_NULL;
X						addstring(out,buf);
X						/* Skip over braces */
X						fpos = (rightbr - f);
X					}
X					break;
X
X				case 'k':					/* File size in K */
X					addnumber(out, BTOK(fhead->length));
X					break;
X
X				case 'n':					/* Catalogue file name of file */
X					addstring(out, fhead->cat_name);
X					break;
X
X				case 'o':					/* Owner of upload */
X					addstring(out, fhead->owner);
X					break;
X
X				case 'p':					/* Path name to disk file */
X					if (checkfiles && fhead->online)
X						addstring(out, dirnames[fhead->dirnum]);
X					break;
X
X				case 'r':					/* Directory number */
X					addnumber(out, fhead->dir);
X					break;
X
X				case 's':					/* Section names */
X					addstring(out, sectnums[fhead->section]);
X					break;
X
X				case 'u':					/* Underline with next char	*/
X					fpos++;
X					numchars = lastlen + ((align == RIGHT) ? -len : len);
X					while (numchars > 0 && opos < omaxlen) {
X						out[opos++] = f[fpos];
X						numchars--;
X					}
X					break;
X
X				case 'w':					/* Date	*/
X					addstring(out,
X					  makedate( (fhead->date & 31),			/* Day		*/
X								((fhead->date>>5)) % 13,	/* Month	*/
X								((fhead->date>>5)) / 13));	/* Year		*/
X					break;
X
X				case 'x':
X					addnumber(out, fhead->length);
X					break;
X
X				case 'y':
X					addnumber(out, fhead->dirnum);
X					break;
X
X				case '{':					/* Format substring in { } */
X					{
X						int numbrackets = 0;
X						int savepos, savealign, savelen;
X						p = &f[fpos + 1];
X						/*
X						 *		Now, find the end of the substring. Nested
X						 *		brackets are skipped over.
X						 */
X						while (*p && !(*p == RIGHTBR && numbrackets == 0)) {
X							switch (*p) {
X								case LEFTBR:  numbrackets++; break;
X								case RIGHTBR: numbrackets--; break;
X							}
X							p++;
X						}
X						if (*p == RIGHTBR) {
X							/*
X							 *		To format the substring, we change the
X							 *		closing } into a NULL so that when
X							 *		we call format recursively, it will
X							 *		think that's the end of the string.
X							 *		After format()ing, we restore the
X							 *		bracket.
X							 */
X							savepos   = opos;
X							savelen   = len;
X							savealign = align;
X							*p = CHAR_NULL;
X							/* Format substring */
X							format(subformat, MAXSUB, &f[fpos + 1],
X														fhead, checkfiles);
X							*p = RIGHTBR;
X							opos  = savepos;
X							len   = savelen;
X							align = savealign;
X							addstring(out, subformat);
X							fpos = p - f;
X						}
X					}
X					break;
X
X				default:
X					len = 0;
X					buf[0] = '%';
X					buf[1] = f[fpos];
X					buf[2] = CHAR_NULL;
X					addstring(out, buf);
X			}
X		}
X	}
X	out[opos] = CHAR_NULL;
X	lastlen = opos;
X	return (out);
X}
X
X/*
X *		echoformat()
X *		------------
X *		This function takes a format string (as passed to the ECHO command)
X *		and uses it to produce an output string. The format string can
X *		contain the same escape sequences list under format(), and also
X *		the following special sequences:
X *
X *		  %b	- Number of bytes occupied by files in last LIST command
X *		  %k	- Number of kilobytes occupied by files in last LIST command
X *		  %m	- Number of megabytes occupied by files in last LIST command
X *		  %n	- Number of files in last LIST command
X *
X *		%B, %K, %M and %N are similar to the above except that they
X *		represent the running totals for all files listed since the
X *		last RESET command.
X *
X *		  %w	- The current date, in dd-mmm-yy format.
X *		  %d	- The current day (Monday, Tuesday etc.)
X *		  %t	- The current time (24 hour clock)
X *		  %u	- As for format()
X *		  %{..}	- As for format()
X */
Xchar *echoformat(out, maxlen, f)
Xchar *out;
Xint maxlen;
Xchar *f;
X{
X	static int lastlen;
X	char subformat[MAXSUB];
X	int fpos = 0;				/* Position in format string */
X	int flen = strlen(f);
X	int value;
X	int numchars;
X	struct tm *today;
X	long seconds;
X	char *p;
X
X	/*
X	 *		First of all, setup the time array
X	 */
X	time(&seconds);
X	today = localtime(&seconds);
X
X	/*
X	 *		Now format output string, according to format string
X	 */
X	omaxlen = maxlen-1;
X	opos = 0;
X
X	for (fpos = 0; fpos < flen && opos < maxlen; fpos++) {
X		if (f[fpos] == BACKSLASH) {
X			fpos += doescape(out+opos, f+fpos+1);
X			opos++;
X		} else if (f[fpos] != '%')
X			out[opos++] = f[fpos];
X		else {
X			align = LEFT;
X			if (f[++fpos] == '-') {
X				align = RIGHT;
X				fpos++;
X			}
X			len = 0;
X			while (isdigit(f[fpos]))
X				len = (len * 10) + f[fpos++] - '0';
X
X			value = -1;
X			switch (f[fpos]) {
X
X				case 'b': value = curbytes;					break;
X				case 'B': value = totalbytes;				break;
X				case 'k': value = BTOK(curbytes);			break;
X				case 'K': value = BTOK(totalbytes);			break;
X				case 'n': value = curfiles;					break;
X				case 'N': value = totalfiles;				break;
X				case 'm': value = BTOK(BTOK(curbytes));		break;
X				case 'M': value = BTOK(BTOK(totalbytes));	break;
X
X				case 'd':		/* Today's day */
X					addstring(out, days[today->tm_wday]);
X					break;
X				case 'w':		/* Today's date */
X					addstring(out, makedate(
X						today->tm_mday,			/* Day		*/
X						today->tm_mon+1,		/* Month	*/
X						today->tm_year  ));		/* Year		*/
X					break;
X
X				case 't':		/* Today's time */
X					addstring(out,
X					  maketime(today->tm_sec, today->tm_min, today->tm_hour));
X					break;
X
X				case 'u':					/* Underline with next char	*/
X					fpos++;
X					numchars = lastlen + ((align == RIGHT) ? -len : len);
X					while (numchars > 0 && opos < omaxlen) {
X						out[opos++] = f[fpos];
X						numchars--;
X					}
X					break;
X
X				/*
X				 *		Note - I'm a little unhappy about including this code
X				 *		twice, but a nice simple way of generalising it for
X				 *		both format and echoformat doesn't spring to mind.
X				 */
X				case '{':					/* Format substring in { } */
X					{
X						int numbrackets = 0;
X						int savepos, savealign, savelen;
X						p = &f[fpos + 1];
X						/*
X						 *		Now, find the end of the substring. Nested
X						 *		brackets are skipped over.
X						 */
X						while (*p && !(*p == RIGHTBR && numbrackets == 0)) {
X							switch (*p) {
X								case LEFTBR:  numbrackets++; break;
X								case RIGHTBR: numbrackets--; break;
X							}
X							p++;
X						}
X						if (*p == RIGHTBR) {
X							/*
X							 *		To format the substring, we change the
X							 *		closing } into a NULL so that when
X							 *		we call format recursively, it will
X							 *		think that's the end of the string.
X							 *		After format()ing, we restore the
X							 *		bracket.
X							 */
X							savepos   = opos;
X							savelen   = len;
X							savealign = align;
X							*p = CHAR_NULL;
X							/* Format substring */
X							echoformat(subformat, MAXSUB, &f[fpos + 1]);
X							*p = RIGHTBR;
X							opos  = savepos;
X							len   = savelen;
X							align = savealign;
X							addstring(out, subformat);
X							fpos = p - f;
X						}
X					}
X					break;
X
X				default:
X					len = 0;
X					buf[0] = '%';
X					buf[1] = f[fpos];
X					buf[2] = CHAR_NULL;
X					addstring(out, buf);
X			}
X			if (value != -1)
X				addnumber(out, value);
X		}
X	}
X	out[opos] = CHAR_NULL;
X	lastlen = opos;
X	return (out);
X}
END_OF_FILE
if test 18069 -ne `wc -c <'src/format.c'`; then
    echo shar: \"'src/format.c'\" unpacked with wrong size!
fi
# end of 'src/format.c'
fi
echo shar: End of archive 2 \(of 3\).
cp /dev/null ark2isdone
MISSING=""
for I in 1 2 3 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 3 archives.
    rm -f ark[1-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0
-- 
Submissions to comp.sources.amiga and comp.binaries.amiga should be sent to:
	amiga@cs.odu.edu	
or	amiga@xanth.cs.odu.edu	( obsolescent mailers may need this address )
or	...!uunet!xanth!amiga	( very obsolescent mailers need this address )

Comments, questions, and suggestions s should be addressed to ``amiga-request''
(only use ``amiga'' for submissions) at the above addresses.