stagit-gopher

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

stagit-gopher-index.c (5418B)


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