pr.c 11 KB

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