Introduction
Introduction Statistics Contact Development Disclaimer Help
saait.c - saait - the most boring static page generator
git clone git://git.codemadness.org/saait
Log
Files
Refs
README
LICENSE
---
saait.c (12892B)
---
1 #include <ctype.h>
2 #include <dirent.h>
3 #include <errno.h>
4 #include <limits.h>
5 #include <stdio.h>
6 #include <stdint.h>
7 #include <stdlib.h>
8 #include <string.h>
9
10 /* OpenBSD pledge(2) */
11 #ifdef __OpenBSD__
12 #include <unistd.h>
13 #else
14 #define pledge(p1,p2) 0
15 #endif
16
17 /* This is the blocksize of my disk, use atleast an equal or higher valu…
18 a multiple of 2 for better performance ((struct stat).st_blksize). */
19 #define READ_BUF_SIZ 16384
20 #define LEN(s) (sizeof(s)/sizeof(*s))
21
22 enum { BlockHeader = 0, BlockItem, BlockFooter, BlockLast };
23
24 struct variable {
25 char *key, *value;
26 struct variable *next;
27 };
28
29 struct block {
30 char *name; /* filename */
31 char *data; /* content (set at runtime) */
32 };
33
34 struct template {
35 char *name;
36 /* blocks: header, item, footer */
37 struct block blocks[BlockLast];
38 /* output FILE * (set at runtime) */
39 FILE *fp;
40 };
41
42 static const char *configfile = "config.cfg";
43 static const char *outputdir = "output";
44 static const char *templatedir = "templates";
45
46 static struct variable *global; /* global config variables */
47
48 char *
49 estrdup(const char *s)
50 {
51 char *p;
52
53 if (!(p = strdup(s))) {
54 fprintf(stderr, "strdup: %s\n", strerror(errno));
55 exit(1);
56 }
57 return p;
58 }
59
60 void *
61 ecalloc(size_t nmemb, size_t size)
62 {
63 void *p;
64
65 if (!(p = calloc(nmemb, size))) {
66 fprintf(stderr, "calloc: %s\n", strerror(errno));
67 exit(1);
68 }
69 return p;
70 }
71
72 void *
73 erealloc(void *ptr, size_t size)
74 {
75 void *p;
76
77 if (!(p = realloc(ptr, size))) {
78 fprintf(stderr, "realloc: %s\n", strerror(errno));
79 exit(1);
80 }
81 return p;
82 }
83
84 FILE *
85 efopen(const char *path, const char *mode)
86 {
87 FILE *fp;
88
89 if (!(fp = fopen(path, mode))) {
90 fprintf(stderr, "fopen: %s, mode: %s: %s\n",
91 path, mode, strerror(errno));
92 exit(1);
93 }
94 return fp;
95 }
96
97 void
98 catfile(FILE *fpin, const char *ifile, FILE *fpout, const char *ofile)
99 {
100 char buf[READ_BUF_SIZ];
101 size_t r;
102
103 while (!feof(fpin)) {
104 if (!(r = fread(buf, 1, sizeof(buf), fpin)))
105 break;
106 if ((fwrite(buf, 1, r, fpout)) != r)
107 break;
108 if (r != sizeof(buf))
109 break;
110 }
111 if (ferror(fpin)) {
112 fprintf(stderr, "%s -> %s: error reading data from strea…
113 ifile, ofile, strerror(errno));
114 exit(1);
115 }
116 if (ferror(fpout)) {
117 fprintf(stderr, "%s -> %s: error writing data to stream:…
118 ifile, ofile, strerror(errno));
119 exit(1);
120 }
121 }
122
123 char *
124 readfile(const char *file)
125 {
126 FILE *fp;
127 char *buf;
128 size_t n, len = 0, size = 0;
129
130 fp = efopen(file, "rb");
131 buf = ecalloc(1, size + 1); /* always allocate an empty buffer */
132 while (!feof(fp)) {
133 if (len + READ_BUF_SIZ + 1 > size) {
134 /* allocate size: common case is small textfiles…
135 size += READ_BUF_SIZ;
136 buf = erealloc(buf, size + 1);
137 }
138 if (!(n = fread(&buf[len], 1, READ_BUF_SIZ, fp)))
139 break;
140 len += n;
141 buf[len] = '\0';
142 if (n != READ_BUF_SIZ)
143 break;
144 }
145 if (ferror(fp)) {
146 fprintf(stderr, "fread: file: %s: %s\n", file, strerror(…
147 exit(1);
148 }
149 fclose(fp);
150
151 return buf;
152 }
153
154 struct variable *
155 newvar(const char *key, const char *value)
156 {
157 struct variable *v;
158
159 v = ecalloc(1, sizeof(*v));
160 v->key = estrdup(key);
161 v->value = estrdup(value);
162
163 return v;
164 }
165
166 /* uses var->key as key */
167 void
168 setvar(struct variable **vars, struct variable *var, int override)
169 {
170 struct variable *p, *v;
171
172 /* new */
173 if (!*vars) {
174 *vars = var;
175 return;
176 }
177
178 /* search: set or append */
179 for (p = NULL, v = *vars; v; v = v->next, p = v) {
180 if (!strcmp(var->key, v->key)) {
181 if (!override)
182 return;
183 /* NOTE: keep v->next */
184 var->next = v->next;
185 if (p)
186 p->next = var;
187 else
188 *vars = var;
189 free(v->key);
190 free(v->value);
191 free(v);
192 return;
193 }
194 /* append */
195 if (!v->next) {
196 var->next = NULL;
197 v->next = var;
198 return;
199 }
200 }
201 }
202
203 struct variable *
204 getvar(struct variable *vars, char *key)
205 {
206 struct variable *v;
207
208 for (v = vars; v; v = v->next)
209 if (!strcmp(key, v->key))
210 return v;
211 return NULL;
212 }
213
214 void
215 freevars(struct variable *vars)
216 {
217 struct variable *v, *tmp;
218
219 for (v = vars; v; ) {
220 tmp = v->next;
221 free(v->key);
222 free(v->value);
223 free(v);
224 v = tmp;
225 }
226 }
227
228 struct variable *
229 parsevars(const char *file, const char *s)
230 {
231 struct variable *vars = NULL, *v;
232 const char *keystart, *keyend, *valuestart, *valueend;
233 size_t linenr = 1;
234
235 for (; *s; ) {
236 if (*s == '\r' || *s == '\n') {
237 linenr += (*s == '\n');
238 s++;
239 continue;
240 }
241
242 /* comment start with #, skip to newline */
243 if (*s == '#') {
244 s++;
245 s = &s[strcspn(s, "\n")];
246 continue;
247 }
248
249 /* trim whitespace before key */
250 s = &s[strspn(s, " \t")];
251
252 keystart = s;
253 s = &s[strcspn(s, "=\r\n")];
254 if (*s != '=') {
255 fprintf(stderr, "%s:%zu: error: no variable\n",
256 file, linenr);
257 exit(1);
258 }
259
260 /* trim whitespace at end of key: but whitespace inside …
261 are allowed */
262 for (keyend = s++; keyend > keystart &&
263 (keyend[-1] == ' ' || keyend[-1] == '\t…
264 keyend--)
265 ;
266 /* no variable name: skip */
267 if (keystart == keyend) {
268 fprintf(stderr, "%s:%zu: error: invalid variable…
269 file, linenr);
270 exit(1);
271 }
272
273 /* trim whitespace before value */
274 valuestart = &s[strspn(s, " \t")];
275 s = &s[strcspn(s, "\r\n")];
276 valueend = s;
277
278 v = ecalloc(1, sizeof(*v));
279 v->key = ecalloc(1, keyend - keystart + 1);
280 memcpy(v->key, keystart, keyend - keystart);
281 v->value = ecalloc(1, valueend - valuestart + 1);
282 memcpy(v->value, valuestart, valueend - valuestart);
283
284 setvar(&vars, v, 1);
285 }
286 return vars;
287 }
288
289 struct variable *
290 readconfig(const char *file)
291 {
292 struct variable *c;
293 char *data;
294
295 data = readfile(file);
296 c = parsevars(file, data);
297 free(data);
298
299 return c;
300 }
301
302 /* Escape characters below as HTML 2.0 / XML 1.0. */
303 void
304 xmlencode(const char *s, FILE *fp)
305 {
306 for (; *s; s++) {
307 switch (*s) {
308 case '<': fputs("&lt;", fp); break;
309 case '>': fputs("&gt;", fp); break;
310 case '\'': fputs("&#39;", fp); break;
311 case '&': fputs("&amp;", fp); break;
312 case '"': fputs("&quot;", fp); break;
313 default: fputc(*s, fp);
314 }
315 }
316 }
317
318 void
319 writepage(FILE *fp, const char *name, const char *forname,
320 struct variable *c, char *s)
321 {
322 FILE *fpin;
323 struct variable *v;
324 char *key;
325 size_t keylen, linenr = 1;
326 int op, tmpc;
327
328 for (; *s; s++) {
329 op = *s;
330 switch (*s) {
331 case '#': /* insert value non-escaped */
332 case '$': /* insert value escaped */
333 case '%': /* insert contents of filename set in variable…
334 if (*(s + 1) == '{') {
335 s += 2;
336 break;
337 }
338 fputc(*s, fp);
339 continue;
340 case '\n':
341 linenr++; /* FALLTHROUGH */
342 default:
343 fputc(*s, fp);
344 continue;
345 }
346
347 /* variable case */
348 for (; *s && isspace((unsigned char)*s); s++)
349 ;
350 key = s;
351 for (keylen = 0; *s && *s != '}'; s++)
352 keylen++;
353 /* trim right whitespace */
354 for (; keylen && isspace((unsigned char)key[keylen - 1])…
355 keylen--;
356
357 /* temporary NUL terminate */
358 tmpc = key[keylen];
359 key[keylen] = '\0';
360
361 /* lookup variable in config, if no config or not found …
362 global config */
363 if (!c || !(v = getvar(c, key)))
364 v = getvar(global, key);
365 key[keylen] = tmpc; /* restore NUL terminator to origina…
366
367 if (!v) {
368 fprintf(stderr, "%s:%zu: error: undefined variab…
369 name, linenr, (int)keylen, key,
370 forname ? " for " : "", forname ? fornam…
371 exit(1);
372 }
373
374 switch (op) {
375 case '#':
376 fputs(v->value, fp);
377 break;
378 case '$':
379 xmlencode(v->value, fp);
380 break;
381 case '%':
382 if (!v->value[0])
383 break;
384 fpin = efopen(v->value, "rb");
385 catfile(fpin, v->value, fp, name);
386 fclose(fpin);
387 break;
388 }
389 }
390 }
391
392 void
393 usage(const char *argv0)
394 {
395 fprintf(stderr, "%s [-c configfile] [-o outputdir] [-t templates…
396 "pages...\n", argv0);
397 exit(1);
398 }
399
400 int
401 main(int argc, char *argv[])
402 {
403 struct template *t, *templates = NULL;
404 struct block *b;
405 struct variable *c, *v;
406 DIR *bdir, *idir;
407 struct dirent *ir, *br;
408 char file[PATH_MAX + 1], contentfile[PATH_MAX + 1], path[PATH_MA…
409 char outputfile[PATH_MAX + 1], *p, *filename;
410 size_t i, j, k, templateslen;
411 int argi, r;
412
413 if (pledge("stdio cpath rpath wpath", NULL) == -1) {
414 fprintf(stderr, "pledge: %s\n", strerror(errno));
415 return 1;
416 }
417
418 for (argi = 1; argi < argc; argi++) {
419 if (argv[argi][0] != '-')
420 break;
421 if (argi + 1 >= argc)
422 usage(argv[0]);
423 switch (argv[argi][1]) {
424 case 'c': configfile = argv[++argi]; break;
425 case 'o': outputdir = argv[++argi]; break;
426 case 't': templatedir = argv[++argi]; break;
427 default: usage(argv[0]); break;
428 }
429 }
430
431 /* global config */
432 global = readconfig(configfile);
433
434 /* load templates, must start with "header.", "item." or "footer…
435 templateslen = 0;
436 if (!(bdir = opendir(templatedir))) {
437 fprintf(stderr, "opendir: %s: %s\n", templatedir, strerr…
438 exit(1);
439 }
440
441 while ((br = readdir(bdir))) {
442 if (br->d_name[0] == '.')
443 continue;
444
445 r = snprintf(path, sizeof(path), "%s/%s", templatedir,
446 br->d_name);
447 if (r < 0 || (size_t)r >= sizeof(path)) {
448 fprintf(stderr, "path truncated: '%s/%s'\n",
449 templatedir, br->d_name);
450 exit(1);
451 }
452
453 if (!(idir = opendir(path))) {
454 fprintf(stderr, "opendir: %s: %s\n", path, strer…
455 exit(1);
456 }
457
458 templateslen++;
459 /* check overflow */
460 if (SIZE_MAX / templateslen < sizeof(*templates)) {
461 fprintf(stderr, "realloc: too many templates: %z…
462 exit(1);
463 }
464 templates = erealloc(templates, templateslen * sizeof(*t…
465 t = &templates[templateslen - 1];
466 memset(t, 0, sizeof(struct template));
467 t->name = estrdup(br->d_name);
468
469 while ((ir = readdir(idir))) {
470 if (!strncmp(ir->d_name, "header.", sizeof("head…
471 b = &(t->blocks[BlockHeader]);
472 else if (!strncmp(ir->d_name, "item.", sizeof("i…
473 b = &(t->blocks[BlockItem]);
474 else if (!strncmp(ir->d_name, "footer.", sizeof(…
475 b = &(t->blocks[BlockFooter]);
476 else
477 continue;
478
479 r = snprintf(file, sizeof(file), "%s/%s", path,
480 ir->d_name);
481 if (r < 0 || (size_t)r >= sizeof(file)) {
482 fprintf(stderr, "path truncated: '%s/%s'…
483 path, ir->d_name);
484 exit(1);
485 }
486 b->name = estrdup(file);
487 b->data = readfile(file);
488 }
489 closedir(idir);
490 }
491 closedir(bdir);
492
493 /* open output files for templates and write header, except for …
494 for (i = 0; i < templateslen; i++) {
495 /* "page" is a special case */
496 if (!strcmp(templates[i].name, "page"))
497 continue;
498 r = snprintf(file, sizeof(file), "%s/%s", outputdir,
499 templates[i].name);
500 if (r < 0 || (size_t)r >= sizeof(file)) {
501 fprintf(stderr, "path truncated: '%s/%s'\n", out…
502 templates[i].name);
503 exit(1);
504 }
505 templates[i].fp = efopen(file, "wb");
506
507 /* header */
508 b = &templates[i].blocks[BlockHeader];
509 if (b->name)
510 writepage(templates[i].fp, b->name, NULL, NULL, …
511 }
512
513 /* pages */
514 for (i = argi; i < (size_t)argc; i++) {
515 c = readconfig(argv[i]);
516
517 if ((p = strrchr(argv[i], '.')))
518 r = snprintf(contentfile, sizeof(contentfile), "…
519 (int)(p - argv[i]), argv[i]);
520 else
521 r = snprintf(contentfile, sizeof(contentfile), "…
522 if (r < 0 || (size_t)r >= sizeof(contentfile)) {
523 fprintf(stderr, "path truncated for file: '%s'\n…
524 exit(1);
525 }
526 /* set contentfile, but allow to override it */
527 setvar(&c, newvar("contentfile", contentfile), 0);
528
529 if ((v = getvar(c, "filename"))) {
530 filename = v->value;
531 } else {
532 /* set output filename (with path removed), but …
533 to override it */
534 if ((p = strrchr(contentfile, '/')))
535 filename = &contentfile[p - contentfile …
536 else
537 filename = contentfile;
538
539 setvar(&c, newvar("filename", filename), 0);
540 }
541
542 /* item blocks */
543 for (j = 0; j < templateslen; j++) {
544 /* "page" is a special case */
545 if (!strcmp(templates[j].name, "page")) {
546 r = snprintf(outputfile, sizeof(outputfi…
547 outputdir, filename);
548 if (r < 0 || (size_t)r >= sizeof(outputf…
549 fprintf(stderr, "path truncated:…
550 outputdir, filename);
551 exit(1);
552 }
553
554 /* "page" template files are opened per …
555 as opposed to other templates */
556 templates[j].fp = efopen(outputfile, "wb…
557 for (k = 0; k < LEN(templates[j].blocks)…
558 b = &templates[j].blocks[k];
559 if (b->name)
560 writepage(templates[j].f…
561 b->name, argv[…
562 b->data);
563 }
564 fclose(templates[j].fp);
565 } else {
566 b = &templates[j].blocks[BlockItem];
567 if (b->name)
568 writepage(templates[j].fp, b->na…
569 argv[i], c, b->data);
570 }
571 }
572 freevars(c);
573 }
574
575 /* write footer, except for "page" */
576 for (i = 0; i < templateslen; i++) {
577 if (!strcmp(templates[i].name, "page"))
578 continue;
579 b = &templates[i].blocks[BlockFooter];
580 if (b->name)
581 writepage(templates[i].fp, b->name, NULL, NULL, …
582 }
583
584 return 0;
585 }
You are viewing proxied material from codemadness.org. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.