build-page.c - sites - public wiki contents of suckless.org
(HTM) git clone git://git.suckless.org/sites
(DIR) Log
(DIR) Files
(DIR) 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.org/pub/style.css\"/>\n"
30 "</head>\n"
31 "\n"
32 "<div id=\"header\">\n"
33 "\t<a href=\"//suckless.org/\"><img src=\"//suckless.org/logo.svg\" alt=\"\"/></a> \n"
34 "\t<a id=\"headerLink\" href=\"//suckless.org/\">suckless.org</a>\n"
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" : "%s", de->d_name, this);
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" : "%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, newdir);
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, index);
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" : "%s", de->d_name, this);
415 if (stat_isfile(newdir) && strcmp(de->d_name, "index.md") == 0)
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 : NULL,
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" : "%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.md",
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, NULL,
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 }