From 8d96c02e5118cf7bd656fde9100570a67115d19a Mon Sep 17 00:00:00 2001 From: "Rafael G. Martins" Date: Tue, 29 Dec 2015 00:12:51 +0100 Subject: man: renderer: template-parser: added foreach iterator support --- man/blogc-template.7.ronn | 38 +++++++++++++ src/renderer.c | 71 +++++++++++++++++++++++++ src/renderer.h | 2 + src/template-parser.c | 67 ++++++++++++++++++++++- src/template-parser.h | 2 + tests/check_renderer.c | 70 +++++++++++++++++++++++- 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 %} + {{ FOREACH_ITEM }} + {% 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); @@ -518,6 +526,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) { @@ -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); } @@ -382,6 +402,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) { @@ -415,6 +453,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) { @@ -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); } @@ -481,6 +535,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) { @@ -498,6 +569,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) { @@ -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); } -- cgit v1.2.3-18-g5258