fileblob.c (3341B)
1 #include "writer/gemini/fileblob.h" 2 3 #include <assert.h> 4 #include <err.h> 5 #include <libgen.h> 6 #include <limits.h> 7 #include <stdbool.h> 8 #include <stdio.h> 9 #include <stdlib.h> 10 #include <string.h> 11 12 #include "format.h" 13 #include "utils.h" 14 #include "writer/gemini/page.h" 15 16 struct GeminiFileBlob { 17 const GitRepo* repo; 18 const FileSystem* fs; 19 FILE* out; 20 GeminiPage* page; 21 }; 22 23 GeminiFileBlob* gemini_fileblob_create(const GitRepo* repo, 24 const FileSystem* fs, 25 const char* path) { 26 assert(repo != NULL); 27 assert(fs != NULL); 28 assert(path != NULL); 29 if (!is_safe_repo_path(path)) { 30 errx(1, "unsafe path: %s", path); 31 } 32 GeminiFileBlob* blob = ecalloc(1, sizeof(GeminiFileBlob)); 33 blob->repo = repo; 34 blob->fs = fs; 35 36 // Create directories. 37 char filename_buffer[PATH_MAX]; 38 int r = snprintf(filename_buffer, sizeof(filename_buffer), "%s.gmi", path); 39 if (r < 0 || (size_t)r >= sizeof(filename_buffer)) { 40 errx(1, "snprintf: filename truncated or error"); 41 } 42 43 char* out_path = path_concat("file", filename_buffer); 44 char* dir_copy = estrdup(out_path); 45 const char* d = dirname(dir_copy); 46 if (!d) { 47 err(1, "dirname"); 48 } 49 fs->mkdirp(d); 50 blob->out = fs->fopen(out_path, "w"); 51 if (!blob->out) { 52 err(1, "fopen: %s", out_path); 53 } 54 55 // Compute the relative path. 56 char* relpath = relpath_from_dir(d); 57 free(dir_copy); 58 59 char* path_copy = estrdup(path); 60 const char* title = basename(path_copy); 61 if (!title) { 62 err(1, "basename"); 63 } 64 blob->page = gemini_page_create(blob->out, repo, fs, title, relpath); 65 66 free(out_path); 67 free(path_copy); 68 free(relpath); 69 70 return blob; 71 } 72 73 void gemini_fileblob_free(GeminiFileBlob* blob) { 74 if (!blob) { 75 return; 76 } 77 blob->fs->fclose(blob->out); 78 blob->out = NULL; 79 gemini_page_free(blob->page); 80 blob->page = NULL; 81 free(blob); 82 } 83 84 void gemini_fileblob_begin(GeminiFileBlob* blob) { 85 assert(blob != NULL); 86 gemini_page_begin(blob->page); 87 } 88 89 void gemini_fileblob_add_file(GeminiFileBlob* blob, const GitFile* file) { 90 assert(blob != NULL); 91 assert(file != NULL); 92 FILE* out = blob->out; 93 94 char path[PATH_MAX]; 95 estrlcpy(path, file->repo_path, sizeof(path)); 96 const char* filename = basename(path); 97 if (!filename) { 98 err(1, "basename"); 99 } 100 fprintf(out, "File: %s (%zdB)\n\n", filename, file->size_bytes); 101 102 ssize_t size_lines = file->size_lines; 103 if (size_lines == -1) { 104 fprintf(out, "Binary file.\n"); 105 return; 106 } 107 if (size_lines == -2) { 108 fprintf(out, "File too large to display.\n"); 109 return; 110 } 111 112 assert(file->content != NULL); 113 fprintf(out, "```\n"); 114 size_t i = 0; 115 const char* end = file->content + file->size_bytes; 116 const char* cur_line = file->content; 117 while (cur_line < end) { 118 const char* next_line = memchr(cur_line, '\n', end - cur_line); 119 size_t len = (next_line ? next_line : end) - cur_line; 120 121 i++; 122 size_t display_len = len; 123 if (display_len > 0 && cur_line[display_len - 1] == '\r') { 124 display_len--; 125 } 126 fprintf(out, "%6zu %.*s\n", i, (int)display_len, cur_line); 127 128 if (next_line) { 129 cur_line = next_line + 1; 130 } else { 131 break; 132 } 133 } 134 fprintf(out, "```\n"); 135 } 136 137 void gemini_fileblob_end(GeminiFileBlob* blob) { 138 assert(blob != NULL); 139 gemini_page_end(blob->page); 140 }