saait

Simple static page generator
git clone https://git.sinitax.com/codemadness/saait
Log | Files | Refs | README | LICENSE | Upstream | sfeed.txt

commit 38b3bc3800cf2c61fe2bd2ec5e80ef65cbad4201
parent e080ab9c32fc00887ae546a64241b03f06e3fc7a
Author: Hiltjo Posthuma <hiltjo@codemadness.org>
Date:   Sat, 26 Nov 2016 13:32:51 +0100

simplify usage

Diffstat:
MMakefile | 3+--
MREADME | 15++++++++++++++-
MTODO | 2++
Rconfig -> config.cfg | 0
Aconfig.h | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsaait.bat | 4----
Msaait.c | 183++++++++++++++++++++++++++++++++-----------------------------------------------
7 files changed, 150 insertions(+), 117 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,6 +1,5 @@ test: build - mkdir -p output - ./saait `ls -1r pages/*.cfg` + ./saait build: clean cc -ggdb saait.c -o saait -Wall -std=c99 -Wextra -pedantic diff --git a/README b/README @@ -13,11 +13,21 @@ Dependencies: - requires non-standard field tm_gmtoff in struct tm. +Tested and works on: +-------------------- +- OpenBSD +- Windows (mingw) + + Directory structure for pages: - pages/file1.html - pages/file1.cfg +The pages are traversed in reverse alphabetical order. This is to make sure +the index and other templates are listed from the most recent to oldest +(for example). + Directory structure for templates: @@ -32,8 +42,11 @@ the templatename "page" is special and will be used per page. otherwise the directory name is used as the output file, for example templates/atom.xml/templatefiles* will be: output/atom.xml +See config.h to modify the templates. + -Cfg file: +Cfg file and template syntax: +----------------------------- Variables set: file (if not already set) path to HTML content filename. diff --git a/TODO b/TODO @@ -1,3 +1,5 @@ +simplify date parsing. + improve documentation make template structure: diff --git a/config b/config.cfg diff --git a/config.h b/config.h @@ -0,0 +1,60 @@ +static const char *configfile = "config.cfg"; +static const char *outputdir = "output"; +static const char *templatedir = "templates"; +static const char *pagesdir = "pages"; + +enum { BlockHeader = 0, BlockItem, BlockFooter, BlockLast }; + +static struct { + char *name; + /* blocks: header, item, footer */ + struct { + char *name; /* filename */ + char *data; /* content (set at runtime) */ + } blocks[BlockLast]; + /* output FILE * (set at runtime) */ + FILE *fp; +} templates[] = { + { + .name = "atom.xml", .blocks = { + { .name = "header.xml" }, + { .name = "item.xml" }, + { .name = "footer.xml" } + } + }, + { + .name = "index.html", .blocks = { + { .name = "header.html" }, + { .name = "item.html" }, + { .name = "footer.html" } + } + }, + { + .name = "page", .blocks = { + { .name = "header.html" }, + { .name = "item.html" }, + { .name = "footer.html" } + } + }, + { + .name = "rss.xml", .blocks = { + { .name = "header.xml" }, + { .name = "item.xml" }, + { .name = "footer.xml" } + } + }, + { + .name = "sitemap.xml", .blocks = { + { .name = "header.xml" }, + { .name = "item.xml" }, + { .name = "footer.xml" } + } + }, + { + .name = "urllist.txt", .blocks = { + { .name = "header.txt" }, + { .name = "item.txt" }, + { .name = "footer.txt" } + } + } +}; diff --git a/saait.bat b/saait.bat @@ -1,4 +0,0 @@ -@echo off -setlocal enabledelayedexpansion -for /F "usebackq" %%f in (`dir /O-N /b pages\*.cfg`) do set FILES=!FILES! pages/%%f -saait %FILES% diff --git a/saait.c b/saait.c @@ -13,10 +13,13 @@ /* string and byte-length */ #define STRP(s) s,sizeof(s)-1 +#define LEN(s) (sizeof(s)/sizeof(*s)) #include "arg.h" char *argv0; +#include "config.h" + struct config { struct variable *vars; }; @@ -28,16 +31,6 @@ struct variable { struct variable *next; }; -struct template { - char *name; - /* output file */ - FILE *fp; - /* blocks: header, item, footer */ - char *header, *item, *footer; - /* linked-list */ - struct template *next; -}; - static struct config *global; /* Escape characters below as HTML 2.0 / XML 1.0. */ @@ -213,7 +206,7 @@ gettzoffset(const char *s, long *off) if (namelen < 1 || namelen > 3) return 0; /* compare tz and adjust offset relative to UTC */ - for (i = 0; i < sizeof(tzones) / sizeof(*tzones); i++) { + for (i = 0; i < LEN(tzones); i++) { if (tzones[i].len == namelen && !strncmp(s, tzones[i].name, namelen)) { *off = tzones[i].offhour; @@ -567,6 +560,17 @@ writepage(FILE *fp, const char *filename, struct config *c, char *s) } } +int +revalphasort(const struct dirent **d1, const struct dirent **d2) { + return -alphasort(d1, d2); +} + +int selectcfgfile(const struct dirent *d) +{ + return (d->d_name[0] != '.' && d->d_namlen > 4 && + !strcmp(&d->d_name[d->d_namlen - 4], ".cfg")); +} + void usage(void) { @@ -577,16 +581,11 @@ usage(void) int main(int argc, char *argv[]) { - const char *configfile = "config"; - const char *outputdir = "output"; - const char *templatedir = "templates"; - struct template *templates = NULL, *t, *tc; struct config *c; - DIR *dirp, *dirt; - struct dirent *dp, *dt; - char *p, *base, *basefile, file[PATH_MAX], dir[PATH_MAX]; - char outputfile[PATH_MAX]; - int i, r; + char *p, *base, *basefile, file[PATH_MAX], outputfile[PATH_MAX]; + struct dirent **namelist = NULL; + int entries; + int i, j, r; #ifdef __OpenBSD__ if (pledge("stdio cpath rpath wpath", NULL) == -1) { @@ -599,6 +598,10 @@ main(int argc, char *argv[]) case 'c': configfile = EARGF(usage()); break; + case 'd': + case 'p': + pagesdir = EARGF(usage()); + break; case 'o': outputdir = EARGF(usage()); break; @@ -609,80 +612,54 @@ main(int argc, char *argv[]) usage(); } ARGEND; - if (!(dirp = opendir(templatedir))) { - fprintf(stderr, "opendir: %s: %s\n", templatedir, strerror(errno)); - exit(1); - } - - while ((dp = readdir(dirp))) { - /* ignore hidden or file */ - if (dp->d_name[0] == '.' || !(dp->d_type & DT_DIR)) - continue; - - t = calloc(1, sizeof(*t)); - t->name = strdup(dp->d_name); - - r = snprintf(dir, sizeof(dir), "%s/%s", templatedir, dp->d_name); - if (r < 0 || (size_t)r >= sizeof(dir)) { - fprintf(stderr, "truncated: %s/%s\n", templatedir, dp->d_name); - exit(1); - } - if (!(dirt = opendir(dir))) { - fprintf(stderr, "opendir: %s: %s\n", dir, strerror(errno)); - exit(1); - } - while ((dt = readdir(dirt))) { - /* ignore hidden or dir */ - if (dt->d_name[0] == '.' || (dt->d_type & DT_DIR)) - continue; - /* page is a special case for now */ - if (strcmp(dt->d_name, "page")) { - r = snprintf(file, sizeof(file), "%s/%s", outputdir, dp->d_name); - if (r < 0 || (size_t) r >= sizeof(file)) { - fprintf(stderr, "truncated: %s/%s\n", outputdir, dp->d_name); - exit(1); - } - t->fp = efopen(file, "wb"); - } + /* global config */ + global = readconfig(configfile); - r = snprintf(file, sizeof(file), "%s/%s/%s", templatedir, dp->d_name, dt->d_name); + /* 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++) { + r = snprintf(file, sizeof(file), "%s/%s/%s", templatedir, templates[i].name, templates[i].blocks[j].name); if (r < 0 || (size_t) r >= sizeof(file)) { - fprintf(stderr, "truncated: %s/%s/%s\n", templatedir, dp->d_name, dt->d_name); + fprintf(stderr, "path truncated: '%s/%s/%s'\n", templatedir, templates[i].name, templates[i].blocks[j].name); exit(1); } - - if (!strcmp(dt->d_name, "item") || strstr(dt->d_name, "item.") == dt->d_name) - t->item = readfile(file); - else if (!strcmp(dt->d_name, "header") || strstr(dt->d_name, "header.") == dt->d_name) - t->header = readfile(file); /* TODO: non-fatal */ - else if (!strcmp(dt->d_name, "footer") || strstr(dt->d_name, "footer.") == dt->d_name) - t->footer = readfile(file); /* TODO: non-fatal */ + templates[i].blocks[j].data = readfile(file); } - closedir(dirt); + } - if (!templates) - templates = t; - else - tc->next = t; - tc = t; + mkdir(outputdir, 0755); + + /* header */ + for (i = 0; i < LEN(templates); i++) { + if (!strcmp(templates[i].name, "page")) + continue; + r = snprintf(file, sizeof(file), "%s/%s", outputdir, templates[i].name); + if (r < 0 || (size_t) r >= sizeof(file)) { + fprintf(stderr, "path truncated: '%s/%s'\n", outputdir, templates[i].name); + exit(1); + } + if ((templates[i].fp = efopen(file, "wb"))) + writepage(templates[i].fp, "", NULL, templates[i].blocks[BlockHeader].data); } - closedir(dirp); - /* global config */ - /* TODO: make optional */ - global = readconfig(configfile); - for (t = templates; t; t = t->next) { - if (t->fp) - writepage(t->fp, "", NULL, t->header); + if ((entries = scandir(pagesdir, &namelist, selectcfgfile, revalphasort)) < 0) { + fprintf(stderr, "scandir: %s\n", strerror(errno)); + exit(1); } /* pages */ - for (i = 1; i < argc; i++) { - if ((p = strrchr(argv[i], '.'))) - base = estrndup(argv[i], p - argv[i]); + for (i = 0; i < entries; i++) { + r = snprintf(file, sizeof(file), "%s/%s", pagesdir, namelist[i]->d_name); + if (r < 0 || (size_t)r >= sizeof(file)) { + fprintf(stderr, "path truncated: '%s'\n", argv[i]); + exit(1); + } + + if ((p = strrchr(file, '.'))) + base = estrndup(file, p - file); else - base = estrdup(argv[i]); + base = estrdup(file); if ((p = strrchr(base, '/'))) basefile = estrdup(&base[p - base + 1]); @@ -690,11 +667,6 @@ main(int argc, char *argv[]) basefile = estrdup(base); /* read config */ - r = snprintf(file, sizeof(file), "%s.cfg", base); - if (r < 0 || (size_t)r >= sizeof(file)) { - fprintf(stderr, "path truncated: '%s'\n", argv[i]); - exit(1); - } c = readconfig(file); /* read file data */ @@ -714,9 +686,10 @@ main(int argc, char *argv[]) else setvar(&c->vars, newvar("filename", file)); - for (t = templates; t; t = t->next) { + /* item block */ + for (j = 0; j < LEN(templates); j++) { /* TODO: page is a special case for now */ - if (!strcmp(t->name, "page")) { + if (!strcmp(templates[j].name, "page")) { /* output file */ r = snprintf(outputfile, sizeof(outputfile), "%s/%s.html", outputdir, basefile); if (r < 0 || (size_t)r >= sizeof(file)) { @@ -724,38 +697,28 @@ main(int argc, char *argv[]) exit(1); } - t->fp = efopen(outputfile, "wb"); - writepage(t->fp, file, c, t->header); - writepage(t->fp, file, c, t->item); - writepage(t->fp, file, c, t->footer); - fclose(t->fp); - t->fp = NULL; + templates[j].fp = efopen(outputfile, "wb"); + writepage(templates[j].fp, file, c, templates[j].blocks[BlockHeader].data); + writepage(templates[j].fp, file, c, templates[j].blocks[BlockItem].data); + writepage(templates[j].fp, file, c, templates[j].blocks[BlockFooter].data); + + fclose(templates[j].fp); + templates[j].fp = NULL; } else { - writepage(t->fp, file, c, t->item); + writepage(templates[j].fp, file, c, templates[j].blocks[BlockItem].data); } } - - /* TODO: check using valgrind */ free(base); free(basefile); freevars(c->vars); free(c); } - for (t = templates; t; t = t->next) { - if (t->fp) { - writepage(t->fp, "", NULL, t->footer); - fclose(t->fp); - } - free(t->name); - free(t->header); - free(t->item); - free(t->footer); + for (i = 0; i < LEN(templates); i++) { + if (!strcmp(templates[i].name, "page")) + continue; + writepage(templates[i].fp, "", NULL, templates[i].blocks[BlockFooter].data); } - /* cleanup */ - freevars(global->vars); - free(global); - return 0; }