1 /* (C) 1997 Robert de Bath
3 * under the terms of the GPL.
5 * This is a manual pager program, it will search for and format manual
6 * pages which it then pipes to more.
8 * The program understands manual pages that have been compressed with
9 * either 'compress' or 'gzip' and will decompress them on the fly.
11 * The environment is checked for these variables:
12 * MANSECT=1:2:3:4:5:6:7:8:9 # Manual section search order.
13 * MANPATH=/usr/local/man:/usr/man # Directorys to search for man tree.
14 * PAGER=more # pager progam to use.
15 * PATH=... # Search for gzip/uncompress
17 * The program will display documents that are either in it's own "nroff -man"
18 * like format or in "catman" format, it will not correctly display pages in
19 * the BSD '-mdoc' format.
21 * Neither nroff nor any similar program is needed as this program has it's
22 * own built in _man_ _page_ _formatter_. This is NOT an nroff clone and will
23 * not (for instance) understand macros or tbl constructs.
25 * Unlike groff this is small, fast and picky!
28 #define DONT_SPLATTER /* Lots of messages out */
36 #include <sys/ioctl.h>
38 static FILE *ofd
, *ifd
;
41 static int keep_nl
; /* How many nl to keep til eof */
42 static int optional_keep
; /* Is the next keep optional ? */
43 static int pending_nl
; /* Is there a pending newline on output? */
44 static int no_fill
; /* Disable 'filling' of lines */
45 static int left_indent
; /* Current step of left margin */
46 static int old_para_indent
; /* Indent to go back to after this paragraph */
48 static int current_line
; /* Line number = ? */
49 static int gaps_on_line
; /* Gaps on line for adjustments */
52 static int line
[256]; /* Buffer for building output line */
53 static char whitespace
[256];
54 static char line_header
[256]; /* Page header line */
55 static char doc_footer
[256]; /* Document footer line */
56 static char man_file
[256];
57 static char word
[80]; /* Current word */
59 static int right_margin
= 65; /* Don't print past this column */
60 static int verbose
= 1; /* print warnings about unknown macros */
61 static int no_nl
= 1; /* Next NL in input file is ignored */
62 static int catmode
= 1; /* Have we seen a '.XX' command ? */
63 static int cur_font
= 0x100; /* Current font, 1 == Roman */
64 static int next_line_indent
= -1; /* Indent after next line_break */
65 static int input_tab
= 8; /* Tab width in input file */
66 static int right_adjust
= 1; /* Adjust right margin */
67 static int standard_tab
= 5; /* Amount left margin stepped by */
70 static int find_page(char *name
, char *sect
);
71 static void step(char **pcurr
, char **pnext
);
72 static int open_page(char *name
);
73 static void close_page(void);
74 static void do_file(void);
75 static int fetch_word(void);
76 static int do_command(void);
77 static void do_skipeol(void);
78 static int do_fontwords(int this_font
, int other_font
, int early_exit
);
79 static int do_noargs(int cmd_id
);
80 static int do_argvcmd(int cmd_id
);
81 static void build_headers(void);
82 static void print_word(char *pword
);
83 static void line_break(void);
84 static void page_break(void);
85 static void print_header(void);
86 static void print_doc_footer(void);
88 static void usage(void)
90 fprintf(stderr
, "man [-w] [-v|-q] [section-number] page\n"
91 "-w\tonly print the location of matching manpages\n"
92 "-v\tForce verbose output. (default)\n"
93 "-q\tForce quiet output.\n"
94 "`page` may be specified as `-`, in which case stdin is used.\n");
98 /****************************************************************************
99 * Main routine, hunt down the manpage.
101 int main(int argc
, char **argv
) {
104 int do_pclose_ofd
= 0;
109 for(ar
= 1; ar
< argc
; ar
++)
110 if(argv
[ar
][0] == '-' && argv
[ar
][1]) {
112 for(p
= argv
[ar
] + 1; *p
; p
++)
124 } else if(argv
[ar
][0] == '-') {
127 } else if(isdigit(argv
[ar
][0]))
129 else if(manname
== 0)
131 else if(mansect
== 0) {
135 fprintf(stderr
, "Ignoring argument %s\n", argv
[ar
]);
139 if(manname
== 0) usage();
141 if(!ifd
&& find_page(manname
, mansect
) < 0) {
143 fprintf(stderr
, "No entry for %s in section %s of the manual.\n", manname
, mansect
);
145 fprintf(stderr
, "No manual entry for %s\n", manname
);
151 /* ifd is now the file - display it */
152 if(isatty(1)) { /* If writing to a tty do it to a pager */
154 char *pager
= getenv("PAGER");
155 if(pager
) ofd
= popen(pager
, "w");
156 if(!ofd
) ofd
= popen("less", "w");
157 if(!ofd
) ofd
= popen("more", "w");
158 if(!ofd
) ofd
= stdout
;
163 if(!ioctl(0, TIOCGWINSZ
, &ws
))
164 right_margin
= ws
.ws_col
>251 ? 250 : ws
.ws_col
-2;
181 static int find_page(char *name
, char *sect
) {
182 static char defpath
[] = "/usr/local/share/man:/usr/share/man";
183 static char defsect
[] = "1p:1:1perl:2:3p:3:3perl:4:5:6:7:8:9:0p";
184 static char defsuff
[] = ":.gz:.xz";
185 static char manorcat
[] = "man:cat";
189 char *mansect
= sect
;
191 char *mc
, *mp
, *ms
, *su
, *nmc
, *nmp
, *nms
, *nsu
;
194 manpath
= getenv("MANPATH");
198 mansect
= getenv("MANSECT");
203 if(strchr(name
, '/')) {
204 for(su
= nsu
= mansuff
, step(&su
, &nsu
); su
; step(&su
, &nsu
)) {
205 snprintf(fbuf
, sizeof(fbuf
), "%s%s", name
, su
);
206 if((rv
= open_page(fbuf
)) >= 0)
214 for(mc
= nmc
= manorcat
, step(&mc
, &nmc
); mc
; step(&mc
, &nmc
))
215 for(ms
= nms
= mansect
, step(&ms
, &nms
); ms
; step(&ms
, &nms
))
216 for(mp
= nmp
= manpath
, step(&mp
, &nmp
); mp
; step(&mp
, &nmp
))
217 for(su
= nsu
= mansuff
, step(&su
, &nsu
); su
; step(&su
, &nsu
)) {
218 snprintf(fbuf
, sizeof fbuf
, "%s/%s%s/%s.%s%s", mp
, mc
, ms
, name
, ms
, su
);
221 if(access(fbuf
, 0) < 0)
224 printf("%s\n", fbuf
);
230 if((rv
= open_page(fbuf
)) >= 0) {
232 snprintf(man_file
, sizeof man_file
, "%s", fbuf
);
233 p
= strrchr(man_file
, '/');
236 p
= strrchr(man_file
, '/');
246 static void step(char **pcurr
, char **pnext
) {
253 next
= strchr(curr
, ':');
260 next
= strchr(curr
, ':');
270 static char* which(const char* prog
, char* buf
, size_t buf_size
) {
271 char* path
= getenv("PATH");
274 path
+= strspn(path
, ":");
275 size_t l
= strcspn(path
, ":");
277 if (snprintf(buf
, buf_size
, "%.*s/%s", (int)l
, path
, prog
) >= (ssize_t
) buf_size
)
279 if(!access(buf
, X_OK
)) return buf
;
285 static int program_exists_in_path(const char* prog
) {
287 return !!which(prog
, buf
, sizeof buf
);
290 #define PREPROC "manpp"
291 static int preprocessor_exists(void) {
292 return program_exists_in_path(PREPROC
);
295 static int open_page(char *name
) {
296 char *p
, *command
= 0;
299 if(access(name
, 0) < 0)
302 if((p
= strrchr(name
, '.'))) {
303 if(!strcmp(p
, ".gz")) command
= "gzip -dc ";
304 else if(!strcmp(p
, ".xz")) command
= "xzcat -dc ";
306 if(!command
) command
= "cat ";
308 snprintf(buf
, sizeof buf
, "%s%s%s", command
, name
, preprocessor_exists() ? " | " PREPROC
: "");
309 if(!(ifd
= popen(buf
, "r"))) return -1;
313 static void close_page(void) {
320 /****************************************************************************
321 * ifd is the manual page, ofd is the 'output' file or pipe, format it!
323 static void do_file(void) {
327 while((nl
= fetch_word()) >= 0) {
329 fprintf(ofd
, ">WS='%s',", whitespace
);
330 fprintf(ofd
, "catmode=%d,", catmode
);
331 fprintf(ofd
, "nl=%d,", nl
);
332 fprintf(ofd
, "no_nl=%d,", no_nl
);
333 fprintf(ofd
, "no_fill=%d,", no_fill
);
334 fprintf(ofd
, "keep_nl=%d,", keep_nl
);
335 fprintf(ofd
, "opt_keep=%d,", optional_keep
);
336 fprintf(ofd
, "WD='%s',", word
);
341 if(strcmp(word
, "'\\\"") == 0 || strcmp(word
, "'''") == 0) {
342 /* This is a marker sometimes used for opening subprocesses like
343 * tbl and equ; this program ignores it.
346 } else if(*whitespace
== '\r')
347 fprintf(ofd
, "%s%s", whitespace
+ 1, word
);
349 fprintf(ofd
, "%s%s", whitespace
, word
);
351 if(keep_nl
&& nl
&& !no_nl
) {
354 if(line_ptr
== 0 || next_line_indent
< 0 ||
355 left_indent
+ (line_ptr
- line
) + 1 > next_line_indent
)
357 else if(line_ptr
!= 0 && next_line_indent
> 0) {
358 while(left_indent
+ (line_ptr
- line
) + 1 <= next_line_indent
)
359 *line_ptr
++ = cur_font
+ ' ';
367 if(nl
== 1 && (word
[0] == '.' ||
368 (word
[0] == '\'' && strcmp(word
, "'\\\"") == 0) ||
369 (word
[0] == '\'' && strcmp(word
, "'''") == 0)
375 if(nl
== 1 && no_fill
)
378 print_word(whitespace
);
388 static int fetch_word(void) {
394 *(p
= whitespace
) = 0;
396 if(!catmode
&& !no_fill
)
399 while((ch
= fgetc(ifd
)) != EOF
&& isspace(ch
)) {
400 if(nl
&& no_fill
&& ch
!= '.' && ch
!= '\n')
402 if(nl
&& !catmode
&& ch
== '\n') {
408 nl
= (ch
== '\n' || ch
== '\r');
414 if(no_fill
&& nl
&& *whitespace
) {
420 if(p
< whitespace
+ sizeof(whitespace
) - 1 && (!nl
|| catmode
))
423 if(ch
== '\t' && !catmode
) {
425 while(col
% input_tab
) {
426 if(p
< whitespace
+ sizeof(whitespace
) - 1)
432 if(!catmode
&& !no_fill
&& nl
)
433 *(p
= whitespace
) = 0;
437 if(catmode
&& ch
== '.' && nl
)
441 if(ch
== EOF
|| p
> word
+ sizeof(word
) / 2) {
451 while((ch
= fgetc(ifd
)) != EOF
&& !isspace(ch
)) {
452 if(p
< word
+ sizeof(word
) - 1)
456 if((ch
= fgetc(ifd
)) == EOF
)
458 // if( ch == ' ' ) ch = ' ' + 0x80; /* XXX Is this line needed? */
459 if(p
< word
+ sizeof(word
) - 1)
470 /****************************************************************************
471 * Accepted nroff commands and executors.
481 static const struct cmd_list_s
{
483 char class; /* enum cmd_class */
486 {"\\\"", CCLASS_NONE
, 0},
487 {"nh", CCLASS_NONE
, 0}, /* This program never inserts hyphens */
488 {"hy", CCLASS_NONE
, 0}, /* This program never inserts hyphens */
489 {"PD", CCLASS_NONE
, 0}, /* Inter-para distance is 1 line */
490 {"DT", CCLASS_NONE
, 0}, /* Default tabs, they can't be non-default! */
491 {"IX", CCLASS_NONE
, 0}, /* Indexing for some weird package */
492 {"Id", CCLASS_NONE
, 0}, /* Line for RCS tokens */
493 {"BY", CCLASS_NONE
, 0}, /* I wonder where this should go ? */
494 {"nf", CCLASS_NONE
, 1}, /* Line break, Turn line fill off */
495 {"fi", CCLASS_NONE
, 2}, /* Line break, Turn line fill on */
496 {"sp", CCLASS_NONE
, 3}, /* Line break, line space (arg for Nr lines) */
497 {"br", CCLASS_NONE
, 4}, /* Line break */
498 {"bp", CCLASS_NONE
, 5}, /* Page break */
499 {"PP", CCLASS_NONE
, 6},
500 {"LP", CCLASS_NONE
, 6},
501 {"P", CCLASS_NONE
, 6}, /* Paragraph */
502 {"RS", CCLASS_NONE
, 7}, /* New Para + Indent start */
503 {"RE", CCLASS_NONE
, 8}, /* New Para + Indent end */
504 {"HP", CCLASS_NONE
, 9}, /* Begin hanging indent (TP without arg?) */
505 {"ad", CCLASS_NONE
, 10}, /* Line up right margin */
506 {"na", CCLASS_NONE
, 11}, /* Leave right margin unaligned */
507 {"ta", CCLASS_NONE
, 12}, /* Changes _input_ tab spacing, right? */
508 {"TH", CCLASS_PARAMETERED
, 1},/* Title and headers */
509 {"SH", CCLASS_PARAMETERED
, 2},/* Section */
510 {"SS", CCLASS_PARAMETERED
, 3},/* Subsection */
511 {"IP", CCLASS_PARAMETERED
, 4},/* New para, indent except argument 1 */
512 {"TP", CCLASS_PARAMETERED
, 5},/* New para, indent except line 1 */
513 {"B", CCLASS_FONTCHANGER
, 22},/* Various font fiddles */
514 {"BI", CCLASS_FONTCHANGER
, 23},
515 {"BR", CCLASS_FONTCHANGER
, 21},
516 {"I", CCLASS_FONTCHANGER
, 33},
517 {"IB", CCLASS_FONTCHANGER
, 32},
518 {"IR", CCLASS_FONTCHANGER
, 31},
519 {"RB", CCLASS_FONTCHANGER
, 12},
520 {"RI", CCLASS_FONTCHANGER
, 13},
521 {"SB", CCLASS_FONTCHANGER
, 42},
522 {"SM", CCLASS_FONTCHANGER
, 44},
523 {"C", CCLASS_FONTCHANGER
, 22},/* PH-UX manual pages! */
524 {"CI", CCLASS_FONTCHANGER
, 23},
525 {"CR", CCLASS_FONTCHANGER
, 21},
526 {"IC", CCLASS_FONTCHANGER
, 32},
527 {"RC", CCLASS_FONTCHANGER
, 12},
528 {"so", CCLASS_SO
, 0},
529 {"\0\0", CCLASS_NONE
, 0}
532 static int do_command(void) {
539 /* Comments don't need the space */
540 if(strncmp(cmd
, "\\\"", 2) == 0)
543 for(i
= 0; cmd_list
[i
].cmd
[0]; i
++) {
544 if(strcmp(cmd_list
[i
].cmd
, cmd
) == 0)
548 if(cmd_list
[i
].cmd
[0] == 0) {
550 strncpy(lbuf
, cmd
, 3);
555 snprintf(word
, sizeof word
, "**** Unknown formatter command: .%s", lbuf
);
561 i
= 0; /* Treat as comment */
564 switch (cmd_list
[i
].class) {
565 case CCLASS_PARAMETERED
: /* Parametered commands */
566 return do_argvcmd(cmd_list
[i
].id
);
568 case CCLASS_FONTCHANGER
: /* Font changers */
569 return do_fontwords(cmd_list
[i
].id
/ 10, cmd_list
[i
].id
% 10, 0);
571 case CCLASS_SO
: /* .so */
573 if(strlen(man_file
) + 4 < sizeof man_file
)
574 strcat(man_file
, word
);
576 if(find_page(man_file
, (char *) 0) < 0) {
577 fprintf(stderr
, "Cannot open .so file %s\n", word
);
587 return do_noargs(cmd_list
[i
].id
);
592 static void do_skipeol(void) {
596 while((ch
= fgetc(ifd
)) != EOF
&& ch
!= '\n')
597 if(p
< word
+ sizeof(word
) - 1)
603 static void flush_word(char **p
) {
604 memcpy(*p
, "\\fR", 4);
609 static void insert_font(char **p
, int font
) {
610 const char ftab
[] = " RBIS";
611 memcpy(*p
, "\\f", 2);
612 (*p
)[2] = ftab
[font
];
616 static int do_fontwords(int this_font
, int other_font
, int early_exit
) {
621 no_nl
= 0; /* Line is effectivly been reprocessed so NL is visible */
623 if(p
== word
) insert_font(&p
, this_font
);
624 /* at each turn, at most 5 bytes are appended to word
625 * in order to flush the buffer, 4 more bytes are required to stay free */
626 if(p
+5+4 >= word
+sizeof word
) {
627 assert(p
+4<word
+sizeof word
);
631 if((ch
= fgetc(ifd
)) == EOF
|| ch
== '\n')
634 in_quote
= !in_quote
;
637 if(in_quote
|| !isspace(ch
)) {
638 if(isspace(ch
) && p
> word
+ 3) {
640 if(no_fill
) print_word(" ");
645 if((ch
= fgetc(ifd
)) == EOF
|| ch
== '\n') break;
652 if(early_exit
) break;
654 if(this_font
== other_font
) flush_word(&p
);
656 this_font
= other_font
;
658 insert_font(&p
, this_font
);
663 if(p
> word
+ 3) flush_word(&p
);
668 static int do_noargs(int cmd_id
) {
687 left_indent
= old_para_indent
;
692 left_indent
+= standard_tab
;
693 old_para_indent
+= standard_tab
;
697 left_indent
-= standard_tab
;
698 old_para_indent
-= standard_tab
;
708 input_tab
= atoi(word
);
716 static int do_argvcmd(int cmd_id
) {
720 while((ch
= fgetc(ifd
)) != EOF
&& (ch
== ' ' || ch
== '\t')) ;
723 switch (cmd_id
+ 10 * (ch
== '\n')) {
724 case 1: /* Title and headers */
726 left_indent
= old_para_indent
= standard_tab
;
730 case 2: /* Section */
732 next_line_indent
= old_para_indent
= standard_tab
;
737 do_fontwords(2, 2, 0);
739 case 3: /* Subsection */
740 left_indent
= standard_tab
/ 2;
741 next_line_indent
= old_para_indent
= standard_tab
;
746 do_fontwords(2, 2, 0);
750 case 5: /* New para, indent except line 1 */
752 next_line_indent
= old_para_indent
+ standard_tab
;
753 left_indent
= old_para_indent
;
759 case 4: /* New para, indent except argument 1 */
760 next_line_indent
= old_para_indent
+ standard_tab
;
761 left_indent
= old_para_indent
;
765 do_fontwords(1, 1, 1);
771 left_indent
= old_para_indent
+ standard_tab
;
778 static void build_headers(void) {
782 int last_ch
= 0, ch
, in_quote
= 0;
784 for(ch
= 0; ch
< 5; ch
++)
788 if((ch
= fgetc(ifd
)) == EOF
|| ch
== '\n')
791 if(last_ch
== '\\') {
796 in_quote
= !in_quote
;
800 if(in_quote
|| !isspace(ch
)) {
801 /* Nb, this does nothing about backslashes, perhaps it should */
802 if(stroff
< sizeof(buffer
[strno
]) - 1)
803 buffer
[strno
][stroff
++] = ch
;
806 buffer
[strno
][stroff
] = 0;
808 if(buffer
[strno
][0]) {
816 buffer
[strno
][stroff
] = 0;
819 /* Ok we should have upto 5 arguments build the header and footer */
821 size_t l0
= strlen(buffer
[0]),
822 l1
= strlen(buffer
[1]),
823 l2
= strlen(buffer
[2]),
824 l3
= strlen(buffer
[3]),
825 l4
= strlen(buffer
[4]),
827 snprintf(line_header
, sizeof line_header
, "%s(%s)%*s%*s(%s)", buffer
[0],
828 buffer
[1], (int) (right_margin
/2-l01
+l4
/2+(l4
&1)), buffer
[4],
829 (int) (right_margin
/2-l4
/2-l01
+l0
-(l4
&1)), buffer
[0], buffer
[1]);
830 snprintf(doc_footer
, sizeof doc_footer
, "%s%*s%*s(%s)", buffer
[3],
831 (int) (right_margin
/2-l3
+l2
/2+(l2
&1)), buffer
[2],
832 (int) (right_margin
/2-l2
/2-l01
+l0
-(l2
&1)), buffer
[0], buffer
[1]);
837 static void print_word(char *pword
) {
838 /* Eat \& \a .. \z and \A .. \Z
839 * \fX Switch to font X (R B I S etc)
840 * \(XX Special character XX
843 #define checkw(X) assert(d+X<wword+(sizeof wword/sizeof wword[0]))
844 #define checkl(X) assert(line_ptr+X<line+(sizeof line/sizeof line[0]))
849 int sp_font
= cur_font
;
851 /* Eat and translate characters. */
852 for(s
= pword
, d
= wword
; *s
; s
++) {
858 *d
++ = (ch
= *s
) + cur_font
;
867 static char fnt
[] = " RBI";
868 char *p
= strchr(fnt
, *++s
);
872 cur_font
= 0x100 * (p
- fnt
);
875 } else if(*s
== 's') {
876 /* Font size adjusts - strip */
877 while(s
[1] && strchr("+-0123456789", s
[1]))
880 } else if(isalpha(*s
) || strchr("!&^[]|~", *s
))
882 else if(*s
== '(' || *s
== '*') {
883 /* XXX Humm character xlate - http://mdocml.bsd.lv/man/mandoc_char.7.html */
888 } else if(s
[1] == 'm' && s
[2] == 'i') {
899 *d
++ = out
+ cur_font
;
905 *d
++ = *s
+ cur_font
;
915 fprintf(ofd
, ">WORD:");
916 for(x
= wword
; *x
; x
++)
926 if(line_ptr
+ ((line_ptr
[-1] & 0xFF) == '.') - line
+ length
>= right_margin
- left_indent
) {
927 right_adjust
= -right_adjust
;
934 assert(line_ptr
> line
);
935 if(!no_fill
&& (line_ptr
[-1] & 0xFF) > ' ') {
936 if((line_ptr
[-1] & 0xFF) == '.') {
938 *line_ptr
++ = cur_font
+ ' ';
941 *line_ptr
++ = sp_font
;
947 memcpy(line_ptr
, wword
, length
* sizeof(int));
953 static void line_break(void) {
955 int spg
= 1, rspg
= 1, spgs
= 0, gap
= 0;
960 if(current_line
== 0)
964 current_line
+= 1 + pending_nl
;
965 for(; pending_nl
> 0; pending_nl
--)
968 if(right_adjust
< 0) {
969 int over
= right_margin
- left_indent
- (line_ptr
- line
);
971 fprintf(ofd
, ">Gaps=%d, Over=%d, ", gaps_on_line
, over
);
973 if(gaps_on_line
&& over
) {
974 spg
= rspg
= 1 + over
/ gaps_on_line
;
975 over
= over
% gaps_on_line
;
977 if(current_line
% 2) {
981 spgs
= gaps_on_line
- over
;
987 fprintf(ofd
, " (%d,%d) sw=%d\n", spg
, rspg
, spgs
);
994 for(ch
= left_indent
; ch
> 0; ch
--)
997 for(d
= line
; *d
; d
++) {
999 if((ch
& 0xFF) == 0) {
1010 fputc(ch
& 0xFF, ofd
);
1012 fputc(ch
& 0xFF, ofd
);
1017 fputc(ch
& 0xFF, ofd
);
1020 fputc(ch
& 0xFF, ofd
);
1028 if(next_line_indent
> 0)
1029 left_indent
= next_line_indent
;
1030 next_line_indent
= -1;
1034 static void page_break(void) {
1038 static void print_header(void) {
1042 fprintf(ofd
, "%s\n\n", line_header
);
1046 static void print_doc_footer(void) {
1049 for(i
= 0; i
< 3; i
++) fputc('\n', ofd
);
1050 fprintf(ofd
, "%s", doc_footer
);