aboutsummaryrefslogtreecommitdiff
path: root/src/template.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/template.c')
-rw-r--r--src/template.c371
1 files changed, 371 insertions, 0 deletions
diff --git a/src/template.c b/src/template.c
index 20cf10b..bd88de0 100644
--- a/src/template.c
+++ b/src/template.c
@@ -526,6 +526,198 @@ static char *parse_indexed_tag(const char *tag_content, int *index_val) {
return key_name;
}
+typedef enum {
+ CONDITION_NONE,
+ CONDITION_TRUTHY,
+ CONDITION_EXISTS,
+ CONDITION_EQUAL,
+ CONDITION_NOT_EQUAL,
+} ConditionType;
+
+typedef struct {
+ ConditionType type;
+ char var_name[MAX_KEY_LEN];
+ char compare_value[MAX_VALUE_LEN];
+ bool negate;
+} Condition;
+
+static bool evaluate_condition(const Condition *cond, TemplateContext *ctx) {
+ if (cond == NULL || cond->type == CONDITION_NONE) {
+ return false;
+ }
+
+ ContextVar *var = find_context_var(ctx, cond->var_name);
+ const char *var_value = NULL;
+
+ if (var == NULL) {
+ if (cond->type == CONDITION_TRUTHY) {
+ return false;
+ }
+ var_value = "";
+ } else if (var->type == CONTEXT_TYPE_STRING) {
+ var_value = var->value.string_val;
+ } else {
+ if (cond->type == CONDITION_TRUTHY) {
+ return false;
+ }
+ var_value = "";
+ }
+
+ switch (cond->type) {
+ case CONDITION_TRUTHY:
+ return (var_value != NULL && strlen(var_value) > 0);
+
+ case CONDITION_EXISTS: {
+ bool exists = (var != NULL);
+ return cond->negate ? !exists : exists;
+ }
+
+ case CONDITION_EQUAL:
+ return (strcmp(var_value, cond->compare_value) == 0);
+
+ case CONDITION_NOT_EQUAL:
+ return (strcmp(var_value, cond->compare_value) != 0);
+
+ default:
+ return false;
+ }
+}
+
+static Condition parse_condition(const char *condition_str) {
+ Condition cond;
+ cond.type = CONDITION_NONE;
+ cond.var_name[0] = '\0';
+ cond.compare_value[0] = '\0';
+ cond.negate = false;
+
+ if (condition_str == NULL || *condition_str == '\0') {
+ return cond;
+ }
+
+ const char *eq_pos = strstr(condition_str, "==");
+ const char *neq_pos = strstr(condition_str, "!=");
+
+ if (eq_pos != NULL && neq_pos != NULL) {
+ if (eq_pos < neq_pos) {
+ neq_pos = NULL;
+ } else {
+ eq_pos = NULL;
+ }
+ }
+
+ if (eq_pos != NULL) {
+ size_t var_len = eq_pos - condition_str;
+ if (var_len >= MAX_KEY_LEN) {
+ var_len = MAX_KEY_LEN - 1;
+ }
+ strncpy(cond.var_name, condition_str, var_len);
+ cond.var_name[var_len] = '\0';
+
+ const char *value_start = eq_pos + 2;
+ while (*value_start == ' ' || *value_start == '\t') {
+ value_start++;
+ }
+
+ if (*value_start == '"') {
+ value_start++;
+ const char *value_end = strchr(value_start, '"');
+ if (value_end != NULL) {
+ size_t val_len = value_end - value_start;
+ if (val_len >= MAX_VALUE_LEN) {
+ val_len = MAX_VALUE_LEN - 1;
+ }
+ strncpy(cond.compare_value, value_start, val_len);
+ cond.compare_value[val_len] = '\0';
+ }
+ } else {
+ size_t val_len = strlen(value_start);
+ if (val_len >= MAX_VALUE_LEN) {
+ val_len = MAX_VALUE_LEN - 1;
+ }
+ strncpy(cond.compare_value, value_start, val_len);
+ cond.compare_value[val_len] = '\0';
+ }
+
+ cond.type = CONDITION_EQUAL;
+ } else if (neq_pos != NULL) {
+ size_t var_len = neq_pos - condition_str;
+ if (var_len >= MAX_KEY_LEN) {
+ var_len = MAX_KEY_LEN - 1;
+ }
+ strncpy(cond.var_name, condition_str, var_len);
+ cond.var_name[var_len] = '\0';
+
+ const char *value_start = neq_pos + 2;
+ while (*value_start == ' ' || *value_start == '\t') {
+ value_start++;
+ }
+
+ if (*value_start == '"') {
+ value_start++;
+ const char *value_end = strchr(value_start, '"');
+ if (value_end != NULL) {
+ size_t val_len = value_end - value_start;
+ if (val_len >= MAX_VALUE_LEN) {
+ val_len = MAX_VALUE_LEN - 1;
+ }
+ strncpy(cond.compare_value, value_start, val_len);
+ cond.compare_value[val_len] = '\0';
+ }
+ } else {
+ size_t val_len = strlen(value_start);
+ if (val_len >= MAX_VALUE_LEN) {
+ val_len = MAX_VALUE_LEN - 1;
+ }
+ strncpy(cond.compare_value, value_start, val_len);
+ cond.compare_value[val_len] = '\0';
+ }
+
+ cond.type = CONDITION_NOT_EQUAL;
+ } else if (strncmp(condition_str, "exists ", 7) == 0) {
+ const char *var_start = condition_str + 7;
+ while (*var_start == ' ' || *var_start == '\t') {
+ var_start++;
+ }
+ size_t var_len = strlen(var_start);
+ if (var_len >= MAX_KEY_LEN) {
+ var_len = MAX_KEY_LEN - 1;
+ }
+ strncpy(cond.var_name, var_start, var_len);
+ cond.var_name[var_len] = '\0';
+ cond.type = CONDITION_EXISTS;
+ } else if (strncmp(condition_str, "not exists ", 11) == 0) {
+ const char *var_start = condition_str + 11;
+ while (*var_start == ' ' || *var_start == '\t') {
+ var_start++;
+ }
+ size_t var_len = strlen(var_start);
+ if (var_len >= MAX_KEY_LEN) {
+ var_len = MAX_KEY_LEN - 1;
+ }
+ strncpy(cond.var_name, var_start, var_len);
+ cond.var_name[var_len] = '\0';
+ cond.type = CONDITION_EXISTS;
+ cond.negate = true;
+ } else {
+ size_t var_len = strlen(condition_str);
+ if (var_len >= MAX_KEY_LEN) {
+ var_len = MAX_KEY_LEN - 1;
+ }
+ strncpy(cond.var_name, condition_str, var_len);
+ cond.var_name[var_len] = '\0';
+ cond.type = CONDITION_TRUTHY;
+ }
+
+ char *p = cond.var_name;
+ char *end = cond.var_name + strlen(cond.var_name) - 1;
+ while (p < end && (*end == ' ' || *end == '\t')) {
+ *end = '\0';
+ end--;
+ }
+
+ return cond;
+}
+
static char *render_template_segment(const char *template_segment,
TemplateContext *ctx) {
size_t initial_max_len = BUFFER_SIZE;
@@ -738,6 +930,185 @@ static char *render_template_segment(const char *template_segment,
"matching '{{for}}'. Appending endfor tag as-is.\n");
}
+ else if (strncmp(trimmed_tag_content, "if ", 3) == 0) {
+ const char *condition_str = trimmed_tag_content + 3;
+ Condition cond = parse_condition(condition_str);
+
+ const char *else_tag = NULL;
+ const char *elif_tag = NULL;
+ const char *endif_tag = NULL;
+
+ int if_depth = 1;
+ const char *search_pos = end_tag + 2;
+ while (*search_pos != '\0' && if_depth > 0) {
+ const char *next_if = strstr(search_pos, "{{if ");
+ const char *next_else = strstr(search_pos, "{{else}}");
+ const char *next_elif = strstr(search_pos, "{{elif ");
+ const char *next_endif = strstr(search_pos, "{{endif}}");
+
+ const char *candidates[4] = {next_if, next_else, next_elif, next_endif};
+ const char *earliest = NULL;
+ int earliest_idx = -1;
+
+ for (int i = 0; i < 4; i++) {
+ if (candidates[i] != NULL) {
+ if (earliest == NULL || candidates[i] < earliest) {
+ earliest = candidates[i];
+ earliest_idx = i;
+ }
+ }
+ }
+
+ if (earliest == NULL) {
+ break;
+ }
+
+ if (earliest_idx == 0) {
+ if_depth++;
+ search_pos = earliest + strlen("{{if ");
+ continue;
+ } else if (earliest_idx == 1) {
+ else_tag = earliest;
+ search_pos = earliest + strlen("{{else}}");
+ continue;
+ } else if (earliest_idx == 2) {
+ if (elif_tag == NULL) {
+ elif_tag = earliest;
+ }
+ search_pos = earliest + strlen("{{elif ");
+ const char *elif_end = strstr(search_pos, "}}");
+ if (elif_end != NULL) {
+ search_pos = elif_end + 2;
+ }
+ continue;
+ } else {
+ if_depth--;
+ if (if_depth == 0) {
+ endif_tag = earliest;
+ }
+ search_pos = earliest + strlen("{{endif}}");
+ continue;
+ }
+ }
+
+ if (endif_tag == NULL) {
+ fprintf(stderr,
+ "[ERROR] render_template_segment: Unclosed '{{if}}' tag. "
+ "Skipping if block.\n");
+ current_pos = end_tag + 2;
+ free(tag_content_raw);
+ continue;
+ }
+
+ if (endif_tag == NULL) {
+ fprintf(stderr,
+ "[ERROR] render_template_segment: Unclosed '{{if}}' tag. "
+ "Skipping if block.\n");
+ current_pos = end_tag + 2;
+ free(tag_content_raw);
+ continue;
+ }
+
+ bool condition_met = evaluate_condition(&cond, ctx);
+
+ const char *block_start = end_tag + 2;
+ const char *block_end = NULL;
+
+ if (condition_met) {
+ if (elif_tag != NULL && elif_tag < endif_tag) {
+ block_end = elif_tag;
+ } else if (else_tag != NULL) {
+ block_end = else_tag;
+ } else {
+ block_end = endif_tag;
+ }
+ } else {
+ bool elif_matched = false;
+ if (elif_tag != NULL && elif_tag < endif_tag) {
+ const char *test_elif = elif_tag;
+ while (test_elif != NULL && test_elif < endif_tag) {
+ const char *elif_cond_start = test_elif + strlen("{{elif ");
+ const char *elif_cond_end = strstr(elif_cond_start, "}}");
+ if (elif_cond_end == NULL || elif_cond_end >= endif_tag) {
+ break;
+ }
+
+ size_t elif_cond_len = elif_cond_end - elif_cond_start;
+ char *elif_cond_str = (char *)malloc(elif_cond_len + 1);
+ if (elif_cond_str != NULL) {
+ strncpy(elif_cond_str, elif_cond_start, elif_cond_len);
+ elif_cond_str[elif_cond_len] = '\0';
+
+ Condition elif_cond = parse_condition(elif_cond_str);
+ if (evaluate_condition(&elif_cond, ctx)) {
+ block_start = test_elif + strlen("{{elif ");
+ const char *elif_end = strstr(block_start, "}}");
+ if (elif_end != NULL) {
+ block_start = elif_end + 2;
+ }
+ block_end = else_tag ? else_tag : endif_tag;
+ elif_matched = true;
+ free(elif_cond_str);
+ break;
+ }
+ free(elif_cond_str);
+ }
+
+ test_elif = strstr(test_elif + strlen("{{elif "), "{{elif ");
+ if (test_elif == NULL || test_elif >= endif_tag) {
+ break;
+ }
+ }
+ }
+
+ if (!elif_matched) {
+ if (else_tag && else_tag < endif_tag) {
+ block_start = else_tag + strlen("{{else}}");
+ block_end = endif_tag;
+ } else {
+ block_end = endif_tag;
+ }
+ }
+ }
+
+ size_t block_len = block_end - block_start;
+ if (block_len > 0) {
+ char *block_template = (char *)malloc(block_len + 1);
+ if (block_template != NULL) {
+ strncpy(block_template, block_start, block_len);
+ block_template[block_len] = '\0';
+
+ char *rendered_block = render_template_segment(block_template, ctx);
+ if (rendered_block != NULL) {
+ append_to_buffer(&rendered_buffer, &current_len, &max_len,
+ rendered_block);
+ free(rendered_block);
+ }
+ free(block_template);
+ }
+ }
+
+ current_pos = endif_tag + strlen("{{endif}}");
+ free(tag_content_raw);
+ continue;
+ }
+
+ else if (strcmp(trimmed_tag_content, "elif ") == 0 ||
+ strcmp(trimmed_tag_content, "elif") == 0) {
+ fprintf(stderr, "[WARNING] render_template_segment: '{{elif}}' without "
+ "matching '{{if}}'. Appending elif tag as-is.\n");
+ }
+
+ else if (strcmp(trimmed_tag_content, "else") == 0) {
+ fprintf(stderr, "[WARNING] render_template_segment: '{{else}}' without "
+ "matching '{{if}}'. Appending else tag as-is.\n");
+ }
+
+ else if (strcmp(trimmed_tag_content, "endif") == 0) {
+ fprintf(stderr, "[WARNING] render_template_segment: '{{endif}}' without "
+ "matching '{{if}}'. Appending endif tag as-is.\n");
+ }
+
else if (strncmp(trimmed_tag_content, "include ", 8) == 0) {
const char *filename_start = trimmed_tag_content + strlen("include ");
if (*filename_start == '"') {