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.