From 8cac2c3e3a61b64b9a9855dec413239bcec7f9d2 Mon Sep 17 00:00:00 2001 From: "Rafael G. Martins" Date: Tue, 27 Dec 2016 02:29:54 +0100 Subject: make: implemented a build tool for blogc so, this is basically what happens when you don't have anything better to do in the christmas weekend. most of this code was written in the last 2 or 3 days. i'd like to thank the chivas brothers, the weather and my psychological problems for this achievement. on a serious note, this tool still needs a man page, more tests, and the aws lambda function should be adapted to use it instead of (or together with) make/busybox. also, while talking about aws lambda, this tool can be nicely embedded into the blogc binary, to produce a single "small" static binary for usage in lambda ;) --- .gitignore | 2 + .travis.yml | 3 + Makefile.am | 91 ++++ build-aux/travis-build-github-lambda.sh | 3 +- build-aux/travis-build.sh | 9 +- build-aux/travis-deploy.sh | 2 +- configure.ac | 24 + src/blogc-make/atom.c | 93 ++++ src/blogc-make/atom.h | 18 + src/blogc-make/ctx.c | 172 ++++++++ src/blogc-make/ctx.h | 43 ++ src/blogc-make/exec-native.c | 161 +++++++ src/blogc-make/exec-native.h | 18 + src/blogc-make/exec.c | 297 +++++++++++++ src/blogc-make/exec.h | 24 + src/blogc-make/main.c | 125 ++++++ src/blogc-make/rules.c | 752 ++++++++++++++++++++++++++++++++ src/blogc-make/rules.h | 35 ++ src/blogc-make/settings.c | 175 ++++++++ src/blogc-make/settings.h | 30 ++ src/blogc/main.c | 46 +- src/common/error.c | 6 + src/common/error.h | 6 + tests/blogc-make/check_settings.c | 158 +++++++ 24 files changed, 2286 insertions(+), 7 deletions(-) create mode 100644 src/blogc-make/atom.c create mode 100644 src/blogc-make/atom.h create mode 100644 src/blogc-make/ctx.c create mode 100644 src/blogc-make/ctx.h create mode 100644 src/blogc-make/exec-native.c create mode 100644 src/blogc-make/exec-native.h create mode 100644 src/blogc-make/exec.c create mode 100644 src/blogc-make/exec.h create mode 100644 src/blogc-make/main.c create mode 100644 src/blogc-make/rules.c create mode 100644 src/blogc-make/rules.h create mode 100644 src/blogc-make/settings.c create mode 100644 src/blogc-make/settings.h create mode 100644 tests/blogc-make/check_settings.c 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 + * + * This program can be distributed under the terms of the BSD License. + * See the file LICENSE. + */ + +#include +#include +#include +#include +#include "../common/error.h" +#include "../common/utils.h" +#include "settings.h" +#include "atom.h" + +static const char atom_template[] = + "\n" + "\n" + " {{ SITE_TITLE }}{%% ifdef FILTER_TAG %%} - " + "{{ FILTER_TAG }}{%% endif %%}\n" + " {{ BASE_URL }}/%s{%% ifdef FILTER_TAG %%}/{{ FILTER_TAG }}" + "{%% endif %%}%s\n" + " {{ DATE_FIRST_FORMATTED }}\n" + " \n" + " \n" + " \n" + " {{ AUTHOR_NAME }}\n" + " {{ AUTHOR_EMAIL }}\n" + " \n" + " {{ SITE_TAGLINE }}\n" + " {%% block listing %%}\n" + " \n" + " {{ TITLE }}\n" + " {{ BASE_URL }}/%s/{{ FILENAME }}/\n" + " {{ DATE_FORMATTED }}\n" + " {{ DATE_FORMATTED }}\n" + " \n" + " \n" + " {{ AUTHOR_NAME }}\n" + " {{ AUTHOR_EMAIL }}\n" + " \n" + " \n" + " \n" + " {%% endblock %%}\n" + "\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 + * + * 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 + * + * This program can be distributed under the terms of the BSD License. + * See the file LICENSE. + */ + +#include +#include +#include +#include +#include +#include +#include +#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 + * + * 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 +#include +#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 + * + * This program can be distributed under the terms of the BSD License. + * See the file LICENSE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 + * + * 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 +#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 + * + * This program can be distributed under the terms of the BSD License. + * See the file LICENSE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 + * + * 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 + * + * This program can be distributed under the terms of the BSD License. + * See the file LICENSE. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /* HAVE_CONFIG_H */ + +#include +#include +#include +#include +#include +#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 + * + * This program can be distributed under the terms of the BSD License. + * See the file LICENSE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 + * + * 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 +#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 + * + * This program can be distributed under the terms of the BSD License. + * See the file LICENSE. + */ + +#include +#include +#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 + * + * 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 + * + * This program can be distributed under the terms of the BSD License. + * See the file LICENSE. + */ + +#include +#include +#include +#include + +#include +#include + +#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); +} -- cgit v1.2.3-18-g5258