mirror of
https://github.com/kemko/liquid.git
synced 2026-01-02 00:05:42 +03:00
Compare commits
20 Commits
c-tokenize
...
string-sli
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb954bce1e | ||
|
|
cc0276bb97 | ||
|
|
03d586aafe | ||
|
|
dc8a34a52f | ||
|
|
99cebf74bc | ||
|
|
7eb64886dc | ||
|
|
f89046e81f | ||
|
|
9ee4573ef4 | ||
|
|
a48b4f47f6 | ||
|
|
72d402837e | ||
|
|
06bef40527 | ||
|
|
a48b245e6e | ||
|
|
d4aabda625 | ||
|
|
dab6bdfdee | ||
|
|
8c075fca1f | ||
|
|
ea8406e36e | ||
|
|
8bb3bca64a | ||
|
|
5de1082201 | ||
|
|
7ba02d2811 | ||
|
|
2066676bf4 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -5,8 +5,6 @@ pkg
|
|||||||
*.rbc
|
*.rbc
|
||||||
.rvmrc
|
.rvmrc
|
||||||
.ruby-version
|
.ruby-version
|
||||||
Gemfile.lock
|
|
||||||
/ext/liquid/Makefile
|
|
||||||
*.o
|
|
||||||
*.bundle
|
*.bundle
|
||||||
/tmp
|
/tmp
|
||||||
|
Gemfile.lock
|
||||||
|
|||||||
@@ -3,10 +3,6 @@
|
|||||||
## 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]
|
||||||
@@ -25,6 +21,7 @@
|
|||||||
* 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"
|
||||||
|
|
||||||
|
|||||||
20
Rakefile
20
Rakefile
@@ -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,13 @@ namespace :profile do
|
|||||||
ruby "./performance/profile.rb"
|
ruby "./performance/profile.rb"
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "Run the liquid profile/performance coverage with strict parsing"
|
task :stackprof do
|
||||||
task :strict do
|
ruby "./performance/stackprof.rb"
|
||||||
ruby "./performance/profile.rb strict"
|
end
|
||||||
|
|
||||||
|
desc "Run KCacheGrind"
|
||||||
|
task :grind => :run do
|
||||||
|
system "qcachegrind /tmp/liquid.rubyprof_calltreeprinter.txt"
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
@@ -76,10 +81,7 @@ task :example do
|
|||||||
ruby "-w -d -Ilib example/server/server.rb"
|
ruby "-w -d -Ilib example/server/server.rb"
|
||||||
end
|
end
|
||||||
|
|
||||||
if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby'
|
Rake::ExtensionTask.new "liquid" do |ext|
|
||||||
require 'rake/extensiontask'
|
ext.lib_dir = "lib/liquid"
|
||||||
Rake::ExtensionTask.new "liquid" do |ext|
|
|
||||||
ext.lib_dir = "lib/liquid"
|
|
||||||
end
|
|
||||||
Rake::Task[:test].prerequisites << :compile
|
|
||||||
end
|
end
|
||||||
|
Rake::Task[:test].prerequisites << :compile
|
||||||
|
|||||||
167
ext/liquid/block.c
Normal file
167
ext/liquid/block.c
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
#include "liquid_ext.h"
|
||||||
|
|
||||||
|
VALUE cLiquidBlock;
|
||||||
|
ID intern_assert_missing_delimitation, intern_block_delimiter, intern_is_blank,
|
||||||
|
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, liquid_string_slice_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_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
8
ext/liquid/block.h
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#ifndef LIQUID_BLOCK_H
|
||||||
|
#define LIQUID_BLOCK_H
|
||||||
|
|
||||||
|
void init_liquid_block();
|
||||||
|
|
||||||
|
extern VALUE cLiquidBlock;
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
require 'mkmf'
|
require 'mkmf'
|
||||||
$CFLAGS << ' -Wall -Werror'
|
$CFLAGS << ' -Wall'
|
||||||
$warnflags.gsub!(/-Wdeclaration-after-statement/, "")
|
|
||||||
create_makefile("liquid/liquid")
|
create_makefile("liquid/liquid")
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
#include "liquid.h"
|
|
||||||
|
|
||||||
VALUE mLiquid;
|
|
||||||
|
|
||||||
void Init_liquid(void)
|
|
||||||
{
|
|
||||||
mLiquid = rb_define_module("Liquid");
|
|
||||||
init_liquid_tokenizer();
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
#ifndef LIQUID_H
|
|
||||||
#define LIQUID_H
|
|
||||||
|
|
||||||
#include <ruby.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
#include "tokenizer.h"
|
|
||||||
|
|
||||||
extern VALUE mLiquid;
|
|
||||||
|
|
||||||
#endif
|
|
||||||
18
ext/liquid/liquid_ext.c
Normal file
18
ext/liquid/liquid_ext.c
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#include "liquid_ext.h"
|
||||||
|
|
||||||
|
VALUE mLiquid;
|
||||||
|
VALUE cLiquidTemplate, cLiquidTag, cLiquidVariable;
|
||||||
|
ID intern_new;
|
||||||
|
|
||||||
|
void Init_liquid(void)
|
||||||
|
{
|
||||||
|
intern_new = rb_intern("new");
|
||||||
|
mLiquid = rb_define_module("Liquid");
|
||||||
|
cLiquidTemplate = rb_define_class_under(mLiquid, "Template", rb_cObject);
|
||||||
|
cLiquidTag = rb_define_class_under(mLiquid, "Tag", rb_cObject);
|
||||||
|
cLiquidVariable = rb_define_class_under(mLiquid, "Variable", rb_cObject);
|
||||||
|
|
||||||
|
init_liquid_tokenizer();
|
||||||
|
init_liquid_block();
|
||||||
|
init_liquid_string_slice();
|
||||||
|
}
|
||||||
17
ext/liquid/liquid_ext.h
Normal file
17
ext/liquid/liquid_ext.h
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#ifndef LIQUID_EXT_H
|
||||||
|
#define LIQUID_EXT_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <ruby.h>
|
||||||
|
|
||||||
|
#include "tokenizer.h"
|
||||||
|
#include "block.h"
|
||||||
|
#include "slice.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
extern ID intern_new;
|
||||||
|
extern VALUE mLiquid;
|
||||||
|
extern VALUE cLiquidTemplate, cLiquidTag, cLiquidVariable;
|
||||||
|
|
||||||
|
#endif
|
||||||
167
ext/liquid/slice.c
Normal file
167
ext/liquid/slice.c
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
#include "liquid_ext.h"
|
||||||
|
|
||||||
|
VALUE cLiquidStringSlice;
|
||||||
|
|
||||||
|
static void mark_slice(void *ptr)
|
||||||
|
{
|
||||||
|
if (!ptr)
|
||||||
|
return;
|
||||||
|
struct string_slice *slice = ptr;
|
||||||
|
rb_gc_mark(slice->source);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void free_slice(void *ptr)
|
||||||
|
{
|
||||||
|
struct string_slice *slice = ptr;
|
||||||
|
xfree(slice);
|
||||||
|
}
|
||||||
|
|
||||||
|
VALUE liquid_string_slice_new(const char *str, long length)
|
||||||
|
{
|
||||||
|
return rb_funcall(cLiquidStringSlice, intern_new, 3, rb_str_new(str, length), INT2FIX(0), INT2FIX(length));
|
||||||
|
}
|
||||||
|
|
||||||
|
static VALUE rb_allocate(VALUE klass)
|
||||||
|
{
|
||||||
|
struct string_slice *slice;
|
||||||
|
VALUE obj = Data_Make_Struct(klass, struct string_slice, mark_slice, free_slice, slice);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
static VALUE rb_initialize(VALUE self, VALUE source, VALUE offset_value, VALUE length_value)
|
||||||
|
{
|
||||||
|
long offset = rb_fix2int(offset_value);
|
||||||
|
long length = rb_fix2int(length_value);
|
||||||
|
if (length < 0)
|
||||||
|
rb_raise(rb_eArgError, "negative string length");
|
||||||
|
if (offset < 0)
|
||||||
|
rb_raise(rb_eArgError, "negative string offset");
|
||||||
|
|
||||||
|
if (TYPE(source) == T_DATA && RBASIC_CLASS(source) == cLiquidStringSlice) {
|
||||||
|
struct string_slice *source_slice = DATA_PTR(source);
|
||||||
|
source = source_slice->source;
|
||||||
|
offset += source_slice->str - RSTRING_PTR(source);
|
||||||
|
} else {
|
||||||
|
source = rb_string_value(&source);
|
||||||
|
source = rb_str_dup_frozen(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct string_slice *slice;
|
||||||
|
Data_Get_Struct(self, struct string_slice, slice);
|
||||||
|
slice->source = source;
|
||||||
|
slice->str = RSTRING_PTR(source) + offset;
|
||||||
|
slice->length = length;
|
||||||
|
if (length > RSTRING_LEN(source) - offset)
|
||||||
|
rb_raise(rb_eArgError, "slice bounds outside source string bounds");
|
||||||
|
|
||||||
|
return Qnil;
|
||||||
|
}
|
||||||
|
|
||||||
|
static VALUE rb_slice_to_str(VALUE self)
|
||||||
|
{
|
||||||
|
struct string_slice *slice;
|
||||||
|
Data_Get_Struct(self, struct string_slice, slice);
|
||||||
|
|
||||||
|
VALUE source = slice->source;
|
||||||
|
if (slice->str == RSTRING_PTR(source) && slice->length == RSTRING_LEN(source))
|
||||||
|
return source;
|
||||||
|
|
||||||
|
source = rb_str_new(slice->str, slice->length);
|
||||||
|
slice->source = source;
|
||||||
|
slice->str = RSTRING_PTR(source);
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
static VALUE rb_slice_slice(VALUE self, VALUE offset, VALUE length)
|
||||||
|
{
|
||||||
|
return rb_funcall(cLiquidStringSlice, intern_new, 3, self, offset, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
static VALUE rb_slice_length(VALUE self)
|
||||||
|
{
|
||||||
|
struct string_slice *slice;
|
||||||
|
Data_Get_Struct(self, struct string_slice, slice);
|
||||||
|
return INT2FIX(slice->length);
|
||||||
|
}
|
||||||
|
|
||||||
|
static VALUE rb_slice_equal(VALUE self, VALUE other)
|
||||||
|
{
|
||||||
|
struct string_slice *this_slice;
|
||||||
|
Data_Get_Struct(self, struct string_slice, this_slice);
|
||||||
|
|
||||||
|
const char *other_str;
|
||||||
|
long other_length;
|
||||||
|
if (TYPE(other) == T_DATA && RBASIC_CLASS(other) == cLiquidStringSlice) {
|
||||||
|
struct string_slice *other_slice = DATA_PTR(other);
|
||||||
|
other_str = other_slice->str;
|
||||||
|
other_length = other_slice->length;
|
||||||
|
} else {
|
||||||
|
other = rb_string_value(&other);
|
||||||
|
other_length = RSTRING_LEN(other);
|
||||||
|
other_str = RSTRING_PTR(other);
|
||||||
|
}
|
||||||
|
bool equal = this_slice->length == other_length && !memcmp(this_slice->str, other_str, other_length);
|
||||||
|
return equal ? Qtrue : Qfalse;
|
||||||
|
}
|
||||||
|
|
||||||
|
static VALUE rb_slice_inspect(VALUE self)
|
||||||
|
{
|
||||||
|
VALUE quoted = rb_str_inspect(rb_slice_to_str(self));
|
||||||
|
return rb_sprintf("#<Liquid::StringSlice: %.*s>", (int)RSTRING_LEN(quoted), RSTRING_PTR(quoted));
|
||||||
|
}
|
||||||
|
|
||||||
|
static VALUE rb_slice_join(VALUE klass, VALUE ary)
|
||||||
|
{
|
||||||
|
ary = rb_ary_to_ary(ary);
|
||||||
|
|
||||||
|
long i;
|
||||||
|
long result_length = 0;
|
||||||
|
for (i = 0; i < RARRAY_LEN(ary); i++) {
|
||||||
|
VALUE element = RARRAY_AREF(ary, i);
|
||||||
|
|
||||||
|
if (TYPE(element) == T_DATA && RBASIC_CLASS(element) == cLiquidStringSlice) {
|
||||||
|
struct string_slice *slice = DATA_PTR(element);
|
||||||
|
result_length += slice->length;
|
||||||
|
} else if (TYPE(element) == T_STRING) {
|
||||||
|
result_length += RSTRING_LEN(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VALUE result = rb_str_buf_new(result_length);
|
||||||
|
for (i = 0; i < RARRAY_LEN(ary); i++) {
|
||||||
|
VALUE element = RARRAY_AREF(ary, i);
|
||||||
|
|
||||||
|
const char *element_string;
|
||||||
|
long element_length;
|
||||||
|
if (TYPE(element) == T_DATA && RBASIC_CLASS(element) == cLiquidStringSlice) {
|
||||||
|
struct string_slice *slice = DATA_PTR(element);
|
||||||
|
element_string = slice->str;
|
||||||
|
element_length = slice->length;
|
||||||
|
} else if (NIL_P(element)) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
element = rb_check_string_type(element);
|
||||||
|
if (NIL_P(element))
|
||||||
|
continue;
|
||||||
|
element_string = RSTRING_PTR(element);
|
||||||
|
element_length = RSTRING_LEN(element);
|
||||||
|
}
|
||||||
|
rb_str_buf_cat(result, element_string, element_length);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_liquid_string_slice()
|
||||||
|
{
|
||||||
|
cLiquidStringSlice = rb_define_class_under(mLiquid, "StringSlice", rb_cObject);
|
||||||
|
rb_define_singleton_method(cLiquidStringSlice, "join", rb_slice_join, 1);
|
||||||
|
rb_define_alloc_func(cLiquidStringSlice, rb_allocate);
|
||||||
|
rb_define_method(cLiquidStringSlice, "initialize", rb_initialize, 3);
|
||||||
|
rb_define_method(cLiquidStringSlice, "==", rb_slice_equal, 1);
|
||||||
|
rb_define_method(cLiquidStringSlice, "length", rb_slice_length, 0);
|
||||||
|
rb_define_alias(cLiquidStringSlice, "size", "length");
|
||||||
|
rb_define_method(cLiquidStringSlice, "slice", rb_slice_slice, 2);
|
||||||
|
rb_define_method(cLiquidStringSlice, "to_str", rb_slice_to_str, 0);
|
||||||
|
rb_define_alias(cLiquidStringSlice, "to_s", "to_str");
|
||||||
|
rb_define_method(cLiquidStringSlice, "inspect", rb_slice_inspect, 0);
|
||||||
|
}
|
||||||
18
ext/liquid/slice.h
Normal file
18
ext/liquid/slice.h
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#ifndef LIQUID_SLICE_H
|
||||||
|
#define LIQUID_SLICE_H
|
||||||
|
|
||||||
|
extern VALUE cLiquidStringSlice;
|
||||||
|
|
||||||
|
struct string_slice {
|
||||||
|
VALUE source;
|
||||||
|
const char *str;
|
||||||
|
long length;
|
||||||
|
};
|
||||||
|
|
||||||
|
VALUE liquid_string_slice_new(const char *str, long length);
|
||||||
|
|
||||||
|
void init_liquid_string_slice();
|
||||||
|
|
||||||
|
#define STRING_SLICE_GET_STRUCT(obj) ((struct string_slice *)obj_get_data_ptr(obj, cLiquidStringSlice))
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,67 +1,44 @@
|
|||||||
#include "liquid.h"
|
#include "liquid_ext.h"
|
||||||
|
|
||||||
VALUE cLiquidTokenizer;
|
VALUE cLiquidTokenizer;
|
||||||
|
|
||||||
static void tokenizer_mark(void *ptr) {
|
static void free_tokenizer(void *ptr)
|
||||||
tokenizer_t *tokenizer = ptr;
|
|
||||||
rb_gc_mark(tokenizer->source);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void tokenizer_free(void *ptr)
|
|
||||||
{
|
{
|
||||||
tokenizer_t *tokenizer = ptr;
|
struct liquid_tokenizer *tokenizer = ptr;
|
||||||
xfree(tokenizer);
|
xfree(tokenizer);
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t tokenizer_memsize(const void *ptr)
|
static VALUE rb_allocate(VALUE klass)
|
||||||
{
|
|
||||||
return ptr ? sizeof(tokenizer_t) : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rb_data_type_t tokenizer_data_type = {
|
|
||||||
"liquid_tokenizer",
|
|
||||||
{tokenizer_mark, tokenizer_free, tokenizer_memsize,},
|
|
||||||
#ifdef RUBY_TYPED_FREE_IMMEDIATELY
|
|
||||||
NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
static VALUE tokenizer_allocate(VALUE klass)
|
|
||||||
{
|
{
|
||||||
VALUE obj;
|
VALUE obj;
|
||||||
tokenizer_t *tokenizer;
|
struct liquid_tokenizer *tokenizer;
|
||||||
|
|
||||||
obj = TypedData_Make_Struct(klass, tokenizer_t, &tokenizer_data_type, tokenizer);
|
obj = Data_Make_Struct(klass, struct liquid_tokenizer, NULL, free_tokenizer, tokenizer);
|
||||||
tokenizer->source = Qnil;
|
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
static VALUE tokenizer_initialize_method(VALUE self, VALUE source)
|
static VALUE rb_initialize(VALUE self, VALUE source)
|
||||||
{
|
{
|
||||||
tokenizer_t *tokenizer;
|
struct liquid_tokenizer *tokenizer;
|
||||||
|
|
||||||
Check_Type(source, T_STRING);
|
source = rb_string_value(&source);
|
||||||
Tokenizer_Get_Struct(self, tokenizer);
|
Data_Get_Struct(self, struct liquid_tokenizer, tokenizer);
|
||||||
source = rb_str_dup_frozen(source);
|
|
||||||
tokenizer->source = source;
|
|
||||||
tokenizer->cursor = RSTRING_PTR(source);
|
tokenizer->cursor = RSTRING_PTR(source);
|
||||||
tokenizer->length = RSTRING_LEN(source);
|
tokenizer->length = RSTRING_LEN(source);
|
||||||
return Qnil;
|
return Qnil;
|
||||||
}
|
}
|
||||||
|
|
||||||
void tokenizer_next(tokenizer_t *tokenizer, token_t *token)
|
void liquid_tokenizer_next(struct liquid_tokenizer *tokenizer, struct token *token)
|
||||||
{
|
{
|
||||||
if (tokenizer->length <= 0) {
|
if (tokenizer->length <= 0) {
|
||||||
memset(token, 0, sizeof(*token));
|
memset(token, 0, sizeof(*token));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *cursor = tokenizer->cursor;
|
|
||||||
const char *last = cursor + tokenizer->length - 1;
|
|
||||||
|
|
||||||
token->str = cursor;
|
|
||||||
token->type = TOKEN_STRING;
|
token->type = TOKEN_STRING;
|
||||||
|
|
||||||
|
char *cursor = tokenizer->cursor;
|
||||||
|
char *last = tokenizer->cursor + tokenizer->length - 1;
|
||||||
|
|
||||||
while (cursor < last) {
|
while (cursor < last) {
|
||||||
if (*cursor++ != '{')
|
if (*cursor++ != '{')
|
||||||
continue;
|
continue;
|
||||||
@@ -74,6 +51,7 @@ void tokenizer_next(tokenizer_t *tokenizer, token_t *token)
|
|||||||
cursor -= 2;
|
cursor -= 2;
|
||||||
goto found;
|
goto found;
|
||||||
}
|
}
|
||||||
|
char *incomplete_end = cursor;
|
||||||
token->type = TOKEN_INVALID;
|
token->type = TOKEN_INVALID;
|
||||||
if (c == '%') {
|
if (c == '%') {
|
||||||
while (cursor < last) {
|
while (cursor < last) {
|
||||||
@@ -87,40 +65,38 @@ void tokenizer_next(tokenizer_t *tokenizer, token_t *token)
|
|||||||
token->type = TOKEN_TAG;
|
token->type = TOKEN_TAG;
|
||||||
goto found;
|
goto found;
|
||||||
}
|
}
|
||||||
// unterminated tag
|
cursor = incomplete_end;
|
||||||
cursor = tokenizer->cursor + 2;
|
|
||||||
goto found;
|
goto found;
|
||||||
} else {
|
} else {
|
||||||
while (cursor < last) {
|
while (cursor < last) {
|
||||||
if (*cursor++ != '}')
|
if (*cursor++ != '}')
|
||||||
continue;
|
continue;
|
||||||
if (*cursor++ != '}') {
|
if (*cursor++ != '}') {
|
||||||
// variable incomplete end, used to end raw tags
|
incomplete_end = cursor - 1;
|
||||||
cursor--;
|
continue;
|
||||||
goto found;
|
|
||||||
}
|
}
|
||||||
token->type = TOKEN_VARIABLE;
|
token->type = TOKEN_VARIABLE;
|
||||||
goto found;
|
goto found;
|
||||||
}
|
}
|
||||||
// unterminated variable
|
cursor = incomplete_end;
|
||||||
cursor = tokenizer->cursor + 2;
|
|
||||||
goto found;
|
goto found;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cursor = last + 1;
|
cursor = last + 1;
|
||||||
found:
|
found:
|
||||||
|
token->str = tokenizer->cursor;
|
||||||
token->length = cursor - tokenizer->cursor;
|
token->length = cursor - tokenizer->cursor;
|
||||||
tokenizer->cursor += token->length;
|
tokenizer->cursor += token->length;
|
||||||
tokenizer->length -= token->length;
|
tokenizer->length -= token->length;
|
||||||
}
|
}
|
||||||
|
|
||||||
static VALUE tokenizer_next_method(VALUE self)
|
static VALUE rb_next(VALUE self)
|
||||||
{
|
{
|
||||||
tokenizer_t *tokenizer;
|
struct liquid_tokenizer *tokenizer;
|
||||||
Tokenizer_Get_Struct(self, tokenizer);
|
Data_Get_Struct(self, struct liquid_tokenizer, tokenizer);
|
||||||
|
|
||||||
token_t token;
|
struct token token;
|
||||||
tokenizer_next(tokenizer, &token);
|
liquid_tokenizer_next(tokenizer, &token);
|
||||||
if (token.type == TOKEN_NONE)
|
if (token.type == TOKEN_NONE)
|
||||||
return Qnil;
|
return Qnil;
|
||||||
|
|
||||||
@@ -130,8 +106,8 @@ static VALUE tokenizer_next_method(VALUE self)
|
|||||||
void init_liquid_tokenizer()
|
void init_liquid_tokenizer()
|
||||||
{
|
{
|
||||||
cLiquidTokenizer = rb_define_class_under(mLiquid, "Tokenizer", rb_cObject);
|
cLiquidTokenizer = rb_define_class_under(mLiquid, "Tokenizer", rb_cObject);
|
||||||
rb_define_alloc_func(cLiquidTokenizer, tokenizer_allocate);
|
rb_define_alloc_func(cLiquidTokenizer, rb_allocate);
|
||||||
rb_define_method(cLiquidTokenizer, "initialize", tokenizer_initialize_method, 1);
|
rb_define_method(cLiquidTokenizer, "initialize", rb_initialize, 1);
|
||||||
rb_define_method(cLiquidTokenizer, "next", tokenizer_next_method, 0);
|
rb_define_method(cLiquidTokenizer, "next", rb_next, 0);
|
||||||
rb_define_alias(cLiquidTokenizer, "shift", "next");
|
rb_define_alias(cLiquidTokenizer, "shift", "next");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#ifndef LIQUID_TOKENIZER_H
|
#ifndef LIQUID_TOKENIZER_H
|
||||||
#define LIQUID_TOKENIZER_H
|
#define LIQUID_TOKENIZER_H
|
||||||
|
|
||||||
|
extern VALUE cLiquidTokenizer;
|
||||||
|
|
||||||
enum token_type {
|
enum token_type {
|
||||||
TOKEN_NONE,
|
TOKEN_NONE,
|
||||||
TOKEN_INVALID,
|
TOKEN_INVALID,
|
||||||
@@ -9,23 +11,20 @@ enum token_type {
|
|||||||
TOKEN_VARIABLE
|
TOKEN_VARIABLE
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct token {
|
struct token {
|
||||||
enum token_type type;
|
enum token_type type;
|
||||||
const char *str;
|
char *str;
|
||||||
long length;
|
int length;
|
||||||
} token_t;
|
};
|
||||||
|
|
||||||
typedef struct tokenizer {
|
struct liquid_tokenizer {
|
||||||
VALUE source;
|
char *cursor;
|
||||||
const char *cursor;
|
int length;
|
||||||
long length;
|
};
|
||||||
} tokenizer_t;
|
|
||||||
|
|
||||||
extern VALUE cLiquidTokenizer;
|
|
||||||
extern const rb_data_type_t tokenizer_data_type;
|
|
||||||
#define Tokenizer_Get_Struct(obj, sval) TypedData_Get_Struct(obj, tokenizer_t, &tokenizer_data_type, sval)
|
|
||||||
|
|
||||||
void init_liquid_tokenizer();
|
void init_liquid_tokenizer();
|
||||||
void tokenizer_next(tokenizer_t *tokenizer, token_t *token);
|
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
|
#endif
|
||||||
|
|||||||
21
ext/liquid/utils.c
Normal file
21
ext/liquid/utils.c
Normal 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
8
ext/liquid/utils.h
Normal 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
|
||||||
@@ -21,9 +21,9 @@
|
|||||||
|
|
||||||
module Liquid
|
module Liquid
|
||||||
FilterSeparator = /\|/
|
FilterSeparator = /\|/
|
||||||
ArgumentSeparator = ','.freeze
|
ArgumentSeparator = ','
|
||||||
FilterArgumentSeparator = ':'.freeze
|
FilterArgumentSeparator = ':'
|
||||||
VariableAttributeSeparator = '.'.freeze
|
VariableAttributeSeparator = '.'
|
||||||
TagStart = /\{\%/
|
TagStart = /\{\%/
|
||||||
TagEnd = /\%\}/
|
TagEnd = /\%\}/
|
||||||
VariableSignature = /\(?[\w\-\.\[\]]\)?/
|
VariableSignature = /\(?[\w\-\.\[\]]\)?/
|
||||||
@@ -32,10 +32,16 @@ module Liquid
|
|||||||
VariableEnd = /\}\}/
|
VariableEnd = /\}\}/
|
||||||
QuotedString = /"[^"]*"|'[^']*'/
|
QuotedString = /"[^"]*"|'[^']*'/
|
||||||
QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o
|
QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o
|
||||||
|
StrictQuotedFragment = /"[^"]+"|'[^']+'|[^\s|:,]+/
|
||||||
|
FirstFilterArgument = /#{FilterArgumentSeparator}(?:#{StrictQuotedFragment})/o
|
||||||
|
OtherFilterArgument = /#{ArgumentSeparator}(?:#{StrictQuotedFragment})/o
|
||||||
|
SpacelessFilter = /\A(?:'[^']+'|"[^"]+"|[^'"])*#{FilterSeparator}(?:#{StrictQuotedFragment})(?:#{FirstFilterArgument}(?:#{OtherFilterArgument})*)?/o
|
||||||
|
Expression = /(?:#{QuotedFragment}(?:#{SpacelessFilter})*)/o
|
||||||
TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/o
|
TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/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'
|
||||||
@@ -52,6 +58,7 @@ 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'
|
||||||
@@ -60,9 +67,3 @@ require 'liquid/utils'
|
|||||||
# Load all the tags of the standard library
|
# Load all the tags of the standard library
|
||||||
#
|
#
|
||||||
Dir[File.dirname(__FILE__) + '/liquid/tags/*.rb'].each { |f| require f }
|
Dir[File.dirname(__FILE__) + '/liquid/tags/*.rb'].each { |f| require f }
|
||||||
|
|
||||||
if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby'
|
|
||||||
require 'liquid/liquid'
|
|
||||||
else
|
|
||||||
require 'liquid/tokenizer'
|
|
||||||
end
|
|
||||||
|
|||||||
@@ -1,93 +1,37 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
class Block < Tag
|
class Block < Tag
|
||||||
IsTag = /\A#{TagStart}/o
|
def initialize(tag_name, markup, tokens)
|
||||||
IsVariable = /\A#{VariableStart}/o
|
super
|
||||||
FullToken = /\A#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
|
parse_body(tokens)
|
||||||
ContentOfVariable = /\A#{VariableStart}(.*)#{VariableEnd}\z/om
|
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.parse($1, $2, tokens, @options)
|
|
||||||
@blank &&= new_tag.blank?
|
|
||||||
@nodelist << new_tag
|
|
||||||
@children << new_tag
|
|
||||||
else
|
|
||||||
# this tag is not registered with the system
|
|
||||||
# pass it to the current block for special handling or error reporting
|
|
||||||
unknown_tag($1, $2, tokens)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_termination".freeze, :token => token, :tag_end => TagEnd.inspect))
|
|
||||||
end
|
|
||||||
when IsVariable
|
|
||||||
new_var = create_variable(token)
|
|
||||||
@nodelist << new_var
|
|
||||||
@children << new_var
|
|
||||||
@blank = false
|
|
||||||
when ''.freeze
|
|
||||||
# pass
|
|
||||||
else
|
|
||||||
@nodelist << token
|
|
||||||
@blank &&= (token =~ /\A\s*\z/)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Make sure that it's ok to end parsing in the current block.
|
|
||||||
# Effectively this method will throw an exception unless the current block is
|
|
||||||
# of type Document
|
|
||||||
assert_missing_delimitation!
|
|
||||||
end
|
|
||||||
|
|
||||||
# warnings of this block and all sub-tags
|
# 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'.freeze
|
when 'else'
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.unexpected_else".freeze,
|
raise SyntaxError.new(options[:locale].t("errors.syntax.unexpected_else",
|
||||||
:block_name => block_name))
|
:block_name => block_name))
|
||||||
when 'end'.freeze
|
when 'end'
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.invalid_delimiter".freeze,
|
raise SyntaxError.new(options[:locale].t("errors.syntax.invalid_delimiter",
|
||||||
: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".freeze, :tag => tag))
|
raise SyntaxError.new(options[:locale].t("errors.syntax.unknown_tag", :tag => tag))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -99,21 +43,22 @@ module Liquid
|
|||||||
@tag_name
|
@tag_name
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_variable(token)
|
|
||||||
token.scan(ContentOfVariable) do |content|
|
|
||||||
return Variable.new(content.first, @options)
|
|
||||||
end
|
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.variable_termination".freeze, :token => token, :tag_end => VariableEnd.inspect))
|
|
||||||
end
|
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
render_all(@nodelist, context)
|
render_all(@nodelist, context)
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
|
def unterminated_variable(token)
|
||||||
|
raise SyntaxError.new(options[:locale].t("errors.syntax.variable_termination", :token => token, :tag_end => VariableEnd.inspect))
|
||||||
|
end
|
||||||
|
|
||||||
|
def unterminated_tag(token)
|
||||||
|
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_termination", :token => token, :tag_end => TagEnd.inspect))
|
||||||
|
end
|
||||||
|
|
||||||
def assert_missing_delimitation!
|
def assert_missing_delimitation!
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_never_closed".freeze, :block_name => block_name))
|
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_never_closed", :block_name => block_name))
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_all(list, context)
|
def render_all(list, context)
|
||||||
@@ -138,7 +83,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".freeze)
|
raise MemoryError.new("Memory limits exceeded")
|
||||||
end
|
end
|
||||||
unless token.is_a?(Block) && token.blank?
|
unless token.is_a?(Block) && token.blank?
|
||||||
output << token_output
|
output << token_output
|
||||||
@@ -150,7 +95,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
output.join
|
StringSlice.join(output)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,14 +8,14 @@ module Liquid
|
|||||||
#
|
#
|
||||||
class Condition #:nodoc:
|
class Condition #:nodoc:
|
||||||
@@operators = {
|
@@operators = {
|
||||||
'=='.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) },
|
'<>' => lambda { |cond, left, right| !cond.send(:equal_variables, left, right) },
|
||||||
'<'.freeze => :<,
|
'<' => :<,
|
||||||
'>'.freeze => :>,
|
'>' => :>,
|
||||||
'>='.freeze => :>=,
|
'>=' => :>=,
|
||||||
'<='.freeze => :<=,
|
'<=' => :<=,
|
||||||
'contains'.freeze => lambda { |cond, left, right| left && right ? left.include?(right) : false }
|
'contains' => 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(' '.freeze)}>"
|
"#<Condition #{[@left, @operator, @right].compact.join(' ')}>"
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def increment_used_resources(key, obj)
|
def increment_used_resources(key, obj)
|
||||||
@resource_limits[key] += if obj.kind_of?(String) || obj.kind_of?(Array) || obj.kind_of?(Hash)
|
@resource_limits[key] += if obj.kind_of?(StringSlice) || obj.kind_of?(String) || obj.kind_of?(Array) || obj.kind_of?(Hash)
|
||||||
obj.length
|
obj.length
|
||||||
else
|
else
|
||||||
1
|
1
|
||||||
@@ -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".freeze if @scopes.length > 100
|
raise StackLevelError, "Nesting too deep" 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'.freeze => nil, 'null'.freeze => nil, ''.freeze => nil,
|
nil => nil, 'nil' => nil, 'null' => nil, '' => nil,
|
||||||
'true'.freeze => true,
|
'true' => true,
|
||||||
'false'.freeze => false,
|
'false' => false,
|
||||||
'blank'.freeze => :blank?,
|
'blank' => :blank?,
|
||||||
'empty'.freeze => :empty?
|
'empty' => :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,9 +171,9 @@ module Liquid
|
|||||||
LITERALS[key]
|
LITERALS[key]
|
||||||
else
|
else
|
||||||
case key
|
case key
|
||||||
when /\A'(.*)'\z/m # Single quoted strings
|
when /\A'(.*)'\z/ # Single quoted strings
|
||||||
$1
|
$1
|
||||||
when /\A"(.*)"\z/m # Double quoted strings
|
when /\A"(.*)"\z/ # Double quoted strings
|
||||||
$1
|
$1
|
||||||
when /\A(-?\d+)\z/ # Integer and floats
|
when /\A(-?\d+)\z/ # Integer and floats
|
||||||
$1.to_i
|
$1.to_i
|
||||||
@@ -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 = /\A\[(.*)\]\z/m
|
square_bracketed = /\A\[(.*)\]\z/
|
||||||
|
|
||||||
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'.freeze, 'first'.freeze, 'last'.freeze].include?(part)
|
elsif !part_resolved and object.respond_to?(part) and ['size', 'first', 'last'].include?(part)
|
||||||
|
|
||||||
object = object.send(part.intern).to_liquid
|
object = object.send(part.intern).to_liquid
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
class Document < Block
|
class Document < Block
|
||||||
def self.parse(tokens, options={})
|
# we don't need markup to open this block
|
||||||
# we don't need markup to open this block
|
def initialize(tokens, options = {})
|
||||||
super(nil, nil, tokens, options)
|
@options = options
|
||||||
|
parse_body(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
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ module Liquid
|
|||||||
class LocalFileSystem
|
class LocalFileSystem
|
||||||
attr_accessor :root
|
attr_accessor :root
|
||||||
|
|
||||||
def initialize(root, pattern = "_%s.liquid".freeze)
|
def initialize(root, pattern = "_%s.liquid")
|
||||||
@root = root
|
@root = root
|
||||||
@pattern = pattern
|
@pattern = pattern
|
||||||
end
|
end
|
||||||
@@ -59,7 +59,7 @@ module Liquid
|
|||||||
def full_path(template_path)
|
def full_path(template_path)
|
||||||
raise FileSystemError, "Illegal template name '#{template_path}'" unless template_path =~ /\A[^.\/][a-zA-Z0-9_\/]+\z/
|
raise FileSystemError, "Illegal template name '#{template_path}'" unless template_path =~ /\A[^.\/][a-zA-Z0-9_\/]+\z/
|
||||||
|
|
||||||
full_path = if template_path.include?('/'.freeze)
|
full_path = if template_path.include?('/')
|
||||||
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)
|
||||||
|
|||||||
74
lib/liquid/htmltags.rb
Normal file
74
lib/liquid/htmltags.rb
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
module Liquid
|
||||||
|
class TableRow < Block
|
||||||
|
Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o
|
||||||
|
|
||||||
|
def initialize(tag_name, markup, tokens)
|
||||||
|
if markup =~ Syntax
|
||||||
|
@variable_name = $1
|
||||||
|
@collection_name = $2
|
||||||
|
@attributes = {}
|
||||||
|
markup.scan(TagAttributes) do |key, value|
|
||||||
|
@attributes[key] = value
|
||||||
|
end
|
||||||
|
else
|
||||||
|
raise SyntaxError.new(options[:locale].t("errors.syntax.table_row"))
|
||||||
|
end
|
||||||
|
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
|
def render(context)
|
||||||
|
collection = context[@collection_name] or return ''
|
||||||
|
|
||||||
|
from = @attributes['offset'] ? context[@attributes['offset']].to_i : 0
|
||||||
|
to = @attributes['limit'] ? from + context[@attributes['limit']].to_i : nil
|
||||||
|
|
||||||
|
collection = Utils.slice_collection(collection, from, to)
|
||||||
|
|
||||||
|
length = collection.length
|
||||||
|
|
||||||
|
cols = context[@attributes['cols']].to_i
|
||||||
|
|
||||||
|
row = 1
|
||||||
|
col = 0
|
||||||
|
|
||||||
|
result = "<tr class=\"row1\">\n"
|
||||||
|
context.stack do
|
||||||
|
|
||||||
|
collection.each_with_index do |item, index|
|
||||||
|
context[@variable_name] = item
|
||||||
|
context['tablerowloop'] = {
|
||||||
|
'length' => length,
|
||||||
|
'index' => index + 1,
|
||||||
|
'index0' => index,
|
||||||
|
'col' => col + 1,
|
||||||
|
'col0' => col,
|
||||||
|
'index0' => index,
|
||||||
|
'rindex' => length - index,
|
||||||
|
'rindex0' => length - index - 1,
|
||||||
|
'first' => (index == 0),
|
||||||
|
'last' => (index == length - 1),
|
||||||
|
'col_first' => (col == 0),
|
||||||
|
'col_last' => (col == cols - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
col += 1
|
||||||
|
|
||||||
|
result << "<td class=\"col#{col}\">" << super << '</td>'
|
||||||
|
|
||||||
|
if col == cols and (index != length - 1)
|
||||||
|
col = 0
|
||||||
|
row += 1
|
||||||
|
result << "</tr>\n<tr class=\"row#{row}\">"
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
result << "</tr>\n"
|
||||||
|
result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Template.register_tag('tablerow', TableRow)
|
||||||
|
end
|
||||||
@@ -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('.'.freeze).reduce(locale) do |level, cur|
|
name.split('.').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
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ module Liquid
|
|||||||
attr_reader :message
|
attr_reader :message
|
||||||
|
|
||||||
def initialize(message=nil)
|
def initialize(message=nil)
|
||||||
@message = message || "interrupt".freeze
|
@message = message || "interrupt"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ require "strscan"
|
|||||||
module Liquid
|
module Liquid
|
||||||
class Lexer
|
class Lexer
|
||||||
SPECIALS = {
|
SPECIALS = {
|
||||||
'|'.freeze => :pipe,
|
'|' => :pipe,
|
||||||
'.'.freeze => :dot,
|
'.' => :dot,
|
||||||
':'.freeze => :colon,
|
':' => :colon,
|
||||||
','.freeze => :comma,
|
',' => :comma,
|
||||||
'['.freeze => :open_square,
|
'[' => :open_square,
|
||||||
']'.freeze => :close_square,
|
']' => :close_square,
|
||||||
'('.freeze => :open_round,
|
'(' => :open_round,
|
||||||
')'.freeze => :close_round
|
')' => :close_round
|
||||||
}
|
}
|
||||||
IDENTIFIER = /[\w\-?!]+/
|
IDENTIFIER = /[\w\-?!]+/
|
||||||
SINGLE_STRING_LITERAL = /'[^\']*'/
|
SINGLE_STRING_LITERAL = /'[^\']*'/
|
||||||
|
|||||||
@@ -66,11 +66,10 @@ 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 << ' '.freeze
|
str << consume << consume << ' '
|
||||||
end
|
end
|
||||||
|
|
||||||
str << expression
|
str << expression
|
||||||
str
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def variable_signature
|
def variable_signature
|
||||||
|
|||||||
@@ -4,17 +4,12 @@ require 'bigdecimal'
|
|||||||
module Liquid
|
module Liquid
|
||||||
|
|
||||||
module StandardFilters
|
module StandardFilters
|
||||||
HTML_ESCAPE = {
|
HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"', "'" => ''' }
|
||||||
'&'.freeze => '&'.freeze,
|
|
||||||
'>'.freeze => '>'.freeze,
|
|
||||||
'<'.freeze => '<'.freeze,
|
|
||||||
'"'.freeze => '"'.freeze,
|
|
||||||
"'".freeze => '''.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
|
||||||
|
|
||||||
@@ -44,19 +39,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 = "...".freeze)
|
def truncate(input, length = 50, truncate_string = "...")
|
||||||
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 = "...".freeze)
|
def truncatewords(input, words = 15, truncate_string = "...")
|
||||||
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(" ".freeze) + truncate_string : input
|
wordlist.length > l ? wordlist[0..l].join(" ") + 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.
|
||||||
@@ -81,17 +76,16 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def strip_html(input)
|
def strip_html(input)
|
||||||
empty = ''.freeze
|
input.to_s.gsub(/<script.*?<\/script>/m, '').gsub(/<!--.*?-->/m, '').gsub(/<style.*?<\/style>/m, '').gsub(/<.*?>/m, '')
|
||||||
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/, ''.freeze)
|
input.to_s.gsub(/\r?\n/, '')
|
||||||
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 = ' '.freeze)
|
def join(input, glue = ' ')
|
||||||
[input].flatten.join(glue)
|
[input].flatten.join(glue)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -101,7 +95,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?('[]'.freeze) and !ary.first[property].nil?
|
elsif ary.first.respond_to?('[]') 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) }
|
||||||
@@ -119,7 +113,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".freeze
|
if property == "to_liquid"
|
||||||
e
|
e
|
||||||
elsif e.respond_to?(:[])
|
elsif e.respond_to?(:[])
|
||||||
e[property]
|
e[property]
|
||||||
@@ -128,23 +122,23 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Replace occurrences of a string with another
|
# Replace occurrences of a string with another
|
||||||
def replace(input, string, replacement = ''.freeze)
|
def replace(input, string, replacement = '')
|
||||||
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 = ''.freeze)
|
def replace_first(input, string, replacement = '')
|
||||||
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, ''.freeze)
|
input.to_s.gsub(string, '')
|
||||||
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, ''.freeze)
|
input.to_s.sub(string, '')
|
||||||
end
|
end
|
||||||
|
|
||||||
# add one string to another
|
# add one string to another
|
||||||
@@ -159,7 +153,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".freeze)
|
input.to_s.gsub(/\n/, "<br />\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
# Reformat a date
|
# Reformat a date
|
||||||
@@ -202,7 +196,7 @@ module Liquid
|
|||||||
|
|
||||||
date = if input.is_a?(String)
|
date = if input.is_a?(String)
|
||||||
case input.downcase
|
case input.downcase
|
||||||
when 'now'.freeze, 'today'.freeze
|
when 'now', 'today'
|
||||||
Time.now
|
Time.now
|
||||||
else
|
else
|
||||||
Time.parse(input)
|
Time.parse(input)
|
||||||
@@ -262,7 +256,7 @@ module Liquid
|
|||||||
apply_operation(input, operand, :%)
|
apply_operation(input, operand, :%)
|
||||||
end
|
end
|
||||||
|
|
||||||
def default(input, default_value = "".freeze)
|
def default(input, default_value = "")
|
||||||
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
|
||||||
|
|||||||
@@ -3,23 +3,19 @@ module Liquid
|
|||||||
attr_accessor :options
|
attr_accessor :options
|
||||||
attr_reader :nodelist, :warnings
|
attr_reader :nodelist, :warnings
|
||||||
|
|
||||||
class << self
|
def self.new_with_options(tag_name, markup, tokens, options)
|
||||||
def parse(tag_name, markup, tokens, options)
|
# Forgive me Matz for I have sinned. I know this code is weird
|
||||||
tag = new(tag_name, markup, options)
|
# but it was necessary to maintain API compatibility.
|
||||||
tag.parse(tokens)
|
new_tag = self.allocate
|
||||||
tag
|
new_tag.options = options
|
||||||
end
|
new_tag.send(:initialize, tag_name, markup, tokens)
|
||||||
|
new_tag
|
||||||
private :new
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(tag_name, markup, options)
|
def initialize(tag_name, markup, tokens)
|
||||||
@tag_name = tag_name
|
@tag_name = tag_name
|
||||||
@markup = markup
|
@markup = markup
|
||||||
@options = options
|
@options ||= {} # needs || because might be set before initialize
|
||||||
end
|
|
||||||
|
|
||||||
def parse(tokens)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def name
|
def name
|
||||||
@@ -27,7 +23,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
''.freeze
|
''
|
||||||
end
|
end
|
||||||
|
|
||||||
def blank?
|
def blank?
|
||||||
|
|||||||
@@ -9,23 +9,24 @@ module Liquid
|
|||||||
# {{ foo }}
|
# {{ foo }}
|
||||||
#
|
#
|
||||||
class Assign < Tag
|
class Assign < Tag
|
||||||
Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om
|
Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/o
|
||||||
|
|
||||||
def initialize(tag_name, markup, options)
|
def initialize(tag_name, markup, tokens)
|
||||||
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".freeze)
|
raise SyntaxError.new options[:locale].t("errors.syntax.assign")
|
||||||
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?
|
||||||
@@ -33,5 +34,5 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('assign'.freeze, Assign)
|
Template.register_tag('assign', Assign)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -17,5 +17,5 @@ module Liquid
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('break'.freeze, Break)
|
Template.register_tag('break', Break)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -14,20 +14,21 @@ module Liquid
|
|||||||
class Capture < Block
|
class Capture < Block
|
||||||
Syntax = /(\w+)/
|
Syntax = /(\w+)/
|
||||||
|
|
||||||
def initialize(tag_name, markup, options)
|
def initialize(tag_name, markup, tokens)
|
||||||
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?
|
||||||
@@ -35,5 +36,5 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('capture'.freeze, Capture)
|
Template.register_tag('capture', Capture)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
class Case < Block
|
class Case < Block
|
||||||
Syntax = /(#{QuotedFragment})/o
|
Syntax = /(#{QuotedFragment})/o
|
||||||
WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/om
|
WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/o
|
||||||
|
|
||||||
def initialize(tag_name, markup, options)
|
def initialize(tag_name, markup, tokens)
|
||||||
super
|
|
||||||
@blocks = []
|
@blocks = []
|
||||||
|
|
||||||
if markup =~ Syntax
|
if markup =~ Syntax
|
||||||
@left = $1
|
@left = $1
|
||||||
else
|
else
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.case".freeze))
|
raise SyntaxError.new(options[:locale].t("errors.syntax.case"))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
def nodelist
|
def nodelist
|
||||||
@@ -21,9 +22,9 @@ module Liquid
|
|||||||
def unknown_tag(tag, markup, tokens)
|
def unknown_tag(tag, markup, tokens)
|
||||||
@nodelist = []
|
@nodelist = []
|
||||||
case tag
|
case tag
|
||||||
when 'when'.freeze
|
when 'when'
|
||||||
record_when_condition(markup)
|
record_when_condition(markup)
|
||||||
when 'else'.freeze
|
when 'else'
|
||||||
record_else_condition(markup)
|
record_else_condition(markup)
|
||||||
else
|
else
|
||||||
super
|
super
|
||||||
@@ -53,12 +54,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".freeze))
|
raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_when"))
|
||||||
end
|
end
|
||||||
|
|
||||||
markup = $2
|
markup = $2
|
||||||
|
|
||||||
block = Condition.new(@left, '=='.freeze, $1)
|
block = Condition.new(@left, '==', $1)
|
||||||
block.attach(@nodelist)
|
block.attach(@nodelist)
|
||||||
@blocks.push(block)
|
@blocks.push(block)
|
||||||
end
|
end
|
||||||
@@ -66,7 +67,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".freeze))
|
raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_else"))
|
||||||
end
|
end
|
||||||
|
|
||||||
block = ElseCondition.new
|
block = ElseCondition.new
|
||||||
@@ -75,5 +76,5 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('case'.freeze, Case)
|
Template.register_tag('case', Case)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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'.freeze, Comment)
|
Template.register_tag('comment', Comment)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
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:
|
||||||
@@ -9,10 +10,12 @@ 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'.freeze, Continue)
|
Template.register_tag('continue', Continue)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -13,10 +13,9 @@ module Liquid
|
|||||||
#
|
#
|
||||||
class Cycle < Tag
|
class Cycle < Tag
|
||||||
SimpleSyntax = /\A#{QuotedFragment}+/o
|
SimpleSyntax = /\A#{QuotedFragment}+/o
|
||||||
NamedSyntax = /\A(#{QuotedFragment})\s*\:\s*(.*)/om
|
NamedSyntax = /\A(#{QuotedFragment})\s*\:\s*(.*)/o
|
||||||
|
|
||||||
def initialize(tag_name, markup, options)
|
def initialize(tag_name, markup, tokens)
|
||||||
super
|
|
||||||
case markup
|
case markup
|
||||||
when NamedSyntax
|
when NamedSyntax
|
||||||
@variables = variables_from_string($2)
|
@variables = variables_from_string($2)
|
||||||
@@ -25,8 +24,9 @@ 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".freeze))
|
raise SyntaxError.new(options[:locale].t("errors.syntax.cycle"))
|
||||||
end
|
end
|
||||||
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
|
|||||||
@@ -19,9 +19,10 @@ module Liquid
|
|||||||
# Hello: -3
|
# Hello: -3
|
||||||
#
|
#
|
||||||
class Decrement < Tag
|
class Decrement < Tag
|
||||||
def initialize(tag_name, markup, options)
|
def initialize(tag_name, markup, tokens)
|
||||||
super
|
|
||||||
@variable = markup.strip
|
@variable = markup.strip
|
||||||
|
|
||||||
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
@@ -34,5 +35,5 @@ module Liquid
|
|||||||
private
|
private
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('decrement'.freeze, Decrement)
|
Template.register_tag('decrement', Decrement)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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, options)
|
def initialize(tag_name, markup, tokens)
|
||||||
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'.freeze
|
return super unless tag == 'else'
|
||||||
@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'.freeze] == 'continue'.freeze
|
from = if @attributes['offset'] == 'continue'
|
||||||
context.registers[:for][@name].to_i
|
context.registers[:for][@name].to_i
|
||||||
else
|
else
|
||||||
context[@attributes['offset'.freeze]].to_i
|
context[@attributes['offset']].to_i
|
||||||
end
|
end
|
||||||
|
|
||||||
limit = context[@attributes['limit'.freeze]]
|
limit = context[@attributes['limit']]
|
||||||
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,16 +99,15 @@ 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'.freeze] = {
|
context['forloop'] = {
|
||||||
'name'.freeze => @name,
|
'name' => @name,
|
||||||
'length'.freeze => length,
|
'length' => length,
|
||||||
'index'.freeze => index + 1,
|
'index' => index + 1,
|
||||||
'index0'.freeze => index,
|
'index0' => index,
|
||||||
'rindex'.freeze => length - index,
|
'rindex' => length - index,
|
||||||
'rindex0'.freeze => length - index - 1,
|
'rindex0' => length - index - 1,
|
||||||
'first'.freeze => (index == 0),
|
'first' => (index == 0),
|
||||||
'last'.freeze => (index == length - 1)
|
'last' => (index == length - 1) }
|
||||||
}
|
|
||||||
|
|
||||||
result << render_all(@for_block, context)
|
result << render_all(@for_block, context)
|
||||||
|
|
||||||
@@ -136,22 +135,22 @@ module Liquid
|
|||||||
@attributes[key] = value
|
@attributes[key] = value
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.for".freeze))
|
raise SyntaxError.new(options[:locale].t("errors.syntax.for"))
|
||||||
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".freeze)) unless p.id?('in'.freeze)
|
raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_in")) unless p.id?('in')
|
||||||
@collection_name = p.expression
|
@collection_name = p.expression
|
||||||
@name = "#{@variable_name}-#{@collection_name}"
|
@name = "#{@variable_name}-#{@collection_name}"
|
||||||
@reversed = p.id?('reversed'.freeze)
|
@reversed = p.id?('reversed')
|
||||||
|
|
||||||
@attributes = {}
|
@attributes = {}
|
||||||
while p.look(:id) && p.look(:colon, 1)
|
while p.look(:id) && p.look(:colon, 1)
|
||||||
unless attribute = p.id?('limit'.freeze) || p.id?('offset'.freeze)
|
unless attribute = p.id?('limit') || p.id?('offset')
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_attribute".freeze))
|
raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_attribute"))
|
||||||
end
|
end
|
||||||
p.consume
|
p.consume
|
||||||
val = p.expression
|
val = p.expression
|
||||||
@@ -162,14 +161,14 @@ module Liquid
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def render_else(context)
|
def render_else(context)
|
||||||
return @else_block ? [render_all(@else_block, context)] : ''.freeze
|
return @else_block ? [render_all(@else_block, context)] : ''
|
||||||
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'.freeze, For)
|
Template.register_tag('for', For)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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, options)
|
def initialize(tag_name, markup, tokens)
|
||||||
super
|
|
||||||
@blocks = []
|
@blocks = []
|
||||||
push_block('if'.freeze, markup)
|
push_block('if', markup)
|
||||||
|
super
|
||||||
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'.freeze, 'else'.freeze].include?(tag)
|
if ['elsif', 'else'].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'.freeze
|
block = if tag == 'else'
|
||||||
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".freeze))) unless expressions.shift =~ Syntax
|
raise(SyntaxError.new(options[:locale].t("errors.syntax.if"))) 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".freeze))) unless expressions.shift.to_s =~ Syntax
|
raise(SyntaxError.new(options[:locale].t("errors.syntax.if"))) 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".freeze))) unless BOOLEAN_OPERATORS.include?(operator)
|
raise(SyntaxError.new(options[:locale].t("errors.syntax.if"))) 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'.freeze) || p.id?('or'.freeze))
|
while op = (p.id?('and') || p.id?('or'))
|
||||||
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'.freeze, If)
|
Template.register_tag('if', If)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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'.freeze, Ifchanged)
|
Template.register_tag('ifchanged', Ifchanged)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -17,9 +17,7 @@ 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, options)
|
def initialize(tag_name, markup, tokens)
|
||||||
super
|
|
||||||
|
|
||||||
if markup =~ Syntax
|
if markup =~ Syntax
|
||||||
|
|
||||||
@template_name = $1
|
@template_name = $1
|
||||||
@@ -31,11 +29,10 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
else
|
else
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.include".freeze))
|
raise SyntaxError.new(options[:locale].t("errors.syntax.include"))
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def parse(tokens)
|
super
|
||||||
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('/'.freeze).last
|
context_variable_name = @template_name[1..-2].split('/').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'.freeze, Include)
|
Template.register_tag('include', Include)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ module Liquid
|
|||||||
# Hello: 2
|
# Hello: 2
|
||||||
#
|
#
|
||||||
class Increment < Tag
|
class Increment < Tag
|
||||||
def initialize(tag_name, markup, options)
|
def initialize(tag_name, markup, tokens)
|
||||||
super
|
|
||||||
@variable = markup.strip
|
@variable = markup.strip
|
||||||
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
@@ -31,5 +31,5 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('increment'.freeze, Increment)
|
Template.register_tag('increment', Increment)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,22 +1,19 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
class Raw < Block
|
class Raw < Block
|
||||||
FullTokenPossiblyInvalid = /\A(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
|
FullTokenPossiblyInvalid = /\A(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/o
|
||||||
|
|
||||||
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 != "".freeze
|
@nodelist << $1 if $1 != ""
|
||||||
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'.freeze, Raw)
|
Template.register_tag('raw', Raw)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
module Liquid
|
|
||||||
class TableRow < Block
|
|
||||||
Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o
|
|
||||||
|
|
||||||
def initialize(tag_name, markup, options)
|
|
||||||
super
|
|
||||||
if markup =~ Syntax
|
|
||||||
@variable_name = $1
|
|
||||||
@collection_name = $2
|
|
||||||
@attributes = {}
|
|
||||||
markup.scan(TagAttributes) do |key, value|
|
|
||||||
@attributes[key] = value
|
|
||||||
end
|
|
||||||
else
|
|
||||||
raise SyntaxError.new(options[:locale].t("errors.syntax.table_row".freeze))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def render(context)
|
|
||||||
collection = context[@collection_name] or return ''.freeze
|
|
||||||
|
|
||||||
from = @attributes['offset'.freeze] ? context[@attributes['offset'.freeze]].to_i : 0
|
|
||||||
to = @attributes['limit'.freeze] ? from + context[@attributes['limit'.freeze]].to_i : nil
|
|
||||||
|
|
||||||
collection = Utils.slice_collection(collection, from, to)
|
|
||||||
|
|
||||||
length = collection.length
|
|
||||||
|
|
||||||
cols = context[@attributes['cols'.freeze]].to_i
|
|
||||||
|
|
||||||
row = 1
|
|
||||||
col = 0
|
|
||||||
|
|
||||||
result = "<tr class=\"row1\">\n"
|
|
||||||
context.stack do
|
|
||||||
|
|
||||||
collection.each_with_index do |item, index|
|
|
||||||
context[@variable_name] = item
|
|
||||||
context['tablerowloop'.freeze] = {
|
|
||||||
'length'.freeze => length,
|
|
||||||
'index'.freeze => index + 1,
|
|
||||||
'index0'.freeze => index,
|
|
||||||
'col'.freeze => col + 1,
|
|
||||||
'col0'.freeze => col,
|
|
||||||
'index0'.freeze => index,
|
|
||||||
'rindex'.freeze => length - index,
|
|
||||||
'rindex0'.freeze => length - index - 1,
|
|
||||||
'first'.freeze => (index == 0),
|
|
||||||
'last'.freeze => (index == length - 1),
|
|
||||||
'col_first'.freeze => (col == 0),
|
|
||||||
'col_last'.freeze => (col == cols - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
col += 1
|
|
||||||
|
|
||||||
result << "<td class=\"col#{col}\">" << render_all(@nodelist, context) << '</td>'
|
|
||||||
|
|
||||||
if col == cols and (index != length - 1)
|
|
||||||
col = 0
|
|
||||||
row += 1
|
|
||||||
result << "</tr>\n<tr class=\"row#{row}\">"
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
result << "</tr>\n"
|
|
||||||
result
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Template.register_tag('tablerow'.freeze, TableRow)
|
|
||||||
end
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
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 %}
|
||||||
@@ -22,10 +23,11 @@ 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
|
||||||
|
|||||||
@@ -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.parse(tokenize(source), DEFAULT_OPTIONS.merge(options))
|
@root = Document.new(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 ''.freeze if @root.nil?
|
return '' if @root.nil?
|
||||||
|
|
||||||
context = case args.first
|
context = case args.first
|
||||||
when Liquid::Context
|
when Liquid::Context
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
module Liquid
|
|
||||||
class Tokenizer
|
|
||||||
VariableIncompleteEnd = /\}\}?/
|
|
||||||
AnyStartingTag = /\{\{|\{\%/
|
|
||||||
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/om
|
|
||||||
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om
|
|
||||||
|
|
||||||
def initialize(source)
|
|
||||||
@tokens = source.split(TemplateParser)
|
|
||||||
|
|
||||||
# removes the rogue empty element at the beginning of the array
|
|
||||||
@tokens.shift if @tokens[0] && @tokens[0].empty?
|
|
||||||
end
|
|
||||||
|
|
||||||
def next
|
|
||||||
@tokens.shift
|
|
||||||
end
|
|
||||||
alias_method :shift, :next
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -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 != ''.freeze
|
collection.is_a?(String) && collection != ''
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.slice_collection_using_each(collection, from, to)
|
def self.slice_collection_using_each(collection, from, to)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ module Liquid
|
|||||||
@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)
|
||||||
@@ -36,9 +37,9 @@ module Liquid
|
|||||||
|
|
||||||
def lax_parse(markup)
|
def lax_parse(markup)
|
||||||
@filters = []
|
@filters = []
|
||||||
if match = markup.match(/\s*(#{QuotedFragment})(.*)/om)
|
if match = markup.match(/\s*(#{QuotedFragment})(.*)/o)
|
||||||
@name = match[1]
|
@name = match[1]
|
||||||
if match[2].match(/#{FilterSeparator}\s*(.*)/om)
|
if match[2].match(/#{FilterSeparator}\s*(.*)/o)
|
||||||
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+)/)
|
||||||
@@ -62,7 +63,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) ? ''.freeze : p.expression
|
@name = p.look(:pipe) ? '' : 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) : []
|
||||||
@@ -85,7 +86,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
return ''.freeze if @name.nil?
|
return '' if @name.nil?
|
||||||
@filters.inject(context[@name]) do |output, filter|
|
@filters.inject(context[@name]) do |output, filter|
|
||||||
filterargs = []
|
filterargs = []
|
||||||
keyword_args = {}
|
keyword_args = {}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
module Liquid
|
module Liquid
|
||||||
VERSION = "3.0.0".freeze
|
VERSION = "3.0.0"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -19,16 +19,14 @@ Gem::Specification.new do |s|
|
|||||||
|
|
||||||
s.test_files = Dir.glob("{test}/**/*")
|
s.test_files = Dir.glob("{test}/**/*")
|
||||||
s.files = Dir.glob("{lib,ext}/**/*") + %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-compiler'
|
||||||
|
s.add_development_dependency 'stackprof'
|
||||||
s.add_development_dependency 'rake'
|
s.add_development_dependency 'rake'
|
||||||
s.add_development_dependency 'activesupport'
|
s.add_development_dependency 'activesupport'
|
||||||
if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby'
|
|
||||||
s.extensions = ['ext/liquid/extconf.rb']
|
|
||||||
s.add_development_dependency 'rake-compiler'
|
|
||||||
s.add_development_dependency 'stackprof' if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.1.0")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
require 'stackprof' rescue fail("install stackprof extension/gem")
|
require 'rubygems'
|
||||||
|
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
|
|
||||||
results = StackProf.run(mode: :cpu) do
|
puts 'Running profiler...'
|
||||||
100.times do
|
|
||||||
profiler.run
|
results = profiler.run_profile
|
||||||
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']
|
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
class CommentForm < Liquid::Block
|
class CommentForm < Liquid::Block
|
||||||
Syntax = /(#{Liquid::VariableSignature}+)/
|
Syntax = /(#{Liquid::VariableSignature}+)/
|
||||||
|
|
||||||
def initialize(tag_name, markup, options)
|
def initialize(tag_name, markup, tokens)
|
||||||
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)
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
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, options)
|
def initialize(tag_name, markup, tokens)
|
||||||
super
|
|
||||||
|
|
||||||
@nodelist = []
|
@nodelist = []
|
||||||
|
|
||||||
if markup =~ Syntax
|
if markup =~ Syntax
|
||||||
@@ -21,6 +19,8 @@ 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)
|
||||||
|
|||||||
@@ -45,11 +45,11 @@ module ShopFilter
|
|||||||
end
|
end
|
||||||
|
|
||||||
def url_for_vendor(vendor_title)
|
def url_for_vendor(vendor_title)
|
||||||
"/collections/#{to_handle(vendor_title)}"
|
"/collections/#{vendor_title.to_handle}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def url_for_type(type_title)
|
def url_for_type(type_title)
|
||||||
"/collections/#{to_handle(type_title)}"
|
"/collections/#{type_title.to_handle}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def product_img_url(url, style = 'small')
|
def product_img_url(url, style = 'small')
|
||||||
@@ -95,16 +95,4 @@ 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
|
||||||
|
|||||||
15
performance/stackprof.rb
Normal file
15
performance/stackprof.rb
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
require 'stackprof' rescue fail("install stackprof extension/gem")
|
||||||
|
require File.dirname(__FILE__) + '/theme_runner'
|
||||||
|
|
||||||
|
profiler = ThemeRunner.new
|
||||||
|
profiler.run
|
||||||
|
results = StackProf.run(mode: :cpu, out: ENV['FILENAME']) do
|
||||||
|
100.times do
|
||||||
|
profiler.run
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if results.kind_of?(File)
|
||||||
|
puts "wrote stackprof dump to #{results.path}"
|
||||||
|
else
|
||||||
|
StackProf::Report.new(results).print_text(false, 20)
|
||||||
|
end
|
||||||
@@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
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'
|
||||||
@@ -65,17 +64,52 @@ 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
|
||||||
|
|||||||
@@ -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_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 " foobar "*(N+1), Template.parse(wrap("{% include ' foobar ' %}")).render()
|
||||||
assert_template_result " "*(N+1), wrap(" {% include ' ' %} ")
|
assert_equal " ", Template.parse(" {% include ' ' %} ").render()
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_case_is_blank
|
def test_case_is_blank
|
||||||
|
|||||||
@@ -12,34 +12,34 @@ class BlockTest < Test::Unit::TestCase
|
|||||||
template = Liquid::Template.parse("{{funk}} ")
|
template = Liquid::Template.parse("{{funk}} ")
|
||||||
assert_equal 2, template.root.nodelist.size
|
assert_equal 2, template.root.nodelist.size
|
||||||
assert_equal Variable, template.root.nodelist[0].class
|
assert_equal Variable, template.root.nodelist[0].class
|
||||||
assert_equal String, template.root.nodelist[1].class
|
assert_equal StringSlice, template.root.nodelist[1].class
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_variable_end
|
def test_variable_end
|
||||||
template = Liquid::Template.parse(" {{funk}}")
|
template = Liquid::Template.parse(" {{funk}}")
|
||||||
assert_equal 2, template.root.nodelist.size
|
assert_equal 2, template.root.nodelist.size
|
||||||
assert_equal String, template.root.nodelist[0].class
|
assert_equal StringSlice, template.root.nodelist[0].class
|
||||||
assert_equal Variable, template.root.nodelist[1].class
|
assert_equal Variable, template.root.nodelist[1].class
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_variable_middle
|
def test_variable_middle
|
||||||
template = Liquid::Template.parse(" {{funk}} ")
|
template = Liquid::Template.parse(" {{funk}} ")
|
||||||
assert_equal 3, template.root.nodelist.size
|
assert_equal 3, template.root.nodelist.size
|
||||||
assert_equal String, template.root.nodelist[0].class
|
assert_equal StringSlice, template.root.nodelist[0].class
|
||||||
assert_equal Variable, template.root.nodelist[1].class
|
assert_equal Variable, template.root.nodelist[1].class
|
||||||
assert_equal String, template.root.nodelist[2].class
|
assert_equal StringSlice, template.root.nodelist[2].class
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_variable_many_embedded_fragments
|
def test_variable_many_embedded_fragments
|
||||||
template = Liquid::Template.parse(" {{funk}} {{so}} {{brother}} ")
|
template = Liquid::Template.parse(" {{funk}} {{so}} {{brother}} ")
|
||||||
assert_equal 7, template.root.nodelist.size
|
assert_equal 7, template.root.nodelist.size
|
||||||
assert_equal [String, Variable, String, Variable, String, Variable, String],
|
assert_equal [StringSlice, Variable, StringSlice, Variable, StringSlice, Variable, StringSlice],
|
||||||
block_types(template.root.nodelist)
|
block_types(template.root.nodelist)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_with_block
|
def test_with_block
|
||||||
template = Liquid::Template.parse(" {% comment %} {% endcomment %} ")
|
template = Liquid::Template.parse(" {% comment %} {% endcomment %} ")
|
||||||
assert_equal [String, Comment, String], block_types(template.root.nodelist)
|
assert_equal [StringSlice, Comment, StringSlice], block_types(template.root.nodelist)
|
||||||
assert_equal 3, template.root.nodelist.size
|
assert_equal 3, template.root.nodelist.size
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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_template_result 'allowedA', "{{ a.allowedA }}", 'a'=>@a
|
assert_equal 'allowedA', Liquid::Template.parse("{{ a.allowedA }}").render('a'=>@a)
|
||||||
assert_template_result 'allowedB', "{{ a.chainedB.allowedB }}", 'a'=>@a
|
assert_equal 'allowedB', Liquid::Template.parse("{{ a.chainedB.allowedB }}").render('a'=>@a)
|
||||||
assert_template_result 'allowedC', "{{ a.chainedB.chainedC.allowedC }}", 'a'=>@a
|
assert_equal 'allowedC', Liquid::Template.parse("{{ a.chainedB.chainedC.allowedC }}").render('a'=>@a)
|
||||||
assert_template_result 'another_allowedC', "{{ a.chainedB.chainedC.another_allowedC }}", 'a'=>@a
|
assert_equal 'another_allowedC', Liquid::Template.parse("{{ a.chainedB.chainedC.another_allowedC }}").render('a'=>@a)
|
||||||
assert_template_result '', "{{ a.restricted }}", 'a'=>@a
|
assert_equal '', Liquid::Template.parse("{{ a.restricted }}").render('a'=>@a)
|
||||||
assert_template_result '', "{{ a.unknown }}", 'a'=>@a
|
assert_equal '', Liquid::Template.parse("{{ a.unknown }}").render('a'=>@a)
|
||||||
end
|
end
|
||||||
end # ModuleExTest
|
end # ModuleExTest
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ 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 [StringSlice], template.root.nodelist.collect {|i| i.class}
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_raise_on_single_close_bracet
|
def test_raise_on_single_close_bracet
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -126,38 +126,37 @@ class StandardFiltersTest < Test::Unit::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_map_doesnt_call_arbitrary_stuff
|
def test_map_doesnt_call_arbitrary_stuff
|
||||||
assert_template_result "", '{{ "foo" | map: "__id__" }}'
|
assert_equal "", Liquid::Template.parse('{{ "foo" | map: "__id__" }}').render
|
||||||
assert_template_result "", '{{ "foo" | map: "inspect" }}'
|
assert_equal "", Liquid::Template.parse('{{ "foo" | map: "inspect" }}').render
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_map_calls_to_liquid
|
def test_map_calls_to_liquid
|
||||||
t = TestThing.new
|
t = TestThing.new
|
||||||
assert_template_result "woot: 1", '{{ foo | map: "whatever" }}', "foo" => [t]
|
assert_equal "woot: 1", Liquid::Template.parse('{{ foo | map: "whatever" }}').render("foo" => [t])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_map_on_hashes
|
def test_map_on_hashes
|
||||||
assert_template_result "4217", '{{ thing | map: "foo" | map: "bar" }}',
|
assert_equal "4217", Liquid::Template.parse('{{ thing | map: "foo" | map: "bar" }}').render("thing" => { "foo" => [ { "bar" => 42 }, { "bar" => 17 } ] })
|
||||||
"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_template_result "woot: 1", '{{ foo | sort: "whatever" }}', "foo" => [t]
|
assert_equal "woot: 1", Liquid::Template.parse('{{ foo | sort: "whatever" }}').render("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_template_result "testfoo", templ, "procs" => [p]
|
assert_equal "testfoo", Liquid::Template.parse(templ).render("procs" => [p])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_map_works_on_enumerables
|
def test_map_works_on_enumerables
|
||||||
assert_template_result "123", '{{ foo | map: "foo" }}', "foo" => TestEnumerable.new
|
assert_equal "123", Liquid::Template.parse('{{ foo | map: "foo" }}').render!("foo" => TestEnumerable.new)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_sort_works_on_enumerables
|
def test_sort_works_on_enumerables
|
||||||
assert_template_result "213", '{{ foo | sort: "bar" | map: "foo" }}', "foo" => TestEnumerable.new
|
assert_equal "213", Liquid::Template.parse('{{ foo | sort: "bar" | map: "foo" }}').render!("foo" => TestEnumerable.new)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_date
|
def test_date
|
||||||
@@ -258,7 +257,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_equal "Liquid error: divided by 0", Template.parse("{{ 5 | divided_by:0 }}").render
|
assert_template_result "Liquid error: divided by 0", "{{ 5 | divided_by:0 }}"
|
||||||
|
|
||||||
assert_template_result "0.5", "{{ 2.0 | divided_by:4 }}"
|
assert_template_result "0.5", "{{ 2.0 | divided_by:4 }}"
|
||||||
end
|
end
|
||||||
|
|||||||
34
test/liquid/string_slice_test.rb
Normal file
34
test/liquid/string_slice_test.rb
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class StringSliceTest < Test::Unit::TestCase
|
||||||
|
def test_new_from_string
|
||||||
|
assert_equal 'slice', Liquid::StringSlice.new("slice and dice", 0, 5).to_str
|
||||||
|
assert_equal 'and', Liquid::StringSlice.new("slice and dice", 6, 3).to_str
|
||||||
|
assert_equal 'dice', Liquid::StringSlice.new("slice and dice", 10, 4).to_str
|
||||||
|
assert_equal 'slice and dice', Liquid::StringSlice.new("slice and dice", 0, 14).to_str
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_new_from_slice
|
||||||
|
slice1 = Liquid::StringSlice.new("slice and dice", 0, 14)
|
||||||
|
slice2 = Liquid::StringSlice.new(slice1, 6, 8)
|
||||||
|
slice3 = Liquid::StringSlice.new(slice2, 0, 3)
|
||||||
|
assert_equal "slice and dice", slice1.to_str
|
||||||
|
assert_equal "and dice", slice2.to_str
|
||||||
|
assert_equal "and", slice3.to_str
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_slice
|
||||||
|
slice = Liquid::StringSlice.new("slice and dice", 2, 10)
|
||||||
|
assert_equal "and", slice.slice(4, 3).to_str
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_length
|
||||||
|
slice = Liquid::StringSlice.new("slice and dice", 6, 3)
|
||||||
|
assert_equal 3, slice.length
|
||||||
|
assert_equal 3, slice.size
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_equal
|
||||||
|
assert_equal 'and', Liquid::StringSlice.new("slice and dice", 6, 3)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class TableRowTest < Test::Unit::TestCase
|
class HtmlTagTest < Test::Unit::TestCase
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
class ArrayDrop < Liquid::Drop
|
class ArrayDrop < Liquid::Drop
|
||||||
@@ -15,7 +15,7 @@ class TableRowTest < Test::Unit::TestCase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_table_row
|
def test_html_table
|
||||||
|
|
||||||
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 TableRowTest < Test::Unit::TestCase
|
|||||||
'numbers' => [])
|
'numbers' => [])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_table_row_with_different_cols
|
def test_html_table_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_table_col_counter
|
def test_html_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 TableRowTest < 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
|
end # HtmlTagTest
|
||||||
@@ -78,52 +78,57 @@ 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_template_result "Product: Draft 151cm ",
|
assert_equal "Product: Draft 151cm ",
|
||||||
"{% include 'product' with products[0] %}", "products" => [ {'title' => 'Draft 151cm'}, {'title' => 'Element 155cm'} ]
|
Template.parse("{% include 'product' with products[0] %}").render( "products" => [ {'title' => 'Draft 151cm'}, {'title' => 'Element 155cm'} ] )
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_include_tag_with_default_name
|
def test_include_tag_with_default_name
|
||||||
assert_template_result "Product: Draft 151cm ",
|
assert_equal "Product: Draft 151cm ",
|
||||||
"{% include 'product' %}", "product" => {'title' => 'Draft 151cm'}
|
Template.parse("{% include 'product' %}").render( "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 ",
|
|
||||||
"{% include 'product' for products %}", "products" => [ {'title' => 'Draft 151cm'}, {'title' => 'Element 155cm'} ]
|
assert_equal "Product: Draft 151cm Product: 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_template_result "Locale: test123 ", "{% include 'locale_variables' echo1: 'test123' %}"
|
assert_equal "Locale: 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_template_result "Locale: test123 test321",
|
assert_equal "Locale: test123 test321",
|
||||||
"{% include 'locale_variables' echo1: 'test123', echo2: 'test321' %}"
|
Template.parse("{% include 'locale_variables' echo1: 'test123', echo2: 'test321' %}").render
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_include_tag_with_multiple_local_variables_from_context
|
def test_include_tag_with_multiple_local_variables_from_context
|
||||||
assert_template_result "Locale: test123 test321",
|
assert_equal "Locale: test123 test321",
|
||||||
"{% include 'locale_variables' echo1: echo1, echo2: more_echos.echo2 %}",
|
Template.parse("{% include 'locale_variables' echo1: echo1, echo2: more_echos.echo2 %}").render('echo1' => 'test123', 'more_echos' => { "echo2" => 'test321'})
|
||||||
'echo1' => 'test123', 'more_echos' => { "echo2" => 'test321'}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_nested_include_tag
|
def test_nested_include_tag
|
||||||
assert_template_result "body body_detail", "{% include 'body' %}"
|
assert_equal "body body_detail",
|
||||||
|
Template.parse("{% include 'body' %}").render
|
||||||
|
|
||||||
assert_template_result "header body body_detail footer", "{% include 'nested_template' %}"
|
assert_equal "header body body_detail footer",
|
||||||
|
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_template_result "Product: Draft 151cm details Product: Element 155cm details ",
|
assert_equal "Product: Draft 151cm details ",
|
||||||
"{% include 'nested_product_template' for products %}", "products" => [{"title" => 'Draft 151cm'}, {"title" => 'Element 155cm'}]
|
Template.parse("{% include 'nested_product_template' with product %}").render("product" => {"title" => 'Draft 151cm'})
|
||||||
|
|
||||||
|
assert_equal "Product: Draft 151cm details Product: Element 155cm details ",
|
||||||
|
Template.parse("{% include 'nested_product_template' for products %}").render("products" => [{"title" => 'Draft 151cm'}, {"title" => 'Element 155cm'}])
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_recursively_included_template_does_not_produce_endless_loop
|
def test_recursively_included_template_does_not_produce_endless_loop
|
||||||
@@ -155,33 +160,34 @@ 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_template_result "Product: Draft 151cm ", "{% include template for product %}",
|
assert_equal "Test123", Template.parse("{% include template %}").render("template" => 'Test123')
|
||||||
"template" => 'product', 'product' => { 'title' => 'Draft 151cm'}
|
assert_equal "Test321", Template.parse("{% include template %}").render("template" => 'Test321')
|
||||||
|
|
||||||
|
assert_equal "Product: Draft 151cm ", Template.parse("{% include template for product %}").render("template" => 'product', 'product' => { 'title' => 'Draft 151cm'})
|
||||||
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_template_result "foo_if_true", "{% if true %}{% include 'foo_if_true' %}{% endif %}"
|
assert_equal "foo_if_true",
|
||||||
|
Template.parse("{% if true %}{% include 'foo_if_true' %}{% endif %}").render
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_custom_include_tag
|
def test_custom_include_tag
|
||||||
@@ -189,7 +195,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
|
||||||
@@ -200,7 +206,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
|
||||||
|
|||||||
@@ -20,6 +20,5 @@ 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
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ class StandardTagTest < Test::Unit::TestCase
|
|||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def test_tag
|
def test_tag
|
||||||
tag = Tag.parse('tag', [], [], {})
|
tag = Tag.new('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_template_result 'variable', '{% assign a = "variable"%}{{a}}'
|
assert_equal 'variable', Liquid::Template.parse( '{% assign a = "variable"%}{{a}}').render
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_assign_unassigned
|
def test_assign_unassigned
|
||||||
@@ -227,11 +227,12 @@ class StandardTagTest < Test::Unit::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_assign_an_empty_string
|
def test_assign_an_empty_string
|
||||||
assert_template_result '', '{% assign a = ""%}{{a}}'
|
assert_equal '', Liquid::Template.parse( '{% assign a = ""%}{{a}}' ).render
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_assign_is_global
|
def test_assign_is_global
|
||||||
assert_template_result 'variable', '{%for i in (1..2) %}{% assign a = "variable"%}{% endfor %}{{a}}'
|
assert_equal 'variable',
|
||||||
|
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
|
||||||
@@ -296,8 +297,4 @@ 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
|
||||||
|
|||||||
@@ -4,78 +4,93 @@ 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 %} |
|
||||||
assert_template_result ' true ', text
|
expected = %| true |
|
||||||
|
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 %} |
|
||||||
assert_template_result ' false ', text
|
expected = %| false |
|
||||||
|
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 %} |
|
||||||
assert_template_result ' false ', text
|
expected = %| false |
|
||||||
|
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 %} |
|
||||||
assert_template_result ' true ', text
|
expected = %| true |
|
||||||
|
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 %} |
|
||||||
assert_template_result ' true ', text
|
expected = %| true |
|
||||||
|
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 %} |
|
||||||
assert_template_result ' true ', text
|
expected = %| true |
|
||||||
|
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 %} |
|
||||||
assert_template_result ' false ', text
|
expected = %| false |
|
||||||
|
assert_equal expected, Template.parse(text).render
|
||||||
|
|
||||||
|
|
||||||
text = ' {% if 0 <= null %} true {% else %} false {% endif %} '
|
text = %| {% if 0 <= null %} true {% else %} false {% endif %} |
|
||||||
assert_template_result ' false ', text
|
expected = %| false |
|
||||||
|
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 %} |
|
||||||
assert_template_result ' true ', text
|
expected = %| true |
|
||||||
|
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 %} |
|
||||||
assert_template_result ' true ', text
|
expected = %| true |
|
||||||
|
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 %} |
|
||||||
assert_template_result ' false ', text
|
expected = %| false |
|
||||||
|
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 %} |
|
||||||
assert_template_result ' true ', text, 'var' => 'hello there!'
|
expected = %| true |
|
||||||
|
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 %} |
|
||||||
assert_template_result ' true ', text, 'var' => 'hello there!'
|
expected = %| true |
|
||||||
|
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 %} |
|
||||||
assert_template_result ' true ', text, 'var' => 'hello there!'
|
expected = %| true |
|
||||||
|
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 %} |
|
||||||
assert_template_result ' true ', text, 'var' => 'hello there!'
|
expected = %| true |
|
||||||
|
assert_equal expected, Template.parse(text).render('var' => 'hello there!')
|
||||||
end
|
end
|
||||||
|
|
||||||
#def test_is_nil
|
#def test_is_nil
|
||||||
@@ -86,28 +101,34 @@ 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 %} |
|
||||||
assert_template_result ' true ', text, 'array' => []
|
expected = %| true |
|
||||||
|
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 %} |
|
||||||
assert_template_result ' false ', text, 'array' => [1,2,3]
|
expected = %| false |
|
||||||
|
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 %} |
|
||||||
assert_template_result ' true ', text, 'var' => nil
|
expected = %| true |
|
||||||
|
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 %} |
|
||||||
assert_template_result ' true ', text, 'var' => nil
|
expected = %| true |
|
||||||
|
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 %} |
|
||||||
assert_template_result ' true ', text, 'var' => 1
|
expected = %| true |
|
||||||
|
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 %} |
|
||||||
assert_template_result ' true ', text, 'var' => 1
|
expected = %| true |
|
||||||
|
assert_equal expected, Template.parse(text).render('var' => 1 )
|
||||||
end
|
end
|
||||||
end # StatementsTest
|
end # StatementsTest
|
||||||
|
|||||||
@@ -27,57 +27,57 @@ class TemplateTest < Test::Unit::TestCase
|
|||||||
|
|
||||||
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
|
||||||
@@ -86,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
|
||||||
|
|
||||||
@@ -100,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
|
||||||
|
|
||||||
@@ -110,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
|
||||||
|
|
||||||
@@ -123,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
|
||||||
@@ -133,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
|
||||||
|
|||||||
@@ -21,8 +21,23 @@ class TokenizerTest < Test::Unit::TestCase
|
|||||||
assert_equal [' ', '{% comment %}', ' ', '{% endcomment %}', ' '], tokenize(" {% comment %} {% endcomment %} ")
|
assert_equal [' ', '{% comment %}', ' ', '{% endcomment %}', ' '], tokenize(" {% comment %} {% endcomment %} ")
|
||||||
end
|
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
|
private
|
||||||
|
|
||||||
|
def assert_tokens(source, expected)
|
||||||
|
assert_equal expected, tokenize(source)
|
||||||
|
assert_equal expected, old_tokenize(source)
|
||||||
|
end
|
||||||
|
|
||||||
def tokenize(source)
|
def tokenize(source)
|
||||||
tokenizer = Liquid::Tokenizer.new(source)
|
tokenizer = Liquid::Tokenizer.new(source)
|
||||||
tokens = []
|
tokens = []
|
||||||
@@ -31,4 +46,19 @@ class TokenizerTest < Test::Unit::TestCase
|
|||||||
end
|
end
|
||||||
tokens
|
tokens
|
||||||
end
|
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
|
end
|
||||||
|
|||||||
@@ -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,8 +197,4 @@ 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
|
||||||
|
|||||||
@@ -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 = {})
|
||||||
|
|||||||
Reference in New Issue
Block a user