aboutsummaryrefslogtreecommitdiffstats
path: root/src/blogc-make
diff options
context:
space:
mode:
Diffstat (limited to 'src/blogc-make')
-rw-r--r--src/blogc-make/atom.c93
-rw-r--r--src/blogc-make/atom.h18
-rw-r--r--src/blogc-make/ctx.c172
-rw-r--r--src/blogc-make/ctx.h43
-rw-r--r--src/blogc-make/exec-native.c161
-rw-r--r--src/blogc-make/exec-native.h18
-rw-r--r--src/blogc-make/exec.c297
-rw-r--r--src/blogc-make/exec.h24
-rw-r--r--src/blogc-make/main.c125
-rw-r--r--src/blogc-make/rules.c752
-rw-r--r--src/blogc-make/rules.h35
-rw-r--r--src/blogc-make/settings.c175
-rw-r--r--src/blogc-make/settings.h30
13 files changed, 1943 insertions, 0 deletions
diff --git a/src/blogc-make/atom.c b/src/blogc-make/atom.c
new file mode 100644
index 0000000..c7f98a5
--- /dev/null
+++ b/src/blogc-make/atom.c
@@ -0,0 +1,93 @@
+/*
+ * 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 <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include "../common/error.h"
+#include "../common/utils.h"
+#include "settings.h"
+#include "atom.h"
+
+static const char atom_template[] =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<feed xmlns=\"http://www.w3.org/2005/Atom\">\n"
+ " <title type=\"text\">{{ SITE_TITLE }}{%% ifdef FILTER_TAG %%} - "
+ "{{ FILTER_TAG }}{%% endif %%}</title>\n"
+ " <id>{{ BASE_URL }}/%s{%% ifdef FILTER_TAG %%}/{{ FILTER_TAG }}"
+ "{%% endif %%}%s</id>\n"
+ " <updated>{{ DATE_FIRST_FORMATTED }}</updated>\n"
+ " <link href=\"{{ BASE_DOMAIN }}{{ BASE_URL }}/\" />\n"
+ " <link href=\"{{ BASE_DOMAIN }}{{ BASE_URL }}/%s{%% ifdef FILTER_TAG %%}"
+ "/{{ FILTER_TAG }}{%% endif %%}%s\" rel=\"self\" />\n"
+ " <author>\n"
+ " <name>{{ AUTHOR_NAME }}</name>\n"
+ " <email>{{ AUTHOR_EMAIL }}</email>\n"
+ " </author>\n"
+ " <subtitle type=\"text\">{{ SITE_TAGLINE }}</subtitle>\n"
+ " {%% block listing %%}\n"
+ " <entry>\n"
+ " <title type=\"text\">{{ TITLE }}</title>\n"
+ " <id>{{ BASE_URL }}/%s/{{ FILENAME }}/</id>\n"
+ " <updated>{{ DATE_FORMATTED }}</updated>\n"
+ " <published>{{ DATE_FORMATTED }}</published>\n"
+ " <link href=\"{{ BASE_DOMAIN }}{{ BASE_URL }}/%s/{{ FILENAME }}/\" />\n"
+ " <author>\n"
+ " <name>{{ AUTHOR_NAME }}</name>\n"
+ " <email>{{ AUTHOR_EMAIL }}</email>\n"
+ " </author>\n"
+ " <content type=\"html\"><![CDATA[{{ CONTENT }}]]></content>\n"
+ " </entry>\n"
+ " {%% endblock %%}\n"
+ "</feed>\n";
+
+
+char*
+bm_atom_deploy(bm_settings_t *settings, bc_error_t **err)
+{
+ if (err == NULL || *err != NULL)
+ return NULL;
+
+ // this is not really portable
+ char fname[] = "/tmp/blogc-make_XXXXXX";
+ int fd;
+ if (-1 == (fd = mkstemp(fname))) {
+ *err = bc_error_new_printf(BLOGC_MAKE_ERROR_ATOM,
+ "Failed to create temporary atom template: %s", strerror(errno));
+ return NULL;
+ }
+
+ const char *atom_prefix = bc_trie_lookup(settings->settings, "atom_prefix");
+ const char *atom_ext = bc_trie_lookup(settings->settings, "atom_ext");
+ const char *post_prefix = bc_trie_lookup(settings->settings, "post_prefix");
+
+ char *content = bc_strdup_printf(atom_template, atom_prefix, atom_ext,
+ atom_prefix, atom_ext, post_prefix, post_prefix);
+
+ if (-1 == write(fd, content, strlen(content))) {
+ *err = bc_error_new_printf(BLOGC_MAKE_ERROR_ATOM,
+ "Failed to write to temporary atom template: %s", strerror(errno));
+ free(content);
+ close(fd);
+ unlink(fname);
+ return NULL;
+ }
+
+ free(content);
+ close(fd);
+
+ return bc_strdup(fname);
+}
+
+
+void
+bm_atom_destroy(const char *fname)
+{
+ unlink(fname);
+}
diff --git a/src/blogc-make/atom.h b/src/blogc-make/atom.h
new file mode 100644
index 0000000..49ff64a
--- /dev/null
+++ b/src/blogc-make/atom.h
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+
+#ifndef _MAKE_ATOM_H
+#define _MAKE_ATOM_H
+
+#include "../common/error.h"
+#include "settings.h"
+
+char* bm_atom_deploy(bm_settings_t *settings, bc_error_t **err);
+void bm_atom_destroy(const char *fname);
+
+#endif /* _MAKE_ATOM_H */
diff --git a/src/blogc-make/ctx.c b/src/blogc-make/ctx.c
new file mode 100644
index 0000000..8c9cc9a
--- /dev/null
+++ b/src/blogc-make/ctx.c
@@ -0,0 +1,172 @@
+/*
+ * 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 <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <libgen.h>
+#include <time.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include "../common/file.h"
+#include "../common/utils.h"
+#include "atom.h"
+#include "settings.h"
+#include "ctx.h"
+
+
+bm_filectx_t*
+bm_filectx_new(bm_ctx_t *ctx, const char *filename)
+{
+ if (ctx == NULL || filename == NULL)
+ return NULL;
+
+ char *f = filename[0] == '/' ? bc_strdup(filename) :
+ bc_strdup_printf("%s/%s", ctx->root_dir, filename);
+
+ bm_filectx_t *rv = bc_malloc(sizeof(bm_filectx_t));
+ rv->path = f;
+ rv->short_path = bc_strdup(filename);
+
+ struct stat buf;
+
+ if (0 != stat(f, &buf)) {
+ struct timespec ts;
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+ rv->timestamp = ts;
+ rv->readable = false;
+ }
+ else {
+ rv->timestamp = buf.st_mtim;
+ rv->readable = true;
+ }
+
+ return rv;
+}
+
+
+void
+bm_filectx_free(bm_filectx_t *fctx)
+{
+ if (fctx == NULL)
+ return;
+ free(fctx->path);
+ free(fctx->short_path);
+ free(fctx);
+}
+
+
+bm_ctx_t*
+bm_ctx_new(const char *settings_file, bc_error_t **err)
+{
+ if (settings_file == NULL || err == NULL || *err != NULL)
+ return NULL;
+
+ size_t content_len;
+ char *content = bc_file_get_contents(settings_file, true, &content_len,
+ err);
+ if (*err != NULL)
+ return NULL;
+
+ bm_settings_t *settings = bm_settings_parse(content, content_len, err);
+ if (*err != NULL) {
+ free(content);
+ return NULL;
+ }
+ free(content);
+
+ char *atom_template = bm_atom_deploy(settings, err);
+ if (*err != NULL) {
+ return NULL;
+ }
+
+ bm_ctx_t *rv = bc_malloc(sizeof(bm_ctx_t));
+ rv->settings = settings;
+
+ char *real_filename = realpath(settings_file, NULL);
+ rv->settings_fctx = bm_filectx_new(rv, real_filename);
+ rv->root_dir = bc_strdup(dirname(real_filename));
+ free(real_filename);
+
+ const char *output_dir = bc_trie_lookup(settings->settings, "output_dir");
+ rv->output_dir = output_dir[0] == '/' ? bc_strdup(output_dir) :
+ bc_strdup_printf("%s/%s", rv->root_dir, output_dir);
+
+ const char *template_dir = bc_trie_lookup(settings->settings,
+ "template_dir");
+
+ char *main_template = bc_strdup_printf("%s/%s", template_dir,
+ bc_trie_lookup(settings->settings, "main_template"));
+ rv->main_template_fctx = bm_filectx_new(rv, main_template);
+ free(main_template);
+
+ rv->atom_template_fctx = bm_filectx_new(rv, atom_template);
+ free(atom_template);
+
+ const char *content_dir = bc_trie_lookup(settings->settings, "content_dir");
+ const char *post_prefix = bc_trie_lookup(settings->settings, "post_prefix");
+ const char *source_ext = bc_trie_lookup(settings->settings, "source_ext");
+
+ rv->posts_fctx = NULL;
+ if (settings->posts != NULL) {
+ for (size_t i = 0; settings->posts[i] != NULL; i++) {
+ char *f = bc_strdup_printf("%s/%s/%s%s", content_dir, post_prefix,
+ settings->posts[i], source_ext);
+ rv->posts_fctx = bc_slist_append(rv->posts_fctx,
+ bm_filectx_new(rv, f));
+ free(f);
+ }
+ }
+
+ rv->pages_fctx = NULL;
+ if (settings->pages != NULL) {
+ for (size_t i = 0; settings->pages[i] != NULL; i++) {
+ char *f = bc_strdup_printf("%s/%s%s", content_dir,
+ settings->pages[i], source_ext);
+ rv->pages_fctx = bc_slist_append(rv->pages_fctx,
+ bm_filectx_new(rv, f));
+ free(f);
+ }
+ }
+
+ rv->copy_files_fctx = NULL;
+ if (settings->copy_files != NULL) {
+ for (size_t i = 0; settings->copy_files[i] != NULL; i++) {
+ rv->copy_files_fctx = bc_slist_append(rv->copy_files_fctx,
+ bm_filectx_new(rv, settings->copy_files[i]));
+ }
+ }
+
+ return rv;
+}
+
+
+void
+bm_ctx_free(bm_ctx_t *ctx)
+{
+ if (ctx == NULL)
+ return;
+
+ bm_settings_free(ctx->settings);
+
+ free(ctx->root_dir);
+ free(ctx->output_dir);
+
+ bm_atom_destroy(ctx->atom_template_fctx->path);
+
+ bm_filectx_free(ctx->main_template_fctx);
+ bm_filectx_free(ctx->atom_template_fctx);
+ bm_filectx_free(ctx->settings_fctx);
+
+ bc_slist_free_full(ctx->posts_fctx, (bc_free_func_t) bm_filectx_free);
+ bc_slist_free_full(ctx->pages_fctx, (bc_free_func_t) bm_filectx_free);
+ bc_slist_free_full(ctx->copy_files_fctx, (bc_free_func_t) bm_filectx_free);
+
+ free(ctx);
+}
diff --git a/src/blogc-make/ctx.h b/src/blogc-make/ctx.h
new file mode 100644
index 0000000..3e18048
--- /dev/null
+++ b/src/blogc-make/ctx.h
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+#ifndef _MAKE_CTX_H
+#define _MAKE_CTX_H
+
+#include <stdbool.h>
+#include <time.h>
+#include "settings.h"
+
+typedef struct {
+ char *path;
+ char *short_path;
+ struct timespec timestamp;
+ bool readable;
+} bm_filectx_t;
+
+typedef struct {
+ bm_settings_t *settings;
+
+ char *root_dir;
+ char *output_dir;
+
+ bm_filectx_t *main_template_fctx;
+ bm_filectx_t *atom_template_fctx;
+ bm_filectx_t *settings_fctx;
+
+ bc_slist_t *posts_fctx;
+ bc_slist_t *pages_fctx;
+ bc_slist_t *copy_files_fctx;
+} bm_ctx_t;
+
+bm_filectx_t* bm_filectx_new(bm_ctx_t *ctx, const char *filename);
+void bm_filectx_free(bm_filectx_t *fctx);
+bm_ctx_t* bm_ctx_new(const char *filename, bc_error_t **err);
+void bm_ctx_free(bm_ctx_t *ctx);
+
+#endif /* _MAKE_CTX_H */
diff --git a/src/blogc-make/exec-native.c b/src/blogc-make/exec-native.c
new file mode 100644
index 0000000..32874b4
--- /dev/null
+++ b/src/blogc-make/exec-native.c
@@ -0,0 +1,161 @@
+/*
+ * 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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <libgen.h>
+#include <errno.h>
+#include "../common/file.h"
+#include "../common/utils.h"
+#include "exec-native.h"
+#include "ctx.h"
+
+
+int
+bm_exec_native_cp(bm_filectx_t *source, bm_filectx_t *dest, bool verbose)
+{
+ if (verbose)
+ printf("Copying '%s' to '%s'\n", source->path, dest->path);
+ else
+ printf(" COPY %s\n", dest->short_path);
+ fflush(stdout);
+
+ char *fname = bc_strdup(dest->path);
+ for (char *tmp = fname; *tmp != '\0'; tmp++) {
+ if (*tmp != '/' && *tmp != '\\')
+ continue;
+ char bkp = *tmp;
+ *tmp = '\0';
+ if ((strlen(fname) > 0) &&
+ (-1 == mkdir(fname, 0777)) &&
+ (errno != EEXIST))
+ {
+ fprintf(stderr, "blogc-make: error: failed to create output "
+ "directory (%s): %s\n", fname, strerror(errno));
+ free(fname);
+ exit(2);
+ }
+ *tmp = bkp;
+ }
+ free(fname);
+
+ int fd_from = open(source->path, O_RDONLY);
+ if (fd_from < 0) {
+ fprintf(stderr, "blogc-make: error: failed to open source file to copy "
+ " (%s): %s\n", source->path, strerror(errno));
+ return 3;
+ }
+
+ int fd_to = open(dest->path, O_WRONLY | O_CREAT, 0666);
+ if (fd_to < 0) {
+ fprintf(stderr, "blogc-make: error: failed to open destination file to "
+ "copy (%s): %s\n", dest->path, strerror(errno));
+ close(fd_from);
+ return 3;
+ }
+
+ ssize_t nread;
+ char buffer[BC_FILE_CHUNK_SIZE];
+ while (0 < (nread = read(fd_from, buffer, BC_FILE_CHUNK_SIZE))) {
+ char *out_ptr = buffer;
+ do {
+ ssize_t nwritten = write(fd_to, out_ptr, nread);
+ if (nwritten == -1) {
+ fprintf(stderr, "blogc-make: error: failed to write to "
+ "destination file (%s): %s\n", dest->path, strerror(errno));
+ close(fd_from);
+ close(fd_to);
+ return 3;
+ }
+ nread -= nwritten;
+ out_ptr += nwritten;
+ } while (nread > 0);
+ }
+
+ return 0;
+}
+
+
+int
+bm_exec_native_rm(bm_filectx_t *dest, bool verbose)
+{
+ if (verbose)
+ printf("Removing file '%s'\n", dest->path);
+ else
+ printf(" CLEAN %s\n", dest->short_path);
+ fflush(stdout);
+
+ if (0 != unlink(dest->path)) {
+ fprintf(stderr, "blogc-make: error: failed to remove file (%s): %s\n",
+ dest->path, strerror(errno));
+ return 3;
+ }
+
+ int rv = 0;
+
+ char *short_path = bc_strdup(dest->short_path);
+ char *path = bc_strdup(dest->path);
+
+ char *dir_short = dirname(short_path);
+ char *dir = dirname(path);
+
+ while (0 != strcmp(dir_short, ".")) {
+
+ DIR *d = opendir(dir);
+ if (d == NULL) {
+ fprintf(stderr, "error: failed to open directory (%s): %s\n",
+ dir, strerror(errno));
+ rv = 3;
+ break;
+ }
+
+ struct dirent *e;
+ size_t count = 0;
+ while (NULL != (e = readdir(d))) {
+ if ((0 == strcmp(e->d_name, ".")) || (0 == strcmp(e->d_name, "..")))
+ continue;
+ count++;
+ break;
+ }
+
+ if (0 != closedir(d)) {
+ fprintf(stderr, "error: failed to close directory (%s): %s\n",
+ dir, strerror(errno));
+ rv = 3;
+ break;
+ }
+
+ if (count == 0) {
+ if (verbose) {
+ printf("Removing directory '%s'\n", dir);
+ fflush(stdout);
+ }
+ if (0 != rmdir(dir)) {
+ fprintf(stderr, "error: failed to remove directory(%s): %s\n",
+ dir, strerror(errno));
+ rv = 3;
+ break;
+ }
+ }
+
+ dir_short = dirname(dir_short);
+ dir = dirname(dir);
+ }
+
+ free(short_path);
+ free(path);
+
+ return rv;
+}
diff --git a/src/blogc-make/exec-native.h b/src/blogc-make/exec-native.h
new file mode 100644
index 0000000..a83b510
--- /dev/null
+++ b/src/blogc-make/exec-native.h
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+
+#ifndef _MAKE_EXEC_NATIVE_H
+#define _MAKE_EXEC_NATIVE_H
+
+#include <stdbool.h>
+#include "ctx.h"
+
+int bm_exec_native_cp(bm_filectx_t *source, bm_filectx_t *dest, bool verbose);
+int bm_exec_native_rm(bm_filectx_t *dest, bool verbose);
+
+#endif /* _MAKE_EXEC_NATIVE_H */
diff --git a/src/blogc-make/exec.c b/src/blogc-make/exec.c
new file mode 100644
index 0000000..75b7c00
--- /dev/null
+++ b/src/blogc-make/exec.c
@@ -0,0 +1,297 @@
+/*
+ * blogc: A blog compiler.
+ * Copyright (C) 2015-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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <libgen.h>
+#include "../common/error.h"
+#include "../common/file.h"
+#include "../common/utils.h"
+#include "ctx.h"
+#include "exec.h"
+#include "settings.h"
+
+
+int
+bm_exec_command(const char *cmd, const char *input, char **output,
+ char **error, bc_error_t **err)
+{
+ if (err == NULL || *err != NULL)
+ return 3;
+
+ int fd_in[2];
+ if (-1 == pipe(fd_in)) {
+ *err = bc_error_new_printf(BLOGC_MAKE_ERROR_EXEC,
+ "Failed to create stdin pipe: %s", strerror(errno));
+ return 3;
+ }
+
+ int fd_out[2];
+ if (-1 == pipe(fd_out)) {
+ *err = bc_error_new_printf(BLOGC_MAKE_ERROR_EXEC,
+ "Failed to create stdout pipe: %s", strerror(errno));
+ close(fd_in[0]);
+ close(fd_in[1]);
+ return 3;
+ }
+
+ int fd_err[2];
+ if (-1 == pipe(fd_err)) {
+ *err = bc_error_new_printf(BLOGC_MAKE_ERROR_EXEC,
+ "Failed to create stderr pipe: %s", strerror(errno));
+ close(fd_in[0]);
+ close(fd_in[1]);
+ close(fd_out[0]);
+ close(fd_out[1]);
+ return 3;
+ }
+
+ pid_t pid = fork();
+ if (pid == -1) {
+ *err = bc_error_new_printf(BLOGC_MAKE_ERROR_EXEC,
+ "Failed to fork: %s", strerror(errno));
+ close(fd_in[0]);
+ close(fd_in[1]);
+ close(fd_out[0]);
+ close(fd_out[1]);
+ close(fd_err[0]);
+ close(fd_err[1]);
+ return 3;
+ }
+
+ // child
+ if (pid == 0) {
+ close(fd_in[1]);
+ close(fd_out[0]);
+ close(fd_err[0]);
+
+ dup2(fd_in[0], STDIN_FILENO);
+ dup2(fd_out[1], STDOUT_FILENO);
+ dup2(fd_err[1], STDERR_FILENO);
+
+ char *const argv[] = {
+ "/bin/sh",
+ "-c",
+ (char*) cmd,
+ NULL,
+ };
+
+ execv(argv[0], argv);
+
+ exit(1);
+ }
+
+ // parent
+ close(fd_in[0]);
+ close(fd_out[1]);
+ close(fd_err[1]);
+
+ if (input != NULL) {
+ if (-1 == write(fd_in[1], input, strlen(input))) {
+ *err = bc_error_new_printf(BLOGC_MAKE_ERROR_EXEC,
+ "Failed to write to stdin pipe: %s", strerror(errno));
+ close(fd_in[1]);
+ close(fd_out[0]);
+ close(fd_err[0]);
+ return 3;
+ }
+ }
+
+ close(fd_in[1]);
+
+ char buffer[BC_FILE_CHUNK_SIZE];
+ ssize_t s;
+
+ bc_string_t *out = NULL;
+ while(0 != (s = read(fd_out[0], buffer, BC_FILE_CHUNK_SIZE))) {
+ if (s == -1) {
+ *err = bc_error_new_printf(BLOGC_MAKE_ERROR_EXEC,
+ "Failed to read from stdout pipe: %s", strerror(errno));
+ close(fd_out[0]);
+ close(fd_err[0]);
+ bc_string_free(out, true);
+ return 3;
+ }
+ if (out == NULL) {
+ out = bc_string_new();
+ }
+ bc_string_append_len(out, buffer, s);
+ }
+ if (out != NULL) {
+ *output = bc_string_free(out, false);
+ }
+ close(fd_out[0]);
+
+ out = NULL;
+ while(0 != (s = read(fd_err[0], buffer, BC_FILE_CHUNK_SIZE))) {
+ if (s == -1) {
+ *err = bc_error_new_printf(BLOGC_MAKE_ERROR_EXEC,
+ "Failed to read from stderr pipe: %s", strerror(errno));
+ close(fd_err[0]);
+ bc_string_free(out, true);
+ return 3;
+ }
+ if (out == NULL)
+ out = bc_string_new();
+ bc_string_append_len(out, buffer, s);
+ }
+ if (out != NULL) {
+ *error = bc_string_free(out, false);
+ }
+ close(fd_err[0]);
+
+ int status;
+ waitpid(pid, &status, 0);
+
+ return WEXITSTATUS(status);
+}
+
+
+static void
+list_variables(const char *key, const char *value, bc_string_t *str)
+{
+ char *tmp = bc_shell_quote(value);
+ bc_string_append_printf(str, " -D %s=%s", key, tmp);
+ free(tmp);
+}
+
+
+char*
+bm_exec_build_blogc_cmd(bm_settings_t *settings, bc_trie_t *variables,
+ bool listing, const char *template, const char *output, bool sources_stdin)
+{
+ bc_string_t *rv = bc_string_new();
+
+ const char *locale = NULL;
+ if (settings != NULL) {
+ locale = bc_trie_lookup(settings->settings, "locale");
+ }
+ if (locale != NULL) {
+ char *tmp = bc_shell_quote(locale);
+ bc_string_append_printf(rv, "LC_ALL=%s ", tmp);
+ free(tmp);
+ }
+
+ bc_string_append(rv, "blogc");
+
+ if (settings != NULL) {
+ bc_trie_foreach(settings->env,
+ (bc_trie_foreach_func_t) list_variables, rv);
+ }
+
+ bc_trie_foreach(variables, (bc_trie_foreach_func_t) list_variables, rv);
+
+ if (listing) {
+ bc_string_append(rv, " -l");
+ }
+
+ if (template != NULL) {
+ char *tmp = bc_shell_quote(template);
+ bc_string_append_printf(rv, " -t %s", tmp);
+ free(tmp);
+ }
+
+ if (output != NULL) {
+ char *tmp = bc_shell_quote(output);
+ bc_string_append_printf(rv, " -o %s", tmp);
+ free(tmp);
+ }
+
+ if (sources_stdin) {
+ bc_string_append(rv, " -i");
+ }
+
+ return bc_string_free(rv, false);
+}
+
+
+int
+bm_exec_blogc(bm_settings_t *settings, bc_trie_t *variables, bool listing,
+ bm_filectx_t *template, bm_filectx_t *output, bc_slist_t *sources,
+ bool verbose, bool only_first_source)
+{
+ bc_string_t *input = bc_string_new();
+ for (bc_slist_t *l = sources; l != NULL; l = l->next) {
+ bc_string_append_printf(input, "%s\n", ((bm_filectx_t*) l->data)->path);
+ if (only_first_source)
+ break;
+ }
+
+ char *cmd = bm_exec_build_blogc_cmd(settings, variables, listing,
+ template->path, output->path, input->len > 0);
+
+ if (verbose)
+ printf("%s\n", cmd);
+ else
+ printf(" BLOGC %s\n", output->short_path);
+ fflush(stdout);
+
+ char *out = NULL;
+ char *err = NULL;
+ bc_error_t *error = NULL;
+
+ int rv = bm_exec_command(cmd, input->str, &out, &err, &error);
+
+ if (error != NULL) {
+ bc_error_print(error, "blogc-make");
+ free(cmd);
+ free(out);
+ free(err);
+ bc_string_free(input, true);
+ bc_error_free(error);
+ return 3;
+ }
+
+ if (rv != 0) {
+ if (verbose) {
+ fprintf(stderr,
+ "error: Failed to execute command.\n"
+ "\n"
+ "STATUS CODE: %d\n", rv);
+ if (input->len > 0) {
+ fprintf(stderr, "\nSTDIN:\n"
+ "----------------------------->8-----------------------------\n"
+ "%s\n"
+ "----------------------------->8-----------------------------\n",
+ bc_str_strip(input->str));
+ }
+ if (out != NULL) {
+ fprintf(stderr, "\nSTDOUT:\n"
+ "----------------------------->8-----------------------------\n"
+ "%s\n"
+ "----------------------------->8-----------------------------\n",
+ bc_str_strip(out));
+ }
+ if (err != NULL) {
+ fprintf(stderr, "\nSTDERR:\n"
+ "----------------------------->8-----------------------------\n"
+ "%s\n"
+ "----------------------------->8-----------------------------\n",
+ bc_str_strip(err));
+ }
+ }
+ else {
+ fprintf(stderr,
+ "error: Failed to execute command, returned status code: %d\n",
+ rv);
+ }
+ }
+
+ bc_string_free(input, true);
+ free(cmd);
+ free(out);
+ free(err);
+
+ return rv;
+}
diff --git a/src/blogc-make/exec.h b/src/blogc-make/exec.h
new file mode 100644
index 0000000..907109a
--- /dev/null
+++ b/src/blogc-make/exec.h
@@ -0,0 +1,24 @@
+/*
+ * blogc: A blog compiler.
+ * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>
+ *
+ * This program can be distributed under the terms of the BSD License.
+ * See the file LICENSE.
+ */
+
+#ifndef _MAKE_EXEC_H
+#define _MAKE_EXEC_H
+
+#include "../common/error.h"
+#include "../common/utils.h"
+#include "settings.h"
+
+int bm_exec_command(const char *cmd, const char *input, char **output,
+ char **error, bc_error_t **err);
+char* bm_exec_build_blogc_cmd(bm_settings_t *settings, bc_trie_t *variables,
+ bool listing, const char *template, const char *output, bool sources_stdin);
+int bm_exec_blogc(bm_settings_t *settings, bc_trie_t *variables, bool listing,
+ bm_filectx_t *template, bm_filectx_t *output, bc_slist_t *sources,
+ bool verbose, bool only_first_source);
+
+#endif /* _MAKE_EXEC_H */
diff --git a/src/blogc-make/main.c b/src/blogc-make/main.c
new file mode 100644
index 0000000..bc8c926
--- /dev/null
+++ b/src/blogc-make/main.c
@@ -0,0 +1,125 @@
+/*
+ * blogc: A blog compiler.
+ * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>
+ *
+ * This program can be distributed under the terms of the BSD License.
+ * See the file LICENSE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <locale.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "../common/error.h"
+#include "../common/utils.h"
+#include "ctx.h"
+#include "rules.h"
+
+
+static void
+print_help(void)
+{
+ printf(
+ "usage:\n"
+ " blogc-make [-h] [-v] [-V] [-f FILE] [RULE ...]\n"
+ " - A simple build tool for blogc.\n"
+ "\n"
+ "positional arguments:\n"
+ " RULE build rule(s) to run (default: all)\n"
+ "\n"
+ "optional arguments:\n"
+ " -h show this help message and exit\n"
+ " -v show version and exit\n"
+ " -V be verbose when executing commands\n"
+ " -f FILE settings file (default: settings.ini)\n");
+ bm_rule_print_help();
+}
+
+
+static void
+print_usage(void)
+{
+ printf("usage: blogc-make [-h] [-v] [-V] [-f FILE] [RULE ...]\n");
+}
+
+
+int
+#ifdef MAKE_EMBEDDED
+bm_main(int argc, char **argv)
+#else
+main(int argc, char **argv)
+#endif
+{
+ setlocale(LC_ALL, "");
+
+ int rv = 0;
+ bc_error_t *err = NULL;
+
+ bc_slist_t *rules = NULL;
+ bool verbose = false;
+ char *settings_file = NULL;
+ bm_ctx_t *ctx = NULL;
+
+ for (unsigned int i = 1; i < argc; i++) {
+ if (argv[i][0] == '-') {
+ switch (argv[i][1]) {
+ case 'h':
+ print_help();
+ goto cleanup;
+ case 'v':
+ printf("%s\n", PACKAGE_STRING);
+ goto cleanup;
+ case 'V':
+ verbose = true;
+ break;
+ case 'f':
+ if (argv[i][2] != '\0')
+ settings_file = bc_strdup(argv[i] + 2);
+ else if (i + 1 < argc)
+ settings_file = bc_strdup(argv[++i]);
+ break;
+#ifdef MAKE_EMBEDDED
+ case 'm':
+ // no-op, for embedding into blogc binary.
+ break;
+#endif
+ default:
+ print_usage();
+ fprintf(stderr, "blogc-make: error: invalid argument: "
+ "-%c\n", argv[i][1]);
+ rv = 3;
+ goto cleanup;
+ }
+ }
+ else {
+ rules = bc_slist_append(rules, bc_strdup(argv[i]));
+ }
+ }
+
+ if (rules == NULL) {
+ rules = bc_slist_append(rules, bc_strdup("all"));
+ }
+
+ ctx = bm_ctx_new(settings_file ? settings_file : "settings.ini", &err);
+ if (err != NULL) {
+ bc_error_print(err, "blogc-make");
+ rv = 3;
+ goto cleanup;
+ }
+
+ rv = bm_rule_executor(ctx, rules, verbose);
+
+cleanup:
+
+ bc_slist_free_full(rules, free);
+ free(settings_file);
+ bm_ctx_free(ctx);
+ bc_error_free(err);
+
+ return rv;
+}
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);
+ }
+}
diff --git a/src/blogc-make/rules.h b/src/blogc-make/rules.h
new file mode 100644
index 0000000..b39e7be
--- /dev/null
+++ b/src/blogc-make/rules.h
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+#ifndef _MAKE_RULES_H
+#define _MAKE_RULES_H
+
+#include <stdbool.h>
+#include "ctx.h"
+#include "../common/utils.h"
+
+typedef bc_slist_t* (*bm_rule_outputlist_func_t) (bm_ctx_t *ctx);
+typedef int (*bm_rule_exec_func_t) (bm_ctx_t *ctx, bc_slist_t *outputs,
+ bool verbose);
+
+typedef struct {
+ const char *name;
+ const char *help;
+ bm_rule_outputlist_func_t outputlist_func;
+ bm_rule_exec_func_t exec_func;
+ bool generate_files;
+} bm_rule_t;
+
+int bm_rule_executor(bm_ctx_t *ctx, bc_slist_t *rule_list, bool verbose);
+int bm_rule_execute(bm_ctx_t *ctx, const bm_rule_t *rule, bool verbose);
+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);
+bc_slist_t* bm_rule_list_built_files(bm_ctx_t *ctx);
+void bm_rule_print_help(void);
+
+#endif /* _MAKE_RULES_H */
diff --git a/src/blogc-make/settings.c b/src/blogc-make/settings.c
new file mode 100644
index 0000000..e2789dc
--- /dev/null
+++ b/src/blogc-make/settings.c
@@ -0,0 +1,175 @@
+/*
+ * 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 <libgen.h>
+#include <stdlib.h>
+#include "../common/config-parser.h"
+#include "../common/error.h"
+#include "../common/file.h"
+#include "../common/utils.h"
+#include "settings.h"
+
+
+static const struct default_settings_map {
+ const char *key;
+ const char *default_value;
+} default_settings[] = {
+
+ // source
+ {"content_dir", "content"},
+ {"template_dir", "templates"},
+ {"main_template", "main.tmpl"},
+ {"source_ext", ".txt"},
+
+ // output
+ {"output_dir", "_build"},
+
+ // pagination
+ {"pagination_prefix", "page"},
+ {"posts_per_page", "10"},
+ {"atom_posts_per_page", "10"},
+
+ // html
+ {"html_ext", "/index.html"},
+ {"index_prefix", NULL},
+ {"post_prefix", "post"},
+ {"tag_prefix", "tag"},
+
+ // atom
+ {"atom_prefix", "atom"},
+ {"atom_ext", ".xml"},
+
+ // generic
+ {"date_format", "%b %d, %Y, %I:%M %p GMT"},
+ {"locale", NULL},
+
+ {NULL, NULL},
+};
+
+
+static const char* required_environment[] = {
+ "AUTHOR_NAME",
+ "AUTHOR_EMAIL",
+ "SITE_TITLE",
+ "SITE_TAGLINE",
+ "BASE_DOMAIN",
+ NULL,
+};
+
+
+static const char* list_sections[] = {
+ "posts",
+ "pages",
+ "copy_files",
+ "tags",
+ NULL,
+};
+
+
+bm_settings_t*
+bm_settings_parse(const char *content, size_t content_len, bc_error_t **err)
+{
+ if (err == NULL || *err != NULL)
+ return NULL;
+
+ if (content == NULL)
+ return NULL;
+
+ bc_config_t *config = bc_config_parse(content, content_len, list_sections,
+ err);
+ if (config == NULL || (err != NULL && *err != NULL))
+ return NULL;
+
+ bm_settings_t *rv = bc_malloc(sizeof(bm_settings_t));
+ rv->root_dir = NULL;
+ rv->env = bc_trie_new(free);
+ rv->settings = bc_trie_new(free);
+ rv->posts = NULL;
+ rv->pages = NULL;
+ rv->copy_files = NULL;
+ rv->tags = NULL;
+
+ char **env = bc_config_list_keys(config, "environment");
+ if (env != NULL) {
+ for (size_t i = 0; env[i] != NULL; i++) {
+ // FIXME: validate keys
+ bc_trie_insert(rv->env, env[i],
+ bc_strdup(bc_config_get(config, "environment", env[i])));
+ }
+ }
+ bc_strv_free(env);
+
+ for (size_t i = 0; required_environment[i] != NULL; i++) {
+ const char *value = bc_trie_lookup(rv->env, required_environment[i]);
+ if (value == NULL || value[0] == '\0') {
+ *err = bc_error_new_printf(BLOGC_MAKE_ERROR_SETTINGS,
+ "[environment] key required but not found or empty: %s",
+ required_environment[i]);
+ bm_settings_free(rv);
+ rv = NULL;
+ goto cleanup;
+ }
+ }
+
+ for (size_t i = 0; default_settings[i].key != NULL; i++) {
+ const char *value = bc_config_get_with_default(
+ config, "settings", default_settings[i].key,
+ default_settings[i].default_value);
+ if (value != NULL) {
+ bc_trie_insert(rv->settings, default_settings[i].key,
+ bc_strdup(value));
+ }
+ }
+
+ rv->posts = bc_config_get_list(config, "posts");
+ rv->pages = bc_config_get_list(config, "pages");
+ rv->copy_files = bc_config_get_list(config, "copy_files");
+ rv->tags = bc_config_get_list(config, "tags");
+
+cleanup:
+
+ bc_config_free(config);
+
+ return rv;
+}
+
+
+bm_settings_t*
+bm_settings_parse_file(const char *filename, bc_error_t **err)
+{
+ if (err == NULL || *err != NULL)
+ return NULL;
+
+ size_t content_len;
+ char *content = bc_file_get_contents(filename, true, &content_len, err);
+ if (*err != NULL)
+ return NULL;
+
+ bm_settings_t *rv = bm_settings_parse(content, content_len, err);
+ char *real_filename = realpath(filename, NULL);
+ rv->root_dir = bc_strdup(dirname(real_filename));
+ free(real_filename);
+ free(content);
+ return rv;
+}
+
+
+void
+bm_settings_free(bm_settings_t *settings)
+{
+ if (settings == NULL)
+ return;
+ free(settings->root_dir);
+ bc_trie_free(settings->env);
+ bc_trie_free(settings->settings);
+ bc_strv_free(settings->posts);
+ bc_strv_free(settings->pages);
+ bc_strv_free(settings->copy_files);
+ bc_strv_free(settings->tags);
+ free(settings);
+}
diff --git a/src/blogc-make/settings.h b/src/blogc-make/settings.h
new file mode 100644
index 0000000..0ba9545
--- /dev/null
+++ b/src/blogc-make/settings.h
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+#ifndef _MAKE_SETTINGS_H
+#define _MAKE_SETTINGS_H
+
+#include "../common/error.h"
+#include "../common/utils.h"
+
+typedef struct {
+ char *root_dir;
+ bc_trie_t *env;
+ bc_trie_t *settings;
+ char **posts;
+ char **pages;
+ char **copy_files;
+ char **tags;
+} bm_settings_t;
+
+bm_settings_t* bm_settings_parse(const char *content, size_t content_len,
+ bc_error_t **err);
+bm_settings_t* bm_settings_parse_file(const char *filename, bc_error_t **err);
+void bm_settings_free(bm_settings_t *settings);
+
+#endif /* _MAKE_SETTINGS_H */