stagit-gopher

static git page generator for gopher
git clone git://git.2f30.org/stagit-gopher.git
Log | Files | Refs | README | LICENSE

commit caa55f2bb419bd2f5327a779fe9afe13f7b4ed38
parent 54a8a7c1533ce60e3cbe8539bf1e801225d0fabd
Author: Hiltjo Posthuma <hiltjo@codemadness.org>
Date:   Sun Jun 11 18:42:03 +0200

gopher version (WIP)

Diffstat:
stagit.c | 329++++++++++++++++++++++++++++++++++++++++---------------------------------------
1 file changed, 165 insertions(+), 164 deletions(-)
diff --git a/stagit.c b/stagit.c @@ -248,6 +248,56 @@ xmlencode(FILE *fp, const char *s, size_t len) } } +/* Escape characters in text in geomyidae .gph format */ +void +gphtext(FILE *fp, const char *s, size_t len) +{ + size_t i, n = 0; + + for (i = 0; *s && i < len; i++) { + if (s[i] == '\n') + n = 0; + + /* escape 't' at the start of a line */ + if (!n && s[i] == 't') { + fputc('t', fp); + n = 1; + } + + if (s[i] == '\t') { + fputs(" ", fp); + } else { + fputc(s[i], fp); + } + n++; + } +} + +/* Escape characters in links in geomyidae .gph format */ +void +gphlink(FILE *fp, const char *s, size_t len) +{ + size_t i; + + for (i = 0; *s && i < len; i++) { + switch (s[i]) { + case '\n': + /* in this context replace newline with space */ + fputc(' ', fp); + break; + case '\r': /* ignore CR */ + case '|': /* ignore separators for now */ + break; + case '\t': + fputs(" ", fp); + break; + default: + fputc(s[i], fp); + break; + } + } +} + int mkdirp(const char *path) { @@ -318,111 +368,102 @@ printtimeshort(FILE *fp, const git_time *intime) void writeheader(FILE *fp, const char *title) { - fputs("<!DOCTYPE html>\n" - "<html>\n<head>\n" - "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n" - "<title>", fp); - xmlencode(fp, title, strlen(title)); + gphtext(fp, title, strlen(title)); if (title[0] && strippedname[0]) fputs(" - ", fp); - xmlencode(fp, strippedname, strlen(strippedname)); + gphtext(fp, strippedname, strlen(strippedname)); if (description[0]) fputs(" - ", fp); - xmlencode(fp, description, strlen(description)); - fprintf(fp, "</title>\n<link rel=\"icon\" type=\"image/png\" href=\"%sfavicon.png\" />\n", relpath); - fprintf(fp, "<link rel=\"alternate\" type=\"application/atom+xml\" title=\"%s Atom Feed\" href=\"%satom.xml\" />\n", - name, relpath); - fprintf(fp, "<link rel=\"stylesheet\" type=\"text/css\" href=\"%sstyle.css\" />\n", relpath); - fputs("</head>\n<body>\n<table><tr><td>", fp); - fprintf(fp, "<a href=\"../%s\"><img src=\"%slogo.png\" alt=\"\" width=\"32\" height=\"32\" /></a>", - relpath, relpath); - fputs("</td><td><h1>", fp); - xmlencode(fp, strippedname, strlen(strippedname)); - fputs("</h1><span class=\"desc\">", fp); - xmlencode(fp, description, strlen(description)); - fputs("</span></td></tr>", fp); + gphtext(fp, description, strlen(description)); + fputs("\n", fp); if (cloneurl[0]) { - fputs("<tr class=\"url\"><td></td><td>git clone <a href=\"", fp); - xmlencode(fp, cloneurl, strlen(cloneurl)); - fputs("\">", fp); - xmlencode(fp, cloneurl, strlen(cloneurl)); - fputs("</a></td></tr>", fp); + fputs("[1|git clone ", fp); + gphlink(fp, cloneurl, strlen(cloneurl)); + fputs("|", fp); + gphlink(fp, cloneurl, strlen(cloneurl)); + fputs("|server|port]\n", fp); } - fputs("<tr><td></td><td>\n", fp); - fprintf(fp, "<a href=\"%slog.html\">Log</a> | ", relpath); - fprintf(fp, "<a href=\"%sfiles.html\">Files</a> | ", relpath); - fprintf(fp, "<a href=\"%srefs.html\">Refs</a>", relpath); + fprintf(fp, "[1|Log|%slog.gph|server|port]\n", relpath); + fprintf(fp, "[1|Files|%sfiles.gph|server|port]\n", relpath); + fprintf(fp, "[1|Refs|%srefs.gph|server|port]\n", relpath); if (hassubmodules) - fprintf(fp, " | <a href=\"%sfile/.gitmodules.html\">Submodules</a>", relpath); + fprintf(fp, "[1|Submodules|%sfile/.gitmodules.gph|server|port]\n", relpath); if (hasreadme) - fprintf(fp, " | <a href=\"%sfile/README.html\">README</a>", relpath); + fprintf(fp, "[1|README|%sfile/README.gph|server|port]\n", relpath); if (haslicense) - fprintf(fp, " | <a href=\"%sfile/LICENSE.html\">LICENSE</a>", relpath); - fputs("</td></tr></table>\n<hr/>\n<div id=\"content\">\n", fp); + fprintf(fp, "[1|LICENSE|%sfile/LICENSE.gph|server|port]\n", relpath); + fputs("===\n", fp); } void writefooter(FILE *fp) { - fputs("</div>\n</body>\n</html>\n", fp); } int -writeblobhtml(FILE *fp, const git_blob *blob) +writeblobgph(FILE *fp, const git_blob *blob) { - size_t n = 0, i, prev; - const char *nfmt = "<a href=\"#l%d\" class=\"line\" id=\"l%d\">%7d</a> "; + size_t n = 0, i, j, prev; + const char *nfmt = "%8d "; const char *s = git_blob_rawcontent(blob); git_off_t len = git_blob_rawsize(blob); - fputs("<pre id=\"blob\">\n", fp); - if (len > 0) { for (i = 0, prev = 0; i < (size_t)len; i++) { if (s[i] != '\n') continue; n++; fprintf(fp, nfmt, n, n, n); - xmlencode(fp, &s[prev], i - prev + 1); + for (j = prev; s[j] && j <= i; j++) { + if (s[j] == '\t') + fputs(" ", fp); + else + fputc(s[j], fp); + } prev = i + 1; } /* trailing data */ if ((len - prev) > 0) { n++; fprintf(fp, nfmt, n, n, n); - xmlencode(fp, &s[prev], len - prev); + for (j = prev; s[j] && j < len - prev; j++) { + if (s[j] == '\t') + fputs(" ", fp); + else + fputc(s[j], fp); + } } } - fputs("</pre>\n", fp); - return n; } void printcommit(FILE *fp, struct commitinfo *ci) { - fprintf(fp, "<b>commit</b> <a href=\"%scommit/%s.html\">%s</a>\n", - relpath, ci->oid, ci->oid); + fprintf(fp, "[1|commit %s|%scommit/%s.gph|server|port]\n", + ci->oid, relpath, ci->oid); if (ci->parentoid[0]) - fprintf(fp, "<b>parent</b> <a href=\"%scommit/%s.html\">%s</a>\n", - relpath, ci->parentoid, ci->parentoid); + fprintf(fp, "[1|parent %s|%scommit/%s.gph|server|port]\n", + ci->parentoid, relpath, ci->parentoid); if (ci->author) { - fputs("<b>Author:</b> ", fp); - xmlencode(fp, ci->author->name, strlen(ci->author->name)); - fputs(" &lt;<a href=\"mailto:", fp); - xmlencode(fp, ci->author->email, strlen(ci->author->email)); - fputs("\">", fp); - xmlencode(fp, ci->author->email, strlen(ci->author->email)); - fputs("</a>&gt;\n<b>Date:</b> ", fp); + /* TODO: fix author email link to redirect as mailto: */ + fputs("[1|Author: ", fp); + gphlink(fp, ci->author->name, strlen(ci->author->name)); + fputs(" <", fp); + gphlink(fp, ci->author->email, strlen(ci->author->email)); + fputs(">|mailto:", fp); + gphlink(fp, ci->author->email, strlen(ci->author->email)); + fputs("|server|port]\n", fp); + fputs("Date: ", fp); printtime(fp, &(ci->author->when)); fputc('\n', fp); } if (ci->msg) { fputc('\n', fp); - xmlencode(fp, ci->msg, strlen(ci->msg)); + gphtext(fp, ci->msg, strlen(ci->msg)); fputc('\n', fp); } } @@ -446,19 +487,19 @@ printshowfile(FILE *fp, struct commitinfo *ci) ci->ndeltas > 1000 || ci->addcount > 100000 || ci->delcount > 100000) { - fputs("Diff is too large, output suppressed.\n", fp); + fputs("\nDiff is too large, output suppressed.\n", fp); return; } /* diff stat */ - fputs("<b>Diffstat:</b>\n<table>", fp); + fputs("Diffstat:\n", fp); for (i = 0; i < ci->ndeltas; i++) { delta = git_patch_get_delta(ci->deltas[i]->patch); - fprintf(fp, "<tr><td><a href=\"#h%zu\">", i); - xmlencode(fp, delta->old_file.path, strlen(delta->old_file.path)); + /* TODO: make file linkable */ + gphtext(fp, delta->old_file.path, strlen(delta->old_file.path)); if (strcmp(delta->old_file.path, delta->new_file.path)) { - fputs(" -&gt; ", fp); - xmlencode(fp, delta->new_file.path, strlen(delta->new_file.path)); + fputs(" -> ", fp); + gphtext(fp, delta->new_file.path, strlen(delta->new_file.path)); } add = ci->deltas[i]->addcount; @@ -474,26 +515,26 @@ printshowfile(FILE *fp, struct commitinfo *ci) memset(&linestr, '+', add); memset(&linestr[add], '-', del); - fprintf(fp, "</a></td><td> | </td><td class=\"num\">%zu</td><td><span class=\"i\">", + fprintf(fp, " | %zu ", ci->deltas[i]->addcount + ci->deltas[i]->delcount); fwrite(&linestr, 1, add, fp); - fputs("</span><span class=\"d\">", fp); fwrite(&linestr[add], 1, del, fp); - fputs("</span></td></tr>\n", fp); + fputs("\n", fp); } - fprintf(fp, "</table></pre><pre>%zu file%s changed, %zu insertion%s(+), %zu deletion%s(-)\n", + fprintf(fp, "\n%zu file%s changed, %zu insertion%s(+), %zu deletion%s(-)\n", ci->filecount, ci->filecount == 1 ? "" : "s", ci->addcount, ci->addcount == 1 ? "" : "s", ci->delcount, ci->delcount == 1 ? "" : "s"); - fputs("<hr/>", fp); + fputs("===\n", fp); for (i = 0; i < ci->ndeltas; i++) { patch = ci->deltas[i]->patch; delta = git_patch_get_delta(patch); - fprintf(fp, "<b>diff --git a/<a id=\"h%zu\" href=\"%sfile/%s.html\">%s</a> b/<a href=\"%sfile/%s.html\">%s</a></b>\n", - i, relpath, delta->old_file.path, delta->old_file.path, - relpath, delta->new_file.path, delta->new_file.path); + /* NOTE: only links to new path */ + fprintf(fp, "[1|diff --git a/%s b/%s", + delta->old_file.path, delta->new_file.path); + fprintf(fp, "|%sfile/%s.gph|server|port]\n", relpath, delta->new_file.path); /* check binary data */ if (delta->flags & GIT_DIFF_FLAG_BINARY) { @@ -506,24 +547,18 @@ printshowfile(FILE *fp, struct commitinfo *ci) if (git_patch_get_hunk(&hunk, &nhunklines, patch, j)) break; - fprintf(fp, "<a href=\"#h%zu-%zu\" id=\"h%zu-%zu\" class=\"h\">", i, j, i, j); - xmlencode(fp, hunk->header, hunk->header_len); - fputs("</a>", fp); + gphtext(fp, hunk->header, hunk->header_len); for (k = 0; ; k++) { if (git_patch_get_line_in_hunk(&line, patch, j, k)) break; if (line->old_lineno == -1) - fprintf(fp, "<a href=\"#h%zu-%zu-%zu\" id=\"h%zu-%zu-%zu\" class=\"i\">+", - i, j, k, i, j, k); + fputs("+", fp); else if (line->new_lineno == -1) - fprintf(fp, "<a href=\"#h%zu-%zu-%zu\" id=\"h%zu-%zu-%zu\" class=\"d\">-", - i, j, k, i, j, k); + fputs("-", fp); else - fputc(' ', fp); - xmlencode(fp, line->content, line->content_len); - if (line->old_lineno == -1 || line->new_lineno == -1) - fputs("</a>", fp); + fputs(" ", fp); + gphtext(fp, line->content, line->content_len); } } } @@ -532,25 +567,20 @@ printshowfile(FILE *fp, struct commitinfo *ci) void writelogline(FILE *fp, struct commitinfo *ci) { - fputs("<tr><td>", fp); + fputs("[1|", fp); if (ci->author) printtimeshort(fp, &(ci->author->when)); - fputs("</td><td>", fp); - if (ci->summary) { - fprintf(fp, "<a href=\"%scommit/%s.html\">", relpath, ci->oid); - xmlencode(fp, ci->summary, strlen(ci->summary)); - fputs("</a>", fp); - } - fputs("</td><td>", fp); + fputs(" ", fp); + if (ci->summary) + gphlink(fp, ci->summary, strlen(ci->summary)); + fputs(" ", fp); if (ci->author) - xmlencode(fp, ci->author->name, strlen(ci->author->name)); - fputs("</td><td class=\"num\" align=\"right\">", fp); - fprintf(fp, "%zu", ci->filecount); - fputs("</td><td class=\"num\" align=\"right\">", fp); - fprintf(fp, "+%zu", ci->addcount); - fputs("</td><td class=\"num\" align=\"right\">", fp); - fprintf(fp, "-%zu", ci->delcount); - fputs("</td></tr>\n", fp); + gphlink(fp, ci->author->name, strlen(ci->author->name)); + fprintf(fp, " %zu", ci->filecount); + fprintf(fp, " +%zu", ci->addcount); + fprintf(fp, " -%zu", ci->delcount); + fprintf(fp, "|%scommit/%s.gph", relpath, ci->oid); + fputs("|server|port]\n", fp); } int @@ -569,8 +599,6 @@ writelog(FILE *fp, const git_oid *oid) git_revwalk_simplify_first_parent(w); while (!git_revwalk_next(&id, w)) { - relpath = ""; - if (cachefile && !memcmp(&id, &lastoid, sizeof(id))) break; if (!(ci = commitinfo_getbyoid(&id))) @@ -580,19 +608,15 @@ writelog(FILE *fp, const git_oid *oid) if (cachefile) writelogline(wcachefp, ci); - relpath = "../"; - - r = snprintf(path, sizeof(path), "commit/%s.html", ci->oid); + r = snprintf(path, sizeof(path), "commit/%s.gph", ci->oid); if (r == -1 || (size_t)r >= sizeof(path)) - errx(1, "path truncated: 'commit/%s.html'", ci->oid); + errx(1, "path truncated: 'commit/%s.gph'", ci->oid); /* check if file exists if so skip it */ if (access(path, F_OK)) { fpfile = efopen(path, "w"); writeheader(fpfile, ci->summary); - fputs("<pre>", fpfile); printshowfile(fpfile, ci); - fputs("</pre>\n", fpfile); writefooter(fpfile); fclose(fpfile); } @@ -600,8 +624,6 @@ writelog(FILE *fp, const git_oid *oid) } git_revwalk_free(w); - relpath = ""; - return 0; } @@ -626,7 +648,7 @@ printcommitatom(FILE *fp, struct commitinfo *ci) xmlencode(fp, ci->summary, strlen(ci->summary)); fputs("</title>\n", fp); } - fprintf(fp, "<link rel=\"alternate\" type=\"text/html\" href=\"commit/%s.html\" />", + fprintf(fp, "<link rel=\"alternate\" type=\"text/html\" href=\"commit/%s.gph\" />", ci->oid); if (ci->author) { @@ -694,7 +716,6 @@ int writeblob(git_object *obj, const char *fpath, const char *filename, git_off_t filesize) { char tmp[PATH_MAX] = "", *d; - const char *p; int lc = 0; FILE *fp; @@ -705,31 +726,22 @@ writeblob(git_object *obj, const char *fpath, const char *filename, git_off_t fi if (mkdirp(d)) return -1; - for (p = fpath, tmp[0] = '\0'; *p; p++) { - if (*p == '/' && strlcat(tmp, "../", sizeof(tmp)) >= sizeof(tmp)) - errx(1, "path truncated: '../%s'", tmp); - } - relpath = tmp; - fp = efopen(fpath, "w"); writeheader(fp, filename); - fputs("<p> ", fp); - xmlencode(fp, filename, strlen(filename)); - fprintf(fp, " (%juB)", (uintmax_t)filesize); - fputs("</p><hr/>", fp); + gphtext(fp, filename, strlen(filename)); + fprintf(fp, " (%juB)\n", (uintmax_t)filesize); + fputs("===\n", fp); if (git_blob_is_binary((git_blob *)obj)) { - fputs("<p>Binary file.</p>\n", fp); + fputs("Binary file.\n", fp); } else { - lc = writeblobhtml(fp, (git_blob *)obj); + lc = writeblobgph(fp, (git_blob *)obj); if (ferror(fp)) err(1, "fwrite"); } writefooter(fp); fclose(fp); - relpath = ""; - return lc; } @@ -794,10 +806,10 @@ writefilestree(FILE *fp, git_tree *tree, const char *path) return -1; joinpath(entrypath, sizeof(entrypath), path, entryname); - r = snprintf(filepath, sizeof(filepath), "file/%s.html", + r = snprintf(filepath, sizeof(filepath), "file/%s.gph", entrypath); if (r == -1 || (size_t)r >= sizeof(filepath)) - errx(1, "path truncated: 'file/%s.html'", entrypath); + errx(1, "path truncated: 'file/%s.gph'", entrypath); if (!git_tree_entry_to_object(&obj, repo, entry)) { switch (git_object_type(obj)) { @@ -819,23 +831,24 @@ writefilestree(FILE *fp, git_tree *tree, const char *path) filesize = git_blob_rawsize((git_blob *)obj); lc = writeblob(obj, filepath, entryname, filesize); - fputs("<tr><td>", fp); + fputs("[1|", fp); fputs(filemode(git_tree_entry_filemode(entry)), fp); - fprintf(fp, "</td><td><a href=\"%s%s\">", relpath, filepath); - xmlencode(fp, entrypath, strlen(entrypath)); - fputs("</a></td><td class=\"num\" align=\"right\">", fp); + fputs(" ", fp); + gphlink(fp, entrypath, strlen(entrypath)); + fputs(" ", fp); if (lc > 0) fprintf(fp, "%dL", lc); else fprintf(fp, "%juB", (uintmax_t)filesize); - fputs("</td></tr>\n", fp); + fprintf(fp, "|%s%s", relpath, filepath); + fputs("|server|port]\n", fp); git_object_free(obj); } else if (!git_submodule_lookup(&module, repo, entryname)) { - fprintf(fp, "<tr><td>m---------</td><td><a href=\"%sfile/.gitmodules.html\">", - relpath); - xmlencode(fp, entrypath, strlen(entrypath)); + fputs("[1|m--------- ", fp); + gphlink(fp, entrypath, strlen(entrypath)); + fprintf(fp, "|%sfile/.gitmodules.gph|server|port]\n", relpath); + /* NOTE: linecount omitted */ git_submodule_free(module); - fputs("</a></td><td class=\"num\" align=\"right\"></td></tr>\n", fp); } } @@ -849,17 +862,12 @@ writefiles(FILE *fp, const git_oid *id) git_commit *commit = NULL; int ret = -1; - fputs("<table id=\"files\"><thead>\n<tr>" - "<td><b>Mode</b></td><td><b>Name</b></td>" - "<td class=\"num\" align=\"right\"><b>Size</b></td>" - "</tr>\n</thead><tbody>\n", fp); + fputs("Mode Name Size\n", fp); if (!git_commit_lookup(&commit, repo, id) && !git_commit_tree(&tree, commit)) ret = writefilestree(fp, tree, ""); - fputs("</tbody></table>", fp); - git_commit_free(commit); git_tree_free(tree); @@ -935,28 +943,21 @@ writerefs(FILE *fp) /* print header if it has an entry (first). */ if (++count == 1) { - fprintf(fp, "<h2>%s</h2><table id=\"%s\">" - "<thead>\n<tr><td><b>Name</b></td>" - "<td><b>Last commit date</b></td>" - "<td><b>Author</b></td>\n</tr>\n" - "</thead><tbody>\n", - titles[j], ids[j]); + gphtext(fp, titles[j], strlen(titles[j])); + fputs("Name Last commit date Author\n\n", fp); } - relpath = ""; name = git_reference_shorthand(r); - fputs("<tr><td>", fp); + fputs(" ", fp); xmlencode(fp, name, strlen(name)); - fputs("</td><td>", fp); + fputs(" ", fp); if (ci->author) printtimeshort(fp, &(ci->author->when)); - fputs("</td><td>", fp); + fputs(" ", fp); if (ci->author) xmlencode(fp, ci->author->name, strlen(ci->author->name)); - fputs("</td></tr>\n", fp); - - relpath = "../"; + fputs("\n", fp); commitinfo_free(ci); git_object_free(obj); @@ -966,7 +967,7 @@ writerefs(FILE *fp) } /* table footer */ if (count) - fputs("</tbody></table><br/>", fp); + fputs("\n", fp); } err: @@ -987,6 +988,7 @@ usage(char *argv0) exit(1); } +/* TODO: add base argument, gopher does not support relative urls, document it too */ int main(int argc, char *argv[]) { @@ -1011,6 +1013,10 @@ main(int argc, char *argv[]) if (i + 1 >= argc) usage(argv[0]); cachefile = argv[++i]; + } else if (argv[i][1] == 'b') { + if (i + 1 >= argc) + usage(argv[0]); + relpath = argv[++i]; } } if (!repodir) @@ -1090,15 +1096,12 @@ main(int argc, char *argv[]) git_object_free(obj); /* log for HEAD */ - fp = efopen("log.html", "w"); - relpath = ""; + fp = efopen("log.gph", "w"); mkdir("commit", 0755); writeheader(fp, "Log"); - fputs("<table id=\"log\"><thead>\n<tr><td><b>Date</b></td>" - "<td><b>Commit message</b></td>" - "<td><b>Author</b></td><td class=\"num\" align=\"right\"><b>Files</b></td>" - "<td class=\"num\" align=\"right\"><b>+</b></td>" - "<td class=\"num\" align=\"right\"><b>-</b></td></tr>\n</thead><tbody>\n", fp); + fputs("Date " + "Commit message " + "Author Files + -\n", fp); if (cachefile) { /* read from cache file (does not need to exist) */ @@ -1121,7 +1124,7 @@ main(int argc, char *argv[]) writelog(fp, head); if (rcachefp) { - /* append previous log to log.html and the new cache */ + /* append previous log to log.gph and the new cache */ while (!feof(rcachefp)) { n = fread(buf, 1, sizeof(buf), rcachefp); if (ferror(rcachefp)) @@ -1137,13 +1140,11 @@ main(int argc, char *argv[]) if (head) writelog(fp, head); } - - fputs("</tbody></table>", fp); writefooter(fp); fclose(fp); /* files for HEAD */ - fp = efopen("files.html", "w"); + fp = efopen("files.gph", "w"); writeheader(fp, "Files"); if (head) writefiles(fp, head); @@ -1151,7 +1152,7 @@ main(int argc, char *argv[]) fclose(fp); /* summary page with branches and tags */ - fp = efopen("refs.html", "w"); + fp = efopen("refs.gph", "w"); writeheader(fp, "Refs"); writerefs(fp); writefooter(fp);