/*
 * Concat - concatenate and print files.
 *          AmigaDOS equivalent of UNIX System V cat.
 *
 * Version 37.1 =TP= 28-Jan-92
 *
 * Compile with SAS/C 5.10a and link without startup code:
 *      lc -cqfist -v -b0 -rr -O -ms Concat
 *      blink Concat.o to Concat sd sc
 *      protect Concat +p
 *
 * Copyright (c) 1992 Torsten Poulin
 *
 * Torsten Poulin
 * Banebrinken 99, 2, lejlighed 77
 * DK 2400  København NV
 * DENMARK
 */

/****** English:CONCAT ****************************************************
*
*   FORMAT
*       CONCAT [[FROM] <files|patterns>] [TO <name>]
*              [SORT] [QUIET] [VISIBLE [TABS] [EOL]] [UNBUF]
*
*   TEMPLATE
*       FROM/M,TO/K,SORT/S,QUIET/S,VISIBLE/S,TABS/S,EOL/S,UNBUF/S
*
*   PURPOSE
*       To concatenate and print files.
*
*   SPECIFICATION
*       Concat reads each of the specified FROM files and writes it
*       to the destination specified by the TO option.  If no FROM
*       option is given Concat reads from its default input; likewise
*       if no TO option is specified it writes to its default output.
*
*       The following switches apply to Concat.
*
*       SORT    The FROM files are read in alphabetical order.
*               Useful when concatenating files split with Split.
*
*       QUIET   Concat is quiet about non-existent input files.
*
*       VISIBLE Causes non-printing characters (with the exception
*               of tabs and new-lines) to be printed visibly.
*               Control characters are printed as ^X (control-x);
*               the DEL character (hex 0x7d) is printed as ^?.
*               Non-ASCII characters (with the high bit set) are
*               printed as M-x, where x is the character specified
*               by the seven low order bits.
*
*       UNBUF   The output is not buffered. (The default is
*               buffered output.)
*
*       When used with the VISIBLE switch, the following switches
*       may be used.
*
*       TABS    Causes tabs to be printed as ^I's.
*
*       EOL     Causes a $ character to be printed at the end of
*               each line (prior to the new-line).
*
*       The TABS and EOL switches are ignored if the VISIBLE
*       switch is not specified.
*
*   SEE ALSO
*       COPY, JOIN, MORE, SPLIT, TYPE
*
***************************************************************************
*
*/
/****** dansk:CONCAT ******************************************************
*
*   FORMAT
*       CONCAT [[FROM] <filer|mønstre>] [TO <navn>]
*              [SORT] [QUIET] [VISIBLE [TABS] [EOL]] [UNBUF]
*
*   SKABELON
*       FROM/M,TO/K,SORT/S,QUIET/S,VISIBLE/S,TABS/S,EOL/S,UNBUF/S
*
*   FORMÅL
*       At sammenkæde og udskrive filer.
*
*   SPECIFIKATION
*       Concat læser hver af de med FROM angivne filer og skriver
*       dem til destinationen angivet af TO.  Hvis der ikke er
*       angivet nogen FROM-filer, læser Concat fra sit standard-
*       input.  Ligeledes skriver den til sit standardoutput hvis
*       der ikke er angivet en destination med TO.
*
*       Følgende kontakter kan bruges med Concat:
*
*       SORT    FROM-filerne læses i alfabetisk orden.  Nyttigt
*               ved sammenkædning af filer splittet med SPLIT.
*
*       QUIET   Concat tier stille om ikke-eksisterende filer.
*
*       VISIBLE Usynlige tegn (med undtagelse af tabulerings- og
*               ny-linjetegn) bliver udskrevet synligt.
*               Styretegn udskrives som ^X (kontrol-x). 
*               Slettetegnet (heksadecimalt 0x7d) udskrives som
*               ^?. Ikke-ASCII-tegn (med mest betydende bit sat)
*               udskrives som M-x, hvor x er tegnet givet ved de
*               syv mindst betydende bit.
*
*       UNBUF   Uddata bliver ikke bufferet. (Standard er bufferet
*               uddata.)
*
*       Når kontakten VISIBLE er angivet kan følgende kontakter
*       bruges:
*
*       TABS    Tabuleringstegn udskrives som ^I'er.
*
*       EOL     Et $-tegn udskrives i slutningen af hver linje (før
*               ny-linjetegnet).
*
*       Kontakterne TABS og EOL bliver ignoreret hvis kontakten
*       VISIBLE ikke er angivet.
*
*   SE OGSÅ
*       COPY, JOIN, MORE, SPLIT, TYPE
*
***************************************************************************
*
*/


#include <exec/types.h>
#include <exec/memory.h>
#include <dos/dos.h>
#include <dos/dosasl.h>
#include <clib/dos_protos.h>
#include <clib/exec_protos.h>
#include <clib/intuition_protos.h>
#include <stdio.h>
#include <string.h>

#ifdef __SASC
#include <pragmas/dos_pragmas.h>
#include <pragmas/exec_pragmas.h>
#include <pragmas/intuition_pragmas.h>
#pragma libcall UtilityBase Stricmp A2 9802
#endif

/* my include files are for early V36, so ... */
LONG Stricmp(char *, char *);

/* !!! Assumption: DOS used to be in BCPL, so max length is 256(?) */
#define MAXNAMELEN 256L

#define TEMPLATE "FROM/M,TO/K,SORT/S,QUIET/S,VISIBLE/S,TABS/S,EOL/S,UNBUF/S"
#define OPT_FROM    0
#define OPT_TO      1
#define OPT_SORT    2
#define OPT_QUIET   3
#define OPT_VISIBLE 4
#define OPT_TABS    5
#define OPT_EOL     6
#define OPT_UNBUF   7


static VOID myFPutC(struct DosLibrary *, BPTR, LONG, BOOL);
static LONG doConcat(struct Library *, struct DosLibrary *,
                     BPTR, BPTR, BOOL, BOOL, BOOL, BOOL);

typedef struct nameList {
  struct nameList *next;
  UBYTE *name;
} nameList;

char const *blurp = "\0$VER: Concat 37.1 (28.1.92) ©1992 Torsten Poulin";


LONG entrypoint(VOID)
{
    struct Library       *SysBase;
    struct DosBase       *DOSBase;
    struct IntuitionBase *IntuitionBase;
    struct Library       *UtilityBase;
    struct RDArgs        *args;
    struct AnchorPath    *ap;
    struct Remember      *rememberkey;
    nameList             *list, *p;
    LONG                 arg[8];
    LONG                 rc = RETURN_OK;
    UBYTE                **fromfiles;
    ULONG                i;
    BPTR                 in, out;
        
    SysBase = *(struct Library **) 4L;
    if(!(DOSBase = (struct DosLibrary *) OpenLibrary("dos.library", 37L)))
        goto noDOS;
    if(!(IntuitionBase = (struct IntuitionBase *)
                        OpenLibrary("intuition.library", 33L)))
        goto noIntuition;
    if(!(UtilityBase = OpenLibrary("utility.library", 37L)))
        goto noUtility;
    
    arg[OPT_FROM]    = arg[OPT_TO]   = arg[OPT_SORT] = arg[OPT_QUIET] =
    arg[OPT_VISIBLE] = arg[OPT_TABS] = arg[OPT_EOL]  = arg[OPT_UNBUF] = 0L;

    if(args = ReadArgs(TEMPLATE, arg, NULL))
    {
        out = NULL;
        if(arg[OPT_TO] && !(out = Open((UBYTE *) arg[OPT_TO], MODE_NEWFILE)))
        {
            LONG err = IoErr();
            PutStr("Concat can't open ");
            PrintFault(err, (UBYTE *) arg[OPT_TO]);
            rc = RETURN_ERROR;
            goto exitProgram;
        }

        if(arg[OPT_FROM])
        {
            rememberkey = NULL;
            list = NULL;

            fromfiles = (UBYTE **) arg[OPT_FROM];
            for(i = 0; fromfiles[i]; i++)
            {
                UBYTE *dummy;
                LONG  IsWild;
            
                if(!(ap = (struct AnchorPath *)
                        AllocRemember(&rememberkey,
                                      sizeof(struct AnchorPath) + MAXNAMELEN,
                                      MEMF_PUBLIC | MEMF_CLEAR)))
                {
                    PrintFault(ERROR_NO_FREE_STORE, "Concat");
                    rc = RETURN_FAIL;
                    goto exitProgram;
                }

                ap->ap_Strlen = MAXNAMELEN;

                /* Kludge to determine if it's pattern */
                if(!(dummy = AllocMem(2 * strlen(fromfiles[i]) + 2,
                                      MEMF_PUBLIC)))
                {
                    PrintFault(ERROR_NO_FREE_STORE, "Concat");
                    rc = RETURN_FAIL;
                    goto exitProgram;
                }
                IsWild = ParsePattern(fromfiles[i], dummy,
                                      2 * strlen(fromfiles[i]) + 2);
                FreeMem(dummy, 2 * strlen(fromfiles[i]) + 2);
                if(IsWild == -1)
                {
                    LONG err = IoErr();;
                    PrintFault(err, "Concat");
                    rc = RETURN_FAIL;
                    goto exitProgram;
                }
                
                if(!IsWild || MatchFirst(fromfiles[i], ap) == 0)
                    do
                    {
                        nameList *newnode, *p;
                        UBYTE    *insertname;
                    
/*
                        if(SetSignal(0L,0L) & SIGBREAKF_CTRL_C)
                        {
                            PrintFault(ERROR_BREAK, NULL);
                            rc = RETURN_WARN;
                            if(IsWild)
                                MatchEnd(ap);
                            goto exitProgram;
                        }
*/

                        if(IsWild)
                        {
                            insertname = ap->ap_Buf;
                            if(ap->ap_Info.fib_DirEntryType > 0)
                                continue;
                        }
                        else
                            insertname = fromfiles[i];

                        /* File names are stored in an ordered linked list
                         * unless arg[OPT_SORT] is FALSE in which case
                         * they are inserted at the end of the list.
                         */
                
                        if(!(newnode = (nameList *)
                                AllocRemember(&rememberkey, sizeof(nameList),
                                              MEMF_PUBLIC)))
                        {
                            PrintFault(ERROR_NO_FREE_STORE, "Concat");
                            rc = RETURN_FAIL;
                            goto noMemory1;
                        }
                        if(!(newnode->name = (UBYTE *)
                                AllocRemember(&rememberkey,
                                    strlen(insertname) + sizeof(UBYTE),
                                    MEMF_PUBLIC)))
                        {
                            PrintFault(ERROR_NO_FREE_STORE, "Concat");
                            rc = RETURN_FAIL;
                            goto noMemory1;
                        }
                        strcpy(newnode->name, insertname);
                        newnode->next = NULL;

                        if(list == NULL)    /* insert into empty list */
                            list = newnode;
                        else
                        {
                            /* inserting into nonempty list */
                            p = list;

                            if(Stricmp(insertname, p->name) < 0
                               && arg[OPT_SORT])
                            {
                                /* insert before first node */
	                        newnode->next = list;
	                        list = newnode;
                            }
                            else
                            {
                                /* general case */
	                        for(; p->next; p = p->next)
	                            if(Stricmp(insertname, p->next->name) < 0
	                               && arg[OPT_SORT])
	                                break;
	                        newnode->next = p->next;
	                        p->next = newnode;
                            }
                        }
                    } while(IsWild == 1 && MatchNext(ap) == 0);
 noMemory1:
                if(IsWild)
                    MatchEnd(ap);
            }

            /* Output by running through list of file names */
            for(p = list; p; p = p->next)
            {
                if(in = Open(p->name, MODE_OLDFILE))
                {
                    rc = doConcat(SysBase, DOSBase,
                                  in, arg[OPT_TO] ? out : Output(),
                                  arg[OPT_VISIBLE], arg[OPT_TABS],
                                  arg[OPT_EOL], arg[OPT_UNBUF]);
                    Close(in);
                    if(rc != 0)
                        break;
                }
                else
                {
                    LONG err = IoErr();
                    if(!arg[OPT_QUIET])
                    {
                        PutStr("Concat can't open ");
                        PrintFault(err, p->name);
                    }
                }
            }
            FreeRemember(&rememberkey, TRUE);
        }
        else
        {
            /* Reading from default input */
            rc = doConcat(SysBase, DOSBase,
                          Input(), arg[OPT_TO] ? out : Output(),
                          arg[OPT_VISIBLE], arg[OPT_TABS],
                          arg[OPT_EOL], arg[OPT_UNBUF]);
        }
 exitProgram:
        if(out)
            Close(out);
        FreeArgs(args);
    }
    else
    {
        LONG err = IoErr();
        PrintFault(err, "Concat");
        rc = RETURN_ERROR;
    }
                
    CloseLibrary(UtilityBase);
 noUtility:
    CloseLibrary((struct Library *) IntuitionBase);
 noIntuition:
    CloseLibrary((struct DosBase *) DOSBase);
 noDOS:
    return rc;

    /*
     * Yes, I KNOW this function is  t o o  l o n g
     * and contains far too many goto's  ;-)
     */
}



static LONG doConcat(struct Library *SysBase, struct DosLibrary *DOSBase,
                     BPTR in, BPTR out,
                     BOOL visible, BOOL tabs, BOOL eol, BOOL unbuf)
{
    register UBYTE breakcheck = 0;
    LONG c;
    
    while((c = FGetC(in)) != -1)
    {
        if(visible)
        {
            if(eol && c == '\n')
                myFPutC(DOSBase, out, '$', unbuf);
            else if(tabs && c == '\t')
            {
                myFPutC(DOSBase, out, '^', unbuf);
                c = 'I';
            }
            else if(c >= 0x80)
            {
                myFPutC(DOSBase, out, 'M', unbuf);
                myFPutC(DOSBase, out, '-', unbuf);
                c &= 0x7f;
            }
            if((c < ' ' && c != '\t' && c != '\n') || c == 0x7F)
            {
                myFPutC(DOSBase, out, '^', unbuf);
                if(c == 0x7F)
                    c = '?';
                else
                    c += 'A' - 1;
            }
        }
        myFPutC(DOSBase, out, c, unbuf);
        if(!(breakcheck -= 8) && SetSignal(0L,0L) & SIGBREAKF_CTRL_C)
        {
            PrintFault(ERROR_BREAK, NULL);
            return RETURN_WARN;
        }
    }
    return 0L;
}


static VOID myFPutC(struct DosLibrary *DOSBase, BPTR out, LONG c, BOOL nobuf)
{
    if(nobuf)
        Flush(out);
    FPutC(out, c);
}
