10f66f451Sopenharmony_ci/* man.c - Read system documentation 20f66f451Sopenharmony_ci * 30f66f451Sopenharmony_ci * Copyright 2019 makepost <makepost@firemail.cc> 40f66f451Sopenharmony_ci * 50f66f451Sopenharmony_ci * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/man.html 60f66f451Sopenharmony_ci 70f66f451Sopenharmony_ciUSE_MAN(NEWTOY(man, "k:M:", TOYFLAG_USR|TOYFLAG_BIN)) 80f66f451Sopenharmony_ci 90f66f451Sopenharmony_ciconfig MAN 100f66f451Sopenharmony_ci bool "man" 110f66f451Sopenharmony_ci default n 120f66f451Sopenharmony_ci help 130f66f451Sopenharmony_ci usage: man [-M PATH] [-k STRING] | [SECTION] COMMAND 140f66f451Sopenharmony_ci 150f66f451Sopenharmony_ci Read manual page for system command. 160f66f451Sopenharmony_ci 170f66f451Sopenharmony_ci -k List pages with STRING in their short description 180f66f451Sopenharmony_ci -M Override $MANPATH 190f66f451Sopenharmony_ci 200f66f451Sopenharmony_ci Man pages are divided into 8 sections: 210f66f451Sopenharmony_ci 1 commands 2 system calls 3 library functions 4 /dev files 220f66f451Sopenharmony_ci 5 file formats 6 games 7 miscellaneous 8 system management 230f66f451Sopenharmony_ci 240f66f451Sopenharmony_ci Sections are searched in the order 1 8 3 2 5 4 6 7 unless you specify a 250f66f451Sopenharmony_ci section. Each section has a page called "intro", and there's a global 260f66f451Sopenharmony_ci introduction under "man-pages". 270f66f451Sopenharmony_ci*/ 280f66f451Sopenharmony_ci 290f66f451Sopenharmony_ci#define FOR_man 300f66f451Sopenharmony_ci#include <toys.h> 310f66f451Sopenharmony_ci 320f66f451Sopenharmony_ciGLOBALS( 330f66f451Sopenharmony_ci char *M, *k; 340f66f451Sopenharmony_ci 350f66f451Sopenharmony_ci char any, cell, ex, *f, k_done, *line, *m, **sct, **scts, **sufs; 360f66f451Sopenharmony_ci regex_t reg; 370f66f451Sopenharmony_ci) 380f66f451Sopenharmony_ci 390f66f451Sopenharmony_cistatic void newln() 400f66f451Sopenharmony_ci{ 410f66f451Sopenharmony_ci if (FLAG(k)) return; 420f66f451Sopenharmony_ci if (TT.any) putchar('\n'); 430f66f451Sopenharmony_ci if (TT.any && TT.cell != 2) putchar('\n'); // gawk alias 440f66f451Sopenharmony_ci TT.any = TT.cell = 0; 450f66f451Sopenharmony_ci} 460f66f451Sopenharmony_ci 470f66f451Sopenharmony_cistatic void put(char *x) 480f66f451Sopenharmony_ci{ 490f66f451Sopenharmony_ci while (*x && (TT.ex || *x != '\n')) TT.any = putchar(*x++); 500f66f451Sopenharmony_ci} 510f66f451Sopenharmony_ci 520f66f451Sopenharmony_ci// Substitute with same length or shorter. 530f66f451Sopenharmony_cistatic void s(char *x, char *y) 540f66f451Sopenharmony_ci{ 550f66f451Sopenharmony_ci int i = strlen(x), j = strlen(y), k, l; 560f66f451Sopenharmony_ci 570f66f451Sopenharmony_ci for (k = 0; TT.line[k]; k++) if (!strncmp(x, &TT.line[k], i)) { 580f66f451Sopenharmony_ci memmove(&TT.line[k], y, j); 590f66f451Sopenharmony_ci for (l = k += j; TT.line[l]; l++) TT.line[l] = TT.line[l + i - j]; 600f66f451Sopenharmony_ci k--; 610f66f451Sopenharmony_ci } 620f66f451Sopenharmony_ci} 630f66f451Sopenharmony_ci 640f66f451Sopenharmony_cistatic char start(char *x) 650f66f451Sopenharmony_ci{ 660f66f451Sopenharmony_ci return !strncmp(x, TT.line, strlen(x)); 670f66f451Sopenharmony_ci} 680f66f451Sopenharmony_ci 690f66f451Sopenharmony_cistatic void trim(char *x) 700f66f451Sopenharmony_ci{ 710f66f451Sopenharmony_ci if (start(x)) while (*x++) TT.line++; 720f66f451Sopenharmony_ci} 730f66f451Sopenharmony_ci 740f66f451Sopenharmony_cistatic char k(char *s) { 750f66f451Sopenharmony_ci TT.k_done = 2; 760f66f451Sopenharmony_ci if (s) TT.line = s; 770f66f451Sopenharmony_ci return !regexec(&TT.reg, TT.k, 0, 0, 0)||!regexec(&TT.reg, TT.line, 0, 0, 0); 780f66f451Sopenharmony_ci} 790f66f451Sopenharmony_ci 800f66f451Sopenharmony_cistatic void do_man(char **pline, long len) 810f66f451Sopenharmony_ci{ 820f66f451Sopenharmony_ci if (!pline) return newln(); 830f66f451Sopenharmony_ci TT.line = *pline; 840f66f451Sopenharmony_ci 850f66f451Sopenharmony_ci if (FLAG(k)) { 860f66f451Sopenharmony_ci if (!TT.k_done && !start(".") && !start("'") && k(strstr(*pline, "- "))) 870f66f451Sopenharmony_ci printf("%-20s %s%s", TT.k, "- "+2*(TT.line!=*pline), TT.line); 880f66f451Sopenharmony_ci else if (!TT.k_done && start(".so") && k(basename(*pline + 4))) 890f66f451Sopenharmony_ci printf("%s - See %s", TT.k, TT.line); 900f66f451Sopenharmony_ci } else { 910f66f451Sopenharmony_ci s("\\fB", ""), s("\\fI", ""), s("\\fP", ""), s("\\fR", ""); // bash bold,ita 920f66f451Sopenharmony_ci s("\\(aq", "'"), s("\\(cq", "'"), s("\\(dq", "\""); // bash,rsync quote 930f66f451Sopenharmony_ci s("\\*(lq", "\""), s("\\*(rq", "\""); // gawk quote 940f66f451Sopenharmony_ci s("\\(bu", "*"), s("\\(bv", "|"); // bash symbol 950f66f451Sopenharmony_ci s("\\&", ""), s("\\f(CW", ""); // gawk,rsync fancy 960f66f451Sopenharmony_ci s("\\-", "-"), s("\\(", ""), s("\\^", ""), s("\\e", "\\"); // bash escape 970f66f451Sopenharmony_ci s("\\*(", "#"); // gawk var 980f66f451Sopenharmony_ci 990f66f451Sopenharmony_ci if (start(".BR")) trim(".BR "), s(" ", ""); // bash boldpunct 1000f66f451Sopenharmony_ci if (start(".IP")) newln(), trim(".IP "); // bash list 1010f66f451Sopenharmony_ci if (start(".IR")) trim(".IR "), s(" ", ""); // bash itapunct 1020f66f451Sopenharmony_ci 1030f66f451Sopenharmony_ci trim(".B "); // bash bold 1040f66f451Sopenharmony_ci trim(".BI "); // gawk boldita 1050f66f451Sopenharmony_ci trim(".FN "); // bash filename 1060f66f451Sopenharmony_ci trim(".I "); // bash ita 1070f66f451Sopenharmony_ci trim(".if n "); // bash nroff 1080f66f451Sopenharmony_ci if (start(".E")) TT.ex = TT.line[2] == 'X'; // stat example 1090f66f451Sopenharmony_ci else if (start(".PP")) newln(); // bash paragraph 1100f66f451Sopenharmony_ci else if (start(".SM")); // bash small 1110f66f451Sopenharmony_ci else if (start(".S")) newln(), put(TT.line + 4), newln(); // bash section 1120f66f451Sopenharmony_ci else if (start(".so")) put("See "), put(basename(TT.line + 4)); // lastb 1130f66f451Sopenharmony_ci else if (start(".TH")) s("\"", " "), put(TT.line + 4); // gawk,git head 1140f66f451Sopenharmony_ci else if (start(".TP")) newln(), TT.cell = 1; // bash table 1150f66f451Sopenharmony_ci else if (start(".") || start("\'")); // bash,git garbage 1160f66f451Sopenharmony_ci else if (!*TT.line); // emerge 1170f66f451Sopenharmony_ci else { 1180f66f451Sopenharmony_ci if (TT.cell) TT.cell++; 1190f66f451Sopenharmony_ci if (!TT.ex) put(" "); 1200f66f451Sopenharmony_ci put(TT.line); 1210f66f451Sopenharmony_ci } 1220f66f451Sopenharmony_ci } 1230f66f451Sopenharmony_ci} 1240f66f451Sopenharmony_ci 1250f66f451Sopenharmony_ci// Open file, decompressing if suffix known. 1260f66f451Sopenharmony_cistatic int zopen(char *s) 1270f66f451Sopenharmony_ci{ 1280f66f451Sopenharmony_ci int fds[] = {-1, -1}; 1290f66f451Sopenharmony_ci char **known = TT.sufs, *suf = strrchr(s, '.'); 1300f66f451Sopenharmony_ci 1310f66f451Sopenharmony_ci if ((*fds = open(s, O_RDONLY)) == -1) return -1; 1320f66f451Sopenharmony_ci while (suf && *known && strcmp(suf, *known++)); 1330f66f451Sopenharmony_ci if (!suf || !*known) return *fds; 1340f66f451Sopenharmony_ci sprintf(toybuf, "%czcat"+2*(suf[1]=='g'), suf[1]); 1350f66f451Sopenharmony_ci xpopen_both((char *[]){toybuf, s, 0}, fds); 1360f66f451Sopenharmony_ci close(fds[0]); 1370f66f451Sopenharmony_ci return fds[1]; 1380f66f451Sopenharmony_ci} 1390f66f451Sopenharmony_ci 1400f66f451Sopenharmony_cistatic char manpath() 1410f66f451Sopenharmony_ci{ 1420f66f451Sopenharmony_ci if (*++TT.sct) return 0; 1430f66f451Sopenharmony_ci if (!(TT.m = strsep(&TT.M, ":"))) return 1; 1440f66f451Sopenharmony_ci TT.sct = TT.scts; 1450f66f451Sopenharmony_ci return 0; 1460f66f451Sopenharmony_ci} 1470f66f451Sopenharmony_ci 1480f66f451Sopenharmony_ci// Try opening all the possible file extensions. 1490f66f451Sopenharmony_cistatic int tryfile(char *name) 1500f66f451Sopenharmony_ci{ 1510f66f451Sopenharmony_ci int dotnum, fd = -1; 1520f66f451Sopenharmony_ci char *s = xmprintf("%s/man%s/%s.%s.bz2", TT.m, *TT.sct, name, *TT.sct), **suf; 1530f66f451Sopenharmony_ci size_t len = strlen(s) - 4; 1540f66f451Sopenharmony_ci 1550f66f451Sopenharmony_ci for (dotnum = 0; dotnum <= 2; dotnum += 2) { 1560f66f451Sopenharmony_ci suf = TT.sufs; 1570f66f451Sopenharmony_ci while ((fd == -1) && *suf) strcpy(s + len - dotnum, *suf++), fd = zopen(s); 1580f66f451Sopenharmony_ci // Recheck suf in zopen, because for x.1.gz name here it is "". 1590f66f451Sopenharmony_ci } 1600f66f451Sopenharmony_ci free(s); 1610f66f451Sopenharmony_ci return fd; 1620f66f451Sopenharmony_ci} 1630f66f451Sopenharmony_ci 1640f66f451Sopenharmony_civoid man_main(void) 1650f66f451Sopenharmony_ci{ 1660f66f451Sopenharmony_ci int fd = -1; 1670f66f451Sopenharmony_ci TT.scts = (char *[]) {"1", "8", "3", "2", "5", "4", "6", "7", 0}; 1680f66f451Sopenharmony_ci TT.sct = TT.scts - 1; // First manpath() read increments. 1690f66f451Sopenharmony_ci TT.sufs = (char *[]) {".bz2", ".gz", ".xz", "", 0}; 1700f66f451Sopenharmony_ci 1710f66f451Sopenharmony_ci if (!TT.M) TT.M = getenv("MANPATH"); 1720f66f451Sopenharmony_ci if (!TT.M) TT.M = "/usr/share/man"; 1730f66f451Sopenharmony_ci 1740f66f451Sopenharmony_ci if (FLAG(k)) { 1750f66f451Sopenharmony_ci char *d, *f; 1760f66f451Sopenharmony_ci DIR *dp; 1770f66f451Sopenharmony_ci struct dirent *entry; 1780f66f451Sopenharmony_ci 1790f66f451Sopenharmony_ci xregcomp(&TT.reg, TT.k, REG_ICASE|REG_NOSUB); 1800f66f451Sopenharmony_ci while (!manpath()) { 1810f66f451Sopenharmony_ci d = xmprintf("%s/man%s", TT.m, *TT.sct); 1820f66f451Sopenharmony_ci if (!(dp = opendir(d))) continue; 1830f66f451Sopenharmony_ci while ((entry = readdir(dp))) { 1840f66f451Sopenharmony_ci if (entry->d_name[0] == '.') continue; 1850f66f451Sopenharmony_ci f = xmprintf("%s/%s", d, TT.k = entry->d_name); 1860f66f451Sopenharmony_ci if (-1 != (fd = zopen(f))) { 1870f66f451Sopenharmony_ci TT.k_done = 0; 1880f66f451Sopenharmony_ci do_lines(fd, '\n', do_man); 1890f66f451Sopenharmony_ci } 1900f66f451Sopenharmony_ci free(f); 1910f66f451Sopenharmony_ci } 1920f66f451Sopenharmony_ci closedir(dp); 1930f66f451Sopenharmony_ci free(d); 1940f66f451Sopenharmony_ci } 1950f66f451Sopenharmony_ci return regfree(&TT.reg); 1960f66f451Sopenharmony_ci } 1970f66f451Sopenharmony_ci 1980f66f451Sopenharmony_ci if (!toys.optc) help_exit("which page?"); 1990f66f451Sopenharmony_ci 2000f66f451Sopenharmony_ci if (toys.optc == 1) { 2010f66f451Sopenharmony_ci if (strchr(*toys.optargs, '/')) fd = zopen(*toys.optargs); 2020f66f451Sopenharmony_ci else while ((fd == -1) && !manpath()) fd = tryfile(*toys.optargs); 2030f66f451Sopenharmony_ci if (fd == -1) error_exit("no %s", *toys.optargs); 2040f66f451Sopenharmony_ci 2050f66f451Sopenharmony_ci // If they specified a section, look for file in that section 2060f66f451Sopenharmony_ci } else { 2070f66f451Sopenharmony_ci TT.scts = (char *[]){*toys.optargs, 0}, TT.sct = TT.scts - 1; 2080f66f451Sopenharmony_ci while ((fd == -1) && !manpath()) fd = tryfile(toys.optargs[1]); 2090f66f451Sopenharmony_ci if (fd == -1) error_exit("section %s no %s", *--TT.sct, toys.optargs[1]); 2100f66f451Sopenharmony_ci } 2110f66f451Sopenharmony_ci 2120f66f451Sopenharmony_ci do_lines(fd, '\n', do_man); 2130f66f451Sopenharmony_ci} 214