diff options
Diffstat (limited to 'src')
93 files changed, 2926 insertions, 8586 deletions
diff --git a/src/blogc-git-receiver.c b/src/blogc-git-receiver.c new file mode 100644 index 0000000..820fce5 --- /dev/null +++ b/src/blogc-git-receiver.c @@ -0,0 +1,501 @@ +/* + * 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 <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <libgen.h> +#include <unistd.h> +#include <errno.h> +#include <sys/stat.h> +#include <dirent.h> +#include <time.h> + +#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 config --local remote.mirror.pushurl &> /dev/null")) { + if (0 != system("git config --local remote.mirror.url &> /dev/null")) { + fprintf(stderr, "warning: repository mirroring disabled\n"); + 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-git-receiver/main.c b/src/blogc-git-receiver/main.c deleted file mode 100644 index 13218dd..0000000 --- a/src/blogc-git-receiver/main.c +++ /dev/null @@ -1,44 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <libgen.h> -#include "shell.h" -#include "pre-receive.h" -#include "post-receive.h" - - -int -main(int argc, char *argv[]) -{ - if (argc > 0) { - if (0 == strcmp(basename(argv[0]), "pre-receive")) - return bgr_pre_receive_hook(argc, argv); - if (0 == strcmp(basename(argv[0]), "post-receive")) - return bgr_post_receive_hook(argc, argv); - } - - if (argc == 3 && (0 == strcmp(argv[1], "-c"))) { - return bgr_shell(argc, argv); - } - - // this is a hack to make blogc-git-receiver work out-of-the-box as a - // `command=` in authorized_keys file. it will only work if the command - // path is absolute. - char *ssh_orig = getenv("SSH_ORIGINAL_COMMAND"); - if (argc == 1 && ssh_orig != NULL && argv[0][0] == '/') { - setenv("SHELL", argv[0], 1); - char* _argv[] = {argv[0], "-c", ssh_orig}; - return bgr_shell(3, _argv); - } - - fprintf(stderr, "error: this is a special shell, go away!\n"); - return 1; -} diff --git a/src/blogc-git-receiver/post-receive.c b/src/blogc-git-receiver/post-receive.c deleted file mode 100644 index 17a8aa7..0000000 --- a/src/blogc-git-receiver/post-receive.c +++ /dev/null @@ -1,91 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#include <errno.h> -#include <string.h> -#include <stdio.h> -#include <libgen.h> -#include <unistd.h> -#include <stdlib.h> -#include "../common/utils.h" -#include "../common/config-parser.h" -#include "settings.h" -#include "post-receive.h" - - -int -bgr_post_receive_hook(int argc, char *argv[]) -{ - int rv = 0; - char *mirror = NULL; - - char *hooks_dir = dirname(argv[0]); // this was validated by main() - char *real_hooks_dir = realpath(hooks_dir, NULL); - if (real_hooks_dir == NULL) { - fprintf(stderr, "error: failed to guess repository root: %s\n", - strerror(errno)); - return 1; - } - - char *repo_path = bc_strdup(dirname(real_hooks_dir)); - free(real_hooks_dir); - if (0 != chdir(repo_path)) { - fprintf(stderr, "error: failed to change to repository root\n"); - rv = 1; - goto cleanup; - } - - // local repository settings should take precedence, so if the repo have - // the 'mirror' remote, just push to it. - // this will be removed at some point, but will be kept for compatibility - // with old setups. - if ((0 == system("git config --local remote.mirror.pushurl > /dev/null")) || - (0 == system("git config --local remote.mirror.url > /dev/null"))) - { - mirror = bc_strdup("mirror"); - goto push; - } - - bc_config_t *config = bgr_settings_parse(); - if (config == NULL) { - fprintf(stderr, "warning: repository mirroring disabled\n"); - goto cleanup; - } - - char *section = bgr_settings_get_section(config, repo_path); - if (section == NULL) { - fprintf(stderr, "warning: repository mirroring disabled\n"); - bc_config_free(config); - goto cleanup; - } - - mirror = bc_strdup(bc_config_get(config, section, "mirror")); - free(section); - bc_config_free(config); - - if (mirror == NULL) { - fprintf(stderr, "warning: repository mirroring disabled\n"); - goto cleanup; - } - -push: - - { - char *git_cmd = bc_strdup_printf("git push --mirror %s", mirror); - if (0 != system(git_cmd)) - fprintf(stderr, "warning: failed push to git mirror\n"); - free(git_cmd); - } - - free(mirror); - -cleanup: - free(repo_path); - - return rv; -} diff --git a/src/blogc-git-receiver/post-receive.h b/src/blogc-git-receiver/post-receive.h deleted file mode 100644 index fc56dc3..0000000 --- a/src/blogc-git-receiver/post-receive.h +++ /dev/null @@ -1,14 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#ifndef _POST_RECEIVE_H -#define _POST_RECEIVE_H - -int bgr_post_receive_hook(int argc, char *argv[]); - -#endif /* _POST_RECEIVE_H */ diff --git a/src/blogc-git-receiver/pre-receive-parser.c b/src/blogc-git-receiver/pre-receive-parser.c deleted file mode 100644 index 61a533c..0000000 --- a/src/blogc-git-receiver/pre-receive-parser.c +++ /dev/null @@ -1,84 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2020 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#include <stddef.h> -#include <stdlib.h> -#include <string.h> -#include "../common/utils.h" -#include "pre-receive-parser.h" - -typedef enum { - START_OLD = 1, - OLD, - START_NEW, - NEW, - START_REF, - REF -} input_state_t; - - -bc_trie_t* -bgr_pre_receive_parse(const char *input, size_t input_len) -{ - input_state_t state = START_OLD; - size_t start = 0; - size_t start_new = 0; - - bc_trie_t* rv = bc_trie_new(free); - - for (size_t current = 0; current < input_len; current++) { - - char c = input[current]; - - switch (state) { - case START_OLD: - start = current; - state = OLD; - break; - case OLD: - if (c != ' ') - break; - // no need to store old - state = START_NEW; - break; - case START_NEW: - start = current; - state = NEW; - break; - case NEW: - if (c != ' ') - break; - state = START_REF; - start_new = start; - break; - case START_REF: - start = current; - state = REF; - break; - case REF: - if (c != '\n') - break; - state = START_OLD; - if ((current - start > 11) && - (0 == strncmp("refs/heads/", input + start, 11))) - { - char *key = bc_strndup(input + start + 11, current - start - 11); - bc_trie_insert(rv, key, bc_strndup(input + start_new, start - 1 - start_new)); - free(key); - } - break; - } - } - - if (bc_trie_size(rv) == 0) { - bc_trie_free(rv); - return NULL; - } - - return rv; -} diff --git a/src/blogc-git-receiver/pre-receive-parser.h b/src/blogc-git-receiver/pre-receive-parser.h deleted file mode 100644 index 45a9da8..0000000 --- a/src/blogc-git-receiver/pre-receive-parser.h +++ /dev/null @@ -1,17 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2020 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#ifndef _PRE_RECEIVE_PARSER_H -#define _PRE_RECEIVE_PARSER_H - -#include <stddef.h> -#include "../common/utils.h" - -bc_trie_t* bgr_pre_receive_parse(const char *input, size_t input_len); - -#endif /* _PRE_RECEIVE_PARSER_H */ diff --git a/src/blogc-git-receiver/pre-receive.c b/src/blogc-git-receiver/pre-receive.c deleted file mode 100644 index 12d29cf..0000000 --- a/src/blogc-git-receiver/pre-receive.c +++ /dev/null @@ -1,318 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2020 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/stat.h> -#include <unistd.h> -#include <errno.h> -#include <dirent.h> -#include <time.h> -#include <libgen.h> -#include "../common/compat.h" -#include "../common/utils.h" -#include "../common/stdin.h" -#include "settings.h" -#include "pre-receive-parser.h" -#include "pre-receive.h" - - -static size_t -cpu_count(void) -{ -#ifdef _SC_NPROCESSORS_ONLN - long num = sysconf(_SC_NPROCESSORS_ONLN); - if (num >= 1) - return (size_t) num; -#endif - return 1; -} - - -static void -rmdir_recursive(const char *dir) -{ - if (dir == NULL) - return; - struct stat buf; - if (0 != stat(dir, &buf)) { - 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 = bc_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); - } -} - - -int -bgr_pre_receive_hook(int argc, char *argv[]) -{ - int rv = 0; - char *ref = NULL; - char *output_dir = NULL; - char *tmpdir = NULL; - char *sym = NULL; - - char *hooks_dir = dirname(argv[0]); // this was validated by main() - char *real_hooks_dir = realpath(hooks_dir, NULL); - if (real_hooks_dir == NULL) { - fprintf(stderr, "error: failed to guess repository root: %s\n", - strerror(errno)); - return 1; - } - - char *repo_dir = bc_strdup(dirname(real_hooks_dir)); - free(real_hooks_dir); - if (0 != chdir(repo_dir)) { - fprintf(stderr, "error: failed to change to repository root\n"); - rv = 1; - goto cleanup; - } - - bc_config_t *config = bgr_settings_parse(); - if (config == NULL) { - goto default_sym; - } - - char *section = bgr_settings_get_section(config, repo_dir); - if (section == NULL) { - bc_config_free(config); - goto default_sym; - } - - const char *sym_tmp = bc_config_get(config, section, "symlink"); - if (sym_tmp == NULL) { - free(section); - bc_config_free(config); - goto default_sym; - } - - sym = bc_str_starts_with(sym_tmp, "/") ? bc_strdup(sym_tmp) : - bc_strdup_printf("%s/%s", repo_dir, sym_tmp); - free(section); - bc_config_free(config); - -default_sym: - - if (sym == NULL) { - sym = bc_strdup_printf("%s/htdocs", repo_dir); - } - - if (NULL == getenv("GIT_DIR")) { - if (0 != access(sym, R_OK)) { - fprintf(stderr, "error: no previous build found. nothing to " - "rebuild.\n"); - rv = 1; - goto cleanup; - } - char *build_dir = realpath(sym, NULL); - if (build_dir == NULL) { - fprintf(stderr, "error: failed to get the hash of last built " - "commit: %s\n", strerror(errno)); - rv = 1; - goto cleanup; - } - char **pieces = bc_str_split(basename(build_dir), '-', 2); - free(build_dir); - if (bc_strv_length(pieces) != 2) { - fprintf(stderr, "error: failed to parse the hash of last built " - "commit.\n"); - bc_strv_free(pieces); - rv = 1; - goto cleanup; - } - ref = bc_strdup(pieces[0]); - bc_strv_free(pieces); - } - else { - size_t input_len; - char *input = bc_stdin_read(&input_len); - bc_trie_t *branches = bgr_pre_receive_parse(input, input_len); - - // try 'master' by default to avoid breaking existing setups - ref = bc_strdup(bc_trie_lookup(branches, "master")); - if (ref == NULL) { - // try 'main' - ref = bc_strdup(bc_trie_lookup(branches, "main")); - } - - bc_trie_free(branches); - free(input); - } - - if (ref == NULL) { - fprintf(stderr, "warning: no suitable branch found. " - "nothing to deploy.\n"); - goto cleanup; - } - - char dir[] = "/tmp/blogc_XXXXXX"; - if (NULL == mkdtemp(dir)) { - rv = 1; - goto cleanup; - } - tmpdir = dir; - - char *git_archive_cmd = bc_strdup_printf( - "git archive \"%s\" | tar -x -C \"%s\" -f -", ref, tmpdir); - if (0 != system(git_archive_cmd)) { - fprintf(stderr, "error: failed to extract git content to temporary " - "directory: %s\n", tmpdir); - rv = 1; - free(git_archive_cmd); - goto cleanup; - } - free(git_archive_cmd); - - if (0 != chdir(tmpdir)) { - fprintf(stderr, "error: failed to chdir (%s): %s\n", tmpdir, - strerror(errno)); - rv = 1; - goto cleanup; - } - - char *buildsd = bgr_settings_get_builds_dir(); - if (buildsd == NULL) { - fprintf(stderr, "error: failed to find builds directory path\n"); - rv = 1; - goto cleanup; - } - - unsigned long epoch = time(NULL); - output_dir = bc_strdup_printf("%s/%s-%lu", buildsd, ref, epoch); - free(buildsd); - - if (0 == access(output_dir, F_OK)) { - char *tmp = output_dir; - output_dir = bc_strdup_printf("%s-", tmp); - free(tmp); - } - - // detect if we will run blogc-make, make or nothing, and generate the - // command. - char *build_cmd = NULL; - if (0 == access("blogcfile", F_OK)) { - int status_bmake = system("blogc-make -v 2> /dev/null > /dev/null"); - if (127 == bc_compat_status_code(status_bmake)) { - fprintf(stderr, "error: failed to find blogc-make binary\n"); - rv = 1; - goto cleanup; - } - build_cmd = bc_strdup_printf("OUTPUT_DIR=\"%s\" blogc-make -V all", - output_dir); - } - else if ((0 == access("Makefile", F_OK)) || (0 == access("GNUMakefile", F_OK))) { - const char *make_impl = NULL; - - int status_gmake = system("gmake -f /dev/null 2> /dev/null > /dev/null"); - if (127 != bc_compat_status_code(status_gmake)) { - make_impl = "gmake"; - } - else { - int status_make = system("make -f /dev/null 2> /dev/null > /dev/null"); - if (127 != bc_compat_status_code(status_make)) { - make_impl = "make"; - } - } - - if (make_impl == NULL) { - fprintf(stderr, "error: no 'make' implementation found\n"); - rv = 1; - goto cleanup; - } - build_cmd = bc_strdup_printf( - "%s -j%d OUTPUT_DIR=\"%s\" BLOGC_GIT_RECEIVER=1", make_impl, - cpu_count(), output_dir); - } - else { - fprintf(stderr, "warning: no blogcfile or Makefile found. skipping ...\n"); - goto cleanup; - } - - fprintf(stdout, "running command: %s\n\n", build_cmd); - fflush(stdout); - if (0 != system(build_cmd)) { - fprintf(stderr, "error: failed to build website ...\n"); - rmdir_recursive(output_dir); - free(build_cmd); - rv = 1; - goto cleanup; - } - free(build_cmd); - - char *htdocs_sym = realpath(sym, NULL); - if ((htdocs_sym != NULL) && (0 != unlink(sym))) { - fprintf(stderr, "error: failed to remove symlink (%s): %s\n", sym, - strerror(errno)); - rmdir_recursive(output_dir); - rv = 1; - goto cleanup2; - } - - if (0 != symlink(output_dir, sym)) { - fprintf(stderr, "error: failed to create symlink (%s): %s\n", sym, - strerror(errno)); - rmdir_recursive(output_dir); - rv = 1; - goto cleanup2; - } - - if (htdocs_sym != NULL) - rmdir_recursive(htdocs_sym); - -cleanup2: - free(htdocs_sym); - -cleanup: - free(sym); - free(ref); - free(output_dir); - rmdir_recursive(tmpdir); - free(repo_dir); - return rv; -} diff --git a/src/blogc-git-receiver/pre-receive.h b/src/blogc-git-receiver/pre-receive.h deleted file mode 100644 index ab19c47..0000000 --- a/src/blogc-git-receiver/pre-receive.h +++ /dev/null @@ -1,14 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#ifndef _PRE_RECEIVE_H -#define _PRE_RECEIVE_H - -int bgr_pre_receive_hook(int argc, char *argv[]); - -#endif /* _PRE_RECEIVE_H */ diff --git a/src/blogc-git-receiver/settings.c b/src/blogc-git-receiver/settings.c deleted file mode 100644 index db29b18..0000000 --- a/src/blogc-git-receiver/settings.c +++ /dev/null @@ -1,110 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#include <stdio.h> -#include <libgen.h> -#include <unistd.h> -#include <stdlib.h> -#include <string.h> -#include <stdbool.h> -#include "../common/utils.h" -#include "../common/config-parser.h" -#include "../common/error.h" -#include "../common/file.h" -#include "settings.h" - - -const char* -bgr_settings_get_base_dir(void) -{ - char *rv = getenv("BLOGC_GIT_RECEIVER_BASE_DIR"); - if (rv != NULL) { - return rv; - } - return getenv("HOME"); -} - - -char* -bgr_settings_get_builds_dir(void) -{ - char *rv = getenv("BLOGC_GIT_RECEIVER_BUILDS_DIR"); - if (rv != NULL) { - return bc_strdup(rv); - } - return bc_strdup_printf("%s/builds", bgr_settings_get_base_dir()); -} - - -char* -bgr_settings_get_section(bc_config_t *config, const char *repo_path) -{ - const char *bd = bgr_settings_get_base_dir(); - if (bd == NULL) { - return NULL; - } - char *rv = NULL; - char** sections = bc_config_list_sections(config); - for (size_t i = 0; sections[i] != NULL; i++) { - if (bc_str_starts_with(sections[i], "repo:")) { - char *tmp_repo = bc_strdup_printf("%s/repos/%s", bd, sections[i] + 5); - char *real_tmp_repo = realpath(tmp_repo, NULL); // maybe not needed - free(tmp_repo); - if (real_tmp_repo == NULL) - continue; - if (0 == strcmp(real_tmp_repo, repo_path)) { - rv = bc_strdup(sections[i]); - free(real_tmp_repo); - break; - } - free(real_tmp_repo); - } - } - bc_strv_free(sections); - return rv; -} - - -bc_config_t* -bgr_settings_parse(void) -{ - const char *bd = bgr_settings_get_base_dir(); - if (bd == NULL) { - return NULL; - } - char *config_file = bc_strdup_printf("%s/blogc-git-receiver.ini", bd); - if ((0 != access(config_file, F_OK))) { - free(config_file); - return NULL; - } - - size_t len; - bc_error_t *err = NULL; - char* config_content = bc_file_get_contents(config_file, true, &len, &err); - if (err != NULL) { - fprintf(stderr, "warning: failed to read configuration file (%s): %s\n", - config_file, err->msg); - bc_error_free(err); - free(config_file); - free(config_content); - return NULL; - } - - bc_config_t *config = bc_config_parse(config_content, len, NULL, &err); - free(config_content); - if (err != NULL) { - fprintf(stderr, "warning: failed to parse configuration file (%s): %s\n", - config_file, err->msg); - bc_error_free(err); - free(config_file); - return NULL; - } - free(config_file); - - return config; -} diff --git a/src/blogc-git-receiver/settings.h b/src/blogc-git-receiver/settings.h deleted file mode 100644 index 04c1a2b..0000000 --- a/src/blogc-git-receiver/settings.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#ifndef _SETTINGS_H -#define _SETTINGS_H - -#include "../common/config-parser.h" - -const char* bgr_settings_get_base_dir(void); -char* bgr_settings_get_builds_dir(void); -char* bgr_settings_get_section(bc_config_t *config, const char *repo_path); -bc_config_t* bgr_settings_parse(void); - -#endif /* _SETTINGS_H */ diff --git a/src/blogc-git-receiver/shell-command-parser.c b/src/blogc-git-receiver/shell-command-parser.c deleted file mode 100644 index 0091e0b..0000000 --- a/src/blogc-git-receiver/shell-command-parser.c +++ /dev/null @@ -1,101 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 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 <string.h> -#include "../common/utils.h" -#include "shell-command-parser.h" - -typedef enum { - START_COMMAND = 1, - START_REPO, - START_REPO2, - REPO, - START_ESCAPED, -} command_state_t; - - -char* -bgr_shell_command_parse(const char *command) -{ - command_state_t state = START_COMMAND; - size_t start = 0; - size_t command_len = strlen(command); - - bc_string_t *rv = bc_string_new(); - - for (size_t current = 0; current < command_len; current++) { - - char c = command[current]; - - switch (state) { - case START_COMMAND: - if (c == ' ') { - if (((current == 16) && - (0 == strncmp("git-receive-pack", command, 16))) || - ((current == 15) && - (0 == strncmp("git-upload-pack", command, 15))) || - ((current == 18) && - (0 == strncmp("git-upload-archive", command, 18)))) - { - state = START_REPO; - break; - } - goto error; - } - break; - - case START_REPO: - if (c == '\'') { // never saw git using double-quotes - state = START_REPO2; - break; - } - if (c == '\\') { // escaped ! or ' - state = START_ESCAPED; - break; - } - goto error; - - case START_REPO2: - if (c == '\'') { - state = START_REPO; - break; - } - start = current; - if (rv->len == 0 && c == '/') { // no absolute urls - start = current + 1; - } - state = REPO; - break; - - case START_ESCAPED: - if (c == '!' || c == '\'') { - bc_string_append_c(rv, c); - state = START_REPO; - break; - } - goto error; - - case REPO: - if (c == '\'') { - bc_string_append_len(rv, command + start, current - start); - state = START_REPO; - break; - } - break; - } - } - - if (rv->len > 0) - return bc_string_free(rv, false); - -error: - bc_string_free(rv, true); - return NULL; -} diff --git a/src/blogc-git-receiver/shell-command-parser.h b/src/blogc-git-receiver/shell-command-parser.h deleted file mode 100644 index 818d098..0000000 --- a/src/blogc-git-receiver/shell-command-parser.h +++ /dev/null @@ -1,14 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#ifndef _SHELL_COMMAND_PARSER_H -#define _SHELL_COMMAND_PARSER_H - -char* bgr_shell_command_parse(const char *command); - -#endif /* _SHELL_COMMAND_PARSER_H */ diff --git a/src/blogc-git-receiver/shell.c b/src/blogc-git-receiver/shell.c deleted file mode 100644 index a4c8a2d..0000000 --- a/src/blogc-git-receiver/shell.c +++ /dev/null @@ -1,190 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2020 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#include <stddef.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/stat.h> -#include <unistd.h> -#include <errno.h> -#include "../common/utils.h" -#include "settings.h" -#include "shell-command-parser.h" -#include "shell.h" - - -static bool -lexists(const char *pathname) -{ - struct stat b; - int tmp_errno = errno; - bool rv = lstat(pathname, &b) == 0; - errno = tmp_errno; - return rv; -} - - -int -bgr_shell(int argc, char *argv[]) -{ - int rv = 0; - - char *repo = NULL; - char *quoted_repo = NULL; - - // 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 base dir path - const char *bd = bgr_settings_get_base_dir(); - if (bd == NULL) { - fprintf(stderr, "error: failed to find base directory path\n"); - rv = 1; - goto cleanup; - } - - // validate command and extract git repository - char *tmp_repo = bgr_shell_command_parse(argv[2]); - if (tmp_repo == NULL) { - fprintf(stderr, "error: invalid git-shell command: %s\n", argv[2]); - rv = 1; - goto cleanup; - } - - repo = bc_strdup_printf("%s/repos/%s", bd, tmp_repo); - quoted_repo = bc_shell_quote(repo); - free(tmp_repo); - - if (0 == strncmp(argv[2], "git-upload-", 11)) // no need to check len here - goto git_exec; - - if (0 != access(repo, F_OK)) { - char *git_init_cmd = bc_strdup_printf( - "git init --bare %s > /dev/null", quoted_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\n", 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/hooks): " - "%s\n", repo, strerror(errno)); - rv = 1; - goto cleanup; - } - } - - if (0 != chdir("hooks")) { - fprintf(stderr, "error: failed to chdir (%s/hooks): %s\n", repo, - strerror(errno)); - rv = 1; - goto cleanup; - } - - if (lexists("pre-receive")) { - if (0 != unlink("pre-receive")) { - fprintf(stderr, "error: failed to remove old symlink " - "(%s/hooks/pre-receive): %s\n", repo, strerror(errno)); - rv = 1; - goto cleanup; - } - } - - if (0 != symlink(self, "pre-receive")) { - fprintf(stderr, "error: failed to create symlink " - "(%s/hooks/pre-receive): %s\n", repo, strerror(errno)); - rv = 1; - goto cleanup; - } - - if (lexists("post-receive")) { - if (0 != unlink("post-receive")) { - fprintf(stderr, "error: failed to remove old symlink " - "(%s/hooks/post-receive): %s\n", repo, strerror(errno)); - rv = 1; - goto cleanup; - } - } - - if (0 != symlink(self, "post-receive")) { - fprintf(stderr, "error: failed to create symlink " - "(%s/hooks/post-receive): %s\n", repo, strerror(errno)); - rv = 1; - goto cleanup; - } - -git_exec: - - if (0 != chdir(bd)) { - fprintf(stderr, "error: failed to chdir (%s): %s\n", bd, strerror(errno)); - rv = 1; - goto cleanup; - } - - // static allocation instead of bc_strdup_printf to avoid leaks - char buffer[4096]; - char *command = bc_strdup(argv[2]); - char *p; - for (p = command; *p != ' ' && *p != '\0'; p++); - if (*p == ' ') - *p = '\0'; - - if (sizeof(buffer) < (strlen(command) + strlen(quoted_repo) + 2)) { - fprintf(stderr, "error: git-shell command is too big\n"); - rv = 1; - goto cleanup; - } - - if (0 > snprintf(buffer, sizeof(buffer), "%s %s", command, quoted_repo)) { - fprintf(stderr, "error: failed to generate git-shell command\n"); - rv = 1; - goto cleanup; - } - - free(command); - free(repo); - free(quoted_repo); - - // this is a hack. no memory handling should be done inside this block - if (NULL == getenv("__VALGRIND_ENABLED")) { - execlp("git-shell", "git-shell", "-c", buffer, NULL); - - // execlp only returns on error, then something bad happened - fprintf(stderr, "error: failed to execute git-shell\n"); - return 1; - } - - printf("git-shell -c \"%s\"\n", buffer); // used by tests, ignore - return 0; - -cleanup: - free(repo); - free(quoted_repo); - return rv; -} diff --git a/src/blogc-git-receiver/shell.h b/src/blogc-git-receiver/shell.h deleted file mode 100644 index b59ff40..0000000 --- a/src/blogc-git-receiver/shell.h +++ /dev/null @@ -1,15 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#ifndef _SHELL_H -#define _SHELL_H - -char* bgr_shell_get_repo(const char *command); -int bgr_shell(int argc, char *argv[]); - -#endif /* _SHELL_H */ diff --git a/src/blogc-make/atom.c b/src/blogc-make/atom.c deleted file mode 100644 index 0b8cfd1..0000000 --- a/src/blogc-make/atom.c +++ /dev/null @@ -1,137 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2020 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 "utils.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_DOMAIN }}{{ BASE_URL }}%s</id>\n" - " <updated>{{ DATE_FIRST_FORMATTED }}</updated>\n" - " <link href=\"{{ BASE_DOMAIN }}{{ BASE_URL }}/\" />\n" - " <link href=\"{{ BASE_DOMAIN }}{{ BASE_URL }}%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_DOMAIN }}{{ BASE_URL }}%s</id>\n" - " <updated>{{ DATE_FORMATTED }}</updated>\n" - " <published>{{ DATE_FORMATTED }}</published>\n" - " <link href=\"{{ BASE_DOMAIN }}{{ BASE_URL }}%s\" />\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_generate(bm_settings_t *settings) -{ - if (settings == NULL) - 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"); - const char *post_ext = bc_trie_lookup(settings->settings, "html_ext"); - - bc_string_t *atom_url = bc_string_new(); - - if (atom_prefix[0] != '\0') - bc_string_append_c(atom_url, '/'); - - bc_string_append(atom_url, atom_prefix); - bc_string_append(atom_url, "{% ifdef FILTER_TAG %}/{{ FILTER_TAG }}"); - - if (atom_prefix[0] == '\0' && atom_ext[0] != '/') - bc_string_append(atom_url, "{% else %}/index"); - - bc_string_append(atom_url, "{% endif %}"); - bc_string_append(atom_url, atom_ext); - - char *post_url = bm_generate_filename(NULL, post_prefix, "{{ FILENAME }}", - post_ext); - - char *rv = bc_strdup_printf(atom_template, atom_url->str, atom_url->str, - post_url, post_url); - - bc_string_free(atom_url, true); - free(post_url); - - return rv; -} - - -char* -bm_atom_deploy(bm_settings_t *settings, bc_error_t **err) -{ - if (settings == NULL || err == NULL || *err != NULL) - return NULL; - - if (NULL != bc_trie_lookup(settings->settings, "atom_legacy_entry_id")) { - *err = bc_error_new_printf(BLOGC_MAKE_ERROR_ATOM, - "'atom_legacy_entry_id' setting is not supported anymore. see " - "https://blogc.rgm.io/news/blogc-0.16.1/ for details"); - 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; - } - - char *content = bm_atom_generate(settings); - if (content == NULL) { - close(fd); - unlink(fname); - return NULL; - } - - 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 deleted file mode 100644 index 29a6dcb..0000000 --- a/src/blogc-make/atom.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 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_generate(bm_settings_t *settings); -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 deleted file mode 100644 index b8d23dd..0000000 --- a/src/blogc-make/ctx.c +++ /dev/null @@ -1,409 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2020 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/stat.h> -#include <sys/types.h> -#include <dirent.h> -#include <errno.h> -#include <libgen.h> -#include <limits.h> -#include <time.h> -#include <stdlib.h> -#include <stdbool.h> -#include <string.h> -#include <unistd.h> -#include "../common/error.h" -#include "../common/file.h" -#include "../common/utils.h" -#include "atom.h" -#include "settings.h" -#include "exec.h" -#include "utils.h" -#include "ctx.h" - - -bm_filectx_t* -bm_filectx_new(bm_ctx_t *ctx, const char *filename, const char *slug, - struct stat *st) -{ - 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); - rv->slug = bc_strdup(slug); - - if (st == NULL) { - struct stat buf; - - if (0 != stat(f, &buf)) { - rv->tv_sec = 0; - rv->tv_nsec = 0; - rv->readable = false; - return rv; - } - - st = &buf; - } - - // if it isn't NULL the file exists for sure - rv->tv_sec = st->st_mtim_tv_sec; - rv->tv_nsec = st->st_mtim_tv_nsec; - rv->readable = true; - return rv; -} - - -bc_slist_t* -bm_filectx_new_r(bc_slist_t *l, 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); - - struct stat buf; - if (0 != stat(f, &buf)) { - free(f); - return l; - } - - if (S_ISDIR(buf.st_mode)) { - DIR *dir = opendir(f); - if (dir == NULL) { - free(f); - return l; - } - - struct dirent *e; - while (NULL != (e = readdir(dir))) { - if ((0 == strcmp(e->d_name, ".")) || (0 == strcmp(e->d_name, ".."))) - continue; - char *tmp = bc_strdup_printf("%s/%s", filename, e->d_name); - l = bm_filectx_new_r(l, ctx, tmp); - free(tmp); - } - - closedir(dir); - free(f); - return l; - } - - l = bc_slist_append(l, bm_filectx_new(ctx, filename, NULL, &buf)); - free(f); - return l; -} - - -bool -bm_filectx_changed(bm_filectx_t *ctx, time_t *tv_sec, long *tv_nsec) -{ - if (ctx == NULL) - return false; - - struct stat buf; - - if (0 == stat(ctx->path, &buf)) { - if (buf.st_mtim_tv_sec == ctx->tv_sec) { - if (buf.st_mtim_tv_nsec > ctx->tv_nsec) { - if (tv_sec != NULL) - *tv_sec = buf.st_mtim_tv_sec; - if (tv_nsec != NULL) - *tv_nsec = buf.st_mtim_tv_nsec; - return true; - } - } - else if (buf.st_mtim_tv_sec > ctx->tv_sec) { - if (tv_sec != NULL) - *tv_sec = buf.st_mtim_tv_sec; - if (tv_nsec != NULL) - *tv_nsec = buf.st_mtim_tv_nsec; - return true; - } - } - - return false; -} - - -void -bm_filectx_reload(bm_filectx_t *ctx) -{ - if (ctx == NULL) - return; - - time_t tv_sec; - long tv_nsec; - - if (!bm_filectx_changed(ctx, &tv_sec, &tv_nsec)) - return; - - ctx->tv_sec = tv_sec; - ctx->tv_nsec = tv_nsec; - ctx->readable = true; -} - - -void -bm_filectx_free(bm_filectx_t *fctx) -{ - if (fctx == NULL) - return; - free(fctx->path); - free(fctx->short_path); - free(fctx->slug); - free(fctx); -} - - -bm_ctx_t* -bm_ctx_new(bm_ctx_t *base, const char *settings_file, const char *argv0, - bc_error_t **err) -{ - if (settings_file == NULL || err == NULL || *err != NULL) - return NULL; - - char *abs_filename = bm_abspath(settings_file, err); - if (*err != NULL) - return NULL; - - size_t content_len; - char *content = bc_file_get_contents(abs_filename, true, &content_len, - err); - if (*err != NULL) { - free(abs_filename); - return NULL; - } - - bm_settings_t *settings = bm_settings_parse(content, content_len, err); - if (settings == NULL || *err != NULL) { - free(abs_filename); - free(content); - return NULL; - } - free(content); - - const char *template_dir = bc_trie_lookup(settings->settings, "template_dir"); - if (template_dir == NULL) - template_dir = ""; - - char *atom_template = NULL; - bool atom_template_tmp = false; - const char *atom_template_conf = bc_trie_lookup(settings->settings, - "atom_template"); - if (atom_template_conf != NULL) { - atom_template = bc_strdup_printf("%s/%s", template_dir, atom_template_conf); - } - else { - atom_template = bm_atom_deploy(settings, err); - atom_template_tmp = true; - if (*err != NULL) { - free(abs_filename); - bm_settings_free(settings); - return NULL; - } - } - - bm_ctx_t *rv = NULL; - if (base == NULL) { - rv = bc_malloc(sizeof(bm_ctx_t)); - rv->blogc = bm_exec_find_binary(argv0, "blogc", "BLOGC"); - rv->blogc_runserver = bm_exec_find_binary(argv0, "blogc-runserver", - "BLOGC_RUNSERVER"); - rv->dev = false; - rv->verbose = false; - } - else { - bm_ctx_free_internal(base); - rv = base; - } - rv->settings = settings; - - rv->settings_fctx = bm_filectx_new(rv, abs_filename, NULL, NULL); - rv->root_dir = bc_strdup(dirname(abs_filename)); - free(abs_filename); - - const char *output_dir = getenv("OUTPUT_DIR"); - rv->short_output_dir = bc_strdup(output_dir != NULL ? output_dir : "_build"); - - if (rv->short_output_dir[0] == '/') { - rv->output_dir = bc_strdup(rv->short_output_dir); - } - else { - rv->output_dir = bc_strdup_printf("%s/%s", rv->root_dir, - rv->short_output_dir); - } - - // can't return null and set error after this! - - char *main_template = bc_strdup_printf("%s/%s", template_dir, - bm_ctx_settings_lookup(rv, "main_template")); - rv->main_template_fctx = bm_filectx_new(rv, main_template, NULL, NULL); - free(main_template); - - rv->atom_template_tmp = atom_template_tmp; - rv->atom_template_fctx = bm_filectx_new(rv, atom_template, NULL, NULL); - free(atom_template); - - const char *content_dir = bm_ctx_settings_lookup(rv, "content_dir"); - const char *post_prefix = bm_ctx_settings_lookup(rv, "post_prefix"); - const char *source_ext = bm_ctx_settings_lookup(rv, "source_ext"); - const char *listing_entry = bm_ctx_settings_lookup(rv, "listing_entry"); - - rv->listing_entry_fctx = NULL; - if (listing_entry != NULL) { - char *f = bm_generate_filename(content_dir, NULL, listing_entry, source_ext); - rv->listing_entry_fctx = bm_filectx_new(rv, f, listing_entry, NULL); - free(f); - } - - rv->posts_fctx = NULL; - if (settings->posts != NULL) { - for (size_t i = 0; settings->posts[i] != NULL; i++) { - char *f = bm_generate_filename(content_dir, post_prefix, - settings->posts[i], source_ext); - rv->posts_fctx = bc_slist_append(rv->posts_fctx, - bm_filectx_new(rv, f, settings->posts[i], NULL)); - free(f); - } - } - - rv->pages_fctx = NULL; - if (settings->pages != NULL) { - for (size_t i = 0; settings->pages[i] != NULL; i++) { - char *f = bm_generate_filename(content_dir, NULL, settings->pages[i], - source_ext); - rv->pages_fctx = bc_slist_append(rv->pages_fctx, - bm_filectx_new(rv, f, settings->pages[i], NULL)); - free(f); - } - } - - rv->copy_fctx = NULL; - if (settings->copy != NULL) { - for (size_t i = 0; settings->copy[i] != NULL; i++) { - rv->copy_fctx = bm_filectx_new_r(rv->copy_fctx, rv, - settings->copy[i]); - } - } - - return rv; -} - - -bool -bm_ctx_reload(bm_ctx_t **ctx) -{ - if (*ctx == NULL || (*ctx)->settings_fctx == NULL) - return false; - - if (bm_filectx_changed((*ctx)->settings_fctx, NULL, NULL)) { - // reload everything! we could just reload settings_fctx, as this - // would force rebuilding everything, but we need to know new/deleted - // files - - // needs to dup path, because it may be freed when reloading. - char *tmp = bc_strdup((*ctx)->settings_fctx->path); - bc_error_t *err = NULL; - *ctx = bm_ctx_new(*ctx, tmp, NULL, &err); - free(tmp); - if (err != NULL) { - bc_error_print(err, "blogc-make"); - bc_error_free(err); - return false; - } - return true; - } - - bm_filectx_reload((*ctx)->main_template_fctx); - bm_filectx_reload((*ctx)->atom_template_fctx); - bm_filectx_reload((*ctx)->listing_entry_fctx); - - for (bc_slist_t *tmp = (*ctx)->posts_fctx; tmp != NULL; tmp = tmp->next) - bm_filectx_reload((bm_filectx_t*) tmp->data); - - for (bc_slist_t *tmp = (*ctx)->pages_fctx; tmp != NULL; tmp = tmp->next) - bm_filectx_reload((bm_filectx_t*) tmp->data); - - for (bc_slist_t *tmp = (*ctx)->copy_fctx; tmp != NULL; tmp = tmp->next) - bm_filectx_reload((bm_filectx_t*) tmp->data); - - return true; -} - - -void -bm_ctx_free_internal(bm_ctx_t *ctx) -{ - if (ctx == NULL) - return; - - bm_settings_free(ctx->settings); - ctx->settings = NULL; - - free(ctx->root_dir); - ctx->root_dir = NULL; - free(ctx->short_output_dir); - ctx->short_output_dir = NULL; - free(ctx->output_dir); - ctx->output_dir = NULL; - - if (ctx->atom_template_tmp) - bm_atom_destroy(ctx->atom_template_fctx->path); - ctx->atom_template_tmp = false; - - bm_filectx_free(ctx->main_template_fctx); - ctx->main_template_fctx = NULL; - bm_filectx_free(ctx->atom_template_fctx); - ctx->atom_template_fctx = NULL; - bm_filectx_free(ctx->settings_fctx); - ctx->settings_fctx = NULL; - bm_filectx_free(ctx->listing_entry_fctx); - ctx->listing_entry_fctx = NULL; - - bc_slist_free_full(ctx->posts_fctx, (bc_free_func_t) bm_filectx_free); - ctx->posts_fctx = NULL; - bc_slist_free_full(ctx->pages_fctx, (bc_free_func_t) bm_filectx_free); - ctx->pages_fctx = NULL; - bc_slist_free_full(ctx->copy_fctx, (bc_free_func_t) bm_filectx_free); - ctx->copy_fctx = NULL; -} - - -void -bm_ctx_free(bm_ctx_t *ctx) -{ - if (ctx == NULL) - return; - bm_ctx_free_internal(ctx); - free(ctx->blogc); - free(ctx->blogc_runserver); - free(ctx); -} - - -const char* -bm_ctx_settings_lookup(bm_ctx_t *ctx, const char *key) -{ - if (ctx == NULL || ctx->settings == NULL || ctx->settings->settings == NULL) - return NULL; - return bc_trie_lookup(ctx->settings->settings, key); -} - - -const char* -bm_ctx_settings_lookup_str(bm_ctx_t *ctx, const char *key) -{ - const char *rv = bm_ctx_settings_lookup(ctx, key); - return rv == NULL ? "" : rv; -} diff --git a/src/blogc-make/ctx.h b/src/blogc-make/ctx.h deleted file mode 100644 index a66d51c..0000000 --- a/src/blogc-make/ctx.h +++ /dev/null @@ -1,84 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 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 <sys/stat.h> -#include <stdbool.h> -#include <time.h> -#include "settings.h" -#include "../common/error.h" -#include "../common/utils.h" - -#ifdef __APPLE__ -#define st_mtim_tv_sec st_mtimespec.tv_sec -#define st_mtim_tv_nsec st_mtimespec.tv_nsec -#endif - -#ifdef __ANDROID__ -#define st_mtim_tv_sec st_mtime -#define st_mtim_tv_nsec st_mtime_nsec -#endif - -#ifndef st_mtim_tv_sec -#define st_mtim_tv_sec st_mtim.tv_sec -#endif -#ifndef st_mtim_tv_nsec -#define st_mtim_tv_nsec st_mtim.tv_nsec -#endif - - -typedef struct { - char *path; - char *short_path; - char *slug; - time_t tv_sec; - long tv_nsec; - bool readable; -} bm_filectx_t; - -typedef struct { - char *blogc; - char *blogc_runserver; - - bool dev; - bool verbose; - bool atom_template_tmp; - - bm_settings_t *settings; - - char *root_dir; - char *output_dir; - char *short_output_dir; - - bm_filectx_t *main_template_fctx; - bm_filectx_t *atom_template_fctx; - bm_filectx_t *settings_fctx; - bm_filectx_t *listing_entry_fctx; - - bc_slist_t *posts_fctx; - bc_slist_t *pages_fctx; - bc_slist_t *copy_fctx; -} bm_ctx_t; - -bm_filectx_t* bm_filectx_new(bm_ctx_t *ctx, const char *filename, const char *slug, - struct stat *st); -bc_slist_t* bm_filectx_new_r(bc_slist_t *l, bm_ctx_t *ctx, const char *filename); -bool bm_filectx_changed(bm_filectx_t *ctx, time_t *tv_sec, long *tv_nsec); -void bm_filectx_reload(bm_filectx_t *ctx); -void bm_filectx_free(bm_filectx_t *fctx); -bm_ctx_t* bm_ctx_new(bm_ctx_t *base, const char *settings_file, - const char *argv0, bc_error_t **err); -bool bm_ctx_reload(bm_ctx_t **ctx); -void bm_ctx_free_internal(bm_ctx_t *ctx); -void bm_ctx_free(bm_ctx_t *ctx); -const char* bm_ctx_settings_lookup(bm_ctx_t *ctx, const char *key); -const char* bm_ctx_settings_lookup_str(bm_ctx_t *ctx, const char *key); - -#endif /* _MAKE_CTX_H */ diff --git a/src/blogc-make/exec-native.c b/src/blogc-make/exec-native.c deleted file mode 100644 index 6d7f58f..0000000 --- a/src/blogc-make/exec-native.c +++ /dev/null @@ -1,192 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2020 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 <dirent.h> -#include <fcntl.h> -#include <unistd.h> -#include <libgen.h> -#include <errno.h> -#include "../common/error.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); - return 1; - } - *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 1; - } - - int fd_to = open(dest->path, O_WRONLY | O_CREAT | O_TRUNC, 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 1; - } - - 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 1; - } - nread -= nwritten; - out_ptr += nwritten; - } while (nread > 0); - } - - close(fd_from); - close(fd_to); - - return 0; -} - - -bool -bm_exec_native_is_empty_dir(const char *dir, bc_error_t **err) -{ - DIR *d = opendir(dir); - if (d == NULL) { - if (errno == ENOENT) { - return true; - } - if (err != NULL) { - *err = bc_error_new_printf(0, "failed to open directory (%s): %s\n", - dir, strerror(errno)); - } - return true; - } - - 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)) { - if (err != NULL) { - *err = bc_error_new_printf(0, "failed to close directory (%s): %s\n", - dir, strerror(errno)); - } - return true; - } - - return count == 0; -} - - -int -bm_exec_native_rm(const char *output_dir, 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 1; - } - - int rv = 0; - - // blame freebsd's libc for all of those memory allocations around dirname - // calls! - char *short_dir = bc_strdup(dirname(dest->short_path)); - char *dir = bc_strdup(dirname(dest->path)); - - bc_error_t *err = NULL; - - while ((0 != strcmp(short_dir, ".")) && (0 != strcmp(short_dir, "/"))) { - bool empty = bm_exec_native_is_empty_dir(dir, &err); - if (err != NULL) { - fprintf(stderr, "blogc-make: error: %s\n", err->msg); - bc_error_free(err); - rv = 1; - break; - } - if (!empty) { - break; - } - if (verbose) { - printf("Removing directory '%s'\n", dir); - fflush(stdout); - } - if (0 != rmdir(dir)) { - fprintf(stderr, - "blogc-make: error: failed to remove directory(%s): %s\n", - dir, strerror(errno)); - rv = 1; - break; - } - if (0 == strcmp(dir, output_dir)) { - break; - } - - char *tmp = short_dir; - short_dir = bc_strdup(dirname(short_dir)); - free(tmp); - tmp = dir; - dir = bc_strdup(dirname(dir)); - free(tmp); - } - - free(short_dir); - free(dir); - - return rv; -} diff --git a/src/blogc-make/exec-native.h b/src/blogc-make/exec-native.h deleted file mode 100644 index 56a1da1..0000000 --- a/src/blogc-make/exec-native.h +++ /dev/null @@ -1,20 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 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 "../common/error.h" -#include "ctx.h" - -int bm_exec_native_cp(bm_filectx_t *source, bm_filectx_t *dest, bool verbose); -bool bm_exec_native_is_empty_dir(const char *dir, bc_error_t **err); -int bm_exec_native_rm(const char *output_dir, 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 deleted file mode 100644 index 4b1f6df..0000000 --- a/src/blogc-make/exec.c +++ /dev/null @@ -1,498 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2020 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 */ - -#ifdef HAVE_SYSEXITS_H -#include <sysexits.h> -#else -#define EX_CONFIG 78 -#endif /* HAVE_SYSEXITS_H */ - -#include <stdbool.h> -#include <stdlib.h> -#include <stdio.h> -#include <string.h> -#include <unistd.h> -#include <sys/wait.h> -#include <errno.h> -#include <libgen.h> -#include "../common/compat.h" -#include "../common/error.h" -#include "../common/file.h" -#include "../common/utils.h" -#include "ctx.h" -#include "exec.h" -#include "settings.h" - - -char* -bm_exec_find_binary(const char *argv0, const char *bin, const char *env) -{ -#ifdef MAKE_EMBEDDED - // for embedded blogc-make, if we are looking for blogc, we just return - // argv0, because the static binary may not be named `blogc`, and we - // prefer to use our own `blogc` instead of some other version around. - if (argv0 != NULL && bin != NULL && (0 == strcmp(bin, "blogc"))) { - return bc_shell_quote(argv0); - } -#endif - - // first try: env var - const char *env_bin = getenv(env); - if (env_bin != NULL) { - return bc_shell_quote(env_bin); - } - - // second try: same dir as current exec - // we rely on some assumptions here: - // - // - if binary is called without a directory, that means location will - // be resolved from $PATH, we don't care about doing a dir lookup. - // - we *never* call chdir anywhere in the code, so we can assume - // that relative paths will work as expected. - // - windows path sep is not supported - if (argv0 != NULL && (NULL != strchr(argv0, '/'))) { - char *path = bc_strdup(argv0); - char *dir = bc_strdup(dirname(path)); - free(path); - char *tmp = bc_strdup_printf("%s/%s", dir, bin); - free(dir); - if (0 == access(tmp, X_OK)) { - char *rv = bc_shell_quote(tmp); - free(tmp); - return rv; - } - free(tmp); - } - - // last try: $PATH - return bc_strdup(bin); -} - - -int -bm_exec_command(const char *cmd, const char *input, char **output, - char **error, bc_error_t **err) -{ - if (err == NULL || *err != NULL) - return 1; - - 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 1; - } - - 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 1; - } - - 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 1; - } - - 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 1; - } - - // 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 1; - } - } - - 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 1; - } - 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 1; - } - 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 bc_compat_status_code(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(const char *blogc_bin, bm_settings_t *settings, - bc_trie_t *global_variables, bc_trie_t *local_variables, const char *print, - bool listing, const char *listing_entry, const char *template, - const char *output, bool dev, 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_bin); - - if (settings != NULL) { - if (settings->tags != NULL) { - char *tags = bc_strv_join(settings->tags, " "); - bc_string_append_printf(rv, " -D MAKE_TAGS='%s'", tags); - free(tags); - } - - bc_trie_foreach(settings->global, - (bc_trie_foreach_func_t) list_variables, rv); - } - - bc_trie_foreach(global_variables, (bc_trie_foreach_func_t) list_variables, rv); - bc_trie_foreach(local_variables, (bc_trie_foreach_func_t) list_variables, rv); - - if (dev) { - bc_string_append(rv, " -D MAKE_ENV_DEV=1 -D MAKE_ENV='dev'"); - } - - if (print != NULL) { - bc_string_append_printf(rv, " -p %s", print); - } - - if (listing) { - bc_string_append(rv, " -l"); - if (listing_entry != NULL) { - char *tmp = bc_shell_quote(listing_entry); - bc_string_append_printf(rv, " -e %s", tmp); - free(tmp); - } - } - - 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_ctx_t *ctx, bc_trie_t *global_variables, bc_trie_t *local_variables, - bool listing, bm_filectx_t *listing_entry, bm_filectx_t *template, - bm_filectx_t *output, bc_slist_t *sources, bool only_first_source) -{ - if (ctx == NULL) - return 1; - - 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(ctx->blogc, ctx->settings, global_variables, - local_variables, NULL, listing, listing_entry == NULL ? NULL : listing_entry->path, - template->path, output->path, ctx->dev, input->len > 0); - - if (ctx->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 1; - } - - if (rv != 0 && ctx->verbose) { - fprintf(stderr, - "blogc-make: 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)); - } - fprintf(stderr, "\n"); - } - else if (err != NULL) { - fprintf(stderr, "%s\n", err); - } - - bc_string_free(input, true); - free(cmd); - free(out); - free(err); - - return rv == 127 ? 1 : rv; -} - - -char* -bm_exec_blogc_get_variable(bm_ctx_t *ctx, bc_trie_t *global_variables, - bc_trie_t *local_variables, const char *variable, bool listing, - bc_slist_t *sources, bool only_first_source) -{ - if (ctx == NULL) - return NULL; - - 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(ctx->blogc, ctx->settings, global_variables, - local_variables, variable, listing, NULL, NULL, NULL, ctx->dev, input->len > 0); - - if (ctx->verbose) - printf("%s\n", cmd); - 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"); - bc_error_free(error); - bc_string_free(input, true); - free(cmd); - free(out); - free(err); - return NULL; - } - - if (rv != 0) { - if (rv != EX_CONFIG) - fprintf(stderr, "blogc-make: error: %s\n", bc_str_strip(err)); - bc_string_free(input, true); - free(cmd); - free(out); - free(err); - return NULL; - } - - char *val = NULL; - if (out != NULL) - val = bc_strndup(out, strlen(out) - 1); - - bc_string_free(input, true); - free(cmd); - free(out); - free(err); - - return val; -} - - -int -bm_exec_blogc_runserver(bm_ctx_t *ctx, const char *host, const char *port, - const char *threads) -{ - if (ctx == NULL) - return 1; - - bc_string_t *cmd = bc_string_new(); - - bc_string_append(cmd, ctx->blogc_runserver); - - if (host != NULL) { - char *tmp = bc_shell_quote(host); - bc_string_append_printf(cmd, " -t %s", tmp); - free(tmp); - } - - if (port != NULL) { - char *tmp = bc_shell_quote(port); - bc_string_append_printf(cmd, " -p %s", tmp); - free(tmp); - } - - if (threads != NULL) { - char *tmp = bc_shell_quote(threads); - bc_string_append_printf(cmd, " -m %s", tmp); - free(tmp); - } - - char *tmp = bc_shell_quote(ctx->output_dir); - bc_string_append_printf(cmd, " %s", tmp); - free(tmp); - - if (ctx->verbose) - printf("%s\n\n", cmd->str); - else - printf("\n"); - fflush(stdout); - - // we don't need pipes to run blogc-runserver, because it is "interactive" - int status = system(cmd->str); - int rv = bc_compat_status_code(status); - bc_string_free(cmd, true); - - if (rv != 0 && rv != 130) { - if (rv == 127) { - fprintf(stderr, - "blogc-make: error: blogc-runserver command not found. Maybe " - "it is not installed?\n"); - rv = 1; // blogc-make exists, so we should not return 127 - } - else { - fprintf(stderr, - "blogc-make: error: Failed to execute command, returned " - "status code: %d\n", rv); - } - } - - return rv; -} diff --git a/src/blogc-make/exec.h b/src/blogc-make/exec.h deleted file mode 100644 index 6bc206f..0000000 --- a/src/blogc-make/exec.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 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 <stdbool.h> -#include "../common/error.h" -#include "../common/utils.h" -#include "ctx.h" -#include "settings.h" - -char* bm_exec_find_binary(const char *argv0, const char *bin, const char *env); -int bm_exec_command(const char *cmd, const char *input, char **output, - char **error, bc_error_t **err); -char* bm_exec_build_blogc_cmd(const char *blogc_bin, bm_settings_t *settings, - bc_trie_t *global_variables, bc_trie_t *local_variables, const char *print, - bool listing, const char *listing_entry, const char *template, - const char *output, bool dev, bool sources_stdin); -int bm_exec_blogc(bm_ctx_t *ctx, bc_trie_t *global_variables, - bc_trie_t *local_variables, bool listing, bm_filectx_t *listing_entry, - bm_filectx_t *template, bm_filectx_t *output, bc_slist_t *sources, - bool only_first_source); -char* bm_exec_blogc_get_variable(bm_ctx_t *ctx, bc_trie_t *global_variables, - bc_trie_t *local_variables, const char *variable, bool listing, - bc_slist_t *sources, bool only_first_source); -int bm_exec_blogc_runserver(bm_ctx_t *ctx, const char *host, const char *port, - const char *threads); - -#endif /* _MAKE_EXEC_H */ diff --git a/src/blogc-make/httpd.c b/src/blogc-make/httpd.c deleted file mode 100644 index c352da0..0000000 --- a/src/blogc-make/httpd.c +++ /dev/null @@ -1,121 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2020 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <pthread.h> -#include <unistd.h> -#include "../common/utils.h" -#include "ctx.h" -#include "exec.h" -#include "reloader.h" -#include "httpd.h" - -// we are not going to unit-test these functions, then printing errors -// directly is not a big issue - - -typedef struct { - bm_ctx_t *ctx; - bc_trie_t *args; -} bm_httpd_t; - -static pthread_mutex_t mutex_httpd_starting = PTHREAD_MUTEX_INITIALIZER; -static bool httpd_starting = false; - - -static void* -httpd_thread(void *arg) -{ - bm_httpd_t *httpd = arg; - - pthread_mutex_lock(&mutex_httpd_starting); - httpd_starting = true; - pthread_mutex_unlock(&mutex_httpd_starting); - - int rv = bm_exec_blogc_runserver(httpd->ctx, bc_trie_lookup(httpd->args, "host"), - bc_trie_lookup(httpd->args, "port"), bc_trie_lookup(httpd->args, "threads")); - - pthread_mutex_lock(&mutex_httpd_starting); - httpd_starting = false; - pthread_mutex_unlock(&mutex_httpd_starting); - - free(httpd); - - // stop the reloader - bm_reloader_stop(rv); - - return NULL; -} - - -int -bm_httpd_run(bm_ctx_t **ctx, bm_rule_exec_func_t rule_exec, bc_slist_t *outputs, - bc_trie_t *args) -{ - pthread_mutex_lock(&mutex_httpd_starting); - bool starting = httpd_starting; - pthread_mutex_unlock(&mutex_httpd_starting); - - if (starting) { - fprintf(stderr, "blogc-make: error: httpd already running\n"); - return 1; - } - - int err; - - pthread_attr_t attr; - if (0 != (err = pthread_attr_init(&attr))) { - fprintf(stderr, "blogc-make: error: failed to initialize httpd " - "thread attributes: %s\n", strerror(err)); - return 1; - } - - // we run the thread detached, because we don't want to wait it to join - // before exiting. the OS can clean it properly - if (0 != (err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED))) { - fprintf(stderr, "blogc-make: error: failed to mark httpd thread as " - "detached: %s\n", strerror(err)); - return 1; - } - - bm_httpd_t *rv = bc_malloc(sizeof(bm_httpd_t)); - rv->ctx = *ctx; - rv->args = args; - - pthread_t thread; - if (0 != (err = pthread_create(&thread, &attr, httpd_thread, rv))) { - fprintf(stderr, "blogc-make: error: failed to create httpd " - "thread: %s\n", strerror(err)); - free(rv); - return 1; - } - - // we could use some pthread_*_timedwait() apis here, but I decided to - // just use simple mutexes for the sake of portability. - size_t count = 0; - while (true) { - pthread_mutex_lock(&mutex_httpd_starting); - starting = httpd_starting; - pthread_mutex_unlock(&mutex_httpd_starting); - - if (starting) - break; - - if (++count > 100) { - fprintf(stderr, "blogc-make: error: failed to start httpd thread: " - "too many retries\n"); - // rv will leak, but it is not safe to free here - return 1; - } - usleep(100000); - } - - return bm_reloader_run(ctx, rule_exec, outputs, args); -} diff --git a/src/blogc-make/httpd.h b/src/blogc-make/httpd.h deleted file mode 100644 index b0fa87d..0000000 --- a/src/blogc-make/httpd.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 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_HTTPD_H -#define _MAKE_HTTPD_H - -#include "../common/utils.h" -#include "ctx.h" -#include "rules.h" - -int bm_httpd_run(bm_ctx_t **ctx, bm_rule_exec_func_t rule_exec, bc_slist_t *outputs, - bc_trie_t *args); - -#endif /* _MAKE_HTTPD_H */ diff --git a/src/blogc-make/main.c b/src/blogc-make/main.c deleted file mode 100644 index 5b4a030..0000000 --- a/src/blogc-make/main.c +++ /dev/null @@ -1,134 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 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 "../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] [-D] [-V] [-f FILE] [RULE ...]\n" - " - A simple build tool for blogc.\n" - "\n" - "positional arguments:\n" - " RULE build rule(s) to run. can include comma-separated\n" - " key-value pairs of rule arguments in the format\n" - " RULE:arg1=value1,arg2=value2,... (default: all)\n" - "\n" - "optional arguments:\n" - " -h show this help message and exit\n" - " -v show version and exit\n" - " -D build for development environment\n" - " -V be verbose when executing commands\n" - " -f FILE read FILE as blogcfile\n"); - bm_rule_print_help(); -} - - -static void -print_usage(void) -{ - printf("usage: blogc-make [-h] [-v] [-D] [-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; - bool dev = false; - char *blogcfile = NULL; - bm_ctx_t *ctx = NULL; - - for (size_t 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 'D': - dev = true; - break; - case 'V': - verbose = true; - break; - case 'f': - if (argv[i][2] != '\0') - blogcfile = bc_strdup(argv[i] + 2); - else if (i + 1 < argc) - blogcfile = 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 = 1; - 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(NULL, blogcfile ? blogcfile : "blogcfile", - argc > 0 ? argv[0] : NULL, &err); - if (err != NULL) { - bc_error_print(err, "blogc-make"); - rv = 1; - goto cleanup; - } - ctx->dev = dev; - ctx->verbose = verbose; - - rv = bm_rule_executor(ctx, rules); - -cleanup: - - bc_slist_free_full(rules, free); - free(blogcfile); - bm_ctx_free(ctx); - bc_error_free(err); - - return rv; -} diff --git a/src/blogc-make/reloader.c b/src/blogc-make/reloader.c deleted file mode 100644 index fea8bd4..0000000 --- a/src/blogc-make/reloader.c +++ /dev/null @@ -1,116 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 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 <stdlib.h> -#include <signal.h> -#include <string.h> -#include <unistd.h> -#include <pthread.h> -#include <errno.h> -#include "../common/utils.h" -#include "ctx.h" -#include "rules.h" -#include "reloader.h" - -// we are not going to unit-test these functions, then printing errors -// directly is not a big issue - -static pthread_mutex_t mutex_running = PTHREAD_MUTEX_INITIALIZER; -static bool running = false; -static int reloader_status_code = 0; -static void (*handler_func)(int) = NULL; - -static void -sig_handler(int signum) -{ - bm_reloader_stop(signum + 128); -} - - -int -bm_reloader_run(bm_ctx_t **ctx, bm_rule_exec_func_t rule_exec, - bc_slist_t *outputs, bc_trie_t *args) -{ - // install ^C handler - struct sigaction current_action; - if (sigaction(SIGINT, NULL, ¤t_action) < 0) { - fprintf(stderr, "blogc-make: failed to run reloader: %s\n", strerror(errno)); - return 1; - } - if (current_action.sa_handler != sig_handler) { // not installed yet - // backup current handler - pthread_mutex_lock(&mutex_running); - handler_func = current_action.sa_handler; - pthread_mutex_unlock(&mutex_running); - - // set new handler - struct sigaction new_action; - new_action.sa_handler = sig_handler; - sigemptyset(&new_action.sa_mask); - new_action.sa_flags = 0; - if (sigaction(SIGINT, &new_action, NULL) < 0) { - fprintf(stderr, "blogc-make: failed to run reloader: %s\n", - strerror(errno)); - return 1; - } - } - - pthread_mutex_lock(&mutex_running); - running = true; - pthread_mutex_unlock(&mutex_running); - - while (running) { - if (!bm_ctx_reload(ctx)) { - fprintf(stderr, "blogc-make: warning: failed to reload context. " - "retrying in 5 seconds ...\n\n"); - sleep(5); - continue; - } - if (0 != rule_exec(*ctx, outputs, args)) { - fprintf(stderr, "blogc-make: warning: failed to rebuild website. " - "retrying in 5 seconds ...\n\n"); - sleep(5); - continue; - } - sleep(1); - } - - return reloader_status_code; -} - - -void -bm_reloader_stop(int status_code) -{ - pthread_mutex_lock(&mutex_running); - - running = false; - reloader_status_code = status_code; - - // reraise if SIGINT - if (status_code == SIGINT + 128) { - - // reinstall old ^C handler - struct sigaction new_action; - new_action.sa_handler = handler_func; - sigemptyset(&new_action.sa_mask); - new_action.sa_flags = 0; - sigaction(SIGINT, &new_action, NULL); - - // run it - raise(SIGINT); - - // SIGINT will usually kill the process, but in the case that the - // `handler_func` prevents it, our custom handler will be reinstalled - // by `bm_reloader_run`. - } - - pthread_mutex_unlock(&mutex_running); -} diff --git a/src/blogc-make/reloader.h b/src/blogc-make/reloader.h deleted file mode 100644 index f3fddf4..0000000 --- a/src/blogc-make/reloader.h +++ /dev/null @@ -1,20 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 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_RELOADER_H -#define _MAKE_RELOADER_H - -#include "../common/utils.h" -#include "ctx.h" -#include "rules.h" - -int bm_reloader_run(bm_ctx_t **ctx, bm_rule_exec_func_t rule_exec, - bc_slist_t *outputs, bc_trie_t *args); -void bm_reloader_stop(int status_code); - -#endif /* _MAKE_RELOADER_H */ diff --git a/src/blogc-make/rules.c b/src/blogc-make/rules.c deleted file mode 100644 index 7d92b95..0000000 --- a/src/blogc-make/rules.c +++ /dev/null @@ -1,1078 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2020 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 "../common/utils.h" -#include "atom.h" -#include "ctx.h" -#include "exec.h" -#include "exec-native.h" -#include "httpd.h" -#include "reloader.h" -#include "settings.h" -#include "utils.h" -#include "rules.h" - - -static void -posts_ordering(bm_ctx_t *ctx, bc_trie_t *variables, const char *variable) -{ - if (ctx == NULL) - return; // something is wrong, let's not add any variable - - const char *value = bm_ctx_settings_lookup_str(ctx, variable); - bool asc = 0 == strcasecmp(value, "asc"); - bool sort = bc_str_to_bool(bm_ctx_settings_lookup(ctx, "posts_sort")); - - if (sort) { - bc_trie_insert(variables, "FILTER_SORT", bc_strdup("1")); - } - - if ((sort && asc) || (!sort && !asc)) { - bc_trie_insert(variables, "FILTER_REVERSE", bc_strdup("1")); - } -} - - -static void -posts_pagination(bm_ctx_t *ctx, bc_trie_t *variables, const char *variable) -{ - if (ctx == NULL) - return; // something is wrong, let's not add any variable - - long posts_per_page = strtol(bm_ctx_settings_lookup_str(ctx, variable), NULL, 10); - if (posts_per_page >= 0) { - bc_trie_insert(variables, "FILTER_PAGE", bc_strdup("1")); - bc_trie_insert(variables, "FILTER_PER_PAGE", - bc_strdup(bm_ctx_settings_lookup(ctx, variable))); - } -} - - -static bool -posts_pagination_enabled(bm_ctx_t *ctx, const char *variable) -{ - if (ctx == NULL) - return false; - - long posts_per_page = strtol(bm_ctx_settings_lookup_str(ctx, variable), NULL, 10); - return posts_per_page != 0; -} - - -// INDEX RULE - -static bc_slist_t* -index_outputlist(bm_ctx_t *ctx) -{ - if (ctx == NULL || ctx->settings->posts == NULL) - return NULL; - - if (!posts_pagination_enabled(ctx, "posts_per_page")) - return NULL; - - bc_slist_t *rv = NULL; - - const char *index_prefix = bm_ctx_settings_lookup(ctx, "index_prefix"); - const char *html_ext = bm_ctx_settings_lookup(ctx, "html_ext"); - - char *f = bm_generate_filename(ctx->short_output_dir, index_prefix, NULL, - html_ext); - rv = bc_slist_append(rv, bm_filectx_new(ctx, f, NULL, NULL)); - free(f); - - return rv; -} - -static int -index_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bc_trie_t *args) -{ - if (ctx == NULL || ctx->settings->posts == NULL) - return 0; - - int rv = 0; - - bc_trie_t *variables = bc_trie_new(free); - posts_pagination(ctx, variables, "posts_per_page"); - posts_ordering(ctx, variables, "html_order"); - bc_trie_insert(variables, "DATE_FORMAT", - bc_strdup(bm_ctx_settings_lookup_str(ctx, "date_format"))); - bc_trie_insert(variables, "MAKE_RULE", bc_strdup("index")); - bc_trie_insert(variables, "MAKE_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->listing_entry_fctx, ctx->main_template_fctx, fctx, false)) - { - rv = bm_exec_blogc(ctx, variables, NULL, true, ctx->listing_entry_fctx, - ctx->main_template_fctx, fctx, ctx->posts_fctx, 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; - - if (!posts_pagination_enabled(ctx, "atom_posts_per_page")) - return NULL; - - bc_slist_t *rv = NULL; - - const char *atom_prefix = bm_ctx_settings_lookup(ctx, "atom_prefix"); - const char *atom_ext = bm_ctx_settings_lookup(ctx, "atom_ext"); - - char *f = bm_generate_filename(ctx->short_output_dir, atom_prefix, NULL, - atom_ext); - rv = bc_slist_append(rv, bm_filectx_new(ctx, f, NULL, NULL)); - free(f); - - return rv; -} - -static int -atom_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bc_trie_t *args) -{ - if (ctx == NULL || ctx->settings->posts == NULL) - return 0; - - int rv = 0; - - bc_trie_t *variables = bc_trie_new(free); - posts_pagination(ctx, variables, "atom_posts_per_page"); - posts_ordering(ctx, variables, "atom_order"); - bc_trie_insert(variables, "DATE_FORMAT", bc_strdup("%Y-%m-%dT%H:%M:%SZ")); - bc_trie_insert(variables, "MAKE_RULE", bc_strdup("atom")); - bc_trie_insert(variables, "MAKE_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, - ctx->atom_template_tmp ? NULL : ctx->atom_template_fctx, - fctx, false)) - { - rv = bm_exec_blogc(ctx, variables, NULL, true, NULL, ctx->atom_template_fctx, - fctx, ctx->posts_fctx, 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; - - if (!posts_pagination_enabled(ctx, "atom_posts_per_page")) - return NULL; - - bc_slist_t *rv = NULL; - - const char *atom_prefix = bm_ctx_settings_lookup(ctx, "atom_prefix"); - const char *atom_ext = bm_ctx_settings_lookup(ctx, "atom_ext"); - - for (size_t i = 0; ctx->settings->tags[i] != NULL; i++) { - char *f = bm_generate_filename(ctx->short_output_dir, atom_prefix, - ctx->settings->tags[i], atom_ext); - rv = bc_slist_append(rv, bm_filectx_new(ctx, f, NULL, NULL)); - free(f); - } - - return rv; -} - -static int -atom_tags_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bc_trie_t *args) -{ - 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); - posts_pagination(ctx, variables, "atom_posts_per_page"); - posts_ordering(ctx, variables, "atom_order"); - bc_trie_insert(variables, "DATE_FORMAT", bc_strdup("%Y-%m-%dT%H:%M:%SZ")); - bc_trie_insert(variables, "MAKE_RULE", bc_strdup("atom_tags")); - bc_trie_insert(variables, "MAKE_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, - ctx->atom_template_tmp ? NULL : ctx->atom_template_fctx, - fctx, false)) - { - rv = bm_exec_blogc(ctx, variables, NULL, true, NULL, ctx->atom_template_fctx, - fctx, ctx->posts_fctx, 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; - - if (!posts_pagination_enabled(ctx, "posts_per_page")) - return NULL; - - bc_trie_t *variables = bc_trie_new(free); - posts_pagination(ctx, variables, "posts_per_page"); - - char *last_page = bm_exec_blogc_get_variable(ctx, variables, NULL, "LAST_PAGE", - true, ctx->posts_fctx, false); - - bc_trie_free(variables); - - if (last_page == NULL) - return NULL; - - long pages = strtol(last_page, NULL, 10); - free(last_page); - - bc_slist_t *rv = NULL; - - const char *pagination_prefix = bm_ctx_settings_lookup(ctx, "pagination_prefix"); - const char *html_ext = bm_ctx_settings_lookup(ctx, "html_ext"); - - for (size_t i = 0; i < pages; i++) { - char *j = bc_strdup_printf("%d", i + 1); - char *f = bm_generate_filename(ctx->short_output_dir, pagination_prefix, - j, html_ext); - rv = bc_slist_append(rv, bm_filectx_new(ctx, f, NULL, NULL)); - free(j); - free(f); - } - - return rv; -} - -static int -pagination_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bc_trie_t *args) -{ - if (ctx == NULL || ctx->settings->posts == NULL) - return 0; - - int rv = 0; - size_t page = 1; - - bc_trie_t *variables = bc_trie_new(free); - // not using posts_pagination because we set FILTER_PAGE anyway, and the - // first value inserted in that function would be useless - bc_trie_insert(variables, "FILTER_PER_PAGE", - bc_strdup(bm_ctx_settings_lookup_str(ctx, "posts_per_page"))); - posts_ordering(ctx, variables, "html_order"); - bc_trie_insert(variables, "DATE_FORMAT", - bc_strdup(bm_ctx_settings_lookup_str(ctx, "date_format"))); - bc_trie_insert(variables, "MAKE_RULE", bc_strdup("pagination")); - bc_trie_insert(variables, "MAKE_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->listing_entry_fctx, ctx->main_template_fctx, fctx, false)) - { - rv = bm_exec_blogc(ctx, variables, NULL, true, ctx->listing_entry_fctx, - ctx->main_template_fctx, fctx, ctx->posts_fctx, false); - if (rv != 0) - break; - } - } - - bc_trie_free(variables); - - return rv; -} - - -// PAGINATION TAGS RULE - -static bc_slist_t* -pagination_tags_outputlist(bm_ctx_t *ctx) -{ - if (ctx == NULL || ctx->settings->posts == NULL || ctx->settings->tags == NULL) - return NULL; - - if (!posts_pagination_enabled(ctx, "posts_per_page")) - return NULL; - - bc_trie_t *variables = bc_trie_new(free); - posts_pagination(ctx, variables, "posts_per_page"); - - const char *tag_prefix = bm_ctx_settings_lookup(ctx, "tag_prefix"); - const char *pagination_prefix = bm_ctx_settings_lookup(ctx, "pagination_prefix"); - const char *html_ext = bm_ctx_settings_lookup(ctx, "html_ext"); - - bc_slist_t *rv = NULL; - - for (size_t k = 0; ctx->settings->tags[k] != NULL; k++) { - bc_trie_t *local = bc_trie_new(free); - bc_trie_insert(local, "FILTER_TAG", bc_strdup(ctx->settings->tags[k])); - - char *last_page = bm_exec_blogc_get_variable(ctx, variables, local, - "LAST_PAGE", true, ctx->posts_fctx, false); - - bc_trie_free(local); - - if (last_page == NULL) - continue; - - long pages = strtol(last_page, NULL, 10); - free(last_page); - - for (size_t i = 0; i < pages; i++) { - char *j = bc_strdup_printf("%d", i + 1); - char *f = bm_generate_filename2(ctx->short_output_dir, tag_prefix, - ctx->settings->tags[k], pagination_prefix, j, html_ext); - rv = bc_slist_append(rv, bm_filectx_new(ctx, f, NULL, NULL)); - free(j); - free(f); - } - } - - bc_trie_free(variables); - - return rv; -} - -static int -pagination_tags_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bc_trie_t *args) -{ - if (ctx == NULL || ctx->settings->posts == NULL || ctx->settings->tags == NULL) - return 0; - - int rv = 0; - size_t page = 1; - - bc_trie_t *variables = bc_trie_new(free); - // not using posts_pagination because we set FILTER_PAGE anyway, and the - // first value inserted in that function would be useless - bc_trie_insert(variables, "FILTER_PER_PAGE", - bc_strdup(bm_ctx_settings_lookup_str(ctx, "posts_per_page"))); - posts_ordering(ctx, variables, "html_order"); - bc_trie_insert(variables, "DATE_FORMAT", - bc_strdup(bm_ctx_settings_lookup_str(ctx, "date_format"))); - bc_trie_insert(variables, "MAKE_RULE", bc_strdup("pagination_tags")); - bc_trie_insert(variables, "MAKE_TYPE", bc_strdup("post")); - - const char *tag_prefix = bm_ctx_settings_lookup(ctx, "tag_prefix"); - const char *pagination_prefix = bm_ctx_settings_lookup(ctx, "pagination_prefix"); - const char *html_ext = bm_ctx_settings_lookup(ctx, "html_ext"); - - for (bc_slist_t *l = outputs; l != NULL; l = l->next) { - bm_filectx_t *fctx = l->data; - if (fctx == NULL) - continue; - - // this is very expensive, but we don't have another way to detect the - // tag and page from the file path right now :/ - char *tag = NULL; - for (size_t i = 0; ctx->settings->tags[i] != NULL; i++) { - bool b = false; - - // it is impossible to have more output files per tag than the whole - // amount of output pages - for (size_t k = 1; k <= bc_slist_length(outputs); k++) { - char *j = bc_strdup_printf("%d", k); - char *f = bm_generate_filename2(ctx->short_output_dir, tag_prefix, - ctx->settings->tags[i], pagination_prefix, j, html_ext); - free(j); - if (0 == strcmp(fctx->short_path, f)) { - tag = ctx->settings->tags[i]; - page = k; - b = true; - free(f); - break; - } - free(f); - } - if (b) - break; - } - if (tag == NULL) - continue; - - bc_trie_insert(variables, "FILTER_TAG", bc_strdup(tag)); - bc_trie_insert(variables, "FILTER_PAGE", bc_strdup_printf("%zu", page)); - - if (bm_rule_need_rebuild(ctx->posts_fctx, ctx->settings_fctx, - ctx->listing_entry_fctx, ctx->main_template_fctx, fctx, false)) - { - rv = bm_exec_blogc(ctx, variables, NULL, true, ctx->listing_entry_fctx, - ctx->main_template_fctx, fctx, ctx->posts_fctx, 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; - - bc_slist_t *rv = NULL; - - const char *post_prefix = bm_ctx_settings_lookup(ctx, "post_prefix"); - const char *html_ext = bm_ctx_settings_lookup(ctx, "html_ext"); - - for (size_t i = 0; ctx->settings->posts[i] != NULL; i++) { - char *f = bm_generate_filename(ctx->short_output_dir, post_prefix, - ctx->settings->posts[i], html_ext); - rv = bc_slist_append(rv, bm_filectx_new(ctx, f, NULL, NULL)); - free(f); - } - - return rv; -} - -static int -posts_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bc_trie_t *args) -{ - 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(bm_ctx_settings_lookup(ctx, "date_format"))); - posts_ordering(ctx, variables, "html_order"); - bc_trie_insert(variables, "MAKE_RULE", bc_strdup("posts")); - bc_trie_insert(variables, "MAKE_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 *s_fctx = s->data; - bm_filectx_t *o_fctx = o->data; - if (o_fctx == NULL) - continue; - if (bm_rule_need_rebuild(s, ctx->settings_fctx, NULL, - ctx->main_template_fctx, o_fctx, true)) - { - bc_trie_t *local = bc_trie_new(NULL); - bc_trie_insert(local, "MAKE_SLUG", s_fctx->slug); // no need to copy - rv = bm_exec_blogc(ctx, variables, local, false, NULL, ctx->main_template_fctx, - o_fctx, s, true); - bc_trie_free(local); - 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; - - if (!posts_pagination_enabled(ctx, "posts_per_page")) - return NULL; - - bc_slist_t *rv = NULL; - - const char *tag_prefix = bm_ctx_settings_lookup(ctx, "tag_prefix"); - const char *html_ext = bm_ctx_settings_lookup(ctx, "html_ext"); - - for (size_t i = 0; ctx->settings->tags[i] != NULL; i++) { - char *f = bm_generate_filename(ctx->short_output_dir, tag_prefix, - ctx->settings->tags[i], html_ext); - rv = bc_slist_append(rv, bm_filectx_new(ctx, f, NULL, NULL)); - free(f); - } - - return rv; -} - -static int -tags_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bc_trie_t *args) -{ - 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); - posts_pagination(ctx, variables, "posts_per_page"); - posts_ordering(ctx, variables, "html_order"); - bc_trie_insert(variables, "DATE_FORMAT", - bc_strdup(bm_ctx_settings_lookup_str(ctx, "date_format"))); - bc_trie_insert(variables, "MAKE_RULE", bc_strdup("tags")); - bc_trie_insert(variables, "MAKE_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->listing_entry_fctx, ctx->main_template_fctx, fctx, false)) - { - rv = bm_exec_blogc(ctx, variables, NULL, true, ctx->listing_entry_fctx, - ctx->main_template_fctx, fctx, ctx->posts_fctx, 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; - - bc_slist_t *rv = NULL; - - const char *html_ext = bm_ctx_settings_lookup(ctx, "html_ext"); - - for (size_t i = 0; ctx->settings->pages[i] != NULL; i++) { - char *f = bm_generate_filename(ctx->short_output_dir, NULL, - ctx->settings->pages[i], html_ext); - rv = bc_slist_append(rv, bm_filectx_new(ctx, f, NULL, NULL)); - free(f); - } - - return rv; -} - -static int -pages_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bc_trie_t *args) -{ - 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(bm_ctx_settings_lookup(ctx, "date_format"))); - bc_trie_insert(variables, "MAKE_RULE", bc_strdup("pages")); - bc_trie_insert(variables, "MAKE_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 *s_fctx = s->data; - bm_filectx_t *o_fctx = o->data; - if (o_fctx == NULL) - continue; - if (bm_rule_need_rebuild(s, ctx->settings_fctx, NULL, - ctx->main_template_fctx, o_fctx, true)) - { - bc_trie_t *local = bc_trie_new(NULL); - bc_trie_insert(local, "MAKE_SLUG", s_fctx->slug); // no need to copy - rv = bm_exec_blogc(ctx, variables, local, false, NULL, ctx->main_template_fctx, - o_fctx, s, true); - bc_trie_free(local); - if (rv != 0) - break; - } - } - - bc_trie_free(variables); - - return rv; -} - - -// COPY FILES RULE - -static bc_slist_t* -copy_outputlist(bm_ctx_t *ctx) -{ - if (ctx == NULL || ctx->settings->copy == NULL) - return NULL; - - bc_slist_t *rv = NULL; - - // we iterate over ctx->copy_fctx list instead of ctx->settings->copy, - // because bm_ctx_new() expands directories into its files, recursively. - for (bc_slist_t *s = ctx->copy_fctx; s != NULL; s = s->next) { - char *f = bc_strdup_printf("%s/%s", ctx->short_output_dir, - ((bm_filectx_t*) s->data)->short_path); - rv = bc_slist_append(rv, bm_filectx_new(ctx, f, NULL, NULL)); - free(f); - } - - return rv; -} - -static int -copy_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bc_trie_t *args) -{ - if (ctx == NULL || ctx->settings->copy == NULL) - return 0; - - int rv = 0; - - bc_slist_t *s, *o; - - for (s = ctx->copy_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, NULL, o_fctx, true)) { - rv = bm_exec_native_cp(s->data, o_fctx, ctx->verbose); - if (rv != 0) - break; - } - } - - return rv; -} - - -// CLEAN RULE - -static int -clean_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bc_trie_t *args) -{ - int rv = 0; - - bc_slist_t *files = bm_rule_list_built_files(ctx); - for (bc_slist_t *l = files; l != NULL; l = l->next) { - bm_filectx_t *fctx = l->data; - if (fctx == NULL) - continue; - - if (fctx->readable) { - rv = bm_exec_native_rm(ctx->output_dir, fctx, ctx->verbose); - if (rv != 0) - break; - } - } - bc_slist_free_full(files, (bc_free_func_t) bm_filectx_free); - - if (!bm_exec_native_is_empty_dir(ctx->output_dir, NULL)) { - fprintf(stderr, "blogc-make: warning: output directory is not empty!\n"); - } - - return rv; -} - - -static int all_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bc_trie_t *args); - - -// RUNSERVER RULE - -static int -runserver_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bc_trie_t *args) -{ - return bm_httpd_run(&ctx, all_exec, outputs, args); -} - - -// WATCH RULE - -static int -watch_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bc_trie_t *args) -{ - return bm_reloader_run(&ctx, all_exec, outputs, args); -} - - -// ATOM DUMP RULE - -static int -atom_dump_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bc_trie_t *args) -{ - char *content = bm_atom_generate(ctx->settings); - if (content == NULL) - return 1; - printf("%s", content); - free(content); - return 0; -} - - -const bm_rule_t rules[] = { - { - .name = "all", - .help = "run all build rules", - .outputlist_func = NULL, - .exec_func = all_exec, - }, - { - .name = "index", - .help = "build website index from posts", - .outputlist_func = index_outputlist, - .exec_func = index_exec, - }, - { - .name = "atom", - .help = "build main atom feed from posts", - .outputlist_func = atom_outputlist, - .exec_func = atom_exec, - }, - { - .name = "atom_tags", - .help = "build atom feeds for each tag from posts", - .outputlist_func = atom_tags_outputlist, - .exec_func = atom_tags_exec, - }, - { - .name = "pagination", - .help = "build pagination pages from posts", - .outputlist_func = pagination_outputlist, - .exec_func = pagination_exec, - }, - { - .name = "pagination_tags", - .help = "build pagination pages for each tag from posts", - .outputlist_func = pagination_tags_outputlist, - .exec_func = pagination_tags_exec, - }, - { - .name = "posts", - .help = "build individual pages for each post", - .outputlist_func = posts_outputlist, - .exec_func = posts_exec, - }, - { - .name = "tags", - .help = "build post listings for each tag from posts", - .outputlist_func = tags_outputlist, - .exec_func = tags_exec, - }, - { - .name = "pages", - .help = "build individual pages for each page", - .outputlist_func = pages_outputlist, - .exec_func = pages_exec, - }, - { - .name = "copy", - .help = "copy static files from source directory to output directory", - .outputlist_func = copy_outputlist, - .exec_func = copy_exec, - }, - { - .name = "clean", - .help = "clean built files and empty directories in output directory", - .outputlist_func = NULL, - .exec_func = clean_exec, - }, - { - .name = "runserver", - .help = "run blogc-runserver pointing to output directory, if available,\n" - " rebuilding as needed\n" - " arguments: host (127.0.0.1), port (8080) and threads (20)", - .outputlist_func = NULL, - .exec_func = runserver_exec, - }, - { - .name = "watch", - .help = "watch for changes in the source files, rebuilding as needed", - .outputlist_func = NULL, - .exec_func = watch_exec, - }, - { - .name = "atom_dump", - .help = "dump default Atom feed template based on current settings", - .outputlist_func = NULL, - .exec_func = atom_dump_exec, - }, - {NULL, NULL, NULL, NULL}, -}; - - -// ALL RULE - -static int -all_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bc_trie_t *args) -{ - for (size_t i = 0; rules[i].name != NULL; i++) { - if (rules[i].outputlist_func == NULL) { - continue; - } - - int rv = bm_rule_execute(ctx, &(rules[i]), NULL); - if (rv != 0) { - return rv; - } - } - - return 0; -} - - -bc_trie_t* -bm_rule_parse_args(const char *sep) -{ - if (sep == NULL || *sep == '\0' || *sep != ':') - return NULL; - - bc_trie_t *rv = bc_trie_new(free); - char *end = (char*) sep + 1; - char *kv_sep; - while (NULL != (kv_sep = strchr(end, '='))) { - char *key = bc_strndup(end, kv_sep - end); - end = kv_sep + 1; - kv_sep = strchr(end, ','); - if (kv_sep == NULL) - kv_sep = strchr(end, '\0'); - char *value = bc_strndup(end, kv_sep - end); - bc_trie_insert(rv, key, value); - free(key); - if (*kv_sep == '\0') - break; - end = kv_sep + 1; - } - if (kv_sep == NULL) { - bc_trie_free(rv); - return NULL; - } - - return rv; -} - - -int -bm_rule_executor(bm_ctx_t *ctx, bc_slist_t *rule_list) -{ - if (ctx == NULL) - return 1; - - const bm_rule_t *rule = NULL; - int rv = 0; - - for (bc_slist_t *l = rule_list; l != NULL; l = l->next) { - - char *rule_str = l->data; - char *sep = strchr(rule_str, ':'); - - bc_trie_t *args = NULL; - if (sep == NULL) { - sep = strchr(rule_str, '\0'); - } - else { - args = bm_rule_parse_args(sep); - if (args == NULL) { - fprintf(stderr, "blogc-make: warning: failed to parse rule " - "arguments, ignoring: %s\n", rule_str); - } - } - - rule = NULL; - for (size_t i = 0; rules[i].name != NULL; i++) { - if (strlen(rules[i].name) != (sep - rule_str)) - continue; - if (0 == strncmp(rule_str, rules[i].name, sep - rule_str)) { - rule = &(rules[i]); - rv = bm_rule_execute(ctx, rule, args); - if (rv != 0) - return rv; - break; - } - } - if (rule == NULL) { - fprintf(stderr, "blogc-make: error: rule not found: %.*s\n", - (int) (sep - rule_str), rule_str); - rv = 1; - } - } - - return rv; -} - - -int -bm_rule_execute(bm_ctx_t *ctx, const bm_rule_t *rule, bc_trie_t *args) -{ - if (ctx == NULL || rule == NULL) - return 1; - - bc_slist_t *outputs = NULL; - if (rule->outputlist_func != NULL) { - outputs = rule->outputlist_func(ctx); - } - - int rv = rule->exec_func(ctx, outputs, args); - - 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 *listing_entry, 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); - if (listing_entry != NULL) - s = bc_slist_append(s, listing_entry); - - 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->tv_sec == output->tv_sec) { - if (source->tv_nsec > output->tv_nsec) { - rv = true; - break; - } - } - else if (source->tv_sec > output->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].outputlist_func == NULL) { - 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("\nhelper rules:\n"); - for (size_t i = 0; rules[i].name != NULL; i++) { - if (rules[i].outputlist_func == NULL) { - printf(" %-15s %s\n", rules[i].name, rules[i].help); - } - } - printf("\nbuild rules:\n"); - for (size_t i = 0; rules[i].name != NULL; i++) { - if (rules[i].outputlist_func != NULL) { - printf(" %-15s %s\n", rules[i].name, rules[i].help); - } - } -} diff --git a/src/blogc-make/rules.h b/src/blogc-make/rules.h deleted file mode 100644 index 9ff39ca..0000000 --- a/src/blogc-make/rules.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2020 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, - bc_trie_t *args); - -typedef struct { - const char *name; - const char *help; - bm_rule_outputlist_func_t outputlist_func; - bm_rule_exec_func_t exec_func; -} bm_rule_t; - -bc_trie_t* bm_rule_parse_args(const char *sep); -int bm_rule_executor(bm_ctx_t *ctx, bc_slist_t *rule_list); -int bm_rule_execute(bm_ctx_t *ctx, const bm_rule_t *rule, bc_trie_t *args); -bool bm_rule_need_rebuild(bc_slist_t *sources, bm_filectx_t *settings, - bm_filectx_t *listing_entry, 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 deleted file mode 100644 index 1079fc0..0000000 --- a/src/blogc-make/settings.c +++ /dev/null @@ -1,203 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 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 <stdbool.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"}, - {"atom_template", NULL}, // default: atom.c - {"main_template", "main.tmpl"}, - {"source_ext", ".txt"}, - {"listing_entry", NULL}, - {"posts_sort", NULL}, - - // pagination - {"pagination_prefix", "page"}, - {"posts_per_page", "10"}, - {"atom_posts_per_page", "10"}, - - // html - {"html_ext", "/index.html"}, - {"index_prefix", ""}, - {"post_prefix", "post"}, - {"tag_prefix", "tag"}, - {"html_order", "DESC"}, - - // atom - {"atom_prefix", "atom"}, - {"atom_ext", ".xml"}, - {"atom_order", "DESC"}, - {"atom_legacy_entry_id", NULL}, - - // generic - {"date_format", "%b %d, %Y, %I:%M %p GMT"}, - {"locale", NULL}, - - {NULL, NULL}, -}; - - -static const char* required_global[] = { - "AUTHOR_NAME", - "AUTHOR_EMAIL", - "SITE_TITLE", - "SITE_TAGLINE", - "BASE_DOMAIN", - NULL, -}; - - -static const char* list_sections[] = { - "posts", - "pages", - "copy", - "copy_files", // backward compatibility - "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->global = bc_trie_new(free); - rv->settings = bc_trie_new(free); - rv->posts = NULL; - rv->pages = NULL; - rv->copy = NULL; - rv->tags = NULL; - - // this is some code for compatibility with the [environment] section, - // even if I never released a version with it, but some people is using - // it already. - const char *section = NULL; - char **global = bc_config_list_keys(config, "global"); - if (global != NULL) { - section = "global"; - } - else { - global = bc_config_list_keys(config, "environment"); - if (global != NULL) { - section = "environment"; - } - else { - section = "global"; - } - } - - if (global != NULL) { - for (size_t i = 0; global[i] != NULL; i++) { - for (size_t j = 0; global[i][j] != '\0'; j++) { - if (j == 0) { - if (!(global[i][j] >= 'A' && global[i][j] <= 'Z')) { - *err = bc_error_new_printf(BLOGC_MAKE_ERROR_SETTINGS, - "Invalid [%s] key (first character must be uppercase): %s", - section, global[i]); - bc_strv_free(global); - bm_settings_free(rv); - rv = NULL; - goto cleanup; - } - continue; - } - if (!((global[i][j] >= 'A' && global[i][j] <= 'Z') || - (global[i][j] >= '0' && global[i][j] <= '9') || - global[i][j] == '_')) { - *err = bc_error_new_printf(BLOGC_MAKE_ERROR_SETTINGS, - "Invalid [%s] key (must be uppercase with '_' and digits after first character): %s", - section, global[i]); - bc_strv_free(global); - bm_settings_free(rv); - rv = NULL; - goto cleanup; - } - } - bc_trie_insert(rv->global, global[i], - bc_strdup(bc_config_get(config, section, global[i]))); - } - } - bc_strv_free(global); - - for (size_t i = 0; required_global[i] != NULL; i++) { - const char *value = bc_trie_lookup(rv->global, required_global[i]); - if (value == NULL || value[0] == '\0') { - *err = bc_error_new_printf(BLOGC_MAKE_ERROR_SETTINGS, - "[%s] key required but not found or empty: %s", section, - required_global[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->tags = bc_config_get_list(config, "tags"); - - // this is for backward compatibility too. - rv->copy = bc_config_get_list(config, "copy"); - if (rv->copy == NULL) - rv->copy = bc_config_get_list(config, "copy_files"); - -cleanup: - - bc_config_free(config); - - return rv; -} - - -void -bm_settings_free(bm_settings_t *settings) -{ - if (settings == NULL) - return; - bc_trie_free(settings->global); - bc_trie_free(settings->settings); - bc_strv_free(settings->posts); - bc_strv_free(settings->pages); - bc_strv_free(settings->copy); - bc_strv_free(settings->tags); - free(settings); -} diff --git a/src/blogc-make/settings.h b/src/blogc-make/settings.h deleted file mode 100644 index 69fdbb6..0000000 --- a/src/blogc-make/settings.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 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 <stddef.h> -#include "../common/error.h" -#include "../common/utils.h" - -typedef struct { - bc_trie_t *global; - bc_trie_t *settings; - char **posts; - char **pages; - char **copy; - char **tags; -} bm_settings_t; - -bm_settings_t* bm_settings_parse(const char *content, size_t content_len, - bc_error_t **err); -void bm_settings_free(bm_settings_t *settings); - -#endif /* _MAKE_SETTINGS_H */ diff --git a/src/blogc-make/utils.c b/src/blogc-make/utils.c deleted file mode 100644 index 8f69e44..0000000 --- a/src/blogc-make/utils.c +++ /dev/null @@ -1,128 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#include <errno.h> -#include <limits.h> -#include <stdbool.h> -#include <string.h> -#include <unistd.h> -#include "../common/error.h" -#include "../common/utils.h" - -#ifndef PATH_MAX -#define PATH_MAX 4096 -#endif - - -char* -bm_generate_filename(const char *dir, const char *prefix, const char *fname, - const char *ext) -{ - bool have_prefix = prefix != NULL && prefix[0] != '\0'; - bool have_fname = fname != NULL && fname[0] != '\0'; - bool have_ext = ext != NULL && ext[0] != '\0'; - bool have_ext_noslash = have_ext && ext[0] != '/'; - bool is_index = have_fname && have_ext && ( - (0 == strcmp(fname, "index")) && ext[0] == '/') && !have_prefix; - - bc_string_t *rv = bc_string_new(); - - if (dir != NULL && (have_prefix || have_fname || have_ext)) - bc_string_append(rv, dir); - - if ((have_prefix || have_fname || have_ext_noslash) && !is_index) - bc_string_append_c(rv, '/'); - - if (have_prefix) - bc_string_append(rv, prefix); - - // with fname we have posts, pages and tags - if (have_fname) { - if (have_prefix && have_fname && fname[0] != '/') - bc_string_append_c(rv, '/'); - if (!is_index) - bc_string_append(rv, fname); - } - - // no fname means index - else if (have_ext_noslash) { - if (have_fname) - bc_string_append_c(rv, '/'); - if (!have_prefix) - bc_string_append(rv, "index"); - } - - if (have_ext) - bc_string_append(rv, ext); - - if (rv->len == 0) { - bc_string_free(rv, true); - return NULL; - } - - return bc_string_free(rv, false); -} - - -char* -bm_generate_filename2(const char *dir, const char *prefix, const char *fname, - const char *prefix2, const char *fname2, const char *ext) -{ - bool have_prefix = prefix != NULL && prefix[0] != '\0'; - bool have_fname = fname != NULL && fname[0] != '\0'; - bool have_prefix2 = prefix2 != NULL && prefix2[0] != '\0'; - - bc_string_t *p = bc_string_new(); - - if (have_prefix) - bc_string_append(p, prefix); - - if (have_prefix && (have_fname || have_prefix2)) - bc_string_append_c(p, '/'); - - if (have_fname) - bc_string_append(p, fname); - - if (have_fname && have_prefix2) - bc_string_append_c(p, '/'); - - if (have_prefix2) - bc_string_append(p, prefix2); - - char *rv = bm_generate_filename(dir, p->str, fname2, ext); - bc_string_free(p, true); - - return rv; -} - - -char* -bm_abspath(const char *path, bc_error_t **err) -{ - if (err == NULL || *err != NULL) - return NULL; - - if (path[0] == '/') { - return bc_strdup(path); - } - - char cwd[PATH_MAX]; - if (NULL == getcwd(cwd, sizeof(cwd))) { - *err = bc_error_new_printf(BLOGC_MAKE_ERROR_UTILS, - "Failed to detect absolute path (%s): %s", path, strerror(errno)); - return NULL; - } - - if (cwd[0] != '/') { - *err = bc_error_new_printf(BLOGC_MAKE_ERROR_UTILS, - "Failed to get current working directory: %s", cwd); - return NULL; - } - - return bc_strdup_printf("%s/%s", cwd, path); -} diff --git a/src/blogc-make/utils.h b/src/blogc-make/utils.h deleted file mode 100644 index cbcfc0e..0000000 --- a/src/blogc-make/utils.h +++ /dev/null @@ -1,20 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 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_UTILS_H -#define _MAKE_UTILS_H - -#include "../common/error.h" - -char* bm_generate_filename(const char *dir, const char *prefix, const char *fname, - const char *ext); -char* bm_generate_filename2(const char *dir, const char *prefix, const char *fname, - const char *prefix2, const char *fname2, const char *ext); -char* bm_abspath(const char *path, bc_error_t **err); - -#endif /* _MAKE_UTILS_H */ diff --git a/src/blogc-runserver.c b/src/blogc-runserver.c new file mode 100644 index 0000000..2fbbbc7 --- /dev/null +++ b/src/blogc-runserver.c @@ -0,0 +1,386 @@ +/* + * 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 <event2/event.h> +#include <event2/http.h> +#include <event2/buffer.h> +#include <magic.h> +#include <signal.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include "utils.h" + + +/** + * this mapping is used to declare "supported" file types, that are forced over + * whatever detected by libmagic, but we will still use the charset provided by + * libmagic anyway. it also helps detecting index files when the client asks + * for a directory. + */ +static const struct content_type_map { + const char *mimetype; + const char *extension; + const char *index; +} content_types[] = { + {"text/html", "html", "index.html"}, + {"text/html", "htm", "index.htm"}, + {"text/xml", "xml", "index.xml"}, + {"text/plain", "txt", "index.txt"}, + {"text/css", "css", NULL}, + {"application/javascript", "js", NULL}, + {NULL, NULL, NULL} +}; + + +static magic_t magic_all = NULL; +static magic_t magic_charset = NULL; + + +static const char* +get_extension(const char *filename) +{ + const char *ext = NULL; + unsigned int i; + for (i = strlen(filename); i > 0; i--) { + if (filename[i] == '.') { + ext = filename + i + 1; + break; + } + } + if (i == 0) + return NULL; + return ext; +} + + +static char* +guess_content_type(const char *filename, int fd) +{ + int newfd; + + // try "supported" types first, and just use libmagic for charset + const char *extension = get_extension(filename); + if (extension == NULL) + goto libmagic; + const char *supported = NULL; + for (unsigned int i = 0; content_types[i].extension != NULL; i++) + if (0 == strcmp(content_types[i].extension, extension)) + supported = content_types[i].mimetype; + if (supported != NULL) { + newfd = dup(fd); + if (-1 != newfd) { + const char* charset = magic_descriptor(magic_charset, newfd); + close(newfd); + if (charset != NULL) + return sb_strdup_printf("%s; charset=%s", supported, charset); + } + return sb_strdup(supported); + } + +libmagic: + + // fallback to use libmagic for everything + newfd = dup(fd); + if (-1 != newfd) { + const char* content_type = magic_descriptor(magic_all, newfd); + close(newfd); + if (content_type != NULL) + return sb_strdup(content_type); + } + return sb_strdup("application/octet-stream"); +} + + +static void +handler(struct evhttp_request *request, void *ptr) +{ + const char *root = ptr; + const char *uri = evhttp_request_get_uri(request); + + struct evhttp_uri *decoded_uri = evhttp_uri_parse(uri); + if (decoded_uri == NULL) { + evhttp_send_error(request, 400, "Bad request"); + return; + } + + const char *path = evhttp_uri_get_path(decoded_uri); + if (path == NULL) + path = "/"; + + char *decoded_path = evhttp_uridecode(path, 0, NULL); + if (decoded_path == NULL) { + evhttp_send_error(request, 400, "Bad request"); + goto point1; + } + + char *abs_path = sb_strdup_printf("%s/%s", root, decoded_path); + char *real_path = realpath(abs_path, NULL); + free(abs_path); + + if (real_path == NULL) { + evhttp_send_error(request, 404, "Not found"); + goto point2; + } + + char *real_root = realpath(root, NULL); + if (real_root == NULL) { + evhttp_send_error(request, 500, "Internal server error"); + goto point3; + } + + if (0 != strncmp(real_root, real_path, strlen(real_root))) { + evhttp_send_error(request, 404, "Not found"); + goto point4; + } + + struct stat st; + if (0 > stat(real_path, &st)) { + evhttp_send_error(request, 404, "Not found"); + goto point4; + } + + bool add_slash = false; + + if (S_ISDIR(st.st_mode)) { + char *found = NULL; + + for (unsigned int i = 0; content_types[i].mimetype != NULL; i++) { + if (content_types[i].index == NULL) + continue; + char *f = sb_strdup_printf("%s/%s", real_path, + content_types[i].index); + if (0 == access(f, F_OK)) { + found = sb_strdup(f); + break; + } + free(f); + } + + if (found == NULL) { + evhttp_send_error(request, 403, "Forbidden"); + goto point4; + } + + size_t path_len = strlen(path); + if (path_len > 0 && path[path_len - 1] != '/') + add_slash = true; + + free(real_path); + real_path = found; + } + + int fd; + if ((fd = open(real_path, O_RDONLY)) < 0) { + evhttp_send_error(request, 500, "Internal server error"); + goto point4; + } + + char *type = guess_content_type(real_path, fd); + + if (fstat(fd, &st) < 0) { + evhttp_send_error(request, 500, "Internal server error"); + goto point5; + } + + struct evkeyvalq *headers = evhttp_request_get_output_headers(request); + + if (add_slash) { + char *tmp = sb_strdup_printf("%s/", path); + evhttp_add_header(headers, "Location", tmp); + free(tmp); + // production webservers usually returns 301 in such cases, but 302 is + // better for development/testing. + evhttp_send_reply(request, 302, "Found", NULL); + goto point5; + } + + evhttp_add_header(headers, "Content-Type", type); + char *content_length = sb_strdup_printf("%zu", st.st_size); + evhttp_add_header(headers, "Content-Length", content_length); + free(content_length); + + struct evbuffer *evb = evbuffer_new(); + evbuffer_add_file(evb, fd, 0, st.st_size); + evhttp_send_reply(request, 200, "OK", evb); + +point5: + free(type); +point4: + free(real_root); +point3: + free(real_path); +point2: + free(decoded_path); +point1: + evhttp_uri_free(decoded_uri); +} + + +static int +runserver(const char *address, unsigned short port, const char *root) +{ + struct event_base *base = event_base_new(); + if (base == NULL) { + fprintf(stderr, "error: failed to initialize event base\n"); + return 1; + } + + struct evhttp *http = evhttp_new(base); + if (http == NULL) { + fprintf(stderr, "error: failed to initialize HTTP server\n"); + return 1; + } + + evhttp_set_gencb(http, handler, (char*) root); + + evhttp_set_allowed_methods(http, EVHTTP_REQ_GET | EVHTTP_REQ_HEAD); + + if (0 != evhttp_bind_socket(http, address, port)) { + fprintf(stderr, "error: failed to bind socket to %s:%d\n", address, + port); + return 1; + } + + fprintf(stderr, " * Running on http://%s:%d/\n", address, port); + + event_base_dispatch(base); + + return 0; +} + + +static void +print_help(void) +{ + printf( + "usage:\n" + " blogc-runserver [-h] [-v] [-t HOST] [-p PORT] DOCROOT\n" + " - A simple HTTP server to test blogc websites.\n" + "\n" + "positional arguments:\n" + " DOCROOT document root directory\n" + "\n" + "optional arguments:\n" + " -h show this help message and exit\n" + " -v show version and exit\n" + " -t HOST set server listen address (default: 127.0.0.1)\n" + " -p PORT set server listen port (default: 8080)\n"); +} + + +static void +print_usage(void) +{ + printf("usage: blogc-runserver [-h] [-v] [-t HOST] [-p PORT] DOCROOT\n"); +} + + +int +main(int argc, char **argv) +{ + signal(SIGPIPE, SIG_IGN); + + int rv = 0; + char *host = NULL; + char *docroot = NULL; + unsigned short port = 8080; + + unsigned int args = 0; + + 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 't': + if (argv[i][2] != '\0') + host = sb_strdup(argv[i] + 2); + else + host = sb_strdup(argv[++i]); + break; + case 'p': + if (argv[i][2] != '\0') + port = strtoul(argv[i] + 2, NULL, 10); + else + port = strtoul(argv[++i], NULL, 10); + break; + default: + print_usage(); + fprintf(stderr, "blogc-runserver: error: invalid " + "argument: -%c\n", argv[i][1]); + rv = 2; + goto cleanup; + } + } + else { + if (args > 0) { + print_usage(); + fprintf(stderr, "blogc-runserver: error: only one positional " + "argument allowed\n"); + rv = 2; + goto cleanup; + } + args++; + docroot = sb_strdup(argv[i]); + } + } + + if (docroot == NULL) { + print_usage(); + fprintf(stderr, "blogc-runserver: error: document root directory " + "required\n"); + rv = 2; + goto cleanup; + } + + if (host == NULL) + host = sb_strdup("127.0.0.1"); + + magic_all = magic_open(MAGIC_MIME); + magic_charset = magic_open(MAGIC_MIME_ENCODING); + if (magic_all == NULL || magic_charset == NULL) { + fprintf(stderr, "error: failed to initialize libmagic\n"); + rv = 1; + goto cleanup; + } + + if ((0 != magic_load(magic_all, NULL)) || + (0 != magic_load(magic_charset, NULL))) + { + fprintf(stderr, "error: failed to load libmagic data\n"); + magic_close(magic_all); + magic_close(magic_charset); + rv = 1; + goto cleanup; + } + + rv = runserver(host, port, docroot); + + magic_close(magic_all); + magic_close(magic_charset); + +cleanup: + free(host); + free(docroot); + + return rv; +} diff --git a/src/blogc-runserver/httpd-utils.c b/src/blogc-runserver/httpd-utils.c deleted file mode 100644 index 9d374ae..0000000 --- a/src/blogc-runserver/httpd-utils.c +++ /dev/null @@ -1,105 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 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 <string.h> -#include <unistd.h> -#include "../common/utils.h" -#include "httpd-utils.h" - - -char* -br_readline(int socket) -{ - bc_string_t *rv = bc_string_new(); - char buffer[READLINE_BUFFER_SIZE]; - ssize_t len; - bool end = false; - - while ((len = read(socket, buffer, READLINE_BUFFER_SIZE)) > 0) { - if (!end) { - for (ssize_t i = 0; i < len; i++) { - if (buffer[i] == '\r' || buffer[i] == '\n' || buffer[i] == '\0') { - // we finished "recording", but still need to exhaust - // request data. - end = true; - break; - } - bc_string_append_c(rv, buffer[i]); - } - } - if (len < READLINE_BUFFER_SIZE) { - break; - } - } - - return bc_string_free(rv, false); -} - - -int -br_hextoi(const char c) -{ - if (c >= '0' && c <= '9') - return c - '0'; - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - return -1; -} - - -char* -br_urldecode(const char *str) -{ - bc_string_t *rv = bc_string_new(); - - for (size_t i = 0; i < strlen(str); i++) { - switch (str[i]) { - case '%': - if (i + 2 < strlen(str)) { - int p1 = br_hextoi(str[i + 1]) * 16; - int p2 = br_hextoi(str[i + 2]); - if (p1 >= 0 && p2 >= 0) { - bc_string_append_c(rv, p1 + p2); - i += 2; - continue; - } - } - bc_string_append_c(rv, '%'); - break; - case '+': - bc_string_append_c(rv, ' '); - break; - default: - bc_string_append_c(rv, str[i]); - } - } - - return bc_string_free(rv, false); -} - - -const char* -br_get_extension(const char *filename) -{ - const char *ext = NULL; - size_t i; - for (i = strlen(filename); i > 0; i--) { - if (filename[i] == '.') { - ext = filename + i + 1; - break; - } - if ((filename[i] == '/') || (filename[i] == '\\')) - return NULL; - } - if (i == 0) - return NULL; - return ext; -} diff --git a/src/blogc-runserver/httpd-utils.h b/src/blogc-runserver/httpd-utils.h deleted file mode 100644 index 06ad6e1..0000000 --- a/src/blogc-runserver/httpd-utils.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#ifndef _HTTPD_UTILS_H -#define _HTTPD_UTILS_H - -#define READLINE_BUFFER_SIZE 2048 - -char* br_readline(int socket); -int br_hextoi(const char c); -char* br_urldecode(const char *str); -const char* br_get_extension(const char *filename); - -#endif /* _HTTPD_UTILS_H */ diff --git a/src/blogc-runserver/httpd.c b/src/blogc-runserver/httpd.c deleted file mode 100644 index fe98288..0000000 --- a/src/blogc-runserver/httpd.c +++ /dev/null @@ -1,413 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2020 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#include <errno.h> -#include <stdio.h> -#include <stdbool.h> -#include <stdlib.h> -#include <string.h> -#include <sys/stat.h> -#include <unistd.h> -#include <sys/types.h> -#include <sys/socket.h> -#include <netdb.h> -#include <netinet/in.h> -#include <arpa/inet.h> -#include <pthread.h> -#include "../common/error.h" -#include "../common/file.h" -#include "../common/utils.h" -#include "mime.h" -#include "httpd-utils.h" - -#define LISTEN_BACKLOG 100 - -typedef struct { - pthread_t thread; - bool initialized; -} thread_data_t; - -typedef struct { - size_t thread_id; - int socket; - char *ip; - const char *docroot; -} request_data_t; - - -static void -error(int socket, int status_code, const char *error) -{ - char *str = bc_strdup_printf( - "HTTP/1.0 %d %s\r\n" - "Content-Type: text/html\r\n" - "Content-Length: %zu\r\n" - "Connection: close\r\n" - "\r\n" - "<h1>%s</h1>\n", status_code, error, strlen(error) + 10, error); - size_t str_len = strlen(str); - if (str_len != write(socket, str, str_len)) { - fprintf(stderr, "warning: Failed to write full response header!\n"); - } - free(str); -} - - -static void* -handle_request(void *arg) -{ - request_data_t *req = arg; - size_t thread_id = req->thread_id; - int client_socket = req->socket; - char *ip = req->ip; - const char *docroot = req->docroot; - free(arg); - - char *conn_line = br_readline(client_socket); - if (conn_line == NULL || conn_line[0] == '\0') - goto point0; - - unsigned short status_code = 200; - - char **pieces = bc_str_split(conn_line, ' ', 3); - if (bc_strv_length(pieces) != 3) { - status_code = 400; - error(client_socket, 400, "Bad Request"); - goto point1; - } - - if (strcmp(pieces[0], "GET") != 0) { - status_code = 405; - error(client_socket, 405, "Method Not Allowed"); - goto point1; - } - - char **pieces2 = bc_str_split(pieces[1], '?', 2); - char *path = br_urldecode(pieces2[0]); - bc_strv_free(pieces2); - - if (path == NULL) { - status_code = 400; - error(client_socket, 400, "Bad Request"); - goto point2; - } - - char *abs_path = bc_strdup_printf("%s/%s", docroot, path); - char *real_path = realpath(abs_path, NULL); - free(abs_path); - - if (real_path == NULL) { - if (errno == ENOENT) { - status_code = 404; - error(client_socket, 404, "Not Found"); - } - else { - status_code = 500; - error(client_socket, 500, "Internal Server Error"); - } - goto point2; - } - - char *real_root = realpath(docroot, NULL); - if (real_root == NULL) { - status_code = 500; - error(client_socket, 500, "Internal Server Error"); - goto point3; - } - - if (0 != strncmp(real_root, real_path, strlen(real_root))) { - status_code = 404; - error(client_socket, 404, "Not Found"); - goto point4; - } - - struct stat st; - if (0 > stat(real_path, &st)) { - status_code = 404; - error(client_socket, 404, "Not Found"); - goto point4; - } - - bool add_slash = false; - - if (S_ISDIR(st.st_mode)) { - char *found = br_mime_guess_index(real_path); - - if (found == NULL) { - status_code = 403; - error(client_socket, 403, "Forbidden"); - goto point4; - } - - size_t path_len = strlen(path); - if (path_len > 0 && path[path_len - 1] != '/') - add_slash = true; - - free(real_path); - real_path = found; - } - - if (0 != access(real_path, F_OK)) { - status_code = 500; - error(client_socket, 500, "Internal Server Error"); - goto point4; - } - - if (add_slash) { - // production webservers usually returns 301 in such cases, but 302 is - // better for development/testing. - char *tmp = bc_strdup_printf( - "HTTP/1.0 302 Found\r\n" - "Location: %s/\r\n" - "Content-Length: 0\r\n" - "Connection: close\r\n" - "\r\n", path); - status_code = 302; - size_t tmp_len = strlen(tmp); - if (tmp_len != write(client_socket, tmp, tmp_len)) { - fprintf(stderr, "warning: Failed to write full response header!\n"); - } - free(tmp); - goto point4; - } - - size_t len; - bc_error_t *err = NULL; - char* contents = bc_file_get_contents(real_path, false, &len, &err); - if (err != NULL) { - status_code = 500; - error(client_socket, 500, "Internal Server Error"); - bc_error_free(err); - goto point4; - } - - char *out = bc_strdup_printf( - "HTTP/1.0 200 OK\r\n" - "Content-Type: %s\r\n" - "Content-Length: %zu\r\n" - "Connection: close\r\n" - "\r\n", br_mime_guess_content_type(real_path), len); - size_t out_len = strlen(out); - if (out_len != write(client_socket, out, out_len)) { - fprintf(stderr, "warning: Failed to write full response header!\n"); - } - free(out); - - if (len != write(client_socket, contents, len)) { - fprintf(stderr, "warning: Failed to write full response body!\n"); - } - free(contents); - -point4: - free(real_root); -point3: - free(real_path); -point2: - free(path); -point1: - fprintf(stderr, "[Thread-%zu] %s - - \"%s\" %d\n", thread_id + 1, - ip, conn_line, status_code); - free(conn_line); - bc_strv_free(pieces); -point0: - free(ip); - close(client_socket); - return NULL; -} - - -static char* -br_httpd_get_ip(int af, const struct sockaddr *addr) -{ - char host[INET6_ADDRSTRLEN]; - if (af == AF_INET6) { - struct sockaddr_in6 *a = (struct sockaddr_in6*) addr; - inet_ntop(af, &(a->sin6_addr), host, INET6_ADDRSTRLEN); - } - else { - struct sockaddr_in *a = (struct sockaddr_in*) addr; - inet_ntop(af, &(a->sin_addr), host, INET6_ADDRSTRLEN); - } - return bc_strdup(host); -} - - -static uint16_t -br_httpd_get_port(int af, const struct sockaddr *addr) -{ - in_port_t port = 0; - if (af == AF_INET6) { - struct sockaddr_in6 *a = (struct sockaddr_in6*) addr; - port = a->sin6_port; - } - else { - struct sockaddr_in *a = (struct sockaddr_in*) addr; - port = a->sin_port; - } - return ntohs(port); -} - - -int -br_httpd_run(const char *host, const char *port, const char *docroot, - size_t max_threads) -{ - int err; - struct addrinfo *result; - struct addrinfo hints = { - .ai_family = AF_UNSPEC, - .ai_socktype = SOCK_STREAM, - .ai_flags = AI_PASSIVE, - .ai_protocol = 0, - .ai_canonname = NULL, - .ai_addr = NULL, - .ai_next = NULL, - }; - if (0 != (err = getaddrinfo(host, port, &hints, &result))) { - fprintf(stderr, "Failed to get host:port info: %s\n", - gai_strerror(err)); - return 1; - } - - thread_data_t threads[max_threads]; - for (size_t i = 0; i < max_threads; i++) - threads[i].initialized = false; - - int rv = 0; - - struct addrinfo *rp; - int server_socket = 0; - - int ai_family = 0; - char *final_host = NULL; - uint16_t final_port = 0; - - for (rp = result; rp != NULL; rp = rp->ai_next) { - final_host = br_httpd_get_ip(rp->ai_family, rp->ai_addr); - final_port = br_httpd_get_port(rp->ai_family, rp->ai_addr); - server_socket = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); - if (server_socket == -1) { - if (rp->ai_next == NULL) { - fprintf(stderr, "Failed to open server socket (%s:%d): %s\n", - final_host, final_port, strerror(errno)); - rv = 1; - goto cleanup0; - } - continue; - } - int value = 1; - if (0 > setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &value, - sizeof(int))) - { - if (rp->ai_next == NULL) { - fprintf(stderr, "Failed to set socket option (%s:%d): %s\n", - final_host, final_port, strerror(errno)); - rv = 1; - goto cleanup; - } - close(server_socket); - continue; - } - if (0 == bind(server_socket, rp->ai_addr, rp->ai_addrlen)) { - ai_family = rp->ai_family; - break; - } - else { - if (rp->ai_next == NULL) { - fprintf(stderr, "Failed to bind to server socket (%s:%d): %s\n", - final_host, final_port, strerror(errno)); - rv = 1; - goto cleanup; - } - } - free(final_host); - close(server_socket); - } - - if (-1 == listen(server_socket, LISTEN_BACKLOG)) { - fprintf(stderr, "Failed to listen to server socket (%s:%d): %s\n", - final_host, final_port, strerror(errno)); - rv = 1; - goto cleanup; - } - - fprintf(stderr, " * Running on http://"); - if (ai_family == AF_INET6) - fprintf(stderr, "[%s]", final_host); - else - fprintf(stderr, "%s", final_host); - if (final_port != 80) - fprintf(stderr, ":%d", final_port); - fprintf(stderr, "/ (max threads: %zu)\n" - "\n" - "WARNING!!! This is a development server, DO NOT RUN IT IN PRODUCTION!\n" - "\n", max_threads); - - size_t current_thread = 0; - - while (1) { - struct sockaddr_in6 addr6; - struct sockaddr_in addr; - - socklen_t addrlen; - struct sockaddr *client_addr = NULL; - - if (ai_family == AF_INET6) { - addrlen = sizeof(addr6); - client_addr = (struct sockaddr*) &addr6; - } - else { - addrlen = sizeof(addr); - client_addr = (struct sockaddr*) &addr; - } - - int client_socket = accept(server_socket, client_addr, &addrlen); - if (client_socket == -1) { - fprintf(stderr, "Failed to accept connection: %s\n", strerror(errno)); - rv = 1; - goto cleanup; - } - - request_data_t *arg = malloc(sizeof(request_data_t)); - arg->thread_id = current_thread; - arg->socket = client_socket; - arg->ip = br_httpd_get_ip(ai_family, client_addr); - arg->docroot = docroot; - - if (threads[current_thread].initialized) { - if (pthread_join(threads[current_thread].thread, NULL) != 0) { - fprintf(stderr, "Failed to join thread\n"); - free(arg->ip); - free(arg); - rv = 1; - goto cleanup; - } - } - - if (pthread_create(&(threads[current_thread].thread), NULL, - handle_request, arg) != 0) - { - fprintf(stderr, "Failed to create thread\n"); - rv = 1; - goto cleanup; - } - - threads[current_thread++].initialized = true; - - if (current_thread >= max_threads) - current_thread = 0; - } - -cleanup: - close(server_socket); - -cleanup0: - free(final_host); - freeaddrinfo(result); - return rv; -} diff --git a/src/blogc-runserver/httpd.h b/src/blogc-runserver/httpd.h deleted file mode 100644 index b71a8d9..0000000 --- a/src/blogc-runserver/httpd.h +++ /dev/null @@ -1,15 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#ifndef _HTTPD_H -#define _HTTPD_H - -int br_httpd_run(const char *host, const char *port, const char *docroot, - size_t max_threads); - -#endif /* _HTTPD_H */ diff --git a/src/blogc-runserver/main.c b/src/blogc-runserver/main.c deleted file mode 100644 index 1c5be29..0000000 --- a/src/blogc-runserver/main.c +++ /dev/null @@ -1,153 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 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 <signal.h> -#include <stdio.h> -#include <stdlib.h> -#include "../common/utils.h" -#include "httpd.h" - - -static void -print_help(const char *default_host, const char *default_port) -{ - printf( - "usage:\n" - " blogc-runserver [-h] [-v] [-t HOST] [-p PORT] [-m THREADS] DOCROOT\n" - " - A simple HTTP server to test blogc websites.\n" - "\n" - "positional arguments:\n" - " DOCROOT document root directory\n" - "\n" - "optional arguments:\n" - " -h show this help message and exit\n" - " -v show version and exit\n" - " -t HOST set server listen address (default: %s)\n" - " -p PORT set server listen port (default: %s)\n" - " -m THREADS set maximum number of threads to spawn (default: 20)\n", - default_host, default_port); -} - - -static void -print_usage(void) -{ - printf("usage: blogc-runserver [-h] [-v] [-t HOST] [-p PORT] [-m THREADS] DOCROOT\n"); -} - - -int -main(int argc, char **argv) -{ - struct sigaction new_action; - new_action.sa_handler = SIG_IGN; - sigemptyset(&new_action.sa_mask); - new_action.sa_flags = 0; - sigaction(SIGPIPE, &new_action, NULL); - - int rv = 0; - char *host = NULL; - char *port = NULL; - char *docroot = NULL; - size_t max_threads = 20; - char *ptr; - char *endptr; - - char *tmp_host = getenv("BLOGC_RUNSERVER_DEFAULT_HOST"); - char *default_host = bc_strdup(tmp_host != NULL ? tmp_host : "127.0.0.1"); - char *tmp_port = getenv("BLOGC_RUNSERVER_DEFAULT_PORT"); - char *default_port = bc_strdup(tmp_port != NULL ? tmp_port : "8080"); - - size_t args = 0; - - for (size_t i = 1; i < argc; i++) { - if (argv[i][0] == '-') { - switch (argv[i][1]) { - case 'h': - print_help(default_host, default_port); - goto cleanup; - case 'v': - printf("%s\n", PACKAGE_STRING); - goto cleanup; - case 't': - if (argv[i][2] != '\0') - host = bc_strdup(argv[i] + 2); - else - host = bc_strdup(argv[++i]); - break; - case 'p': - if (argv[i][2] != '\0') - port = bc_strdup(argv[i] + 2); - else - port = bc_strdup(argv[++i]); - break; - case 'm': - if (argv[i][2] != '\0') - ptr = argv[i] + 2; - else - ptr = argv[++i]; - max_threads = strtoul(ptr, &endptr, 10); - if (*ptr != '\0' && *endptr != '\0') - fprintf(stderr, "blogc-runserver: warning: invalid value " - "for -m argument: %s. using %zu instead\n", ptr, max_threads); - break; - default: - print_usage(); - fprintf(stderr, "blogc-runserver: error: invalid " - "argument: -%c\n", argv[i][1]); - rv = 1; - goto cleanup; - } - } - else { - if (args > 0) { - print_usage(); - fprintf(stderr, "blogc-runserver: error: only one positional " - "argument allowed\n"); - rv = 1; - goto cleanup; - } - args++; - docroot = bc_strdup(argv[i]); - } - } - - if (docroot == NULL) { - print_usage(); - fprintf(stderr, "blogc-runserver: error: document root directory " - "required\n"); - rv = 1; - goto cleanup; - } - - if (max_threads <= 0 || max_threads > 1000) { - print_usage(); - fprintf(stderr, "blogc-runserver: error: invalid value for -m. " - "Must be integer > 0 and <= 1000\n"); - rv = 1; - goto cleanup; - } - - rv = br_httpd_run( - host != NULL ? host : default_host, - port != NULL ? port : default_port, - docroot, max_threads); - -cleanup: - free(default_host); - free(default_port); - free(host); - free(port); - free(docroot); - - return rv; -} diff --git a/src/blogc-runserver/mime.c b/src/blogc-runserver/mime.c deleted file mode 100644 index 636c496..0000000 --- a/src/blogc-runserver/mime.c +++ /dev/null @@ -1,164 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 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 <unistd.h> -#include "../common/utils.h" -#include "httpd-utils.h" - - -// mime types with index should be in the begin of the list. first NULL -// index aborts the lookup, for optimization -static const struct content_type_map { - const char *mimetype; - const char *extension; - const char *index; -} content_types[] = { - - // with index - {"text/html", "html", "index.html"}, - {"text/html", "htm", "index.htm"}, - {"text/html", "shtml", "index.shtml"}, - {"text/xml", "xml", "index.xml"}, - {"text/plain", "txt", "index.txt"}, - {"application/xhtml+xml", "xhtml", "index.xhtml"}, - - // without index - {"text/css", "css", NULL}, - {"image/gif", "gif", NULL}, - {"image/jpeg", "jpeg", NULL}, - {"image/jpeg", "jpg", NULL}, - {"application/javascript", "js", NULL}, - {"application/atom+xml", "atom", NULL}, - {"application/rss+xml", "rss", NULL}, - {"text/mathml", "mml", NULL}, - {"text/vnd.sun.j2me.app-descriptor", "jad", NULL}, - {"text/vnd.wap.wml", "wml", NULL}, - {"text/x-component", "htc", NULL}, - {"image/png", "png", NULL}, - {"image/tiff", "tif", NULL}, - {"image/tiff", "tiff", NULL}, - {"image/vnd.wap.wbmp", "wbmp", NULL}, - {"image/x-icon", "ico", NULL}, - {"image/x-jng", "jng", NULL}, - {"image/x-ms-bmp", "bmp", NULL}, - {"image/svg+xml", "svg", NULL}, - {"image/svg+xml", "svgz", NULL}, - {"image/webp", "webp", NULL}, - {"application/font-woff", "woff", NULL}, - {"application/java-archive", "jar", NULL}, - {"application/java-archive", "war", NULL}, - {"application/java-archive", "ear", NULL}, - {"application/json", "json", NULL}, - {"application/mac-binhex40", "hqx", NULL}, - {"application/msword", "doc", NULL}, - {"application/pdf", "pdf", NULL}, - {"application/postscript", "ps", NULL}, - {"application/postscript", "eps", NULL}, - {"application/postscript", "ai", NULL}, - {"application/rtf", "rtf", NULL}, - {"application/vnd.apple.mpegurl", "m3u8", NULL}, - {"application/vnd.ms-excel", "xls", NULL}, - {"application/vnd.ms-fontobject", "eot", NULL}, - {"application/vnd.ms-powerpoint", "ppt", NULL}, - {"application/vnd.wap.wmlc", "wmlc", NULL}, - {"application/vnd.google-earth.kml+xml", "kml", NULL}, - {"application/vnd.google-earth.kmz", "kmz", NULL}, - {"application/x-7z-compressed", "7z", NULL}, - {"application/x-cocoa", "cco", NULL}, - {"application/x-java-archive-diff", "jardiff", NULL}, - {"application/x-java-jnlp-file", "jnlp", NULL}, - {"application/x-makeself", "run", NULL}, - {"application/x-perl", "pl", NULL}, - {"application/x-perl", "pm", NULL}, - {"application/x-pilot", "prc", NULL}, - {"application/x-pilot", "pdb", NULL}, - {"application/x-rar-compressed", "rar", NULL}, - {"application/x-redhat-package-manager", "rpm", NULL}, - {"application/x-sea", "sea", NULL}, - {"application/x-shockwave-flash", "swf", NULL}, - {"application/x-stuffit", "sit", NULL}, - {"application/x-tcl", "tcl", NULL}, - {"application/x-tcl", "tk", NULL}, - {"application/x-x509-ca-cert", "der", NULL}, - {"application/x-x509-ca-cert", "pem", NULL}, - {"application/x-x509-ca-cert", "crt", NULL}, - {"application/x-xpinstall", "xpi", NULL}, - {"application/xspf+xml", "xspf", NULL}, - {"application/zip", "zip", NULL}, - {"application/octet-stream", "bin", NULL}, - {"application/octet-stream", "exe", NULL}, - {"application/octet-stream", "dll", NULL}, - {"application/octet-stream", "deb", NULL}, - {"application/octet-stream", "dmg", NULL}, - {"application/octet-stream", "iso", NULL}, - {"application/octet-stream", "img", NULL}, - {"application/octet-stream", "msi", NULL}, - {"application/octet-stream", "msp", NULL}, - {"application/octet-stream", "msm", NULL}, - {"application/vnd.openxmlformats-officedocument.wordprocessingml.document", "docx", NULL}, - {"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "xlsx", NULL}, - {"application/vnd.openxmlformats-officedocument.presentationml.presentation", "pptx", NULL}, - {"audio/midi", "mid", NULL}, - {"audio/midi", "midi", NULL}, - {"audio/midi", "kar", NULL}, - {"audio/mpeg", "mp3", NULL}, - {"audio/ogg", "ogg", NULL}, - {"audio/x-m4a", "m4a", NULL}, - {"audio/x-realaudio", "ra", NULL}, - {"video/3gpp", "3gpp", NULL}, - {"video/3gpp", "3gp", NULL}, - {"video/mp2t", "ts", NULL}, - {"video/mp4", "mp4", NULL}, - {"video/mpeg", "mpeg", NULL}, - {"video/mpeg", "mpg", NULL}, - {"video/quicktime", "mov", NULL}, - {"video/webm", "webm", NULL}, - {"video/x-flv", "flv", NULL}, - {"video/x-m4v", "m4v", NULL}, - {"video/x-mng", "mng", NULL}, - {"video/x-ms-asf", "asx", NULL}, - {"video/x-ms-asf", "asf", NULL}, - {"video/x-ms-wmv", "wmv", NULL}, - {"video/x-msvideo", "avi", NULL}, - {NULL, NULL, NULL} -}; - - -const char* -br_mime_guess_content_type(const char *filename) -{ - const char *extension = br_get_extension(filename); - if (extension == NULL) - goto default_type; - for (size_t i = 0; content_types[i].extension != NULL; i++) { - if (0 == strcmp(content_types[i].extension, extension)) { - return content_types[i].mimetype; - } - } - -default_type: - return "application/octet-stream"; -} - - -char* -br_mime_guess_index(const char *path) -{ - char *found = NULL; - for (size_t i = 0; content_types[i].index != NULL; i++) { - char *f = bc_strdup_printf("%s/%s", path, content_types[i].index); - if (0 == access(f, F_OK)) { - found = f; - break; - } - free(f); - } - return found; -} diff --git a/src/blogc-runserver/mime.h b/src/blogc-runserver/mime.h deleted file mode 100644 index b9fb013..0000000 --- a/src/blogc-runserver/mime.h +++ /dev/null @@ -1,15 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#ifndef _MIME_H -#define _MIME_H - -const char* br_mime_guess_content_type(const char *filename); -char* br_mime_guess_index(const char *path); - -#endif /* _MIME_H */ diff --git a/src/blogc.c b/src/blogc.c new file mode 100644 index 0000000..c90070d --- /dev/null +++ b/src/blogc.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. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif /* HAVE_CONFIG_H */ + +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif /* HAVE_SYS_STAT_H */ + +#include <errno.h> +#include <locale.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "debug.h" +#include "template-parser.h" +#include "loader.h" +#include "renderer.h" +#include "error.h" +#include "utf8.h" +#include "utils.h" + +#ifndef PACKAGE_VERSION +#define PACKAGE_VERSION "Unknown" +#endif + + +static void +blogc_print_help(void) +{ + printf( + "usage:\n" + " blogc [-h] [-v] [-d] [-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" + " -d enable debug\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] [-d] [-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; +#ifdef HAVE_SYS_STAT_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 debug = false; + 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 'd': + debug = true; + break; + 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) { + if (!blogc_utf8_validate((uint8_t*) tmp, strlen(tmp))) { + fprintf(stderr, "blogc: error: invalid value for " + "-D (must be valid UTF-8 string): %s\n", tmp); + goto cleanup; + } + 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; + } + + 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 cleanup2; + } + + if (template == NULL) { + blogc_print_usage(); + fprintf(stderr, "blogc: error: argument -t is required when rendering content\n"); + 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 (debug) + blogc_debug_template(l); + + 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/blogc/content-parser.h b/src/blogc/content-parser.h deleted file mode 100644 index a321155..0000000 --- a/src/blogc/content-parser.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2020 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#ifndef _CONTENT_PARSER_H -#define _CONTENT_PARSER_H - -#include <stddef.h> -#include <stdbool.h> -#include "../common/utils.h" - -char* blogc_slugify(const char *str); -char* blogc_htmlentities(const char *str); -char* blogc_fix_description(const char *paragraph); -char* blogc_content_parse_inline(const char *src); -bool blogc_is_ordered_list_item(const char *str, size_t prefix_len); -char* blogc_content_parse(const char *src, size_t *end_excerpt, - char **first_header, char **description, char **endl, - bc_slist_t **headers); - -#endif /* _CONTENT_PARSER_H */ diff --git a/src/blogc/debug.c b/src/blogc/debug.c deleted file mode 100644 index 11e7973..0000000 --- a/src/blogc/debug.c +++ /dev/null @@ -1,80 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#include <stdio.h> - -#include "template-parser.h" -#include "../common/utils.h" -#include "debug.h" - - -static const char* -get_operator(blogc_template_operator_t op) -{ - if (op & BLOGC_TEMPLATE_OP_NEQ) - return "!="; - if (op & BLOGC_TEMPLATE_OP_EQ) { - if (op & BLOGC_TEMPLATE_OP_LT) - return "<="; - else if (op & BLOGC_TEMPLATE_OP_GT) - return ">="; - return "=="; - } - if (op & BLOGC_TEMPLATE_OP_LT) - return "<"; - else if (op & BLOGC_TEMPLATE_OP_GT) - return ">"; - return ""; -} - - -void -blogc_debug_template(bc_slist_t *ast) -{ - for (bc_slist_t *tmp = ast; tmp != NULL; tmp = tmp->next) { - blogc_template_node_t *data = tmp->data; - fprintf(stderr, "DEBUG: <TEMPLATE "); - switch (data->type) { - case BLOGC_TEMPLATE_NODE_IFDEF: - fprintf(stderr, "IFDEF: %s", data->data[0]); - break; - case BLOGC_TEMPLATE_NODE_IFNDEF: - fprintf(stderr, "IFNDEF: %s", data->data[0]); - break; - case BLOGC_TEMPLATE_NODE_IF: - fprintf(stderr, "IF: %s %s %s", data->data[0], - get_operator(data->op), data->data[1]); - break; - case BLOGC_TEMPLATE_NODE_ELSE: - fprintf(stderr, "ELSE"); - break; - case BLOGC_TEMPLATE_NODE_ENDIF: - fprintf(stderr, "ENDIF"); - break; - case BLOGC_TEMPLATE_NODE_FOREACH: - fprintf(stderr, "FOREACH: %s", data->data[0]); - break; - case BLOGC_TEMPLATE_NODE_ENDFOREACH: - fprintf(stderr, "ENDFOREACH"); - break; - case BLOGC_TEMPLATE_NODE_BLOCK: - fprintf(stderr, "BLOCK: %s", data->data[0]); - break; - case BLOGC_TEMPLATE_NODE_ENDBLOCK: - fprintf(stderr, "ENDBLOCK"); - break; - case BLOGC_TEMPLATE_NODE_VARIABLE: - fprintf(stderr, "VARIABLE: %s", data->data[0]); - break; - case BLOGC_TEMPLATE_NODE_CONTENT: - fprintf(stderr, "CONTENT: `%s`", data->data[0]); - break; - } - fprintf(stderr, ">\n"); - } -} diff --git a/src/blogc/filelist-parser.c b/src/blogc/filelist-parser.c deleted file mode 100644 index 984f25f..0000000 --- a/src/blogc/filelist-parser.c +++ /dev/null @@ -1,67 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2020 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#include "../common/utils.h" - - -typedef enum { - LINE_START = 1, - LINE, -} blogc_filelist_parser_state_t; - - -bc_slist_t* -blogc_filelist_parse(const char *src, size_t src_len) -{ - size_t current = 0; - bc_slist_t *rv = NULL; - bc_string_t *line = bc_string_new(); - blogc_filelist_parser_state_t state = LINE_START; - - while (current < src_len) { - char c = src[current]; - bool is_last = current == src_len - 1; - - switch (state) { - - case LINE_START: - if (c == '#') { - while (current < src_len) { - if (src[current] == '\r' || src[current] == '\n') - break; - current++; - } - break; - } - if (c == ' ' || c == '\t' || c == '\r' || c == '\n') - break; - state = LINE; - continue; - - case LINE: - if (c == '\r' || c == '\n' || is_last) { - if (is_last && c != '\r' && c != '\n') - bc_string_append_c(line, c); - rv = bc_slist_append(rv, bc_str_strip(line->str)); - bc_string_free(line, false); - line = bc_string_new(); - state = LINE_START; - break; - } - bc_string_append_c(line, c); - break; - - } - - current++; - } - - bc_string_free(line, true); - - return rv; -} diff --git a/src/blogc/filelist-parser.h b/src/blogc/filelist-parser.h deleted file mode 100644 index b068554..0000000 --- a/src/blogc/filelist-parser.h +++ /dev/null @@ -1,16 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2020 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#ifndef _FILELIST_PARSER_H -#define _FILELIST_PARSER_H - -#include "../common/utils.h" - -bc_slist_t* blogc_filelist_parse(const char *src, size_t src_len); - -#endif /* _FILELIST_PARSER_H */ diff --git a/src/blogc/funcvars.c b/src/blogc/funcvars.c deleted file mode 100644 index 6f0700b..0000000 --- a/src/blogc/funcvars.c +++ /dev/null @@ -1,62 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 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 <string.h> - -#include "funcvars.h" -#include "rusage.h" -#include "sysinfo.h" -#include "../common/utils.h" - - -static const struct func_map { - const char *variable; - const blogc_funcvars_func_t func; -} funcs[] = { - -#ifdef HAVE_RUSAGE - {"BLOGC_RUSAGE_CPU_TIME", blogc_rusage_inject}, - {"BLOGC_RUSAGE_MEMORY", blogc_rusage_inject}, -#endif - -#ifdef HAVE_SYSINFO_HOSTNAME - {"BLOGC_SYSINFO_HOSTNAME", blogc_sysinfo_inject_hostname}, -#endif - -#ifdef HAVE_SYSINFO_DATETIME - {"BLOGC_SYSINFO_DATETIME", blogc_sysinfo_inject_datetime}, -#endif - - {"BLOGC_SYSINFO_USERNAME", blogc_sysinfo_inject_username}, - {"BLOGC_SYSINFO_INSIDE_DOCKER", blogc_sysinfo_inject_inside_docker}, - {NULL, NULL}, -}; - - -void -blogc_funcvars_eval(bc_trie_t *global, const char *name) -{ - if (global == NULL || name == NULL) - return; - - // protect against evaluating the same function twice in the same global - // context - if (NULL != bc_trie_lookup(global, name)) - return; - - for (size_t i = 0; funcs[i].variable != NULL; i++) { - if (0 == strcmp(name, funcs[i].variable)) { - funcs[i].func(global); - return; - } - } - - return; -} diff --git a/src/blogc/funcvars.h b/src/blogc/funcvars.h deleted file mode 100644 index aae1bc3..0000000 --- a/src/blogc/funcvars.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#ifndef ___FUNCVARS_H -#define ___FUNCVARS_H - -#include <stdbool.h> -#include "../common/utils.h" - -typedef void (*blogc_funcvars_func_t) (bc_trie_t*); - -void blogc_funcvars_eval(bc_trie_t *global, const char *name); - -#endif /* ___FUNCVARS_H */ diff --git a/src/blogc/loader.c b/src/blogc/loader.c deleted file mode 100644 index af30a1b..0000000 --- a/src/blogc/loader.c +++ /dev/null @@ -1,318 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2020 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#include <math.h> -#include <stdbool.h> -#include <stddef.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include "datetime-parser.h" -#include "source-parser.h" -#include "template-parser.h" -#include "loader.h" -#include "../common/error.h" -#include "../common/file.h" -#include "../common/utils.h" -#include "../common/sort.h" - - -char* -blogc_get_filename(const char *f) -{ - if (f == NULL) - return NULL; - - if (strlen(f) == 0) - return NULL; - - char *filename = bc_strdup(f); - - // keep a pointer to original string - char *tmp = filename; - - bool removed_dot = false; - for (int i = strlen(tmp); i >= 0 ; i--) { - - // remove last extension - if (!removed_dot && tmp[i] == '.') { - tmp[i] = '\0'; - removed_dot = true; - continue; - } - - if (tmp[i] == '/' || tmp[i] == '\\') { - tmp += i + 1; - break; - } - } - - char *final_filename = bc_strdup(tmp); - free(filename); - - return final_filename; -} - - -bc_slist_t* -blogc_template_parse_from_file(const char *f, bc_error_t **err) -{ - if (err == NULL || *err != NULL) - return NULL; - - size_t len; - char *s = bc_file_get_contents(f, true, &len, err); - if (s == NULL) - return NULL; - bc_slist_t *rv = blogc_template_parse(s, len, err); - free(s); - return rv; -} - - -bc_trie_t* -blogc_source_parse_from_file(bc_trie_t *conf, const char *f, bc_error_t **err) -{ - if (err == NULL || *err != NULL) - return NULL; - - size_t len; - char *s = bc_file_get_contents(f, true, &len, err); - if (s == NULL) - return NULL; - - int toctree_maxdepth = -1; - const char *maxdepth = bc_trie_lookup(conf, "TOCTREE_MAXDEPTH"); - if (maxdepth != NULL) { - char *endptr; - toctree_maxdepth = strtol(maxdepth, &endptr, 10); - if (*maxdepth != '\0' && *endptr != '\0') { - fprintf(stderr, "warning: invalid value for 'TOCTREE_MAXDEPTH' " - "variable: %s. using %d instead\n", maxdepth, toctree_maxdepth); - } - } - - bc_trie_t *rv = blogc_source_parse(s, len, toctree_maxdepth, err); - - // set FILENAME variable - if (rv != NULL) { - char *filename = blogc_get_filename(f); - if (filename != NULL) - bc_trie_insert(rv, "FILENAME", filename); - } - - free(s); - return rv; -} - - -static int -sort_source(const void *a, const void *b) -{ - const char *ca = bc_trie_lookup((bc_trie_t*) a, "c"); - const char *cb = bc_trie_lookup((bc_trie_t*) b, "c"); - - if (ca == NULL || cb == NULL) { - return 0; // wat - } - - unsigned long la = strtoul(ca, NULL, 10); - unsigned long lb = strtoul(cb, NULL, 10); - - return (int) (lb - la); -} - - -static int -sort_source_reverse(const void *a, const void *b) -{ - return sort_source(b, a); -} - - -bc_slist_t* -blogc_source_parse_from_files(bc_trie_t *conf, bc_slist_t *l, bc_error_t **err) -{ - if (err == NULL || *err != NULL) - return NULL; - - bool sort = bc_str_to_bool(bc_trie_lookup(conf, "FILTER_SORT")); - - bc_slist_t* sources = NULL; - bc_error_t *tmp_err = NULL; - size_t with_date = 0; - for (bc_slist_t *tmp = l; tmp != NULL; tmp = tmp->next) { - char *f = tmp->data; - bc_trie_t *s = blogc_source_parse_from_file(conf, f, &tmp_err); - if (s == NULL) { - *err = bc_error_new_printf(BLOGC_ERROR_LOADER, - "An error occurred while parsing source file: %s\n\n%s", - f, tmp_err->msg); - bc_error_free(tmp_err); - bc_slist_free_full(sources, (bc_free_func_t) bc_trie_free); - return NULL; - } - - const char *date = bc_trie_lookup(s, "DATE"); - if (date != NULL) { - with_date++; - } - - if (sort) { - if (date == NULL) { - *err = bc_error_new_printf(BLOGC_ERROR_LOADER, - "'FILTER_SORT' requires that 'DATE' variable is set for " - "every source file: %s", f); - bc_trie_free(s); - bc_slist_free_full(sources, (bc_free_func_t) bc_trie_free); - return NULL; - } - - char *timestamp = blogc_convert_datetime(date, "%s", &tmp_err); - if (timestamp == NULL) { - *err = bc_error_new_printf(BLOGC_ERROR_LOADER, - "An error occurred while parsing 'DATE' variable: %s" - "\n\n%s", f, tmp_err->msg); - bc_error_free(tmp_err); - bc_trie_free(s); - bc_slist_free_full(sources, (bc_free_func_t) bc_trie_free); - return NULL; - } - - bc_trie_insert(s, "c", timestamp); - } - - sources = bc_slist_append(sources, s); - } - - if (with_date > 0 && with_date < bc_slist_length(l)) { - *err = bc_error_new_printf(BLOGC_ERROR_LOADER, - "'DATE' variable provided for at least one source file, but not " - "for all source files. It must be provided for all files."); - bc_slist_free_full(sources, (bc_free_func_t) bc_trie_free); - return NULL; - } - - bool reverse = bc_str_to_bool(bc_trie_lookup(conf, "FILTER_REVERSE")); - - if (sort) { - sources = bc_slist_sort(sources, - (bc_sort_func_t) (reverse ? sort_source_reverse : sort_source)); - } - else if (reverse) { - bc_slist_t *tmp_sources = NULL; - for (bc_slist_t *tmp = sources; tmp != NULL; tmp = tmp->next) { - tmp_sources = bc_slist_prepend(tmp_sources, tmp->data); - } - bc_slist_t *tmp = sources; - sources = tmp_sources; - bc_slist_free(tmp); - } - - const char *filter_tag = bc_trie_lookup(conf, "FILTER_TAG"); - const char *filter_page = bc_trie_lookup(conf, "FILTER_PAGE"); - const char *filter_per_page = bc_trie_lookup(conf, "FILTER_PER_PAGE"); - - const char *ptr; - char *endptr; - - ptr = filter_page != NULL ? filter_page : ""; - long page = strtol(ptr, &endptr, 10); - if (*ptr != '\0' && *endptr != '\0') - fprintf(stderr, "warning: invalid value for 'FILTER_PAGE' variable: " - "%s. using %ld instead\n", ptr, page); - if (page <= 0) - page = 1; - - ptr = filter_per_page != NULL ? filter_per_page : "10"; - long per_page = strtol(ptr, &endptr, 10); - if (*ptr != '\0' && *endptr != '\0') - fprintf(stderr, "warning: invalid value for 'FILTER_PER_PAGE' variable: " - "%s. using %ld instead\n", ptr, per_page); - if (per_page < 0) - per_page = 0; - - // poor man's pagination - size_t start = (page - 1) * per_page; - size_t end = start + per_page; - size_t counter = 0; - - bc_slist_t *rv = NULL; - for (bc_slist_t *tmp = sources; tmp != NULL; tmp = tmp->next) { - bc_trie_t *s = tmp->data; - if (filter_tag != NULL) { - const char *tags_str = bc_trie_lookup(s, "TAGS"); - // if user wants to filter by tag and no tag is provided, skip it - if (tags_str == NULL) { - bc_trie_free(s); - continue; - } - char **tags = bc_str_split(tags_str, ' ', 0); - bool found = false; - for (size_t i = 0; tags[i] != NULL; i++) { - if (tags[i][0] == '\0') - continue; - if (0 == strcmp(tags[i], filter_tag)) - found = true; - } - bc_strv_free(tags); - if (!found) { - bc_trie_free(s); - continue; - } - } - if (filter_page != NULL) { - if (counter < start || counter >= end) { - counter++; - bc_trie_free(s); - continue; - } - counter++; - } - rv = bc_slist_append(rv, s); - } - - bc_slist_free(sources); - - bool first = true; - for (bc_slist_t *tmp = rv; tmp != NULL; tmp = tmp->next) { - bc_trie_t *s = tmp->data; - if (first) { - const char *val = bc_trie_lookup(s, "DATE"); - if (val != NULL) - bc_trie_insert(conf, "DATE_FIRST", bc_strdup(val)); - val = bc_trie_lookup(s, "FILENAME"); - if (val != NULL) - bc_trie_insert(conf, "FILENAME_FIRST", bc_strdup(val)); - first = false; - } - if (tmp->next == NULL) { // last - const char *val = bc_trie_lookup(s, "DATE"); - if (val != NULL) - bc_trie_insert(conf, "DATE_LAST", bc_strdup(val)); - val = bc_trie_lookup(s, "FILENAME"); - if (val != NULL) - bc_trie_insert(conf, "FILENAME_LAST", bc_strdup(val)); - } - } - - if (filter_page != NULL) { - size_t last_page = ceilf(((float) counter) / per_page); - bc_trie_insert(conf, "CURRENT_PAGE", bc_strdup_printf("%ld", page)); - if (page > 1) - bc_trie_insert(conf, "PREVIOUS_PAGE", bc_strdup_printf("%ld", page - 1)); - if (page < last_page) - bc_trie_insert(conf, "NEXT_PAGE", bc_strdup_printf("%ld", page + 1)); - if (bc_slist_length(rv) > 0) - bc_trie_insert(conf, "FIRST_PAGE", bc_strdup("1")); - if (last_page > 0) - bc_trie_insert(conf, "LAST_PAGE", bc_strdup_printf("%d", last_page)); - } - - return rv; -} diff --git a/src/blogc/loader.h b/src/blogc/loader.h deleted file mode 100644 index fe88730..0000000 --- a/src/blogc/loader.h +++ /dev/null @@ -1,22 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2020 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#ifndef _LOADER_H -#define _LOADER_H - -#include "../common/error.h" -#include "../common/utils.h" - -char* blogc_get_filename(const char *f); -bc_slist_t* blogc_template_parse_from_file(const char *f, bc_error_t **err); -bc_trie_t* blogc_source_parse_from_file(bc_trie_t *conf, const char *f, - bc_error_t **err); -bc_slist_t* blogc_source_parse_from_files(bc_trie_t *conf, bc_slist_t *l, - bc_error_t **err); - -#endif /* _LOADER_H */ diff --git a/src/blogc/main.c b/src/blogc/main.c deleted file mode 100644 index 9bfa9cf..0000000 --- a/src/blogc/main.c +++ /dev/null @@ -1,393 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2020 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 */ - -#ifdef HAVE_SYS_STAT_H -#include <sys/stat.h> -#endif /* HAVE_SYS_STAT_H */ - -#ifdef HAVE_SYSEXITS_H -#include <sysexits.h> -#else -#define EX_CONFIG 78 -#endif /* HAVE_SYSEXITS_H */ - -#include <errno.h> -#include <locale.h> -#include <stdbool.h> -#include <stdio.h> -#include <stdint.h> -#include <stdlib.h> -#include <string.h> - -#include "debug.h" -#include "filelist-parser.h" -#include "template-parser.h" -#include "loader.h" -#include "renderer.h" -#include "../common/error.h" -#include "../common/utf8.h" -#include "../common/utils.h" -#include "../common/stdin.h" - -#ifdef MAKE_EMBEDDED -extern int bm_main(int argc, char **argv); -#endif - -#ifndef PACKAGE_VERSION -#define PACKAGE_VERSION "Unknown" -#endif - - -static void -blogc_print_help(void) -{ - printf( - "usage:\n" - " blogc " -#ifdef MAKE_EMBEDDED - "[-m] " -#endif - "[-h] [-v] [-d] [-i] [-l [-e SOURCE]] [-D KEY=VALUE ...] [-p KEY]\n" - " [-t TEMPLATE] [-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" - " -d enable debug\n" - " -i read list of source files from standard input\n" - " -l build listing page, from multiple source files\n" - " -e SOURCE source file with content for listing page. requires '-l'\n" - " -D KEY=VALUE set global variable\n" - " -p KEY show the value of a variable after source parsing and exit\n" - " -t TEMPLATE template file\n" - " -o OUTPUT output file\n" -#ifdef MAKE_EMBEDDED - " -m call and pass arguments to embedded blogc-make\n" -#endif - ); -} - - -static void -blogc_print_usage(void) -{ - printf( - "usage: blogc " -#ifdef MAKE_EMBEDDED - "[-m] " -#endif - "[-h] [-v] [-d] [-i] [-l [-e SOURCE]] [-D KEY=VALUE ...] [-p KEY]\n" - " [-t TEMPLATE] [-o OUTPUT] [SOURCE ...]\n"); -} - - -static void -blogc_mkdir_recursive(const char *filename) -{ - char *fname = bc_strdup(filename); - for (char *tmp = fname; *tmp != '\0'; tmp++) { - if (*tmp != '/' && *tmp != '\\') - continue; -#ifdef HAVE_SYS_STAT_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; - -#ifdef MAKE_EMBEDDED - bool embedded = false; -#endif - bool debug = false; - bool input_stdin = false; - bool listing = false; - char *template = NULL; - char *output = NULL; - char *print = NULL; - char *tmp = NULL; - char **pieces = NULL; - - bc_slist_t *sources = NULL; - bc_slist_t *listing_entries = NULL; - bc_slist_t *listing_entries_source = NULL; - bc_trie_t *config = bc_trie_new(free); - bc_trie_insert(config, "BLOGC_VERSION", bc_strdup(PACKAGE_VERSION)); - - for (size_t 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 'd': - debug = true; - break; - case 'i': - input_stdin = true; - break; - case 'l': - listing = true; - break; - case 'e': - if (argv[i][2] != '\0') - listing_entries = bc_slist_append(listing_entries, bc_strdup(argv[i] + 2)); - else if (i + 1 < argc) - listing_entries = bc_slist_append(listing_entries, bc_strdup(argv[++i])); - break; - case 't': - if (argv[i][2] != '\0') - template = bc_strdup(argv[i] + 2); - else if (i + 1 < argc) - template = bc_strdup(argv[++i]); - break; - case 'o': - if (argv[i][2] != '\0') - output = bc_strdup(argv[i] + 2); - else if (i + 1 < argc) - output = bc_strdup(argv[++i]); - break; - case 'p': - if (argv[i][2] != '\0') - print = bc_strdup(argv[i] + 2); - else if (i + 1 < argc) - print = bc_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) { - if (!bc_utf8_validate((uint8_t*) tmp, strlen(tmp))) { - fprintf(stderr, "blogc: error: invalid value for " - "-D (must be valid UTF-8 string): %s\n", tmp); - goto cleanup; - } - pieces = bc_str_split(tmp, '=', 2); - if (bc_strv_length(pieces) != 2) { - fprintf(stderr, "blogc: error: invalid value for " - "-D (must have an '='): %s\n", tmp); - bc_strv_free(pieces); - rv = 1; - goto cleanup; - } - for (size_t j = 0; pieces[0][j] != '\0'; j++) { - char c = pieces[0][j]; - if (j == 0) { - if (!(c >= 'A' && c <= 'Z')) { - fprintf(stderr, "blogc: error: invalid value " - "for -D (first character in configuration " - "key must be uppercase): %s\n", pieces[0]); - bc_strv_free(pieces); - rv = 1; - goto cleanup; - } - continue; - } - if (!((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_')) { - fprintf(stderr, "blogc: error: invalid value " - "for -D (configuration key must be uppercase " - "with '_' and digits after first character): %s\n", - pieces[0]); - bc_strv_free(pieces); - rv = 1; - goto cleanup; - } - } - bc_trie_insert(config, pieces[0], bc_strdup(pieces[1])); - bc_strv_free(pieces); - pieces = NULL; - } - break; -#ifdef MAKE_EMBEDDED - case 'm': - embedded = true; - break; -#endif - default: - blogc_print_usage(); - fprintf(stderr, "blogc: error: invalid argument: -%c\n", - argv[i][1]); - rv = 1; - goto cleanup; - } - } - 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) { - size_t input_len; - char *input = bc_stdin_read(&input_len); - bc_slist_t *input_list = blogc_filelist_parse(input, input_len); - free(input); - sources = bc_slist_append_list(sources, input_list); - } - - if (!listing && bc_slist_length(sources) == 0) { - blogc_print_usage(); - fprintf(stderr, "blogc: error: one source file is required\n"); - rv = 1; - goto cleanup; - } - - if (!listing && bc_slist_length(sources) > 1) { - blogc_print_usage(); - fprintf(stderr, "blogc: error: only one source file should be provided, " - "if running without '-l'\n"); - rv = 1; - goto cleanup; - } - - bc_error_t *err = NULL; - - bc_slist_t *s = blogc_source_parse_from_files(config, sources, &err); - if (err != NULL) { - bc_error_print(err, "blogc"); - rv = 1; - goto cleanup2; - } - - if (listing) { - for (bc_slist_t *tmp = listing_entries; tmp != NULL; tmp = tmp->next) { - if (0 == strlen(tmp->data)) { - listing_entries_source = bc_slist_append(listing_entries_source, NULL); - continue; - } - bc_trie_t *e = blogc_source_parse_from_file(config, tmp->data, &err); - if (err != NULL) { - bc_error_print(err, "blogc"); - rv = 1; - goto cleanup2; - } - listing_entries_source = bc_slist_append(listing_entries_source, e); - } - } - - if (print != NULL) { - bc_trie_t *local = NULL; - if (!listing && s != NULL) { - local = s->data; - } - char *val = blogc_format_variable(print, config, local, NULL, NULL); - if (val == NULL) { - fprintf(stderr, "blogc: error: variable not found: %s\n", - print); - rv = EX_CONFIG; - } - else { - printf("%s\n", val); - } - free(val); - goto cleanup2; - } - - if (template == NULL) { - blogc_print_usage(); - fprintf(stderr, "blogc: error: argument -t is required when rendering content\n"); - rv = 1; - goto cleanup2; - } - - bc_slist_t* l = blogc_template_parse_from_file(template, &err); - if (err != NULL) { - bc_error_print(err, "blogc"); - rv = 1; - goto cleanup3; - } - - if (debug) - blogc_debug_template(l); - - char *out = blogc_render(l, s, listing_entries_source, 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 = 1; - goto cleanup4; - } - } - - if (out != NULL) - fprintf(fp, "%s", out); - - if (!write_to_stdout) - fclose(fp); - -cleanup4: - free(out); -cleanup3: - blogc_template_free_ast(l); -cleanup2: - bc_slist_free_full(s, (bc_free_func_t) bc_trie_free); - bc_error_free(err); -cleanup: - bc_trie_free(config); - free(template); - free(output); - free(print); - bc_slist_free_full(listing_entries, free); - bc_slist_free_full(listing_entries_source, (bc_free_func_t) bc_trie_free); - bc_slist_free_full(sources, free); - return rv; -} diff --git a/src/blogc/renderer.h b/src/blogc/renderer.h deleted file mode 100644 index 77c660a..0000000 --- a/src/blogc/renderer.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2020 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#ifndef _RENDERER_H -#define _RENDERER_H - -#include <stdbool.h> -#include "../common/utils.h" - -const char* blogc_get_variable(const char *name, bc_trie_t *global, bc_trie_t *local); -char* blogc_format_date(const char *date, bc_trie_t *global, bc_trie_t *local); -char* blogc_format_variable(const char *name, bc_trie_t *global, bc_trie_t *local, - const char *foreach_name, bc_slist_t *foreach_var); -bc_slist_t* blogc_split_list_variable(const char *name, bc_trie_t *global, - bc_trie_t *local); -char* blogc_render(bc_slist_t *tmpl, bc_slist_t *sources, bc_slist_t *listing_entries, - bc_trie_t *config, bool listing); - -#endif /* _RENDERER_H */ diff --git a/src/blogc/rusage.c b/src/blogc/rusage.c deleted file mode 100644 index a38848d..0000000 --- a/src/blogc/rusage.c +++ /dev/null @@ -1,86 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 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 */ - -#ifdef HAVE_SYS_TIME_H -#include <sys/time.h> -#endif /* HAVE_SYS_TIME_H */ - -#ifdef HAVE_SYS_RESOURCE_H -#include <sys/resource.h> -#endif /* HAVE_SYS_RESOURCE_H */ - -#include <stdlib.h> -#include "../common/utils.h" -#include "rusage.h" - - -blogc_rusage_t* -blogc_rusage_get(void) -{ -#ifndef HAVE_RUSAGE - return NULL; -#else - struct rusage usage; - if (0 != getrusage(RUSAGE_SELF, &usage)) - return NULL; - - blogc_rusage_t *rv = bc_malloc(sizeof(blogc_rusage_t)); - rv->cpu_time = ( - (usage.ru_utime.tv_sec * 1000000) + usage.ru_utime.tv_usec + - (usage.ru_stime.tv_sec * 1000000) + usage.ru_stime.tv_usec); - rv->memory = usage.ru_maxrss; - - return rv; -#endif -} - - -char* -blogc_rusage_format_cpu_time(long long time) -{ - if (time >= 1000000) - return bc_strdup_printf("%.3fs", ((float) time) / 1000000.0); - - // this is a special case: some systems may report the cpu time rounded up to the - // milisecond. it is useless to show ".000" in this case. - if (time >= 1000) - return bc_strdup_printf("%.*fms", time % 1000 ? 3 : 0, ((float) time) / 1000.0); - - return bc_strdup_printf("%dus", time); -} - - -char* -blogc_rusage_format_memory(long mem) -{ - if (mem >= 1048576) - return bc_strdup_printf("%.3fGB", ((float) mem) / 1048576.0); - if (mem >= 1024) - return bc_strdup_printf("%.3fMB", ((float) mem) / 1024.0); - return bc_strdup_printf("%dKB", mem); -} - - -void -blogc_rusage_inject(bc_trie_t *global) -{ - blogc_rusage_t *usage = blogc_rusage_get(); - if (usage == NULL) - return; - - bc_trie_insert(global, "BLOGC_RUSAGE_CPU_TIME", - blogc_rusage_format_cpu_time(usage->cpu_time)); - bc_trie_insert(global, "BLOGC_RUSAGE_MEMORY", - blogc_rusage_format_memory(usage->memory)); - - free(usage); -} diff --git a/src/blogc/rusage.h b/src/blogc/rusage.h deleted file mode 100644 index 6344066..0000000 --- a/src/blogc/rusage.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#ifndef ___RUSAGE_H -#define ___RUSAGE_H - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif /* HAVE_CONFIG_H */ - -#ifdef HAVE_SYS_TIME_H -#ifdef HAVE_SYS_RESOURCE_H -#define HAVE_RUSAGE 1 -#endif -#endif - -#include "../common/utils.h" - -typedef struct { - long long cpu_time; // in microseconds - long memory; // in kilobytes -} blogc_rusage_t; - -blogc_rusage_t* blogc_rusage_get(void); - -char* blogc_rusage_format_cpu_time(long long time); -char* blogc_rusage_format_memory(long mem); - -void blogc_rusage_inject(bc_trie_t *global); - -#endif /* ___RUSAGE_H */ diff --git a/src/blogc/sysinfo.c b/src/blogc/sysinfo.c deleted file mode 100644 index 3c30996..0000000 --- a/src/blogc/sysinfo.c +++ /dev/null @@ -1,153 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 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 */ - -#ifdef HAVE_UNISTD_H -#include <unistd.h> -#endif /* HAVE_UNISTD_H */ - -#ifdef HAVE_TIME_H -#include <time.h> -#endif /* HAVE_TIME_H */ - -#ifdef HAVE_NETDB_H -#include <netdb.h> -#endif - -#include <stdbool.h> -#include <stdlib.h> -#include <string.h> -#include "../common/error.h" -#include "../common/file.h" -#include "../common/utils.h" -#include "sysinfo.h" - - -char* -blogc_sysinfo_get_hostname(void) -{ -#ifndef HAVE_SYSINFO_HOSTNAME - return NULL; -#else - char buf[1024]; // could be 256 according to gethostname(2), but *shrug*. - buf[1023] = '\0'; - if (-1 == gethostname(buf, 1024)) - return NULL; - -#ifdef HAVE_NETDB_H - struct hostent *h = gethostbyname(buf); - if (h != NULL && h->h_name != NULL) - return bc_strdup(h->h_name); -#endif - - // FIXME: return FQDN instead of local host name - return bc_strdup(buf); -#endif -} - - -void -blogc_sysinfo_inject_hostname(bc_trie_t *global) -{ - char *hostname = blogc_sysinfo_get_hostname(); - if (hostname == NULL) - return; - - bc_trie_insert(global, "BLOGC_SYSINFO_HOSTNAME", hostname); -} - - -char* -blogc_sysinfo_get_username(void) -{ - return bc_strdup(getenv("LOGNAME")); -} - - -void -blogc_sysinfo_inject_username(bc_trie_t *global) -{ - char *username = blogc_sysinfo_get_username(); - if (username == NULL) - return; - - bc_trie_insert(global, "BLOGC_SYSINFO_USERNAME", username); -} - - -char* -blogc_sysinfo_get_datetime(void) -{ -#ifndef HAVE_SYSINFO_DATETIME - return NULL; -#else - time_t tmp; - if (-1 == time(&tmp)) - return NULL; - - struct tm *t = gmtime(&tmp); - if (t == NULL) - return NULL; - - char buf[1024]; - if (0 == strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", t)) - return NULL; - - return bc_strdup(buf); -#endif -} - - -void -blogc_sysinfo_inject_datetime(bc_trie_t *global) -{ - char *t = blogc_sysinfo_get_datetime(); - if (t == NULL) - return; - - bc_trie_insert(global, "BLOGC_SYSINFO_DATETIME", t); -} - - -// it is obviously impossible that the same process runs inside and outside -// docker at the same time, then an unprotected global variable should be fine -// here -static bool inside_docker_evaluated = false; -static bool inside_docker = false; - -bool -blogc_sysinfo_get_inside_docker(void) -{ - if (inside_docker_evaluated) - return inside_docker; - inside_docker_evaluated = true; - - size_t len; - bc_error_t *err = NULL; - char *contents = bc_file_get_contents("/proc/1/cgroup", false, &len, &err); - if (err != NULL) { - bc_error_free(err); - inside_docker = false; - return inside_docker; - } - - bool inside_docker = NULL != strstr(contents, "/docker/"); - free(contents); - return inside_docker; -} - - -void -blogc_sysinfo_inject_inside_docker(bc_trie_t *global) -{ - if (blogc_sysinfo_get_inside_docker()) - bc_trie_insert(global, "BLOGC_SYSINFO_INSIDE_DOCKER", bc_strdup("1")); -} diff --git a/src/blogc/sysinfo.h b/src/blogc/sysinfo.h deleted file mode 100644 index b249661..0000000 --- a/src/blogc/sysinfo.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#ifndef ___SYSINFO_H -#define ___SYSINFO_H - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif /* HAVE_CONFIG_H */ - -#ifdef HAVE_UNISTD_H -#ifdef HAVE_GETHOSTNAME -#define HAVE_SYSINFO_HOSTNAME 1 -#endif /* HAVE_GETHOSTNAME */ -#endif /* HAVE_UNISTD_H */ - -#ifdef HAVE_TIME_H -#define HAVE_SYSINFO_DATETIME 1 -#endif /* HAVE_TIME_H */ - -#include <stdbool.h> -#include "../common/utils.h" - -char* blogc_sysinfo_get_hostname(void); -void blogc_sysinfo_inject_hostname(bc_trie_t *global); -char* blogc_sysinfo_get_username(void); -void blogc_sysinfo_inject_username(bc_trie_t *global); -char* blogc_sysinfo_get_datetime(void); -void blogc_sysinfo_inject_datetime(bc_trie_t *global); -bool blogc_sysinfo_get_inside_docker(void); -void blogc_sysinfo_inject_inside_docker(bc_trie_t *global); - -#endif /* ___SYSINFO_H */ diff --git a/src/blogc/template-parser.h b/src/blogc/template-parser.h deleted file mode 100644 index 6183016..0000000 --- a/src/blogc/template-parser.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#ifndef _TEMPLATE_PARSER_H -#define _TEMPLATE_PARSER_H - -#include <stddef.h> -#include "../common/error.h" -#include "../common/utils.h" - -/* - * note: whitespace cleaners are NOT added to AST. we fix strings right during - * template parsing. renderer does not need to care about it, for the sake of - * simplicity. - * - * another note: technically this is not an AST, because it is not a tree. duh! - */ -typedef enum { - BLOGC_TEMPLATE_NODE_IFDEF = 1, - BLOGC_TEMPLATE_NODE_IFNDEF, - BLOGC_TEMPLATE_NODE_IF, - BLOGC_TEMPLATE_NODE_ELSE, - BLOGC_TEMPLATE_NODE_ENDIF, - BLOGC_TEMPLATE_NODE_FOREACH, - BLOGC_TEMPLATE_NODE_ENDFOREACH, - BLOGC_TEMPLATE_NODE_BLOCK, - BLOGC_TEMPLATE_NODE_ENDBLOCK, - BLOGC_TEMPLATE_NODE_VARIABLE, - BLOGC_TEMPLATE_NODE_CONTENT, -} blogc_template_node_type_t; - -typedef enum { - BLOGC_TEMPLATE_OP_NEQ = 1 << 0, - BLOGC_TEMPLATE_OP_EQ = 1 << 1, - BLOGC_TEMPLATE_OP_LT = 1 << 2, - BLOGC_TEMPLATE_OP_GT = 1 << 3, -} blogc_template_operator_t; - -typedef struct { - blogc_template_node_type_t type; - blogc_template_operator_t op; - - // 2 slots to store node data. - char *data[2]; - - bc_slist_t *childs; -} blogc_template_node_t; - -bc_slist_t* blogc_template_parse(const char *src, size_t src_len, - bc_error_t **err); -void blogc_template_free_ast(bc_slist_t *ast); - -#endif /* _TEMPLATE_PARSER_H */ diff --git a/src/blogc/toctree.c b/src/blogc/toctree.c deleted file mode 100644 index 307c62c..0000000 --- a/src/blogc/toctree.c +++ /dev/null @@ -1,103 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2020 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 "../common/utils.h" -#include "toctree.h" - -bc_slist_t* -blogc_toctree_append(bc_slist_t *headers, size_t level, const char *slug, const char *text) -{ - if (level == 0) - return headers; - - blogc_toctree_header_t *t = bc_malloc(sizeof(blogc_toctree_header_t)); - t->level = level; - t->slug = bc_strdup(slug); - t->text = bc_strdup(text); - return bc_slist_append(headers, t); -} - - -char* -blogc_toctree_render(bc_slist_t *headers, int maxdepth, const char *endl) -{ - if (headers == NULL || maxdepth == 0) - return NULL; - - // find lower level - size_t lower_level = 0; - for (bc_slist_t *l = headers; l != NULL; l = l->next) { - size_t lv = ((blogc_toctree_header_t*) l->data)->level; - if (lower_level == 0 || lower_level > lv) { - lower_level = lv; - } - } - - if (lower_level == 0) - return NULL; - - // render - bc_string_t *rv = bc_string_new(); - bc_string_append_printf(rv, "<ul>%s", endl == NULL ? "\n" : endl); - size_t spacing = 4; - size_t current_level = lower_level; - for (bc_slist_t *l = headers; l != NULL; l = l->next) { - blogc_toctree_header_t *t = l->data; - if (t->level - lower_level >= maxdepth) { - continue; - } - while (current_level > t->level) { - spacing -= 4; - bc_string_append_printf(rv, "%*s</ul>%s", spacing, "", - endl == NULL ? "\n" : endl); - current_level--; - } - while (current_level < t->level) { - bc_string_append_printf(rv, "%*s<ul>%s", spacing, "", - endl == NULL ? "\n" : endl); - current_level++; - spacing += 4; - } - bc_string_append_printf(rv, "%*s<li>", spacing, ""); - if (t->slug != NULL) { - bc_string_append_printf(rv, "<a href=\"#%s\">%s</a>", t->slug, - t->text != NULL ? t->text : ""); - } - else { - bc_string_append(rv, t->text); - } - bc_string_append_printf(rv, "</li>%s", endl == NULL ? "\n" : endl); - } - - // close leftovers - while (current_level >= lower_level) { - spacing -= 4; - bc_string_append_printf(rv, "%*s</ul>%s", spacing, "", - endl == NULL ? "\n" : endl); - current_level--; - } - - return bc_string_free(rv, false); -} - - -static void -free_header(blogc_toctree_header_t *h) -{ - free(h->slug); - free(h->text); - free(h); -} - - -void -blogc_toctree_free(bc_slist_t *l) -{ - bc_slist_free_full(l, (bc_free_func_t) free_header); -} diff --git a/src/blogc/toctree.h b/src/blogc/toctree.h deleted file mode 100644 index 460119b..0000000 --- a/src/blogc/toctree.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2020 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#ifndef ___TOCTREE_H -#define ___TOCTREE_H - -#include "../common/utils.h" - -typedef struct { - size_t level; - char *slug; - char *text; -} blogc_toctree_header_t; - -bc_slist_t* blogc_toctree_append(bc_slist_t *headers, size_t level, - const char *slug, const char *text); -char* blogc_toctree_render(bc_slist_t *headers, int maxdepth, - const char *endl); -void blogc_toctree_free(bc_slist_t *l); - -#endif /* ___TOCTREE_H */ diff --git a/src/common/compat.c b/src/common/compat.c deleted file mode 100644 index f7394c8..0000000 --- a/src/common/compat.c +++ /dev/null @@ -1,39 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 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 */ - -#ifdef HAVE_SYS_WAIT_H -#include <sys/wait.h> -#endif /* HAVE_SYS_WAIT_H */ - -#include <signal.h> -#include "compat.h" - - -int -bc_compat_status_code(int waitstatus) -{ - int rv = waitstatus; -#if defined(WIFEXITED) && defined(WEXITSTATUS) && defined(WIFSIGNALED) && defined(WTERMSIG) - if (WIFEXITED(waitstatus)) { - rv = WEXITSTATUS(waitstatus); - } - else if (WIFSIGNALED(waitstatus)) { - rv = WTERMSIG(waitstatus); - rv += 128; - } -#elif defined(WIN32) || defined(_WIN32) - if (waitstatus == 3) { - rv = SIGTERM + 128; // can't get signal on windows. - } -#endif - return rv; -} diff --git a/src/common/compat.h b/src/common/compat.h deleted file mode 100644 index 1e743db..0000000 --- a/src/common/compat.h +++ /dev/null @@ -1,14 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#ifndef _COMPAT_H -#define _COMPAT_H - -int bc_compat_status_code(int waitstatus); - -#endif /* _COMPAT_H */ diff --git a/src/common/config-parser.c b/src/common/config-parser.c deleted file mode 100644 index fbb79af..0000000 --- a/src/common/config-parser.c +++ /dev/null @@ -1,442 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 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 <string.h> -#include "error.h" -#include "utils.h" -#include "config-parser.h" - - -typedef enum { - CONFIG_START = 1, - CONFIG_SECTION_START, - CONFIG_SECTION, - CONFIG_SECTION_KEY, - CONFIG_SECTION_VALUE_START, - CONFIG_SECTION_VALUE_QUOTE, - CONFIG_SECTION_VALUE_POST_QUOTED, - CONFIG_SECTION_VALUE, - CONFIG_SECTION_LIST_START, - CONFIG_SECTION_LIST_QUOTE, - CONFIG_SECTION_LIST_POST_QUOTED, - CONFIG_SECTION_LIST, -} bc_configparser_state_t; - -typedef enum { - CONFIG_SECTION_TYPE_MAP = 1, - CONFIG_SECTION_TYPE_LIST, -} bc_configparser_section_type_t; - -typedef struct { - bc_configparser_section_type_t type; - void *data; -} bc_configparser_section_t; - - -static void -free_section(bc_configparser_section_t *section) -{ - if (section == NULL) - return; - - switch (section->type) { - case CONFIG_SECTION_TYPE_MAP: - bc_trie_free(section->data); - break; - case CONFIG_SECTION_TYPE_LIST: - bc_slist_free_full(section->data, free); - break; - } - free(section); -} - - -bc_config_t* -bc_config_parse(const char *src, size_t src_len, const char *list_sections[], - bc_error_t **err) -{ - if (err == NULL || *err != NULL) - return NULL; - - size_t current = 0; - size_t start = 0; - - bc_configparser_section_t *section = NULL; - - char *section_name = NULL; - char *key = NULL; - bc_string_t *value = NULL; - bool escaped = false; - - bc_config_t *rv = bc_malloc(sizeof(bc_config_t)); - rv->root = bc_trie_new((bc_free_func_t) free_section); - - bc_configparser_state_t state = CONFIG_START; - - while (current < src_len) { - char c = src[current]; - bool is_last = current == src_len - 1; - - if (escaped) { - bc_string_append_c(value, c); - escaped = false; - current++; - continue; - } - - if (value != NULL && c == '\\') { - escaped = true; - current++; - continue; - } - - switch (state) { - - case CONFIG_START: - if (c == '#' || c == ';') { - while (current < src_len) { - if (src[current] == '\r' || src[current] == '\n') - break; - current++; - } - break; - } - if (c == ' ' || c == '\t' || c == '\r' || c == '\n') - break; - if (c == '[') { - state = CONFIG_SECTION_START; - break; - } - if (section != NULL) { - start = current; - switch (section->type) { - case CONFIG_SECTION_TYPE_MAP: - state = CONFIG_SECTION_KEY; - break; - case CONFIG_SECTION_TYPE_LIST: - state = CONFIG_SECTION_LIST_START; - if (value == NULL) - value = bc_string_new(); - break; - } - continue; - } - *err = bc_error_parser(BC_ERROR_CONFIG_PARSER, src, src_len, - current, "File must start with section."); - break; - - case CONFIG_SECTION_START: - start = current; - state = CONFIG_SECTION; - break; - - case CONFIG_SECTION: - if (c == ']') { - section_name = bc_strndup(src + start, current - start); - section = bc_malloc(sizeof(bc_configparser_section_t)); - section->type = CONFIG_SECTION_TYPE_MAP; - if (list_sections != NULL) { - for (size_t i = 0; list_sections[i] != NULL; i++) { - if (0 == strcmp(section_name, list_sections[i])) { - section->type = CONFIG_SECTION_TYPE_LIST; - break; - } - } - } - switch (section->type) { - case CONFIG_SECTION_TYPE_MAP: - section->data = bc_trie_new(free); - break; - case CONFIG_SECTION_TYPE_LIST: - section->data = NULL; - break; - } - bc_trie_insert(rv->root, section_name, section); - free(section_name); - section_name = NULL; - state = CONFIG_START; - break; - } - if (c != '\r' && c != '\n') - break; - *err = bc_error_parser(BC_ERROR_CONFIG_PARSER, src, src_len, - current, "Section names can't have new lines."); - break; - - case CONFIG_SECTION_KEY: - if (c == '=') { - key = bc_strndup(src + start, current - start); - state = CONFIG_SECTION_VALUE_START; - if (is_last) { - bc_trie_insert(section->data, bc_str_strip(key), - bc_strdup("")); - free(key); - key = NULL; - break; - } - if (value == NULL) - value = bc_string_new(); - break; - } - if (c != '\r' && c != '\n' && !is_last) - break; - // key without value, should we support it? - size_t end = is_last && c != '\n' && c != '\r' ? src_len : - current; - key = bc_strndup(src + start, end - start); - *err = bc_error_parser(BC_ERROR_CONFIG_PARSER, src, src_len, - current, "Key without value: %s.", key); - free(key); - key = NULL; - break; - - case CONFIG_SECTION_VALUE_START: - if (c == ' ' || c == '\t' || c == '\f' || c == '\v') - break; - if (c == '"') { - state = CONFIG_SECTION_VALUE_QUOTE; - break; - } - if (c == '\r' || c == '\n' || is_last) { - state = CONFIG_SECTION_VALUE; - continue; - } - bc_string_append_c(value, c); - state = CONFIG_SECTION_VALUE; - break; - - case CONFIG_SECTION_VALUE_QUOTE: - if (c == '"') { - bc_trie_insert(section->data, bc_str_strip(key), - bc_string_free(value, false)); - free(key); - key = NULL; - value = NULL; - state = CONFIG_SECTION_VALUE_POST_QUOTED; - break; - } - bc_string_append_c(value, c); - break; - - case CONFIG_SECTION_VALUE_POST_QUOTED: - if (c == ' ' || c == '\t' || c == '\f' || c == '\v') - break; - if (c == '\r' || c == '\n' || is_last) { - state = CONFIG_START; - break; - } - *err = bc_error_parser(BC_ERROR_CONFIG_PARSER, src, src_len, - current, "Invalid value for key, should not have anything " - "after quotes."); - break; - - case CONFIG_SECTION_VALUE: - if (c == '\r' || c == '\n' || is_last) { - if (is_last && c != '\r' && c != '\n') - bc_string_append_c(value, c); - bc_trie_insert(section->data, bc_str_strip(key), - bc_strdup(bc_str_rstrip(value->str))); - free(key); - key = NULL; - bc_string_free(value, true); - value = NULL; - state = CONFIG_START; - break; - } - bc_string_append_c(value, c); - break; - - case CONFIG_SECTION_LIST_START: - if (c == ' ' || c == '\t' || c == '\f' || c == '\v') - break; - if (c == '"') { - state = CONFIG_SECTION_LIST_QUOTE; - break; - } - bc_string_append_c(value, c); - state = CONFIG_SECTION_LIST; - break; - - case CONFIG_SECTION_LIST_QUOTE: - if (c == '"') { - section->data = bc_slist_append(section->data, - bc_string_free(value, false)); - value = NULL; - state = CONFIG_SECTION_LIST_POST_QUOTED; - break; - - } - bc_string_append_c(value, c); - break; - - case CONFIG_SECTION_LIST_POST_QUOTED: - if (c == ' ' || c == '\t' || c == '\f' || c == '\v') - break; - if (c == '\r' || c == '\n' || is_last) { - state = CONFIG_START; - break; - } - *err = bc_error_parser(BC_ERROR_CONFIG_PARSER, src, src_len, - current, "Invalid value for list item, should not have " - "anything after quotes."); - break; - - case CONFIG_SECTION_LIST: - if (c == '\r' || c == '\n' || is_last) { - if (is_last && c != '\r' && c != '\n') - bc_string_append_c(value, c); - section->data = bc_slist_append(section->data, - bc_strdup(bc_str_strip(value->str))); - bc_string_free(value, true); - value = NULL; - state = CONFIG_START; - break; - - } - bc_string_append_c(value, c); - break; - - } - - if (*err != NULL) { - bc_config_free(rv); - rv = NULL; - break; - } - - current++; - } - - free(section_name); - free(key); - bc_string_free(value, true); - - return rv; -} - - -static void -list_keys(const char *key, const char *value, bc_slist_t **l) -{ - *l = bc_slist_append(*l, bc_strdup(key)); -} - - -char** -bc_config_list_sections(bc_config_t *config) -{ - if (config == NULL) - return NULL; - - bc_slist_t *l = NULL; - bc_trie_foreach(config->root, (bc_trie_foreach_func_t) list_keys, &l); - - char **rv = bc_malloc(sizeof(char*) * (bc_slist_length(l) + 1)); - - size_t i = 0; - for (bc_slist_t *tmp = l; tmp != NULL; tmp = tmp->next, i++) - rv[i] = tmp->data; - rv[i] = NULL; - - bc_slist_free(l); - - return rv; -} - - -char** -bc_config_list_keys(bc_config_t *config, const char *section) -{ - if (config == NULL) - return NULL; - - const bc_configparser_section_t *s = bc_trie_lookup(config->root, section); - if (s == NULL) - return NULL; - - if (s->type != CONFIG_SECTION_TYPE_MAP) - return NULL; - - bc_slist_t *l = NULL; - bc_trie_foreach(s->data, (bc_trie_foreach_func_t) list_keys, &l); - - char **rv = bc_malloc(sizeof(char*) * (bc_slist_length(l) + 1)); - - size_t i = 0; - for (bc_slist_t *tmp = l; tmp != NULL; tmp = tmp->next, i++) - rv[i] = tmp->data; - rv[i] = NULL; - - bc_slist_free(l); - - return rv; -} - - -const char* -bc_config_get(bc_config_t *config, const char *section, const char *key) -{ - if (config == NULL) - return NULL; - - const bc_configparser_section_t *s = bc_trie_lookup(config->root, section); - if (s == NULL) - return NULL; - - if (s->type != CONFIG_SECTION_TYPE_MAP) - return NULL; - - return bc_trie_lookup(s->data, key); -} - - -const char* -bc_config_get_with_default(bc_config_t *config, const char *section, const char *key, - const char *default_) -{ - const char *rv = bc_config_get(config, section, key); - if (rv == NULL) - return default_; - return rv; -} - - -char** -bc_config_get_list(bc_config_t *config, const char *section) -{ - if (config == NULL) - return NULL; - - const bc_configparser_section_t *s = bc_trie_lookup(config->root, section); - if (s == NULL) - return NULL; - - if (s->type != CONFIG_SECTION_TYPE_LIST) - return NULL; - - char **rv = bc_malloc(sizeof(char*) * (bc_slist_length(s->data) + 1)); - - size_t i = 0; - for (bc_slist_t *tmp = s->data; tmp != NULL; tmp = tmp->next, i++) - rv[i] = bc_strdup(tmp->data); - rv[i] = NULL; - - return rv; -} - - -void -bc_config_free(bc_config_t *config) -{ - if (config == NULL) - return; - bc_trie_free(config->root); - free(config); -} diff --git a/src/common/config-parser.h b/src/common/config-parser.h deleted file mode 100644 index 0b75ff8..0000000 --- a/src/common/config-parser.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#ifndef _CONFIG_PARSER_H -#define _CONFIG_PARSER_H - -#include <stddef.h> -#include "utils.h" -#include "error.h" - -typedef struct { - bc_trie_t *root; -} bc_config_t; - -bc_config_t* bc_config_parse(const char *src, size_t src_len, - const char *list_sections[], bc_error_t **err); -char** bc_config_list_sections(bc_config_t *config); -char** bc_config_list_keys(bc_config_t *config, const char *section); -const char* bc_config_get(bc_config_t *config, const char *section, - const char *key); -const char* bc_config_get_with_default(bc_config_t *config, const char *section, - const char *key, const char *default_); -char** bc_config_get_list(bc_config_t *config, const char *section); -void bc_config_free(bc_config_t *config); - -#endif /* _CONFIG_PARSER_H */ diff --git a/src/common/error.h b/src/common/error.h deleted file mode 100644 index b85f006..0000000 --- a/src/common/error.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#ifndef _ERROR_H -#define _ERROR_H - -#include <stddef.h> - -// error handling is centralized here for the sake of simplicity :/ -typedef enum { - - // errors for src/common - BC_ERROR_CONFIG_PARSER = 1, - BC_ERROR_FILE, - - // errors for src/blogc - BLOGC_ERROR_SOURCE_PARSER = 100, - 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, - BLOGC_MAKE_ERROR_UTILS, - -} bc_error_type_t; - -typedef struct { - char *msg; - bc_error_type_t type; -} bc_error_t; - -bc_error_t* bc_error_new(bc_error_type_t type, const char *msg); -bc_error_t* bc_error_new_printf(bc_error_type_t type, const char *format, ...); -bc_error_t* bc_error_parser(bc_error_type_t type, const char *src, - size_t src_len, size_t current, const char *format, ...); -void bc_error_print(bc_error_t *err, const char *prefix); -void bc_error_free(bc_error_t *err); - -#endif /* _ERROR_H */ diff --git a/src/common/file.c b/src/common/file.c deleted file mode 100644 index 1e55f64..0000000 --- a/src/common/file.c +++ /dev/null @@ -1,66 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#include <errno.h> -#include <stdbool.h> -#include <stdint.h> -#include <stdio.h> -#include <string.h> -#include "file.h" -#include "error.h" -#include "utf8.h" -#include "utils.h" - - -char* -bc_file_get_contents(const char *path, bool utf8, size_t *len, bc_error_t **err) -{ - if (path == NULL || len == NULL || err == NULL || *err != NULL) - return NULL; - - *len = 0; - FILE *fp = fopen(path, "r"); - - if (fp == NULL) { - int tmp_errno = errno; - *err = bc_error_new_printf(BC_ERROR_FILE, - "Failed to open file (%s): %s", path, strerror(tmp_errno)); - return NULL; - } - - bc_string_t *str = bc_string_new(); - char buffer[BC_FILE_CHUNK_SIZE]; - char *tmp; - - while (!feof(fp)) { - size_t read_len = fread(buffer, sizeof(char), BC_FILE_CHUNK_SIZE, fp); - - tmp = buffer; - - if (utf8 && str->len == 0 && read_len > 0) { - // skipping BOM before validation, for performance. should be safe - // enough - size_t skip = bc_utf8_skip_bom((uint8_t*) buffer, read_len); - read_len -= skip; - tmp += skip; - } - - *len += read_len; - bc_string_append_len(str, tmp, read_len); - } - fclose(fp); - - if (utf8 && !bc_utf8_validate_str(str)) { - *err = bc_error_new_printf(BC_ERROR_FILE, - "File content is not valid UTF-8: %s", path); - bc_string_free(str, true); - return NULL; - } - - return bc_string_free(str, false); -} diff --git a/src/common/file.h b/src/common/file.h deleted file mode 100644 index d002bf7..0000000 --- a/src/common/file.h +++ /dev/null @@ -1,20 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#ifndef _FILE_H -#define _FILE_H - -#include <stddef.h> -#include <stdbool.h> -#include "error.h" - -#define BC_FILE_CHUNK_SIZE 1024 - -char* bc_file_get_contents(const char *path, bool utf8, size_t *len, bc_error_t **err); - -#endif /* _FILE_H */ diff --git a/src/common/sort.c b/src/common/sort.c deleted file mode 100644 index 3e24706..0000000 --- a/src/common/sort.c +++ /dev/null @@ -1,44 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 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 "utils.h" -#include "sort.h" - - -bc_slist_t* -bc_slist_sort(bc_slist_t *l, bc_sort_func_t cmp) -{ - if (l == NULL) { - return NULL; - } - - bool swapped = false; - bc_slist_t *lptr = NULL; - bc_slist_t *rptr = NULL; - - do { - swapped = false; - lptr = l; - - while (lptr->next != rptr) { - if (0 < cmp(lptr->data, lptr->next->data)) { - void *tmp = lptr->data; - lptr->data = lptr->next->data; - lptr->next->data = tmp; - swapped = true; - } - - lptr = lptr->next; - } - - rptr = lptr; - } while(swapped); - - return l; -} diff --git a/src/common/sort.h b/src/common/sort.h deleted file mode 100644 index 9f4dda2..0000000 --- a/src/common/sort.h +++ /dev/null @@ -1,18 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#ifndef _SORT_H -#define _SORT_H - -#include "utils.h" - -typedef int (*bc_sort_func_t) (const void *a, const void *b); - -bc_slist_t* bc_slist_sort(bc_slist_t *l, bc_sort_func_t cmp); - -#endif /* _SORT_H */ diff --git a/src/common/stdin.c b/src/common/stdin.c deleted file mode 100644 index 556c7cf..0000000 --- a/src/common/stdin.c +++ /dev/null @@ -1,28 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2020 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 "utils.h" -#include "stdin.h" - - -// splitted in single file to make it easier to test -char* -bc_stdin_read(size_t *len) -{ - if (len == NULL) - return NULL; - - int c; - bc_string_t *rv = bc_string_new(); - while (EOF != (c = fgetc(stdin))) - bc_string_append_c(rv, c); - *len = rv->len; - return bc_string_free(rv, false); -} diff --git a/src/common/stdin.h b/src/common/stdin.h deleted file mode 100644 index b27ecaf..0000000 --- a/src/common/stdin.h +++ /dev/null @@ -1,16 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2020 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#ifndef _STDIN_H -#define _STDIN_H - -#include <stddef.h> - -char* bc_stdin_read(size_t *len); - -#endif /* _STDIN_H */ diff --git a/src/common/utils.h b/src/common/utils.h deleted file mode 100644 index 4bdab8c..0000000 --- a/src/common/utils.h +++ /dev/null @@ -1,110 +0,0 @@ -/* - * blogc: A blog compiler. - * Copyright (C) 2014-2020 Rafael G. Martins <rafael@rafaelmartins.eng.br> - * - * This program can be distributed under the terms of the BSD License. - * See the file LICENSE. - */ - -#ifndef _UTILS_H -#define _UTILS_H - -#include <stddef.h> -#include <stdarg.h> -#include <stdbool.h> - - -// memory - -typedef void (*bc_free_func_t) (void *ptr); - -void* bc_malloc(size_t size); -void* bc_realloc(void *ptr, size_t size); - - -// slist - -typedef struct _bc_slist_t { - struct _bc_slist_t *next; - void *data; -} bc_slist_t; - -bc_slist_t* bc_slist_append(bc_slist_t *l, void *data); -bc_slist_t* bc_slist_prepend(bc_slist_t *l, void *data); -bc_slist_t* bc_slist_append_list(bc_slist_t *l, bc_slist_t *n); -void bc_slist_free(bc_slist_t *l); -void bc_slist_free_full(bc_slist_t *l, bc_free_func_t free_func); -size_t bc_slist_length(bc_slist_t *l); - - -// strfuncs - -char* bc_strdup(const char *s); -char* bc_strndup(const char *s, size_t n); -char* bc_strdup_vprintf(const char *format, va_list ap); -char* bc_strdup_printf(const char *format, ...); -bool bc_isspace(int c); -bool bc_str_starts_with(const char *str, const char *prefix); -bool bc_str_ends_with(const char *str, const char *suffix); -char* bc_str_lstrip(char *str); -char* bc_str_rstrip(char *str); -char* bc_str_strip(char *str); -char** bc_str_split(const char *str, char c, size_t max_pieces); -char* bc_str_replace(const char *str, const char search, const char *replace); -char* bc_str_find(const char *str, char c); -bool bc_str_to_bool(const char *str); -void bc_strv_free(char **strv); -char* bc_strv_join(char **strv, const char *separator); -size_t bc_strv_length(char **strv); - - -// string - -typedef struct { - char *str; - size_t len; - size_t allocated_len; -} bc_string_t; - -bc_string_t* bc_string_new(void); -char* bc_string_free(bc_string_t *str, bool free_str); -bc_string_t* bc_string_dup(bc_string_t *str); -bc_string_t* bc_string_append_len(bc_string_t *str, const char *suffix, size_t len); -bc_string_t* bc_string_append(bc_string_t *str, const char *suffix); -bc_string_t* bc_string_append_c(bc_string_t *str, char c); -bc_string_t* bc_string_append_printf(bc_string_t *str, const char *format, ...); -bc_string_t* bc_string_append_escaped(bc_string_t *str, const char *suffix); - - -// trie - -typedef struct _bc_trie_node_t { - char key; - void *data; - struct _bc_trie_node_t *next, *child; -} bc_trie_node_t; - -struct _bc_trie_t { - bc_trie_node_t *root; - bc_free_func_t free_func; -}; - -typedef struct _bc_trie_t bc_trie_t; - -typedef void (*bc_trie_foreach_func_t)(const char *key, void *data, - void *user_data); - -bc_trie_t* bc_trie_new(bc_free_func_t free_func); -void bc_trie_free(bc_trie_t *trie); -void bc_trie_insert(bc_trie_t *trie, const char *key, void *data); -void* bc_trie_lookup(bc_trie_t *trie, const char *key); -size_t bc_trie_size(bc_trie_t *trie); -void bc_trie_foreach(bc_trie_t *trie, bc_trie_foreach_func_t func, - void *user_data); - - -// shell - -char* bc_shell_quote(const char *command); - -#endif /* _UTILS_H */ diff --git a/src/blogc/content-parser.c b/src/content-parser.c index a42f6f6..410fc90 100644 --- a/src/blogc/content-parser.c +++ b/src/content-parser.c @@ -1,18 +1,18 @@ /* * blogc: A blog compiler. - * Copyright (C) 2014-2020 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * 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 <stdio.h> #include <stdlib.h> #include <string.h> #include "content-parser.h" -#include "toctree.h" -#include "../common/utils.h" +#include "utils.h" // this is a half ass implementation of a markdown-like syntax. bugs are // expected. feel free to improve the parser and add new features. @@ -23,7 +23,7 @@ blogc_slugify(const char *str) { if (str == NULL) return NULL; - char *new_str = bc_strdup(str); + char *new_str = sb_strdup(str); int diff = 'a' - 'A'; // just to avoid magic numbers for (size_t i = 0; new_str[i] != '\0'; i++) { if (new_str[i] >= 'a' && new_str[i] <= 'z') @@ -61,13 +61,13 @@ htmlentities(char c) static void -htmlentities_append(bc_string_t *str, char c) +htmlentities_append(sb_string_t *str, char c) { const char *e = htmlentities(c); if (e == NULL) - bc_string_append_c(str, c); + sb_string_append_c(str, c); else - bc_string_append(str, e); + sb_string_append(str, e); } @@ -76,10 +76,10 @@ blogc_htmlentities(const char *str) { if (str == NULL) return NULL; - bc_string_t *rv = bc_string_new(); + sb_string_t *rv = sb_string_new(); for (size_t i = 0; str[i] != '\0'; i++) htmlentities_append(rv, str[i]); - return bc_string_free(rv, false); + return sb_string_free(rv, false); } @@ -88,7 +88,7 @@ blogc_fix_description(const char *paragraph) { if (paragraph == NULL) return NULL; - bc_string_t *rv = bc_string_new(); + sb_string_t *rv = sb_string_new(); bool last = false; bool newline = false; char *tmp = NULL; @@ -102,12 +102,12 @@ blogc_fix_description(const char *paragraph) case '\n': if (newline) break; - tmp = bc_strndup(paragraph + start, current - start); - bc_string_append(rv, bc_str_strip(tmp)); + tmp = sb_strndup(paragraph + start, current - start); + sb_string_append(rv, sb_str_strip(tmp)); free(tmp); tmp = NULL; if (!last) - bc_string_append_c(rv, ' '); + sb_string_append_c(rv, ' '); start = current + 1; newline = true; break; @@ -118,8 +118,8 @@ blogc_fix_description(const char *paragraph) break; current++; } - tmp = blogc_htmlentities(bc_str_strip(rv->str)); - bc_string_free(rv, true); + tmp = blogc_htmlentities(sb_str_strip(rv->str)); + sb_string_free(rv, true); return tmp; } @@ -139,8 +139,7 @@ typedef enum { CONTENT_CODE, CONTENT_CODE_START, CONTENT_CODE_END, - CONTENT_UNORDERED_LIST_OR_HORIZONTAL_RULE_OR_EMPHASIS, - CONTENT_HORIZONTAL_RULE_OR_EMPHASIS, + CONTENT_UNORDERED_LIST_OR_HORIZONTAL_RULE, CONTENT_HORIZONTAL_RULE, CONTENT_UNORDERED_LIST_START, CONTENT_UNORDERED_LIST_END, @@ -191,7 +190,7 @@ blogc_content_parse_inline_internal(const char *src, size_t src_len) size_t start_link = 0; char *link1 = NULL; - bc_string_t *rv = bc_string_new(); + sb_string_t *rv = sb_string_new(); blogc_content_parser_inline_state_t state = CONTENT_INLINE_START; @@ -245,15 +244,15 @@ blogc_content_parse_inline_internal(const char *src, size_t src_len) state = CONTENT_INLINE_ASTERISK_DOUBLE; break; } - tmp = bc_str_find(src + current, '*'); + tmp = sb_str_find(src + current, '*'); if (tmp == NULL || ((tmp - src) >= src_len)) { - bc_string_append_c(rv, '*'); + sb_string_append_c(rv, '*'); state = CONTENT_INLINE_START; continue; } tmp2 = blogc_content_parse_inline_internal( src + current, (tmp - src) - current); - bc_string_append_printf(rv, "<em>%s</em>", tmp2); + sb_string_append_printf(rv, "<em>%s</em>", tmp2); current = tmp - src; tmp = NULL; free(tmp2); @@ -264,21 +263,21 @@ blogc_content_parse_inline_internal(const char *src, size_t src_len) case CONTENT_INLINE_ASTERISK_DOUBLE: tmp = src + current; do { - tmp = bc_str_find(tmp, '*'); + tmp = sb_str_find(tmp, '*'); if (((tmp - src) < src_len) && *(tmp + 1) == '*') { break; } tmp++; } while (tmp != NULL && (tmp - src) < src_len); if (tmp == NULL || ((tmp - src) >= src_len)) { - bc_string_append_c(rv, '*'); - bc_string_append_c(rv, '*'); + sb_string_append_c(rv, '*'); + sb_string_append_c(rv, '*'); state = CONTENT_INLINE_START; continue; } tmp2 = blogc_content_parse_inline_internal( src + current, (tmp - src) - current); - bc_string_append_printf(rv, "<strong>%s</strong>", tmp2); + sb_string_append_printf(rv, "<strong>%s</strong>", tmp2); current = tmp - src + 1; tmp = NULL; free(tmp2); @@ -291,15 +290,15 @@ blogc_content_parse_inline_internal(const char *src, size_t src_len) state = CONTENT_INLINE_UNDERSCORE_DOUBLE; break; } - tmp = bc_str_find(src + current, '_'); + tmp = sb_str_find(src + current, '_'); if (tmp == NULL || ((tmp - src) >= src_len)) { - bc_string_append_c(rv, '_'); + sb_string_append_c(rv, '_'); state = CONTENT_INLINE_START; continue; } tmp2 = blogc_content_parse_inline_internal( src + current, (tmp - src) - current); - bc_string_append_printf(rv, "<em>%s</em>", tmp2); + sb_string_append_printf(rv, "<em>%s</em>", tmp2); current = tmp - src; tmp = NULL; free(tmp2); @@ -310,21 +309,21 @@ blogc_content_parse_inline_internal(const char *src, size_t src_len) case CONTENT_INLINE_UNDERSCORE_DOUBLE: tmp = src + current; do { - tmp = bc_str_find(tmp, '_'); + tmp = sb_str_find(tmp, '_'); if (((tmp - src) < src_len) && *(tmp + 1) == '_') { break; } tmp++; } while (tmp != NULL && (tmp - src) < src_len); if (tmp == NULL || ((tmp - src) >= src_len)) { - bc_string_append_c(rv, '_'); - bc_string_append_c(rv, '_'); + sb_string_append_c(rv, '_'); + sb_string_append_c(rv, '_'); state = CONTENT_INLINE_START; continue; } tmp2 = blogc_content_parse_inline_internal( src + current, (tmp - src) - current); - bc_string_append_printf(rv, "<strong>%s</strong>", tmp2); + sb_string_append_printf(rv, "<strong>%s</strong>", tmp2); current = tmp - src + 1; tmp = NULL; free(tmp2); @@ -337,19 +336,19 @@ blogc_content_parse_inline_internal(const char *src, size_t src_len) state = CONTENT_INLINE_BACKTICKS_DOUBLE; break; } - tmp = bc_str_find(src + current, '`'); + tmp = sb_str_find(src + current, '`'); if (tmp == NULL || ((tmp - src) >= src_len)) { - bc_string_append_c(rv, '`'); + sb_string_append_c(rv, '`'); state = CONTENT_INLINE_START; continue; } - tmp3 = bc_strndup(src + current, (tmp - src) - current); + tmp3 = sb_strndup(src + current, (tmp - src) - current); tmp2 = blogc_htmlentities(tmp3); free(tmp3); tmp3 = NULL; - bc_string_append(rv, "<code>"); - bc_string_append(rv, tmp2); - bc_string_append(rv, "</code>"); + sb_string_append(rv, "<code>"); + sb_string_append_escaped(rv, tmp2); + sb_string_append(rv, "</code>"); current = tmp - src; tmp = NULL; free(tmp2); @@ -360,25 +359,25 @@ blogc_content_parse_inline_internal(const char *src, size_t src_len) case CONTENT_INLINE_BACKTICKS_DOUBLE: tmp = src + current; do { - tmp = bc_str_find(tmp, '`'); + tmp = sb_str_find(tmp, '`'); if (((tmp - src) < src_len) && *(tmp + 1) == '`') { break; } tmp++; } while (tmp != NULL && (tmp - src) < src_len); if (tmp == NULL || ((tmp - src) >= src_len)) { - bc_string_append_c(rv, '`'); - bc_string_append_c(rv, '`'); + sb_string_append_c(rv, '`'); + sb_string_append_c(rv, '`'); state = CONTENT_INLINE_START; continue; } - tmp3 = bc_strndup(src + current, (tmp - src) - current); + tmp3 = sb_strndup(src + current, (tmp - src) - current); tmp2 = blogc_htmlentities(tmp3); free(tmp3); tmp3 = NULL; - bc_string_append(rv, "<code>"); - bc_string_append(rv, tmp2); - bc_string_append(rv, "</code>"); + sb_string_append(rv, "<code>"); + sb_string_append_escaped(rv, tmp2); + sb_string_append(rv, "</code>"); current = tmp - src + 1; tmp = NULL; free(tmp2); @@ -399,24 +398,24 @@ blogc_content_parse_inline_internal(const char *src, size_t src_len) case CONTENT_INLINE_LINK_AUTO: tmp = src + current; do { - tmp = bc_str_find(tmp, ']'); + tmp = sb_str_find(tmp, ']'); if (((tmp - src) < src_len) && *(tmp + 1) == ']') { break; } tmp++; } while (tmp != NULL && (tmp - src) < src_len); if (tmp == NULL || ((tmp - src) >= src_len)) { - bc_string_append_c(rv, '['); - bc_string_append_c(rv, '['); + sb_string_append_c(rv, '['); + sb_string_append_c(rv, '['); state = CONTENT_INLINE_START; continue; } - tmp2 = bc_strndup(src + current, (tmp - src) - current); - bc_string_append(rv, "<a href=\""); - bc_string_append_escaped(rv, tmp2); - bc_string_append(rv, "\">"); - bc_string_append_escaped(rv, tmp2); - bc_string_append(rv, "</a>"); + tmp2 = sb_strndup(src + current, (tmp - src) - current); + sb_string_append(rv, "<a href=\""); + sb_string_append_escaped(rv, tmp2); + sb_string_append(rv, "\">"); + sb_string_append_escaped(rv, tmp2); + sb_string_append(rv, "</a>"); current = tmp - src + 1; tmp = NULL; free(tmp2); @@ -435,7 +434,7 @@ blogc_content_parse_inline_internal(const char *src, size_t src_len) } if (c == ']') { if (--count == 0) { - link1 = bc_strndup(src + start_link, current - start_link); + link1 = sb_strndup(src + start_link, current - start_link); state = CONTENT_INLINE_LINK_URL_START; } } @@ -449,7 +448,7 @@ blogc_content_parse_inline_internal(const char *src, size_t src_len) start = current + 1; break; } - bc_string_append_c(rv, '['); + sb_string_append_c(rv, '['); state = CONTENT_INLINE_START; current = start_link; start_link = 0; @@ -461,13 +460,13 @@ blogc_content_parse_inline_internal(const char *src, size_t src_len) break; } if (c == ')') { - tmp2 = bc_strndup(src + start, current - start); + tmp2 = sb_strndup(src + start, current - start); tmp3 = blogc_content_parse_inline(link1); free(link1); link1 = NULL; - bc_string_append(rv, "<a href=\""); - bc_string_append_escaped(rv, tmp2); - bc_string_append_printf(rv, "\">%s</a>", tmp3); + sb_string_append(rv, "<a href=\""); + sb_string_append_escaped(rv, tmp2); + sb_string_append_printf(rv, "\">%s</a>", tmp3); free(tmp2); tmp2 = NULL; free(tmp3); @@ -484,7 +483,7 @@ blogc_content_parse_inline_internal(const char *src, size_t src_len) start_link = current + 1; break; } - bc_string_append_c(rv, '!'); + sb_string_append_c(rv, '!'); state = CONTENT_INLINE_START; continue; @@ -494,7 +493,7 @@ blogc_content_parse_inline_internal(const char *src, size_t src_len) break; } if (c == ']') { - link1 = bc_strndup(src + start_link, current - start_link); + link1 = sb_strndup(src + start_link, current - start_link); state = CONTENT_INLINE_IMAGE_URL_START; } break; @@ -507,8 +506,8 @@ blogc_content_parse_inline_internal(const char *src, size_t src_len) start = current + 1; break; } - bc_string_append_c(rv, '!'); - bc_string_append_c(rv, '['); + sb_string_append_c(rv, '!'); + sb_string_append_c(rv, '['); state = CONTENT_INLINE_START; current = start_link; start_link = 0; @@ -520,12 +519,12 @@ blogc_content_parse_inline_internal(const char *src, size_t src_len) break; } if (c == ')') { - tmp2 = bc_strndup(src + start, current - start); - bc_string_append(rv, "<img src=\""); - bc_string_append_escaped(rv, tmp2); - bc_string_append(rv, "\" alt=\""); - bc_string_append_escaped(rv, link1); - bc_string_append(rv, "\">"); + tmp2 = sb_strndup(src + start, current - start); + sb_string_append(rv, "<img src=\""); + sb_string_append_escaped(rv, tmp2); + sb_string_append(rv, "\" alt=\""); + sb_string_append_escaped(rv, link1); + sb_string_append(rv, "\">"); free(tmp2); tmp2 = NULL; free(link1); @@ -538,31 +537,31 @@ blogc_content_parse_inline_internal(const char *src, size_t src_len) case CONTENT_INLINE_ENDASH: if (c == '-') { if (is_last) { - bc_string_append(rv, "–"); + sb_string_append(rv, "–"); state = CONTENT_INLINE_START; // wat break; } state = CONTENT_INLINE_EMDASH; break; } - bc_string_append_c(rv, '-'); + sb_string_append_c(rv, '-'); state = CONTENT_INLINE_START; continue; case CONTENT_INLINE_EMDASH: if (c == '-') { - bc_string_append(rv, "—"); + sb_string_append(rv, "—"); state = CONTENT_INLINE_START; break; } - bc_string_append(rv, "–"); + sb_string_append(rv, "–"); state = CONTENT_INLINE_START; continue; case CONTENT_INLINE_LINE_BREAK_START: if (c == ' ') { if (is_last) { - bc_string_append(rv, "<br />"); + sb_string_append(rv, "<br />"); state = CONTENT_INLINE_START; // wat break; } @@ -570,14 +569,14 @@ blogc_content_parse_inline_internal(const char *src, size_t src_len) state = CONTENT_INLINE_LINE_BREAK; break; } - bc_string_append_c(rv, ' '); + sb_string_append_c(rv, ' '); state = CONTENT_INLINE_START; continue; case CONTENT_INLINE_LINE_BREAK: if (c == ' ') { if (is_last) { - bc_string_append(rv, "<br />"); + sb_string_append(rv, "<br />"); state = CONTENT_INLINE_START; // wat break; } @@ -585,12 +584,12 @@ blogc_content_parse_inline_internal(const char *src, size_t src_len) break; } if (c == '\n' || c == '\r') { - bc_string_append_printf(rv, "<br />%c", c); + sb_string_append_printf(rv, "<br />%c", c); state = CONTENT_INLINE_START; break; } for (size_t i = 0; i < count; i++) - bc_string_append_c(rv, ' '); + sb_string_append_c(rv, ' '); state = CONTENT_INLINE_START; continue; } @@ -605,14 +604,14 @@ blogc_content_parse_inline_internal(const char *src, size_t src_len) case CONTENT_INLINE_IMAGE_ALT: case CONTENT_INLINE_IMAGE_URL_START: case CONTENT_INLINE_IMAGE_URL: - bc_string_append_c(rv, '!'); + sb_string_append_c(rv, '!'); case CONTENT_INLINE_LINK_CONTENT: case CONTENT_INLINE_LINK_URL_START: case CONTENT_INLINE_LINK_URL: tmp2 = blogc_content_parse_inline(src + start_link); - bc_string_append_c(rv, '['); - bc_string_append_escaped(rv, tmp2); // no need to free, as it wil be done below. + sb_string_append_c(rv, '['); + sb_string_append_escaped(rv, tmp2); // no need to free, as it wil be done below. break; // add all the other states here explicitly, so the compiler helps us @@ -637,7 +636,7 @@ blogc_content_parse_inline_internal(const char *src, size_t src_len) free(tmp3); free(link1); - return bc_string_free(rv, false); + return sb_string_free(rv, false); } @@ -675,9 +674,36 @@ blogc_is_ordered_list_item(const char *str, size_t prefix_len) } -char* -blogc_content_parse(const char *src, size_t *end_excerpt, char **first_header, - char **description, char **endl, bc_slist_t **headers) +static blogc_content_node_t* +block_node_new(blogc_content_block_type_t type, char *content, sb_trie_t *parameters) +{ + blogc_content_node_t *rv = sb_malloc(sizeof(blogc_content_node_t)); + rv->node_type = BLOGC_CONTENT_BLOCK; + rv->type.block_type = type; + rv->content = content; + rv->parameters = parameters; + rv->child = NULL; + rv->next = NULL; + return rv; +} + + +static blogc_content_node_t* +inline_node_new(blogc_content_inline_type_t type, char *content, sb_trie_t *parameters) +{ + blogc_content_node_t *rv = sb_malloc(sizeof(blogc_content_node_t)); + rv->node_type = BLOGC_CONTENT_INLINE; + rv->type.inline_type = type; + rv->content = content; + rv->parameters = parameters; + rv->child = NULL; + rv->next = NULL; + return rv; +} + + +blogc_content_node_t* +blogc_content_parse_ast(const char *src, char **nl) { // src is always nul-terminated. size_t src_len = strlen(src); @@ -686,47 +712,30 @@ blogc_content_parse(const char *src, size_t *end_excerpt, char **first_header, size_t start = 0; size_t start2 = 0; size_t end = 0; - size_t eend = 0; size_t real_end = 0; - size_t header_level = 0; + unsigned int header_level = 0; char *prefix = NULL; size_t prefix_len = 0; char *tmp = NULL; char *tmp2 = NULL; char *parsed = NULL; - char *slug = NULL; - - char *line_ending = NULL; - bool line_ending_found = false; - if (endl != NULL) { - if (*endl != NULL) { - line_ending_found = true; - } - else { - *endl = bc_malloc(3 * sizeof(char)); - } - line_ending = *endl; - } - else { - line_ending = bc_malloc(3 * sizeof(char)); - } // this isn't empty because we need some reasonable default value in the // unlikely case that we need to print some line ending before evaluating // the "real" value. - if (!line_ending_found) { - line_ending[0] = '\n'; - line_ending[1] = '\0'; - } + char line_ending[3] = "\n"; + bool line_ending_found = false; char d = '\0'; - bc_slist_t *lines = NULL; - bc_slist_t *lines2 = NULL; + sb_slist_t *lines = NULL; + sb_slist_t *lines2 = NULL; - bc_string_t *rv = bc_string_new(); - bc_string_t *tmp_str = NULL; + sb_string_t *tmp_str = NULL; + + blogc_content_node_t *ast = NULL; + blogc_content_node_t *last = NULL; blogc_content_parser_state_t state = CONTENT_START_LINE; @@ -764,11 +773,8 @@ blogc_content_parse(const char *src, size_t *end_excerpt, char **first_header, break; start = current; if (c == '.') { - if (end_excerpt != NULL) { - eend = rv->len; // fuck it - state = CONTENT_EXCERPT; - break; - } + state = CONTENT_EXCERPT; + break; } if (c == '#') { header_level = 1; @@ -777,7 +783,7 @@ blogc_content_parse(const char *src, size_t *end_excerpt, char **first_header, } if (c == '*' || c == '+' || c == '-') { start2 = current; - state = CONTENT_UNORDERED_LIST_OR_HORIZONTAL_RULE_OR_EMPHASIS; + state = CONTENT_UNORDERED_LIST_OR_HORIZONTAL_RULE; d = c; break; } @@ -804,27 +810,28 @@ blogc_content_parse(const char *src, size_t *end_excerpt, char **first_header, break; case CONTENT_EXCERPT: - if (end_excerpt != NULL) { - if (c == '.') - break; - if (c == '\n' || c == '\r') { - state = CONTENT_EXCERPT_END; - break; - } + if (c == '.') + break; + if (c == '\n' || c == '\r') { + state = CONTENT_EXCERPT_END; + break; } - eend = 0; state = CONTENT_PARAGRAPH; break; case CONTENT_EXCERPT_END: - if (end_excerpt != NULL) { - if (c == '\n' || c == '\r') { - *end_excerpt = eend; - state = CONTENT_START_LINE; - break; + if (c == '\n' || c == '\r') { + if (ast == NULL) { + ast = block_node_new(BLOGC_CONTENT_BLOCK_EXCERPT, NULL, NULL); + last = ast; + } + else { + last->next = block_node_new(BLOGC_CONTENT_BLOCK_EXCERPT, NULL, NULL); + last = last->next; } + state = CONTENT_START_LINE; + break; } - eend = 0; state = CONTENT_PARAGRAPH_END; break; @@ -853,23 +860,17 @@ blogc_content_parse(const char *src, size_t *end_excerpt, char **first_header, if (c == '\n' || c == '\r' || is_last) { end = is_last && c != '\n' && c != '\r' ? src_len : (real_end != 0 ? real_end : current); - tmp = bc_strndup(src + start, end - start); - if (first_header != NULL && *first_header == NULL) - *first_header = blogc_htmlentities(tmp); - parsed = blogc_content_parse_inline(tmp); - slug = blogc_slugify(tmp); - if (headers != NULL) - *headers = blogc_toctree_append(*headers, header_level, slug, parsed); - if (slug == NULL) - bc_string_append_printf(rv, "<h%d>%s</h%d>%s", - header_level, parsed, header_level, line_ending); - else - bc_string_append_printf(rv, "<h%d id=\"%s\">%s</h%d>%s", - header_level, slug, parsed, header_level, - line_ending); - free(slug); - free(parsed); - parsed = NULL; + tmp = sb_strndup(src + start, end - start); + sb_trie_t *t = sb_trie_new(free); + sb_trie_insert(t, "level", sb_strdup_printf("%d", header_level)); + if (ast == NULL) { + ast = block_node_new(BLOGC_CONTENT_BLOCK_HEADER, blogc_content_parse_inline(tmp), t); + last = ast; + } + else { + last->next = block_node_new(BLOGC_CONTENT_BLOCK_HEADER, blogc_content_parse_inline(tmp), t); // TODO: inline-me + last = last->next; + } free(tmp); tmp = NULL; state = CONTENT_START_LINE; @@ -888,10 +889,16 @@ blogc_content_parse(const char *src, size_t *end_excerpt, char **first_header, case CONTENT_HTML_END: if (c == '\n' || c == '\r' || is_last) { - tmp = bc_strndup(src + start, end - start); - bc_string_append_printf(rv, "%s%s", tmp, line_ending); - free(tmp); - tmp = NULL; + if (ast == NULL) { + ast = block_node_new(BLOGC_CONTENT_BLOCK_RAW, + sb_strndup(src + start, end - start), NULL); + last = ast; + } + else { + last->next = block_node_new(BLOGC_CONTENT_BLOCK_RAW, + sb_strndup(src + start, end - start), NULL); + last = last->next; + } state = CONTENT_START_LINE; start = current; } @@ -902,7 +909,7 @@ blogc_content_parse(const char *src, size_t *end_excerpt, char **first_header, case CONTENT_BLOCKQUOTE: if (c == ' ' || c == '\t') break; - prefix = bc_strndup(src + start, current - start); + prefix = sb_strndup(src + start, current - start); state = CONTENT_BLOCKQUOTE_START; break; @@ -910,16 +917,16 @@ blogc_content_parse(const char *src, size_t *end_excerpt, char **first_header, if (c == '\n' || c == '\r' || is_last) { end = is_last && c != '\n' && c != '\r' ? src_len : (real_end != 0 ? real_end : current); - tmp = bc_strndup(src + start2, end - start2); - if (bc_str_starts_with(tmp, prefix)) { - lines = bc_slist_append(lines, bc_strdup(tmp + strlen(prefix))); + tmp = sb_strndup(src + start2, end - start2); + if (sb_str_starts_with(tmp, prefix)) { + lines = sb_slist_append(lines, sb_strdup(tmp + strlen(prefix))); state = CONTENT_BLOCKQUOTE_END; } else { state = CONTENT_PARAGRAPH; free(prefix); prefix = NULL; - bc_slist_free_full(lines, free); + sb_slist_free_full(lines, free); lines = NULL; if (is_last) { free(tmp); @@ -935,21 +942,25 @@ blogc_content_parse(const char *src, size_t *end_excerpt, char **first_header, case CONTENT_BLOCKQUOTE_END: if (c == '\n' || c == '\r' || is_last) { - tmp_str = bc_string_new(); - for (bc_slist_t *l = lines; l != NULL; l = l->next) - bc_string_append_printf(tmp_str, "%s%s", l->data, + tmp_str = sb_string_new(); + for (sb_slist_t *l = lines; l != NULL; l = l->next) + sb_string_append_printf(tmp_str, "%s%s", l->data, line_ending); - // do not propagate title and description to blockquote parsing, - // because we just want paragraphs from first level of - // content. - tmp = blogc_content_parse(tmp_str->str, NULL, NULL, NULL, endl, NULL); - bc_string_append_printf(rv, "<blockquote>%s</blockquote>%s", - tmp, line_ending); - free(tmp); - tmp = NULL; - bc_string_free(tmp_str, true); + if (ast == NULL) { + ast = block_node_new(BLOGC_CONTENT_BLOCK_BLOCKQUOTE, + NULL, NULL); + ast->child = blogc_content_parse_ast(tmp_str->str, nl); + last = ast; + } + else { + last->next = block_node_new(BLOGC_CONTENT_BLOCK_BLOCKQUOTE, + NULL, NULL); + last->next->child = blogc_content_parse_ast(tmp_str->str, nl); + last = last->next; + } + sb_string_free(tmp_str, true); tmp_str = NULL; - bc_slist_free_full(lines, free); + sb_slist_free_full(lines, free); lines = NULL; free(prefix); prefix = NULL; @@ -965,7 +976,7 @@ blogc_content_parse(const char *src, size_t *end_excerpt, char **first_header, case CONTENT_CODE: if (c == ' ' || c == '\t') break; - prefix = bc_strndup(src + start, current - start); + prefix = sb_strndup(src + start, current - start); state = CONTENT_CODE_START; break; @@ -973,16 +984,16 @@ blogc_content_parse(const char *src, size_t *end_excerpt, char **first_header, if (c == '\n' || c == '\r' || is_last) { end = is_last && c != '\n' && c != '\r' ? src_len : (real_end != 0 ? real_end : current); - tmp = bc_strndup(src + start2, end - start2); - if (bc_str_starts_with(tmp, prefix)) { - lines = bc_slist_append(lines, bc_strdup(tmp + strlen(prefix))); + tmp = sb_strndup(src + start2, end - start2); + if (sb_str_starts_with(tmp, prefix)) { + lines = sb_slist_append(lines, sb_strdup(tmp + strlen(prefix))); state = CONTENT_CODE_END; } else { state = CONTENT_PARAGRAPH; free(prefix); prefix = NULL; - bc_slist_free_full(lines, free); + sb_slist_free_full(lines, free); lines = NULL; free(tmp); tmp = NULL; @@ -998,18 +1009,26 @@ blogc_content_parse(const char *src, size_t *end_excerpt, char **first_header, case CONTENT_CODE_END: if (c == '\n' || c == '\r' || is_last) { - bc_string_append(rv, "<pre><code>"); - for (bc_slist_t *l = lines; l != NULL; l = l->next) { - char *tmp_line = blogc_htmlentities(l->data); + tmp_str = sb_string_new(); + for (sb_slist_t *l = lines; l != NULL; l = l->next) { if (l->next == NULL) - bc_string_append_printf(rv, "%s", tmp_line); + sb_string_append_printf(tmp_str, "%s", l->data); else - bc_string_append_printf(rv, "%s%s", tmp_line, + sb_string_append_printf(tmp_str, "%s%s", l->data, line_ending); - free(tmp_line); } - bc_string_append_printf(rv, "</code></pre>%s", line_ending); - bc_slist_free_full(lines, free); + if (ast == NULL) { + ast = block_node_new(BLOGC_CONTENT_BLOCK_CODE, + sb_string_free(tmp_str, false), NULL); + last = ast; + } + else { + last->next = block_node_new(BLOGC_CONTENT_BLOCK_CODE, + sb_string_free(tmp_str, false), NULL); + last = last->next; + } + tmp_str = NULL; + sb_slist_free_full(lines, free); lines = NULL; free(prefix); prefix = NULL; @@ -1022,33 +1041,17 @@ blogc_content_parse(const char *src, size_t *end_excerpt, char **first_header, } break; - case CONTENT_UNORDERED_LIST_OR_HORIZONTAL_RULE_OR_EMPHASIS: - if (current == start+1) { - if (c == d) { // horizontal rule or '**' emphasis - state = CONTENT_HORIZONTAL_RULE_OR_EMPHASIS; - break; - } - else if (c != ' ' && c != '\t' && d == '*') { // is '*' emphasis - state = CONTENT_PARAGRAPH; - break; - } - } - if (c == ' ' || c == '\t') - break; - prefix = bc_strndup(src + start, current - start); - state = CONTENT_UNORDERED_LIST_START; - break; - - case CONTENT_HORIZONTAL_RULE_OR_EMPHASIS: - // 3rd '-' or '*' required for a horizontal rule + case CONTENT_UNORDERED_LIST_OR_HORIZONTAL_RULE: if (c == d) { state = CONTENT_HORIZONTAL_RULE; if (is_last) continue; break; } - // is '**' emphasis - state = CONTENT_PARAGRAPH; + if (c == ' ' || c == '\t') + break; + prefix = sb_strndup(src + start, current - start); + state = CONTENT_UNORDERED_LIST_START; break; case CONTENT_HORIZONTAL_RULE: @@ -1056,7 +1059,16 @@ blogc_content_parse(const char *src, size_t *end_excerpt, char **first_header, break; } if (c == '\n' || c == '\r' || is_last) { - bc_string_append_printf(rv, "<hr />%s", line_ending); + if (ast == NULL) { + ast = block_node_new(BLOGC_CONTENT_BLOCK_HORIZONTAL_RULE, + NULL, NULL); + last = ast; + } + else { + last->next = block_node_new(BLOGC_CONTENT_BLOCK_HORIZONTAL_RULE, + NULL, NULL); + last = last->next; + } state = CONTENT_START_LINE; start = current; d = '\0'; @@ -1069,30 +1081,30 @@ blogc_content_parse(const char *src, size_t *end_excerpt, char **first_header, if (c == '\n' || c == '\r' || is_last) { end = is_last && c != '\n' && c != '\r' ? src_len : (real_end != 0 ? real_end : current); - tmp = bc_strndup(src + start2, end - start2); - tmp2 = bc_strdup_printf("%-*s", strlen(prefix), ""); - if (bc_str_starts_with(tmp, prefix)) { + tmp = sb_strndup(src + start2, end - start2); + tmp2 = sb_strdup_printf("%-*s", strlen(prefix), ""); + if (sb_str_starts_with(tmp, prefix)) { if (lines2 != NULL) { - tmp_str = bc_string_new(); - for (bc_slist_t *l = lines2; l != NULL; l = l->next) { + tmp_str = sb_string_new(); + for (sb_slist_t *l = lines2; l != NULL; l = l->next) { if (l->next == NULL) - bc_string_append_printf(tmp_str, "%s", l->data); + sb_string_append_printf(tmp_str, "%s", l->data); else - bc_string_append_printf(tmp_str, "%s%s", l->data, + sb_string_append_printf(tmp_str, "%s%s", l->data, line_ending); } - bc_slist_free_full(lines2, free); + sb_slist_free_full(lines2, free); lines2 = NULL; parsed = blogc_content_parse_inline(tmp_str->str); - bc_string_free(tmp_str, true); - lines = bc_slist_append(lines, bc_strdup(parsed)); + sb_string_free(tmp_str, true); + lines = sb_slist_append(lines, sb_strdup(parsed)); free(parsed); parsed = NULL; } - lines2 = bc_slist_append(lines2, bc_strdup(tmp + strlen(prefix))); + lines2 = sb_slist_append(lines2, sb_strdup(tmp + strlen(prefix))); } - else if (bc_str_starts_with(tmp, tmp2)) { - lines2 = bc_slist_append(lines2, bc_strdup(tmp + strlen(prefix))); + else if (sb_str_starts_with(tmp, tmp2)) { + lines2 = sb_slist_append(lines2, sb_strdup(tmp + strlen(prefix))); } else { state = CONTENT_PARAGRAPH_END; @@ -1102,8 +1114,8 @@ blogc_content_parse(const char *src, size_t *end_excerpt, char **first_header, tmp2 = NULL; free(prefix); prefix = NULL; - bc_slist_free_full(lines, free); - bc_slist_free_full(lines2, free); + sb_slist_free_full(lines, free); + sb_slist_free_full(lines2, free); lines = NULL; if (is_last) continue; @@ -1122,28 +1134,46 @@ blogc_content_parse(const char *src, size_t *end_excerpt, char **first_header, if (c == '\n' || c == '\r' || is_last) { if (lines2 != NULL) { // FIXME: avoid repeting the code below - tmp_str = bc_string_new(); - for (bc_slist_t *l = lines2; l != NULL; l = l->next) { + tmp_str = sb_string_new(); + for (sb_slist_t *l = lines2; l != NULL; l = l->next) { if (l->next == NULL) - bc_string_append_printf(tmp_str, "%s", l->data); + sb_string_append_printf(tmp_str, "%s", l->data); else - bc_string_append_printf(tmp_str, "%s%s", l->data, + sb_string_append_printf(tmp_str, "%s%s", l->data, line_ending); } - bc_slist_free_full(lines2, free); + sb_slist_free_full(lines2, free); lines2 = NULL; parsed = blogc_content_parse_inline(tmp_str->str); - bc_string_free(tmp_str, true); - lines = bc_slist_append(lines, bc_strdup(parsed)); + sb_string_free(tmp_str, true); + lines = sb_slist_append(lines, sb_strdup(parsed)); free(parsed); parsed = NULL; } - bc_string_append_printf(rv, "<ul>%s", line_ending); - for (bc_slist_t *l = lines; l != NULL; l = l->next) - bc_string_append_printf(rv, "<li>%s</li>%s", l->data, - line_ending); - bc_string_append_printf(rv, "</ul>%s", line_ending); - bc_slist_free_full(lines, free); + if (ast == NULL) { + ast = block_node_new(BLOGC_CONTENT_BLOCK_UNORDERED_LIST, + NULL, NULL); + last = ast; + } + else { + last->next = block_node_new(BLOGC_CONTENT_BLOCK_UNORDERED_LIST, + NULL, NULL); + last = last->next; + } + blogc_content_node_t *last_list = NULL; + for (sb_slist_t *l = lines; l != NULL; l = l->next) { + if (last_list == NULL) { + last->child = block_node_new(BLOGC_CONTENT_BLOCK_LIST_ITEM, + l->data, NULL); + last_list = last->child; + } + else { + last_list->next = block_node_new(BLOGC_CONTENT_BLOCK_LIST_ITEM, + l->data, NULL); + last_list = last_list->next; + } + } + sb_slist_free(lines); lines = NULL; free(prefix); prefix = NULL; @@ -1180,30 +1210,30 @@ blogc_content_parse(const char *src, size_t *end_excerpt, char **first_header, if (c == '\n' || c == '\r' || is_last) { end = is_last && c != '\n' && c != '\r' ? src_len : (real_end != 0 ? real_end : current); - tmp = bc_strndup(src + start2, end - start2); - tmp2 = bc_strdup_printf("%-*s", prefix_len, ""); + tmp = sb_strndup(src + start2, end - start2); + tmp2 = sb_strdup_printf("%-*s", prefix_len, ""); if (blogc_is_ordered_list_item(tmp, prefix_len)) { if (lines2 != NULL) { - tmp_str = bc_string_new(); - for (bc_slist_t *l = lines2; l != NULL; l = l->next) { + tmp_str = sb_string_new(); + for (sb_slist_t *l = lines2; l != NULL; l = l->next) { if (l->next == NULL) - bc_string_append_printf(tmp_str, "%s", l->data); + sb_string_append_printf(tmp_str, "%s", l->data); else - bc_string_append_printf(tmp_str, "%s%s", l->data, + sb_string_append_printf(tmp_str, "%s%s", l->data, line_ending); } - bc_slist_free_full(lines2, free); + sb_slist_free_full(lines2, free); lines2 = NULL; parsed = blogc_content_parse_inline(tmp_str->str); - bc_string_free(tmp_str, true); - lines = bc_slist_append(lines, bc_strdup(parsed)); + sb_string_free(tmp_str, true); + lines = sb_slist_append(lines, sb_strdup(parsed)); free(parsed); parsed = NULL; } - lines2 = bc_slist_append(lines2, bc_strdup(tmp + prefix_len)); + lines2 = sb_slist_append(lines2, sb_strdup(tmp + prefix_len)); } - else if (bc_str_starts_with(tmp, tmp2)) { - lines2 = bc_slist_append(lines2, bc_strdup(tmp + prefix_len)); + else if (sb_str_starts_with(tmp, tmp2)) { + lines2 = sb_slist_append(lines2, sb_strdup(tmp + prefix_len)); } else { state = CONTENT_PARAGRAPH_END; @@ -1213,8 +1243,8 @@ blogc_content_parse(const char *src, size_t *end_excerpt, char **first_header, tmp2 = NULL; free(parsed); parsed = NULL; - bc_slist_free_full(lines, free); - bc_slist_free_full(lines2, free); + sb_slist_free_full(lines, free); + sb_slist_free_full(lines2, free); lines = NULL; if (is_last) continue; @@ -1233,28 +1263,46 @@ blogc_content_parse(const char *src, size_t *end_excerpt, char **first_header, if (c == '\n' || c == '\r' || is_last) { if (lines2 != NULL) { // FIXME: avoid repeting the code below - tmp_str = bc_string_new(); - for (bc_slist_t *l = lines2; l != NULL; l = l->next) { + tmp_str = sb_string_new(); + for (sb_slist_t *l = lines2; l != NULL; l = l->next) { if (l->next == NULL) - bc_string_append_printf(tmp_str, "%s", l->data); + sb_string_append_printf(tmp_str, "%s", l->data); else - bc_string_append_printf(tmp_str, "%s%s", l->data, + sb_string_append_printf(tmp_str, "%s%s", l->data, line_ending); } - bc_slist_free_full(lines2, free); + sb_slist_free_full(lines2, free); lines2 = NULL; parsed = blogc_content_parse_inline(tmp_str->str); - bc_string_free(tmp_str, true); - lines = bc_slist_append(lines, bc_strdup(parsed)); + sb_string_free(tmp_str, true); + lines = sb_slist_append(lines, sb_strdup(parsed)); free(parsed); parsed = NULL; } - bc_string_append_printf(rv, "<ol>%s", line_ending); - for (bc_slist_t *l = lines; l != NULL; l = l->next) - bc_string_append_printf(rv, "<li>%s</li>%s", l->data, - line_ending); - bc_string_append_printf(rv, "</ol>%s", line_ending); - bc_slist_free_full(lines, free); + if (ast == NULL) { + ast = block_node_new(BLOGC_CONTENT_BLOCK_ORDERED_LIST, + NULL, NULL); + last = ast; + } + else { + last->next = block_node_new(BLOGC_CONTENT_BLOCK_ORDERED_LIST, + NULL, NULL); + last = last->next; + } + blogc_content_node_t *last_list = NULL; + for (sb_slist_t *l = lines; l != NULL; l = l->next) { + if (last_list == NULL) { + last->child = block_node_new(BLOGC_CONTENT_BLOCK_LIST_ITEM, + l->data, NULL); + last_list = last->child; + } + else { + last_list->next = block_node_new(BLOGC_CONTENT_BLOCK_LIST_ITEM, + l->data, NULL); + last_list = last_list->next; + } + } + sb_slist_free(lines); lines = NULL; free(prefix); prefix = NULL; @@ -1278,16 +1326,19 @@ blogc_content_parse(const char *src, size_t *end_excerpt, char **first_header, case CONTENT_PARAGRAPH_END: if (c == '\n' || c == '\r' || is_last) { - tmp = bc_strndup(src + start, end - start); - if (description != NULL && *description == NULL) - *description = blogc_fix_description(tmp); - parsed = blogc_content_parse_inline(tmp); - bc_string_append_printf(rv, "<p>%s</p>%s", parsed, - line_ending); - free(parsed); - parsed = NULL; - free(tmp); - tmp = NULL; + char *tmp2 = sb_strndup(src + start, end - start); + sb_trie_t *t = sb_trie_new(free); + sb_trie_insert(t, "parsed", blogc_content_parse_inline(tmp2)); + if (ast == NULL) { + ast = block_node_new(BLOGC_CONTENT_BLOCK_PARAGRAPH, + tmp2, t); + last = ast; + } + else { + last->next = block_node_new(BLOGC_CONTENT_BLOCK_PARAGRAPH, + tmp2, t); + last = last->next; + } state = CONTENT_START_LINE; start = current; } @@ -1300,9 +1351,159 @@ blogc_content_parse(const char *src, size_t *end_excerpt, char **first_header, current++; } - if (endl == NULL) { - free(line_ending); + if (nl != NULL && *nl == NULL) + *nl = sb_strdup(line_ending); + + return ast; +} + + +void +blogc_content_free_ast(blogc_content_node_t *ast) +{ + if (ast == NULL) + return; + free(ast->content); + sb_trie_free(ast->parameters); + blogc_content_free_ast(ast->child); + blogc_content_free_ast(ast->next); + free(ast); +} + + +char* +blogc_content_parse(const char *src, char **excerpt, char **description) +{ + char *nl = NULL; + blogc_content_node_t *c = blogc_content_parse_ast(src, &nl); + char *rv = blogc_content_render_html(c, nl, excerpt, description); + free(nl); + blogc_content_free_ast(c); + return rv; +} + + +char* +blogc_content_render_html(blogc_content_node_t *ast, char *nl, char **excerpt, + char **description) +{ + sb_string_t *rv = sb_string_new(); + char *tmp = NULL; + for (blogc_content_node_t *l = ast; l != NULL; l = l->next) { + switch (l->node_type) { + case BLOGC_CONTENT_BLOCK: + switch (l->type.block_type) { + case BLOGC_CONTENT_BLOCK_RAW: + sb_string_append_printf(rv, "%s%s", l->content, nl); + break; + case BLOGC_CONTENT_BLOCK_HEADER: + tmp = blogc_slugify(l->content); + sb_string_append_printf(rv, "<h%s id=\"%s\">%s</h%s>%s", + sb_trie_lookup(l->parameters, "level"), tmp, l->content, + sb_trie_lookup(l->parameters, "level"), nl); + free(tmp); + tmp = NULL; + break; + case BLOGC_CONTENT_BLOCK_BLOCKQUOTE: + tmp = blogc_content_render_html(l->child, nl, NULL, NULL); + sb_string_append_printf(rv, "<blockquote>%s</blockquote>%s", + tmp, nl); + free(tmp); + tmp = NULL; + break; + case BLOGC_CONTENT_BLOCK_CODE: + tmp = blogc_htmlentities(l->content); + sb_string_append_printf(rv, "<pre><code>%s</code></pre>%s", + tmp, nl); + free(tmp); + tmp = NULL; + break; + case BLOGC_CONTENT_BLOCK_HORIZONTAL_RULE: + sb_string_append_printf(rv, "<hr />%s", nl); + break; + case BLOGC_CONTENT_BLOCK_UNORDERED_LIST: + tmp = blogc_content_render_html(l->child, nl, NULL, NULL); + sb_string_append_printf(rv, "<ul>%s%s</ul>%s", nl, + tmp, nl); + free(tmp); + tmp = NULL; + break; + case BLOGC_CONTENT_BLOCK_ORDERED_LIST: + tmp = blogc_content_render_html(l->child, nl, NULL, NULL); + sb_string_append_printf(rv, "<ol>%s%s</ol>%s", nl, + tmp, nl); + free(tmp); + tmp = NULL; + break; + case BLOGC_CONTENT_BLOCK_LIST_ITEM: + sb_string_append_printf(rv, "<li>%s</li>%s", + l->content, nl); + break; + case BLOGC_CONTENT_BLOCK_PARAGRAPH: + if (description != NULL && *description == NULL) + *description = blogc_fix_description(l->content); + sb_string_append_printf(rv, "<p>%s</p>%s", + sb_trie_lookup(l->parameters, "parsed"), nl); + break; + case BLOGC_CONTENT_BLOCK_EXCERPT: + if (excerpt != NULL && *excerpt == NULL) + *excerpt = sb_strdup(rv->str); + break; + } + break; + case BLOGC_CONTENT_INLINE: + break; + } } + return sb_string_free(rv, false); +} + - return bc_string_free(rv, false); +void +blogc_content_debug(blogc_content_node_t *ast) +{ + for (blogc_content_node_t *l = ast; l != NULL; l = l->next) { + switch (l->node_type) { + case BLOGC_CONTENT_BLOCK: + fprintf(stderr, "DEBUG: <CONTENT BLOCK "); + switch (l->type.block_type) { + case BLOGC_CONTENT_BLOCK_RAW: + fprintf(stderr, "RAW: `%s`", l->content); + break; + case BLOGC_CONTENT_BLOCK_HEADER: + fprintf(stderr, "HEADER: \"%s\"", l->content); + break; + case BLOGC_CONTENT_BLOCK_BLOCKQUOTE: + fprintf(stderr, "BLOCKQUOTE"); + break; + case BLOGC_CONTENT_BLOCK_CODE: + fprintf(stderr, "CODE: `%s`", l->content); + break; + case BLOGC_CONTENT_BLOCK_HORIZONTAL_RULE: + fprintf(stderr, "HORIZONTAL_RULE"); + break; + case BLOGC_CONTENT_BLOCK_UNORDERED_LIST: + fprintf(stderr, "UNORDERED_LIST"); + break; + case BLOGC_CONTENT_BLOCK_ORDERED_LIST: + fprintf(stderr, "ORDERED_LIST"); + break; + case BLOGC_CONTENT_BLOCK_LIST_ITEM: + fprintf(stderr, "LIST_ITEM: `%s`", l->content); + break; + case BLOGC_CONTENT_BLOCK_PARAGRAPH: + fprintf(stderr, "PARAGRAPH: `%s`", l->content); + break; + case BLOGC_CONTENT_BLOCK_EXCERPT: + fprintf(stderr, "EXCERPT"); + break; + } + fprintf(stderr, ">\n"); + if (l->child != NULL) + blogc_content_debug(l->child); + break; + case BLOGC_CONTENT_INLINE: + break; + } + } } diff --git a/src/content-parser.h b/src/content-parser.h new file mode 100644 index 0000000..3a72317 --- /dev/null +++ b/src/content-parser.h @@ -0,0 +1,174 @@ +/* + * 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 _CONTENT_PARSER_H +#define _CONTENT_PARSER_H + +#include <stddef.h> +#include <stdbool.h> + +#include "utils.h" + +/* + * Raw node + * + * +-----+ + * | RAW | -> ... + * +-----+ + * + */ + +/* + * Header node + * + * +--------+ + * | HEADER | -> ... + * +--------+ + * | + * +-----+ +------+ +-----+ + * | raw | -> | bold | -> | raw | -> ... + * +-----+ +------+ +-----+ + * + */ + +/* + * Blockquote node + * + * +------------+ + * | BLOCKQUOTE | -> ... + * +------------+ + * | + * +-----------+ +--------+ +-----------+ + * | PARAGRAPH | -> | HEADER | -> | PARAGRAPH | -> ... + * +-----------+ +--------+ +-----------+ + * | + * +-----+ + * | raw | -> ... + * +-----+ + * + */ + +/* + * Code node + * + * +------+ + * | CODE | -> ... + * +------+ + * + */ + +/* + * Horizontal rule node + * + * +-----------------+ + * | HORIZONTAL_RULE | -> ... + * +-----------------+ + * + */ + +/* + * Unordered list node + * + * +----------------+ + * | UNORDERED_LIST | -> ... + * +----------------+ + * | + * +-----------+ +-----------+ + * | LIST_ITEM | -> | LIST_ITEM | -> ... + * +-----------+ +-----------+ + * | + * +-----+ +------+ + * | raw | -> | bold | -> ... + * +-----+ +------+ + * + */ + +/* + * Ordered list node + * + * +--------------+ + * | ORDERED_LIST | -> ... + * +--------------+ + * | + * +-----------+ +-----------+ + * | LIST_ITEM | -> | LIST_ITEM | -> ... + * +-----------+ +-----------+ + * | + * +-----+ +------+ + * | raw | -> | bold | -> ... + * +-----+ +------+ + * + */ + +/* + * Paragraph node + * + * +-----------+ + * | PARAGRAPH | -> ... + * +-----------+ + * | + * +-----+ +------+ +-----+ + * | raw | -> | bold | -> | raw | + * +-----+ +------+ +-----+ + * + */ + + +typedef enum { + BLOGC_CONTENT_BLOCK = 1, + BLOGC_CONTENT_INLINE, +} blogc_content_node_type_t; + +typedef enum { + BLOGC_CONTENT_BLOCK_RAW = 1, + BLOGC_CONTENT_BLOCK_HEADER, + BLOGC_CONTENT_BLOCK_BLOCKQUOTE, + BLOGC_CONTENT_BLOCK_CODE, + BLOGC_CONTENT_BLOCK_HORIZONTAL_RULE, + BLOGC_CONTENT_BLOCK_UNORDERED_LIST, + BLOGC_CONTENT_BLOCK_ORDERED_LIST, + BLOGC_CONTENT_BLOCK_LIST_ITEM, + BLOGC_CONTENT_BLOCK_PARAGRAPH, + BLOGC_CONTENT_BLOCK_EXCERPT, +} blogc_content_block_type_t; + +typedef enum { + BLOGC_CONTENT_INLINE_RAW = 1, + BLOGC_CONTENT_INLINE_LINK, + BLOGC_CONTENT_INLINE_IMAGE, + BLOGC_CONTENT_INLINE_BOLD, + BLOGC_CONTENT_INLINE_ITALIC, + BLOGC_CONTENT_INLINE_CODE, + BLOGC_CONTENT_INLINE_BREAK_LINE, +} blogc_content_inline_type_t; + +typedef struct _blogc_content_node_t { + blogc_content_node_type_t node_type; + union { + blogc_content_block_type_t block_type; + blogc_content_inline_type_t inline_type; + } type; + char *content; + struct _blogc_content_node_t *child; + struct _blogc_content_node_t *next; + sb_trie_t *parameters; +} blogc_content_node_t; + +char* blogc_slugify(const char *str); +char* blogc_htmlentities(const char *str); +char* blogc_fix_description(const char *paragraph); +char* blogc_content_parse_inline(const char *src); +bool blogc_is_ordered_list_item(const char *str, size_t prefix_len); +blogc_content_node_t* blogc_content_parse_ast(const char *src, char **nl); +void blogc_content_free_ast(blogc_content_node_t *ast); +char* blogc_content_parse(const char *src, char **excerpt, char **description); +char* blogc_content_render_html(blogc_content_node_t *ast, char *nl, char **excerpt, + char **description); +void blogc_content_debug(blogc_content_node_t *ast); + +#endif /* _CONTENT_PARSER_H */ diff --git a/src/blogc/datetime-parser.c b/src/datetime-parser.c index ddaf6ef..ad65ff7 100644 --- a/src/blogc/datetime-parser.c +++ b/src/datetime-parser.c @@ -1,6 +1,6 @@ /* * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * 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. @@ -16,9 +16,9 @@ #include <string.h> +#include "error.h" #include "datetime-parser.h" -#include "../common/error.h" -#include "../common/utils.h" +#include "utils.h" typedef enum { @@ -47,14 +47,14 @@ typedef enum { char* blogc_convert_datetime(const char *orig, const char *format, - bc_error_t **err) + blogc_error_t **err) { if (err == NULL || *err != NULL) return NULL; #ifndef HAVE_TIME_H - *err = bc_error_new(BLOGC_WARNING_DATETIME_PARSER, + *err = blogc_error_new(BLOGC_WARNING_DATETIME_PARSER, "Your operating system does not supports the datetime functionalities " "used by blogc. Sorry."); return NULL; @@ -69,7 +69,7 @@ blogc_convert_datetime(const char *orig, const char *format, int tmp = 0; int diff = '0'; - for (size_t i = 0; orig[i] != '\0'; i++) { + for (unsigned int i = 0; orig[i] != '\0'; i++) { char c = orig[i]; switch (state) { @@ -80,7 +80,7 @@ blogc_convert_datetime(const char *orig, const char *format, state = DATETIME_SECOND_YEAR; break; } - *err = bc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, + *err = blogc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, "Invalid first digit of year. " "Found '%c', must be integer >= 0 and <= 9.", c); break; @@ -91,7 +91,7 @@ blogc_convert_datetime(const char *orig, const char *format, state = DATETIME_THIRD_YEAR; break; } - *err = bc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, + *err = blogc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, "Invalid second digit of year. " "Found '%c', must be integer >= 0 and <= 9.", c); break; @@ -102,7 +102,7 @@ blogc_convert_datetime(const char *orig, const char *format, state = DATETIME_FOURTH_YEAR; break; } - *err = bc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, + *err = blogc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, "Invalid third digit of year. " "Found '%c', must be integer >= 0 and <= 9.", c); break; @@ -111,7 +111,7 @@ blogc_convert_datetime(const char *orig, const char *format, if (c >= '0' && c <= '9') { tmp += c - diff - 1900; if (tmp < 0) { - *err = bc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, + *err = blogc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, "Invalid year. Found %d, must be >= 1900.", tmp + 1900); break; @@ -120,7 +120,7 @@ blogc_convert_datetime(const char *orig, const char *format, state = DATETIME_FIRST_HYPHEN; break; } - *err = bc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, + *err = blogc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, "Invalid fourth digit of year. " "Found '%c', must be integer >= 0 and <= 9.", c); break; @@ -131,7 +131,7 @@ blogc_convert_datetime(const char *orig, const char *format, state = DATETIME_FIRST_MONTH; break; } - *err = bc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, + *err = blogc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, "Invalid separator between year and month. " "Found '%c', must be '-'.", c); break; @@ -142,7 +142,7 @@ blogc_convert_datetime(const char *orig, const char *format, state = DATETIME_SECOND_MONTH; break; } - *err = bc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, + *err = blogc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, "Invalid first digit of month. " "Found '%c', must be integer >= 0 and <= 1.", c); break; @@ -151,7 +151,7 @@ blogc_convert_datetime(const char *orig, const char *format, if (c >= '0' && c <= '9') { tmp += c - diff - 1; if (tmp < 0 || tmp > 11) { - *err = bc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, + *err = blogc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, "Invalid month. Found %d, must be >= 1 and <= 12.", tmp + 1); break; @@ -160,7 +160,7 @@ blogc_convert_datetime(const char *orig, const char *format, state = DATETIME_SECOND_HYPHEN; break; } - *err = bc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, + *err = blogc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, "Invalid second digit of month. " "Found '%c', must be integer >= 0 and <= 9.", c); break; @@ -171,7 +171,7 @@ blogc_convert_datetime(const char *orig, const char *format, state = DATETIME_FIRST_DAY; break; } - *err = bc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, + *err = blogc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, "Invalid separator between month and day. " "Found '%c', must be '-'.", c); break; @@ -182,7 +182,7 @@ blogc_convert_datetime(const char *orig, const char *format, state = DATETIME_SECOND_DAY; break; } - *err = bc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, + *err = blogc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, "Invalid first digit of day. " "Found '%c', must be integer >= 0 and <= 3.", c); break; @@ -191,7 +191,7 @@ blogc_convert_datetime(const char *orig, const char *format, if (c >= '0' && c <= '9') { tmp += c - diff; if (tmp < 1 || tmp > 31) { - *err = bc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, + *err = blogc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, "Invalid day. Found %d, must be >= 1 and <= 31.", tmp); break; @@ -200,7 +200,7 @@ blogc_convert_datetime(const char *orig, const char *format, state = DATETIME_SPACE; break; } - *err = bc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, + *err = blogc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, "Invalid second digit of day. " "Found '%c', must be integer >= 0 and <= 9.", c); break; @@ -211,7 +211,7 @@ blogc_convert_datetime(const char *orig, const char *format, state = DATETIME_FIRST_HOUR; break; } - *err = bc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, + *err = blogc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, "Invalid separator between date and time. " "Found '%c', must be ' ' (empty space).", c); break; @@ -222,7 +222,7 @@ blogc_convert_datetime(const char *orig, const char *format, state = DATETIME_SECOND_HOUR; break; } - *err = bc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, + *err = blogc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, "Invalid first digit of hours. " "Found '%c', must be integer >= 0 and <= 2.", c); break; @@ -231,7 +231,7 @@ blogc_convert_datetime(const char *orig, const char *format, if (c >= '0' && c <= '9') { tmp += c - diff; if (tmp < 0 || tmp > 23) { - *err = bc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, + *err = blogc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, "Invalid hours. Found %d, must be >= 0 and <= 23.", tmp); break; @@ -240,7 +240,7 @@ blogc_convert_datetime(const char *orig, const char *format, state = DATETIME_FIRST_COLON; break; } - *err = bc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, + *err = blogc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, "Invalid second digit of hours. " "Found '%c', must be integer >= 0 and <= 9.", c); break; @@ -251,7 +251,7 @@ blogc_convert_datetime(const char *orig, const char *format, state = DATETIME_FIRST_MINUTE; break; } - *err = bc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, + *err = blogc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, "Invalid separator between hours and minutes. " "Found '%c', must be ':'.", c); break; @@ -262,7 +262,7 @@ blogc_convert_datetime(const char *orig, const char *format, state = DATETIME_SECOND_MINUTE; break; } - *err = bc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, + *err = blogc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, "Invalid first digit of minutes. " "Found '%c', must be integer >= 0 and <= 5.", c); break; @@ -274,7 +274,7 @@ blogc_convert_datetime(const char *orig, const char *format, // this won't happen because we are restricting the digits // to 00-59 already, but lets keep the code here for // reference. - *err = bc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, + *err = blogc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, "Invalid minutes. Found %d, must be >= 0 and <= 59.", tmp); break; @@ -283,7 +283,7 @@ blogc_convert_datetime(const char *orig, const char *format, state = DATETIME_SECOND_COLON; break; } - *err = bc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, + *err = blogc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, "Invalid second digit of minutes. " "Found '%c', must be integer >= 0 and <= 9.", c); break; @@ -294,7 +294,7 @@ blogc_convert_datetime(const char *orig, const char *format, state = DATETIME_FIRST_SECOND; break; } - *err = bc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, + *err = blogc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, "Invalid separator between minutes and seconds. " "Found '%c', must be ':'.", c); break; @@ -305,7 +305,7 @@ blogc_convert_datetime(const char *orig, const char *format, state = DATETIME_SECOND_SECOND; break; } - *err = bc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, + *err = blogc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, "Invalid first digit of seconds. " "Found '%c', must be integer >= 0 and <= 6.", c); break; @@ -314,7 +314,7 @@ blogc_convert_datetime(const char *orig, const char *format, if (c >= '0' && c <= '9') { tmp += c - diff; if (tmp < 0 || tmp > 60) { - *err = bc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, + *err = blogc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, "Invalid seconds. Found %d, must be >= 0 and <= 60.", tmp); break; @@ -323,7 +323,7 @@ blogc_convert_datetime(const char *orig, const char *format, state = DATETIME_DONE; break; } - *err = bc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, + *err = blogc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, "Invalid second digit of seconds. " "Found '%c', must be integer >= 0 and <= 9.", c); break; @@ -355,7 +355,7 @@ blogc_convert_datetime(const char *orig, const char *format, case DATETIME_SECOND_MINUTE: case DATETIME_FIRST_SECOND: case DATETIME_SECOND_SECOND: - *err = bc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, + *err = blogc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, "Invalid datetime string. " "Found '%s', formats allowed are: 'yyyy-mm-dd hh:mm:ss', " "'yyyy-mm-dd hh:ss', 'yyyy-mm-dd hh' and 'yyyy-mm-dd'.", @@ -374,13 +374,13 @@ blogc_convert_datetime(const char *orig, const char *format, char buf[1024]; if (0 == strftime(buf, sizeof(buf), format, &t)) { - *err = bc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, + *err = blogc_error_new_printf(BLOGC_WARNING_DATETIME_PARSER, "Failed to format DATE variable, FORMAT is too long: %s", format); return NULL; } - return bc_strdup(buf); + return sb_strdup(buf); #endif } diff --git a/src/blogc/datetime-parser.h b/src/datetime-parser.h index 8617ad0..a5087b3 100644 --- a/src/blogc/datetime-parser.h +++ b/src/datetime-parser.h @@ -1,6 +1,6 @@ /* * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * 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. @@ -9,9 +9,9 @@ #ifndef _DATETIME_H #define _DATETIME_H -#include "../common/error.h" +#include "error.h" char* blogc_convert_datetime(const char *orig, const char *format, - bc_error_t **err); + blogc_error_t **err); #endif /* _DATETIME_H */ diff --git a/src/debug.c b/src/debug.c new file mode 100644 index 0000000..9689028 --- /dev/null +++ b/src/debug.c @@ -0,0 +1,80 @@ +/* + * 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 <stdio.h> + +#include "template-parser.h" +#include "utils.h" +#include "debug.h" + + +static const char* +get_operator(blogc_template_stmt_operator_t op) +{ + if (op & BLOGC_TEMPLATE_OP_NEQ) + return "!="; + if (op & BLOGC_TEMPLATE_OP_EQ) { + if (op & BLOGC_TEMPLATE_OP_LT) + return "<="; + else if (op & BLOGC_TEMPLATE_OP_GT) + return ">="; + return "=="; + } + if (op & BLOGC_TEMPLATE_OP_LT) + return "<"; + else if (op & BLOGC_TEMPLATE_OP_GT) + return ">"; + return ""; +} + + +void +blogc_debug_template(sb_slist_t *stmts) +{ + for (sb_slist_t *tmp = stmts; tmp != NULL; tmp = tmp->next) { + blogc_template_stmt_t *data = tmp->data; + fprintf(stderr, "DEBUG: <TEMPLATE "); + switch (data->type) { + case BLOGC_TEMPLATE_IFDEF_STMT: + fprintf(stderr, "IFDEF: %s", data->value); + break; + case BLOGC_TEMPLATE_IFNDEF_STMT: + fprintf(stderr, "IFNDEF: %s", data->value); + break; + case BLOGC_TEMPLATE_IF_STMT: + fprintf(stderr, "IF: %s %s %s", data->value, + get_operator(data->op), data->value2); + break; + case BLOGC_TEMPLATE_ELSE_STMT: + fprintf(stderr, "ELSE"); + break; + case BLOGC_TEMPLATE_ENDIF_STMT: + fprintf(stderr, "ENDIF"); + break; + case BLOGC_TEMPLATE_FOREACH_STMT: + fprintf(stderr, "FOREACH: %s", data->value); + break; + case BLOGC_TEMPLATE_ENDFOREACH_STMT: + fprintf(stderr, "ENDFOREACH"); + break; + case BLOGC_TEMPLATE_BLOCK_STMT: + fprintf(stderr, "BLOCK: %s", data->value); + break; + case BLOGC_TEMPLATE_ENDBLOCK_STMT: + fprintf(stderr, "ENDBLOCK"); + break; + case BLOGC_TEMPLATE_VARIABLE_STMT: + fprintf(stderr, "VARIABLE: %s", data->value); + break; + case BLOGC_TEMPLATE_CONTENT_STMT: + fprintf(stderr, "CONTENT: `%s`", data->value); + break; + } + fprintf(stderr, ">\n"); + } +} diff --git a/src/blogc/debug.h b/src/debug.h index 6138a91..cd454d4 100644 --- a/src/blogc/debug.h +++ b/src/debug.h @@ -1,6 +1,6 @@ /* * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * 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. @@ -9,8 +9,8 @@ #ifndef ___DEBUG_H #define ___DEBUG_H -#include "../common/utils.h" +#include "utils.h" -void blogc_debug_template(bc_slist_t *ast); +void blogc_debug_template(sb_slist_t *stmts); #endif /* ___DEBUG_H */ diff --git a/src/common/error.c b/src/error.c index caf47c3..c238f51 100644 --- a/src/common/error.c +++ b/src/error.c @@ -1,6 +1,6 @@ /* * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * 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. @@ -13,40 +13,36 @@ #include "utils.h" -bc_error_t* -bc_error_new(bc_error_type_t type, const char *msg) +blogc_error_t* +blogc_error_new(blogc_error_type_t type, const char *msg) { - bc_error_t *err = bc_malloc(sizeof(bc_error_t)); + blogc_error_t *err = sb_malloc(sizeof(blogc_error_t)); err->type = type; - err->msg = bc_strdup(msg); + err->msg = sb_strdup(msg); return err; } -bc_error_t* -bc_error_new_printf(bc_error_type_t type, const char *format, ...) +blogc_error_t* +blogc_error_new_printf(blogc_error_type_t type, const char *format, ...) { - if (format == NULL) - return bc_error_new(type, ""); va_list ap; va_start(ap, format); - char *tmp = bc_strdup_vprintf(format, ap); + char *tmp = sb_strdup_vprintf(format, ap); va_end(ap); - bc_error_t *rv = bc_error_new(type, tmp); + blogc_error_t *rv = blogc_error_new(type, tmp); free(tmp); return rv; } -bc_error_t* -bc_error_parser(bc_error_type_t type, const char *src, size_t src_len, +blogc_error_t* +blogc_error_parser(blogc_error_type_t type, const char *src, size_t src_len, size_t current, const char *format, ...) { - if (format == NULL) - return bc_error_new(type, ""); va_list ap; va_start(ap, format); - char *msg = bc_strdup_vprintf(format, ap); + char *msg = sb_strdup_vprintf(format, ap); va_end(ap); size_t lineno = 1; @@ -87,14 +83,14 @@ bc_error_parser(bc_error_type_t type, const char *src, size_t src_len, if (lineend <= linestart && src_len >= linestart) lineend = src_len; - char *line = bc_strndup(src + linestart, lineend - linestart); + char *line = sb_strndup(src + linestart, lineend - linestart); - bc_error_t *rv = NULL; + blogc_error_t *rv = NULL; if (line[0] == '\0') // "near" message isn't useful if line is empty - rv = bc_error_new(type, msg); + rv = blogc_error_new(type, msg); else - rv = bc_error_new_printf(type, + rv = blogc_error_new_printf(type, "%s\nError occurred near line %d, position %d: %s", msg, lineno, pos, line); @@ -105,55 +101,36 @@ bc_error_parser(bc_error_type_t type, const char *src, size_t src_len, } -// error handling is centralized here for the sake of simplicity :/ void -bc_error_print(bc_error_t *err, const char *prefix) +blogc_error_print(blogc_error_t *err) { if (err == NULL) return; - if (prefix != NULL) - fprintf(stderr, "%s: ", prefix); - switch(err->type) { - case BC_ERROR_CONFIG_PARSER: - fprintf(stderr, "error: config-parser: %s\n", err->msg); - break; - case BC_ERROR_FILE: - fprintf(stderr, "error: file: %s\n", err->msg); - break; case BLOGC_ERROR_SOURCE_PARSER: - fprintf(stderr, "error: source: %s\n", err->msg); + fprintf(stderr, "blogc: error: source: %s\n", err->msg); break; case BLOGC_ERROR_TEMPLATE_PARSER: - fprintf(stderr, "error: template: %s\n", err->msg); + fprintf(stderr, "blogc: error: template: %s\n", err->msg); break; case BLOGC_ERROR_LOADER: - fprintf(stderr, "error: loader: %s\n", err->msg); - break; - 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); + fprintf(stderr, "blogc: error: loader: %s\n", err->msg); break; - case BLOGC_MAKE_ERROR_EXEC: - fprintf(stderr, "error: exec: %s\n", err->msg); + case BLOGC_ERROR_FILE: + fprintf(stderr, "blogc: error: file: %s\n", err->msg); break; - case BLOGC_MAKE_ERROR_ATOM: - fprintf(stderr, "error: atom: %s\n", err->msg); - break; - case BLOGC_MAKE_ERROR_UTILS: - fprintf(stderr, "error: utils: %s\n", err->msg); + case BLOGC_WARNING_DATETIME_PARSER: + fprintf(stderr, "blogc: warning: datetime: %s\n", err->msg); break; default: - fprintf(stderr, "error: %s\n", err->msg); + fprintf(stderr, "blogc: error: %s\n", err->msg); } } void -bc_error_free(bc_error_t *err) +blogc_error_free(blogc_error_t *err) { if (err == NULL) return; diff --git a/src/error.h b/src/error.h new file mode 100644 index 0000000..31fbaf2 --- /dev/null +++ b/src/error.h @@ -0,0 +1,34 @@ +/* + * 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 _ERROR_H +#define _ERROR_H + +#include <stddef.h> + +typedef enum { + BLOGC_ERROR_SOURCE_PARSER = 1, + BLOGC_ERROR_TEMPLATE_PARSER, + BLOGC_ERROR_LOADER, + BLOGC_ERROR_FILE, + BLOGC_WARNING_DATETIME_PARSER, +} blogc_error_type_t; + +typedef struct { + char *msg; + blogc_error_type_t type; +} blogc_error_t; + +blogc_error_t* blogc_error_new(blogc_error_type_t type, const char *msg); +blogc_error_t* blogc_error_new_printf(blogc_error_type_t type, const char *format, ...); +blogc_error_t* blogc_error_parser(blogc_error_type_t type, const char *src, + size_t src_len, size_t current, const char *format, ...); +void blogc_error_print(blogc_error_t *err); +void blogc_error_free(blogc_error_t *err); + +#endif /* _ERROR_H */ diff --git a/src/file.c b/src/file.c new file mode 100644 index 0000000..dc43056 --- /dev/null +++ b/src/file.c @@ -0,0 +1,81 @@ +/* + * 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 <errno.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include "file.h" +#include "error.h" +#include "utf8.h" +#include "utils.h" + +// this would belong to loader.c, but we need it in a separated file to be +// able to mock it when unit testing the loader functions. + + +char* +blogc_file_get_contents(const char *path, size_t *len, blogc_error_t **err) +{ + if (path == NULL || err == NULL || *err != NULL) + return NULL; + + *len = 0; + FILE *fp = fopen(path, "r"); + + if (fp == NULL) { + int tmp_errno = errno; + *err = blogc_error_new_printf(BLOGC_ERROR_FILE, + "Failed to open file (%s): %s", path, strerror(tmp_errno)); + return NULL; + } + + sb_string_t *str = sb_string_new(); + char buffer[BLOGC_FILE_CHUNK_SIZE]; + char *tmp; + + while (!feof(fp)) { + size_t read_len = fread(buffer, sizeof(char), BLOGC_FILE_CHUNK_SIZE, fp); + + tmp = buffer; + + if (str->len == 0 && read_len > 0) { + // skipping BOM before validation, for performance. should be safe + // enough + size_t skip = blogc_utf8_skip_bom((uint8_t*) buffer, read_len); + read_len -= skip; + tmp += skip; + } + + *len += read_len; + sb_string_append_len(str, tmp, read_len); + } + fclose(fp); + + if (!blogc_utf8_validate_str(str)) { + *err = blogc_error_new_printf(BLOGC_ERROR_FILE, + "File content is not valid UTF-8: %s", path); + sb_string_free(str, true); + return NULL; + } + + return sb_string_free(str, false); +} + + +int +blogc_fprintf(FILE *stream, const char *format, ...) +{ + va_list ap; + va_start(ap, format); + int rv = vfprintf(stream, format, ap); + va_end(ap); + return rv; +} diff --git a/src/file.h b/src/file.h new file mode 100644 index 0000000..d2c4390 --- /dev/null +++ b/src/file.h @@ -0,0 +1,21 @@ +/* + * 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 _FILE_H +#define _FILE_H + +#include <stddef.h> +#include <stdio.h> +#include "error.h" + +#define BLOGC_FILE_CHUNK_SIZE 1024 + +char* blogc_file_get_contents(const char *path, size_t *len, blogc_error_t **err); +int blogc_fprintf(FILE *stream, const char *format, ...); + +#endif /* _FILE_H */ diff --git a/src/loader.c b/src/loader.c new file mode 100644 index 0000000..bd3251e --- /dev/null +++ b/src/loader.c @@ -0,0 +1,212 @@ +/* + * 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 <math.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "file.h" +#include "source-parser.h" +#include "template-parser.h" +#include "loader.h" +#include "error.h" +#include "utils.h" + + +char* +blogc_get_filename(const char *f) +{ + if (f == NULL) + return NULL; + + if (strlen(f) == 0) + return NULL; + + char *filename = sb_strdup(f); + + // keep a pointer to original string + char *tmp = filename; + + bool removed_dot = false; + for (int i = strlen(tmp); i >= 0 ; i--) { + + // remove last extension + if (!removed_dot && tmp[i] == '.') { + tmp[i] = '\0'; + removed_dot = true; + continue; + } + + if (tmp[i] == '/' || tmp[i] == '\\') { + tmp += i + 1; + break; + } + } + + char *final_filename = sb_strdup(tmp); + free(filename); + + return final_filename; +} + + +sb_slist_t* +blogc_template_parse_from_file(const char *f, blogc_error_t **err) +{ + if (err == NULL || *err != NULL) + return NULL; + size_t len; + char *s = blogc_file_get_contents(f, &len, err); + if (s == NULL) + return NULL; + sb_slist_t *rv = blogc_template_parse(s, len, err); + free(s); + return rv; +} + + +sb_trie_t* +blogc_source_parse_from_file(const char *f, blogc_error_t **err) +{ + if (err == NULL || *err != NULL) + return NULL; + size_t len; + char *s = blogc_file_get_contents(f, &len, err); + if (s == NULL) + return NULL; + sb_trie_t *rv = blogc_source_parse(s, len, err); + + // set FILENAME variable + if (rv != NULL) { + char *filename = blogc_get_filename(f); + if (filename != NULL) + sb_trie_insert(rv, "FILENAME", filename); + } + + free(s); + return rv; +} + + +sb_slist_t* +blogc_source_parse_from_files(sb_trie_t *conf, sb_slist_t *l, blogc_error_t **err) +{ + blogc_error_t *tmp_err = NULL; + sb_slist_t *rv = NULL; + unsigned int with_date = 0; + + const char *filter_tag = sb_trie_lookup(conf, "FILTER_TAG"); + const char *filter_page = sb_trie_lookup(conf, "FILTER_PAGE"); + const char *filter_per_page = sb_trie_lookup(conf, "FILTER_PER_PAGE"); + + long page = strtol(filter_page != NULL ? filter_page : "", NULL, 10); + if (page <= 0) + page = 1; + long per_page = strtol(filter_per_page != NULL ? filter_per_page : "10", + NULL, 10); + if (per_page <= 0) + per_page = 10; + + // poor man's pagination + unsigned int start = (page - 1) * per_page; + unsigned int end = start + per_page; + unsigned int counter = 0; + + for (sb_slist_t *tmp = l; tmp != NULL; tmp = tmp->next) { + char *f = tmp->data; + sb_trie_t *s = blogc_source_parse_from_file(f, &tmp_err); + if (s == NULL) { + *err = blogc_error_new_printf(BLOGC_ERROR_LOADER, + "An error occurred while parsing source file: %s\n\n%s", + f, tmp_err->msg); + blogc_error_free(tmp_err); + tmp_err = NULL; + sb_slist_free_full(rv, (sb_free_func_t) sb_trie_free); + rv = NULL; + break; + } + if (filter_tag != NULL) { + const char *tags_str = sb_trie_lookup(s, "TAGS"); + // if user wants to filter by tag and no tag is provided, skip it + if (tags_str == NULL) { + sb_trie_free(s); + continue; + } + char **tags = sb_str_split(tags_str, ' ', 0); + bool found = false; + for (unsigned int i = 0; tags[i] != NULL; i++) { + if (tags[i][0] == '\0') + continue; + if (0 == strcmp(tags[i], filter_tag)) + found = true; + } + sb_strv_free(tags); + if (!found) { + sb_trie_free(s); + continue; + } + } + if (filter_page != NULL) { + if (counter < start || counter >= end) { + counter++; + sb_trie_free(s); + continue; + } + counter++; + } + if (sb_trie_lookup(s, "DATE") != NULL) + with_date++; + rv = sb_slist_append(rv, s); + } + + if (with_date > 0 && with_date < sb_slist_length(rv)) + // fatal error, maybe? + blogc_fprintf(stderr, + "blogc: warning: 'DATE' variable provided for at least one source " + "file, but not for all source files. This means that you may get " + "wrong values for 'DATE_FIRST' and 'DATE_LAST' variables.\n"); + + bool first = true; + for (sb_slist_t *tmp = rv; tmp != NULL; tmp = tmp->next) { + sb_trie_t *s = tmp->data; + if (first) { + const char *val = sb_trie_lookup(s, "DATE"); + if (val != NULL) + sb_trie_insert(conf, "DATE_FIRST", sb_strdup(val)); + val = sb_trie_lookup(s, "FILENAME"); + if (val != NULL) + sb_trie_insert(conf, "FILENAME_FIRST", sb_strdup(val)); + first = false; + } + if (tmp->next == NULL) { // last + const char *val = sb_trie_lookup(s, "DATE"); + if (val != NULL) + sb_trie_insert(conf, "DATE_LAST", sb_strdup(val)); + val = sb_trie_lookup(s, "FILENAME"); + if (val != NULL) + sb_trie_insert(conf, "FILENAME_LAST", sb_strdup(val)); + } + } + + if (filter_page != NULL) { + unsigned int last_page = ceilf(((float) counter) / per_page); + sb_trie_insert(conf, "CURRENT_PAGE", sb_strdup_printf("%ld", page)); + if (page > 1) + sb_trie_insert(conf, "PREVIOUS_PAGE", sb_strdup_printf("%ld", page - 1)); + if (page < last_page) + sb_trie_insert(conf, "NEXT_PAGE", sb_strdup_printf("%ld", page + 1)); + if (sb_slist_length(rv) > 0) + sb_trie_insert(conf, "FIRST_PAGE", sb_strdup("1")); + if (last_page > 0) + sb_trie_insert(conf, "LAST_PAGE", sb_strdup_printf("%d", last_page)); + } + + return rv; +} diff --git a/src/loader.h b/src/loader.h new file mode 100644 index 0000000..32b58be --- /dev/null +++ b/src/loader.h @@ -0,0 +1,21 @@ +/* + * 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 _LOADER_H +#define _LOADER_H + +#include "error.h" +#include "utils.h" + +char* blogc_get_filename(const char *f); +sb_slist_t* blogc_template_parse_from_file(const char *f, blogc_error_t **err); +sb_trie_t* blogc_source_parse_from_file(const char *f, blogc_error_t **err); +sb_slist_t* blogc_source_parse_from_files(sb_trie_t *conf, sb_slist_t *l, + blogc_error_t **err); + +#endif /* _LOADER_H */ diff --git a/src/blogc/renderer.c b/src/renderer.c index a91d105..27e92bd 100644 --- a/src/blogc/renderer.c +++ b/src/renderer.c @@ -1,112 +1,77 @@ /* * blogc: A blog compiler. - * Copyright (C) 2014-2020 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * 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 <errno.h> #include <stdbool.h> #include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "datetime-parser.h" -#include "funcvars.h" +#include "error.h" #include "template-parser.h" #include "renderer.h" -#include "../common/error.h" -#include "../common/utils.h" +#include "utils.h" const char* -blogc_get_variable(const char *name, bc_trie_t *global, bc_trie_t *local) +blogc_get_variable(const char *name, sb_trie_t *global, sb_trie_t *local) { const char *rv = NULL; if (local != NULL) { - rv = bc_trie_lookup(local, name); + rv = sb_trie_lookup(local, name); if (rv != NULL) return rv; } if (global != NULL) - rv = bc_trie_lookup(global, name); + rv = sb_trie_lookup(global, name); return rv; } char* -blogc_format_date(const char *date, bc_trie_t *global, bc_trie_t *local) +blogc_format_date(const char *date, sb_trie_t *global, sb_trie_t *local) { const char *date_format = blogc_get_variable("DATE_FORMAT", global, local); if (date == NULL) return NULL; if (date_format == NULL) - return bc_strdup(date); + return sb_strdup(date); - bc_error_t *err = NULL; + blogc_error_t *err = NULL; char *rv = blogc_convert_datetime(date, date_format, &err); if (err != NULL) { - bc_error_print(err, "blogc"); - bc_error_free(err); - return bc_strdup(date); + blogc_error_print(err); + blogc_error_free(err); + return sb_strdup(date); } return rv; } -static char* -foreach_value_variable(const char *name, const char *item) -{ - if (name == NULL || item == NULL) - return NULL; - - char *rv = bc_strdup_printf("%s__%s", name, item); - int diff = 'a' - 'A'; // just to avoid magic numbers - for (size_t i = 0; rv[i] != '\0'; i++) { - if ((rv[i] >= '0' && rv[i] <= '9') || - (rv[i] >= 'A' && rv[i] <= 'Z')) { - continue; - } - if (rv[i] >= 'a' && rv[i] <= 'z') { - rv[i] -= diff; - continue; - } - rv[i] = '_'; - } - - return rv; -} - - char* -blogc_format_variable(const char *name, bc_trie_t *global, bc_trie_t *local, - const char *foreach_name, bc_slist_t *foreach_var) +blogc_format_variable(const char *name, sb_trie_t *global, sb_trie_t *local, + sb_slist_t *foreach_var) { // if used asked for a variable that exists, just return it right away const char *value = blogc_get_variable(name, global, local); if (value != NULL) - return bc_strdup(value); + return sb_strdup(value); - // do the same for special foreach variables + // do the same for special variable 'FOREACH_ITEM' if (0 == strcmp(name, "FOREACH_ITEM")) { if (foreach_var != NULL && foreach_var->data != NULL) { - return bc_strdup(foreach_var->data); - } - return NULL; - } - if (0 == strcmp(name, "FOREACH_VALUE")) { - if (foreach_name != NULL && foreach_var != NULL && foreach_var->data != NULL) { - char *value_var = foreach_value_variable(foreach_name, foreach_var->data); - if (value_var != NULL) { - value = blogc_get_variable(value_var, global, local); - free(value_var); - return bc_strdup(value); - } + return sb_strdup(foreach_var->data); } return NULL; } - char *var = bc_strdup(name); + char *var = sb_strdup(name); size_t i; size_t last = strlen(var); @@ -117,11 +82,11 @@ blogc_format_variable(const char *name, bc_trie_t *global, bc_trie_t *local, for (i = last - 1; i > 0 && var[i] >= '0' && var[i] <= '9'; i--); if (var[i] == '_' && (i + 1) < last) { // var ends with '_[0-9]+' - char *endptr; - len = strtol(var + i + 1, &endptr, 10); - if (*endptr != '\0') { - fprintf(stderr, "warning: invalid variable size for '%s', " - "ignoring.\n", var); + // passing NULL to endptr because our string was previously validated + len = strtol(var + i + 1, NULL, 10); + if (errno != 0) { + fprintf(stderr, "warning: invalid variable size for '%s' (%s), " + "ignoring.\n", var, strerror(errno)); len = -1; } else { @@ -131,27 +96,16 @@ blogc_format_variable(const char *name, bc_trie_t *global, bc_trie_t *local, bool must_format = false; - if (bc_str_ends_with(var, "_FORMATTED")) { + if (sb_str_ends_with(var, "_FORMATTED")) { var[strlen(var) - 10] = '\0'; must_format = true; } if ((0 == strcmp(var, "FOREACH_ITEM")) && - (foreach_var != NULL && foreach_var->data != NULL)) { + (foreach_var != NULL && foreach_var->data != NULL)) value = foreach_var->data; - } - else if ((0 == strcmp(var, "FOREACH_VALUE")) && - (foreach_name != NULL && foreach_var != NULL && foreach_var->data != NULL)) { - char *value_var = foreach_value_variable(foreach_name, foreach_var->data); - if (value_var != NULL) { - value = blogc_get_variable(value_var, global, local); - free(value_var); - } - } - else { - blogc_funcvars_eval(global, var); + else value = blogc_get_variable(var, global, local); - } if (value == NULL) { free(var); @@ -161,23 +115,23 @@ blogc_format_variable(const char *name, bc_trie_t *global, bc_trie_t *local, char *rv = NULL; if (must_format) { - if (bc_str_starts_with(name, "DATE_")) { + if (sb_str_starts_with(name, "DATE_")) { rv = blogc_format_date(value, global, local); } else { fprintf(stderr, "warning: no formatter found for '%s', " "ignoring.\n", var); - rv = bc_strdup(value); + rv = sb_strdup(value); } } else { - rv = bc_strdup(value); + rv = sb_strdup(value); } free(var); if (len > 0) { - char *tmp = bc_strndup(rv, len); + char *tmp = sb_strndup(rv, len); free(rv); rv = tmp; } @@ -186,19 +140,19 @@ blogc_format_variable(const char *name, bc_trie_t *global, bc_trie_t *local, } -bc_slist_t* -blogc_split_list_variable(const char *name, bc_trie_t *global, bc_trie_t *local) +sb_slist_t* +blogc_split_list_variable(const char *name, sb_trie_t *global, sb_trie_t *local) { const char *value = blogc_get_variable(name, global, local); if (value == NULL) return NULL; - bc_slist_t *rv = NULL; + sb_slist_t *rv = NULL; - char **tmp = bc_str_split(value, ' ', 0); - for (size_t i = 0; tmp[i] != NULL; i++) { + char **tmp = sb_str_split(value, ' ', 0); + for (unsigned int i = 0; tmp[i] != NULL; i++) { if (tmp[i][0] != '\0') // ignore empty strings - rv = bc_slist_append(rv, tmp[i]); + rv = sb_slist_append(rv, tmp[i]); else free(tmp[i]); } @@ -209,27 +163,25 @@ blogc_split_list_variable(const char *name, bc_trie_t *global, bc_trie_t *local) char* -blogc_render(bc_slist_t *tmpl, bc_slist_t *sources, bc_slist_t *listing_entries, - bc_trie_t *config, bool listing) +blogc_render(sb_slist_t *tmpl, sb_slist_t *sources, sb_trie_t *config, bool listing) { if (tmpl == NULL) return NULL; - bc_slist_t *current_source = NULL; - bc_slist_t *listing_start = NULL; + sb_slist_t *current_source = NULL; + sb_slist_t *listing_start = NULL; - bc_string_t *str = bc_string_new(); + sb_string_t *str = sb_string_new(); - bc_trie_t *tmp_source = NULL; + sb_trie_t *tmp_source = NULL; char *config_value = NULL; char *defined = NULL; - size_t if_count = 0; + unsigned int if_count = 0; - char *foreach_name = NULL; - bc_slist_t *foreach_var = NULL; - bc_slist_t *foreach_var_start = NULL; - bc_slist_t *foreach_start = NULL; + sb_slist_t *foreach_var = NULL; + sb_slist_t *foreach_var_start = NULL; + sb_slist_t *foreach_start = NULL; bool if_not = false; bool inside_block = false; @@ -238,87 +190,55 @@ blogc_render(bc_slist_t *tmpl, bc_slist_t *sources, bc_slist_t *listing_entries, int cmp = 0; - bc_slist_t *tmp = tmpl; - bc_slist_t *current_listing_entry = listing_entries; + sb_slist_t *tmp = tmpl; while (tmp != NULL) { - blogc_template_node_t *node = tmp->data; + blogc_template_stmt_t *stmt = tmp->data; - switch (node->type) { + switch (stmt->type) { - case BLOGC_TEMPLATE_NODE_CONTENT: - if (node->data[0] != NULL) - bc_string_append(str, node->data[0]); + case BLOGC_TEMPLATE_CONTENT_STMT: + if (stmt->value != NULL) + sb_string_append(str, stmt->value); break; - case BLOGC_TEMPLATE_NODE_BLOCK: + case BLOGC_TEMPLATE_BLOCK_STMT: inside_block = true; if_count = 0; - if (0 == strcmp("entry", node->data[0])) { + if (0 == strcmp("entry", stmt->value)) { if (listing) { // we can just skip anything and walk until the next // 'endblock' - while (node->type != BLOGC_TEMPLATE_NODE_ENDBLOCK) { + while (stmt->type != BLOGC_TEMPLATE_ENDBLOCK_STMT) { tmp = tmp->next; - node = tmp->data; + stmt = tmp->data; } break; } current_source = sources; - tmp_source = current_source != NULL ? current_source->data : NULL; - } - if (0 == strcmp("listing_entry", node->data[0])) { - bc_trie_t *listing_entry = NULL; - if (current_listing_entry != NULL) { - listing_entry = current_listing_entry->data; - current_listing_entry = current_listing_entry->next; - } - if (listing_entry == NULL || !listing) { - // we can just skip anything and walk until the next - // 'endblock' - while (node->type != BLOGC_TEMPLATE_NODE_ENDBLOCK) { - tmp = tmp->next; - node = tmp->data; - } - break; - } - current_source = NULL; - tmp_source = listing_entry; + tmp_source = current_source->data; } - else if ((0 == strcmp("listing", node->data[0])) || - (0 == strcmp("listing_empty", node->data[0])) || - (0 == strcmp("listing_once", node->data[0]))) { + else if ((0 == strcmp("listing", stmt->value)) || + (0 == strcmp("listing_once", stmt->value))) { if (!listing) { // we can just skip anything and walk until the next // 'endblock' - while (node->type != BLOGC_TEMPLATE_NODE_ENDBLOCK) { + while (stmt->type != BLOGC_TEMPLATE_ENDBLOCK_STMT) { tmp = tmp->next; - node = tmp->data; + stmt = tmp->data; } break; } } - if (0 == strcmp("listing_empty", node->data[0])) { - if (sources != NULL) { - - // we can just skip anything and walk until the next - // 'endblock' - while (node->type != BLOGC_TEMPLATE_NODE_ENDBLOCK) { - tmp = tmp->next; - node = tmp->data; - } - break; - } - } - if (0 == strcmp("listing", node->data[0])) { + if (0 == strcmp("listing", stmt->value)) { if (sources == NULL) { // we can just skip anything and walk until the next // 'endblock' - while (node->type != BLOGC_TEMPLATE_NODE_ENDBLOCK) { + while (stmt->type != BLOGC_TEMPLATE_ENDBLOCK_STMT) { tmp = tmp->next; - node = tmp->data; + stmt = tmp->data; } break; } @@ -326,16 +246,16 @@ blogc_render(bc_slist_t *tmpl, bc_slist_t *sources, bc_slist_t *listing_entries, listing_start = tmp; current_source = sources; } - tmp_source = current_source != NULL ? current_source->data : NULL; + tmp_source = current_source->data; } break; - case BLOGC_TEMPLATE_NODE_VARIABLE: - if (node->data[0] != NULL) { - config_value = blogc_format_variable(node->data[0], - config, inside_block ? tmp_source : NULL, foreach_name, foreach_var); + case BLOGC_TEMPLATE_VARIABLE_STMT: + if (stmt->value != NULL) { + config_value = blogc_format_variable(stmt->value, + config, inside_block ? tmp_source : NULL, foreach_var); if (config_value != NULL) { - bc_string_append(str, config_value); + sb_string_append(str, config_value); free(config_value); config_value = NULL; break; @@ -343,7 +263,7 @@ blogc_render(bc_slist_t *tmpl, bc_slist_t *sources, bc_slist_t *listing_entries, } break; - case BLOGC_TEMPLATE_NODE_ENDBLOCK: + case BLOGC_TEMPLATE_ENDBLOCK_STMT: inside_block = false; if (listing_start != NULL && current_source != NULL) { current_source = current_source->next; @@ -356,46 +276,46 @@ blogc_render(bc_slist_t *tmpl, bc_slist_t *sources, bc_slist_t *listing_entries, } break; - case BLOGC_TEMPLATE_NODE_IFNDEF: + case BLOGC_TEMPLATE_IFNDEF_STMT: if_not = true; - case BLOGC_TEMPLATE_NODE_IF: - case BLOGC_TEMPLATE_NODE_IFDEF: + case BLOGC_TEMPLATE_IF_STMT: + case BLOGC_TEMPLATE_IFDEF_STMT: if_count = 0; defined = NULL; - if (node->data[0] != NULL) - defined = blogc_format_variable(node->data[0], config, - inside_block ? tmp_source : NULL, foreach_name, foreach_var); + if (stmt->value != NULL) + defined = blogc_format_variable(stmt->value, config, + inside_block ? tmp_source : NULL, foreach_var); evaluate = false; - if (node->op != 0) { + if (stmt->op != 0) { // Strings that start with a '"' are actually strings, the // others are meant to be looked up as a second variable // check. char *defined2 = NULL; - if (node->data[1] != NULL) { - if ((strlen(node->data[1]) >= 2) && - (node->data[1][0] == '"') && - (node->data[1][strlen(node->data[1]) - 1] == '"')) + if (stmt->value2 != NULL) { + if ((strlen(stmt->value2) >= 2) && + (stmt->value2[0] == '"') && + (stmt->value2[strlen(stmt->value2) - 1] == '"')) { - defined2 = bc_strndup(node->data[1] + 1, - strlen(node->data[1]) - 2); + defined2 = sb_strndup(stmt->value2 + 1, + strlen(stmt->value2) - 2); } else { - defined2 = blogc_format_variable(node->data[1], + defined2 = blogc_format_variable(stmt->value2, config, inside_block ? tmp_source : NULL, - foreach_name, foreach_var); + foreach_var); } } if (defined != NULL && defined2 != NULL) { cmp = strcmp(defined, defined2); - if (cmp != 0 && node->op & BLOGC_TEMPLATE_OP_NEQ) + if (cmp != 0 && stmt->op & BLOGC_TEMPLATE_OP_NEQ) evaluate = true; - else if (cmp == 0 && node->op & BLOGC_TEMPLATE_OP_EQ) + else if (cmp == 0 && stmt->op & BLOGC_TEMPLATE_OP_EQ) evaluate = true; - else if (cmp < 0 && node->op & BLOGC_TEMPLATE_OP_LT) + else if (cmp < 0 && stmt->op & BLOGC_TEMPLATE_OP_LT) evaluate = true; - else if (cmp > 0 && node->op & BLOGC_TEMPLATE_OP_GT) + else if (cmp > 0 && stmt->op & BLOGC_TEMPLATE_OP_GT) evaluate = true; } @@ -414,15 +334,15 @@ blogc_render(bc_slist_t *tmpl, bc_slist_t *sources, bc_slist_t *listing_entries, // skip as well. while (1) { tmp = tmp->next; - node = tmp->data; - if ((node->type == BLOGC_TEMPLATE_NODE_IF) || - (node->type == BLOGC_TEMPLATE_NODE_IFDEF) || - (node->type == BLOGC_TEMPLATE_NODE_IFNDEF)) + stmt = tmp->data; + if ((stmt->type == BLOGC_TEMPLATE_IF_STMT) || + (stmt->type == BLOGC_TEMPLATE_IFDEF_STMT) || + (stmt->type == BLOGC_TEMPLATE_IFNDEF_STMT)) { if_count++; continue; } - if ((node->type == BLOGC_TEMPLATE_NODE_ELSE) && + if ((stmt->type == BLOGC_TEMPLATE_ELSE_STMT) && (if_count == 0)) { // this is somewhat complex. only an else statement @@ -433,7 +353,7 @@ blogc_render(bc_slist_t *tmpl, bc_slist_t *sources, bc_slist_t *listing_entries, valid_else = true; break; } - if (node->type == BLOGC_TEMPLATE_NODE_ENDIF) { + if (stmt->type == BLOGC_TEMPLATE_ENDIF_STMT) { if (if_count > 0) { if_count--; continue; @@ -442,15 +362,12 @@ blogc_render(bc_slist_t *tmpl, bc_slist_t *sources, bc_slist_t *listing_entries, } } } - else { - valid_else = false; - } free(defined); defined = NULL; if_not = false; break; - case BLOGC_TEMPLATE_NODE_ELSE: + case BLOGC_TEMPLATE_ELSE_STMT: if_count = 0; if (!valid_else) { @@ -459,17 +376,17 @@ blogc_render(bc_slist_t *tmpl, bc_slist_t *sources, bc_slist_t *listing_entries, // skip as well. while (1) { tmp = tmp->next; - node = tmp->data; - if ((node->type == BLOGC_TEMPLATE_NODE_IF) || - (node->type == BLOGC_TEMPLATE_NODE_IFDEF) || - (node->type == BLOGC_TEMPLATE_NODE_IFNDEF)) + stmt = tmp->data; + if ((stmt->type == BLOGC_TEMPLATE_IF_STMT) || + (stmt->type == BLOGC_TEMPLATE_IFDEF_STMT) || + (stmt->type == BLOGC_TEMPLATE_IFNDEF_STMT)) { if_count++; continue; } // no need to handle else statements here, because every // if should have an endif. - if (node->type == BLOGC_TEMPLATE_NODE_ENDIF) { + if (stmt->type == BLOGC_TEMPLATE_ENDIF_STMT) { if (if_count > 0) { if_count--; continue; @@ -481,7 +398,7 @@ blogc_render(bc_slist_t *tmpl, bc_slist_t *sources, bc_slist_t *listing_entries, valid_else = false; break; - case BLOGC_TEMPLATE_NODE_ENDIF: + case BLOGC_TEMPLATE_ENDIF_STMT: // any endif statement should invalidate valid_else, to avoid // propagation to outter conditionals. valid_else = false; @@ -489,14 +406,13 @@ blogc_render(bc_slist_t *tmpl, bc_slist_t *sources, bc_slist_t *listing_entries, if_count--; break; - case BLOGC_TEMPLATE_NODE_FOREACH: + case BLOGC_TEMPLATE_FOREACH_STMT: if (foreach_var_start == NULL) { - if (node->data[0] != NULL) - foreach_var_start = blogc_split_list_variable(node->data[0], + if (stmt->value != NULL) + foreach_var_start = blogc_split_list_variable(stmt->value, config, inside_block ? tmp_source : NULL); if (foreach_var_start != NULL) { - foreach_name = bc_strdup(node->data[0]); foreach_var = foreach_var_start; foreach_start = tmp; } @@ -504,9 +420,9 @@ blogc_render(bc_slist_t *tmpl, bc_slist_t *sources, bc_slist_t *listing_entries, // we can just skip anything and walk until the next // 'endforeach' - while (node->type != BLOGC_TEMPLATE_NODE_ENDFOREACH) { + while (stmt->type != BLOGC_TEMPLATE_ENDFOREACH_STMT) { tmp = tmp->next; - node = tmp->data; + stmt = tmp->data; } break; } @@ -514,12 +430,11 @@ blogc_render(bc_slist_t *tmpl, bc_slist_t *sources, bc_slist_t *listing_entries, if (foreach_var == NULL) { foreach_start = tmp; - foreach_name = bc_strdup(node->data[0]); foreach_var = foreach_var_start; } break; - case BLOGC_TEMPLATE_NODE_ENDFOREACH: + case BLOGC_TEMPLATE_ENDFOREACH_STMT: if (foreach_start != NULL && foreach_var != NULL) { foreach_var = foreach_var->next; if (foreach_var != NULL) { @@ -528,10 +443,8 @@ blogc_render(bc_slist_t *tmpl, bc_slist_t *sources, bc_slist_t *listing_entries, } } foreach_start = NULL; - bc_slist_free_full(foreach_var_start, free); + sb_slist_free_full(foreach_var_start, free); foreach_var_start = NULL; - free(foreach_name); - foreach_name = NULL; break; } tmp = tmp->next; @@ -540,5 +453,5 @@ blogc_render(bc_slist_t *tmpl, bc_slist_t *sources, bc_slist_t *listing_entries, // no need to free temporary variables here. the template parser makes sure // that templates are sane and statements are closed. - return bc_string_free(str, false); + return sb_string_free(str, false); } diff --git a/src/renderer.h b/src/renderer.h new file mode 100644 index 0000000..2982ee8 --- /dev/null +++ b/src/renderer.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 _RENDERER_H +#define _RENDERER_H + +#include <stdbool.h> +#include "utils.h" + +const char* blogc_get_variable(const char *name, sb_trie_t *global, sb_trie_t *local); +char* blogc_format_date(const char *date, sb_trie_t *global, sb_trie_t *local); +char* blogc_format_variable(const char *name, sb_trie_t *global, sb_trie_t *local, + sb_slist_t *foreach_var); +sb_slist_t* blogc_split_list_variable(const char *name, sb_trie_t *global, + sb_trie_t *local); +char* blogc_render(sb_slist_t *tmpl, sb_slist_t *sources, sb_trie_t *config, + bool listing); + +#endif /* _RENDERER_H */ diff --git a/src/blogc/source-parser.c b/src/source-parser.c index 13df9e3..349d3f7 100644 --- a/src/blogc/source-parser.c +++ b/src/source-parser.c @@ -1,6 +1,6 @@ /* * blogc: A blog compiler. - * Copyright (C) 2014-2020 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * 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. @@ -11,9 +11,8 @@ #include "content-parser.h" #include "source-parser.h" -#include "toctree.h" -#include "../common/error.h" -#include "../common/utils.h" +#include "error.h" +#include "utils.h" typedef enum { @@ -27,21 +26,19 @@ typedef enum { } blogc_source_parser_state_t; -bc_trie_t* -blogc_source_parse(const char *src, size_t src_len, int toctree_maxdepth, - bc_error_t **err) +sb_trie_t* +blogc_source_parse(const char *src, size_t src_len, blogc_error_t **err) { if (err == NULL || *err != NULL) return NULL; size_t current = 0; size_t start = 0; - size_t end_excerpt = 0; char *key = NULL; char *tmp = NULL; char *content = NULL; - bc_trie_t *rv = bc_trie_new(free); + sb_trie_t *rv = sb_trie_new(free); blogc_source_parser_state_t state = SOURCE_START; @@ -62,7 +59,7 @@ blogc_source_parse(const char *src, size_t src_len, int toctree_maxdepth, state = SOURCE_SEPARATOR; break; } - *err = bc_error_parser(BLOGC_ERROR_SOURCE_PARSER, src, src_len, + *err = blogc_error_parser(BLOGC_ERROR_SOURCE_PARSER, src, src_len, current, "Can't find a configuration key or the content separator."); break; @@ -71,7 +68,7 @@ blogc_source_parse(const char *src, size_t src_len, int toctree_maxdepth, if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_') break; if (c == ':') { - key = bc_strndup(src + start, current - start); + key = sb_strndup(src + start, current - start); if (((current - start == 8) && (0 == strncmp("FILENAME", src + start, 8))) || ((current - start == 7) && @@ -95,7 +92,7 @@ blogc_source_parse(const char *src, size_t src_len, int toctree_maxdepth, ((current - start == 13) && (0 == strncmp("BLOGC_VERSION", src + start, 13)))) { - *err = bc_error_new_printf(BLOGC_ERROR_SOURCE_PARSER, + *err = blogc_error_new_printf(BLOGC_ERROR_SOURCE_PARSER, "'%s' variable is forbidden in source files. It will " "be set for you by the compiler.", key); break; @@ -103,7 +100,7 @@ blogc_source_parse(const char *src, size_t src_len, int toctree_maxdepth, state = SOURCE_CONFIG_VALUE_START; break; } - *err = bc_error_parser(BLOGC_ERROR_SOURCE_PARSER, src, src_len, + *err = blogc_error_parser(BLOGC_ERROR_SOURCE_PARSER, src, src_len, current, "Invalid configuration key."); break; @@ -113,16 +110,15 @@ blogc_source_parse(const char *src, size_t src_len, int toctree_maxdepth, start = current; break; } - bc_trie_insert(rv, key, bc_strdup("")); - free(key); - key = NULL; - state = SOURCE_START; + *err = blogc_error_parser(BLOGC_ERROR_SOURCE_PARSER, src, src_len, + current, "Configuration value not provided for '%s'.", + key); break; case SOURCE_CONFIG_VALUE: if (c == '\n' || c == '\r') { - tmp = bc_strndup(src + start, current - start); - bc_trie_insert(rv, key, bc_strdup(bc_str_strip(tmp))); + tmp = sb_strndup(src + start, current - start); + sb_trie_insert(rv, key, sb_strdup(sb_str_strip(tmp))); free(tmp); free(key); key = NULL; @@ -137,7 +133,7 @@ blogc_source_parse(const char *src, size_t src_len, int toctree_maxdepth, state = SOURCE_CONTENT_START; break; } - *err = bc_error_parser(BLOGC_ERROR_SOURCE_PARSER, src, src_len, + *err = blogc_error_parser(BLOGC_ERROR_SOURCE_PARSER, src, src_len, current, "Invalid content separator. Must be more than one '-' characters."); break; @@ -151,65 +147,25 @@ blogc_source_parse(const char *src, size_t src_len, int toctree_maxdepth, case SOURCE_CONTENT: if (current == (src_len - 1)) { - tmp = bc_strndup(src + start, src_len - start); - bc_trie_insert(rv, "RAW_CONTENT", tmp); - char *first_header = NULL; + tmp = sb_strndup(src + start, src_len - start); + sb_trie_insert(rv, "RAW_CONTENT", tmp); + char *excerpt = NULL; char *description = NULL; - char *endl = NULL; - bc_slist_t *headers = NULL; - bool read_headers = (NULL == bc_trie_lookup(rv, "TOCTREE")); - content = blogc_content_parse(tmp, &end_excerpt, - &first_header, &description, &endl, read_headers ? &headers : NULL); - if (first_header != NULL) { - // do not override source-provided first_header. - if (NULL == bc_trie_lookup(rv, "FIRST_HEADER")) { - // no need to free, because we are transfering memory - // ownership to the trie. - bc_trie_insert(rv, "FIRST_HEADER", first_header); - } - else { - free(first_header); - } - } + content = blogc_content_parse(tmp, &excerpt, &description); if (description != NULL) { // do not override source-provided description. - if (NULL == bc_trie_lookup(rv, "DESCRIPTION")) { + if (NULL == sb_trie_lookup(rv, "DESCRIPTION")) { // no need to free, because we are transfering memory // ownership to the trie. - bc_trie_insert(rv, "DESCRIPTION", description); + sb_trie_insert(rv, "DESCRIPTION", description); } else { free(description); } } - if (headers != NULL) { - // we already validated that the user do not defined TOCTREE - // manually in source file. - const char *maxdepth = bc_trie_lookup(rv, "TOCTREE_MAXDEPTH"); - if (maxdepth != NULL) { - char *endptr; - toctree_maxdepth = strtol(maxdepth, &endptr, 10); - if (*maxdepth != '\0' && *endptr != '\0') { - *err = bc_error_parser(BLOGC_ERROR_SOURCE_PARSER, src, src_len, - current, - "Invalid value for 'TOCTREE_MAXDEPTH' variable: %s.", - maxdepth); - blogc_toctree_free(headers); - free(endl); - free(content); - break; - } - } - char *toctree = blogc_toctree_render(headers, toctree_maxdepth, endl); - blogc_toctree_free(headers); - if (toctree != NULL) { - bc_trie_insert(rv, "TOCTREE", toctree); - } - } - free(endl); - bc_trie_insert(rv, "CONTENT", content); - bc_trie_insert(rv, "EXCERPT", end_excerpt == 0 ? - bc_strdup(content) : bc_strndup(content, end_excerpt)); + sb_trie_insert(rv, "EXCERPT", excerpt == NULL ? + sb_strdup(content) : excerpt); + sb_trie_insert(rv, "CONTENT", content); } break; } @@ -220,28 +176,28 @@ blogc_source_parse(const char *src, size_t src_len, int toctree_maxdepth, current++; } - if (*err == NULL && bc_trie_size(rv) == 0) { + if (*err == NULL && sb_trie_size(rv) == 0) { // ok, nothing found in the config trie, but no error set either. // let's try to be nice with the users and provide some reasonable // output. :) switch (state) { case SOURCE_START: - *err = bc_error_parser(BLOGC_ERROR_SOURCE_PARSER, src, src_len, + *err = blogc_error_parser(BLOGC_ERROR_SOURCE_PARSER, src, src_len, current, "Your source file is empty."); break; case SOURCE_CONFIG_KEY: - *err = bc_error_parser(BLOGC_ERROR_SOURCE_PARSER, src, src_len, + *err = blogc_error_parser(BLOGC_ERROR_SOURCE_PARSER, src, src_len, current, "Your last configuration key is missing ':' and " "the value"); break; case SOURCE_CONFIG_VALUE_START: - *err = bc_error_parser(BLOGC_ERROR_SOURCE_PARSER, src, src_len, + *err = blogc_error_parser(BLOGC_ERROR_SOURCE_PARSER, src, src_len, current, "Configuration value not provided for '%s'.", key); break; case SOURCE_CONFIG_VALUE: - *err = bc_error_parser(BLOGC_ERROR_SOURCE_PARSER, src, src_len, + *err = blogc_error_parser(BLOGC_ERROR_SOURCE_PARSER, src, src_len, current, "No line ending after the configuration value for " "'%s'.", key); break; @@ -254,7 +210,7 @@ blogc_source_parse(const char *src, size_t src_len, int toctree_maxdepth, if (*err != NULL) { free(key); - bc_trie_free(rv); + sb_trie_free(rv); return NULL; } diff --git a/src/blogc/source-parser.h b/src/source-parser.h index 2acd753..0d54742 100644 --- a/src/blogc/source-parser.h +++ b/src/source-parser.h @@ -1,6 +1,6 @@ /* * blogc: A blog compiler. - * Copyright (C) 2014-2020 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * 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. @@ -10,10 +10,10 @@ #define _SOURCE_PARSER_H #include <stddef.h> -#include "../common/error.h" -#include "../common/utils.h" +#include "error.h" +#include "utils.h" -bc_trie_t* blogc_source_parse(const char *src, size_t src_len, int toctree_maxdepth, - bc_error_t **err); +sb_trie_t* blogc_source_parse(const char *src, size_t src_len, + blogc_error_t **err); #endif /* _SOURCE_PARSER_H */ diff --git a/src/blogc/template-parser.c b/src/template-parser.c index 8e7ca84..6ca6eb7 100644 --- a/src/blogc/template-parser.c +++ b/src/template-parser.c @@ -1,6 +1,6 @@ /* * blogc: A blog compiler. - * Copyright (C) 2014-2020 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * 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. @@ -11,8 +11,8 @@ #include <string.h> #include "template-parser.h" -#include "../common/error.h" -#include "../common/utils.h" +#include "error.h" +#include "utils.h" typedef enum { @@ -41,8 +41,8 @@ typedef enum { } blogc_template_parser_state_t; -bc_slist_t* -blogc_template_parse(const char *src, size_t src_len, bc_error_t **err) +sb_slist_t* +blogc_template_parse(const char *src, size_t src_len, blogc_error_t **err) { if (err == NULL || *err != NULL) return NULL; @@ -55,16 +55,16 @@ blogc_template_parse(const char *src, size_t src_len, bc_error_t **err) size_t start2 = 0; size_t end2 = 0; - blogc_template_operator_t tmp_op = 0; + blogc_template_stmt_operator_t tmp_op = 0; - size_t if_count = 0; - size_t block_if_count = 0; + unsigned int if_count = 0; + unsigned int block_if_count = 0; bool else_open = false; bool foreach_open = false; bool block_foreach_open = false; - bc_slist_t *ast = NULL; - blogc_template_node_t *node = NULL; + sb_slist_t *stmts = NULL; + blogc_template_stmt_t *stmt = NULL; /* * this is a reference to the content of previous node in the singly-linked @@ -75,14 +75,14 @@ blogc_template_parse(const char *src, size_t src_len, bc_error_t **err) * - template parser never walk backwards, then the list itself does not * need to know its previous node. */ - blogc_template_node_t *previous = NULL; + blogc_template_stmt_t *previous = NULL; bool lstrip_next = false; char *tmp = NULL; char *block_type = NULL; blogc_template_parser_state_t state = TEMPLATE_START; - blogc_template_node_type_t type = BLOGC_TEMPLATE_NODE_CONTENT; + blogc_template_stmt_type_t type = BLOGC_TEMPLATE_CONTENT_STMT; bool block_open = false; @@ -94,23 +94,23 @@ blogc_template_parse(const char *src, size_t src_len, bc_error_t **err) case TEMPLATE_START: if (last) { - node = bc_malloc(sizeof(blogc_template_node_t)); - node->type = type; + stmt = sb_malloc(sizeof(blogc_template_stmt_t)); + stmt->type = type; if (lstrip_next) { - tmp = bc_strndup(src + start, src_len - start); - node->data[0] = bc_strdup(bc_str_lstrip(tmp)); + tmp = sb_strndup(src + start, src_len - start); + stmt->value = sb_strdup(sb_str_lstrip(tmp)); free(tmp); tmp = NULL; lstrip_next = false; } else { - node->data[0] = bc_strndup(src + start, src_len - start); + stmt->value = sb_strndup(src + start, src_len - start); } - node->op = 0; - node->data[1] = NULL; - ast = bc_slist_append(ast, node); - previous = node; - node = NULL; + stmt->op = 0; + stmt->value2 = NULL; + stmts = sb_slist_append(stmts, stmt); + previous = stmt; + stmt = NULL; } if (c == '{') { end = current; @@ -125,23 +125,23 @@ blogc_template_parse(const char *src, size_t src_len, bc_error_t **err) else state = TEMPLATE_VARIABLE_START; if (end > start) { - node = bc_malloc(sizeof(blogc_template_node_t)); - node->type = type; + stmt = sb_malloc(sizeof(blogc_template_stmt_t)); + stmt->type = type; if (lstrip_next) { - tmp = bc_strndup(src + start, end - start); - node->data[0] = bc_strdup(bc_str_lstrip(tmp)); + tmp = sb_strndup(src + start, end - start); + stmt->value = sb_strdup(sb_str_lstrip(tmp)); free(tmp); tmp = NULL; lstrip_next = false; } else { - node->data[0] = bc_strndup(src + start, end - start); + stmt->value = sb_strndup(src + start, end - start); } - node->op = 0; - node->data[1] = NULL; - ast = bc_slist_append(ast, node); - previous = node; - node = NULL; + stmt->op = 0; + stmt->value2 = NULL; + stmts = sb_slist_append(stmts, stmt); + previous = stmt; + stmt = NULL; } break; } @@ -151,9 +151,9 @@ blogc_template_parse(const char *src, size_t src_len, bc_error_t **err) case TEMPLATE_BLOCK_START_WHITESPACE_CLEANER: if (c == '-') { if ((previous != NULL) && - (previous->type == BLOGC_TEMPLATE_NODE_CONTENT)) + (previous->type == BLOGC_TEMPLATE_CONTENT_STMT)) { - previous->data[0] = bc_str_rstrip(previous->data[0]); // does not need copy + previous->value = sb_str_rstrip(previous->value); // does not need copy } state = TEMPLATE_BLOCK_START; break; @@ -161,7 +161,7 @@ blogc_template_parse(const char *src, size_t src_len, bc_error_t **err) state = TEMPLATE_BLOCK_START; case TEMPLATE_BLOCK_START: - if (bc_isspace(c)) + if (c == ' ') break; if (c >= 'a' && c <= 'z') { state = TEMPLATE_BLOCK_TYPE; @@ -169,13 +169,13 @@ blogc_template_parse(const char *src, size_t src_len, bc_error_t **err) break; } if (c == '-') { - *err = bc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, + *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, src_len, current, "Invalid statement syntax. Duplicated whitespace " "cleaner before statement."); break; } - *err = bc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, + *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, src_len, current, "Invalid statement syntax. Must begin with lowercase letter."); break; @@ -183,19 +183,19 @@ blogc_template_parse(const char *src, size_t src_len, bc_error_t **err) case TEMPLATE_BLOCK_TYPE: if (c >= 'a' && c <= 'z') break; - if (bc_isspace(c)) { + if (c == ' ') { if ((current - start == 5) && (0 == strncmp("block", src + start, 5))) { if (!block_open) { state = TEMPLATE_BLOCK_BLOCK_TYPE_START; - type = BLOGC_TEMPLATE_NODE_BLOCK; + type = BLOGC_TEMPLATE_BLOCK_STMT; start = current; block_if_count = if_count; block_foreach_open = foreach_open; break; } - *err = bc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, + *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, src_len, current, "Blocks can't be nested."); break; } @@ -204,24 +204,24 @@ blogc_template_parse(const char *src, size_t src_len, bc_error_t **err) { if (block_open) { if (if_count != block_if_count) { - *err = bc_error_new_printf(BLOGC_ERROR_TEMPLATE_PARSER, + *err = blogc_error_new_printf(BLOGC_ERROR_TEMPLATE_PARSER, "%d open 'if', 'ifdef' and/or 'ifndef' statements " "were not closed inside a '%s' block!", if_count - block_if_count, block_type); break; } if (!block_foreach_open && foreach_open) { - *err = bc_error_new_printf(BLOGC_ERROR_TEMPLATE_PARSER, + *err = blogc_error_new_printf(BLOGC_ERROR_TEMPLATE_PARSER, "An open 'foreach' statement was not closed " "inside a '%s' block!", block_type); break; } state = TEMPLATE_BLOCK_END_WHITESPACE_CLEANER; - type = BLOGC_TEMPLATE_NODE_ENDBLOCK; + type = BLOGC_TEMPLATE_ENDBLOCK_STMT; block_open = false; break; } - *err = bc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, + *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, src_len, current, "'endblock' statement without an open 'block' statement."); break; @@ -230,7 +230,7 @@ blogc_template_parse(const char *src, size_t src_len, bc_error_t **err) (0 == strncmp("ifdef", src + start, 5))) { state = TEMPLATE_BLOCK_IF_START; - type = BLOGC_TEMPLATE_NODE_IFDEF; + type = BLOGC_TEMPLATE_IFDEF_STMT; start = current; if_count++; else_open = false; @@ -240,7 +240,7 @@ blogc_template_parse(const char *src, size_t src_len, bc_error_t **err) (0 == strncmp("ifndef", src + start, 6))) { state = TEMPLATE_BLOCK_IF_START; - type = BLOGC_TEMPLATE_NODE_IFNDEF; + type = BLOGC_TEMPLATE_IFNDEF_STMT; start = current; if_count++; else_open = false; @@ -250,7 +250,7 @@ blogc_template_parse(const char *src, size_t src_len, bc_error_t **err) (0 == strncmp("if", src + start, 2))) { state = TEMPLATE_BLOCK_IF_START; - type = BLOGC_TEMPLATE_NODE_IF; + type = BLOGC_TEMPLATE_IF_STMT; start = current; if_count++; else_open = false; @@ -264,17 +264,17 @@ blogc_template_parse(const char *src, size_t src_len, bc_error_t **err) { if (!else_open) { state = TEMPLATE_BLOCK_END_WHITESPACE_CLEANER; - type = BLOGC_TEMPLATE_NODE_ELSE; + type = BLOGC_TEMPLATE_ELSE_STMT; else_open = true; break; } - *err = bc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, + *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, src_len, current, "More than one 'else' statement for an open 'if', " "'ifdef' or 'ifndef' statement."); break; } - *err = bc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, + *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, src_len, current, "'else' statement without an open 'if', 'ifdef' or " "'ifndef' statement."); @@ -287,12 +287,12 @@ blogc_template_parse(const char *src, size_t src_len, bc_error_t **err) (!block_open && if_count > 0)) { state = TEMPLATE_BLOCK_END_WHITESPACE_CLEANER; - type = BLOGC_TEMPLATE_NODE_ENDIF; + type = BLOGC_TEMPLATE_ENDIF_STMT; if_count--; else_open = false; break; } - *err = bc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, + *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, src_len, current, "'endif' statement without an open 'if', 'ifdef' or " "'ifndef' statement."); @@ -303,12 +303,12 @@ blogc_template_parse(const char *src, size_t src_len, bc_error_t **err) { if (!foreach_open) { state = TEMPLATE_BLOCK_FOREACH_START; - type = BLOGC_TEMPLATE_NODE_FOREACH; + type = BLOGC_TEMPLATE_FOREACH_STMT; start = current; foreach_open = true; break; } - *err = bc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, + *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, src_len, current, "'foreach' statements can't " "be nested."); break; @@ -320,18 +320,18 @@ blogc_template_parse(const char *src, size_t src_len, bc_error_t **err) (!block_open && foreach_open)) { state = TEMPLATE_BLOCK_END_WHITESPACE_CLEANER; - type = BLOGC_TEMPLATE_NODE_ENDFOREACH; + type = BLOGC_TEMPLATE_ENDFOREACH_STMT; foreach_open = false; break; } - *err = bc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, + *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, src_len, current, "'endforeach' statement without an open 'foreach' " "statement."); break; } } - *err = bc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, + *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, src_len, current, "Invalid statement type: Allowed types are: 'block', " "'endblock', 'if', 'ifdef', 'ifndef', 'else', 'endif', " @@ -339,14 +339,14 @@ blogc_template_parse(const char *src, size_t src_len, bc_error_t **err) break; case TEMPLATE_BLOCK_BLOCK_TYPE_START: - if (bc_isspace(c)) + if (c == ' ') break; if (c >= 'a' && c <= 'z') { state = TEMPLATE_BLOCK_BLOCK_TYPE; start = current; break; } - *err = bc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, + *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, src_len, current, "Invalid block syntax. Must begin with lowercase letter."); break; @@ -354,7 +354,7 @@ blogc_template_parse(const char *src, size_t src_len, bc_error_t **err) case TEMPLATE_BLOCK_BLOCK_TYPE: if ((c >= 'a' && c <= 'z') || c == '_') break; - if (bc_isspace(c)) { + if (c == ' ') { if ((current - start == 5) && (0 == strncmp("entry", src + start, 5))) { @@ -379,38 +379,22 @@ blogc_template_parse(const char *src, size_t src_len, bc_error_t **err) state = TEMPLATE_BLOCK_END_WHITESPACE_CLEANER; break; } - else if ((current - start == 13) && - (0 == strncmp("listing_empty", src + start, 13))) - { - block_open = true; - end = current; - state = TEMPLATE_BLOCK_END_WHITESPACE_CLEANER; - break; - } - else if ((current - start == 13) && - (0 == strncmp("listing_entry", src + start, 13))) - { - block_open = true; - end = current; - state = TEMPLATE_BLOCK_END_WHITESPACE_CLEANER; - break; - } } - *err = bc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, + *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, src_len, current, - "Invalid block type. Allowed types are: 'entry', 'listing', " - "'listing_once', 'listing_empty' and 'listing_entry'."); + "Invalid block type. Allowed types are: 'entry', 'listing' " + "and 'listing_once'."); break; case TEMPLATE_BLOCK_IF_START: - if (bc_isspace(c)) + if (c == ' ') break; if (c >= 'A' && c <= 'Z') { state = TEMPLATE_BLOCK_IF_VARIABLE; start = current; break; } - *err = bc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, + *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, src_len, current, "Invalid variable name. Must begin with uppercase letter."); break; @@ -418,36 +402,37 @@ blogc_template_parse(const char *src, size_t src_len, bc_error_t **err) case TEMPLATE_BLOCK_IF_VARIABLE: if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_') break; - if (bc_isspace(c)) { + if (c == ' ') { end = current; - if (type == BLOGC_TEMPLATE_NODE_IF) + if (type == BLOGC_TEMPLATE_IF_STMT) state = TEMPLATE_BLOCK_IF_OPERATOR_START; else state = TEMPLATE_BLOCK_END_WHITESPACE_CLEANER; break; } - *err = bc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, + *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, src_len, current, "Invalid variable name. Must be uppercase letter, number " "or '_'."); break; case TEMPLATE_BLOCK_IF_OPERATOR_START: - if (bc_isspace(c)) + if (c == ' ') { break; + } state = TEMPLATE_BLOCK_IF_OPERATOR; op_start = current; break; case TEMPLATE_BLOCK_IF_OPERATOR: - if (!bc_isspace(c)) + if (c != ' ') break; state = TEMPLATE_BLOCK_IF_OPERAND_START; op_end = current; break; case TEMPLATE_BLOCK_IF_OPERAND_START: - if (bc_isspace(c)) + if (c == ' ') break; if (c >= 'A' && c <= 'Z') { state = TEMPLATE_BLOCK_IF_VARIABLE_OPERAND; @@ -461,7 +446,7 @@ blogc_template_parse(const char *src, size_t src_len, bc_error_t **err) } op_start = 0; op_end = 0; - *err = bc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, + *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, src_len, current, "Invalid 'if' operand. Must be double-quoted static " "string or variable."); @@ -484,14 +469,14 @@ blogc_template_parse(const char *src, size_t src_len, bc_error_t **err) break; case TEMPLATE_BLOCK_FOREACH_START: - if (bc_isspace(c)) + if (c == ' ') break; if (c >= 'A' && c <= 'Z') { state = TEMPLATE_BLOCK_FOREACH_VARIABLE; start = current; break; } - *err = bc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, + *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, src_len, current, "Invalid foreach variable name. Must begin with uppercase " "letter."); @@ -500,19 +485,19 @@ blogc_template_parse(const char *src, size_t src_len, bc_error_t **err) case TEMPLATE_BLOCK_FOREACH_VARIABLE: if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_') break; - if (bc_isspace(c)) { + if (c == ' ') { end = current; state = TEMPLATE_BLOCK_END_WHITESPACE_CLEANER; break; } - *err = bc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, + *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, src_len, current, "Invalid foreach variable name. Must be uppercase letter, " "number or '_'."); break; case TEMPLATE_BLOCK_END_WHITESPACE_CLEANER: - if (bc_isspace(c)) + if (c == ' ') break; if (c == '-') { lstrip_next = true; @@ -527,27 +512,27 @@ blogc_template_parse(const char *src, size_t src_len, bc_error_t **err) break; } if (c == '-') { - *err = bc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, + *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, src_len, current, "Invalid statement syntax. Duplicated whitespace " "cleaner after statement."); break; } - *err = bc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, + *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, src_len, current, "Invalid statement syntax. Must end with '%%}'."); break; case TEMPLATE_VARIABLE_START: - if (bc_isspace(c)) + if (c == ' ') break; if (c >= 'A' && c <= 'Z') { state = TEMPLATE_VARIABLE; - type = BLOGC_TEMPLATE_NODE_VARIABLE; + type = BLOGC_TEMPLATE_VARIABLE_STMT; start = current; break; } - *err = bc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, + *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, src_len, current, "Invalid variable name. Must begin with uppercase letter."); break; @@ -555,7 +540,7 @@ blogc_template_parse(const char *src, size_t src_len, bc_error_t **err) case TEMPLATE_VARIABLE: if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_') break; - if (bc_isspace(c)) { + if (c == ' ') { end = current; state = TEMPLATE_VARIABLE_END; break; @@ -565,20 +550,20 @@ blogc_template_parse(const char *src, size_t src_len, bc_error_t **err) state = TEMPLATE_CLOSE_BRACKET; break; } - *err = bc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, + *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, src_len, current, "Invalid variable name. Must be uppercase letter, number " "or '_'."); break; case TEMPLATE_VARIABLE_END: - if (bc_isspace(c)) + if (c == ' ') break; if (c == '}') { state = TEMPLATE_CLOSE_BRACKET; break; } - *err = bc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, + *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, src_len, current, "Invalid statement syntax. Must end with '}}'."); break; @@ -604,7 +589,7 @@ blogc_template_parse(const char *src, size_t src_len, bc_error_t **err) tmp_op = BLOGC_TEMPLATE_OP_NEQ; } if (tmp_op == 0) { - *err = bc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, + *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, src_len, op_start, "Invalid 'if' operator. Must be '<', '>', " "'<=', '>=', '==' or '!='."); @@ -615,29 +600,29 @@ blogc_template_parse(const char *src, size_t src_len, bc_error_t **err) op_start = 0; op_end = 0; } - node = bc_malloc(sizeof(blogc_template_node_t)); - node->type = type; - node->op = tmp_op; - node->data[0] = NULL; - node->data[1] = NULL; + stmt = sb_malloc(sizeof(blogc_template_stmt_t)); + stmt->type = type; + stmt->value = NULL; + stmt->op = tmp_op; + stmt->value2 = NULL; if (end > start) - node->data[0] = bc_strndup(src + start, end - start); + stmt->value = sb_strndup(src + start, end - start); if (end2 > start2) { - node->data[1] = bc_strndup(src + start2, end2 - start2); + stmt->value2 = sb_strndup(src + start2, end2 - start2); start2 = 0; end2 = 0; } - if (type == BLOGC_TEMPLATE_NODE_BLOCK) - block_type = node->data[0]; - ast = bc_slist_append(ast, node); - previous = node; - node = NULL; + if (type == BLOGC_TEMPLATE_BLOCK_STMT) + block_type = stmt->value; + stmts = sb_slist_append(stmts, stmt); + previous = stmt; + stmt = NULL; state = TEMPLATE_START; - type = BLOGC_TEMPLATE_NODE_CONTENT; + type = BLOGC_TEMPLATE_CONTENT_STMT; start = current + 1; break; } - *err = bc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, + *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, src_len, current, "Invalid statement syntax. Must end with '}'."); break; @@ -652,43 +637,43 @@ blogc_template_parse(const char *src, size_t src_len, bc_error_t **err) if (*err == NULL) { if (state == TEMPLATE_BLOCK_IF_STRING_OPERAND) - *err = bc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, src_len, + *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, src_len, start2, "Found an open double-quoted string."); else if (if_count != 0) - *err = bc_error_new_printf(BLOGC_ERROR_TEMPLATE_PARSER, + *err = blogc_error_new_printf(BLOGC_ERROR_TEMPLATE_PARSER, "%d open 'if', 'ifdef' and/or 'ifndef' statements were not closed!", if_count); else if (block_open) - *err = bc_error_new(BLOGC_ERROR_TEMPLATE_PARSER, + *err = blogc_error_new(BLOGC_ERROR_TEMPLATE_PARSER, "An open block was not closed!"); else if (foreach_open) - *err = bc_error_new(BLOGC_ERROR_TEMPLATE_PARSER, + *err = blogc_error_new(BLOGC_ERROR_TEMPLATE_PARSER, "An open 'foreach' statement was not closed!"); } if (*err != NULL) { - if (node != NULL) { - free(node->data[0]); - free(node); + if (stmt != NULL) { + free(stmt->value); + free(stmt); } - blogc_template_free_ast(ast); + blogc_template_free_stmts(stmts); return NULL; } - return ast; + return stmts; } void -blogc_template_free_ast(bc_slist_t *ast) +blogc_template_free_stmts(sb_slist_t *stmts) { - for (bc_slist_t *tmp = ast; tmp != NULL; tmp = tmp->next) { - blogc_template_node_t *data = tmp->data; + for (sb_slist_t *tmp = stmts; tmp != NULL; tmp = tmp->next) { + blogc_template_stmt_t *data = tmp->data; if (data == NULL) continue; - free(data->data[0]); - free(data->data[1]); + free(data->value); + free(data->value2); free(data); } - bc_slist_free(ast); + sb_slist_free(stmts); } diff --git a/src/template-parser.h b/src/template-parser.h new file mode 100644 index 0000000..19df6af --- /dev/null +++ b/src/template-parser.h @@ -0,0 +1,53 @@ +/* + * 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 _TEMPLATE_PARSER_H +#define _TEMPLATE_PARSER_H + +#include <stddef.h> +#include "error.h" +#include "utils.h" + +/* + * note: whitespace cleaners are NOT added to ast. we fix strings right during + * template parsing. renderer does not need to care about it, for the sake of + * simplicity. + */ +typedef enum { + BLOGC_TEMPLATE_IFDEF_STMT = 1, + BLOGC_TEMPLATE_IFNDEF_STMT, + BLOGC_TEMPLATE_IF_STMT, + BLOGC_TEMPLATE_ELSE_STMT, + BLOGC_TEMPLATE_ENDIF_STMT, + BLOGC_TEMPLATE_FOREACH_STMT, + BLOGC_TEMPLATE_ENDFOREACH_STMT, + BLOGC_TEMPLATE_BLOCK_STMT, + BLOGC_TEMPLATE_ENDBLOCK_STMT, + BLOGC_TEMPLATE_VARIABLE_STMT, + BLOGC_TEMPLATE_CONTENT_STMT, +} blogc_template_stmt_type_t; + +typedef enum { + BLOGC_TEMPLATE_OP_NEQ = 1 << 0, + BLOGC_TEMPLATE_OP_EQ = 1 << 1, + BLOGC_TEMPLATE_OP_LT = 1 << 2, + BLOGC_TEMPLATE_OP_GT = 1 << 3, +} blogc_template_stmt_operator_t; + +typedef struct { + blogc_template_stmt_type_t type; + char *value; + char *value2; + blogc_template_stmt_operator_t op; +} blogc_template_stmt_t; + +sb_slist_t* blogc_template_parse(const char *src, size_t src_len, + blogc_error_t **err); +void blogc_template_free_stmts(sb_slist_t *stmts); + +#endif /* _TEMPLATE_PARSER_H */ diff --git a/src/common/utf8.c b/src/utf8.c index df5e2d2..a2f4fdd 100644 --- a/src/common/utf8.c +++ b/src/utf8.c @@ -1,7 +1,7 @@ /* * blogc: A blog compiler. * Copyright (c) 2008-2010 Bjoern Hoehrmann <bjoern@hoehrmann.de> - * Copyright (c) 2014-2019 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * Copyright (c) 2016 Rafael G. Martins <rafael@rafaelmartins.eng.br> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -56,27 +56,41 @@ static const uint8_t utf8d[] = { }; +static uint32_t inline +decode(uint32_t* state, uint32_t* codep, uint32_t byte) { + uint32_t type = utf8d[byte]; + + *codep = (*state != UTF8_ACCEPT) ? + (byte & 0x3fu) | (*codep << 6) : + (0xff >> type) & (byte); + + *state = utf8d[256 + *state + type]; + return *state; +} + + bool -bc_utf8_validate(const uint8_t *str, size_t len) +blogc_utf8_validate(const uint8_t *str, size_t len) { + uint32_t codepoint; uint32_t state = 0; for (size_t i = 0; i < len; i++) - state = utf8d[256 + state + utf8d[str[i]]]; + decode(&state, &codepoint, str[i]); return state == UTF8_ACCEPT; } bool -bc_utf8_validate_str(bc_string_t *str) +blogc_utf8_validate_str(sb_string_t *str) { - return bc_utf8_validate((uint8_t*) str->str, str->len); + return blogc_utf8_validate((uint8_t*) str->str, str->len); } size_t -bc_utf8_skip_bom(const uint8_t *str, size_t len) +blogc_utf8_skip_bom(const uint8_t *str, size_t len) { if (len < 3) return 0; diff --git a/src/common/utf8.h b/src/utf8.h index bf0f1ae..06fe07e 100644 --- a/src/common/utf8.h +++ b/src/utf8.h @@ -1,6 +1,6 @@ /* * blogc: A blog compiler. - * Copyright (C) 2014-2019 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * 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. @@ -14,8 +14,8 @@ #include <stdint.h> #include "utils.h" -bool bc_utf8_validate(const uint8_t *str, size_t len); -bool bc_utf8_validate_str(bc_string_t *str); -size_t bc_utf8_skip_bom(const uint8_t *str, size_t len); +bool blogc_utf8_validate(const uint8_t *str, size_t len); +bool blogc_utf8_validate_str(sb_string_t *str); +size_t blogc_utf8_skip_bom(const uint8_t *str, size_t len); #endif /* _UTF_8_H */ diff --git a/src/common/utils.c b/src/utils.c index ddd3911..b42ae4e 100644 --- a/src/common/utils.c +++ b/src/utils.c @@ -1,15 +1,14 @@ /* * blogc: A blog compiler. - * Copyright (C) 2014-2020 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * Copyright (C) 2014-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br> * * This program can be distributed under the terms of the BSD License. * See the file LICENSE. */ -#define BC_STRING_CHUNK_SIZE 128 +#define SB_STRING_CHUNK_SIZE 128 #include <string.h> -#include <strings.h> #include <stdarg.h> #include <stdbool.h> #include <stdlib.h> @@ -19,7 +18,7 @@ void* -bc_malloc(size_t size) +sb_malloc(size_t size) { // simple things simple! void *rv = malloc(size); @@ -32,7 +31,7 @@ bc_malloc(size_t size) void* -bc_realloc(void *ptr, size_t size) +sb_realloc(void *ptr, size_t size) { // simple things even simpler :P void *rv = realloc(ptr, size); @@ -45,17 +44,17 @@ bc_realloc(void *ptr, size_t size) } -bc_slist_t* -bc_slist_append(bc_slist_t *l, void *data) +sb_slist_t* +sb_slist_append(sb_slist_t *l, void *data) { - bc_slist_t *node = bc_malloc(sizeof(bc_slist_t)); + sb_slist_t *node = sb_malloc(sizeof(sb_slist_t)); node->data = data; node->next = NULL; if (l == NULL) { l = node; } else { - bc_slist_t *tmp; + sb_slist_t *tmp; for (tmp = l; tmp->next != NULL; tmp = tmp->next); tmp->next = node; } @@ -63,10 +62,10 @@ bc_slist_append(bc_slist_t *l, void *data) } -bc_slist_t* -bc_slist_prepend(bc_slist_t *l, void *data) +sb_slist_t* +sb_slist_prepend(sb_slist_t *l, void *data) { - bc_slist_t *node = bc_malloc(sizeof(bc_slist_t)); + sb_slist_t *node = sb_malloc(sizeof(sb_slist_t)); node->data = data; node->next = l; l = node; @@ -74,27 +73,11 @@ bc_slist_prepend(bc_slist_t *l, void *data) } -bc_slist_t* -bc_slist_append_list(bc_slist_t *l, bc_slist_t *n) -{ - if (l == NULL) { - return n; - } - if (n == NULL) { - return l; - } - bc_slist_t *tmp; - for (tmp = l; tmp->next != NULL; tmp = tmp->next); - tmp->next = n; - return l; -} - - void -bc_slist_free_full(bc_slist_t *l, bc_free_func_t free_func) +sb_slist_free_full(sb_slist_t *l, sb_free_func_t free_func) { while (l != NULL) { - bc_slist_t *tmp = l->next; + sb_slist_t *tmp = l->next; if ((free_func != NULL) && (l->data != NULL)) free_func(l->data); free(l); @@ -104,26 +87,26 @@ bc_slist_free_full(bc_slist_t *l, bc_free_func_t free_func) void -bc_slist_free(bc_slist_t *l) +sb_slist_free(sb_slist_t *l) { - bc_slist_free_full(l, NULL); + sb_slist_free_full(l, NULL); } size_t -bc_slist_length(bc_slist_t *l) +sb_slist_length(sb_slist_t *l) { if (l == NULL) return 0; size_t i; - bc_slist_t *tmp; + sb_slist_t *tmp; for (tmp = l, i = 0; tmp != NULL; tmp = tmp->next, i++); return i; } char* -bc_strdup(const char *s) +sb_strdup(const char *s) { if (s == NULL) return NULL; @@ -137,7 +120,7 @@ bc_strdup(const char *s) char* -bc_strndup(const char *s, size_t n) +sb_strndup(const char *s, size_t n) { if (s == NULL) return NULL; @@ -152,10 +135,8 @@ bc_strndup(const char *s, size_t n) char* -bc_strdup_vprintf(const char *format, va_list ap) +sb_strdup_vprintf(const char *format, va_list ap) { - if (format == NULL) - return NULL; va_list ap2; va_copy(ap2, ap); int l = vsnprintf(NULL, 0, format, ap2); @@ -175,28 +156,18 @@ bc_strdup_vprintf(const char *format, va_list ap) char* -bc_strdup_printf(const char *format, ...) +sb_strdup_printf(const char *format, ...) { - if (format == NULL) - return NULL; va_list ap; va_start(ap, format); - char *tmp = bc_strdup_vprintf(format, ap); + char *tmp = sb_strdup_vprintf(format, ap); va_end(ap); return tmp; } -// locale-independent implementation of isspace bool -bc_isspace(int c) -{ - return c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v'; -} - - -bool -bc_str_starts_with(const char *str, const char *prefix) +sb_str_starts_with(const char *str, const char *prefix) { int str_l = strlen(str); int str_lp = strlen(prefix); @@ -207,7 +178,7 @@ bc_str_starts_with(const char *str, const char *prefix) bool -bc_str_ends_with(const char *str, const char *suffix) +sb_str_ends_with(const char *str, const char *suffix) { int str_l = strlen(str); int str_ls = strlen(suffix); @@ -218,7 +189,7 @@ bc_str_ends_with(const char *str, const char *suffix) char* -bc_str_lstrip(char *str) +sb_str_lstrip(char *str) { if (str == NULL) return NULL; @@ -242,7 +213,7 @@ bc_str_lstrip(char *str) char* -bc_str_rstrip(char *str) +sb_str_rstrip(char *str) { if (str == NULL) return NULL; @@ -266,52 +237,52 @@ bc_str_rstrip(char *str) char* -bc_str_strip(char *str) +sb_str_strip(char *str) { - return bc_str_lstrip(bc_str_rstrip(str)); + return sb_str_lstrip(sb_str_rstrip(str)); } char** -bc_str_split(const char *str, char c, size_t max_pieces) +sb_str_split(const char *str, char c, unsigned int max_pieces) { if (str == NULL) return NULL; - char **rv = bc_malloc(sizeof(char*)); - size_t i, start = 0, count = 0; + char **rv = sb_malloc(sizeof(char*)); + unsigned int i, start = 0, count = 0; for (i = 0; i < strlen(str) + 1; i++) { if (str[0] == '\0') break; if ((str[i] == c && (!max_pieces || count + 1 < max_pieces)) || str[i] == '\0') { - rv = bc_realloc(rv, (count + 1) * sizeof(char*)); - rv[count] = bc_malloc(i - start + 1); + rv = sb_realloc(rv, (count + 1) * sizeof(char*)); + rv[count] = sb_malloc(i - start + 1); memcpy(rv[count], str + start, i - start); rv[count++][i - start] = '\0'; start = i + 1; } } - rv = bc_realloc(rv, (count + 1) * sizeof(char*)); + rv = sb_realloc(rv, (count + 1) * sizeof(char*)); rv[count] = NULL; return rv; } char* -bc_str_replace(const char *str, const char search, const char *replace) +sb_str_replace(const char *str, const char search, const char *replace) { - char **pieces = bc_str_split(str, search, 0); + char **pieces = sb_str_split(str, search, 0); if (pieces == NULL) return NULL; - char* rv = bc_strv_join(pieces, replace); - bc_strv_free(pieces); + char* rv = sb_strv_join(pieces, replace); + sb_strv_free(pieces); if (rv == NULL) - return bc_strdup(str); + return sb_strdup(str); return rv; } char* -bc_str_find(const char *str, char c) +sb_str_find(const char *str, char c) { // this is somewhat similar to strchr, but respects '\' escaping. if (str == NULL) @@ -331,33 +302,8 @@ bc_str_find(const char *str, char c) } -bool -bc_str_to_bool(const char *str) -{ - if (str == NULL) - return false; - - if (0 == strcmp(str, "1")) - return true; - - if (0 == strcasecmp(str, "y")) - return true; - - if (0 == strcasecmp(str, "yes")) - return true; - - if (0 == strcasecmp(str, "true")) - return true; - - if (0 == strcasecmp(str, "on")) - return true; - - return false; -} - - void -bc_strv_free(char **strv) +sb_strv_free(char **strv) { if (strv == NULL) return; @@ -368,22 +314,22 @@ bc_strv_free(char **strv) char* -bc_strv_join(char **strv, const char *separator) +sb_strv_join(char **strv, const char *separator) { if (strv == NULL || separator == NULL) return NULL; - bc_string_t *str = bc_string_new(); + sb_string_t *str = sb_string_new(); for (size_t i = 0; strv[i] != NULL; i++) { - str = bc_string_append(str, strv[i]); + str = sb_string_append(str, strv[i]); if (strv[i + 1] != NULL) - str = bc_string_append(str, separator); + str = sb_string_append(str, separator); } - return bc_string_free(str, false); + return sb_string_free(str, false); } size_t -bc_strv_length(char **strv) +sb_strv_length(char **strv) { if (strv == NULL) return 0; @@ -393,23 +339,23 @@ bc_strv_length(char **strv) } -bc_string_t* -bc_string_new(void) +sb_string_t* +sb_string_new(void) { - bc_string_t* rv = bc_malloc(sizeof(bc_string_t)); + sb_string_t* rv = sb_malloc(sizeof(sb_string_t)); rv->str = NULL; rv->len = 0; rv->allocated_len = 0; // initialize with empty string - rv = bc_string_append(rv, ""); + rv = sb_string_append(rv, ""); return rv; } char* -bc_string_free(bc_string_t *str, bool free_str) +sb_string_free(sb_string_t *str, bool free_str) { if (str == NULL) return NULL; @@ -423,18 +369,18 @@ bc_string_free(bc_string_t *str, bool free_str) } -bc_string_t* -bc_string_dup(bc_string_t *str) +sb_string_t* +sb_string_dup(sb_string_t *str) { if (str == NULL) return NULL; - bc_string_t* new = bc_string_new(); - return bc_string_append_len(new, str->str, str->len); + sb_string_t* new = sb_string_new(); + return sb_string_append_len(new, str->str, str->len); } -bc_string_t* -bc_string_append_len(bc_string_t *str, const char *suffix, size_t len) +sb_string_t* +sb_string_append_len(sb_string_t *str, const char *suffix, size_t len) { if (str == NULL) return NULL; @@ -443,8 +389,8 @@ bc_string_append_len(bc_string_t *str, const char *suffix, size_t len) size_t old_len = str->len; str->len += len; if (str->len + 1 > str->allocated_len) { - str->allocated_len = (((str->len + 1) / BC_STRING_CHUNK_SIZE) + 1) * BC_STRING_CHUNK_SIZE; - str->str = bc_realloc(str->str, str->allocated_len); + str->allocated_len = (((str->len + 1) / SB_STRING_CHUNK_SIZE) + 1) * SB_STRING_CHUNK_SIZE; + str->str = sb_realloc(str->str, str->allocated_len); } memcpy(str->str + old_len, suffix, len); str->str[str->len] = '\0'; @@ -452,26 +398,26 @@ bc_string_append_len(bc_string_t *str, const char *suffix, size_t len) } -bc_string_t* -bc_string_append(bc_string_t *str, const char *suffix) +sb_string_t* +sb_string_append(sb_string_t *str, const char *suffix) { if (str == NULL) return NULL; const char *my_suffix = suffix == NULL ? "" : suffix; - return bc_string_append_len(str, my_suffix, strlen(my_suffix)); + return sb_string_append_len(str, my_suffix, strlen(my_suffix)); } -bc_string_t* -bc_string_append_c(bc_string_t *str, char c) +sb_string_t* +sb_string_append_c(sb_string_t *str, char c) { if (str == NULL) return NULL; size_t old_len = str->len; str->len += 1; if (str->len + 1 > str->allocated_len) { - str->allocated_len = (((str->len + 1) / BC_STRING_CHUNK_SIZE) + 1) * BC_STRING_CHUNK_SIZE; - str->str = bc_realloc(str->str, str->allocated_len); + str->allocated_len = (((str->len + 1) / SB_STRING_CHUNK_SIZE) + 1) * SB_STRING_CHUNK_SIZE; + str->str = sb_realloc(str->str, str->allocated_len); } str->str[old_len] = c; str->str[str->len] = '\0'; @@ -479,25 +425,23 @@ bc_string_append_c(bc_string_t *str, char c) } -bc_string_t* -bc_string_append_printf(bc_string_t *str, const char *format, ...) +sb_string_t* +sb_string_append_printf(sb_string_t *str, const char *format, ...) { if (str == NULL) return NULL; - if (format == NULL) - return str; va_list ap; va_start(ap, format); - char *tmp = bc_strdup_vprintf(format, ap); + char *tmp = sb_strdup_vprintf(format, ap); va_end(ap); - str = bc_string_append(str, tmp); + str = sb_string_append(str, tmp); free(tmp); return str; } -bc_string_t* -bc_string_append_escaped(bc_string_t *str, const char *suffix) +sb_string_t* +sb_string_append_escaped(sb_string_t *str, const char *suffix) { if (str == NULL) return NULL; @@ -510,16 +454,16 @@ bc_string_append_escaped(bc_string_t *str, const char *suffix) continue; } escaped = false; - str = bc_string_append_c(str, suffix[i]); + str = sb_string_append_c(str, suffix[i]); } return str; } -bc_trie_t* -bc_trie_new(bc_free_func_t free_func) +sb_trie_t* +sb_trie_new(sb_free_func_t free_func) { - bc_trie_t *trie = bc_malloc(sizeof(bc_trie_t)); + sb_trie_t *trie = sb_malloc(sizeof(sb_trie_t)); trie->root = NULL; trie->free_func = free_func; return trie; @@ -527,43 +471,43 @@ bc_trie_new(bc_free_func_t free_func) static void -bc_trie_free_node(bc_trie_t *trie, bc_trie_node_t *node) +sb_trie_free_node(sb_trie_t *trie, sb_trie_node_t *node) { if (trie == NULL || node == NULL) return; if (node->data != NULL && trie->free_func != NULL) trie->free_func(node->data); - bc_trie_free_node(trie, node->next); - bc_trie_free_node(trie, node->child); + sb_trie_free_node(trie, node->next); + sb_trie_free_node(trie, node->child); free(node); } void -bc_trie_free(bc_trie_t *trie) +sb_trie_free(sb_trie_t *trie) { if (trie == NULL) return; - bc_trie_free_node(trie, trie->root); + sb_trie_free_node(trie, trie->root); free(trie); } void -bc_trie_insert(bc_trie_t *trie, const char *key, void *data) +sb_trie_insert(sb_trie_t *trie, const char *key, void *data) { if (trie == NULL || key == NULL || data == NULL) return; - bc_trie_node_t *parent = NULL; - bc_trie_node_t *previous; - bc_trie_node_t *current; - bc_trie_node_t *tmp; + sb_trie_node_t *parent = NULL; + sb_trie_node_t *previous; + sb_trie_node_t *current; + sb_trie_node_t *tmp; while (1) { if (trie->root == NULL || (parent != NULL && parent->child == NULL)) { - current = bc_malloc(sizeof(bc_trie_node_t)); + current = sb_malloc(sizeof(sb_trie_node_t)); current->key = *key; current->data = NULL; current->next = NULL; @@ -589,7 +533,7 @@ bc_trie_insert(bc_trie_t *trie, const char *key, void *data) if (previous == NULL || parent != NULL) goto clean; - current = bc_malloc(sizeof(bc_trie_node_t)); + current = sb_malloc(sizeof(sb_trie_node_t)); current->key = *key; current->data = NULL; current->next = NULL; @@ -610,13 +554,13 @@ clean: void* -bc_trie_lookup(bc_trie_t *trie, const char *key) +sb_trie_lookup(sb_trie_t *trie, const char *key) { if (trie == NULL || trie->root == NULL || key == NULL) return NULL; - bc_trie_node_t *parent = trie->root; - bc_trie_node_t *tmp; + sb_trie_node_t *parent = trie->root; + sb_trie_node_t *tmp; while (1) { for (tmp = parent; tmp != NULL; tmp = tmp->next) { @@ -639,7 +583,7 @@ bc_trie_lookup(bc_trie_t *trie, const char *key) static void -bc_trie_size_node(bc_trie_node_t *node, size_t *count) +sb_trie_size_node(sb_trie_node_t *node, size_t *count) { if (node == NULL || count == NULL) return; @@ -647,77 +591,55 @@ bc_trie_size_node(bc_trie_node_t *node, size_t *count) if (node->key == '\0') (*count)++; - bc_trie_size_node(node->next, count); - bc_trie_size_node(node->child, count); + sb_trie_size_node(node->next, count); + sb_trie_size_node(node->child, count); } size_t -bc_trie_size(bc_trie_t *trie) +sb_trie_size(sb_trie_t *trie) { if (trie == NULL) return 0; size_t count = 0; - bc_trie_size_node(trie->root, &count); + sb_trie_size_node(trie->root, &count); return count; } static void -bc_trie_foreach_node(bc_trie_node_t *node, bc_string_t *str, - bc_trie_foreach_func_t func, void *user_data) +sb_trie_foreach_node(sb_trie_node_t *node, sb_string_t *str, + sb_trie_foreach_func_t func, void *user_data) { if (node == NULL || str == NULL || func == NULL) return; - if (node->key == '\0') + if (node->key == '\0') { func(str->str, node->data, user_data); + return; + } if (node->child != NULL) { - bc_string_t *child = bc_string_dup(str); - child = bc_string_append_c(child, node->key); - bc_trie_foreach_node(node->child, child, func, user_data); - bc_string_free(child, true); + sb_string_t *child = sb_string_dup(str); + child = sb_string_append_c(child, node->key); + sb_trie_foreach_node(node->child, child, func, user_data); + sb_string_free(child, true); } if (node->next != NULL) - bc_trie_foreach_node(node->next, str, func, user_data); + sb_trie_foreach_node(node->next, str, func, user_data); } void -bc_trie_foreach(bc_trie_t *trie, bc_trie_foreach_func_t func, +sb_trie_foreach(sb_trie_t *trie, sb_trie_foreach_func_t func, void *user_data) { if (trie == NULL || trie->root == NULL || func == NULL) return; - bc_string_t *str = bc_string_new(); - bc_trie_foreach_node(trie->root, str, func, user_data); - bc_string_free(str, true); -} - - -char* -bc_shell_quote(const char *command) -{ - bc_string_t *rv = bc_string_new(); - bc_string_append_c(rv, '\''); - if (command != NULL) { - for (size_t i = 0; i < strlen(command); i++) { - switch (command[i]) { - case '!': - bc_string_append(rv, "'\\!'"); - break; - case '\'': - bc_string_append(rv, "'\\''"); - break; - default: - bc_string_append_c(rv, command[i]); - } - } - } - bc_string_append_c(rv, '\''); - return bc_string_free(rv, false); + sb_string_t *str = sb_string_new(); + sb_trie_foreach_node(trie->root, str, func, user_data); + sb_string_free(str, true); } diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..aca02c0 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,102 @@ +/* + * blogc: A blog compiler. + * Copyright (C) 2014-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 _UTILS_H +#define _UTILS_H + +#include <stddef.h> +#include <stdarg.h> +#include <stdbool.h> + + +// memory + +typedef void (*sb_free_func_t) (void *ptr); + +void* sb_malloc(size_t size); +void* sb_realloc(void *ptr, size_t size); + + +// slist + +typedef struct _sb_slist_t { + struct _sb_slist_t *next; + void *data; +} sb_slist_t; + +sb_slist_t* sb_slist_append(sb_slist_t *l, void *data); +sb_slist_t* sb_slist_prepend(sb_slist_t *l, void *data); +void sb_slist_free(sb_slist_t *l); +void sb_slist_free_full(sb_slist_t *l, sb_free_func_t free_func); +size_t sb_slist_length(sb_slist_t *l); + + +// strfuncs + +char* sb_strdup(const char *s); +char* sb_strndup(const char *s, size_t n); +char* sb_strdup_vprintf(const char *format, va_list ap); +char* sb_strdup_printf(const char *format, ...); +bool sb_str_starts_with(const char *str, const char *prefix); +bool sb_str_ends_with(const char *str, const char *suffix); +char* sb_str_lstrip(char *str); +char* sb_str_rstrip(char *str); +char* sb_str_strip(char *str); +char** sb_str_split(const char *str, char c, unsigned int max_pieces); +char* sb_str_replace(const char *str, const char search, const char *replace); +char* sb_str_find(const char *str, char c); +void sb_strv_free(char **strv); +char* sb_strv_join(char **strv, const char *separator); +size_t sb_strv_length(char **strv); + + +// string + +typedef struct { + char *str; + size_t len; + size_t allocated_len; +} sb_string_t; + +sb_string_t* sb_string_new(void); +char* sb_string_free(sb_string_t *str, bool free_str); +sb_string_t* sb_string_dup(sb_string_t *str); +sb_string_t* sb_string_append_len(sb_string_t *str, const char *suffix, size_t len); +sb_string_t* sb_string_append(sb_string_t *str, const char *suffix); +sb_string_t* sb_string_append_c(sb_string_t *str, char c); +sb_string_t* sb_string_append_printf(sb_string_t *str, const char *format, ...); +sb_string_t* sb_string_append_escaped(sb_string_t *str, const char *suffix); + + +// trie + +typedef struct _sb_trie_node_t { + char key; + void *data; + struct _sb_trie_node_t *next, *child; +} sb_trie_node_t; + +struct _sb_trie_t { + sb_trie_node_t *root; + sb_free_func_t free_func; +}; + +typedef struct _sb_trie_t sb_trie_t; + +typedef void (*sb_trie_foreach_func_t)(const char *key, void *data, + void *user_data); + +sb_trie_t* sb_trie_new(sb_free_func_t free_func); +void sb_trie_free(sb_trie_t *trie); +void sb_trie_insert(sb_trie_t *trie, const char *key, void *data); +void* sb_trie_lookup(sb_trie_t *trie, const char *key); +size_t sb_trie_size(sb_trie_t *trie); +void sb_trie_foreach(sb_trie_t *trie, sb_trie_foreach_func_t func, + void *user_data); + +#endif /* _UTILS_H */ |