Compare commits

...

36 Commits

Author SHA1 Message Date
Dylan Thacker-Smith
8a93a7ff55 Add convenience methods for getting a struct from a ruby object.
If we are trying to get the struct from something other than self, then we
should make sure to check the class of the object.  This util functions
make this easier.
2014-03-25 16:16:38 -04:00
Dylan Thacker-Smith
e2974ed95f Implement Block#parse_body in C. 2014-03-25 16:16:38 -04:00
Dylan Thacker-Smith
99f950c167 Rename Block#parse to parse_body since that is how it is being used. 2014-03-25 16:16:38 -04:00
Dylan Thacker-Smith
dc78e565ab Move the parse method out of Tag, only blocks need the body parsed.
The parse method should be renamed to something like parse_body,
since that is how it is used, and no non-block tags were using the
parse method.
2014-03-25 16:16:38 -04:00
Dylan Thacker-Smith
0fac50aea7 Use super rather than render_all in single block render classes. 2014-03-25 16:16:38 -04:00
Dylan Thacker-Smith
8e45b44b21 Avoid keeping track of two lists of nodes during parsing. 2014-03-25 16:16:38 -04:00
Dylan Thacker-Smith
c0832ce0d1 Return nil in Document#block_delimiter rather than an empty array.
The block delimiter is normally a string, so nil makes more sense when
there is no delimiter. We also don't want to allocate an array for no
reason.
2014-03-25 16:16:38 -04:00
Dylan Thacker-Smith
802a6671cb Remove unused Block#end_tag method.
Although the method is called, it is defined with an empty body and not
overridden to do anything else.
2014-03-25 16:16:38 -04:00
Dylan Thacker-Smith
87472e73b6 Implement tokenization in a C extension. 2014-03-25 16:15:02 -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
72 changed files with 1016 additions and 691 deletions

3
.gitignore vendored
View File

@@ -5,3 +5,6 @@ pkg
*.rbc *.rbc
.rvmrc .rvmrc
.ruby-version .ruby-version
Gemfile.lock
*.bundle
/tmp

3
Gemfile Normal file
View File

@@ -0,0 +1,3 @@
source 'https://rubygems.org'
gemspec

View File

@@ -3,6 +3,10 @@
## 3.0.0 / not yet released / branch "master" ## 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 a to_s default for liquid drops, see #306 [Adam Doeler, releod]
* Add strip, lstrip, and rstrip to standard filters [Florian Weingarten, fw42] * 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] * 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] * Fix clashing method names in enumerable drops, see #238 [Florian Weingarten, fw42]
* Make map filter work on enumerable drops, see #233 [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] * 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" ## 2.6.0 / 2013-11-25 / branch "2.6-stable"

View File

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

View File

@@ -13,7 +13,7 @@ class LiquidServlet < WEBrick::HTTPServlet::AbstractServlet
def handle(type, req, res) def handle(type, req, res)
@request, @response = req, res @request, @response = req, res
@request.path_info =~ /(\w+)$/ @request.path_info =~ /(\w+)\z/
@action = $1 || 'index' @action = $1 || 'index'
@assigns = send(@action) if respond_to?(@action) @assigns = send(@action) if respond_to?(@action)

168
ext/liquid/block.c Normal file
View File

@@ -0,0 +1,168 @@
#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);
}

8
ext/liquid/block.h Normal file
View File

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

3
ext/liquid/extconf.rb Normal file
View File

@@ -0,0 +1,3 @@
require 'mkmf'
$CFLAGS << ' -Wall'
create_makefile("liquid/liquid")

15
ext/liquid/liquid_ext.c Normal file
View File

@@ -0,0 +1,15 @@
#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();
}

15
ext/liquid/liquid_ext.h Normal file
View File

@@ -0,0 +1,15 @@
#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"
extern VALUE mLiquid;
extern VALUE cLiquidTemplate, cLiquidTag, cLiquidVariable;
#endif

113
ext/liquid/tokenizer.c Normal file
View File

@@ -0,0 +1,113 @@
#include "liquid_ext.h"
VALUE cLiquidTokenizer;
static void free_tokenizer(void *ptr)
{
struct liquid_tokenizer *tokenizer = ptr;
xfree(tokenizer);
}
static VALUE rb_allocate(VALUE klass)
{
VALUE obj;
struct liquid_tokenizer *tokenizer;
obj = Data_Make_Struct(klass, struct liquid_tokenizer, NULL, free_tokenizer, tokenizer);
return obj;
}
static VALUE rb_initialize(VALUE self, VALUE source)
{
struct liquid_tokenizer *tokenizer;
Check_Type(source, T_STRING);
Data_Get_Struct(self, struct liquid_tokenizer, tokenizer);
tokenizer->cursor = RSTRING_PTR(source);
tokenizer->length = RSTRING_LEN(source);
return Qnil;
}
void liquid_tokenizer_next(struct liquid_tokenizer *tokenizer, struct token *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;
while (cursor < last) {
if (*cursor++ != '{')
continue;
char c = *cursor++;
if (c != '%' && c != '{')
continue;
if (cursor - tokenizer->cursor > 2) {
token->type = TOKEN_STRING;
cursor -= 2;
goto found;
}
char *incomplete_end = cursor;
token->type = TOKEN_INVALID;
if (c == '%') {
while (cursor < last) {
if (*cursor++ != '%')
continue;
c = *cursor++;
while (c == '%' && cursor <= last)
c = *cursor++;
if (c != '}')
continue;
token->type = TOKEN_TAG;
goto found;
}
cursor = incomplete_end;
goto found;
} else {
while (cursor < last) {
if (*cursor++ != '}')
continue;
if (*cursor++ != '}') {
incomplete_end = cursor - 1;
continue;
}
token->type = TOKEN_VARIABLE;
goto found;
}
cursor = incomplete_end;
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)
{
struct liquid_tokenizer *tokenizer;
Data_Get_Struct(self, struct liquid_tokenizer, tokenizer);
struct token token;
liquid_tokenizer_next(tokenizer, &token);
if (token.type == TOKEN_NONE)
return Qnil;
return rb_str_new(token.str, token.length);
}
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_alias(cLiquidTokenizer, "shift", "next");
}

30
ext/liquid/tokenizer.h Normal file
View File

@@ -0,0 +1,30 @@
#ifndef LIQUID_TOKENIZER_H
#define LIQUID_TOKENIZER_H
extern VALUE cLiquidTokenizer;
enum token_type {
TOKEN_NONE,
TOKEN_INVALID,
TOKEN_STRING,
TOKEN_TAG,
TOKEN_VARIABLE
};
struct token {
enum token_type type;
char *str;
int length;
};
struct liquid_tokenizer {
char *cursor;
int length;
};
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))
#endif

21
ext/liquid/utils.c Normal file
View File

@@ -0,0 +1,21 @@
#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);
}

8
ext/liquid/utils.h Normal file
View File

@@ -0,0 +1,8 @@
#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

@@ -21,30 +21,22 @@
module Liquid module Liquid
FilterSeparator = /\|/ FilterSeparator = /\|/
ArgumentSeparator = ',' ArgumentSeparator = ','.freeze
FilterArgumentSeparator = ':' FilterArgumentSeparator = ':'.freeze
VariableAttributeSeparator = '.' VariableAttributeSeparator = '.'.freeze
TagStart = /\{\%/ TagStart = /\{\%/
TagEnd = /\%\}/ TagEnd = /\%\}/
VariableSignature = /\(?[\w\-\.\[\]]\)?/ VariableSignature = /\(?[\w\-\.\[\]]\)?/
VariableSegment = /[\w\-]/ VariableSegment = /[\w\-]/
VariableStart = /\{\{/ VariableStart = /\{\{/
VariableEnd = /\}\}/ VariableEnd = /\}\}/
VariableIncompleteEnd = /\}\}?/
QuotedString = /"[^"]*"|'[^']*'/ QuotedString = /"[^"]*"|'[^']*'/
QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o
StrictQuotedFragment = /"[^"]+"|'[^']+'|[^\s|:,]+/
FirstFilterArgument = /#{FilterArgumentSeparator}(?:#{StrictQuotedFragment})/o
OtherFilterArgument = /#{ArgumentSeparator}(?:#{StrictQuotedFragment})/o
SpacelessFilter = /^(?:'[^']+'|"[^"]+"|[^'"])*#{FilterSeparator}(?:#{StrictQuotedFragment})(?:#{FirstFilterArgument}(?:#{OtherFilterArgument})*)?/o
Expression = /(?:#{QuotedFragment}(?:#{SpacelessFilter})*)/o
TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/o TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/o
AnyStartingTag = /\{\{|\{\%/
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/o
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/o
VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o
end end
require 'liquid/liquid'
require "liquid/version" require "liquid/version"
require 'liquid/lexer' require 'liquid/lexer'
require 'liquid/parser' require 'liquid/parser'
@@ -61,7 +53,6 @@ require 'liquid/document'
require 'liquid/variable' require 'liquid/variable'
require 'liquid/file_system' require 'liquid/file_system'
require 'liquid/template' require 'liquid/template'
require 'liquid/htmltags'
require 'liquid/standardfilters' require 'liquid/standardfilters'
require 'liquid/condition' require 'liquid/condition'
require 'liquid/module_ex' require 'liquid/module_ex'

View File

@@ -1,93 +1,37 @@
module Liquid module Liquid
class Block < Tag class Block < Tag
IsTag = /^#{TagStart}/o def initialize(tag_name, markup, tokens)
IsVariable = /^#{VariableStart}/o super
FullToken = /^#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}$/o parse_body(tokens)
ContentOfVariable = /^#{VariableStart}(.*)#{VariableEnd}$/o end
def blank? def blank?
@blank || false @blank || false
end 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.new_with_options($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", :token => token, :tag_end => TagEnd.inspect))
end
when IsVariable
new_var = create_variable(token)
@nodelist << new_var
@children << new_var
@blank = false
when ''
# 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 # warnings of this block and all sub-tags
def warnings def warnings
all_warnings = [] all_warnings = []
all_warnings.concat(@warnings) if @warnings all_warnings.concat(@warnings) if @warnings
(@children || []).each do |node| (nodelist || []).each do |node|
all_warnings.concat(node.warnings || []) all_warnings.concat(node.warnings || []) if node.respond_to?(:warnings)
end end
all_warnings all_warnings
end end
def end_tag
end
def unknown_tag(tag, params, tokens) def unknown_tag(tag, params, tokens)
case tag case tag
when 'else' when 'else'.freeze
raise SyntaxError.new(options[:locale].t("errors.syntax.unexpected_else", raise SyntaxError.new(options[:locale].t("errors.syntax.unexpected_else".freeze,
:block_name => block_name)) :block_name => block_name))
when 'end' when 'end'.freeze
raise SyntaxError.new(options[:locale].t("errors.syntax.invalid_delimiter", raise SyntaxError.new(options[:locale].t("errors.syntax.invalid_delimiter".freeze,
:block_name => block_name, :block_name => block_name,
:block_delimiter => block_delimiter)) :block_delimiter => block_delimiter))
else 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
end end
@@ -103,7 +47,7 @@ module Liquid
token.scan(ContentOfVariable) do |content| token.scan(ContentOfVariable) do |content|
return Variable.new(content.first, @options) return Variable.new(content.first, @options)
end end
raise SyntaxError.new(options[:locale].t("errors.syntax.variable_termination", :token => token, :tag_end => VariableEnd.inspect)) raise SyntaxError.new(options[:locale].t("errors.syntax.variable_termination".freeze, :token => token, :tag_end => VariableEnd.inspect))
end end
def render(context) def render(context)
@@ -112,8 +56,16 @@ module Liquid
protected protected
def unterminated_variable(token)
raise SyntaxError.new(options[:locale].t("errors.syntax.variable_termination".freeze, :token => token, :tag_end => VariableEnd.inspect))
end
def unterminated_tag(token)
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_termination".freeze, :token => token, :tag_end => TagEnd.inspect))
end
def assert_missing_delimitation! 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 end
def render_all(list, context) def render_all(list, context)
@@ -138,7 +90,7 @@ module Liquid
context.increment_used_resources(:render_length_current, token_output) context.increment_used_resources(:render_length_current, token_output)
if context.resource_limits_reached? if context.resource_limits_reached?
context.resource_limits[:reached] = true context.resource_limits[:reached] = true
raise MemoryError.new("Memory limits exceeded") raise MemoryError.new("Memory limits exceeded".freeze)
end end
unless token.is_a?(Block) && token.blank? unless token.is_a?(Block) && token.blank?
output << token_output output << token_output

View File

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

View File

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

View File

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

View File

@@ -44,7 +44,7 @@ module Liquid
class LocalFileSystem class LocalFileSystem
attr_accessor :root attr_accessor :root
def initialize(root, pattern = "_%s.liquid") def initialize(root, pattern = "_%s.liquid".freeze)
@root = root @root = root
@pattern = pattern @pattern = pattern
end end
@@ -57,15 +57,15 @@ module Liquid
end end
def full_path(template_path) def full_path(template_path)
raise FileSystemError, "Illegal template name '#{template_path}'" unless template_path =~ /^[^.\/][a-zA-Z0-9_\/]+$/ 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)) File.join(root, File.dirname(template_path), @pattern % File.basename(template_path))
else else
File.join(root, @pattern % template_path) File.join(root, @pattern % template_path)
end end
raise FileSystemError, "Illegal template path '#{File.expand_path(full_path)}'" unless File.expand_path(full_path) =~ /^#{File.expand_path(root)}/ raise FileSystemError, "Illegal template path '#{File.expand_path(full_path)}'" unless File.expand_path(full_path) =~ /\A#{File.expand_path(root)}/
full_path full_path
end end

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}\">" << 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', TableRow)
end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -12,10 +12,11 @@ module Liquid
# <div class="green"> Item five</div> # <div class="green"> Item five</div>
# #
class Cycle < Tag class Cycle < Tag
SimpleSyntax = /^#{QuotedFragment}+/o SimpleSyntax = /\A#{QuotedFragment}+/o
NamedSyntax = /^(#{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 case markup
when NamedSyntax when NamedSyntax
@variables = variables_from_string($2) @variables = variables_from_string($2)
@@ -24,9 +25,8 @@ module Liquid
@variables = variables_from_string(markup) @variables = variables_from_string(markup)
@name = "'#{@variables.to_s}'" @name = "'#{@variables.to_s}'"
else else
raise SyntaxError.new(options[:locale].t("errors.syntax.cycle")) raise SyntaxError.new(options[:locale].t("errors.syntax.cycle".freeze))
end end
super
end end
def render(context) def render(context)

View File

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

View File

@@ -46,10 +46,10 @@ module Liquid
class For < Block class For < Block
Syntax = /\A(#{VariableSegment}+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o 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) parse_with_selected_parser(markup)
@nodelist = @for_block = [] @nodelist = @for_block = []
super
end end
def nodelist def nodelist
@@ -61,7 +61,7 @@ module Liquid
end end
def unknown_tag(tag, markup, tokens) def unknown_tag(tag, markup, tokens)
return super unless tag == 'else' return super unless tag == 'else'.freeze
@nodelist = @else_block = [] @nodelist = @else_block = []
end end
@@ -74,13 +74,13 @@ module Liquid
# Maintains Ruby 1.8.7 String#each behaviour on 1.9 # Maintains Ruby 1.8.7 String#each behaviour on 1.9
return render_else(context) unless iterable?(collection) 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 context.registers[:for][@name].to_i
else else
context[@attributes['offset']].to_i context[@attributes['offset'.freeze]].to_i
end end
limit = context[@attributes['limit']] limit = context[@attributes['limit'.freeze]]
to = limit ? limit.to_i + from : nil to = limit ? limit.to_i + from : nil
segment = Utils.slice_collection(collection, from, to) segment = Utils.slice_collection(collection, from, to)
@@ -99,15 +99,16 @@ module Liquid
context.stack do context.stack do
segment.each_with_index do |item, index| segment.each_with_index do |item, index|
context[@variable_name] = item context[@variable_name] = item
context['forloop'] = { context['forloop'.freeze] = {
'name' => @name, 'name'.freeze => @name,
'length' => length, 'length'.freeze => length,
'index' => index + 1, 'index'.freeze => index + 1,
'index0' => index, 'index0'.freeze => index,
'rindex' => length - index, 'rindex'.freeze => length - index,
'rindex0' => length - index - 1, 'rindex0'.freeze => length - index - 1,
'first' => (index == 0), 'first'.freeze => (index == 0),
'last' => (index == length - 1) } 'last'.freeze => (index == length - 1)
}
result << render_all(@for_block, context) result << render_all(@for_block, context)
@@ -135,22 +136,22 @@ module Liquid
@attributes[key] = value @attributes[key] = value
end end
else else
raise SyntaxError.new(options[:locale].t("errors.syntax.for")) raise SyntaxError.new(options[:locale].t("errors.syntax.for".freeze))
end end
end end
def strict_parse(markup) def strict_parse(markup)
p = Parser.new(markup) p = Parser.new(markup)
@variable_name = p.consume(:id) @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 @collection_name = p.expression
@name = "#{@variable_name}-#{@collection_name}" @name = "#{@variable_name}-#{@collection_name}"
@reversed = p.id?('reversed') @reversed = p.id?('reversed'.freeze)
@attributes = {} @attributes = {}
while p.look(:id) && p.look(:colon, 1) while p.look(:id) && p.look(:colon, 1)
unless attribute = p.id?('limit') || p.id?('offset') unless attribute = p.id?('limit'.freeze) || p.id?('offset'.freeze)
raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_attribute")) raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_attribute".freeze))
end end
p.consume p.consume
val = p.expression val = p.expression
@@ -161,14 +162,14 @@ module Liquid
private private
def render_else(context) def render_else(context)
return @else_block ? [render_all(@else_block, context)] : '' return @else_block ? [render_all(@else_block, context)] : ''.freeze
end end
def iterable?(collection) def iterable?(collection)
collection.respond_to?(:each) || Utils.non_blank_string?(collection) collection.respond_to?(:each) || Utils.non_blank_string?(collection)
end end
end end
Template.register_tag('for', For) Template.register_tag('for'.freeze, For)
end 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 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) BOOLEAN_OPERATORS = %w(and or)
def initialize(tag_name, markup, tokens) def initialize(tag_name, markup, options)
@blocks = []
push_block('if', markup)
super super
@blocks = []
push_block('if'.freeze, markup)
end end
def nodelist def nodelist
@@ -25,7 +25,7 @@ module Liquid
end end
def unknown_tag(tag, markup, tokens) def unknown_tag(tag, markup, tokens)
if ['elsif', 'else'].include?(tag) if ['elsif'.freeze, 'else'.freeze].include?(tag)
push_block(tag, markup) push_block(tag, markup)
else else
super super
@@ -39,14 +39,14 @@ module Liquid
return render_all(block.attachment, context) return render_all(block.attachment, context)
end end
end end
'' ''.freeze
end end
end end
private private
def push_block(tag, markup) def push_block(tag, markup)
block = if tag == 'else' block = if tag == 'else'.freeze
ElseCondition.new ElseCondition.new
else else
parse_with_selected_parser(markup) parse_with_selected_parser(markup)
@@ -58,17 +58,17 @@ module Liquid
def lax_parse(markup) def lax_parse(markup)
expressions = markup.scan(ExpressionsAndOperators).reverse 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) condition = Condition.new($1, $2, $3)
while not expressions.empty? while not expressions.empty?
operator = (expressions.shift).to_s.strip 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) 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) new_condition.send(operator, condition)
condition = new_condition condition = new_condition
end end
@@ -81,7 +81,7 @@ module Liquid
condition = parse_comparison(p) 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 = parse_comparison(p)
new_cond.send(op, condition) new_cond.send(op, condition)
condition = new_cond condition = new_cond
@@ -102,5 +102,5 @@ module Liquid
end end
end end
Template.register_tag('if', If) Template.register_tag('if'.freeze, If)
end end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -72,7 +72,7 @@ module Liquid
# Parse source code. # Parse source code.
# Returns self for easy chaining # Returns self for easy chaining
def parse(source, options = {}) def parse(source, options = {})
@root = Document.new(tokenize(source), DEFAULT_OPTIONS.merge(options)) @root = Document.parse(tokenize(source), DEFAULT_OPTIONS.merge(options))
@warnings = nil @warnings = nil
self self
end end
@@ -110,7 +110,7 @@ module Liquid
# filters and tags and might be useful to integrate liquid more with its host application # filters and tags and might be useful to integrate liquid more with its host application
# #
def render(*args) def render(*args)
return '' if @root.nil? return ''.freeze if @root.nil?
context = case args.first context = case args.first
when Liquid::Context when Liquid::Context
@@ -162,16 +162,9 @@ module Liquid
private private
# Uses the <tt>Liquid::TemplateParser</tt> regexp to tokenize the passed source
def tokenize(source) def tokenize(source)
source = source.source if source.respond_to?(:source) source = source.source if source.respond_to?(:source)
return [] if source.to_s.empty? Tokenizer.new(source.to_s)
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
end end

View File

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

View File

@@ -12,14 +12,13 @@ module Liquid
# #
class Variable class Variable
FilterParser = /(?:#{FilterSeparator}|(?:\s*(?:#{QuotedFragment}|#{ArgumentSeparator})\s*)+)/o FilterParser = /(?:#{FilterSeparator}|(?:\s*(?:#{QuotedFragment}|#{ArgumentSeparator})\s*)+)/o
EasyParse = /^ *(\w+(?:\.\w+)*) *$/ EasyParse = /\A *(\w+(?:\.\w+)*) *\z/
attr_accessor :filters, :name, :warnings attr_accessor :filters, :name, :warnings
def initialize(markup, options = {}) def initialize(markup, options = {})
@markup = markup @markup = markup
@name = nil @name = nil
@options = options || {} @options = options || {}
case @options[:error_mode] || Template.error_mode case @options[:error_mode] || Template.error_mode
when :strict then strict_parse(markup) when :strict then strict_parse(markup)
@@ -37,9 +36,9 @@ module Liquid
def lax_parse(markup) def lax_parse(markup)
@filters = [] @filters = []
if match = markup.match(/\s*(#{QuotedFragment})(.*)/o) if match = markup.match(/\s*(#{QuotedFragment})(.*)/om)
@name = match[1] @name = match[1]
if match[2].match(/#{FilterSeparator}\s*(.*)/o) if match[2].match(/#{FilterSeparator}\s*(.*)/om)
filters = Regexp.last_match(1).scan(FilterParser) filters = Regexp.last_match(1).scan(FilterParser)
filters.each do |f| filters.each do |f|
if matches = f.match(/\s*(\w+)/) if matches = f.match(/\s*(\w+)/)
@@ -63,7 +62,7 @@ module Liquid
@filters = [] @filters = []
p = Parser.new(markup) p = Parser.new(markup)
# Could be just filters with no input # Could be just filters with no input
@name = p.look(:pipe) ? '' : p.expression @name = p.look(:pipe) ? ''.freeze : p.expression
while p.consume?(:pipe) while p.consume?(:pipe)
filtername = p.consume(:id) filtername = p.consume(:id)
filterargs = p.consume?(:colon) ? parse_filterargs(p) : [] filterargs = p.consume?(:colon) ? parse_filterargs(p) : []
@@ -86,7 +85,7 @@ module Liquid
end end
def render(context) def render(context)
return '' if @name.nil? return ''.freeze if @name.nil?
@filters.inject(context[@name]) do |output, filter| @filters.inject(context[@name]) do |output, filter|
filterargs = [] filterargs = []
keyword_args = {} keyword_args = {}

View File

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

View File

@@ -18,9 +18,17 @@ Gem::Specification.new do |s|
s.required_rubygems_version = ">= 1.3.7" s.required_rubygems_version = ">= 1.3.7"
s.test_files = Dir.glob("{test}/**/*") s.test_files = Dir.glob("{test}/**/*")
s.files = Dir.glob("{lib}/**/*") + %w(MIT-LICENSE README.md) 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.extra_rdoc_files = ["History.md", "README.md"]
s.require_path = "lib" s.require_path = "lib"
s.add_development_dependency 'rake'
s.add_development_dependency 'activesupport'
if RUBY_ENGINE == 'ruby'
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 end

View File

@@ -1,19 +1,13 @@
require 'rubygems' require 'stackprof' rescue fail("install stackprof extension/gem")
require 'ruby-prof' rescue fail("install ruby-prof extension/gem")
require File.dirname(__FILE__) + '/theme_runner' require File.dirname(__FILE__) + '/theme_runner'
Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
profiler = ThemeRunner.new profiler = ThemeRunner.new
profiler.run
puts 'Running profiler...' results = StackProf.run(mode: :cpu) do
100.times do
results = profiler.run_profile profiler.run
end
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}"
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 class CommentForm < Liquid::Block
Syntax = /(#{Liquid::VariableSignature}+)/ Syntax = /(#{Liquid::VariableSignature}+)/
def initialize(tag_name, markup, tokens) def initialize(tag_name, markup, options)
super
if markup =~ Syntax if markup =~ Syntax
@variable_name = $1 @variable_name = $1
@attributes = {} @attributes = {}
else else
raise SyntaxError.new("Syntax Error in 'comment_form' - Valid syntax: comment_form [article]") raise SyntaxError.new("Syntax Error in 'comment_form' - Valid syntax: comment_form [article]")
end end
super
end end
def render(context) def render(context)

View File

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

View File

@@ -45,16 +45,16 @@ module ShopFilter
end end
def url_for_vendor(vendor_title) def url_for_vendor(vendor_title)
"/collections/#{vendor_title.to_handle}" "/collections/#{to_handle(vendor_title)}"
end end
def url_for_type(type_title) def url_for_type(type_title)
"/collections/#{type_title.to_handle}" "/collections/#{to_handle(type_title)}"
end end
def product_img_url(url, style = 'small') def product_img_url(url, style = 'small')
unless url =~ /^products\/([\w\-\_]+)\.(\w{2,4})/ unless url =~ /\Aproducts\/([\w\-\_]+)\.(\w{2,4})/
raise ArgumentError, 'filter "size" can only be called on product images' raise ArgumentError, 'filter "size" can only be called on product images'
end end
@@ -95,4 +95,16 @@ module ShopFilter
input == 1 ? singular : plural input == 1 ? singular : plural
end 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 end

View File

@@ -8,6 +8,7 @@
require 'rubygems' require 'rubygems'
require 'active_support' require 'active_support'
require 'active_support/json'
require 'yaml' require 'yaml'
require 'digest/md5' require 'digest/md5'
require File.dirname(__FILE__) + '/shopify/liquid' require File.dirname(__FILE__) + '/shopify/liquid'
@@ -64,52 +65,17 @@ class ThemeRunner
end 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) def compile_and_render(template, layout, assigns, page_template, template_file)
tmpl = Liquid::Template.new tmpl = Liquid::Template.new
tmpl.assigns['page_title'] = 'Page title' tmpl.assigns['page_title'] = 'Page title'
tmpl.assigns['template'] = page_template tmpl.assigns['template'] = page_template
tmpl.registers[:file_system] = ThemeRunner::FileSystem.new(File.dirname(template_file)) 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 if layout
assigns['content_for_layout'] = content_for_layout assigns['content_for_layout'] = content_for_layout
tmpl.parse(layout).render(assigns) tmpl.parse(layout).render!(assigns)
else else
content_for_layout content_for_layout
end end

View File

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

View File

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

View File

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

View File

@@ -106,140 +106,140 @@ class DropsTest < Test::Unit::TestCase
def test_product_drop def test_product_drop
assert_nothing_raised do assert_nothing_raised do
tpl = Liquid::Template.parse( ' ' ) tpl = Liquid::Template.parse( ' ' )
tpl.render('product' => ProductDrop.new) tpl.render!('product' => ProductDrop.new)
end end
end end
def test_drop_does_only_respond_to_whitelisted_methods 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.inspect }}").render!('product' => ProductDrop.new)
assert_equal "", Liquid::Template.parse("{{ product.pretty_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.whatever }}").render!('product' => ProductDrop.new)
assert_equal "", Liquid::Template.parse('{{ product | map: "inspect" }}').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: "pretty_inspect" }}').render!('product' => ProductDrop.new)
assert_equal "", Liquid::Template.parse('{{ product | map: "whatever" }}').render('product' => ProductDrop.new) assert_equal "", Liquid::Template.parse('{{ product | map: "whatever" }}').render!('product' => ProductDrop.new)
end end
def test_drops_respond_to_to_liquid 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.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 | map: "to_liquid" | map: "texts" | map: "text" }}').render!('product' => ProductDrop.new)
end end
def test_text_drop 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 assert_equal ' text1 ', output
end end
def test_unknown_method 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 assert_equal ' method: unknown ', output
end end
def test_integer_argument_drop 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 assert_equal ' method: 8 ', output
end end
def test_text_array_drop 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 assert_equal ' text1 text2 ', output
end end
def test_context_drop 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 assert_equal ' carrot ', output
end end
def test_nested_context_drop 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 assert_equal ' monkey ', output
end end
def test_protected 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 assert_equal ' ', output
end end
def test_object_methods_not_allowed def test_object_methods_not_allowed
[:dup, :clone, :singleton_class, :eval, :class_eval, :inspect].each do |method| [: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 assert_equal ' ', output
end end
end end
def test_scope def test_scope
assert_equal '1', Liquid::Template.parse( '{{ context.scopes }}' ).render('context' => ContextDrop.new) 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 '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 '3', Liquid::Template.parse( '{%for i in dummy%}{%for i in dummy%}{{ context.scopes }}{%endfor%}{%endfor%}' ).render!('context' => ContextDrop.new, 'dummy' => [1])
end end
def test_scope_though_proc 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 '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 '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 '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 end
def test_scope_with_assigns 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"%}{{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 '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 '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 end
def test_scope_from_tags 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 '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 '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 '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 end
def test_access_context_from_drop 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 end
def test_enumerable_drop 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 end
def test_enumerable_drop_size 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 end
def test_enumerable_drop_will_invoke_before_method_for_clashing_method_names def test_enumerable_drop_will_invoke_before_method_for_clashing_method_names
["select", "each", "map", "cycle"].each do |method| ["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' => 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' => RealEnumerableDrop.new) assert_equal method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new)
end end
end end
def test_some_enumerable_methods_still_get_invoked def test_some_enumerable_methods_still_get_invoked
[ :count, :max ].each do |method| [ :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' => 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' => EnumerableDrop.new) assert_equal "3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new)
end 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| [ :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' => 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' => EnumerableDrop.new) assert_equal "1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new)
end end
end end
def test_empty_string_value_access 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 end
def test_nil_value_access 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 end
def test_default_to_s_on_drops def test_default_to_s_on_drops
assert_equal 'ProductDrop', Liquid::Template.parse("{{ product }}").render('product' => ProductDrop.new) assert_equal 'ProductDrop', Liquid::Template.parse("{{ product }}").render!('product' => ProductDrop.new)
assert_equal 'EnumerableDrop', Liquid::Template.parse('{{ collection }}').render('collection' => EnumerableDrop.new) assert_equal 'EnumerableDrop', Liquid::Template.parse('{{ collection }}').render!('collection' => EnumerableDrop.new)
end end
end # DropsTest end # DropsTest

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -126,37 +126,38 @@ class StandardFiltersTest < Test::Unit::TestCase
end end
def test_map_doesnt_call_arbitrary_stuff def test_map_doesnt_call_arbitrary_stuff
assert_equal "", Liquid::Template.parse('{{ "foo" | map: "__id__" }}').render assert_template_result "", '{{ "foo" | map: "__id__" }}'
assert_equal "", Liquid::Template.parse('{{ "foo" | map: "inspect" }}').render assert_template_result "", '{{ "foo" | map: "inspect" }}'
end end
def test_map_calls_to_liquid def test_map_calls_to_liquid
t = TestThing.new 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 end
def test_map_on_hashes 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 end
def test_sort_calls_to_liquid def test_sort_calls_to_liquid
t = TestThing.new 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 end
def test_map_over_proc def test_map_over_proc
drop = TestDrop.new drop = TestDrop.new
p = Proc.new{ drop } p = Proc.new{ drop }
templ = '{{ procs | map: "test" }}' templ = '{{ procs | map: "test" }}'
assert_equal "testfoo", Liquid::Template.parse(templ).render("procs" => [p]) assert_template_result "testfoo", templ, "procs" => [p]
end end
def test_map_works_on_enumerables 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 end
def test_sort_works_on_enumerables 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 end
def test_date def test_date
@@ -257,7 +258,7 @@ class StandardFiltersTest < Test::Unit::TestCase
assert_template_result "4", "{{ 14 | divided_by:3 }}" assert_template_result "4", "{{ 14 | divided_by:3 }}"
assert_template_result "5", "{{ 15 | 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 }}" assert_template_result "0.5", "{{ 2.0 | divided_by:4 }}"
end end

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class HtmlTagTest < Test::Unit::TestCase class TableRowTest < Test::Unit::TestCase
include Liquid include Liquid
class ArrayDrop < Liquid::Drop class ArrayDrop < Liquid::Drop
@@ -15,7 +15,7 @@ class HtmlTagTest < Test::Unit::TestCase
end end
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", 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 %}', '{% tablerow n in numbers cols:3%} {{n}} {% endtablerow %}',
@@ -26,14 +26,14 @@ class HtmlTagTest < Test::Unit::TestCase
'numbers' => []) 'numbers' => [])
end 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", 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 %}', '{% tablerow n in numbers cols:5%} {{n}} {% endtablerow %}',
'numbers' => [1,2,3,4,5,6]) 'numbers' => [1,2,3,4,5,6])
end 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", 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 %}', '{% tablerow n in numbers cols:2%}{{tablerowloop.col}}{% endtablerow %}',
'numbers' => [1,2,3,4,5,6]) '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 %}', '{% tablerow n in numbers cols:3 offset:1 limit:6%} {{n}} {% endtablerow %}',
'numbers' => [0,1,2,3,4,5,6,7]) 'numbers' => [0,1,2,3,4,5,6,7])
end end
end # HtmlTagTest end

View File

@@ -25,79 +25,59 @@ end
class TemplateTest < Test::Unit::TestCase class TemplateTest < Test::Unit::TestCase
include Liquid include Liquid
def test_tokenize_strings
assert_equal [' '], Template.new.send(:tokenize, ' ')
assert_equal ['hello world'], Template.new.send(:tokenize, 'hello world')
end
def test_tokenize_variables
assert_equal ['{{funk}}'], Template.new.send(:tokenize, '{{funk}}')
assert_equal [' ', '{{funk}}', ' '], Template.new.send(:tokenize, ' {{funk}} ')
assert_equal [' ', '{{funk}}', ' ', '{{so}}', ' ', '{{brother}}', ' '], Template.new.send(:tokenize, ' {{funk}} {{so}} {{brother}} ')
assert_equal [' ', '{{ funk }}', ' '], Template.new.send(:tokenize, ' {{ funk }} ')
end
def test_tokenize_blocks
assert_equal ['{%comment%}'], Template.new.send(:tokenize, '{%comment%}')
assert_equal [' ', '{%comment%}', ' '], Template.new.send(:tokenize, ' {%comment%} ')
assert_equal [' ', '{%comment%}', ' ', '{%endcomment%}', ' '], Template.new.send(:tokenize, ' {%comment%} {%endcomment%} ')
assert_equal [' ', '{% comment %}', ' ', '{% endcomment %}', ' '], Template.new.send(:tokenize, " {% comment %} {% endcomment %} ")
end
def test_instance_assigns_persist_on_same_template_object_between_parses def test_instance_assigns_persist_on_same_template_object_between_parses
t = Template.new 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!
assert_equal 'from instance assigns', t.parse("{{ foo }}").render assert_equal 'from instance assigns', t.parse("{{ foo }}").render!
end end
def test_instance_assigns_persist_on_same_template_parsing_between_renders def test_instance_assigns_persist_on_same_template_parsing_between_renders
t = Template.new.parse("{{ foo }}{% assign foo = 'foo' %}{{ foo }}") t = Template.new.parse("{{ foo }}{% assign foo = 'foo' %}{{ foo }}")
assert_equal 'foo', t.render assert_equal 'foo', t.render!
assert_equal 'foofoo', t.render assert_equal 'foofoo', t.render!
end end
def test_custom_assigns_do_not_persist_on_same_template def test_custom_assigns_do_not_persist_on_same_template
t = Template.new t = Template.new
assert_equal 'from custom assigns', t.parse("{{ foo }}").render('foo' => 'from custom assigns') assert_equal 'from custom assigns', t.parse("{{ foo }}").render!('foo' => 'from custom assigns')
assert_equal '', t.parse("{{ foo }}").render assert_equal '', t.parse("{{ foo }}").render!
end end
def test_custom_assigns_squash_instance_assigns def test_custom_assigns_squash_instance_assigns
t = Template.new 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!
assert_equal 'from custom assigns', t.parse("{{ foo }}").render('foo' => 'from custom assigns') assert_equal 'from custom assigns', t.parse("{{ foo }}").render!('foo' => 'from custom assigns')
end end
def test_persistent_assigns_squash_instance_assigns def test_persistent_assigns_squash_instance_assigns
t = Template.new 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' 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 end
def test_lambda_is_called_once_from_persistent_assigns_over_multiple_parses_and_renders def test_lambda_is_called_once_from_persistent_assigns_over_multiple_parses_and_renders
t = Template.new t = Template.new
t.assigns['number'] = lambda { @global ||= 0; @global += 1 } 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.parse("{{number}}").render assert_equal '1', t.parse("{{number}}").render!
assert_equal '1', t.render assert_equal '1', t.render!
@global = nil @global = nil
end end
def test_lambda_is_called_once_from_custom_assigns_over_multiple_parses_and_renders def test_lambda_is_called_once_from_custom_assigns_over_multiple_parses_and_renders
t = Template.new t = Template.new
assigns = {'number' => lambda { @global ||= 0; @global += 1 }} 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.parse("{{number}}").render(assigns) assert_equal '1', t.parse("{{number}}").render!(assigns)
assert_equal '1', t.render(assigns) assert_equal '1', t.render!(assigns)
@global = nil @global = nil
end end
def test_resource_limits_works_with_custom_length_method def test_resource_limits_works_with_custom_length_method
t = Template.parse("{% assign foo = bar %}") t = Template.parse("{% assign foo = bar %}")
t.resource_limits = { :render_length_limit => 42 } t.resource_limits = { :render_length_limit => 42 }
assert_equal "", t.render("bar" => SomethingWithLength.new) assert_equal "", t.render!("bar" => SomethingWithLength.new)
end end
def test_resource_limits_render_length def test_resource_limits_render_length
@@ -106,7 +86,7 @@ class TemplateTest < Test::Unit::TestCase
assert_equal "Liquid error: Memory limits exceeded", t.render() assert_equal "Liquid error: Memory limits exceeded", t.render()
assert t.resource_limits[:reached] assert t.resource_limits[:reached]
t.resource_limits = { :render_length_limit => 10 } 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] assert_not_nil t.resource_limits[:render_length_current]
end end
@@ -120,7 +100,7 @@ class TemplateTest < Test::Unit::TestCase
assert_equal "Liquid error: Memory limits exceeded", t.render() assert_equal "Liquid error: Memory limits exceeded", t.render()
assert t.resource_limits[:reached] assert t.resource_limits[:reached]
t.resource_limits = { :render_score_limit => 200 } 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] assert_not_nil t.resource_limits[:render_score_current]
end end
@@ -130,7 +110,7 @@ class TemplateTest < Test::Unit::TestCase
assert_equal "Liquid error: Memory limits exceeded", t.render() assert_equal "Liquid error: Memory limits exceeded", t.render()
assert t.resource_limits[:reached] assert t.resource_limits[:reached]
t.resource_limits = { :assign_score_limit => 2 } t.resource_limits = { :assign_score_limit => 2 }
assert_equal "", t.render() assert_equal "", t.render!()
assert_not_nil t.resource_limits[:assign_score_current] assert_not_nil t.resource_limits[:assign_score_current]
end end
@@ -143,7 +123,7 @@ class TemplateTest < Test::Unit::TestCase
def test_resource_limits_hash_in_template_gets_updated_even_if_no_limits_are_set 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 = 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[:assign_score_current] > 0
assert t.resource_limits[:render_score_current] > 0 assert t.resource_limits[:render_score_current] > 0
assert t.resource_limits[:render_length_current] > 0 assert t.resource_limits[:render_length_current] > 0
@@ -153,9 +133,9 @@ class TemplateTest < Test::Unit::TestCase
t = Template.new t = Template.new
t.registers['lulz'] = 'haha' t.registers['lulz'] = 'haha'
drop = TemplateContextDrop.new drop = TemplateContextDrop.new
assert_equal 'fizzbuzz', t.parse('{{foo}}').render(drop) assert_equal 'fizzbuzz', t.parse('{{foo}}').render!(drop)
assert_equal 'bar', t.parse('{{bar}}').render(drop) assert_equal 'bar', t.parse('{{bar}}').render!(drop)
assert_equal 'haha', t.parse("{{baz}}").render(drop) assert_equal 'haha', t.parse("{{baz}}").render!(drop)
end end
def test_sets_default_localization_in_document def test_sets_default_localization_in_document

View File

@@ -0,0 +1,64 @@
require 'test_helper'
class TokenizerTest < Test::Unit::TestCase
def test_tokenize_strings
assert_equal [' '], tokenize(' ')
assert_equal ['hello world'], tokenize('hello world')
end
def test_tokenize_variables
assert_equal ['{{funk}}'], tokenize('{{funk}}')
assert_equal [' ', '{{funk}}', ' '], tokenize(' {{funk}} ')
assert_equal [' ', '{{funk}}', ' ', '{{so}}', ' ', '{{brother}}', ' '], tokenize(' {{funk}} {{so}} {{brother}} ')
assert_equal [' ', '{{ funk }}', ' '], tokenize(' {{ funk }} ')
end
def test_tokenize_blocks
assert_equal ['{%comment%}'], tokenize('{%comment%}')
assert_equal [' ', '{%comment%}', ' '], tokenize(' {%comment%} ')
assert_equal [' ', '{%comment%}', ' ', '{%endcomment%}', ' '], tokenize(' {%comment%} {%endcomment%} ')
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 = []
while token = tokenizer.next
tokens << token
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

@@ -141,49 +141,49 @@ class VariableResolutionTest < Test::Unit::TestCase
def test_simple_variable def test_simple_variable
template = Template.parse(%|{{test}}|) template = Template.parse(%|{{test}}|)
assert_equal 'worked', template.render('test' => 'worked') assert_equal 'worked', template.render!('test' => 'worked')
assert_equal 'worked wonderfully', template.render('test' => 'worked wonderfully') assert_equal 'worked wonderfully', template.render!('test' => 'worked wonderfully')
end end
def test_simple_with_whitespaces def test_simple_with_whitespaces
template = Template.parse(%| {{ test }} |) template = Template.parse(%| {{ test }} |)
assert_equal ' worked ', template.render('test' => 'worked') assert_equal ' worked ', template.render!('test' => 'worked')
assert_equal ' worked wonderfully ', template.render('test' => 'worked wonderfully') assert_equal ' worked wonderfully ', template.render!('test' => 'worked wonderfully')
end end
def test_ignore_unknown def test_ignore_unknown
template = Template.parse(%|{{ test }}|) template = Template.parse(%|{{ test }}|)
assert_equal '', template.render assert_equal '', template.render!
end end
def test_hash_scoping def test_hash_scoping
template = Template.parse(%|{{ test.test }}|) template = Template.parse(%|{{ test.test }}|)
assert_equal 'worked', template.render('test' => {'test' => 'worked'}) assert_equal 'worked', template.render!('test' => {'test' => 'worked'})
end end
def test_preset_assigns def test_preset_assigns
template = Template.parse(%|{{ test }}|) template = Template.parse(%|{{ test }}|)
template.assigns['test'] = 'worked' template.assigns['test'] = 'worked'
assert_equal 'worked', template.render assert_equal 'worked', template.render!
end end
def test_reuse_parsed_template def test_reuse_parsed_template
template = Template.parse(%|{{ greeting }} {{ name }}|) template = Template.parse(%|{{ greeting }} {{ name }}|)
template.assigns['greeting'] = 'Goodbye' template.assigns['greeting'] = 'Goodbye'
assert_equal 'Hello Tobi', template.render('greeting' => 'Hello', 'name' => 'Tobi') assert_equal 'Hello Tobi', template.render!('greeting' => 'Hello', 'name' => 'Tobi')
assert_equal 'Hello ', template.render('greeting' => 'Hello', 'unknown' => 'Tobi') assert_equal 'Hello ', template.render!('greeting' => 'Hello', 'unknown' => 'Tobi')
assert_equal 'Hello Brian', template.render('greeting' => 'Hello', 'name' => 'Brian') assert_equal 'Hello Brian', template.render!('greeting' => 'Hello', 'name' => 'Brian')
assert_equal 'Goodbye Brian', template.render('name' => 'Brian') assert_equal 'Goodbye Brian', template.render!('name' => 'Brian')
assert_equal({'greeting'=>'Goodbye'}, template.assigns) assert_equal({'greeting'=>'Goodbye'}, template.assigns)
end end
def test_assigns_not_polluted_from_template def test_assigns_not_polluted_from_template
template = Template.parse(%|{{ test }}{% assign test = 'bar' %}{{ test }}|) template = Template.parse(%|{{ test }}{% assign test = 'bar' %}{{ test }}|)
template.assigns['test'] = 'baz' template.assigns['test'] = 'baz'
assert_equal 'bazbar', template.render assert_equal 'bazbar', template.render!
assert_equal 'bazbar', template.render assert_equal 'bazbar', template.render!
assert_equal 'foobar', template.render('test' => 'foo') assert_equal 'foobar', template.render!('test' => 'foo')
assert_equal 'bazbar', template.render assert_equal 'bazbar', template.render!
end end
def test_hash_with_default_proc def test_hash_with_default_proc
@@ -197,4 +197,8 @@ class VariableResolutionTest < Test::Unit::TestCase
} }
assert_equal "Unknown variable 'test'", e.message assert_equal "Unknown variable 'test'", e.message
end end
def test_multiline_variable
assert_equal 'worked', Template.parse("{{\ntest\n}}").render!('test' => 'worked')
end
end # VariableTest end # VariableTest

View File

@@ -26,13 +26,13 @@ module Test
include Liquid include Liquid
def assert_template_result(expected, template, assigns = {}, message = nil) 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 end
def assert_template_result_matches(expected, template, assigns = {}, message = nil) def assert_template_result_matches(expected, template, assigns = {}, message = nil)
return assert_template_result(expected, template, assigns, message) unless expected.is_a? Regexp 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 end
def assert_match_syntax_error(match, template, registers = {}) def assert_match_syntax_error(match, template, registers = {})