stagit
My personal build of stagit
stagit-index.c (6016B)
1 #include <err.h> 2 #include <limits.h> 3 #include <stdio.h> 4 #include <stdlib.h> 5 #include <stdbool.h> 6 #include <string.h> 7 #include <time.h> 8 #include <unistd.h> 9 10 #include <git2.h> 11 12 static git_repository *repo; 13 14 static const char *relpath = ""; 15 16 static char description[255] = "Repositories"; 17 static char *name = ""; 18 static char owner[255]; 19 20 // Returns true if the repo has a README and it is a Markdown file. Otherwise 21 // returns false. 22 bool has_markdown_readme(git_repository* repo) 23 { 24 git_object *obj; 25 return !git_revparse_single(&obj, repo, "HEAD:README.md") 26 && !git_blob_is_binary((git_blob *)obj) 27 && (git_object_type(obj) == GIT_OBJ_BLOB); 28 } 29 30 void joinpath(char *buf, size_t bufsiz, const char *path, const char *path2) 31 { 32 int r; 33 34 r = snprintf(buf, bufsiz, "%s%s%s", 35 path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2); 36 if (r < 0 || (size_t)r >= bufsiz) 37 errx(1, "path truncated: '%s%s%s'", 38 path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2); 39 } 40 41 /* Escape characters below as HTML 2.0 / XML 1.0. */ 42 void xmlencode(FILE *fp, const char *s, size_t len) 43 { 44 size_t i; 45 46 for (i = 0; *s && i < len; s++, i++) { 47 switch(*s) { 48 case '<': fputs("<", fp); break; 49 case '>': fputs(">", fp); break; 50 case '\'': fputs("'" , fp); break; 51 case '&': fputs("&", fp); break; 52 case '"': fputs(""", fp); break; 53 default: putc(*s, fp); 54 } 55 } 56 } 57 58 void printtimeshort(FILE *fp, const git_time *intime) 59 { 60 struct tm *intm; 61 time_t t; 62 char out[32]; 63 64 t = (time_t)intime->time; 65 if (!(intm = gmtime(&t))) 66 return; 67 strftime(out, sizeof(out), "%Y-%m-%d", intm); 68 fputs(out, fp); 69 } 70 71 void printtimehuman(FILE *fp, const git_time *intime) 72 { 73 struct tm *intm; 74 time_t t; 75 char out[32]; 76 77 t = (time_t)intime->time; 78 if (!(intm = gmtime(&t))) 79 return; 80 strftime(out, sizeof(out), "%d/%m/%Y", intm); 81 fputs(out, fp); 82 } 83 84 void writeheader(FILE *fp) 85 { 86 fputs("<!DOCTYPE html>\n" 87 "<html>\n<head>\n" 88 "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>\n" 89 "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"/>\n" 90 "<title>", fp); 91 xmlencode(fp, description, strlen(description)); 92 fprintf(fp, "</title>\n<link rel=\"icon\" type=\"image/svg\" href=\"/icons/favicon.svg\" />\n"); 93 fprintf(fp, "<link rel=\"stylesheet\" type=\"text/css\" href=\"/styles.css\" />\n", relpath); 94 fputs("</head>\n<body>\n", fp); 95 96 // The navigation bar 97 fputs("<header>\n" 98 "<nav>\n" 99 "<a href=\"https://git.pablopie.xyz\">\n" 100 "<img aria-hidden=\"true\" alt=\"Website logo\" src=\"/icons/favicon.svg\">\n" 101 "git.pablopie.xyz\n" 102 "</a>\n" 103 "</nav>\n" 104 "</header>\n", fp); 105 106 fputs("<main>\n", fp); 107 fputs("<div class=\"article-list\">\n", fp); 108 } 109 110 void writefooter(FILE *fp) 111 { 112 fputs("</div>\n</main>\n", fp); 113 fputs("<footer>" 114 "made with ❤️ by " 115 "<a rel=\"author\" href=\"https://pablopie.xyz/\">@pablo</a>" 116 "</footer>\n", fp); 117 fputs("</body>\n</html>\n", fp); 118 } 119 120 int writelog(FILE *fp) 121 { 122 git_commit *commit = NULL; 123 const git_signature *author; 124 git_revwalk *w = NULL; 125 git_oid id; 126 char *stripped_name = NULL, *p; 127 int ret = 0; 128 129 git_revwalk_new(&w, repo); 130 git_revwalk_push_head(w); 131 git_revwalk_simplify_first_parent(w); 132 133 if (git_revwalk_next(&id, w) || 134 git_commit_lookup(&commit, repo, &id)) { 135 ret = -1; 136 goto err; 137 } 138 139 author = git_commit_author(commit); 140 141 /* strip .git suffix */ 142 if (!(stripped_name = strdup(name))) 143 err(1, "strdup"); 144 if ((p = strrchr(stripped_name, '.'))) 145 if (!strcmp(p, ".git")) 146 *p = '\0'; 147 148 fputs("<article>\n<h4>\n<a href=\"", fp); 149 xmlencode(fp, stripped_name, strlen(stripped_name)); 150 fputs("/index.html\">", fp); 151 xmlencode(fp, stripped_name, strlen(stripped_name)); 152 fputs("</a>\n</h4>\n", fp); 153 154 fputs("<div>\n<span>", fp); 155 xmlencode(fp, owner, strlen(owner)); 156 fputs("</span>\n", fp); 157 if (author) 158 { 159 fputs("<time datetime=\"", fp); 160 printtimeshort(fp, &(author->when)); 161 fputs("\">", fp); 162 printtimehuman(fp, &(author->when)); 163 fputs("</time>\n", fp); 164 } 165 fputs("</div>\n<p>\n", fp); 166 167 xmlencode(fp, description, strlen(description)); 168 fputs("\n</p>\n</article>\n", fp); 169 170 git_commit_free(commit); 171 err: 172 git_revwalk_free(w); 173 free(stripped_name); 174 175 return ret; 176 } 177 178 int main(int argc, char *argv[]) 179 { 180 FILE *fp; 181 char path[PATH_MAX]; 182 const char *repodir; 183 int i, ret = 0; 184 185 if (argc < 2) { 186 fprintf(stderr, "%s [repodir...]\n", argv[0]); 187 return 1; 188 } 189 190 git_libgit2_init(); 191 192 #ifdef __OpenBSD__ 193 if (pledge("stdio rpath", NULL) == -1) 194 err(1, "pledge"); 195 #endif 196 197 writeheader(stdout); 198 199 for (i = 1; i < argc; i++) { 200 repodir = argv[i]; 201 202 if (git_repository_open_ext(&repo, repodir, 203 GIT_REPOSITORY_OPEN_NO_SEARCH, NULL)) { 204 fprintf(stderr, "%s: cannot open repository\n", argv[0]); 205 ret = 1; 206 continue; 207 } 208 209 /* use directory name as name */ 210 if ((name = strrchr(repodir, '/'))) 211 name++; 212 else 213 name = ""; 214 215 /* read description or .git/description */ 216 joinpath(path, sizeof(path), repodir, "description"); 217 if (!(fp = fopen(path, "r"))) { 218 joinpath(path, sizeof(path), repodir, ".git/description"); 219 fp = fopen(path, "r"); 220 } 221 description[0] = '\0'; 222 if (fp) { 223 if (!fgets(description, sizeof(description), fp)) 224 description[0] = '\0'; 225 fclose(fp); 226 } 227 228 /* read owner or .git/owner */ 229 joinpath(path, sizeof(path), repodir, "owner"); 230 if (!(fp = fopen(path, "r"))) { 231 joinpath(path, sizeof(path), repodir, ".git/owner"); 232 fp = fopen(path, "r"); 233 } 234 owner[0] = '\0'; 235 if (fp) { 236 if (!fgets(owner, sizeof(owner), fp)) 237 owner[0] = '\0'; 238 owner[strcspn(owner, "\n")] = '\0'; 239 fclose(fp); 240 } 241 writelog(stdout); 242 } 243 writefooter(stdout); 244 245 /* cleanup */ 246 git_repository_free(repo); 247 git_libgit2_shutdown(); 248 249 return ret; 250 }