stagit-gopher

static git page generator for gopher (mirror)
git clone git://git.2f30.org/stagit-gopher.git
Log | Files | Refs | README | LICENSE

stagit-gopher-index.c (5485B)


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