diff options
| -rw-r--r-- | man/blogc-template.7.ronn | 38 | ||||
| -rw-r--r-- | src/renderer.c | 71 | ||||
| -rw-r--r-- | src/renderer.h | 2 | ||||
| -rw-r--r-- | src/template-parser.c | 67 | ||||
| -rw-r--r-- | src/template-parser.h | 2 | ||||
| -rw-r--r-- | tests/check_renderer.c | 70 | ||||
| -rw-r--r-- | tests/check_template_parser.c | 121 | 
7 files changed, 361 insertions, 10 deletions
| diff --git a/man/blogc-template.7.ronn b/man/blogc-template.7.ronn index 77073af..46413c2 100644 --- a/man/blogc-template.7.ronn +++ b/man/blogc-template.7.ronn @@ -163,6 +163,44 @@ Or:      Title is the default title      {% endif %} +## TEMPLATE ITERATORS + +Template iterators are used to iterate over the value of a variable, that is handled +as a list. + +The available conditionals are: `foreach`. + +### foreach iterator + +The content of a `foreach` iterator is included in the output file when the target +variable is defined, and is repeated for each item in the list parsed from the variable +value. + +The variable value should be formatted as a comma-separated list of items. Quotes are +not supported, as this is intended to work with identifiers, like slugs, and not with +arbitrary strings. + +Spaces and tabs before and after list items are stripped for consistency. + +This is how a variable would be formatted: + +    item1, item2, item3 + +For more info about how to define variables, see blogc(1) and blogc-source(7). + +This is how a `foreach` iterator is defined in a template: + +    {% foreach TAGS %} +    <a href="/tag/{{ FOREACH_ITEM }}/">{{ FOREACH_ITEM }}</a> +    {% endforeach %} + +Where `TAGS` is the variable with comma-separated list of items, and `FOREACH_ITEM` +is the variable defined by blogc(1), that will store the item value for a given +iteration. + +If the value of the `TAGS` variable is "item1, item2, item3", this template would be +rendered as 3 times, one for each item value. +  ## BUGS  The template content is handled by handwritten parsers, that even being well diff --git a/src/renderer.c b/src/renderer.c index 3061c43..6cf924e 100644 --- a/src/renderer.c +++ b/src/renderer.c @@ -88,6 +88,24 @@ blogc_format_variable(const char *name, b_trie_t *global, b_trie_t *local)  } +b_slist_t* +blogc_split_list_variable(const char *name, b_trie_t *global, b_trie_t *local) +{ +    const char *value = blogc_get_variable(name, global, local); +    if (value == NULL) +        return NULL; + +    b_slist_t *rv = NULL; + +    char **tmp = b_str_split(value, ',', 0); +    for (unsigned int i = 0; tmp[i] != NULL; i++) +        rv = b_slist_append(rv, b_strdup(b_str_strip(tmp[i]))); +    b_strv_free(tmp); + +    return rv; +} + +  char*  blogc_render(b_slist_t *tmpl, b_slist_t *sources, b_trie_t *config, bool listing)  { @@ -106,6 +124,10 @@ blogc_render(b_slist_t *tmpl, b_slist_t *sources, b_trie_t *config, bool listing      unsigned int if_count = 0;      unsigned int if_skip = 0; +    b_slist_t *foreach_var = NULL; +    b_slist_t *foreach_var_start = NULL; +    b_slist_t *foreach_start = NULL; +      bool if_not = false;      bool inside_block = false;      bool evaluate = false; @@ -174,6 +196,11 @@ blogc_render(b_slist_t *tmpl, b_slist_t *sources, b_trie_t *config, bool listing              case BLOGC_TEMPLATE_VARIABLE_STMT:                  if (stmt->value != NULL) { +                    if (0 == strcmp(stmt->value, "FOREACH_ITEM")) {  // foreach +                        if (foreach_var != NULL && foreach_var->data != NULL) +                            b_string_append(str, foreach_var->data); +                        break; +                    }                      config_value = blogc_format_variable(stmt->value,                          config, inside_block ? tmp_source : NULL);                      if (config_value != NULL) { @@ -281,9 +308,53 @@ blogc_render(b_slist_t *tmpl, b_slist_t *sources, b_trie_t *config, bool listing              case BLOGC_TEMPLATE_ENDIF_STMT:                  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; +                b_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 b_string_free(str, false);  } diff --git a/src/renderer.h b/src/renderer.h index e5cff6e..de26e98 100644 --- a/src/renderer.h +++ b/src/renderer.h @@ -15,6 +15,8 @@  const char* blogc_get_variable(const char *name, b_trie_t *global, b_trie_t *local);  char* blogc_format_date(const char *date, b_trie_t *global, b_trie_t *local);  char* blogc_format_variable(const char *name, b_trie_t *global, b_trie_t *local); +b_slist_t* blogc_split_list_variable(const char *name, b_trie_t *global, +    b_trie_t *local);  char* blogc_render(b_slist_t *tmpl, b_slist_t *sources, b_trie_t *config,      bool listing); diff --git a/src/template-parser.c b/src/template-parser.c index f6912df..e5c750e 100644 --- a/src/template-parser.c +++ b/src/template-parser.c @@ -32,6 +32,8 @@ typedef enum {      TEMPLATE_BLOCK_IF_OPERAND_START,      TEMPLATE_BLOCK_IF_STRING_OPERAND,      TEMPLATE_BLOCK_IF_VARIABLE_OPERAND, +    TEMPLATE_BLOCK_FOREACH_START, +    TEMPLATE_BLOCK_FOREACH_VARIABLE,      TEMPLATE_BLOCK_END,      TEMPLATE_VARIABLE_START,      TEMPLATE_VARIABLE, @@ -65,6 +67,7 @@ blogc_template_parse(const char *src, size_t src_len, blogc_error_t **err)      blogc_template_stmt_operator_t tmp_op = 0;      unsigned int if_count = 0; +    bool foreach_open = false;      b_slist_t *stmts = NULL;      blogc_template_stmt_t *stmt = NULL; @@ -201,11 +204,42 @@ blogc_template_parse(const char *src, size_t src_len, blogc_error_t **err)                              "statement.");                          break;                      } +                    else if ((current - start == 7) && +                        (0 == strncmp("foreach", src + start, 7))) +                    { +                        if (!foreach_open) { +                            state = TEMPLATE_BLOCK_FOREACH_START; +                            type = BLOGC_TEMPLATE_FOREACH_STMT; +                            start = current; +                            foreach_open = true; +                            break; +                        } +                        *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, +                            src, src_len, current, "'foreach' statements can't " +                            "be nested."); +                        break; +                    } +                    else if ((current - start == 10) && +                        (0 == strncmp("endforeach", src + start, 10))) +                    { +                        if (foreach_open) { +                            state = TEMPLATE_BLOCK_END; +                            type = BLOGC_TEMPLATE_ENDFOREACH_STMT; +                            foreach_open = false; +                            break; +                        } +                        *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, +                            src, src_len, current, +                            "'endforeach' statement without an open 'foreach' " +                            "statement."); +                        break; +                    }                  }                  *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src,                      src_len, current,                      "Invalid statement type: Allowed types are: 'block', " -                    "'endblock', 'ifdef', 'ifndef' and 'endif'."); +                    "'endblock', 'ifdef', 'ifndef', 'endif', 'foreach' and " +                    "'endforeach'.");                  break;              case TEMPLATE_BLOCK_BLOCK_TYPE_START: @@ -337,6 +371,34 @@ blogc_template_parse(const char *src, size_t src_len, blogc_error_t **err)                  end2 = current;                  break; +            case TEMPLATE_BLOCK_FOREACH_START: +                if (c == ' ') +                    break; +                if (c >= 'A' && c <= 'Z') { +                    state = TEMPLATE_BLOCK_FOREACH_VARIABLE; +                    start = current; +                    break; +                } +                *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, +                    src_len, current, +                    "Invalid foreach variable name. Must begin with uppercase " +                    "letter."); +                break; + +            case TEMPLATE_BLOCK_FOREACH_VARIABLE: +                if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_') +                    break; +                if (c == ' ') { +                    end = current; +                    state = TEMPLATE_BLOCK_END; +                    break; +                } +                *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:                  if (c == ' ')                      break; @@ -469,6 +531,9 @@ blogc_template_parse(const char *src, size_t src_len, blogc_error_t **err)          else if (block_state != BLOCK_CLOSED)              *err = blogc_error_new(BLOGC_ERROR_TEMPLATE_PARSER,                  "An open block was not closed!"); +        else if (foreach_open) +            *err = blogc_error_new(BLOGC_ERROR_TEMPLATE_PARSER, +                "An open 'foreach' statement was not closed!");      }      if (*err != NULL) { diff --git a/src/template-parser.h b/src/template-parser.h index d1e9bd6..9da5fff 100644 --- a/src/template-parser.h +++ b/src/template-parser.h @@ -17,6 +17,8 @@ typedef enum {      BLOGC_TEMPLATE_IFNDEF_STMT,      BLOGC_TEMPLATE_IF_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, diff --git a/tests/check_renderer.c b/tests/check_renderer.c index cb8f8f1..cc04fdc 100644 --- a/tests/check_renderer.c +++ b/tests/check_renderer.c @@ -31,6 +31,7 @@ create_sources(unsigned int count)          "GUDA2: zxc\n"          "DATE: 2015-01-02 03:04:05\n"          "DATE_FORMAT: %R\n" +        "TAGS: foo, bar,baz\n"          "-----\n"          "ahahahahahahahaha",          "BOLA: asd2\n" @@ -74,7 +75,8 @@ test_render_entry(void **state)          "{% if GUDA != \"bola\" %}HEHE{% endif %}\n"          "{% if GUDA < \"zxd\" %}LOL2{% endif %}\n"          "{% if GUDA > \"zxd\" %}LOL3{% endif %}\n" -        "{% if GUDA <= \"zxc\" %}LOL4{% endif %}\n"; +        "{% if GUDA <= \"zxc\" %}LOL4{% endif %}\n" +        "{% foreach TAGS %}lol {{ FOREACH_ITEM }} haha {% endforeach %}\n";      blogc_error_t *err = NULL;      b_slist_t *l = blogc_template_parse(str, strlen(str), &err);      assert_non_null(l); @@ -97,7 +99,8 @@ test_render_entry(void **state)          "HEHE\n"          "LOL2\n"          "\n" -        "LOL4\n"); +        "LOL4\n" +        "lol foo haha lol bar haha lol baz haha \n");      blogc_template_free_stmts(l);      b_slist_free_full(s, (b_free_func_t) b_trie_free);      free(out); @@ -117,6 +120,7 @@ test_render_listing(void **state)          "{% block listing %}\n"          "{% ifdef DATE_FORMATTED %}{{ DATE_FORMATTED }}{% endif %}\n"          "bola: {% ifdef BOLA %}{{ BOLA }}{% endif %}\n" +        "{% foreach TAGS %}lol {{ FOREACH_ITEM }} haha {% endforeach %}\n"          "{% endblock %}\n";      blogc_error_t *err = NULL;      b_slist_t *l = blogc_template_parse(str, strlen(str), &err); @@ -132,12 +136,15 @@ test_render_listing(void **state)          "\n"          "03:04\n"          "bola: asd\n" +        "lol foo haha lol bar haha lol baz haha \n"          "\n"          "2014-02-03 04:05:06\n"          "bola: asd2\n"          "\n" +        "\n"          "2013-01-02 03:04:05\n"          "bola: asd3\n" +        "\n"          "\n");      blogc_template_free_stmts(l);      b_slist_free_full(s, (b_free_func_t) b_trie_free); @@ -158,6 +165,7 @@ test_render_listing_empty(void **state)          "{% block listing %}\n"          "{% ifdef DATE_FORMATTED %}{{ DATE_FORMATTED }}{% endif %}\n"          "bola: {% ifdef BOLA %}{{ BOLA }}{% endif %}\n" +        "{% foreach TAGS %}lol {{ FOREACH_ITEM }} haha {% endforeach %}\n"          "{% endblock %}\n";      blogc_error_t *err = NULL;      b_slist_t *l = blogc_template_parse(str, strlen(str), &err); @@ -519,6 +527,30 @@ test_render_if_gt_eq(void **state)  static void +test_render_foreach(void **state) +{ +    const char *str = +        "{% block entry %}\n" +        "{% foreach TAGS %} {{ FOREACH_ITEM }} {% endforeach %}\n" +        "{% endblock %}\n"; +    blogc_error_t *err = NULL; +    b_slist_t *l = blogc_template_parse(str, strlen(str), &err); +    assert_non_null(l); +    assert_null(err); +    b_slist_t *s = create_sources(1); +    assert_non_null(s); +    char *out = blogc_render(l, s, NULL, false); +    assert_string_equal(out, +        "\n" +        " foo  bar  baz \n" +        "\n"); +    blogc_template_free_stmts(l); +    b_slist_free_full(s, (b_free_func_t) b_trie_free); +    free(out); +} + + +static void  test_render_null(void **state)  {      assert_null(blogc_render(NULL, NULL, NULL, false)); @@ -760,6 +792,37 @@ test_format_variable_with_date(void **state)  } +static void +test_split_list_variable(void **state) +{ +    b_trie_t *g = b_trie_new(free); +    b_trie_insert(g, "TAGS", b_strdup("asd, lol,hehe")); +    b_trie_t *l = b_trie_new(free); +    b_trie_insert(l, "TAGS", b_strdup("asd, lol,XD")); +    b_slist_t *tmp = blogc_split_list_variable("TAGS", g, l); +    assert_string_equal(tmp->data, "asd"); +    assert_string_equal(tmp->next->data, "lol"); +    assert_string_equal(tmp->next->next->data, "XD"); +    b_slist_free_full(tmp, free); +    b_trie_free(g); +    b_trie_free(l); +} + + +static void +test_split_list_variable_not_found(void **state) +{ +    b_trie_t *g = b_trie_new(free); +    b_trie_insert(g, "TAGS", b_strdup("asd, lol,hehe")); +    b_trie_t *l = b_trie_new(free); +    b_trie_insert(l, "TAGS", b_strdup("asd, lol,XD")); +    b_slist_t *tmp = blogc_split_list_variable("TAG", g, l); +    assert_null(tmp); +    b_trie_free(g); +    b_trie_free(l); +} + +  int  main(void)  { @@ -777,6 +840,7 @@ main(void)          unit_test(test_render_if_gt),          unit_test(test_render_if_lt_eq),          unit_test(test_render_if_gt_eq), +        unit_test(test_render_foreach),          unit_test(test_render_null),          unit_test(test_render_outside_block),          unit_test(test_render_prefer_local_variable), @@ -790,6 +854,8 @@ main(void)          unit_test(test_format_date_without_date),          unit_test(test_format_variable),          unit_test(test_format_variable_with_date), +        unit_test(test_split_list_variable), +        unit_test(test_split_list_variable_not_found),      };      return run_tests(tests);  } diff --git a/tests/check_template_parser.c b/tests/check_template_parser.c index b712f22..6aaceed 100644 --- a/tests/check_template_parser.c +++ b/tests/check_template_parser.c @@ -61,6 +61,7 @@ test_template_parse(void **state)          "{% endblock %}\n"          "{% block listing %}{{ BOLA }}{% endblock %}\n"          "{% block listing_once %}asd{% endblock %}\n" +        "{% foreach BOLA %}hahaha{% endforeach %}\n"          "{% if BOLA == \"1\\\"0\" %}aee{% endif %}";      blogc_error_t *err = NULL;      b_slist_t *stmts = blogc_template_parse(a, strlen(a), &err); @@ -106,11 +107,20 @@ test_template_parse(void **state)      blogc_assert_template_stmt(tmp->next->next->next->next->next->next->next->next->next,          "\n", BLOGC_TEMPLATE_CONTENT_STMT);      tmp = tmp->next->next->next->next->next->next->next->next->next->next; -    blogc_assert_template_if_stmt(tmp, "BOLA", BLOGC_TEMPLATE_OP_EQ, "\"1\\\"0\""); -    blogc_assert_template_stmt(tmp->next, "aee", BLOGC_TEMPLATE_CONTENT_STMT); +    blogc_assert_template_stmt(tmp, "BOLA", BLOGC_TEMPLATE_FOREACH_STMT); +    blogc_assert_template_stmt(tmp->next, "hahaha", +        BLOGC_TEMPLATE_CONTENT_STMT);      blogc_assert_template_stmt(tmp->next->next, NULL, +        BLOGC_TEMPLATE_ENDFOREACH_STMT); +    blogc_assert_template_stmt(tmp->next->next->next, "\n", +        BLOGC_TEMPLATE_CONTENT_STMT); +    blogc_assert_template_if_stmt(tmp->next->next->next->next, "BOLA", +        BLOGC_TEMPLATE_OP_EQ, "\"1\\\"0\""); +    blogc_assert_template_stmt(tmp->next->next->next->next->next, "aee", +        BLOGC_TEMPLATE_CONTENT_STMT); +    blogc_assert_template_stmt(tmp->next->next->next->next->next->next, NULL,          BLOGC_TEMPLATE_ENDIF_STMT); -    assert_null(tmp->next->next->next); +    assert_null(tmp->next->next->next->next->next->next->next);      blogc_template_free_stmts(stmts);  } @@ -131,6 +141,7 @@ test_template_parse_crlf(void **state)          "{% endblock %}\r\n"          "{% block listing %}{{ BOLA }}{% endblock %}\r\n"          "{% block listing_once %}asd{% endblock %}\r\n" +        "{% foreach BOLA %}hahaha{% endforeach %}\r\n"          "{% if BOLA == \"1\\\"0\" %}aee{% endif %}";      blogc_error_t *err = NULL;      b_slist_t *stmts = blogc_template_parse(a, strlen(a), &err); @@ -176,11 +187,20 @@ test_template_parse_crlf(void **state)      blogc_assert_template_stmt(tmp->next->next->next->next->next->next->next->next->next,          "\r\n", BLOGC_TEMPLATE_CONTENT_STMT);      tmp = tmp->next->next->next->next->next->next->next->next->next->next; -    blogc_assert_template_if_stmt(tmp, "BOLA", BLOGC_TEMPLATE_OP_EQ, "\"1\\\"0\""); -    blogc_assert_template_stmt(tmp->next, "aee", BLOGC_TEMPLATE_CONTENT_STMT); +    blogc_assert_template_stmt(tmp, "BOLA", BLOGC_TEMPLATE_FOREACH_STMT); +    blogc_assert_template_stmt(tmp->next, "hahaha", +        BLOGC_TEMPLATE_CONTENT_STMT);      blogc_assert_template_stmt(tmp->next->next, NULL, +        BLOGC_TEMPLATE_ENDFOREACH_STMT); +    blogc_assert_template_stmt(tmp->next->next->next, "\r\n", +        BLOGC_TEMPLATE_CONTENT_STMT); +    blogc_assert_template_if_stmt(tmp->next->next->next->next, "BOLA", +        BLOGC_TEMPLATE_OP_EQ, "\"1\\\"0\""); +    blogc_assert_template_stmt(tmp->next->next->next->next->next, "aee", +        BLOGC_TEMPLATE_CONTENT_STMT); +    blogc_assert_template_stmt(tmp->next->next->next->next->next->next, NULL,          BLOGC_TEMPLATE_ENDIF_STMT); -    assert_null(tmp->next->next->next); +    assert_null(tmp->next->next->next->next->next->next->next);      blogc_template_free_stmts(stmts);  } @@ -383,6 +403,24 @@ test_template_parse_invalid_block_nested(void **state)  static void +test_template_parse_invalid_foreach_nested(void **state) +{ +    const char *a = +        "{% foreach A %}\n" +        "{% foreach B %}\n"; +    blogc_error_t *err = NULL; +    b_slist_t *stmts = blogc_template_parse(a, strlen(a), &err); +    assert_non_null(err); +    assert_null(stmts); +    assert_int_equal(err->type, BLOGC_ERROR_TEMPLATE_PARSER); +    assert_string_equal(err->msg, +        "'foreach' statements can't be nested.\n" +        "Error occurred near line 2, position 11: {% foreach B %}"); +    blogc_error_free(err); +} + + +static void  test_template_parse_invalid_block_not_open(void **state)  {      const char *a = "{% endblock %}\n"; @@ -416,6 +454,22 @@ test_template_parse_invalid_endif_not_open(void **state)  static void +test_template_parse_invalid_endforeach_not_open(void **state) +{ +    const char *a = "{% endforeach %}\n"; +    blogc_error_t *err = NULL; +    b_slist_t *stmts = blogc_template_parse(a, strlen(a), &err); +    assert_non_null(err); +    assert_null(stmts); +    assert_int_equal(err->type, BLOGC_ERROR_TEMPLATE_PARSER); +    assert_string_equal(err->msg, +        "'endforeach' statement without an open 'foreach' statement.\n" +        "Error occurred near line 1, position 14: {% endforeach %}"); +    blogc_error_free(err); +} + + +static void  test_template_parse_invalid_block_name(void **state)  {      const char *a = "{% chunda %}\n"; @@ -426,7 +480,7 @@ test_template_parse_invalid_block_name(void **state)      assert_int_equal(err->type, BLOGC_ERROR_TEMPLATE_PARSER);      assert_string_equal(err->msg,          "Invalid statement type: Allowed types are: 'block', 'endblock', 'ifdef', " -        "'ifndef' and 'endif'.\n" +        "'ifndef', 'endif', 'foreach' and 'endforeach'.\n"          "Error occurred near line 1, position 10: {% chunda %}");      blogc_error_free(err);  } @@ -482,6 +536,23 @@ test_template_parse_invalid_ifdef_start(void **state)  static void +test_template_parse_invalid_foreach_start(void **state) +{ +    const char *a = "{% block entry %}{% foreach guda %}\n"; +    blogc_error_t *err = NULL; +    b_slist_t *stmts = blogc_template_parse(a, strlen(a), &err); +    assert_non_null(err); +    assert_null(stmts); +    assert_int_equal(err->type, BLOGC_ERROR_TEMPLATE_PARSER); +    assert_string_equal(err->msg, +        "Invalid foreach variable name. Must begin with uppercase letter.\n" +        "Error occurred near line 1, position 29: " +        "{% block entry %}{% foreach guda %}"); +    blogc_error_free(err); +} + + +static void  test_template_parse_invalid_ifdef_variable(void **state)  {      const char *a = "{% block entry %}{% ifdef BoLA %}\n"; @@ -499,6 +570,23 @@ test_template_parse_invalid_ifdef_variable(void **state)  static void +test_template_parse_invalid_foreach_variable(void **state) +{ +    const char *a = "{% block entry %}{% foreach BoLA %}\n"; +    blogc_error_t *err = NULL; +    b_slist_t *stmts = blogc_template_parse(a, strlen(a), &err); +    assert_non_null(err); +    assert_null(stmts); +    assert_int_equal(err->type, BLOGC_ERROR_TEMPLATE_PARSER); +    assert_string_equal(err->msg, +        "Invalid foreach variable name. Must be uppercase letter, number or '_'.\n" +        "Error occurred near line 1, position 30: " +        "{% block entry %}{% foreach BoLA %}"); +    blogc_error_free(err); +} + + +static void  test_template_parse_invalid_if_operator(void **state)  {      const char *a = "{% block entry %}{% if BOLA = \"asd\" %}\n"; @@ -678,6 +766,20 @@ test_template_parse_invalid_block_not_closed(void **state)  } +static void +test_template_parse_invalid_foreach_not_closed(void **state) +{ +    const char *a = "{% foreach ASD %}\n"; +    blogc_error_t *err = NULL; +    b_slist_t *stmts = blogc_template_parse(a, strlen(a), &err); +    assert_non_null(err); +    assert_null(stmts); +    assert_int_equal(err->type, BLOGC_ERROR_TEMPLATE_PARSER); +    assert_string_equal(err->msg, "An open 'foreach' statement was not closed!"); +    blogc_error_free(err); +} + +  int  main(void)  { @@ -688,13 +790,17 @@ main(void)          unit_test(test_template_parse_ifdef_and_var_outside_block),          unit_test(test_template_parse_invalid_block_start),          unit_test(test_template_parse_invalid_block_nested), +        unit_test(test_template_parse_invalid_foreach_nested),          unit_test(test_template_parse_invalid_block_not_open),          unit_test(test_template_parse_invalid_endif_not_open), +        unit_test(test_template_parse_invalid_endforeach_not_open),          unit_test(test_template_parse_invalid_block_name),          unit_test(test_template_parse_invalid_block_type_start),          unit_test(test_template_parse_invalid_block_type),          unit_test(test_template_parse_invalid_ifdef_start), +        unit_test(test_template_parse_invalid_foreach_start),          unit_test(test_template_parse_invalid_ifdef_variable), +        unit_test(test_template_parse_invalid_foreach_variable),          unit_test(test_template_parse_invalid_if_operator),          unit_test(test_template_parse_invalid_if_operand),          unit_test(test_template_parse_invalid_if_operand2), @@ -706,6 +812,7 @@ main(void)          unit_test(test_template_parse_invalid_close2),          unit_test(test_template_parse_invalid_if_not_closed),          unit_test(test_template_parse_invalid_block_not_closed), +        unit_test(test_template_parse_invalid_foreach_not_closed),      };      return run_tests(tests);  } | 
