diff options
author | Rafael G. Martins <rafael@rafaelmartins.eng.br> | 2016-12-27 02:29:54 +0100 |
---|---|---|
committer | Rafael G. Martins <rafael@rafaelmartins.eng.br> | 2016-12-27 02:43:59 +0100 |
commit | 8cac2c3e3a61b64b9a9855dec413239bcec7f9d2 (patch) | |
tree | 6240cdabaa0352f08d62bbfa6de7c53f5a3f1063 /src/blogc-make/rules.c | |
parent | 7bf68b0b617fb3ffa86f38fe06a49786883037f4 (diff) | |
download | blogc-8cac2c3e3a61b64b9a9855dec413239bcec7f9d2.tar.gz blogc-8cac2c3e3a61b64b9a9855dec413239bcec7f9d2.tar.bz2 blogc-8cac2c3e3a61b64b9a9855dec413239bcec7f9d2.zip |
make: implemented a build tool for blogc
so, this is basically what happens when you don't have anything better
to do in the christmas weekend. most of this code was written in the
last 2 or 3 days.
i'd like to thank the chivas brothers, the weather and my psychological
problems for this achievement.
on a serious note, this tool still needs a man page, more tests, and the
aws lambda function should be adapted to use it instead of (or together
with) make/busybox.
also, while talking about aws lambda, this tool can be nicely embedded
into the blogc binary, to produce a single "small" static binary for
usage in lambda ;)
Diffstat (limited to 'src/blogc-make/rules.c')
-rw-r--r-- | src/blogc-make/rules.c | 752 |
1 files changed, 752 insertions, 0 deletions
diff --git a/src/blogc-make/rules.c b/src/blogc-make/rules.c new file mode 100644 index 0000000..80de6bb --- /dev/null +++ b/src/blogc-make/rules.c @@ -0,0 +1,752 @@ +/* + * blogc: A blog compiler. + * Copyright (C) 2016 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * + * This program can be distributed under the terms of the BSD License. + * See the file LICENSE. + */ + +#include <stdbool.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <time.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <math.h> +#include "../common/utils.h" +#include "ctx.h" +#include "exec.h" +#include "exec-native.h" +#include "rules.h" + + +// INDEX RULE + +static bc_slist_t* +index_outputlist(bm_ctx_t *ctx) +{ + if (ctx == NULL || ctx->settings->posts == NULL) + return NULL; + + bc_slist_t *rv = NULL; + const char *html_ext = bc_trie_lookup(ctx->settings->settings, + "html_ext"); + const char *output_dir = bc_trie_lookup(ctx->settings->settings, + "output_dir"); + const char *index_prefix = bc_trie_lookup(ctx->settings->settings, + "index_prefix"); + bool is_index = (index_prefix == NULL) && (html_ext[0] == '/'); + char *f = bc_strdup_printf("%s%s%s%s", output_dir, + is_index ? "" : "/", is_index ? "" : index_prefix, + html_ext); + rv = bc_slist_append(rv, bm_filectx_new(ctx, f)); + free(f); + return rv; +} + +static int +index_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bool verbose) +{ + if (ctx == NULL || ctx->settings->posts == NULL) + return 0; + + int rv = 0; + + bc_trie_t *variables = bc_trie_new(free); + bc_trie_insert(variables, "FILTER_PER_PAGE", + bc_strdup(bc_trie_lookup(ctx->settings->settings, "posts_per_page"))); + bc_trie_insert(variables, "FILTER_PAGE", bc_strdup("1")); + bc_trie_insert(variables, "DATE_FORMAT", + bc_strdup(bc_trie_lookup(ctx->settings->settings, "date_format"))); + bc_trie_insert(variables, "BM_RULE", bc_strdup("index")); + bc_trie_insert(variables, "BM_TYPE", bc_strdup("post")); + + for (bc_slist_t *l = outputs; l != NULL; l = l->next) { + bm_filectx_t *fctx = l->data; + if (fctx == NULL) + continue; + if (bm_rule_need_rebuild(ctx->posts_fctx, ctx->settings_fctx, + ctx->main_template_fctx, fctx, false)) + { + rv = bm_exec_blogc(ctx->settings, variables, true, + ctx->main_template_fctx, fctx, ctx->posts_fctx, verbose, + false); + if (rv != 0) + break; + } + } + + bc_trie_free(variables); + + return rv; +} + + +// ATOM RULE + +static bc_slist_t* +atom_outputlist(bm_ctx_t *ctx) +{ + if (ctx == NULL || ctx->settings->posts == NULL) + return NULL; + + bc_slist_t *rv = NULL; + const char *output_dir = bc_trie_lookup(ctx->settings->settings, + "output_dir"); + const char *atom_prefix = bc_trie_lookup(ctx->settings->settings, + "atom_prefix"); + const char *atom_ext = bc_trie_lookup(ctx->settings->settings, "atom_ext"); + char *f = bc_strdup_printf("%s/%s%s", output_dir, atom_prefix, atom_ext); + rv = bc_slist_append(rv, bm_filectx_new(ctx, f)); + free(f); + return rv; +} + +static int +atom_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bool verbose) +{ + if (ctx == NULL || ctx->settings->posts == NULL) + return 0; + + int rv = 0; + + bc_trie_t *variables = bc_trie_new(free); + bc_trie_insert(variables, "FILTER_PER_PAGE", + bc_strdup(bc_trie_lookup(ctx->settings->settings, + "atom_posts_per_page"))); + bc_trie_insert(variables, "FILTER_PAGE", bc_strdup("1")); + bc_trie_insert(variables, "DATE_FORMAT", bc_strdup("%Y-%m-%dT%H:%M:%SZ")); + bc_trie_insert(variables, "BM_RULE", bc_strdup("atom")); + bc_trie_insert(variables, "BM_TYPE", bc_strdup("atom")); + + for (bc_slist_t *l = outputs; l != NULL; l = l->next) { + bm_filectx_t *fctx = l->data; + if (fctx == NULL) + continue; + if (bm_rule_need_rebuild(ctx->posts_fctx, ctx->settings_fctx, NULL, + fctx, false)) + { + rv = bm_exec_blogc(ctx->settings, variables, true, + ctx->atom_template_fctx, fctx, ctx->posts_fctx, verbose, + false); + if (rv != 0) + break; + } + } + + bc_trie_free(variables); + + return rv; +} + + +// ATOM TAGS RULE + +static bc_slist_t* +atom_tags_outputlist(bm_ctx_t *ctx) +{ + if (ctx == NULL || ctx->settings->posts == NULL || ctx->settings->tags == NULL) + return NULL; + + bc_slist_t *rv = NULL; + const char *output_dir = bc_trie_lookup(ctx->settings->settings, + "output_dir"); + const char *atom_prefix = bc_trie_lookup(ctx->settings->settings, + "atom_prefix"); + const char *atom_ext = bc_trie_lookup(ctx->settings->settings, "atom_ext"); + for (size_t i = 0; ctx->settings->tags[i] != NULL; i++) { + char *f = bc_strdup_printf("%s/%s/%s%s", output_dir, atom_prefix, + ctx->settings->tags[i], atom_ext); + rv = bc_slist_append(rv, bm_filectx_new(ctx, f)); + free(f); + } + return rv; +} + +static int +atom_tags_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bool verbose) +{ + if (ctx == NULL || ctx->settings->posts == NULL || ctx->settings->tags == NULL) + return 0; + + int rv = 0; + size_t i = 0; + + bc_trie_t *variables = bc_trie_new(free); + bc_trie_insert(variables, "FILTER_PER_PAGE", + bc_strdup(bc_trie_lookup(ctx->settings->settings, + "atom_posts_per_page"))); + bc_trie_insert(variables, "FILTER_PAGE", bc_strdup("1")); + bc_trie_insert(variables, "DATE_FORMAT", bc_strdup("%Y-%m-%dT%H:%M:%SZ")); + bc_trie_insert(variables, "BM_RULE", bc_strdup("atom_tags")); + bc_trie_insert(variables, "BM_TYPE", bc_strdup("atom")); + + for (bc_slist_t *l = outputs; l != NULL; l = l->next, i++) { + bm_filectx_t *fctx = l->data; + if (fctx == NULL) + continue; + + bc_trie_insert(variables, "FILTER_TAG", + bc_strdup(ctx->settings->tags[i])); + + if (bm_rule_need_rebuild(ctx->posts_fctx, ctx->settings_fctx, NULL, + fctx, false)) + { + rv = bm_exec_blogc(ctx->settings, variables, true, + ctx->atom_template_fctx, fctx, ctx->posts_fctx, verbose, + false); + if (rv != 0) + break; + } + } + + bc_trie_free(variables); + + return rv; +} + + +// PAGINATION RULE + +static bc_slist_t* +pagination_outputlist(bm_ctx_t *ctx) +{ + if (ctx == NULL || ctx->settings->posts == NULL) + return NULL; + + long num_posts = bc_slist_length(ctx->posts_fctx); + long posts_per_page = strtol( + bc_trie_lookup(ctx->settings->settings, "posts_per_page"), + NULL, 10); // FIXME: improve + size_t pages = ceilf(((float) num_posts) / posts_per_page); + + const char *output_dir = bc_trie_lookup(ctx->settings->settings, + "output_dir"); + const char *pagination_prefix = bc_trie_lookup(ctx->settings->settings, + "pagination_prefix"); + const char *html_ext = bc_trie_lookup(ctx->settings->settings, + "html_ext"); + + bc_slist_t *rv = NULL; + for (size_t i = 0; i < pages; i++) { + char *f = bc_strdup_printf("%s/%s/%d%s", output_dir, pagination_prefix, + i + 1, html_ext); + rv = bc_slist_append(rv, bm_filectx_new(ctx, f)); + free(f); + } + return rv; +} + +static int +pagination_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bool verbose) +{ + if (ctx == NULL || ctx->settings->posts == NULL) + return 0; + + int rv = 0; + size_t page = 1; + + bc_trie_t *variables = bc_trie_new(free); + bc_trie_insert(variables, "FILTER_PER_PAGE", + bc_strdup(bc_trie_lookup(ctx->settings->settings, "posts_per_page"))); + bc_trie_insert(variables, "DATE_FORMAT", + bc_strdup(bc_trie_lookup(ctx->settings->settings, "date_format"))); + bc_trie_insert(variables, "BM_RULE", bc_strdup("pagination")); + bc_trie_insert(variables, "BM_TYPE", bc_strdup("post")); + + for (bc_slist_t *l = outputs; l != NULL; l = l->next, page++) { + bm_filectx_t *fctx = l->data; + if (fctx == NULL) + continue; + bc_trie_insert(variables, "FILTER_PAGE", bc_strdup_printf("%zu", page)); + if (bm_rule_need_rebuild(ctx->posts_fctx, ctx->settings_fctx, + ctx->main_template_fctx, fctx, false)) + { + rv = bm_exec_blogc(ctx->settings, variables, true, + ctx->main_template_fctx, fctx, ctx->posts_fctx, verbose, false); + if (rv != 0) + break; + } + } + + bc_trie_free(variables); + + return rv; +} + + +// POSTS RULE + +static bc_slist_t* +posts_outputlist(bm_ctx_t *ctx) +{ + if (ctx == NULL || ctx->settings->posts == NULL) + return NULL; + + const char *output_dir = bc_trie_lookup(ctx->settings->settings, + "output_dir"); + const char *post_prefix = bc_trie_lookup(ctx->settings->settings, + "post_prefix"); + const char *html_ext = bc_trie_lookup(ctx->settings->settings, + "html_ext"); + + bc_slist_t *rv = NULL; + for (size_t i = 0; ctx->settings->posts[i] != NULL; i++) { + char *f = bc_strdup_printf("%s/%s/%s%s", output_dir, post_prefix, + ctx->settings->posts[i], html_ext); + rv = bc_slist_append(rv, bm_filectx_new(ctx, f)); + free(f); + } + return rv; +} + +static int +posts_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bool verbose) +{ + if (ctx == NULL || ctx->settings->posts == NULL) + return 0; + + int rv = 0; + + bc_trie_t *variables = bc_trie_new(free); + bc_trie_insert(variables, "IS_POST", bc_strdup("1")); + bc_trie_insert(variables, "DATE_FORMAT", + bc_strdup(bc_trie_lookup(ctx->settings->settings, "date_format"))); + bc_trie_insert(variables, "BM_RULE", bc_strdup("posts")); + bc_trie_insert(variables, "BM_TYPE", bc_strdup("post")); + + bc_slist_t *s, *o; + + for (s = ctx->posts_fctx, o = outputs; s != NULL && o != NULL; + s = s->next, o = o->next) + { + bm_filectx_t *o_fctx = o->data; + if (o_fctx == NULL) + continue; + if (bm_rule_need_rebuild(s, ctx->settings_fctx, + ctx->main_template_fctx, o_fctx, true)) + { + rv = bm_exec_blogc(ctx->settings, variables, false, + ctx->main_template_fctx, o_fctx, s, verbose, true); + if (rv != 0) + break; + } + } + + bc_trie_free(variables); + + return rv; +} + + +// TAGS RULE + +static bc_slist_t* +tags_outputlist(bm_ctx_t *ctx) +{ + if (ctx == NULL || ctx->settings->posts == NULL || ctx->settings->tags == NULL) + return NULL; + + bc_slist_t *rv = NULL; + const char *output_dir = bc_trie_lookup(ctx->settings->settings, + "output_dir"); + const char *tag_prefix = bc_trie_lookup(ctx->settings->settings, + "tag_prefix"); + const char *html_ext = bc_trie_lookup(ctx->settings->settings, "html_ext"); + for (size_t i = 0; ctx->settings->tags[i] != NULL; i++) { + char *f = bc_strdup_printf("%s/%s/%s%s", output_dir, tag_prefix, + ctx->settings->tags[i], html_ext); + rv = bc_slist_append(rv, bm_filectx_new(ctx, f)); + free(f); + } + return rv; +} + +static int +tags_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bool verbose) +{ + if (ctx == NULL || ctx->settings->posts == NULL || ctx->settings->tags == NULL) + return 0; + + int rv = 0; + size_t i = 0; + + bc_trie_t *variables = bc_trie_new(free); + bc_trie_insert(variables, "FILTER_PER_PAGE", + bc_strdup(bc_trie_lookup(ctx->settings->settings, + "atom_posts_per_page"))); + bc_trie_insert(variables, "FILTER_PAGE", bc_strdup("1")); + bc_trie_insert(variables, "DATE_FORMAT", + bc_strdup(bc_trie_lookup(ctx->settings->settings, "date_format"))); + bc_trie_insert(variables, "BM_RULE", bc_strdup("tags")); + bc_trie_insert(variables, "BM_TYPE", bc_strdup("post")); + + for (bc_slist_t *l = outputs; l != NULL; l = l->next, i++) { + bm_filectx_t *fctx = l->data; + if (fctx == NULL) + continue; + + bc_trie_insert(variables, "FILTER_TAG", + bc_strdup(ctx->settings->tags[i])); + + if (bm_rule_need_rebuild(ctx->posts_fctx, ctx->settings_fctx, + ctx->main_template_fctx, fctx, false)) + { + rv = bm_exec_blogc(ctx->settings, variables, true, + ctx->main_template_fctx, fctx, ctx->posts_fctx, verbose, + false); + if (rv != 0) + break; + } + } + + bc_trie_free(variables); + + return rv; +} + + +// PAGES RULE + +static bc_slist_t* +pages_outputlist(bm_ctx_t *ctx) +{ + if (ctx == NULL || ctx->settings->pages == NULL) + return NULL; + + const char *output_dir = bc_trie_lookup(ctx->settings->settings, + "output_dir"); + const char *html_ext = bc_trie_lookup(ctx->settings->settings, "html_ext"); + + bc_slist_t *rv = NULL; + for (size_t i = 0; ctx->settings->pages[i] != NULL; i++) { + bool is_index = (0 == strcmp(ctx->settings->pages[i], "index")) + && (html_ext[0] == '/'); + char *f = bc_strdup_printf("%s%s%s%s", output_dir, + is_index ? "" : "/", is_index ? "" : ctx->settings->pages[i], + html_ext); + rv = bc_slist_append(rv, bm_filectx_new(ctx, f)); + free(f); + } + return rv; +} + +static int +pages_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bool verbose) +{ + if (ctx == NULL || ctx->settings->pages == NULL) + return 0; + + int rv = 0; + + bc_trie_t *variables = bc_trie_new(free); + bc_trie_insert(variables, "DATE_FORMAT", + bc_strdup(bc_trie_lookup(ctx->settings->settings, "date_format"))); + bc_trie_insert(variables, "BM_RULE", bc_strdup("pages")); + bc_trie_insert(variables, "BM_TYPE", bc_strdup("page")); + + bc_slist_t *s, *o; + + for (s = ctx->pages_fctx, o = outputs; s != NULL && o != NULL; + s = s->next, o = o->next) + { + bm_filectx_t *o_fctx = o->data; + if (o_fctx == NULL) + continue; + if (bm_rule_need_rebuild(s, ctx->settings_fctx, + ctx->main_template_fctx, o_fctx, true)) + { + rv = bm_exec_blogc(ctx->settings, variables, false, + ctx->main_template_fctx, o_fctx, s, verbose, true); + if (rv != 0) + break; + } + } + + bc_trie_free(variables); + + return rv; +} + + +// COPY FILES RULE + +static bc_slist_t* +copy_files_outputlist(bm_ctx_t *ctx) +{ + if (ctx == NULL || ctx->settings->copy_files == NULL) + return NULL; + + bc_slist_t *rv = NULL; + const char *dir = bc_trie_lookup(ctx->settings->settings, "output_dir"); + for (size_t i = 0; ctx->settings->copy_files[i] != NULL; i++) { + char *f = bc_strdup_printf("%s/%s", dir, ctx->settings->copy_files[i]); + rv = bc_slist_append(rv, bm_filectx_new(ctx, f)); + free(f); + } + return rv; +} + +static int +copy_files_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bool verbose) +{ + if (ctx == NULL || ctx->settings->copy_files == NULL) + return 0; + + int rv = 0; + + bc_slist_t *s, *o; + + for (s = ctx->copy_files_fctx, o = outputs; s != NULL && o != NULL; + s = s->next, o = o->next) + { + bm_filectx_t *o_fctx = o->data; + if (o_fctx == NULL) + continue; + + if (bm_rule_need_rebuild(s, ctx->settings_fctx, NULL, o_fctx, true)) { + rv = bm_exec_native_cp(s->data, o_fctx, verbose); + if (rv != 0) + break; + } + } + + return rv; +} + + +// CLEAN RULE + +static bc_slist_t* +clean_outputlist(bm_ctx_t *ctx) +{ + return bm_rule_list_built_files(ctx); +} + +static int +clean_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bool verbose) +{ + int rv = 0; + + for (bc_slist_t *l = outputs; l != NULL; l = l->next) + { + bm_filectx_t *fctx = l->data; + if (fctx == NULL) + continue; + + if (fctx->readable) { + rv = bm_exec_native_rm(fctx, verbose); + if (rv != 0) + break; + } + } + + return rv; +} + + +const bm_rule_t const rules[] = { + { + .name = "index", + .help = "build website index from posts", + .outputlist_func = index_outputlist, + .exec_func = index_exec, + .generate_files = true, + }, + { + .name = "atom", + .help = "build main atom feed from posts", + .outputlist_func = atom_outputlist, + .exec_func = atom_exec, + .generate_files = true, + }, + { + .name = "atom_tags", + .help = "build atom feeds for each tag from posts", + .outputlist_func = atom_tags_outputlist, + .exec_func = atom_tags_exec, + .generate_files = true, + }, + { + .name = "pagination", + .help = "build pagination pages from posts", + .outputlist_func = pagination_outputlist, + .exec_func = pagination_exec, + .generate_files = true, + }, + { + .name = "posts", + .help = "build individual pages for each post", + .outputlist_func = posts_outputlist, + .exec_func = posts_exec, + .generate_files = true, + }, + { + .name = "tags", + .help = "build post listings for each tag from posts", + .outputlist_func = tags_outputlist, + .exec_func = tags_exec, + .generate_files = true, + }, + { + .name = "pages", + .help = "build individual pages for each page", + .outputlist_func = pages_outputlist, + .exec_func = pages_exec, + .generate_files = true, + }, + { + .name = "copy_files", + .help = "copy static files from source directory to output directory", + .outputlist_func = copy_files_outputlist, + .exec_func = copy_files_exec, + .generate_files = true, + }, + { + .name = "clean", + .help = "clean built files and empty directories in output directory", + .outputlist_func = clean_outputlist, + .exec_func = clean_exec, + .generate_files = false, + }, + {NULL, NULL, NULL, NULL, false}, +}; + + +int +bm_rule_executor(bm_ctx_t *ctx, bc_slist_t *rule_list, bool verbose) +{ + const bm_rule_t *rule = NULL; + int rv = 0; + + for (bc_slist_t *l = rule_list; l != NULL; l = l->next) { + if (0 == strcmp("all", (char*) l->data)) { + bc_slist_t *s = NULL; + for (size_t i = 0; rules[i].name != NULL; i++) { + if (!rules[i].generate_files) { + continue; + } + s = bc_slist_append(s, bc_strdup(rules[i].name)); + } + rv = bm_rule_executor(ctx, s, verbose); + bc_slist_free_full(s, free); + continue; + } + rule = NULL; + for (size_t i = 0; rules[i].name != NULL; i++) { + if (0 == strcmp((char*) l->data, rules[i].name)) { + rule = &(rules[i]); + rv = bm_rule_execute(ctx, rule, verbose); + if (rv != 0) + return rv; + } + } + if (rule == NULL) { + fprintf(stderr, "blogc-make: error: rule not found: %s\n", + (char*) l->data); + rv = 3; + } + } + + return rv; +} + + +int +bm_rule_execute(bm_ctx_t *ctx, const bm_rule_t *rule, bool verbose) +{ + if (rule == NULL) + return 3; + + bc_slist_t *outputs = rule->outputlist_func(ctx); + int rv = rule->exec_func(ctx, outputs, verbose); + + bc_slist_free_full(outputs, (bc_free_func_t) bm_filectx_free); + + return rv; +} + + +bool +bm_rule_need_rebuild(bc_slist_t *sources, bm_filectx_t *settings, + bm_filectx_t *template, bm_filectx_t *output, bool only_first_source) +{ + if (output == NULL || !output->readable) + return true; + + bool rv = false; + + bc_slist_t *s = NULL; + if (settings != NULL) + s = bc_slist_append(s, settings); + if (template != NULL) + s = bc_slist_append(s, template); + + for (bc_slist_t *l = sources; l != NULL; l = l->next) { + s = bc_slist_append(s, l->data); + if (only_first_source) + break; + } + + for (bc_slist_t *l = s; l != NULL; l = l->next) { + bm_filectx_t *source = l->data; + if (source == NULL || !source->readable) { + // this is unlikely to happen, but lets just say that we need + // a rebuild and let blogc bail out. + rv = true; + break; + } + if (source->timestamp.tv_sec == output->timestamp.tv_sec) { + if (source->timestamp.tv_nsec > output->timestamp.tv_nsec) { + rv = true; + break; + } + } + else if (source->timestamp.tv_sec > output->timestamp.tv_sec) { + rv = true; + break; + } + } + + bc_slist_free(s); + + return rv; +} + + +bc_slist_t* +bm_rule_list_built_files(bm_ctx_t *ctx) +{ + if (ctx == NULL) + return NULL; + + bc_slist_t *rv = NULL; + for (size_t i = 0; rules[i].name != NULL; i++) { + if (!rules[i].generate_files) { + continue; + } + + bc_slist_t *o = rules[i].outputlist_func(ctx); + for (bc_slist_t *l = o; l != NULL; l = l->next) { + rv = bc_slist_append(rv, l->data); + } + bc_slist_free(o); + } + return rv; +} + + +void +bm_rule_print_help(void) +{ + printf( + "\n" + "build rules:\n" + " all run all rules that generate output files\n"); + + for (size_t i = 0; rules[i].name != NULL; i++) { + printf(" %-12s %s\n", rules[i].name, rules[i].help); + } +} |