mirror of
https://github.com/kemko/liquid.git
synced 2026-01-01 15:55:40 +03:00
Compare commits
120 Commits
lax-parse-
...
v4.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad00998ef8 | ||
|
|
869dbc7ebf | ||
|
|
fae3a2de7b | ||
|
|
f27bd619b9 | ||
|
|
a9b84b7806 | ||
|
|
6cc2c567c5 | ||
|
|
812e3c51b9 | ||
|
|
9dd0801f5c | ||
|
|
b146b49f46 | ||
|
|
86944fe7b7 | ||
|
|
a549d289d7 | ||
|
|
b2feeacbce | ||
|
|
143ba39a08 | ||
|
|
43e59796f6 | ||
|
|
bb3624b799 | ||
|
|
64fca66ef5 | ||
|
|
e9d7486758 | ||
|
|
2bb98c1431 | ||
|
|
95d5c24bfc | ||
|
|
b7ee1a2176 | ||
|
|
0eca61a977 | ||
|
|
9bfd04da2d | ||
|
|
302185a7fc | ||
|
|
50c85afc35 | ||
|
|
5876dff326 | ||
|
|
f25185631d | ||
|
|
283f1bad18 | ||
|
|
e1d40c7d89 | ||
|
|
19c6eb426a | ||
|
|
f87b06095d | ||
|
|
b81d54e789 | ||
|
|
00f53b16e8 | ||
|
|
e4cf55b112 | ||
|
|
5bb211d933 | ||
|
|
6adc431a19 | ||
|
|
23d2beed41 | ||
|
|
a80ecb7678 | ||
|
|
361c695264 | ||
|
|
f93243cc1a | ||
|
|
1e533a52e7 | ||
|
|
3ea84f095f | ||
|
|
4239c899a4 | ||
|
|
1597f8859f | ||
|
|
b3dda384c9 | ||
|
|
6828670bfe | ||
|
|
d2f16d92d6 | ||
|
|
d233acb483 | ||
|
|
8920e2a2a2 | ||
|
|
bfee507005 | ||
|
|
929c89789f | ||
|
|
d03c4ae8e8 | ||
|
|
021bafd260 | ||
|
|
04c393ab07 | ||
|
|
9a7778e52c | ||
|
|
dde00253f9 | ||
|
|
18d1644980 | ||
|
|
c424d47274 | ||
|
|
8e6b9d503d | ||
|
|
8be38d1795 | ||
|
|
3146d5c3f2 | ||
|
|
0cc8b68a97 | ||
|
|
5a50c12953 | ||
|
|
a6fa4c5c38 | ||
|
|
dadd9b4dd2 | ||
|
|
6434b8d2bb | ||
|
|
2d891ddd8f | ||
|
|
60b508b151 | ||
|
|
3891f14a1a | ||
|
|
198f0aa366 | ||
|
|
f2e6adf566 | ||
|
|
08de6ed2c5 | ||
|
|
7e322f5cf8 | ||
|
|
bf86a5a069 | ||
|
|
0141444814 | ||
|
|
6d30226768 | ||
|
|
63e8bac1a4 | ||
|
|
8449849ed5 | ||
|
|
4bc198a0db | ||
|
|
3921dbe919 | ||
|
|
79e2d1d8b4 | ||
|
|
b7c4041db8 | ||
|
|
e113c891ec | ||
|
|
a32ad449c0 | ||
|
|
1662ba6679 | ||
|
|
99b5e86f0a | ||
|
|
b892a73463 | ||
|
|
0b55d09cea | ||
|
|
5f8086572b | ||
|
|
bdb9a4a47f | ||
|
|
c38eec0293 | ||
|
|
8d5a907dc8 | ||
|
|
74cc41ce74 | ||
|
|
a120cc587a | ||
|
|
c582023321 | ||
|
|
ac041c4ad1 | ||
|
|
31d7682f4e | ||
|
|
5f1acbc086 | ||
|
|
8612716129 | ||
|
|
e6392d1cc1 | ||
|
|
04381418d3 | ||
|
|
89ccdabe9a | ||
|
|
c0fc6777b0 | ||
|
|
cd03346239 | ||
|
|
b4f19da127 | ||
|
|
4100f8d641 | ||
|
|
d8bda2c892 | ||
|
|
4f81c0a658 | ||
|
|
704937bc00 | ||
|
|
27c6b8074a | ||
|
|
affae5ebef | ||
|
|
fc1c0d0d83 | ||
|
|
a215b70de9 | ||
|
|
1f70928f8a | ||
|
|
7713f6709d | ||
|
|
239cf0e5f5 | ||
|
|
fa187665b3 | ||
|
|
cd0c5e954c | ||
|
|
490b457738 | ||
|
|
4d6dec9b5a | ||
|
|
0b11b573d9 |
@@ -118,6 +118,8 @@ Style/ClassVars:
|
||||
Style/PerlBackrefs:
|
||||
Enabled: false
|
||||
|
||||
Style/TrivialAccessors:
|
||||
AllowPredicates: true
|
||||
|
||||
Style/WordArray:
|
||||
Enabled: false
|
||||
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
language: ruby
|
||||
|
||||
rvm:
|
||||
- 2.0
|
||||
- 2.1
|
||||
- 2.2
|
||||
- 2.3.3
|
||||
- ruby-head
|
||||
- jruby-head
|
||||
# - rbx-2
|
||||
|
||||
sudo: false
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libgmp3-dev
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- rvm: jruby-head
|
||||
|
||||
@@ -4,23 +4,22 @@
|
||||
|
||||
* Bugfixes
|
||||
* Performance improvements
|
||||
* Features which are likely to be useful to the majority of Liquid users
|
||||
* Features that are likely to be useful to the majority of Liquid users
|
||||
|
||||
## Things we won't merge
|
||||
|
||||
* Code which introduces considerable performance degrations
|
||||
* Code which touches performance critical parts of Liquid and comes without benchmarks
|
||||
* Features which are not important for most people (we want to keep the core Liquid code small and tidy)
|
||||
* Features which can easily be implemented on top of Liquid (for example as a custom filter or custom filesystem)
|
||||
* Code which comes without tests
|
||||
* Code which breaks existing tests
|
||||
* Code that introduces considerable performance degrations
|
||||
* Code that touches performance-critical parts of Liquid and comes without benchmarks
|
||||
* Features that are not important for most people (we want to keep the core Liquid code small and tidy)
|
||||
* Features that can easily be implemented on top of Liquid (for example as a custom filter or custom filesystem)
|
||||
* Code that does not include tests
|
||||
* Code that breaks existing tests
|
||||
|
||||
## Workflow
|
||||
|
||||
* Fork the Liquid repository
|
||||
* Create a new branch in your fork
|
||||
* If it makes sense, add tests for your code and run a performance benchmark
|
||||
* Make sure all tests pass
|
||||
* If it makes sense, add tests for your code and/or run a performance benchmark
|
||||
* Make sure all tests pass (`bundle exec rake`)
|
||||
* Create a pull request
|
||||
* In the description, ping one of [@boourns](https://github.com/boourns), [@fw42](https://github.com/fw42), [@camilo](https://github.com/camilo), [@dylanahsmith](https://github.com/dylanahsmith), or [@arthurnn](https://github.com/arthurnn) and ask for a code review.
|
||||
|
||||
|
||||
4
Gemfile
4
Gemfile
@@ -6,9 +6,9 @@ gem 'stackprof', platforms: :mri_21
|
||||
group :test do
|
||||
gem 'spy', '0.4.1'
|
||||
gem 'benchmark-ips'
|
||||
gem 'rubocop', '>=0.32.0'
|
||||
gem 'rubocop', '0.34.2'
|
||||
|
||||
platform :mri do
|
||||
gem 'liquid-c', github: 'Shopify/liquid-c', ref: '2570693d8d03faa0df9160ec74348a7149436df3'
|
||||
gem 'liquid-c', github: 'Shopify/liquid-c', ref: 'bd53db95de3d44d631e7c5a267c3d934e66107dd'
|
||||
end
|
||||
end
|
||||
|
||||
34
History.md
34
History.md
@@ -3,6 +3,14 @@
|
||||
## 4.0.0 / not yet released / branch "master"
|
||||
|
||||
### Changed
|
||||
* Render an opaque internal error by default for non-Liquid::Error (#835) [Dylan Thacker-Smith]
|
||||
* Ruby 2.0 support dropped (#832) [Dylan Thacker-Smith]
|
||||
* Add to_number Drop method to allow custom drops to work with number filters (#731)
|
||||
* Add strict_variables and strict_filters options to detect undefined references (#691)
|
||||
* Improve loop performance (#681) [Florian Weingarten]
|
||||
* Rename Drop method `before_method` to `liquid_method_missing` (#661) [Thierry Joyal]
|
||||
* Add url_decode filter to invert url_encode (#645) [Larry Archer]
|
||||
* Add global_filter to apply a filter to all output (#610) [Loren Hale]
|
||||
* Add compact filter (#600) [Carson Reinke]
|
||||
* Rename deprecated "has_key?" and "has_interrupt?" methods (#593) [Florian Weingarten]
|
||||
* Include template name with line numbers in render errors (574) [Dylan Thacker-Smith]
|
||||
@@ -13,8 +21,14 @@
|
||||
* Ruby 1.9 support dropped (#491) [Justin Li]
|
||||
* Liquid::Template.file_system's read_template_file method is no longer passed the context. (#441) [James Reid-Smith]
|
||||
* Remove support for `liquid_methods`
|
||||
* Liquid::Template.register_filter raises when the module overrides registered public methods as private or protected (#705) [Gaurav Chande]
|
||||
|
||||
### Fixed
|
||||
* Fix map filter when value is a Proc (#672) [Guillaume Malette]
|
||||
* Fix truncate filter when value is not a string (#672) [Guillaume Malette]
|
||||
* Fix behaviour of escape filter when input is nil (#665) [Tanel Jakobsoo]
|
||||
* Fix sort filter behaviour with empty array input (#652) [Marcel Cary]
|
||||
* Fix test failure under certain timezones (#631) [Dylan Thacker-Smith]
|
||||
* Fix bug in uniq filter (#595) [Florian Weingarten]
|
||||
* Fix bug when "blank" and "empty" are used as variable names (#592) [Florian Weingarten]
|
||||
* Fix condition parse order in strict mode (#569) [Justin Li]
|
||||
@@ -26,7 +40,15 @@
|
||||
* Disallow variable names in the strict parser that are not valid in the lax parser (#463) [Justin Li]
|
||||
* Fix BlockBody#warnings taking exponential time to compute (#486) [Justin Li]
|
||||
|
||||
## 3.0.3 / 2015-05-28 / branch "3-0-stable"
|
||||
## 3.0.5 / 2015-07-23 / branch "3-0-stable"
|
||||
|
||||
* Fix test failure under certain timezones [Dylan Thacker-Smith]
|
||||
|
||||
## 3.0.4 / 2015-07-17
|
||||
|
||||
* Fix chained access to multi-dimensional hashes [Florian Weingarten]
|
||||
|
||||
## 3.0.3 / 2015-05-28
|
||||
|
||||
* Fix condition parse order in strict mode (#569) [Justin Li]
|
||||
|
||||
@@ -74,7 +96,15 @@
|
||||
* Make map filter work on enumerable drops (#233) [Florian Weingarten]
|
||||
* Improved whitespace stripping for blank blocks, related to #216 [Florian Weingarten]
|
||||
|
||||
## 2.6.1 / 2014-01-10 / branch "2-6-stable"
|
||||
## 2.6.3 / 2015-07-23 / branch "2-6-stable"
|
||||
|
||||
* Fix test failure under certain timezones [Dylan Thacker-Smith]
|
||||
|
||||
## 2.6.2 / 2015-01-23
|
||||
|
||||
* Remove duplicate hash key [Parker Moore]
|
||||
|
||||
## 2.6.1 / 2014-01-10
|
||||
|
||||
Security fix, cherry-picked from master (4e14a65):
|
||||
* Don't call to_sym when creating conditions for security reasons (#273) [Bouke van der Bijl]
|
||||
|
||||
31
README.md
31
README.md
@@ -73,3 +73,34 @@ This is useful for doing things like enabling strict mode only in the theme edit
|
||||
|
||||
It is recommended that you enable `:strict` or `:warn` mode on new apps to stop invalid templates from being created.
|
||||
It is also recommended that you use it in the template editors of existing apps to give editors better error messages.
|
||||
|
||||
### Undefined variables and filters
|
||||
|
||||
By default, the renderer doesn't raise or in any other way notify you if some variables or filters are missing, i.e. not passed to the `render` method.
|
||||
You can improve this situation by passing `strict_variables: true` and/or `strict_filters: true` options to the `render` method.
|
||||
When one of these options is set to true, all errors about undefined variables and undefined filters will be stored in `errors` array of a `Liquid::Template` instance.
|
||||
Here are some examples:
|
||||
|
||||
```ruby
|
||||
template = Liquid::Template.parse("{{x}} {{y}} {{z.a}} {{z.b}}")
|
||||
template.render({ 'x' => 1, 'z' => { 'a' => 2 } }, { strict_variables: true })
|
||||
#=> '1 2 ' # when a variable is undefined, it's rendered as nil
|
||||
template.errors
|
||||
#=> [#<Liquid::UndefinedVariable: Liquid error: undefined variable y>, #<Liquid::UndefinedVariable: Liquid error: undefined variable b>]
|
||||
```
|
||||
|
||||
```ruby
|
||||
template = Liquid::Template.parse("{{x | filter1 | upcase}}")
|
||||
template.render({ 'x' => 'foo' }, { strict_filters: true })
|
||||
#=> '' # when at least one filter in the filter chain is undefined, a whole expression is rendered as nil
|
||||
template.errors
|
||||
#=> [#<Liquid::UndefinedFilter: Liquid error: undefined filter filter1>]
|
||||
```
|
||||
|
||||
If you want to raise on a first exception instead of pushing all of them in `errors`, you can use `render!` method:
|
||||
|
||||
```ruby
|
||||
template = Liquid::Template.parse("{{x}} {{y}}")
|
||||
template.render!({ 'x' => 1}, { strict_variables: true })
|
||||
#=> Liquid::UndefinedVariable: Liquid error: undefined variable y
|
||||
```
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
machine:
|
||||
ruby:
|
||||
version: ruby-2.0
|
||||
version: ruby-2.1
|
||||
|
||||
@@ -24,6 +24,7 @@ module Liquid
|
||||
ArgumentSeparator = ','.freeze
|
||||
FilterArgumentSeparator = ':'.freeze
|
||||
VariableAttributeSeparator = '.'.freeze
|
||||
WhitespaceControl = '-'.freeze
|
||||
TagStart = /\{\%/
|
||||
TagEnd = /\%\}/
|
||||
VariableSignature = /\(?[\w\-\.\[\]]\)?/
|
||||
@@ -34,7 +35,7 @@ module Liquid
|
||||
QuotedString = /"[^"]*"|'[^']*'/
|
||||
QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o
|
||||
TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/o
|
||||
AnyStartingTag = /\{\{|\{\%/
|
||||
AnyStartingTag = /#{TagStart}|#{VariableStart}/o
|
||||
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/om
|
||||
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om
|
||||
VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o
|
||||
@@ -48,6 +49,8 @@ require 'liquid/lexer'
|
||||
require 'liquid/parser'
|
||||
require 'liquid/i18n'
|
||||
require 'liquid/drop'
|
||||
require 'liquid/tablerowloop_drop'
|
||||
require 'liquid/forloop_drop'
|
||||
require 'liquid/extensions'
|
||||
require 'liquid/errors'
|
||||
require 'liquid/interrupts'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module Liquid
|
||||
class BlockBody
|
||||
FullToken = /\A#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
|
||||
ContentOfVariable = /\A#{VariableStart}(.*)#{VariableEnd}\z/om
|
||||
FullToken = /\A#{TagStart}#{WhitespaceControl}?\s*(\w+)\s*(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
|
||||
ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om
|
||||
TAGSTART = "{%".freeze
|
||||
VARSTART = "{{".freeze
|
||||
|
||||
@@ -18,6 +18,7 @@ module Liquid
|
||||
unless token.empty?
|
||||
case
|
||||
when token.start_with?(TAGSTART)
|
||||
whitespace_handler(token, parse_context)
|
||||
if token =~ FullToken
|
||||
tag_name = $1
|
||||
markup = $2
|
||||
@@ -35,9 +36,14 @@ module Liquid
|
||||
raise_missing_tag_terminator(token, parse_context)
|
||||
end
|
||||
when token.start_with?(VARSTART)
|
||||
whitespace_handler(token, parse_context)
|
||||
@nodelist << create_variable(token, parse_context)
|
||||
@blank = false
|
||||
else
|
||||
if parse_context.trim_whitespace
|
||||
token.lstrip!
|
||||
end
|
||||
parse_context.trim_whitespace = false
|
||||
@nodelist << token
|
||||
@blank &&= !!(token =~ /\A\s*\z/)
|
||||
end
|
||||
@@ -48,6 +54,16 @@ module Liquid
|
||||
yield nil, nil
|
||||
end
|
||||
|
||||
def whitespace_handler(token, parse_context)
|
||||
if token[2] == WhitespaceControl
|
||||
previous_token = @nodelist.last
|
||||
if previous_token.is_a? String
|
||||
previous_token.rstrip!
|
||||
end
|
||||
end
|
||||
parse_context.trim_whitespace = (token[-3] == WhitespaceControl)
|
||||
end
|
||||
|
||||
def blank?
|
||||
@blank
|
||||
end
|
||||
@@ -76,8 +92,11 @@ module Liquid
|
||||
end
|
||||
rescue MemoryError => e
|
||||
raise e
|
||||
rescue UndefinedVariable, UndefinedDropMethod, UndefinedFilter => e
|
||||
context.handle_error(e, token.line_number, token.raw)
|
||||
output << nil
|
||||
rescue ::StandardError => e
|
||||
output << context.handle_error(e, token.line_number)
|
||||
output << context.handle_error(e, token.line_number, token.raw)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ module Liquid
|
||||
# context['bob'] #=> nil class Context
|
||||
class Context
|
||||
attr_reader :scopes, :errors, :registers, :environments, :resource_limits
|
||||
attr_accessor :exception_handler, :template_name, :partial
|
||||
attr_accessor :exception_renderer, :template_name, :partial, :global_filter, :strict_variables, :strict_filters
|
||||
|
||||
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil)
|
||||
@environments = [environments].flatten
|
||||
@@ -21,17 +21,20 @@ module Liquid
|
||||
@registers = registers
|
||||
@errors = []
|
||||
@partial = false
|
||||
@strict_variables = false
|
||||
@resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
|
||||
squash_instance_assigns_with_environments
|
||||
|
||||
@this_stack_used = false
|
||||
|
||||
self.exception_renderer = Template.default_exception_renderer
|
||||
if rethrow_errors
|
||||
self.exception_handler = ->(e) { raise }
|
||||
self.exception_renderer = ->(e) { raise }
|
||||
end
|
||||
|
||||
@interrupts = []
|
||||
@filters = []
|
||||
@global_filter = nil
|
||||
end
|
||||
|
||||
def warnings
|
||||
@@ -52,6 +55,10 @@ module Liquid
|
||||
@strainer = nil
|
||||
end
|
||||
|
||||
def apply_global_filter(obj)
|
||||
global_filter.nil? ? obj : global_filter.call(obj)
|
||||
end
|
||||
|
||||
# are there any not handled interrupts?
|
||||
def interrupt?
|
||||
!@interrupts.empty?
|
||||
@@ -67,31 +74,12 @@ module Liquid
|
||||
@interrupts.pop
|
||||
end
|
||||
|
||||
def handle_error(e, line_number = nil)
|
||||
if e.is_a?(Liquid::Error)
|
||||
e.template_name ||= template_name
|
||||
e.line_number ||= line_number
|
||||
end
|
||||
|
||||
output = nil
|
||||
|
||||
if exception_handler
|
||||
result = exception_handler.call(e)
|
||||
case result
|
||||
when Exception
|
||||
e = result
|
||||
if e.is_a?(Liquid::Error)
|
||||
e.template_name ||= template_name
|
||||
e.line_number ||= line_number
|
||||
end
|
||||
when String
|
||||
output = result
|
||||
else
|
||||
raise if result
|
||||
end
|
||||
end
|
||||
def handle_error(e, line_number = nil, raw_token = nil)
|
||||
e = internal_error unless e.is_a?(Liquid::Error)
|
||||
e.template_name ||= template_name
|
||||
e.line_number ||= line_number
|
||||
errors.push(e)
|
||||
output || Liquid::Error.render(e)
|
||||
exception_renderer.call(e).to_s
|
||||
end
|
||||
|
||||
def invoke(method, *args)
|
||||
@@ -200,7 +188,13 @@ module Liquid
|
||||
end
|
||||
|
||||
def lookup_and_evaluate(obj, key)
|
||||
if (value = obj[key]).is_a?(Proc) && obj.respond_to?(:[]=)
|
||||
if @strict_variables && obj.respond_to?(:key?) && !obj.key?(key)
|
||||
raise Liquid::UndefinedVariable, "undefined variable #{key}"
|
||||
end
|
||||
|
||||
value = obj[key]
|
||||
|
||||
if value.is_a?(Proc) && obj.respond_to?(:[]=)
|
||||
obj[key] = (value.arity == 0) ? value.call : value.call(self)
|
||||
else
|
||||
value
|
||||
@@ -209,6 +203,13 @@ module Liquid
|
||||
|
||||
private
|
||||
|
||||
def internal_error
|
||||
# raise and catch to set backtrace and cause on exception
|
||||
raise Liquid::InternalError, 'internal'
|
||||
rescue Liquid::InternalError => exc
|
||||
exc
|
||||
end
|
||||
|
||||
def squash_instance_assigns_with_environments
|
||||
@scopes.last.each_key do |k|
|
||||
@environments.each do |env|
|
||||
|
||||
@@ -18,24 +18,23 @@ module Liquid
|
||||
# tmpl = Liquid::Template.parse( ' {% for product in product.top_sales %} {{ product.name }} {%endfor%} ' )
|
||||
# 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.
|
||||
# Your drop can either implement the methods sans any parameters
|
||||
# or implement the liquid_method_missing(name) method which is a catch all.
|
||||
class Drop
|
||||
attr_writer :context
|
||||
|
||||
EMPTY_STRING = ''.freeze
|
||||
|
||||
# Catch all for the method
|
||||
def before_method(_method)
|
||||
nil
|
||||
def liquid_method_missing(method)
|
||||
return nil unless @context && @context.strict_variables
|
||||
raise Liquid::UndefinedDropMethod, "undefined method #{method}"
|
||||
end
|
||||
|
||||
# called by liquid to invoke a drop
|
||||
def invoke_drop(method_or_key)
|
||||
if method_or_key && method_or_key != EMPTY_STRING && self.class.invokable?(method_or_key)
|
||||
if self.class.invokable?(method_or_key)
|
||||
send(method_or_key)
|
||||
else
|
||||
before_method(method_or_key)
|
||||
liquid_method_missing(method_or_key)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -63,16 +62,17 @@ module Liquid
|
||||
end
|
||||
|
||||
def self.invokable_methods
|
||||
unless @invokable_methods
|
||||
@invokable_methods ||= begin
|
||||
blacklist = Liquid::Drop.public_instance_methods + [:each]
|
||||
|
||||
if include?(Enumerable)
|
||||
blacklist += Enumerable.public_instance_methods
|
||||
blacklist -= [:sort, :count, :first, :min, :max, :include?]
|
||||
end
|
||||
|
||||
whitelist = [:to_liquid] + (public_instance_methods - blacklist)
|
||||
@invokable_methods = Set.new(whitelist.map(&:to_s))
|
||||
Set.new(whitelist.map(&:to_s))
|
||||
end
|
||||
@invokable_methods
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -17,14 +17,6 @@ module Liquid
|
||||
str
|
||||
end
|
||||
|
||||
def self.render(e)
|
||||
if e.is_a?(Liquid::Error)
|
||||
e.to_s
|
||||
else
|
||||
"Liquid error: #{e}"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def message_prefix
|
||||
@@ -56,4 +48,9 @@ module Liquid
|
||||
MemoryError = Class.new(Error)
|
||||
ZeroDivisionError = Class.new(Error)
|
||||
FloatDomainError = Class.new(Error)
|
||||
UndefinedVariable = Class.new(Error)
|
||||
UndefinedDropMethod = Class.new(Error)
|
||||
UndefinedFilter = Class.new(Error)
|
||||
MethodOverrideError = Class.new(Error)
|
||||
InternalError = Class.new(Error)
|
||||
end
|
||||
|
||||
@@ -7,44 +7,50 @@ class String # :nodoc:
|
||||
end
|
||||
end
|
||||
|
||||
class Array # :nodoc:
|
||||
class Array # :nodoc:
|
||||
def to_liquid
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
class Hash # :nodoc:
|
||||
class Hash # :nodoc:
|
||||
def to_liquid
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
class Numeric # :nodoc:
|
||||
class Numeric # :nodoc:
|
||||
def to_liquid
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
class Time # :nodoc:
|
||||
class Range # :nodoc:
|
||||
def to_liquid
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
class DateTime < Date # :nodoc:
|
||||
class Time # :nodoc:
|
||||
def to_liquid
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
class Date # :nodoc:
|
||||
class DateTime < Date # :nodoc:
|
||||
def to_liquid
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
class Date # :nodoc:
|
||||
def to_liquid
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
class TrueClass
|
||||
def to_liquid # :nodoc:
|
||||
def to_liquid # :nodoc:
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,8 +8,8 @@ module Liquid
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# Liquid::Template.file_system = Liquid::LocalFileSystem.new(template_path)
|
||||
# liquid = Liquid::Template.parse(template)
|
||||
# Liquid::Template.file_system = Liquid::LocalFileSystem.new(template_path)
|
||||
# liquid = Liquid::Template.parse(template)
|
||||
#
|
||||
# This will parse the template with a LocalFileSystem implementation rooted at 'template_path'.
|
||||
class BlankFileSystem
|
||||
@@ -26,10 +26,10 @@ module Liquid
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# file_system = Liquid::LocalFileSystem.new("/some/path")
|
||||
# file_system = Liquid::LocalFileSystem.new("/some/path")
|
||||
#
|
||||
# file_system.full_path("mypartial") # => "/some/path/_mypartial.liquid"
|
||||
# file_system.full_path("dir/mypartial") # => "/some/path/dir/_mypartial.liquid"
|
||||
# file_system.full_path("mypartial") # => "/some/path/_mypartial.liquid"
|
||||
# file_system.full_path("dir/mypartial") # => "/some/path/dir/_mypartial.liquid"
|
||||
#
|
||||
# Optionally in the second argument you can specify a custom pattern for template filenames.
|
||||
# The Kernel::sprintf format specification is used.
|
||||
@@ -37,9 +37,9 @@ module Liquid
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# file_system = Liquid::LocalFileSystem.new("/some/path", "%s.html")
|
||||
# file_system = Liquid::LocalFileSystem.new("/some/path", "%s.html")
|
||||
#
|
||||
# file_system.full_path("index") # => "/some/path/index.html"
|
||||
# file_system.full_path("index") # => "/some/path/index.html"
|
||||
#
|
||||
class LocalFileSystem
|
||||
attr_accessor :root
|
||||
@@ -65,7 +65,7 @@ module Liquid
|
||||
File.join(root, @pattern % template_path)
|
||||
end
|
||||
|
||||
raise FileSystemError, "Illegal template path '#{File.expand_path(full_path)}'" unless File.expand_path(full_path) =~ /\A#{File.expand_path(root)}/
|
||||
raise FileSystemError, "Illegal template path '#{File.expand_path(full_path)}'" unless File.expand_path(full_path).start_with?(File.expand_path(root))
|
||||
|
||||
full_path
|
||||
end
|
||||
|
||||
42
lib/liquid/forloop_drop.rb
Normal file
42
lib/liquid/forloop_drop.rb
Normal file
@@ -0,0 +1,42 @@
|
||||
module Liquid
|
||||
class ForloopDrop < Drop
|
||||
def initialize(name, length, parentloop)
|
||||
@name = name
|
||||
@length = length
|
||||
@parentloop = parentloop
|
||||
@index = 0
|
||||
end
|
||||
|
||||
attr_reader :name, :length, :parentloop
|
||||
|
||||
def index
|
||||
@index + 1
|
||||
end
|
||||
|
||||
def index0
|
||||
@index
|
||||
end
|
||||
|
||||
def rindex
|
||||
@length - @index
|
||||
end
|
||||
|
||||
def rindex0
|
||||
@length - @index - 1
|
||||
end
|
||||
|
||||
def first
|
||||
@index == 0
|
||||
end
|
||||
|
||||
def last
|
||||
@index == @length - 1
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def increment!
|
||||
@index += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -22,3 +22,5 @@
|
||||
tag_never_closed: "'%{block_name}' tag was never closed"
|
||||
meta_syntax_error: "Liquid syntax error: #{e.message}"
|
||||
table_row: "Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3"
|
||||
argument:
|
||||
include: "Argument error in tag 'include' - Illegal template name"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module Liquid
|
||||
class ParseContext
|
||||
attr_accessor :partial, :locale, :line_number
|
||||
attr_reader :warnings, :error_mode
|
||||
attr_accessor :locale, :line_number, :trim_whitespace
|
||||
attr_reader :partial, :warnings, :error_mode
|
||||
|
||||
def initialize(options = {})
|
||||
@template_options = options ? options.dup : {}
|
||||
|
||||
@@ -75,7 +75,7 @@ module Liquid
|
||||
|
||||
def variable_signature
|
||||
str = consume(:id)
|
||||
if look(:open_square)
|
||||
while look(:open_square)
|
||||
str << consume
|
||||
str << expression
|
||||
str << consume(:close_square)
|
||||
|
||||
@@ -33,7 +33,7 @@ module Liquid
|
||||
end
|
||||
|
||||
def escape(input)
|
||||
CGI.escapeHTML(input).untaint
|
||||
CGI.escapeHTML(input).untaint unless input.nil?
|
||||
end
|
||||
alias_method :h, :escape
|
||||
|
||||
@@ -42,7 +42,11 @@ module Liquid
|
||||
end
|
||||
|
||||
def url_encode(input)
|
||||
CGI.escape(input) rescue input
|
||||
CGI.escape(input) unless input.nil?
|
||||
end
|
||||
|
||||
def url_decode(input)
|
||||
CGI.unescape(input) unless input.nil?
|
||||
end
|
||||
|
||||
def slice(input, offset, length = nil)
|
||||
@@ -59,10 +63,12 @@ module Liquid
|
||||
# Truncate a string down to x characters
|
||||
def truncate(input, length = 50, truncate_string = "...".freeze)
|
||||
return if input.nil?
|
||||
input_str = input.to_s
|
||||
length = Utils.to_integer(length)
|
||||
l = length - truncate_string.length
|
||||
truncate_string_str = truncate_string.to_s
|
||||
l = length - truncate_string_str.length
|
||||
l = 0 if l < 0
|
||||
input.length > length ? input[0...l] + truncate_string : input
|
||||
input_str.length > length ? input_str[0...l] + truncate_string_str : input_str
|
||||
end
|
||||
|
||||
def truncatewords(input, words = 15, truncate_string = "...".freeze)
|
||||
@@ -71,7 +77,7 @@ module Liquid
|
||||
words = Utils.to_integer(words)
|
||||
l = words - 1
|
||||
l = 0 if l < 0
|
||||
wordlist.length > l ? wordlist[0..l].join(" ".freeze) + truncate_string : input
|
||||
wordlist.length > l ? wordlist[0..l].join(" ".freeze) + truncate_string.to_s : input
|
||||
end
|
||||
|
||||
# Split input string into an array of substrings separated by given pattern.
|
||||
@@ -80,7 +86,7 @@ module Liquid
|
||||
# <div class="summary">{{ post | split '//' | first }}</div>
|
||||
#
|
||||
def split(input, pattern)
|
||||
input.to_s.split(pattern)
|
||||
input.to_s.split(pattern.to_s)
|
||||
end
|
||||
|
||||
def strip(input)
|
||||
@@ -116,10 +122,18 @@ module Liquid
|
||||
ary = InputIterator.new(input)
|
||||
if property.nil?
|
||||
ary.sort
|
||||
elsif ary.empty? # The next two cases assume a non-empty array.
|
||||
[]
|
||||
elsif ary.first.respond_to?(:[]) && !ary.first[property].nil?
|
||||
ary.sort { |a, b| a[property] <=> b[property] }
|
||||
elsif ary.first.respond_to?(property)
|
||||
ary.sort { |a, b| a.send(property) <=> b.send(property) }
|
||||
ary.sort do |a, b|
|
||||
a = a[property]
|
||||
b = b[property]
|
||||
if a && b
|
||||
a <=> b
|
||||
else
|
||||
a ? -1 : 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -130,10 +144,10 @@ module Liquid
|
||||
|
||||
if property.nil?
|
||||
ary.sort { |a, b| a.casecmp(b) }
|
||||
elsif ary.empty? # The next two cases assume a non-empty array.
|
||||
[]
|
||||
elsif ary.first.respond_to?(:[]) && !ary.first[property].nil?
|
||||
ary.sort { |a, b| a[property].casecmp(b[property]) }
|
||||
elsif ary.first.respond_to?(property)
|
||||
ary.sort { |a, b| a.send(property).casecmp(b.send(property)) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -144,6 +158,8 @@ module Liquid
|
||||
|
||||
if property.nil?
|
||||
ary.uniq
|
||||
elsif ary.empty? # The next two cases assume a non-empty array.
|
||||
[]
|
||||
elsif ary.first.respond_to?(:[])
|
||||
ary.uniq{ |a| a[property] }
|
||||
end
|
||||
@@ -163,7 +179,8 @@ module Liquid
|
||||
if property == "to_liquid".freeze
|
||||
e
|
||||
elsif e.respond_to?(:[])
|
||||
e[property]
|
||||
r = e[property]
|
||||
r.is_a?(Proc) ? r.call : r
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -175,10 +192,10 @@ module Liquid
|
||||
|
||||
if property.nil?
|
||||
ary.compact
|
||||
elsif ary.empty? # The next two cases assume a non-empty array.
|
||||
[]
|
||||
elsif ary.first.respond_to?(:[])
|
||||
ary.reject{ |a| a[property].nil? }
|
||||
elsif ary.first.respond_to?(property)
|
||||
ary.reject { |a| a.send(property).nil? }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -208,6 +225,9 @@ module Liquid
|
||||
end
|
||||
|
||||
def concat(input, array)
|
||||
unless array.respond_to?(:to_ary)
|
||||
raise ArgumentError.new("concat filter requires an array argument")
|
||||
end
|
||||
InputIterator.new(input).concat(array)
|
||||
end
|
||||
|
||||
@@ -278,6 +298,12 @@ module Liquid
|
||||
array.last if array.respond_to?(:last)
|
||||
end
|
||||
|
||||
# absolute value
|
||||
def abs(input)
|
||||
result = Utils.to_number(input).abs
|
||||
result.is_a?(BigDecimal) ? result.to_f : result
|
||||
end
|
||||
|
||||
# addition
|
||||
def plus(input, operand)
|
||||
apply_operation(input, operand, :+)
|
||||
@@ -327,9 +353,12 @@ module Liquid
|
||||
raise Liquid::FloatDomainError, e.message
|
||||
end
|
||||
|
||||
def default(input, default_value = "".freeze)
|
||||
is_blank = input.respond_to?(:empty?) ? input.empty? : !input
|
||||
is_blank ? default_value : input
|
||||
def default(input, default_value = ''.freeze)
|
||||
if !input || input.respond_to?(:empty?) && input.empty?
|
||||
default_value
|
||||
else
|
||||
input
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
@@ -359,7 +388,7 @@ module Liquid
|
||||
end
|
||||
|
||||
def concat(args)
|
||||
to_a.concat args
|
||||
to_a.concat(args)
|
||||
end
|
||||
|
||||
def reverse
|
||||
@@ -374,6 +403,11 @@ module Liquid
|
||||
to_a.compact
|
||||
end
|
||||
|
||||
def empty?
|
||||
@input.each { return false }
|
||||
true
|
||||
end
|
||||
|
||||
def each
|
||||
@input.each do |e|
|
||||
yield(e.respond_to?(:to_liquid) ? e.to_liquid : e)
|
||||
|
||||
@@ -26,14 +26,20 @@ module Liquid
|
||||
end
|
||||
|
||||
def self.add_filter(filter)
|
||||
raise ArgumentError, "Expected module but got: #{f.class}" unless filter.is_a?(Module)
|
||||
raise ArgumentError, "Expected module but got: #{filter.class}" unless filter.is_a?(Module)
|
||||
unless self.class.include?(filter)
|
||||
send(:include, filter)
|
||||
@filter_methods.merge(filter.public_instance_methods.map(&:to_s))
|
||||
invokable_non_public_methods = (filter.private_instance_methods + filter.protected_instance_methods).select { |m| invokable?(m) }
|
||||
if invokable_non_public_methods.any?
|
||||
raise MethodOverrideError, "Filter overrides registered public methods as non public: #{invokable_non_public_methods.join(', ')}"
|
||||
else
|
||||
send(:include, filter)
|
||||
@filter_methods.merge(filter.public_instance_methods.map(&:to_s))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.global_filter(filter)
|
||||
@@strainer_class_cache.clear
|
||||
@@global_strainer.add_filter(filter)
|
||||
end
|
||||
|
||||
@@ -48,11 +54,13 @@ module Liquid
|
||||
def invoke(method, *args)
|
||||
if self.class.invokable?(method)
|
||||
send(method, *args)
|
||||
elsif @context && @context.strict_filters
|
||||
raise Liquid::UndefinedFilter, "undefined filter #{method}"
|
||||
else
|
||||
args.first
|
||||
end
|
||||
rescue ::ArgumentError => e
|
||||
raise Liquid::ArgumentError.new(e.message)
|
||||
raise Liquid::ArgumentError, e.message, e.backtrace
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
62
lib/liquid/tablerowloop_drop.rb
Normal file
62
lib/liquid/tablerowloop_drop.rb
Normal file
@@ -0,0 +1,62 @@
|
||||
module Liquid
|
||||
class TablerowloopDrop < Drop
|
||||
def initialize(length, cols)
|
||||
@length = length
|
||||
@row = 1
|
||||
@col = 1
|
||||
@cols = cols
|
||||
@index = 0
|
||||
end
|
||||
|
||||
attr_reader :length, :col, :row
|
||||
|
||||
def index
|
||||
@index + 1
|
||||
end
|
||||
|
||||
def index0
|
||||
@index
|
||||
end
|
||||
|
||||
def col0
|
||||
@col - 1
|
||||
end
|
||||
|
||||
def rindex
|
||||
@length - @index
|
||||
end
|
||||
|
||||
def rindex0
|
||||
@length - @index - 1
|
||||
end
|
||||
|
||||
def first
|
||||
@index == 0
|
||||
end
|
||||
|
||||
def last
|
||||
@index == @length - 1
|
||||
end
|
||||
|
||||
def col_first
|
||||
@col == 1
|
||||
end
|
||||
|
||||
def col_last
|
||||
@col == @cols
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def increment!
|
||||
@index += 1
|
||||
|
||||
if @col == @cols
|
||||
@col = 1
|
||||
@row += 1
|
||||
else
|
||||
@col += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -23,16 +23,28 @@ module Liquid
|
||||
def render(context)
|
||||
val = @from.render(context)
|
||||
context.scopes.last[@to] = val
|
||||
|
||||
inc = val.instance_of?(String) || val.instance_of?(Array) || val.instance_of?(Hash) ? val.length : 1
|
||||
context.resource_limits.assign_score += inc
|
||||
|
||||
context.resource_limits.assign_score += assign_score_of(val)
|
||||
''.freeze
|
||||
end
|
||||
|
||||
def blank?
|
||||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assign_score_of(val)
|
||||
if val.instance_of?(String)
|
||||
val.length
|
||||
elsif val.instance_of?(Array) || val.instance_of?(Hash)
|
||||
sum = 1
|
||||
# Uses #each to avoid extra allocations.
|
||||
val.each { |child| sum += assign_score_of(child) }
|
||||
sum
|
||||
else
|
||||
1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Template.register_tag('assign'.freeze, Assign)
|
||||
|
||||
@@ -37,7 +37,7 @@ module Liquid
|
||||
iteration = context.registers[:cycle][key]
|
||||
result = context.evaluate(@variables[iteration])
|
||||
iteration += 1
|
||||
iteration = 0 if iteration >= @variables.size
|
||||
iteration = 0 if iteration >= @variables.size
|
||||
context.registers[:cycle][key] = iteration
|
||||
result
|
||||
end
|
||||
|
||||
@@ -48,8 +48,10 @@ module Liquid
|
||||
|
||||
def initialize(tag_name, markup, options)
|
||||
super
|
||||
@from = @limit = nil
|
||||
parse_with_selected_parser(markup)
|
||||
@for_block = BlockBody.new
|
||||
@else_block = nil
|
||||
end
|
||||
|
||||
def parse(tokens)
|
||||
@@ -67,69 +69,13 @@ module Liquid
|
||||
end
|
||||
|
||||
def render(context)
|
||||
for_offsets = context.registers[:for] ||= Hash.new(0)
|
||||
for_stack = context.registers[:for_stack] ||= []
|
||||
segment = collection_segment(context)
|
||||
|
||||
parent_loop = for_stack.last
|
||||
for_stack.push(nil)
|
||||
|
||||
collection = context.evaluate(@collection_name)
|
||||
collection = collection.to_a if collection.is_a?(Range)
|
||||
|
||||
from = if @from == :continue
|
||||
for_offsets[@name].to_i
|
||||
if segment.empty?
|
||||
render_else(context)
|
||||
else
|
||||
context.evaluate(@from).to_i
|
||||
render_segment(context, segment)
|
||||
end
|
||||
|
||||
limit = context.evaluate(@limit)
|
||||
to = limit ? limit.to_i + from : nil
|
||||
|
||||
segment = Utils.slice_collection(collection, from, to)
|
||||
|
||||
return render_else(context) if segment.empty?
|
||||
|
||||
segment.reverse! if @reversed
|
||||
|
||||
result = ''
|
||||
|
||||
length = segment.length
|
||||
|
||||
# Store our progress through the collection for the continue flag
|
||||
for_offsets[@name] = from + segment.length
|
||||
|
||||
context.stack do
|
||||
segment.each_with_index do |item, index|
|
||||
context[@variable_name] = item
|
||||
loop_vars = {
|
||||
'name'.freeze => @name,
|
||||
'length'.freeze => length,
|
||||
'index'.freeze => index + 1,
|
||||
'index0'.freeze => index,
|
||||
'rindex'.freeze => length - index,
|
||||
'rindex0'.freeze => length - index - 1,
|
||||
'first'.freeze => (index == 0),
|
||||
'last'.freeze => (index == length - 1),
|
||||
'parentloop'.freeze => parent_loop
|
||||
}
|
||||
|
||||
context['forloop'.freeze] = loop_vars
|
||||
for_stack[-1] = loop_vars
|
||||
|
||||
result << @for_block.render(context)
|
||||
|
||||
# Handle any interrupts if they exist.
|
||||
if context.interrupt?
|
||||
interrupt = context.pop_interrupt
|
||||
break if interrupt.is_a? BreakInterrupt
|
||||
next if interrupt.is_a? ContinueInterrupt
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
result
|
||||
ensure
|
||||
for_stack.pop
|
||||
end
|
||||
|
||||
protected
|
||||
@@ -152,7 +98,7 @@ module Liquid
|
||||
def strict_parse(markup)
|
||||
p = Parser.new(markup)
|
||||
@variable_name = p.consume(:id)
|
||||
raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_in".freeze)) unless p.id?('in'.freeze)
|
||||
raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_in".freeze)) unless p.id?('in'.freeze)
|
||||
collection_name = p.expression
|
||||
@name = "#{@variable_name}-#{collection_name}"
|
||||
@collection_name = Expression.parse(collection_name)
|
||||
@@ -170,6 +116,63 @@ module Liquid
|
||||
|
||||
private
|
||||
|
||||
def collection_segment(context)
|
||||
offsets = context.registers[:for] ||= Hash.new(0)
|
||||
|
||||
from = if @from == :continue
|
||||
offsets[@name].to_i
|
||||
else
|
||||
context.evaluate(@from).to_i
|
||||
end
|
||||
|
||||
collection = context.evaluate(@collection_name)
|
||||
collection = collection.to_a if collection.is_a?(Range)
|
||||
|
||||
limit = context.evaluate(@limit)
|
||||
to = limit ? limit.to_i + from : nil
|
||||
|
||||
segment = Utils.slice_collection(collection, from, to)
|
||||
segment.reverse! if @reversed
|
||||
|
||||
offsets[@name] = from + segment.length
|
||||
|
||||
segment
|
||||
end
|
||||
|
||||
def render_segment(context, segment)
|
||||
for_stack = context.registers[:for_stack] ||= []
|
||||
length = segment.length
|
||||
|
||||
result = ''
|
||||
|
||||
context.stack do
|
||||
loop_vars = Liquid::ForloopDrop.new(@name, length, for_stack[-1])
|
||||
|
||||
for_stack.push(loop_vars)
|
||||
|
||||
begin
|
||||
context['forloop'.freeze] = loop_vars
|
||||
|
||||
segment.each_with_index do |item, index|
|
||||
context[@variable_name] = item
|
||||
result << @for_block.render(context)
|
||||
loop_vars.send(:increment!)
|
||||
|
||||
# Handle any interrupts if they exist.
|
||||
if context.interrupt?
|
||||
interrupt = context.pop_interrupt
|
||||
break if interrupt.is_a? BreakInterrupt
|
||||
next if interrupt.is_a? ContinueInterrupt
|
||||
end
|
||||
end
|
||||
ensure
|
||||
for_stack.pop
|
||||
end
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def set_attribute(key, expr)
|
||||
case key
|
||||
when 'offset'.freeze
|
||||
|
||||
@@ -42,8 +42,9 @@ module Liquid
|
||||
|
||||
def render(context)
|
||||
template_name = context.evaluate(@template_name_expr)
|
||||
partial = load_cached_partial(template_name, context)
|
||||
raise ArgumentError.new(options[:locale].t("errors.argument.include")) unless template_name
|
||||
|
||||
partial = load_cached_partial(template_name, context)
|
||||
context_variable_name = template_name.split('/'.freeze).last
|
||||
|
||||
variable = if @variable_name_expr
|
||||
|
||||
@@ -6,9 +6,7 @@ module Liquid
|
||||
def initialize(tag_name, markup, parse_context)
|
||||
super
|
||||
|
||||
unless markup =~ Syntax
|
||||
raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_unexpected_args".freeze, tag: tag_name))
|
||||
end
|
||||
ensure_valid_markup(tag_name, markup, parse_context)
|
||||
end
|
||||
|
||||
def parse(tokens)
|
||||
@@ -35,6 +33,14 @@ module Liquid
|
||||
def blank?
|
||||
@body.empty?
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def ensure_valid_markup(tag_name, markup, parse_context)
|
||||
unless markup =~ Syntax
|
||||
raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_unexpected_args".freeze, tag: tag_name))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Template.register_tag('raw'.freeze, Raw)
|
||||
|
||||
@@ -28,36 +28,21 @@ module Liquid
|
||||
|
||||
cols = context.evaluate(@attributes['cols'.freeze]).to_i
|
||||
|
||||
row = 1
|
||||
col = 0
|
||||
|
||||
result = "<tr class=\"row1\">\n"
|
||||
context.stack do
|
||||
tablerowloop = Liquid::TablerowloopDrop.new(length, cols)
|
||||
context['tablerowloop'.freeze] = tablerowloop
|
||||
|
||||
collection.each_with_index do |item, index|
|
||||
context[@variable_name] = item
|
||||
context['tablerowloop'.freeze] = {
|
||||
'length'.freeze => length,
|
||||
'index'.freeze => index + 1,
|
||||
'index0'.freeze => index,
|
||||
'col'.freeze => col + 1,
|
||||
'col0'.freeze => col,
|
||||
'rindex'.freeze => length - index,
|
||||
'rindex0'.freeze => length - index - 1,
|
||||
'first'.freeze => (index == 0),
|
||||
'last'.freeze => (index == length - 1),
|
||||
'col_first'.freeze => (col == 0),
|
||||
'col_last'.freeze => (col == cols - 1)
|
||||
}
|
||||
|
||||
col += 1
|
||||
result << "<td class=\"col#{tablerowloop.col}\">" << super << '</td>'
|
||||
|
||||
result << "<td class=\"col#{col}\">" << super << '</td>'
|
||||
|
||||
if col == cols && (index != length - 1)
|
||||
col = 0
|
||||
row += 1
|
||||
result << "</tr>\n<tr class=\"row#{row}\">"
|
||||
if tablerowloop.col_last && !tablerowloop.last
|
||||
result << "</tr>\n<tr class=\"row#{tablerowloop.row + 1}\">"
|
||||
end
|
||||
|
||||
tablerowloop.send(:increment!)
|
||||
end
|
||||
end
|
||||
result << "</tr>\n"
|
||||
|
||||
@@ -19,8 +19,10 @@ module Liquid
|
||||
@@file_system = BlankFileSystem.new
|
||||
|
||||
class TagRegistry
|
||||
include Enumerable
|
||||
|
||||
def initialize
|
||||
@tags = {}
|
||||
@tags = {}
|
||||
@cache = {}
|
||||
end
|
||||
|
||||
@@ -41,6 +43,10 @@ module Liquid
|
||||
@cache.delete(tag_name)
|
||||
end
|
||||
|
||||
def each(&block)
|
||||
@tags.each(&block)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def lookup_class(name)
|
||||
@@ -63,6 +69,11 @@ module Liquid
|
||||
# :error raises an error when tainted output is used
|
||||
attr_writer :taint_mode
|
||||
|
||||
attr_accessor :default_exception_renderer
|
||||
Template.default_exception_renderer = lambda do |exception|
|
||||
exception
|
||||
end
|
||||
|
||||
def file_system
|
||||
@@file_system
|
||||
end
|
||||
@@ -80,11 +91,11 @@ module Liquid
|
||||
end
|
||||
|
||||
def error_mode
|
||||
@error_mode || :lax
|
||||
@error_mode ||= :lax
|
||||
end
|
||||
|
||||
def taint_mode
|
||||
@taint_mode || :lax
|
||||
@taint_mode ||= :lax
|
||||
end
|
||||
|
||||
# Pass a module with filter methods which should be available
|
||||
@@ -107,6 +118,7 @@ module Liquid
|
||||
end
|
||||
|
||||
def initialize
|
||||
@rethrow_errors = false
|
||||
@resource_limits = ResourceLimits.new(self.class.default_resource_limits)
|
||||
end
|
||||
|
||||
@@ -160,7 +172,7 @@ module Liquid
|
||||
c = args.shift
|
||||
|
||||
if @rethrow_errors
|
||||
c.exception_handler = ->(e) { raise }
|
||||
c.exception_renderer = ->(e) { raise }
|
||||
end
|
||||
|
||||
c
|
||||
@@ -179,20 +191,10 @@ module Liquid
|
||||
when Hash
|
||||
options = args.pop
|
||||
|
||||
if options[:registers].is_a?(Hash)
|
||||
registers.merge!(options[:registers])
|
||||
end
|
||||
registers.merge!(options[:registers]) if options[:registers].is_a?(Hash)
|
||||
|
||||
if options[:filters]
|
||||
context.add_filters(options[:filters])
|
||||
end
|
||||
|
||||
if options[:exception_handler]
|
||||
context.exception_handler = options[:exception_handler]
|
||||
end
|
||||
when Module
|
||||
context.add_filters(args.pop)
|
||||
when Array
|
||||
apply_options_to_context(context, options)
|
||||
when Module, Array
|
||||
context.add_filters(args.pop)
|
||||
end
|
||||
|
||||
@@ -240,5 +242,13 @@ module Liquid
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
def apply_options_to_context(context, options)
|
||||
context.add_filters(options[:filters]) if options[:filters]
|
||||
context.global_filter = options[:global_filter] if options[:global_filter]
|
||||
context.exception_renderer = options[:exception_renderer] if options[:exception_renderer]
|
||||
context.strict_variables = options[:strict_variables] if options[:strict_variables]
|
||||
context.strict_filters = options[:strict_filters] if options[:strict_filters]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,7 +4,7 @@ module Liquid
|
||||
|
||||
def initialize(source, line_numbers = false)
|
||||
@source = source
|
||||
@line_number = 1 if line_numbers
|
||||
@line_number = line_numbers ? 1 : nil
|
||||
@tokens = tokenize
|
||||
end
|
||||
|
||||
|
||||
@@ -50,9 +50,13 @@ module Liquid
|
||||
when Numeric
|
||||
obj
|
||||
when String
|
||||
(obj.strip =~ /\A\d+\.\d+\z/) ? BigDecimal.new(obj) : obj.to_i
|
||||
(obj.strip =~ /\A-?\d+\.\d+\z/) ? BigDecimal.new(obj) : obj.to_i
|
||||
else
|
||||
0
|
||||
if obj.respond_to?(:to_number)
|
||||
obj.to_number
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -72,7 +76,7 @@ module Liquid
|
||||
when String
|
||||
Time.parse(obj)
|
||||
end
|
||||
rescue ArgumentError
|
||||
rescue ::ArgumentError
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -73,10 +73,16 @@ module Liquid
|
||||
end
|
||||
|
||||
def render(context)
|
||||
@filters.inject(context.evaluate(@name)) do |output, (filter_name, filter_args, filter_kwargs)|
|
||||
obj = @filters.inject(context.evaluate(@name)) do |output, (filter_name, filter_args, filter_kwargs)|
|
||||
filter_args = evaluate_filter_expressions(context, filter_args, filter_kwargs)
|
||||
context.invoke(filter_name, output, *filter_args)
|
||||
end.tap{ |obj| taint_check(context, obj) }
|
||||
end
|
||||
|
||||
obj = context.apply_global_filter(obj)
|
||||
|
||||
taint_check(context, obj)
|
||||
|
||||
obj
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -55,9 +55,11 @@ module Liquid
|
||||
object = object.send(key).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
|
||||
# keywords either. The only thing we got left is to return nil or
|
||||
# raise an exception if `strict_variables` option is set to true
|
||||
else
|
||||
return nil
|
||||
return nil unless context.strict_variables
|
||||
raise Liquid::UndefinedVariable, "undefined variable #{key}"
|
||||
end
|
||||
|
||||
# If we are dealing with a drop here we have to
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# encoding: utf-8
|
||||
module Liquid
|
||||
VERSION = "4.0.0.alpha"
|
||||
VERSION = "4.0.0"
|
||||
end
|
||||
|
||||
@@ -15,15 +15,16 @@ Gem::Specification.new do |s|
|
||||
s.license = "MIT"
|
||||
# s.description = "A secure, non-evaling end user template engine with aesthetic markup."
|
||||
|
||||
s.required_ruby_version = ">= 2.1.0"
|
||||
s.required_rubygems_version = ">= 1.3.7"
|
||||
|
||||
s.test_files = Dir.glob("{test}/**/*")
|
||||
s.files = Dir.glob("{lib}/**/*") + %w(MIT-LICENSE README.md)
|
||||
s.files = Dir.glob("{lib}/**/*") + %w(LICENSE README.md)
|
||||
|
||||
s.extra_rdoc_files = ["History.md", "README.md"]
|
||||
s.extra_rdoc_files = ["History.md", "README.md"]
|
||||
|
||||
s.require_path = "lib"
|
||||
|
||||
s.add_development_dependency 'rake'
|
||||
s.add_development_dependency 'rake', '~> 11.3'
|
||||
s.add_development_dependency 'minitest'
|
||||
end
|
||||
|
||||
@@ -13,7 +13,7 @@ class ContextDrop < Liquid::Drop
|
||||
@context['forloop.index']
|
||||
end
|
||||
|
||||
def before_method(method)
|
||||
def liquid_method_missing(method)
|
||||
@context[method]
|
||||
end
|
||||
end
|
||||
@@ -30,8 +30,8 @@ class ProductDrop < Liquid::Drop
|
||||
end
|
||||
|
||||
class CatchallDrop < Liquid::Drop
|
||||
def before_method(method)
|
||||
'method: ' << method.to_s
|
||||
def liquid_method_missing(method)
|
||||
'catchall_method: ' << method.to_s
|
||||
end
|
||||
end
|
||||
|
||||
@@ -59,7 +59,7 @@ class ProductDrop < Liquid::Drop
|
||||
end
|
||||
|
||||
class EnumerableDrop < Liquid::Drop
|
||||
def before_method(method)
|
||||
def liquid_method_missing(method)
|
||||
method
|
||||
end
|
||||
|
||||
@@ -93,7 +93,7 @@ end
|
||||
class RealEnumerableDrop < Liquid::Drop
|
||||
include Enumerable
|
||||
|
||||
def before_method(method)
|
||||
def liquid_method_missing(method)
|
||||
method
|
||||
end
|
||||
|
||||
@@ -157,14 +157,14 @@ class DropsTest < Minitest::Test
|
||||
assert_equal ' text1 ', output
|
||||
end
|
||||
|
||||
def test_unknown_method
|
||||
def test_catchall_unknown_method
|
||||
output = Liquid::Template.parse(' {{ product.catchall.unknown }} ').render!('product' => ProductDrop.new)
|
||||
assert_equal ' method: unknown ', output
|
||||
assert_equal ' catchall_method: unknown ', output
|
||||
end
|
||||
|
||||
def test_integer_argument_drop
|
||||
def test_catchall_integer_argument_drop
|
||||
output = Liquid::Template.parse(' {{ product.catchall[8] }} ').render!('product' => ProductDrop.new)
|
||||
assert_equal ' method: 8 ', output
|
||||
assert_equal ' catchall_method: 8 ', output
|
||||
end
|
||||
|
||||
def test_text_array_drop
|
||||
@@ -231,7 +231,7 @@ class DropsTest < Minitest::Test
|
||||
assert_equal '3', Liquid::Template.parse('{{collection.size}}').render!('collection' => EnumerableDrop.new)
|
||||
end
|
||||
|
||||
def test_enumerable_drop_will_invoke_before_method_for_clashing_method_names
|
||||
def test_enumerable_drop_will_invoke_liquid_method_missing_for_clashing_method_names
|
||||
["select", "each", "map", "cycle"].each do |method|
|
||||
assert_equal method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new)
|
||||
assert_equal method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new)
|
||||
|
||||
@@ -94,7 +94,23 @@ class ErrorHandlingTest < Minitest::Test
|
||||
)
|
||||
end
|
||||
|
||||
assert_match /Liquid syntax error \(line 4\)/, err.message
|
||||
assert_match(/Liquid syntax error \(line 4\)/, err.message)
|
||||
end
|
||||
|
||||
def test_with_line_numbers_adds_numbers_to_parser_errors_with_whitespace_trim
|
||||
err = assert_raises(SyntaxError) do
|
||||
Liquid::Template.parse(%q(
|
||||
foobar
|
||||
|
||||
{%- "cat" | foobar -%}
|
||||
|
||||
bla
|
||||
),
|
||||
line_numbers: true
|
||||
)
|
||||
end
|
||||
|
||||
assert_match(/Liquid syntax error \(line 4\)/, err.message)
|
||||
end
|
||||
|
||||
def test_parsing_warn_with_line_numbers_adds_numbers_to_lexer_errors
|
||||
@@ -186,22 +202,40 @@ class ErrorHandlingTest < Minitest::Test
|
||||
end
|
||||
end
|
||||
|
||||
def test_exception_handler_with_string_result
|
||||
template = Liquid::Template.parse('This is an argument error: {{ errors.argument_error }}')
|
||||
output = template.render({ 'errors' => ErrorDrop.new }, exception_handler: ->(e) { '' })
|
||||
assert_equal 'This is an argument error: ', output
|
||||
assert_equal [ArgumentError], template.errors.map(&:class)
|
||||
end
|
||||
|
||||
class InternalError < Liquid::Error
|
||||
end
|
||||
|
||||
def test_exception_handler_with_exception_result
|
||||
def test_default_exception_renderer_with_internal_error
|
||||
template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true)
|
||||
handler = ->(e) { e.is_a?(Liquid::Error) ? e : InternalError.new('internal') }
|
||||
output = template.render({ 'errors' => ErrorDrop.new }, exception_handler: handler)
|
||||
|
||||
output = template.render({ 'errors' => ErrorDrop.new })
|
||||
|
||||
assert_equal 'This is a runtime error: Liquid error (line 1): internal', output
|
||||
assert_equal [InternalError], template.errors.map(&:class)
|
||||
assert_equal [Liquid::InternalError], template.errors.map(&:class)
|
||||
end
|
||||
|
||||
def test_setting_default_exception_renderer
|
||||
old_exception_renderer = Liquid::Template.default_exception_renderer
|
||||
exceptions = []
|
||||
Liquid::Template.default_exception_renderer = ->(e) { exceptions << e; '' }
|
||||
template = Liquid::Template.parse('This is a runtime error: {{ errors.argument_error }}')
|
||||
|
||||
output = template.render({ 'errors' => ErrorDrop.new })
|
||||
|
||||
assert_equal 'This is a runtime error: ', output
|
||||
assert_equal [Liquid::ArgumentError], template.errors.map(&:class)
|
||||
ensure
|
||||
Liquid::Template.default_exception_renderer = old_exception_renderer if old_exception_renderer
|
||||
end
|
||||
|
||||
def test_exception_renderer_exposing_non_liquid_error
|
||||
template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true)
|
||||
exceptions = []
|
||||
handler = ->(e) { exceptions << e; e.cause }
|
||||
|
||||
output = template.render({ 'errors' => ErrorDrop.new }, exception_renderer: handler)
|
||||
|
||||
assert_equal 'This is a runtime error: runtime error', output
|
||||
assert_equal [Liquid::InternalError], exceptions.map(&:class)
|
||||
assert_equal exceptions, template.errors
|
||||
assert_equal '#<RuntimeError: runtime error>', exceptions.first.cause.inspect
|
||||
end
|
||||
|
||||
class TestFileSystem
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
require 'test_helper'
|
||||
|
||||
module MoneyFilter
|
||||
def money(input)
|
||||
sprintf(' %d$ ', input)
|
||||
end
|
||||
end
|
||||
|
||||
module CanadianMoneyFilter
|
||||
def money(input)
|
||||
sprintf(' %d$ CAD ', input)
|
||||
end
|
||||
end
|
||||
|
||||
class HashOrderingTest < Minitest::Test
|
||||
module MoneyFilter
|
||||
def money(input)
|
||||
sprintf(' %d$ ', input)
|
||||
end
|
||||
end
|
||||
|
||||
module CanadianMoneyFilter
|
||||
def money(input)
|
||||
sprintf(' %d$ CAD ', input)
|
||||
end
|
||||
end
|
||||
|
||||
include Liquid
|
||||
|
||||
def test_global_register_order
|
||||
|
||||
@@ -43,6 +43,14 @@ class OutputTest < Minitest::Test
|
||||
assert_equal expected, Template.parse(text).render!(@assigns)
|
||||
end
|
||||
|
||||
def test_variable_traversing_with_two_brackets
|
||||
text = %({{ site.data.menu[include.menu][include.locale] }})
|
||||
assert_equal "it works!", Template.parse(text).render!(
|
||||
"site" => { "data" => { "menu" => { "foo" => { "bar" => "it works!" } } } },
|
||||
"include" => { "menu" => "foo", "locale" => "bar" }
|
||||
)
|
||||
end
|
||||
|
||||
def test_variable_traversing
|
||||
text = %( {{car.bmw}} {{car.gm}} {{car.bmw}} )
|
||||
|
||||
|
||||
@@ -9,6 +9,10 @@ end
|
||||
class SecurityTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def setup
|
||||
@assigns = {}
|
||||
end
|
||||
|
||||
def test_no_instance_eval
|
||||
text = %( {{ '1+1' | instance_eval }} )
|
||||
expected = %( 1+1 )
|
||||
|
||||
@@ -41,6 +41,16 @@ class TestEnumerable < Liquid::Drop
|
||||
end
|
||||
end
|
||||
|
||||
class NumberLikeThing < Liquid::Drop
|
||||
def initialize(amount)
|
||||
@amount = amount
|
||||
end
|
||||
|
||||
def to_number
|
||||
@amount
|
||||
end
|
||||
end
|
||||
|
||||
class StandardFiltersTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
@@ -105,19 +115,20 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_equal '...', @filters.truncate('1234567890', 0)
|
||||
assert_equal '1234567890', @filters.truncate('1234567890')
|
||||
assert_equal "测试...", @filters.truncate("测试测试测试测试", 5)
|
||||
assert_equal '12341', @filters.truncate("1234567890", 5, 1)
|
||||
end
|
||||
|
||||
def test_split
|
||||
assert_equal ['12', '34'], @filters.split('12~34', '~')
|
||||
assert_equal ['A? ', ' ,Z'], @filters.split('A? ~ ~ ~ ,Z', '~ ~ ~')
|
||||
assert_equal ['A?Z'], @filters.split('A?Z', '~')
|
||||
# Regexp works although Liquid does not support.
|
||||
assert_equal ['A', 'Z'], @filters.split('AxZ', /x/)
|
||||
assert_equal [], @filters.split(nil, ' ')
|
||||
assert_equal ['A', 'Z'], @filters.split('A1Z', 1)
|
||||
end
|
||||
|
||||
def test_escape
|
||||
assert_equal '<strong>', @filters.escape('<strong>')
|
||||
assert_equal nil, @filters.escape(nil)
|
||||
assert_equal '<strong>', @filters.h('<strong>')
|
||||
end
|
||||
|
||||
@@ -130,12 +141,20 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_equal nil, @filters.url_encode(nil)
|
||||
end
|
||||
|
||||
def test_url_decode
|
||||
assert_equal 'foo bar', @filters.url_decode('foo+bar')
|
||||
assert_equal 'foo bar', @filters.url_decode('foo%20bar')
|
||||
assert_equal 'foo+1@example.com', @filters.url_decode('foo%2B1%40example.com')
|
||||
assert_equal nil, @filters.url_decode(nil)
|
||||
end
|
||||
|
||||
def test_truncatewords
|
||||
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 three', @filters.truncatewords('one two three')
|
||||
assert_equal 'Two small (13” x 5.5” x 10” high) baskets fit inside one large basket (13”...', @filters.truncatewords('Two small (13” x 5.5” x 10” high) baskets fit inside one large basket (13” x 16” x 10.5” high) with cover.', 15)
|
||||
assert_equal "测试测试测试测试", @filters.truncatewords('测试测试测试测试', 5)
|
||||
assert_equal 'one two1', @filters.truncatewords("one two three", 2, 1)
|
||||
end
|
||||
|
||||
def test_strip_html
|
||||
@@ -158,6 +177,32 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_equal [{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }], @filters.sort([{ "a" => 4 }, { "a" => 3 }, { "a" => 1 }, { "a" => 2 }], "a")
|
||||
end
|
||||
|
||||
def test_sort_when_property_is_sometimes_missing_puts_nils_last
|
||||
input = [
|
||||
{ "price" => 4, "handle" => "alpha" },
|
||||
{ "handle" => "beta" },
|
||||
{ "price" => 1, "handle" => "gamma" },
|
||||
{ "handle" => "delta" },
|
||||
{ "price" => 2, "handle" => "epsilon" }
|
||||
]
|
||||
expectation = [
|
||||
{ "price" => 1, "handle" => "gamma" },
|
||||
{ "price" => 2, "handle" => "epsilon" },
|
||||
{ "price" => 4, "handle" => "alpha" },
|
||||
{ "handle" => "delta" },
|
||||
{ "handle" => "beta" }
|
||||
]
|
||||
assert_equal expectation, @filters.sort(input, "price")
|
||||
end
|
||||
|
||||
def test_sort_empty_array
|
||||
assert_equal [], @filters.sort([], "a")
|
||||
end
|
||||
|
||||
def test_sort_natural_empty_array
|
||||
assert_equal [], @filters.sort_natural([], "a")
|
||||
end
|
||||
|
||||
def test_legacy_sort_hash
|
||||
assert_equal [{ a: 1, b: 2 }], @filters.sort({ a: 1, b: 2 })
|
||||
end
|
||||
@@ -177,6 +222,14 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_equal [testdrop], @filters.uniq([testdrop, TestDrop.new], 'test')
|
||||
end
|
||||
|
||||
def test_uniq_empty_array
|
||||
assert_equal [], @filters.uniq([], "a")
|
||||
end
|
||||
|
||||
def test_compact_empty_array
|
||||
assert_equal [], @filters.compact([], "a")
|
||||
end
|
||||
|
||||
def test_reverse
|
||||
assert_equal [4, 3, 2, 1], @filters.reverse([1, 2, 3, 4])
|
||||
end
|
||||
@@ -225,6 +278,19 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_template_result "testfoo", templ, "procs" => [p]
|
||||
end
|
||||
|
||||
def test_map_over_drops_returning_procs
|
||||
drops = [
|
||||
{
|
||||
"proc" => ->{ "foo" },
|
||||
},
|
||||
{
|
||||
"proc" => ->{ "bar" },
|
||||
},
|
||||
]
|
||||
templ = '{{ drops | map: "proc" }}'
|
||||
assert_template_result "foobar", templ, "drops" => drops
|
||||
end
|
||||
|
||||
def test_map_works_on_enumerables
|
||||
assert_template_result "123", '{{ foo | map: "foo" }}', "foo" => TestEnumerable.new
|
||||
end
|
||||
@@ -238,6 +304,10 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_template_result 'foobar', '{{ foo | last }}', 'foo' => [ThingWithToLiquid.new]
|
||||
end
|
||||
|
||||
def test_truncate_calls_to_liquid
|
||||
assert_template_result "wo...", '{{ foo | truncate: 5 }}', "foo" => TestThing.new
|
||||
end
|
||||
|
||||
def test_date
|
||||
assert_equal 'May', @filters.date(Time.parse("2006-05-05 10:00:00"), "%B")
|
||||
assert_equal 'June', @filters.date(Time.parse("2006-06-05 10:00:00"), "%B")
|
||||
@@ -263,8 +333,10 @@ class StandardFiltersTest < Minitest::Test
|
||||
|
||||
assert_equal '', @filters.date('', "%B")
|
||||
|
||||
assert_equal "07/05/2006", @filters.date(1152098955, "%m/%d/%Y")
|
||||
assert_equal "07/05/2006", @filters.date("1152098955", "%m/%d/%Y")
|
||||
with_timezone("UTC") do
|
||||
assert_equal "07/05/2006", @filters.date(1152098955, "%m/%d/%Y")
|
||||
assert_equal "07/05/2006", @filters.date("1152098955", "%m/%d/%Y")
|
||||
end
|
||||
end
|
||||
|
||||
def test_first_last
|
||||
@@ -321,20 +393,38 @@ class StandardFiltersTest < Minitest::Test
|
||||
def test_plus
|
||||
assert_template_result "2", "{{ 1 | plus:1 }}"
|
||||
assert_template_result "2.0", "{{ '1' | plus:'1.0' }}"
|
||||
|
||||
assert_template_result "5", "{{ price | plus:'2' }}", 'price' => NumberLikeThing.new(3)
|
||||
end
|
||||
|
||||
def test_minus
|
||||
assert_template_result "4", "{{ input | minus:operand }}", 'input' => 5, 'operand' => 1
|
||||
assert_template_result "2.3", "{{ '4.3' | minus:'2' }}"
|
||||
|
||||
assert_template_result "5", "{{ price | minus:'2' }}", 'price' => NumberLikeThing.new(7)
|
||||
end
|
||||
|
||||
def test_abs
|
||||
assert_template_result "17", "{{ 17 | abs }}"
|
||||
assert_template_result "17", "{{ -17 | abs }}"
|
||||
assert_template_result "17", "{{ '17' | abs }}"
|
||||
assert_template_result "17", "{{ '-17' | abs }}"
|
||||
assert_template_result "0", "{{ 0 | abs }}"
|
||||
assert_template_result "0", "{{ '0' | abs }}"
|
||||
assert_template_result "17.42", "{{ 17.42 | abs }}"
|
||||
assert_template_result "17.42", "{{ -17.42 | abs }}"
|
||||
assert_template_result "17.42", "{{ '17.42' | abs }}"
|
||||
assert_template_result "17.42", "{{ '-17.42' | abs }}"
|
||||
end
|
||||
|
||||
def test_times
|
||||
assert_template_result "12", "{{ 3 | times:4 }}"
|
||||
assert_template_result "0", "{{ 'foo' | times:4 }}"
|
||||
|
||||
assert_template_result "6", "{{ '2.1' | times:3 | replace: '.','-' | plus:0}}"
|
||||
|
||||
assert_template_result "7.25", "{{ 0.0725 | times:100 }}"
|
||||
assert_template_result "-7.25", '{{ "-0.0725" | times:100 }}'
|
||||
assert_template_result "7.25", '{{ "-0.0725" | times: -100 }}'
|
||||
assert_template_result "4", "{{ price | times:2 }}", 'price' => NumberLikeThing.new(2)
|
||||
end
|
||||
|
||||
def test_divided_by
|
||||
@@ -348,6 +438,8 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_raises(Liquid::ZeroDivisionError) do
|
||||
assert_template_result "4", "{{ 1 | modulo: 0 }}"
|
||||
end
|
||||
|
||||
assert_template_result "5", "{{ price | divided_by:2 }}", 'price' => NumberLikeThing.new(10)
|
||||
end
|
||||
|
||||
def test_modulo
|
||||
@@ -355,6 +447,8 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_raises(Liquid::ZeroDivisionError) do
|
||||
assert_template_result "4", "{{ 1 | modulo: 0 }}"
|
||||
end
|
||||
|
||||
assert_template_result "1", "{{ price | modulo:2 }}", 'price' => NumberLikeThing.new(3)
|
||||
end
|
||||
|
||||
def test_round
|
||||
@@ -364,6 +458,9 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_raises(Liquid::FloatDomainError) do
|
||||
assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | round }}"
|
||||
end
|
||||
|
||||
assert_template_result "5", "{{ price | round }}", 'price' => NumberLikeThing.new(4.6)
|
||||
assert_template_result "4", "{{ price | round }}", 'price' => NumberLikeThing.new(4.3)
|
||||
end
|
||||
|
||||
def test_ceil
|
||||
@@ -372,6 +469,8 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_raises(Liquid::FloatDomainError) do
|
||||
assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | ceil }}"
|
||||
end
|
||||
|
||||
assert_template_result "5", "{{ price | ceil }}", 'price' => NumberLikeThing.new(4.6)
|
||||
end
|
||||
|
||||
def test_floor
|
||||
@@ -380,6 +479,8 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_raises(Liquid::FloatDomainError) do
|
||||
assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | floor }}"
|
||||
end
|
||||
|
||||
assert_template_result "5", "{{ price | floor }}", 'price' => NumberLikeThing.new(5.4)
|
||||
end
|
||||
|
||||
def test_append
|
||||
@@ -393,8 +494,7 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_equal [1, 2, 'a'], @filters.concat([1, 2], ['a'])
|
||||
assert_equal [1, 2, 10], @filters.concat([1, 2], [10])
|
||||
|
||||
assert_raises(TypeError) do
|
||||
# no implicit conversion of Fixnum into Array
|
||||
assert_raises(Liquid::ArgumentError, "concat filter requires an array argument") do
|
||||
@filters.concat([1, 2], 10)
|
||||
end
|
||||
end
|
||||
@@ -417,4 +517,19 @@ class StandardFiltersTest < Minitest::Test
|
||||
def test_cannot_access_private_methods
|
||||
assert_template_result('a', "{{ 'a' | to_number }}")
|
||||
end
|
||||
|
||||
def test_date_raises_nothing
|
||||
assert_template_result('', "{{ '' | date: '%D' }}")
|
||||
assert_template_result('abc', "{{ 'abc' | date: '%D' }}")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def with_timezone(tz)
|
||||
old_tz = ENV['TZ']
|
||||
ENV['TZ'] = tz
|
||||
yield
|
||||
ensure
|
||||
ENV['TZ'] = old_tz
|
||||
end
|
||||
end # StandardFiltersTest
|
||||
|
||||
@@ -29,10 +29,10 @@ class IfElseTagTest < Minitest::Test
|
||||
assert_template_result(' YES ', '{% if a or b %} YES {% endif %}', 'a' => true, 'b' => true)
|
||||
assert_template_result(' YES ', '{% if a or b %} YES {% endif %}', 'a' => true, 'b' => false)
|
||||
assert_template_result(' YES ', '{% if a or b %} YES {% endif %}', 'a' => false, 'b' => true)
|
||||
assert_template_result('', '{% if a or b %} YES {% endif %}', 'a' => false, 'b' => false)
|
||||
assert_template_result('', '{% if a or b %} YES {% endif %}', 'a' => false, 'b' => false)
|
||||
|
||||
assert_template_result(' YES ', '{% if a or b or c %} YES {% endif %}', 'a' => false, 'b' => false, 'c' => true)
|
||||
assert_template_result('', '{% if a or b or c %} YES {% endif %}', 'a' => false, 'b' => false, 'c' => false)
|
||||
assert_template_result('', '{% if a or b or c %} YES {% endif %}', 'a' => false, 'b' => false, 'c' => false)
|
||||
end
|
||||
|
||||
def test_if_or_with_operators
|
||||
|
||||
@@ -217,6 +217,17 @@ class IncludeTagTest < Minitest::Test
|
||||
end
|
||||
end
|
||||
|
||||
def test_render_raise_argument_error_when_template_is_undefined
|
||||
assert_raises(Liquid::ArgumentError) do
|
||||
template = Liquid::Template.parse('{% include undefined_variable %}')
|
||||
template.render!
|
||||
end
|
||||
assert_raises(Liquid::ArgumentError) do
|
||||
template = Liquid::Template.parse('{% include nil %}')
|
||||
template.render!
|
||||
end
|
||||
end
|
||||
|
||||
def test_including_via_variable_value
|
||||
assert_template_result "from TestFileSystem", "{% assign page = 'pick_a_source' %}{% include page %}"
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ class RawTagTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_invalid_raw
|
||||
assert_match_syntax_error /tag was never closed/, '{% raw %} foo'
|
||||
assert_match_syntax_error /Valid syntax/, '{% raw } foo {% endraw %}'
|
||||
assert_match_syntax_error /Valid syntax/, '{% raw } foo %}{% endraw %}'
|
||||
assert_match_syntax_error(/tag was never closed/, '{% raw %} foo')
|
||||
assert_match_syntax_error(/Valid syntax/, '{% raw } foo {% endraw %}')
|
||||
assert_match_syntax_error(/Valid syntax/, '{% raw } foo %}{% endraw %}')
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,7 +2,7 @@ require 'test_helper'
|
||||
require 'timeout'
|
||||
|
||||
class TemplateContextDrop < Liquid::Drop
|
||||
def before_method(method)
|
||||
def liquid_method_missing(method)
|
||||
method
|
||||
end
|
||||
|
||||
@@ -27,6 +27,12 @@ class ErroneousDrop < Liquid::Drop
|
||||
end
|
||||
end
|
||||
|
||||
class DropWithUndefinedMethod < Liquid::Drop
|
||||
def foo
|
||||
'foo'
|
||||
end
|
||||
end
|
||||
|
||||
class TemplateTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
@@ -133,6 +139,17 @@ class TemplateTest < Minitest::Test
|
||||
refute_nil t.resource_limits.assign_score
|
||||
end
|
||||
|
||||
def test_resource_limits_assign_score_nested
|
||||
t = Template.parse("{% assign foo = 'aaaa' | reverse %}")
|
||||
|
||||
t.resource_limits.assign_score_limit = 3
|
||||
assert_equal "Liquid error: Memory limits exceeded", t.render
|
||||
assert t.resource_limits.reached?
|
||||
|
||||
t.resource_limits.assign_score_limit = 5
|
||||
assert_equal "", t.render!
|
||||
end
|
||||
|
||||
def test_resource_limits_aborts_rendering_after_first_error
|
||||
t = Template.parse("{% for a in (1..100) %} foo1 {% endfor %} bar {% for a in (1..100) %} foo2 {% endfor %}")
|
||||
t.resource_limits.render_score_limit = 50
|
||||
@@ -198,17 +215,109 @@ class TemplateTest < Minitest::Test
|
||||
assert_equal 'ruby error in drop', e.message
|
||||
end
|
||||
|
||||
def test_exception_handler_doesnt_reraise_if_it_returns_false
|
||||
def test_exception_renderer_that_returns_string
|
||||
exception = nil
|
||||
Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_handler: ->(e) { exception = e; false })
|
||||
handler = ->(e) { exception = e; '<!-- error -->' }
|
||||
|
||||
output = Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_renderer: handler)
|
||||
|
||||
assert exception.is_a?(Liquid::ZeroDivisionError)
|
||||
assert_equal '<!-- error -->', output
|
||||
end
|
||||
|
||||
def test_exception_handler_does_reraise_if_it_returns_true
|
||||
def test_exception_renderer_that_raises
|
||||
exception = nil
|
||||
assert_raises(Liquid::ZeroDivisionError) do
|
||||
Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_handler: ->(e) { exception = e; true })
|
||||
Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_renderer: ->(e) { exception = e; raise })
|
||||
end
|
||||
assert exception.is_a?(Liquid::ZeroDivisionError)
|
||||
end
|
||||
|
||||
def test_global_filter_option_on_render
|
||||
global_filter_proc = ->(output) { "#{output} filtered" }
|
||||
rendered_template = Template.parse("{{name}}").render({ "name" => "bob" }, global_filter: global_filter_proc)
|
||||
|
||||
assert_equal 'bob filtered', rendered_template
|
||||
end
|
||||
|
||||
def test_global_filter_option_when_native_filters_exist
|
||||
global_filter_proc = ->(output) { "#{output} filtered" }
|
||||
rendered_template = Template.parse("{{name | upcase}}").render({ "name" => "bob" }, global_filter: global_filter_proc)
|
||||
|
||||
assert_equal 'BOB filtered', rendered_template
|
||||
end
|
||||
|
||||
def test_undefined_variables
|
||||
t = Template.parse("{{x}} {{y}} {{z.a}} {{z.b}} {{z.c.d}}")
|
||||
result = t.render({ 'x' => 33, 'z' => { 'a' => 32, 'c' => { 'e' => 31 } } }, { strict_variables: true })
|
||||
|
||||
assert_equal '33 32 ', result
|
||||
assert_equal 3, t.errors.count
|
||||
assert_instance_of Liquid::UndefinedVariable, t.errors[0]
|
||||
assert_equal 'Liquid error: undefined variable y', t.errors[0].message
|
||||
assert_instance_of Liquid::UndefinedVariable, t.errors[1]
|
||||
assert_equal 'Liquid error: undefined variable b', t.errors[1].message
|
||||
assert_instance_of Liquid::UndefinedVariable, t.errors[2]
|
||||
assert_equal 'Liquid error: undefined variable d', t.errors[2].message
|
||||
end
|
||||
|
||||
def test_undefined_variables_raise
|
||||
t = Template.parse("{{x}} {{y}} {{z.a}} {{z.b}} {{z.c.d}}")
|
||||
|
||||
assert_raises UndefinedVariable do
|
||||
t.render!({ 'x' => 33, 'z' => { 'a' => 32, 'c' => { 'e' => 31 } } }, { strict_variables: true })
|
||||
end
|
||||
end
|
||||
|
||||
def test_undefined_drop_methods
|
||||
d = DropWithUndefinedMethod.new
|
||||
t = Template.new.parse('{{ foo }} {{ woot }}')
|
||||
result = t.render(d, { strict_variables: true })
|
||||
|
||||
assert_equal 'foo ', result
|
||||
assert_equal 1, t.errors.count
|
||||
assert_instance_of Liquid::UndefinedDropMethod, t.errors[0]
|
||||
end
|
||||
|
||||
def test_undefined_drop_methods_raise
|
||||
d = DropWithUndefinedMethod.new
|
||||
t = Template.new.parse('{{ foo }} {{ woot }}')
|
||||
|
||||
assert_raises UndefinedDropMethod do
|
||||
t.render!(d, { strict_variables: true })
|
||||
end
|
||||
end
|
||||
|
||||
def test_undefined_filters
|
||||
t = Template.parse("{{a}} {{x | upcase | somefilter1 | somefilter2 | somefilter3}}")
|
||||
filters = Module.new do
|
||||
def somefilter3(v)
|
||||
"-#{v}-"
|
||||
end
|
||||
end
|
||||
result = t.render({ 'a' => 123, 'x' => 'foo' }, { filters: [filters], strict_filters: true })
|
||||
|
||||
assert_equal '123 ', result
|
||||
assert_equal 1, t.errors.count
|
||||
assert_instance_of Liquid::UndefinedFilter, t.errors[0]
|
||||
assert_equal 'Liquid error: undefined filter somefilter1', t.errors[0].message
|
||||
end
|
||||
|
||||
def test_undefined_filters_raise
|
||||
t = Template.parse("{{x | somefilter1 | upcase | somefilter2}}")
|
||||
|
||||
assert_raises UndefinedFilter do
|
||||
t.render!({ 'x' => 'foo' }, { strict_filters: true })
|
||||
end
|
||||
end
|
||||
|
||||
def test_using_range_literal_works_as_expected
|
||||
t = Template.parse("{% assign foo = (x..y) %}{{ foo }}")
|
||||
result = t.render({ 'x' => 1, 'y' => 5 })
|
||||
assert_equal '1..5', result
|
||||
|
||||
t = Template.parse("{% assign nums = (x..y) %}{% for num in nums %}{{ num }}{% endfor %}")
|
||||
result = t.render({ 'x' => 1, 'y' => 5 })
|
||||
assert_equal '12345', result
|
||||
end
|
||||
end
|
||||
|
||||
525
test/integration/trim_mode_test.rb
Normal file
525
test/integration/trim_mode_test.rb
Normal file
@@ -0,0 +1,525 @@
|
||||
require 'test_helper'
|
||||
|
||||
class TrimModeTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
# Make sure the trim isn't applied to standard output
|
||||
def test_standard_output
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{{ 'John' }}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>
|
||||
John
|
||||
</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_variable_output_with_multiple_blank_lines
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
|
||||
|
||||
{{- 'John' -}}
|
||||
|
||||
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>John</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_tag_output_with_multiple_blank_lines
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
|
||||
|
||||
{%- if true -%}
|
||||
yes
|
||||
{%- endif -%}
|
||||
|
||||
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>yes</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
# Make sure the trim isn't applied to standard tags
|
||||
def test_standard_tags
|
||||
whitespace = ' '
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{% if true %}
|
||||
yes
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>
|
||||
#{whitespace}
|
||||
yes
|
||||
#{whitespace}
|
||||
</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{% if false %}
|
||||
no
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>
|
||||
#{whitespace}
|
||||
</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
# Make sure the trim isn't too agressive
|
||||
def test_no_trim_output
|
||||
text = '<p>{{- \'John\' -}}</p>'
|
||||
expected = '<p>John</p>'
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
# Make sure the trim isn't too agressive
|
||||
def test_no_trim_tags
|
||||
text = '<p>{%- if true -%}yes{%- endif -%}</p>'
|
||||
expected = '<p>yes</p>'
|
||||
assert_template_result(expected, text)
|
||||
|
||||
text = '<p>{%- if false -%}no{%- endif -%}</p>'
|
||||
expected = '<p></p>'
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_single_line_outer_tag
|
||||
text = '<p> {%- if true %} yes {% endif -%} </p>'
|
||||
expected = '<p> yes </p>'
|
||||
assert_template_result(expected, text)
|
||||
|
||||
text = '<p> {%- if false %} no {% endif -%} </p>'
|
||||
expected = '<p></p>'
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_single_line_inner_tag
|
||||
text = '<p> {% if true -%} yes {%- endif %} </p>'
|
||||
expected = '<p> yes </p>'
|
||||
assert_template_result(expected, text)
|
||||
|
||||
text = '<p> {% if false -%} no {%- endif %} </p>'
|
||||
expected = '<p> </p>'
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_single_line_post_tag
|
||||
text = '<p> {% if true -%} yes {% endif -%} </p>'
|
||||
expected = '<p> yes </p>'
|
||||
assert_template_result(expected, text)
|
||||
|
||||
text = '<p> {% if false -%} no {% endif -%} </p>'
|
||||
expected = '<p> </p>'
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_single_line_pre_tag
|
||||
text = '<p> {%- if true %} yes {%- endif %} </p>'
|
||||
expected = '<p> yes </p>'
|
||||
assert_template_result(expected, text)
|
||||
|
||||
text = '<p> {%- if false %} no {%- endif %} </p>'
|
||||
expected = '<p> </p>'
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_pre_trim_output
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{{- 'John' }}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>John
|
||||
</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_pre_trim_tags
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{%- if true %}
|
||||
yes
|
||||
{%- endif %}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>
|
||||
yes
|
||||
</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{%- if false %}
|
||||
no
|
||||
{%- endif %}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>
|
||||
</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_post_trim_output
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{{ 'John' -}}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>
|
||||
John</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_post_trim_tags
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{% if true -%}
|
||||
yes
|
||||
{% endif -%}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>
|
||||
yes
|
||||
</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{% if false -%}
|
||||
no
|
||||
{% endif -%}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>
|
||||
</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_pre_and_post_trim_tags
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{%- if true %}
|
||||
yes
|
||||
{% endif -%}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>
|
||||
yes
|
||||
</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{%- if false %}
|
||||
no
|
||||
{% endif -%}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p></p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_post_and_pre_trim_tags
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{% if true -%}
|
||||
yes
|
||||
{%- endif %}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>
|
||||
yes
|
||||
</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
|
||||
whitespace = ' '
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{% if false -%}
|
||||
no
|
||||
{%- endif %}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>
|
||||
#{whitespace}
|
||||
</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_trim_output
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{{- 'John' -}}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>John</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_trim_tags
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{%- if true -%}
|
||||
yes
|
||||
{%- endif -%}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>yes</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{%- if false -%}
|
||||
no
|
||||
{%- endif -%}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p></p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_whitespace_trim_output
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{{- 'John' -}},
|
||||
{{- '30' -}}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>John,30</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_whitespace_trim_tags
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{%- if true -%}
|
||||
yes
|
||||
{%- endif -%}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>yes</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{%- if false -%}
|
||||
no
|
||||
{%- endif -%}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p></p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_complex_trim_output
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{{- 'John' -}}
|
||||
{{- '30' -}}
|
||||
</p>
|
||||
<b>
|
||||
{{ 'John' -}}
|
||||
{{- '30' }}
|
||||
</b>
|
||||
<i>
|
||||
{{- 'John' }}
|
||||
{{ '30' -}}
|
||||
</i>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>John30</p>
|
||||
<b>
|
||||
John30
|
||||
</b>
|
||||
<i>John
|
||||
30</i>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_complex_trim
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
{%- if true -%}
|
||||
{%- if true -%}
|
||||
<p>
|
||||
{{- 'John' -}}
|
||||
</p>
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div><p>John</p></div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_raw_output
|
||||
whitespace = ' '
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
{% raw %}
|
||||
{%- if true -%}
|
||||
<p>
|
||||
{{- 'John' -}}
|
||||
</p>
|
||||
{%- endif -%}
|
||||
{% endraw %}
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
#{whitespace}
|
||||
{%- if true -%}
|
||||
<p>
|
||||
{{- 'John' -}}
|
||||
</p>
|
||||
{%- endif -%}
|
||||
#{whitespace}
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
end # TrimModeTest
|
||||
@@ -46,6 +46,8 @@ class BlockUnitTest < Minitest::Test
|
||||
def test_with_custom_tag
|
||||
Liquid::Template.register_tag("testtag", Block)
|
||||
assert Liquid::Template.parse("{% testtag %} {% endtesttag %}")
|
||||
ensure
|
||||
Liquid::Template.tags.delete('testtag')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -3,50 +3,54 @@ require 'test_helper'
|
||||
class ConditionUnitTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def setup
|
||||
@context = Liquid::Context.new
|
||||
end
|
||||
|
||||
def test_basic_condition
|
||||
assert_equal false, Condition.new(1, '==', 2).evaluate
|
||||
assert_equal true, Condition.new(1, '==', 1).evaluate
|
||||
end
|
||||
|
||||
def test_default_operators_evalute_true
|
||||
assert_evalutes_true 1, '==', 1
|
||||
assert_evalutes_true 1, '!=', 2
|
||||
assert_evalutes_true 1, '<>', 2
|
||||
assert_evalutes_true 1, '<', 2
|
||||
assert_evalutes_true 2, '>', 1
|
||||
assert_evalutes_true 1, '>=', 1
|
||||
assert_evalutes_true 2, '>=', 1
|
||||
assert_evalutes_true 1, '<=', 2
|
||||
assert_evalutes_true 1, '<=', 1
|
||||
assert_evaluates_true 1, '==', 1
|
||||
assert_evaluates_true 1, '!=', 2
|
||||
assert_evaluates_true 1, '<>', 2
|
||||
assert_evaluates_true 1, '<', 2
|
||||
assert_evaluates_true 2, '>', 1
|
||||
assert_evaluates_true 1, '>=', 1
|
||||
assert_evaluates_true 2, '>=', 1
|
||||
assert_evaluates_true 1, '<=', 2
|
||||
assert_evaluates_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
|
||||
assert_evaluates_true 1, '>', -1
|
||||
assert_evaluates_true (-1), '<', 1
|
||||
assert_evaluates_true 1.0, '>', -1.0
|
||||
assert_evaluates_true (-1.0), '<', 1.0
|
||||
end
|
||||
|
||||
def test_default_operators_evalute_false
|
||||
assert_evalutes_false 1, '==', 2
|
||||
assert_evalutes_false 1, '!=', 1
|
||||
assert_evalutes_false 1, '<>', 1
|
||||
assert_evalutes_false 1, '<', 0
|
||||
assert_evalutes_false 2, '>', 4
|
||||
assert_evalutes_false 1, '>=', 3
|
||||
assert_evalutes_false 2, '>=', 4
|
||||
assert_evalutes_false 1, '<=', 0
|
||||
assert_evalutes_false 1, '<=', 0
|
||||
assert_evaluates_false 1, '==', 2
|
||||
assert_evaluates_false 1, '!=', 1
|
||||
assert_evaluates_false 1, '<>', 1
|
||||
assert_evaluates_false 1, '<', 0
|
||||
assert_evaluates_false 2, '>', 4
|
||||
assert_evaluates_false 1, '>=', 3
|
||||
assert_evaluates_false 2, '>=', 4
|
||||
assert_evaluates_false 1, '<=', 0
|
||||
assert_evaluates_false 1, '<=', 0
|
||||
end
|
||||
|
||||
def test_contains_works_on_strings
|
||||
assert_evalutes_true 'bob', 'contains', 'o'
|
||||
assert_evalutes_true 'bob', 'contains', 'b'
|
||||
assert_evalutes_true 'bob', 'contains', 'bo'
|
||||
assert_evalutes_true 'bob', 'contains', 'ob'
|
||||
assert_evalutes_true 'bob', 'contains', 'bob'
|
||||
assert_evaluates_true 'bob', 'contains', 'o'
|
||||
assert_evaluates_true 'bob', 'contains', 'b'
|
||||
assert_evaluates_true 'bob', 'contains', 'bo'
|
||||
assert_evaluates_true 'bob', 'contains', 'ob'
|
||||
assert_evaluates_true 'bob', 'contains', 'bob'
|
||||
|
||||
assert_evalutes_false 'bob', 'contains', 'bob2'
|
||||
assert_evalutes_false 'bob', 'contains', 'a'
|
||||
assert_evalutes_false 'bob', 'contains', '---'
|
||||
assert_evaluates_false 'bob', 'contains', 'bob2'
|
||||
assert_evaluates_false 'bob', 'contains', 'a'
|
||||
assert_evaluates_false 'bob', 'contains', '---'
|
||||
end
|
||||
|
||||
def test_invalid_comparation_operator
|
||||
@@ -65,29 +69,29 @@ class ConditionUnitTest < Minitest::Test
|
||||
@context['array'] = [1, 2, 3, 4, 5]
|
||||
array_expr = VariableLookup.new("array")
|
||||
|
||||
assert_evalutes_false array_expr, 'contains', 0
|
||||
assert_evalutes_true array_expr, 'contains', 1
|
||||
assert_evalutes_true array_expr, 'contains', 2
|
||||
assert_evalutes_true array_expr, 'contains', 3
|
||||
assert_evalutes_true array_expr, 'contains', 4
|
||||
assert_evalutes_true array_expr, 'contains', 5
|
||||
assert_evalutes_false array_expr, 'contains', 6
|
||||
assert_evalutes_false array_expr, 'contains', "1"
|
||||
assert_evaluates_false array_expr, 'contains', 0
|
||||
assert_evaluates_true array_expr, 'contains', 1
|
||||
assert_evaluates_true array_expr, 'contains', 2
|
||||
assert_evaluates_true array_expr, 'contains', 3
|
||||
assert_evaluates_true array_expr, 'contains', 4
|
||||
assert_evaluates_true array_expr, 'contains', 5
|
||||
assert_evaluates_false array_expr, 'contains', 6
|
||||
assert_evaluates_false array_expr, 'contains', "1"
|
||||
end
|
||||
|
||||
def test_contains_returns_false_for_nil_operands
|
||||
@context = Liquid::Context.new
|
||||
assert_evalutes_false VariableLookup.new('not_assigned'), 'contains', '0'
|
||||
assert_evalutes_false 0, 'contains', VariableLookup.new('not_assigned')
|
||||
assert_evaluates_false VariableLookup.new('not_assigned'), 'contains', '0'
|
||||
assert_evaluates_false 0, 'contains', VariableLookup.new('not_assigned')
|
||||
end
|
||||
|
||||
def test_contains_return_false_on_wrong_data_type
|
||||
assert_evalutes_false 1, 'contains', 0
|
||||
assert_evaluates_false 1, 'contains', 0
|
||||
end
|
||||
|
||||
def test_contains_with_string_left_operand_coerces_right_operand_to_string
|
||||
assert_evalutes_true ' 1 ', 'contains', 1
|
||||
assert_evalutes_false ' 1 ', 'contains', 2
|
||||
assert_evaluates_true ' 1 ', 'contains', 1
|
||||
assert_evaluates_false ' 1 ', 'contains', 2
|
||||
end
|
||||
|
||||
def test_or_condition
|
||||
@@ -121,8 +125,8 @@ class ConditionUnitTest < Minitest::Test
|
||||
def test_should_allow_custom_proc_operator
|
||||
Condition.operators['starts_with'] = proc { |cond, left, right| left =~ %r{^#{right}} }
|
||||
|
||||
assert_evalutes_true 'bob', 'starts_with', 'b'
|
||||
assert_evalutes_false 'bob', 'starts_with', 'o'
|
||||
assert_evaluates_true 'bob', 'starts_with', 'b'
|
||||
assert_evaluates_false 'bob', 'starts_with', 'o'
|
||||
ensure
|
||||
Condition.operators.delete 'starts_with'
|
||||
end
|
||||
@@ -131,24 +135,24 @@ class ConditionUnitTest < Minitest::Test
|
||||
@context = Liquid::Context.new
|
||||
@context['one'] = @context['another'] = "gnomeslab-and-or-liquid"
|
||||
|
||||
assert_evalutes_true VariableLookup.new("one"), '==', VariableLookup.new("another")
|
||||
assert_evaluates_true VariableLookup.new("one"), '==', VariableLookup.new("another")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assert_evalutes_true(left, op, right)
|
||||
assert Condition.new(left, op, right).evaluate(@context || Liquid::Context.new),
|
||||
def assert_evaluates_true(left, op, right)
|
||||
assert Condition.new(left, op, right).evaluate(@context),
|
||||
"Evaluated false: #{left} #{op} #{right}"
|
||||
end
|
||||
|
||||
def assert_evalutes_false(left, op, right)
|
||||
assert !Condition.new(left, op, right).evaluate(@context || Liquid::Context.new),
|
||||
def assert_evaluates_false(left, op, right)
|
||||
assert !Condition.new(left, op, right).evaluate(@context),
|
||||
"Evaluated true: #{left} #{op} #{right}"
|
||||
end
|
||||
|
||||
def assert_evaluates_argument_error(left, op, right)
|
||||
assert_raises(Liquid::ArgumentError) do
|
||||
Condition.new(left, op, right).evaluate(@context || Liquid::Context.new)
|
||||
Condition.new(left, op, right).evaluate(@context)
|
||||
end
|
||||
end
|
||||
end # ConditionTest
|
||||
|
||||
@@ -267,7 +267,7 @@ class ContextUnitTest < Minitest::Test
|
||||
|
||||
def test_access_hashes_with_hash_notation
|
||||
@context['products'] = { 'count' => 5, 'tags' => ['deepsnow', 'freestyle'] }
|
||||
@context['product'] = { 'variants' => [ { 'title' => 'draft151cm' }, { 'title' => 'element151cm' } ] }
|
||||
@context['product'] = { 'variants' => [ { 'title' => 'draft151cm' }, { 'title' => 'element151cm' } ] }
|
||||
|
||||
assert_equal 5, @context['products["count"]']
|
||||
assert_equal 'deepsnow', @context['products["tags"][0]']
|
||||
@@ -305,7 +305,7 @@ class ContextUnitTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_first_can_appear_in_middle_of_callchain
|
||||
@context['product'] = { 'variants' => [ { 'title' => 'draft151cm' }, { 'title' => 'element151cm' } ] }
|
||||
@context['product'] = { 'variants' => [ { 'title' => 'draft151cm' }, { 'title' => 'element151cm' } ] }
|
||||
|
||||
assert_equal 'draft151cm', @context['product.variants[0].title']
|
||||
assert_equal 'element151cm', @context['product.variants[1].title']
|
||||
@@ -466,4 +466,18 @@ class ContextUnitTest < Minitest::Test
|
||||
assert contx
|
||||
assert_nil contx['poutine']
|
||||
end
|
||||
|
||||
def test_apply_global_filter
|
||||
global_filter_proc = ->(output) { "#{output} filtered" }
|
||||
|
||||
context = Context.new
|
||||
context.global_filter = global_filter_proc
|
||||
|
||||
assert_equal 'hi filtered', context.apply_global_filter('hi')
|
||||
end
|
||||
|
||||
def test_apply_global_filter_when_no_global_filter_exist
|
||||
context = Context.new
|
||||
assert_equal 'hi', context.apply_global_filter('hi')
|
||||
end
|
||||
end # ContextTest
|
||||
|
||||
@@ -29,6 +29,18 @@ class StrainerUnitTest < Minitest::Test
|
||||
end
|
||||
end
|
||||
|
||||
def test_stainer_argument_error_contains_backtrace
|
||||
strainer = Strainer.create(nil)
|
||||
begin
|
||||
strainer.invoke("public_filter", 1)
|
||||
rescue Liquid::ArgumentError => e
|
||||
assert_match(
|
||||
/\ALiquid error: wrong number of arguments \((1 for 0|given 1, expected 0)\)\z/,
|
||||
e.message)
|
||||
assert_equal e.backtrace[0].split(':')[0], __FILE__
|
||||
end
|
||||
end
|
||||
|
||||
def test_strainer_only_invokes_public_filter_methods
|
||||
strainer = Strainer.create(nil)
|
||||
assert_equal false, strainer.class.invokable?('__test__')
|
||||
@@ -65,4 +77,72 @@ class StrainerUnitTest < Minitest::Test
|
||||
assert_kind_of b, strainer
|
||||
assert_kind_of Liquid::StandardFilters, strainer
|
||||
end
|
||||
|
||||
def test_add_filter_when_wrong_filter_class
|
||||
c = Context.new
|
||||
s = c.strainer
|
||||
wrong_filter = ->(v) { v.reverse }
|
||||
|
||||
assert_raises ArgumentError do
|
||||
s.class.add_filter(wrong_filter)
|
||||
end
|
||||
end
|
||||
|
||||
module PrivateMethodOverrideFilter
|
||||
private
|
||||
|
||||
def public_filter
|
||||
"overriden as private"
|
||||
end
|
||||
end
|
||||
|
||||
def test_add_filter_raises_when_module_privately_overrides_registered_public_methods
|
||||
strainer = Context.new.strainer
|
||||
|
||||
error = assert_raises(Liquid::MethodOverrideError) do
|
||||
strainer.class.add_filter(PrivateMethodOverrideFilter)
|
||||
end
|
||||
assert_equal 'Liquid error: Filter overrides registered public methods as non public: public_filter', error.message
|
||||
end
|
||||
|
||||
module ProtectedMethodOverrideFilter
|
||||
protected
|
||||
|
||||
def public_filter
|
||||
"overriden as protected"
|
||||
end
|
||||
end
|
||||
|
||||
def test_add_filter_raises_when_module_overrides_registered_public_method_as_protected
|
||||
strainer = Context.new.strainer
|
||||
|
||||
error = assert_raises(Liquid::MethodOverrideError) do
|
||||
strainer.class.add_filter(ProtectedMethodOverrideFilter)
|
||||
end
|
||||
assert_equal 'Liquid error: Filter overrides registered public methods as non public: public_filter', error.message
|
||||
end
|
||||
|
||||
module PublicMethodOverrideFilter
|
||||
def public_filter
|
||||
"public"
|
||||
end
|
||||
end
|
||||
|
||||
def test_add_filter_does_not_raise_when_module_overrides_previously_registered_method
|
||||
strainer = Context.new.strainer
|
||||
strainer.class.add_filter(PublicMethodOverrideFilter)
|
||||
assert strainer.class.filter_methods.include?('public_filter')
|
||||
end
|
||||
|
||||
module LateAddedFilter
|
||||
def late_added_filter(input)
|
||||
"filtered"
|
||||
end
|
||||
end
|
||||
|
||||
def test_global_filter_clears_cache
|
||||
assert_equal 'input', Strainer.create(nil).invoke('late_added_filter', 'input')
|
||||
Strainer.global_filter(LateAddedFilter)
|
||||
assert_equal 'filtered', Strainer.create(nil).invoke('late_added_filter', 'input')
|
||||
end
|
||||
end # StrainerTest
|
||||
|
||||
@@ -67,4 +67,12 @@ class TemplateUnitTest < Minitest::Test
|
||||
Template.tags.delete('fake')
|
||||
assert_nil Template.tags['fake']
|
||||
end
|
||||
|
||||
def test_tags_can_be_looped_over
|
||||
Template.register_tag('fake', FakeTag)
|
||||
result = Template.tags.map { |name, klass| [name, klass] }
|
||||
assert result.include?(["fake", "TemplateUnitTest::FakeTag"])
|
||||
ensure
|
||||
Template.tags.delete('fake')
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user