ssnail

crappy and opinionated static site generator
git clone https://git.e1e0.net/ssnail.git
Log | Files | Refs | README | LICENSE

commit 5c26ee1e613e30bb5ae0614e91755c8fda1176df
Author: Paco Esteban <paco@e1e0.net>
Date:   Sat,  2 May 2020 17:21:44 +0200

preliminary test version

For now it just takes files from source folder, converts md to html,
loads the header and footer templates, performs the substitutions and
writes all to dest folder.

It's a bit rough around the edges, but it does what's supposed to.

Diffstat:
A.gitignore | 8++++++++
AMakefile | 9+++++++++
Assnail.1 | 0
Assnail.c | 242+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 259 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,8 @@ +ssnail +*.d +*.o +*.core +data +destHtml +destTxt +_*.html diff --git a/Makefile b/Makefile @@ -0,0 +1,9 @@ +PROG = ssnail + +# SRCS += staticsite.c + +LDFLAGS += -L/usr/local/lib +LDADD += -llowdown -lm +CFLAGS += -I/usr/local/include + +.include <bsd.prog.mk> diff --git a/ssnail.1 b/ssnail.1 diff --git a/ssnail.c b/ssnail.c @@ -0,0 +1,242 @@ +#include <dirent.h> +#include <err.h> +#include <sys/queue.h> +#include <sys/types.h> +#include <sys/syslimits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <lowdown.h> + +#define MAX_META 255 + +char *str_rep(const char *, const char *, const char *); +int gen_html(char *, char *); +int build_full_path(char *, char *, char *); +int load_template(char **, char *); +void usage(); + +int +main(int argc, char *argv[]) +{ + DIR *dirp; + struct dirent *dp; + int ch; + char srcdir[PATH_MAX] = "", dstdir[PATH_MAX] = ""; + + while ((ch = getopt(argc, argv, "s:d:")) != -1) { + switch (ch) { + case 's': + (void)strlcpy(srcdir, optarg, PATH_MAX); + break; + case 'd': + (void)strlcpy(dstdir, optarg, PATH_MAX); + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + + // src and dst dir are mandatory. + if (srcdir[0] == '\0' || dstdir[0] == '\0') + usage(); + + if ((dirp = opendir(srcdir)) != NULL) { + while ((dp = readdir(dirp)) != NULL) { + if (dp->d_type == DT_REG) { // only take regular files + char srcpath[PATH_MAX] = ""; + char dstpath[PATH_MAX] = ""; + build_full_path(srcpath, srcdir, dp->d_name); + build_full_path(dstpath, dstdir, dp->d_name); + if(!gen_html(srcpath, dstpath)) + err(1, "cannot generate file %s", + srcpath); + } + } + closedir(dirp); + } else { + /* could not open directory */ + err(1, "cannot open src dir"); + } + + return 0; +} + +char * +str_rep(const char *s, const char *oldW, const char *newW) +{ + char *r; + int i, cnt = 0; + int newWlen = strlen(newW); + int oldWlen = strlen(oldW); + + if (newWlen == 0 || oldWlen == 0) + return NULL; + + // Counting the number of times old word + // occur in the string + for (i = 0; s[i] != '\0'; i++) { + if (strstr(&s[i], oldW) == &s[i]) { + cnt++; + // Jumping to index after the old word. + i += oldWlen - 1; + } + } + + // Making new string long enough + r = (char *)malloc(i + cnt * (newWlen - oldWlen) + 1); + if (!r) return NULL; + + i = 0; + while (*s) { + /* + * compare the substring the old word + * if found (pointers are equal), copy new word in 'i' position + * of result. Then increment by the lenght so we do not check + * unnecessarily. + */ + if (strstr(s, oldW) == s) { + /* + * On the original example it was done like this: + * strcpy(&r[i], newW); + * I think there's no need to dereference and reference + * again. + */ + strcpy(r+i, newW); + i += newWlen; + s += oldWlen; + } else // if not copy the char and increment pointers + r[i++] = *s++; + } + + r[i] = '\0'; + return r; +} + +int +gen_html(char origfile[PATH_MAX], char destfile[PATH_MAX]) +{ + FILE *fin, *fout; + char *ret = NULL, *head_tpl = NULL, *foot_tpl = NULL, + *header = NULL, *footer = NULL; + char title[MAX_META] = "", author[MAX_META] = "", + date[MAX_META] = ""; + size_t retsz = 0; + struct lowdown_opts opts; + struct lowdown_metaq mq; + struct lowdown_meta *md; + int r = 0; + + TAILQ_INIT(&mq); // create the queue(linked list) of lowdown_meta + memset(&opts, 0, sizeof(struct lowdown_opts)); // init opts to 0 + + opts.maxdepth = 128; + /* opts.type = LOWDOWN_TERM; */ + opts.type = LOWDOWN_HTML; + opts.feat = LOWDOWN_FOOTNOTES | + LOWDOWN_AUTOLINK | + LOWDOWN_TABLES | + LOWDOWN_SUPER | + LOWDOWN_STRIKE | + LOWDOWN_FENCED | + LOWDOWN_MATH | + LOWDOWN_METADATA; + opts.oflags = LOWDOWN_HTML_HEAD_IDS | + LOWDOWN_HTML_NUM_ENT | + LOWDOWN_HTML_OWASP | + LOWDOWN_HTML_SKIP_HTML; + + if ((fin = fopen(origfile, "r")) == NULL) + goto out1; + if (!lowdown_file(&opts, fin, &ret, &retsz, &mq)) + goto out1; + + TAILQ_FOREACH(md, &mq, entries) { + if (strcmp(md->key, "title") == 0 ) + (void)strlcpy(title, md->value, MAX_META); + if (strcmp(md->key, "author") == 0 ) + (void)strlcpy(author, md->value, MAX_META); + if (strcmp(md->key, "date") == 0 ) + (void)strlcpy(date, md->value, MAX_META); + } + + if (strlen(title) == 0 || strlen(author) == 0 || strlen(date) == 0) + goto out1; + + load_template(&head_tpl, "_header.html"); + header = str_rep(head_tpl, "$title$", title); + header = str_rep(header, "$author$", author); + header = str_rep(header, "$date$", date); + load_template(&foot_tpl, "_footer.html"); + footer = str_rep(foot_tpl, "$title$", title); + + + if ((fout = fopen(destfile, "w")) == NULL) + goto out2; + fwrite(header, 1, strlen(header), fout); + fwrite(ret, 1, retsz, fout); + fwrite(footer, 1, strlen(footer), fout); + + r = 1; + +out2: + fclose(fout); +out1: + fclose(fin); + free(ret); + lowdown_metaq_free(&mq); + + return r; +} + +int +build_full_path(char fullpath[PATH_MAX], char *dir, char *file) +{ + // build the full path + (void)strlcpy(fullpath, dir, PATH_MAX); + if (fullpath[strlen(fullpath)-2] != '/') + (void)strlcat(fullpath, "/", PATH_MAX); + (void)strlcat(fullpath, file, PATH_MAX); + + return 1; +} + +int +load_template(char **buffer, char *path) +{ + long length; + FILE *f = NULL; + + if ((f = fopen(path, "rb")) == NULL) + return 0; + + if (f) { + fseek(f, 0, SEEK_END); // go to the end of file + length = ftell(f); // record the length of the file + fseek(f, 0, SEEK_SET); // go to the start again + *buffer = malloc(length + 1); + if (*buffer) + fread(*buffer, 1, length, f); + fclose(f); + /* add null terminator to string. + * apparently this is not the same as: + * *buffer[length] = '\0'; + * but I do not know why ... + */ + *(*buffer + length) = '\0'; + } + + return 1; +} + +void +usage() +{ + // TODO + printf("usage\n"); + exit(1); +}