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 }