diff options
| author | frosty <gabriel@bwaaa.monster> | 2026-03-12 18:13:04 -0400 |
|---|---|---|
| committer | frosty <gabriel@bwaaa.monster> | 2026-03-12 18:13:04 -0400 |
| commit | 4722542caf4192fe702adc975e8ee557e3526426 (patch) | |
| tree | a9419dea2815652f3f53ab8d90009774494883fc /src | |
| parent | ec82c9ec4eb40ba28c228414e501d73ecc5326af (diff) | |
| download | beaker-4722542caf4192fe702adc975e8ee557e3526426.tar.gz | |
feature: added conditionals + added examples
Diffstat (limited to 'src')
| -rw-r--r-- | src/template.c | 371 |
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, ¤t_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 == '"') { |
