stagit-gemini-index.c - stagit-gemini - Stagit for gemini protocol Openbsd
 (HTM) git clone git://thinkerwim.org/stagit-gemini.git
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
       stagit-gemini-index.c (6556B)
       ---
            1 #include <err.h>
            2 #include <locale.h>
            3 #include <limits.h>
            4 #include <stdio.h>
            5 #include <stdlib.h>
            6 #include <string.h>
            7 #include <time.h>
            8 #include <unistd.h>
            9 #include <wchar.h>
           10 
           11 #include <git2.h>
           12 
           13 #define PAD_TRUNCATE_SYMBOL    "\xe2\x80\xa6" /* symbol: "ellipsis" */
           14 #define UTF_INVALID_SYMBOL     "\xef\xbf\xbd" /* symbol: "replacement" */
           15 
           16 static git_repository *repo;
           17 
           18 static const char *relpath = "";
           19 
           20 static char description[255] = "Repositories";
           21 static char *name = "";
           22 
           23 /* Handle read or write errors for a FILE * stream */
           24 void
           25 checkfileerror(FILE *fp, const char *name, int mode)
           26 {
           27         if (mode == 'r' && ferror(fp))
           28                 errx(1, "read error: %s", name);
           29         else if (mode == 'w' && (fflush(fp) || ferror(fp)))
           30                 errx(1, "write error: %s", name);
           31 }
           32 
           33 /* Format `len' columns of characters. If string is shorter pad the rest
           34  * with characters `pad`. */
           35 int
           36 utf8pad(char *buf, size_t bufsiz, const char *s, size_t len, int pad)
           37 {
           38         wchar_t wc;
           39         size_t col = 0, i, slen, siz = 0;
           40         int inc, rl, w;
           41 
           42         if (!bufsiz)
           43                 return -1;
           44         if (!len) {
           45                 buf[0] = '\0';
           46                 return 0;
           47         }
           48 
           49         slen = strlen(s);
           50         for (i = 0; i < slen; i += inc) {
           51                 inc = 1; /* next byte */
           52                 if ((unsigned char)s[i] < 32)
           53                         continue;
           54 
           55                 rl = mbtowc(&wc, &s[i], slen - i < 4 ? slen - i : 4);
           56                 inc = rl;
           57                 if (rl < 0) {
           58                         mbtowc(NULL, NULL, 0); /* reset state */
           59                         inc = 1; /* invalid, seek next byte */
           60                         w = 1; /* replacement char is one width */
           61                 } else if ((w = wcwidth(wc)) == -1) {
           62                         continue;
           63                 }
           64 
           65                 if (col + w > len || (col + w == len && s[i + inc])) {
           66                         if (siz + 4 >= bufsiz)
           67                                 return -1;
           68                         memcpy(&buf[siz], PAD_TRUNCATE_SYMBOL, sizeof(PAD_TRUNCATE_SYMBOL) - 1);
           69                         siz += sizeof(PAD_TRUNCATE_SYMBOL) - 1;
           70                         buf[siz] = '\0';
           71                         col++;
           72                         break;
           73                 } else if (rl < 0) {
           74                         if (siz + 4 >= bufsiz)
           75                                 return -1;
           76                         memcpy(&buf[siz], UTF_INVALID_SYMBOL, sizeof(UTF_INVALID_SYMBOL) - 1);
           77                         siz += sizeof(UTF_INVALID_SYMBOL) - 1;
           78                         buf[siz] = '\0';
           79                         col++;
           80                         continue;
           81                 }
           82                 if (siz + inc + 1 >= bufsiz)
           83                         return -1;
           84                 memcpy(&buf[siz], &s[i], inc);
           85                 siz += inc;
           86                 buf[siz] = '\0';
           87                 col += w;
           88         }
           89 
           90         len -= col;
           91         if (siz + len + 1 >= bufsiz)
           92                 return -1;
           93         memset(&buf[siz], pad, len);
           94         siz += len;
           95         buf[siz] = '\0';
           96 
           97         return 0;
           98 }
           99 
          100 /* Escape characters in text in geomyidae .gmi format,
          101    newlines are ignored */
          102 void
          103 gmitext(FILE *fp, const char *s, size_t len)
          104 {
          105         size_t i;
          106 
          107         for (i = 0; *s && i < len; s++, i++) {
          108                 switch (*s) {
          109                 case '\r': /* ignore CR */
          110                 case '\n': /* ignore LF */
          111                         break;
          112                 default:
          113                         putc(*s, fp);
          114                         break;
          115                 }
          116         }
          117 }
          118 
          119 /* Escape characters in links in geomyidae .gmi format */
          120 void
          121 gmilink(FILE *fp, const char *s, size_t len)
          122 {
          123         size_t i;
          124 
          125         for (i = 0; *s && i < len; s++, i++) {
          126                 switch (*s) {
          127                 case '\r': /* ignore CR */
          128                 case '\n': /* ignore LF */
          129                         break;
          130                 default:
          131                         putc(*s, fp);
          132                         break;
          133                 }
          134         }
          135 }
          136 
          137 void
          138 joinpath(char *buf, size_t bufsiz, const char *path, const char *path2)
          139 {
          140         int r;
          141 
          142         r = snprintf(buf, bufsiz, "%s%s%s",
          143                 path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
          144         if (r < 0 || (size_t)r >= bufsiz)
          145                 errx(1, "path truncated: '%s%s%s'",
          146                         path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
          147 }
          148 
          149 void
          150 printtimeshort(FILE *fp, const git_time *intime)
          151 {
          152         struct tm *intm;
          153         time_t t;
          154         char out[32];
          155 
          156         t = (time_t)intime->time;
          157         if (!(intm = gmtime(&t)))
          158                 return;
          159         strftime(out, sizeof(out), "%Y-%m-%d %H:%M", intm);
          160         fputs(out, fp);
          161 }
          162 
          163 void
          164 writeheader(FILE *fp)
          165 {
          166         if (description[0]) {
          167                 fputs(description, fp);
          168                 fputs("\n\n", fp);
          169         }
          170 
          171         fprintf(fp, "%-20.20s  ", "Name");
          172         fprintf(fp, "%-39.39s  ", "Description");
          173         fprintf(fp, "%s\n", "Last commit");
          174 }
          175 
          176 int
          177 writelog(FILE *fp)
          178 {
          179         git_commit *commit = NULL;
          180         const git_signature *author;
          181         git_revwalk *w = NULL;
          182         git_oid id;
          183         char *stripped_name = NULL, *p;
          184         char buf[1024];
          185         int ret = 0;
          186 
          187         git_revwalk_new(&w, repo);
          188         git_revwalk_push_head(w);
          189 
          190         if (git_revwalk_next(&id, w) ||
          191             git_commit_lookup(&commit, repo, &id)) {
          192                 ret = -1;
          193                 goto err;
          194         }
          195 
          196         author = git_commit_author(commit);
          197 
          198         /* strip .git suffix */
          199         if (!(stripped_name = strdup(name)))
          200                 err(1, "strdup");
          201         if ((p = strrchr(stripped_name, '.')))
          202                 if (!strcmp(p, ".git"))
          203                         *p = '\0';
          204 
          205         fprintf(fp, "=> %s/%s/log.gmi ", relpath, stripped_name);
          206         utf8pad(buf, sizeof(buf), stripped_name, 20, ' ');
          207         gmilink(fp, buf, strlen(buf));
          208         fputs("  ", fp);
          209         utf8pad(buf, sizeof(buf), description, 39, ' ');
          210         gmilink(fp, buf, strlen(buf));
          211         fputs("  ", fp);
          212         if (author)
          213                 printtimeshort(fp, &(author->when));
          214   fputc('\n', fp);
          215 
          216         git_commit_free(commit);
          217 err:
          218         git_revwalk_free(w);
          219         free(stripped_name);
          220 
          221         return ret;
          222 }
          223 
          224 void
          225 usage(const char *argv0)
          226 {
          227         fprintf(stderr, "usage: %s [-b baseprefix] [repodir...]\n", argv0);
          228         exit(1);
          229 }
          230 
          231 int
          232 main(int argc, char *argv[])
          233 {
          234         FILE *fp;
          235         char path[PATH_MAX], repodirabs[PATH_MAX + 1];
          236         const char *repodir = NULL;
          237         int i, r, ret = 0;
          238 
          239         setlocale(LC_CTYPE, "");
          240 
          241         /* do not search outside the git repository:
          242            GIT_CONFIG_LEVEL_APP is the highest level currently */
          243         git_libgit2_init();
          244         for (i = 1; i <= GIT_CONFIG_LEVEL_APP; i++)
          245                 git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, i, "");
          246         /* do not require the git repository to be owned by the current user */
          247         git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 0);
          248 
          249 #ifdef __OpenBSD__
          250         if (pledge("stdio rpath", NULL) == -1)
          251                 err(1, "pledge");
          252 #endif
          253 
          254         for (i = 1, r = 0; i < argc; i++) {
          255                 if (argv[i][0] == '-') {
          256                         if (argv[i][1] != 'b' || i + 1 >= argc)
          257                                 usage(argv[0]);
          258                         relpath = argv[++i];
          259                         continue;
          260                 }
          261 
          262                 if (r++ == 0)
          263                         writeheader(stdout);
          264 
          265                 repodir = argv[i];
          266                 if (!realpath(repodir, repodirabs))
          267                         err(1, "realpath");
          268 
          269                 if (git_repository_open_ext(&repo, repodir,
          270                     GIT_REPOSITORY_OPEN_NO_SEARCH, NULL)) {
          271                         fprintf(stderr, "%s: cannot open repository\n", argv[0]);
          272                         ret = 1;
          273                         continue;
          274                 }
          275 
          276                 /* use directory name as name */
          277                 if ((name = strrchr(repodirabs, '/')))
          278                         name++;
          279                 else
          280                         name = "";
          281 
          282                 /* read description or .git/description */
          283                 joinpath(path, sizeof(path), repodir, "description");
          284                 if (!(fp = fopen(path, "r"))) {
          285                         joinpath(path, sizeof(path), repodir, ".git/description");
          286                         fp = fopen(path, "r");
          287                 }
          288                 description[0] = '\0';
          289                 if (fp) {
          290                         if (fgets(description, sizeof(description), fp))
          291                                 description[strcspn(description, "\t\r\n")] = '\0';
          292                         else
          293                                 description[0] = '\0';
          294                         checkfileerror(fp, "description", 'r');
          295                         fclose(fp);
          296                 }
          297 
          298                 writelog(stdout);
          299         }
          300         if (!repodir)
          301                 usage(argv[0]);
          302 
          303         /* cleanup */
          304         git_repository_free(repo);
          305         git_libgit2_shutdown();
          306 
          307         checkfileerror(stdout, "<stdout>", 'w');
          308 
          309         return ret;
          310 }