Compare commits

..

26 Commits

Author SHA1 Message Date
DBA
0526348cae Gemspec
- rewritten
2010-08-24 11:57:26 +08:00
Tobias Lütke
a3fb7ba2b3 Merge branch 'master' of github.com:tobi/liquid
Conflicts:
	liquid.gemspec
2010-08-23 20:24:08 -04:00
Tobias Lütke
77cc0f2ed9 Version bump 2010-08-23 20:22:35 -04:00
DBA
3d43efe2bc Ruby compatibility issues
- regexp engines are different from 1.8 to 1.9, fixed the literal shorthand regexp accordingly
  - changed the shorthand regexp text from a match to a string scan
  - test_helper now loads rubygems unless RUBY_VERSION is > 1.9
2010-08-24 08:17:42 +08:00
DBA
772233d881 Readme: pre to code 2010-08-24 08:17:42 +08:00
DBA
90d1bc26d8 History
- updated (2.2.0 & 2.2.1)

Manifest
  - updated readme reference

Readme
  - Converted to markdown
  - cleaned up

Gemspec
  - updated to 2.2.1
2010-08-24 08:17:42 +08:00
DBA
c00a650492 Literal
- added support for literals [closes #6]

Code beautifier
  - indented some files
2010-08-24 08:17:42 +08:00
DBA
4819eb1a92 IF tag
- now properly allows operands to have conditions (eg and, or) [closes #13]
2010-08-24 08:17:41 +08:00
DBA
8579807d29 Conditions
- added test to assert that conditions can contain conditions within its value (eg 'a-and-b')

Tags
  - indented the if tag

Tests
  - added ruby-debug to the test_helper
  - indented some tests
2010-08-24 08:17:41 +08:00
DBA
daf786fd28 Test Helper
- added assert_template_result_matches
  - fixed indentation / white spacing
2010-08-24 08:17:41 +08:00
Tobias Lütke
101137045e remove swp files 2010-08-22 13:33:26 -04:00
DBA
8a0a8cfd99 FiltersTest
- added test that asserts nonexistent filters are ignored

Liquid
  - Bill's mind blowing liquid patch to support filter separators (|) in quoted strings (svn r7516).
  - This is a consolidation effort based on newrelic's liquid fork commit 88a5b891d009054d56b994c9448725c74e2b1e13
2010-08-23 01:30:05 +08:00
DBA
8304a046c9 Context
- Check arity of proc before calling, to prevent error when using ruby 1.9.1+

Code beautifer
  - context.rb
2010-08-23 01:30:03 +08:00
DBA
c72c84ea9b Tests
- Organized the files
  - Cleaned up some of the white spacing issues
  - A lot can still be done to make the tests more readable to the new developers
2010-08-23 01:30:01 +08:00
DBA
01145f872b Test Helper
- Removed unnecessary test helper file. The file being used is helper.rb
2010-08-23 01:30:00 +08:00
DBA
5409814552 Code beautifier
- standard_filter_test.rb
2010-08-23 01:30:00 +08:00
DBA
2d9331a234 StandardFilters
- Ruby 1.9.2-rc changed the float precision, thus the tests are now more generic and backwards compatible.
2010-08-23 01:30:00 +08:00
DBA
c59cde9d17 Code beautifier
- standardfilters.rb
  - standard_filter_test.rb
2010-08-23 01:30:00 +08:00
DBA
29e140b655 StandardFilters
- added escape_once, based on ActionView
2010-08-23 01:30:00 +08:00
DBA
a48332871a Test helper
- extras path now uses File.join instead of string concatenation
  - extras path is only loaded into $LOAD_PATH if it's not already part of it
2010-08-23 01:30:00 +08:00
DBA
bd7f867759 Code beautifier
- strainer_test.rb
2010-08-23 01:30:00 +08:00
DBA
8e4573a7bf Strainer
- respond_to_missing? is now a required method
2010-08-23 01:29:59 +08:00
DBA
5425679a96 Rakefile
- Updated to run with Ruby 1.9.2-p0
  - Fixed some indentations / white spacing
2010-08-23 01:29:59 +08:00
Tobias Lütke
6831eac902 Released gem 2.1.3 2010-08-05 18:07:05 -04:00
Dennis Theisen
13f98de7f3 Change behavior of capture tag to use existing variables if they already have been initialized in an outer scope. 2010-08-06 06:02:37 +08:00
Dennis Theisen
e26f509277 Fixed minor typos in inline documentation for assign and capture 2010-08-06 06:02:37 +08:00
48 changed files with 1149 additions and 981 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
*.gem *.gem
*.swp
pkg pkg

View File

@@ -1,3 +1,5 @@
* Make context and assign work the same
* Ruby 1.9.1 bugfixes * Ruby 1.9.1 bugfixes
* Fix LiquidView for Rails 2.2. Fix local assigns for all versions of Rails * Fix LiquidView for Rails 2.2. Fix local assigns for all versions of Rails

View File

@@ -1,3 +1,12 @@
2.2.1 / 2010-08-23
* Added support for literal tags
2.2.0 / 2010-08-22
* Compatible with Ruby 1.8.7, 1.9.1 and 1.9.2-p0
* Merged some changed made by the community
1.9.0 / 2008-03-04 1.9.0 / 2008-03-04
* Fixed gem install rake task * Fixed gem install rake task

View File

@@ -2,7 +2,7 @@ CHANGELOG
History.txt History.txt
MIT-LICENSE MIT-LICENSE
Manifest.txt Manifest.txt
README.txt README.md
Rakefile Rakefile
init.rb init.rb
lib/extras/liquid_view.rb lib/extras/liquid_view.rb

42
README.md Normal file
View File

@@ -0,0 +1,42 @@
# Liquid template engine
## Introduction
Liquid is a template engine which I wrote for very specific requirements
* It has to have beautiful and simple markup. Template engines which don't produce good looking markup are no fun to use.
* It needs to be non evaling and secure. Liquid templates are made so that users can edit them. You don't want to run code on your server which your users wrote.
* It has to be stateless. Compile and render steps have to be seperate so that the expensive parsing and compiling can be done once and later on you can just render it passing in a hash with local variables and objects.
## Why should I use Liquid
* You want to allow your users to edit the appearance of your application but don't want them to run **insecure code on your server**.
* You want to render templates directly from the database
* You like smarty (PHP) style template engines
* You need a template engine which does HTML just as well as emails
* You don't like the markup of your current templating engine
## What does it look like?
<code>
<ul id="products">
{% for product in products %}
<li>
<h2>{{product.name}}</h2>
Only {{product.price | price }}
{{product.description | prettyprint | paragraph }}
</li>
{% endfor %}
</ul>
</code>
## Howto use Liquid
Liquid supports a very simple API based around the Liquid::Template class.
For standard use you can just pass it the content of a file and call render with a parameters hash.
<pre>
@template = Liquid::Template.parse("hi {{name}}") # Parses and compiles the template
@template.render( 'name' => 'tobi' ) # => "hi tobi"
</pre>

View File

@@ -1,38 +0,0 @@
= Liquid template engine
Liquid is a template engine which I wrote for very specific requirements
* It has to have beautiful and simple markup.
Template engines which don't produce good looking markup are no fun to use.
* It needs to be non evaling and secure. Liquid templates are made so that users can edit them. You don't want to run code on your server which your users wrote.
* It has to be stateless. Compile and render steps have to be seperate so that the expensive parsing and compiling can be done once and later on you can
just render it passing in a hash with local variables and objects.
== Why should i use Liquid
* You want to allow your users to edit the appearance of your application but don't want them to run insecure code on your server.
* You want to render templates directly from the database
* You like smarty style template engines
* You need a template engine which does HTML just as well as Emails
* You don't like the markup of your current one
== What does it look like?
<ul id="products">
{% for product in products %}
<li>
<h2>{{product.name}}</h2>
Only {{product.price | price }}
{{product.description | prettyprint | paragraph }}
</li>
{% endfor %}
</ul>
== Howto use Liquid
Liquid supports a very simple API based around the Liquid::Template class.
For standard use you can just pass it the content of a file and call render with a parameters hash.
@template = Liquid::Template.parse("hi {{name}}") # Parses and compiles the template
@template.render( 'name' => 'tobi' ) # => "hi tobi"

View File

@@ -1,4 +1,6 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
$:.unshift File.join(File.dirname(__FILE__), 'test') unless $:.include? File.join(File.dirname(__FILE__), 'test')
require 'rubygems' require 'rubygems'
require 'rake' require 'rake'
require 'rake/testtask' require 'rake/testtask'
@@ -7,9 +9,8 @@ require 'rake/gempackagetask'
task :default => 'test' task :default => 'test'
Rake::TestTask.new(:test) do |t| Rake::TestTask.new(:test) do |t|
t.libs << "lib" t.libs << '.' << 'lib' << 'test'
t.libs << "test" t.pattern = 'test/lib/**/*_test.rb'
t.pattern = 'test/*_test.rb'
t.verbose = false t.verbose = false
end end
@@ -25,7 +26,6 @@ end
namespace :profile do namespace :profile do
task :default => [:run] task :default => [:run]
desc "Run the liquid profile/perforamce coverage" desc "Run the liquid profile/perforamce coverage"
@@ -39,6 +39,5 @@ namespace :profile do
task :grind => :run do task :grind => :run do
system "kcachegrind /tmp/liquid.rubyprof_calltreeprinter.txt" system "kcachegrind /tmp/liquid.rubyprof_calltreeprinter.txt"
end end
end end

View File

@@ -38,13 +38,14 @@ module Liquid
StrictQuotedFragment = /"[^"]+"|'[^']+'|[^\s,\|,\:,\,]+/ StrictQuotedFragment = /"[^"]+"|'[^']+'|[^\s,\|,\:,\,]+/
FirstFilterArgument = /#{FilterArgumentSeparator}(?:#{StrictQuotedFragment})/ FirstFilterArgument = /#{FilterArgumentSeparator}(?:#{StrictQuotedFragment})/
OtherFilterArgument = /#{ArgumentSeparator}(?:#{StrictQuotedFragment})/ OtherFilterArgument = /#{ArgumentSeparator}(?:#{StrictQuotedFragment})/
SpacelessFilter = /#{FilterSeparator}(?:#{StrictQuotedFragment})(?:#{FirstFilterArgument}(?:#{OtherFilterArgument})*)?/ SpacelessFilter = /^(?:'[^']+'|"[^"]+"|[^'"])*#{FilterSeparator}(?:#{StrictQuotedFragment})(?:#{FirstFilterArgument}(?:#{OtherFilterArgument})*)?/
Expression = /(?:#{QuotedFragment}(?:#{SpacelessFilter})*)/ Expression = /(?:#{QuotedFragment}(?:#{SpacelessFilter})*)/
TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/ TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/
AnyStartingTag = /\{\{|\{\%/ AnyStartingTag = /\{\{|\{\%/
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/ PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/ TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/
VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/ VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/
LiteralShorthand = /^(?:\{\{\{\s?)(.*?)(?:\s*\}\}\})$/
end end
require 'liquid/drop' require 'liquid/drop'

View File

@@ -28,8 +28,9 @@ module Liquid
@strainer ||= Strainer.create(self) @strainer ||= Strainer.create(self)
end end
# adds filters to this context. # Adds filters to this context.
# this does not register the filters with the main Template object. see <tt>Template.register_filter</tt> #
# Note that this does not register the filters with the main Template object. see <tt>Template.register_filter</tt>
# for that # for that
def add_filters(filters) def add_filters(filters)
filters = [filters].flatten.compact filters = [filters].flatten.compact
@@ -52,7 +53,6 @@ module Liquid
end end
end end
def invoke(method, *args) def invoke(method, *args)
if strainer.respond_to?(method) if strainer.respond_to?(method)
strainer.__send__(method, *args) strainer.__send__(method, *args)
@@ -61,40 +61,41 @@ module Liquid
end end
end end
# 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={})
raise StackLevelError, "Nesting too deep" if @scopes.length > 100 raise StackLevelError, "Nesting too deep" if @scopes.length > 100
@scopes.unshift(new_scope) @scopes.unshift(new_scope)
end end
# merge a hash of variables in the current local scope # Merge a hash of variables in the current local scope
def merge(new_scopes) def merge(new_scopes)
@scopes[0].merge!(new_scopes) @scopes[0].merge!(new_scopes)
end end
# pop from the stack. use <tt>Context#stack</tt> instead # Pop from the stack. use <tt>Context#stack</tt> instead
def pop def pop
raise ContextError if @scopes.size == 1 raise ContextError if @scopes.size == 1
@scopes.shift @scopes.shift
end end
# pushes a new local scope on the stack, pops it at the end of the block # Pushes a new local scope on the stack, pops it at the end of the block
# #
# Example: # Example:
#
# context.stack do # context.stack do
# context['var'] = 'hi' # context['var'] = 'hi'
# end # end
# context['var] #=> nil
# #
# context['var] #=> nil
def stack(new_scope={},&block) def stack(new_scope={},&block)
result = nil result = nil
push(new_scope) push(new_scope)
begin begin
result = yield result = yield
ensure ensure
pop pop
end end
result result
end end
@@ -116,139 +117,133 @@ module Liquid
end end
private private
# 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 # Strings, digits, floats and booleans (true,false).
# Strings, digits, floats and booleans (true,false). If no match is made we lookup the variable in the current scope and # 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. # 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 # Some special keywords return symbols. Those symbols are to be called on the rhs object in expressions
# #
# Example: # Example:
# # products == empty #=> products.empty?
# products == empty #=> products.empty? def resolve(key)
# case key
def resolve(key) when nil, 'nil', 'null', ''
case key nil
when nil, 'nil', 'null', '' when 'true'
nil true
when 'true' when 'false'
true false
when 'false' when 'blank'
false :blank?
when 'blank' when 'empty' # Single quoted strings
:blank? :empty?
when 'empty' when /^'(.*)'$/ # Double quoted strings
:empty? $1.to_s
# Single quoted strings when /^"(.*)"$/ # Integer and floats
when /^'(.*)'$/ $1.to_s
$1.to_s when /^(\d+)$/ # Ranges
# Double quoted strings $1.to_i
when /^"(.*)"$/ when /^\((\S+)\.\.(\S+)\)$/ # Floats
$1.to_s (resolve($1).to_i..resolve($2).to_i)
# Integer and floats when /^(\d[\d\.]+)$/
when /^(\d+)$/ $1.to_f
$1.to_i else
# Ranges variable(key)
when /^\((\S+)\.\.(\S+)\)$/
(resolve($1).to_i..resolve($2).to_i)
# Floats
when /^(\d[\d\.]+)$/
$1.to_f
else
variable(key)
end
end
# fetches an object starting at the local scope and then moving up
# the hierachy
def find_variable(key)
scope = @scopes.find { |s| s.has_key?(key) }
if scope.nil?
@environments.each do |e|
if variable = lookup_and_evaluate(e, key)
scope = e
break
end
end
end
scope ||= @environments.last || @scopes.last
variable ||= lookup_and_evaluate(scope, key)
variable = variable.to_liquid
variable.context = self if variable.respond_to?(:context=)
return variable
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
end end
object # Fetches an object starting at the local scope and then moving up the hierachy
end def find_variable(key)
scope = @scopes.find { |s| s.has_key?(key) }
def lookup_and_evaluate(obj, key) if scope.nil?
if (value = obj[key]).is_a?(Proc) && obj.respond_to?(:[]=) @environments.each do |e|
obj[key] = value.call(self) if variable = lookup_and_evaluate(e, key)
else scope = e
value break
end end
end
def squash_instance_assigns_with_environments
@scopes.last.each_key do |k|
@environments.each do |env|
if env.has_key?(k)
scopes.last[k] = lookup_and_evaluate(env, k)
break
end end
end end
end
end
end scope ||= @environments.last || @scopes.last
end variable ||= lookup_and_evaluate(scope, key)
variable = variable.to_liquid
variable.context = self if variable.respond_to?(:context=)
return variable
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)
if (value = obj[key]).is_a?(Proc) && obj.respond_to?(:[]=)
obj[key] = (value.arity == 0) ? value.call : value.call(self)
else
value
end
end # lookup_and_evaluate
def squash_instance_assigns_with_environments
@scopes.last.each_key do |k|
@environments.each do |env|
if env.has_key?(k)
scopes.last[k] = lookup_and_evaluate(env, k)
break
end
end
end
end # squash_instance_assigns_with_environments
end # Context
end # Liquid

View File

@@ -29,6 +29,10 @@ module Liquid
CGI.escapeHTML(input) rescue input CGI.escapeHTML(input) rescue input
end end
def escape_once(input)
ActionView::Helpers::TagHelper.escape_once(input) rescue input
end
alias_method :h, :escape alias_method :h, :escape
# Truncate a string down to x characters # Truncate a string down to x characters
@@ -205,16 +209,16 @@ module Liquid
private private
def to_number(obj) def to_number(obj)
case obj case obj
when Numeric when Numeric
obj obj
when String when String
(obj.strip =~ /^\d+\.\d+$/) ? obj.to_f : obj.to_i (obj.strip =~ /^\d+\.\d+$/) ? obj.to_f : obj.to_i
else else
0 0
end
end end
end
end end

View File

@@ -16,6 +16,9 @@ module Liquid
INTERNAL_METHOD = /^__/ INTERNAL_METHOD = /^__/
@@required_methods = Set.new([:__id__, :__send__, :respond_to?, :extend, :methods, :class, :object_id]) @@required_methods = Set.new([:__id__, :__send__, :respond_to?, :extend, :methods, :class, :object_id])
# Ruby 1.9.2 introduces Object#respond_to_missing?, which is invoked by Object#respond_to?
@@required_methods << :respond_to_missing? if Object.respond_to? :respond_to_missing?
@@filters = {} @@filters = {}
def initialize(context) def initialize(context)

View File

@@ -1,6 +1,7 @@
module Liquid module Liquid
class Tag class Tag
attr_accessor :nodelist attr_accessor :nodelist
def initialize(tag_name, markup, tokens) def initialize(tag_name, markup, tokens)
@@ -19,8 +20,7 @@ module Liquid
def render(context) def render(context)
'' ''
end end
end
end # Tag
end end # Tag

View File

@@ -6,7 +6,7 @@ module Liquid
# #
# You can then use the variable later in the page. # You can then use the variable later in the page.
# #
# {{ monkey }} # {{ foo }}
# #
class Assign < Tag class Assign < Tag
Syntax = /(#{VariableSignature}+)\s*=\s*(#{QuotedFragment}+)/ Syntax = /(#{VariableSignature}+)\s*=\s*(#{QuotedFragment}+)/
@@ -23,7 +23,7 @@ module Liquid
end end
def render(context) def render(context)
context.scopes.last[@to.to_s] = context[@from] context.scopes.last[@to] = context[@from]
'' ''
end end

View File

@@ -6,7 +6,7 @@ module Liquid
# Monkeys! # Monkeys!
# {% endcapture %} # {% endcapture %}
# ... # ...
# <h1>{{ monkeys }}</h1> # <h1>{{ heading }}</h1>
# #
# Capture is useful for saving content for use later in your template, such as # Capture is useful for saving content for use later in your template, such as
# in a sidebar or footer. # in a sidebar or footer.
@@ -26,7 +26,7 @@ module Liquid
def render(context) def render(context)
output = super output = super
context[@to] = output.join context.scopes.last[@to] = output.join
'' ''
end end
end end

View File

@@ -14,10 +14,9 @@ module Liquid
class If < Block class If < Block
SyntaxHelp = "Syntax Error in tag 'if' - Valid syntax: if [expression]" SyntaxHelp = "Syntax Error in tag 'if' - Valid syntax: if [expression]"
Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/ Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/
ExpressionsAndOperators = /(?:\b(?:and|or)\b|(?:\s*(?!\b(?:and|or)\b)(?:#{QuotedFragment}|\S+)\s*)+)/ ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/
def initialize(tag_name, markup, tokens) def initialize(tag_name, markup, tokens)
@blocks = [] @blocks = []
push_block('if', markup) push_block('if', markup)
@@ -46,33 +45,33 @@ module Liquid
private private
def push_block(tag, markup) def push_block(tag, markup)
block = if tag == 'else' block = if tag == 'else'
ElseCondition.new ElseCondition.new
else else
expressions = markup.scan(ExpressionsAndOperators).reverse expressions = markup.scan(ExpressionsAndOperators).reverse
raise(SyntaxError, SyntaxHelp) unless expressions.shift =~ Syntax raise(SyntaxError, SyntaxHelp) 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 operator = (expressions.shift).to_s.strip
raise(SyntaxError, SyntaxHelp) unless expressions.shift.to_s =~ Syntax raise(SyntaxError, SyntaxHelp) unless expressions.shift.to_s =~ Syntax
new_condition = Condition.new($1, $2, $3) new_condition = Condition.new($1, $2, $3)
new_condition.send(operator.to_sym, condition) new_condition.send(operator.to_sym, condition)
condition = new_condition condition = new_condition
end
condition
end end
condition @blocks.push(block)
@nodelist = block.attach(Array.new)
end end
@blocks.push(block)
@nodelist = block.attach(Array.new)
end
end end

View File

@@ -0,0 +1,42 @@
module Liquid
class Literal < Block
# Class methods
# Converts a shorthand Liquid literal into its long representation.
#
# Currently the Template parser only knows how to handle the long version.
# So, it always checks if it is in the presence of a literal, in which case it gets converted through this method.
#
# Example:
# Liquid::Literal "{{{ hello world }}}" #=> "{% literal %} hello world {% endliteral %}"
def self.from_shorthand(literal)
literal =~ LiteralShorthand ? "{% literal %}#{$1}{% endliteral %}" : literal
end
# Public instance methods
def parse(tokens) # :nodoc:
@nodelist ||= []
@nodelist.clear
while token = tokens.shift
if token =~ FullToken && block_delimiter == $1
end_tag
return
else
@nodelist << token
end
end
# Make sure that its ok to end parsing in the current block.
# Effectively this method will throw and exception unless the current block is
# of type Document
assert_missing_delimitation!
end # parse
end
Template.register_tag('literal', Literal)
end

View File

@@ -55,7 +55,7 @@ module Liquid
# Parse source code. # Parse source code.
# Returns self for easy chaining # Returns self for easy chaining
def parse(source) def parse(source)
@root = Document.new(tokenize(source)) @root = Document.new(tokenize(Liquid::Literal.from_shorthand(source)))
self self
end end

View File

@@ -1,28 +1,23 @@
# -*- encoding: utf-8 -*-
Gem::Specification.new do |s| Gem::Specification.new do |s|
s.name = %q{liquid} s.platform = Gem::Platform::RUBY
s.version = "2.1.2"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.name = "liquid"
s.authors = ["Tobias Luetke"] s.version = '2.2.2'
s.description = %q{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.email = %q{tobi@leetsoft.com}
s.extra_rdoc_files = ["History.txt", "Manifest.txt", "README.txt"]
s.files = ["CHANGELOG", "History.txt", "MIT-LICENSE", "Manifest.txt", "README.txt", "Rakefile", "lib/extras/liquid_view.rb", "lib/liquid.rb", "lib/liquid/block.rb", "lib/liquid/condition.rb", "lib/liquid/context.rb", "lib/liquid/document.rb", "lib/liquid/drop.rb", "lib/liquid/errors.rb", "lib/liquid/extensions.rb", "lib/liquid/file_system.rb", "lib/liquid/htmltags.rb", "lib/liquid/module_ex.rb", "lib/liquid/standardfilters.rb", "lib/liquid/strainer.rb", "lib/liquid/tag.rb", "lib/liquid/tags/assign.rb", "lib/liquid/tags/capture.rb", "lib/liquid/tags/case.rb", "lib/liquid/tags/comment.rb", "lib/liquid/tags/cycle.rb", "lib/liquid/tags/for.rb", "lib/liquid/tags/if.rb", "lib/liquid/tags/ifchanged.rb", "lib/liquid/tags/include.rb", "lib/liquid/tags/unless.rb", "lib/liquid/template.rb", "lib/liquid/variable.rb"]
s.has_rdoc = true
s.homepage = %q{http://www.liquidmarkup.org}
s.rdoc_options = ["--main", "README.txt"]
s.require_paths = ["lib"]
s.rubyforge_project = %q{liquid}
s.rubygems_version = %q{1.3.1}
s.summary = %q{A secure, non-evaling end user template engine with aesthetic markup.}
if s.respond_to? :specification_version then s.authors = ["Tobias Luetke"]
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION s.email = ["tobi@leetsoft.com"]
s.specification_version = 2 s.homepage = "http://www.liquidmarkup.org"
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then s.description = "A secure, non-evaling end user template engine with aesthetic markup."
else
end s.required_rubygems_version = ">= 1.3.7"
else
end s.files = Dir.glob("{lib}/**/*") + %w(MIT-LICENSE README.md)
s.extra_rdoc_files = ["History.txt", "Manifest.txt", "README.md"]
s.require_path = 'lib'
end end

View File

@@ -1,11 +0,0 @@
require File.dirname(__FILE__) + '/helper'
class AssignTest < Test::Unit::TestCase
include Liquid
def test_assigned_variable
assert_template_result('.foo.','{% assign foo = values %}.{{ foo[0] }}.', 'values' => %w{foo bar baz})
assert_template_result('.bar.','{% assign foo = values %}.{{ foo[1] }}.', 'values' => %w{foo bar baz})
end
end

View File

@@ -1,20 +0,0 @@
#!/usr/bin/env ruby
$LOAD_PATH.unshift(File.dirname(__FILE__)+ '/extra')
require 'test/unit'
require 'test/unit/assertions'
require 'caller'
require 'breakpoint'
require File.dirname(__FILE__) + '/../lib/liquid'
module Test
module Unit
module Assertions
include Liquid
def assert_template_result(expected, template, assigns={}, message=nil)
assert_equal expected, Template.parse(template).render(assigns)
end
end
end
end

View File

@@ -1,129 +0,0 @@
require File.dirname(__FILE__) + '/helper'
class TestFileSystem
def read_template_file(template_path)
case template_path
when "product"
"Product: {{ product.title }} "
when "locale_variables"
"Locale: {{echo1}} {{echo2}}"
when "variant"
"Variant: {{ variant.title }}"
when "nested_template"
"{% include 'header' %} {% include 'body' %} {% include 'footer' %}"
when "body"
"body {% include 'body_detail' %}"
when "nested_product_template"
"Product: {{ nested_product_template.title }} {%include 'details'%} "
when "recursively_nested_template"
"-{% include 'recursively_nested_template' %}"
when "pick_a_source"
"from TestFileSystem"
else
template_path
end
end
end
class OtherFileSystem
def read_template_file(template_path)
'from OtherFileSystem'
end
end
class IncludeTagTest < Test::Unit::TestCase
include Liquid
def setup
Liquid::Template.file_system = TestFileSystem.new
end
def test_include_tag_looks_for_file_system_in_registers_first
assert_equal 'from OtherFileSystem',
Template.parse("{% include 'pick_a_source' %}").render({}, :registers => {:file_system => OtherFileSystem.new})
end
def test_include_tag_with
assert_equal "Product: Draft 151cm ",
Template.parse("{% include 'product' with products[0] %}").render( "products" => [ {'title' => 'Draft 151cm'}, {'title' => 'Element 155cm'} ] )
end
def test_include_tag_with_default_name
assert_equal "Product: Draft 151cm ",
Template.parse("{% include 'product' %}").render( "product" => {'title' => 'Draft 151cm'} )
end
def test_include_tag_for
assert_equal "Product: Draft 151cm Product: Element 155cm ",
Template.parse("{% include 'product' for products %}").render( "products" => [ {'title' => 'Draft 151cm'}, {'title' => 'Element 155cm'} ] )
end
def test_include_tag_with_local_variables
assert_equal "Locale: test123 ",
Template.parse("{% include 'locale_variables' echo1: 'test123' %}").render
end
def test_include_tag_with_multiple_local_variables
assert_equal "Locale: test123 test321",
Template.parse("{% include 'locale_variables' echo1: 'test123', echo2: 'test321' %}").render
end
def test_include_tag_with_multiple_local_variables_from_context
assert_equal "Locale: test123 test321",
Template.parse("{% include 'locale_variables' echo1: echo1, echo2: more_echos.echo2 %}").render('echo1' => 'test123', 'more_echos' => { "echo2" => 'test321'})
end
def test_nested_include_tag
assert_equal "body body_detail",
Template.parse("{% include 'body' %}").render
assert_equal "header body body_detail footer",
Template.parse("{% include 'nested_template' %}").render
end
def test_nested_include_with_variable
assert_equal "Product: Draft 151cm details ",
Template.parse("{% include 'nested_product_template' with product %}").render("product" => {"title" => 'Draft 151cm'})
assert_equal "Product: Draft 151cm details Product: Element 155cm details ",
Template.parse("{% include 'nested_product_template' for products %}").render("products" => [{"title" => 'Draft 151cm'}, {"title" => 'Element 155cm'}])
end
def test_recursively_included_template_does_not_produce_endless_loop
infinite_file_system = Class.new do
def read_template_file(template_path)
"-{% include 'loop' %}"
end
end
Liquid::Template.file_system = infinite_file_system.new
assert_raise(Liquid::StackLevelError) do
Template.parse("{% include 'loop' %}").render!
end
end
def test_dynamically_choosen_template
assert_equal "Test123", Template.parse("{% include template %}").render("template" => 'Test123')
assert_equal "Test321", Template.parse("{% include template %}").render("template" => 'Test321')
assert_equal "Product: Draft 151cm ", Template.parse("{% include template for product %}").render("template" => 'product', 'product' => { 'title' => 'Draft 151cm'})
end
end

View File

@@ -0,0 +1,15 @@
require 'test_helper'
class AssignTest < Test::Unit::TestCase
include Liquid
def test_assigned_variable
assert_template_result('.foo.',
'{% assign foo = values %}.{{ foo[0] }}.',
'values' => %w{foo bar baz})
assert_template_result('.bar.',
'{% assign foo = values %}.{{ foo[1] }}.',
'values' => %w{foo bar baz})
end
end # AssignTest

View File

@@ -1,4 +1,4 @@
require File.dirname(__FILE__) + '/helper' require 'test_helper'
class VariableTest < Test::Unit::TestCase class VariableTest < Test::Unit::TestCase
include Liquid include Liquid
@@ -33,7 +33,8 @@ class VariableTest < Test::Unit::TestCase
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], block_types(template.root.nodelist) assert_equal [String, Variable, String, Variable, String, Variable, String],
block_types(template.root.nodelist)
end end
def test_with_block def test_with_block
@@ -51,8 +52,7 @@ class VariableTest < Test::Unit::TestCase
end end
private private
def block_types(nodelist)
def block_types(nodelist) nodelist.collect { |node| node.class }
nodelist.collect { |node| node.class } end
end end # VariableTest
end

View File

@@ -0,0 +1,40 @@
require 'test_helper'
class CaptureTest < Test::Unit::TestCase
include Liquid
def test_captures_block_content_in_variable
assert_template_result("test string", "{% capture 'var' %}test string{% endcapture %}{{var}}", {})
end
def test_capture_to_variable_from_outer_scope_if_existing
template_source = <<-END_TEMPLATE
{% assign var = '' %}
{% if true %}
{% capture var %}first-block-string{% endcapture %}
{% endif %}
{% if true %}
{% capture var %}test-string{% endcapture %}
{% endif %}
{{var}}
END_TEMPLATE
template = Template.parse(template_source)
rendered = template.render
assert_equal "test-string", rendered.gsub(/\s/, '')
end
def test_assigning_from_capture
template_source = <<-END_TEMPLATE
{% assign first = '' %}
{% assign second = '' %}
{% for number in (1..3) %}
{% capture first %}{{number}}{% endcapture %}
{% assign second = first %}
{% endfor %}
{{ first }}-{{ second }}
END_TEMPLATE
template = Template.parse(template_source)
rendered = template.render
assert_equal "3-3", rendered.gsub(/\s/, '')
end
end # CaptureTest

View File

@@ -1,4 +1,4 @@
require File.dirname(__FILE__) + '/helper' require 'test_helper'
class ConditionTest < Test::Unit::TestCase class ConditionTest < Test::Unit::TestCase
include Liquid include Liquid
@@ -48,16 +48,14 @@ class ConditionTest < Test::Unit::TestCase
@context = Liquid::Context.new @context = Liquid::Context.new
@context['array'] = [1,2,3,4,5] @context['array'] = [1,2,3,4,5]
assert_evalutes_false "array", 'contains', '0' assert_evalutes_false "array", 'contains', '0'
assert_evalutes_true "array", 'contains', '1' assert_evalutes_true "array", 'contains', '1'
assert_evalutes_true "array", 'contains', '2' assert_evalutes_true "array", 'contains', '2'
assert_evalutes_true "array", 'contains', '3' assert_evalutes_true "array", 'contains', '3'
assert_evalutes_true "array", 'contains', '4' assert_evalutes_true "array", 'contains', '4'
assert_evalutes_true "array", 'contains', '5' assert_evalutes_true "array", 'contains', '5'
assert_evalutes_false "array", 'contains', '6' assert_evalutes_false "array", 'contains', '6'
assert_evalutes_false "array", 'contains', '"1"'
assert_evalutes_false "array", 'contains', '"1"'
end end
def test_contains_returns_false_for_nil_operands def test_contains_returns_false_for_nil_operands
@@ -94,22 +92,31 @@ class ConditionTest < Test::Unit::TestCase
assert_equal false, condition.evaluate assert_equal false, condition.evaluate
end end
def test_should_allow_custom_proc_operator def test_should_allow_custom_proc_operator
Condition.operators['starts_with'] = Proc.new { |cond, left, right| left =~ %r{^#{right}}} Condition.operators['starts_with'] = Proc.new { |cond, left, right| left =~ %r{^#{right}} }
assert_evalutes_true "'bob'", 'starts_with', "'b'" assert_evalutes_true "'bob'", 'starts_with', "'b'"
assert_evalutes_false "'bob'", 'starts_with', "'o'" assert_evalutes_false "'bob'", 'starts_with', "'o'"
ensure
Condition.operators.delete 'starts_with' ensure
Condition.operators.delete 'starts_with'
end
def test_left_or_right_may_contain_operators
@context = Liquid::Context.new
@context['one'] = @context['another'] = "gnomeslab-and-or-liquid"
assert_evalutes_true "one", '==', "another"
end end
private private
def assert_evalutes_true(left, op, right) def assert_evalutes_true(left, op, right)
assert Condition.new(left, op, right).evaluate(@context || Liquid::Context.new), "Evaluated false: #{left} #{op} #{right}" assert Condition.new(left, op, right).evaluate(@context || Liquid::Context.new),
"Evaluated false: #{left} #{op} #{right}"
end end
def assert_evalutes_false(left, op, right) def assert_evalutes_false(left, op, right)
assert !Condition.new(left, op, right).evaluate(@context || Liquid::Context.new), "Evaluated true: #{left} #{op} #{right}" assert !Condition.new(left, op, right).evaluate(@context || Liquid::Context.new),
"Evaluated true: #{left} #{op} #{right}"
end end
end end # ConditionTest

View File

@@ -1,4 +1,5 @@
require File.dirname(__FILE__) + '/helper' require 'test_helper'
class HundredCentes class HundredCentes
def to_liquid def to_liquid
100 100
@@ -62,7 +63,6 @@ class ArrayLike
end end
end end
class ContextTest < Test::Unit::TestCase class ContextTest < Test::Unit::TestCase
include Liquid include Liquid
@@ -262,10 +262,10 @@ class ContextTest < Test::Unit::TestCase
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'],
'Green' => ['003300','336633', '669966', '99CC99'], 'Green' => ['003300','336633', '669966', '99CC99'],
'Yellow' => ['CC9900','FFCC00', 'FFFF99', 'FFFFCC'], 'Yellow' => ['CC9900','FFCC00', 'FFFF99', 'FFFFCC'],
'Red' => ['660000','993333', 'CC6666', 'FF9999'] 'Red' => ['660000','993333', 'CC6666', 'FF9999']
} }
assert_equal '003366', @context['colors.Blue[0]'] assert_equal '003366', @context['colors.Blue[0]']
@@ -475,5 +475,4 @@ class ContextTest < Test::Unit::TestCase
assert_kind_of CategoryDrop, @context['category'] assert_kind_of CategoryDrop, @context['category']
assert_equal @context, @context['category'].context assert_equal @context, @context['category'].context
end end
end # ContextTest
end

View File

@@ -1,6 +1,4 @@
require 'test_helper'
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/helper'
class ContextDrop < Liquid::Drop class ContextDrop < Liquid::Drop
def scopes def scopes
@@ -24,7 +22,6 @@ class ContextDrop < Liquid::Drop
end end
end end
class ProductDrop < Liquid::Drop class ProductDrop < Liquid::Drop
class TextDrop < Liquid::Drop class TextDrop < Liquid::Drop
@@ -74,7 +71,6 @@ class EnumerableDrop < Liquid::Drop
end end
end end
class DropsTest < Test::Unit::TestCase class DropsTest < Test::Unit::TestCase
include Liquid include Liquid
@@ -154,9 +150,4 @@ class DropsTest < Test::Unit::TestCase
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
end # DropsTest
end

View File

@@ -1,6 +1,4 @@
require 'test_helper'
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/helper'
class ErrorDrop < Liquid::Drop class ErrorDrop < Liquid::Drop
def standard_error def standard_error
@@ -17,7 +15,6 @@ class ErrorDrop < Liquid::Drop
end end
class ErrorHandlingTest < Test::Unit::TestCase class ErrorHandlingTest < Test::Unit::TestCase
include Liquid include Liquid
@@ -42,11 +39,9 @@ class ErrorHandlingTest < Test::Unit::TestCase
assert_equal SyntaxError, template.errors.first.class assert_equal SyntaxError, template.errors.first.class
end end
end end
def test_argument def test_argument
assert_nothing_raised do assert_nothing_raised do
template = Liquid::Template.parse( ' {{ errors.argument_error }} ' ) template = Liquid::Template.parse( ' {{ errors.argument_error }} ' )
@@ -54,36 +49,21 @@ class ErrorHandlingTest < Test::Unit::TestCase
assert_equal 1, template.errors.size assert_equal 1, template.errors.size
assert_equal ArgumentError, template.errors.first.class assert_equal ArgumentError, template.errors.first.class
end end
end end
def test_missing_endtag_parse_time_error def test_missing_endtag_parse_time_error
assert_raise(Liquid::SyntaxError) do assert_raise(Liquid::SyntaxError) do
template = Liquid::Template.parse(' {% for a in b %} ... ') template = Liquid::Template.parse(' {% for a in b %} ... ')
end end
end end
def test_unrecognized_operator def test_unrecognized_operator
assert_nothing_raised do assert_nothing_raised do
template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ') template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ')
assert_equal ' Liquid error: Unknown operator =! ', template.render assert_equal ' Liquid error: Unknown operator =! ', template.render
assert_equal 1, template.errors.size assert_equal 1, template.errors.size
assert_equal Liquid::ArgumentError, template.errors.first.class assert_equal Liquid::ArgumentError, template.errors.first.class
end end
end end
end # ErrorHandlingTest
end

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env ruby require 'test_helper'
require File.dirname(__FILE__) + '/helper'
class FileSystemTest < Test::Unit::TestCase class FileSystemTest < Test::Unit::TestCase
include Liquid include Liquid
@@ -27,4 +26,4 @@ class FileSystemTest < Test::Unit::TestCase
file_system.full_path("/etc/passwd") file_system.full_path("/etc/passwd")
end end
end end
end end # FileSystemTest

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env ruby require 'test_helper'
require File.dirname(__FILE__) + '/helper'
module MoneyFilter module MoneyFilter
def money(input) def money(input)
@@ -27,6 +26,7 @@ class FiltersTest < Test::Unit::TestCase
def test_local_filter def test_local_filter
@context['var'] = 1000 @context['var'] = 1000
@context.add_filters(MoneyFilter) @context.add_filters(MoneyFilter)
assert_equal ' 1000$ ', Variable.new("var | money").render(@context) assert_equal ' 1000$ ', Variable.new("var | money").render(@context)
end end
@@ -40,17 +40,20 @@ class FiltersTest < Test::Unit::TestCase
@context['var'] = 1000 @context['var'] = 1000
@context.add_filters(MoneyFilter) @context.add_filters(MoneyFilter)
@context.add_filters(CanadianMoneyFilter) @context.add_filters(CanadianMoneyFilter)
assert_equal ' 1000$ CAD ', Variable.new("var | money").render(@context) assert_equal ' 1000$ CAD ', Variable.new("var | money").render(@context)
end end
def test_size def test_size
@context['var'] = 'abcd' @context['var'] = 'abcd'
@context.add_filters(MoneyFilter) @context.add_filters(MoneyFilter)
assert_equal 4, Variable.new("var | size").render(@context) assert_equal 4, Variable.new("var | size").render(@context)
end end
def test_join def test_join
@context['var'] = [1,2,3,4] @context['var'] = [1,2,3,4]
assert_equal "1 2 3 4", Variable.new("var | join").render(@context) assert_equal "1 2 3 4", Variable.new("var | join").render(@context)
end end
@@ -59,22 +62,30 @@ class FiltersTest < Test::Unit::TestCase
@context['numbers'] = [2,1,4,3] @context['numbers'] = [2,1,4,3]
@context['words'] = ['expected', 'as', 'alphabetic'] @context['words'] = ['expected', 'as', 'alphabetic']
@context['arrays'] = [['flattened'], ['are']] @context['arrays'] = [['flattened'], ['are']]
assert_equal [1,2,3,4], Variable.new("numbers | sort").render(@context) assert_equal [1,2,3,4], Variable.new("numbers | sort").render(@context)
assert_equal ['alphabetic', 'as', 'expected'], assert_equal ['alphabetic', 'as', 'expected'], Variable.new("words | sort").render(@context)
Variable.new("words | sort").render(@context)
assert_equal [3], Variable.new("value | sort").render(@context) assert_equal [3], Variable.new("value | sort").render(@context)
assert_equal ['are', 'flattened'], Variable.new("arrays | sort").render(@context) assert_equal ['are', 'flattened'], Variable.new("arrays | sort").render(@context)
end end
def test_strip_html def test_strip_html
@context['var'] = "<b>bla blub</a>" @context['var'] = "<b>bla blub</a>"
assert_equal "bla blub", Variable.new("var | strip_html").render(@context) assert_equal "bla blub", Variable.new("var | strip_html").render(@context)
end end
def test_capitalize def test_capitalize
@context['var'] = "blub" @context['var'] = "blub"
assert_equal "Blub", Variable.new("var | capitalize").render(@context) assert_equal "Blub", Variable.new("var | capitalize").render(@context)
end end
def test_nonexistent_filter_is_ignored
@context['var'] = 1000
assert_equal 1000, Variable.new("var | xyzzy").render(@context)
end
end end
class FiltersInTemplate < Test::Unit::TestCase class FiltersInTemplate < Test::Unit::TestCase
@@ -92,4 +103,4 @@ class FiltersInTemplate < Test::Unit::TestCase
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 end # FiltersTest

View File

@@ -1,4 +1,4 @@
require File.dirname(__FILE__) + '/helper' require 'test_helper'
class HtmlTagTest < Test::Unit::TestCase class HtmlTagTest < Test::Unit::TestCase
include Liquid include Liquid
@@ -10,8 +10,8 @@ class HtmlTagTest < Test::Unit::TestCase
'numbers' => [1,2,3,4,5,6]) 'numbers' => [1,2,3,4,5,6])
assert_template_result("<tr class=\"row1\">\n</tr>\n", assert_template_result("<tr class=\"row1\">\n</tr>\n",
'{% tablerow n in numbers cols:3%} {{n}} {% endtablerow %}', '{% tablerow n in numbers cols:3%} {{n}} {% endtablerow %}',
'numbers' => []) 'numbers' => [])
end end
def test_html_table_with_different_cols def test_html_table_with_different_cols
@@ -25,7 +25,5 @@ class HtmlTagTest < Test::Unit::TestCase
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])
end end
end # HtmlTagTest
end

View File

@@ -0,0 +1,127 @@
require 'test_helper'
class TestFileSystem
def read_template_file(template_path)
case template_path
when "product"
"Product: {{ product.title }} "
when "locale_variables"
"Locale: {{echo1}} {{echo2}}"
when "variant"
"Variant: {{ variant.title }}"
when "nested_template"
"{% include 'header' %} {% include 'body' %} {% include 'footer' %}"
when "body"
"body {% include 'body_detail' %}"
when "nested_product_template"
"Product: {{ nested_product_template.title }} {%include 'details'%} "
when "recursively_nested_template"
"-{% include 'recursively_nested_template' %}"
when "pick_a_source"
"from TestFileSystem"
else
template_path
end
end
end
class OtherFileSystem
def read_template_file(template_path)
'from OtherFileSystem'
end
end
class IncludeTagTest < Test::Unit::TestCase
include Liquid
def setup
Liquid::Template.file_system = TestFileSystem.new
end
def test_include_tag_looks_for_file_system_in_registers_first
assert_equal 'from OtherFileSystem',
Template.parse("{% include 'pick_a_source' %}").render({}, :registers => {:file_system => OtherFileSystem.new})
end
def test_include_tag_with
assert_equal "Product: Draft 151cm ",
Template.parse("{% include 'product' with products[0] %}").render( "products" => [ {'title' => 'Draft 151cm'}, {'title' => 'Element 155cm'} ] )
end
def test_include_tag_with_default_name
assert_equal "Product: Draft 151cm ",
Template.parse("{% include 'product' %}").render( "product" => {'title' => 'Draft 151cm'} )
end
def test_include_tag_for
assert_equal "Product: Draft 151cm Product: Element 155cm ",
Template.parse("{% include 'product' for products %}").render( "products" => [ {'title' => 'Draft 151cm'}, {'title' => 'Element 155cm'} ] )
end
def test_include_tag_with_local_variables
assert_equal "Locale: test123 ",
Template.parse("{% include 'locale_variables' echo1: 'test123' %}").render
end
def test_include_tag_with_multiple_local_variables
assert_equal "Locale: test123 test321",
Template.parse("{% include 'locale_variables' echo1: 'test123', echo2: 'test321' %}").render
end
def test_include_tag_with_multiple_local_variables_from_context
assert_equal "Locale: test123 test321",
Template.parse("{% include 'locale_variables' echo1: echo1, echo2: more_echos.echo2 %}").render('echo1' => 'test123', 'more_echos' => { "echo2" => 'test321'})
end
def test_nested_include_tag
assert_equal "body body_detail",
Template.parse("{% include 'body' %}").render
assert_equal "header body body_detail footer",
Template.parse("{% include 'nested_template' %}").render
end
def test_nested_include_with_variable
assert_equal "Product: Draft 151cm details ",
Template.parse("{% include 'nested_product_template' with product %}").render("product" => {"title" => 'Draft 151cm'})
assert_equal "Product: Draft 151cm details Product: Element 155cm details ",
Template.parse("{% include 'nested_product_template' for products %}").render("products" => [{"title" => 'Draft 151cm'}, {"title" => 'Element 155cm'}])
end
def test_recursively_included_template_does_not_produce_endless_loop
infinite_file_system = Class.new do
def read_template_file(template_path)
"-{% include 'loop' %}"
end
end
Liquid::Template.file_system = infinite_file_system.new
assert_raise(Liquid::StackLevelError) do
Template.parse("{% include 'loop' %}").render!
end
end
def test_dynamically_choosen_template
assert_equal "Test123", Template.parse("{% include template %}").render("template" => 'Test123')
assert_equal "Test321", Template.parse("{% include template %}").render("template" => 'Test321')
assert_equal "Product: Draft 151cm ", Template.parse("{% include template for product %}").render("template" => 'product', 'product' => { 'title' => 'Draft 151cm'})
end
end # IncludeTagTest

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env ruby require 'test_helper'
require File.dirname(__FILE__) + '/helper'
class TestClassA class TestClassA
liquid_methods :allowedA, :chainedB liquid_methods :allowedA, :chainedB
@@ -84,6 +83,5 @@ class ModuleExTest < Test::Unit::TestCase
assert_equal 'another_allowedC', Liquid::Template.parse("{{ a.chainedB.chainedC.another_allowedC }}").render('a'=>@a) assert_equal 'another_allowedC', Liquid::Template.parse("{{ a.chainedB.chainedC.another_allowedC }}").render('a'=>@a)
assert_equal '', Liquid::Template.parse("{{ a.restricted }}").render('a'=>@a) assert_equal '', Liquid::Template.parse("{{ a.restricted }}").render('a'=>@a)
assert_equal '', Liquid::Template.parse("{{ a.unknown }}").render('a'=>@a) assert_equal '', Liquid::Template.parse("{{ a.unknown }}").render('a'=>@a)
end end
end # ModuleExTest
end

View File

@@ -1,8 +1,6 @@
#!/usr/bin/env ruby require 'test_helper'
require File.dirname(__FILE__) + '/helper'
module FunnyFilter module FunnyFilter
def make_funny(input) def make_funny(input)
'LOL' 'LOL'
end end
@@ -26,8 +24,8 @@ module FunnyFilter
def link_to(name, url) def link_to(name, url)
%|<a href="#{url}">#{name}</a>| %|<a href="#{url}">#{name}</a>|
end end
end
end
class OutputTest < Test::Unit::TestCase class OutputTest < Test::Unit::TestCase
include Liquid include Liquid
@@ -37,7 +35,6 @@ class OutputTest < Test::Unit::TestCase
'best_cars' => 'bmw', 'best_cars' => 'bmw',
'car' => {'bmw' => 'good', 'gm' => 'bad'} 'car' => {'bmw' => 'good', 'gm' => 'bad'}
} }
end end
def test_variable def test_variable
@@ -116,6 +113,4 @@ class OutputTest < Test::Unit::TestCase
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

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env ruby require 'test_helper'
require File.dirname(__FILE__) + '/helper'
class ParsingQuirksTest < Test::Unit::TestCase class ParsingQuirksTest < Test::Unit::TestCase
include Liquid include Liquid
@@ -50,5 +49,4 @@ class ParsingQuirksTest < Test::Unit::TestCase
markup = "false || true" markup = "false || true"
assert_template_result('',"{% if #{markup} %} YES {% endif %}") assert_template_result('',"{% if #{markup} %} YES {% endif %}")
end end
end # ParsingQuirksTest
end

View File

@@ -1,4 +1,4 @@
require File.dirname(__FILE__) + '/helper' require 'test_helper'
class RegexpTest < Test::Unit::TestCase class RegexpTest < Test::Unit::TestCase
include Liquid include Liquid
@@ -34,12 +34,16 @@ class RegexpTest < Test::Unit::TestCase
end end
def test_variable_parser def test_variable_parser
assert_equal ['var'], 'var'.scan(VariableParser) assert_equal ['var'], 'var'.scan(VariableParser)
assert_equal ['var', 'method'], 'var.method'.scan(VariableParser) assert_equal ['var', 'method'], 'var.method'.scan(VariableParser)
assert_equal ['var', '[method]'], 'var[method]'.scan(VariableParser) assert_equal ['var', '[method]'], 'var[method]'.scan(VariableParser)
assert_equal ['var', '[method]', '[0]'], 'var[method][0]'.scan(VariableParser) assert_equal ['var', '[method]', '[0]'], 'var[method][0]'.scan(VariableParser)
assert_equal ['var', '["method"]', '[0]'], 'var["method"][0]'.scan(VariableParser) assert_equal ['var', '["method"]', '[0]'], 'var["method"][0]'.scan(VariableParser)
assert_equal ['var', '[method]', '[0]', 'method'], 'var[method][0].method'.scan(VariableParser) assert_equal ['var', '[method]', '[0]', 'method'], 'var[method][0].method'.scan(VariableParser)
end end
end def test_literal_shorthand_regexp
assert_equal [["{% if 'gnomeslab' contains 'liquid' %}yes{% endif %}"]],
"{{{ {% if 'gnomeslab' contains 'liquid' %}yes{% endif %} }}}".scan(LiteralShorthand)
end
end # RegexpTest

View File

@@ -1,4 +1,4 @@
require File.dirname(__FILE__) + '/helper' require 'test_helper'
module SecurityFilter module SecurityFilter
def add_one(input) def add_one(input)
@@ -38,4 +38,4 @@ class SecurityTest < Test::Unit::TestCase
assert_equal expected, Template.parse(text).render(@assigns, :filters => SecurityFilter) assert_equal expected, Template.parse(text).render(@assigns, :filters => SecurityFilter)
end end
end end # SecurityTest

View File

@@ -1,12 +1,9 @@
#!/usr/bin/env ruby require 'test_helper'
require File.dirname(__FILE__) + '/helper'
class Filters class Filters
include Liquid::StandardFilters include Liquid::StandardFilters
end end
class StandardFiltersTest < Test::Unit::TestCase class StandardFiltersTest < Test::Unit::TestCase
include Liquid include Liquid
@@ -47,6 +44,10 @@ class StandardFiltersTest < Test::Unit::TestCase
assert_equal '&lt;strong&gt;', @filters.h('<strong>') assert_equal '&lt;strong&gt;', @filters.h('<strong>')
end end
def test_escape_once
assert_equal '&lt;strong&gt;', @filters.escape_once(@filters.escape('<strong>'))
end
def test_truncatewords def test_truncatewords
assert_equal 'one two three', @filters.truncatewords('one two three', 4) assert_equal 'one two three', @filters.truncatewords('one two three', 4)
assert_equal 'one two...', @filters.truncatewords('one two three', 2) assert_equal 'one two...', @filters.truncatewords('one two three', 2)
@@ -143,14 +144,20 @@ class StandardFiltersTest < Test::Unit::TestCase
def test_times def test_times
assert_template_result "12", "{{ 3 | times:4 }}" assert_template_result "12", "{{ 3 | times:4 }}"
assert_template_result "0", "{{ 'foo' | times:4 }}" assert_template_result "0", "{{ 'foo' | times:4 }}"
assert_template_result "6.3", "{{ '2.1' | times:3 }}"
# Ruby v1.9.2-rc1, or higher, backwards compatible Float test
assert_match(/(6\.3)|(6\.(0{13})1)/, Template.parse("{{ '2.1' | times:3 }}").render)
assert_template_result "6", "{{ '2.1' | times:3 | replace: '.','-' | plus:0}}" assert_template_result "6", "{{ '2.1' | times:3 | replace: '.','-' | plus:0}}"
end end
def test_divided_by def test_divided_by
assert_template_result "4", "{{ 12 | divided_by:3 }}" assert_template_result "4", "{{ 12 | divided_by:3 }}"
assert_template_result "4", "{{ 14 | divided_by:3 }}" assert_template_result "4", "{{ 14 | divided_by:3 }}"
assert_template_result "4.66666666666667", "{{ 14 | divided_by:'3.0' }}"
# Ruby v1.9.2-rc1, or higher, backwards compatible Float test
assert_match(/4\.(6{13,14})7/, Template.parse("{{ 14 | divided_by:'3.0' }}").render)
assert_template_result "5", "{{ 15 | divided_by:3 }}" assert_template_result "5", "{{ 15 | divided_by:3 }}"
assert_template_result "Liquid error: divided by 0", "{{ 5 | divided_by:0 }}" assert_template_result "Liquid error: divided by 0", "{{ 5 | divided_by:0 }}"
end end
@@ -170,6 +177,4 @@ class StandardFiltersTest < Test::Unit::TestCase
def test_cannot_access_private_methods def test_cannot_access_private_methods
assert_template_result('a',"{{ 'a' | to_number }}") assert_template_result('a',"{{ 'a' | to_number }}")
end end
end # StandardFiltersTest
end

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env ruby require 'test_helper'
require File.dirname(__FILE__) + '/helper'
class StrainerTest < Test::Unit::TestCase class StrainerTest < Test::Unit::TestCase
include Liquid include Liquid
@@ -18,4 +17,9 @@ class StrainerTest < Test::Unit::TestCase
assert_equal true, strainer.respond_to?('size', false) assert_equal true, strainer.respond_to?('size', false)
end end
end # Asserts that Object#respond_to_missing? is not being undefined in Ruby versions where it has been implemented
# Currently this method is only present in Ruby v1.9.2, or higher
def test_object_respond_to_missing
assert_equal Object.respond_to?(:respond_to_missing?), Strainer.create(nil).respond_to?(:respond_to_missing?)
end
end # StrainerTest

View File

@@ -1,4 +1,4 @@
require File.dirname(__FILE__) + '/helper' require 'test_helper'
class IfElseTest < Test::Unit::TestCase class IfElseTest < Test::Unit::TestCase
include Liquid include Liquid
@@ -6,7 +6,7 @@ class IfElseTest < Test::Unit::TestCase
def test_if def test_if
assert_template_result(' ',' {% if false %} this text should not go into the output {% endif %} ') assert_template_result(' ',' {% if false %} this text should not go into the output {% endif %} ')
assert_template_result(' this text should go into the output ', assert_template_result(' this text should go into the output ',
' {% if true %} this text should go into the output {% endif %} ') ' {% if true %} this text should go into the output {% endif %} ')
assert_template_result(' you rock ?','{% if false %} you suck {% endif %} {% if true %} you rock {% endif %}?') assert_template_result(' you rock ?','{% if false %} you suck {% endif %} {% if true %} you rock {% endif %}?')
end end
@@ -150,4 +150,11 @@ class IfElseTest < Test::Unit::TestCase
ensure ensure
Condition.operators.delete 'contains' Condition.operators.delete 'contains'
end end
end
def test_operators_are_ignored_unless_isolated
Condition.operators['contains'] = :[]
assert_template_result('yes',
%({% if 'gnomeslab-and-or-liquid' contains 'gnomeslab-and-or-liquid' %}yes{% endif %}))
end
end # IfElseTest

View File

@@ -0,0 +1,39 @@
require 'test_helper'
class LiteralTagTest < Test::Unit::TestCase
include Liquid
def test_empty_literal
assert_template_result '', '{% literal %}{% endliteral %}'
assert_template_result '', '{{{}}}'
end
def test_simple_literal_value
assert_template_result 'howdy',
'{% literal %}howdy{% endliteral %}'
end
def test_literals_ignore_liquid_markup
expected = %({% if 'gnomeslab' contain 'liquid' %}yes{ % endif %})
template = %({% literal %}#{expected}{% endliteral %})
assert_template_result expected, template
end
def test_shorthand_syntax
expected = %({% if 'gnomeslab' contain 'liquid' %}yes{ % endif %})
template = %({{{#{expected}}}})
assert_template_result expected, template
end
# Class methods
def test_from_shorthand
assert_equal '{% literal %}gnomeslab{% endliteral %}', Liquid::Literal.from_shorthand('{{{gnomeslab}}}')
end
def test_from_shorthand_ignores_improper_syntax
text = "{% if 'hi' == 'hi' %}hi{% endif %}"
assert_equal text, Liquid::Literal.from_shorthand(text)
end
end # AssignTest

View File

@@ -1,10 +1,8 @@
require File.dirname(__FILE__) + '/helper' require 'test_helper'
class StandardTagTest < Test::Unit::TestCase class StandardTagTest < Test::Unit::TestCase
include Liquid include Liquid
def test_tag def test_tag
tag = Tag.new('tag', [], []) tag = Tag.new('tag', [], [])
assert_equal 'liquid::tag', tag.name assert_equal 'liquid::tag', tag.name
@@ -14,6 +12,7 @@ class StandardTagTest < Test::Unit::TestCase
def test_no_transform def test_no_transform
assert_template_result('this text should come out of the template without change...', assert_template_result('this text should come out of the template without change...',
'this text should come out of the template without change...') 'this text should come out of the template without change...')
assert_template_result('blah','blah') assert_template_result('blah','blah')
assert_template_result('<blah>','<blah>') assert_template_result('<blah>','<blah>')
assert_template_result('|,.:','|,.:') assert_template_result('|,.:','|,.:')
@@ -85,43 +84,48 @@ HERE
def test_for_helpers def test_for_helpers
assigns = {'array' => [1,2,3] } assigns = {'array' => [1,2,3] }
assert_template_result(' 1/3 2/3 3/3 ','{%for item in array%} {{forloop.index}}/{{forloop.length}} {%endfor%}',assigns) assert_template_result(' 1/3 2/3 3/3 ',
assert_template_result(' 1 2 3 ','{%for item in array%} {{forloop.index}} {%endfor%}',assigns) '{%for item in array%} {{forloop.index}}/{{forloop.length}} {%endfor%}',
assert_template_result(' 0 1 2 ','{%for item in array%} {{forloop.index0}} {%endfor%}',assigns) assigns)
assert_template_result(' 2 1 0 ','{%for item in array%} {{forloop.rindex0}} {%endfor%}',assigns) assert_template_result(' 1 2 3 ', '{%for item in array%} {{forloop.index}} {%endfor%}', assigns)
assert_template_result(' 3 2 1 ','{%for item in array%} {{forloop.rindex}} {%endfor%}',assigns) assert_template_result(' 0 1 2 ', '{%for item in array%} {{forloop.index0}} {%endfor%}', assigns)
assert_template_result(' true false false ','{%for item in array%} {{forloop.first}} {%endfor%}',assigns) assert_template_result(' 2 1 0 ', '{%for item in array%} {{forloop.rindex0}} {%endfor%}', assigns)
assert_template_result(' false false true ','{%for item in array%} {{forloop.last}} {%endfor%}',assigns) assert_template_result(' 3 2 1 ', '{%for item in array%} {{forloop.rindex}} {%endfor%}', assigns)
assert_template_result(' true false false ', '{%for item in array%} {{forloop.first}} {%endfor%}', assigns)
assert_template_result(' false false true ', '{%for item in array%} {{forloop.last}} {%endfor%}', assigns)
end end
def test_for_and_if def test_for_and_if
assigns = {'array' => [1,2,3] } assigns = {'array' => [1,2,3] }
assert_template_result('+--', '{%for item in array%}{% if forloop.first %}+{% else %}-{% endif %}{%endfor%}', assigns) assert_template_result('+--',
'{%for item in array%}{% if forloop.first %}+{% else %}-{% endif %}{%endfor%}',
assigns)
end end
def test_limiting def test_limiting
assigns = {'array' => [1,2,3,4,5,6,7,8,9,0]} assigns = {'array' => [1,2,3,4,5,6,7,8,9,0]}
assert_template_result('12','{%for i in array limit:2 %}{{ i }}{%endfor%}',assigns) assert_template_result('12', '{%for i in array limit:2 %}{{ i }}{%endfor%}', assigns)
assert_template_result('1234','{%for i in array limit:4 %}{{ i }}{%endfor%}',assigns) assert_template_result('1234', '{%for i in array limit:4 %}{{ i }}{%endfor%}', assigns)
assert_template_result('3456','{%for i in array limit:4 offset:2 %}{{ i }}{%endfor%}',assigns) assert_template_result('3456', '{%for i in array limit:4 offset:2 %}{{ i }}{%endfor%}', assigns)
assert_template_result('3456','{%for i in array limit: 4 offset: 2 %}{{ i }}{%endfor%}',assigns) assert_template_result('3456', '{%for i in array limit: 4 offset: 2 %}{{ i }}{%endfor%}', assigns)
end end
def test_dynamic_variable_limiting def test_dynamic_variable_limiting
assigns = {'array' => [1,2,3,4,5,6,7,8,9,0]} assigns = {'array' => [1,2,3,4,5,6,7,8,9,0]}
assigns['limit'] = 2 assigns['limit'] = 2
assigns['offset'] = 2 assigns['offset'] = 2
assert_template_result('34','{%for i in array limit: limit offset: offset %}{{ i }}{%endfor%}',assigns)
assert_template_result('34', '{%for i in array limit: limit offset: offset %}{{ i }}{%endfor%}', assigns)
end end
def test_nested_for def test_nested_for
assigns = {'array' => [[1,2],[3,4],[5,6]] } assigns = {'array' => [[1,2],[3,4],[5,6]] }
assert_template_result('123456','{%for item in array%}{%for i in item%}{{ i }}{%endfor%}{%endfor%}',assigns) assert_template_result('123456', '{%for item in array%}{%for i in item%}{{ i }}{%endfor%}{%endfor%}', assigns)
end end
def test_offset_only def test_offset_only
assigns = {'array' => [1,2,3,4,5,6,7,8,9,0]} assigns = {'array' => [1,2,3,4,5,6,7,8,9,0]}
assert_template_result('890','{%for i in array offset:7 %}{{ i }}{%endfor%}',assigns) assert_template_result('890', '{%for i in array offset:7 %}{{ i }}{%endfor%}', assigns)
end end
def test_pause_resume def test_pause_resume
@@ -199,86 +203,131 @@ HERE
def test_assign def test_assign
assigns = {'var' => 'content' } assigns = {'var' => 'content' }
assert_template_result('var2: var2:content','var2:{{var2}} {%assign var2 = var%} var2:{{var2}}',assigns) assert_template_result('var2: var2:content', 'var2:{{var2}} {%assign var2 = var%} var2:{{var2}}', assigns)
end end
def test_hyphenated_assign def test_hyphenated_assign
assigns = {'a-b' => '1' } assigns = {'a-b' => '1' }
assert_template_result('a-b:1 a-b:2','a-b:{{a-b}} {%assign a-b = 2 %}a-b:{{a-b}}',assigns) assert_template_result('a-b:1 a-b:2', 'a-b:{{a-b}} {%assign a-b = 2 %}a-b:{{a-b}}', assigns)
end end
def test_assign_with_colon_and_spaces def test_assign_with_colon_and_spaces
assigns = {'var' => {'a:b c' => {'paged' => '1' }}} assigns = {'var' => {'a:b c' => {'paged' => '1' }}}
assert_template_result('var2: 1','{%assign var2 = var["a:b c"].paged %}var2: {{var2}}',assigns) assert_template_result('var2: 1', '{%assign var2 = var["a:b c"].paged %}var2: {{var2}}', assigns)
end end
def test_capture def test_capture
assigns = {'var' => 'content' } assigns = {'var' => 'content' }
assert_template_result('content foo content foo ','{{ var2 }}{% capture var2 %}{{ var }} foo {% endcapture %}{{ var2 }}{{ var2 }}', assigns) assert_template_result('content foo content foo ',
'{{ var2 }}{% capture var2 %}{{ var }} foo {% endcapture %}{{ var2 }}{{ var2 }}',
assigns)
end end
def test_capture_detects_bad_syntax def test_capture_detects_bad_syntax
assert_raise(SyntaxError) do assert_raise(SyntaxError) do
assert_template_result('content foo content foo ','{{ var2 }}{% capture %}{{ var }} foo {% endcapture %}{{ var2 }}{{ var2 }}', {'var' => 'content' }) assert_template_result('content foo content foo ',
'{{ var2 }}{% capture %}{{ var }} foo {% endcapture %}{{ var2 }}{{ var2 }}',
{'var' => 'content' })
end end
end end
def test_case def test_case
assigns = {'condition' => 2 } assigns = {'condition' => 2 }
assert_template_result(' its 2 ','{% case condition %}{% when 1 %} its 1 {% when 2 %} its 2 {% endcase %}', assigns) assert_template_result(' its 2 ',
'{% case condition %}{% when 1 %} its 1 {% when 2 %} its 2 {% endcase %}',
assigns)
assigns = {'condition' => 1 } assigns = {'condition' => 1 }
assert_template_result(' its 1 ','{% case condition %}{% when 1 %} its 1 {% when 2 %} its 2 {% endcase %}', assigns) assert_template_result(' its 1 ',
'{% case condition %}{% when 1 %} its 1 {% when 2 %} its 2 {% endcase %}',
assigns)
assigns = {'condition' => 3 } assigns = {'condition' => 3 }
assert_template_result('','{% case condition %}{% when 1 %} its 1 {% when 2 %} its 2 {% endcase %}', assigns) assert_template_result('',
'{% case condition %}{% when 1 %} its 1 {% when 2 %} its 2 {% endcase %}',
assigns)
assigns = {'condition' => "string here" } assigns = {'condition' => "string here" }
assert_template_result(' hit ','{% case condition %}{% when "string here" %} hit {% endcase %}', assigns) assert_template_result(' hit ',
'{% case condition %}{% when "string here" %} hit {% endcase %}',
assigns)
assigns = {'condition' => "bad string here" } assigns = {'condition' => "bad string here" }
assert_template_result('','{% case condition %}{% when "string here" %} hit {% endcase %}', assigns) assert_template_result('',
'{% case condition %}{% when "string here" %} hit {% endcase %}',\
assigns)
end end
def test_case_with_else def test_case_with_else
assigns = {'condition' => 5 } assigns = {'condition' => 5 }
assert_template_result(' hit ','{% case condition %}{% when 5 %} hit {% else %} else {% endcase %}', assigns) assert_template_result(' hit ',
'{% case condition %}{% when 5 %} hit {% else %} else {% endcase %}',
assigns)
assigns = {'condition' => 6 } assigns = {'condition' => 6 }
assert_template_result(' else ','{% case condition %}{% when 5 %} hit {% else %} else {% endcase %}', assigns) assert_template_result(' else ',
'{% case condition %}{% when 5 %} hit {% else %} else {% endcase %}',
assigns)
assigns = {'condition' => 6 } assigns = {'condition' => 6 }
assert_template_result(' else ','{% case condition %} {% when 5 %} hit {% else %} else {% endcase %}', assigns) assert_template_result(' else ',
'{% case condition %} {% when 5 %} hit {% else %} else {% endcase %}',
assigns)
end end
def test_case_on_size def test_case_on_size
assert_template_result('', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => []) assert_template_result('', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [])
assert_template_result('1' , '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [1]) assert_template_result('1', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [1])
assert_template_result('2' , '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [1, 1]) assert_template_result('2', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [1, 1])
assert_template_result('', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [1, 1, 1]) assert_template_result('', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [1, 1, 1])
assert_template_result('', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [1, 1, 1, 1]) assert_template_result('', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [1, 1, 1, 1])
assert_template_result('', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [1, 1, 1, 1, 1]) assert_template_result('', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [1, 1, 1, 1, 1])
end end
def test_case_on_size_with_else def test_case_on_size_with_else
assert_template_result('else', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}', 'a' => []) assert_template_result('else',
assert_template_result('1', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}', 'a' => [1]) '{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
assert_template_result('2', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}', 'a' => [1, 1]) 'a' => [])
assert_template_result('else', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}', 'a' => [1, 1, 1])
assert_template_result('else', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}', 'a' => [1, 1, 1, 1]) assert_template_result('1',
assert_template_result('else', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}', 'a' => [1, 1, 1, 1, 1]) '{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
'a' => [1])
assert_template_result('2',
'{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
'a' => [1, 1])
assert_template_result('else',
'{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
'a' => [1, 1, 1])
assert_template_result('else',
'{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
'a' => [1, 1, 1, 1])
assert_template_result('else',
'{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
'a' => [1, 1, 1, 1, 1])
end end
def test_case_on_length_with_else def test_case_on_length_with_else
assert_template_result('else', '{% case a.empty? %}{% when true %}true{% when false %}false{% else %}else{% endcase %}', {}) assert_template_result('else',
assert_template_result('false', '{% case false %}{% when true %}true{% when false %}false{% else %}else{% endcase %}', {}) '{% case a.empty? %}{% when true %}true{% when false %}false{% else %}else{% endcase %}',
assert_template_result('true', '{% case true %}{% when true %}true{% when false %}false{% else %}else{% endcase %}', {}) {})
assert_template_result('else', '{% case NULL %}{% when true %}true{% when false %}false{% else %}else{% endcase %}', {})
assert_template_result('false',
'{% case false %}{% when true %}true{% when false %}false{% else %}else{% endcase %}',
{})
assert_template_result('true',
'{% case true %}{% when true %}true{% when false %}false{% else %}else{% endcase %}',
{})
assert_template_result('else',
'{% case NULL %}{% when true %}true{% when false %}false{% else %}else{% endcase %}',
{})
end end
def test_assign_from_case def test_assign_from_case
@@ -331,7 +380,8 @@ HERE
end end
def test_assign_is_global def test_assign_is_global
assert_equal 'variable', Liquid::Template.parse( '{%for i in (1..2) %}{% assign a = "variable"%}{% endfor %}{{a}}' ).render 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
@@ -345,31 +395,31 @@ HERE
end end
def test_cycle def test_cycle
assert_template_result('one','{%cycle "one", "two"%}') assert_template_result('one','{%cycle "one", "two"%}')
assert_template_result('one two','{%cycle "one", "two"%} {%cycle "one", "two"%}') assert_template_result('one two','{%cycle "one", "two"%} {%cycle "one", "two"%}')
assert_template_result(' two','{%cycle "", "two"%} {%cycle "", "two"%}') assert_template_result(' two','{%cycle "", "two"%} {%cycle "", "two"%}')
assert_template_result('one two one','{%cycle "one", "two"%} {%cycle "one", "two"%} {%cycle "one", "two"%}') assert_template_result('one two one','{%cycle "one", "two"%} {%cycle "one", "two"%} {%cycle "one", "two"%}')
assert_template_result('text-align: left text-align: right','{%cycle "text-align: left", "text-align: right" %} {%cycle "text-align: left", "text-align: right"%}') assert_template_result('text-align: left text-align: right',
'{%cycle "text-align: left", "text-align: right" %} {%cycle "text-align: left", "text-align: right"%}')
end end
def test_multiple_cycles def test_multiple_cycles
assert_template_result('1 2 1 1 2 3 1','{%cycle 1,2%} {%cycle 1,2%} {%cycle 1,2%} {%cycle 1,2,3%} {%cycle 1,2,3%} {%cycle 1,2,3%} {%cycle 1,2,3%}') assert_template_result('1 2 1 1 2 3 1',
'{%cycle 1,2%} {%cycle 1,2%} {%cycle 1,2%} {%cycle 1,2,3%} {%cycle 1,2,3%} {%cycle 1,2,3%} {%cycle 1,2,3%}')
end end
def test_multiple_named_cycles def test_multiple_named_cycles
assert_template_result('one one two two one one','{%cycle 1: "one", "two" %} {%cycle 2: "one", "two" %} {%cycle 1: "one", "two" %} {%cycle 2: "one", "two" %} {%cycle 1: "one", "two" %} {%cycle 2: "one", "two" %}') assert_template_result('one one two two one one',
'{%cycle 1: "one", "two" %} {%cycle 2: "one", "two" %} {%cycle 1: "one", "two" %} {%cycle 2: "one", "two" %} {%cycle 1: "one", "two" %} {%cycle 2: "one", "two" %}')
end end
def test_multiple_named_cycles_with_names_from_context def test_multiple_named_cycles_with_names_from_context
assigns = {"var1" => 1, "var2" => 2 } assigns = {"var1" => 1, "var2" => 2 }
assert_template_result('one one two two one one','{%cycle var1: "one", "two" %} {%cycle var2: "one", "two" %} {%cycle var1: "one", "two" %} {%cycle var2: "one", "two" %} {%cycle var1: "one", "two" %} {%cycle var2: "one", "two" %}', assigns) assert_template_result('one one two two one one',
'{%cycle var1: "one", "two" %} {%cycle var2: "one", "two" %} {%cycle var1: "one", "two" %} {%cycle var2: "one", "two" %} {%cycle var1: "one", "two" %} {%cycle var2: "one", "two" %}', assigns)
end end
def test_size_of_array def test_size_of_array
@@ -383,10 +433,10 @@ HERE
end end
def test_illegal_symbols def test_illegal_symbols
assert_template_result('', '{% if true == empty %}?{% endif %}', {}) assert_template_result('', '{% if true == empty %}?{% endif %}', {})
assert_template_result('', '{% if true == null %}?{% endif %}', {}) assert_template_result('', '{% if true == null %}?{% endif %}', {})
assert_template_result('', '{% if empty == true %}?{% endif %}', {}) assert_template_result('', '{% if empty == true %}?{% endif %}', {})
assert_template_result('', '{% if null == true %}?{% endif %}', {}) assert_template_result('', '{% if null == true %}?{% endif %}', {})
end end
def test_for_reversed def test_for_reversed
@@ -402,4 +452,4 @@ HERE
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
end end # StandardTagTest

View File

@@ -1,10 +1,8 @@
#!/usr/bin/env ruby require 'test_helper'
require File.dirname(__FILE__) + '/helper'
class StatementsTest < Test::Unit::TestCase class StatementsTest < Test::Unit::TestCase
include Liquid include Liquid
def test_true_eql_true def test_true_eql_true
text = %| {% if true == true %} true {% else %} false {% endif %} | text = %| {% if true == true %} true {% else %} false {% endif %} |
expected = %| true | expected = %| true |
@@ -133,5 +131,4 @@ class StatementsTest < Test::Unit::TestCase
expected = %| true | expected = %| true |
assert_equal expected, Template.parse(text).render('var' => 1 ) assert_equal expected, Template.parse(text).render('var' => 1 )
end end
end # StatementsTest
end

View File

@@ -1,4 +1,4 @@
require File.dirname(__FILE__) + '/helper' require 'test_helper'
class UnlessElseTest < Test::Unit::TestCase class UnlessElseTest < Test::Unit::TestCase
include Liquid include Liquid
@@ -6,7 +6,7 @@ class UnlessElseTest < Test::Unit::TestCase
def test_unless def test_unless
assert_template_result(' ',' {% unless true %} this text should not go into the output {% endunless %} ') assert_template_result(' ',' {% unless true %} this text should not go into the output {% endunless %} ')
assert_template_result(' this text should go into the output ', assert_template_result(' this text should go into the output ',
' {% unless false %} this text should go into the output {% endunless %} ') ' {% unless false %} this text should go into the output {% endunless %} ')
assert_template_result(' you rock ?','{% unless true %} you suck {% endunless %} {% unless false %} you rock {% endunless %}?') assert_template_result(' you rock ?','{% unless true %} you suck {% endunless %} {% unless false %} you rock {% endunless %}?')
end end
@@ -23,5 +23,4 @@ class UnlessElseTest < Test::Unit::TestCase
def test_unless_else_in_loop def test_unless_else_in_loop
assert_template_result ' TRUE 2 3 ', '{% for i in choices %}{% unless i %} {{ forloop.index }} {% else %} TRUE {% endunless %}{% endfor %}', 'choices' => [1, nil, false] assert_template_result ' TRUE 2 3 ', '{% for i in choices %}{% unless i %} {{ forloop.index }} {% else %} TRUE {% endunless %}{% endfor %}', 'choices' => [1, nil, false]
end end
end # UnlessElseTest
end

View File

@@ -1,4 +1,4 @@
require File.dirname(__FILE__) + '/helper' require 'test_helper'
class TemplateTest < Test::Unit::TestCase class TemplateTest < Test::Unit::TestCase
include Liquid include Liquid
@@ -71,5 +71,4 @@ class TemplateTest < Test::Unit::TestCase
assert_equal '1', t.render(assigns) assert_equal '1', t.render(assigns)
@global = nil @global = nil
end end
end # TemplateTest
end

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env ruby require 'test_helper'
require File.dirname(__FILE__) + '/helper'
class VariableTest < Test::Unit::TestCase class VariableTest < Test::Unit::TestCase
include Liquid include Liquid
@@ -168,5 +167,4 @@ class VariableResolutionTest < Test::Unit::TestCase
} }
assert_equal "Unknown variable 'test'", e.message assert_equal "Unknown variable 'test'", e.message
end end
end # VariableTest
end

View File

@@ -1,20 +1,34 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
$LOAD_PATH.unshift(File.dirname(__FILE__)+ '/extra') extras_path = File.join File.dirname(__FILE__), 'extra'
$LOAD_PATH.unshift(extras_path) unless $LOAD_PATH.include? extras_path
require 'rubygems' unless RUBY_VERSION =~ /^(?:1.9.*)$/
require 'test/unit' require 'test/unit'
require 'test/unit/assertions' require 'test/unit/assertions'
require 'caller' require 'caller'
require 'breakpoint' require 'breakpoint'
require File.dirname(__FILE__) + '/../lib/liquid' require 'ruby-debug'
require File.join File.dirname(__FILE__), '..', 'lib', 'liquid'
module Test module Test
module Unit module Unit
module Assertions module Assertions
include Liquid include Liquid
def assert_template_result(expected, template, assigns={}, message=nil)
assert_equal expected, Template.parse(template).render(assigns) def assert_template_result(expected, template, assigns = {}, message = nil)
end assert_equal expected, Template.parse(template).render(assigns)
end end
end
end def assert_template_result_matches(expected, template, assigns = {}, message = nil)
return assert_template_result(expected, template, assigns, message) unless expected.is_a? Regexp
assert_match expected, Template.parse(template).render(assigns)
end
end # Assertions
end # Unit
end # Test