[Toybox] [PATCH] Implement man.

makepost at firemail.cc makepost at firemail.cc
Sun Apr 28 17:24:05 PDT 2019


Having section number explanations right in the help text is cool, 
thanks. Why does it say "show the lowest numbered one", given the 
18325467 order addition?

By todos in the end of the test, I meant the following. Somewhere in the 
emerge man there's a section header that with current implementation 
follows four newlines instead of two, it was noticeable enough for me to 
make a note but not distracting enough to explore. Fdm, an app fetching 
and delivering mail, has a man page in some language other than roff. 
The other two, about \-\- and -k, I implemented over the weekend, and 
clarified what is still missing for convenient display of, for example, 
the Linux man-pages project. Appending the patch below.

If you can optimize trimming while keeping do_man compact, that'd be 
great, I tried but the code gets too low level. Word wrap can be more 
beneficial to implement in a pager or an editor. Usually I pipe man to 
vim, to get other niceties such as yanking and saving with notes.

Adding banana/ to the test preceding the man -k implementation is a 
quite enjoyable pun.
---
 From 2c676f8bf1e884a12ab9b7a3edcce8317da8a7de Mon Sep 17 00:00:00 2001
 From: makepost <makepost at firemail.cc>
Date: Sun, 28 Apr 2019 17:00:31 +0300
Subject: Search name and first line with man -k regex.

Exec -k value as regex on basename, and on the first content line
outside a tag or on a referenced see-other, whichever appears earlier.
Reuse zcat choice as a function when looping over files. Fix \-\- and
glob.h include leftover. Handle man-pages example newlines. Clarify the
todos, naming package and issue. Remaining items are more of a wishlist
than a plan. Remove `<1>2` because it doesn't let `-k .` work, please
look into that.
---
  tests/man.test     |  31 +++++++++--
  toys/pending/man.c | 125 ++++++++++++++++++++++++++++-----------------
  2 files changed, 106 insertions(+), 50 deletions(-)

diff --git a/tests/man.test b/tests/man.test
index f261437c..65b8a06f 100644
--- a/tests/man.test
+++ b/tests/man.test
@@ -16,6 +16,7 @@ echo five > banana/man5/numbers.5
  testing "man" "$MAN numbers" " one\n\n" "" ""
  testing "man.section" "$MAN numbers.3" " three\n\n" "" ""
  testing "section man" "$MAN 5 numbers" " five\n\n" "" ""
+testing "/" "$MAN /" "" "" "" # Regression guard for !suf in zopen

  cat >banana/man1/toybox.1 <<EOF
  .TP
@@ -27,7 +28,7 @@ cat >banana/man1/toybox.1 <<EOF
  Does something.
  .TP
  .PD 0
-.B \\-\\^\\-no\\-alias
+.B \\-\\-no\\-alias
  Has no alias.
  EOF

@@ -128,6 +129,14 @@ cat >banana/man1/toybox.1 <<EOF
  EOF
  testing "bash nroff" "$MAN toybox" " Copyright (C) 2019\n\n" "" ""

+cat >banana/man1/toybox.1 <<EOF
+.EX
+#include <stdio.h>
+#include <stdlib.h>
+.EE
+EOF
+testing "stat example" "$MAN toybox" "#include <stdio.h>\n#include 
<stdlib.h>\n\n\n" "" ""
+
  cat >banana/man1/toybox.1 <<EOF
  (...)
  .PP
@@ -183,9 +192,23 @@ cat >banana/man1/toybox.1 <<EOF
  EOF
  testing "bash,git garbage" "$MAN toybox" "" "" ""

-# TODO: -k
+# "bash,git garbage" from above has no content, -k skips it.
+bzip2 >banana/man1/numbers.1.bz2 <<EOF
+.TH NUMBERS 1 "2019 Apr 28" "Toybox Manual"
+.SH NAME
+man \- test -k
+.SH ANOTHER SECTION
+.so noop.1
+Skip this text.
+EOF
+echo "No dash." | gzip >banana/man3/numbers.3.gz
+echo .so man1/numbers.1 >banana/man5/numbers.5
+testing "-k ." "$MAN -k ." "numbers.1.bz2 - test -k\nnumbers.3.gz - No 
dash.\nnumbers.5 - See numbers.1\n" "" ""
+testing "-k -k" "$MAN -k -k" "numbers.1.bz2 - test -k\n" "" ""
+testing "-k d.*h" "$MAN -k 'd.*h'" "numbers.3.gz - No dash.\n" "" ""
+testing "-k ers.1" "$MAN -k ers.1" "numbers.1.bz2 - test -k\nnumbers.5 
- See numbers.1\n" "" ""
+
  # TODO: emerge section header newline
-# TODO: fdm not roff
-# TODO: git-pull consecutive escaped slashes
+# TODO: fdm,man-pages man1p/, .nf, rare tags

  rm -rf banana
diff --git a/toys/pending/man.c b/toys/pending/man.c
index 9abf92bc..0e54e93c 100644
--- a/toys/pending/man.c
+++ b/toys/pending/man.c
@@ -4,7 +4,7 @@
   *
   * See 
http://pubs.opengroup.org/onlinepubs/9699919799/utilities/man.html

-USE_MAN(NEWTOY(man, "<1>2k:M:", TOYFLAG_USR|TOYFLAG_BIN))
+USE_MAN(NEWTOY(man, "k:M:", TOYFLAG_USR|TOYFLAG_BIN))

  config MAN
    bool "man"
@@ -28,16 +28,17 @@ config MAN

  #define FOR_man
  #include <toys.h>
-#include <glob.h>

  GLOBALS(
    char *M, *k;

-  char any, cell, *f, *line;
+  char any, cell, ex, *f, k_done, *line, **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;
@@ -45,7 +46,7 @@ static void newln()

  static void put(char *x)
  {
-  while (*x && *x != '\n') TT.any = putchar(*x++);
+  while (*x && (TT.ex || *x != '\n')) TT.any = putchar(*x++);
  }

  // Substitute with same length or shorter.
@@ -56,6 +57,7 @@ static void s(char *x, char *y)
    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--;
    }
  }

@@ -69,17 +71,23 @@ 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)
  {
-  char *line;
-
-  if (!pline) {
-    newln();
-    return;
-  }
-  line = *pline;
-
-    TT.line = line;
+  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
@@ -97,7 +105,8 @@ static void do_man(char **pline, long len)
      trim(".FN "); // bash filename
      trim(".I "); // bash ita
      trim(".if n "); // bash nroff
-    if (start(".PP")) newln(); // bash paragraph
+    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
@@ -107,56 +116,80 @@ static void do_man(char **pline, long len)
      else if (!*TT.line); // emerge
      else {
        if (TT.cell) TT.cell++;
-      put(" ");
+      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];
  }

  // Try opening all the possible file extensions.
  int tryfile(char *section, char *name)
  {
-  char *suf[] = {".gz", ".bz2", ".xz", ""}, *end,
-    *s = xmprintf("%s/man%s/%s.%s.bz2", TT.M, section, name, section);
-  int fd, i, and = 1;
-
-  end = s+strlen(s);
-  do {
-    for (i = 0; i<ARRAY_LEN(suf); i++) {
-      strcpy(end-4, suf[i]);
-      if ((fd = open(s, O_RDONLY))!= -1) {
-        if (*suf[i]) {
-          int fds[] = {fd, -1};
-
-          sprintf(toybuf, "%czcat"+(2*!i), suf[i][1]);
-          xpopen_both((char *[]){toybuf, s, 0}, fds);
-          fd = fds[1];
-        }
-        goto done;
-      }
-    }
-    end -= strlen(section)+1;
-  } while (and--);
-
-done:
+  char *s = xmprintf("%s/man%s/%s.%s.bz2", TT.M, section, name, 
section), **suf;
+  int dotnum, fd = -1;
+  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)
  {
-  char *order = "18325467";
-  int fd;
+  int fd = -1;
+  char **order = (char *[]) {"1", "8", "3", "2", "5", "4", "6", "7", 
0};
+  TT.sufs = (char *[]) {".bz2", ".gz", ".xz", "", 0};

    if (!TT.M) TT.M = "/usr/share/man";

-  if (!toys.optc || FLAG(k)) error_exit("not yet");
+  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 (*order) {
+      d = xmprintf("%s/man%s", TT.M, *order++);
+      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 = xopen(*toys.optargs, 
O_RDONLY);
-    else for (order = "18325467"; *order; order++) {
-      *toybuf = *order;
-      if (-1 != (fd = tryfile(toybuf, *toys.optargs))) break;
-    }
+    if (strchr(*toys.optargs, '/')) fd = zopen(*toys.optargs);
+    else while ((fd == -1) && *order) fd = tryfile(*order++, 
*toys.optargs);
      if (!*order) error_exit("no %s", *toys.optargs);

    // If they specified a section, look for file in that section



More information about the Toybox mailing list