gout

A static git page generator
git clone https://git.bracken.jp/gout.git
Log | Files | Refs | README | LICENSE

commit c6e814bc389c90eef887b075d5790fc45c99f8e9
parent d429e285091db54f4b2d2bea157bd28a28355a93
Author: Chris Bracken <chris@bracken.jp>
Date:   Fri, 20 Feb 2026 20:18:45 +0900

git: extract special files list (README, etc.)

Diffstat:
Msrc/git/repo.c | 48++++++++++++++++++++++++++++++++++++------------
Msrc/git/repo.h | 13++++++++++---
Msrc/writer/gopher/page.c | 22++++++----------------
Msrc/writer/gopher/page_tests.c | 4+++-
Msrc/writer/gopher/repo_writer_tests.c | 3---
Msrc/writer/html/page.c | 22++++++----------------
Msrc/writer/html/page_tests.c | 4+++-
Msrc/writer/html/repo_writer_tests.c | 3---
8 files changed, 64 insertions(+), 55 deletions(-)

diff --git a/src/git/repo.c b/src/git/repo.c @@ -24,6 +24,7 @@ #include <unistd.h> #include "git/internal.h" +#include "third_party/openbsd/reallocarray.h" #include "utils.h" /* Local const data */ @@ -167,15 +168,35 @@ void gitrepo_load_filesystem_metadata(GitRepo* repo, free(url_path); } +static void gitrepo_add_special_file(GitRepo* repo, + const char* label, + const char* path) { + if (!path || path[0] == '\0') { + return; + } + repo->special_files = reallocarray( + repo->special_files, repo->special_files_len + 1, sizeof(RepoSpecialFile)); + if (!repo->special_files) { + err(1, "reallocarray"); + } + repo->special_files[repo->special_files_len].label = estrdup(label); + repo->special_files[repo->special_files_len].path = estrdup(path); + repo->special_files_len++; +} + static void gitrepo_load_git_metadata(GitRepo* repo, git_repository* git_repo) { assert(repo != NULL); assert(git_repo != NULL); - repo->submodules = estrdup( - gitrepo_has_blob(git_repo, "HEAD:.gitmodules") ? ".gitmodules" : ""); - repo->readme = - estrdup(gitrepo_first_matching_file(git_repo, kReadmes, kReadmesLen)); - repo->license = - estrdup(gitrepo_first_matching_file(git_repo, kLicenses, kLicensesLen)); + + if (gitrepo_has_blob(git_repo, "HEAD:.gitmodules")) { + gitrepo_add_special_file(repo, "Submodules", ".gitmodules"); + } + gitrepo_add_special_file( + repo, "README", + gitrepo_first_matching_file(git_repo, kReadmes, kReadmesLen)); + gitrepo_add_special_file( + repo, "LICENSE", + gitrepo_first_matching_file(git_repo, kLicenses, kLicensesLen)); repo->last_commit_time = 0; git_object* obj = NULL; @@ -225,11 +246,14 @@ void gitrepo_free(GitRepo* repo) { repo->description = NULL; free(repo->clone_url); repo->clone_url = NULL; - free(repo->submodules); - repo->submodules = NULL; - free(repo->readme); - repo->readme = NULL; - free(repo->license); - repo->license = NULL; + + for (size_t i = 0; i < repo->special_files_len; i++) { + free(repo->special_files[i].label); + free(repo->special_files[i].path); + } + free(repo->special_files); + repo->special_files = NULL; + repo->special_files_len = 0; + free(repo); } diff --git a/src/git/repo.h b/src/git/repo.h @@ -1,17 +1,24 @@ #ifndef GOUT_GIT_REPO_H_ #define GOUT_GIT_REPO_H_ +#include <stddef.h> #include <time.h> +typedef struct RepoSpecialFile { + char* label; + char* path; +} RepoSpecialFile; + typedef struct GitRepo { char* name; char* short_name; char* owner; char* description; char* clone_url; - char* submodules; - char* readme; - char* license; + + RepoSpecialFile* special_files; + size_t special_files_len; + time_t last_commit_time; } GitRepo; diff --git a/src/writer/gopher/page.c b/src/writer/gopher/page.c @@ -72,22 +72,12 @@ void gopher_page_begin(GopherPage* page) { fprintf(out, "[1|Files|%sfiles.gph|server|port]\n", page->relpath); fprintf(out, "[1|Refs|%srefs.gph|server|port]\n", page->relpath); - const char* submodules = page->repo->submodules; - if (submodules && submodules[0] != '\0') { - fprintf(out, "[1|Submodules|%sfile/", page->relpath); - print_gopher_link(out, submodules); - fprintf(out, ".gph|server|port]\n"); - } - const char* readme = page->repo->readme; - if (readme && readme[0] != '\0') { - fprintf(out, "[1|README|%sfile/", page->relpath); - print_gopher_link(out, readme); - fprintf(out, ".gph|server|port]\n"); - } - const char* license = page->repo->license; - if (license && license[0] != '\0') { - fprintf(out, "[1|LICENSE|%sfile/", page->relpath); - print_gopher_link(out, license); + for (size_t i = 0; i < page->repo->special_files_len; i++) { + RepoSpecialFile* sf = &page->repo->special_files[i]; + fprintf(out, "[1|"); + print_gopher_text(out, sf->label, false); + fprintf(out, "|%sfile/", page->relpath); + print_gopher_link(out, sf->path); fprintf(out, ".gph|server|port]\n"); } fprintf(out, "---\n"); diff --git a/src/writer/gopher/page_tests.c b/src/writer/gopher/page_tests.c @@ -20,11 +20,13 @@ UTEST_F_SETUP(gopher_page) { UTEST_F_TEARDOWN(gopher_page) {} UTEST_F(gopher_page, basic) { + RepoSpecialFile sf = {.label = "LICENSE", .path = "LICENSE"}; GitRepo repo = { .short_name = "test-repo", .description = "Repo description", .clone_url = "git://example.com/repo.git", - .license = "LICENSE", + .special_files = &sf, + .special_files_len = 1, }; FILE* out = g_fs_inmemory->fopen("test.gph", "w"); diff --git a/src/writer/gopher/repo_writer_tests.c b/src/writer/gopher/repo_writer_tests.c @@ -26,9 +26,6 @@ UTEST_F(gopher_repo_writer, orchestration) { .description = "A test repository", .owner = "Test Owner", .clone_url = "git://example.com/test-repo.git", - .submodules = "", - .readme = "", - .license = "", }; GopherRepoWriter* writer = gopher_repowriter_create(&repo, g_fs_inmemory); ASSERT_NE(NULL, writer); diff --git a/src/writer/html/page.c b/src/writer/html/page.c @@ -123,23 +123,13 @@ void html_page_begin(HtmlPage* page) { fprintf(out, "<a href=\"%sfiles.html\">Files</a> | ", relpath); fprintf(out, "<a href=\"%srefs.html\">Refs</a>", relpath); - const char* submodules = page->repo->submodules; - if (submodules && submodules[0] != '\0') { + for (size_t i = 0; i < page->repo->special_files_len; i++) { + RepoSpecialFile* sf = &page->repo->special_files[i]; fprintf(out, " | <a href=\"%sfile/", relpath); - print_xml_encoded(out, submodules); - fprintf(out, ".html\">Submodules</a>"); - } - const char* readme = page->repo->readme; - if (readme && readme[0] != '\0') { - fprintf(out, " | <a href=\"%sfile/", relpath); - print_xml_encoded(out, readme); - fprintf(out, ".html\">README</a>"); - } - const char* license = page->repo->license; - if (license && license[0] != '\0') { - fprintf(out, " | <a href=\"%sfile/", relpath); - print_xml_encoded(out, license); - fprintf(out, ".html\">LICENSE</a>"); + print_percent_encoded(out, sf->path); + fprintf(out, ".html\">"); + print_xml_encoded(out, sf->label); + fprintf(out, "</a>"); } fprintf(out, "</td></tr></table>\n<hr/>\n<div id=\"content\">\n"); } diff --git a/src/writer/html/page_tests.c b/src/writer/html/page_tests.c @@ -20,11 +20,13 @@ UTEST_F_SETUP(html_page) { UTEST_F_TEARDOWN(html_page) {} UTEST_F(html_page, basic) { + RepoSpecialFile sf = {.label = "LICENSE", .path = "LICENSE"}; GitRepo repo = { .short_name = "test-repo", .description = "Repo description", .clone_url = "git://example.com/repo.git", - .license = "LICENSE", + .special_files = &sf, + .special_files_len = 1, }; FILE* out = g_fs_inmemory->fopen("test.html", "w"); diff --git a/src/writer/html/repo_writer_tests.c b/src/writer/html/repo_writer_tests.c @@ -26,9 +26,6 @@ UTEST_F(html_repo_writer, orchestration) { .description = "A test repository", .owner = "Test Owner", .clone_url = "git://example.com/test-repo.git", - .submodules = "", - .readme = "", - .license = "", }; HtmlRepoWriter* writer = html_repowriter_create(&repo, g_fs_inmemory); ASSERT_NE(NULL, writer);