toybox/toys/pending/man.c
<<
>>
Prefs
   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
   7USE_MAN(NEWTOY(man, "k:M:", TOYFLAG_USR|TOYFLAG_BIN))
   8
   9config 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
  32GLOBALS(
  33  char *M, *k;
  34
  35  char any, cell, ex, *f, k_done, *line, *m, **sct, **scts, **sufs;
  36  regex_t reg;
  37)
  38
  39static 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
  47static 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.
  53static 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
  64static char start(char *x)
  65{
  66  return !strncmp(x, TT.line, strlen(x));
  67}
  68
  69static void trim(char *x)
  70{
  71  if (start(x)) while (*x++) TT.line++;
  72}
  73
  74static 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
  80static 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.
 126static 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
 140static 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.
 149static 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
 164void 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