aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--.travis.yml3
-rw-r--r--Makefile.am91
-rwxr-xr-xbuild-aux/travis-build-github-lambda.sh3
-rwxr-xr-xbuild-aux/travis-build.sh9
-rwxr-xr-xbuild-aux/travis-deploy.sh2
-rw-r--r--configure.ac24
-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
-rw-r--r--src/blogc/main.c46
-rw-r--r--src/common/error.c6
-rw-r--r--src/common/error.h6
-rw-r--r--tests/blogc-make/check_settings.c158
24 files changed, 2286 insertions, 7 deletions
diff --git a/.gitignore b/.gitignore
index e4b6874..844d416 100644
--- a/.gitignore
+++ b/.gitignore
@@ -50,6 +50,7 @@ blogc*.html
# binaries
/blogc
/blogc-git-receiver
+/blogc-make
/blogc-runserver
# tests
@@ -66,6 +67,7 @@ blogc*.html
/tests/blogc-git-receiver/check_post_receive.sh
/tests/blogc-git-receiver/check_shell_command_parser
/tests/blogc-git-receiver/check_shell.sh
+/tests/blogc-make/check_settings
/tests/blogc-runserver/check_httpd_utils
/tests/blogc-runserver/check_mime
/tests/common/check_config_parser
diff --git a/.travis.yml b/.travis.yml
index be001ee..f34e0d3 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -18,6 +18,7 @@ env:
- TARGET=distcheck
- TARGET=dist-srpm
- TARGET=blogc-github-lambda
+ - TARGET=blogc-make-embedded
matrix:
exclude:
@@ -25,6 +26,8 @@ matrix:
env: TARGET=dist-srpm
- compiler: clang
env: TARGET=blogc-github-lambda
+ - compiler: clang
+ env: TARGET=blogc-make-embedded
install: gem install ronn
diff --git a/Makefile.am b/Makefile.am
index 622d43d..bd69302 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -5,6 +5,7 @@ ACLOCAL_AMFLAGS = -I m4
AM_DISTCHECK_CONFIGURE_FLAGS = \
CFLAGS="-Wall -g -O0" \
--enable-git-receiver \
+ --enable-make \
--enable-ronn \
--enable-runserver \
--enable-tests \
@@ -51,6 +52,12 @@ noinst_HEADERS = \
src/blogc-git-receiver/pre-receive-parser.h \
src/blogc-git-receiver/shell.h \
src/blogc-git-receiver/shell-command-parser.h \
+ src/blogc-make/atom.h \
+ src/blogc-make/ctx.h \
+ src/blogc-make/exec.h \
+ src/blogc-make/exec-native.h \
+ src/blogc-make/rules.h \
+ src/blogc-make/settings.h \
src/blogc-runserver/httpd.h \
src/blogc-runserver/httpd-utils.h \
src/blogc-runserver/mime.h \
@@ -84,6 +91,16 @@ noinst_LTLIBRARIES += \
$(NULL)
endif
+if BUILD_MAKE
+bin_PROGRAMS += \
+ blogc-make \
+ $(NULL)
+
+noinst_LTLIBRARIES += \
+ libblogc_make.la \
+ $(NULL)
+endif
+
if BUILD_RUNSERVER
bin_PROGRAMS += \
blogc-runserver \
@@ -148,6 +165,16 @@ blogc_LDADD = \
libblogc_common.la \
$(NULL)
+if BUILD_MAKE_EMBEDDED
+blogc_SOURCES += \
+ src/blogc-make/main.c \
+ $(NULL)
+
+blogc_LDADD += \
+ libblogc_make.la \
+ $(NULL)
+endif
+
if BUILD_GIT_RECEIVER
blogc_git_receiver_SOURCES = \
@@ -181,6 +208,46 @@ libblogc_git_receiver_la_LIBADD = \
endif
+if BUILD_MAKE
+blogc_make_SOURCES = \
+ src/blogc-make/main.c \
+ $(NULL)
+
+blogc_make_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(PTHREAD_CFLAGS) \
+ $(NULL)
+
+blogc_make_LDADD = \
+ $(PTHREAD_LIBS) \
+ libblogc_make.la \
+ libblogc_common.la \
+ $(NULL)
+endif
+
+if BUILD_MAKE_LIB
+libblogc_make_la_SOURCES = \
+ src/blogc-make/atom.c \
+ src/blogc-make/ctx.c \
+ src/blogc-make/exec.c \
+ src/blogc-make/exec-native.c \
+ src/blogc-make/rules.c \
+ src/blogc-make/settings.c \
+ $(NULL)
+
+libblogc_make_la_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(PTHREAD_CFLAGS) \
+ $(NULL)
+
+libblogc_make_la_LIBADD = \
+ $(LIBM) \
+ $(PTHREAD_LIBS) \
+ libblogc_common.la \
+ $(NULL)
+endif
+
+
if BUILD_RUNSERVER
blogc_runserver_SOURCES = \
src/blogc-runserver/main.c \
@@ -662,6 +729,30 @@ tests_blogc_git_receiver_check_shell_command_parser_LDADD = \
$(NULL)
endif
+if BUILD_MAKE_LIB
+check_PROGRAMS += \
+ tests/blogc-make/check_settings \
+ $(NULL)
+
+tests_blogc_make_check_settings_SOURCES = \
+ tests/blogc-make/check_settings.c \
+ $(NULL)
+
+tests_blogc_make_check_settings_CFLAGS = \
+ $(CMOCKA_CFLAGS) \
+ $(NULL)
+
+tests_blogc_make_check_settings_LDFLAGS = \
+ -no-install \
+ $(NULL)
+
+tests_blogc_make_check_settings_LDADD = \
+ $(CMOCKA_LIBS) \
+ libblogc_make.la \
+ libblogc_common.la \
+ $(NULL)
+endif
+
endif
endif
diff --git a/build-aux/travis-build-github-lambda.sh b/build-aux/travis-build-github-lambda.sh
index 711ed79..6a46c05 100755
--- a/build-aux/travis-build-github-lambda.sh
+++ b/build-aux/travis-build-github-lambda.sh
@@ -24,7 +24,8 @@ pushd build > /dev/null
--disable-tests \
--disable-valgrind \
--disable-git-receiver \
- --disable-runserver
+ --disable-runserver \
+ --enable-make-embedded
popd > /dev/null
make -C build LDFLAGS="-all-static" blogc
diff --git a/build-aux/travis-build.sh b/build-aux/travis-build.sh
index 70b2475..8725a0b 100755
--- a/build-aux/travis-build.sh
+++ b/build-aux/travis-build.sh
@@ -7,6 +7,12 @@ if [[ "x${TARGET}" = "xblogc-github-lambda" ]]; then
exit $?
fi
+MAKE_CONFIGURE="--enable-make"
+if [[ "x${TARGET}" = "xblogc-make-embedded" ]]; then
+ MAKE_CONFIGURE="--enable-make-embedded"
+ TARGET="check"
+fi
+
rm -rf build
mkdir -p build
@@ -18,7 +24,8 @@ pushd build > /dev/null
--enable-tests \
--enable-valgrind \
--enable-git-receiver \
- --enable-runserver
+ --enable-runserver \
+ ${MAKE_CONFIGURE}
popd > /dev/null
make -C build "${TARGET}"
diff --git a/build-aux/travis-deploy.sh b/build-aux/travis-deploy.sh
index def5e9b..07248a2 100755
--- a/build-aux/travis-deploy.sh
+++ b/build-aux/travis-deploy.sh
@@ -12,7 +12,7 @@ if [[ "x${TRAVIS_BRANCH}" != "xmaster" ]] && [[ "x${TRAVIS_TAG}" != xv* ]]; then
exit 0
fi
-if [[ "x${CC}" != "xgcc" ]] || [[ "x${TARGET}" = "xvalgrind" ]]; then
+if [[ "x${CC}" != "xgcc" ]] || [[ "x${TARGET}" = "xvalgrind" ]] || [[ "x${TARGET}" = "xblogc-make-embedded"; then
echo "Invalid target for deploy. skipping ..."
exit 0
fi
diff --git a/configure.ac b/configure.ac
index b7abf15..cc08106 100644
--- a/configure.ac
+++ b/configure.ac
@@ -97,6 +97,29 @@ AS_IF([test "x$enable_git_receiver" = "xyes"], [
])
AM_CONDITIONAL([BUILD_GIT_RECEIVER], [test "x$have_git_receiver" = "xyes"])
+MAKE_="disabled"
+AC_ARG_ENABLE([make-embedded], AS_HELP_STRING([--enable-make-embedded],
+ [build blogc-make tool embedded on blogc binary]))
+AC_ARG_ENABLE([make], AS_HELP_STRING([--enable-make],
+ [build blogc-make tool]))
+AS_IF([test "x$enable_make" = "xyes" -o "x$enable_make_embedded" = "xyes"], [
+ AX_PTHREAD([], [
+ AC_MSG_ERROR([blogc-make tool requested but pthread is not supported])
+ ])
+ have_make_lib=yes
+ AS_IF([test "x$enable_make_embedded" = "xyes"], [
+ MAKE_="enabled (embedded)"
+ have_make_embedded=yes
+ AC_DEFINE([MAKE_EMBEDDED], [], [Build blogc-make embedded to blogc binary])
+ ], [
+ MAKE_="enabled"
+ have_make=yes
+ ])
+])
+AM_CONDITIONAL([BUILD_MAKE], [test "x$have_make" = "xyes"])
+AM_CONDITIONAL([BUILD_MAKE_LIB], [test "x$have_make_lib" = "xyes"])
+AM_CONDITIONAL([BUILD_MAKE_EMBEDDED], [test "x$have_make_embedded" = "xyes"])
+
RUNSERVER="disabled"
AC_ARG_ENABLE([runserver], AS_HELP_STRING([--enable-runserver],
[build blogc-runserver tool]))
@@ -219,6 +242,7 @@ AS_ECHO("
ldflags: ${LDFLAGS}
blogc-git-receiver: ${GIT_RECEIVER}
+ blogc-make: ${MAKE_}
blogc-runserver: ${RUNSERVER}
tests: ${TESTS}
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 */
diff --git a/src/blogc/main.c b/src/blogc/main.c
index 8a22ae2..1bdf2fe 100644
--- a/src/blogc/main.c
+++ b/src/blogc/main.c
@@ -30,6 +30,10 @@
#include "../common/utf8.h"
#include "../common/utils.h"
+#ifdef MAKE_EMBEDDED
+extern int bm_main(int argc, char **argv);
+#endif
+
#ifndef PACKAGE_VERSION
#define PACKAGE_VERSION "Unknown"
#endif
@@ -40,7 +44,11 @@ blogc_print_help(void)
{
printf(
"usage:\n"
- " blogc [-h] [-v] [-d] [-i] [-l] [-D KEY=VALUE ...] [-p KEY] [-t TEMPLATE]\n"
+ " blogc "
+#ifdef MAKE_EMBEDDED
+ "[-m] "
+#endif
+ "[-h] [-v] [-d] [-i] [-l] [-D KEY=VALUE ...] [-p KEY] [-t TEMPLATE]\n"
" [-o OUTPUT] [SOURCE ...] - A blog compiler.\n"
"\n"
"positional arguments:\n"
@@ -56,7 +64,11 @@ blogc_print_help(void)
" -p KEY show the value of a global configuration parameter\n"
" after source parsing and exit\n"
" -t TEMPLATE template file\n"
- " -o OUTPUT output file\n");
+ " -o OUTPUT output file\n"
+#ifdef MAKE_EMBEDDED
+ " -m call and pass arguments to embedded blogc-make\n"
+#endif
+ );
}
@@ -64,7 +76,11 @@ static void
blogc_print_usage(void)
{
printf(
- "usage: blogc [-h] [-v] [-d] [-i] [-l] [-D KEY=VALUE ...] [-p KEY] [-t TEMPLATE]\n"
+ "usage: blogc "
+#ifdef MAKE_EMBEDDED
+ "[-m] "
+#endif
+ "[-h] [-v] [-d] [-i] [-l] [-D KEY=VALUE ...] [-p KEY] [-t TEMPLATE]\n"
" [-o OUTPUT] [SOURCE ...]\n");
}
@@ -129,6 +145,14 @@ blogc_read_stdin_to_list(bc_slist_t *l)
int
main(int argc, char **argv)
{
+#ifdef MAKE_EMBEDDED
+ // this isn't going to work on windows, but -m can still be used there.
+ if (bc_str_ends_with(argv[0], "/blogc-make"))
+ return bm_main(argc, argv);
+
+ bool embedded = false;
+#endif
+
setlocale(LC_ALL, "");
int rv = 0;
@@ -219,6 +243,11 @@ main(int argc, char **argv)
pieces = NULL;
}
break;
+#ifdef MAKE_EMBEDDED
+ case 'm':
+ embedded = true;
+ break;
+#endif
default:
blogc_print_usage();
fprintf(stderr, "blogc: error: invalid argument: -%c\n",
@@ -227,8 +256,17 @@ main(int argc, char **argv)
goto cleanup;
}
}
- else
+ else {
sources = bc_slist_append(sources, bc_strdup(argv[i]));
+ }
+
+#ifdef MAKE_EMBEDDED
+ if (embedded) {
+ rv = bm_main(argc, argv);
+ goto cleanup;
+ }
+#endif
+
}
if (input_stdin)
diff --git a/src/common/error.c b/src/common/error.c
index 031e4a9..cfe9d3b 100644
--- a/src/common/error.c
+++ b/src/common/error.c
@@ -130,6 +130,12 @@ bc_error_print(bc_error_t *err, const char *prefix)
case BLOGC_WARNING_DATETIME_PARSER:
fprintf(stderr, "warning: datetime: %s\n", err->msg);
break;
+ case BLOGC_MAKE_ERROR_SETTINGS:
+ fprintf(stderr, "error: settings: %s\n", err->msg);
+ break;
+ case BLOGC_MAKE_ERROR_EXEC:
+ fprintf(stderr, "error: exec: %s\n", err->msg);
+ break;
default:
fprintf(stderr, "error: %s\n", err->msg);
}
diff --git a/src/common/error.h b/src/common/error.h
index 5ac2b15..c685ee6 100644
--- a/src/common/error.h
+++ b/src/common/error.h
@@ -23,6 +23,12 @@ typedef enum {
BLOGC_ERROR_TEMPLATE_PARSER,
BLOGC_ERROR_LOADER,
BLOGC_WARNING_DATETIME_PARSER,
+
+ // errors for src/blogc-make
+ BLOGC_MAKE_ERROR_SETTINGS = 300,
+ BLOGC_MAKE_ERROR_EXEC,
+ BLOGC_MAKE_ERROR_ATOM,
+
} bc_error_type_t;
typedef struct {
diff --git a/tests/blogc-make/check_settings.c b/tests/blogc-make/check_settings.c
new file mode 100644
index 0000000..0078073
--- /dev/null
+++ b/tests/blogc-make/check_settings.c
@@ -0,0 +1,158 @@
+/*
+ * 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 <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "../../src/blogc-make/settings.h"
+#include "../../src/common/error.h"
+#include "../../src/common/utils.h"
+
+
+static void
+test_settings_empty(void **state)
+{
+ const char *a = "";
+ bc_error_t *err = NULL;
+ bm_settings_t *s = bm_settings_parse(a, strlen(a), &err);
+ assert_non_null(err);
+ assert_null(s);
+ assert_int_equal(err->type, BLOGC_MAKE_ERROR_SETTINGS);
+ assert_string_equal(err->msg,
+ "[environment] key required but not found or empty: AUTHOR_NAME");
+ bc_error_free(err);
+}
+
+
+static void
+test_settings(void **state)
+{
+ const char *a =
+ "[settings]\n"
+ "output_dir = bola\n"
+ "content_dir = guda\n"
+ "main_template = foo.tmpl\n"
+ "\n"
+ "[environment]\n"
+ "BOLA = asd\n"
+ "GUDA = qwe\n";
+ bc_error_t *err = NULL;
+ bm_settings_t *s = bm_settings_parse(a, strlen(a), &err);
+ assert_non_null(err);
+ assert_null(s);
+ assert_int_equal(err->type, BLOGC_MAKE_ERROR_SETTINGS);
+ assert_string_equal(err->msg,
+ "[environment] key required but not found or empty: AUTHOR_NAME");
+ bc_error_free(err);
+}
+
+
+static void
+test_settings2(void **state)
+{
+ const char *a =
+ "[settings]\n"
+ "output_dir = bola\n"
+ "content_dir = guda\n"
+ "main_template = foo.tmpl\n"
+ "\n"
+ "[environment]\n"
+ "BOLA = asd\n"
+ "GUDA = qwe\n"
+ "AUTHOR_NAME = chunda\n"
+ "AUTHOR_EMAIL = chunda@example.com\n"
+ "SITE_TITLE = Fuuuuuuuuu\n"
+ "SITE_TAGLINE = My cool tagline\n"
+ "BASE_DOMAIN = http://example.com\n"
+ "\n"
+ "[posts]\n"
+ "\n"
+ "aaaa\n"
+ "bbbb\n"
+ "cccc\n"
+ "[pages]\n"
+ " dddd\n"
+ "eeee\n"
+ "ffff\n"
+ "[tags]\n"
+ "gggg\n"
+ "\n"
+ " hhhh\n"
+ "iiii\n"
+ "[copy_files]\n"
+ "jjjj\n"
+ "kkkk\n"
+ "llll\n";
+ bc_error_t *err = NULL;
+ bm_settings_t *s = bm_settings_parse(a, strlen(a), &err);
+ assert_null(err);
+ assert_non_null(s);
+ assert_null(s->root_dir);
+ assert_int_equal(bc_trie_size(s->env), 7);
+ assert_string_equal(bc_trie_lookup(s->env, "BOLA"), "asd");
+ assert_string_equal(bc_trie_lookup(s->env, "GUDA"), "qwe");
+ assert_string_equal(bc_trie_lookup(s->env, "AUTHOR_NAME"), "chunda");
+ assert_string_equal(bc_trie_lookup(s->env, "AUTHOR_EMAIL"), "chunda@example.com");
+ assert_string_equal(bc_trie_lookup(s->env, "SITE_TITLE"), "Fuuuuuuuuu");
+ assert_string_equal(bc_trie_lookup(s->env, "SITE_TAGLINE"), "My cool tagline");
+ assert_string_equal(bc_trie_lookup(s->env, "BASE_DOMAIN"), "http://example.com");
+ assert_int_equal(bc_trie_size(s->settings), 14);
+ assert_string_equal(bc_trie_lookup(s->settings, "source_ext"), ".txt");
+ assert_string_equal(bc_trie_lookup(s->settings, "html_ext"), "/index.html");
+ assert_string_equal(bc_trie_lookup(s->settings, "output_dir"), "bola");
+ assert_string_equal(bc_trie_lookup(s->settings, "content_dir"), "guda");
+ assert_string_equal(bc_trie_lookup(s->settings, "template_dir"), "templates");
+ assert_string_equal(bc_trie_lookup(s->settings, "main_template"), "foo.tmpl");
+ assert_string_equal(bc_trie_lookup(s->settings, "date_format"),
+ "%b %d, %Y, %I:%M %p GMT");
+ assert_string_equal(bc_trie_lookup(s->settings, "posts_per_page"), "10");
+ assert_string_equal(bc_trie_lookup(s->settings, "atom_prefix"), "atom");
+ assert_string_equal(bc_trie_lookup(s->settings, "atom_ext"), ".xml");
+ assert_string_equal(bc_trie_lookup(s->settings, "atom_posts_per_page"), "10");
+ assert_string_equal(bc_trie_lookup(s->settings, "pagination_prefix"), "page");
+ assert_string_equal(bc_trie_lookup(s->settings, "post_prefix"), "post");
+ assert_string_equal(bc_trie_lookup(s->settings, "tag_prefix"), "tag");
+ assert_non_null(s->posts);
+ assert_string_equal(s->posts[0], "aaaa");
+ assert_string_equal(s->posts[1], "bbbb");
+ assert_string_equal(s->posts[2], "cccc");
+ assert_null(s->posts[3]);
+ assert_non_null(s->pages);
+ assert_string_equal(s->pages[0], "dddd");
+ assert_string_equal(s->pages[1], "eeee");
+ assert_string_equal(s->pages[2], "ffff");
+ assert_null(s->pages[3]);
+ assert_non_null(s->copy_files);
+ assert_string_equal(s->copy_files[0], "jjjj");
+ assert_string_equal(s->copy_files[1], "kkkk");
+ assert_string_equal(s->copy_files[2], "llll");
+ assert_null(s->copy_files[3]);
+ assert_non_null(s->tags);
+ assert_string_equal(s->tags[0], "gggg");
+ assert_string_equal(s->tags[1], "hhhh");
+ assert_string_equal(s->tags[2], "iiii");
+ assert_null(s->tags[3]);
+ bm_settings_free(s);
+}
+
+
+int
+main(void)
+{
+ const UnitTest tests[] = {
+ unit_test(test_settings_empty),
+ unit_test(test_settings),
+ unit_test(test_settings2),
+ };
+ return run_tests(tests);
+}