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. --- src/blogc-git-receiver.c | 502 +++++++++++++++++++++++++++++++++++++++++++++++ src/blogc.c | 285 +++++++++++++++++++++++++++ src/main.c | 285 --------------------------- 3 files changed, 787 insertions(+), 285 deletions(-) create mode 100644 src/blogc-git-receiver.c create mode 100644 src/blogc.c delete mode 100644 src/main.c (limited to 'src') 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