ssnail

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

commit 3aa0f66199646daae7c99d0003ec82555b852338
parent ca5ed11b330ed22036c39a4ca2abd4f86438e4d0
Author: Paco Esteban <paco@e1e0.net>
Date:   Thu, 18 Jun 2020 20:52:46 +0200

better error handling stolen from Stefan and Tracey

Diffstat:
MMakefile | 2+-
Aerror.c | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mhelpers.c | 15++++++++-------
Mhelpers.h | 3++-
Mssnail.c | 215++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Assnail_error.h | 41+++++++++++++++++++++++++++++++++++++++++
6 files changed, 253 insertions(+), 92 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,6 +1,6 @@ PROG = ssnail -SRCS += ssnail.c helpers.c +SRCS += ssnail.c helpers.c error.c LDFLAGS += -L/usr/local/lib LDADD += -llowdown -lm diff --git a/error.c b/error.c @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2018, 2019, 2020 Stefan Sperling <stsp@openbsd.org> + * Copyright (c) 2020 Tracey Emery <tracey@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/syslimits.h> + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "ssnail_error.h" + +#ifndef nitems +#define nitems(_a) (sizeof(_a) / sizeof((_a)[0])) +#endif + +const struct ssnail_error * +ssnail_error_msg(int code, const char *msg) +{ + static struct ssnail_error error; + int i; + + for (i = 0; i < nitems(ssnail_errors); i++) { + if (code == ssnail_errors[i].code) { + error.code = code; + error.msg = msg; + return &error; + } + } + + abort(); +} + +const struct ssnail_error * +ssnail_error_from_errno(const char *prefix) +{ + static struct ssnail_error error; + static char error_msg[PATH_MAX + 20]; + + snprintf(error_msg, sizeof(error_msg), "%s: %s", prefix, + strerror(errno)); + + error.code = SSNAIL_ERR_ERRNO; + error.msg = error_msg; + return &error; +} + +void * +ssnail_error_fprintf(const struct ssnail_error *error) +{ + /* not checking for fprintf errors */ + fprintf(stderr, "%s: %s\n", getprogname(), error->msg); + + return NULL; +} diff --git a/helpers.c b/helpers.c @@ -22,6 +22,7 @@ #include <stdlib.h> #include <string.h> +#include "ssnail_error.h" #include "helpers.h" char * @@ -42,7 +43,7 @@ build_full_path(char *dir, char *file) return fullpath; } -int +const struct ssnail_error * copy_file(const char *source_file, const char *dest_file, int force) { FILE *src, *dst; @@ -51,12 +52,12 @@ copy_file(const char *source_file, const char *dest_file, int force) long long src_mtime, dst_mtime; if (stat(source_file, &st) == -1) - return -1; + return ssnail_error_from_errno("cp stat src"); src_mtime = st.st_mtim.tv_sec; if (stat(dest_file, &st) == -1) { /* dst file does not have to exist */ if (errno != ENOENT) - return -1; + return ssnail_error_from_errno("cp stat dst"); dst_mtime = 0; } else { dst_mtime = st.st_mtim.tv_sec; @@ -64,9 +65,9 @@ copy_file(const char *source_file, const char *dest_file, int force) if ((src_mtime > dst_mtime) || force) { if ((src = fopen(source_file, "rb")) == NULL) - return -1; + return ssnail_error_from_errno("cp fopen src"); if ((dst = fopen(dest_file, "wb")) == NULL) - return -1; + return ssnail_error_from_errno("cp fopen dst"); printf("copying %s --> %s\n", source_file, dest_file); size_t rcnt, wcnt; @@ -80,13 +81,13 @@ copy_file(const char *source_file, const char *dest_file, int force) } } while ((rcnt > 0) && (rcnt == wcnt)); if (wcnt) - return -1; + return ssnail_error_msg(2, "cp"); fclose(src); fclose(dst); } - return 0; + return NULL; } const char * diff --git a/helpers.h b/helpers.h @@ -20,7 +20,8 @@ #define HEADER "_header.html" #define FOOTER "_footer.html" -int copy_file(const char *, const char *, int); +const struct ssnail_error *copy_file(const char *, const char *, int); + int load_from_file(char **, char *); const char *get_filename_ext(const char *); diff --git a/ssnail.c b/ssnail.c @@ -30,6 +30,7 @@ #include <lowdown.h> #include "helpers.h" +#include "ssnail_error.h" struct article { size_t htmlz; @@ -55,11 +56,12 @@ SLIST_HEAD(listhead, article) head; __dead static void usage(void); static void articleq_free(struct listhead *); +static const struct ssnail_error *process_dir(char *, char *, int); +static const struct ssnail_error *populate_article_entry(struct article *); +static const struct ssnail_error *write_html(struct article *, char *, char *); +static const struct ssnail_error *generate_index(struct listhead *, char *, char *, char *); + static int gen_html(struct article *); -static int generate_index(struct listhead *, char *, char *, char *); -static int populate_article_entry(struct article *); -static int process_dir(char *, char *, int); -static int write_html(struct article *, char *, char *); __dead static void usage(void) @@ -72,19 +74,25 @@ usage(void) int main(int argc, char *argv[]) { - DIR *dirp; - struct dirent *dp; - struct article *ap = NULL; + DIR *dirp; + struct dirent *dp; + struct article *ap = NULL; + const struct ssnail_error *error = NULL; + int ch, force = 0, index = 0, needs_index = 0; char *srcdir = NULL, *dstdir = NULL, *fbuf = NULL; char *header_tpl = NULL, *footer_tpl = NULL; header_tpl = strdup(HEADER); - if (header_tpl == NULL) - return EXIT_FAILURE; + if (header_tpl == NULL) { + error = ssnail_error_from_errno("header_tpl def"); + goto done; + } footer_tpl = strdup(FOOTER); - if (footer_tpl == NULL) - return EXIT_FAILURE; + if (footer_tpl == NULL) { + error = ssnail_error_from_errno("footer_tpl def"); + goto done; + } while ((ch = getopt(argc, argv, "Ff:h:i")) != -1) { switch (ch) { @@ -94,14 +102,18 @@ main(int argc, char *argv[]) case 'f': free(footer_tpl); footer_tpl = strdup(optarg); - if (footer_tpl == NULL) - return EXIT_FAILURE; + if (footer_tpl == NULL) { + error = ssnail_error_from_errno("footer_tpl"); + goto done; + } break; case 'h': free(header_tpl); header_tpl = strdup(optarg); - if (header_tpl == NULL) - return EXIT_FAILURE; + if (header_tpl == NULL) { + error = ssnail_error_from_errno("footer_tpl"); + goto done; + } break; case 'i': index = 1; @@ -119,30 +131,38 @@ main(int argc, char *argv[]) usage(); srcdir = strdup(argv[0]); - if (srcdir == NULL) - return EXIT_FAILURE; + if (srcdir == NULL) { + error = ssnail_error_from_errno("srcdir arg"); + goto done; + } dstdir = strdup(argv[1]); - if (dstdir == NULL) - return EXIT_FAILURE; + if (dstdir == NULL) { + error = ssnail_error_from_errno("dstdir arg"); + goto done; + } - if (process_dir(srcdir, dstdir, force) == -1) - err(1, "cannot process src dir"); + if ((error = process_dir(srcdir, dstdir, force)) != NULL) + goto done; printf("Generate html files ... \n"); SLIST_FOREACH(ap, &head, entries) { if ((ap->src_mtime > ap->dst_mtime) || force) { printf("... %s\n", ap->dst_path); - if (write_html(ap, header_tpl, footer_tpl) == -1) - err(3, "cannot write html file"); + error = write_html(ap, header_tpl, footer_tpl); + if (error) + goto done; needs_index = 1; } } if (index && (needs_index || force)) { printf("Generate html index ... \n"); - generate_index(&head, header_tpl, footer_tpl, dstdir); + error = generate_index(&head, header_tpl, footer_tpl, dstdir); + if (error) + goto done; } +done: free(srcdir); free(dstdir); free(fbuf); @@ -150,17 +170,21 @@ main(int argc, char *argv[]) free(footer_tpl); articleq_free(&head); + if (error) { + ssnail_error_fprintf(error); + return error->code; + } return EXIT_SUCCESS; } -static int +static const struct ssnail_error * generate_index(struct listhead *h, char *head_tpl, char *foot_tpl, char *dst_dir) { + const struct ssnail_error *error = NULL; struct article *a; char *header = NULL, *footer = NULL; FILE *fout; - int r = -1; time_t now; struct tm *timeInfo; char mytime[12], *index_path = NULL; @@ -169,20 +193,27 @@ generate_index(struct listhead *h, char *head_tpl, timeInfo = localtime(&now); strftime(mytime, sizeof(mytime), "%Y-%m-%d", timeInfo); - if (load_from_file(&header, head_tpl) == 0) + if (load_from_file(&header, head_tpl) == 0) { + error = ssnail_error_msg(2, "load head_tpl"); goto out; - /* XXX: 13. here is where you would null term *header = '\0' */ + } header = str_rep(header, "$title$", "index"); header = str_rep(header, "$author$", getlogin()); header = str_rep(header, "$date$", mytime); - if (load_from_file(&footer, foot_tpl) == 0) + if (load_from_file(&footer, foot_tpl) == 0) { + error = ssnail_error_msg(2, "load foot_tpl"); goto out; + } footer = str_rep(footer, "$title$", "index"); - if ((index_path = build_full_path(dst_dir, "index.html")) == NULL) + if ((index_path = build_full_path(dst_dir, "index.html")) == NULL) { + error = ssnail_error_msg(2, "build_path dst_dir"); goto out; - if ((fout = fopen(index_path, "w")) == NULL) + } + if ((fout = fopen(index_path, "w")) == NULL) { + error = ssnail_error_msg(2, "fopen index"); goto out; + } fwrite(header, 1, strlen(header), fout); /* TODO: make this list reverse date ordered */ fprintf(fout, "<ul>\n"); @@ -197,13 +228,11 @@ generate_index(struct listhead *h, char *head_tpl, fwrite(footer, 1, strlen(footer), fout); fclose(fout); - r = 0; out: free(index_path); free(header); free(footer); - return r; - + return error; } static void @@ -229,7 +258,7 @@ gen_html(struct article *a) struct lowdown_opts opts; struct lowdown_metaq mq; struct lowdown_meta *md; - int r = 0; + int r = -1; size_t orig_size = 0; TAILQ_INIT(&mq); /* create the queue(linked list) of lowdown_meta */ @@ -259,9 +288,17 @@ gen_html(struct article *a) /* set default values for metadata */ a->title = strdup(""); + if (a->title == NULL) + goto out; a->author = strdup(""); + if (a->author == NULL) + goto out; a->date = strdup(""); + if (a->date == NULL) + goto out; a->type = strdup("page"); + if (a->type == NULL) + goto out; /* collect metadata from markdown */ TAILQ_FOREACH(md, &mq, entries) { @@ -279,7 +316,7 @@ gen_html(struct article *a) } } - r = 1; + r = 0; out: lowdown_metaq_free(&mq); @@ -287,138 +324,150 @@ out: return r; } -static int +static const struct ssnail_error * write_html(struct article *ap, char *head_tpl, char *foot_tpl) { - char *header = NULL, *footer = NULL; + const struct ssnail_error *error = NULL; FILE *fout; - int r = -1; - -/* - * XXX: 15. need error handling below. This is where the segfault is, because I - * don't have the required article struct filled. So, without pouring over - * everything you've written, I'm obviously missing something in a file - * somewhere. :D - */ + char *header = NULL, *footer = NULL; + if (strlen(ap->title) == 0 || strlen(ap->author) == 0 - || strlen(ap->date) == 0) + || strlen(ap->date) == 0) { + error = ssnail_error_msg(2, "metadata"); goto out; + } - if (load_from_file(&header, head_tpl) == 0) - return r; + if (load_from_file(&header, head_tpl) == 0) { + error = ssnail_error_msg(2, "load header"); + goto out; + } header = str_rep(header, "$title$", ap->title); header = str_rep(header, "$author$", ap->author); header = str_rep(header, "$date$", ap->date); - if (load_from_file(&footer, foot_tpl) == 0) + if (load_from_file(&footer, foot_tpl) == 0) { + error = ssnail_error_msg(2, "load header"); goto out; + } footer = str_rep(footer, "$title$", ap->title); - if ((fout = fopen(ap->dst_path, "w")) == NULL) + if ((fout = fopen(ap->dst_path, "w")) == NULL) { + error = ssnail_error_from_errno("openw dst_path"); goto out; + } fwrite(header, 1, strlen(header), fout); fwrite(ap->html_content, 1, ap->htmlz, fout); fwrite(footer, 1, strlen(footer), fout); fclose(fout); - r = 0; out: free(header); free(footer); - return r; + return error; } -static int +static const struct ssnail_error * populate_article_entry(struct article *ap) { - struct stat st; - int retval = -1; - int len = 0; + const struct ssnail_error *error = NULL; + struct stat st; + int len = 0; - if (stat(ap->src_path, &st) == -1) + if (stat(ap->src_path, &st) == -1) { + error = ssnail_error_from_errno("src_path"); goto out; + } ap->src_mtime = st.st_mtim.tv_sec; if (stat(ap->dst_path, &st) == -1) { /* dst file does not have to exist */ - if (errno != ENOENT) + if (errno != ENOENT) { + error = ssnail_error_from_errno("dst_path"); goto out; + } ap->dst_mtime = 0; } else { ap->dst_mtime = st.st_mtim.tv_sec; } - if ((ap->origz = load_from_file(&ap->orig_content, ap->src_path)) == 0) + if ((ap->origz = load_from_file(&ap->orig_content, ap->src_path)) == 0) { + error = ssnail_error_from_errno("load orig"); goto out; + } - gen_html(ap); - retval = 0; + if (gen_html(ap) != 0) + error = ssnail_error_msg(2, "gen_html"); out: - return retval; + return error; } -static int +static const struct ssnail_error * process_dir(char *src, char *dst, int force) { - DIR *dirp; - struct dirent *dp; - struct stat st; - struct article *a = NULL; - - int retval = -1; + DIR *dirp; + struct dirent *dp; + struct stat st; + struct article *a = NULL; + const struct ssnail_error *error = NULL; char *src_path = NULL, *dst_path = NULL; /* if dst folder does not exist, create it */ if (stat(dst, &st) == -1 && errno == ENOENT) { if (mkdir(dst, 0755) == -1) - return -1; + return ssnail_error_from_errno("mkdir dst"); } if ((dirp = opendir(src)) == NULL) - return -1; + return ssnail_error_from_errno("opendir src"); while ((dp = readdir(dirp)) != NULL) { if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) continue; - if ((src_path = build_full_path(src, dp->d_name)) == NULL) + if ((src_path = build_full_path(src, dp->d_name)) == NULL) { + error = ssnail_error_msg(2, "buildpath src"); goto out; - if ((dst_path = build_full_path(dst, dp->d_name)) == NULL) + } + if ((dst_path = build_full_path(dst, dp->d_name)) == NULL) { + error = ssnail_error_msg(2, "buildpath src"); goto out; + } if (dp->d_type == DT_REG) { if (strcmp(get_filename_ext(src_path), "md") == 0) { char *fbuf = NULL; a = malloc(sizeof(struct article)); + if (a == NULL) { + error = ssnail_error_from_errno("malloc a"); + goto out; + } strlcpy(a->src_path, src_path, PATH_MAX); fbuf = str_rep(dst_path, ".md", ".html"); strlcpy(a->dst_path, fbuf, PATH_MAX); free(fbuf); - if((populate_article_entry(a) == -1)) + error = populate_article_entry(a); + if (error) goto out; SLIST_INSERT_HEAD(&head, a, entries); } else { - if (copy_file(src_path, dst_path, force) == -1) + error = copy_file(src_path, dst_path, force); + if (error) goto out; } } - if (dp->d_type == DT_DIR) { - if (process_dir(src_path, dst_path, force) == -1) - goto out; - } + if (dp->d_type == DT_DIR) + error = process_dir(src_path, dst_path, force); } - retval = 0; - out: free(src_path); free(dst_path); src_path = NULL; dst_path = NULL; closedir(dirp); - return retval; + return error; } diff --git a/ssnail_error.h b/ssnail_error.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018, 2019, 2020 Stefan Sperling <stsp@openbsd.org> + * Copyright (c) 2020 Tracey Emery <tracey@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef SSNAIL_ERROR_H +#define SSNAIL_ERROR_H + +/* Error codes */ +#define SSNAIL_ERR_OK 0 +#define SSNAIL_ERR_ERRNO 1 +#define SSNAIL_ERR_GENERIC 2 + +static const struct ssnail_error { + int code; + const char *msg; +} ssnail_errors[] = { + { SSNAIL_ERR_OK, "no error occured?!?" }, + { SSNAIL_ERR_ERRNO, "see errno" }, + { SSNAIL_ERR_GENERIC, "error" }, +}; + +const struct ssnail_error *ssnail_error_msg(int, const char *); + +const struct ssnail_error *ssnail_error_from_errno(const char *); + +void *ssnail_error_fprintf(const struct ssnail_error *); + +#endif /* SSNAIL_ERROR_H */