rework templates support - saait - the most boring static page generator
 (HTM) git clone git://git.codemadness.org/saait
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 015bc6ccd23e3e1ac78b6373e26d648bff4d08e0
 (DIR) parent aac9d4d389441f56dd883b5eec1aea7672797e27
 (HTM) Author: Hiltjo Posthuma <hiltjo@codemadness.org>
       Date:   Sat, 25 May 2019 20:33:33 +0200
       
       rework templates support
       
       templates are now iterated from the templates directory. It detects the
       following prefixes:
       
       "header." - header block
       "item."   - item block
       "footer." - footer block
       
       when a block is missing it is simply skipped (an empty file is not needed
       anymore).
       
       - This now allows adding/remove templates at run-time without having to specify the
       templates in config.h and recompile.
       
       - remove listing tcc as supported. It has limited POSIX support and does not
       support the required opendir,readdir_r,closedir interfaces.
       
       Diffstat:
         M README                              |       2 +-
         M config.h                            |      46 -------------------------------
         M saait.c                             |     117 ++++++++++++++++++++++++-------
         D templates/urllist.txt/footer.txt    |       0 
         D templates/urllist.txt/header.txt    |       0 
       
       5 files changed, 91 insertions(+), 74 deletions(-)
       ---
 (DIR) diff --git a/README b/README
       @@ -8,7 +8,7 @@ Some parts are intentionally hardcoded, C knowledge is required.
        
        Dependencies:
        -------------
       -- C compiler (C99), tested on gcc, clang, tcc.
       +- C compiler (C99), tested on gcc, clang.
        - libc
        
        
 (DIR) diff --git a/config.h b/config.h
       @@ -16,49 +16,3 @@ struct template {
                /* output FILE * (set at runtime) */
                FILE *fp;
        };
       -
       -static struct template templates[] = {
       -        /* special: will be applied per page */
       -        {
       -                .name = "page", .blocks = {
       -                        { .name = "page/header.html" },
       -                        { .name = "page/item.html" },
       -                        { .name = "page/footer.html" }
       -                }
       -        },
       -        {
       -                .name = "atom.xml", .blocks = {
       -                        { .name = "atom.xml/header.xml" },
       -                        { .name = "atom.xml/item.xml" },
       -                        { .name = "atom.xml/footer.xml" }
       -                }
       -        },
       -        {
       -                .name = "index.html", .blocks = {
       -                        { .name = "index.html/header.html" },
       -                        { .name = "index.html/item.html" },
       -                        { .name = "index.html/footer.html" }
       -                }
       -        },
       -        {
       -                .name = "sitemap.xml", .blocks = {
       -                        { .name = "sitemap.xml/header.xml" },
       -                        { .name = "sitemap.xml/item.xml" },
       -                        { .name = "sitemap.xml/footer.xml" }
       -                }
       -        },
       -        {
       -                .name = "urllist.txt", .blocks = {
       -                        { .name = "urllist.txt/header.txt" },
       -                        { .name = "urllist.txt/item.txt" },
       -                        { .name = "urllist.txt/footer.txt" }
       -                }
       -        },
       -        {
       -                .name = "twtxt.txt", .blocks = {
       -                        { .name = "twtxt.txt/header.txt" },
       -                        { .name = "twtxt.txt/item.txt" },
       -                        { .name = "twtxt.txt/footer.txt" }
       -                }
       -        }
       -};
 (DIR) diff --git a/saait.c b/saait.c
       @@ -1,7 +1,11 @@
       +#include <sys/types.h>
       +
        #include <ctype.h>
       +#include <dirent.h>
        #include <errno.h>
        #include <limits.h>
        #include <stdio.h>
       +#include <stdint.h>
        #include <stdlib.h>
        #include <string.h>
        
       @@ -371,12 +375,15 @@ usage(void)
        int
        main(int argc, char *argv[])
        {
       +        struct template *t, *templates = NULL;
                struct block *b;
                struct variable *c, *v;
       -        char file[PATH_MAX + 1], htmlfile[PATH_MAX + 1];
       -        char outputfile[PATH_MAX + 1], *p;
       -        size_t i, j, k;
       -        int r;
       +        DIR *bdir, *idir;
       +        struct dirent be, ie, *br, *ir;
       +        char file[PATH_MAX + 1], htmlfile[PATH_MAX + 1], path[PATH_MAX + 1];
       +        char outputfile[PATH_MAX + 1], *p, *filename;
       +        size_t i, j, k, templateslen;
       +        int r, doindex;
        
                if (pledge("stdio cpath rpath wpath", NULL) == -1) {
                        fprintf(stderr, "pledge: %s\n", strerror(errno));
       @@ -400,23 +407,69 @@ main(int argc, char *argv[])
                /* global config */
                global = readconfig(configfile);
        
       -        /* load templates: all templates must be loaded correctly first. */
       -        for (i = 0; i < LEN(templates); i++) {
       -                for (j = 0; j < LEN(templates[i].blocks); j++) {
       -                        b = &templates[i].blocks[j];
       -                        r = snprintf(file, sizeof(file), "%s/%s", templatedir,
       -                                     b->name);
       +        /* load templates, must start with header., item. or footer.  */
       +        templateslen = 0;
       +        if (!(bdir = opendir(templatedir))) {
       +                fprintf(stderr, "opendir: %s: %s\n", templatedir, strerror(errno));
       +                exit(1);
       +        }
       +
       +        while (!readdir_r(bdir, &be, &br) && br) {
       +                if (br->d_name[0] == '.')
       +                        continue;
       +
       +                r = snprintf(path, sizeof(path), "%s/%s", templatedir,
       +                             br->d_name);
       +                if (r < 0 || (size_t)r >= sizeof(path)) {
       +                        fprintf(stderr, "path truncated: '%s/%s'\n",
       +                                templatedir, br->d_name);
       +                        exit(1);
       +                }
       +
       +                if (!(idir = opendir(path))) {
       +                        fprintf(stderr, "opendir: %s: %s\n", path, strerror(errno));
       +                        exit(1);
       +                }
       +
       +                templateslen++;
       +                /* check overflow */
       +                if (SIZE_MAX / templateslen < sizeof(*templates)) {
       +                        fprintf(stderr, "realloc: too many templates: %zu\n", templateslen);
       +                        exit(1);
       +                }
       +                templates = realloc(templates, templateslen * sizeof(*templates));
       +                t = &templates[templateslen - 1];
       +                memset(t, 0, sizeof(struct template));
       +                t->name = estrdup(br->d_name);
       +
       +                while (!readdir_r(idir, &ie, &ir) && ir) {
       +                        if (!strncmp(ir->d_name, "header.", sizeof("header.") - 1))
       +                                b = &(t->blocks[BlockHeader]);
       +                        else if (!strncmp(ir->d_name, "item.", sizeof("item.") - 1))
       +                                b = &(t->blocks[BlockItem]);
       +                        else if (!strncmp(ir->d_name, "footer.", sizeof("footer.") - 1))
       +                                b = &(t->blocks[BlockFooter]);
       +                        else
       +                                continue;
       +
       +                        r = snprintf(file, sizeof(file), "%s/%s", path,
       +                                     ir->d_name);
                                if (r < 0 || (size_t)r >= sizeof(file)) {
                                        fprintf(stderr, "path truncated: '%s/%s'\n",
       -                                        templatedir, b->name);
       +                                        path, ir->d_name);
                                        exit(1);
                                }
                                b->data = readfile(file);
       +                        b->name = estrdup(file);
                        }
       +                closedir(idir);
                }
       +        closedir(bdir);
        
                /* header */
       -        for (i = 0; i < LEN(templates); i++) {
       +        for (i = 0; i < templateslen; i++) {
       +                /* TODO: document special "page".
       +                   TODO: make "page" template mandatory? */
                        if (!strcmp(templates[i].name, "page"))
                                continue;
                        r = snprintf(file, sizeof(file), "%s/%s", outputdir,
       @@ -428,13 +481,17 @@ main(int argc, char *argv[])
                        }
                        templates[i].fp = efopen(file, "wb");
                        b = &templates[i].blocks[BlockHeader];
       -                writepage(templates[i].fp, b->name, NULL, b->data);
       +                if (b->name)
       +                        writepage(templates[i].fp, b->name, NULL, b->data);
                }
        
                /* pages */
                for (i = 0; i < (size_t)argc; i++) {
                        c = readconfig(argv[i]);
        
       +                v = getvar(c, "index");
       +                doindex = !(v && v->value[0] == '0');
       +
                        if ((p = strrchr(argv[i], '.')))
                                r = snprintf(htmlfile, sizeof(htmlfile), "%.*s.html",
                                             (int)(p - argv[i]), argv[i]);
       @@ -450,35 +507,40 @@ main(int argc, char *argv[])
                        /* set HTML output filename (with part removed), but allow to
                           override it */
                        if ((p = strrchr(htmlfile, '/')))
       -                        setvar(&c, newvar("filename", &htmlfile[p - htmlfile + 1]), 0);
       +                        filename = &htmlfile[p - htmlfile + 1];
                        else
       -                        setvar(&c, newvar("filename", htmlfile), 0);
       +                        filename = htmlfile;
        
       -                /* get output filename */
       -                v = getvar(c, "filename");
       +                if ((v = getvar(c, "filename")))
       +                        filename = v->value;
       +                else
       +                        setvar(&c, newvar("filename", filename), 0);
        
       -                /* item block */
       -                for (j = 0; j < LEN(templates); j++) {
       +                /* item blocks */
       +                for (j = 0; j < templateslen; j++) {
                                /* TODO: page is a special case for now */
                                if (!strcmp(templates[j].name, "page")) {
       +                                /* TODO: config option to not process as page (opposite of "index" option)? */
                                        r = snprintf(outputfile, sizeof(outputfile), "%s/%s",
       -                                             outputdir, v->value);
       +                                             outputdir, filename);
                                        if (r < 0 || (size_t)r >= sizeof(outputfile)) {
                                                fprintf(stderr, "path truncated: '%s/%s'\n",
       -                                                outputdir, v->value);
       +                                                outputdir, filename);
                                                exit(1);
                                        }
        
       +                                /* "page" template files are opened per item
       +                                   as opposed to other templates */
                                        templates[j].fp = efopen(outputfile, "wb");
                                        for (k = 0; k < LEN(templates[j].blocks); k++) {
                                                b = &templates[j].blocks[k];
       -                                        writepage(templates[j].fp, b->name, c, b->data);
       +                                        if (b->name)
       +                                                writepage(templates[j].fp, b->name, c, b->data);
                                        }
                                        fclose(templates[j].fp);
                                } else {
       -                                v = getvar(c, "index");
       -                                if (v && v->value[0] == '0')
       -                                        continue;
       +                                if (!doindex)
       +                                        continue; /* do not include in index */
                                        b = &templates[j].blocks[BlockItem];
                                        writepage(templates[j].fp, b->name, c, b->data);
                                }
       @@ -486,11 +548,12 @@ main(int argc, char *argv[])
                        freevars(c);
                }
        
       -        for (i = 0; i < LEN(templates); i++) {
       +        for (i = 0; i < templateslen; i++) {
                        if (!strcmp(templates[i].name, "page"))
                                continue;
                        b = &templates[i].blocks[BlockFooter];
       -                writepage(templates[i].fp, b->name, NULL, b->data);
       +                if (b->name)
       +                        writepage(templates[i].fp, b->name, NULL, b->data);
                }
        
                return 0;
 (DIR) diff --git a/templates/urllist.txt/footer.txt b/templates/urllist.txt/footer.txt
 (DIR) diff --git a/templates/urllist.txt/header.txt b/templates/urllist.txt/header.txt