commit 546c6d82185ea286e7365c7abe137be2ef23045d
parent 022c85fdcf44b9b8723ce6983c62d585e2563c9d
Author: Emma Weaver <emma@waeaves.com>
Date: Sun, 17 May 2026 17:03:00 -0400
Binary file previews, more prettying
Diffstat:
| M | TODO | 35 | +++++++++++++++-------------------- |
| M | stagit.c | 153 | +++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------- |
| M | style.css | 5 | ++--- |
3 files changed, 117 insertions(+), 76 deletions(-)
diff --git a/TODO b/TODO
@@ -1,30 +1,25 @@
-STUFF I WANT TO CHANGE ABOUT THIS
+-STUFF I WANT TO CHANGE ABOUT STAGIT-
+(might rename it to "statigit" also.)
[ ] Redesign options / I/O
- - Usage: stagit [-i] [-d DESTDIR] [-c CACHEDIR] REPO [REPO_2 ... REPO_N]
- - generates index page if -i
- - puts repos in their own subdirectory if -i or REPO_2
- - writes output into DESTDIR (defaults to pwd)
+ - Usage: stagit [-i] [-d DESTDIR] [-c CACHEDIR] REPO [REPO_2 ... REPO_N]
+ - generates index page if -i
+ - puts repos in their own subdirectory if -i or REPO_2
+ - writes output into DESTDIR (defaults to pwd)
-[ ] add warnings on missing decorations
- - flag that skips repos with incomplete metadata
- - (possible mechanism to hide private repos)
+[ ] handle missing decorations (desc/logo/favicon) better
[ ] create config.h file
- - reused format strings (such as header)
+ - reused format strings (such as header)
-[ ] handle projects with large numbers of files better
- - show "{total LoC}L + {total binary B}B" for each directory
- - (VERY IMPORTANT to still punish large projects)
-
- - NEW FEATURES
+---NEW FEATURES---
[ ] add lines of code totals on files page
-[ ] add lines of code to top of each file's page
-[ ] allow download of arbitrary binary files
-[ ] display images on their file page (png, jpg, webp, which others?)
-[ ] / add support for projects with folders in them
-[ ] remove leading dot from links, so they can be served more easily
- - END NEW FEATURES
+[ ] add lines of code on each file's page
+[x] include binary files in static site + link to them
+[ ] add support for projects with folders in them
+ - show totals "123L + 456B" for each directory
+[ ] tweak paths that start with dots, so that they don't get hidden or 403'd
+---END NEW FEATURES---
[ ] Redo caching, to never repeat work
- but it needs to also not increase the complexity.
diff --git a/stagit.c b/stagit.c
@@ -16,7 +16,8 @@
#include "compat.h"
-#define LEN(s) (sizeof(s)/sizeof(*s))
+#define SPUTF(f, s) fputs((s), (f))
+#define LENGTH(s) (sizeof(s)/sizeof(*s))
struct DeltaInfo {
git_patch *patch;
@@ -525,7 +526,7 @@ printtimeshort(FILE *fp, const git_time *intime)
}
void
-write_header(FILE *fp, const char *title)
+put_header(FILE *fp, const char *title)
{
fputs(
"<!DOCTYPE html>\n"
@@ -612,40 +613,41 @@ write_header(FILE *fp, const char *title)
}
void
-write_footer(FILE *fp)
+put_footer(FILE *fp)
{
fputs("</div>" "\n" "</body>" "\n" "</html>" "\n", fp);
}
size_t
-write_blob_html(FILE *fp, const git_blob *blob)
+put_file_html(FILE *fp, const git_blob *blob)
{
size_t n = 0, i, len, prev;
- const char *nfmt = "<a href=\"#l%zu\" class=\"line\" id=\"l%zu\">%7zu</a>";
+ // TODO use spaces + pre to right-align the line numbers, instead of css
+ const char *number_format = "<a href=\"#l%zu\" class=\"line\" id=\"l%zu\">%7zu </a>";
const char *s = git_blob_rawcontent(blob);
len = git_blob_rawsize(blob);
- fputs("<pre id=\"blob\">" "\n", fp);
+ SPUTF(fp, "<pre id=\"blob\">" "\n");
if (len > 0) {
for (i = 0, prev = 0; i < len; i++) {
if (s[i] != '\n')
continue;
n++;
- fprintf(fp, nfmt, n, n, n);
+ fprintf(fp, number_format, n, n, n);
put_xml_line(fp, &s[prev], i - prev + 1);
- fputs("\n", fp);
+ SPUTF(fp, "\n");
prev = i + 1;
}
/* trailing data */
if ((len - prev) > 0) {
n++;
- fprintf(fp, nfmt, n, n, n);
+ fprintf(fp, number_format, n, n, n);
put_xml_line(fp, &s[prev], len - prev);
}
}
- fputs("</pre>" "\n", fp);
+ SPUTF(fp, "</pre>" "\n");
return n;
}
@@ -798,11 +800,17 @@ print_show_file(FILE *fp, struct CommitInfo *ci)
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);
+ fprintf(
+ fp,
+ "<a href=\"#h%zu-%zu-%zu\" id=\"h%zu-%zu-%zu\" class=\"i\">+",
+ i, j, k, i, j, k
+ );
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);
+ fprintf(
+ fp,
+ "<a href=\"#h%zu-%zu-%zu\" id=\"h%zu-%zu-%zu\" class=\"d\">-",
+ i, j, k, i, j, k
+ );
else
putc(' ', fp);
put_xml_line(fp, line->content, line->content_len);
@@ -891,13 +899,13 @@ write_log(FILE *fp, const git_oid *oid)
if (r) {
relpath = "../";
fpfile = efopen(path, "w");
- write_header(fpfile, ci->summary);
+ put_header(fpfile, ci->summary);
fputs("<pre>", fpfile);
print_show_file(fpfile, ci);
fputs("</pre>\n", fpfile);
- write_footer(fpfile);
+ put_footer(fpfile);
check_file_error(fpfile, path, 'w');
fclose(fpfile);
}
@@ -988,7 +996,7 @@ write_log_page(FILE *fp, const git_oid *head)
{
relpath = "";
mkdir("commit", S_IRWXU | S_IRWXG | S_IRWXO);
- write_header(fp, "Log");
+ put_header(fp, "Log");
fputs(
"<table id=\"log\"><thead>" "\n"
"<tr>"
@@ -1007,7 +1015,7 @@ write_log_page(FILE *fp, const git_oid *head)
if (head && cachefile) write_log_cached(fp, head);
fputs("</tbody></table>", fp);
- write_footer(fp);
+ put_footer(fp);
}
void
@@ -1122,18 +1130,42 @@ write_atom(FILE *fp, int all)
return 0;
}
+void
+write_binary_file(const char *filepath, git_blob *blob) {
+ char dest[PATH_MAX] = "";
+ const char *raw = git_blob_rawcontent(blob);
+ int size = git_blob_rawsize(blob);
+
+ /* set dest */
+ join_path(dest, PATH_MAX, "file", filepath);
+
+ /* write the file! */
+ FILE *fp = efopen(dest, "w");
+ fwrite(raw, size, 1, fp);
+ check_file_error(fp, dest, 'w');
+ fclose(fp);
+}
+
size_t
-write_blob(git_object *obj, const char *fpath, const char *filename, size_t filesize)
-{
+write_blob(
+ git_object *obj,
+ const char *fpath, /* absolute path of page */
+ const char *entrypath, /* directory of file */
+ const char *filename, /* name of file */
+ size_t filesize
+) {
char tmp[PATH_MAX] = "", *d;
const char *p;
size_t lc = 0;
FILE *fp;
+ int is_binary;
if (strlcpy(tmp, fpath, sizeof(tmp)) >= sizeof(tmp))
errx(1, "path truncated: '%s'", fpath);
+
if (!(d = dirname(tmp)))
err(1, "dirname");
+
if (mkdirp(d))
return -1;
@@ -1143,20 +1175,31 @@ write_blob(git_object *obj, const char *fpath, const char *filename, size_t file
}
relpath = tmp;
+ is_binary = git_blob_is_binary((git_blob *)obj);
+ const char *file_url = is_binary ? filename : "";
+
fp = efopen(fpath, "w");
- write_header(fp, filename);
- fputs("<p> ", fp);
+ put_header(fp, filename);
+ fprintf(fp, "<p>");
put_xml(fp, filename, strlen(filename));
- fprintf(fp, " (%zuB)", filesize);
- fputs("</p>", fp);
+ fprintf(fp, " (%zuB)</p>", filesize); // TODO also show line number (if possible)
- if (git_blob_is_binary((git_blob *)obj))
- // TODO check and display image
- fputs("<p>Binary file.</p>\n", fp);
- else
- lc = write_blob_html(fp, (git_blob *)obj);
+ if (is_binary) {
+ fprintf(
+ fp,
+ "<p>Binary file: <a href=\"%s\">raw</a></p>" "\n"
+ "<p><object data=\"%s\">"
+ "no preview available."
+ "</object></p>" "\n",
+ file_url,
+ file_url
+ );
+ write_binary_file(entrypath, (git_blob *) obj);
+ } else {
+ lc = put_file_html(fp, (git_blob *) obj);
+ }
- write_footer(fp);
+ put_footer(fp);
check_file_error(fp, fpath, 'w');
fclose(fp);
@@ -1165,37 +1208,41 @@ write_blob(git_object *obj, const char *fpath, const char *filename, size_t file
return lc;
}
+char
+filemode_type(git_filemode_t m)
+{
+ if (S_ISREG(m)) return '-';
+ if (S_ISBLK(m)) return 'b';
+ if (S_ISCHR(m)) return 'c';
+ if (S_ISDIR(m)) return 'd';
+ if (S_ISFIFO(m)) return 'p';
+ if (S_ISLNK(m)) return 'l';
+ if (S_ISSOCK(m)) return 's';
+ return '?';
+}
+
const char *
filemode(git_filemode_t m)
{
static char mode[11];
memset(mode, '-', sizeof(mode) - 1);
- mode[10] = '\0';
-
- if (S_ISREG(m)) mode[0] = '-';
- else if (S_ISBLK(m)) mode[0] = 'b';
- else if (S_ISCHR(m)) mode[0] = 'c';
- else if (S_ISDIR(m)) mode[0] = 'd';
- else if (S_ISFIFO(m)) mode[0] = 'p';
- else if (S_ISLNK(m)) mode[0] = 'l';
- else if (S_ISSOCK(m)) mode[0] = 's';
- else mode[0] = '?';
-
+ mode[0] = filemode_type(m);
if (m & S_IRUSR) mode[1] = 'r';
if (m & S_IWUSR) mode[2] = 'w';
if (m & S_IXUSR) mode[3] = 'x';
+ if (m & S_ISUID) mode[3] = (mode[3] == 'x') ? 's' : 'S';
if (m & S_IRGRP) mode[4] = 'r';
if (m & S_IWGRP) mode[5] = 'w';
if (m & S_IXGRP) mode[6] = 'x';
+ if (m & S_ISGID) mode[6] = (mode[6] == 'x') ? 's' : 'S';
if (m & S_IROTH) mode[7] = 'r';
if (m & S_IWOTH) mode[8] = 'w';
if (m & S_IXOTH) mode[9] = 'x';
-
- if (m & S_ISUID) mode[3] = (mode[3] == 'x') ? 's' : 'S';
- if (m & S_ISGID) mode[6] = (mode[6] == 'x') ? 's' : 'S';
if (m & S_ISVTX) mode[9] = (mode[9] == 'x') ? 't' : 'T';
+ mode[10] = '\0';
+
return mode;
}
@@ -1227,11 +1274,11 @@ write_file_list(FILE *fp, git_tree *tree, const char *path)
case GIT_OBJ_BLOB:
break;
case GIT_OBJ_TREE:
- /* NOTE: recurses */
// TODO create page for each dir
// instead of dumping all files into "files.html"
// or, is that a good idea? hm...
- ret = write_file_list( fp, (git_tree *)obj, entrypath);
+ /* NOTE: recurses */
+ ret = write_file_list(fp, (git_tree *)obj, entrypath);
git_object_free(obj);
if (ret)
return ret;
@@ -1242,7 +1289,7 @@ write_file_list(FILE *fp, git_tree *tree, const char *path)
}
filesize = git_blob_rawsize((git_blob *)obj);
- lc = write_blob(obj, filepath, entryname, filesize);
+ lc = write_blob(obj, filepath, entrypath, entryname, filesize);
fputs("<tr><td>", fp);
fputs(filemode(git_tree_entry_filemode(entry)), fp);
@@ -1498,7 +1545,7 @@ main(int argc, char *argv[])
}
/* check LICENSE */
- for (i = 0; i < LEN(license_files) && !license; i++) {
+ for (i = 0; i < LENGTH(license_files) && !license; i++) {
if (
!git_revparse_single(&obj, repo, license_files[i]) &&
git_object_type(obj) == GIT_OBJ_BLOB
@@ -1508,7 +1555,7 @@ main(int argc, char *argv[])
}
/* check README */
- for (i = 0; i < LEN(readme_files) && !readme; i++) {
+ for (i = 0; i < LENGTH(readme_files) && !readme; i++) {
if (
!git_revparse_single(&obj, repo, readme_files[i]) &&
git_object_type(obj) == GIT_OBJ_BLOB
@@ -1532,17 +1579,17 @@ main(int argc, char *argv[])
/* files for HEAD */
fp = efopen("files.html", "w");
- write_header(fp, "Files");
+ put_header(fp, "Files");
if (head) write_files(fp, head);
- write_footer(fp);
+ put_footer(fp);
check_file_error(fp, "files.html", 'w');
fclose(fp);
/* summary page with branches and tags */
fp = efopen("refs.html", "w");
- write_header(fp, "Refs");
+ put_header(fp, "Refs");
write_refs(fp);
- write_footer(fp);
+ put_footer(fp);
check_file_error(fp, "refs.html", 'w');
fclose(fp);
diff --git a/style.css b/style.css
@@ -12,8 +12,8 @@ img, h1, h2 {
vertical-align: middle;
}
-img {
- border: 0;
+img, object {
+ border: 1px solid black;
}
a {
@@ -82,7 +82,6 @@ table#diffstat {
}
#blob a {
- text-align: right;
border-right: 1px solid black;
margin-right: 5px;
padding-right: 5px;