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.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>&nbsp;\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 }