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