Compare commits

..

28 Commits

Author SHA1 Message Date
Dylan Thacker-Smith
d3e4e4c419 Implement tokenization in a C extension. 2014-03-26 03:20:34 -04:00
Florian Weingarten
3dbb35d823 Merge branch 'freeze_all_the_things'
Conflicts:
	History.md
	lib/liquid/tags/assign.rb
	lib/liquid/tags/capture.rb
	lib/liquid/tags/decrement.rb
	lib/liquid/tags/if.rb
2014-03-24 12:39:34 -04:00
Florian Weingarten
44f29a87a8 Update history 2014-03-24 12:35:47 -04:00
Dylan Thacker-Smith
f0afbc27e0 Add regression test for raw tags with open variable tags. 2014-03-24 10:01:03 -04:00
Dylan Thacker-Smith
fdf03076e0 Revert "Merge pull request #325 from Shopify/remove-variable-incomplete-end"
That pull request broke raw tags with open variable tags. E.g.

{% raw %}
{{
{% endraw %}
{{ 1 }}

This reverts commit fbaabf3b59, reversing
changes made to af24d2c2ab.
2014-03-24 09:59:07 -04:00
Dylan Thacker-Smith
d1bfda15e3 Add profile:strict rake task. 2014-03-21 21:54:53 -04:00
Dylan Thacker-Smith
d8d9984a7b Remove some unused regexes. 2014-03-21 15:50:14 -04:00
Dylan Thacker-Smith
fbaabf3b59 Merge pull request #325 from Shopify/remove-variable-incomplete-end
Allow quoted single curly braces in variables.
2014-03-21 13:48:47 -04:00
Dylan Thacker-Smith
7e0ef867d2 Make tag/variable termination error clearer. 2014-03-21 02:04:01 -04:00
Dylan Thacker-Smith
3682414cc4 Allow quoted single curly braces in variables. 2014-03-21 02:04:01 -04:00
Dylan Thacker-Smith
af24d2c2ab Add missing PR reference and author to a History.md entry. 2014-03-21 02:03:31 -04:00
Dylan Thacker-Smith
4ee43bc5d2 Merge pull request #324 from Shopify/multiline-tags-and-vars
Allow newlines in tags and variables.
2014-03-21 00:26:14 -04:00
Dylan Thacker-Smith
1320a69fca Merge pull request #323 from Shopify/render_bang_in_tests
Use render! in tests to make debugging test failures easier.
2014-03-20 18:33:40 -04:00
Dylan Thacker-Smith
3b14e27f55 Allow newlines in tags and variables. 2014-03-20 17:27:03 -04:00
Dylan Thacker-Smith
face33a137 Merge pull request #321 from Shopify/move-tag-parse-out-of-initialize
Refactor to create tags with a parse class method instead of new.
2014-03-20 16:15:58 -04:00
Dylan Thacker-Smith
d4ecaff8b8 Refactor to create tags with a parse class method instead of new.
By moving parse out of the initializer, we can call super at the start of
the initializers for subclasses, and avoid the nasty allocate hack.
2014-03-20 16:10:10 -04:00
Dylan Thacker-Smith
a5990042ff Use render! in tests to make debugging test failures easier. 2014-03-20 12:04:17 -04:00
Florian Weingarten
e190bbba9e move change to top 2014-03-19 18:15:31 -04:00
Dylan Thacker-Smith
4b5e41d04e Merge pull request #322 from Shopify/use-render-bang-in-benchmarks
Use render! in benchmarks to avoid making it faster by breaking things.
2014-03-19 18:06:15 -04:00
Dylan Thacker-Smith
4b69f6ae83 Use render! in benchmarks to avoid making it faster by breaking things. 2014-03-19 18:01:33 -04:00
Dylan Thacker-Smith
b9feb415f6 Merge pull request #320 from Shopify/move-table-row-tag
Move definition for TableRow to the tags folder.
2014-03-18 17:16:32 -04:00
Dylan Thacker-Smith
92781ec43b Move definition for TableRow to the tags folder. 2014-03-18 17:13:39 -04:00
Dylan Thacker-Smith
ff5c1f83f7 Merge pull request #318 from Shopify/stackprof
Use stackprof for profiling.
2014-03-14 10:27:46 -04:00
Dylan Thacker-Smith
e2b337af2f Merge pull request #317 from Shopify/string-anchors
Use start and end of string rather than line matching in regexes.
2014-03-14 10:17:33 -04:00
Dylan Thacker-Smith
f373b1003d Use stackprof for profiling. 2014-03-14 10:03:50 -04:00
Dylan Thacker-Smith
503d924274 Use start and end of string rather than line matching in regexes. 2014-03-13 17:56:42 -04:00
Florian Weingarten
0a7de51e2b Revert some freezes on non-literals 2014-01-27 14:56:07 -05:00
Florian Weingarten
43ac8d560b Freeze all the things 2014-01-07 12:35:16 -05:00
76 changed files with 744 additions and 1130 deletions

4
.gitignore vendored
View File

@@ -5,6 +5,8 @@ pkg
*.rbc
.rvmrc
.ruby-version
Gemfile.lock
/ext/liquid/Makefile
*.o
*.bundle
/tmp
Gemfile.lock

View File

@@ -3,6 +3,10 @@
## 3.0.0 / not yet released / branch "master"
* ...
* Freeze lots of string literals for new Ruby 2.1 optimization, see #297 [Florian Weingarten, fw42]
* Allow newlines in tags and variables, see #324 [Dylan Thacker-Smith, dylanahsmith]
* Tag#parse is called after initialize, which now takes options instead of tokens as the 3rd argument. See #321 [Dylan Thacker-Smith, dylanahsmith]
* Raise `Liquid::ArgumentError` instead of `::ArgumentError` when filter has wrong number of arguments #309 [Bogdan Gusiev, bogdan]
* Add a to_s default for liquid drops, see #306 [Adam Doeler, releod]
* Add strip, lstrip, and rstrip to standard filters [Florian Weingarten, fw42]
* Make if, for & case tags return complete and consistent nodelists, see #250 [Nick Jones, dntj]
@@ -21,7 +25,6 @@
* Fix clashing method names in enumerable drops, see #238 [Florian Weingarten, fw42]
* Make map filter work on enumerable drops, see #233 [Florian Weingarten, fw42]
* Improved whitespace stripping for blank blocks, related to #216 [Florian Weingarten, fw42]
* Raise `Liquid::ArgumentError` instead of `::ArgumentError` when filter has wrong number of arguments #309 [Bogdan Gusiev, bogdan]
## 2.6.0 / 2013-11-25 / branch "2.6-stable"

View File

@@ -1,6 +1,5 @@
require 'rake'
require 'rake/testtask'
require 'rake/extensiontask'
$LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
require "liquid/version"
@@ -65,13 +64,9 @@ namespace :profile do
ruby "./performance/profile.rb"
end
task :stackprof do
ruby "./performance/stackprof.rb"
end
desc "Run KCacheGrind"
task :grind => :run do
system "qcachegrind /tmp/liquid.rubyprof_calltreeprinter.txt"
desc "Run the liquid profile/performance coverage with strict parsing"
task :strict do
ruby "./performance/profile.rb strict"
end
end
@@ -81,7 +76,10 @@ task :example do
ruby "-w -d -Ilib example/server/server.rb"
end
Rake::ExtensionTask.new "liquid" do |ext|
ext.lib_dir = "lib/liquid"
if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby'
require 'rake/extensiontask'
Rake::ExtensionTask.new "liquid" do |ext|
ext.lib_dir = "lib/liquid"
end
Rake::Task[:test].prerequisites << :compile
end
Rake::Task[:test].prerequisites << :compile

View File

@@ -1,168 +0,0 @@
#include "liquid_ext.h"
VALUE cLiquidBlock;
ID intern_assert_missing_delimitation, intern_block_delimiter, intern_is_blank, intern_new,
intern_new_with_options, intern_tags, intern_unknown_tag, intern_unterminated_tag,
intern_unterminated_variable;
struct liquid_tag
{
char *name, *markup;
long name_length, markup_length;
};
static bool parse_tag(struct liquid_tag *tag, char *token, long token_length)
{
// Strip {{ and }} braces
token += 2;
token_length -= 4;
char *end = token + token_length;
while (token < end && isspace(*token))
token++;
tag->name = token;
char c = *token;
while (token < end && (isalnum(c) || c == '_'))
c = *(++token);
tag->name_length = token - tag->name;
if (!tag->name_length) {
memset(tag, 0, sizeof(*tag));
return false;
}
while (token < end && isspace(*token))
token++;
tag->markup = token;
char *last = end - 1;
while (token < last && isspace(*last))
last--;
end = last + 1;
tag->markup_length = end - token;
return true;
}
static VALUE rb_parse_body(VALUE self, VALUE tokenizerObj)
{
struct liquid_tokenizer *tokenizer = LIQUID_TOKENIZER_GET_STRUCT(tokenizerObj);
bool blank = true;
VALUE nodelist = rb_iv_get(self, "@nodelist");
if (nodelist == Qnil) {
nodelist = rb_ary_new();
rb_iv_set(self, "@nodelist", nodelist);
} else {
rb_ary_clear(nodelist);
}
struct token token;
while (true) {
liquid_tokenizer_next(tokenizer, &token);
switch (token.type) {
case TOKEN_NONE:
/*
* Make sure that it's ok to end parsing in the current block.
* Effectively this method will throw an exception unless the current block is
* of type Document
*/
rb_funcall(self, intern_assert_missing_delimitation, 0);
goto done;
case TOKEN_INVALID:
{
VALUE token_obj = rb_str_new(token.str, token.length);
if (token.str[1] == '%')
rb_funcall(self, intern_unterminated_tag, 1, token_obj);
else
rb_funcall(self, intern_unterminated_variable, 1, token_obj);
break;
}
case TOKEN_TAG:
{
struct liquid_tag tag;
if (!parse_tag(&tag, token.str, token.length)) {
// FIXME: provide more appropriate error message
rb_funcall(self, intern_unterminated_tag, 1, rb_str_new(token.str, token.length));
} else {
if (tag.name_length >= 3 && !memcmp(tag.name, "end", 3)) {
VALUE block_delimiter = rb_funcall(self, intern_block_delimiter, 0);
if (TYPE(block_delimiter) == T_STRING &&
tag.name_length == RSTRING_LEN(block_delimiter) &&
!memcmp(tag.name, RSTRING_PTR(block_delimiter), tag.name_length))
{
goto done;
}
}
VALUE tags = rb_funcall(cLiquidTemplate, intern_tags, 0);
Check_Type(tags, T_HASH);
VALUE tag_name = rb_str_new(tag.name, tag.name_length);
VALUE tag_class = rb_hash_lookup(tags, tag_name);
VALUE markup = rb_str_new(tag.markup, tag.markup_length);
if (tag_class != Qnil) {
VALUE options = rb_iv_get(self, "@options");
if (options == Qnil)
options = rb_hash_new();
VALUE new_tag = rb_funcall(tag_class, intern_new_with_options, 4,
tag_name, markup, tokenizerObj, options);
if (blank) {
VALUE blank_block = rb_funcall(new_tag, intern_is_blank, 0);
if (blank_block == Qnil || blank_block == Qfalse)
blank = false;
}
rb_ary_push(nodelist, new_tag);
} else {
rb_funcall(self, intern_unknown_tag, 3, tag_name, markup, tokenizerObj);
/*
* multi-block tags may store the nodelist in a block array on unknown_tag
* then replace @nodelist with a new array. We need to use the new array
* for the block following the tag token.
*/
nodelist = rb_iv_get(self, "@nodelist");
}
}
break;
}
case TOKEN_VARIABLE:
{
VALUE markup = rb_str_new(token.str + 2, token.length - 4);
VALUE options = rb_iv_get(self, "@options");
VALUE new_var = rb_funcall(cLiquidVariable, intern_new, 2, markup, options);
rb_ary_push(nodelist, new_var);
blank = false;
break;
}
case TOKEN_STRING:
rb_ary_push(nodelist, rb_str_new(token.str, token.length));
if (blank) {
int i;
for (i = 0; i < token.length; i++) {
if (!isspace(token.str[i])) {
blank = false;
break;
}
}
}
break;
}
}
done:
rb_iv_set(self, "@blank", blank ? Qtrue : Qfalse);
return Qnil;
}
void init_liquid_block()
{
intern_assert_missing_delimitation = rb_intern("assert_missing_delimitation!");
intern_block_delimiter = rb_intern("block_delimiter");
intern_is_blank = rb_intern("blank?");
intern_new = rb_intern("new");
intern_new_with_options = rb_intern("new_with_options");
intern_tags = rb_intern("tags");
intern_unknown_tag = rb_intern("unknown_tag");
intern_unterminated_tag = rb_intern("unterminated_tag");
intern_unterminated_variable = rb_intern("unterminated_variable");
cLiquidBlock = rb_define_class_under(mLiquid, "Block", cLiquidTag);
rb_define_method(cLiquidBlock, "parse_body", rb_parse_body, 1);
}

View File

@@ -1,8 +0,0 @@
#ifndef LIQUID_BLOCK_H
#define LIQUID_BLOCK_H
void init_liquid_block();
extern VALUE cLiquidBlock;
#endif

View File

@@ -1,3 +1,4 @@
require 'mkmf'
$CFLAGS << ' -Wall'
$CFLAGS << ' -Wall -Werror'
$warnflags.gsub!(/-Wdeclaration-after-statement/, "")
create_makefile("liquid/liquid")

9
ext/liquid/liquid.c Normal file
View File

@@ -0,0 +1,9 @@
#include "liquid.h"
VALUE mLiquid;
void Init_liquid(void)
{
mLiquid = rb_define_module("Liquid");
init_liquid_tokenizer();
}

11
ext/liquid/liquid.h Normal file
View File

@@ -0,0 +1,11 @@
#ifndef LIQUID_H
#define LIQUID_H
#include <ruby.h>
#include <stdbool.h>
#include "tokenizer.h"
extern VALUE mLiquid;
#endif

View File

@@ -1,16 +0,0 @@
#include "liquid_ext.h"
VALUE mLiquid;
VALUE cLiquidTemplate, cLiquidTag, cLiquidVariable;
void Init_liquid(void)
{
mLiquid = rb_define_module("Liquid");
cLiquidTemplate = rb_define_class_under(mLiquid, "Template", rb_cObject);
cLiquidTag = rb_define_class_under(mLiquid, "Tag", rb_cObject);
cLiquidVariable = rb_define_class_under(mLiquid, "Variable", rb_cObject);
init_liquid_tokenizer();
init_liquid_block();
init_liquid_variable();
}

View File

@@ -1,16 +0,0 @@
#ifndef LIQUID_EXT_H
#define LIQUID_EXT_H
#include <stdbool.h>
#include <ctype.h>
#include <ruby.h>
#include "tokenizer.h"
#include "block.h"
#include "utils.h"
#include "variable.h"
extern VALUE mLiquid;
extern VALUE cLiquidTemplate, cLiquidTag, cLiquidVariable;
#endif

View File

@@ -1,43 +1,66 @@
#include "liquid_ext.h"
#include "liquid.h"
VALUE cLiquidTokenizer;
static void free_tokenizer(void *ptr)
static void tokenizer_mark(void *ptr) {
tokenizer_t *tokenizer = ptr;
rb_gc_mark(tokenizer->source);
}
static void tokenizer_free(void *ptr)
{
struct liquid_tokenizer *tokenizer = ptr;
tokenizer_t *tokenizer = ptr;
xfree(tokenizer);
}
static VALUE rb_allocate(VALUE klass)
static size_t tokenizer_memsize(const void *ptr)
{
return ptr ? sizeof(tokenizer_t) : 0;
}
const rb_data_type_t tokenizer_data_type = {
"liquid_tokenizer",
{tokenizer_mark, tokenizer_free, tokenizer_memsize,},
#ifdef RUBY_TYPED_FREE_IMMEDIATELY
NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY
#endif
};
static VALUE tokenizer_allocate(VALUE klass)
{
VALUE obj;
struct liquid_tokenizer *tokenizer;
tokenizer_t *tokenizer;
obj = Data_Make_Struct(klass, struct liquid_tokenizer, NULL, free_tokenizer, tokenizer);
obj = TypedData_Make_Struct(klass, tokenizer_t, &tokenizer_data_type, tokenizer);
tokenizer->source = Qnil;
return obj;
}
static VALUE rb_initialize(VALUE self, VALUE source)
static VALUE tokenizer_initialize_method(VALUE self, VALUE source)
{
struct liquid_tokenizer *tokenizer;
tokenizer_t *tokenizer;
Check_Type(source, T_STRING);
Data_Get_Struct(self, struct liquid_tokenizer, tokenizer);
Tokenizer_Get_Struct(self, tokenizer);
source = rb_str_dup_frozen(source);
tokenizer->source = source;
tokenizer->cursor = RSTRING_PTR(source);
tokenizer->length = RSTRING_LEN(source);
return Qnil;
}
void liquid_tokenizer_next(struct liquid_tokenizer *tokenizer, struct token *token)
void tokenizer_next(tokenizer_t *tokenizer, token_t *token)
{
if (tokenizer->length <= 0) {
memset(token, 0, sizeof(*token));
return;
}
token->type = TOKEN_STRING;
char *cursor = tokenizer->cursor;
char *last = tokenizer->cursor + tokenizer->length - 1;
const char *cursor = tokenizer->cursor;
const char *last = cursor + tokenizer->length - 1;
token->str = cursor;
token->type = TOKEN_STRING;
while (cursor < last) {
if (*cursor++ != '{')
@@ -51,7 +74,6 @@ void liquid_tokenizer_next(struct liquid_tokenizer *tokenizer, struct token *tok
cursor -= 2;
goto found;
}
char *incomplete_end = cursor;
token->type = TOKEN_INVALID;
if (c == '%') {
while (cursor < last) {
@@ -65,38 +87,40 @@ void liquid_tokenizer_next(struct liquid_tokenizer *tokenizer, struct token *tok
token->type = TOKEN_TAG;
goto found;
}
cursor = incomplete_end;
// unterminated tag
cursor = tokenizer->cursor + 2;
goto found;
} else {
while (cursor < last) {
if (*cursor++ != '}')
continue;
if (*cursor++ != '}') {
incomplete_end = cursor - 1;
continue;
// variable incomplete end, used to end raw tags
cursor--;
goto found;
}
token->type = TOKEN_VARIABLE;
goto found;
}
cursor = incomplete_end;
// unterminated variable
cursor = tokenizer->cursor + 2;
goto found;
}
}
cursor = last + 1;
found:
token->str = tokenizer->cursor;
token->length = cursor - tokenizer->cursor;
tokenizer->cursor += token->length;
tokenizer->length -= token->length;
}
static VALUE rb_next(VALUE self)
static VALUE tokenizer_next_method(VALUE self)
{
struct liquid_tokenizer *tokenizer;
Data_Get_Struct(self, struct liquid_tokenizer, tokenizer);
tokenizer_t *tokenizer;
Tokenizer_Get_Struct(self, tokenizer);
struct token token;
liquid_tokenizer_next(tokenizer, &token);
token_t token;
tokenizer_next(tokenizer, &token);
if (token.type == TOKEN_NONE)
return Qnil;
@@ -106,8 +130,8 @@ static VALUE rb_next(VALUE self)
void init_liquid_tokenizer()
{
cLiquidTokenizer = rb_define_class_under(mLiquid, "Tokenizer", rb_cObject);
rb_define_alloc_func(cLiquidTokenizer, rb_allocate);
rb_define_method(cLiquidTokenizer, "initialize", rb_initialize, 1);
rb_define_method(cLiquidTokenizer, "next", rb_next, 0);
rb_define_alloc_func(cLiquidTokenizer, tokenizer_allocate);
rb_define_method(cLiquidTokenizer, "initialize", tokenizer_initialize_method, 1);
rb_define_method(cLiquidTokenizer, "next", tokenizer_next_method, 0);
rb_define_alias(cLiquidTokenizer, "shift", "next");
}

View File

@@ -1,8 +1,6 @@
#ifndef LIQUID_TOKENIZER_H
#define LIQUID_TOKENIZER_H
extern VALUE cLiquidTokenizer;
enum token_type {
TOKEN_NONE,
TOKEN_INVALID,
@@ -11,20 +9,23 @@ enum token_type {
TOKEN_VARIABLE
};
struct token {
typedef struct token {
enum token_type type;
char *str;
int length;
};
const char *str;
long length;
} token_t;
struct liquid_tokenizer {
char *cursor;
int length;
};
typedef struct tokenizer {
VALUE source;
const char *cursor;
long length;
} tokenizer_t;
extern VALUE cLiquidTokenizer;
extern const rb_data_type_t tokenizer_data_type;
#define Tokenizer_Get_Struct(obj, sval) TypedData_Get_Struct(obj, tokenizer_t, &tokenizer_data_type, sval)
void init_liquid_tokenizer();
void liquid_tokenizer_next(struct liquid_tokenizer *tokenizer, struct token *token);
#define LIQUID_TOKENIZER_GET_STRUCT(obj) ((struct liquid_tokenizer *)obj_get_data_ptr(obj, cLiquidTokenizer))
void tokenizer_next(tokenizer_t *tokenizer, token_t *token);
#endif

View File

@@ -1,21 +0,0 @@
#include <ruby.h>
void raise_type_error(VALUE expected, VALUE got)
{
rb_raise(rb_eTypeError, "wrong argument type %s (expected %s)",
rb_class2name(got), rb_class2name(expected));
}
void check_class(VALUE obj, int type, VALUE klass)
{
Check_Type(obj, type);
VALUE obj_klass = RBASIC_CLASS(obj);
if (obj_klass != klass)
raise_type_error(klass, obj_klass);
}
void *obj_get_data_ptr(VALUE obj, VALUE klass)
{
check_class(obj, T_DATA, klass);
return DATA_PTR(obj);
}

View File

@@ -1,8 +0,0 @@
#ifndef LIQUID_UTILS_H
#define LIQUID_UTILS_H
void raise_type_error(VALUE expected, VALUE got);
void check_class(VALUE klass);
void *obj_get_data_ptr(VALUE obj, VALUE klass);
#endif

View File

@@ -1,179 +0,0 @@
#include "liquid_ext.h"
VALUE cLiquidVariable;
extern VALUE mLiquid;
static void free_variable(void *ptr)
{
struct liquid_variable *variable = ptr;
xfree(variable);
}
static VALUE rb_variable_allocate(VALUE klass)
{
VALUE obj;
struct liquid_variable *variable;
obj = Data_Make_Struct(klass, struct liquid_variable, NULL, free_variable, variable);
return obj;
}
static inline int skip_whitespace(char * str, int len)
{
int skipped = 0; char * ptr = str;
while (skipped < len && isspace(*ptr))
{skipped++; ptr++;}
return skipped;
}
static char * get_quoted_fragment(char * str, int len, int * ret_size, int * end_offset, bool colon)
{
int p = 0; /* Current position in string */
int start = -1, end = -1; /* Start and end indices for the found string */
char quoted_by = -1; /* Is the current part of string quoted by a single or double quote? If so
ignore any special chars */
while (p < len) {
switch (str[p]) {
case '"':
if (start == -1) {start = p; quoted_by = '"';}
else if (str[start] == '"') {end = p; goto quoted_fragment_found;}
else if (quoted_by == -1) quoted_by = '"';
else if (quoted_by == '"') quoted_by = -1;
break;
case '\'':
if (start == -1) {start = p; quoted_by = '\'';}
else if (str[start] == '\'') {end = p; goto quoted_fragment_found;}
else if (quoted_by == -1) quoted_by = '\'';
else if (quoted_by == '\'') quoted_by = -1;
break;
case ':':
if (colon)
if (start != -1 && quoted_by == -1) {end = p-1; goto quoted_fragment_found;}
else
if (start == -1) start = p;
break;
case '|':
case ',':
case '\n':
case '\r':
case '\f':
case '\t':
case '\v':
case ' ':
if (start != -1 && quoted_by == -1) {end = p-1; goto quoted_fragment_found;}
break;
default:
if (start == -1) start = p;
break;
}
p++;
}
if (p == len && start != -1 && end == -1) end = len-1;
quoted_fragment_found:
if (end >= start) {
*ret_size = end-start+1;
*end_offset = end+1;
return &str[start];
} else {
*ret_size = 0;
return NULL;
}
}
static VALUE get_filters(char * str, int len, VALUE self) {
VALUE filters_arr = rb_ary_new();
int p = 0;
int ret_size, end_offset;
char * f;
while(p<len) {
if (str[p] == '|') {
VALUE filter = rb_ary_new();
VALUE f_args = rb_ary_new();
p += skip_whitespace(&str[p+1], len-p-1);
f = get_quoted_fragment(&str[p], len-p, &ret_size, &end_offset, true);
p += end_offset;
if (f) {
if (f[ret_size-1] == ':') ret_size--;
rb_ary_push(filter, rb_str_new(f, ret_size));
}
/* Check for filter arguments */
do {
if (p<len) {
p += skip_whitespace(&str[p], len-p);
// printf("\n1. %.*s\n", len-p, &str[p]);
if (str[p] != '|') {
f = get_quoted_fragment(&str[p], len-p, &ret_size, &end_offset, false);
// printf("\n2. %.*s\n", ret_size, f);
p += end_offset;
p += skip_whitespace(&str[p], len-p);
if (str[p] == '|') p--;
if (f) rb_ary_push(f_args, rb_str_new(f, ret_size));
} else p--;
}
} while (str[p] == ',' || str[p] == ':');
rb_ary_push(filter, f_args);
/* Add to filters_arr array */
rb_ary_push(filters_arr, filter);
}
p++;
}
return filters_arr;
}
static VALUE rb_variable_lax_parse(VALUE self, VALUE m)
{
char * markup = RSTRING_PTR(m);
int markup_len = RSTRING_LEN(m);
char * cursor = markup; int cursor_pos = 0;
VALUE filters_arr;
int size, end_offset;
/* Extract name */
cursor_pos += skip_whitespace(markup, markup_len);
cursor = markup + cursor_pos;
cursor = get_quoted_fragment(cursor, markup_len - cursor_pos, &size, &end_offset, false);
if (cursor == NULL) {
rb_iv_set(self, "@name", Qnil);
filters_arr = rb_ary_new();
rb_iv_set(self, "@filters", filters_arr);
}
else
{
rb_iv_set(self, "@name", rb_str_new(cursor, size));
/* Extract filters */
if (end_offset < markup_len) {
cursor = &markup[end_offset];
filters_arr = get_filters(cursor, markup_len - end_offset, self);
rb_iv_set(self, "@filters", filters_arr);
}
}
return filters_arr;
}
void init_liquid_variable()
{
cLiquidVariable = rb_define_class_under(mLiquid, "Variable", rb_cObject);
rb_define_alloc_func(cLiquidVariable, rb_variable_allocate);
rb_define_method(cLiquidVariable, "lax_parse", rb_variable_lax_parse, 1);
}

View File

@@ -1,13 +0,0 @@
#ifndef LIQUID_VARIABLE_H
#define LIQUID_VARIABLE_H
#include <regex.h>
struct liquid_variable {
char *markup; long markup_len;
char *name; long name_len;
};
void init_liquid_variable();
#endif

View File

@@ -21,9 +21,9 @@
module Liquid
FilterSeparator = /\|/
ArgumentSeparator = ','
FilterArgumentSeparator = ':'
VariableAttributeSeparator = '.'
ArgumentSeparator = ','.freeze
FilterArgumentSeparator = ':'.freeze
VariableAttributeSeparator = '.'.freeze
TagStart = /\{\%/
TagEnd = /\%\}/
VariableSignature = /\(?[\w\-\.\[\]]\)?/
@@ -32,16 +32,10 @@ module Liquid
VariableEnd = /\}\}/
QuotedString = /"[^"]*"|'[^']*'/
QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o
StrictQuotedFragment = /"[^"]+"|'[^']+'|[^\s|:,]+/
FirstFilterArgument = /#{FilterArgumentSeparator}(?:#{StrictQuotedFragment})/o
OtherFilterArgument = /#{ArgumentSeparator}(?:#{StrictQuotedFragment})/o
SpacelessFilter = /\A(?:'[^']+'|"[^"]+"|[^'"])*#{FilterSeparator}(?:#{StrictQuotedFragment})(?:#{FirstFilterArgument}(?:#{OtherFilterArgument})*)?/o
Expression = /(?:#{QuotedFragment}(?:#{SpacelessFilter})*)/o
TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/o
VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o
end
require 'liquid/liquid'
require "liquid/version"
require 'liquid/lexer'
require 'liquid/parser'
@@ -58,7 +52,6 @@ require 'liquid/document'
require 'liquid/variable'
require 'liquid/file_system'
require 'liquid/template'
require 'liquid/htmltags'
require 'liquid/standardfilters'
require 'liquid/condition'
require 'liquid/module_ex'
@@ -67,3 +60,9 @@ require 'liquid/utils'
# Load all the tags of the standard library
#
Dir[File.dirname(__FILE__) + '/liquid/tags/*.rb'].each { |f| require f }
if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby'
require 'liquid/liquid'
else
require 'liquid/tokenizer'
end

View File

@@ -1,37 +1,93 @@
module Liquid
class Block < Tag
def initialize(tag_name, markup, tokens)
super
parse_body(tokens)
end
IsTag = /\A#{TagStart}/o
IsVariable = /\A#{VariableStart}/o
FullToken = /\A#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
ContentOfVariable = /\A#{VariableStart}(.*)#{VariableEnd}\z/om
def blank?
@blank || false
end
def parse(tokens)
@blank = true
@nodelist ||= []
@nodelist.clear
# All child tags of the current block.
@children = []
while token = tokens.shift
case token
when IsTag
if token =~ FullToken
# if we found the proper block delimiter just end parsing here and let the outer block
# proceed
if block_delimiter == $1
end_tag
return
end
# fetch the tag from registered blocks
if tag = Template.tags[$1]
new_tag = tag.parse($1, $2, tokens, @options)
@blank &&= new_tag.blank?
@nodelist << new_tag
@children << new_tag
else
# this tag is not registered with the system
# pass it to the current block for special handling or error reporting
unknown_tag($1, $2, tokens)
end
else
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_termination".freeze, :token => token, :tag_end => TagEnd.inspect))
end
when IsVariable
new_var = create_variable(token)
@nodelist << new_var
@children << new_var
@blank = false
when ''.freeze
# pass
else
@nodelist << token
@blank &&= (token =~ /\A\s*\z/)
end
end
# Make sure that it's ok to end parsing in the current block.
# Effectively this method will throw an exception unless the current block is
# of type Document
assert_missing_delimitation!
end
# warnings of this block and all sub-tags
def warnings
all_warnings = []
all_warnings.concat(@warnings) if @warnings
(nodelist || []).each do |node|
all_warnings.concat(node.warnings || []) if node.respond_to?(:warnings)
(@children || []).each do |node|
all_warnings.concat(node.warnings || [])
end
all_warnings
end
def end_tag
end
def unknown_tag(tag, params, tokens)
case tag
when 'else'
raise SyntaxError.new(options[:locale].t("errors.syntax.unexpected_else",
when 'else'.freeze
raise SyntaxError.new(options[:locale].t("errors.syntax.unexpected_else".freeze,
:block_name => block_name))
when 'end'
raise SyntaxError.new(options[:locale].t("errors.syntax.invalid_delimiter",
:block_name => block_name,
when 'end'.freeze
raise SyntaxError.new(options[:locale].t("errors.syntax.invalid_delimiter".freeze,
:block_name => block_name,
:block_delimiter => block_delimiter))
else
raise SyntaxError.new(options[:locale].t("errors.syntax.unknown_tag", :tag => tag))
raise SyntaxError.new(options[:locale].t("errors.syntax.unknown_tag".freeze, :tag => tag))
end
end
@@ -43,22 +99,21 @@ module Liquid
@tag_name
end
def create_variable(token)
token.scan(ContentOfVariable) do |content|
return Variable.new(content.first, @options)
end
raise SyntaxError.new(options[:locale].t("errors.syntax.variable_termination".freeze, :token => token, :tag_end => VariableEnd.inspect))
end
def render(context)
render_all(@nodelist, context)
end
protected
def unterminated_variable(token)
raise SyntaxError.new(options[:locale].t("errors.syntax.variable_termination", :token => token, :tag_end => VariableEnd.inspect))
end
def unterminated_tag(token)
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_termination", :token => token, :tag_end => TagEnd.inspect))
end
def assert_missing_delimitation!
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_never_closed", :block_name => block_name))
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_never_closed".freeze, :block_name => block_name))
end
def render_all(list, context)
@@ -83,7 +138,7 @@ module Liquid
context.increment_used_resources(:render_length_current, token_output)
if context.resource_limits_reached?
context.resource_limits[:reached] = true
raise MemoryError.new("Memory limits exceeded")
raise MemoryError.new("Memory limits exceeded".freeze)
end
unless token.is_a?(Block) && token.blank?
output << token_output

View File

@@ -8,14 +8,14 @@ module Liquid
#
class Condition #:nodoc:
@@operators = {
'==' => lambda { |cond, left, right| cond.send(:equal_variables, left, right) },
'!=' => lambda { |cond, left, right| !cond.send(:equal_variables, left, right) },
'<>' => lambda { |cond, left, right| !cond.send(:equal_variables, left, right) },
'<' => :<,
'>' => :>,
'>=' => :>=,
'<=' => :<=,
'contains' => lambda { |cond, left, right| left && right ? left.include?(right) : false }
'=='.freeze => lambda { |cond, left, right| cond.send(:equal_variables, left, right) },
'!='.freeze => lambda { |cond, left, right| !cond.send(:equal_variables, left, right) },
'<>'.freeze => lambda { |cond, left, right| !cond.send(:equal_variables, left, right) },
'<'.freeze => :<,
'>'.freeze => :>,
'>='.freeze => :>=,
'<='.freeze => :<=,
'contains'.freeze => lambda { |cond, left, right| left && right ? left.include?(right) : false }
}
def self.operators
@@ -61,7 +61,7 @@ module Liquid
end
def inspect
"#<Condition #{[@left, @operator, @right].compact.join(' ')}>"
"#<Condition #{[@left, @operator, @right].compact.join(' '.freeze)}>"
end
private

View File

@@ -103,7 +103,7 @@ module Liquid
# Push new local scope on the stack. use <tt>Context#stack</tt> instead
def push(new_scope={})
@scopes.unshift(new_scope)
raise StackLevelError, "Nesting too deep" if @scopes.length > 100
raise StackLevelError, "Nesting too deep".freeze if @scopes.length > 100
end
# Merge a hash of variables in the current local scope
@@ -151,11 +151,11 @@ module Liquid
private
LITERALS = {
nil => nil, 'nil' => nil, 'null' => nil, '' => nil,
'true' => true,
'false' => false,
'blank' => :blank?,
'empty' => :empty?
nil => nil, 'nil'.freeze => nil, 'null'.freeze => nil, ''.freeze => nil,
'true'.freeze => true,
'false'.freeze => false,
'blank'.freeze => :blank?,
'empty'.freeze => :empty?
}
# Look up variable, either resolve directly after considering the name. We can directly handle
@@ -171,9 +171,9 @@ module Liquid
LITERALS[key]
else
case key
when /\A'(.*)'\z/ # Single quoted strings
when /\A'(.*)'\z/m # Single quoted strings
$1
when /\A"(.*)"\z/ # Double quoted strings
when /\A"(.*)"\z/m # Double quoted strings
$1
when /\A(-?\d+)\z/ # Integer and floats
$1.to_i
@@ -218,7 +218,7 @@ module Liquid
# assert_equal 'tobi', @context['hash["name"]']
def variable(markup)
parts = markup.scan(VariableParser)
square_bracketed = /\A\[(.*)\]\z/
square_bracketed = /\A\[(.*)\]\z/m
first_part = parts.shift
@@ -244,7 +244,7 @@ module Liquid
# Some special cases. If the part wasn't in square brackets and
# no key with the same name was found we interpret following calls
# as commands and call them on the current object
elsif !part_resolved and object.respond_to?(part) and ['size', 'first', 'last'].include?(part)
elsif !part_resolved and object.respond_to?(part) and ['size'.freeze, 'first'.freeze, 'last'.freeze].include?(part)
object = object.send(part.intern).to_liquid

View File

@@ -1,14 +1,13 @@
module Liquid
class Document < Block
# we don't need markup to open this block
def initialize(tokens, options = {})
@options = options
parse_body(tokens)
def self.parse(tokens, options={})
# we don't need markup to open this block
super(nil, nil, tokens, options)
end
# There isn't a real delimiter
def block_delimiter
nil
[]
end
# Document blocks don't need to be terminated since they are not actually opened

View File

@@ -44,7 +44,7 @@ module Liquid
class LocalFileSystem
attr_accessor :root
def initialize(root, pattern = "_%s.liquid")
def initialize(root, pattern = "_%s.liquid".freeze)
@root = root
@pattern = pattern
end
@@ -59,7 +59,7 @@ module Liquid
def full_path(template_path)
raise FileSystemError, "Illegal template name '#{template_path}'" unless template_path =~ /\A[^.\/][a-zA-Z0-9_\/]+\z/
full_path = if template_path.include?('/')
full_path = if template_path.include?('/'.freeze)
File.join(root, File.dirname(template_path), @pattern % File.basename(template_path))
else
File.join(root, @pattern % template_path)

View File

@@ -1,74 +0,0 @@
module Liquid
class TableRow < Block
Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o
def initialize(tag_name, markup, tokens)
if markup =~ Syntax
@variable_name = $1
@collection_name = $2
@attributes = {}
markup.scan(TagAttributes) do |key, value|
@attributes[key] = value
end
else
raise SyntaxError.new(options[:locale].t("errors.syntax.table_row"))
end
super
end
def render(context)
collection = context[@collection_name] or return ''
from = @attributes['offset'] ? context[@attributes['offset']].to_i : 0
to = @attributes['limit'] ? from + context[@attributes['limit']].to_i : nil
collection = Utils.slice_collection(collection, from, to)
length = collection.length
cols = context[@attributes['cols']].to_i
row = 1
col = 0
result = "<tr class=\"row1\">\n"
context.stack do
collection.each_with_index do |item, index|
context[@variable_name] = item
context['tablerowloop'] = {
'length' => length,
'index' => index + 1,
'index0' => index,
'col' => col + 1,
'col0' => col,
'index0' => index,
'rindex' => length - index,
'rindex0' => length - index - 1,
'first' => (index == 0),
'last' => (index == length - 1),
'col_first' => (col == 0),
'col_last' => (col == cols - 1)
}
col += 1
result << "<td class=\"col#{col}\">" << super << '</td>'
if col == cols and (index != length - 1)
col = 0
row += 1
result << "</tr>\n<tr class=\"row#{row}\">"
end
end
end
result << "</tr>\n"
result
end
end
Template.register_tag('tablerow', TableRow)
end

View File

@@ -6,7 +6,7 @@ module Liquid
class TranslationError < StandardError
end
attr_reader :path
def initialize(path = DEFAULT_LOCALE)
@@ -31,7 +31,7 @@ module Liquid
end
def deep_fetch_translation(name)
name.split('.').reduce(locale) do |level, cur|
name.split('.'.freeze).reduce(locale) do |level, cur|
level[cur] or raise TranslationError, "Translation for #{name} does not exist in locale #{path}"
end
end

View File

@@ -5,7 +5,7 @@ module Liquid
attr_reader :message
def initialize(message=nil)
@message = message || "interrupt"
@message = message || "interrupt".freeze
end
end

View File

@@ -2,14 +2,14 @@ require "strscan"
module Liquid
class Lexer
SPECIALS = {
'|' => :pipe,
'.' => :dot,
':' => :colon,
',' => :comma,
'[' => :open_square,
']' => :close_square,
'(' => :open_round,
')' => :close_round
'|'.freeze => :pipe,
'.'.freeze => :dot,
':'.freeze => :colon,
','.freeze => :comma,
'['.freeze => :open_square,
']'.freeze => :close_square,
'('.freeze => :open_round,
')'.freeze => :close_round
}
IDENTIFIER = /[\w\-?!]+/
SINGLE_STRING_LITERAL = /'[^\']*'/

View File

@@ -66,10 +66,11 @@ module Liquid
str = ""
# might be a keyword argument (identifier: expression)
if look(:id) && look(:colon, 1)
str << consume << consume << ' '
str << consume << consume << ' '.freeze
end
str << expression
str
end
def variable_signature

View File

@@ -4,12 +4,17 @@ require 'bigdecimal'
module Liquid
module StandardFilters
HTML_ESCAPE = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;', "'" => '&#39;' }
HTML_ESCAPE = {
'&'.freeze => '&amp;'.freeze,
'>'.freeze => '&gt;'.freeze,
'<'.freeze => '&lt;'.freeze,
'"'.freeze => '&quot;'.freeze,
"'".freeze => '&#39;'.freeze
}
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/
# Return the size of an array or of an string
def size(input)
input.respond_to?(:size) ? input.size : 0
end
@@ -39,19 +44,19 @@ module Liquid
alias_method :h, :escape
# Truncate a string down to x characters
def truncate(input, length = 50, truncate_string = "...")
def truncate(input, length = 50, truncate_string = "...".freeze)
if input.nil? then return end
l = length.to_i - truncate_string.length
l = 0 if l < 0
input.length > length.to_i ? input[0...l] + truncate_string : input
end
def truncatewords(input, words = 15, truncate_string = "...")
def truncatewords(input, words = 15, truncate_string = "...".freeze)
if input.nil? then return end
wordlist = input.to_s.split
l = words.to_i - 1
l = 0 if l < 0
wordlist.length > l ? wordlist[0..l].join(" ") + truncate_string : input
wordlist.length > l ? wordlist[0..l].join(" ".freeze) + truncate_string : input
end
# Split input string into an array of substrings separated by given pattern.
@@ -76,16 +81,17 @@ module Liquid
end
def strip_html(input)
input.to_s.gsub(/<script.*?<\/script>/m, '').gsub(/<!--.*?-->/m, '').gsub(/<style.*?<\/style>/m, '').gsub(/<.*?>/m, '')
empty = ''.freeze
input.to_s.gsub(/<script.*?<\/script>/m, empty).gsub(/<!--.*?-->/m, empty).gsub(/<style.*?<\/style>/m, empty).gsub(/<.*?>/m, empty)
end
# Remove all newlines from the string
def strip_newlines(input)
input.to_s.gsub(/\r?\n/, '')
input.to_s.gsub(/\r?\n/, ''.freeze)
end
# Join elements of the array with certain character between them
def join(input, glue = ' ')
def join(input, glue = ' '.freeze)
[input].flatten.join(glue)
end
@@ -95,7 +101,7 @@ module Liquid
ary = flatten_if_necessary(input)
if property.nil?
ary.sort
elsif ary.first.respond_to?('[]') and !ary.first[property].nil?
elsif ary.first.respond_to?('[]'.freeze) and !ary.first[property].nil?
ary.sort {|a,b| a[property] <=> b[property] }
elsif ary.first.respond_to?(property)
ary.sort {|a,b| a.send(property) <=> b.send(property) }
@@ -113,7 +119,7 @@ module Liquid
flatten_if_necessary(input).map do |e|
e = e.call if e.is_a?(Proc)
if property == "to_liquid"
if property == "to_liquid".freeze
e
elsif e.respond_to?(:[])
e[property]
@@ -122,23 +128,23 @@ module Liquid
end
# Replace occurrences of a string with another
def replace(input, string, replacement = '')
def replace(input, string, replacement = ''.freeze)
input.to_s.gsub(string, replacement.to_s)
end
# Replace the first occurrences of a string with another
def replace_first(input, string, replacement = '')
def replace_first(input, string, replacement = ''.freeze)
input.to_s.sub(string, replacement.to_s)
end
# remove a substring
def remove(input, string)
input.to_s.gsub(string, '')
input.to_s.gsub(string, ''.freeze)
end
# remove the first occurrences of a substring
def remove_first(input, string)
input.to_s.sub(string, '')
input.to_s.sub(string, ''.freeze)
end
# add one string to another
@@ -153,7 +159,7 @@ module Liquid
# Add <br /> tags in front of all newlines in input string
def newline_to_br(input)
input.to_s.gsub(/\n/, "<br />\n")
input.to_s.gsub(/\n/, "<br />\n".freeze)
end
# Reformat a date
@@ -196,7 +202,7 @@ module Liquid
date = if input.is_a?(String)
case input.downcase
when 'now', 'today'
when 'now'.freeze, 'today'.freeze
Time.now
else
Time.parse(input)
@@ -256,7 +262,7 @@ module Liquid
apply_operation(input, operand, :%)
end
def default(input, default_value = "")
def default(input, default_value = "".freeze)
is_blank = input.respond_to?(:empty?) ? input.empty? : !input
is_blank ? default_value : input
end

View File

@@ -3,19 +3,23 @@ module Liquid
attr_accessor :options
attr_reader :nodelist, :warnings
def self.new_with_options(tag_name, markup, tokens, options)
# Forgive me Matz for I have sinned. I know this code is weird
# but it was necessary to maintain API compatibility.
new_tag = self.allocate
new_tag.options = options
new_tag.send(:initialize, tag_name, markup, tokens)
new_tag
class << self
def parse(tag_name, markup, tokens, options)
tag = new(tag_name, markup, options)
tag.parse(tokens)
tag
end
private :new
end
def initialize(tag_name, markup, tokens)
def initialize(tag_name, markup, options)
@tag_name = tag_name
@markup = markup
@options ||= {} # needs || because might be set before initialize
@options = options
end
def parse(tokens)
end
def name
@@ -23,7 +27,7 @@ module Liquid
end
def render(context)
''
''.freeze
end
def blank?

View File

@@ -9,24 +9,23 @@ module Liquid
# {{ foo }}
#
class Assign < Tag
Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/o
Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om
def initialize(tag_name, markup, tokens)
def initialize(tag_name, markup, options)
super
if markup =~ Syntax
@to = $1
@from = Variable.new($2)
else
raise SyntaxError.new options[:locale].t("errors.syntax.assign")
raise SyntaxError.new options[:locale].t("errors.syntax.assign".freeze)
end
super
end
def render(context)
val = @from.render(context)
context.scopes.last[@to] = val
context.increment_used_resources(:assign_score_current, val)
''
''.freeze
end
def blank?
@@ -34,5 +33,5 @@ module Liquid
end
end
Template.register_tag('assign', Assign)
Template.register_tag('assign'.freeze, Assign)
end

View File

@@ -17,5 +17,5 @@ module Liquid
end
Template.register_tag('break', Break)
Template.register_tag('break'.freeze, Break)
end

View File

@@ -14,21 +14,20 @@ module Liquid
class Capture < Block
Syntax = /(\w+)/
def initialize(tag_name, markup, tokens)
def initialize(tag_name, markup, options)
super
if markup =~ Syntax
@to = $1
else
raise SyntaxError.new(options[:locale].t("errors.syntax.capture"))
end
super
end
def render(context)
output = super
context.scopes.last[@to] = output
context.increment_used_resources(:assign_score_current, output)
''
''.freeze
end
def blank?
@@ -36,5 +35,5 @@ module Liquid
end
end
Template.register_tag('capture', Capture)
Template.register_tag('capture'.freeze, Capture)
end

View File

@@ -1,18 +1,17 @@
module Liquid
class Case < Block
Syntax = /(#{QuotedFragment})/o
WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/o
WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/om
def initialize(tag_name, markup, tokens)
def initialize(tag_name, markup, options)
super
@blocks = []
if markup =~ Syntax
@left = $1
else
raise SyntaxError.new(options[:locale].t("errors.syntax.case"))
raise SyntaxError.new(options[:locale].t("errors.syntax.case".freeze))
end
super
end
def nodelist
@@ -22,9 +21,9 @@ module Liquid
def unknown_tag(tag, markup, tokens)
@nodelist = []
case tag
when 'when'
when 'when'.freeze
record_when_condition(markup)
when 'else'
when 'else'.freeze
record_else_condition(markup)
else
super
@@ -54,12 +53,12 @@ module Liquid
while markup
# Create a new nodelist and assign it to the new block
if not markup =~ WhenSyntax
raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_when"))
raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_when".freeze))
end
markup = $2
block = Condition.new(@left, '==', $1)
block = Condition.new(@left, '=='.freeze, $1)
block.attach(@nodelist)
@blocks.push(block)
end
@@ -67,7 +66,7 @@ module Liquid
def record_else_condition(markup)
if not markup.strip.empty?
raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_else"))
raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_else".freeze))
end
block = ElseCondition.new
@@ -76,5 +75,5 @@ module Liquid
end
end
Template.register_tag('case', Case)
Template.register_tag('case'.freeze, Case)
end

View File

@@ -1,7 +1,7 @@
module Liquid
class Comment < Block
def render(context)
''
''.freeze
end
def unknown_tag(tag, markup, tokens)
@@ -12,5 +12,5 @@ module Liquid
end
end
Template.register_tag('comment', Comment)
Template.register_tag('comment'.freeze, Comment)
end

View File

@@ -1,5 +1,4 @@
module Liquid
# Continue tag to be used to break out of a for loop.
#
# == Basic Usage:
@@ -10,12 +9,10 @@ module Liquid
# {% endfor %}
#
class Continue < Tag
def interrupt
ContinueInterrupt.new
end
end
Template.register_tag('continue', Continue)
Template.register_tag('continue'.freeze, Continue)
end

View File

@@ -13,9 +13,10 @@ module Liquid
#
class Cycle < Tag
SimpleSyntax = /\A#{QuotedFragment}+/o
NamedSyntax = /\A(#{QuotedFragment})\s*\:\s*(.*)/o
NamedSyntax = /\A(#{QuotedFragment})\s*\:\s*(.*)/om
def initialize(tag_name, markup, tokens)
def initialize(tag_name, markup, options)
super
case markup
when NamedSyntax
@variables = variables_from_string($2)
@@ -24,9 +25,8 @@ module Liquid
@variables = variables_from_string(markup)
@name = "'#{@variables.to_s}'"
else
raise SyntaxError.new(options[:locale].t("errors.syntax.cycle"))
raise SyntaxError.new(options[:locale].t("errors.syntax.cycle".freeze))
end
super
end
def render(context)

View File

@@ -19,10 +19,9 @@ module Liquid
# Hello: -3
#
class Decrement < Tag
def initialize(tag_name, markup, tokens)
@variable = markup.strip
def initialize(tag_name, markup, options)
super
@variable = markup.strip
end
def render(context)
@@ -35,5 +34,5 @@ module Liquid
private
end
Template.register_tag('decrement', Decrement)
Template.register_tag('decrement'.freeze, Decrement)
end

View File

@@ -46,10 +46,10 @@ module Liquid
class For < Block
Syntax = /\A(#{VariableSegment}+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
def initialize(tag_name, markup, tokens)
def initialize(tag_name, markup, options)
super
parse_with_selected_parser(markup)
@nodelist = @for_block = []
super
end
def nodelist
@@ -61,7 +61,7 @@ module Liquid
end
def unknown_tag(tag, markup, tokens)
return super unless tag == 'else'
return super unless tag == 'else'.freeze
@nodelist = @else_block = []
end
@@ -74,13 +74,13 @@ module Liquid
# Maintains Ruby 1.8.7 String#each behaviour on 1.9
return render_else(context) unless iterable?(collection)
from = if @attributes['offset'] == 'continue'
from = if @attributes['offset'.freeze] == 'continue'.freeze
context.registers[:for][@name].to_i
else
context[@attributes['offset']].to_i
context[@attributes['offset'.freeze]].to_i
end
limit = context[@attributes['limit']]
limit = context[@attributes['limit'.freeze]]
to = limit ? limit.to_i + from : nil
segment = Utils.slice_collection(collection, from, to)
@@ -99,15 +99,16 @@ module Liquid
context.stack do
segment.each_with_index do |item, index|
context[@variable_name] = item
context['forloop'] = {
'name' => @name,
'length' => length,
'index' => index + 1,
'index0' => index,
'rindex' => length - index,
'rindex0' => length - index - 1,
'first' => (index == 0),
'last' => (index == length - 1) }
context['forloop'.freeze] = {
'name'.freeze => @name,
'length'.freeze => length,
'index'.freeze => index + 1,
'index0'.freeze => index,
'rindex'.freeze => length - index,
'rindex0'.freeze => length - index - 1,
'first'.freeze => (index == 0),
'last'.freeze => (index == length - 1)
}
result << render_all(@for_block, context)
@@ -135,22 +136,22 @@ module Liquid
@attributes[key] = value
end
else
raise SyntaxError.new(options[:locale].t("errors.syntax.for"))
raise SyntaxError.new(options[:locale].t("errors.syntax.for".freeze))
end
end
def strict_parse(markup)
p = Parser.new(markup)
@variable_name = p.consume(:id)
raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_in")) unless p.id?('in')
raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_in".freeze)) unless p.id?('in'.freeze)
@collection_name = p.expression
@name = "#{@variable_name}-#{@collection_name}"
@reversed = p.id?('reversed')
@reversed = p.id?('reversed'.freeze)
@attributes = {}
while p.look(:id) && p.look(:colon, 1)
unless attribute = p.id?('limit') || p.id?('offset')
raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_attribute"))
unless attribute = p.id?('limit'.freeze) || p.id?('offset'.freeze)
raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_attribute".freeze))
end
p.consume
val = p.expression
@@ -161,14 +162,14 @@ module Liquid
private
def render_else(context)
return @else_block ? [render_all(@else_block, context)] : ''
end
def render_else(context)
return @else_block ? [render_all(@else_block, context)] : ''.freeze
end
def iterable?(collection)
collection.respond_to?(:each) || Utils.non_blank_string?(collection)
end
def iterable?(collection)
collection.respond_to?(:each) || Utils.non_blank_string?(collection)
end
end
Template.register_tag('for', For)
Template.register_tag('for'.freeze, For)
end

View File

@@ -14,10 +14,10 @@ module Liquid
ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/o
BOOLEAN_OPERATORS = %w(and or)
def initialize(tag_name, markup, tokens)
@blocks = []
push_block('if', markup)
def initialize(tag_name, markup, options)
super
@blocks = []
push_block('if'.freeze, markup)
end
def nodelist
@@ -25,7 +25,7 @@ module Liquid
end
def unknown_tag(tag, markup, tokens)
if ['elsif', 'else'].include?(tag)
if ['elsif'.freeze, 'else'.freeze].include?(tag)
push_block(tag, markup)
else
super
@@ -39,14 +39,14 @@ module Liquid
return render_all(block.attachment, context)
end
end
''
''.freeze
end
end
private
def push_block(tag, markup)
block = if tag == 'else'
block = if tag == 'else'.freeze
ElseCondition.new
else
parse_with_selected_parser(markup)
@@ -58,17 +58,17 @@ module Liquid
def lax_parse(markup)
expressions = markup.scan(ExpressionsAndOperators).reverse
raise(SyntaxError.new(options[:locale].t("errors.syntax.if"))) unless expressions.shift =~ Syntax
raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless expressions.shift =~ Syntax
condition = Condition.new($1, $2, $3)
while not expressions.empty?
operator = (expressions.shift).to_s.strip
raise(SyntaxError.new(options[:locale].t("errors.syntax.if"))) unless expressions.shift.to_s =~ Syntax
raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless expressions.shift.to_s =~ Syntax
new_condition = Condition.new($1, $2, $3)
raise(SyntaxError.new(options[:locale].t("errors.syntax.if"))) unless BOOLEAN_OPERATORS.include?(operator)
raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless BOOLEAN_OPERATORS.include?(operator)
new_condition.send(operator, condition)
condition = new_condition
end
@@ -81,7 +81,7 @@ module Liquid
condition = parse_comparison(p)
while op = (p.id?('and') || p.id?('or'))
while op = (p.id?('and'.freeze) || p.id?('or'.freeze))
new_cond = parse_comparison(p)
new_cond.send(op, condition)
condition = new_cond
@@ -102,5 +102,5 @@ module Liquid
end
end
Template.register_tag('if', If)
Template.register_tag('if'.freeze, If)
end

View File

@@ -4,17 +4,17 @@ module Liquid
def render(context)
context.stack do
output = super
output = render_all(@nodelist, context)
if output != context.registers[:ifchanged]
context.registers[:ifchanged] = output
output
else
''
''.freeze
end
end
end
end
Template.register_tag('ifchanged', Ifchanged)
Template.register_tag('ifchanged'.freeze, Ifchanged)
end

View File

@@ -17,7 +17,9 @@ module Liquid
class Include < Tag
Syntax = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?/o
def initialize(tag_name, markup, tokens)
def initialize(tag_name, markup, options)
super
if markup =~ Syntax
@template_name = $1
@@ -29,10 +31,11 @@ module Liquid
end
else
raise SyntaxError.new(options[:locale].t("errors.syntax.include"))
raise SyntaxError.new(options[:locale].t("errors.syntax.include".freeze))
end
end
super
def parse(tokens)
end
def blank?
@@ -48,7 +51,7 @@ module Liquid
context[key] = context[value]
end
context_variable_name = @template_name[1..-2].split('/').last
context_variable_name = @template_name[1..-2].split('/'.freeze).last
if variable.is_a?(Array)
variable.collect do |var|
context[context_variable_name] = var
@@ -91,5 +94,5 @@ module Liquid
end
end
Template.register_tag('include', Include)
Template.register_tag('include'.freeze, Include)
end

View File

@@ -15,9 +15,9 @@ module Liquid
# Hello: 2
#
class Increment < Tag
def initialize(tag_name, markup, tokens)
@variable = markup.strip
def initialize(tag_name, markup, options)
super
@variable = markup.strip
end
def render(context)
@@ -31,5 +31,5 @@ module Liquid
end
end
Template.register_tag('increment', Increment)
Template.register_tag('increment'.freeze, Increment)
end

View File

@@ -1,19 +1,22 @@
module Liquid
class Raw < Block
FullTokenPossiblyInvalid = /\A(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/o
FullTokenPossiblyInvalid = /\A(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
def parse_body(tokens)
def parse(tokens)
@nodelist ||= []
@nodelist.clear
while token = tokens.shift
if token =~ FullTokenPossiblyInvalid
@nodelist << $1 if $1 != ""
return if block_delimiter == $2
@nodelist << $1 if $1 != "".freeze
if block_delimiter == $2
end_tag
return
end
end
@nodelist << token if not token.empty?
end
end
end
Template.register_tag('raw', Raw)
Template.register_tag('raw'.freeze, Raw)
end

View File

@@ -0,0 +1,73 @@
module Liquid
class TableRow < Block
Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o
def initialize(tag_name, markup, options)
super
if markup =~ Syntax
@variable_name = $1
@collection_name = $2
@attributes = {}
markup.scan(TagAttributes) do |key, value|
@attributes[key] = value
end
else
raise SyntaxError.new(options[:locale].t("errors.syntax.table_row".freeze))
end
end
def render(context)
collection = context[@collection_name] or return ''.freeze
from = @attributes['offset'.freeze] ? context[@attributes['offset'.freeze]].to_i : 0
to = @attributes['limit'.freeze] ? from + context[@attributes['limit'.freeze]].to_i : nil
collection = Utils.slice_collection(collection, from, to)
length = collection.length
cols = context[@attributes['cols'.freeze]].to_i
row = 1
col = 0
result = "<tr class=\"row1\">\n"
context.stack do
collection.each_with_index do |item, index|
context[@variable_name] = item
context['tablerowloop'.freeze] = {
'length'.freeze => length,
'index'.freeze => index + 1,
'index0'.freeze => index,
'col'.freeze => col + 1,
'col0'.freeze => col,
'index0'.freeze => index,
'rindex'.freeze => length - index,
'rindex0'.freeze => length - index - 1,
'first'.freeze => (index == 0),
'last'.freeze => (index == length - 1),
'col_first'.freeze => (col == 0),
'col_last'.freeze => (col == cols - 1)
}
col += 1
result << "<td class=\"col#{col}\">" << render_all(@nodelist, context) << '</td>'
if col == cols and (index != length - 1)
col = 0
row += 1
result << "</tr>\n<tr class=\"row#{row}\">"
end
end
end
result << "</tr>\n"
result
end
end
Template.register_tag('tablerow'.freeze, TableRow)
end

View File

@@ -1,7 +1,6 @@
require File.dirname(__FILE__) + '/if'
module Liquid
# Unless is a conditional just like 'if' but works on the inverse logic.
#
# {% unless x < 0 %} x is greater than zero {% end %}
@@ -23,11 +22,10 @@ module Liquid
end
end
''
''.freeze
end
end
end
Template.register_tag('unless', Unless)
Template.register_tag('unless'.freeze, Unless)
end

View File

@@ -72,7 +72,7 @@ module Liquid
# Parse source code.
# Returns self for easy chaining
def parse(source, options = {})
@root = Document.new(tokenize(source), DEFAULT_OPTIONS.merge(options))
@root = Document.parse(tokenize(source), DEFAULT_OPTIONS.merge(options))
@warnings = nil
self
end
@@ -110,7 +110,7 @@ module Liquid
# filters and tags and might be useful to integrate liquid more with its host application
#
def render(*args)
return '' if @root.nil?
return ''.freeze if @root.nil?
context = case args.first
when Liquid::Context

20
lib/liquid/tokenizer.rb Normal file
View File

@@ -0,0 +1,20 @@
module Liquid
class Tokenizer
VariableIncompleteEnd = /\}\}?/
AnyStartingTag = /\{\{|\{\%/
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/om
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om
def initialize(source)
@tokens = source.split(TemplateParser)
# removes the rogue empty element at the beginning of the array
@tokens.shift if @tokens[0] && @tokens[0].empty?
end
def next
@tokens.shift
end
alias_method :shift, :next
end
end

View File

@@ -10,7 +10,7 @@ module Liquid
end
def self.non_blank_string?(collection)
collection.is_a?(String) && collection != ''
collection.is_a?(String) && collection != ''.freeze
end
def self.slice_collection_using_each(collection, from, to)

View File

@@ -11,6 +11,7 @@ module Liquid
# {{ user | link }}
#
class Variable
FilterParser = /(?:#{FilterSeparator}|(?:\s*(?:#{QuotedFragment}|#{ArgumentSeparator})\s*)+)/o
EasyParse = /\A *(\w+(?:\.\w+)*) *\z/
attr_accessor :filters, :name, :warnings
@@ -18,7 +19,6 @@ module Liquid
@markup = markup
@name = nil
@options = options || {}
case @options[:error_mode] || Template.error_mode
when :strict then strict_parse(markup)
@@ -34,22 +34,22 @@ module Liquid
end
end
# def lax_parse(markup)
# @filters = []
# if match = markup.match(/\s*(#{QuotedFragment})(.*)/o)
# @name = match[1]
# if match[2].match(/#{FilterSeparator}\s*(.*)/o)
# filters = Regexp.last_match(1).scan(FilterParser)
# filters.each do |f|
# if matches = f.match(/\s*(\w+)/)
# filtername = matches[1]
# filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o).flatten
# @filters << [filtername, filterargs]
# end
# end
# end
# end
# end
def lax_parse(markup)
@filters = []
if match = markup.match(/\s*(#{QuotedFragment})(.*)/om)
@name = match[1]
if match[2].match(/#{FilterSeparator}\s*(.*)/om)
filters = Regexp.last_match(1).scan(FilterParser)
filters.each do |f|
if matches = f.match(/\s*(\w+)/)
filtername = matches[1]
filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o).flatten
@filters << [filtername, filterargs]
end
end
end
end
end
def strict_parse(markup)
# Very simple valid cases
@@ -62,7 +62,7 @@ module Liquid
@filters = []
p = Parser.new(markup)
# Could be just filters with no input
@name = p.look(:pipe) ? '' : p.expression
@name = p.look(:pipe) ? ''.freeze : p.expression
while p.consume?(:pipe)
filtername = p.consume(:id)
filterargs = p.consume?(:colon) ? parse_filterargs(p) : []
@@ -85,7 +85,7 @@ module Liquid
end
def render(context)
return '' if @name.nil?
return ''.freeze if @name.nil?
@filters.inject(context[@name]) do |output, filter|
filterargs = []
keyword_args = {}

View File

@@ -1,4 +1,4 @@
# encoding: utf-8
module Liquid
VERSION = "3.0.0"
VERSION = "3.0.0".freeze
end

View File

@@ -19,14 +19,16 @@ Gem::Specification.new do |s|
s.test_files = Dir.glob("{test}/**/*")
s.files = Dir.glob("{lib,ext}/**/*") + %w(MIT-LICENSE README.md)
s.extensions = ['ext/liquid/extconf.rb']
s.extra_rdoc_files = ["History.md", "README.md"]
s.require_path = "lib"
s.add_development_dependency 'rake-compiler'
s.add_development_dependency 'stackprof'
s.add_development_dependency 'rake'
s.add_development_dependency 'activesupport'
if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby'
s.extensions = ['ext/liquid/extconf.rb']
s.add_development_dependency 'rake-compiler'
s.add_development_dependency 'stackprof' if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.1.0")
end
end

View File

@@ -1,19 +1,13 @@
require 'rubygems'
require 'ruby-prof' rescue fail("install ruby-prof extension/gem")
require 'stackprof' rescue fail("install stackprof extension/gem")
require File.dirname(__FILE__) + '/theme_runner'
Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
profiler = ThemeRunner.new
puts 'Running profiler...'
results = profiler.run_profile
puts 'Success'
puts
[RubyProf::FlatPrinter, RubyProf::GraphHtmlPrinter, RubyProf::CallTreePrinter, RubyProf::DotPrinter].each do |klass|
filename = (ENV['TMP'] || '/tmp') + (klass.name.include?('Html') ? "/liquid.#{klass.name.downcase}.html" : "/callgrind.liquid.#{klass.name.downcase}.txt")
filename.gsub!(/:+/, '_')
File.open(filename, "w+") { |fp| klass.new(results).print(fp, :print_file => true, :min_percent => 3) }
$stderr.puts "wrote #{klass.name} output to #{filename}"
profiler.run
results = StackProf.run(mode: :cpu) do
100.times do
profiler.run
end
end
StackProf::Report.new(results).print_text(false, 20)
File.write(ENV['FILENAME'], Marshal.dump(results)) if ENV['FILENAME']

View File

@@ -1,15 +1,15 @@
class CommentForm < Liquid::Block
Syntax = /(#{Liquid::VariableSignature}+)/
def initialize(tag_name, markup, tokens)
def initialize(tag_name, markup, options)
super
if markup =~ Syntax
@variable_name = $1
@attributes = {}
else
raise SyntaxError.new("Syntax Error in 'comment_form' - Valid syntax: comment_form [article]")
end
super
end
def render(context)

View File

@@ -1,7 +1,9 @@
class Paginate < Liquid::Block
Syntax = /(#{Liquid::QuotedFragment})\s*(by\s*(\d+))?/
def initialize(tag_name, markup, tokens)
def initialize(tag_name, markup, options)
super
@nodelist = []
if markup =~ Syntax
@@ -19,8 +21,6 @@ class Paginate < Liquid::Block
else
raise SyntaxError.new("Syntax Error in tag 'paginate' - Valid syntax: paginate [collection] by number")
end
super
end
def render(context)

View File

@@ -45,11 +45,11 @@ module ShopFilter
end
def url_for_vendor(vendor_title)
"/collections/#{vendor_title.to_handle}"
"/collections/#{to_handle(vendor_title)}"
end
def url_for_type(type_title)
"/collections/#{type_title.to_handle}"
"/collections/#{to_handle(type_title)}"
end
def product_img_url(url, style = 'small')
@@ -95,4 +95,16 @@ module ShopFilter
input == 1 ? singular : plural
end
private
def to_handle(str)
result = str.dup
result.downcase!
result.delete!("'\"()[]")
result.gsub!(/\W+/, '-')
result.gsub!(/-+\z/, '') if result[-1] == '-'
result.gsub!(/\A-+/, '') if result[0] == '-'
result
end
end

View File

@@ -1,15 +0,0 @@
require 'stackprof' rescue fail("install stackprof extension/gem")
require File.dirname(__FILE__) + '/theme_runner'
profiler = ThemeRunner.new
profiler.run
results = StackProf.run(mode: :cpu, out: ENV['FILENAME']) do
100.times do
profiler.run
end
end
if results.kind_of?(File)
puts "wrote stackprof dump to #{results.path}"
else
StackProf::Report.new(results).print_text(false, 20)
end

View File

@@ -8,6 +8,7 @@
require 'rubygems'
require 'active_support'
require 'active_support/json'
require 'yaml'
require 'digest/md5'
require File.dirname(__FILE__) + '/shopify/liquid'
@@ -64,52 +65,17 @@ class ThemeRunner
end
def run_profile
RubyProf.measure_mode = RubyProf::WALL_TIME
# Dup assigns because will make some changes to them
assigns = Database.tables.dup
@tests.each do |liquid, layout, template_name|
# Compute page_tempalte outside of profiler run, uninteresting to profiler
html = nil
page_template = File.basename(template_name, File.extname(template_name))
unless @started
RubyProf.start
RubyProf.pause
@started = true
end
html = nil
RubyProf.resume
html = compile_and_render(liquid, layout, assigns, page_template, template_name)
RubyProf.pause
# return the result and the MD5 of the content, this can be used to detect regressions between liquid version
$stdout.puts "* rendered template %s, content: %s" % [template_name, Digest::MD5.hexdigest(html)]
# Uncomment to dump html files to /tmp so that you can inspect for errors
# File.open("/tmp/#{File.basename(template_name)}.html", "w+") { |fp| fp <<html}
end
RubyProf.stop
end
def compile_and_render(template, layout, assigns, page_template, template_file)
tmpl = Liquid::Template.new
tmpl.assigns['page_title'] = 'Page title'
tmpl.assigns['template'] = page_template
tmpl.registers[:file_system] = ThemeRunner::FileSystem.new(File.dirname(template_file))
content_for_layout = tmpl.parse(template).render(assigns)
content_for_layout = tmpl.parse(template).render!(assigns)
if layout
assigns['content_for_layout'] = content_for_layout
tmpl.parse(layout).render(assigns)
tmpl.parse(layout).render!(assigns)
else
content_for_layout
end

View File

@@ -93,9 +93,9 @@ class BlankTest < Test::Unit::TestCase
def test_include_is_blank
Liquid::Template.file_system = BlankTestFileSystem.new
assert_equal "foobar"*(N+1), Template.parse(wrap("{% include 'foobar' %}")).render()
assert_equal " foobar "*(N+1), Template.parse(wrap("{% include ' foobar ' %}")).render()
assert_equal " ", Template.parse(" {% include ' ' %} ").render()
assert_template_result "foobar"*(N+1), wrap("{% include 'foobar' %}")
assert_template_result " foobar "*(N+1), wrap("{% include ' foobar ' %}")
assert_template_result " "*(N+1), wrap(" {% include ' ' %} ")
end
def test_case_is_blank

View File

@@ -19,7 +19,7 @@ class CaptureTest < Test::Unit::TestCase
{{var}}
END_TEMPLATE
template = Template.parse(template_source)
rendered = template.render
rendered = template.render!
assert_equal "test-string", rendered.gsub(/\s/, '')
end
@@ -34,7 +34,7 @@ class CaptureTest < Test::Unit::TestCase
{{ first }}-{{ second }}
END_TEMPLATE
template = Template.parse(template_source)
rendered = template.render
rendered = template.render!
assert_equal "3-3", rendered.gsub(/\s/, '')
end
end # CaptureTest

View File

@@ -176,8 +176,8 @@ class ContextTest < Test::Unit::TestCase
end
Template.register_filter(global)
assert_equal 'Global test', Template.parse("{{'test' | notice }}").render
assert_equal 'Local test', Template.parse("{{'test' | notice }}").render({}, :filters => [local])
assert_equal 'Global test', Template.parse("{{'test' | notice }}").render!
assert_equal 'Local test', Template.parse("{{'test' | notice }}").render!({}, :filters => [local])
end
def test_only_intended_filters_make_it_there

View File

@@ -106,140 +106,140 @@ class DropsTest < Test::Unit::TestCase
def test_product_drop
assert_nothing_raised do
tpl = Liquid::Template.parse( ' ' )
tpl.render('product' => ProductDrop.new)
tpl.render!('product' => ProductDrop.new)
end
end
def test_drop_does_only_respond_to_whitelisted_methods
assert_equal "", Liquid::Template.parse("{{ product.inspect }}").render('product' => ProductDrop.new)
assert_equal "", Liquid::Template.parse("{{ product.pretty_inspect }}").render('product' => ProductDrop.new)
assert_equal "", Liquid::Template.parse("{{ product.whatever }}").render('product' => ProductDrop.new)
assert_equal "", Liquid::Template.parse('{{ product | map: "inspect" }}').render('product' => ProductDrop.new)
assert_equal "", Liquid::Template.parse('{{ product | map: "pretty_inspect" }}').render('product' => ProductDrop.new)
assert_equal "", Liquid::Template.parse('{{ product | map: "whatever" }}').render('product' => ProductDrop.new)
assert_equal "", Liquid::Template.parse("{{ product.inspect }}").render!('product' => ProductDrop.new)
assert_equal "", Liquid::Template.parse("{{ product.pretty_inspect }}").render!('product' => ProductDrop.new)
assert_equal "", Liquid::Template.parse("{{ product.whatever }}").render!('product' => ProductDrop.new)
assert_equal "", Liquid::Template.parse('{{ product | map: "inspect" }}').render!('product' => ProductDrop.new)
assert_equal "", Liquid::Template.parse('{{ product | map: "pretty_inspect" }}').render!('product' => ProductDrop.new)
assert_equal "", Liquid::Template.parse('{{ product | map: "whatever" }}').render!('product' => ProductDrop.new)
end
def test_drops_respond_to_to_liquid
assert_equal "text1", Liquid::Template.parse("{{ product.to_liquid.texts.text }}").render('product' => ProductDrop.new)
assert_equal "text1", Liquid::Template.parse('{{ product | map: "to_liquid" | map: "texts" | map: "text" }}').render('product' => ProductDrop.new)
assert_equal "text1", Liquid::Template.parse("{{ product.to_liquid.texts.text }}").render!('product' => ProductDrop.new)
assert_equal "text1", Liquid::Template.parse('{{ product | map: "to_liquid" | map: "texts" | map: "text" }}').render!('product' => ProductDrop.new)
end
def test_text_drop
output = Liquid::Template.parse( ' {{ product.texts.text }} ' ).render('product' => ProductDrop.new)
output = Liquid::Template.parse( ' {{ product.texts.text }} ' ).render!('product' => ProductDrop.new)
assert_equal ' text1 ', output
end
def test_unknown_method
output = Liquid::Template.parse( ' {{ product.catchall.unknown }} ' ).render('product' => ProductDrop.new)
output = Liquid::Template.parse( ' {{ product.catchall.unknown }} ' ).render!('product' => ProductDrop.new)
assert_equal ' method: unknown ', output
end
def test_integer_argument_drop
output = Liquid::Template.parse( ' {{ product.catchall[8] }} ' ).render('product' => ProductDrop.new)
output = Liquid::Template.parse( ' {{ product.catchall[8] }} ' ).render!('product' => ProductDrop.new)
assert_equal ' method: 8 ', output
end
def test_text_array_drop
output = Liquid::Template.parse( '{% for text in product.texts.array %} {{text}} {% endfor %}' ).render('product' => ProductDrop.new)
output = Liquid::Template.parse( '{% for text in product.texts.array %} {{text}} {% endfor %}' ).render!('product' => ProductDrop.new)
assert_equal ' text1 text2 ', output
end
def test_context_drop
output = Liquid::Template.parse( ' {{ context.bar }} ' ).render('context' => ContextDrop.new, 'bar' => "carrot")
output = Liquid::Template.parse( ' {{ context.bar }} ' ).render!('context' => ContextDrop.new, 'bar' => "carrot")
assert_equal ' carrot ', output
end
def test_nested_context_drop
output = Liquid::Template.parse( ' {{ product.context.foo }} ' ).render('product' => ProductDrop.new, 'foo' => "monkey")
output = Liquid::Template.parse( ' {{ product.context.foo }} ' ).render!('product' => ProductDrop.new, 'foo' => "monkey")
assert_equal ' monkey ', output
end
def test_protected
output = Liquid::Template.parse( ' {{ product.callmenot }} ' ).render('product' => ProductDrop.new)
output = Liquid::Template.parse( ' {{ product.callmenot }} ' ).render!('product' => ProductDrop.new)
assert_equal ' ', output
end
def test_object_methods_not_allowed
[:dup, :clone, :singleton_class, :eval, :class_eval, :inspect].each do |method|
output = Liquid::Template.parse(" {{ product.#{method} }} ").render('product' => ProductDrop.new)
output = Liquid::Template.parse(" {{ product.#{method} }} ").render!('product' => ProductDrop.new)
assert_equal ' ', output
end
end
def test_scope
assert_equal '1', Liquid::Template.parse( '{{ context.scopes }}' ).render('context' => ContextDrop.new)
assert_equal '2', Liquid::Template.parse( '{%for i in dummy%}{{ context.scopes }}{%endfor%}' ).render('context' => ContextDrop.new, 'dummy' => [1])
assert_equal '3', Liquid::Template.parse( '{%for i in dummy%}{%for i in dummy%}{{ context.scopes }}{%endfor%}{%endfor%}' ).render('context' => ContextDrop.new, 'dummy' => [1])
assert_equal '1', Liquid::Template.parse( '{{ context.scopes }}' ).render!('context' => ContextDrop.new)
assert_equal '2', Liquid::Template.parse( '{%for i in dummy%}{{ context.scopes }}{%endfor%}' ).render!('context' => ContextDrop.new, 'dummy' => [1])
assert_equal '3', Liquid::Template.parse( '{%for i in dummy%}{%for i in dummy%}{{ context.scopes }}{%endfor%}{%endfor%}' ).render!('context' => ContextDrop.new, 'dummy' => [1])
end
def test_scope_though_proc
assert_equal '1', Liquid::Template.parse( '{{ s }}' ).render('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] })
assert_equal '2', Liquid::Template.parse( '{%for i in dummy%}{{ s }}{%endfor%}' ).render('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] }, 'dummy' => [1])
assert_equal '3', Liquid::Template.parse( '{%for i in dummy%}{%for i in dummy%}{{ s }}{%endfor%}{%endfor%}' ).render('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] }, 'dummy' => [1])
assert_equal '1', Liquid::Template.parse( '{{ s }}' ).render!('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] })
assert_equal '2', Liquid::Template.parse( '{%for i in dummy%}{{ s }}{%endfor%}' ).render!('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] }, 'dummy' => [1])
assert_equal '3', Liquid::Template.parse( '{%for i in dummy%}{%for i in dummy%}{{ s }}{%endfor%}{%endfor%}' ).render!('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] }, 'dummy' => [1])
end
def test_scope_with_assigns
assert_equal 'variable', Liquid::Template.parse( '{% assign a = "variable"%}{{a}}' ).render('context' => ContextDrop.new)
assert_equal 'variable', Liquid::Template.parse( '{% assign a = "variable"%}{%for i in dummy%}{{a}}{%endfor%}' ).render('context' => ContextDrop.new, 'dummy' => [1])
assert_equal 'test', Liquid::Template.parse( '{% assign header_gif = "test"%}{{header_gif}}' ).render('context' => ContextDrop.new)
assert_equal 'test', Liquid::Template.parse( "{% assign header_gif = 'test'%}{{header_gif}}" ).render('context' => ContextDrop.new)
assert_equal 'variable', Liquid::Template.parse( '{% assign a = "variable"%}{{a}}' ).render!('context' => ContextDrop.new)
assert_equal 'variable', Liquid::Template.parse( '{% assign a = "variable"%}{%for i in dummy%}{{a}}{%endfor%}' ).render!('context' => ContextDrop.new, 'dummy' => [1])
assert_equal 'test', Liquid::Template.parse( '{% assign header_gif = "test"%}{{header_gif}}' ).render!('context' => ContextDrop.new)
assert_equal 'test', Liquid::Template.parse( "{% assign header_gif = 'test'%}{{header_gif}}" ).render!('context' => ContextDrop.new)
end
def test_scope_from_tags
assert_equal '1', Liquid::Template.parse( '{% for i in context.scopes_as_array %}{{i}}{% endfor %}' ).render('context' => ContextDrop.new, 'dummy' => [1])
assert_equal '12', Liquid::Template.parse( '{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}' ).render('context' => ContextDrop.new, 'dummy' => [1])
assert_equal '123', Liquid::Template.parse( '{%for a in dummy%}{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}{% endfor %}' ).render('context' => ContextDrop.new, 'dummy' => [1])
assert_equal '1', Liquid::Template.parse( '{% for i in context.scopes_as_array %}{{i}}{% endfor %}' ).render!('context' => ContextDrop.new, 'dummy' => [1])
assert_equal '12', Liquid::Template.parse( '{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}' ).render!('context' => ContextDrop.new, 'dummy' => [1])
assert_equal '123', Liquid::Template.parse( '{%for a in dummy%}{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}{% endfor %}' ).render!('context' => ContextDrop.new, 'dummy' => [1])
end
def test_access_context_from_drop
assert_equal '123', Liquid::Template.parse( '{%for a in dummy%}{{ context.loop_pos }}{% endfor %}' ).render('context' => ContextDrop.new, 'dummy' => [1,2,3])
assert_equal '123', Liquid::Template.parse( '{%for a in dummy%}{{ context.loop_pos }}{% endfor %}' ).render!('context' => ContextDrop.new, 'dummy' => [1,2,3])
end
def test_enumerable_drop
assert_equal '123', Liquid::Template.parse( '{% for c in collection %}{{c}}{% endfor %}').render('collection' => EnumerableDrop.new)
assert_equal '123', Liquid::Template.parse( '{% for c in collection %}{{c}}{% endfor %}').render!('collection' => EnumerableDrop.new)
end
def test_enumerable_drop_size
assert_equal '3', Liquid::Template.parse( '{{collection.size}}').render('collection' => EnumerableDrop.new)
assert_equal '3', Liquid::Template.parse( '{{collection.size}}').render!('collection' => EnumerableDrop.new)
end
def test_enumerable_drop_will_invoke_before_method_for_clashing_method_names
["select", "each", "map", "cycle"].each do |method|
assert_equal method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render('collection' => EnumerableDrop.new)
assert_equal method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => EnumerableDrop.new)
assert_equal method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render('collection' => RealEnumerableDrop.new)
assert_equal method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => RealEnumerableDrop.new)
assert_equal method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new)
assert_equal method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new)
assert_equal method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new)
assert_equal method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new)
end
end
def test_some_enumerable_methods_still_get_invoked
[ :count, :max ].each do |method|
assert_equal "3", Liquid::Template.parse("{{collection.#{method}}}").render('collection' => RealEnumerableDrop.new)
assert_equal "3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => RealEnumerableDrop.new)
assert_equal "3", Liquid::Template.parse("{{collection.#{method}}}").render('collection' => EnumerableDrop.new)
assert_equal "3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => EnumerableDrop.new)
assert_equal "3", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new)
assert_equal "3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new)
assert_equal "3", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new)
assert_equal "3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new)
end
assert_equal "yes", Liquid::Template.parse("{% if collection contains 3 %}yes{% endif %}").render('collection' => RealEnumerableDrop.new)
assert_equal "yes", Liquid::Template.parse("{% if collection contains 3 %}yes{% endif %}").render!('collection' => RealEnumerableDrop.new)
[ :min, :first ].each do |method|
assert_equal "1", Liquid::Template.parse("{{collection.#{method}}}").render('collection' => RealEnumerableDrop.new)
assert_equal "1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => RealEnumerableDrop.new)
assert_equal "1", Liquid::Template.parse("{{collection.#{method}}}").render('collection' => EnumerableDrop.new)
assert_equal "1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render('collection' => EnumerableDrop.new)
assert_equal "1", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new)
assert_equal "1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new)
assert_equal "1", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new)
assert_equal "1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new)
end
end
def test_empty_string_value_access
assert_equal '', Liquid::Template.parse('{{ product[value] }}').render('product' => ProductDrop.new, 'value' => '')
assert_equal '', Liquid::Template.parse('{{ product[value] }}').render!('product' => ProductDrop.new, 'value' => '')
end
def test_nil_value_access
assert_equal '', Liquid::Template.parse('{{ product[value] }}').render('product' => ProductDrop.new, 'value' => nil)
assert_equal '', Liquid::Template.parse('{{ product[value] }}').render!('product' => ProductDrop.new, 'value' => nil)
end
def test_default_to_s_on_drops
assert_equal 'ProductDrop', Liquid::Template.parse("{{ product }}").render('product' => ProductDrop.new)
assert_equal 'EnumerableDrop', Liquid::Template.parse('{{ collection }}').render('collection' => EnumerableDrop.new)
assert_equal 'ProductDrop', Liquid::Template.parse("{{ product }}").render!('product' => ProductDrop.new)
assert_equal 'EnumerableDrop', Liquid::Template.parse('{{ collection }}').render!('collection' => EnumerableDrop.new)
end
end # DropsTest

View File

@@ -113,13 +113,13 @@ class FiltersInTemplate < Test::Unit::TestCase
def test_local_global
Template.register_filter(MoneyFilter)
assert_equal " 1000$ ", Template.parse("{{1000 | money}}").render(nil, nil)
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render(nil, :filters => CanadianMoneyFilter)
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render(nil, :filters => [CanadianMoneyFilter])
assert_equal " 1000$ ", Template.parse("{{1000 | money}}").render!(nil, nil)
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, :filters => CanadianMoneyFilter)
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, :filters => [CanadianMoneyFilter])
end
def test_local_filter_with_deprecated_syntax
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render(nil, CanadianMoneyFilter)
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render(nil, [CanadianMoneyFilter])
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, CanadianMoneyFilter)
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, [CanadianMoneyFilter])
end
end # FiltersTest

View File

@@ -77,11 +77,11 @@ class ModuleExTest < Test::Unit::TestCase
end
def test_should_use_regular_objects_as_drops
assert_equal 'allowedA', Liquid::Template.parse("{{ a.allowedA }}").render('a'=>@a)
assert_equal 'allowedB', Liquid::Template.parse("{{ a.chainedB.allowedB }}").render('a'=>@a)
assert_equal 'allowedC', Liquid::Template.parse("{{ a.chainedB.chainedC.allowedC }}").render('a'=>@a)
assert_equal 'another_allowedC', Liquid::Template.parse("{{ a.chainedB.chainedC.another_allowedC }}").render('a'=>@a)
assert_equal '', Liquid::Template.parse("{{ a.restricted }}").render('a'=>@a)
assert_equal '', Liquid::Template.parse("{{ a.unknown }}").render('a'=>@a)
assert_template_result 'allowedA', "{{ a.allowedA }}", 'a'=>@a
assert_template_result 'allowedB', "{{ a.chainedB.allowedB }}", 'a'=>@a
assert_template_result 'allowedC', "{{ a.chainedB.chainedC.allowedC }}", 'a'=>@a
assert_template_result 'another_allowedC', "{{ a.chainedB.chainedC.another_allowedC }}", 'a'=>@a
assert_template_result '', "{{ a.restricted }}", 'a'=>@a
assert_template_result '', "{{ a.unknown }}", 'a'=>@a
end
end # ModuleExTest

View File

@@ -41,76 +41,76 @@ class OutputTest < Test::Unit::TestCase
text = %| {{best_cars}} |
expected = %| bmw |
assert_equal expected, Template.parse(text).render(@assigns)
assert_equal expected, Template.parse(text).render!(@assigns)
end
def test_variable_traversing
text = %| {{car.bmw}} {{car.gm}} {{car.bmw}} |
expected = %| good bad good |
assert_equal expected, Template.parse(text).render(@assigns)
assert_equal expected, Template.parse(text).render!(@assigns)
end
def test_variable_piping
text = %( {{ car.gm | make_funny }} )
expected = %| LOL |
assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
end
def test_variable_piping_with_input
text = %( {{ car.gm | cite_funny }} )
expected = %| LOL: bad |
assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
end
def test_variable_piping_with_args
text = %! {{ car.gm | add_smiley : ':-(' }} !
expected = %| bad :-( |
assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
end
def test_variable_piping_with_no_args
text = %! {{ car.gm | add_smiley }} !
expected = %| bad :-) |
assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
end
def test_multiple_variable_piping_with_args
text = %! {{ car.gm | add_smiley : ':-(' | add_smiley : ':-('}} !
expected = %| bad :-( :-( |
assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
end
def test_variable_piping_with_multiple_args
text = %! {{ car.gm | add_tag : 'span', 'bar'}} !
expected = %| <span id="bar">bad</span> |
assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
end
def test_variable_piping_with_variable_args
text = %! {{ car.gm | add_tag : 'span', car.bmw}} !
expected = %| <span id="good">bad</span> |
assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
end
def test_multiple_pipings
text = %( {{ best_cars | cite_funny | paragraph }} )
expected = %| <p>LOL: bmw</p> |
assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
end
def test_link_to
text = %( {{ 'Typo' | link_to: 'http://typo.leetsoft.com' }} )
expected = %| <a href="http://typo.leetsoft.com">Typo</a> |
assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
end
end # OutputTest

View File

@@ -7,7 +7,7 @@ class ParsingQuirksTest < Test::Unit::TestCase
text = %| div { font-weight: bold; } |
template = Template.parse(text)
assert_equal text, template.render
assert_equal text, template.render!
assert_equal [String], template.root.nodelist.collect {|i| i.class}
end

View File

@@ -13,14 +13,14 @@ class SecurityTest < Test::Unit::TestCase
text = %( {{ '1+1' | instance_eval }} )
expected = %| 1+1 |
assert_equal expected, Template.parse(text).render(@assigns)
assert_equal expected, Template.parse(text).render!(@assigns)
end
def test_no_existing_instance_eval
text = %( {{ '1+1' | __instance_eval__ }} )
expected = %| 1+1 |
assert_equal expected, Template.parse(text).render(@assigns)
assert_equal expected, Template.parse(text).render!(@assigns)
end
@@ -28,7 +28,7 @@ class SecurityTest < Test::Unit::TestCase
text = %( {{ '1+1' | instance_eval }} )
expected = %| 1+1 |
assert_equal expected, Template.parse(text).render(@assigns)
assert_equal expected, Template.parse(text).render!(@assigns)
end
@@ -36,7 +36,7 @@ class SecurityTest < Test::Unit::TestCase
text = %( {{ '1+1' | add_one | instance_eval }} )
expected = %| 1+1 + 1 |
assert_equal expected, Template.parse(text).render(@assigns, :filters => SecurityFilter)
assert_equal expected, Template.parse(text).render!(@assigns, :filters => SecurityFilter)
end
def test_does_not_add_filters_to_symbol_table
@@ -47,7 +47,7 @@ class SecurityTest < Test::Unit::TestCase
template = Template.parse(test)
assert_equal [], (Symbol.all_symbols - current_symbols)
template.render
template.render!
assert_equal [], (Symbol.all_symbols - current_symbols)
end

View File

@@ -126,37 +126,38 @@ class StandardFiltersTest < Test::Unit::TestCase
end
def test_map_doesnt_call_arbitrary_stuff
assert_equal "", Liquid::Template.parse('{{ "foo" | map: "__id__" }}').render
assert_equal "", Liquid::Template.parse('{{ "foo" | map: "inspect" }}').render
assert_template_result "", '{{ "foo" | map: "__id__" }}'
assert_template_result "", '{{ "foo" | map: "inspect" }}'
end
def test_map_calls_to_liquid
t = TestThing.new
assert_equal "woot: 1", Liquid::Template.parse('{{ foo | map: "whatever" }}').render("foo" => [t])
assert_template_result "woot: 1", '{{ foo | map: "whatever" }}', "foo" => [t]
end
def test_map_on_hashes
assert_equal "4217", Liquid::Template.parse('{{ thing | map: "foo" | map: "bar" }}').render("thing" => { "foo" => [ { "bar" => 42 }, { "bar" => 17 } ] })
assert_template_result "4217", '{{ thing | map: "foo" | map: "bar" }}',
"thing" => { "foo" => [ { "bar" => 42 }, { "bar" => 17 } ] }
end
def test_sort_calls_to_liquid
t = TestThing.new
assert_equal "woot: 1", Liquid::Template.parse('{{ foo | sort: "whatever" }}').render("foo" => [t])
assert_template_result "woot: 1", '{{ foo | sort: "whatever" }}', "foo" => [t]
end
def test_map_over_proc
drop = TestDrop.new
p = Proc.new{ drop }
templ = '{{ procs | map: "test" }}'
assert_equal "testfoo", Liquid::Template.parse(templ).render("procs" => [p])
assert_template_result "testfoo", templ, "procs" => [p]
end
def test_map_works_on_enumerables
assert_equal "123", Liquid::Template.parse('{{ foo | map: "foo" }}').render!("foo" => TestEnumerable.new)
assert_template_result "123", '{{ foo | map: "foo" }}', "foo" => TestEnumerable.new
end
def test_sort_works_on_enumerables
assert_equal "213", Liquid::Template.parse('{{ foo | sort: "bar" | map: "foo" }}').render!("foo" => TestEnumerable.new)
assert_template_result "213", '{{ foo | sort: "bar" | map: "foo" }}', "foo" => TestEnumerable.new
end
def test_date
@@ -257,7 +258,7 @@ class StandardFiltersTest < Test::Unit::TestCase
assert_template_result "4", "{{ 14 | divided_by:3 }}"
assert_template_result "5", "{{ 15 | divided_by:3 }}"
assert_template_result "Liquid error: divided by 0", "{{ 5 | divided_by:0 }}"
assert_equal "Liquid error: divided by 0", Template.parse("{{ 5 | divided_by:0 }}").render
assert_template_result "0.5", "{{ 2.0 | divided_by:4 }}"
end

View File

@@ -78,57 +78,52 @@ class IncludeTagTest < Test::Unit::TestCase
def test_include_tag_looks_for_file_system_in_registers_first
assert_equal 'from OtherFileSystem',
Template.parse("{% include 'pick_a_source' %}").render({}, :registers => {:file_system => OtherFileSystem.new})
Template.parse("{% include 'pick_a_source' %}").render!({}, :registers => {:file_system => OtherFileSystem.new})
end
def test_include_tag_with
assert_equal "Product: Draft 151cm ",
Template.parse("{% include 'product' with products[0] %}").render( "products" => [ {'title' => 'Draft 151cm'}, {'title' => 'Element 155cm'} ] )
assert_template_result "Product: Draft 151cm ",
"{% include 'product' with products[0] %}", "products" => [ {'title' => 'Draft 151cm'}, {'title' => 'Element 155cm'} ]
end
def test_include_tag_with_default_name
assert_equal "Product: Draft 151cm ",
Template.parse("{% include 'product' %}").render( "product" => {'title' => 'Draft 151cm'} )
assert_template_result "Product: Draft 151cm ",
"{% include 'product' %}", "product" => {'title' => 'Draft 151cm'}
end
def test_include_tag_for
assert_equal "Product: Draft 151cm Product: Element 155cm ",
Template.parse("{% include 'product' for products %}").render( "products" => [ {'title' => 'Draft 151cm'}, {'title' => 'Element 155cm'} ] )
assert_template_result "Product: Draft 151cm Product: Element 155cm ",
"{% include 'product' for products %}", "products" => [ {'title' => 'Draft 151cm'}, {'title' => 'Element 155cm'} ]
end
def test_include_tag_with_local_variables
assert_equal "Locale: test123 ",
Template.parse("{% include 'locale_variables' echo1: 'test123' %}").render
assert_template_result "Locale: test123 ", "{% include 'locale_variables' echo1: 'test123' %}"
end
def test_include_tag_with_multiple_local_variables
assert_equal "Locale: test123 test321",
Template.parse("{% include 'locale_variables' echo1: 'test123', echo2: 'test321' %}").render
assert_template_result "Locale: test123 test321",
"{% include 'locale_variables' echo1: 'test123', echo2: 'test321' %}"
end
def test_include_tag_with_multiple_local_variables_from_context
assert_equal "Locale: test123 test321",
Template.parse("{% include 'locale_variables' echo1: echo1, echo2: more_echos.echo2 %}").render('echo1' => 'test123', 'more_echos' => { "echo2" => 'test321'})
assert_template_result "Locale: test123 test321",
"{% include 'locale_variables' echo1: echo1, echo2: more_echos.echo2 %}",
'echo1' => 'test123', 'more_echos' => { "echo2" => 'test321'}
end
def test_nested_include_tag
assert_equal "body body_detail",
Template.parse("{% include 'body' %}").render
assert_template_result "body body_detail", "{% include 'body' %}"
assert_equal "header body body_detail footer",
Template.parse("{% include 'nested_template' %}").render
assert_template_result "header body body_detail footer", "{% include 'nested_template' %}"
end
def test_nested_include_with_variable
assert_template_result "Product: Draft 151cm details ",
"{% include 'nested_product_template' with product %}", "product" => {"title" => 'Draft 151cm'}
assert_equal "Product: Draft 151cm details ",
Template.parse("{% include 'nested_product_template' with product %}").render("product" => {"title" => 'Draft 151cm'})
assert_equal "Product: Draft 151cm details Product: Element 155cm details ",
Template.parse("{% include 'nested_product_template' for products %}").render("products" => [{"title" => 'Draft 151cm'}, {"title" => 'Element 155cm'}])
assert_template_result "Product: Draft 151cm details Product: Element 155cm details ",
"{% include 'nested_product_template' for products %}", "products" => [{"title" => 'Draft 151cm'}, {"title" => 'Element 155cm'}]
end
def test_recursively_included_template_does_not_produce_endless_loop
@@ -160,34 +155,33 @@ class IncludeTagTest < Test::Unit::TestCase
end
def test_dynamically_choosen_template
assert_template_result "Test123", "{% include template %}", "template" => 'Test123'
assert_template_result "Test321", "{% include template %}", "template" => 'Test321'
assert_equal "Test123", Template.parse("{% include template %}").render("template" => 'Test123')
assert_equal "Test321", Template.parse("{% include template %}").render("template" => 'Test321')
assert_equal "Product: Draft 151cm ", Template.parse("{% include template for product %}").render("template" => 'product', 'product' => { 'title' => 'Draft 151cm'})
assert_template_result "Product: Draft 151cm ", "{% include template for product %}",
"template" => 'product', 'product' => { 'title' => 'Draft 151cm'}
end
def test_include_tag_caches_second_read_of_same_partial
file_system = CountingFileSystem.new
assert_equal 'from CountingFileSystemfrom CountingFileSystem',
Template.parse("{% include 'pick_a_source' %}{% include 'pick_a_source' %}").render({}, :registers => {:file_system => file_system})
Template.parse("{% include 'pick_a_source' %}{% include 'pick_a_source' %}").render!({}, :registers => {:file_system => file_system})
assert_equal 1, file_system.count
end
def test_include_tag_doesnt_cache_partials_across_renders
file_system = CountingFileSystem.new
assert_equal 'from CountingFileSystem',
Template.parse("{% include 'pick_a_source' %}").render({}, :registers => {:file_system => file_system})
Template.parse("{% include 'pick_a_source' %}").render!({}, :registers => {:file_system => file_system})
assert_equal 1, file_system.count
assert_equal 'from CountingFileSystem',
Template.parse("{% include 'pick_a_source' %}").render({}, :registers => {:file_system => file_system})
Template.parse("{% include 'pick_a_source' %}").render!({}, :registers => {:file_system => file_system})
assert_equal 2, file_system.count
end
def test_include_tag_within_if_statement
assert_equal "foo_if_true",
Template.parse("{% if true %}{% include 'foo_if_true' %}{% endif %}").render
assert_template_result "foo_if_true", "{% if true %}{% include 'foo_if_true' %}{% endif %}"
end
def test_custom_include_tag
@@ -195,7 +189,7 @@ class IncludeTagTest < Test::Unit::TestCase
Liquid::Template.tags['include'] = CustomInclude
begin
assert_equal "custom_foo",
Template.parse("{% include 'custom_foo' %}").render
Template.parse("{% include 'custom_foo' %}").render!
ensure
Liquid::Template.tags['include'] = original_tag
end
@@ -206,7 +200,7 @@ class IncludeTagTest < Test::Unit::TestCase
Liquid::Template.tags['include'] = CustomInclude
begin
assert_equal "custom_foo_if_true",
Template.parse("{% if true %}{% include 'custom_foo_if_true' %}{% endif %}").render
Template.parse("{% if true %}{% include 'custom_foo_if_true' %}{% endif %}").render!
ensure
Liquid::Template.tags['include'] = original_tag
end

View File

@@ -20,5 +20,6 @@ class RawTagTest < Test::Unit::TestCase
assert_template_result ' Foobar {% invalid {% {% endraw ', '{% raw %} Foobar {% invalid {% {% endraw {% endraw %}'
assert_template_result ' Foobar {% {% {% ', '{% raw %} Foobar {% {% {% {% endraw %}'
assert_template_result ' test {% raw %} {% endraw %}', '{% raw %} test {% raw %} {% {% endraw %}endraw %}'
assert_template_result ' Foobar {{ invalid 1', '{% raw %} Foobar {{ invalid {% endraw %}{{ 1 }}'
end
end

View File

@@ -4,7 +4,7 @@ class StandardTagTest < Test::Unit::TestCase
include Liquid
def test_tag
tag = Tag.new('tag', [], [])
tag = Tag.parse('tag', [], [], {})
assert_equal 'liquid::tag', tag.name
assert_equal '', tag.render(Context.new)
end
@@ -180,11 +180,11 @@ class StandardTagTest < Test::Unit::TestCase
# Example from the shopify forums
code = %q({% case collection.handle %}{% when 'menswear-jackets' %}{% assign ptitle = 'menswear' %}{% when 'menswear-t-shirts' %}{% assign ptitle = 'menswear' %}{% else %}{% assign ptitle = 'womenswear' %}{% endcase %}{{ ptitle }})
template = Liquid::Template.parse(code)
assert_equal "menswear", template.render("collection" => {'handle' => 'menswear-jackets'})
assert_equal "menswear", template.render("collection" => {'handle' => 'menswear-t-shirts'})
assert_equal "womenswear", template.render("collection" => {'handle' => 'x'})
assert_equal "womenswear", template.render("collection" => {'handle' => 'y'})
assert_equal "womenswear", template.render("collection" => {'handle' => 'z'})
assert_equal "menswear", template.render!("collection" => {'handle' => 'menswear-jackets'})
assert_equal "menswear", template.render!("collection" => {'handle' => 'menswear-t-shirts'})
assert_equal "womenswear", template.render!("collection" => {'handle' => 'x'})
assert_equal "womenswear", template.render!("collection" => {'handle' => 'y'})
assert_equal "womenswear", template.render!("collection" => {'handle' => 'z'})
end
def test_case_when_or
@@ -218,7 +218,7 @@ class StandardTagTest < Test::Unit::TestCase
end
def test_assign
assert_equal 'variable', Liquid::Template.parse( '{% assign a = "variable"%}{{a}}').render
assert_template_result 'variable', '{% assign a = "variable"%}{{a}}'
end
def test_assign_unassigned
@@ -227,12 +227,11 @@ class StandardTagTest < Test::Unit::TestCase
end
def test_assign_an_empty_string
assert_equal '', Liquid::Template.parse( '{% assign a = ""%}{{a}}' ).render
assert_template_result '', '{% assign a = ""%}{{a}}'
end
def test_assign_is_global
assert_equal 'variable',
Liquid::Template.parse( '{%for i in (1..2) %}{% assign a = "variable"%}{% endfor %}{{a}}' ).render
assert_template_result 'variable', '{%for i in (1..2) %}{% assign a = "variable"%}{% endfor %}{{a}}'
end
def test_case_detects_bad_syntax
@@ -297,4 +296,8 @@ class StandardTagTest < Test::Unit::TestCase
assigns = {'array' => [ 1, 1, 1, 1] }
assert_template_result('1','{%for item in array%}{%ifchanged%}{{item}}{% endifchanged %}{%endfor%}',assigns)
end
def test_multiline_tag
assert_template_result '0 1 2 3', "0{%\nfor i in (1..3)\n%} {{\ni\n}}{%\nendfor\n%}"
end
end # StandardTagTest

View File

@@ -4,93 +4,78 @@ class StatementsTest < Test::Unit::TestCase
include Liquid
def test_true_eql_true
text = %| {% if true == true %} true {% else %} false {% endif %} |
expected = %| true |
assert_equal expected, Template.parse(text).render
text = ' {% if true == true %} true {% else %} false {% endif %} '
assert_template_result ' true ', text
end
def test_true_not_eql_true
text = %| {% if true != true %} true {% else %} false {% endif %} |
expected = %| false |
assert_equal expected, Template.parse(text).render
text = ' {% if true != true %} true {% else %} false {% endif %} '
assert_template_result ' false ', text
end
def test_true_lq_true
text = %| {% if 0 > 0 %} true {% else %} false {% endif %} |
expected = %| false |
assert_equal expected, Template.parse(text).render
text = ' {% if 0 > 0 %} true {% else %} false {% endif %} '
assert_template_result ' false ', text
end
def test_one_lq_zero
text = %| {% if 1 > 0 %} true {% else %} false {% endif %} |
expected = %| true |
assert_equal expected, Template.parse(text).render
text = ' {% if 1 > 0 %} true {% else %} false {% endif %} '
assert_template_result ' true ', text
end
def test_zero_lq_one
text = %| {% if 0 < 1 %} true {% else %} false {% endif %} |
expected = %| true |
assert_equal expected, Template.parse(text).render
text = ' {% if 0 < 1 %} true {% else %} false {% endif %} '
assert_template_result ' true ', text
end
def test_zero_lq_or_equal_one
text = %| {% if 0 <= 0 %} true {% else %} false {% endif %} |
expected = %| true |
assert_equal expected, Template.parse(text).render
text = ' {% if 0 <= 0 %} true {% else %} false {% endif %} '
assert_template_result ' true ', text
end
def test_zero_lq_or_equal_one_involving_nil
text = %| {% if null <= 0 %} true {% else %} false {% endif %} |
expected = %| false |
assert_equal expected, Template.parse(text).render
text = ' {% if null <= 0 %} true {% else %} false {% endif %} '
assert_template_result ' false ', text
text = %| {% if 0 <= null %} true {% else %} false {% endif %} |
expected = %| false |
assert_equal expected, Template.parse(text).render
text = ' {% if 0 <= null %} true {% else %} false {% endif %} '
assert_template_result ' false ', text
end
def test_zero_lqq_or_equal_one
text = %| {% if 0 >= 0 %} true {% else %} false {% endif %} |
expected = %| true |
assert_equal expected, Template.parse(text).render
text = ' {% if 0 >= 0 %} true {% else %} false {% endif %} '
assert_template_result ' true ', text
end
def test_strings
text = %| {% if 'test' == 'test' %} true {% else %} false {% endif %} |
expected = %| true |
assert_equal expected, Template.parse(text).render
text = " {% if 'test' == 'test' %} true {% else %} false {% endif %} "
assert_template_result ' true ', text
end
def test_strings_not_equal
text = %| {% if 'test' != 'test' %} true {% else %} false {% endif %} |
expected = %| false |
assert_equal expected, Template.parse(text).render
text = " {% if 'test' != 'test' %} true {% else %} false {% endif %} "
assert_template_result ' false ', text
end
def test_var_strings_equal
text = %| {% if var == "hello there!" %} true {% else %} false {% endif %} |
expected = %| true |
assert_equal expected, Template.parse(text).render('var' => 'hello there!')
text = ' {% if var == "hello there!" %} true {% else %} false {% endif %} '
assert_template_result ' true ', text, 'var' => 'hello there!'
end
def test_var_strings_are_not_equal
text = %| {% if "hello there!" == var %} true {% else %} false {% endif %} |
expected = %| true |
assert_equal expected, Template.parse(text).render('var' => 'hello there!')
text = ' {% if "hello there!" == var %} true {% else %} false {% endif %} '
assert_template_result ' true ', text, 'var' => 'hello there!'
end
def test_var_and_long_string_are_equal
text = %| {% if var == 'hello there!' %} true {% else %} false {% endif %} |
expected = %| true |
assert_equal expected, Template.parse(text).render('var' => 'hello there!')
text = " {% if var == 'hello there!' %} true {% else %} false {% endif %} "
assert_template_result ' true ', text, 'var' => 'hello there!'
end
def test_var_and_long_string_are_equal_backwards
text = %| {% if 'hello there!' == var %} true {% else %} false {% endif %} |
expected = %| true |
assert_equal expected, Template.parse(text).render('var' => 'hello there!')
text = " {% if 'hello there!' == var %} true {% else %} false {% endif %} "
assert_template_result ' true ', text, 'var' => 'hello there!'
end
#def test_is_nil
@@ -101,34 +86,28 @@ class StatementsTest < Test::Unit::TestCase
#end
def test_is_collection_empty
text = %| {% if array == empty %} true {% else %} false {% endif %} |
expected = %| true |
assert_equal expected, Template.parse(text).render('array' => [])
text = ' {% if array == empty %} true {% else %} false {% endif %} '
assert_template_result ' true ', text, 'array' => []
end
def test_is_not_collection_empty
text = %| {% if array == empty %} true {% else %} false {% endif %} |
expected = %| false |
assert_equal expected, Template.parse(text).render('array' => [1,2,3])
text = ' {% if array == empty %} true {% else %} false {% endif %} '
assert_template_result ' false ', text, 'array' => [1,2,3]
end
def test_nil
text = %| {% if var == nil %} true {% else %} false {% endif %} |
expected = %| true |
assert_equal expected, Template.parse(text).render('var' => nil)
text = ' {% if var == nil %} true {% else %} false {% endif %} '
assert_template_result ' true ', text, 'var' => nil
text = %| {% if var == null %} true {% else %} false {% endif %} |
expected = %| true |
assert_equal expected, Template.parse(text).render('var' => nil)
text = ' {% if var == null %} true {% else %} false {% endif %} '
assert_template_result ' true ', text, 'var' => nil
end
def test_not_nil
text = %| {% if var != nil %} true {% else %} false {% endif %} |
expected = %| true |
assert_equal expected, Template.parse(text).render('var' => 1 )
text = ' {% if var != nil %} true {% else %} false {% endif %} '
assert_template_result ' true ', text, 'var' => 1
text = %| {% if var != null %} true {% else %} false {% endif %} |
expected = %| true |
assert_equal expected, Template.parse(text).render('var' => 1 )
text = ' {% if var != null %} true {% else %} false {% endif %} '
assert_template_result ' true ', text, 'var' => 1
end
end # StatementsTest

View File

@@ -1,6 +1,6 @@
require 'test_helper'
class HtmlTagTest < Test::Unit::TestCase
class TableRowTest < Test::Unit::TestCase
include Liquid
class ArrayDrop < Liquid::Drop
@@ -15,7 +15,7 @@ class HtmlTagTest < Test::Unit::TestCase
end
end
def test_html_table
def test_table_row
assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
'{% tablerow n in numbers cols:3%} {{n}} {% endtablerow %}',
@@ -26,14 +26,14 @@ class HtmlTagTest < Test::Unit::TestCase
'numbers' => [])
end
def test_html_table_with_different_cols
def test_table_row_with_different_cols
assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td><td class=\"col4\"> 4 </td><td class=\"col5\"> 5 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 6 </td></tr>\n",
'{% tablerow n in numbers cols:5%} {{n}} {% endtablerow %}',
'numbers' => [1,2,3,4,5,6])
end
def test_html_col_counter
def test_table_col_counter
assert_template_result("<tr class=\"row1\">\n<td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n<tr class=\"row2\"><td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n<tr class=\"row3\"><td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n",
'{% tablerow n in numbers cols:2%}{{tablerowloop.col}}{% endtablerow %}',
'numbers' => [1,2,3,4,5,6])
@@ -60,4 +60,4 @@ class HtmlTagTest < Test::Unit::TestCase
'{% tablerow n in numbers cols:3 offset:1 limit:6%} {{n}} {% endtablerow %}',
'numbers' => [0,1,2,3,4,5,6,7])
end
end # HtmlTagTest
end

View File

@@ -27,57 +27,57 @@ class TemplateTest < Test::Unit::TestCase
def test_instance_assigns_persist_on_same_template_object_between_parses
t = Template.new
assert_equal 'from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render
assert_equal 'from instance assigns', t.parse("{{ foo }}").render
assert_equal 'from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render!
assert_equal 'from instance assigns', t.parse("{{ foo }}").render!
end
def test_instance_assigns_persist_on_same_template_parsing_between_renders
t = Template.new.parse("{{ foo }}{% assign foo = 'foo' %}{{ foo }}")
assert_equal 'foo', t.render
assert_equal 'foofoo', t.render
assert_equal 'foo', t.render!
assert_equal 'foofoo', t.render!
end
def test_custom_assigns_do_not_persist_on_same_template
t = Template.new
assert_equal 'from custom assigns', t.parse("{{ foo }}").render('foo' => 'from custom assigns')
assert_equal '', t.parse("{{ foo }}").render
assert_equal 'from custom assigns', t.parse("{{ foo }}").render!('foo' => 'from custom assigns')
assert_equal '', t.parse("{{ foo }}").render!
end
def test_custom_assigns_squash_instance_assigns
t = Template.new
assert_equal 'from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render
assert_equal 'from custom assigns', t.parse("{{ foo }}").render('foo' => 'from custom assigns')
assert_equal 'from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render!
assert_equal 'from custom assigns', t.parse("{{ foo }}").render!('foo' => 'from custom assigns')
end
def test_persistent_assigns_squash_instance_assigns
t = Template.new
assert_equal 'from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render
assert_equal 'from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render!
t.assigns['foo'] = 'from persistent assigns'
assert_equal 'from persistent assigns', t.parse("{{ foo }}").render
assert_equal 'from persistent assigns', t.parse("{{ foo }}").render!
end
def test_lambda_is_called_once_from_persistent_assigns_over_multiple_parses_and_renders
t = Template.new
t.assigns['number'] = lambda { @global ||= 0; @global += 1 }
assert_equal '1', t.parse("{{number}}").render
assert_equal '1', t.parse("{{number}}").render
assert_equal '1', t.render
assert_equal '1', t.parse("{{number}}").render!
assert_equal '1', t.parse("{{number}}").render!
assert_equal '1', t.render!
@global = nil
end
def test_lambda_is_called_once_from_custom_assigns_over_multiple_parses_and_renders
t = Template.new
assigns = {'number' => lambda { @global ||= 0; @global += 1 }}
assert_equal '1', t.parse("{{number}}").render(assigns)
assert_equal '1', t.parse("{{number}}").render(assigns)
assert_equal '1', t.render(assigns)
assert_equal '1', t.parse("{{number}}").render!(assigns)
assert_equal '1', t.parse("{{number}}").render!(assigns)
assert_equal '1', t.render!(assigns)
@global = nil
end
def test_resource_limits_works_with_custom_length_method
t = Template.parse("{% assign foo = bar %}")
t.resource_limits = { :render_length_limit => 42 }
assert_equal "", t.render("bar" => SomethingWithLength.new)
assert_equal "", t.render!("bar" => SomethingWithLength.new)
end
def test_resource_limits_render_length
@@ -86,7 +86,7 @@ class TemplateTest < Test::Unit::TestCase
assert_equal "Liquid error: Memory limits exceeded", t.render()
assert t.resource_limits[:reached]
t.resource_limits = { :render_length_limit => 10 }
assert_equal "0123456789", t.render()
assert_equal "0123456789", t.render!()
assert_not_nil t.resource_limits[:render_length_current]
end
@@ -100,7 +100,7 @@ class TemplateTest < Test::Unit::TestCase
assert_equal "Liquid error: Memory limits exceeded", t.render()
assert t.resource_limits[:reached]
t.resource_limits = { :render_score_limit => 200 }
assert_equal (" foo " * 100), t.render()
assert_equal (" foo " * 100), t.render!()
assert_not_nil t.resource_limits[:render_score_current]
end
@@ -110,7 +110,7 @@ class TemplateTest < Test::Unit::TestCase
assert_equal "Liquid error: Memory limits exceeded", t.render()
assert t.resource_limits[:reached]
t.resource_limits = { :assign_score_limit => 2 }
assert_equal "", t.render()
assert_equal "", t.render!()
assert_not_nil t.resource_limits[:assign_score_current]
end
@@ -123,7 +123,7 @@ class TemplateTest < Test::Unit::TestCase
def test_resource_limits_hash_in_template_gets_updated_even_if_no_limits_are_set
t = Template.parse("{% for a in (1..100) %} {% assign foo = 1 %} {% endfor %}")
t.render()
t.render!()
assert t.resource_limits[:assign_score_current] > 0
assert t.resource_limits[:render_score_current] > 0
assert t.resource_limits[:render_length_current] > 0
@@ -133,9 +133,9 @@ class TemplateTest < Test::Unit::TestCase
t = Template.new
t.registers['lulz'] = 'haha'
drop = TemplateContextDrop.new
assert_equal 'fizzbuzz', t.parse('{{foo}}').render(drop)
assert_equal 'bar', t.parse('{{bar}}').render(drop)
assert_equal 'haha', t.parse("{{baz}}").render(drop)
assert_equal 'fizzbuzz', t.parse('{{foo}}').render!(drop)
assert_equal 'bar', t.parse('{{bar}}').render!(drop)
assert_equal 'haha', t.parse("{{baz}}").render!(drop)
end
def test_sets_default_localization_in_document

View File

@@ -21,23 +21,8 @@ class TokenizerTest < Test::Unit::TestCase
assert_equal [' ', '{% comment %}', ' ', '{% endcomment %}', ' '], tokenize(" {% comment %} {% endcomment %} ")
end
def test_tokenize_incomplete_end
assert_tokens 'before{{ incomplete }after', ['before', '{{ incomplete }', 'after']
assert_tokens 'before{% incomplete %after', ['before', '{%', ' incomplete %after']
end
def test_tokenize_no_end
assert_tokens 'before{{ unterminated ', ['before', '{{', ' unterminated ']
assert_tokens 'before{% unterminated ', ['before', '{%', ' unterminated ']
end
private
def assert_tokens(source, expected)
assert_equal expected, tokenize(source)
assert_equal expected, old_tokenize(source)
end
def tokenize(source)
tokenizer = Liquid::Tokenizer.new(source)
tokens = []
@@ -46,19 +31,4 @@ class TokenizerTest < Test::Unit::TestCase
end
tokens
end
AnyStartingTag = /\{\{|\{\%/
VariableIncompleteEnd = /\}\}?/
PartialTemplateParser = /#{Liquid::TagStart}.*?#{Liquid::TagEnd}|#{Liquid::VariableStart}.*?#{VariableIncompleteEnd}/o
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/o
def old_tokenize(source)
return [] if source.to_s.empty?
tokens = source.split(TemplateParser)
# removes the rogue empty element at the beginning of the array
tokens.shift if tokens[0] and tokens[0].empty?
tokens
end
end

View File

@@ -51,9 +51,11 @@ class VariableTest < Test::Unit::TestCase
end
def test_filter_with_date_parameter
var = Variable.new(%! '2006-06-06' | date: "%m/%d/%Y"!)
assert_equal "'2006-06-06'", var.name
assert_equal [["date",["\"%m/%d/%Y\""]]], var.filters
end
def test_filters_without_whitespace
@@ -71,7 +73,7 @@ class VariableTest < Test::Unit::TestCase
end
def test_symbol
var = Variable.new("http://disney.com/logo.gif | image: 'med'", :error_mode => :lax)
var = Variable.new("http://disney.com/logo.gif | image: 'med' ", :error_mode => :lax)
assert_equal "http://disney.com/logo.gif", var.name
assert_equal [["image",["'med'"]]], var.filters
end
@@ -139,49 +141,49 @@ class VariableResolutionTest < Test::Unit::TestCase
def test_simple_variable
template = Template.parse(%|{{test}}|)
assert_equal 'worked', template.render('test' => 'worked')
assert_equal 'worked wonderfully', template.render('test' => 'worked wonderfully')
assert_equal 'worked', template.render!('test' => 'worked')
assert_equal 'worked wonderfully', template.render!('test' => 'worked wonderfully')
end
def test_simple_with_whitespaces
template = Template.parse(%| {{ test }} |)
assert_equal ' worked ', template.render('test' => 'worked')
assert_equal ' worked wonderfully ', template.render('test' => 'worked wonderfully')
assert_equal ' worked ', template.render!('test' => 'worked')
assert_equal ' worked wonderfully ', template.render!('test' => 'worked wonderfully')
end
def test_ignore_unknown
template = Template.parse(%|{{ test }}|)
assert_equal '', template.render
assert_equal '', template.render!
end
def test_hash_scoping
template = Template.parse(%|{{ test.test }}|)
assert_equal 'worked', template.render('test' => {'test' => 'worked'})
assert_equal 'worked', template.render!('test' => {'test' => 'worked'})
end
def test_preset_assigns
template = Template.parse(%|{{ test }}|)
template.assigns['test'] = 'worked'
assert_equal 'worked', template.render
assert_equal 'worked', template.render!
end
def test_reuse_parsed_template
template = Template.parse(%|{{ greeting }} {{ name }}|)
template.assigns['greeting'] = 'Goodbye'
assert_equal 'Hello Tobi', template.render('greeting' => 'Hello', 'name' => 'Tobi')
assert_equal 'Hello ', template.render('greeting' => 'Hello', 'unknown' => 'Tobi')
assert_equal 'Hello Brian', template.render('greeting' => 'Hello', 'name' => 'Brian')
assert_equal 'Goodbye Brian', template.render('name' => 'Brian')
assert_equal 'Hello Tobi', template.render!('greeting' => 'Hello', 'name' => 'Tobi')
assert_equal 'Hello ', template.render!('greeting' => 'Hello', 'unknown' => 'Tobi')
assert_equal 'Hello Brian', template.render!('greeting' => 'Hello', 'name' => 'Brian')
assert_equal 'Goodbye Brian', template.render!('name' => 'Brian')
assert_equal({'greeting'=>'Goodbye'}, template.assigns)
end
def test_assigns_not_polluted_from_template
template = Template.parse(%|{{ test }}{% assign test = 'bar' %}{{ test }}|)
template.assigns['test'] = 'baz'
assert_equal 'bazbar', template.render
assert_equal 'bazbar', template.render
assert_equal 'foobar', template.render('test' => 'foo')
assert_equal 'bazbar', template.render
assert_equal 'bazbar', template.render!
assert_equal 'bazbar', template.render!
assert_equal 'foobar', template.render!('test' => 'foo')
assert_equal 'bazbar', template.render!
end
def test_hash_with_default_proc
@@ -195,4 +197,8 @@ class VariableResolutionTest < Test::Unit::TestCase
}
assert_equal "Unknown variable 'test'", e.message
end
def test_multiline_variable
assert_equal 'worked', Template.parse("{{\ntest\n}}").render!('test' => 'worked')
end
end # VariableTest

View File

@@ -26,13 +26,13 @@ module Test
include Liquid
def assert_template_result(expected, template, assigns = {}, message = nil)
assert_equal expected, Template.parse(template).render(assigns)
assert_equal expected, Template.parse(template).render!(assigns)
end
def assert_template_result_matches(expected, template, assigns = {}, message = nil)
return assert_template_result(expected, template, assigns, message) unless expected.is_a? Regexp
assert_match expected, Template.parse(template).render(assigns)
assert_match expected, Template.parse(template).render!(assigns)
end
def assert_match_syntax_error(match, template, registers = {})