(22 hours ago)commit f942600: smarter breadcrumbs
| author | Tanner Stenson <tanner@brickware.sh> |
| date | Wed Jan 7 23:20:07 2026 -0500 |
smarter breadcrumbs
commit f942600e99a031235597e6858d965cd12934203b
Author: Tanner Stenson <tanner@brickware.sh>
Date: Wed Jan 7 23:20:07 2026 -0500
smarter breadcrumbs
diff --git a/marrow-static.c b/marrow-static.c
index 72d52aa..0b4b4a6 100644
--- a/marrow-static.c
+++ b/marrow-static.c
@@ -170,8 +170,57 @@ generate_html_header(FILE *out, const char *repo_name, const char *suffix) {
fprintf(out, "</style>\n");
fprintf(out, "</head>\n<body>\n");
- /* Header with repo name as link */
- fprintf(out, "<div class=\"header\"><h1><a href=\"../../../\">%s</a>", repo_name);
+ /* Header with breadcrumb navigation */
+ fprintf(out, "<div class=\"header\"><h1>");
+
+ /* Count slashes in repo_name to determine directory depth */
+ int32_t repo_depth = 0;
+ for (const char *p = repo_name; *p; p++) {
+ if ('/' == *p) repo_depth++;
+ }
+
+ /* Root "git" link - go up through branch/commit structure AND repo path */
+ fprintf(out, "<a href=\"");
+ /* From branch/main/tree or commit/hash/summary (3 levels) up through repo path */
+ for (int32_t i = 0; i < 3 + repo_depth; i++) {
+ fprintf(out, "../");
+ }
+ fprintf(out, "\">git</a>");
+
+ /* Parse repo path and create breadcrumb links */
+ if (repo_name && repo_name[0]) {
+ char path_copy[PATH_MAX];
+ snprintf(path_copy, sizeof(path_copy), "%s", repo_name);
+
+ char *token = strtok(path_copy, "/");
+
+ while (token) {
+ fprintf(out, " / ");
+
+ /* Calculate how many more segments remain */
+ char *remaining = strtok(NULL, "/");
+ int32_t remaining_segments = 0;
+ if (remaining) {
+ remaining_segments = 1;
+ for (char *p = remaining; *p; p++) {
+ if ('/' == *p) remaining_segments++;
+ }
+ }
+
+ /* Create link */
+ fprintf(out, "<a href=\"");
+ /* Go up 3 levels (branch/main/tree) plus remaining segments in path */
+ for (int32_t i = 0; i < 3 + remaining_segments; i++) {
+ fprintf(out, "../");
+ }
+ fprintf(out, "\">");
+ html_escape(out, token);
+ fprintf(out, "</a>");
+
+ token = remaining;
+ }
+ }
+
if (suffix && suffix[0]) {
fprintf(out, " - %s", suffix);
}
@@ -1372,13 +1421,71 @@ generate_directory_index_header(FILE *out, const char *dir_path) {
fprintf(out, "</style>\n");
fprintf(out, "</head>\n<body>\n");
+ /* Generate breadcrumb header */
fprintf(out, "<div class=\"header\"><h1>");
+
if (dir_path && dir_path[0]) {
- fprintf(out, "git / ");
- html_escape(out, dir_path);
+ /* Root link */
+ fprintf(out, "<a href=\"");
+
+ /* Calculate relative path back to root */
+ /* Count directory segments, not slashes (e.g., "user" = 1, "user/team" = 2) */
+ int32_t depth = 1; /* Start at 1 for first directory */
+ for (const char *p = dir_path; *p; p++) {
+ if ('/' == *p) depth++;
+ }
+ for (int32_t i = 0; i < depth; i++) {
+ fprintf(out, "../");
+ }
+ fprintf(out, "\">git</a>");
+
+ /* Parse path and create breadcrumb links */
+ char path_copy[PATH_MAX];
+ snprintf(path_copy, sizeof(path_copy), "%s", dir_path);
+
+ char *token = strtok(path_copy, "/");
+ char accumulated_path[PATH_MAX] = {0};
+
+ while (token) {
+ fprintf(out, " / ");
+
+ /* Build accumulated path for this segment */
+ if (accumulated_path[0]) {
+ strncat(accumulated_path, "/", sizeof(accumulated_path) - strlen(accumulated_path) - 1);
+ }
+ strncat(accumulated_path, token, sizeof(accumulated_path) - strlen(accumulated_path) - 1);
+
+ /* Check if this is the last segment */
+ char *remaining = strtok(NULL, "/");
+
+ if (remaining) {
+ /* Not last segment - make it a link */
+ fprintf(out, "<a href=\"");
+
+ /* Calculate relative path to this segment */
+ int32_t remaining_depth = 1;
+ for (char *p = remaining; *p; p++) {
+ if ('/' == *p) remaining_depth++;
+ }
+ for (int32_t i = 0; i < remaining_depth; i++) {
+ fprintf(out, "../");
+ }
+
+ fprintf(out, "\">");
+ html_escape(out, token);
+ fprintf(out, "</a>");
+
+ token = remaining;
+ } else {
+ /* Last segment - no link */
+ html_escape(out, token);
+ token = NULL;
+ }
+ }
} else {
fprintf(out, "git repositories");
}
+
fprintf(out, "</h1></div>\n");
}
@@ -1431,8 +1538,10 @@ generate_directory_index(const char *output_dir, const char *git_base_path,
/* Generate header */
generate_directory_index_header(out, relative_dir_path);
- /* Generate subdirectories table if any exist */
- if (dirs.count > 0) {
+ /* Generate subdirectories table if any exist OR if we need parent link */
+ int32_t show_dirs_table = (dirs.count > 0) || (relative_dir_path && relative_dir_path[0]);
+
+ if (show_dirs_table) {
fprintf(out, "<h2>directories</h2>\n");
fprintf(out, "<table>\n");
fprintf(out, "<thead><tr><th>name</th><th>description</th></tr></thead>\n");