1 /* man.c - Read system documentation 2 * 3 * Copyright 2019 makepost <makepost@firemail.cc> 4 * 5 * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/man.html 6 7 USE_MAN(NEWTOY(man, "k:M:", TOYFLAG_USR|TOYFLAG_BIN)) 8 9 config MAN 10 bool "man" 11 default n 12 help 13 usage: man [-M PATH] [-k STRING] | [SECTION] COMMAND 14 15 Read manual page for system command. 16 17 -k List pages with STRING in their short description 18 -M Override $MANPATH 19 20 Man pages are divided into 8 sections: 21 1 commands 2 system calls 3 library functions 4 /dev files 22 5 file formats 6 games 7 miscellaneous 8 system management 23 24 Sections are searched in the order 1 8 3 2 5 4 6 7 unless you specify a 25 section. Each section has a page called "intro", and there's a global 26 introduction under "man-pages". 27 */ 28 29 #define FOR_man 30 #include <toys.h> 31 32 GLOBALS( 33 char *M, *k; 34 35 char any, cell, ex, *f, k_done, *line, *m, **sct, **scts, **sufs; 36 regex_t reg; 37 ) 38 39 static void newln() 40 { 41 if (FLAG(k)) return; 42 if (TT.any) putchar('\n'); 43 if (TT.any && TT.cell != 2) putchar('\n'); // gawk alias 44 TT.any = TT.cell = 0; 45 } 46 47 static void put(char *x) 48 { 49 while (*x && (TT.ex || *x != '\n')) TT.any = putchar(*x++); 50 } 51 52 // Substitute with same length or shorter. 53 static void s(char *x, char *y) 54 { 55 int i = strlen(x), j = strlen(y), k, l; 56 57 for (k = 0; TT.line[k]; k++) if (!strncmp(x, &TT.line[k], i)) { 58 memmove(&TT.line[k], y, j); 59 for (l = k += j; TT.line[l]; l++) TT.line[l] = TT.line[l + i - j]; 60 k--; 61 } 62 } 63 64 static char start(char *x) 65 { 66 return !strncmp(x, TT.line, strlen(x)); 67 } 68 69 static void trim(char *x) 70 { 71 if (start(x)) while (*x++) TT.line++; 72 } 73 74 static char k(char *s) { 75 TT.k_done = 2; 76 if (s) TT.line = s; 77 return !regexec(&TT.reg, TT.k, 0, 0, 0)||!regexec(&TT.reg, TT.line, 0, 0, 0); 78 } 79 80 static void do_man(char **pline, long len) 81 { 82 if (!pline) return newln(); 83 TT.line = *pline; 84 85 if (FLAG(k)) { 86 if (!TT.k_done && !start(".") && !start("'") && k(strstr(*pline, "- "))) 87 printf("%-20s %s%s", TT.k, "- "+2*(TT.line!=*pline), TT.line); 88 else if (!TT.k_done && start(".so") && k(basename(*pline + 4))) 89 printf("%s - See %s", TT.k, TT.line); 90 } else { 91 s("\\fB", ""), s("\\fI", ""), s("\\fP", ""), s("\\fR", ""); // bash bold,ita 92 s("\\(aq", "'"), s("\\(cq", "'"), s("\\(dq", "\""); // bash,rsync quote 93 s("\\*(lq", "\""), s("\\*(rq", "\""); // gawk quote 94 s("\\(bu", "*"), s("\\(bv", "|"); // bash symbol 95 s("\\&", ""), s("\\f(CW", ""); // gawk,rsync fancy 96 s("\\-", "-"), s("\\(", ""), s("\\^", ""), s("\\e", "\\"); // bash escape 97 s("\\*(", "#"); // gawk var 98 99 if (start(".BR")) trim(".BR "), s(" ", ""); // bash boldpunct 100 if (start(".IP")) newln(), trim(".IP "); // bash list 101 if (start(".IR")) trim(".IR "), s(" ", ""); // bash itapunct 102 103 trim(".B "); // bash bold 104 trim(".BI "); // gawk boldita 105 trim(".FN "); // bash filename 106 trim(".I "); // bash ita 107 trim(".if n "); // bash nroff 108 if (start(".E")) TT.ex = TT.line[2] == 'X'; // stat example 109 else if (start(".PP")) newln(); // bash paragraph 110 else if (start(".SM")); // bash small 111 else if (start(".S")) newln(), put(TT.line + 4), newln(); // bash section 112 else if (start(".so")) put("See "), put(basename(TT.line + 4)); // lastb 113 else if (start(".TH")) s("\"", " "), put(TT.line + 4); // gawk,git head 114 else if (start(".TP")) newln(), TT.cell = 1; // bash table 115 else if (start(".") || start("\'")); // bash,git garbage 116 else if (!*TT.line); // emerge 117 else { 118 if (TT.cell) TT.cell++; 119 if (!TT.ex) put(" "); 120 put(TT.line); 121 } 122 } 123 } 124 125 // Open file, decompressing if suffix known. 126 static int zopen(char *s) 127 { 128 int fds[] = {-1, -1}; 129 char **known = TT.sufs, *suf = strrchr(s, '.'); 130 131 if ((*fds = open(s, O_RDONLY)) == -1) return -1; 132 while (suf && *known && strcmp(suf, *known++)); 133 if (!suf || !*known) return *fds; 134 sprintf(toybuf, "%czcat"+2*(suf[1]=='g'), suf[1]); 135 xpopen_both((char *[]){toybuf, s, 0}, fds); 136 close(fds[0]); 137 return fds[1]; 138 } 139 140 static char manpath() 141 { 142 if (*++TT.sct) return 0; 143 if (!(TT.m = strsep(&TT.M, ":"))) return 1; 144 TT.sct = TT.scts; 145 return 0; 146 } 147 148 // Try opening all the possible file extensions. 149 static int tryfile(char *name) 150 { 151 int dotnum, fd = -1; 152 char *s = xmprintf("%s/man%s/%s.%s.bz2", TT.m, *TT.sct, name, *TT.sct), **suf; 153 size_t len = strlen(s) - 4; 154 155 for (dotnum = 0; dotnum <= 2; dotnum += 2) { 156 suf = TT.sufs; 157 while ((fd == -1) && *suf) strcpy(s + len - dotnum, *suf++), fd = zopen(s); 158 // Recheck suf in zopen, because for x.1.gz name here it is "". 159 } 160 free(s); 161 return fd; 162 } 163 164 void man_main(void) 165 { 166 int fd = -1; 167 TT.scts = (char *[]) {"1", "8", "3", "2", "5", "4", "6", "7", 0}; 168 TT.sct = TT.scts - 1; // First manpath() read increments. 169 TT.sufs = (char *[]) {".bz2", ".gz", ".xz", "", 0}; 170 171 if (!TT.M) TT.M = getenv("MANPATH"); 172 if (!TT.M) TT.M = "/usr/share/man"; 173 174 if (FLAG(k)) { 175 char *d, *f; 176 DIR *dp; 177 struct dirent *entry; 178 179 xregcomp(&TT.reg, TT.k, REG_ICASE|REG_NOSUB); 180 while (!manpath()) { 181 d = xmprintf("%s/man%s", TT.m, *TT.sct); 182 if (!(dp = opendir(d))) continue; 183 while ((entry = readdir(dp))) { 184 if (entry->d_name[0] == '.') continue; 185 f = xmprintf("%s/%s", d, TT.k = entry->d_name); 186 if (-1 != (fd = zopen(f))) { 187 TT.k_done = 0; 188 do_lines(fd, '\n', do_man); 189 } 190 free(f); 191 } 192 closedir(dp); 193 free(d); 194 } 195 return regfree(&TT.reg); 196 } 197 198 if (!toys.optc) help_exit("which page?"); 199 200 if (toys.optc == 1) { 201 if (strchr(*toys.optargs, '/')) fd = zopen(*toys.optargs); 202 else while ((fd == -1) && !manpath()) fd = tryfile(*toys.optargs); 203 if (fd == -1) error_exit("no %s", *toys.optargs); 204 205 // If they specified a section, look for file in that section 206 } else { 207 TT.scts = (char *[]){*toys.optargs, 0}, TT.sct = TT.scts - 1; 208 while ((fd == -1) && !manpath()) fd = tryfile(toys.optargs[1]); 209 if (fd == -1) error_exit("section %s no %s", *--TT.sct, toys.optargs[1]); 210 } 211 212 do_lines(fd, '\n', do_man); 213 } 214