Compare commits

..

40 Commits

Author SHA1 Message Date
Jonathan Rudenberg
a8e9327f0b Update HISTORY.md for v2.4.0 release 2012-08-07 09:32:38 -04:00
Dylan Smith
f5a20ff8e8 Fix a regression in tablerow limit parameter.
I had accidentally read slice_collection_using_each as using to as an
inclusive limit rather than exclusive, and no tests covered the offset or
limit parameters.
2012-06-21 14:56:05 -04:00
Dylan Smith
d0184555d9 Allow tablerow to work with any Enumerable. Closes #132 2012-06-20 11:07:11 -04:00
Jason Normore
6ebdded8f2 Merge branch 'issue_1650_strip_html_ignore_comments' 2012-06-11 10:33:00 -04:00
Jason Normore
515b31158e strip_html to ignore comments with html tags. fixes #1650 2012-06-11 10:32:12 -04:00
7rans
40cc799f3d Add example to split filter. 2012-06-11 10:32:12 -04:00
Daniel Schierbeck
5ac91e0837 Fix typo and add punctuation 2012-06-11 10:32:12 -04:00
Jonathan Rudenberg
f6cb54fa59 Merge pull request #93 from trans/master
Split Filter Example
2012-06-07 12:21:40 -07:00
Jonathan Rudenberg
1606b4b705 Merge pull request #118 from dasch/patch-1
Fix typo and add punctuation
2012-06-07 12:20:54 -07:00
Jonathan Rudenberg
7cfd0f15d1 Merge pull request #128 from andmej/patch-1
Tpyo.
2012-06-07 12:18:09 -07:00
Jonathan Rudenberg
25ba54fc52 Enable 19mode for travis rbx/jruby 2012-06-07 15:16:58 -04:00
Jonathan Rudenberg
1aff63ff57 Merge pull request #107 from amateurhuman/syntax-error-fixes-for-rubinius
Fix syntax error in htmltags.rb and for.rb for compatibility with rbx-2.0.0-dev (1.9.3)
2012-06-07 12:14:35 -07:00
Jonathan Rudenberg
08fdcbbf65 Merge pull request #120 from infospace/interpolate_regex_once
add interpolate once flag to regexes that never change
2012-06-07 12:06:57 -07:00
Jonathan Rudenberg
2dba9ed0ea Merge pull request #113 from arika/improve-process-time
apply "o" option to regexps to improve process time
2012-06-07 12:04:28 -07:00
Andrés Mejía
6d02d59fbd Tpyo. 2012-06-06 22:32:42 -05:00
Michael Green
281e3ea9c8 add interpolate once flag to regexes that never change 2012-05-08 16:27:50 -07:00
Daniel Schierbeck
b51b30fac1 Fix typo and add punctuation 2012-05-03 14:16:06 +03:00
akira yamada
84ed3d9964 apply "o" option to regexps to improve process time 2012-04-02 19:31:55 +09:00
Dennis Theisen
c10f936d2a Merge pull request #109 from DanAtkinson/patch-1
Minor modification to readme
2012-03-14 08:27:43 -07:00
Dan Atkinson
1ee342d83b * Seperated 'Howto' into 'How to'.
* Added periods to the second list as the first item has them. I guess I'm anally retentive like that. :)
2012-03-14 14:58:12 +00:00
Dennis Theisen
0e3b522fe2 Fix conditions using negative number comparisons 2012-03-12 16:38:34 -04:00
Chris Kelly
db07e2b67e Fix syntax error in for and htmltags files for compatibility with Rubinius 2.0.0-dev 2012-03-09 00:24:31 -08:00
Dennis Theisen
b8d7b9aeda Fix for-tag update to also work properly in Ruby 1.8.
* Follow up commit to 3d7c1c8
2012-02-29 16:13:46 -05:00
Dennis Theisen
3d7c1c80a0 Ruby 1.8 compatibility fix: Ensure for-loop on an empty string does not enter for-body. 2012-02-29 14:41:21 -05:00
Dennis Theisen
1b2d0198ea Added backwards compatibility test for tablerow tag update
* Follow up to 043d816
2012-02-22 14:30:19 -05:00
Dennis Theisen
043d816460 Fix tablerow block to work with collection names in quoted syntax.
* Allows e.g. {% tablerow product in collections['frontpage'] %} instead of only collections.frontpage
2012-02-22 12:45:38 -05:00
7rans
974ea40cca Add example to split filter. 2012-02-10 13:45:35 -05:00
Kristian PD
d8b416187a Merge pull request #88 from kristianpd/master
Add 1.9.3 support for 1.8.7 like strings behaviour in forloop
2012-01-31 10:27:44 -08:00
Kristian PD
58ad90677b Clarified compatibility comments, removed unused var from tag test. 2012-01-20 17:22:23 -05:00
Kristian PD
2b04590d4b Revert "Revert "Added backwards compatibility for 1.8.7 String.each returning itself (and 1.9.3 not supporting .each).""
This reverts commit bce0033c65.
2012-01-20 16:54:26 -05:00
Kristian PD
bce0033c65 Revert "Added backwards compatibility for 1.8.7 String.each returning itself (and 1.9.3 not supporting .each)."
This reverts commit 01dea94671.
2012-01-20 16:47:34 -05:00
Kristian PD
01dea94671 Added backwards compatibility for 1.8.7 String.each returning itself (and 1.9.3 not supporting .each).
Moved for tag tests to their own test model from StandardTagTest.
2012-01-20 16:45:41 -05:00
Tobias Lütke
1a1b4702d7 Merge pull request #80 from ROFISH/master
Add Filters to Assign
2011-12-20 13:58:05 -08:00
Steven Soroka
85d1dc0d07 Merge pull request #84 from biow0lf/master
Fix typo
2011-12-16 09:12:07 -08:00
Igor Zubkov
0eafe7f2fd Fix typo 2011-12-16 12:28:29 +02:00
Jonathan Rudenberg
815e4e2b8d Remove travis rbx-1.9 mode for now 2011-11-17 16:55:50 -05:00
Jonathan Rudenberg
ef98715b12 Update Travis rubies 2011-11-17 14:23:25 -05:00
Jonathan Rudenberg
c4d713b6bb Add modulo filter 2011-11-17 14:08:57 -05:00
Ryan Alyea
975b17b529 Allow filters in assign.
A simple attempt, but basically it shifts the parsing of the right hand side of the assign from a simple context lookup to using the Variable parser (that includes filters). Fixes #79.
2011-11-01 01:46:26 -07:00
Jonathan Rudenberg
745d875e79 Add date to History.md 2011-10-16 10:22:20 -04:00
25 changed files with 375 additions and 249 deletions

View File

@@ -1,10 +1,11 @@
rvm:
- 1.8.7
- 1.9.2
- 1.9.3
- ree
- jruby
- rbx
- rbx-2.0
- jruby-18mode
- jruby-19mode
- rbx-18mode
- rbx-19mode
script: "rake test"

View File

@@ -1,6 +1,17 @@
# Liquid Version History
## 2.3.0
## 2.4.0 / 2012-08-03
* Performance improvements
* Allow filters in `assign`
* Add `modulo` filter
* Ruby 1.8, 1.9, and Rubinius compatibility fixes
* Add support for `quoted['references']` in `tablerow`
* Add support for Enumerable to `tablerow`
* `strip\_html` filter removes html comments
## 2.3.0 / 2011-10-16
* Several speed/memory improvements
* Numerous bug fixes

View File

@@ -6,15 +6,15 @@ Liquid is a template engine which was written with 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.
* It has to be stateless. Compile and render steps have to be separate 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 you should 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
* 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?
@@ -31,7 +31,7 @@ Liquid is a template engine which was written with very specific requirements:
</ul>
```
## Howto use Liquid
## How to 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.

View File

@@ -32,17 +32,17 @@ module Liquid
VariableEnd = /\}\}/
VariableIncompleteEnd = /\}\}?/
QuotedString = /"[^"]*"|'[^']*'/
QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/
QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o
StrictQuotedFragment = /"[^"]+"|'[^']+'|[^\s|:,]+/
FirstFilterArgument = /#{FilterArgumentSeparator}(?:#{StrictQuotedFragment})/
OtherFilterArgument = /#{ArgumentSeparator}(?:#{StrictQuotedFragment})/
SpacelessFilter = /^(?:'[^']+'|"[^"]+"|[^'"])*#{FilterSeparator}(?:#{StrictQuotedFragment})(?:#{FirstFilterArgument}(?:#{OtherFilterArgument})*)?/
Expression = /(?:#{QuotedFragment}(?:#{SpacelessFilter})*)/
TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/
FirstFilterArgument = /#{FilterArgumentSeparator}(?:#{StrictQuotedFragment})/o
OtherFilterArgument = /#{ArgumentSeparator}(?:#{StrictQuotedFragment})/o
SpacelessFilter = /^(?:'[^']+'|"[^"]+"|[^'"])*#{FilterSeparator}(?:#{StrictQuotedFragment})(?:#{FirstFilterArgument}(?:#{OtherFilterArgument})*)?/o
Expression = /(?:#{QuotedFragment}(?:#{SpacelessFilter})*)/o
TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/o
AnyStartingTag = /\{\{|\{\%/
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/
VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/o
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/o
VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o
end
require 'liquid/drop'
@@ -60,6 +60,7 @@ require 'liquid/htmltags'
require 'liquid/standardfilters'
require 'liquid/condition'
require 'liquid/module_ex'
require 'liquid/utils'
# Load all the tags of the standard library
#

View File

@@ -1,10 +1,10 @@
module Liquid
class Block < Tag
IsTag = /^#{TagStart}/
IsVariable = /^#{VariableStart}/
FullToken = /^#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}$/
ContentOfVariable = /^#{VariableStart}(.*)#{VariableEnd}$/
IsTag = /^#{TagStart}/o
IsVariable = /^#{VariableStart}/o
FullToken = /^#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}$/o
ContentOfVariable = /^#{VariableStart}(.*)#{VariableEnd}$/o
def parse(tokens)
@nodelist ||= []

View File

@@ -136,11 +136,11 @@ module Liquid
$1
when /^"(.*)"$/ # Double quoted strings
$1
when /^(\d+)$/ # Integer and floats
when /^(-?\d+)$/ # Integer and floats
$1.to_i
when /^\((\S+)\.\.(\S+)\)$/ # Ranges
(resolve($1).to_i..resolve($2).to_i)
when /^(\d[\d\.]+)$/ # Floats
when /^(-?\d[\d\.]+)$/ # Floats
$1.to_f
else
variable(key)

View File

@@ -1,10 +1,10 @@
module Liquid
# A drop in liquid is a class which allows you to to export DOM like things to liquid.
# A drop in liquid is a class which allows you to export DOM like things to liquid.
# Methods of drops are callable.
# The main use for liquid drops is the implement lazy loaded objects.
# The main use for liquid drops is to implement lazy loaded objects.
# If you would like to make data available to the web designers which you don't want loaded unless needed then
# a drop is a great way to do that
# a drop is a great way to do that.
#
# Example:
#
@@ -18,7 +18,7 @@ module Liquid
# tmpl.render('product' => ProductDrop.new ) # will invoke top_sales query.
#
# Your drop can either implement the methods sans any parameters or implement the before_method(name) method which is a
# catch all
# catch all.
class Drop
attr_writer :context

View File

@@ -1,6 +1,6 @@
module Liquid
class TableRow < Block
Syntax = /(\w+)\s+in\s+(#{VariableSignature}+)/
Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o
def initialize(tag_name, markup, tokens)
if markup =~ Syntax
@@ -20,11 +20,10 @@ module Liquid
def render(context)
collection = context[@collection_name] or return ''
if @attributes['limit'] or @attributes['offset']
limit = context[@attributes['limit']] || -1
offset = context[@attributes['offset']] || 0
collection = collection[offset.to_i..(limit.to_i + offset.to_i - 1)]
end
from = @attributes['offset'] ? context[@attributes['offset']].to_i : 0
to = @attributes['limit'] ? from + context[@attributes['limit']].to_i : nil
collection = Utils.slice_collection_using_each(collection, from, to)
length = collection.length
@@ -46,7 +45,7 @@ module Liquid
'col0' => col,
'index0' => index,
'rindex' => length - index,
'rindex0' => length - index -1,
'rindex0' => length - index - 1,
'first' => (index == 0),
'last' => (index == length - 1),
'col_first' => (col == 0),

View File

@@ -54,12 +54,16 @@ module Liquid
end
# Split input string into an array of substrings separated by given pattern.
#
# Example:
# <div class="summary">{{ post | split '//' | first }}</div>
#
def split(input, pattern)
input.split(pattern)
end
def strip_html(input)
input.to_s.gsub(/<script.*?<\/script>/, '').gsub(/<.*?>/, '')
input.to_s.gsub(/<script.*?<\/script>/, '').gsub(/<!--.*?-->/, '').gsub(/<.*?>/, '')
end
# Remove all newlines from the string
@@ -218,6 +222,10 @@ module Liquid
to_number(input) / to_number(operand)
end
def modulo(input, operand)
to_number(input) % to_number(operand)
end
private
def to_number(obj)

View File

@@ -9,12 +9,12 @@ module Liquid
# {{ foo }}
#
class Assign < Tag
Syntax = /(#{VariableSignature}+)\s*=\s*(#{QuotedFragment}+)/
Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/o
def initialize(tag_name, markup, tokens)
if markup =~ Syntax
@to = $1
@from = $2
@from = Variable.new($2)
else
raise SyntaxError.new("Syntax Error in 'assign' - Valid syntax: assign [var] = [source]")
end
@@ -23,7 +23,7 @@ module Liquid
end
def render(context)
context.scopes.last[@to] = context[@from]
context.scopes.last[@to] = @from.render(context)
''
end

View File

@@ -1,7 +1,7 @@
module Liquid
class Case < Block
Syntax = /(#{QuotedFragment})/
WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/
Syntax = /(#{QuotedFragment})/o
WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/o
def initialize(tag_name, markup, tokens)
@blocks = []

View File

@@ -13,8 +13,8 @@ module Liquid
# <div class="green"> Item five</div>
#
class Cycle < Tag
SimpleSyntax = /^#{QuotedFragment}+/
NamedSyntax = /^(#{QuotedFragment})\s*\:\s*(.*)/
SimpleSyntax = /^#{QuotedFragment}+/o
NamedSyntax = /^(#{QuotedFragment})\s*\:\s*(.*)/o
def initialize(tag_name, markup, tokens)
case markup
@@ -48,7 +48,7 @@ module Liquid
def variables_from_string(markup)
markup.split(',').collect do |var|
var =~ /\s*(#{QuotedFragment})\s*/
var =~ /\s*(#{QuotedFragment})\s*/o
$1 ? $1 : nil
end.compact
end
@@ -56,4 +56,4 @@ module Liquid
end
Template.register_tag('cycle', Cycle)
end
end

View File

@@ -44,7 +44,7 @@ module Liquid
# forloop.last:: Returns true if the item is the last item.
#
class For < Block
Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/
Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
def initialize(tag_name, markup, tokens)
if markup =~ Syntax
@@ -75,7 +75,8 @@ module Liquid
collection = context[@collection_name]
collection = collection.to_a if collection.is_a?(Range)
return render_else(context) unless collection.respond_to?(:each)
# Maintains Ruby 1.8.7 String#each behaviour on 1.9
return render_else(context) unless iterable?(collection)
from = if @attributes['offset'] == 'continue'
context.registers[:for][@name].to_i
@@ -85,10 +86,10 @@ module Liquid
limit = context[@attributes['limit']]
to = limit ? limit.to_i + from : nil
segment = slice_collection_using_each(collection, from, to)
segment = Utils.slice_collection_using_each(collection, from, to)
return render_else(context) if segment.empty?
segment.reverse! if @reversed
@@ -109,7 +110,7 @@ module Liquid
'index' => index + 1,
'index0' => index,
'rindex' => length - index,
'rindex0' => length - index -1,
'rindex0' => length - index - 1,
'first' => (index == 0),
'last' => (index == length - 1) }
@@ -118,26 +119,6 @@ module Liquid
end
result
end
def slice_collection_using_each(collection, from, to)
segments = []
index = 0
yielded = 0
collection.each do |item|
if to && to <= index
break
end
if from <= index
segments << item
end
index += 1
end
segments
end
private
@@ -145,6 +126,9 @@ module Liquid
return @else_block ? [render_all(@else_block, context)] : ''
end
def iterable?(collection)
collection.respond_to?(:each) || Utils.non_blank_string?(collection)
end
end
Template.register_tag('for', For)

View File

@@ -13,8 +13,8 @@ module Liquid
#
class If < Block
SyntaxHelp = "Syntax Error in tag 'if' - Valid syntax: if [expression]"
Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/
ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/
Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/o
ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/o
def initialize(tag_name, markup, tokens)
@blocks = []

View File

@@ -1,6 +1,6 @@
module Liquid
class Include < Tag
Syntax = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?/
Syntax = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?/o
def initialize(tag_name, markup, tokens)
if markup =~ Syntax
@@ -62,4 +62,4 @@ module Liquid
end
Template.register_tag('include', Include)
end
end

31
lib/liquid/utils.rb Normal file
View File

@@ -0,0 +1,31 @@
module Liquid
module Utils
def self.slice_collection_using_each(collection, from, to)
segments = []
index = 0
yielded = 0
# Maintains Ruby 1.8.7 String#each behaviour on 1.9
return [collection] if non_blank_string?(collection)
collection.each do |item|
if to && to <= index
break
end
if from <= index
segments << item
end
index += 1
end
segments
end
def self.non_blank_string?(collection)
collection.is_a?(String) && collection != ''
end
end
end

View File

@@ -11,21 +11,21 @@ module Liquid
# {{ user | link }}
#
class Variable
FilterParser = /(?:#{FilterSeparator}|(?:\s*(?!(?:#{FilterSeparator}))(?:#{QuotedFragment}|\S+)\s*)+)/
FilterParser = /(?:#{FilterSeparator}|(?:\s*(?!(?:#{FilterSeparator}))(?:#{QuotedFragment}|\S+)\s*)+)/o
attr_accessor :filters, :name
def initialize(markup)
@markup = markup
@name = nil
@filters = []
if match = markup.match(/\s*(#{QuotedFragment})(.*)/)
if match = markup.match(/\s*(#{QuotedFragment})(.*)/o)
@name = match[1]
if match[2].match(/#{FilterSeparator}\s*(.*)/)
if match[2].match(/#{FilterSeparator}\s*(.*)/o)
filters = Regexp.last_match(1).scan(FilterParser)
filters.each do |f|
if matches = f.match(/\s*(\w+)/)
filtername = matches[1]
filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*(#{QuotedFragment})/).flatten
filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*(#{QuotedFragment})/o).flatten
@filters << [filtername.to_sym, filterargs]
end
end

View File

@@ -2,7 +2,7 @@
Gem::Specification.new do |s|
s.name = "liquid"
s.version = "2.3.0"
s.version = "2.4.0"
s.platform = Gem::Platform::RUBY
s.summary = "A secure, non-evaling end user template engine with aesthetic markup."
s.authors = ["Tobias Luetke"]

View File

@@ -12,4 +12,10 @@ class AssignTest < Test::Unit::TestCase
'{% assign foo = values %}.{{ foo[1] }}.',
'values' => %w{foo bar baz})
end
def test_assign_with_filter
assert_template_result('.bar.',
'{% assign foo = values | split: "," %}.{{ foo[1] }}.',
'values' => "foo,bar,baz")
end
end # AssignTest

View File

@@ -18,6 +18,11 @@ class ConditionTest < Test::Unit::TestCase
assert_evalutes_true '2', '>=', '1'
assert_evalutes_true '1', '<=', '2'
assert_evalutes_true '1', '<=', '1'
# negative numbers
assert_evalutes_true '1', '>', '-1'
assert_evalutes_true '-1', '<', '1'
assert_evalutes_true '1.0', '>', '-1.0'
assert_evalutes_true '-1.0', '<', '1.0'
end
def test_default_operators_evalute_false

View File

@@ -75,6 +75,12 @@ class FiltersTest < Test::Unit::TestCase
assert_equal "bla blub", Variable.new("var | strip_html").render(@context)
end
def test_strip_html_ignore_comments_with_html
@context['var'] = "<!-- split and some <ul> tag --><b>bla blub</a>"
assert_equal "bla blub", Variable.new("var | strip_html").render(@context)
end
def test_capitalize
@context['var'] = "blub"

View File

@@ -173,6 +173,10 @@ class StandardFiltersTest < Test::Unit::TestCase
assert_template_result "Liquid error: divided by 0", "{{ 5 | divided_by:0 }}"
end
def test_modulo
assert_template_result "1", "{{ 3 | modulo:2 }}"
end
def test_append
assigns = {'a' => 'bc', 'b' => 'd' }
assert_template_result('bcd',"{{ a | append: 'd'}}",assigns)

View File

@@ -0,0 +1,202 @@
require 'test_helper'
class ForTagTest < Test::Unit::TestCase
include Liquid
def test_for
assert_template_result(' yo yo yo yo ','{%for item in array%} yo {%endfor%}','array' => [1,2,3,4])
assert_template_result('yoyo','{%for item in array%}yo{%endfor%}','array' => [1,2])
assert_template_result(' yo ','{%for item in array%} yo {%endfor%}','array' => [1])
assert_template_result('','{%for item in array%}{%endfor%}','array' => [1,2])
expected = <<HERE
yo
yo
yo
HERE
template = <<HERE
{%for item in array%}
yo
{%endfor%}
HERE
assert_template_result(expected,template,'array' => [1,2,3])
end
def test_for_reversed
assigns = {'array' => [ 1, 2, 3] }
assert_template_result('321','{%for item in array reversed %}{{item}}{%endfor%}',assigns)
end
def test_for_with_range
assert_template_result(' 1 2 3 ','{%for item in (1..3) %} {{item}} {%endfor%}')
end
def test_for_with_variable
assert_template_result(' 1 2 3 ','{%for item in array%} {{item}} {%endfor%}','array' => [1,2,3])
assert_template_result('123','{%for item in array%}{{item}}{%endfor%}','array' => [1,2,3])
assert_template_result('123','{% for item in array %}{{item}}{% endfor %}','array' => [1,2,3])
assert_template_result('abcd','{%for item in array%}{{item}}{%endfor%}','array' => ['a','b','c','d'])
assert_template_result('a b c','{%for item in array%}{{item}}{%endfor%}','array' => ['a',' ','b',' ','c'])
assert_template_result('abc','{%for item in array%}{{item}}{%endfor%}','array' => ['a','','b','','c'])
end
def test_for_helpers
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 2 3 ', '{%for item in array%} {{forloop.index}} {%endfor%}', assigns)
assert_template_result(' 0 1 2 ', '{%for item in array%} {{forloop.index0}} {%endfor%}', assigns)
assert_template_result(' 2 1 0 ', '{%for item in array%} {{forloop.rindex0}} {%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
def test_for_and_if
assigns = {'array' => [1,2,3] }
assert_template_result('+--',
'{%for item in array%}{% if forloop.first %}+{% else %}-{% endif %}{%endfor%}',
assigns)
end
def test_for_else
assert_template_result('+++', '{%for item in array%}+{%else%}-{%endfor%}', 'array'=>[1,2,3])
assert_template_result('-', '{%for item in array%}+{%else%}-{%endfor%}', 'array'=>[])
assert_template_result('-', '{%for item in array%}+{%else%}-{%endfor%}', 'array'=>nil)
end
def test_limiting
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('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)
end
def test_dynamic_variable_limiting
assigns = {'array' => [1,2,3,4,5,6,7,8,9,0]}
assigns['limit'] = 2
assigns['offset'] = 2
assert_template_result('34', '{%for i in array limit: limit offset: offset %}{{ i }}{%endfor%}', assigns)
end
def test_nested_for
assigns = {'array' => [[1,2],[3,4],[5,6]] }
assert_template_result('123456', '{%for item in array%}{%for i in item%}{{ i }}{%endfor%}{%endfor%}', assigns)
end
def test_offset_only
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)
end
def test_pause_resume
assigns = {'array' => {'items' => [1,2,3,4,5,6,7,8,9,0]}}
markup = <<-MKUP
{%for i in array.items limit: 3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit: 3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit: 3 %}{{i}}{%endfor%}
MKUP
expected = <<-XPCTD
123
next
456
next
789
XPCTD
assert_template_result(expected,markup,assigns)
end
def test_pause_resume_limit
assigns = {'array' => {'items' => [1,2,3,4,5,6,7,8,9,0]}}
markup = <<-MKUP
{%for i in array.items limit:3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit:3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit:1 %}{{i}}{%endfor%}
MKUP
expected = <<-XPCTD
123
next
456
next
7
XPCTD
assert_template_result(expected,markup,assigns)
end
def test_pause_resume_BIG_limit
assigns = {'array' => {'items' => [1,2,3,4,5,6,7,8,9,0]}}
markup = <<-MKUP
{%for i in array.items limit:3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit:3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit:1000 %}{{i}}{%endfor%}
MKUP
expected = <<-XPCTD
123
next
456
next
7890
XPCTD
assert_template_result(expected,markup,assigns)
end
def test_pause_resume_BIG_offset
assigns = {'array' => {'items' => [1,2,3,4,5,6,7,8,9,0]}}
markup = %q({%for i in array.items limit:3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit:3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit:3 offset:1000 %}{{i}}{%endfor%})
expected = %q(123
next
456
next
)
assert_template_result(expected,markup,assigns)
end
def test_for_tag_string
# ruby 1.8.7 "String".each => Enumerator with single "String" element.
# ruby 1.9.3 no longer supports .each on String though we mimic
# the functionality for backwards compatibility
assert_template_result('test string',
'{%for val in string%}{{val}}{%endfor%}',
'string' => "test string")
assert_template_result('test string',
'{%for val in string limit:1%}{{val}}{%endfor%}',
'string' => "test string")
assert_template_result('val-string-1-1-0-1-0-true-true-test string',
'{%for val in string%}' +
'{{forloop.name}}-' +
'{{forloop.index}}-' +
'{{forloop.length}}-' +
'{{forloop.index0}}-' +
'{{forloop.rindex}}-' +
'{{forloop.rindex0}}-' +
'{{forloop.first}}-' +
'{{forloop.last}}-' +
'{{val}}{%endfor%}',
'string' => "test string")
end
def test_blank_string_not_iterable
assert_template_result('', "{% for char in characters %}I WILL NOT BE OUTPUT{% endfor %}", 'characters' => '')
end
end

View File

@@ -3,6 +3,18 @@ require 'test_helper'
class HtmlTagTest < Test::Unit::TestCase
include Liquid
class ArrayDrop < Liquid::Drop
include Enumerable
def initialize(array)
@array = array
end
def each(&block)
@array.each(&block)
end
end
def test_html_table
assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
@@ -26,4 +38,26 @@ class HtmlTagTest < Test::Unit::TestCase
'{% tablerow n in numbers cols:2%}{{tablerowloop.col}}{% endtablerow %}',
'numbers' => [1,2,3,4,5,6])
end
def test_quoted_fragment
assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
"{% tablerow n in collections.frontpage cols:3%} {{n}} {% endtablerow %}",
'collections' => {'frontpage' => [1,2,3,4,5,6]})
assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
"{% tablerow n in collections['frontpage'] cols:3%} {{n}} {% endtablerow %}",
'collections' => {'frontpage' => [1,2,3,4,5,6]})
end
def test_enumerable_drop
assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
'{% tablerow n in numbers cols:3%} {{n}} {% endtablerow %}',
'numbers' => ArrayDrop.new([1,2,3,4,5,6]))
end
def test_offset_and_limit
assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
'{% tablerow n in numbers cols:3 offset:1 limit:6%} {{n}} {% endtablerow %}',
'numbers' => [0,1,2,3,4,5,6,7])
end
end # HtmlTagTest

View File

@@ -47,166 +47,6 @@ class StandardTagTest < Test::Unit::TestCase
{%endcomment%}bar')
end
def test_for
assert_template_result(' yo yo yo yo ','{%for item in array%} yo {%endfor%}','array' => [1,2,3,4])
assert_template_result('yoyo','{%for item in array%}yo{%endfor%}','array' => [1,2])
assert_template_result(' yo ','{%for item in array%} yo {%endfor%}','array' => [1])
assert_template_result('','{%for item in array%}{%endfor%}','array' => [1,2])
expected = <<HERE
yo
yo
yo
HERE
template = <<HERE
{%for item in array%}
yo
{%endfor%}
HERE
assert_template_result(expected,template,'array' => [1,2,3])
end
def test_for_with_range
assert_template_result(' 1 2 3 ','{%for item in (1..3) %} {{item}} {%endfor%}')
end
def test_for_with_variable
assert_template_result(' 1 2 3 ','{%for item in array%} {{item}} {%endfor%}','array' => [1,2,3])
assert_template_result('123','{%for item in array%}{{item}}{%endfor%}','array' => [1,2,3])
assert_template_result('123','{% for item in array %}{{item}}{% endfor %}','array' => [1,2,3])
assert_template_result('abcd','{%for item in array%}{{item}}{%endfor%}','array' => ['a','b','c','d'])
assert_template_result('a b c','{%for item in array%}{{item}}{%endfor%}','array' => ['a',' ','b',' ','c'])
assert_template_result('abc','{%for item in array%}{{item}}{%endfor%}','array' => ['a','','b','','c'])
end
def test_for_helpers
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 2 3 ', '{%for item in array%} {{forloop.index}} {%endfor%}', assigns)
assert_template_result(' 0 1 2 ', '{%for item in array%} {{forloop.index0}} {%endfor%}', assigns)
assert_template_result(' 2 1 0 ', '{%for item in array%} {{forloop.rindex0}} {%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
def test_for_and_if
assigns = {'array' => [1,2,3] }
assert_template_result('+--',
'{%for item in array%}{% if forloop.first %}+{% else %}-{% endif %}{%endfor%}',
assigns)
end
def test_for_else
assert_template_result('+++', '{%for item in array%}+{%else%}-{%endfor%}', 'array'=>[1,2,3])
assert_template_result('-', '{%for item in array%}+{%else%}-{%endfor%}', 'array'=>[])
assert_template_result('-', '{%for item in array%}+{%else%}-{%endfor%}', 'array'=>nil)
end
def test_limiting
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('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)
end
def test_dynamic_variable_limiting
assigns = {'array' => [1,2,3,4,5,6,7,8,9,0]}
assigns['limit'] = 2
assigns['offset'] = 2
assert_template_result('34', '{%for i in array limit: limit offset: offset %}{{ i }}{%endfor%}', assigns)
end
def test_nested_for
assigns = {'array' => [[1,2],[3,4],[5,6]] }
assert_template_result('123456', '{%for item in array%}{%for i in item%}{{ i }}{%endfor%}{%endfor%}', assigns)
end
def test_offset_only
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)
end
def test_pause_resume
assigns = {'array' => {'items' => [1,2,3,4,5,6,7,8,9,0]}}
markup = <<-MKUP
{%for i in array.items limit: 3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit: 3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit: 3 %}{{i}}{%endfor%}
MKUP
expected = <<-XPCTD
123
next
456
next
789
XPCTD
assert_template_result(expected,markup,assigns)
end
def test_pause_resume_limit
assigns = {'array' => {'items' => [1,2,3,4,5,6,7,8,9,0]}}
markup = <<-MKUP
{%for i in array.items limit:3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit:3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit:1 %}{{i}}{%endfor%}
MKUP
expected = <<-XPCTD
123
next
456
next
7
XPCTD
assert_template_result(expected,markup,assigns)
end
def test_pause_resume_BIG_limit
assigns = {'array' => {'items' => [1,2,3,4,5,6,7,8,9,0]}}
markup = <<-MKUP
{%for i in array.items limit:3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit:3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit:1000 %}{{i}}{%endfor%}
MKUP
expected = <<-XPCTD
123
next
456
next
7890
XPCTD
assert_template_result(expected,markup,assigns)
end
def test_pause_resume_BIG_offset
assigns = {'array' => {'items' => [1,2,3,4,5,6,7,8,9,0]}}
markup = %q({%for i in array.items limit:3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit:3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit:3 offset:1000 %}{{i}}{%endfor%})
expected = %q(123
next
456
next
)
assert_template_result(expected,markup,assigns)
end
def test_assign
assigns = {'var' => 'content' }
assert_template_result('var2: var2:content', 'var2:{{var2}} {%assign var2 = var%} var2:{{var2}}', assigns)
@@ -445,12 +285,6 @@ HERE
assert_template_result('', '{% if null == true %}?{% endif %}', {})
end
def test_for_reversed
assigns = {'array' => [ 1, 2, 3] }
assert_template_result('321','{%for item in array reversed %}{{item}}{%endfor%}',assigns)
end
def test_ifchanged
assigns = {'array' => [ 1, 1, 2, 2, 3, 3] }
assert_template_result('123','{%for item in array%}{%ifchanged%}{{item}}{% endifchanged %}{%endfor%}',assigns)