URI: 
       stagit-gopher-index.c - stagit-gopher - A git gopher frontend. (mirror)
  HTML git clone git://bitreich.org/stagit-gopher/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/stagit-gopher/
   DIR Log
   DIR Files
   DIR Refs
   DIR Tags
   DIR README
   DIR LICENSE
       ---
       stagit-gopher-index.c (6805B)
       ---
            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 .gph format,
          101    newlines are ignored */
          102 void
          103 gphtext(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                 case '\t':
          113                         fputs("        ", fp);
          114                         break;
          115                 default:
          116                         putc(*s, fp);
          117                         break;
          118                 }
          119         }
          120 }
          121 
          122 /* Escape characters in links in geomyidae .gph format */
          123 void
          124 gphlink(FILE *fp, const char *s, size_t len)
          125 {
          126         size_t i;
          127 
          128         for (i = 0; *s && i < len; s++, i++) {
          129                 switch (*s) {
          130                 case '\r': /* ignore CR */
          131                 case '\n': /* ignore LF */
          132                         break;
          133                 case '\t':
          134                         fputs("        ", fp);
          135                         break;
          136                 case '|': /* escape separators */
          137                         fputs("\\|", fp);
          138                         break;
          139                 default:
          140                         putc(*s, fp);
          141                         break;
          142                 }
          143         }
          144 }
          145 
          146 void
          147 joinpath(char *buf, size_t bufsiz, const char *path, const char *path2)
          148 {
          149         int r;
          150 
          151         r = snprintf(buf, bufsiz, "%s%s%s",
          152                 path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
          153         if (r < 0 || (size_t)r >= bufsiz)
          154                 errx(1, "path truncated: '%s%s%s'",
          155                         path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
          156 }
          157 
          158 void
          159 printtimeshort(FILE *fp, const git_time *intime)
          160 {
          161         struct tm *intm;
          162         time_t t;
          163         char out[32];
          164 
          165         t = (time_t)intime->time;
          166         if (!(intm = gmtime(&t)))
          167                 return;
          168         strftime(out, sizeof(out), "%Y-%m-%d %H:%M", intm);
          169         fputs(out, fp);
          170 }
          171 
          172 void
          173 writeheader(FILE *fp)
          174 {
          175         if (description[0]) {
          176                 if (description[0] == '[')
          177                         fputs("[|", fp);
          178                 gphtext(fp, description, strlen(description));
          179                 fputs("\n\n", fp);
          180         }
          181 
          182         fprintf(fp, "%-20.20s  ", "Name");
          183         fprintf(fp, "%-39.39s  ", "Description");
          184         fprintf(fp, "%s\n", "Last commit");
          185 }
          186 
          187 int
          188 writelog(FILE *fp)
          189 {
          190         git_commit *commit = NULL;
          191         const git_signature *author;
          192         git_revwalk *w = NULL;
          193         git_oid id;
          194         char *stripped_name = NULL, *p;
          195         char buf[1024];
          196         int ret = 0;
          197 
          198         git_revwalk_new(&w, repo);
          199         git_revwalk_push_head(w);
          200 
          201         if (git_revwalk_next(&id, w) ||
          202             git_commit_lookup(&commit, repo, &id)) {
          203                 ret = -1;
          204                 goto err;
          205         }
          206 
          207         author = git_commit_author(commit);
          208 
          209         /* strip .git suffix */
          210         if (!(stripped_name = strdup(name)))
          211                 err(1, "strdup");
          212         if ((p = strrchr(stripped_name, '.')))
          213                 if (!strcmp(p, ".git"))
          214                         *p = '\0';
          215 
          216         fputs("[1|", fp);
          217         utf8pad(buf, sizeof(buf), stripped_name, 20, ' ');
          218         gphlink(fp, buf, strlen(buf));
          219         fputs("  ", fp);
          220         utf8pad(buf, sizeof(buf), description, 39, ' ');
          221         gphlink(fp, buf, strlen(buf));
          222         fputs("  ", fp);
          223         if (author)
          224                 printtimeshort(fp, &(author->when));
          225         fprintf(fp, "|%s/%s/log.gph|server|port]\n", relpath, stripped_name);
          226 
          227         git_commit_free(commit);
          228 err:
          229         git_revwalk_free(w);
          230         free(stripped_name);
          231 
          232         return ret;
          233 }
          234 
          235 void
          236 usage(const char *argv0)
          237 {
          238         fprintf(stderr, "usage: %s [-b baseprefix] [repodir...]\n", argv0);
          239         exit(1);
          240 }
          241 
          242 int
          243 main(int argc, char *argv[])
          244 {
          245         FILE *fp;
          246         char path[PATH_MAX], repodirabs[PATH_MAX + 1];
          247         const char *repodir = NULL;
          248         int i, r, ret = 0;
          249 
          250         setlocale(LC_CTYPE, "");
          251 
          252         /* do not search outside the git repository:
          253            GIT_CONFIG_LEVEL_APP is the highest level currently */
          254         git_libgit2_init();
          255         for (i = 1; i <= GIT_CONFIG_LEVEL_APP; i++)
          256                 git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, i, "");
          257         /* do not require the git repository to be owned by the current user */
          258         git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 0);
          259 
          260 #ifdef __OpenBSD__
          261         if (pledge("stdio rpath", NULL) == -1)
          262                 err(1, "pledge");
          263 #endif
          264 
          265         for (i = 1, r = 0; i < argc; i++) {
          266                 if (argv[i][0] == '-') {
          267                         if (argv[i][1] != 'b' || i + 1 >= argc)
          268                                 usage(argv[0]);
          269                         relpath = argv[++i];
          270                         continue;
          271                 }
          272 
          273                 if (r++ == 0)
          274                         writeheader(stdout);
          275 
          276                 repodir = argv[i];
          277                 if (!realpath(repodir, repodirabs))
          278                         err(1, "realpath");
          279 
          280                 if (git_repository_open_ext(&repo, repodir,
          281                     GIT_REPOSITORY_OPEN_NO_SEARCH, NULL)) {
          282                         fprintf(stderr, "%s: cannot open repository\n", argv[0]);
          283                         ret = 1;
          284                         continue;
          285                 }
          286 
          287                 /* use directory name as name */
          288                 if ((name = strrchr(repodirabs, '/')))
          289                         name++;
          290                 else
          291                         name = "";
          292 
          293                 /* read description or .git/description */
          294                 joinpath(path, sizeof(path), repodir, "description");
          295                 if (!(fp = fopen(path, "r"))) {
          296                         joinpath(path, sizeof(path), repodir, ".git/description");
          297                         fp = fopen(path, "r");
          298                 }
          299                 description[0] = '\0';
          300                 if (fp) {
          301                         if (fgets(description, sizeof(description), fp))
          302                                 description[strcspn(description, "\t\r\n")] = '\0';
          303                         else
          304                                 description[0] = '\0';
          305                         checkfileerror(fp, "description", 'r');
          306                         fclose(fp);
          307                 }
          308 
          309                 writelog(stdout);
          310         }
          311         if (!repodir)
          312                 usage(argv[0]);
          313 
          314         /* cleanup */
          315         git_repository_free(repo);
          316         git_libgit2_shutdown();
          317 
          318         checkfileerror(stdout, "<stdout>", 'w');
          319 
          320         return ret;
          321 }