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