[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