/* man.c - Read system documentation * * Copyright 2019 makepost * * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/man.html USE_MAN(NEWTOY(man, "k:M:", TOYFLAG_USR|TOYFLAG_BIN)) config MAN bool "man" default n help usage: man [-k STRING] | [SECTION] COMMAND Read manual page for system command. -k Search short Man pages are divided into 8 sections, each with an info page (man 8 info). 1) executables, 2) syscalls, 3) library functions, 4) /dev files, 5) file formats (ala /etc/hosts), 6) games, 7) miscelanous, 8) sysadmin If you don't specify a section it'll show the lowest numbered one, but "man 1 mkdir" and "man 2 mkdir" are different things. The shell builtins don't have section 1 man pages, see the "help" command. */ #define FOR_man #include GLOBALS( char *M, *k; char any, cell, ex, *f, k_done, *line, *m, **sct, **scts, **sufs; regex_t reg; ) static void newln() { if (FLAG(k)) return; if (TT.any) putchar('\n'); if (TT.any && TT.cell != 2) putchar('\n'); // gawk alias TT.any = TT.cell = 0; } static void put(char *x) { while (*x && (TT.ex || *x != '\n')) TT.any = putchar(*x++); } // Substitute with same length or shorter. static void s(char *x, char *y) { int i = strlen(x), j = strlen(y), k, l; for (k = 0; TT.line[k]; k++) if (!strncmp(x, &TT.line[k], i)) { memmove(&TT.line[k], y, j); for (l = k += j; TT.line[l]; l++) TT.line[l] = TT.line[l + i - j]; k--; } } static char start(char *x) { return !strncmp(x, TT.line, strlen(x)); } static void trim(char *x) { if (start(x)) while (*x++) TT.line++; } static char k(char *s) { TT.k_done = 2; if (s) TT.line = s; return !regexec(&TT.reg, TT.k, 0, 0, 0)||!regexec(&TT.reg, TT.line, 0, 0, 0); } static void do_man(char **pline, long len) { if (!pline) return newln(); TT.line = *pline; if (FLAG(k)) { if (!TT.k_done && !start(".") && !start("'") && k(strstr(*pline, "- "))) printf("%s %s%s", TT.k, "- "+2*(TT.line!=*pline), TT.line); else if (!TT.k_done && start(".so") && k(basename(*pline + 4))) printf("%s - See %s", TT.k, TT.line); } else { s("\\fB", ""), s("\\fI", ""), s("\\fP", ""), s("\\fR", ""); // bash bold,ita s("\\(aq", "'"), s("\\(cq", "'"), s("\\(dq", "\""); // bash,rsync quote s("\\*(lq", "\""), s("\\*(rq", "\""); // gawk quote s("\\(bu", "*"), s("\\(bv", "|"); // bash symbol s("\\&", ""), s("\\f(CW", ""); // gawk,rsync fancy s("\\-", "-"), s("\\(", ""), s("\\^", ""), s("\\e", "\\"); // bash escape s("\\*(", "#"); // gawk var if (start(".BR")) trim(".BR "), s(" ", ""); // bash boldpunct if (start(".IP")) newln(), trim(".IP "); // bash list if (start(".IR")) trim(".IR "), s(" ", ""); // bash itapunct trim(".B "); // bash bold trim(".BI "); // gawk boldita trim(".FN "); // bash filename trim(".I "); // bash ita trim(".if n "); // bash nroff if (start(".E")) TT.ex = TT.line[2] == 'X'; // stat example else if (start(".PP")) newln(); // bash paragraph else if (start(".SM")); // bash small else if (start(".S")) newln(), put(TT.line + 4), newln(); // bash section else if (start(".so")) put("See "), put(basename(TT.line + 4)); // lastb else if (start(".TH")) s("\"", " "), put(TT.line + 4); // gawk,git head else if (start(".TP")) newln(), TT.cell = 1; // bash table else if (start(".") || start("\'")); // bash,git garbage else if (!*TT.line); // emerge else { if (TT.cell) TT.cell++; if (!TT.ex) put(" "); put(TT.line); } } } // Open file, decompressing if suffix known. static int zopen(char *s) { int fds[] = {-1, -1}; char **known = TT.sufs, *suf = strrchr(s, '.'); if ((*fds = open(s, O_RDONLY)) == -1) return -1; while (suf && *known && strcmp(suf, *known++)); if (!suf || !*known) return *fds; sprintf(toybuf, "%czcat"+2*(suf[1]=='g'), suf[1]); xpopen_both((char *[]){toybuf, s, 0}, fds); close(fds[0]); return fds[1]; } static char manpath() { if (*++TT.sct) return 0; if (!(TT.m = strsep(&TT.M, ":"))) return 1; TT.sct = TT.scts; return 0; } // Try opening all the possible file extensions. static int tryfile(char *name) { int dotnum, fd = -1; char *s = xmprintf("%s/man%s/%s.%s.bz2", TT.m, *TT.sct, name, *TT.sct), **suf; size_t len = strlen(s) - 4; for (dotnum = 0; dotnum <= 2; dotnum += 2) { suf = TT.sufs; while ((fd == -1) && *suf) strcpy(s + len - dotnum, *suf++), fd = zopen(s); // Recheck suf in zopen, because for x.1.gz name here it is "". } free(s); return fd; } void man_main(void) { int fd = -1; TT.scts = (char *[]) {"1", "8", "3", "2", "5", "4", "6", "7", 0}; TT.sct = TT.scts - 1; // First manpath() read increments. TT.sufs = (char *[]) {".bz2", ".gz", ".xz", "", 0}; if (!TT.M) TT.M = getenv("MANPATH"); if (!TT.M) TT.M = "/usr/share/man"; if (FLAG(k)) { char *d, *f; DIR *dp; struct dirent *entry; if (regcomp(&TT.reg, TT.k, REG_ICASE|REG_NOSUB)) error_exit("bad regex"); while (!manpath()) { d = xmprintf("%s/man%s", TT.m, *TT.sct); if (!(dp = opendir(d))) continue; while ((entry = readdir(dp))) { if (entry->d_name[0] == '.') continue; f = xmprintf("%s/%s", d, TT.k = entry->d_name); if (-1 != (fd = zopen(f))) { TT.k_done = 0; do_lines(fd, '\n', do_man); close(fd); } free(f); } closedir(dp); free(d); } return regfree(&TT.reg); } if (!toys.optc) error_exit("not yet"); if (toys.optc == 1) { if (strchr(*toys.optargs, '/')) fd = zopen(*toys.optargs); else while ((fd == -1) && !manpath()) fd = tryfile(*toys.optargs); if (fd == -1) error_exit("no %s", *toys.optargs); // If they specified a section, look for file in that section } else { TT.scts = (char *[]){*toys.optargs, 0}, TT.sct = TT.scts - 1; while ((fd == -1) && !manpath()) fd = tryfile(toys.optargs[1]); if (fd == -1) error_exit("section %s no %s", *--TT.sct, toys.optargs[1]); } do_lines(fd, '\n', do_man); }