aboutsummaryrefslogtreecommitdiffstats
path: root/src/blogc/renderer.c
diff options
context:
space:
mode:
authorRafael G. Martins <rafael@rafaelmartins.eng.br>2016-09-03 19:57:54 +0200
committerRafael G. Martins <rafael@rafaelmartins.eng.br>2016-09-03 20:28:55 +0200
commit74ca21a41bcb5a49d19e65c9ba88f1f864cb7095 (patch)
tree4774587e47abc0ff20453abbf714b63c36697f26 /src/blogc/renderer.c
parent30ae5fd4f65f48009e6956e42ccc2c9d9ad80901 (diff)
downloadblogc-74ca21a41bcb5a49d19e65c9ba88f1f864cb7095.tar.gz
blogc-74ca21a41bcb5a49d19e65c9ba88f1f864cb7095.tar.bz2
blogc-74ca21a41bcb5a49d19e65c9ba88f1f864cb7095.zip
*: big code reorganization.
- source and tests are now splitted by target - utils lib is now called common still pending move error.c from blogc to common
Diffstat (limited to 'src/blogc/renderer.c')
-rw-r--r--src/blogc/renderer.c457
1 files changed, 457 insertions, 0 deletions
diff --git a/src/blogc/renderer.c b/src/blogc/renderer.c
new file mode 100644
index 0000000..97a226e
--- /dev/null
+++ b/src/blogc/renderer.c
@@ -0,0 +1,457 @@
+/*
+ * 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 <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "datetime-parser.h"
+#include "error.h"
+#include "template-parser.h"
+#include "renderer.h"
+#include "../common/utils.h"
+
+
+const char*
+blogc_get_variable(const char *name, sb_trie_t *global, sb_trie_t *local)
+{
+ const char *rv = NULL;
+ if (local != NULL) {
+ rv = sb_trie_lookup(local, name);
+ if (rv != NULL)
+ return rv;
+ }
+ if (global != NULL)
+ rv = sb_trie_lookup(global, name);
+ return rv;
+}
+
+
+char*
+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 sb_strdup(date);
+
+ blogc_error_t *err = NULL;
+ char *rv = blogc_convert_datetime(date, date_format, &err);
+ if (err != NULL) {
+ blogc_error_print(err);
+ blogc_error_free(err);
+ return sb_strdup(date);
+ }
+ return rv;
+}
+
+
+char*
+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 sb_strdup(value);
+
+ // do the same for special variable 'FOREACH_ITEM'
+ if (0 == strcmp(name, "FOREACH_ITEM")) {
+ if (foreach_var != NULL && foreach_var->data != NULL) {
+ return sb_strdup(foreach_var->data);
+ }
+ return NULL;
+ }
+
+ char *var = sb_strdup(name);
+
+ size_t i;
+ size_t last = strlen(var);
+
+ long int len = -1;
+
+ // just walk till the last '_'
+ for (i = last - 1; i > 0 && var[i] >= '0' && var[i] <= '9'; i--);
+
+ if (var[i] == '_' && (i + 1) < last) { // var ends with '_[0-9]+'
+ // 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 {
+ var[i] = '\0';
+ }
+ }
+
+ bool must_format = false;
+
+ 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))
+ value = foreach_var->data;
+ else
+ value = blogc_get_variable(var, global, local);
+
+ if (value == NULL) {
+ free(var);
+ return NULL;
+ }
+
+ char *rv = NULL;
+
+ if (must_format) {
+ 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 = sb_strdup(value);
+ }
+ }
+ else {
+ rv = sb_strdup(value);
+ }
+
+ free(var);
+
+ if (len > 0) {
+ char *tmp = sb_strndup(rv, len);
+ free(rv);
+ rv = tmp;
+ }
+
+ return rv;
+}
+
+
+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;
+
+ sb_slist_t *rv = NULL;
+
+ 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 = sb_slist_append(rv, tmp[i]);
+ else
+ free(tmp[i]);
+ }
+ free(tmp);
+
+ return rv;
+}
+
+
+char*
+blogc_render(sb_slist_t *tmpl, sb_slist_t *sources, sb_trie_t *config, bool listing)
+{
+ if (tmpl == NULL)
+ return NULL;
+
+ sb_slist_t *current_source = NULL;
+ sb_slist_t *listing_start = NULL;
+
+ sb_string_t *str = sb_string_new();
+
+ sb_trie_t *tmp_source = NULL;
+ char *config_value = NULL;
+ char *defined = NULL;
+
+ unsigned int if_count = 0;
+
+ 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;
+ bool evaluate = false;
+ bool valid_else = false;
+
+ int cmp = 0;
+
+ sb_slist_t *tmp = tmpl;
+ while (tmp != NULL) {
+ blogc_template_stmt_t *stmt = tmp->data;
+
+ switch (stmt->type) {
+
+ case BLOGC_TEMPLATE_CONTENT_STMT:
+ if (stmt->value != NULL)
+ sb_string_append(str, stmt->value);
+ break;
+
+ case BLOGC_TEMPLATE_BLOCK_STMT:
+ inside_block = true;
+ if_count = 0;
+ if (0 == strcmp("entry", stmt->value)) {
+ if (listing) {
+
+ // we can just skip anything and walk until the next
+ // 'endblock'
+ while (stmt->type != BLOGC_TEMPLATE_ENDBLOCK_STMT) {
+ tmp = tmp->next;
+ stmt = tmp->data;
+ }
+ break;
+ }
+ current_source = sources;
+ tmp_source = current_source->data;
+ }
+ 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 (stmt->type != BLOGC_TEMPLATE_ENDBLOCK_STMT) {
+ tmp = tmp->next;
+ stmt = tmp->data;
+ }
+ break;
+ }
+ }
+ if (0 == strcmp("listing", stmt->value)) {
+ if (sources == NULL) {
+
+ // we can just skip anything and walk until the next
+ // 'endblock'
+ while (stmt->type != BLOGC_TEMPLATE_ENDBLOCK_STMT) {
+ tmp = tmp->next;
+ stmt = tmp->data;
+ }
+ break;
+ }
+ if (current_source == NULL) {
+ listing_start = tmp;
+ current_source = sources;
+ }
+ tmp_source = current_source->data;
+ }
+ break;
+
+ 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) {
+ sb_string_append(str, config_value);
+ free(config_value);
+ config_value = NULL;
+ break;
+ }
+ }
+ break;
+
+ case BLOGC_TEMPLATE_ENDBLOCK_STMT:
+ inside_block = false;
+ if (listing_start != NULL && current_source != NULL) {
+ current_source = current_source->next;
+ if (current_source != NULL) {
+ tmp = listing_start;
+ continue;
+ }
+ else
+ listing_start = NULL;
+ }
+ break;
+
+ case BLOGC_TEMPLATE_IFNDEF_STMT:
+ if_not = true;
+
+ case BLOGC_TEMPLATE_IF_STMT:
+ case BLOGC_TEMPLATE_IFDEF_STMT:
+ if_count = 0;
+ defined = NULL;
+ if (stmt->value != NULL)
+ defined = blogc_format_variable(stmt->value, config,
+ inside_block ? tmp_source : NULL, foreach_var);
+ evaluate = false;
+ 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 (stmt->value2 != NULL) {
+ if ((strlen(stmt->value2) >= 2) &&
+ (stmt->value2[0] == '"') &&
+ (stmt->value2[strlen(stmt->value2) - 1] == '"'))
+ {
+ defined2 = sb_strndup(stmt->value2 + 1,
+ strlen(stmt->value2) - 2);
+ }
+ else {
+ defined2 = blogc_format_variable(stmt->value2,
+ config, inside_block ? tmp_source : NULL,
+ foreach_var);
+ }
+ }
+
+ if (defined != NULL && defined2 != NULL) {
+ cmp = strcmp(defined, defined2);
+ if (cmp != 0 && stmt->op & BLOGC_TEMPLATE_OP_NEQ)
+ evaluate = true;
+ else if (cmp == 0 && stmt->op & BLOGC_TEMPLATE_OP_EQ)
+ evaluate = true;
+ else if (cmp < 0 && stmt->op & BLOGC_TEMPLATE_OP_LT)
+ evaluate = true;
+ else if (cmp > 0 && stmt->op & BLOGC_TEMPLATE_OP_GT)
+ evaluate = true;
+ }
+
+ free(defined2);
+ }
+ else {
+ if (if_not && defined == NULL)
+ evaluate = true;
+ if (!if_not && defined != NULL)
+ evaluate = true;
+ }
+ if (!evaluate) {
+
+ // at this point we can just skip anything, counting the
+ // number of 'if's, to know how many 'endif's we need to
+ // skip as well.
+ while (1) {
+ tmp = tmp->next;
+ 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 ((stmt->type == BLOGC_TEMPLATE_ELSE_STMT) &&
+ (if_count == 0))
+ {
+ // this is somewhat complex. only an else statement
+ // right after a non evaluated block should be considered
+ // valid, because all the inner conditionals were just
+ // skipped, and all the outter conditionals evaluated
+ // to true.
+ valid_else = true;
+ break;
+ }
+ if (stmt->type == BLOGC_TEMPLATE_ENDIF_STMT) {
+ if (if_count > 0) {
+ if_count--;
+ continue;
+ }
+ break;
+ }
+ }
+ }
+ free(defined);
+ defined = NULL;
+ if_not = false;
+ break;
+
+ case BLOGC_TEMPLATE_ELSE_STMT:
+ if_count = 0;
+ if (!valid_else) {
+
+ // at this point we can just skip anything, counting the
+ // number of 'if's, to know how many 'endif's we need to
+ // skip as well.
+ while (1) {
+ tmp = tmp->next;
+ 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 (stmt->type == BLOGC_TEMPLATE_ENDIF_STMT) {
+ if (if_count > 0) {
+ if_count--;
+ continue;
+ }
+ break;
+ }
+ }
+ }
+ valid_else = false;
+ break;
+
+ case BLOGC_TEMPLATE_ENDIF_STMT:
+ // any endif statement should invalidate valid_else, to avoid
+ // propagation to outter conditionals.
+ valid_else = false;
+ if (if_count > 0)
+ if_count--;
+ break;
+
+ case BLOGC_TEMPLATE_FOREACH_STMT:
+ if (foreach_var_start == NULL) {
+ 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_var = foreach_var_start;
+ foreach_start = tmp;
+ }
+ else {
+
+ // we can just skip anything and walk until the next
+ // 'endforeach'
+ while (stmt->type != BLOGC_TEMPLATE_ENDFOREACH_STMT) {
+ tmp = tmp->next;
+ stmt = tmp->data;
+ }
+ break;
+ }
+ }
+
+ if (foreach_var == NULL) {
+ foreach_start = tmp;
+ foreach_var = foreach_var_start;
+ }
+ break;
+
+ case BLOGC_TEMPLATE_ENDFOREACH_STMT:
+ if (foreach_start != NULL && foreach_var != NULL) {
+ foreach_var = foreach_var->next;
+ if (foreach_var != NULL) {
+ tmp = foreach_start;
+ continue;
+ }
+ }
+ foreach_start = NULL;
+ sb_slist_free_full(foreach_var_start, free);
+ foreach_var_start = NULL;
+ break;
+ }
+ tmp = tmp->next;
+ }
+
+ // no need to free temporary variables here. the template parser makes sure
+ // that templates are sane and statements are closed.
+
+ return sb_string_free(str, false);
+}