Subversion Repositories planix.SVN

Rev

Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

#include <u.h>
#include <libc.h>
#include <bio.h>
#include <ctype.h>

/*
 *      PR command (print files in pages and columns, with headings)
 *      2+head+2+page[56]+5
 */

#define ISPRINT(c)      ((c) >= ' ')
#define ESC             '\033'
#define LENGTH          66
#define LINEW           72
#define NUMW            5
#define MARGIN          10
#define DEFTAB          8
#define NFILES          20
#define HEAD            "%12.12s %4.4s  %s Page %d\n\n\n", date+4, date+24, head, Page
#define TOLOWER(c)      (isupper(c) ? tolower(c) : c)   /* ouch! */
#define cerror(S)       fprint(2, "pr: %s", S)
#define STDINNAME()     nulls
#define TTY             "/dev/cons", 0
#define PROMPT()        fprint(2, "\a") /* BEL */
#define TABS(N,C)       if((N = intopt(argv, &C)) < 0) N = DEFTAB
#define ETABS           (Inpos % Etabn)
#define ITABS           (Itabn > 0 && Nspace > 1 && Nspace >= (nc = Itabn - Outpos % Itabn))
#define NSEPC           '\t'
#define EMPTY           14      /* length of " -- empty file" */

typedef struct  Fils    Fils;
typedef struct  Colp*   Colp;
typedef struct  Err     Err;

struct  Fils
{
        Biobuf* f_f;
        char*   f_name;
        long    f_nextc;
};
struct  Colp
{
        Rune*   c_ptr;
        Rune*   c_ptr0;
        long    c_lno;
};
struct  Err
{
        Err*    e_nextp;
        char*   e_mess;
};

int     Balance = 0;
Biobuf  bout;
Rune*   Bufend;
Rune*   Buffer = 0;
int     C = '\0';
Colp    Colpts;
int     Colw;
int     Dblspace = 1;
Err*    err = 0;
int     error = 0;
int     Etabc = '\t';
int     Etabn = 0;
Fils*   Files;
int     Formfeed = 0;
int     Fpage = 1;
char*   Head = 0;
int     Inpos;
int     Itabc = '\t';
int     Itabn = 0;
Err*    Lasterr = (Err*)&err;
int     Lcolpos;
int     Len = LENGTH;
int     Line;
int     Linew = 0;
long    Lnumb = 0;
int     Margin = MARGIN;
int     Multi = 0;
int     Ncols = 1;
int     Nfiles = 0;
int     Nsepc = NSEPC;
int     Nspace;
char    nulls[] = "";
int     Numw;
int     Offset = 0;
int     Outpos;
int     Padodd;
int     Page;
int     Pcolpos;
int     Plength;
int     Sepc = 0;

extern  int     atoix(char**);
extern  void    balance(int);
extern  void    die(char*);
extern  void    errprint(void);
extern  char*   ffiler(char*);
extern  int     findopt(int, char**);
extern  int     get(int);
extern  void*   getspace(ulong);
extern  int     intopt(char**, int*);
extern  void    main(int, char**);
extern  Biobuf* mustopen(char*, Fils*);
extern  void    nexbuf(void);
extern  int     pr(char*);
extern  void    put(long);
extern  void    putpage(void);
extern  void    putspace(void);

/*
 * return date file was last modified
 */
char*
getdate(void)
{
        static char *now = 0;
        static Dir *sbuf;
        ulong mtime;

        if(Nfiles > 1 || Files->f_name == nulls) {
                if(now == 0) {
                        mtime = time(0);
                        now = ctime(mtime);
                }
                return now;
        }
        mtime = 0;
        sbuf = dirstat(Files->f_name);
        if(sbuf){
                mtime = sbuf->mtime;
                free(sbuf);
        }
        return ctime(mtime);
}

char*
ffiler(char *s)
{
        return smprint("can't open %s\n", s);
}

void
main(int argc, char *argv[])
{
        Fils fstr[NFILES];
        int nfdone = 0;

        Binit(&bout, 1, OWRITE);
        Files = fstr;
        for(argc = findopt(argc, argv); argc > 0; --argc, ++argv)
                if(Multi == 'm') {
                        if(Nfiles >= NFILES - 1)
                                die("too many files");
                        if(mustopen(*argv, &Files[Nfiles++]) == 0)
                                nfdone++; /* suppress printing */
                } else {
                        if(pr(*argv))
                                Bterm(Files->f_f);
                        nfdone++;
                }
        if(!nfdone)                     /* no files named, use stdin */
                pr(nulls);              /* on GCOS, use current file, if any */
        errprint();                     /* print accumulated error reports */
        exits(error? "error": 0);
}

int
findopt(int argc, char *argv[])
{
        char **eargv = argv;
        int eargc = 0, c;

        while(--argc > 0) {
                switch(c = **++argv) {
                case '-':
                        if((c = *++*argv) == '\0')
                                break;
                case '+':
                        do {
                                if(isdigit(c)) {
                                        --*argv;
                                        Ncols = atoix(argv);
                                } else
                                switch(c = TOLOWER(c)) {
                                case '+':
                                        if((Fpage = atoix(argv)) < 1)
                                                Fpage = 1;
                                        continue;
                                case 'd':
                                        Dblspace = 2;
                                        continue;
                                case 'e':
                                        TABS(Etabn, Etabc);
                                        continue;
                                case 'f':
                                        Formfeed++;
                                        continue;
                                case 'h':
                                        if(--argc > 0)
                                                Head = argv[1];
                                        continue;
                                case 'i':
                                        TABS(Itabn, Itabc);
                                        continue;
                                case 'l':
                                        Len = atoix(argv);
                                        continue;
                                case 'a':
                                case 'm':
                                        Multi = c;
                                        continue;
                                case 'o':
                                        Offset = atoix(argv);
                                        continue;
                                case 's':
                                        if((Sepc = (*argv)[1]) != '\0')
                                                ++*argv;
                                        else
                                                Sepc = '\t';
                                        continue;
                                case 't':
                                        Margin = 0;
                                        continue;
                                case 'w':
                                        Linew = atoix(argv);
                                        continue;
                                case 'n':
                                        Lnumb++;
                                        if((Numw = intopt(argv, &Nsepc)) <= 0)
                                                Numw = NUMW;
                                case 'b':
                                        Balance = 1;
                                        continue;
                                case 'p':
                                        Padodd = 1;
                                        continue;
                                default:
                                        die("bad option");
                                }
                        } while((c = *++*argv) != '\0');
                        if(Head == argv[1])
                                argv++;
                        continue;
                }
                *eargv++ = *argv;
                eargc++;
        }
        if(Len == 0)
                Len = LENGTH;
        if(Len <= Margin)
                Margin = 0;
        Plength = Len - Margin/2;
        if(Multi == 'm')
                Ncols = eargc;
        switch(Ncols) {
        case 0:
                Ncols = 1;
        case 1:
                break;
        default:
                if(Etabn == 0)          /* respect explicit tab specification */
                        Etabn = DEFTAB;
        }
        if(Linew == 0)
                Linew = Ncols != 1 && Sepc == 0? LINEW: 512;
        if(Lnumb)
                Linew -= Multi == 'm'? Numw: Numw*Ncols;
        if((Colw = (Linew - Ncols + 1)/Ncols) < 1)
                die("width too small");
        if(Ncols != 1 && Multi == 0) {
                ulong buflen = ((ulong)(Plength/Dblspace + 1))*(Linew+1)*sizeof(char);
                Buffer = getspace(buflen*sizeof(*Buffer));
                Bufend = &Buffer[buflen];
                Colpts = getspace((Ncols+1)*sizeof(*Colpts));
        }
        return eargc;
}

int
intopt(char *argv[], int *optp)
{
        int c;

        if((c = (*argv)[1]) != '\0' && !isdigit(c)) {
                *optp = c;
                (*argv)++;
        }
        c = atoix(argv);
        return c != 0? c: -1;
}

int
pr(char *name)
{
        char *date = 0, *head = 0;

        if(Multi != 'm' && mustopen(name, &Files[0]) == 0)
                return 0;
        if(Buffer)
                Bungetc(Files->f_f);
        if(Lnumb)
                Lnumb = 1;
        for(Page = 0;; putpage()) {
                if(C == -1)
                        break;
                if(Buffer)
                        nexbuf();
                Inpos = 0;
                if(get(0) == -1)
                        break;
                Bflush(&bout);
                Page++;
                if(Page >= Fpage) {
                        if(Margin == 0)
                                continue;
                        if(date == 0)
                                date = getdate();
                        if(head == 0)
                                head = Head != 0 ? Head :
                                        Nfiles < 2? Files->f_name: nulls;
                        Bprint(&bout, "\n\n");
                        Nspace = Offset;
                        putspace();
                        Bprint(&bout, HEAD);
                }
        }
        if(Padodd && (Page&1) == 1) {
                Line = 0;
                if(Formfeed)
                        put('\f');
                else
                        while(Line < Len)
                                put('\n');
        }
        C = '\0';
        return 1;
}

void
putpage(void)
{
        int colno;

        for(Line = Margin/2;; get(0)) {
                for(Nspace = Offset, colno = 0, Outpos = 0; C != '\f';) {
                        if(Lnumb && C != -1 && (colno == 0 || Multi == 'a')) {
                                if(Page >= Fpage) {
                                        putspace();
                                        Bprint(&bout, "%*ld", Numw, Buffer?
                                                Colpts[colno].c_lno++: Lnumb);
                                        Outpos += Numw;
                                        put(Nsepc);
                                }
                                Lnumb++;
                        }
                        for(Lcolpos=0, Pcolpos=0; C!='\n' && C!='\f' && C!=-1; get(colno))
                                        put(C);
                        if(C==-1 || ++colno==Ncols || C=='\n' && get(colno)==-1)
                                break;
                        if(Sepc)
                                put(Sepc);
                        else
                                if((Nspace += Colw - Lcolpos + 1) < 1)
                                        Nspace = 1;
                }
        /*
                if(C == -1) {
                        if(Margin != 0)
                                break;
                        if(colno != 0)
                                put('\n');
                        return;
                }
        */
                if(C == -1 && colno == 0) {
                        if(Margin != 0)
                                break;
                        return;
                }
                if(C == '\f')
                        break;
                put('\n');
                if(Dblspace == 2 && Line < Plength)
                        put('\n');
                if(Line >= Plength)
                        break;
        }
        if(Formfeed)
                put('\f');
        else
                while(Line < Len)
                        put('\n');
}

void
nexbuf(void)
{
        Rune *s = Buffer;
        Colp p = Colpts;
        int j, c, bline = 0;

        for(;;) {
                p->c_ptr0 = p->c_ptr = s;
                if(p == &Colpts[Ncols])
                        return;
                (p++)->c_lno = Lnumb + bline;
                for(j = (Len - Margin)/Dblspace; --j >= 0; bline++)
                        for(Inpos = 0;;) {
                                if((c = Bgetrune(Files->f_f)) == -1) {
                                        for(*s = -1; p <= &Colpts[Ncols]; p++)
                                                p->c_ptr0 = p->c_ptr = s;
                                        if(Balance)
                                                balance(bline);
                                        return;
                                }
                                if(ISPRINT(c))
                                        Inpos++;
                                if(Inpos <= Colw || c == '\n') {
                                        *s = c;
                                        if(++s >= Bufend)
                                                die("page-buffer overflow");
                                }
                                if(c == '\n')
                                        break;
                                switch(c) {
                                case '\b':
                                        if(Inpos == 0)
                                                s--;
                                case ESC:
                                        if(Inpos > 0)
                                                Inpos--;
                                }
                        }
        }
}

/*
 * line balancing for last page
 */
void
balance(int bline)
{
        Rune *s = Buffer;
        Colp p = Colpts;
        int colno = 0, j, c, l;

        c = bline % Ncols;
        l = (bline + Ncols - 1)/Ncols;
        bline = 0;
        do {
                for(j = 0; j < l; ++j)
                        while(*s++ != '\n')
                                ;
                (++p)->c_lno = Lnumb + (bline += l);
                p->c_ptr0 = p->c_ptr = s;
                if(++colno == c)
                        l--;
        } while(colno < Ncols - 1);
}

int
get(int colno)
{
        static int peekc = 0;
        Colp p;
        Fils *q;
        long c;

        if(peekc) {
                peekc = 0;
                c = Etabc;
        } else
        if(Buffer) {
                p = &Colpts[colno];
                if(p->c_ptr >= (p+1)->c_ptr0)
                        c = -1;
                else
                        if((c = *p->c_ptr) != -1)
                                p->c_ptr++;
        } else
        if((c = (q = &Files[Multi == 'a'? 0: colno])->f_nextc) == -1) {
                for(q = &Files[Nfiles]; --q >= Files && q->f_nextc == -1;)
                        ;
                if(q >= Files)
                        c = '\n';
        } else
                q->f_nextc = Bgetrune(q->f_f);
        if(Etabn != 0 && c == Etabc) {
                Inpos++;
                peekc = ETABS;
                c = ' ';
        } else
        if(ISPRINT(c))
                Inpos++;
        else
                switch(c) {
                case '\b':
                case ESC:
                        if(Inpos > 0)
                                Inpos--;
                        break;
                case '\f':
                        if(Ncols == 1)
                                break;
                        c = '\n';
                case '\n':
                case '\r':
                        Inpos = 0;
                }
        return C = c;
}

void
put(long c)
{
        int move;

        switch(c) {
        case ' ':
                Nspace++;
                Lcolpos++;
                return;
        case '\b':
                if(Lcolpos == 0)
                        return;
                if(Nspace > 0) {
                        Nspace--;
                        Lcolpos--;
                        return;
                }
                if(Lcolpos > Pcolpos) {
                        Lcolpos--;
                        return;
                }
        case ESC:
                move = -1;
                break;
        case '\n':
                Line++;
        case '\r':
        case '\f':
                Pcolpos = 0;
                Lcolpos = 0;
                Nspace = 0;
                Outpos = 0;
        default:
                move = (ISPRINT(c) != 0);
        }
        if(Page < Fpage)
                return;
        if(Lcolpos > 0 || move > 0)
                Lcolpos += move;
        if(Lcolpos <= Colw) {
                putspace();
                Bputrune(&bout, c);
                Pcolpos = Lcolpos;
                Outpos += move;
        }
}

void
putspace(void)
{
        int nc;

        for(; Nspace > 0; Outpos += nc, Nspace -= nc)
                if(ITABS)
                        Bputc(&bout, Itabc);
                else {
                        nc = 1;
                        Bputc(&bout, ' ');
                }
}

int
atoix(char **p)
{
        int n = 0, c;

        while(isdigit(c = *++*p))
                n = 10*n + c - '0';
        (*p)--;
        return n;
}

/*
 * Defer message about failure to open file to prevent messing up
 * alignment of page with tear perforations or form markers.
 * Treat empty file as special case and report as diagnostic.
 */
Biobuf*
mustopen(char *s, Fils *f)
{
        char *tmp;

        if(*s == '\0') {
                f->f_name = STDINNAME();
                f->f_f = malloc(sizeof(Biobuf));
                if(f->f_f == 0)
                        cerror("no memory");
                Binit(f->f_f, 0, OREAD);
        } else
        if((f->f_f = Bopen(f->f_name = s, OREAD)) == 0) {
                tmp = ffiler(f->f_name);
                s = strcpy((char*)getspace(strlen(tmp) + 1), tmp);
                free(tmp);
        }
        if(f->f_f != 0) {
                if((f->f_nextc = Bgetrune(f->f_f)) >= 0 || Multi == 'm')
                        return f->f_f;
                sprint(s = (char*)getspace(strlen(f->f_name) + 1 + EMPTY),
                        "%s -- empty file\n", f->f_name);
                Bterm(f->f_f);
        }
        error = 1;
        cerror(s);
        fprint(2, "\n");
        return 0;
}

void*
getspace(ulong n)
{
        void *t;

        if((t = malloc(n)) == 0)
                die("out of space");
        return t;
}

void
die(char *s)
{
        error++;
        errprint();
        cerror(s);
        Bputc(&bout, '\n');
        exits("error");
}

/*
void
onintr(void)
{
        error++;
        errprint();
        exits("error");
}
/**/

/*
 * print accumulated error reports
 */
void
errprint(void)
{
        Bflush(&bout);
        for(; err != 0; err = err->e_nextp) {
                cerror(err->e_mess);
                fprint(2, "\n");
        }
}