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("&lt;",   fp); break;
 49       case '>':  fputs("&gt;",   fp); break;
 50       case '\'': fputs("&#39;" , fp); break;
 51       case '&':  fputs("&amp;",  fp); break;
 52       case '"':  fputs("&quot;", 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 }