blob: 2967e0037b8e8e7e7c362a5d406efae48fe01f99 [file] [log] [blame]
#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 err pr_err
#define ISPRINT(c) ((c) >= ' ')
#define ESC '\033'
#define LENGTH 66
#define LINEW 72
#define NUMW 5
#define MARGIN 10
#define DEFTAB 8
#define NFILES 10
#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
*/
#define getdate prgetdate
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");
}
}