aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRafael G. Martins <rafael@rafaelmartins.eng.br>2016-01-30 22:45:46 +0100
committerRafael G. Martins <rafael@rafaelmartins.eng.br>2016-01-30 22:45:46 +0100
commitc4d19f731dc683c5a65327d99f2b416d4781f00b (patch)
tree09e8941c5ac840b2a74ca28e5a474b7746e5fe79
parent008c96d6518125f2683c1b4e842ca3ed6e865c3d (diff)
parent3b0ea0a4908f1702308354ec7cbd8db5c80f87ee (diff)
downloadblogc-c4d19f731dc683c5a65327d99f2b416d4781f00b.tar.gz
blogc-c4d19f731dc683c5a65327d99f2b416d4781f00b.tar.bz2
blogc-c4d19f731dc683c5a65327d99f2b416d4781f00b.zip
Merge remote-tracking branch 'origin/master' into feature/directives
-rw-r--r--.gitignore1
-rw-r--r--LICENSE2
-rw-r--r--Makefile.am1
-rw-r--r--blogc.spec.in75
-rwxr-xr-xbuild-aux/build-windows.sh18
-rw-r--r--configure.ac9
-rw-r--r--man/blogc-template.7.ronn53
-rw-r--r--src/content-parser.c44
-rw-r--r--src/content-parser.h3
-rw-r--r--src/datetime-parser.c2
-rw-r--r--src/datetime-parser.h2
-rw-r--r--src/error.c2
-rw-r--r--src/error.h2
-rw-r--r--src/file.c2
-rw-r--r--src/file.h2
-rw-r--r--src/loader.c11
-rw-r--r--src/loader.h2
-rw-r--r--src/main.c2
-rw-r--r--src/renderer.c99
-rw-r--r--src/renderer.h7
-rw-r--r--src/source-parser.c2
-rw-r--r--src/source-parser.h2
-rw-r--r--src/template-parser.c183
-rw-r--r--src/template-parser.h9
-rw-r--r--src/utils/mem.c2
-rw-r--r--src/utils/slist.c2
-rw-r--r--src/utils/strings.c49
-rw-r--r--src/utils/trie.c2
-rw-r--r--src/utils/utils.h4
-rw-r--r--tests/check_content_parser.c22
-rw-r--r--tests/check_datetime_parser.c2
-rw-r--r--tests/check_error.c2
-rw-r--r--tests/check_loader.c6
-rw-r--r--tests/check_renderer.c166
-rw-r--r--tests/check_source_parser.c2
-rw-r--r--tests/check_template_parser.c241
-rw-r--r--tests/check_utils.c57
37 files changed, 965 insertions, 127 deletions
diff --git a/.gitignore b/.gitignore
index 65e51f1..2ccfd38 100644
--- a/.gitignore
+++ b/.gitignore
@@ -60,4 +60,5 @@ blogc-*.tar.*
blogc-*.zip
# rpms
+blogc.spec
blogc-*.rpm
diff --git a/LICENSE b/LICENSE
index 167fbc7..0f0ff43 100644
--- a/LICENSE
+++ b/LICENSE
@@ -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, "&amp;");
+ break;
+ case '<':
+ b_string_append(rv, "&lt;");
+ break;
+ case '>':
+ b_string_append(rv, "&gt;");
+ break;
+ case '"':
+ b_string_append(rv, "&quot;");
+ break;
+ case '\'':
+ b_string_append(rv, "&#x27;");
+ break;
+ case '/':
+ b_string_append(rv, "&#x2F;");
+ 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.
diff --git a/src/file.c b/src/file.c
index d660afc..a4c763a 100644
--- a/src/file.c
+++ b/src/file.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/file.h b/src/file.h
index 00145ee..97e5274 100644
--- a/src/file.h
+++ b/src/file.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/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.
diff --git a/src/main.c b/src/main.c
index 600a131..f3f990b 100644
--- a/src/main.c
+++ b/src/main.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/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 &amp; &lt; &gt; &quot; &#x27;sfd&#x2F;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>&lt;asd&gt;bola&lt;&#x2F;asd&gt;\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>&lt;asd&gt;bola&lt;&#x2F;asd&gt;\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),