diff options
37 files changed, 965 insertions, 127 deletions
@@ -60,4 +60,5 @@ blogc-*.tar.*  blogc-*.zip  # rpms +blogc.spec  blogc-*.rpm @@ -1,4 +1,4 @@ -Copyright (c) 2015, Rafael G. Martins +Copyright (c) 2014-2016, Rafael G. Martins  All rights reserved.  Redistribution and use in source and binary forms, with or without diff --git a/Makefile.am b/Makefile.am index 65b6c36..55c73dc 100644 --- a/Makefile.am +++ b/Makefile.am @@ -12,6 +12,7 @@ AM_DISTCHECK_CONFIGURE_FLAGS = \  ## File listings  EXTRA_DIST = \ +	build-aux/build-windows.sh \  	autogen.sh \  	LICENSE \  	README.md \ diff --git a/blogc.spec.in b/blogc.spec.in new file mode 100644 index 0000000..3fa3353 --- /dev/null +++ b/blogc.spec.in @@ -0,0 +1,75 @@ +Name:		@PACKAGE_NAME@ +Version:	@PACKAGE_VERSION@ +Release:	1%{?dist} +License:	BSD +Group:		Applications/Text +Summary:	A blog compiler +URL:		http://blogc.org/ +Source0:	https://github.com/blogc/blogc/releases/download/v%{version}/blogc-%{version}.tar.xz + +#BuildRequires: +#Requires: + +%description +blogc(1) is a blog compiler. It converts source files and templates into +blog/website resources. + + +%prep +%setup -q -n blogc-%{version} + + +%build +%configure +make %{?_smp_mflags} + + +%install +rm -rf $RPM_BUILD_ROOT +%make_install + + +%files +%{_mandir}/man*/blogc* +%{_bindir}/blogc + +%doc README.md +%license LICENSE + + +%changelog +* Mon Jan 25 2016 Rafael G. Martins <rafael@rafaelmartins.eng.br> 0.7.2-1 +- New release. + +* Fri Jan 22 2016 Rafael G. Martins <rafael@rafaelmartins.eng.br> 0.7.1-1 +- New release. + +* Thu Jan 14 2016 Rafael G. Martins <rafael@rafaelmartins.eng.br> 0.7-1 +- New release. + +* Sun Jan 10 2016 Rafael G. Martins <rafael@rafaelmartins.eng.br> 0.6.1-1 +- New release. + +* Thu Jan 07 2016 Rafael G. Martins <rafael@rafaelmartins.eng.br> 0.6-1 +- New release. + +* Thu Dec 03 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> 0.5.1-1 +- New release. + +* Thu Nov 05 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> 0.5-1 +- New release. + +* Sun Oct 25 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> 0.4-1 +- New release. + +* Fri Oct 16 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> 0.3-1 +- New release. + +* Thu Oct 08 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> 0.2.1-1 +- New release. + +* Wed Sep 16 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> 0.1-1 +- First stable release. + +* Mon Sep 14 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> 0.1-0.1.beta4 +- Initial package. diff --git a/build-aux/build-windows.sh b/build-aux/build-windows.sh index 267a8aa..bdecc8b 100755 --- a/build-aux/build-windows.sh +++ b/build-aux/build-windows.sh @@ -5,24 +5,18 @@  #  # mingw32-gcc mingw64-gcc zip  # -# This script must be called with the xz source tarball as argument. +# This script must be called with the xz source tarball and the target version +# as arguments.  set -ex -[[ $# -eq 1 ]] - - -get_version() { -    local a=$(basename "${1}") -    a="${a%.tar.xz}" -    echo "${a#blogc-}" -} +[[ $# -eq 2 ]]  build() { -    local version=$(get_version "${1}") -    local arch=${2} +    local version=${2} +    local arch=${3}      local build_dir="/tmp/blogc_build_${version}_${arch}"      local dest_dir="/tmp/blogc-${version}-w${arch}" @@ -50,5 +44,5 @@ build() {  for arch in 32 64; do -    build "$1" "${arch}" +    build "$1" "$2" "${arch}"  done diff --git a/configure.ac b/configure.ac index 3cfb07d..9ceed22 100644 --- a/configure.ac +++ b/configure.ac @@ -1,18 +1,18 @@  AC_PREREQ([2.69]) -AC_INIT([blogc], [0.5.1], [https://github.com/blogc/blogc], [blogc], [https://blogc.rgm.io]) +AC_INIT([blogc], [0.7.2], [https://github.com/blogc/blogc], [blogc], +        [https://blogc.rgm.io])  AC_CONFIG_AUX_DIR([build-aux])  AC_CONFIG_MACRO_DIR([m4]) -AM_INIT_AUTOMAKE([1.13 foreign dist-bzip2 dist-xz dist-zip subdir-objects serial-tests -Wall -Werror]) +AM_INIT_AUTOMAKE([1.13 foreign dist-bzip2 dist-xz dist-zip subdir-objects +                  serial-tests -Wall -Wno-extra-portability -Werror])  AC_CONFIG_HEADERS([config.h])  AM_SILENT_RULES([yes])  AM_MAINTAINER_MODE([enable])  AC_USE_SYSTEM_EXTENSIONS -AM_PROG_AR -  LT_INIT  AC_PROG_CC_C99 @@ -101,6 +101,7 @@ LT_LIB_M  AC_CONFIG_FILES([      Makefile +    blogc.spec  ])  AC_OUTPUT diff --git a/man/blogc-template.7.ronn b/man/blogc-template.7.ronn index 77073af..bd38618 100644 --- a/man/blogc-template.7.ronn +++ b/man/blogc-template.7.ronn @@ -4,8 +4,8 @@ blogc-template(7) -- blogc's template format  ## DESCRIPTION  Template files are used as base to build output files by blogc(1). These files -can include variables, blocks and conditionals, that will directly affect the -output files. +can include variables, blocks, conditionals and iterators, that will directly +affect the output files.  The syntax of the template files is defined to be simple, without affecting the  content output. The syntax is somewhat inspired by Jinja2 syntax. @@ -163,6 +163,55 @@ 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 space-separated list of items. Quotes are +not supported, as this is intended to work with identifiers, like slugs, and not with +arbitrary strings. + +This is how a variable value 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 space-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 is +rendered 3 times, one for each item value. + +## WHITESPACE CONTROL + +Users can control how whitespaces (space, form-feed (`\f`), newline (`\n`), +carriage return (`\r`), horizontal tab (`\t`), and vertical tab (`\v`)) are +handled before and after statements delimited with `{%` and `%}` sequences, +respectively. + +Adding a minus sign (`-`)  after a `{%` sequence (`{%-`) will remove whitespaces +before the sequence and after the last non-whitespace character before the sequence. + +Adding a minus sign (`-`)  before a `%}` sequence (`-%}`) will remove whitespaces +after the sequence and before the first non-whitespace character after the sequence. +  ## BUGS  The template content is handled by handwritten parsers, that even being well diff --git a/src/content-parser.c b/src/content-parser.c index 8e98405..e8e169d 100644 --- a/src/content-parser.c +++ b/src/content-parser.c @@ -1,6 +1,6 @@  /*   * blogc: A blog compiler. - * Copyright (C) 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>   *   * This program can be distributed under the terms of the BSD License.   * See the file LICENSE. @@ -28,7 +28,7 @@ blogc_slugify(const char *str)          return NULL;      char *new_str = b_strdup(str);      int diff = 'a' - 'A';  // just to avoid magic numbers -    for (unsigned int i = 0; new_str[i] != '\0'; i++) { +    for (size_t i = 0; new_str[i] != '\0'; i++) {          if (new_str[i] >= 'a' && new_str[i] <= 'z')              continue;          if (new_str[i] >= '0' && new_str[i] <= '9') @@ -42,6 +42,40 @@ blogc_slugify(const char *str)  } +char* +blogc_htmlentities(const char *str) +{ +    if (str == NULL) +        return NULL; +    b_string_t *rv = b_string_new(); +    for (size_t i = 0; str[i] != '\0'; i++) { +        switch (str[i]) { +            case '&': +                b_string_append(rv, "&"); +                break; +            case '<': +                b_string_append(rv, "<"); +                break; +            case '>': +                b_string_append(rv, ">"); +                break; +            case '"': +                b_string_append(rv, """); +                break; +            case '\'': +                b_string_append(rv, "'"); +                break; +            case '/': +                b_string_append(rv, "/"); +                break; +            default: +                b_string_append_c(rv, str[i]); +        } +    } +    return b_string_free(rv, false); +} + +  typedef enum {      CONTENT_START_LINE = 1,      CONTENT_EXCERPT_OR_DIRECTIVE, @@ -717,11 +751,13 @@ blogc_content_parse(const char *src, size_t *end_excerpt)                  if (c == '\n' || c == '\r' || is_last) {                      b_string_append(rv, "<pre><code>");                      for (b_slist_t *l = lines; l != NULL; l = l->next) { +                        char *tmp_line = blogc_htmlentities(l->data);                          if (l->next == NULL) -                            b_string_append_printf(rv, "%s", l->data); +                            b_string_append_printf(rv, "%s", tmp_line);                          else -                            b_string_append_printf(rv, "%s%s", l->data, +                            b_string_append_printf(rv, "%s%s", tmp_line,                                  line_ending); +                        free(tmp_line);                      }                      b_string_append_printf(rv, "</code></pre>%s", line_ending);                      b_slist_free_full(lines, free); diff --git a/src/content-parser.h b/src/content-parser.h index 2f6b8b9..6617bb4 100644 --- a/src/content-parser.h +++ b/src/content-parser.h @@ -1,6 +1,6 @@  /*   * blogc: A blog compiler. - * Copyright (C) 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>   *   * This program can be distributed under the terms of the BSD License.   * See the file LICENSE. @@ -13,6 +13,7 @@  #include <stdbool.h>  char* blogc_slugify(const char *str); +char* blogc_htmlentities(const char *str);  char* blogc_content_parse_inline(const char *src);  bool blogc_is_ordered_list_item(const char *str, size_t prefix_len);  char* blogc_content_parse(const char *src, size_t *end_excerpt); diff --git a/src/datetime-parser.c b/src/datetime-parser.c index 6a2162d..8785a89 100644 --- a/src/datetime-parser.c +++ b/src/datetime-parser.c @@ -1,6 +1,6 @@  /*   * blogc: A blog compiler. - * Copyright (C) 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>   *   * This program can be distributed under the terms of the BSD License.   * See the file LICENSE. diff --git a/src/datetime-parser.h b/src/datetime-parser.h index 7f94545..a5087b3 100644 --- a/src/datetime-parser.h +++ b/src/datetime-parser.h @@ -1,6 +1,6 @@  /*   * blogc: A blog compiler. - * Copyright (C) 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>   *   * This program can be distributed under the terms of the BSD License.   * See the file LICENSE. diff --git a/src/error.c b/src/error.c index e472d1f..59a85a3 100644 --- a/src/error.c +++ b/src/error.c @@ -1,6 +1,6 @@  /*   * blogc: A blog compiler. - * Copyright (C) 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>   *   * This program can be distributed under the terms of the BSD License.   * See the file LICENSE. diff --git a/src/error.h b/src/error.h index 02ccc96..886d7d3 100644 --- a/src/error.h +++ b/src/error.h @@ -1,6 +1,6 @@  /*   * blogc: A blog compiler. - * Copyright (C) 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>   *   * This program can be distributed under the terms of the BSD License.   * See the file LICENSE. @@ -1,6 +1,6 @@  /*   * blogc: A blog compiler. - * Copyright (C) 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>   *   * This program can be distributed under the terms of the BSD License.   * See the file LICENSE. @@ -1,6 +1,6 @@  /*   * blogc: A blog compiler. - * Copyright (C) 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>   *   * This program can be distributed under the terms of the BSD License.   * See the file LICENSE. diff --git a/src/loader.c b/src/loader.c index 8f04dae..baa81fa 100644 --- a/src/loader.c +++ b/src/loader.c @@ -1,6 +1,6 @@  /*   * blogc: A blog compiler. - * Copyright (C) 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>   *   * This program can be distributed under the terms of the BSD License.   * See the file LICENSE. @@ -141,11 +141,14 @@ blogc_source_parse_from_files(b_trie_t *conf, b_slist_t *l, blogc_error_t **err)                  b_trie_free(s);                  continue;              } -            char **tags = b_str_split(tags_str, ',', 0); +            char **tags = b_str_split(tags_str, ' ', 0);              bool found = false; -            for (unsigned int i = 0; tags[i] != NULL; i++) -                if (0 == strcmp(b_str_strip(tags[i]), filter_tag)) +            for (unsigned int i = 0; tags[i] != NULL; i++) { +                if (tags[i][0] == '\0') +                    continue; +                if (0 == strcmp(tags[i], filter_tag))                      found = true; +            }              b_strv_free(tags);              if (!found) {                  b_trie_free(s); diff --git a/src/loader.h b/src/loader.h index 610aa42..c432e20 100644 --- a/src/loader.h +++ b/src/loader.h @@ -1,6 +1,6 @@  /*   * blogc: A blog compiler. - * Copyright (C) 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>   *   * This program can be distributed under the terms of the BSD License.   * See the file LICENSE. @@ -1,6 +1,6 @@  /*   * blogc: A blog compiler. - * Copyright (C) 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>   *   * This program can be distributed under the terms of the BSD License.   * See the file LICENSE. diff --git a/src/renderer.c b/src/renderer.c index 3061c43..5e07b0c 100644 --- a/src/renderer.c +++ b/src/renderer.c @@ -1,6 +1,6 @@  /*   * blogc: A blog compiler. - * Copyright (C) 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>   *   * This program can be distributed under the terms of the BSD License.   * See the file LICENSE. @@ -58,8 +58,16 @@ 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) +blogc_format_variable(const char *name, b_trie_t *global, b_trie_t *local, +    b_slist_t *foreach_var)  { +    if (0 == strcmp(name, "FOREACH_ITEM")) { +        if (foreach_var != NULL && foreach_var->data != NULL) { +            return b_strdup(foreach_var->data); +        } +        return NULL; +    } +      char *var = NULL;      bool must_format = false;      if (b_str_ends_with(name, "_FORMATTED")) { @@ -88,6 +96,28 @@ 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++) { +        if (tmp[i][0] != '\0')  // ignore empty strings +            rv = b_slist_append(rv, tmp[i]); +        else +            free(tmp[i]); +    } +    free(tmp); + +    return rv; +} + +  char*  blogc_render(b_slist_t *tmpl, b_slist_t *sources, b_trie_t *config, bool listing)  { @@ -104,7 +134,10 @@ blogc_render(b_slist_t *tmpl, b_slist_t *sources, b_trie_t *config, bool listing      char *defined = NULL;      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; @@ -175,7 +208,7 @@ 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) {                      config_value = blogc_format_variable(stmt->value, -                        config, inside_block ? tmp_source : NULL); +                        config, inside_block ? tmp_source : NULL, foreach_var);                      if (config_value != NULL) {                          b_string_append(str, config_value);                          free(config_value); @@ -203,10 +236,11 @@ blogc_render(b_slist_t *tmpl, b_slist_t *sources, b_trie_t *config, bool listing              case BLOGC_TEMPLATE_IF_STMT:              case BLOGC_TEMPLATE_IFDEF_STMT: +                if_count = 0;                  defined = NULL;                  if (stmt->value != NULL)                      defined = blogc_format_variable(stmt->value, config, -                        inside_block ? tmp_source : NULL); +                        inside_block ? tmp_source : NULL, foreach_var);                  evaluate = false;                  if (stmt->op != 0) {                      // Strings that start with a '"' are actually strings, the @@ -223,7 +257,8 @@ blogc_render(b_slist_t *tmpl, b_slist_t *sources, b_trie_t *config, bool listing                          }                          else {                              defined2 = blogc_format_variable(stmt->value2, -                                config, inside_block ? tmp_source : NULL); +                                config, inside_block ? tmp_source : NULL, +                                foreach_var);                          }                      } @@ -248,7 +283,6 @@ blogc_render(b_slist_t *tmpl, b_slist_t *sources, b_trie_t *config, bool listing                          evaluate = true;                  }                  if (!evaluate) { -                    if_skip = if_count;                      // at this point we can just skip anything, counting the                      // number of 'if's, to know how many 'endif's we need to @@ -264,11 +298,11 @@ blogc_render(b_slist_t *tmpl, b_slist_t *sources, b_trie_t *config, bool listing                              continue;                          }                          if (stmt->type == BLOGC_TEMPLATE_ENDIF_STMT) { -                            if (if_count > if_skip) { +                            if (if_count > 0) {                                  if_count--;                                  continue;                              } -                            if (if_count == if_skip) +                            if (if_count == 0)                                  break;                          }                      } @@ -279,11 +313,56 @@ blogc_render(b_slist_t *tmpl, b_slist_t *sources, b_trie_t *config, bool listing                  break;              case BLOGC_TEMPLATE_ENDIF_STMT: -                if_count--; +                if (if_count > 0) +                    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..15204e6 100644 --- a/src/renderer.h +++ b/src/renderer.h @@ -1,6 +1,6 @@  /*   * blogc: A blog compiler. - * Copyright (C) 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>   *   * This program can be distributed under the terms of the BSD License.   * See the file LICENSE. @@ -14,7 +14,10 @@  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); +char* blogc_format_variable(const char *name, b_trie_t *global, b_trie_t *local, +    b_slist_t *foreach_var); +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/source-parser.c b/src/source-parser.c index db0792c..65fdd4e 100644 --- a/src/source-parser.c +++ b/src/source-parser.c @@ -1,6 +1,6 @@  /*   * blogc: A blog compiler. - * Copyright (C) 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>   *   * This program can be distributed under the terms of the BSD License.   * See the file LICENSE. diff --git a/src/source-parser.h b/src/source-parser.h index d92b1ce..f359f9e 100644 --- a/src/source-parser.h +++ b/src/source-parser.h @@ -1,6 +1,6 @@  /*   * blogc: A blog compiler. - * Copyright (C) 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>   *   * This program can be distributed under the terms of the BSD License.   * See the file LICENSE. diff --git a/src/template-parser.c b/src/template-parser.c index f6912df..1d9046e 100644 --- a/src/template-parser.c +++ b/src/template-parser.c @@ -1,6 +1,6 @@  /*   * blogc: A blog compiler. - * Copyright (C) 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>   *   * This program can be distributed under the terms of the BSD License.   * See the file LICENSE. @@ -22,6 +22,7 @@ typedef enum {      TEMPLATE_START = 1,      TEMPLATE_OPEN_BRACKET,      TEMPLATE_BLOCK_START, +    TEMPLATE_BLOCK_START_WHITESPACE_CLEANER,      TEMPLATE_BLOCK_TYPE,      TEMPLATE_BLOCK_BLOCK_TYPE_START,      TEMPLATE_BLOCK_BLOCK_TYPE, @@ -32,6 +33,9 @@ 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_WHITESPACE_CLEANER,      TEMPLATE_BLOCK_END,      TEMPLATE_VARIABLE_START,      TEMPLATE_VARIABLE, @@ -65,10 +69,25 @@ 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; +    /* +     * this is a reference to the content of previous node in the singly-linked +     * list. The "correct" solution here would be implement a doubly-linked +     * list, but here are a few reasons to avoid it: +     * +     * - i'm too tired to implement it :P +     * - template parser never walk backwards, then the list itself does not +     *   need to know its previous node. +     */ +    blogc_template_stmt_t *previous = NULL; + +    bool lstrip_next = false; +    char *tmp = NULL; +      blogc_template_parser_state_t state = TEMPLATE_START;      blogc_template_parser_block_state_t block_state = BLOCK_CLOSED;      blogc_template_stmt_type_t type = BLOGC_TEMPLATE_CONTENT_STMT; @@ -83,10 +102,20 @@ blogc_template_parse(const char *src, size_t src_len, blogc_error_t **err)                  if (last) {                      stmt = b_malloc(sizeof(blogc_template_stmt_t));                      stmt->type = type; -                    stmt->value = b_strndup(src + start, src_len - start); +                    if (lstrip_next) { +                        tmp = b_strndup(src + start, src_len - start); +                        stmt->value = b_strdup(b_str_lstrip(tmp)); +                        free(tmp); +                        tmp = NULL; +                        lstrip_next = false; +                    } +                    else { +                        stmt->value = b_strndup(src + start, src_len - start); +                    }                      stmt->op = 0;                      stmt->value2 = NULL;                      stmts = b_slist_append(stmts, stmt); +                    previous = stmt;                      stmt = NULL;                  }                  if (c == '{') { @@ -98,16 +127,26 @@ blogc_template_parse(const char *src, size_t src_len, blogc_error_t **err)              case TEMPLATE_OPEN_BRACKET:                  if (c == '%' || c == '{') {                      if (c == '%') -                        state = TEMPLATE_BLOCK_START; +                        state = TEMPLATE_BLOCK_START_WHITESPACE_CLEANER;                      else                          state = TEMPLATE_VARIABLE_START;                      if (end > start) {                          stmt = b_malloc(sizeof(blogc_template_stmt_t));                          stmt->type = type; -                        stmt->value = b_strndup(src + start, end - start); +                        if (lstrip_next) { +                            tmp = b_strndup(src + start, end - start); +                            stmt->value = b_strdup(b_str_lstrip(tmp)); +                            free(tmp); +                            tmp = NULL; +                            lstrip_next = false; +                        } +                        else { +                            stmt->value = b_strndup(src + start, end - start); +                        }                          stmt->op = 0;                          stmt->value2 = NULL;                          stmts = b_slist_append(stmts, stmt); +                        previous = stmt;                          stmt = NULL;                      }                      break; @@ -115,6 +154,18 @@ blogc_template_parse(const char *src, size_t src_len, blogc_error_t **err)                  state = TEMPLATE_START;                  break; +            case TEMPLATE_BLOCK_START_WHITESPACE_CLEANER: +                if (c == '-') { +                    if ((previous != NULL) && +                        (previous->type == BLOGC_TEMPLATE_CONTENT_STMT)) +                    { +                        previous->value = b_str_rstrip(previous->value);  // does not need copy +                    } +                    state = TEMPLATE_BLOCK_START; +                    break; +                } +                state = TEMPLATE_BLOCK_START; +              case TEMPLATE_BLOCK_START:                  if (c == ' ')                      break; @@ -123,6 +174,13 @@ blogc_template_parse(const char *src, size_t src_len, blogc_error_t **err)                      start = current;                      break;                  } +                if (c == '-') { +                    *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, +                        src_len, current, +                        "Invalid statement syntax. Duplicated whitespace " +                        "cleaner before statement."); +                    break; +                }                  *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src,                      src_len, current,                      "Invalid statement syntax. Must begin with lowercase letter."); @@ -149,7 +207,7 @@ blogc_template_parse(const char *src, size_t src_len, blogc_error_t **err)                          (0 == strncmp("endblock", src + start, 8)))                      {                          if (block_state != BLOCK_CLOSED) { -                            state = TEMPLATE_BLOCK_END; +                            state = TEMPLATE_BLOCK_END_WHITESPACE_CLEANER;                              type = BLOGC_TEMPLATE_ENDBLOCK_STMT;                              block_state = BLOCK_CLOSED;                              break; @@ -190,7 +248,7 @@ blogc_template_parse(const char *src, size_t src_len, blogc_error_t **err)                          (0 == strncmp("endif", src + start, 5)))                      {                          if (if_count > 0) { -                            state = TEMPLATE_BLOCK_END; +                            state = TEMPLATE_BLOCK_END_WHITESPACE_CLEANER;                              type = BLOGC_TEMPLATE_ENDIF_STMT;                              if_count--;                              break; @@ -201,11 +259,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_WHITESPACE_CLEANER; +                            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: @@ -230,7 +319,7 @@ blogc_template_parse(const char *src, size_t src_len, blogc_error_t **err)                      {                          block_state = BLOCK_ENTRY;                          end = current; -                        state = TEMPLATE_BLOCK_END; +                        state = TEMPLATE_BLOCK_END_WHITESPACE_CLEANER;                          break;                      }                      else if ((current - start == 7) && @@ -238,7 +327,7 @@ blogc_template_parse(const char *src, size_t src_len, blogc_error_t **err)                      {                          block_state = BLOCK_LISTING;                          end = current; -                        state = TEMPLATE_BLOCK_END; +                        state = TEMPLATE_BLOCK_END_WHITESPACE_CLEANER;                          break;                      }                      else if ((current - start == 12) && @@ -246,7 +335,7 @@ blogc_template_parse(const char *src, size_t src_len, blogc_error_t **err)                      {                          block_state = BLOCK_LISTING_ONCE;                          end = current; -                        state = TEMPLATE_BLOCK_END; +                        state = TEMPLATE_BLOCK_END_WHITESPACE_CLEANER;                          break;                      }                  } @@ -277,7 +366,7 @@ blogc_template_parse(const char *src, size_t src_len, blogc_error_t **err)                      if (type == BLOGC_TEMPLATE_IF_STMT)                          state = TEMPLATE_BLOCK_IF_OPERATOR_START;                      else -                        state = TEMPLATE_BLOCK_END; +                        state = TEMPLATE_BLOCK_END_WHITESPACE_CLEANER;                      break;                  }                  *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, @@ -304,21 +393,22 @@ blogc_template_parse(const char *src, size_t src_len, blogc_error_t **err)              case TEMPLATE_BLOCK_IF_OPERAND_START:                  if (c == ' ')                      break; -                if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_') { +                if (c >= 'A' && c <= 'Z') {                      state = TEMPLATE_BLOCK_IF_VARIABLE_OPERAND;                      start2 = current;                      break;                  } -                if (c != '"') { -                    op_start = 0; -                    op_end = 0; -                    *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, -                        src_len, current, -                        "Invalid 'if' operand. Must be double-quoted static string."); +                if (c == '"') { +                    state = TEMPLATE_BLOCK_IF_STRING_OPERAND; +                    start2 = current;                      break;                  } -                state = TEMPLATE_BLOCK_IF_STRING_OPERAND; -                start2 = current; +                op_start = 0; +                op_end = 0; +                *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, +                    src_len, current, +                    "Invalid 'if' operand. Must be double-quoted static " +                    "string or variable.");                  break;              case TEMPLATE_BLOCK_IF_STRING_OPERAND: @@ -326,24 +416,67 @@ blogc_template_parse(const char *src, size_t src_len, blogc_error_t **err)                      break;                  if (c == '"' && src[current - 1] == '\\')                      break; -                state = TEMPLATE_BLOCK_END; +                state = TEMPLATE_BLOCK_END_WHITESPACE_CLEANER;                  end2 = current + 1;                  break;             case TEMPLATE_BLOCK_IF_VARIABLE_OPERAND:                  if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_')                      break; -                state = TEMPLATE_BLOCK_END; +                state = TEMPLATE_BLOCK_END_WHITESPACE_CLEANER;                  end2 = current;                  break; -            case TEMPLATE_BLOCK_END: +            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_WHITESPACE_CLEANER; +                    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_WHITESPACE_CLEANER: +                if (c == ' ') +                    break; +                if (c == '-') { +                    lstrip_next = true; +                    state = TEMPLATE_BLOCK_END; +                    break; +                } +                state = TEMPLATE_BLOCK_END; + +            case TEMPLATE_BLOCK_END:                  if (c == '%') {                      state = TEMPLATE_CLOSE_BRACKET;                      break;                  } +                if (c == '-') { +                    *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src, +                        src_len, current, +                        "Invalid statement syntax. Duplicated whitespace " +                        "cleaner after statement."); +                    break; +                }                  *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src,                      src_len, current,                      "Invalid statement syntax. Must end with '%%}'."); @@ -439,6 +572,7 @@ blogc_template_parse(const char *src, size_t src_len, blogc_error_t **err)                          end2 = 0;                      }                      stmts = b_slist_append(stmts, stmt); +                    previous = stmt;                      stmt = NULL;                      state = TEMPLATE_START;                      type = BLOGC_TEMPLATE_CONTENT_STMT; @@ -469,6 +603,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..6cd2c80 100644 --- a/src/template-parser.h +++ b/src/template-parser.h @@ -1,6 +1,6 @@  /*   * blogc: A blog compiler. - * Copyright (C) 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>   *   * This program can be distributed under the terms of the BSD License.   * See the file LICENSE. @@ -12,11 +12,18 @@  #include "utils/utils.h"  #include "error.h" +/* + * note: whitespace cleaners are NOT added to ast. we fix strings right during + * template parsing. renderer does not need to care about it, for the sake of + * simplicity. + */  typedef enum {      BLOGC_TEMPLATE_IFDEF_STMT = 1,      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/src/utils/mem.c b/src/utils/mem.c index 7c5e0a2..693d555 100644 --- a/src/utils/mem.c +++ b/src/utils/mem.c @@ -1,6 +1,6 @@  /*   * blogc: A blog compiler. - * Copyright (C) 2014-2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * Copyright (C) 2014-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>   *   * This program can be distributed under the terms of the BSD License.   * See the file LICENSE. diff --git a/src/utils/slist.c b/src/utils/slist.c index 3d9b892..9753aa7 100644 --- a/src/utils/slist.c +++ b/src/utils/slist.c @@ -1,6 +1,6 @@  /*   * blogc: A blog compiler. - * Copyright (C) 2014-2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * Copyright (C) 2014-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>   *   * This program can be distributed under the terms of the BSD License.   * See the file LICENSE. diff --git a/src/utils/strings.c b/src/utils/strings.c index 40174a1..3151612 100644 --- a/src/utils/strings.c +++ b/src/utils/strings.c @@ -1,6 +1,6 @@  /*   * blogc: A blog compiler. - * Copyright (C) 2014-2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * Copyright (C) 2014-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>   *   * This program can be distributed under the terms of the BSD License.   * See the file LICENSE. @@ -103,22 +103,46 @@ b_str_ends_with(const char *str, const char *suffix)  char* -b_str_strip(char *str) +b_str_lstrip(char *str)  {      if (str == NULL) -        return str; +        return NULL; +    int i; +    size_t str_len = strlen(str); +    for (i = 0; i < str_len; i++) { +        if ((str[i] != ' ') && (str[i] != '\t') && (str[i] != '\n') && +            (str[i] != '\r') && (str[i] != '\t') && (str[i] != '\f') && +            (str[i] != '\v')) +        { +            str += i; +            break; +        } +        if (i == str_len - 1) { +            str += str_len; +            break; +        } +    } +    return str; +} + + +char* +b_str_rstrip(char *str) +{ +    if (str == NULL) +        return NULL;      int i;      size_t str_len = strlen(str);      for (i = str_len - 1; i >= 0; i--) { -        if (!isspace(str[i])) { +        if ((str[i] != ' ') && (str[i] != '\t') && (str[i] != '\n') && +            (str[i] != '\r') && (str[i] != '\t') && (str[i] != '\f') && +            (str[i] != '\v')) +        {              str[i + 1] = '\0';              break;          } -    } -    str_len = strlen(str); -    for (i = 0; i < str_len; i++) { -        if (!isspace(str[i])) { -            str = str + i; +        if (i == 0) { +            str[0] = '\0';              break;          }      } @@ -126,6 +150,13 @@ b_str_strip(char *str)  } +char* +b_str_strip(char *str) +{ +    return b_str_lstrip(b_str_rstrip(str)); +} + +  char**  b_str_split(const char *str, char c, unsigned int max_pieces)  { diff --git a/src/utils/trie.c b/src/utils/trie.c index db64e1d..2e6a0d8 100644 --- a/src/utils/trie.c +++ b/src/utils/trie.c @@ -1,6 +1,6 @@  /*   * blogc: A blog compiler. - * Copyright (C) 2014-2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * Copyright (C) 2014-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>   *   * This program can be distributed under the terms of the BSD License.   * See the file LICENSE. diff --git a/src/utils/utils.h b/src/utils/utils.h index 5a1505b..dc67497 100644 --- a/src/utils/utils.h +++ b/src/utils/utils.h @@ -1,6 +1,6 @@  /*   * blogc: A blog compiler. - * Copyright (C) 2014-2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * Copyright (C) 2014-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>   *   * This program can be distributed under the terms of the BSD License.   * See the file LICENSE. @@ -50,6 +50,8 @@ char* b_strdup_vprintf(const char *format, va_list ap);  char* b_strdup_printf(const char *format, ...);  bool b_str_starts_with(const char *str, const char *prefix);  bool b_str_ends_with(const char *str, const char *suffix); +char* b_str_lstrip(char *str); +char* b_str_rstrip(char *str);  char* b_str_strip(char *str);  char** b_str_split(const char *str, char c, unsigned int max_pieces);  char* b_str_replace(const char *str, const char search, const char *replace); diff --git a/tests/check_content_parser.c b/tests/check_content_parser.c index 817edba..165810d 100644 --- a/tests/check_content_parser.c +++ b/tests/check_content_parser.c @@ -1,6 +1,6 @@  /*   * blogc: A blog compiler. - * Copyright (C) 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>   *   * This program can be distributed under the terms of the BSD License.   * See the file LICENSE. @@ -50,6 +50,17 @@ test_slugify(void **state)  static void +test_htmlentities(void **state) +{ +    char *s = blogc_htmlentities(NULL); +    assert_null(s); +    s = blogc_htmlentities("asdxcv & < > \" 'sfd/gf"); +    assert_string_equal(s, "asdxcv & < > " 'sfd/gf"); +    free(s); +} + + +static void  test_is_ordered_list_item(void **state)  {      assert_true(blogc_is_ordered_list_item("1.bola", 2)); @@ -88,7 +99,7 @@ test_content_parse(void **state)          ">  \n"          ">    asd\n"          "\n" -        "    bola\n" +        "    <asd>bola</asd>\n"          "     asd\n"          "    qwewer\n"          "\n" @@ -123,7 +134,7 @@ test_content_parse(void **state)          "buga</p>\n"          "<pre><code>asd</code></pre>\n"          "</blockquote>\n" -        "<pre><code>bola\n" +        "<pre><code><asd>bola</asd>\n"          " asd\n"          "qwewer</code></pre>\n"          "<hr />\n" @@ -166,7 +177,7 @@ test_content_parse_crlf(void **state)          ">  \r\n"          ">    asd\r\n"          "\r\n" -        "    bola\r\n" +        "    <asd>bola</asd>\r\n"          "     asd\r\n"          "    qwewer\r\n"          "\r\n" @@ -201,7 +212,7 @@ test_content_parse_crlf(void **state)          "buga</p>\r\n"          "<pre><code>asd</code></pre>\r\n"          "</blockquote>\r\n" -        "<pre><code>bola\r\n" +        "<pre><code><asd>bola</asd>\r\n"          " asd\r\n"          "qwewer</code></pre>\r\n"          "<hr />\r\n" @@ -2607,6 +2618,7 @@ main(void)  {      const UnitTest tests[] = {          unit_test(test_slugify), +        unit_test(test_htmlentities),          unit_test(test_is_ordered_list_item),          unit_test(test_content_parse),          unit_test(test_content_parse_crlf), diff --git a/tests/check_datetime_parser.c b/tests/check_datetime_parser.c index 1ac976d..ba5a79d 100644 --- a/tests/check_datetime_parser.c +++ b/tests/check_datetime_parser.c @@ -1,6 +1,6 @@  /*   * blogc: A blog compiler. - * Copyright (C) 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>   *   * This program can be distributed under the terms of the BSD License.   * See the file LICENSE. diff --git a/tests/check_error.c b/tests/check_error.c index 17e1c40..d3af9c0 100644 --- a/tests/check_error.c +++ b/tests/check_error.c @@ -1,6 +1,6 @@  /*   * blogc: A blog compiler. - * Copyright (C) 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>   *   * This program can be distributed under the terms of the BSD License.   * See the file LICENSE. diff --git a/tests/check_loader.c b/tests/check_loader.c index f5be3e7..ac8bdb3 100644 --- a/tests/check_loader.c +++ b/tests/check_loader.c @@ -1,6 +1,6 @@  /*   * blogc: A blog compiler. - * Copyright (C) 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>   *   * This program can be distributed under the terms of the BSD License.   * See the file LICENSE. @@ -478,7 +478,7 @@ test_source_parse_from_files_filter_by_page_and_tag(void **state)      will_return(__wrap_blogc_file_get_contents, b_strdup(          "ASD: 789\n"          "DATE: 2003-02-03 04:05:06\n" -        "TAGS: chunda, bola\n" +        "TAGS: chunda bola\n"          "--------\n"          "bola"));      will_return(__wrap_blogc_file_get_contents, "bola4.txt"); @@ -505,7 +505,7 @@ test_source_parse_from_files_filter_by_page_and_tag(void **state)      will_return(__wrap_blogc_file_get_contents, b_strdup(          "ASD: 7894\n"          "DATE: 2007-02-03 04:05:06\n" -        "TAGS: yay, chunda\n" +        "TAGS: yay chunda\n"          "--------\n"          "bola"));      blogc_error_t *err = NULL; diff --git a/tests/check_renderer.c b/tests/check_renderer.c index cb8f8f1..d6bc947 100644 --- a/tests/check_renderer.c +++ b/tests/check_renderer.c @@ -1,6 +1,6 @@  /*   * blogc: A blog compiler. - * Copyright (C) 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>   *   * This program can be distributed under the terms of the BSD License.   * See the file LICENSE. @@ -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,9 @@ 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" +        "{% foreach TAGS_ASD %}yay{% endforeach %}\n";      blogc_error_t *err = NULL;      b_slist_t *l = blogc_template_parse(str, strlen(str), &err);      assert_non_null(l); @@ -97,7 +100,9 @@ test_render_entry(void **state)          "HEHE\n"          "LOL2\n"          "\n" -        "LOL4\n"); +        "LOL4\n" +        "lol foo haha lol bar haha lol baz haha \n" +        "\n");      blogc_template_free_stmts(l);      b_slist_free_full(s, (b_free_func_t) b_trie_free);      free(out); @@ -117,6 +122,8 @@ 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" +        "{% foreach TAGS_ASD %}yay{% endforeach %}\n"          "{% endblock %}\n";      blogc_error_t *err = NULL;      b_slist_t *l = blogc_template_parse(str, strlen(str), &err); @@ -132,12 +139,18 @@ test_render_listing(void **state)          "\n"          "03:04\n"          "bola: asd\n" +        "lol foo haha lol bar haha lol baz haha \n" +        "\n"          "\n"          "2014-02-03 04:05:06\n"          "bola: asd2\n"          "\n" +        "\n" +        "\n"          "2013-01-02 03:04:05\n"          "bola: asd3\n" +        "\n" +        "\n"          "\n");      blogc_template_free_stmts(l);      b_slist_free_full(s, (b_free_func_t) b_trie_free); @@ -158,6 +171,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 +533,55 @@ 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_foreach_if(void **state) +{ +    const char *str = +        "{% block entry %}\n" +        "{% foreach TAGS %} {% if FOREACH_ITEM == \"bar\" %}{{ FOREACH_ITEM }}" +        "{% endif %} {% 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" +        "   bar   \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)); @@ -624,6 +687,37 @@ test_render_respect_variable_scope(void **state)  static void +test_render_ifcount_bug(void **state) +{ +    const char *str = +        "{% block entry %}\n" +        "{% ifdef TITLE %}<h3>{{ TITLE }}</h3>{% endif %}\n" +        "{% ifdef IS_POST %}\n" +        "{% ifdef ASD %}ASD{% endif %}\n" +        "{% endif %}\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 = NULL; +    s = b_slist_append(s, b_trie_new(free)); +    b_trie_insert(s->data, "TITLE", b_strdup("bola")); +    b_trie_t *c = b_trie_new(free); +    char *out = blogc_render(l, s, c, false); +    assert_string_equal(out, +        "\n" +        "<h3>bola</h3>\n" +        "\n" +        "\n"); +    b_trie_free(c); +    blogc_template_free_stmts(l); +    b_slist_free_full(s, (b_free_func_t) b_trie_free); +    free(out); +} + + +static void  test_get_variable(void **state)  {      b_trie_t *g = b_trie_new(free); @@ -732,13 +826,13 @@ test_format_variable(void **state)      b_trie_t *l = b_trie_new(free);      b_trie_insert(l, "NAME", b_strdup("chunda"));      b_trie_insert(l, "TITLE", b_strdup("chunda2")); -    char *tmp = blogc_format_variable("NAME", g, l); +    char *tmp = blogc_format_variable("NAME", g, l, NULL);      assert_string_equal(tmp, "chunda");      free(tmp); -    tmp = blogc_format_variable("TITLE", g, l); +    tmp = blogc_format_variable("TITLE", g, l, NULL);      assert_string_equal(tmp, "chunda2");      free(tmp); -    assert_null(blogc_format_variable("BOLA", g, l)); +    assert_null(blogc_format_variable("BOLA", g, l, NULL));      b_trie_free(g);      b_trie_free(l);  } @@ -752,7 +846,7 @@ test_format_variable_with_date(void **state)      b_trie_insert(g, "DATE_FORMAT", b_strdup("%R"));      b_trie_t *l = b_trie_new(free);      b_trie_insert(l, "DATE", b_strdup("2011-12-13 14:15:16")); -    char *tmp = blogc_format_variable("DATE_FORMATTED", g, l); +    char *tmp = blogc_format_variable("DATE_FORMATTED", g, l, NULL);      assert_string_equal(tmp, "14:15");      free(tmp);      b_trie_free(g); @@ -760,6 +854,57 @@ test_format_variable_with_date(void **state)  } +static void +test_format_variable_foreach(void **state) +{ +    b_slist_t *l = NULL; +    l = b_slist_append(l, b_strdup("asd")); +    l = b_slist_append(l, b_strdup("qwe")); +    char *tmp = blogc_format_variable("FOREACH_ITEM", NULL, NULL, l->next); +    assert_string_equal(tmp, "qwe"); +    free(tmp); +    b_slist_free_full(l, free); +} + + +static void +test_format_variable_foreach_empty(void **state) +{ +    assert_null(blogc_format_variable("FOREACH_ITEM", NULL, NULL, NULL)); +} + + +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,10 +922,13 @@ 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_foreach_if),          unit_test(test_render_null),          unit_test(test_render_outside_block),          unit_test(test_render_prefer_local_variable),          unit_test(test_render_respect_variable_scope), +        unit_test(test_render_ifcount_bug),          unit_test(test_get_variable),          unit_test(test_get_variable_only_local),          unit_test(test_get_variable_only_global), @@ -790,6 +938,10 @@ main(void)          unit_test(test_format_date_without_date),          unit_test(test_format_variable),          unit_test(test_format_variable_with_date), +        unit_test(test_format_variable_foreach), +        unit_test(test_format_variable_foreach_empty), +        unit_test(test_split_list_variable), +        unit_test(test_split_list_variable_not_found),      };      return run_tests(tests);  } diff --git a/tests/check_source_parser.c b/tests/check_source_parser.c index 4d8518e..8d6c039 100644 --- a/tests/check_source_parser.c +++ b/tests/check_source_parser.c @@ -1,6 +1,6 @@  /*   * blogc: A blog compiler. - * Copyright (C) 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>   *   * This program can be distributed under the terms of the BSD License.   * See the file LICENSE. diff --git a/tests/check_template_parser.c b/tests/check_template_parser.c index b712f22..f655896 100644 --- a/tests/check_template_parser.c +++ b/tests/check_template_parser.c @@ -1,6 +1,6 @@  /*   * blogc: A blog compiler. - * Copyright (C) 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>   *   * This program can be distributed under the terms of the BSD License.   * See the file LICENSE. @@ -51,26 +51,27 @@ test_template_parse(void **state)      const char *a =          "Test\n"          "\n" -        "    {% block entry %}\n" +        "    {%- block entry -%}\n"          "{% ifdef CHUNDA %}\n"          "bola\n"          "{% endif %}\n"          "{% ifndef BOLA %}\n"          "bolao\n" -        "{% endif %}\n" +        "{%- endif %}\n"          "{% 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);      assert_null(err);      assert_non_null(stmts); -    blogc_assert_template_stmt(stmts, "Test\n\n    ", +    blogc_assert_template_stmt(stmts, "Test",          BLOGC_TEMPLATE_CONTENT_STMT);      blogc_assert_template_stmt(stmts->next, "entry",          BLOGC_TEMPLATE_BLOCK_STMT); -    blogc_assert_template_stmt(stmts->next->next, "\n", +    blogc_assert_template_stmt(stmts->next->next, "",          BLOGC_TEMPLATE_CONTENT_STMT);      blogc_assert_template_stmt(stmts->next->next->next, "CHUNDA",          BLOGC_TEMPLATE_IFDEF_STMT); @@ -82,7 +83,7 @@ test_template_parse(void **state)          BLOGC_TEMPLATE_CONTENT_STMT);      b_slist_t *tmp = stmts->next->next->next->next->next->next->next;      blogc_assert_template_stmt(tmp, "BOLA", BLOGC_TEMPLATE_IFNDEF_STMT); -    blogc_assert_template_stmt(tmp->next, "\nbolao\n", BLOGC_TEMPLATE_CONTENT_STMT); +    blogc_assert_template_stmt(tmp->next, "\nbolao", BLOGC_TEMPLATE_CONTENT_STMT);      blogc_assert_template_stmt(tmp->next->next, NULL, BLOGC_TEMPLATE_ENDIF_STMT);      blogc_assert_template_stmt(tmp->next->next->next, "\n",          BLOGC_TEMPLATE_CONTENT_STMT); @@ -104,13 +105,22 @@ test_template_parse(void **state)      blogc_assert_template_stmt(tmp->next->next->next->next->next->next->next->next,          NULL, BLOGC_TEMPLATE_ENDBLOCK_STMT);      blogc_assert_template_stmt(tmp->next->next->next->next->next->next->next->next->next, -        "\n", BLOGC_TEMPLATE_CONTENT_STMT); +        "", 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);  } @@ -121,26 +131,27 @@ test_template_parse_crlf(void **state)      const char *a =          "Test\r\n"          "\r\n" -        "    {% block entry %}\r\n" +        "    {%- block entry -%}\r\n"          "{% ifdef CHUNDA %}\r\n"          "bola\r\n"          "{% endif %}\r\n"          "{% ifndef BOLA %}\r\n"          "bolao\r\n" -        "{% endif %}\r\n" +        "{%- endif %}\r\n"          "{% 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);      assert_null(err);      assert_non_null(stmts); -    blogc_assert_template_stmt(stmts, "Test\r\n\r\n    ", +    blogc_assert_template_stmt(stmts, "Test",          BLOGC_TEMPLATE_CONTENT_STMT);      blogc_assert_template_stmt(stmts->next, "entry",          BLOGC_TEMPLATE_BLOCK_STMT); -    blogc_assert_template_stmt(stmts->next->next, "\r\n", +    blogc_assert_template_stmt(stmts->next->next, "",          BLOGC_TEMPLATE_CONTENT_STMT);      blogc_assert_template_stmt(stmts->next->next->next, "CHUNDA",          BLOGC_TEMPLATE_IFDEF_STMT); @@ -152,7 +163,7 @@ test_template_parse_crlf(void **state)          BLOGC_TEMPLATE_CONTENT_STMT);      b_slist_t *tmp = stmts->next->next->next->next->next->next->next;      blogc_assert_template_stmt(tmp, "BOLA", BLOGC_TEMPLATE_IFNDEF_STMT); -    blogc_assert_template_stmt(tmp->next, "\r\nbolao\r\n", BLOGC_TEMPLATE_CONTENT_STMT); +    blogc_assert_template_stmt(tmp->next, "\r\nbolao", BLOGC_TEMPLATE_CONTENT_STMT);      blogc_assert_template_stmt(tmp->next->next, NULL, BLOGC_TEMPLATE_ENDIF_STMT);      blogc_assert_template_stmt(tmp->next->next->next, "\r\n",          BLOGC_TEMPLATE_CONTENT_STMT); @@ -174,13 +185,22 @@ test_template_parse_crlf(void **state)      blogc_assert_template_stmt(tmp->next->next->next->next->next->next->next->next,          NULL, BLOGC_TEMPLATE_ENDBLOCK_STMT);      blogc_assert_template_stmt(tmp->next->next->next->next->next->next->next->next->next, -        "\r\n", BLOGC_TEMPLATE_CONTENT_STMT); +        "", 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);  } @@ -361,6 +381,26 @@ test_template_parse_invalid_block_start(void **state)          "Invalid statement syntax. Must begin with lowercase letter.\n"          "Error occurred near line 1, position 4: {% ASD %}");      blogc_error_free(err); +    a = "{%-- block entry %}\n"; +    err = NULL; +    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 statement syntax. Duplicated whitespace cleaner before statement.\n" +        "Error occurred near line 1, position 4: {%-- block entry %}"); +    blogc_error_free(err); +    a = "{% block entry --%}\n"; +    err = NULL; +    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 statement syntax. Duplicated whitespace cleaner after statement.\n" +        "Error occurred near line 1, position 17: {% block entry --%}"); +    blogc_error_free(err);  } @@ -383,6 +423,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 +474,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 +500,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 +556,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 +590,57 @@ test_template_parse_invalid_ifdef_variable(void **state)  static void +test_template_parse_invalid_ifdef_variable2(void **state) +{ +    const char *a = "{% block entry %}{% ifdef 0123 %}\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 variable name. Must begin with uppercase letter.\n" +        "Error occurred near line 1, position 27: " +        "{% block entry %}{% ifdef 0123 %}"); +    blogc_error_free(err); +} + + +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_foreach_variable2(void **state) +{ +    const char *a = "{% block entry %}{% foreach 0123 %}\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 0123 %}"); +    blogc_error_free(err); +} + + +static void  test_template_parse_invalid_if_operator(void **state)  {      const char *a = "{% block entry %}{% if BOLA = \"asd\" %}\n"; @@ -525,7 +667,7 @@ test_template_parse_invalid_if_operand(void **state)      assert_null(stmts);      assert_int_equal(err->type, BLOGC_ERROR_TEMPLATE_PARSER);      assert_string_equal(err->msg, -        "Invalid 'if' operand. Must be double-quoted static string.\n" +        "Invalid 'if' operand. Must be double-quoted static string or variable.\n"          "Error occurred near line 1, position 32: "          "{% block entry %}{% if BOLA == asd %}");      blogc_error_free(err); @@ -550,6 +692,23 @@ test_template_parse_invalid_if_operand2(void **state)  static void +test_template_parse_invalid_if_operand3(void **state) +{ +    const char *a = "{% block entry %}{% if BOLA == 0123 %}\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 'if' operand. Must be double-quoted static string or variable.\n" +        "Error occurred near line 1, position 32: " +        "{% block entry %}{% if BOLA == 0123 %}"); +    blogc_error_free(err); +} + + +static void  test_template_parse_invalid_block_end(void **state)  {      const char *a = "{% block entry }}\n"; @@ -600,6 +759,23 @@ test_template_parse_invalid_variable_name2(void **state)  static void +test_template_parse_invalid_variable_name3(void **state) +{ +    const char *a = "{% block entry %}{{ 0123 }}{% endblock %}\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 variable name. Must begin with uppercase letter.\n" +        "Error occurred near line 1, position 21: {% block entry %}{{ 0123 }}" +        "{% endblock %}"); +    blogc_error_free(err); +} + + +static void  test_template_parse_invalid_variable_end(void **state)  {      const char *a = "{% block entry %}{{ BOLA %}{% endblock %}\n"; @@ -678,6 +854,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,24 +878,33 @@ 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_ifdef_variable2), +        unit_test(test_template_parse_invalid_foreach_variable), +        unit_test(test_template_parse_invalid_foreach_variable2),          unit_test(test_template_parse_invalid_if_operator),          unit_test(test_template_parse_invalid_if_operand),          unit_test(test_template_parse_invalid_if_operand2), +        unit_test(test_template_parse_invalid_if_operand3),          unit_test(test_template_parse_invalid_block_end),          unit_test(test_template_parse_invalid_variable_name),          unit_test(test_template_parse_invalid_variable_name2), +        unit_test(test_template_parse_invalid_variable_name3),          unit_test(test_template_parse_invalid_variable_end),          unit_test(test_template_parse_invalid_close),          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);  } diff --git a/tests/check_utils.c b/tests/check_utils.c index a42c75a..a511dda 100644 --- a/tests/check_utils.c +++ b/tests/check_utils.c @@ -1,6 +1,6 @@  /*   * blogc: A blog compiler. - * Copyright (C) 2014-2015 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * Copyright (C) 2014-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br>   *   * This program can be distributed under the terms of the BSD License.   * See the file LICENSE. @@ -129,6 +129,50 @@ test_str_ends_with(void **state)  static void +test_str_lstrip(void **state) +{ +    char *str = b_strdup("  \tbola\n  \t"); +    assert_string_equal(b_str_lstrip(str), "bola\n  \t"); +    free(str); +    str = b_strdup("guda"); +    assert_string_equal(b_str_lstrip(str), "guda"); +    free(str); +    str = b_strdup("\n"); +    assert_string_equal(b_str_lstrip(str), ""); +    free(str); +    str = b_strdup("\t \n"); +    assert_string_equal(b_str_lstrip(str), ""); +    free(str); +    str = b_strdup(""); +    assert_string_equal(b_str_lstrip(str), ""); +    free(str); +    assert_null(b_str_lstrip(NULL)); +} + + +static void +test_str_rstrip(void **state) +{ +    char *str = b_strdup("  \tbola\n  \t"); +    assert_string_equal(b_str_rstrip(str), "  \tbola"); +    free(str); +    str = b_strdup("guda"); +    assert_string_equal(b_str_rstrip(str), "guda"); +    free(str); +    str = b_strdup("\n"); +    assert_string_equal(b_str_rstrip(str), ""); +    free(str); +    str = b_strdup("\t \n"); +    assert_string_equal(b_str_rstrip(str), ""); +    free(str); +    str = b_strdup(""); +    assert_string_equal(b_str_rstrip(str), ""); +    free(str); +    assert_null(b_str_rstrip(NULL)); +} + + +static void  test_str_strip(void **state)  {      char *str = b_strdup("  \tbola\n  \t"); @@ -137,6 +181,15 @@ test_str_strip(void **state)      str = b_strdup("guda");      assert_string_equal(b_str_strip(str), "guda");      free(str); +    str = b_strdup("\n"); +    assert_string_equal(b_str_strip(str), ""); +    free(str); +    str = b_strdup("\t \n"); +    assert_string_equal(b_str_strip(str), ""); +    free(str); +    str = b_strdup(""); +    assert_string_equal(b_str_strip(str), ""); +    free(str);      assert_null(b_str_strip(NULL));  } @@ -799,6 +852,8 @@ main(void)          unit_test(test_strdup_printf),          unit_test(test_str_starts_with),          unit_test(test_str_ends_with), +        unit_test(test_str_lstrip), +        unit_test(test_str_rstrip),          unit_test(test_str_strip),          unit_test(test_str_split),          unit_test(test_str_replace),  | 
