mirror of
https://github.com/kemko/liquid.git
synced 2026-01-08 03:05:42 +03:00
Compare commits
9 Commits
liquid_c_e
...
v2.5.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e77b1a09b6 | ||
|
|
73b39beef2 | ||
|
|
fc63219087 | ||
|
|
53b6db48e3 | ||
|
|
0bbc22b027 | ||
|
|
145920738b | ||
|
|
6eb1f174de | ||
|
|
2e71ce1efe | ||
|
|
8204c61e31 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -4,8 +4,3 @@
|
|||||||
pkg
|
pkg
|
||||||
*.rbc
|
*.rbc
|
||||||
.rvmrc
|
.rvmrc
|
||||||
*.o
|
|
||||||
*.bundle
|
|
||||||
ext/liquid/Makefile
|
|
||||||
ext/liquid/liquid_context.c
|
|
||||||
|
|
||||||
|
|||||||
16
History.md
16
History.md
@@ -1,5 +1,21 @@
|
|||||||
# Liquid Version History
|
# Liquid Version History
|
||||||
|
|
||||||
|
## 2.5.4 / 2013-11-11 / branch "2.5-stable"
|
||||||
|
|
||||||
|
* Fix "can't convert Fixnum into String" for "replace", see #173, [wǒ_is神仙, jsw0528]
|
||||||
|
|
||||||
|
## 2.5.3 / 2013-10-09
|
||||||
|
|
||||||
|
* #232, #234, #237: Fix map filter bugs [Florian Weingarten, fw42]
|
||||||
|
|
||||||
|
## 2.5.2 / 2013-09-03 / deleted
|
||||||
|
|
||||||
|
Yanked from rubygems, as it contained too many changes that broke compatibility. Those changes will be on following major releases.
|
||||||
|
|
||||||
|
## 2.5.1 / 2013-07-24
|
||||||
|
|
||||||
|
* #230: Fix security issue with map filter, Use invoke_drop in map filter [Florian Weingarten, fw42]
|
||||||
|
|
||||||
## 2.5.0 / 2013-03-06
|
## 2.5.0 / 2013-03-06
|
||||||
|
|
||||||
* Prevent Object methods from being called on drops
|
* Prevent Object methods from being called on drops
|
||||||
|
|||||||
44
Rakefile
44
Rakefile
@@ -2,50 +2,12 @@
|
|||||||
|
|
||||||
require 'rubygems'
|
require 'rubygems'
|
||||||
require 'rake'
|
require 'rake'
|
||||||
require 'rake/clean'
|
|
||||||
require 'fileutils'
|
|
||||||
require 'rake/testtask'
|
require 'rake/testtask'
|
||||||
require 'rubygems/package_task'
|
require 'rubygems/package_task'
|
||||||
|
|
||||||
task :default => [:compile, :test]
|
task :default => 'test'
|
||||||
|
|
||||||
task :gen do
|
Rake::TestTask.new(:test) do |t|
|
||||||
sh "leg -oext/liquid/liquid_context.c ext/liquid/liquid_context.leg"
|
|
||||||
end
|
|
||||||
|
|
||||||
task :compile => [:gen, :liquid_ext]
|
|
||||||
|
|
||||||
extension = "liquid_ext"
|
|
||||||
ext = "ext/liquid"
|
|
||||||
ext_so = "#{ext}/#{extension}.#{RbConfig::CONFIG['DLEXT']}"
|
|
||||||
ext_files = FileList[
|
|
||||||
"#{ext}/*.c",
|
|
||||||
"#{ext}/*.h",
|
|
||||||
"#{ext}/*.leg",
|
|
||||||
"#{ext}/extconf.rb",
|
|
||||||
"#{ext}/Makefile",
|
|
||||||
"lib"
|
|
||||||
]
|
|
||||||
|
|
||||||
task "lib" do
|
|
||||||
directory "lib"
|
|
||||||
end
|
|
||||||
|
|
||||||
desc "Builds just the #{extension} extension"
|
|
||||||
task extension.to_sym => [:gen, "#{ext}/Makefile", ext_so ]
|
|
||||||
|
|
||||||
file "#{ext}/Makefile" => ["#{ext}/extconf.rb"] do
|
|
||||||
Dir.chdir(ext) do ruby "extconf.rb" end
|
|
||||||
end
|
|
||||||
|
|
||||||
file ext_so => ext_files do
|
|
||||||
Dir.chdir(ext) do
|
|
||||||
sh "make"
|
|
||||||
end
|
|
||||||
cp ext_so, "lib"
|
|
||||||
end
|
|
||||||
|
|
||||||
Rake::TestTask.new(:test => [:gen, 'liquid_ext']) do |t|
|
|
||||||
t.libs << '.' << 'lib' << 'test'
|
t.libs << '.' << 'lib' << 'test'
|
||||||
t.test_files = FileList['test/liquid/**/*_test.rb']
|
t.test_files = FileList['test/liquid/**/*_test.rb']
|
||||||
t.verbose = false
|
t.verbose = false
|
||||||
@@ -80,7 +42,7 @@ namespace :profile do
|
|||||||
|
|
||||||
desc "Run KCacheGrind"
|
desc "Run KCacheGrind"
|
||||||
task :grind => :run do
|
task :grind => :run do
|
||||||
system "qcachegrind /tmp//callgrind.liquid.txt"
|
system "qcachegrind /tmp/liquid.rubyprof_calltreeprinter.txt"
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
require 'mkmf'
|
|
||||||
|
|
||||||
dir_config("liquid_ext")
|
|
||||||
have_library("c", "main")
|
|
||||||
|
|
||||||
create_makefile("liquid_ext")
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
|
|
||||||
%{
|
|
||||||
//uncomment to get more debug instrumentation
|
|
||||||
//
|
|
||||||
//#define YY_DEBUG
|
|
||||||
|
|
||||||
#include <ruby.h>
|
|
||||||
|
|
||||||
#define EMIT(sym, data) \
|
|
||||||
rb_ary_push(ctx->rb_tokens, rb_ary_new3(2, ID2SYM(rb_intern(sym)), data));
|
|
||||||
|
|
||||||
#define yy_rb_str rb_str_new(yytext, yyleng)
|
|
||||||
|
|
||||||
#define YYSTYPE VALUE
|
|
||||||
#define YY_CTX_LOCAL
|
|
||||||
#define YY_CTX_MEMBERS VALUE rb_tokens; char *p; int p_len;
|
|
||||||
|
|
||||||
#define YY_INPUT(buf, result, max_size) { \
|
|
||||||
result = ctx->p_len; \
|
|
||||||
if (result>0 || EOF == ctx->p[0]) { \
|
|
||||||
if (max_size < result) { result = max_size; } \
|
|
||||||
strncpy(buf, ctx->p, result); \
|
|
||||||
buf[result] = '\0'; \
|
|
||||||
yyprintf((stderr, "\nREFILLING %d chars now:<%s>", result, buf)); \
|
|
||||||
ctx->p += result; ctx->p_len -= result; \
|
|
||||||
yyprintf((stderr, "\nREFILLING DONE size left: %d <%s>", ctx->p_len, ctx->p)); \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
%}
|
|
||||||
|
|
||||||
grammar = primary
|
|
||||||
| entity
|
|
||||||
| range
|
|
||||||
| hash { EMIT("lookup", Qnil); }
|
|
||||||
;
|
|
||||||
|
|
||||||
hash = '[' (primary|entity) ']';
|
|
||||||
|
|
||||||
primary = var:const { EMIT("id", var); }
|
|
||||||
| var:string { EMIT("id", var); }
|
|
||||||
| var:numeric { EMIT("id", var); }
|
|
||||||
;
|
|
||||||
|
|
||||||
accessors = hash { EMIT("call", Qnil); }
|
|
||||||
| '.first' { EMIT("buildin", rb_str_new2("first")); }
|
|
||||||
| '.last' { EMIT("buildin", rb_str_new2("last")); }
|
|
||||||
| '.size' { EMIT("buildin", rb_str_new2("size")); }
|
|
||||||
| '.' <identifier> { EMIT("id", yy_rb_str); EMIT("call", Qnil); }
|
|
||||||
;
|
|
||||||
|
|
||||||
entity = <identifier> { EMIT("id", yy_rb_str); EMIT("lookup", Qnil); }
|
|
||||||
accessors*
|
|
||||||
;
|
|
||||||
|
|
||||||
rangelet = var:integer { EMIT("id", var); }
|
|
||||||
| entity
|
|
||||||
;
|
|
||||||
|
|
||||||
range = '(' rangelet '..' rangelet ')' { EMIT("range", Qnil); }
|
|
||||||
|
|
||||||
|
|
||||||
string = ['] < ( !['] . )* > ['] { $$ = yy_rb_str; }
|
|
||||||
| ["] < ( !["] . )* > ["] { $$ = yy_rb_str; }
|
|
||||||
;
|
|
||||||
|
|
||||||
numeric = float
|
|
||||||
| integer
|
|
||||||
;
|
|
||||||
|
|
||||||
float = <'-'? digit+'.'digit+> { $$ = rb_funcall(rb_cObject, rb_intern("Float"), 1, yy_rb_str); }
|
|
||||||
integer = <'-'? digit+> { $$ = rb_funcall(rb_cObject, rb_intern("Integer"), 1, yy_rb_str); }
|
|
||||||
|
|
||||||
|
|
||||||
const = "true" { $$ = Qtrue; }
|
|
||||||
| 'false' { $$ = Qfalse; }
|
|
||||||
| 'nil' { $$ = Qnil; }
|
|
||||||
| 'null' { $$ = Qnil; }
|
|
||||||
;
|
|
||||||
|
|
||||||
digit = [0-9];
|
|
||||||
identifier = [a-zA-Z][a-zA-Z0-9_\-]*[?!]?;
|
|
||||||
|
|
||||||
%%
|
|
||||||
|
|
||||||
VALUE liquid_context_parse_impl(VALUE self, VALUE text) {
|
|
||||||
char *p;
|
|
||||||
int len;
|
|
||||||
yycontext ctx;
|
|
||||||
|
|
||||||
memset(&ctx, 0, sizeof(yycontext));
|
|
||||||
ctx.p = RSTRING_PTR(text);
|
|
||||||
ctx.p_len = (int) RSTRING_LEN(text);
|
|
||||||
ctx.rb_tokens = rb_ary_new();
|
|
||||||
|
|
||||||
yyparse(&ctx);
|
|
||||||
|
|
||||||
return ctx.rb_tokens;
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
#include <ruby.h>
|
|
||||||
|
|
||||||
static VALUE rb_Liquid;
|
|
||||||
static VALUE rb_Parser;
|
|
||||||
|
|
||||||
VALUE liquid_context_parse_impl(VALUE text);
|
|
||||||
|
|
||||||
void Init_liquid_ext()
|
|
||||||
{
|
|
||||||
rb_Liquid = rb_define_module("Liquid");
|
|
||||||
rb_Parser = rb_define_class_under(rb_Liquid, "Parser", rb_cObject);
|
|
||||||
rb_define_singleton_method(rb_Parser, "parse", liquid_context_parse_impl, 1);
|
|
||||||
}
|
|
||||||
@@ -62,7 +62,6 @@ require 'liquid/standardfilters'
|
|||||||
require 'liquid/condition'
|
require 'liquid/condition'
|
||||||
require 'liquid/module_ex'
|
require 'liquid/module_ex'
|
||||||
require 'liquid/utils'
|
require 'liquid/utils'
|
||||||
require 'liquid_ext'
|
|
||||||
|
|
||||||
# Load all the tags of the standard library
|
# Load all the tags of the standard library
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
output
|
output.join
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
|
|
||||||
|
|
||||||
# Context keeps the variable stack and resolves variables, as well as keywords
|
# Context keeps the variable stack and resolves variables, as well as keywords
|
||||||
#
|
#
|
||||||
# context['variable'] = 'testing'
|
# context['variable'] = 'testing'
|
||||||
@@ -112,89 +111,54 @@ module Liquid
|
|||||||
@scopes[0] = {}
|
@scopes[0] = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
# Look up variable, either resolve directly after considering the name. We can directly handle
|
|
||||||
# Strings, digits, floats and booleans (true,false).
|
|
||||||
# If no match is made we lookup the variable in the current scope and
|
|
||||||
# later move up to the parent blocks to see if we can resolve the variable somewhere up the tree.
|
|
||||||
# Some special keywords return symbols. Those symbols are to be called on the rhs object in expressions
|
|
||||||
#
|
|
||||||
# Example:
|
|
||||||
# products == empty #=> products.empty?
|
|
||||||
def resolve(key)
|
|
||||||
case key
|
|
||||||
when nil, ""
|
|
||||||
return nil
|
|
||||||
when "blank"
|
|
||||||
return :blank?
|
|
||||||
when "empty"
|
|
||||||
return :empty?
|
|
||||||
end
|
|
||||||
|
|
||||||
result = Parser.parse(key)
|
|
||||||
stack = []
|
|
||||||
|
|
||||||
result.each do |(sym, value)|
|
|
||||||
|
|
||||||
case sym
|
|
||||||
when :id
|
|
||||||
stack.push value
|
|
||||||
when :lookup
|
|
||||||
left = stack.pop
|
|
||||||
value = find_variable(left)
|
|
||||||
|
|
||||||
stack.push(harden(value))
|
|
||||||
when :range
|
|
||||||
right = stack.pop.to_i
|
|
||||||
left = stack.pop.to_i
|
|
||||||
|
|
||||||
stack.push (left..right)
|
|
||||||
when :buildin
|
|
||||||
left = stack.pop
|
|
||||||
value = invoke_buildin(left, value)
|
|
||||||
|
|
||||||
stack.push(harden(value))
|
|
||||||
when :call
|
|
||||||
left = stack.pop
|
|
||||||
right = stack.pop
|
|
||||||
value = lookup_and_evaluate(right, left)
|
|
||||||
|
|
||||||
stack.push(harden(value))
|
|
||||||
else
|
|
||||||
raise "unknown #{sym}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return stack.first
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
# Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
|
# Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
|
||||||
def []=(key, value)
|
def []=(key, value)
|
||||||
@scopes[0][key] = value
|
@scopes[0][key] = value
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def [](key)
|
||||||
|
resolve(key)
|
||||||
|
end
|
||||||
|
|
||||||
def has_key?(key)
|
def has_key?(key)
|
||||||
resolve(key) != nil
|
resolve(key) != nil
|
||||||
end
|
end
|
||||||
|
|
||||||
alias_method :[], :resolve
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
LITERALS = {
|
||||||
|
nil => nil, 'nil' => nil, 'null' => nil, '' => nil,
|
||||||
|
'true' => true,
|
||||||
|
'false' => false,
|
||||||
|
'blank' => :blank?,
|
||||||
|
'empty' => :empty?
|
||||||
|
}
|
||||||
|
|
||||||
def invoke_buildin(obj, key)
|
# Look up variable, either resolve directly after considering the name. We can directly handle
|
||||||
# as weird as this is, liquid unit tests demand that we prioritize hash lookups
|
# Strings, digits, floats and booleans (true,false).
|
||||||
# to buildins. So if we got a hash and it has a :first element we need to call that
|
# If no match is made we lookup the variable in the current scope and
|
||||||
# instead of sending the first message...
|
# later move up to the parent blocks to see if we can resolve the variable somewhere up the tree.
|
||||||
|
# Some special keywords return symbols. Those symbols are to be called on the rhs object in expressions
|
||||||
if obj.respond_to?(:has_key?) && obj.has_key?(key)
|
#
|
||||||
return lookup_and_evaluate(obj, key)
|
# Example:
|
||||||
end
|
# products == empty #=> products.empty?
|
||||||
|
def resolve(key)
|
||||||
if obj.respond_to?(key)
|
if LITERALS.key?(key)
|
||||||
return obj.send(key)
|
LITERALS[key]
|
||||||
else
|
else
|
||||||
return nil
|
case key
|
||||||
|
when /^'(.*)'$/ # Single quoted strings
|
||||||
|
$1
|
||||||
|
when /^"(.*)"$/ # Double quoted strings
|
||||||
|
$1
|
||||||
|
when /^(-?\d+)$/ # Integer and floats
|
||||||
|
$1.to_i
|
||||||
|
when /^\((\S+)\.\.(\S+)\)$/ # Ranges
|
||||||
|
(resolve($1).to_i..resolve($2).to_i)
|
||||||
|
when /^(-?\d[\d\.]+)$/ # Floats
|
||||||
|
$1.to_f
|
||||||
|
else
|
||||||
|
variable(key)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -214,34 +178,71 @@ module Liquid
|
|||||||
scope ||= @environments.last || @scopes.last
|
scope ||= @environments.last || @scopes.last
|
||||||
variable ||= lookup_and_evaluate(scope, key)
|
variable ||= lookup_and_evaluate(scope, key)
|
||||||
|
|
||||||
|
variable = variable.to_liquid
|
||||||
|
variable.context = self if variable.respond_to?(:context=)
|
||||||
|
|
||||||
return variable
|
return variable
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Resolves namespaced queries gracefully.
|
||||||
|
#
|
||||||
|
# Example
|
||||||
|
# @context['hash'] = {"name" => 'tobi'}
|
||||||
|
# assert_equal 'tobi', @context['hash.name']
|
||||||
|
# assert_equal 'tobi', @context['hash["name"]']
|
||||||
|
def variable(markup)
|
||||||
|
parts = markup.scan(VariableParser)
|
||||||
|
square_bracketed = /^\[(.*)\]$/
|
||||||
|
|
||||||
|
first_part = parts.shift
|
||||||
|
|
||||||
|
if first_part =~ square_bracketed
|
||||||
|
first_part = resolve($1)
|
||||||
|
end
|
||||||
|
|
||||||
|
if object = find_variable(first_part)
|
||||||
|
|
||||||
|
parts.each do |part|
|
||||||
|
part = resolve($1) if part_resolved = (part =~ square_bracketed)
|
||||||
|
|
||||||
|
# If object is a hash- or array-like object we look for the
|
||||||
|
# presence of the key and if its available we return it
|
||||||
|
if object.respond_to?(:[]) and
|
||||||
|
((object.respond_to?(:has_key?) and object.has_key?(part)) or
|
||||||
|
(object.respond_to?(:fetch) and part.is_a?(Integer)))
|
||||||
|
|
||||||
|
# if its a proc we will replace the entry with the proc
|
||||||
|
res = lookup_and_evaluate(object, part)
|
||||||
|
object = res.to_liquid
|
||||||
|
|
||||||
|
# Some special cases. If the part wasn't in square brackets and
|
||||||
|
# no key with the same name was found we interpret following calls
|
||||||
|
# as commands and call them on the current object
|
||||||
|
elsif !part_resolved and object.respond_to?(part) and ['size', 'first', 'last'].include?(part)
|
||||||
|
|
||||||
|
object = object.send(part.intern).to_liquid
|
||||||
|
|
||||||
|
# No key was present with the desired value and it wasn't one of the directly supported
|
||||||
|
# keywords either. The only thing we got left is to return nil
|
||||||
|
else
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
# If we are dealing with a drop here we have to
|
||||||
|
object.context = self if object.respond_to?(:context=)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
object
|
||||||
|
end # variable
|
||||||
|
|
||||||
def lookup_and_evaluate(obj, key)
|
def lookup_and_evaluate(obj, key)
|
||||||
return nil unless obj.respond_to?(:[])
|
if (value = obj[key]).is_a?(Proc) && obj.respond_to?(:[]=)
|
||||||
|
obj[key] = (value.arity == 0) ? value.call : value.call(self)
|
||||||
if obj.is_a?(Array)
|
else
|
||||||
return nil unless key.is_a?(Integer)
|
value
|
||||||
end
|
end
|
||||||
|
end # lookup_and_evaluate
|
||||||
value = obj[key]
|
|
||||||
|
|
||||||
if value.is_a?(Proc)
|
|
||||||
# call the proc
|
|
||||||
value = (value.arity == 0) ? value.call : value.call(self)
|
|
||||||
|
|
||||||
# memozie if possible
|
|
||||||
obj[key] = value if obj.respond_to?(:[]=)
|
|
||||||
end
|
|
||||||
|
|
||||||
value
|
|
||||||
end
|
|
||||||
|
|
||||||
def harden(value)
|
|
||||||
value = value.to_liquid
|
|
||||||
value.context = self if value.respond_to?(:context=)
|
|
||||||
return value
|
|
||||||
end
|
|
||||||
|
|
||||||
def squash_instance_assigns_with_environments
|
def squash_instance_assigns_with_environments
|
||||||
@scopes.last.each_key do |k|
|
@scopes.last.each_key do |k|
|
||||||
@@ -253,7 +254,6 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end # squash_instance_assigns_with_environments
|
end # squash_instance_assigns_with_environments
|
||||||
|
|
||||||
end # Context
|
end # Context
|
||||||
|
|
||||||
end # Liquid
|
end # Liquid
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ module Liquid
|
|||||||
|
|
||||||
# Check for method existence without invoking respond_to?, which creates symbols
|
# Check for method existence without invoking respond_to?, which creates symbols
|
||||||
def self.invokable?(method_name)
|
def self.invokable?(method_name)
|
||||||
@invokable_methods ||= Set.new((public_instance_methods - Liquid::Drop.public_instance_methods).map(&:to_s))
|
@invokable_methods ||= Set.new(["to_liquid"] + (public_instance_methods - Liquid::Drop.public_instance_methods).map(&:to_s))
|
||||||
@invokable_methods.include?(method_name.to_s)
|
@invokable_methods.include?(method_name.to_s)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ module Liquid
|
|||||||
if markup =~ Syntax
|
if markup =~ Syntax
|
||||||
@variable_name = $1
|
@variable_name = $1
|
||||||
@collection_name = $2
|
@collection_name = $2
|
||||||
@idx_i = "#{$1}-#{$2}-i"
|
|
||||||
@idx_col = "#{$1}-#{$2}-c"
|
|
||||||
@attributes = {}
|
@attributes = {}
|
||||||
markup.scan(TagAttributes) do |key, value|
|
markup.scan(TagAttributes) do |key, value|
|
||||||
@attributes[key] = value
|
@attributes[key] = value
|
||||||
@@ -20,8 +18,6 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
context.registers[:tablerowloop] ||= Hash.new(0)
|
|
||||||
|
|
||||||
collection = context[@collection_name] or return ''
|
collection = context[@collection_name] or return ''
|
||||||
|
|
||||||
from = @attributes['offset'] ? context[@attributes['offset']].to_i : 0
|
from = @attributes['offset'] ? context[@attributes['offset']].to_i : 0
|
||||||
@@ -36,15 +32,26 @@ module Liquid
|
|||||||
row = 1
|
row = 1
|
||||||
col = 0
|
col = 0
|
||||||
|
|
||||||
result = ["<tr class=\"row1\">\n"]
|
result = "<tr class=\"row1\">\n"
|
||||||
context.stack do
|
context.stack do
|
||||||
|
|
||||||
context.registers[:tablerowloop][@idx]
|
|
||||||
context['tablerowloop'] = lambda { Tablerowloop.new(@idx_i, @idx_col, length) }
|
|
||||||
collection.each_with_index do |item, index|
|
collection.each_with_index do |item, index|
|
||||||
context.registers[:tablerowloop][@idx_i] = index
|
context[@variable_name] = item
|
||||||
context.registers[:tablerowloop][@idx_col] = col
|
context['tablerowloop'] = {
|
||||||
context[@variable_name] = item
|
'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
|
col += 1
|
||||||
|
|
||||||
@@ -61,58 +68,6 @@ module Liquid
|
|||||||
result << "</tr>\n"
|
result << "</tr>\n"
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
class Tablerowloop < Liquid::Drop
|
|
||||||
attr_accessor :length
|
|
||||||
|
|
||||||
def initialize(idx_i, idx_col, length)
|
|
||||||
@idx_i, @idx_col, @length = idx_i, idx_col, length
|
|
||||||
end
|
|
||||||
|
|
||||||
def index
|
|
||||||
@context.registers[:tablerowloop][@idx_i] + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
def index0
|
|
||||||
@context.registers[:tablerowloop][@idx_i]
|
|
||||||
end
|
|
||||||
|
|
||||||
def rindex
|
|
||||||
length - @context.registers[:tablerowloop][@idx_i]
|
|
||||||
end
|
|
||||||
|
|
||||||
def rindex0
|
|
||||||
length - @context.registers[:tablerowloop][@idx_i] - 1
|
|
||||||
end
|
|
||||||
|
|
||||||
def first
|
|
||||||
(@context.registers[:tablerowloop][@idx_i] == 0)
|
|
||||||
end
|
|
||||||
|
|
||||||
def last
|
|
||||||
(@context.registers[:tablerowloop][@idx_i] == length - 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
def col
|
|
||||||
@context.registers[:tablerowloop][@idx_col] + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
def col0
|
|
||||||
@context.registers[:tablerowloop][@idx_col]
|
|
||||||
end
|
|
||||||
|
|
||||||
def col_first
|
|
||||||
(@context.registers[:tablerowloop][@idx_col] == 0)
|
|
||||||
end
|
|
||||||
|
|
||||||
def col_last
|
|
||||||
(@context.registers[:tablerowloop][@idx_col] == cols - 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('tablerow', TableRow)
|
Template.register_tag('tablerow', TableRow)
|
||||||
|
|||||||
@@ -93,21 +93,26 @@ module Liquid
|
|||||||
# map/collect on a given property
|
# map/collect on a given property
|
||||||
def map(input, property)
|
def map(input, property)
|
||||||
ary = [input].flatten
|
ary = [input].flatten
|
||||||
if ary.first.respond_to?('[]') and !ary.first[property].nil?
|
ary.map do |e|
|
||||||
ary.map {|e| e[property] }
|
e = e.call if e.is_a?(Proc)
|
||||||
elsif ary.first.respond_to?(property)
|
e = e.to_liquid if e.respond_to?(:to_liquid)
|
||||||
ary.map {|e| e.send(property) }
|
|
||||||
|
if property == "to_liquid"
|
||||||
|
e
|
||||||
|
elsif e.respond_to?(:[])
|
||||||
|
e[property]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Replace occurrences of a string with another
|
# Replace occurrences of a string with another
|
||||||
def replace(input, string, replacement = '')
|
def replace(input, string, replacement = '')
|
||||||
input.to_s.gsub(string, replacement)
|
input.to_s.gsub(string, replacement.to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Replace the first occurrences of a string with another
|
# Replace the first occurrences of a string with another
|
||||||
def replace_first(input, string, replacement = '')
|
def replace_first(input, string, replacement = '')
|
||||||
input.to_s.sub(string, replacement)
|
input.to_s.sub(string, replacement.to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
# remove a substring
|
# remove a substring
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ module Liquid
|
|||||||
context.stack do
|
context.stack do
|
||||||
execute_else_block = true
|
execute_else_block = true
|
||||||
|
|
||||||
output = []
|
output = ''
|
||||||
@blocks.each do |block|
|
@blocks.each do |block|
|
||||||
if block.else?
|
if block.else?
|
||||||
return render_all(block.attachment, context) if execute_else_block
|
return render_all(block.attachment, context) if execute_else_block
|
||||||
|
|||||||
@@ -50,8 +50,7 @@ module Liquid
|
|||||||
if markup =~ Syntax
|
if markup =~ Syntax
|
||||||
@variable_name = $1
|
@variable_name = $1
|
||||||
@collection_name = $2
|
@collection_name = $2
|
||||||
@name = "#{$1}-#{$2}"
|
@name = "#{$1}-#{$2}"
|
||||||
@idx = "#{@name}-i"
|
|
||||||
@reversed = $3
|
@reversed = $3
|
||||||
@attributes = {}
|
@attributes = {}
|
||||||
markup.scan(TagAttributes) do |key, value|
|
markup.scan(TagAttributes) do |key, value|
|
||||||
@@ -88,13 +87,14 @@ module Liquid
|
|||||||
limit = context[@attributes['limit']]
|
limit = context[@attributes['limit']]
|
||||||
to = limit ? limit.to_i + from : nil
|
to = limit ? limit.to_i + from : nil
|
||||||
|
|
||||||
|
|
||||||
segment = Utils.slice_collection_using_each(collection, from, to)
|
segment = Utils.slice_collection_using_each(collection, from, to)
|
||||||
|
|
||||||
return render_else(context) if segment.empty?
|
return render_else(context) if segment.empty?
|
||||||
|
|
||||||
segment.reverse! if @reversed
|
segment.reverse! if @reversed
|
||||||
|
|
||||||
result = []
|
result = ''
|
||||||
|
|
||||||
length = segment.length
|
length = segment.length
|
||||||
|
|
||||||
@@ -102,10 +102,17 @@ module Liquid
|
|||||||
context.registers[:for][@name] = from + segment.length
|
context.registers[:for][@name] = from + segment.length
|
||||||
|
|
||||||
context.stack do
|
context.stack do
|
||||||
context['forloop'] = lambda { Forloop.new(@name, @idx, length) }
|
|
||||||
segment.each_with_index do |item, index|
|
segment.each_with_index do |item, index|
|
||||||
context.registers[:for][@idx] = index
|
|
||||||
context[@variable_name] = item
|
context[@variable_name] = item
|
||||||
|
context['forloop'] = {
|
||||||
|
'name' => @name,
|
||||||
|
'length' => length,
|
||||||
|
'index' => index + 1,
|
||||||
|
'index0' => index,
|
||||||
|
'rindex' => length - index,
|
||||||
|
'rindex0' => length - index - 1,
|
||||||
|
'first' => (index == 0),
|
||||||
|
'last' => (index == length - 1) }
|
||||||
|
|
||||||
result << render_all(@for_block, context)
|
result << render_all(@for_block, context)
|
||||||
|
|
||||||
@@ -122,39 +129,6 @@ module Liquid
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
class Forloop < Liquid::Drop
|
|
||||||
attr_accessor :name, :length
|
|
||||||
|
|
||||||
def initialize(name, idx, length)
|
|
||||||
@name, @idx, @length = name, idx, length
|
|
||||||
end
|
|
||||||
|
|
||||||
def index
|
|
||||||
@context.registers[:for][@idx] + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
def index0
|
|
||||||
@context.registers[:for][@idx]
|
|
||||||
end
|
|
||||||
|
|
||||||
def rindex
|
|
||||||
length - @context.registers[:for][@idx]
|
|
||||||
end
|
|
||||||
|
|
||||||
def rindex0
|
|
||||||
length - @context.registers[:for][@idx] - 1
|
|
||||||
end
|
|
||||||
|
|
||||||
def first
|
|
||||||
(@context.registers[:for][@idx] == 0)
|
|
||||||
end
|
|
||||||
|
|
||||||
def last
|
|
||||||
(@context.registers[:for][@idx] == length - 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
def render_else(context)
|
def render_else(context)
|
||||||
return @else_block ? [render_all(@else_block, context)] : ''
|
return @else_block ? [render_all(@else_block, context)] : ''
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -121,7 +121,8 @@ module Liquid
|
|||||||
begin
|
begin
|
||||||
# render the nodelist.
|
# render the nodelist.
|
||||||
# for performance reasons we get a array back here. join will make a string out of it
|
# for performance reasons we get a array back here. join will make a string out of it
|
||||||
@root.render(context).join
|
result = @root.render(context)
|
||||||
|
result.respond_to?(:join) ? result.join : result
|
||||||
ensure
|
ensure
|
||||||
@errors = context.errors
|
@errors = context.errors
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Gem::Specification.new do |s|
|
Gem::Specification.new do |s|
|
||||||
s.name = "liquid"
|
s.name = "liquid"
|
||||||
s.version = "2.5.0"
|
s.version = "2.5.4"
|
||||||
s.platform = Gem::Platform::RUBY
|
s.platform = Gem::Platform::RUBY
|
||||||
s.summary = "A secure, non-evaling end user template engine with aesthetic markup."
|
s.summary = "A secure, non-evaling end user template engine with aesthetic markup."
|
||||||
s.authors = ["Tobias Luetke"]
|
s.authors = ["Tobias Luetke"]
|
||||||
@@ -13,12 +13,9 @@ Gem::Specification.new do |s|
|
|||||||
s.required_rubygems_version = ">= 1.3.7"
|
s.required_rubygems_version = ">= 1.3.7"
|
||||||
|
|
||||||
s.test_files = Dir.glob("{test}/**/*")
|
s.test_files = Dir.glob("{test}/**/*")
|
||||||
s.files = Dir.glob("{lib}/**/*") +
|
s.files = Dir.glob("{lib}/**/*") + %w(MIT-LICENSE README.md)
|
||||||
Dir.glob("{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_paths = ["lib", "ext"]
|
s.require_path = "lib"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ require File.dirname(__FILE__) + '/theme_runner'
|
|||||||
|
|
||||||
profiler = ThemeRunner.new
|
profiler = ThemeRunner.new
|
||||||
|
|
||||||
Benchmark.bm do |x|
|
Benchmark.bmbm do |x|
|
||||||
# x.report("parse:") { 100.times { profiler.compile } }
|
x.report("parse:") { 100.times { profiler.compile } }
|
||||||
x.report("parse & run:") { 100.times { profiler.run } }
|
x.report("parse & run:") { 100.times { profiler.run } }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,14 @@ profiler = ThemeRunner.new
|
|||||||
|
|
||||||
puts 'Running profiler...'
|
puts 'Running profiler...'
|
||||||
|
|
||||||
results = profiler.run_profile
|
results = profiler.run
|
||||||
|
|
||||||
puts 'Success'
|
puts 'Success'
|
||||||
|
puts
|
||||||
|
|
||||||
filename = (ENV['TMP'] || '/tmp') + "/callgrind.liquid.txt"
|
[RubyProf::FlatPrinter, RubyProf::GraphPrinter, RubyProf::GraphHtmlPrinter, RubyProf::CallTreePrinter].each do |klass|
|
||||||
File.open(filename, "w+") do |fp|
|
filename = (ENV['TMP'] || '/tmp') + (klass.name.include?('Html') ? "/liquid.#{klass.name.downcase}.html" : "/callgrind.liquid.#{klass.name.downcase}.txt")
|
||||||
RubyProf::CallTreePrinter.new(results).print(fp, :print_file => true)
|
filename.gsub!(/:+/, '_')
|
||||||
|
File.open(filename, "w+") { |fp| klass.new(results).print(fp, :print_file => true) }
|
||||||
|
$stderr.puts "wrote #{klass.name} output to #{filename}"
|
||||||
end
|
end
|
||||||
$stderr.puts "wrote RubyProf::CallTreePrinter output to #{filename}"
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ class ThemeRunner
|
|||||||
# Dup assigns because will make some changes to them
|
# Dup assigns because will make some changes to them
|
||||||
|
|
||||||
@tests.each do |liquid, layout, template_name|
|
@tests.each do |liquid, layout, template_name|
|
||||||
|
|
||||||
tmpl = Liquid::Template.new
|
tmpl = Liquid::Template.new
|
||||||
tmpl.parse(liquid)
|
tmpl.parse(liquid)
|
||||||
tmpl = Liquid::Template.new
|
tmpl = Liquid::Template.new
|
||||||
@@ -53,7 +54,7 @@ class ThemeRunner
|
|||||||
|
|
||||||
|
|
||||||
def run_profile
|
def run_profile
|
||||||
RubyProf.measure_mode = RubyProf::PROCESS_TIME
|
RubyProf.measure_mode = RubyProf::WALL_TIME
|
||||||
|
|
||||||
# Dup assigns because will make some changes to them
|
# Dup assigns because will make some changes to them
|
||||||
assigns = Database.tables.dup
|
assigns = Database.tables.dup
|
||||||
|
|||||||
@@ -254,16 +254,12 @@ class ContextTest < Test::Unit::TestCase
|
|||||||
@context['test'] = {'test' => [1,2,3,4,5]}
|
@context['test'] = {'test' => [1,2,3,4,5]}
|
||||||
|
|
||||||
assert_equal 1, @context['test.test[0]']
|
assert_equal 1, @context['test.test[0]']
|
||||||
end
|
|
||||||
|
|
||||||
def test_recoursive_array_notation_for_hash
|
|
||||||
@context['test'] = [{'test' => 'worked'}]
|
@context['test'] = [{'test' => 'worked'}]
|
||||||
|
|
||||||
assert_equal 'worked', @context['test[0].test']
|
assert_equal 'worked', @context['test[0].test']
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def test_hash_to_array_transition
|
def test_hash_to_array_transition
|
||||||
@context['colors'] = {
|
@context['colors'] = {
|
||||||
'Blue' => ['003366','336699', '6699CC', '99CCFF'],
|
'Blue' => ['003366','336699', '6699CC', '99CCFF'],
|
||||||
@@ -319,7 +315,7 @@ class ContextTest < Test::Unit::TestCase
|
|||||||
@context['nested'] = {'var' => 'tags'}
|
@context['nested'] = {'var' => 'tags'}
|
||||||
@context['products'] = {'count' => 5, 'tags' => ['deepsnow', 'freestyle'] }
|
@context['products'] = {'count' => 5, 'tags' => ['deepsnow', 'freestyle'] }
|
||||||
|
|
||||||
#assert_equal 'deepsnow', @context['products[var].first']
|
assert_equal 'deepsnow', @context['products[var].first']
|
||||||
assert_equal 'freestyle', @context['products[nested.var].last']
|
assert_equal 'freestyle', @context['products[nested.var].last']
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -71,23 +71,34 @@ class DropsTest < Test::Unit::TestCase
|
|||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
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
|
||||||
|
assert_equal "", Liquid::Template.parse("{{ product.inspect }}").render('product' => ProductDrop.new)
|
||||||
|
assert_equal "", Liquid::Template.parse("{{ product.pretty_inspect }}").render('product' => ProductDrop.new)
|
||||||
|
assert_equal "", Liquid::Template.parse("{{ product.whatever }}").render('product' => ProductDrop.new)
|
||||||
|
assert_equal "", Liquid::Template.parse('{{ product | map: "inspect" }}').render('product' => ProductDrop.new)
|
||||||
|
assert_equal "", Liquid::Template.parse('{{ product | map: "pretty_inspect" }}').render('product' => ProductDrop.new)
|
||||||
|
assert_equal "", Liquid::Template.parse('{{ product | map: "whatever" }}').render('product' => ProductDrop.new)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_drops_respond_to_to_liquid
|
||||||
|
assert_equal "text1", Liquid::Template.parse("{{ product.to_liquid.texts.text }}").render('product' => ProductDrop.new)
|
||||||
|
assert_equal "text1", Liquid::Template.parse('{{ product | map: "to_liquid" | map: "texts" | map: "text" }}').render('product' => ProductDrop.new)
|
||||||
|
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
|
||||||
|
|||||||
@@ -1,93 +0,0 @@
|
|||||||
require 'test_helper'
|
|
||||||
|
|
||||||
|
|
||||||
class ParserTest < Test::Unit::TestCase
|
|
||||||
include Liquid
|
|
||||||
|
|
||||||
|
|
||||||
def test_strings
|
|
||||||
assert_equal [[:id, "string"]], Parser.parse('"string"')
|
|
||||||
assert_equal [[:id, "string"]], Parser.parse('\'string\'')
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_integer
|
|
||||||
assert_equal [[:id, 1]], Parser.parse('1')
|
|
||||||
assert_equal [[:id, 100001]], Parser.parse('100001')
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_float
|
|
||||||
assert_equal [[:id, 1.1]], Parser.parse('1.1')
|
|
||||||
assert_equal [[:id, 1.55435]], Parser.parse('1.55435')
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_null
|
|
||||||
assert_equal [[:id, nil]], Parser.parse('null')
|
|
||||||
assert_equal [[:id, nil]], Parser.parse('nil')
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_bool
|
|
||||||
assert_equal [[:id, true]], Parser.parse('true')
|
|
||||||
assert_equal [[:id, false]], Parser.parse('false')
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_ranges
|
|
||||||
assert_equal [[:id, 1], [:id, 5], [:range, nil]], Parser.parse('(1..5)')
|
|
||||||
assert_equal [[:id, 100], [:id, 500], [:range, nil]], Parser.parse('(100..500)')
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_ranges_with_lookups
|
|
||||||
assert_equal [[:id, 1], [:id, "test"], [:lookup, nil], [:range, nil]], Parser.parse('(1..test)')
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_lookups
|
|
||||||
assert_equal [[:id, "variable"], [:lookup, nil]], Parser.parse('variable')
|
|
||||||
assert_equal [[:id, "underscored_variable"], [:lookup, nil]], Parser.parse('underscored_variable')
|
|
||||||
assert_equal [[:id, "c"], [:lookup, nil]], Parser.parse('c')
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_global_hash
|
|
||||||
assert_equal [[:id, true], [:lookup, nil]], Parser.parse('[true]')
|
|
||||||
|
|
||||||
assert_equal [[:id, "string"], [:lookup, nil]], Parser.parse('["string"]')
|
|
||||||
assert_equal [[:id, 5.55], [:lookup, nil]], Parser.parse('[5.55]')
|
|
||||||
assert_equal [[:id, 0], [:lookup, nil]], Parser.parse('[0]')
|
|
||||||
assert_equal [[:id, "variable"], [:lookup, nil], [:lookup, nil]], Parser.parse('[variable]')
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_descent
|
|
||||||
assert_equal [[:id, "variable1"], [:lookup, nil], [:id, "variable2"], [:call, nil]], Parser.parse('variable1.variable2')
|
|
||||||
assert_equal [[:id, "variable1"], [:lookup, nil], [:id, "variable2"], [:call, nil], [:id, "variable3"], [:call, nil]], Parser.parse('variable1.variable2.variable3')
|
|
||||||
assert_equal [[:id, "variable1"], [:lookup, nil], [:id, "under_score"], [:call, nil]], Parser.parse('variable1.under_score')
|
|
||||||
assert_equal [[:id, "variable1"], [:lookup, nil], [:id, "question?"], [:call, nil]], Parser.parse('variable1.question?')
|
|
||||||
assert_equal [[:id, "variable1"], [:lookup, nil], [:id, "exclaimation!"], [:call, nil]], Parser.parse('variable1.exclaimation!')
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_descent_hash
|
|
||||||
assert_equal [[:id, "variable1"], [:lookup, nil], [:id, "variable2"], [:call, nil]], Parser.parse('variable1["variable2"]')
|
|
||||||
assert_equal [[:id, "variable1"], [:lookup, nil], [:id, "variable2"], [:lookup, nil], [:call, nil]], Parser.parse('variable1[variable2]')
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_buildin
|
|
||||||
assert_equal [[:id, "first"], [:lookup, nil]], Parser.parse('first')
|
|
||||||
|
|
||||||
assert_equal [[:id, "var"], [:lookup, nil], [:buildin, "first"]], Parser.parse('var.first')
|
|
||||||
assert_equal [[:id, "var"], [:lookup, nil], [:buildin, "last"]], Parser.parse('var.last')
|
|
||||||
assert_equal [[:id, "var"], [:lookup, nil], [:buildin, "size"]], Parser.parse('var.size')
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_descent_hash_descent
|
|
||||||
assert_equal [[:id, "variable1"], [:lookup, nil], [:id, "test1"], [:lookup, nil], [:id, "test2"], [:call, nil], [:call, nil]],
|
|
||||||
Parser.parse('variable1[test1.test2]'), "resolove: variable1[test1.test2]"
|
|
||||||
|
|
||||||
assert_equal [[:id, "variable1"], [:lookup, nil], [:id, "test1"], [:lookup, nil], [:id, "test2"], [:call, nil], [:call, nil]],
|
|
||||||
Parser.parse('variable1[test1["test2"]]'), 'resolove: variable1[test1["test2"]]'
|
|
||||||
|
|
||||||
assert_equal [[:id, "variable1"], [:lookup, nil], [:id, "test1"], [:lookup, nil], [:id, "test2"], [:lookup, nil], [:call, nil], [:call, nil]],
|
|
||||||
Parser.parse('variable1[test1[test2]]'), "resolove: variable1[test1[test2]]"
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
end
|
|
||||||
@@ -39,7 +39,7 @@ class ParsingQuirksTest < Test::Unit::TestCase
|
|||||||
|
|
||||||
def test_meaningless_parens
|
def test_meaningless_parens
|
||||||
assigns = {'b' => 'bar', 'c' => 'baz'}
|
assigns = {'b' => 'bar', 'c' => 'baz'}
|
||||||
markup = "a == 'foo' or b == 'bar' and c == 'baz' or false"
|
markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false"
|
||||||
assert_template_result(' YES ',"{% if #{markup} %} YES {% endif %}", assigns)
|
assert_template_result(' YES ',"{% if #{markup} %} YES {% endif %}", assigns)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,27 @@ class Filters
|
|||||||
include Liquid::StandardFilters
|
include Liquid::StandardFilters
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class TestThing
|
||||||
|
def initialize
|
||||||
|
@foo = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
"woot: #{@foo}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_liquid
|
||||||
|
@foo += 1
|
||||||
|
self
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class TestDrop < Liquid::Drop
|
||||||
|
def test
|
||||||
|
"testfoo"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class StandardFiltersTest < Test::Unit::TestCase
|
class StandardFiltersTest < Test::Unit::TestCase
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
@@ -86,6 +107,23 @@ class StandardFiltersTest < Test::Unit::TestCase
|
|||||||
'ary' => [{'foo' => {'bar' => 'a'}}, {'foo' => {'bar' => 'b'}}, {'foo' => {'bar' => 'c'}}]
|
'ary' => [{'foo' => {'bar' => 'a'}}, {'foo' => {'bar' => 'b'}}, {'foo' => {'bar' => 'c'}}]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_map_doesnt_call_arbitrary_stuff
|
||||||
|
assert_equal "", Liquid::Template.parse('{{ "foo" | map: "__id__" }}').render
|
||||||
|
assert_equal "", Liquid::Template.parse('{{ "foo" | map: "inspect" }}').render
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_map_calls_to_liquid
|
||||||
|
t = TestThing.new
|
||||||
|
assert_equal "woot: 1", Liquid::Template.parse('{{ foo }}').render("foo" => t)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_map_over_proc
|
||||||
|
drop = TestDrop.new
|
||||||
|
p = Proc.new{ drop }
|
||||||
|
templ = '{{ procs | map: "test" }}'
|
||||||
|
assert_equal "testfoo", Liquid::Template.parse(templ).render("procs" => [p])
|
||||||
|
end
|
||||||
|
|
||||||
def test_date
|
def test_date
|
||||||
assert_equal 'May', @filters.date(Time.parse("2006-05-05 10:00:00"), "%B")
|
assert_equal 'May', @filters.date(Time.parse("2006-05-05 10:00:00"), "%B")
|
||||||
assert_equal 'June', @filters.date(Time.parse("2006-06-05 10:00:00"), "%B")
|
assert_equal 'June', @filters.date(Time.parse("2006-06-05 10:00:00"), "%B")
|
||||||
@@ -119,9 +157,9 @@ class StandardFiltersTest < Test::Unit::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_replace
|
def test_replace
|
||||||
assert_equal 'b b b b', @filters.replace("a a a a", 'a', 'b')
|
assert_equal '2 2 2 2', @filters.replace('1 1 1 1', '1', 2)
|
||||||
assert_equal 'b a a a', @filters.replace_first("a a a a", 'a', 'b')
|
assert_equal '2 1 1 1', @filters.replace_first('1 1 1 1', '1', 2)
|
||||||
assert_template_result 'b a a a', "{{ 'a a a a' | replace_first: 'a', 'b' }}"
|
assert_template_result '2 1 1 1', "{{ '1 1 1 1' | replace_first: '1', 2 }}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_remove
|
def test_remove
|
||||||
|
|||||||
Reference in New Issue
Block a user