From 792ac4ec66dd098109a88065420ef95c1a78624f Mon Sep 17 00:00:00 2001 From: "Rafael G. Martins" Date: Sat, 27 Apr 2019 04:22:56 +0200 Subject: make: support posts autoloading --- man/blogcfile.5.ronn | 9 ++ src/blogc-make/ctx.c | 108 +++++++++++++- src/blogc-make/ctx.h | 3 + src/blogc-make/rules.c | 48 ++++--- src/blogc-make/settings.c | 1 + tests/blogc-make/check_blogc_make.sh.in | 248 ++++++++++++++++++++++++++++++++ 6 files changed, 393 insertions(+), 24 deletions(-) diff --git a/man/blogcfile.5.ronn b/man/blogcfile.5.ronn index fd77e1c..b63bcf7 100644 --- a/man/blogcfile.5.ronn +++ b/man/blogcfile.5.ronn @@ -110,6 +110,12 @@ however these rules can be customized with the following settings, from the The prefix of the posts file names. It is used for both content and output directories, and is relative to `content_dir` and the output directory. + * `posts_autoload` (default: `false`): + If true, blogc-make(1) will guess the source files from the posts directory + (`content_dir`/`post_prefix`) and ignore anything listed in `[posts]` section. + This setting automatically enable `posts_sort`, as there's no manually-provided + order of posts to follow. + * `posts_per_page` (default: `10`): Number of posts per page in the pagination pages. If negative, all the posts are included. If `0`, no post listing pages are generated. Also, if negative or @@ -141,6 +147,9 @@ is `foo`. All the posts are relative to the `post_prefix` in the root of the website. +This section can be omitted if using the `posts_autoload` setting, as it will +guess the posts from the directory. + ### Pages listing The `[pages]` section is a listing of the pages that will be included in the diff --git a/src/blogc-make/ctx.c b/src/blogc-make/ctx.c index 423b334..d7a503d 100644 --- a/src/blogc-make/ctx.c +++ b/src/blogc-make/ctx.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -267,7 +268,11 @@ bm_ctx_new(bm_ctx_t *base, const char *settings_file, const char *argv0, } rv->posts_fctx = NULL; - if (settings->posts != NULL) { + rv->posts_deleted = false; + if (bc_str_to_bool(bc_trie_lookup(settings->settings, "posts_autoload"))) { + bm_ctx_autoload_posts(rv); + } + else if (settings->posts != NULL) { for (size_t i = 0; settings->posts[i] != NULL; i++) { char *f = bm_generate_filename(content_dir, post_prefix, settings->posts[i], source_ext); @@ -306,7 +311,7 @@ bm_ctx_reload(bm_ctx_t **ctx) if (*ctx == NULL || (*ctx)->settings_fctx == NULL) return false; - if (bm_filectx_changed((*ctx)->settings_fctx, NULL, NULL)) { + if ((*ctx)->posts_deleted || bm_filectx_changed((*ctx)->settings_fctx, NULL, NULL)) { // reload everything! we could just reload settings_fctx, as this // would force rebuilding everything, but we need to know new/deleted // files @@ -328,8 +333,13 @@ bm_ctx_reload(bm_ctx_t **ctx) bm_filectx_reload((*ctx)->atom_template_fctx); bm_filectx_reload((*ctx)->listing_entry_fctx); - for (bc_slist_t *tmp = (*ctx)->posts_fctx; tmp != NULL; tmp = tmp->next) - bm_filectx_reload((bm_filectx_t*) tmp->data); + if (bc_str_to_bool(bm_ctx_settings_lookup(*ctx, "posts_autoload"))) { + bm_ctx_autoload_posts(*ctx); + } + else { + for (bc_slist_t *tmp = (*ctx)->posts_fctx; tmp != NULL; tmp = tmp->next) + bm_filectx_reload((bm_filectx_t*) tmp->data); + } for (bc_slist_t *tmp = (*ctx)->pages_fctx; tmp != NULL; tmp = tmp->next) bm_filectx_reload((bm_filectx_t*) tmp->data); @@ -341,6 +351,96 @@ bm_ctx_reload(bm_ctx_t **ctx) } +void +bm_ctx_autoload_posts(bm_ctx_t *ctx) +{ + if (ctx == NULL) + return; + + const char *content_dir = bm_ctx_settings_lookup(ctx, "content_dir"); + const char *post_prefix = bm_ctx_settings_lookup(ctx, "post_prefix"); + const char *source_ext = bm_ctx_settings_lookup(ctx, "source_ext"); + + char *posts_dir = bm_generate_filename(content_dir, post_prefix, NULL, NULL); + char *abs_posts_dir = bc_strdup_printf("%s/%s", ctx->root_dir, posts_dir); + DIR *dir = opendir(abs_posts_dir); + if (dir == NULL) { + fprintf(stderr, + "blogc-make: warning: failed to open directory for posts autoload " + "(%s): %s\n", abs_posts_dir, strerror(errno)); + free(posts_dir); + free(abs_posts_dir); + return; + } + free(abs_posts_dir); + + // trick 0: avoid additional loops if we are not reloading + bool reloading = ctx->posts_fctx != NULL; + + // trick 1: set all the post file contexts as unreadable, so we can detect + // which ones were deleted. + for (bc_slist_t *tmp = ctx->posts_fctx; tmp != NULL; tmp = tmp->next) { + bm_filectx_t *fc = tmp->data; + if (fc->readable) { + fc->readable = false; + } + } + + struct dirent *e; + while (NULL != (e = readdir(dir))) { + if ((0 == strcmp(e->d_name, ".")) || (0 == strcmp(e->d_name, ".."))) + continue; + if (!bc_str_ends_with(e->d_name, source_ext)) + continue; + char *tmp = bc_strdup_printf("%s/%s", posts_dir, e->d_name); + + // trick 2: if the file context exists in the index, just set it as + // readable + bm_filectx_t *fc = NULL; + if (reloading) { + for (bc_slist_t *t = ctx->posts_fctx; t != NULL; t = t->next) { + bm_filectx_t *fct = t->data; + if (0 == strcmp(fct->short_path, tmp)) { + fc = fct; + break; + } + } + } + if (fc != NULL) { + fc->readable = true; + bm_filectx_reload(fc); + } + else { + e->d_name[strlen(e->d_name) - strlen(source_ext)] = 0; + fc = bm_filectx_new(ctx, tmp, e->d_name, NULL); + ctx->posts_fctx = bc_slist_append(ctx->posts_fctx, fc); + } + free(tmp); + } + + // trick 3: delete the unreadable file contexts + ctx->posts_deleted = false; + if (reloading) { + for (bc_slist_t *tmp = ctx->posts_fctx; tmp != NULL;) { + bm_filectx_t *fc = tmp->data; + if (!fc->readable) { + bc_slist_t *next = tmp->next; + ctx->posts_fctx = bc_slist_remove(ctx->posts_fctx, tmp, + (bc_free_func_t) bm_filectx_free); + tmp = next; + ctx->posts_deleted = true; + } + else { + tmp = tmp->next; + } + } + } + + closedir(dir); + free(posts_dir); +} + + void bm_ctx_free_internal(bm_ctx_t *ctx) { diff --git a/src/blogc-make/ctx.h b/src/blogc-make/ctx.h index a66d51c..43284a5 100644 --- a/src/blogc-make/ctx.h +++ b/src/blogc-make/ctx.h @@ -63,8 +63,10 @@ typedef struct { bm_filectx_t *listing_entry_fctx; bc_slist_t *posts_fctx; + bool posts_deleted; bc_slist_t *pages_fctx; bc_slist_t *copy_fctx; + } bm_ctx_t; bm_filectx_t* bm_filectx_new(bm_ctx_t *ctx, const char *filename, const char *slug, @@ -76,6 +78,7 @@ void bm_filectx_free(bm_filectx_t *fctx); bm_ctx_t* bm_ctx_new(bm_ctx_t *base, const char *settings_file, const char *argv0, bc_error_t **err); bool bm_ctx_reload(bm_ctx_t **ctx); +void bm_ctx_autoload_posts(bm_ctx_t *ctx); void bm_ctx_free_internal(bm_ctx_t *ctx); void bm_ctx_free(bm_ctx_t *ctx); const char* bm_ctx_settings_lookup(bm_ctx_t *ctx, const char *key); diff --git a/src/blogc-make/rules.c b/src/blogc-make/rules.c index 96cf51d..f42b99a 100644 --- a/src/blogc-make/rules.c +++ b/src/blogc-make/rules.c @@ -32,7 +32,9 @@ posts_ordering(bm_ctx_t *ctx, bc_trie_t *variables, const char *variable) const char *value = bm_ctx_settings_lookup_str(ctx, variable); bool asc = 0 == strcasecmp(value, "asc"); - bool sort = bc_str_to_bool(bm_ctx_settings_lookup(ctx, "posts_sort")); + bool posts_autoload = bc_str_to_bool(bm_ctx_settings_lookup(ctx, "posts_autoload")); + bool posts_sort = bc_str_to_bool(bm_ctx_settings_lookup(ctx, "posts_sort")); + bool sort = posts_sort || posts_autoload; if (sort) { bc_trie_insert(variables, "FILTER_SORT", bc_strdup("1")); @@ -75,7 +77,7 @@ posts_pagination_enabled(bm_ctx_t *ctx, const char *variable) static bc_slist_t* index_outputlist(bm_ctx_t *ctx) { - if (ctx == NULL || ctx->settings->posts == NULL) + if (ctx == NULL || ctx->posts_fctx == NULL) return NULL; if (!posts_pagination_enabled(ctx, "posts_per_page")) @@ -97,7 +99,7 @@ index_outputlist(bm_ctx_t *ctx) static int index_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bc_trie_t *args) { - if (ctx == NULL || ctx->settings->posts == NULL) + if (ctx == NULL || ctx->posts_fctx == NULL) return 0; int rv = 0; @@ -114,7 +116,8 @@ index_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bc_trie_t *args) bm_filectx_t *fctx = l->data; if (fctx == NULL) continue; - if (bm_rule_need_rebuild(ctx->posts_fctx, ctx->settings_fctx, + if (ctx->posts_deleted || + bm_rule_need_rebuild(ctx->posts_fctx, ctx->settings_fctx, ctx->listing_entry_fctx, ctx->main_template_fctx, fctx, false)) { rv = bm_exec_blogc(ctx, variables, NULL, true, ctx->listing_entry_fctx, @@ -135,7 +138,7 @@ index_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bc_trie_t *args) static bc_slist_t* atom_outputlist(bm_ctx_t *ctx) { - if (ctx == NULL || ctx->settings->posts == NULL) + if (ctx == NULL || ctx->posts_fctx == NULL) return NULL; if (!posts_pagination_enabled(ctx, "atom_posts_per_page")) @@ -157,7 +160,7 @@ atom_outputlist(bm_ctx_t *ctx) static int atom_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bc_trie_t *args) { - if (ctx == NULL || ctx->settings->posts == NULL) + if (ctx == NULL || ctx->posts_fctx == NULL) return 0; int rv = 0; @@ -173,7 +176,8 @@ atom_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bc_trie_t *args) bm_filectx_t *fctx = l->data; if (fctx == NULL) continue; - if (bm_rule_need_rebuild(ctx->posts_fctx, ctx->settings_fctx, NULL, NULL, + if (ctx->posts_deleted || + bm_rule_need_rebuild(ctx->posts_fctx, ctx->settings_fctx, NULL, NULL, fctx, false)) { rv = bm_exec_blogc(ctx, variables, NULL, true, NULL, ctx->atom_template_fctx, @@ -194,7 +198,7 @@ atom_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bc_trie_t *args) static bc_slist_t* atom_tags_outputlist(bm_ctx_t *ctx) { - if (ctx == NULL || ctx->settings->posts == NULL || ctx->settings->tags == NULL) + if (ctx == NULL || ctx->posts_fctx == NULL || ctx->settings->tags == NULL) return NULL; if (!posts_pagination_enabled(ctx, "atom_posts_per_page")) @@ -218,7 +222,7 @@ atom_tags_outputlist(bm_ctx_t *ctx) static int atom_tags_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bc_trie_t *args) { - if (ctx == NULL || ctx->settings->posts == NULL || ctx->settings->tags == NULL) + if (ctx == NULL || ctx->posts_fctx == NULL || ctx->settings->tags == NULL) return 0; int rv = 0; @@ -239,7 +243,8 @@ atom_tags_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bc_trie_t *args) bc_trie_insert(variables, "FILTER_TAG", bc_strdup(ctx->settings->tags[i])); - if (bm_rule_need_rebuild(ctx->posts_fctx, ctx->settings_fctx, NULL, NULL, + if (ctx->posts_deleted || + bm_rule_need_rebuild(ctx->posts_fctx, ctx->settings_fctx, NULL, NULL, fctx, false)) { rv = bm_exec_blogc(ctx, variables, NULL, true, NULL, ctx->atom_template_fctx, @@ -260,7 +265,7 @@ atom_tags_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bc_trie_t *args) static bc_slist_t* pagination_outputlist(bm_ctx_t *ctx) { - if (ctx == NULL || ctx->settings->posts == NULL) + if (ctx == NULL || ctx->posts_fctx == NULL) return NULL; // not using posts_pagination_enabled() here because we need to calculate @@ -293,7 +298,7 @@ pagination_outputlist(bm_ctx_t *ctx) static int pagination_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bc_trie_t *args) { - if (ctx == NULL || ctx->settings->posts == NULL) + if (ctx == NULL || ctx->posts_fctx == NULL) return 0; int rv = 0; @@ -315,7 +320,8 @@ pagination_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bc_trie_t *args) 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, + if (ctx->posts_deleted || + bm_rule_need_rebuild(ctx->posts_fctx, ctx->settings_fctx, ctx->listing_entry_fctx, ctx->main_template_fctx, fctx, false)) { rv = bm_exec_blogc(ctx, variables, NULL, true, ctx->listing_entry_fctx, @@ -336,7 +342,7 @@ pagination_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bc_trie_t *args) static bc_slist_t* posts_outputlist(bm_ctx_t *ctx) { - if (ctx == NULL || ctx->settings->posts == NULL) + if (ctx == NULL || ctx->posts_fctx == NULL) return NULL; bc_slist_t *rv = NULL; @@ -344,9 +350,10 @@ posts_outputlist(bm_ctx_t *ctx) const char *post_prefix = bm_ctx_settings_lookup(ctx, "post_prefix"); const char *html_ext = bm_ctx_settings_lookup(ctx, "html_ext"); - for (size_t i = 0; ctx->settings->posts[i] != NULL; i++) { + for (bc_slist_t *l = ctx->posts_fctx; l != NULL; l = l->next) { + bm_filectx_t *fc = l->data; char *f = bm_generate_filename(ctx->short_output_dir, post_prefix, - ctx->settings->posts[i], html_ext); + fc->slug, html_ext); rv = bc_slist_append(rv, bm_filectx_new(ctx, f, NULL, NULL)); free(f); } @@ -357,7 +364,7 @@ posts_outputlist(bm_ctx_t *ctx) static int posts_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bc_trie_t *args) { - if (ctx == NULL || ctx->settings->posts == NULL) + if (ctx == NULL || ctx->posts_fctx == NULL) return 0; int rv = 0; @@ -403,7 +410,7 @@ posts_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bc_trie_t *args) static bc_slist_t* tags_outputlist(bm_ctx_t *ctx) { - if (ctx == NULL || ctx->settings->posts == NULL || ctx->settings->tags == NULL) + if (ctx == NULL || ctx->posts_fctx == NULL || ctx->settings->tags == NULL) return NULL; if (!posts_pagination_enabled(ctx, "posts_per_page")) @@ -427,7 +434,7 @@ tags_outputlist(bm_ctx_t *ctx) static int tags_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bc_trie_t *args) { - if (ctx == NULL || ctx->settings->posts == NULL || ctx->settings->tags == NULL) + if (ctx == NULL || ctx->posts_fctx == NULL || ctx->settings->tags == NULL) return 0; int rv = 0; @@ -449,7 +456,8 @@ tags_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bc_trie_t *args) bc_trie_insert(variables, "FILTER_TAG", bc_strdup(ctx->settings->tags[i])); - if (bm_rule_need_rebuild(ctx->posts_fctx, ctx->settings_fctx, + if (ctx->posts_deleted || + bm_rule_need_rebuild(ctx->posts_fctx, ctx->settings_fctx, ctx->listing_entry_fctx, ctx->main_template_fctx, fctx, false)) { rv = bm_exec_blogc(ctx, variables, NULL, true, ctx->listing_entry_fctx, diff --git a/src/blogc-make/settings.c b/src/blogc-make/settings.c index be976e3..2f56d9c 100644 --- a/src/blogc-make/settings.c +++ b/src/blogc-make/settings.c @@ -28,6 +28,7 @@ static const struct default_settings_map { {"main_template", "main.tmpl"}, {"source_ext", ".txt"}, {"listing_entry", NULL}, + {"posts_autoload", NULL}, {"posts_sort", NULL}, // pagination diff --git a/tests/blogc-make/check_blogc_make.sh.in b/tests/blogc-make/check_blogc_make.sh.in index 36dfceb..d171027 100755 --- a/tests/blogc-make/check_blogc_make.sh.in +++ b/tests/blogc-make/check_blogc_make.sh.in @@ -755,6 +755,254 @@ diff -uN "${TEMP}/proj/_build/post/post11/index.html" "${TEMP}/expected-post-pos rm -rf "${TEMP}/proj/_build" +### default settings with some posts, order asc, posts_autoload + +mkdir -p "${TEMP}/proj/content/post2" + +cat > "${TEMP}/proj/content/post2/foo.txt" < "${TEMP}/proj/content/post2/bar.txt" < "${TEMP}/proj/blogcfile" <&1 | tee "${TEMP}/output.txt" +grep "_build/index\\.html" "${TEMP}/output.txt" +grep "_build/atom\\.xml" "${TEMP}/output.txt" +grep "_build/page/1/index\\.html" "${TEMP}/output.txt" +grep "_build/post2/foo/index\\.html" "${TEMP}/output.txt" +grep "_build/post2/bar/index\\.html" "${TEMP}/output.txt" + +rm "${TEMP}/output.txt" + +cat > "${TEMP}/expected-index.html" < "${TEMP}/expected-atom.xml" < + + Lol's Website + http://example.org/atom.xml + 2016-09-01T00:00:00Z + + + + Lol + author@example.com + + WAT?! + + + Bar + http://example.org/post2/bar/index.html + 2016-09-01T00:00:00Z + 2016-09-01T00:00:00Z + + + Lol + author@example.com + + This is bar.

+]]>
+
+ + + Foo + http://example.org/post2/foo/index.html + 2016-10-01T00:00:00Z + 2016-10-01T00:00:00Z + + + Lol + author@example.com + + This is foo.

+]]>
+
+ +
+EOF +diff -uN "${TEMP}/proj/_build/atom.xml" "${TEMP}/expected-atom.xml" + +cat > "${TEMP}/expected-post-foo.html" <This is foo.

+ + +EOF +diff -uN "${TEMP}/proj/_build/post2/foo/index.html" "${TEMP}/expected-post-foo.html" + +cat > "${TEMP}/expected-post-bar.html" <This is bar.

+ + +EOF +diff -uN "${TEMP}/proj/_build/post2/bar/index.html" "${TEMP}/expected-post-bar.html" + +rm -rf "${TEMP}/proj/_build" "${TEMP}/proj/content/post2" + + +### default settings with some posts, order desc, posts_autoload + +mkdir -p "${TEMP}/proj/content/post2" + +cat > "${TEMP}/proj/content/post2/foo.txt" < "${TEMP}/proj/content/post2/bar.txt" < "${TEMP}/proj/blogcfile" <&1 | tee "${TEMP}/output.txt" +grep "_build/index\\.html" "${TEMP}/output.txt" +grep "_build/atom\\.xml" "${TEMP}/output.txt" +grep "_build/page/1/index\\.html" "${TEMP}/output.txt" +grep "_build/post2/foo/index\\.html" "${TEMP}/output.txt" +grep "_build/post2/bar/index\\.html" "${TEMP}/output.txt" + +rm "${TEMP}/output.txt" + +cat > "${TEMP}/expected-index.html" < "${TEMP}/expected-atom.xml" < + + Lol's Website + http://example.org/atom.xml + 2016-09-01T00:00:00Z + + + + Lol + author@example.com + + WAT?! + + + Bar + http://example.org/post2/bar/index.html + 2016-09-01T00:00:00Z + 2016-09-01T00:00:00Z + + + Lol + author@example.com + + This is bar.

+]]>
+
+ + + Foo + http://example.org/post2/foo/index.html + 2016-08-01T00:00:00Z + 2016-08-01T00:00:00Z + + + Lol + author@example.com + + This is foo.

+]]>
+
+ +
+EOF +diff -uN "${TEMP}/proj/_build/atom.xml" "${TEMP}/expected-atom.xml" + +cat > "${TEMP}/expected-post-foo.html" <This is foo.

+ + +EOF +diff -uN "${TEMP}/proj/_build/post2/foo/index.html" "${TEMP}/expected-post-foo.html" + +cat > "${TEMP}/expected-post-bar.html" <This is bar.

+ + +EOF +diff -uN "${TEMP}/proj/_build/post2/bar/index.html" "${TEMP}/expected-post-bar.html" + +rm -rf "${TEMP}/proj/_build" "${TEMP}/proj/content/post2" + + ### default settings with some posts, order asc, posts_sort cat > "${TEMP}/proj/blogcfile" <