From 6f975248895d0300c6fd8783b1138bdd4be00bcc Mon Sep 17 00:00:00 2001 From: "Rafael G. Martins" Date: Wed, 27 Apr 2016 03:22:31 +0200 Subject: blogc-git-receiver: import external tool to blogc repository still in the effort to reduce maintenance work, I'm importing blogc-git-receiver tool to the main blogc repository. the tool is build by default, if needed headers are found. that means that it will probably only be built for posix-compliant operating systems. --- .gitignore | 3 +- Makefile.am | 55 ++++- build-aux/travis-build.sh | 3 +- configure.ac | 42 +++- src/blogc-git-receiver.c | 502 ++++++++++++++++++++++++++++++++++++++++++++++ src/blogc.c | 285 ++++++++++++++++++++++++++ src/main.c | 285 -------------------------- 7 files changed, 874 insertions(+), 301 deletions(-) create mode 100644 src/blogc-git-receiver.c create mode 100644 src/blogc.c delete mode 100644 src/main.c diff --git a/.gitignore b/.gitignore index 48f0735..911360f 100644 --- a/.gitignore +++ b/.gitignore @@ -44,8 +44,9 @@ Makefile.in /blogc*.[17] blogc*.html -# blogc +# binaries /blogc +/blogc-git-receiver # tests /tests/check_content_parser diff --git a/Makefile.am b/Makefile.am index 8237508..934e1cc 100644 --- a/Makefile.am +++ b/Makefile.am @@ -6,6 +6,7 @@ AM_DISTCHECK_CONFIGURE_FLAGS = \ --enable-tests \ --enable-ronn \ --disable-valgrind \ + --enable-git-receiver \ $(NULL) @@ -43,6 +44,7 @@ noinst_HEADERS = \ noinst_LTLIBRARIES = \ libblogc.la \ + libblogc_utils.la \ $(NULL) noinst_PROGRAMS = \ @@ -52,6 +54,12 @@ bin_PROGRAMS = \ blogc \ $(NULL) +if BUILD_GIT_RECEIVER +bin_PROGRAMS += \ + blogc-git-receiver \ + $(NULL) +endif + check_PROGRAMS = \ $(NULL) @@ -65,7 +73,6 @@ libblogc_la_SOURCES = \ src/renderer.c \ src/source-parser.c \ src/template-parser.c \ - src/utils.c \ $(NULL) libblogc_la_CFLAGS = \ @@ -78,8 +85,18 @@ libblogc_la_LIBADD = \ $(NULL) +libblogc_utils_la_SOURCES = \ + src/utils.c \ + $(NULL) + +libblogc_utils_la_CFLAGS = \ + $(AM_CFLAGS) \ + -I$(top_srcdir)/src \ + $(NULL) + + blogc_SOURCES = \ - src/main.c \ + src/blogc.c \ $(NULL) blogc_CFLAGS = \ @@ -89,9 +106,26 @@ blogc_CFLAGS = \ blogc_LDADD = \ libblogc.la \ + libblogc_utils.la \ $(NULL) +if BUILD_GIT_RECEIVER +blogc_git_receiver_SOURCES = \ + src/blogc-git-receiver.c \ + $(NULL) + +blogc_git_receiver_CFLAGS = \ + $(AM_CFLAGS) \ + -I$(top_srcdir)/src \ + $(NULL) + +blogc_git_receiver_LDADD = \ + libblogc_utils.la \ + $(NULL) +endif + + ## Build rules: man pages EXTRA_DIST += \ @@ -107,6 +141,14 @@ dist_man_MANS = \ blogc-template.7 \ $(NULL) +if BUILD_GIT_RECEIVER +EXTRA_DIST += \ + $(NULL) + +dist_man_MANS += \ + $(NULL) +endif + MAINTAINERCLEANFILES += \ $(dist_man_MANS) \ $(NULL) @@ -181,6 +223,7 @@ tests_check_error_LDFLAGS = \ tests_check_error_LDADD = \ $(CMOCKA_LIBS) \ libblogc.la \ + libblogc_utils.la \ $(NULL) tests_check_loader_SOURCES = \ @@ -200,6 +243,7 @@ tests_check_loader_LDFLAGS = \ tests_check_loader_LDADD = \ $(CMOCKA_LIBS) \ libblogc.la \ + libblogc_utils.la \ $(NULL) tests_check_content_parser_SOURCES = \ @@ -217,6 +261,7 @@ tests_check_content_parser_LDFLAGS = \ tests_check_content_parser_LDADD = \ $(CMOCKA_LIBS) \ libblogc.la \ + libblogc_utils.la \ $(NULL) tests_check_datetime_parser_SOURCES = \ @@ -234,6 +279,7 @@ tests_check_datetime_parser_LDFLAGS = \ tests_check_datetime_parser_LDADD = \ $(CMOCKA_LIBS) \ libblogc.la \ + libblogc_utils.la \ $(NULL) tests_check_renderer_SOURCES = \ @@ -251,6 +297,7 @@ tests_check_renderer_LDFLAGS = \ tests_check_renderer_LDADD = \ $(CMOCKA_LIBS) \ libblogc.la \ + libblogc_utils.la \ $(NULL) tests_check_source_parser_SOURCES = \ @@ -268,6 +315,7 @@ tests_check_source_parser_LDFLAGS = \ tests_check_source_parser_LDADD = \ $(CMOCKA_LIBS) \ libblogc.la \ + libblogc_utils.la \ $(NULL) tests_check_template_parser_SOURCES = \ @@ -285,6 +333,7 @@ tests_check_template_parser_LDFLAGS = \ tests_check_template_parser_LDADD = \ $(CMOCKA_LIBS) \ libblogc.la \ + libblogc_utils.la \ $(NULL) tests_check_utils_SOURCES = \ @@ -301,7 +350,7 @@ tests_check_utils_LDFLAGS = \ tests_check_utils_LDADD = \ $(CMOCKA_LIBS) \ - libblogc.la \ + libblogc_utils.la \ $(NULL) endif diff --git a/build-aux/travis-build.sh b/build-aux/travis-build.sh index 1fcac10..4187575 100755 --- a/build-aux/travis-build.sh +++ b/build-aux/travis-build.sh @@ -13,9 +13,10 @@ if [[ "x${TARGET}" = xw* ]]; then export CHOST="x86_64-w64-mingw32" [[ "x${TARGET}" = "xw32" ]] && export CHOST="i686-w64-mingw32" MAKE_TARGET="all" + CONFIGURE_ARGS="--disable-git-receiver" else export CFLAGS="-Wall -g" - export CONFIGURE_ARGS="--enable-tests --enable-valgrind" + CONFIGURE_ARGS="--enable-tests --enable-valgrind" fi pushd build > /dev/null diff --git a/configure.ac b/configure.ac index 1e7366e..858db59 100644 --- a/configure.ac +++ b/configure.ac @@ -119,6 +119,24 @@ AS_IF([test "x$have_cmocka" = "xyes"], , [ ]) AM_CONDITIONAL([USE_CMOCKA], [test "x$have_cmocka" = "xyes"]) +GIT_RECEIVER="disabled" +AC_ARG_ENABLE([git-receiver], AS_HELP_STRING([--disable-git-receiver], + [disable blogc-git-receiver build])) +AS_IF([test "x$enable_git_receiver" != "xno"], [ + AC_CHECK_HEADERS([sys/types.h sys/stat.h time.h libgen.h unistd.h errno.h dirent.h], [ + GIT_RECEIVER="enabled" + have_git_receiver=yes + ], [ + have_git_receiver=no + ]) +]) +AS_IF([test "x$have_git_receiver" = "xyes"], , [ + AS_IF([test "x$enable_git_receiver" = "xyes"], [ + AC_MSG_ERROR([blogc-git-receiver requested but required headers not found]) + ]) +]) +AM_CONDITIONAL([BUILD_GIT_RECEIVER], [test "x$have_git_receiver" = "xyes"]) + AC_CHECK_HEADERS([sys/types.h sys/stat.h time.h]) LT_LIB_M @@ -131,19 +149,21 @@ AC_CONFIG_FILES([ AC_OUTPUT AS_ECHO(" - ==== ${PACKAGE_STRING} ==== + ====== ${PACKAGE_STRING} ====== + + prefix: ${prefix} + exec_prefix: ${exec_prefix} + bindir: ${bindir} - prefix: ${prefix} - exec_prefix: ${exec_prefix} - bindir: ${bindir} + compiler: ${CC} + cflags: ${CFLAGS} + ldflags: ${LDFLAGS} - compiler: ${CC} - cflags: ${CFLAGS} - ldflags: ${LDFLAGS} + blogc-git-receiver: ${GIT_RECEIVER} - tests: ${TESTS} + tests: ${TESTS} - ronn: ${RONN} - valgrind: ${VALGRIND} - rpmbuild: ${RPMBUILD} + ronn: ${RONN} + valgrind: ${VALGRIND} + rpmbuild: ${RPMBUILD} ") diff --git a/src/blogc-git-receiver.c b/src/blogc-git-receiver.c new file mode 100644 index 0000000..ffeac61 --- /dev/null +++ b/src/blogc-git-receiver.c @@ -0,0 +1,502 @@ +/* + * blogc: A blog compiler. + * Copyright (C) 2015-2016 Rafael G. Martins + * + * This program can be distributed under the terms of the BSD License. + * See the file LICENSE. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /* HAVE_CONFIG_H */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" + +#ifndef BUFFER_SIZE +#define BUFFER_SIZE 4096 +#endif + + +static unsigned int +cpu_count(void) +{ +#ifdef _SC_NPROCESSORS_ONLN + long num = sysconf(_SC_NPROCESSORS_ONLN); + if (num >= 1) + return (unsigned int) num; +#endif + return 1; +} + + +static void +rmdir_recursive(const char *dir) +{ + struct stat buf; + if (0 != stat(dir, &buf)) { + fprintf(stderr, "warning: failed to remove directory (%s): %s\n", dir, + strerror(errno)); + return; + } + if (!S_ISDIR(buf.st_mode)) { + fprintf(stderr, "error: trying to remove invalid directory: %s\n", dir); + exit(2); + } + DIR *d = opendir(dir); + if (d == NULL) { + fprintf(stderr, "error: failed to open directory: %s\n", + strerror(errno)); + exit(2); + } + struct dirent *e; + while (NULL != (e = readdir(d))) { + if ((0 == strcmp(e->d_name, ".")) || (0 == strcmp(e->d_name, ".."))) + continue; + char *f = sb_strdup_printf("%s/%s", dir, e->d_name); + if (0 != stat(f, &buf)) { + fprintf(stderr, "error: failed to stat directory entry (%s): %s\n", + e->d_name, strerror(errno)); + free(f); + exit(2); + } + if (S_ISDIR(buf.st_mode)) { + rmdir_recursive(f); + } + else if (0 != unlink(f)) { + fprintf(stderr, "error: failed to remove file (%s): %s\n", f, + strerror(errno)); + free(f); + exit(2); + } + free(f); + } + if (0 != closedir(d)) { + fprintf(stderr, "error: failed to close directory: %s\n", + strerror(errno)); + exit(2); + } + if (0 != rmdir(dir)) { + fprintf(stderr, "error: failed to remove directory: %s\n", + strerror(errno)); + exit(2); + } +} + + +static int +git_shell(int argc, char *argv[]) +{ + int rv = 0; + + char *repo = NULL; + char *command_orig = NULL; + char *command_name = NULL; + char command_new[BUFFER_SIZE]; + + bool exec_git = false; + + // validate git command + size_t len = strlen(argv[2]); + if (!((len > 17 && (0 == strncmp(argv[2], "git-receive-pack ", 17))) || + (len > 16 && (0 == strncmp(argv[2], "git-upload-pack ", 16))) || + (len > 19 && (0 == strncmp(argv[2], "git-upload-archive ", 19))))) + { + fprintf(stderr, "error: unsupported git command: %s\n", argv[2]); + rv = 1; + goto cleanup; + } + + // get shell path + char *self = getenv("SHELL"); + if (self == NULL) { + fprintf(stderr, "error: failed to find blogc-git-receiver path\n"); + rv = 1; + goto cleanup; + } + + // get home path + char *home = getenv("HOME"); + if (home == NULL) { + fprintf(stderr, "error: failed to find user home path\n"); + rv = 1; + goto cleanup; + } + + // get git repository + command_orig = sb_strdup(argv[2]); + char *p, *r; + for (p = command_orig; *p != ' ' && *p != '\0'; p++); + if (*p == ' ') + p++; + if (*p == '\'' || *p == '"') + p++; + if (*p == '/') + p++; + for (r = p; *p != '\'' && *p != '"' && *p != '\0'; p++); + if (*p == '\'' || *p == '"') + *p = '\0'; + if (*--p == '/') + *p = '\0'; + + repo = sb_strdup_printf("repos/%s", r); + + // check if repository is sane + if (0 == strlen(repo)) { + fprintf(stderr, "error: invalid repository\n"); + rv = 1; + goto cleanup; + } + + if (0 == strncmp(argv[2], "git-upload-", 11)) // no need to check len here + goto git_exec; + + if (0 != chdir(home)) { + fprintf(stderr, "error: failed to chdir (%s): %s\n", home, + strerror(errno)); + rv = 1; + goto cleanup; + } + + if (0 != access(repo, F_OK)) { + char *git_init_cmd = sb_strdup_printf( + "git init --bare \"%s\" > /dev/null", repo); + if (0 != system(git_init_cmd)) { + fprintf(stderr, "error: failed to create git repository: %s\n", + repo); + rv = 1; + free(git_init_cmd); + goto cleanup; + } + free(git_init_cmd); + } + + if (0 != chdir(repo)) { + fprintf(stderr, "error: failed to chdir (%s/%s): %s\n", home, repo, + strerror(errno)); + rv = 1; + goto cleanup; + } + + if (0 != access("hooks", F_OK)) { + // openwrt git package won't install git templates, then the git + // repositories created with it won't have the hooks/ directory. + if (0 != mkdir("hooks", 0777)) { // mkdir honors umask for us. + fprintf(stderr, "error: failed to create directory (%s/%s/hooks): " + "%s\n", home, repo, strerror(errno)); + rv = 1; + goto cleanup; + } + } + + if (0 != chdir("hooks")) { + fprintf(stderr, "error: failed to chdir (%s/%s/hooks): %s\n", home, + repo, strerror(errno)); + rv = 1; + goto cleanup; + } + + if (0 == access("pre-receive", F_OK)) { + if (0 != unlink("pre-receive")) { + fprintf(stderr, "error: failed to remove old symlink " + "(%s/%s/hooks/pre-receive): %s\n", home, repo, strerror(errno)); + rv = 1; + goto cleanup; + } + } + + if (0 != symlink(self, "pre-receive")) { + fprintf(stderr, "error: failed to create symlink " + "(%s/%s/hooks/pre-receive): %s\n", home, repo, strerror(errno)); + rv = 1; + goto cleanup; + } + + if (0 == access("post-receive", F_OK)) { + if (0 != unlink("post-receive")) { + fprintf(stderr, "error: failed to remove old symlink " + "(%s/%s/hooks/post-receive): %s\n", home, repo, strerror(errno)); + rv = 1; + goto cleanup; + } + } + + if (0 != symlink(self, "post-receive")) { + fprintf(stderr, "error: failed to create symlink " + "(%s/%s/hooks/post-receive): %s\n", home, repo, strerror(errno)); + rv = 1; + goto cleanup; + } + + if (0 != chdir(home)) { + fprintf(stderr, "error: failed to chdir (%s): %s\n", home, + strerror(errno)); + rv = 1; + goto cleanup; + } + +git_exec: + command_name = sb_strdup(argv[2]); + for (p = command_name; *p != ' ' && *p != '\0'; p++); + if (*p == ' ') + *p = '\0'; + + if (BUFFER_SIZE < (strlen(command_name) + strlen(repo) + 4)) { + fprintf(stderr, "error: git-shell command is too big\n"); + rv = 1; + goto cleanup; + } + + if (snprintf(command_new, BUFFER_SIZE, "%s '%s'", command_name, repo)) + exec_git = true; + +cleanup: + free(repo); + free(command_orig); + free(command_name); + + if (exec_git) { + execlp("git-shell", "git-shell", "-c", command_new, NULL); + + // execlp only returns on error, then something bad happened + fprintf(stderr, "error: failed to execute git-shell\n"); + rv = 1; + } + + return rv; +} + + +static int +git_post_receive_hook(int argc, char *argv[]) +{ + if (0 != system("git remote get-url --push mirror &> /dev/null")) + return 0; + + // at this point we know that we have a remote called mirror, we can just + // push to it. + if (0 != system("git push --mirror mirror")) + fprintf(stderr, "warning: failed push to git mirror\n"); + + return 0; +} + + +typedef enum { + START_OLD = 1, + OLD, + START_NEW, + NEW, + START_REF, + REF +} input_state_t; + + +static int +git_pre_receive_hook(int argc, char *argv[]) +{ + int c; + char buffer[BUFFER_SIZE]; + + input_state_t state = START_OLD; + size_t i = 0; + size_t start = 0; + + int rv = 0; + char *new = NULL; + char *master = NULL; + + while (EOF != (c = getc(stdin))) { + + buffer[i] = (char) c; + + switch (state) { + case START_OLD: + start = i; + state = OLD; + break; + case OLD: + if (c != ' ') + break; + // no need to store old + state = START_NEW; + break; + case START_NEW: + start = i; + state = NEW; + break; + case NEW: + if (c != ' ') + break; + state = START_REF; + new = strndup(buffer + start, i - start); + break; + case START_REF: + start = i; + state = REF; + break; + case REF: + if (c != '\n') + break; + state = START_OLD; + // we just care about a ref (refs/heads/master), everything + // else is disposable :) + if (!((i - start == 17) && + (0 == strncmp("refs/heads/master", buffer + start, 17)))) + { + free(new); + new = NULL; + break; + } + master = new; + break; + } + + if (++i >= BUFFER_SIZE) { + fprintf(stderr, "error: pre-receive hook payload is too big.\n"); + rv = 1; + goto cleanup2; + } + } + + if (master == NULL) { + fprintf(stderr, "warning: no reference to master branch found. " + "nothing to deploy.\n"); + goto cleanup2; + } + + char *repo_dir = NULL; + char *output_dir = NULL; + + if (NULL == getcwd(buffer, BUFFER_SIZE)) { + fprintf(stderr, "error: failed to get repository remote path: %s\n", + strerror(errno)); + rv = 1; + goto cleanup; + } + + repo_dir = sb_strdup(buffer); + + char dir[] = "/tmp/blogc_XXXXXX"; + if (NULL == mkdtemp(dir)) { + rv = 1; + goto cleanup; + } + + char *git_archive_cmd = sb_strdup_printf( + "git archive \"%s\" | tar -x -C \"%s\" -f -", master, dir); + if (0 != system(git_archive_cmd)) { + fprintf(stderr, "error: failed to extract git content to temporary " + "directory: %s\n", dir); + rv = 1; + free(git_archive_cmd); + goto cleanup; + } + free(git_archive_cmd); + + if (0 != chdir(dir)) { + fprintf(stderr, "error: failed to chdir (%s): %s\n", dir, + strerror(errno)); + rv = 1; + goto cleanup; + } + + if ((0 != access("Makefile", F_OK)) && (0 != access("GNUMakefile", F_OK))) { + fprintf(stderr, "warning: no makefile found. skipping ...\n"); + goto cleanup; + } + + char *home = getenv("HOME"); + if (home == NULL) { + fprintf(stderr, "error: failed to find user home path\n"); + rv = 1; + goto cleanup; + } + + unsigned long epoch = time(NULL); + output_dir = sb_strdup_printf("%s/builds/%s-%lu", home, master, epoch); + char *gmake_cmd = sb_strdup_printf( + "gmake -j%d OUTPUT_DIR=\"%s\" BLOGC_GIT_RECEIVER=1", + cpu_count(), output_dir); + fprintf(stdout, "running command: %s\n\n", gmake_cmd); + fflush(stdout); + if (0 != system(gmake_cmd)) { + fprintf(stderr, "error: failed to build website ...\n"); + rmdir_recursive(output_dir); + free(gmake_cmd); + rv = 1; + goto cleanup; + } + free(gmake_cmd); + + if (0 != chdir(repo_dir)) { + fprintf(stderr, "error: failed to chdir (%s): %s\n", repo_dir, + strerror(errno)); + rmdir_recursive(output_dir); + rv = 1; + goto cleanup; + } + + char *htdocs_sym = NULL; + ssize_t htdocs_sym_len = readlink("htdocs", buffer, BUFFER_SIZE); + if (0 < htdocs_sym_len) { + if (0 != unlink("htdocs")) { + fprintf(stderr, "error: failed to remove symlink (%s/htdocs): %s\n", + repo_dir, strerror(errno)); + rmdir_recursive(output_dir); + rv = 1; + goto cleanup; + } + buffer[htdocs_sym_len] = '\0'; + htdocs_sym = buffer; + } + + if (0 != symlink(output_dir, "htdocs")) { + fprintf(stderr, "error: failed to create symlink (%s/htdocs): %s\n", + repo_dir, strerror(errno)); + rmdir_recursive(output_dir); + rv = 1; + goto cleanup; + } + + if (htdocs_sym != NULL) + rmdir_recursive(htdocs_sym); + +cleanup: + free(output_dir); + rmdir_recursive(dir); + free(repo_dir); +cleanup2: + free(new); + return rv; +} + + +int +main(int argc, char *argv[]) +{ + if (argc > 0) { + if (0 == strcmp(basename(argv[0]), "pre-receive")) + return git_pre_receive_hook(argc, argv); + if (0 == strcmp(basename(argv[0]), "post-receive")) + return git_post_receive_hook(argc, argv); + } + + if (argc == 3 && (0 == strcmp(argv[1], "-c"))) + return git_shell(argc, argv); + + fprintf(stderr, "error: this is a special shell, go away!\n"); + return 1; +} diff --git a/src/blogc.c b/src/blogc.c new file mode 100644 index 0000000..2338c9e --- /dev/null +++ b/src/blogc.c @@ -0,0 +1,285 @@ +/* + * blogc: A blog compiler. + * Copyright (C) 2015-2016 Rafael G. Martins + * + * This program can be distributed under the terms of the BSD License. + * See the file LICENSE. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /* HAVE_CONFIG_H */ + +#ifdef HAVE_SYS_STAT_H +#include +#endif /* HAVE_SYS_STAT_H */ + +#ifdef HAVE_SYS_TYPES_H +#include +#endif /* HAVE_SYS_TYPES_H */ + +#include +#include +#include +#include +#include + +#include "source-parser.h" +#include "template-parser.h" +#include "loader.h" +#include "renderer.h" +#include "error.h" +#include "utils.h" + +#ifndef PACKAGE_VERSION +#define PACKAGE_VERSION "Unknown" +#endif + + +static void +blogc_print_help(void) +{ + printf( + "usage:\n" + " blogc [-h] [-v] [-l] [-D KEY=VALUE ...] [-p KEY] [-t TEMPLATE]\n" + " [-o OUTPUT] [SOURCE ...] - A blog compiler.\n" + "\n" + "positional arguments:\n" + " SOURCE source file(s)\n" + "\n" + "optional arguments:\n" + " -h show this help message and exit\n" + " -v show version and exit\n" + " -l build listing page, from multiple source files\n" + " -D KEY=VALUE set global configuration parameter\n" + " -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"); +} + + +static void +blogc_print_usage(void) +{ + printf( + "usage: blogc [-h] [-v] [-l] [-D KEY=VALUE ...] [-p KEY] [-t TEMPLATE]\n" + " [-o OUTPUT] [SOURCE ...]\n"); +} + + +static void +blogc_mkdir_recursive(const char *filename) +{ + char *fname = sb_strdup(filename); + for (char *tmp = fname; *tmp != '\0'; tmp++) { + if (*tmp != '/' && *tmp != '\\') + continue; +#if defined(HAVE_SYS_STAT_H) && defined(HAVE_SYS_TYPES_H) + char bkp = *tmp; + *tmp = '\0'; + if ((strlen(fname) > 0) && +#if defined(WIN32) || defined(_WIN32) + (-1 == mkdir(fname)) && +#else + (-1 == mkdir(fname, 0777)) && +#endif + (errno != EEXIST)) + { + fprintf(stderr, "blogc: error: failed to create output " + "directory (%s): %s\n", fname, strerror(errno)); + free(fname); + exit(2); + } + *tmp = bkp; +#else + // FIXME: show this warning only if actually trying to create a directory. + fprintf(stderr, "blogc: warning: can't create output directories " + "for your platform. please create the directories yourself.\n"); + break; +#endif + } + free(fname); +} + + +int +main(int argc, char **argv) +{ + setlocale(LC_ALL, ""); + + int rv = 0; + + bool listing = false; + char *template = NULL; + char *output = NULL; + char *print = NULL; + char *tmp = NULL; + char **pieces = NULL; + + sb_slist_t *sources = NULL; + sb_trie_t *config = sb_trie_new(free); + sb_trie_insert(config, "BLOGC_VERSION", sb_strdup(PACKAGE_VERSION)); + + for (unsigned int i = 1; i < argc; i++) { + tmp = NULL; + if (argv[i][0] == '-') { + switch (argv[i][1]) { + case 'h': + blogc_print_help(); + goto cleanup; + case 'v': + printf("%s\n", PACKAGE_STRING); + goto cleanup; + case 'l': + listing = true; + break; + case 't': + if (argv[i][2] != '\0') + template = sb_strdup(argv[i] + 2); + else if (i + 1 < argc) + template = sb_strdup(argv[++i]); + break; + case 'o': + if (argv[i][2] != '\0') + output = sb_strdup(argv[i] + 2); + else if (i + 1 < argc) + output = sb_strdup(argv[++i]); + break; + case 'p': + if (argv[i][2] != '\0') + print = sb_strdup(argv[i] + 2); + else if (i + 1 < argc) + print = sb_strdup(argv[++i]); + break; + case 'D': + if (argv[i][2] != '\0') + tmp = argv[i] + 2; + else if (i + 1 < argc) + tmp = argv[++i]; + if (tmp != NULL) { + pieces = sb_str_split(tmp, '=', 2); + if (sb_strv_length(pieces) != 2) { + fprintf(stderr, "blogc: error: invalid value for " + "-D (must have an '='): %s\n", tmp); + sb_strv_free(pieces); + rv = 2; + goto cleanup; + } + for (unsigned int j = 0; pieces[0][j] != '\0'; j++) { + if (!((pieces[0][j] >= 'A' && pieces[0][j] <= 'Z') || + pieces[0][j] == '_')) + { + fprintf(stderr, "blogc: error: invalid value " + "for -D (configuration key must be uppercase " + "with '_'): %s\n", pieces[0]); + sb_strv_free(pieces); + rv = 2; + goto cleanup; + } + } + sb_trie_insert(config, pieces[0], sb_strdup(pieces[1])); + sb_strv_free(pieces); + pieces = NULL; + } + break; + default: + blogc_print_usage(); + fprintf(stderr, "blogc: error: invalid argument: -%c\n", + argv[i][1]); + rv = 2; + goto cleanup; + } + } + else + sources = sb_slist_append(sources, sb_strdup(argv[i])); + } + + if (!listing && sb_slist_length(sources) == 0) { + blogc_print_usage(); + fprintf(stderr, "blogc: error: one source file is required\n"); + rv = 2; + goto cleanup; + } + + if (!listing && sb_slist_length(sources) > 1) { + blogc_print_usage(); + fprintf(stderr, "blogc: error: only one source file should be provided, " + "if running without '-l'\n"); + rv = 2; + goto cleanup; + } + + blogc_error_t *err = NULL; + + sb_slist_t *s = blogc_source_parse_from_files(config, sources, &err); + if (err != NULL) { + blogc_error_print(err); + rv = 2; + goto cleanup2; + } + + sb_slist_t* l = blogc_template_parse_from_file(template, &err); + if (err != NULL) { + blogc_error_print(err); + rv = 2; + goto cleanup3; + } + + if (print != NULL) { + const char *val = sb_trie_lookup(config, print); + if (val == NULL) { + fprintf(stderr, "blogc: error: configuration variable not found: %s\n", + print); + rv = 2; + } + else { + printf("%s\n", val); + } + goto cleanup3; + } + + if (template == NULL) { + blogc_print_usage(); + fprintf(stderr, "blogc: error: argument -t is required when rendering content\n"); + rv = 2; + goto cleanup3; + } + + char *out = blogc_render(l, s, config, listing); + + bool write_to_stdout = (output == NULL || (0 == strcmp(output, "-"))); + + FILE *fp = stdout; + if (!write_to_stdout) { + blogc_mkdir_recursive(output); + fp = fopen(output, "w"); + if (fp == NULL) { + fprintf(stderr, "blogc: error: failed to open output file (%s): %s\n", + output, strerror(errno)); + rv = 2; + goto cleanup4; + } + } + + if (out != NULL) + fprintf(fp, "%s", out); + + if (!write_to_stdout) + fclose(fp); + +cleanup4: + free(out); +cleanup3: + blogc_template_free_stmts(l); +cleanup2: + sb_slist_free_full(s, (sb_free_func_t) sb_trie_free); + blogc_error_free(err); +cleanup: + sb_trie_free(config); + free(template); + free(output); + free(print); + sb_slist_free_full(sources, free); + return rv; +} diff --git a/src/main.c b/src/main.c deleted file mode 100644 index 2338c9e..0000000 --- a/src/main.c +++ /dev/null @@ -1,285 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2015-2016 Rafael G. Martins - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#ifdef HAVE_CONFIG_H -#include -#endif /* HAVE_CONFIG_H */ - -#ifdef HAVE_SYS_STAT_H -#include -#endif /* HAVE_SYS_STAT_H */ - -#ifdef HAVE_SYS_TYPES_H -#include -#endif /* HAVE_SYS_TYPES_H */ - -#include -#include -#include -#include -#include - -#include "source-parser.h" -#include "template-parser.h" -#include "loader.h" -#include "renderer.h" -#include "error.h" -#include "utils.h" - -#ifndef PACKAGE_VERSION -#define PACKAGE_VERSION "Unknown" -#endif - - -static void -blogc_print_help(void) -{ - printf( - "usage:\n" - " blogc [-h] [-v] [-l] [-D KEY=VALUE ...] [-p KEY] [-t TEMPLATE]\n" - " [-o OUTPUT] [SOURCE ...] - A blog compiler.\n" - "\n" - "positional arguments:\n" - " SOURCE source file(s)\n" - "\n" - "optional arguments:\n" - " -h show this help message and exit\n" - " -v show version and exit\n" - " -l build listing page, from multiple source files\n" - " -D KEY=VALUE set global configuration parameter\n" - " -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"); -} - - -static void -blogc_print_usage(void) -{ - printf( - "usage: blogc [-h] [-v] [-l] [-D KEY=VALUE ...] [-p KEY] [-t TEMPLATE]\n" - " [-o OUTPUT] [SOURCE ...]\n"); -} - - -static void -blogc_mkdir_recursive(const char *filename) -{ - char *fname = sb_strdup(filename); - for (char *tmp = fname; *tmp != '\0'; tmp++) { - if (*tmp != '/' && *tmp != '\\') - continue; -#if defined(HAVE_SYS_STAT_H) && defined(HAVE_SYS_TYPES_H) - char bkp = *tmp; - *tmp = '\0'; - if ((strlen(fname) > 0) && -#if defined(WIN32) || defined(_WIN32) - (-1 == mkdir(fname)) && -#else - (-1 == mkdir(fname, 0777)) && -#endif - (errno != EEXIST)) - { - fprintf(stderr, "blogc: error: failed to create output " - "directory (%s): %s\n", fname, strerror(errno)); - free(fname); - exit(2); - } - *tmp = bkp; -#else - // FIXME: show this warning only if actually trying to create a directory. - fprintf(stderr, "blogc: warning: can't create output directories " - "for your platform. please create the directories yourself.\n"); - break; -#endif - } - free(fname); -} - - -int -main(int argc, char **argv) -{ - setlocale(LC_ALL, ""); - - int rv = 0; - - bool listing = false; - char *template = NULL; - char *output = NULL; - char *print = NULL; - char *tmp = NULL; - char **pieces = NULL; - - sb_slist_t *sources = NULL; - sb_trie_t *config = sb_trie_new(free); - sb_trie_insert(config, "BLOGC_VERSION", sb_strdup(PACKAGE_VERSION)); - - for (unsigned int i = 1; i < argc; i++) { - tmp = NULL; - if (argv[i][0] == '-') { - switch (argv[i][1]) { - case 'h': - blogc_print_help(); - goto cleanup; - case 'v': - printf("%s\n", PACKAGE_STRING); - goto cleanup; - case 'l': - listing = true; - break; - case 't': - if (argv[i][2] != '\0') - template = sb_strdup(argv[i] + 2); - else if (i + 1 < argc) - template = sb_strdup(argv[++i]); - break; - case 'o': - if (argv[i][2] != '\0') - output = sb_strdup(argv[i] + 2); - else if (i + 1 < argc) - output = sb_strdup(argv[++i]); - break; - case 'p': - if (argv[i][2] != '\0') - print = sb_strdup(argv[i] + 2); - else if (i + 1 < argc) - print = sb_strdup(argv[++i]); - break; - case 'D': - if (argv[i][2] != '\0') - tmp = argv[i] + 2; - else if (i + 1 < argc) - tmp = argv[++i]; - if (tmp != NULL) { - pieces = sb_str_split(tmp, '=', 2); - if (sb_strv_length(pieces) != 2) { - fprintf(stderr, "blogc: error: invalid value for " - "-D (must have an '='): %s\n", tmp); - sb_strv_free(pieces); - rv = 2; - goto cleanup; - } - for (unsigned int j = 0; pieces[0][j] != '\0'; j++) { - if (!((pieces[0][j] >= 'A' && pieces[0][j] <= 'Z') || - pieces[0][j] == '_')) - { - fprintf(stderr, "blogc: error: invalid value " - "for -D (configuration key must be uppercase " - "with '_'): %s\n", pieces[0]); - sb_strv_free(pieces); - rv = 2; - goto cleanup; - } - } - sb_trie_insert(config, pieces[0], sb_strdup(pieces[1])); - sb_strv_free(pieces); - pieces = NULL; - } - break; - default: - blogc_print_usage(); - fprintf(stderr, "blogc: error: invalid argument: -%c\n", - argv[i][1]); - rv = 2; - goto cleanup; - } - } - else - sources = sb_slist_append(sources, sb_strdup(argv[i])); - } - - if (!listing && sb_slist_length(sources) == 0) { - blogc_print_usage(); - fprintf(stderr, "blogc: error: one source file is required\n"); - rv = 2; - goto cleanup; - } - - if (!listing && sb_slist_length(sources) > 1) { - blogc_print_usage(); - fprintf(stderr, "blogc: error: only one source file should be provided, " - "if running without '-l'\n"); - rv = 2; - goto cleanup; - } - - blogc_error_t *err = NULL; - - sb_slist_t *s = blogc_source_parse_from_files(config, sources, &err); - if (err != NULL) { - blogc_error_print(err); - rv = 2; - goto cleanup2; - } - - sb_slist_t* l = blogc_template_parse_from_file(template, &err); - if (err != NULL) { - blogc_error_print(err); - rv = 2; - goto cleanup3; - } - - if (print != NULL) { - const char *val = sb_trie_lookup(config, print); - if (val == NULL) { - fprintf(stderr, "blogc: error: configuration variable not found: %s\n", - print); - rv = 2; - } - else { - printf("%s\n", val); - } - goto cleanup3; - } - - if (template == NULL) { - blogc_print_usage(); - fprintf(stderr, "blogc: error: argument -t is required when rendering content\n"); - rv = 2; - goto cleanup3; - } - - char *out = blogc_render(l, s, config, listing); - - bool write_to_stdout = (output == NULL || (0 == strcmp(output, "-"))); - - FILE *fp = stdout; - if (!write_to_stdout) { - blogc_mkdir_recursive(output); - fp = fopen(output, "w"); - if (fp == NULL) { - fprintf(stderr, "blogc: error: failed to open output file (%s): %s\n", - output, strerror(errno)); - rv = 2; - goto cleanup4; - } - } - - if (out != NULL) - fprintf(fp, "%s", out); - - if (!write_to_stdout) - fclose(fp); - -cleanup4: - free(out); -cleanup3: - blogc_template_free_stmts(l); -cleanup2: - sb_slist_free_full(s, (sb_free_func_t) sb_trie_free); - blogc_error_free(err); -cleanup: - sb_trie_free(config); - free(template); - free(output); - free(print); - sb_slist_free_full(sources, free); - return rv; -} -- cgit v1.2.3-18-g5258