| 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 } |