man.c 6.0 KB

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