stagit-gopher

[fork] gopher git frontend
git clone https://hhvn.uk/stagit-gopher
git clone git://hhvn.uk/stagit-gopher
Log | Files | Refs | README | LICENSE

stagit-gopher-index.c (6329B)


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