diff options
| author | frosty <gabriel@bwaaa.monster> | 2026-03-12 18:39:22 -0400 |
|---|---|---|
| committer | frosty <gabriel@bwaaa.monster> | 2026-03-12 18:39:22 -0400 |
| commit | caadb1627cbf393c919a6e0a57e5f42a03fa8c75 (patch) | |
| tree | 28123bc175e85087ae0c59c2064e67d3215290c1 | |
| parent | ad7ec71761e55b9a2800d6d1185059eae9bace37 (diff) | |
| parent | 4722542caf4192fe702adc975e8ee557e3526426 (diff) | |
| download | beaker-caadb1627cbf393c919a6e0a57e5f42a03fa8c75.tar.gz | |
Merge branch 'indev'
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | examples/template-demo/main.c | 4 | ||||
| -rw-r--r-- | examples/template-demo/templates/index.html | 49 | ||||
| -rw-r--r-- | src/template.c | 371 |
4 files changed, 426 insertions, 0 deletions
@@ -1 +1,3 @@ +examples/hello-world/hello-world +examples/template-demo/template-demo /build diff --git a/examples/template-demo/main.c b/examples/template-demo/main.c index 2adbb52..9989a68 100644 --- a/examples/template-demo/main.c +++ b/examples/template-demo/main.c @@ -26,6 +26,10 @@ int templating_handler(UrlParams *params) { context_set(&ctx, "safe_html", "This is <b>bold</b> and <i>italic</i> HTML."); context_set(&ctx, "unsafe_html", "<script>alert(0);"); + context_set(&ctx, "user_logged_in", "true"); + context_set(&ctx, "role", "admin"); + context_set(&ctx, "status", "active"); + char *features[] = {"Fast and Lightweight", "Simple API", "Basic Routing", "Templating Engine", "Static File Serving", "Cookie Management"}; diff --git a/examples/template-demo/templates/index.html b/examples/template-demo/templates/index.html index 837688f..2a08485 100644 --- a/examples/template-demo/templates/index.html +++ b/examples/template-demo/templates/index.html @@ -40,6 +40,55 @@ {{endfor}} </ul> + <h2>Conditional Statements (if/elif/else)</h2> + + <h3>Simple truthy check</h3> + <p> + {{if user_logged_in}} + <span class="highlight">User is logged in!</span> + {{else}} + <span>User is NOT logged in.</span> + {{endif}} + </p> + + <h3>Equality check (role == "admin")</h3> + <p> + {{if role == "admin"}} + <span class="highlight">Welcome, Admin! You have full access.</span> + {{elif role == "user"}} + <span>Welcome, User! You have standard access.</span> + {{else}} + <span>Welcome, Guest! You have limited access.</span> + {{endif}} + </p> + + <h3>Not equal check (status != "inactive")</h3> + <p> + {{if status != "inactive"}} + <span class="highlight">Account is active.</span> + {{else}} + <span>Account is inactive.</span> + {{endif}} + </p> + + <h3>Variable existence check (exists)</h3> + <p> + {{if exists username}} + <span class="highlight">Username exists!</span> + {{else}} + <span>Username does NOT exist.</span> + {{endif}} + </p> + + <h3>Variable existence check (not exists)</h3> + <p> + {{if not exists missing_var}} + <span class="highlight">missing_var does NOT exist (as expected)</span> + {{else}} + <span>missing_var exists (unexpected!)</span> + {{endif}} + </p> + <div class="footer"> <p>Rendered at: {{timestamp}}</p> </div> 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 == '"') { |
