pr.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663
  1. #include <u.h>
  2. #include <libc.h>
  3. #include <bio.h>
  4. #include <ctype.h>
  5. /*
  6. * PR command (print files in pages and columns, with headings)
  7. * 2+head+2+page[56]+5
  8. */
  9. #define ISPRINT(c) ((c) >= ' ')
  10. #define ESC '\033'
  11. #define LENGTH 66
  12. #define LINEW 72
  13. #define NUMW 5
  14. #define MARGIN 10
  15. #define DEFTAB 8
  16. #define NFILES 20
  17. #define HEAD "%12.12s %4.4s %s Page %d\n\n\n", date+4, date+24, head, Page
  18. #define TOLOWER(c) (isupper(c) ? tolower(c) : c) /* ouch! */
  19. #define cerror(S) fprint(2, "pr: %s", S)
  20. #define STDINNAME() nulls
  21. #define TTY "/dev/cons", 0
  22. #define PROMPT() fprint(2, "\a") /* BEL */
  23. #define TABS(N,C) if((N = intopt(argv, &C)) < 0) N = DEFTAB
  24. #define ETABS (Inpos % Etabn)
  25. #define ITABS (Itabn > 0 && Nspace > 1 && Nspace >= (nc = Itabn - Outpos % Itabn))
  26. #define NSEPC '\t'
  27. #define EMPTY 14 /* length of " -- empty file" */
  28. typedef struct Fils Fils;
  29. typedef struct Colp* Colp;
  30. typedef struct Err Err;
  31. struct Fils
  32. {
  33. Biobuf* f_f;
  34. char* f_name;
  35. long f_nextc;
  36. };
  37. struct Colp
  38. {
  39. Rune* c_ptr;
  40. Rune* c_ptr0;
  41. long c_lno;
  42. };
  43. struct Err
  44. {
  45. Err* e_nextp;
  46. char* e_mess;
  47. };
  48. int Balance = 0;
  49. Biobuf bout;
  50. Rune* Bufend;
  51. Rune* Buffer = 0;
  52. int C = '\0';
  53. Colp Colpts;
  54. int Colw;
  55. int Dblspace = 1;
  56. Err* err = 0;
  57. int error = 0;
  58. int Etabc = '\t';
  59. int Etabn = 0;
  60. Fils* Files;
  61. int Formfeed = 0;
  62. int Fpage = 1;
  63. char* Head = 0;
  64. int Inpos;
  65. int Itabc = '\t';
  66. int Itabn = 0;
  67. Err* Lasterr = (Err*)&err;
  68. int Lcolpos;
  69. int Len = LENGTH;
  70. int Line;
  71. int Linew = 0;
  72. long Lnumb = 0;
  73. int Margin = MARGIN;
  74. int Multi = 0;
  75. int Ncols = 1;
  76. int Nfiles = 0;
  77. int Nsepc = NSEPC;
  78. int Nspace;
  79. char nulls[] = "";
  80. int Numw;
  81. int Offset = 0;
  82. int Outpos;
  83. int Padodd;
  84. int Page;
  85. int Pcolpos;
  86. int Plength;
  87. int Sepc = 0;
  88. extern int atoix(char**);
  89. extern void balance(int);
  90. extern void die(char*);
  91. extern void errprint(void);
  92. extern char* ffiler(char*);
  93. extern int findopt(int, char**);
  94. extern int get(int);
  95. extern void* getspace(ulong);
  96. extern int intopt(char**, int*);
  97. extern void main(int, char**);
  98. extern Biobuf* mustopen(char*, Fils*);
  99. extern void nexbuf(void);
  100. extern int pr(char*);
  101. extern void put(long);
  102. extern void putpage(void);
  103. extern void putspace(void);
  104. /*
  105. * return date file was last modified
  106. */
  107. char*
  108. getdate(void)
  109. {
  110. static char *now = 0;
  111. static Dir *sbuf;
  112. ulong mtime;
  113. if(Nfiles > 1 || Files->f_name == nulls) {
  114. if(now == 0) {
  115. mtime = time(0);
  116. now = ctime(mtime);
  117. }
  118. return now;
  119. }
  120. mtime = 0;
  121. sbuf = dirstat(Files->f_name);
  122. if(sbuf){
  123. mtime = sbuf->mtime;
  124. free(sbuf);
  125. }
  126. return ctime(mtime);
  127. }
  128. char*
  129. ffiler(char *s)
  130. {
  131. return smprint("can't open %s\n", s);
  132. }
  133. void
  134. main(int argc, char *argv[])
  135. {
  136. Fils fstr[NFILES];
  137. int nfdone = 0;
  138. Binit(&bout, 1, OWRITE);
  139. Files = fstr;
  140. for(argc = findopt(argc, argv); argc > 0; --argc, ++argv)
  141. if(Multi == 'm') {
  142. if(Nfiles >= NFILES - 1)
  143. die("too many files");
  144. if(mustopen(*argv, &Files[Nfiles++]) == 0)
  145. nfdone++; /* suppress printing */
  146. } else {
  147. if(pr(*argv))
  148. Bterm(Files->f_f);
  149. nfdone++;
  150. }
  151. if(!nfdone) /* no files named, use stdin */
  152. pr(nulls); /* on GCOS, use current file, if any */
  153. errprint(); /* print accumulated error reports */
  154. exits(error? "error": 0);
  155. }
  156. int
  157. findopt(int argc, char *argv[])
  158. {
  159. char **eargv = argv;
  160. int eargc = 0, c;
  161. while(--argc > 0) {
  162. switch(c = **++argv) {
  163. case '-':
  164. if((c = *++*argv) == '\0')
  165. break;
  166. case '+':
  167. do {
  168. if(isdigit(c)) {
  169. --*argv;
  170. Ncols = atoix(argv);
  171. } else
  172. switch(c = TOLOWER(c)) {
  173. case '+':
  174. if((Fpage = atoix(argv)) < 1)
  175. Fpage = 1;
  176. continue;
  177. case 'd':
  178. Dblspace = 2;
  179. continue;
  180. case 'e':
  181. TABS(Etabn, Etabc);
  182. continue;
  183. case 'f':
  184. Formfeed++;
  185. continue;
  186. case 'h':
  187. if(--argc > 0)
  188. Head = argv[1];
  189. continue;
  190. case 'i':
  191. TABS(Itabn, Itabc);
  192. continue;
  193. case 'l':
  194. Len = atoix(argv);
  195. continue;
  196. case 'a':
  197. case 'm':
  198. Multi = c;
  199. continue;
  200. case 'o':
  201. Offset = atoix(argv);
  202. continue;
  203. case 's':
  204. if((Sepc = (*argv)[1]) != '\0')
  205. ++*argv;
  206. else
  207. Sepc = '\t';
  208. continue;
  209. case 't':
  210. Margin = 0;
  211. continue;
  212. case 'w':
  213. Linew = atoix(argv);
  214. continue;
  215. case 'n':
  216. Lnumb++;
  217. if((Numw = intopt(argv, &Nsepc)) <= 0)
  218. Numw = NUMW;
  219. case 'b':
  220. Balance = 1;
  221. continue;
  222. case 'p':
  223. Padodd = 1;
  224. continue;
  225. default:
  226. die("bad option");
  227. }
  228. } while((c = *++*argv) != '\0');
  229. if(Head == argv[1])
  230. argv++;
  231. continue;
  232. }
  233. *eargv++ = *argv;
  234. eargc++;
  235. }
  236. if(Len == 0)
  237. Len = LENGTH;
  238. if(Len <= Margin)
  239. Margin = 0;
  240. Plength = Len - Margin/2;
  241. if(Multi == 'm')
  242. Ncols = eargc;
  243. switch(Ncols) {
  244. case 0:
  245. Ncols = 1;
  246. case 1:
  247. break;
  248. default:
  249. if(Etabn == 0) /* respect explicit tab specification */
  250. Etabn = DEFTAB;
  251. }
  252. if(Linew == 0)
  253. Linew = Ncols != 1 && Sepc == 0? LINEW: 512;
  254. if(Lnumb)
  255. Linew -= Multi == 'm'? Numw: Numw*Ncols;
  256. if((Colw = (Linew - Ncols + 1)/Ncols) < 1)
  257. die("width too small");
  258. if(Ncols != 1 && Multi == 0) {
  259. ulong buflen = ((ulong)(Plength/Dblspace + 1))*(Linew+1)*sizeof(char);
  260. Buffer = getspace(buflen*sizeof(*Buffer));
  261. Bufend = &Buffer[buflen];
  262. Colpts = getspace((Ncols+1)*sizeof(*Colpts));
  263. }
  264. return eargc;
  265. }
  266. int
  267. intopt(char *argv[], int *optp)
  268. {
  269. int c;
  270. if((c = (*argv)[1]) != '\0' && !isdigit(c)) {
  271. *optp = c;
  272. (*argv)++;
  273. }
  274. c = atoix(argv);
  275. return c != 0? c: -1;
  276. }
  277. int
  278. pr(char *name)
  279. {
  280. char *date = 0, *head = 0;
  281. if(Multi != 'm' && mustopen(name, &Files[0]) == 0)
  282. return 0;
  283. if(Buffer)
  284. Bungetc(Files->f_f);
  285. if(Lnumb)
  286. Lnumb = 1;
  287. for(Page = 0;; putpage()) {
  288. if(C == -1)
  289. break;
  290. if(Buffer)
  291. nexbuf();
  292. Inpos = 0;
  293. if(get(0) == -1)
  294. break;
  295. Bflush(&bout);
  296. Page++;
  297. if(Page >= Fpage) {
  298. if(Margin == 0)
  299. continue;
  300. if(date == 0)
  301. date = getdate();
  302. if(head == 0)
  303. head = Head != 0 ? Head :
  304. Nfiles < 2? Files->f_name: nulls;
  305. Bprint(&bout, "\n\n");
  306. Nspace = Offset;
  307. putspace();
  308. Bprint(&bout, HEAD);
  309. }
  310. }
  311. if(Padodd && (Page&1) == 1) {
  312. Line = 0;
  313. if(Formfeed)
  314. put('\f');
  315. else
  316. while(Line < Len)
  317. put('\n');
  318. }
  319. C = '\0';
  320. return 1;
  321. }
  322. void
  323. putpage(void)
  324. {
  325. int colno;
  326. for(Line = Margin/2;; get(0)) {
  327. for(Nspace = Offset, colno = 0, Outpos = 0; C != '\f';) {
  328. if(Lnumb && C != -1 && (colno == 0 || Multi == 'a')) {
  329. if(Page >= Fpage) {
  330. putspace();
  331. Bprint(&bout, "%*ld", Numw, Buffer?
  332. Colpts[colno].c_lno++: Lnumb);
  333. Outpos += Numw;
  334. put(Nsepc);
  335. }
  336. Lnumb++;
  337. }
  338. for(Lcolpos=0, Pcolpos=0; C!='\n' && C!='\f' && C!=-1; get(colno))
  339. put(C);
  340. if(C==-1 || ++colno==Ncols || C=='\n' && get(colno)==-1)
  341. break;
  342. if(Sepc)
  343. put(Sepc);
  344. else
  345. if((Nspace += Colw - Lcolpos + 1) < 1)
  346. Nspace = 1;
  347. }
  348. /*
  349. if(C == -1) {
  350. if(Margin != 0)
  351. break;
  352. if(colno != 0)
  353. put('\n');
  354. return;
  355. }
  356. */
  357. if(C == -1 && colno == 0) {
  358. if(Margin != 0)
  359. break;
  360. return;
  361. }
  362. if(C == '\f')
  363. break;
  364. put('\n');
  365. if(Dblspace == 2 && Line < Plength)
  366. put('\n');
  367. if(Line >= Plength)
  368. break;
  369. }
  370. if(Formfeed)
  371. put('\f');
  372. else
  373. while(Line < Len)
  374. put('\n');
  375. }
  376. void
  377. nexbuf(void)
  378. {
  379. Rune *s = Buffer;
  380. Colp p = Colpts;
  381. int j, c, bline = 0;
  382. for(;;) {
  383. p->c_ptr0 = p->c_ptr = s;
  384. if(p == &Colpts[Ncols])
  385. return;
  386. (p++)->c_lno = Lnumb + bline;
  387. for(j = (Len - Margin)/Dblspace; --j >= 0; bline++)
  388. for(Inpos = 0;;) {
  389. if((c = Bgetrune(Files->f_f)) == -1) {
  390. for(*s = -1; p <= &Colpts[Ncols]; p++)
  391. p->c_ptr0 = p->c_ptr = s;
  392. if(Balance)
  393. balance(bline);
  394. return;
  395. }
  396. if(ISPRINT(c))
  397. Inpos++;
  398. if(Inpos <= Colw || c == '\n') {
  399. *s = c;
  400. if(++s >= Bufend)
  401. die("page-buffer overflow");
  402. }
  403. if(c == '\n')
  404. break;
  405. switch(c) {
  406. case '\b':
  407. if(Inpos == 0)
  408. s--;
  409. case ESC:
  410. if(Inpos > 0)
  411. Inpos--;
  412. }
  413. }
  414. }
  415. }
  416. /*
  417. * line balancing for last page
  418. */
  419. void
  420. balance(int bline)
  421. {
  422. Rune *s = Buffer;
  423. Colp p = Colpts;
  424. int colno = 0, j, c, l;
  425. c = bline % Ncols;
  426. l = (bline + Ncols - 1)/Ncols;
  427. bline = 0;
  428. do {
  429. for(j = 0; j < l; ++j)
  430. while(*s++ != '\n')
  431. ;
  432. (++p)->c_lno = Lnumb + (bline += l);
  433. p->c_ptr0 = p->c_ptr = s;
  434. if(++colno == c)
  435. l--;
  436. } while(colno < Ncols - 1);
  437. }
  438. int
  439. get(int colno)
  440. {
  441. static int peekc = 0;
  442. Colp p;
  443. Fils *q;
  444. long c;
  445. if(peekc) {
  446. peekc = 0;
  447. c = Etabc;
  448. } else
  449. if(Buffer) {
  450. p = &Colpts[colno];
  451. if(p->c_ptr >= (p+1)->c_ptr0)
  452. c = -1;
  453. else
  454. if((c = *p->c_ptr) != -1)
  455. p->c_ptr++;
  456. } else
  457. if((c = (q = &Files[Multi == 'a'? 0: colno])->f_nextc) == -1) {
  458. for(q = &Files[Nfiles]; --q >= Files && q->f_nextc == -1;)
  459. ;
  460. if(q >= Files)
  461. c = '\n';
  462. } else
  463. q->f_nextc = Bgetrune(q->f_f);
  464. if(Etabn != 0 && c == Etabc) {
  465. Inpos++;
  466. peekc = ETABS;
  467. c = ' ';
  468. } else
  469. if(ISPRINT(c))
  470. Inpos++;
  471. else
  472. switch(c) {
  473. case '\b':
  474. case ESC:
  475. if(Inpos > 0)
  476. Inpos--;
  477. break;
  478. case '\f':
  479. if(Ncols == 1)
  480. break;
  481. c = '\n';
  482. case '\n':
  483. case '\r':
  484. Inpos = 0;
  485. }
  486. return C = c;
  487. }
  488. void
  489. put(long c)
  490. {
  491. int move;
  492. switch(c) {
  493. case ' ':
  494. Nspace++;
  495. Lcolpos++;
  496. return;
  497. case '\b':
  498. if(Lcolpos == 0)
  499. return;
  500. if(Nspace > 0) {
  501. Nspace--;
  502. Lcolpos--;
  503. return;
  504. }
  505. if(Lcolpos > Pcolpos) {
  506. Lcolpos--;
  507. return;
  508. }
  509. case ESC:
  510. move = -1;
  511. break;
  512. case '\n':
  513. Line++;
  514. case '\r':
  515. case '\f':
  516. Pcolpos = 0;
  517. Lcolpos = 0;
  518. Nspace = 0;
  519. Outpos = 0;
  520. default:
  521. move = (ISPRINT(c) != 0);
  522. }
  523. if(Page < Fpage)
  524. return;
  525. if(Lcolpos > 0 || move > 0)
  526. Lcolpos += move;
  527. if(Lcolpos <= Colw) {
  528. putspace();
  529. Bputrune(&bout, c);
  530. Pcolpos = Lcolpos;
  531. Outpos += move;
  532. }
  533. }
  534. void
  535. putspace(void)
  536. {
  537. int nc;
  538. for(; Nspace > 0; Outpos += nc, Nspace -= nc)
  539. if(ITABS)
  540. Bputc(&bout, Itabc);
  541. else {
  542. nc = 1;
  543. Bputc(&bout, ' ');
  544. }
  545. }
  546. int
  547. atoix(char **p)
  548. {
  549. int n = 0, c;
  550. while(isdigit(c = *++*p))
  551. n = 10*n + c - '0';
  552. (*p)--;
  553. return n;
  554. }
  555. /*
  556. * Defer message about failure to open file to prevent messing up
  557. * alignment of page with tear perforations or form markers.
  558. * Treat empty file as special case and report as diagnostic.
  559. */
  560. Biobuf*
  561. mustopen(char *s, Fils *f)
  562. {
  563. char *tmp;
  564. if(*s == '\0') {
  565. f->f_name = STDINNAME();
  566. f->f_f = malloc(sizeof(Biobuf));
  567. if(f->f_f == 0)
  568. cerror("no memory");
  569. Binit(f->f_f, 0, OREAD);
  570. } else
  571. if((f->f_f = Bopen(f->f_name = s, OREAD)) == 0) {
  572. tmp = ffiler(f->f_name);
  573. s = strcpy((char*)getspace(strlen(tmp) + 1), tmp);
  574. free(tmp);
  575. }
  576. if(f->f_f != 0) {
  577. if((f->f_nextc = Bgetrune(f->f_f)) >= 0 || Multi == 'm')
  578. return f->f_f;
  579. sprint(s = (char*)getspace(strlen(f->f_name) + 1 + EMPTY),
  580. "%s -- empty file\n", f->f_name);
  581. Bterm(f->f_f);
  582. }
  583. error = 1;
  584. cerror(s);
  585. fprint(2, "\n");
  586. return 0;
  587. }
  588. void*
  589. getspace(ulong n)
  590. {
  591. void *t;
  592. if((t = malloc(n)) == 0)
  593. die("out of space");
  594. return t;
  595. }
  596. void
  597. die(char *s)
  598. {
  599. error++;
  600. errprint();
  601. cerror(s);
  602. Bputc(&bout, '\n');
  603. exits("error");
  604. }
  605. /*
  606. void
  607. onintr(void)
  608. {
  609. error++;
  610. errprint();
  611. exits("error");
  612. }
  613. /**/
  614. /*
  615. * print accumulated error reports
  616. */
  617. void
  618. errprint(void)
  619. {
  620. Bflush(&bout);
  621. for(; err != 0; err = err->e_nextp) {
  622. cerror(err->e_mess);
  623. fprint(2, "\n");
  624. }
  625. }