diff options
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | .travis.yml | 3 | ||||
| -rw-r--r-- | Makefile.am | 91 | ||||
| -rwxr-xr-x | build-aux/travis-build-github-lambda.sh | 3 | ||||
| -rwxr-xr-x | build-aux/travis-build.sh | 9 | ||||
| -rwxr-xr-x | build-aux/travis-deploy.sh | 2 | ||||
| -rw-r--r-- | configure.ac | 24 | ||||
| -rw-r--r-- | src/blogc-make/atom.c | 93 | ||||
| -rw-r--r-- | src/blogc-make/atom.h | 18 | ||||
| -rw-r--r-- | src/blogc-make/ctx.c | 172 | ||||
| -rw-r--r-- | src/blogc-make/ctx.h | 43 | ||||
| -rw-r--r-- | src/blogc-make/exec-native.c | 161 | ||||
| -rw-r--r-- | src/blogc-make/exec-native.h | 18 | ||||
| -rw-r--r-- | src/blogc-make/exec.c | 297 | ||||
| -rw-r--r-- | src/blogc-make/exec.h | 24 | ||||
| -rw-r--r-- | src/blogc-make/main.c | 125 | ||||
| -rw-r--r-- | src/blogc-make/rules.c | 752 | ||||
| -rw-r--r-- | src/blogc-make/rules.h | 35 | ||||
| -rw-r--r-- | src/blogc-make/settings.c | 175 | ||||
| -rw-r--r-- | src/blogc-make/settings.h | 30 | ||||
| -rw-r--r-- | src/blogc/main.c | 46 | ||||
| -rw-r--r-- | src/common/error.c | 6 | ||||
| -rw-r--r-- | src/common/error.h | 6 | ||||
| -rw-r--r-- | tests/blogc-make/check_settings.c | 158 | 
24 files changed, 2286 insertions, 7 deletions
| @@ -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); +} | 
