Make the existing -d (depth) option apply to the -H (static HTML
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Mon, 22 Feb 2010 19:03:08 +0000 (19:03 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Mon, 22 Feb 2010 19:03:08 +0000 (19:03 +0000)
report) mode, transforming its output from a single HTML file giving
a report for one directory with no crosslinks to a collection of
HTML files with crosslinks between them.

I've also added a special 'max' value to -d (meaning no depth
limit), which also works when used in text mode; I've added -o to
specify an output file for both text and HTML report modes; and I've
also documented -d, which I had previously forgotten to do (but
nobody seems to have noticed :-).

git-svn-id: svn://svn.tartarus.org/sgt/agedu@8880 cda61777-01e9-0310-a592-d414129be87e

agedu.but
agedu.c
html.c
html.h
httpd.c

index 1494140..6e5e5b0 100644 (file)
--- a/agedu.but
+++ b/agedu.but
@@ -190,10 +190,10 @@ completely) and a username and password of your choice.
 
 \dd In this mode, \cw{agedu} generates a textual report on standard
 output, listing the disk usage in the specified directory and all
-its subdirectories down to a fixed depth. By default that depth is
+its subdirectories down to a given depth. By default that depth is
 1, so that you see a report for \e{directory} itself and all of its
-immediate subdirectories. You can configure a different depth using
-\cw{-d}, described in the next section.
+immediate subdirectories. You can configure a different depth (or no
+depth limit) using \cw{-d}, described in the next section.
 
 \lcont{
 
@@ -257,9 +257,18 @@ for further detail.)
 \dd In this mode, \cw{agedu} will generate an HTML report of the
 disk usage in the specified directory and its immediate
 subdirectories, in the same form that it serves from its web server
-in \cw{-w} mode. However, this time, a single HTML report will be
-generated and simply written to standard output, with no hyperlinks
-pointing to other similar pages.
+in \cw{-w} mode.
+
+\lcont{
+
+By default, a single HTML report will be generated and simply
+written to standard output, with no hyperlinks pointing to other
+similar pages. If you also specify the \cw{-d} option (see below),
+\cw{agedu} will instead write out a collection of HTML files with
+hyperlinks between them, and call the top-level file
+\cw{index.html}.
+
+}
 
 \U OPTIONS
 
@@ -467,6 +476,42 @@ the web server mode \cw{-w}, the stand-alone HTML generation mode
 files in each directory, instead of just giving a combined report
 for everything that's not in a subdirectory.
 
+The following options affect the stand-alone HTML generation mode
+\cw{-H} and the text report mode \cw{-t}.
+
+\dt \cw{-d} \e{depth} or \cw{--depth} \e{depth}
+
+\dd This option controls the maximum depth to which \cw{agedu}
+recurses when generating a text or HTML report.
+
+\lcont{
+
+In text mode, the default is 1, meaning that the report will include
+the directory given on the command line and all of its immediate
+subdirectories. A depth of two includes another level below that,
+and so on; a depth of zero means \e{only} the directory on the
+command line.
+
+In HTML mode, specifying this option switches \cw{agedu} from
+writing out a single HTML file to writing out multiple files which
+link to each other. A depth of 1 means \cw{agedu} will write out an
+HTML file for the given directory and also one for each of its
+immediate subdirectories.
+
+If you want \cw{agedu} to recurse as deeply as possible, give the
+special word \cq{max} as an argument to \cw{-d}.
+
+}
+
+\dt \cw{-o} \e{filename} or \cw{--output} \e{filename}
+
+\dd This option is used to specify an output file for \cw{agedu} to
+write its report to. In text mode or single-file HTML mode, the
+argument is treated as the name of a file. In multiple-file HTML
+mode, the argument is treated as the name of a directory: the
+directory will be created if it does not already exist, and the
+output HTML files will be created inside it.
+
 The following options affect the web server mode \cw{-w}, and in one
 case also the stand-alone HTML generation mode \cw{-H}:
 
diff --git a/agedu.c b/agedu.c
index b253f18..463104f 100644 (file)
--- a/agedu.c
+++ b/agedu.c
@@ -163,7 +163,7 @@ static void scan_error(void *vctx, const char *fmt, ...)
 }
 
 static void text_query(const void *mappedfile, const char *querydir,
-                      time_t t, int showfiles, int depth)
+                      time_t t, int showfiles, int depth, FILE *fp)
 {
     size_t maxpathlen;
     char *pathbuf;
@@ -211,22 +211,23 @@ static void text_query(const void *mappedfile, const char *querydir,
     if (size == 0)
        return;                        /* no space taken up => no display */
 
-    if (depth > 0) {
+    if (depth != 0) {
        /*
         * Now scan for first-level subdirectories and report
         * those too.
         */
+       int newdepth = (depth > 0 ? depth - 1 : depth);
        xi1++;
        while (xi1 < xi2) {
            trie_getpath(mappedfile, xi1, pathbuf);
-           text_query(mappedfile, pathbuf, t, showfiles, depth-1);
+           text_query(mappedfile, pathbuf, t, showfiles, newdepth, fp);
            make_successor(pathbuf);
            xi1 = trie_before(mappedfile, pathbuf);
        }
     }
 
     /* Display in units of 1Kb */
-    printf("%-11llu %s\n", (size) / 1024, querydir);
+    fprintf(fp, "%-11llu %s\n", (size) / 1024, querydir);
 }
 
 /*
@@ -344,6 +345,8 @@ static void text_query(const void *mappedfile, const char *querydir,
         HELPOPT("[--web,--html,--text] list individual files") \
     VAL(AGERANGE) SHORT(r) LONG(age_range) LONG(range) LONG(ages) \
         HELPARG("age[-age]") HELPOPT("[--web,--html] set limits of colour coding") \
+    VAL(OUTFILE) SHORT(o) LONG(output) \
+       HELPARG("filename") HELPOPT("[--html] specify output file or directory name") \
     VAL(SERVERADDR) LONG(address) LONG(addr) LONG(server_address) \
               LONG(server_addr) \
         HELPARG("addr[:port]") HELPOPT("[--web] specify HTTP server address") \
@@ -354,8 +357,8 @@ static void text_query(const void *mappedfile, const char *querydir,
         HELPARG("filename") HELPOPT("[--web] read HTTP Basic user/pass from file") \
     VAL(AUTHFD) LONG(auth_fd) \
         HELPARG("fd") HELPOPT("[--web] read HTTP Basic user/pass from fd") \
-    VAL(TQDEPTH) SHORT(d) LONG(depth) LONG(max_depth) LONG(maximum_depth) \
-        HELPARG("levels") HELPOPT("[--text] recurse to this many levels") \
+    VAL(DEPTH) SHORT(d) LONG(depth) LONG(max_depth) LONG(maximum_depth) \
+        HELPARG("levels") HELPOPT("[--text,--html] recurse to this many levels") \
     VAL(MINAGE) SHORT(a) LONG(age) LONG(min_age) LONG(minimum_age) \
         HELPARG("age") HELPOPT("[--text] include only files older than this") \
     HELPPFX("also") \
@@ -496,12 +499,13 @@ int main(int argc, char **argv)
     const char *httpserveraddr = NULL;
     int httpserverport = 0;
     const char *httpauthdata = NULL;
+    const char *outfile = NULL;
     int auth = HTTPD_AUTH_MAGIC | HTTPD_AUTH_BASIC;
     int progress = 1;
     struct inclusion_exclusion *inex = NULL;
     int ninex = 0, inexsize = 0;
     int crossfs = 0;
-    int tqdepth = 1;
+    int depth = -1, gotdepth = 0;
     int fakediratimes = 1;
     int mtime = 0;
     int showfiles = 0;
@@ -764,8 +768,20 @@ int main(int argc, char **argv)
                  case OPT_DATAFILE:
                    filename = optval;
                    break;
-                 case OPT_TQDEPTH:
-                   tqdepth = atoi(optval);
+                 case OPT_DEPTH:
+                   if (!strcasecmp(optval, "unlimited") ||
+                       !strcasecmp(optval, "infinity") ||
+                       !strcasecmp(optval, "infinite") ||
+                       !strcasecmp(optval, "inf") ||
+                       !strcasecmp(optval, "maximum") ||
+                       !strcasecmp(optval, "max"))
+                       depth = -1;
+                   else
+                       depth = atoi(optval);
+                   gotdepth = 1;
+                   break;
+                 case OPT_OUTFILE:
+                   outfile = optval;
                    break;
                  case OPT_MINAGE:
                    textcutoff = parse_age(now, optval);
@@ -1250,7 +1266,22 @@ int main(int argc, char **argv)
            if (pathlen > 0 && querydir[pathlen-1] == pathsep)
                querydir[--pathlen] = '\0';
 
-           text_query(mappedfile, querydir, textcutoff, showfiles, tqdepth);
+           if (!gotdepth)
+               depth = 1;             /* default for text mode */
+           if (outfile != NULL) {
+               FILE *fp = fopen(outfile, "w");
+               if (!fp) {
+                   fprintf(stderr, "%s: %s: open: %s\n", PNAME,
+                           outfile, strerror(errno));
+                   return 1;
+               }
+               text_query(mappedfile, querydir, textcutoff, showfiles,
+                          depth, fp);
+               fclose(fp);
+           } else {
+               text_query(mappedfile, querydir, textcutoff, showfiles,
+                          depth, stdout);
+           }
 
            munmap(mappedfile, totalsize);
        } else if (mode == HTML) {
@@ -1302,14 +1333,59 @@ int main(int argc, char **argv)
            } else if (!index_has_root(mappedfile, xi)) {
                fprintf(stderr, "%s: pathname '%s' is"
                        " a file, not a directory\n", PNAME, querydir);
-           } else {
+           } else if (!gotdepth) {
+               /*
+                * Single output file.
+                */
                cfg.format = NULL;
+               cfg.rootpage = NULL;
                cfg.autoage = htmlautoagerange;
                cfg.oldest = htmloldest;
                cfg.newest = htmlnewest;
                cfg.showfiles = showfiles;
-               html = html_query(mappedfile, xi, &cfg);
-               fputs(html, stdout);
+               html = html_query(mappedfile, xi, &cfg, 0);
+               if (outfile != NULL) {
+                   FILE *fp = fopen(outfile, "w");
+                   if (!fp) {
+                       fprintf(stderr, "%s: %s: open: %s\n", PNAME,
+                               outfile, strerror(errno));
+                       return 1;
+                   } else if (fputs(html, fp) < 0) {
+                       fprintf(stderr, "%s: %s: write: %s\n", PNAME,
+                               outfile, strerror(errno));
+                       fclose(fp);
+                       return 1;
+                   } else if (fclose(fp) < 0) {
+                       fprintf(stderr, "%s: %s: fclose: %s\n", PNAME,
+                               outfile, strerror(errno));
+                       return 1;
+                   }
+               } else {
+                   fputs(html, stdout);
+               }
+           } else {
+               /*
+                * Multiple output files.
+                */
+               int dirlen = outfile ? 2+strlen(outfile) : 3;
+               char prefix[dirlen];
+               if (outfile)
+                   snprintf(prefix, dirlen, "%s/", outfile);
+               else
+                   snprintf(prefix, dirlen, "./");
+
+               unsigned long xi2;
+               make_successor(pathbuf);
+               xi2 = trie_before(mappedfile, pathbuf);
+
+               cfg.format = "%lu.html";
+               cfg.rootpage = "index.html";
+               cfg.autoage = htmlautoagerange;
+               cfg.oldest = htmloldest;
+               cfg.newest = htmlnewest;
+               cfg.showfiles = showfiles;
+               if (html_dump(mappedfile, xi, xi2, depth, &cfg, prefix))
+                   return 1;
            }
 
            munmap(mappedfile, totalsize);
@@ -1372,6 +1448,7 @@ int main(int argc, char **argv)
            dcfg.port = httpserverport;
            dcfg.basicauthdata = httpauthdata;
            pcfg.format = NULL;
+           pcfg.rootpage = NULL;
            pcfg.autoage = htmlautoagerange;
            pcfg.oldest = htmloldest;
            pcfg.newest = htmlnewest;
diff --git a/html.c b/html.c
index dacd68c..8293374 100644 (file)
--- a/html.c
+++ b/html.c
@@ -18,7 +18,7 @@ struct html {
     char *path2;
     char *href;
     size_t hreflen;
-    const char *format;
+    const char *format, *rootpage;
     unsigned long long thresholds[MAXCOLOUR];
     char *titletexts[MAXCOLOUR+1];
     time_t now;
@@ -334,6 +334,16 @@ static void compute_display_size(unsigned long long size,
     *fmt = fmts[shift];
 }
 
+static void make_filename(char *buf, size_t buflen,
+                         const char *format, const char *rootpage,
+                         unsigned long index)
+{
+    if (index == 0 && rootpage)
+       snprintf(buf, buflen, "%s", rootpage);
+    else
+       snprintf(buf, buflen, format, index);
+}
+
 #define PIXEL_SIZE 600                /* FIXME: configurability? */
 static void write_report_line(struct html *ctx, struct vector *vec)
 {
@@ -399,7 +409,9 @@ static void write_report_line(struct html *ctx, struct vector *vec)
        int doing_href = 0;
 
        if (ctx->format && vec->want_href) {
-           snprintf(ctx->href, ctx->hreflen, ctx->format, vec->index);
+           make_filename(ctx->href, ctx->hreflen,
+                         ctx->format, ctx->rootpage,
+                         vec->index);
            htprintf(ctx, "<a href=\"%s\">", ctx->href);
            doing_href = 1;
        }
@@ -427,7 +439,7 @@ int strcmptrailingpathsep(const char *a, const char *b)
 }
 
 char *html_query(const void *t, unsigned long index,
-                const struct html_config *cfg)
+                const struct html_config *cfg, int downlink)
 {
     struct html actx, *ctx = &actx;
     char *path, *path2, *p, *q, *href;
@@ -446,6 +458,7 @@ char *html_query(const void *t, unsigned long index,
     ctx->buflen = ctx->bufsize = 0;
     ctx->t = t;
     ctx->format = cfg->format;
+    ctx->rootpage = cfg->rootpage;
     htprintf(ctx, "<html>\n");
 
     path = snewn(1+trie_maxpathlen(t), char);
@@ -502,7 +515,7 @@ char *html_query(const void *t, unsigned long index,
        index2 = trie_before(t, path);
        trie_getpath(t, index2, path2);
        if (!strcmptrailingpathsep(path, path2) && cfg->format) {
-           snprintf(href, hreflen, cfg->format, index2);
+           make_filename(href, hreflen, cfg->format, cfg->rootpage, index2);
            if (!*href)                /* special case that we understand */
                strcpy(href, "./");
            htprintf(ctx, "<a href=\"%s\">", href);
@@ -611,7 +624,7 @@ char *html_query(const void *t, unsigned long index,
            vecs = sresize(vecs, vecsize, struct vector *);
        }
        assert(strlen(path2) > pathlen);
-       vecs[nvecs] = make_vector(ctx, path2, (xj2 - xj1 > 1), 0,
+       vecs[nvecs] = make_vector(ctx, path2, downlink && (xj2 - xj1 > 1), 0,
                                  path2 + subdirpos, 1);
        for (i = 0; i <= MAXCOLOUR; i++)
            vecs[0]->sizes[i] -= vecs[nvecs]->sizes[i];
@@ -644,3 +657,68 @@ char *html_query(const void *t, unsigned long index,
 
     return ctx->buf;
 }
+
+int html_dump(const void *t, unsigned long index, unsigned long endindex,
+             int maxdepth, const struct html_config *cfg,
+             const char *pathprefix)
+{
+    /*
+     * Determine the filename for this file.
+     */
+    assert(cfg->format != NULL);
+    int prefixlen = strlen(pathprefix);
+    int fnmax = strlen(pathprefix) + strlen(cfg->format) + 100;
+    char filename[fnmax];
+    strcpy(filename, pathprefix);
+    make_filename(filename + prefixlen, fnmax - prefixlen,
+                 cfg->format, cfg->rootpage, index);
+
+    /*
+     * Create the HTML itself. Don't write out downlinks from our
+     * deepest level.
+     */
+    char *html = html_query(t, index, cfg, maxdepth != 0);
+
+    /*
+     * Write it out.
+     */
+    FILE *fp = fopen(filename, "w");
+    if (!fp) {
+       fprintf(stderr, "%s: %s: open: %s\n", PNAME,
+               filename, strerror(errno));
+       return 1;
+    }
+    if (fputs(html, fp) < 0) {
+       fprintf(stderr, "%s: %s: write: %s\n", PNAME,
+               filename, strerror(errno));
+       fclose(fp);
+       return 1;
+    }
+    if (fclose(fp) < 0) {
+       fprintf(stderr, "%s: %s: fclose: %s\n", PNAME,
+               filename, strerror(errno));
+       return 1;
+    }
+
+    /*
+     * Recurse.
+     */
+    if (maxdepth != 0) {
+       unsigned long subindex, subendindex;
+       int newdepth = (maxdepth > 0 ? maxdepth - 1 : maxdepth);
+       char path[1+trie_maxpathlen(t)];
+
+       index++;
+       while (index < endindex) {
+           trie_getpath(t, index, path);
+           get_indices(t, path, &subindex, &subendindex);
+           index = subendindex;
+           if (subendindex - subindex > 1) {
+               if (html_dump(t, subindex, subendindex, newdepth,
+                             cfg, pathprefix))
+                   return 1;
+           }
+       }
+    }
+    return 0;
+}
diff --git a/html.h b/html.h
index d99af0a..dc90c4b 100644 (file)
--- a/html.h
+++ b/html.h
@@ -14,6 +14,13 @@ struct html_config {
     const char *format;
 
     /*
+     * If "rootpage" is non-NULL, it overrides "format" to give a
+     * special name (e.g. "index.html") to the top-level page of the
+     * index.
+     */
+    const char *rootpage;
+
+    /*
      * Time stamps to assign to the extreme ends of the colour
      * scale. If "autoage" is true, they are ignored and the time
      * stamps are derived from the limits of the age data stored
@@ -34,6 +41,34 @@ struct html_config {
  * against the pathname at a given index. Returns a dynamically
  * allocated piece of memory containing the entire HTML document,
  * as an ordinary C zero-terminated string.
+ *
+ * 'downlink' is TRUE if hyperlinks should be generated for
+ * subdirectories. (This can also be disabled by setting cfg->format
+ * to NULL, but that also disables the upward hyperlinks to parent
+ * directories. Setting cfg->format to non-NULL but downlink to NULL
+ * will generate uplinks but no downlinks.)
  */
 char *html_query(const void *t, unsigned long index, 
-                const struct html_config *cfg);
+                const struct html_config *cfg, int downlink);
+
+/*
+ * Recursively output a dump of lots of HTML files which crosslink
+ * to each other. cfg->format and cfg->rootpath will be used to
+ * generate the filenames for both the hyperlinks and the output
+ * file names; the file names will have "pathprefix" prepended to
+ * them before being opened.
+ *
+ * "index" and "endindex" point to the region of index file that
+ * should be generated by the dump, which must be a subdirectory.
+ *
+ * "maxdepth" limits the depth of recursion. Setting it to zero
+ * outputs only one page, 1 outputs the current directory and its
+ * immediate children but no further, and so on. Making it negative
+ * gives unlimited depth.
+ *
+ * Return value is 0 on success, or 1 if an error occurs during
+ * output.
+ */
+int html_dump(const void *t, unsigned long index, unsigned long endindex,
+             int maxdepth, const struct html_config *cfg,
+             const char *pathprefix);
diff --git a/httpd.c b/httpd.c
index e5ee270..90d4e42 100644 (file)
--- a/httpd.c
+++ b/httpd.c
@@ -277,7 +277,7 @@ char *got_data(struct connctx *ctx, char *data, int length,
                ret = http_error("404", "Not Found", NULL,
                                 "This is not a valid pathname index.");
            } else {
-               document = html_query(ctx->t, index, cfg);
+               document = html_query(ctx->t, index, cfg, 1);
                if (document) {
                    ret = http_success("text/html", 1, document);
                    sfree(document);