Introduction
Introduction Statistics Contact Development Disclaimer Help
stagit-gopher-index.c - stagit-gopher - static git page generator for gopher
git clone git://git.codemadness.org/stagit-gopher
Log
Files
Refs
README
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(PA…
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…
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] != '/' ? "/" : "…
153 if (r < 0 || (size_t)r >= bufsiz)
154 errx(1, "path truncated: '%s%s%s'",
155 path, path[0] && path[strlen(path) - 1] != '/' ?…
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_n…
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", argv…
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 …
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", …
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/desc…
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…
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 }
You are viewing proxied material from codemadness.org. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.