build-page.c - sites - public wiki contents of suckless.org | |
git clone git://git.suckless.org/sites | |
Log | |
Files | |
Refs | |
--- | |
build-page.c (10322B) | |
--- | |
1 #define _POSIX_C_SOURCE 200809L | |
2 | |
3 #include <sys/stat.h> | |
4 #include <sys/types.h> | |
5 #include <sys/wait.h> | |
6 | |
7 #include <dirent.h> | |
8 #include <limits.h> | |
9 #include <stdarg.h> | |
10 #include <stdio.h> | |
11 #include <stdlib.h> | |
12 #include <string.h> | |
13 #include <unistd.h> | |
14 | |
15 #define CONVERTER "smu","-n" | |
16 #define LEN(x) (sizeof(x) / sizeof(x[0])) | |
17 #define TITLE_MAX 1024 | |
18 #define TITLE_DEFAULT "suckless.org" | |
19 | |
20 #define GOPHER_ROW_MAX 80 | |
21 #define GOPHER_PORT 70 | |
22 | |
23 char *html_header = | |
24 "<!doctype html>\n" | |
25 "<html>\n" | |
26 "<head>\n" | |
27 "\t<meta charset=\"utf-8\"/>\n" | |
28 "\t<title>%1$s | suckless.org software that sucks less</title>\n" | |
29 "\t<link rel=\"stylesheet\" type=\"text/css\" href=\"//suckless.… | |
30 "</head>\n" | |
31 "\n" | |
32 "<div id=\"header\">\n" | |
33 "\t<a href=\"//suckless.org/\"><img src=\"//suckless.org/logo.sv… | |
34 "\t<a id=\"headerLink\" href=\"//suckless.org/\">suckless.org</a… | |
35 "\t<span class=\"hidden\"> - </span>\n" | |
36 "\t<span id=\"headerSubtitle\">%1$s</span>\n" | |
37 "</div>\n" | |
38 "<hr class=\"hidden\"/>\n"; | |
39 | |
40 char *html_nav_bar = | |
41 "\t<span class=\"right\">\n" | |
42 "\t\t<a href=\"//dl.suckless.org\">download</a>\n" | |
43 "\t\t<a href=\"//git.suckless.org\">source</a>\n" | |
44 "\t</span>\n"; | |
45 | |
46 char *html_footer = "</html>\n"; | |
47 | |
48 char *gopher_header = "suckless.org %1$s\n\n"; | |
49 | |
50 struct domain { | |
51 char *label; | |
52 char *dir; | |
53 } domain_list[] = { | |
54 { "home", "suckless.org" }, | |
55 { "dwm", "dwm.suckless.org", }, | |
56 { "st", "st.suckless.org", }, | |
57 { "core", "core.suckless.org", }, | |
58 { "surf", "surf.suckless.org", }, | |
59 { "tools", "tools.suckless.org", }, | |
60 { "libs", "libs.suckless.org", }, | |
61 { "e.V.", "ev.suckless.org" }, | |
62 { NULL, NULL } | |
63 }; | |
64 | |
65 void | |
66 die_perror(char *fmt, ...) | |
67 { | |
68 va_list ap; | |
69 | |
70 va_start(ap, fmt); | |
71 vfprintf(stderr, fmt, ap); | |
72 va_end(ap); | |
73 fputs(": ", stderr); | |
74 perror(NULL); | |
75 exit(1); | |
76 } | |
77 | |
78 void | |
79 die(char *fmt, ...) | |
80 { | |
81 va_list ap; | |
82 | |
83 va_start(ap, fmt); | |
84 vfprintf(stderr, fmt, ap); | |
85 va_end(ap); | |
86 fputc('\n', stderr); | |
87 exit(1); | |
88 } | |
89 | |
90 char * | |
91 xstrdup(const char *s) | |
92 { | |
93 char *p = strdup(s); | |
94 | |
95 if (!p) | |
96 die_perror("strdup"); | |
97 | |
98 return p; | |
99 } | |
100 | |
101 int | |
102 stat_isdir(const char *f) | |
103 { | |
104 struct stat s; | |
105 | |
106 if (stat(f, &s) == -1) { | |
107 perror(f); | |
108 return 0; | |
109 } | |
110 return S_ISDIR(s.st_mode); | |
111 } | |
112 | |
113 int | |
114 stat_isfile(const char *f) | |
115 { | |
116 struct stat s; | |
117 | |
118 if (stat(f, &s) == -1) { | |
119 perror(f); | |
120 return 0; | |
121 } | |
122 return S_ISREG(s.st_mode); | |
123 } | |
124 | |
125 int | |
126 spawn_wait(char **argv) | |
127 { | |
128 int status; | |
129 | |
130 switch (fork()) { | |
131 case 0: | |
132 execvp(argv[0], argv); | |
133 exit(126); | |
134 case -1: | |
135 return -1; | |
136 } | |
137 if (wait(&status) == -1) | |
138 return -1; | |
139 | |
140 return WIFEXITED(status) ? 0 : -1; | |
141 } | |
142 | |
143 int | |
144 oneline(char *buf, size_t bufsiz, const char *path) | |
145 { | |
146 char *r; | |
147 FILE *fp; | |
148 | |
149 if (!buf || bufsiz == 0) | |
150 return 0; | |
151 if (!(fp = fopen(path, "r"))) { | |
152 perror(path); | |
153 return 0; | |
154 } | |
155 | |
156 fgets(buf, bufsiz, fp); | |
157 if (ferror(fp)) | |
158 die_perror("fgets: %s", path); | |
159 | |
160 fclose(fp); | |
161 | |
162 for (r = buf; *r && *r != '\n'; ++r) | |
163 ; | |
164 *r = '\0'; | |
165 | |
166 return 1; | |
167 } | |
168 | |
169 void | |
170 print_name(const char *name) | |
171 { | |
172 int c; | |
173 | |
174 for (; (c = *name); ++name) | |
175 putchar((c == '_' || c == '-') ? ' ' : c); | |
176 } | |
177 | |
178 void | |
179 print_gopher_name(const char *name) | |
180 { | |
181 int c; | |
182 | |
183 for (; (c = *name); ++name) { | |
184 switch (c) { | |
185 case '\r': /* ignore CR */ | |
186 case '\n': /* ignore LF */ | |
187 break; | |
188 case '_': | |
189 case '-': | |
190 putchar(' '); | |
191 break; | |
192 case '\t': | |
193 printf(" "); | |
194 break; | |
195 case '|': /* escape separators */ | |
196 printf("\\|"); | |
197 break; | |
198 default: | |
199 putchar(c); | |
200 } | |
201 } | |
202 } | |
203 | |
204 void | |
205 print_header(void) | |
206 { | |
207 char title[TITLE_MAX]; | |
208 | |
209 printf(html_header, oneline(title, sizeof(title), ".title") ? | |
210 title : TITLE_DEFAULT); | |
211 } | |
212 | |
213 void | |
214 print_nav_bar(char *domain) | |
215 { | |
216 struct domain *d; | |
217 | |
218 puts("<div id=\"menu\">"); | |
219 for (d = domain_list; d->dir; ++d) { | |
220 if (strcmp(domain, d->dir) == 0) | |
221 printf("\t<a href=\"//%s/\"><b>%s</b></a>\n", | |
222 d->dir, d->label); | |
223 else | |
224 printf("\t<a href=\"//%s/\">%s</a>\n", | |
225 d->dir, d->label); | |
226 | |
227 } | |
228 fputs(html_nav_bar, stdout); | |
229 puts("</div>"); | |
230 puts("<hr class=\"hidden\"/>"); | |
231 } | |
232 | |
233 int | |
234 qsort_strcmp(const void *a, const void *b) | |
235 { | |
236 return strcmp(*(const char **)a, *(const char **)b); | |
237 } | |
238 | |
239 int | |
240 has_subdirs(char *this) | |
241 { | |
242 DIR *dp; | |
243 struct dirent *de; | |
244 char newdir[PATH_MAX]; | |
245 int dir; | |
246 | |
247 if ((dp = opendir(this ? this : ".")) == NULL) | |
248 die_perror("opendir: %s", this ? this : "."); | |
249 | |
250 dir = 0; | |
251 while (dir == 0 && (de = readdir(dp))) { | |
252 if (de->d_name[0] == '.') | |
253 continue; | |
254 snprintf(newdir, sizeof(newdir), this ? "%2$s/%1$s" : "%… | |
255 if (stat_isdir(newdir)) | |
256 dir = 1; | |
257 } | |
258 closedir(dp); | |
259 | |
260 return dir; | |
261 } | |
262 | |
263 void | |
264 menu_panel(char *domain, char *page, char *this, int depth) | |
265 { | |
266 DIR *dp; | |
267 struct dirent *de; | |
268 char newdir[PATH_MAX]; | |
269 char *d_list[PATH_MAX], *d; | |
270 size_t d_len, l; | |
271 int i, highlight; | |
272 | |
273 if ((dp = opendir(this ? this : ".")) == NULL) | |
274 die_perror("opendir: %s", this ? this : "."); | |
275 | |
276 d_len = 0; | |
277 while (d_len < LEN(d_list) && (de = readdir(dp))) | |
278 d_list[d_len++] = xstrdup(de->d_name); | |
279 closedir(dp); | |
280 | |
281 qsort(d_list, d_len, sizeof *d_list, qsort_strcmp); | |
282 | |
283 for (l = 0; l < d_len; free(d_list[l++])) { | |
284 d = d_list[l]; | |
285 if (*d == '.') | |
286 continue; | |
287 snprintf(newdir, sizeof(newdir), this ? "%2$s/%1$s" : "%… | |
288 d, this); | |
289 if (!stat_isdir(newdir)) | |
290 continue; | |
291 | |
292 highlight = page && !strncmp(newdir, page, strlen(newdir… | |
293 strchr("/", page[strlen(newdir)]); /* / or NUL */ | |
294 | |
295 for (i = 0; i < depth + 1; ++i) | |
296 putchar('\t'); | |
297 fputs("<li>", stdout); | |
298 if (highlight) { | |
299 printf("<a href=\"//%s/%s/\"><b>", domain, newdi… | |
300 print_name(d); | |
301 fputs("/</b></a>", stdout); | |
302 } else { | |
303 printf("<a href=\"//%s/%s/\">", domain, newdir); | |
304 print_name(d); | |
305 fputs("/</a>", stdout); | |
306 } | |
307 | |
308 if (highlight && has_subdirs(newdir)) { | |
309 putchar('\n'); | |
310 for (i = 0; i < depth + 2; ++i) | |
311 putchar('\t'); | |
312 puts("<ul>"); | |
313 menu_panel(domain, page, newdir, depth + 1); | |
314 for (i = 0; i < depth + 2; ++i) | |
315 putchar('\t'); | |
316 puts("</ul>"); | |
317 for (i = 0; i < depth + 1; ++i) | |
318 putchar('\t'); | |
319 } | |
320 puts("</li>"); | |
321 } | |
322 } | |
323 | |
324 void | |
325 print_menu_panel(char *domain, char *page) | |
326 { | |
327 fputs("<div id=\"nav\">\n\t<ul>\n\t<li>", stdout); | |
328 if (!page) | |
329 puts("<a href=\"/\"><b>about</b></a></li>"); | |
330 else | |
331 puts("<a href=\"/\">about</a></li>"); | |
332 menu_panel(domain, page, NULL, 0); | |
333 puts("\t</ul>"); | |
334 puts("</div>"); | |
335 puts("<hr class=\"hidden\"/>"); | |
336 } | |
337 | |
338 void | |
339 print_content(char *domain, char *page) | |
340 { | |
341 char index[PATH_MAX]; | |
342 char *argv[] = { CONVERTER, index, NULL }; | |
343 | |
344 snprintf(index, sizeof(index), page ? "%2$s/%1$s" : "%s", | |
345 "index.md", page); | |
346 | |
347 puts("<div id=\"main\">\n"); | |
348 | |
349 if (stat_isfile(index)) { | |
350 fflush(stdout); | |
351 if (spawn_wait(argv) == -1) | |
352 die_perror("spawn: %s/%s/%s", domain, page, inde… | |
353 } | |
354 puts("</div>\n"); | |
355 } | |
356 | |
357 void | |
358 print_footer(void) | |
359 { | |
360 fputs(html_footer, stdout); | |
361 } | |
362 | |
363 void | |
364 print_gopher_item(char type, char *disp, char *domain, char *path, | |
365 char * file, int port, int level) | |
366 { | |
367 char d[GOPHER_ROW_MAX]; | |
368 int l; | |
369 | |
370 strncpy(d, disp, sizeof(d) - 1); | |
371 d[sizeof(d) - 1] = '\0'; | |
372 | |
373 printf("[%c|", type); | |
374 | |
375 for (l = 0; l < level; ++l) | |
376 printf(" "); | |
377 print_gopher_name(d); | |
378 if (type == '1') | |
379 putchar('/'); | |
380 putchar('|'); | |
381 | |
382 if (path) | |
383 printf("/%s", path); | |
384 if (file) | |
385 printf("/%s", file); | |
386 | |
387 printf("|%s|%d]\n", domain, port); | |
388 } | |
389 | |
390 void | |
391 print_gopher_header(void) | |
392 { | |
393 char title[GOPHER_ROW_MAX]; | |
394 | |
395 printf(gopher_header, oneline(title, sizeof(title), ".title") ? | |
396 title : TITLE_DEFAULT); | |
397 } | |
398 | |
399 int | |
400 has_index(char *this) | |
401 { | |
402 DIR *dp; | |
403 struct dirent *de; | |
404 char newdir[PATH_MAX]; | |
405 int index; | |
406 | |
407 if ((dp = opendir(this ? this : ".")) == NULL) | |
408 die_perror("opendir: %s", this ? this : "."); | |
409 | |
410 index = 0; | |
411 while (index == 0 && (de = readdir(dp))) { | |
412 if (de->d_name[0] == '.') | |
413 continue; | |
414 snprintf(newdir, sizeof(newdir), this ? "%2$s/%1$s" : "%… | |
415 if (stat_isfile(newdir) && strcmp(de->d_name, "index.md"… | |
416 index = 1; | |
417 } | |
418 closedir(dp); | |
419 | |
420 return index; | |
421 } | |
422 | |
423 void | |
424 print_gopher_menu(char *domain, char *this) | |
425 { | |
426 DIR *dp; | |
427 struct dirent *de; | |
428 char newdir[PATH_MAX]; | |
429 char *d_list[PATH_MAX], *d; | |
430 size_t d_len, l; | |
431 int depth = this ? 1 : 0; | |
432 | |
433 if ((dp = opendir(this ? this : ".")) == NULL) | |
434 die_perror("opendir: %s", this ? this : "."); | |
435 | |
436 d_len = 0; | |
437 while (d_len < LEN(d_list) && (de = readdir(dp))) { | |
438 d_list[d_len++] = xstrdup(de->d_name); | |
439 } | |
440 closedir(dp); | |
441 | |
442 qsort(d_list, d_len, sizeof *d_list, qsort_strcmp); | |
443 | |
444 printf("%s/\n", this ? this : ""); | |
445 | |
446 if (has_index(this)) | |
447 print_gopher_item('0', "about", domain, this ? this : NU… | |
448 "index.md", GOPHER_PORT, depth); | |
449 | |
450 for (l = 0; l < d_len; free(d_list[l++])) { | |
451 d = d_list[l]; | |
452 if (*d == '.') | |
453 continue; | |
454 snprintf(newdir, sizeof(newdir), this ? "%2$s/%1$s" : "%… | |
455 d, this); | |
456 if (!stat_isdir(newdir)) | |
457 continue; | |
458 | |
459 if (has_subdirs(newdir)) | |
460 print_gopher_item('1', d, domain, newdir, NULL, | |
461 GOPHER_PORT, depth); | |
462 else | |
463 print_gopher_item('0', d, domain, newdir, "index… | |
464 GOPHER_PORT, depth); | |
465 } | |
466 } | |
467 | |
468 void | |
469 print_gopher_nav(char *domain) | |
470 { | |
471 struct domain *d; | |
472 | |
473 for (d = domain_list; d->dir; ++d) { | |
474 if (strcmp(domain, d->dir) == 0) | |
475 printf("%s\n", d->label); | |
476 else | |
477 print_gopher_item('1', d->label, d->dir, NULL, N… | |
478 GOPHER_PORT, 0); | |
479 } | |
480 | |
481 putchar('\n'); | |
482 print_gopher_item('1', "download", "dl.suckless.org", NULL, NULL, | |
483 GOPHER_PORT, 0); | |
484 print_gopher_item('1', "source", "git.suckless.org", NULL, NULL, | |
485 GOPHER_PORT, 0); | |
486 } | |
487 | |
488 void | |
489 usage(char *argv0) | |
490 { | |
491 die("usage: %s [-g] directory", argv0); | |
492 } | |
493 | |
494 int | |
495 main(int argc, char *argv[]) | |
496 { | |
497 char *domain = NULL, *page; | |
498 int gopher = 0, i, j; | |
499 | |
500 for (i = 1; i < argc; i++) { | |
501 if (argv[i][0] != '-') { | |
502 if (domain) | |
503 usage(argv[0]); | |
504 domain = argv[i]; | |
505 continue; | |
506 } | |
507 for (j = 1; j < argv[i][j]; j++) { | |
508 switch (argv[i][j]) { | |
509 case 'g': | |
510 gopher = 1; | |
511 break; | |
512 default: | |
513 usage(argv[0]); | |
514 } | |
515 } | |
516 } | |
517 if (domain == NULL) | |
518 usage(argv[0]); | |
519 | |
520 domain = xstrdup(domain); | |
521 if ((page = strchr(domain, '/'))) { | |
522 *page++ = '\0'; | |
523 if (strlen(page) == 0) | |
524 page = NULL; | |
525 } | |
526 if (chdir(domain) == -1) | |
527 die_perror("chdir: %s", domain); | |
528 | |
529 if (gopher) { | |
530 print_gopher_header(); | |
531 print_gopher_menu(domain, page); | |
532 printf("-------------\n"); | |
533 print_gopher_nav(domain); | |
534 } else { | |
535 print_header(); | |
536 print_nav_bar(domain); | |
537 puts("<div id=\"content\">"); | |
538 print_menu_panel(domain, page); | |
539 print_content(domain, page); | |
540 puts("</div>\n"); | |
541 print_footer(); | |
542 } | |
543 | |
544 return 0; | |
545 } |