
/*
    cw - one-shot stream editor
    Version 1.1
    Copyright (C) 1988 Daniel Elbaum

    New in Version 1.1:

        -Fixed an output buffering problem wherein
         the i/o buffer got flushed twice (it's a
         long way to CON: ;{))

        -Fixed a case wherein a few bytes of memory
         were left unfreed when the target file
         couldn't be found.

        -Added a special-case replacement string arg,
         "\0", to specify a null replacement string.
         This feature avoids difficulties with the
         quoting/argument-parsing conventions of
         various command interpreters.
*/

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

#ifndef BUFSIZ
#define BUFSIZ  (512)
#endif
#define LSIZ    (258)
#ifndef MIN
#define MIN(x, y)   ((x)>(y)?(y):(x))
#endif

UBYTE iobuf[BUFSIZ];
int iosofar=0;
int nflag;

UBYTE *strchr();         /* defined in cwlib.asm */
#define ismeta(x)       (strchr("*?.^$", (x)))
#define plur(x)         ((x)==1?'\0':'s')       /* pluralizes a number  */


struct finfo {
        UBYTE *name;             /* file name                    */
        UBYTE *mp;               /* file buffer                  */
        struct FileHandle *fh;   /* file handle                  */
        ULONG n;                 /* bytes in file                */
        };

main(c, v)
UBYTE *v[];
{
        register n, usage=0;

        register UBYTE **vp;
        UBYTE sub[LSIZ], targ[LSIZ];
        UBYTE obu[BUFSIZ];

        if (c<4) usage++;
        else if (v[1][0]=='-')
                if (v[1][1]=='n'){
                        if (!v[1][2]) nflag=1;
                        else if (!(nflag=atoi(&(v[1][2])))) usage++;
                        v++;
                }
                else usage++;
        if (usage){
                puts("Usage: cw [-nN] target replacement files...");
                puts("N is the maximum number of substitutions to make");
                exit(-1);
        }
        prepstrings(v[1], v[2], targ, sub);     /* map metachars        */
        for (vp=&v[3]; *vp; ++vp){
                strcpy(obu, *vp);
                strcat(obu, ": ");
                fputs(obu, stdout);
                n=cw(targ, sub, *vp);
                if (n>=0){
                    sprintf(obu, "%d change%c", n, plur(n));
                    puts(obu);
                }
                else puts("error");
        }
        if (n<0) exit(-1);
        exit(0);
}

cw(ip, op, nam)
register UBYTE *ip, *op;
UBYTE *nam;
{
        register UBYTE *iop;
        register ULONG n;
        register struct FileHandle *fh;
        register ilen;
        register rpt;
        int olen;
        int nchanges=0;
        struct finfo f;

        olen=strlen(op);
        setmem(&f, sizeof(f), '\0');
        f.name=nam;

        if (fillbuf(&f)<0) {cleanup(&f); return(-1);}
        if (!(rpt=nflag)) rpt=-1;
        iop=f.mp;
        n=f.n;
        fh=f.fh;
        while (n){
                if (rpt&&(ilen=regcmp(ip, iop))){
                        if (bufout(fh, op, olen)){
                                puts("write error--FILE TRASHED");
                                return(-1);
                        }
                        iop+=ilen;
                        n-=ilen;
                        if (n<0) puts("DS error");
                        nchanges++;
                        if (rpt>0) if (nchanges>=rpt) rpt=0;
                }
                else {
                        if (bufout(fh, iop++, 1)) puts("write error");
                        --n;
                }
        }
        cleanup(&f);
        return(nchanges);
}

cleanup(sp)
struct finfo *sp;
{
        Write(sp->fh, iobuf, iosofar);  /* flush output buffer  */
        iosofar=0;
        if (sp->fh) Close(sp->fh);
        if (sp->mp) FreeMem(sp->mp, sp->n);
}

prepstrings(ip, op, ipbu, opbu)
UBYTE *ip, *op;
register UBYTE *opbu, *ipbu;
{
        register UBYTE *foop;
        register hat=0, cash=0;

        /* special case: op = "\0"  */
        if (op[0]=='\\'&&op[1]=='0'&&op[2]=='\0') op[0]='\0';
        /* special case: target=='^$'   */
        if ((ip[0]=='^')&&(ip[1]=='$')&&(ip[2]=='\0')){
                movmem(ipbu, "\n\n\0", 3);
                *opbu='\n'; opbu[1]='\0';
                if (strlen(op)){
                        strcat(opbu, op);
                        strcat(opbu, "\n");
                }
        }
        strcpy(ipbu, ip);
        escape(ipbu);
        /* ... and here's something for the commoners   */
        for (foop=ipbu; *foop; ++foop)
                if (*foop==0xDE) {++hat; *foop='\n';}
                else if (*foop==0xA4) {++cash; *foop='\n';}
        if (hat) *opbu++ = '\n';
        *opbu='\0';
        strcat(opbu, op);
        if (cash) strcat(opbu, "\n");
        return(0);
}

/*
        set high bit of non-escaped metacharacters
        and convert escape sequences
*/

escape(s)
register UBYTE *s;
{
        UBYTE bu[256];
        register UBYTE *op;
        register esc, i, c;

        op=s;
        for (esc=i=0; *s; ++s){
                if (!esc){
                        if (ismeta(*s)) bu[i++]=*s|0x80;
                        else if (*s=='\\') ++esc;
                        else bu[i++]=*s;
                        continue;
                }
                else{
                        switch(*s){
                                case 't': c='\t'; break;
                                case 'n': c='\n'; break;
                                case 'r': c='\r'; break;
                                case 'b': c='\b'; break;
                                case 'f': c='\f'; break;
                                default: c=*s;
                            }
                        bu[i++]=c;
                        esc=0;
                }
        }
        bu[i]='\0';
        strcpy(op, bu);
        return(0);
}

/*
        Hand-buffer output.
        Works for cnt==0; theoretically for cnt>BUFSIZ.
*/

bufout(fh, buf, cnt)
register struct FileHandle *fh;
register UBYTE *buf;
register cnt;
{
        register n=0, off;

        do{
                off=n;          /* keep track of #chars written */
                cnt-=n;         /* subtract #written from # to write    */
                n=MIN(cnt, BUFSIZ-iosofar);     /* chunk to put in iobu */
                movmem(buf+off, iobuf+iosofar, n);
                iosofar+=n;
                if (iosofar>=BUFSIZ){
                        if (Write(fh, iobuf, iosofar)<0) return(-1);
                        iosofar=0;
                }
        } while (cnt);
        return(0);
}

/*
        Allocate space for a whole-file buffer, fill it,
        then truncate the file and open for writing.
        Name passed in sp->name;
        File handle returned in sp->fh;
        Length of file returned in sp->n;
        Address of buffer returned in sp->mp;
*/

fillbuf(sp)
register struct finfo *sp;
{
        register fibsiz;
        struct FileLock *l;
        struct FileInfoBlock *fi;
        UBYTE *AllocMem();
        struct FileHandle *Open();
        struct FileLock *Lock();

        fibsiz=sizeof(struct FileInfoBlock);
        if (!(fi=(struct FileInfoBlock *)AllocMem(fibsiz, MEMF_PUBLIC)))
                return(-1);
        if (!(l=Lock(sp->name, ACCESS_WRITE))) {
                FreeMem(fi, fibsiz);
                return(-1);
                }
        if (!(Examine(l, fi))) {FreeMem(fi, fibsiz); UnLock(l); return(-1);}
        sp->n=fi->fib_Size;
        FreeMem(fi, fibsiz);
        UnLock(l);
        if (!(sp->mp=AllocMem(sp->n, MEMF_PUBLIC))) return(-1);
        if (!(sp->fh=Open(sp->name, MODE_OLDFILE))){
                FreeMem(sp->mp, sp->n);
                return(-1);
                }
        if (Read(sp->fh, sp->mp, sp->n)!=sp->n){
                Close(sp->fh);
                FreeMem(sp->mp, sp->n);
                return(-1);
                }
        Close(sp->fh);
        if (!(sp->fh=Open(sp->name, MODE_NEWFILE))) {
                FreeMem(sp->mp, sp->n);
                return(-1);
                }
        return(0);
}

regcmp(p1, p2)
register UBYTE *p1, *p2;
{
        register len;
        register out=0;

        for (len=0; p2[len]; ++p1, ++len){
                if (!(*p1)) break;
                if (!(*p1&0x80))
                        if (regchar(*p1, p2[len])) {len=0; break;}
                        else continue;
                switch(*p1&0xFF){
                        case 0xAA:      /* *    */
                                if (!*++p1) {++out; break;}
                                while (p2[len]&&regchar(*p1, p2[len]))
                                        ++len;
                                if (!p2[len]) ++out;
                                break;
                        case 0xBF:      /* ?    */
                                break;
                        case 0xAE:      /* .    */
                                if (p2[len]!=' ') break;
                        default:        /* AE may fall through  */
                                len=0; ++out;
                                break;
                }
                if (out) break;
        }
        if (*p1) len=0;
        return(len);
}

/*
        0 means c1 matches c2
*/

regchar(c1, c2)
register c1, c2;
{
        if (c1==c2) return(0);
        if ((c1=='\n'||c1=='\r')&&(c2=='\n'||c2=='\r')) return(0);
        if (!(c1&0x80)) return(1);
        switch(c1&0xFF){
                case 0xAA:      /* *--falls through    */
                case 0xBF:      /* ?    */
                        return(0);
                case 0xAE:      /* .--falls through    */
                        if (c2!=' ') return(0);
                default:
                        return(1);
        }
        /* NOTREACHED   */
}

UBYTE *
strchr(s, c)
UBYTE *s;
{
    if (s)
        while (*s)
            if (*s==c) return(s);
            else s++;
    return(NULL);
}


