shayla

a simple yet fast static site builder
Log | Files | Refs | Submodules | README | LICENSE | git clone https://git.ne02ptzero.me/git/shayla

commit 289e123762b428a8223cd3a1d45e81f891569372
parent f6d0086b2029eef2e6993842523d46896050255e
Author: Louis Solofrizzo <lsolofrizzo@online.net>
Date:   Fri, 28 Sep 2018 05:52:57 +0200

Builder: Now reading layouts and copying final files

This commit do introduce layouts reading, in order to easily use them
later on. I've also coded the entire file copy needed for the final
website (Favicon, styles and images).

On another note, I've reworked the main offset API a bit, since I use it
in two different places now.

Signed-off-by: Louis Solofrizzo <lsolofrizzo@online.net>

Diffstat:
Mheader.c | 54++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mmain.c | 193++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Mshayla.h | 4++++
Mutils.h | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 295 insertions(+), 33 deletions(-)

diff --git a/header.c b/header.c @@ -20,9 +20,37 @@ #define IS_HEADER_DELIM_LINE(str) (strncmp(HEADER_DELIM, str, sizeof(HEADER_DELIM) - 1) == 0) #define HEADER_LINE_DELIM ':' +static bool get_filename_without_ext(file_t *ptr) +{ + size_t size = strlen(ptr->path) - 1; + size_t j; + + /* Find the extension in the filename */ + for (; size > 0 && ptr->path[size] != '.'; size--) + ; + + if (size == 0) + return false; + + ptr->path[size] = 0; + + /* Go back to the root directory */ + for (j = size - 1; j > 0 && ptr->path[j] != '/'; j--) + ; + + ptr->route = strdup(ptr->path + j + 1); + ptr->path[size] = '.'; + + if (ptr->route == NULL) + return false; + return true; +} + typedef struct { const char *name; bool (*fn)(file_t *, char *); + bool required; + bool found; } header_options_t; static bool header_set_title(file_t *file, char *val) @@ -75,14 +103,16 @@ static bool header_set_list(file_t *file, char *val) return false; } -static const header_options_t options[] = { +static header_options_t options[] = { { .name = "title", - .fn = header_set_title + .fn = header_set_title, + .required = true }, { .name = "summary", .fn = header_set_summary, + .required = true }, { .name = "route", @@ -122,6 +152,7 @@ static bool file_parse_header_line(file_t *file, char *line) { if (strcmp(line, opt->name) == 0) { + ((header_options_t *)opt)->found = true; return opt->fn(file, value); } } @@ -165,5 +196,24 @@ bool file_parse_header(file_t *file) } + STATIC_ARRAY_FOREACH(const header_options_t *opt, options) + { + if (opt->required && !opt->found) + { + PROGRESS_WARNING("The file %s is missing the '%s' required header", + file->path, opt->name); + return false; + } + } + + if (!file->route) + { + if (!get_filename_without_ext(file)) + { + PROGRESS_WARNING("Could not determine the route for %s", file->path); + return false; + } + } + return true; } diff --git a/main.c b/main.c @@ -135,9 +135,9 @@ static void help(void) usage(" ---"); usage(" title: Article title"); usage(" summary: Article summary"); - usage(" route: route-of-the-article"); usage(" ---"); usage("Optionnal headers can be passed to:"); + usage("- 'route': Route of article (/argument-value.html)"); usage("- 'date': Date of the article, in YYYY-MM-DD format. If this option"); usage(" is not here, shayla will look for the last modified timestamp from"); usage(" the filesystem in order to establish a chronology."); @@ -148,7 +148,7 @@ static void help(void) usage("website. " SDOWN_NAME " will look for them under the layouts directory"); usage("(either in the default 'layouts' directory or through the --layouts"); usage("option), and will expect HTML files named 'footer.html', 'header.html'"); - usage("and 'footer.html'. Any other file will be ignored."); + usage("and 'intro.html'. Any other file will be ignored."); usage(""); usage("This program is free software: you can redistribute it and/or modify"); usage("it under the terms of the GNU General Public License as published by"); @@ -174,13 +174,13 @@ static void version(void) typedef struct { size_t off; /*!< Structure variable offset */ - const char *def; /*!< Default value */ -} shayla_opt_t; +#define shayla_offset(member) offsetof(shayla_t, member) + const char *str; /*!< Named value */ +} shayla_named_offset_t; -static void shayla_options(shayla_t *ctx) +static bool shayla_options(shayla_t *ctx) { - static const shayla_opt_t addrs[] = { -#define shayla_offset(member) offsetof(shayla_t, member) + static const shayla_named_offset_t addrs[] = { [OPT_TITLE] = { shayla_offset(title), SDOWN_NAME }, [OPT_SRC] = { shayla_offset(posts_dir), "markdown" }, [OPT_STYLE] = { shayla_offset(styles_dir), "styles" }, @@ -190,10 +190,15 @@ static void shayla_options(shayla_t *ctx) [OPT_FAVICON] = { shayla_offset(favicon), NULL }, [OPT_URL] = { shayla_offset(url), NULL }, [OPT_IMG] = { shayla_offset(img_dir), "img" } -#undef shayla_offset }; - STATIC_ARRAY_FOREACH_I(i, const shayla_opt_t *opt, addrs) + if (IS_ARG_HERE(options, OPT_VERSION)) + { + version(); + return false; + } + + STATIC_ARRAY_FOREACH_I(i, const shayla_named_offset_t *opt, addrs) { uintptr_t ptr; char **str; @@ -206,8 +211,14 @@ static void shayla_options(shayla_t *ctx) if (IS_ARG_HERE(options, i)) *str = strdup(GET_STR_ARG(options, i)); - else if (opt->def != NULL) - *str = strdup(opt->def); + else if (opt->str != NULL) + *str = strdup(opt->str); + + if ((*str) == NULL && opt->str != NULL) + { + ERROR("No memory, could not parse arguments\n"); + return false; + } } if (IS_ARG_HERE(options, OPT_DEBUG)) @@ -218,6 +229,142 @@ static void shayla_options(shayla_t *ctx) pool_init(GET_INT_ARG(options, OPT_THREADS)); else pool_init(DEFAULT_THREADS); + + return true; +} + +static bool list_and_parse_posts(shayla_t *ctx) +{ + SET_PREFIX("Posts:"); + if (!list_dir(&ctx->posts, ctx->posts_dir, ".md")) + goto fail; + + if (!posts_parse_all(&ctx->posts)) + goto fail; + + SET_PREFIX("Layouts:"); + if (!list_dir(&ctx->layouts, ctx->layouts_dir, ".html")) + goto fail; + + SET_PREFIX("Styles:"); + if (!list_dir(&ctx->styles, ctx->styles_dir, "css")) + goto fail; + + SET_PREFIX("Images:"); + if (!list_dir(&ctx->img, ctx->img_dir, NULL)) + goto fail; + + return true; +fail: + return false; +} + +static bool read_layouts(shayla_t *ctx) +{ + static const shayla_named_offset_t layouts[] = { + { shayla_offset(header), "header.html" }, + { shayla_offset(footer), "footer.html" }, + { shayla_offset(intro), "intro.html" } + }; + file_t *ptr; + file_t **to; + bool found; + + list_for_each_entry(ptr, &ctx->layouts, list) + { + found = false; + STATIC_ARRAY_FOREACH(const shayla_named_offset_t *lyt, layouts) + { + if (strcmp(lyt->str, get_filename(ptr->path)) == 0) + { + found = true; + to = (file_t **)((uintptr_t)ctx + (uintptr_t)lyt->off); + break; + } + } + + if (!found) + { + WARNING("Ignoring file %s in layouts...\n", ptr->path); + continue; + } + + if (!file_read(ptr)) + { + ERROR("Could not read file %s\n", ptr->path); + return false; + } + + *to = ptr; + } + + return true; +} + +static bool create_final_directories_and_copy(shayla_t *ctx) +{ + char dir[PATH_MAX]; + char path[PATH_MAX * 2]; + file_t *ptr; + + if (!create_dir(ctx->dest)) + { + ERROR("Could not create directory %s: %s\n", ctx->dest, strerror(errno)); + return false; + } + + if (list_count(&ctx->img) > 0) + { + snprintf(dir, sizeof(dir), "%s/img", ctx->dest); + if (!create_dir(dir)) + goto fail; + + list_for_each_entry(ptr, &ctx->img, list) + { + snprintf(path, sizeof(path), "%s/%s", dir, get_filename(ptr->path)); + if (!fast_copy(ptr->path, path)) + { + ERROR("Could not copy %s to %s: %s\n", ptr->path, path, + strerror(errno)); + return false; + } + } + } + + if (list_count(&ctx->styles) > 0) + { + snprintf(dir, sizeof(dir), "%s/styles", ctx->dest); + if (!create_dir(dir)) + goto fail; + + list_for_each_entry(ptr, &ctx->styles, list) + { + snprintf(path, sizeof(path), "%s/%s", dir, get_filename(ptr->path)); + if (!fast_copy(ptr->path, path)) + { + ERROR("Could not copy %s to %s: %s\n", ptr->path, path, + strerror(errno)); + return false; + } + } + } + + if (ctx->favicon != NULL) + { + snprintf(dir, sizeof(dir), "%s/favicon.ico", ctx->dest); + if (!fast_copy(ctx->favicon, dir)) + { + ERROR("Could not copy %s to %s: %s\n", ctx->favicon, path, + strerror(errno)); + return false; + } + } + + return true; + +fail: + ERROR("Could not create directory %s: %s\n", dir, strerror(errno)); + return false; } int main(int ac, const char **av) @@ -230,32 +377,16 @@ int main(int ac, const char **av) if (!parse_args(ac, av, options, COUNT_OF(options), help)) return 1; - if (IS_ARG_HERE(options, OPT_VERSION)) - { - version(); - ret = 0; - goto end; - } - - shayla_options(&ctx); - - SET_PREFIX("Posts:"); - if (!list_dir(&ctx.posts, ctx.posts_dir, ".md")) - goto end; - - if (!posts_parse_all(&ctx.posts)) + if (!shayla_options(&ctx)) goto end; - SET_PREFIX("Layouts:"); - if (!list_dir(&ctx.layouts, ctx.layouts_dir, ".html")) + if (!list_and_parse_posts(&ctx)) goto end; - SET_PREFIX("Styles:"); - if (!list_dir(&ctx.styles, ctx.styles_dir, "css")) + if (!read_layouts(&ctx)) goto end; - SET_PREFIX("Images:"); - if (!list_dir(&ctx.img, ctx.img_dir, NULL)) + if (!create_final_directories_and_copy(&ctx)) goto end; ret = 0; diff --git a/shayla.h b/shayla.h @@ -37,6 +37,10 @@ typedef struct { const char *root; /*!< Root path of the website */ const char *url; /*!< URL of the website */ const char *favicon; /*!< Favicon of the website */ + + file_t *header; /*!< Helper pointer for site header */ + file_t *footer; /*!< Helper pointer for site footer */ + file_t *intro; /*!< Helper pointer for site intro */ } shayla_t; /*! diff --git a/utils.h b/utils.h @@ -19,6 +19,15 @@ # include <fcntl.h> # include <unistd.h> # include <stdbool.h> +# include <string.h> +# include <dirent.h> +# include <sys/types.h> +# include <sys/stat.h> +# if defined(__APPLE__) || defined(__FreeBSD__) +# include <copyfile.h> +# else +# include <sys/sendfile.h> +# endif /* Compile-time assertion */ # define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(COND)?1:-1] @@ -78,4 +87,72 @@ # define UNREACHABLE_CODE assert(!"Unreachable code " __FILE__ ": " STR(__FUNCTION__)) +/*! + * \brief Get a filename from a path + */ +static inline char *get_filename(char *path) +{ + size_t len = strlen(path); + + for (; len > 0 && path[len] != '/'; len--) + ; + + if (len == 0) + return path; + return path + len + 1; +} + +/*! + * \brief Create a directory if non-existent from a path + */ +static inline bool create_dir(const char *path) +{ + DIR *fd = opendir(path); + + if (fd == NULL) + { + if (mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == -1) + return false; + } + else + closedir(fd); + + return true; +} + +/*! + * \brief Zero-buffering kernel space copy of a file + */ +static inline bool fast_copy(const char *from, const char *to) +{ + int in_fd; + int out_fd; + int result; + + in_fd = open(from, O_RDONLY); + if (in_fd == -1) + return false; + + out_fd = creat(to, 0660); + if (out_fd == -1) + { + close(in_fd); + return false; + } + +#if defined(__APPLE__) || defined(__FreeBSD__) + result = fcopyfile(in_fd, out_fd, 0, COPYFILE_ALL); +#else + off_t bytesCopied = 0; + struct stat fileinfo = { 0 }; + + fstat(in_fd, &fileinfo); + result = sendfile(out_fd, in_fd, &bytesCopied, fileinfo.st_size); +#endif + + close(in_fd); + close(out_fd); + return result != -1; +} + #endif /* UTILS_H */