Compare commits

..
Author SHA1 Message Date
Peter Zhu 8493f95acb Test invalid floats 2020-11-05 09:35:23 -05:00
58 changed files with 1326 additions and 689 deletions
+3 -2
View File
@@ -6,8 +6,9 @@ jobs:
strategy:
matrix:
entry:
- { ruby: 2.5, allowed-failure: false } # minimum supported
- { ruby: 3.0, allowed-failure: false } # latest
- { ruby: 2.5, allowed-failure: false }
- { ruby: 2.6, allowed-failure: false }
- { ruby: 2.7, allowed-failure: false }
- { ruby: ruby-head, allowed-failure: true }
name: test (${{ matrix.entry.ruby }})
steps:
File diff suppressed because it is too large Load Diff
+2 -9
View File
@@ -1,7 +1,5 @@
inherit_gem:
rubocop-shopify: rubocop.yml
inherit_from:
- 'https://shopify.github.io/ruby-style-guide/rubocop.yml'
- .rubocop_todo.yml
require: rubocop-performance
@@ -10,15 +8,10 @@ Performance:
Enabled: true
AllCops:
TargetRubyVersion: 2.5
NewCops: disable
TargetRubyVersion: 2.4
Exclude:
- 'vendor/bundle/**/*'
Naming/MethodName:
Exclude:
- 'example/server/liquid_servlet.rb'
# Backport https://github.com/Shopify/ruby-style-guide/pull/258
Layout/BeginEndAlignment:
Enabled: true
+8 -17
View File
@@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2020-12-11 18:53:41 UTC using RuboCop version 1.6.1.
# on 2019-09-11 06:34:25 +1000 using RuboCop version 0.74.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
@@ -14,26 +14,14 @@ Lint/InheritException:
Exclude:
- 'lib/liquid/interrupts.rb'
# Offense count: 113
# Offense count: 98
# Cop supports --auto-correct.
# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
# URISchemes: http, https
Layout/LineLength:
Max: 260
Max: 294
# Offense count: 8
Lint/MissingSuper:
Exclude:
- 'lib/liquid/forloop_drop.rb'
- 'lib/liquid/tablerowloop_drop.rb'
- 'test/integration/assign_test.rb'
- 'test/integration/context_test.rb'
- 'test/integration/filter_test.rb'
- 'test/integration/standard_filter_test.rb'
- 'test/integration/tags/for_tag_test.rb'
- 'test/integration/tags/table_row_test.rb'
# Offense count: 43
# Offense count: 44
Naming/ConstantName:
Exclude:
- 'lib/liquid.rb'
@@ -44,6 +32,7 @@ Naming/ConstantName:
- 'lib/liquid/tags/cycle.rb'
- 'lib/liquid/tags/for.rb'
- 'lib/liquid/tags/if.rb'
- 'lib/liquid/tags/include.rb'
- 'lib/liquid/tags/raw.rb'
- 'lib/liquid/tags/table_row.rb'
- 'lib/liquid/variable.rb'
@@ -51,7 +40,9 @@ Naming/ConstantName:
- 'performance/shopify/paginate.rb'
- 'test/integration/tags/include_tag_test.rb'
# Offense count: 2
# Offense count: 5
Style/ClassVars:
Exclude:
- 'lib/liquid/condition.rb'
- 'lib/liquid/strainer.rb'
- 'lib/liquid/template.rb'
+1 -4
View File
@@ -5,7 +5,6 @@
* Bugfixes
* Performance improvements
* Features that are likely to be useful to the majority of Liquid users
* Documentation updates that are concise and likely to be useful to the majority of Liquid users
## Things we won't merge
@@ -15,14 +14,12 @@
* 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
* Documentation changes that are verbose, incorrect or not important to most people (we want to keep it simple and easy to understand)
## Workflow
* [Sign the CLA](https://cla.shopify.com/) if you haven't already
* Fork the Liquid repository
* Create a new branch in your fork
* For updating [Liquid documentation](https://shopify.github.io/liquid/), create it from `gh-pages` branch. (You can skip tests.)
* 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
+2 -3
View File
@@ -18,11 +18,10 @@ group :benchmark, :test do
end
group :test do
gem 'rubocop', '~> 1.4', require: false
gem 'rubocop-shopify', '~> 1.0.7', require: false
gem 'rubocop', '~> 0.78.0', require: false
gem 'rubocop-performance', require: false
platform :mri, :truffleruby do
gem 'liquid-c', github: 'Shopify/liquid-c', ref: 'master'
gem 'liquid-c', github: 'Shopify/liquid-c', ref: 'pz-fix-invalid-float'
end
end
+3 -51
View File
@@ -1,58 +1,10 @@
# Liquid Change Log
## 5.1.0 / 2021-09-09
### Unreleased
### Features
* Add `base64_encode`, `base64_decode`, `base64_url_safe_encode`, and `base64_url_safe_decode` filters (#1450) [Daniel Insley]
* Introduce `to_liquid_value` in `Liquid::Drop` (#1441) [Michael Go]
### Fixes
* Fix support for using a String subclass for the liquid source (#1421) [Dylan Thacker-Smith]
* Add `ParseTreeVisitor` to `RangeLookup` (#1470) [CP Clermont]
* Translate `RangeError` to `Liquid::Error` for `truncatewords` with large int (#1431) [Dylan Thacker-Smith]
## 5.0.1 / 2021-03-24
### Fixes
* Add ParseTreeVisitor to Echo tag (#1414) [CP Clermont]
* Test with ruby 3.0 as the latest ruby version (#1398) [Dylan Thacker-Smith]
* Handle carriage return in newlines_to_br (#1391) [Unending]
### Performance Improvements
* Use split limit in truncatewords (#1361) [Dylan Thacker-Smith]
## 5.0.0 / 2021-01-06
### Features
* Add new `{% render %}` tag (#1122) [Samuel Doiron]
* Add support for `as` in `{% render %}` and `{% include %}` (#1181) [Mike Angell]
* Add `{% liquid %}` and `{% echo %}` tags (#1086) [Justin Li]
* Add [usage tracking](README.md#usage-tracking) [Mike Angell]
* Add `Tag.disable_tags` for disabling tags that prepend `Tag::Disableable` at render time (#1162, #1274, #1275) [Mike Angell]
* Support using a profiler for multiple renders (#1365, #1366) [Dylan Thacker-Smith]
### Fixes
* Fix catastrophic backtracking in `RANGES_REGEX` regular expression (#1357) [Dylan Thacker-Smith]
* Make sure the for tag's limit and offset are integers (#1094) [David Cornu]
* Invokable methods for enumerable reject include (#1151) [Thierry Joyal]
* Allow `default` filter to handle `false` as value (#1144) [Mike Angell]
* Fix render length resource limit so it doesn't multiply nested output (#1285) [Dylan Thacker-Smith]
* Fix duplication of text in raw tags (#1304) [Peter Zhu]
* Fix strict parsing of find variable with a name expression (#1317) [Dylan Thacker-Smith]
* Use monotonic time to measure durations in Liquid::Profiler (#1362) [Dylan Thacker-Smith]
### Breaking Changes
* Require Ruby >= 2.5 (#1131, #1310) [Mike Angell, Dylan Thacker-Smith]
* Remove support for taint checking (#1268) [Dylan Thacker-Smith]
* Split Strainer class into StrainerFactory and StrainerTemplate (#1208) [Thierry Joyal]
* Split Strainer class as a factory and a template (#1208) [Thierry Joyal]
* Remove handling of a nil context in the Strainer class (#1218) [Thierry Joyal]
* Handle `BlockBody#blank?` at parse time (#1287) [Dylan Thacker-Smith]
* Pass the tag markup and tokenizer to `Document#unknown_tag` (#1290) [Dylan Thacker-Smith]
* And several internal changes
### Performance Improvements
* Reduce allocations (#1073, #1091, #1115, #1099, #1117, #1141, #1322, #1341) [Richard Monette, Florian Weingarten, Ashwin Maroli]
* Improve resources limits performance (#1093, #1323) [Florian Weingarten, Dylan Thacker-Smith]
* StaticRegisters#fetch to raise on missing key (#1250) [Thierry Joyal]
## 4.0.3 / 2019-03-12
+2 -2
View File
@@ -5,7 +5,7 @@
* [Contributing guidelines](CONTRIBUTING.md)
* [Version history](History.md)
* [Liquid documentation from Shopify](https://shopify.dev/api/liquid)
* [Liquid documentation from Shopify](http://docs.shopify.com/themes/liquid-basics)
* [Liquid Wiki at GitHub](https://github.com/Shopify/liquid/wiki)
* [Website](http://liquidmarkup.org/)
@@ -56,7 +56,7 @@ For standard use you can just pass it the content of a file and call render with
Setting the error mode of Liquid lets you specify how strictly you want your templates to be interpreted.
Normally the parser is very lax and will accept almost anything without error. Unfortunately this can make
it very hard to debug and can lead to unexpected behaviour.
it very hard to debug and can lead to unexpected behaviour.
Liquid also comes with a stricter parser that can be used when editing templates to give better error messages
when templates are invalid. You can enable this new parser like this:
+1 -1
View File
@@ -36,7 +36,7 @@ module Liquid
VariableIncompleteEnd = /\}\}?/
QuotedString = /"[^"]*"|'[^']*'/
QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o
TagAttributes = /(\w[\w-]*)\s*\:\s*(#{QuotedFragment})/o
TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/o
AnyStartingTag = /#{TagStart}|#{VariableStart}/o
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/om
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om
+1 -3
View File
@@ -99,9 +99,7 @@ module Liquid
end
private def parse_liquid_tag(markup, parse_context)
liquid_tag_tokenizer = parse_context.new_tokenizer(
markup, start_line_number: parse_context.line_number, for_liquid_tag: true
)
liquid_tag_tokenizer = Tokenizer.new(markup, line_number: parse_context.line_number, for_liquid_tag: true)
parse_for_liquid_tag(liquid_tag_tokenizer, parse_context) do |end_tag_name, _end_tag_markup|
if end_tag_name
BlockBody.unknown_tag_in_liquid_tag(end_tag_name, parse_context)
+3 -3
View File
@@ -8,7 +8,7 @@ module Liquid
# c = Condition.new(1, '==', 1)
# c.evaluate #=> true
#
class Condition # :nodoc:
class Condition #:nodoc:
@@operators = {
'==' => ->(cond, left, right) { cond.send(:equal_variables, left, right) },
'!=' => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },
@@ -134,8 +134,8 @@ module Liquid
# return this as the result.
return context.evaluate(left) if op.nil?
left = Liquid::Utils.to_liquid_value(context.evaluate(left))
right = Liquid::Utils.to_liquid_value(context.evaluate(right))
left = context.evaluate(left)
right = context.evaluate(right)
operation = self.class.operators[op] || raise(Liquid::ArgumentError, "Unknown operator #{op}")
+4 -6
View File
@@ -18,8 +18,8 @@ module Liquid
attr_accessor :exception_renderer, :template_name, :partial, :global_filter, :strict_variables, :strict_filters
# rubocop:disable Metrics/ParameterLists
def self.build(environments: {}, outer_scope: {}, registers: {}, rethrow_errors: false, resource_limits: nil, static_environments: {}, &block)
new(environments, outer_scope, registers, rethrow_errors, resource_limits, static_environments, &block)
def self.build(environments: {}, outer_scope: {}, registers: {}, rethrow_errors: false, resource_limits: nil, static_environments: {})
new(environments, outer_scope, registers, rethrow_errors, resource_limits, static_environments)
end
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_environments = {})
@@ -44,8 +44,6 @@ module Liquid
self.exception_renderer = Liquid::RAISE_EXCEPTION_LAMBDA
end
yield self if block_given?
# Do this last, since it could result in this object being passed to a Proc in the environment
squash_instance_assigns_with_environments
end
@@ -124,7 +122,7 @@ module Liquid
# context['var'] = 'hi'
# end
#
# context['var'] #=> nil
# context['var] #=> nil
def stack(new_scope = {})
push(new_scope)
yield
@@ -137,7 +135,7 @@ module Liquid
def new_isolated_subcontext
check_overflow
self.class.build(
Context.build(
resource_limits: resource_limits,
static_environments: static_environments,
registers: StaticRegisters.new(registers)
+1 -1
View File
@@ -42,7 +42,7 @@ module Liquid
end
def render(context)
render_to_output_buffer(context, +'')
@body.render(context)
end
private
+1 -4
View File
@@ -14,10 +14,7 @@ module Liquid
DOUBLE_QUOTED_STRING = /\A\s*"(.*)"\s*\z/m
INTEGERS_REGEX = /\A\s*(-?\d+)\s*\z/
FLOATS_REGEX = /\A\s*(-?\d[\d\.]+)\s*\z/
# Use an atomic group (?>...) to avoid pathological backtracing from
# malicious input as described in https://github.com/Shopify/liquid/issues/1357
RANGES_REGEX = /\A\s*\(\s*(?>(\S+)\s*\.\.)\s*(\S+)\s*\)\s*\z/
RANGES_REGEX = /\A\s*\(\s*(\S+)\s*\.\.\s*(\S+)\s*\)\s*\z/
def self.parse(markup)
case markup
+1 -6
View File
@@ -9,12 +9,7 @@ module Liquid
@index = 0
end
attr_reader :length, :parentloop
def name
Usage.increment('forloop_drop_name')
@name
end
attr_reader :name, :length, :parentloop
def index
@index + 1
+1
View File
@@ -20,6 +20,7 @@
tag_termination: "Tag '%{token}' was not properly terminated with regexp: %{tag_end}"
variable_termination: "Variable '%{token}' was not properly terminated with regexp: %{tag_end}"
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"
render: "Syntax error in tag 'render' - Template name must be a quoted string"
argument:
-4
View File
@@ -23,10 +23,6 @@ module Liquid
Liquid::BlockBody.new
end
def new_tokenizer(markup, start_line_number: nil, for_liquid_tag: false)
Tokenizer.new(markup, line_number: start_line_number, for_liquid_tag: for_liquid_tag)
end
def parse_expression(markup)
Expression.parse(markup)
end
+95 -63
View File
@@ -25,7 +25,7 @@ module Liquid
# node.code
#
# # Which template and line number of this node.
# # The top-level template name is `nil` by default, but can be set in the Liquid::Context before rendering.
# # If top level, this will be "<root>".
# node.partial
# node.line_number
#
@@ -46,94 +46,126 @@ module Liquid
include Enumerable
class Timing
attr_reader :code, :template_name, :line_number, :children
attr_accessor :total_time
alias_method :render_time, :total_time
alias_method :partial, :template_name
attr_reader :code, :partial, :line_number, :children, :total_time, :self_time
def initialize(code: nil, template_name: nil, line_number: nil)
@code = code
@template_name = template_name
@line_number = line_number
@children = []
def initialize(node, partial)
@code = node.respond_to?(:raw) ? node.raw : node
@partial = partial
@line_number = node.respond_to?(:line_number) ? node.line_number : nil
@children = []
end
def self_time
@self_time ||= begin
total_children_time = 0.0
def self.start(node, partial)
new(node, partial).tap(&:start)
end
def start
@start_time = Time.now
end
def finish
@end_time = Time.now
@total_time = @end_time - @start_time
if @children.empty?
@self_time = @total_time
else
total_children_time = 0
@children.each do |child|
total_children_time += child.total_time
end
@total_time - total_children_time
@self_time = @total_time - total_children_time
end
end
def render_time
@end_time - @start_time
end
end
attr_reader :total_time
alias_method :total_render_time, :total_time
def initialize
@root_children = []
@current_children = nil
@total_time = 0.0
end
def profile(template_name, &block)
# nested renders are done from a tag that already has a timing node
return yield if @current_children
root_children = @root_children
render_idx = root_children.length
begin
@current_children = root_children
profile_node(template_name, &block)
ensure
@current_children = nil
if (timing = root_children[render_idx])
@total_time += timing.total_time
end
end
end
def children
children = @root_children
if children.length == 1
children.first.children
def self.profile_node_render(node)
if Profiler.current_profile && node.respond_to?(:render)
Profiler.current_profile.start_node(node)
output = yield
Profiler.current_profile.end_node(node)
output
else
children
yield
end
end
def self.profile_children(template_name)
if Profiler.current_profile
Profiler.current_profile.push_partial(template_name)
output = yield
Profiler.current_profile.pop_partial
output
else
yield
end
end
def self.current_profile
Thread.current[:liquid_profiler]
end
def initialize(partial_name = "<root>")
@partial_stack = [partial_name]
@root_timing = Timing.new("", current_partial)
@timing_stack = [@root_timing]
@render_start_at = Time.now
@render_end_at = @render_start_at
end
def start
Thread.current[:liquid_profiler] = self
@render_start_at = Time.now
end
def stop
Thread.current[:liquid_profiler] = nil
@render_end_at = Time.now
end
def total_render_time
@render_end_at - @render_start_at
end
def each(&block)
children.each(&block)
@root_timing.children.each(&block)
end
def [](idx)
children[idx]
@root_timing.children[idx]
end
def length
children.length
@root_timing.children.length
end
def profile_node(template_name, code: nil, line_number: nil)
timing = Timing.new(code: code, template_name: template_name, line_number: line_number)
parent_children = @current_children
start_time = monotonic_time
begin
@current_children = timing.children
yield
ensure
@current_children = parent_children
timing.total_time = monotonic_time - start_time
parent_children << timing
end
def start_node(node)
@timing_stack.push(Timing.start(node, current_partial))
end
private
def end_node(_node)
timing = @timing_stack.pop
timing.finish
def monotonic_time
Process.clock_gettime(Process::CLOCK_MONOTONIC)
@timing_stack.last.children << timing
end
def current_partial
@partial_stack.last
end
def push_partial(partial_name)
@partial_stack.push(partial_name)
end
def pop_partial
@partial_stack.pop
end
end
end
+6 -20
View File
@@ -3,33 +3,19 @@
module Liquid
module BlockBodyProfilingHook
def render_node(context, output, node)
if (profiler = context.profiler)
profiler.profile_node(context.template_name, code: node.raw, line_number: node.line_number) do
super
end
else
Profiler.profile_node_render(node) do
super
end
end
end
BlockBody.prepend(BlockBodyProfilingHook)
module DocumentProfilingHook
module IncludeProfilingHook
def render_to_output_buffer(context, output)
return super unless context.profiler
context.profiler.profile(context.template_name) { super }
Profiler.profile_children(context.evaluate(@template_name_expr).to_s) do
super
end
end
end
Document.prepend(DocumentProfilingHook)
module ContextProfilingHook
attr_accessor :profiler
def new_isolated_subcontext
new_context = super
new_context.profiler = profiler
new_context
end
end
Context.prepend(ContextProfilingHook)
Include.prepend(IncludeProfilingHook)
end
-8
View File
@@ -12,8 +12,6 @@ module Liquid
end
end
attr_reader :start_obj, :end_obj
def initialize(start_obj, end_obj)
@start_obj = start_obj
@end_obj = end_obj
@@ -37,11 +35,5 @@ module Liquid
Utils.to_integer(input)
end
end
class ParseTreeVisitor < Liquid::ParseTreeVisitor
def children
[@node.start_obj, @node.end_obj]
end
end
end
end
+7 -37
View File
@@ -1,12 +1,10 @@
# frozen_string_literal: true
require 'cgi'
require 'base64'
require 'bigdecimal'
module Liquid
module StandardFilters
MAX_INT = (1 << 31) - 1
HTML_ESCAPE = {
'&' => '&amp;',
'>' => '&gt;',
@@ -64,26 +62,6 @@ module Liquid
result
end
def base64_encode(input)
Base64.strict_encode64(input.to_s)
end
def base64_decode(input)
Base64.strict_decode64(input.to_s)
rescue ::ArgumentError
raise Liquid::ArgumentError, "invalid base64 provided to base64_decode"
end
def base64_url_safe_encode(input)
Base64.urlsafe_encode64(input.to_s)
end
def base64_url_safe_decode(input)
Base64.urlsafe_decode64(input.to_s)
rescue ::ArgumentError
raise Liquid::ArgumentError, "invalid base64 provided to base64_url_safe_decode"
end
def slice(input, offset, length = nil)
offset = Utils.to_integer(offset)
length = length ? Utils.to_integer(length) : 1
@@ -111,21 +89,13 @@ module Liquid
def truncatewords(input, words = 15, truncate_string = "...")
return if input.nil?
input = input.to_s
words = Utils.to_integer(words)
words = 1 if words <= 0
wordlist = input.to_s.split
words = Utils.to_integer(words)
wordlist = begin
input.split(" ", words + 1)
rescue RangeError
raise if words + 1 < MAX_INT
# e.g. integer #{words} too big to convert to `int'
raise Liquid::ArgumentError, "integer #{words} too big for truncatewords"
end
return input if wordlist.length <= words
l = words - 1
l = 0 if l < 0
wordlist.pop
wordlist.join(" ").concat(truncate_string.to_s)
wordlist.length > l ? wordlist[0..l].join(" ").concat(truncate_string.to_s) : input
end
# Split input string into an array of substrings separated by given pattern.
@@ -325,7 +295,7 @@ module Liquid
# Add <br /> tags in front of all newlines in input string
def newline_to_br(input)
input.to_s.gsub(/\r?\n/, "<br />\n")
input.to_s.gsub(/\n/, "<br />\n")
end
# Reformat a date using Ruby's core Time#strftime( string ) -> string
@@ -468,7 +438,7 @@ module Liquid
#
def default(input, default_value = '', options = {})
options = {} unless options.is_a?(Hash)
false_check = options['allow_false'] ? input.nil? : !Liquid::Utils.to_liquid_value(input)
false_check = options['allow_false'] ? input.nil? : !input
false_check || (input.respond_to?(:empty?) && input.empty?) ? default_value : input
end
+3 -11
View File
@@ -19,16 +19,15 @@ module Liquid
end
def parse(tokens)
body = case_body = new_body
body = new_body
body = @blocks.last.attachment while parse_body(body, tokens)
@blocks.reverse_each do |condition|
@blocks.each do |condition|
body = condition.attachment
unless body.frozen?
body.remove_blank_strings if blank?
body.freeze
end
end
case_body.freeze
end
def nodelist
@@ -52,14 +51,7 @@ module Liquid
@blocks.each do |block|
if block.else?
block.attachment.render_to_output_buffer(context, output) if execute_else_block
next
end
result = Liquid::Utils.to_liquid_value(
block.evaluate(context)
)
if result
elsif block.evaluate(context)
execute_else_block = false
block.attachment.render_to_output_buffer(context, output)
end
-8
View File
@@ -12,8 +12,6 @@ module Liquid
# {% echo user | link %}
#
class Echo < Tag
attr_reader :variable
def initialize(tag_name, markup, parse_context)
super
@variable = Variable.new(markup, parse_context)
@@ -22,12 +20,6 @@ module Liquid
def render(context)
@variable.render_to_output_buffer(context, +'')
end
class ParseTreeVisitor < Liquid::ParseTreeVisitor
def children
[@node.variable]
end
end
end
Template.register_tag('echo', Echo)
+2 -3
View File
@@ -63,11 +63,11 @@ module Liquid
parse_body(@else_block, tokens)
end
if blank?
@else_block&.remove_blank_strings
@for_block.remove_blank_strings
@else_block&.remove_blank_strings
end
@else_block&.freeze
@for_block.freeze
@else_block&.freeze
end
def nodelist
@@ -198,7 +198,6 @@ module Liquid
case key
when 'offset'
@from = if expr == 'continue'
Usage.increment('for_offset_continue')
:continue
else
parse_expression(expr)
+2 -6
View File
@@ -31,7 +31,7 @@ module Liquid
def parse(tokens)
while parse_body(@blocks.last.attachment, tokens)
end
@blocks.reverse_each do |block|
@blocks.each do |block|
block.attachment.remove_blank_strings if blank?
block.attachment.freeze
end
@@ -50,11 +50,7 @@ module Liquid
def render_to_output_buffer(context, output)
@blocks.each do |block|
result = Liquid::Utils.to_liquid_value(
block.evaluate(context)
)
if result
if block.evaluate(context)
return block.attachment.render_to_output_buffer(context, output)
end
end
+2 -10
View File
@@ -11,21 +11,13 @@ module Liquid
def render_to_output_buffer(context, output)
# First condition is interpreted backwards ( if not )
first_block = @blocks.first
result = Liquid::Utils.to_liquid_value(
first_block.evaluate(context)
)
unless result
unless first_block.evaluate(context)
return first_block.attachment.render_to_output_buffer(context, output)
end
# After the first condition unless works just like if
@blocks[1..-1].each do |block|
result = Liquid::Utils.to_liquid_value(
block.evaluate(context)
)
if result
if block.evaluate(context)
return block.attachment.render_to_output_buffer(context, output)
end
end
+27 -18
View File
@@ -106,9 +106,12 @@ module Liquid
# Parse source code.
# Returns self for easy chaining
def parse(source, options = {})
parse_context = configure_options(options)
tokenizer = parse_context.new_tokenizer(source, start_line_number: @line_numbers && 1)
@root = Document.parse(tokenizer, parse_context)
@options = options
@profiling = options[:profile]
@line_numbers = options[:line_numbers] || @profiling
parse_context = options.is_a?(ParseContext) ? options : ParseContext.new(options)
@root = Document.parse(tokenize(source), parse_context)
@warnings = parse_context.warnings
self
end
@@ -186,13 +189,11 @@ module Liquid
# Retrying a render resets resource usage
context.resource_limits.reset
if @profiling && context.profiler.nil?
@profiler = context.profiler = Liquid::Profiler.new
end
begin
# render the nodelist.
@root.render_to_output_buffer(context, output || +'')
with_profiling(context) do
@root.render_to_output_buffer(context, output || +'')
end
rescue Liquid::MemoryError => e
context.handle_error(e)
ensure
@@ -211,17 +212,25 @@ module Liquid
private
def configure_options(options)
if (profiling = options[:profile])
raise "Profiler not loaded, require 'liquid/profiler' first" unless defined?(Liquid::Profiler)
end
def tokenize(source)
Tokenizer.new(source, @line_numbers)
end
@options = options
@profiling = profiling
@line_numbers = options[:line_numbers] || @profiling
parse_context = options.is_a?(ParseContext) ? options : ParseContext.new(options)
@warnings = parse_context.warnings
parse_context
def with_profiling(context)
if @profiling && !context.partial
raise "Profiler not loaded, require 'liquid/profiler' first" unless defined?(Liquid::Profiler)
@profiler = Profiler.new(context.template_name)
@profiler.start
begin
yield
ensure
@profiler.stop
end
else
yield
end
end
def apply_options_to_context(context, options)
+2 -2
View File
@@ -5,7 +5,7 @@ module Liquid
attr_reader :line_number, :for_liquid_tag
def initialize(source, line_numbers = false, line_number: nil, for_liquid_tag: false)
@source = source.to_s.to_str
@source = source
@line_number = line_number || (line_numbers ? 1 : nil)
@for_liquid_tag = for_liquid_tag
@tokens = tokenize
@@ -24,7 +24,7 @@ module Liquid
private
def tokenize
return [] if @source.empty?
return [] if @source.to_s.empty?
return @source.split("\n") if @for_liquid_tag
-8
View File
@@ -81,13 +81,5 @@ module Liquid
rescue ::ArgumentError
nil
end
def self.to_liquid_value(obj)
# Enable "obj" to represent itself as a primitive value like integer, string, or boolean
return obj.to_liquid_value if obj.respond_to?(:to_liquid_value)
# Otherwise return the object itself
obj
end
end
end
-3
View File
@@ -40,9 +40,6 @@ module Liquid
@lookups.each_index do |i|
key = context.evaluate(@lookups[i])
# Cast "key" to its liquid value to enable it to act as a primitive value
key = Liquid::Utils.to_liquid_value(key)
# If object is a hash- or array-like object we look for the
# presence of the key and if its available we return it
if object.respond_to?(:[]) &&
+1 -1
View File
@@ -2,5 +2,5 @@
# frozen_string_literal: true
module Liquid
VERSION = "5.1.0"
VERSION = "4.0.3"
end
-2
View File
@@ -20,8 +20,6 @@ Gem::Specification.new do |s|
s.required_ruby_version = ">= 2.5.0"
s.required_rubygems_version = ">= 1.3.7"
s.metadata['allowed_push_host'] = 'https://rubygems.org'
s.test_files = Dir.glob("{test}/**/*")
s.files = Dir.glob("{lib}/**/*") + %w(LICENSE README.md)
+1 -9
View File
@@ -3,19 +3,11 @@
require 'yaml'
module Database
DATABASE_FILE_PATH = "#{__dir__}/vision.database.yml"
# Load the standard vision toolkit database and re-arrage it to be simply exportable
# to liquid as assigns. All this is based on Shopify
def self.tables
@tables ||= begin
db =
if YAML.respond_to?(:unsafe_load_file) # Only Psych 4+ can use unsafe_load_file
# unsafe_load_file is needed for YAML references
YAML.unsafe_load_file(DATABASE_FILE_PATH)
else
YAML.load_file(DATABASE_FILE_PATH)
end
db = YAML.load_file("#{__dir__}/vision.database.yml")
# From vision source
db['products'].each do |product|
+3 -7
View File
@@ -73,14 +73,10 @@ class ThemeRunner
private
def render_layout(template, layout, assigns)
assigns['content_for_layout'] = template.render!(assigns)
layout&.render!(assigns)
end
def compile_and_render(template, layout, assigns, page_template, template_file)
compiled_test = compile_test(template, layout, assigns, page_template, template_file)
render_layout(compiled_test[:tmpl], compiled_test[:layout], compiled_test[:assigns])
compiled_test = compile_test(template, layout, assigns, page_template, template_file)
assigns['content_for_layout'] = compiled_test[:tmpl].render!(assigns)
compiled_test[:layout].render!(assigns) if layout
end
def compile_all_tests
+1 -1
View File
@@ -44,7 +44,7 @@ class AssignTest < Minitest::Test
end
end
with_error_mode(:lax) do
assert(Template.parse("{% assign foo = ('X' | downcase) %}"))
assert Template.parse("{% assign foo = ('X' | downcase) %}")
end
end
+9 -9
View File
@@ -14,7 +14,7 @@ class BlockTest < Minitest::Test
def test_with_custom_tag
with_custom_tag('testtag', Block) do
assert(Liquid::Template.parse("{% testtag %} {% endtesttag %}"))
assert Liquid::Template.parse("{% testtag %} {% endtesttag %}")
end
end
@@ -28,13 +28,13 @@ class BlockTest < Minitest::Test
with_custom_tag('blabla', klass1) do
template = Liquid::Template.parse("{% blabla %} bla {% endblabla %}")
assert_equal('hello', template.render)
assert_equal 'hello', template.render
buf = +''
output = template.render({}, output: buf)
assert_equal('hello', output)
assert_equal('hello', buf)
assert_equal(buf.object_id, output.object_id)
assert_equal 'hello', output
assert_equal 'hello', buf
assert_equal buf.object_id, output.object_id
end
klass2 = Class.new(klass1) do
@@ -46,13 +46,13 @@ class BlockTest < Minitest::Test
with_custom_tag('blabla', klass2) do
template = Liquid::Template.parse("{% blabla %} foo {% endblabla %}")
assert_equal('foohellobar', template.render)
assert_equal 'foohellobar', template.render
buf = +''
output = template.render({}, output: buf)
assert_equal('foohellobar', output)
assert_equal('foohellobar', buf)
assert_equal(buf.object_id, output.object_id)
assert_equal 'foohellobar', output
assert_equal 'foohellobar', buf
assert_equal buf.object_id, output.object_id
end
end
end
+15 -16
View File
@@ -461,7 +461,6 @@ class ContextTest < Minitest::Test
end
def test_interrupt_avoids_object_allocations
@context.interrupt? # ruby 3.0.0 allocates on the first call
assert_no_object_allocations do
@context.interrupt?
end
@@ -567,9 +566,9 @@ class ContextTest < Minitest::Test
def test_disables_tag_specified
context = Context.new
context.with_disabled_tags(%w(foo bar)) do
assert_equal(true, context.tag_disabled?("foo"))
assert_equal(true, context.tag_disabled?("bar"))
assert_equal(false, context.tag_disabled?("unknown"))
assert_equal true, context.tag_disabled?("foo")
assert_equal true, context.tag_disabled?("bar")
assert_equal false, context.tag_disabled?("unknown")
end
end
@@ -577,19 +576,19 @@ class ContextTest < Minitest::Test
context = Context.new
context.with_disabled_tags(["foo"]) do
context.with_disabled_tags(["foo"]) do
assert_equal(true, context.tag_disabled?("foo"))
assert_equal(false, context.tag_disabled?("bar"))
assert_equal true, context.tag_disabled?("foo")
assert_equal false, context.tag_disabled?("bar")
end
context.with_disabled_tags(["bar"]) do
assert_equal(true, context.tag_disabled?("foo"))
assert_equal(true, context.tag_disabled?("bar"))
assert_equal true, context.tag_disabled?("foo")
assert_equal true, context.tag_disabled?("bar")
context.with_disabled_tags(["foo"]) do
assert_equal(true, context.tag_disabled?("foo"))
assert_equal(true, context.tag_disabled?("bar"))
assert_equal true, context.tag_disabled?("foo")
assert_equal true, context.tag_disabled?("bar")
end
end
assert_equal(true, context.tag_disabled?("foo"))
assert_equal(false, context.tag_disabled?("bar"))
assert_equal true, context.tag_disabled?("foo")
assert_equal false, context.tag_disabled?("bar")
end
end
@@ -607,16 +606,16 @@ class ContextTest < Minitest::Test
end
with_global_filter(global) do
assert_equal('Global test', Template.parse("{{'test' | notice }}").render!)
assert_equal('Local test', Template.parse("{{'test' | notice }}").render!({}, filters: [local]))
assert_equal 'Global test', Template.parse("{{'test' | notice }}").render!
assert_equal 'Local test', Template.parse("{{'test' | notice }}").render!({}, filters: [local])
end
end
def test_has_key_will_not_add_an_error_for_missing_keys
with_error_mode(:strict) do
with_error_mode :strict do
context = Context.new
context.key?('unknown')
assert_empty(context.errors)
assert_empty context.errors
end
end
+13 -13
View File
@@ -167,7 +167,7 @@ class DropsTest < Minitest::Test
def test_object_methods_not_allowed
[:dup, :clone, :singleton_class, :eval, :class_eval, :inspect].each do |method|
output = Liquid::Template.parse(" {{ product.#{method} }} ").render!('product' => ProductDrop.new)
assert_equal(' ', output)
assert_equal ' ', output
end
end
@@ -210,28 +210,28 @@ class DropsTest < Minitest::Test
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))
assert_equal(method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new))
assert_equal(method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new))
assert_equal method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new)
assert_equal method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new)
assert_equal method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new)
assert_equal method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new)
end
end
def test_some_enumerable_methods_still_get_invoked
[:count, :max].each do |method|
assert_equal("3", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new))
assert_equal("3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new))
assert_equal("3", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new))
assert_equal("3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new))
assert_equal "3", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new)
assert_equal "3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new)
assert_equal "3", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new)
assert_equal "3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new)
end
assert_equal("yes", Liquid::Template.parse("{% if collection contains 3 %}yes{% endif %}").render!('collection' => RealEnumerableDrop.new))
[:min, :first].each do |method|
assert_equal("1", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new))
assert_equal("1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new))
assert_equal("1", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new))
assert_equal("1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new))
assert_equal "1", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new)
assert_equal "1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new)
assert_equal "1", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new)
assert_equal "1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new)
end
end
+1 -1
View File
@@ -193,7 +193,7 @@ class ErrorHandlingTest < Minitest::Test
# Liquid should not catch Exceptions that are not subclasses of StandardError, like Interrupt and NoMemoryError
def test_exceptions_propagate
assert_raises(Exception) do
assert_raises Exception do
template = Liquid::Template.parse('{{ errors.exception }}')
template.render('errors' => ErrorDrop.new)
end
-24
View File
@@ -1,24 +0,0 @@
# frozen_string_literal: true
require 'test_helper'
class FilterKwargTest < Minitest::Test
module KwargFilter
def html_tag(_tag, attributes)
attributes
.map { |key, value| "#{key}='#{value}'" }
.join(' ')
end
end
include Liquid
def test_can_parse_data_kwargs
with_global_filter(KwargFilter) do
assert_equal(
"data-src='src' data-widths='100, 200'",
Template.parse("{{ 'img' | html_tag: data-src: 'src', data-widths: '100, 200' }}").render(nil, nil)
)
end
end
end
+3 -3
View File
@@ -169,9 +169,9 @@ class FiltersInTemplate < Minitest::Test
def test_local_global
with_global_filter(MoneyFilter) do
assert_equal(" 1000$ ", Template.parse("{{1000 | money}}").render!(nil, nil))
assert_equal(" 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, filters: CanadianMoneyFilter))
assert_equal(" 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, filters: [CanadianMoneyFilter]))
assert_equal " 1000$ ", Template.parse("{{1000 | money}}").render!(nil, nil)
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, filters: CanadianMoneyFilter)
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, filters: [CanadianMoneyFilter])
end
end
+1 -1
View File
@@ -19,7 +19,7 @@ class HashOrderingTest < Minitest::Test
def test_global_register_order
with_global_filter(MoneyFilter, CanadianMoneyFilter) do
assert_equal(" 1000$ CAD ", Template.parse("{{1000 | money}}").render(nil, nil))
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render(nil, nil)
end
end
end
+13 -1
View File
@@ -32,7 +32,7 @@ class ParsingQuirksTest < Minitest::Test
assert(Template.parse("{{test}}"))
with_error_mode(:lax) do
assert(Template.parse("{{|test}}"))
assert Template.parse("{{|test}}")
end
with_error_mode(:strict) do
@@ -131,4 +131,16 @@ class ParsingQuirksTest < Minitest::Test
def test_contains_in_id
assert_template_result(' YES ', '{% if containsallshipments == true %} YES {% endif %}', 'containsallshipments' => true)
end
def test_invalid_float_with_no_leading_integer
with_error_mode(:lax) do
assert_template_result("", "{{ -.1 }}")
assert_template_result("", "{{ .1 }}")
end
with_error_mode(:strict) do
assert_raises(SyntaxError) { Template.parse("{{ -.1 }}") }
assert_raises(SyntaxError) { Template.parse("{{ .1 }}") }
end
end
end # ParsingQuirksTest
@@ -2,7 +2,7 @@
require 'test_helper'
class ProfilerTest < Minitest::Test
class RenderProfilingTest < Minitest::Test
include Liquid
class ProfilingFileSystem
@@ -62,17 +62,6 @@ class ProfilerTest < Minitest::Test
assert_equal(2, included_children[1].line_number)
end
def test_profiling_render_tag
t = Template.parse("{% render 'a_template' %}", profile: true)
t.render!
render_children = t.profiler[0].children
render_children.each do |timing|
assert_equal('a_template', timing.partial)
end
assert_equal([1, 2], render_children.map(&:line_number))
end
def test_profiling_times_the_rendering_of_tokens
t = Template.parse("{% include 'a_template' %}", profile: true)
t.render!
@@ -88,37 +77,6 @@ class ProfilerTest < Minitest::Test
assert(t.profiler.total_render_time >= 0, "Total render time was not calculated")
end
class SleepTag < Liquid::Tag
def initialize(tag_name, markup, parse_context)
super
@duration = Float(markup)
end
def render_to_output_buffer(_context, _output)
sleep(@duration)
end
end
def test_profiling_multiple_renders
with_custom_tag('sleep', SleepTag) do
context = Liquid::Context.new
t = Liquid::Template.parse("{% sleep 0.001 %}", profile: true)
context.template_name = 'index'
t.render!(context)
context.template_name = 'layout'
first_render_time = context.profiler.total_time
t.render!(context)
profiler = context.profiler
children = profiler.children
assert_operator(first_render_time, :>=, 0.001)
assert_operator(profiler.total_time, :>=, 0.001 + first_render_time)
assert_equal(["index", "layout"], children.map(&:template_name))
assert_equal([nil, nil], children.map(&:code))
assert_equal(profiler.total_time, children.map(&:total_time).reduce(&:+))
end
end
def test_profiling_uses_include_to_mark_children
t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}", profile: true)
t.render!
@@ -133,7 +91,7 @@ class ProfilerTest < Minitest::Test
include_node = t.profiler[1]
include_node.children.each do |child|
assert_equal("a_template", child.partial)
assert_equal "a_template", child.partial
end
end
@@ -143,12 +101,12 @@ class ProfilerTest < Minitest::Test
a_template = t.profiler[1]
a_template.children.each do |child|
assert_equal("a_template", child.partial)
assert_equal "a_template", child.partial
end
b_template = t.profiler[2]
b_template.children.each do |child|
assert_equal("b_template", child.partial)
assert_equal "b_template", child.partial
end
end
@@ -158,12 +116,12 @@ class ProfilerTest < Minitest::Test
a_template1 = t.profiler[1]
a_template1.children.each do |child|
assert_equal("a_template", child.partial)
assert_equal "a_template", child.partial
end
a_template2 = t.profiler[2]
a_template2.children.each do |child|
assert_equal("a_template", child.partial)
assert_equal "a_template", child.partial
end
end
+15 -73
View File
@@ -145,40 +145,6 @@ class StandardFiltersTest < Minitest::Test
assert_equal('&lt;strong&gt;Hulk&lt;/strong&gt;', @filters.escape_once('&lt;strong&gt;Hulk</strong>'))
end
def test_base64_encode
assert_equal('b25lIHR3byB0aHJlZQ==', @filters.base64_encode('one two three'))
assert_equal('', @filters.base64_encode(nil))
end
def test_base64_decode
assert_equal('one two three', @filters.base64_decode('b25lIHR3byB0aHJlZQ=='))
exception = assert_raises(Liquid::ArgumentError) do
@filters.base64_decode("invalidbase64")
end
assert_equal('Liquid error: invalid base64 provided to base64_decode', exception.message)
end
def test_base64_url_safe_encode
assert_equal(
'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXogQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVogMTIzNDU2Nzg5MCAhQCMkJV4mKigpLT1fKy8_Ljo7W117fVx8',
@filters.base64_url_safe_encode('abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 !@#$%^&*()-=_+/?.:;[]{}\|')
)
assert_equal('', @filters.base64_url_safe_encode(nil))
end
def test_base64_url_safe_decode
assert_equal(
'abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 !@#$%^&*()-=_+/?.:;[]{}\|',
@filters.base64_url_safe_decode('YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXogQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVogMTIzNDU2Nzg5MCAhQCMkJV4mKigpLT1fKy8_Ljo7W117fVx8')
)
exception = assert_raises(Liquid::ArgumentError) do
@filters.base64_url_safe_decode("invalidbase64")
end
assert_equal('Liquid error: invalid base64 provided to base64_url_safe_decode', exception.message)
end
def test_url_encode
assert_equal('foo%2B1%40example.com', @filters.url_encode('[email protected]'))
assert_equal('1', @filters.url_encode(1))
@@ -193,7 +159,7 @@ class StandardFiltersTest < Minitest::Test
assert_equal('1', @filters.url_decode(1))
assert_equal('2001-02-03', @filters.url_decode(Date.new(2001, 2, 3)))
assert_nil(@filters.url_decode(nil))
exception = assert_raises(Liquid::ArgumentError) do
exception = assert_raises Liquid::ArgumentError do
@filters.url_decode('%ff')
end
assert_equal('Liquid error: invalid byte sequence in UTF-8', exception.message)
@@ -205,17 +171,10 @@ class StandardFiltersTest < Minitest::Test
assert_equal('one two three', @filters.truncatewords('one two three'))
assert_equal(
'Two small (13&#8221; x 5.5&#8221; x 10&#8221; high) baskets fit inside one large basket (13&#8221;...',
@filters.truncatewords('Two small (13&#8221; x 5.5&#8221; x 10&#8221; high) baskets fit inside one large basket (13&#8221; x 16&#8221; x 10.5&#8221; high) with cover.', 15)
@filters.truncatewords('Two small (13&#8221; x 5.5&#8221; x 10&#8221; high) baskets fit inside one large basket (13&#8221; x 16&#8221; x 10.5&#8221; high) with cover.', 15)
)
assert_equal("测试测试测试测试", @filters.truncatewords('测试测试测试测试', 5))
assert_equal('one two1', @filters.truncatewords("one two three", 2, 1))
assert_equal('one two three...', @filters.truncatewords("one two\tthree\nfour", 3))
assert_equal('one two...', @filters.truncatewords("one two three four", 2))
assert_equal('one...', @filters.truncatewords("one two three four", 0))
exception = assert_raises(Liquid::ArgumentError) do
@filters.truncatewords("one two three four", 1 << 31)
end
assert_equal("Liquid error: integer #{1 << 31} too big for truncatewords", exception.message)
end
def test_strip_html
@@ -327,7 +286,7 @@ class StandardFiltersTest < Minitest::Test
[3],
]
assert_raises(Liquid::ArgumentError) do
assert_raises Liquid::ArgumentError do
@filters.sort(foo, "bar")
end
end
@@ -343,7 +302,7 @@ class StandardFiltersTest < Minitest::Test
[3],
]
assert_raises(Liquid::ArgumentError) do
assert_raises Liquid::ArgumentError do
@filters.sort_natural(foo, "bar")
end
end
@@ -378,7 +337,7 @@ class StandardFiltersTest < Minitest::Test
[3],
]
assert_raises(Liquid::ArgumentError) do
assert_raises Liquid::ArgumentError do
@filters.uniq(foo, "bar")
end
end
@@ -394,7 +353,7 @@ class StandardFiltersTest < Minitest::Test
[3],
]
assert_raises(Liquid::ArgumentError) do
assert_raises Liquid::ArgumentError do
@filters.compact(foo, "bar")
end
end
@@ -471,7 +430,7 @@ class StandardFiltersTest < Minitest::Test
[3],
]
assert_raises(Liquid::ArgumentError) do
assert_raises Liquid::ArgumentError do
@filters.map(foo, "bar")
end
end
@@ -482,7 +441,7 @@ class StandardFiltersTest < Minitest::Test
[2],
[3],
]
assert_raises(Liquid::ArgumentError) do
assert_raises Liquid::ArgumentError do
@filters.map(foo, nil)
end
end
@@ -526,8 +485,8 @@ class StandardFiltersTest < Minitest::Test
assert_equal('', @filters.date('', "%B"))
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"))
assert_equal "07/05/2006", @filters.date(1152098955, "%m/%d/%Y")
assert_equal "07/05/2006", @filters.date("1152098955", "%m/%d/%Y")
end
end
@@ -580,7 +539,6 @@ class StandardFiltersTest < Minitest::Test
def test_newlines_to_br
assert_template_result("a<br />\nb<br />\nc", "{{ source | newline_to_br }}", 'source' => "a\nb\nc")
assert_template_result("a<br />\nb<br />\nc", "{{ source | newline_to_br }}", 'source' => "a\r\nb\nc")
end
def test_plus
@@ -629,7 +587,7 @@ class StandardFiltersTest < Minitest::Test
assert_template_result("0.5", "{{ 2.0 | divided_by:4 }}")
assert_raises(Liquid::ZeroDivisionError) do
assert_template_result("4", "{{ 1 | modulo: 0 }}")
assert_template_result "4", "{{ 1 | modulo: 0 }}"
end
assert_template_result("5", "{{ price | divided_by:2 }}", 'price' => NumberLikeThing.new(10))
@@ -638,7 +596,7 @@ class StandardFiltersTest < Minitest::Test
def test_modulo
assert_template_result("1", "{{ 3 | modulo:2 }}")
assert_raises(Liquid::ZeroDivisionError) do
assert_template_result("4", "{{ 1 | modulo: 0 }}")
assert_template_result "4", "{{ 1 | modulo: 0 }}"
end
assert_template_result("1", "{{ price | modulo:2 }}", 'price' => NumberLikeThing.new(3))
@@ -649,7 +607,7 @@ class StandardFiltersTest < Minitest::Test
assert_template_result("4", "{{ '4.3' | round }}")
assert_template_result("4.56", "{{ input | round: 2 }}", 'input' => 4.5612)
assert_raises(Liquid::FloatDomainError) do
assert_template_result("4", "{{ 1.0 | divided_by: 0.0 | round }}")
assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | round }}"
end
assert_template_result("5", "{{ price | round }}", 'price' => NumberLikeThing.new(4.6))
@@ -660,7 +618,7 @@ class StandardFiltersTest < Minitest::Test
assert_template_result("5", "{{ input | ceil }}", 'input' => 4.6)
assert_template_result("5", "{{ '4.3' | ceil }}")
assert_raises(Liquid::FloatDomainError) do
assert_template_result("4", "{{ 1.0 | divided_by: 0.0 | ceil }}")
assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | ceil }}"
end
assert_template_result("5", "{{ price | ceil }}", 'price' => NumberLikeThing.new(4.6))
@@ -670,7 +628,7 @@ class StandardFiltersTest < Minitest::Test
assert_template_result("4", "{{ input | floor }}", 'input' => 4.6)
assert_template_result("4", "{{ '4.3' | floor }}")
assert_raises(Liquid::FloatDomainError) do
assert_template_result("4", "{{ 1.0 | divided_by: 0.0 | floor }}")
assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | floor }}"
end
assert_template_result("5", "{{ price | floor }}", 'price' => NumberLikeThing.new(5.4))
@@ -728,8 +686,6 @@ class StandardFiltersTest < Minitest::Test
assert_equal("bar", @filters.default([], "bar"))
assert_equal("bar", @filters.default({}, "bar"))
assert_template_result('bar', "{{ false | default: 'bar' }}")
assert_template_result('bar', "{{ drop | default: 'bar' }}", 'drop' => BooleanDrop.new(false))
assert_template_result('Yay', "{{ drop | default: 'bar' }}", 'drop' => BooleanDrop.new(true))
end
def test_default_handle_false
@@ -740,8 +696,6 @@ class StandardFiltersTest < Minitest::Test
assert_equal("bar", @filters.default([], "bar", "allow_false" => true))
assert_equal("bar", @filters.default({}, "bar", "allow_false" => true))
assert_template_result('false', "{{ false | default: 'bar', allow_false: true }}")
assert_template_result('Nay', "{{ drop | default: 'bar', allow_false: true }}", 'drop' => BooleanDrop.new(false))
assert_template_result('Yay', "{{ drop | default: 'bar', allow_false: true }}", 'drop' => BooleanDrop.new(true))
end
def test_cannot_access_private_methods
@@ -770,18 +724,6 @@ class StandardFiltersTest < Minitest::Test
assert_equal(expectation, @filters.where(input, "ok"))
end
def test_where_string_keys
input = [
"alpha", "beta", "gamma", "delta"
]
expectation = [
"beta",
]
assert_equal(expectation, @filters.where(input, "be"))
end
def test_where_no_key_set
input = [
{ "handle" => "alpha", "ok" => true },
+8 -8
View File
@@ -15,13 +15,13 @@ class TagTest < Minitest::Test
with_custom_tag('blabla', klass1) do
template = Liquid::Template.parse("{% blabla %}")
assert_equal('hello', template.render)
assert_equal 'hello', template.render
buf = +''
output = template.render({}, output: buf)
assert_equal('hello', output)
assert_equal('hello', buf)
assert_equal(buf.object_id, output.object_id)
assert_equal 'hello', output
assert_equal 'hello', buf
assert_equal buf.object_id, output.object_id
end
klass2 = Class.new(klass1) do
@@ -33,13 +33,13 @@ class TagTest < Minitest::Test
with_custom_tag('blabla', klass2) do
template = Liquid::Template.parse("{% blabla %}")
assert_equal('foohellobar', template.render)
assert_equal 'foohellobar', template.render
buf = +''
output = template.render({}, output: buf)
assert_equal('foohellobar', output)
assert_equal('foohellobar', buf)
assert_equal(buf.object_id, output.object_id)
assert_equal 'foohellobar', output
assert_equal 'foohellobar', buf
assert_equal buf.object_id, output.object_id
end
end
end
-26
View File
@@ -437,30 +437,4 @@ HERE
assert(context.registers[:for_stack].empty?)
end
def test_instrument_for_offset_continue
assert_usage_increment('for_offset_continue') do
Template.parse('{% for item in items offset:continue %}{{item}}{% endfor %}')
end
assert_usage_increment('for_offset_continue', times: 0) do
Template.parse('{% for item in items offset:2 %}{{item}}{% endfor %}')
end
end
def test_instrument_forloop_drop_name
assigns = { 'items' => [1, 2, 3, 4, 5] }
assert_usage_increment('forloop_drop_name', times: 5) do
Template.parse('{% for item in items %}{{forloop.name}}{% endfor %}').render!(assigns)
end
assert_usage_increment('forloop_drop_name', times: 0) do
Template.parse('{% for item in items %}{{forloop.index}}{% endfor %}').render!(assigns)
end
assert_usage_increment('forloop_drop_name', times: 0) do
Template.parse('{% for item in items %}{{item}}{% endfor %}').render!(assigns)
end
end
end
+1 -1
View File
@@ -184,7 +184,7 @@ class IfElseTagTest < Minitest::Test
tests.each do |vals, expected|
a, b, c = vals
assigns = { 'a' => a, 'b' => b, 'c' => c }
assert_template_result(expected.to_s, tpl, assigns, assigns.to_s)
assert_template_result expected.to_s, tpl, assigns, assigns.to_s
end
end
end
+4 -4
View File
@@ -96,12 +96,12 @@ class IncludeTagTest < Minitest::Test
def test_include_tag_with_alias
assert_template_result("Product: Draft 151cm ",
"{% include 'product_alias' with products[0] as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
"{% include 'product_alias' with products[0] as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
end
def test_include_tag_for_alias
assert_template_result("Product: Draft 151cm Product: Element 155cm ",
"{% include 'product_alias' for products as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
"{% include 'product_alias' for products as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
end
def test_include_tag_with_default_name
@@ -226,13 +226,13 @@ class IncludeTagTest < Minitest::Test
Template.parse("{% include template %}", error_mode: :strict).render!("template" => '{{ "X" || downcase }}')
end
with_error_mode(:lax) do
assert_equal('x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: true).render!("template" => '{{ "X" || downcase }}'))
assert_equal 'x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: true).render!("template" => '{{ "X" || downcase }}')
end
assert_raises(Liquid::SyntaxError) do
Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: [:locale]).render!("template" => '{{ "X" || downcase }}')
end
with_error_mode(:lax) do
assert_equal('x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: [:error_mode]).render!("template" => '{{ "X" || downcase }}'))
assert_equal 'x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: [:error_mode]).render!("template" => '{{ "X" || downcase }}')
end
end
+10 -10
View File
@@ -58,7 +58,7 @@ class RenderTagTest < Minitest::Test
def test_recursively_rendered_template_does_not_produce_endless_loop
Liquid::Template.file_system = StubFileSystem.new('loop' => '{% render "loop" %}')
assert_raises(Liquid::StackLevelError) do
assert_raises Liquid::StackLevelError do
Template.parse('{% render "loop" %}').render!
end
end
@@ -67,7 +67,7 @@ class RenderTagTest < Minitest::Test
Liquid::Template.file_system = StubFileSystem.new(
'loop_render' => '{% render "loop_render" %}',
)
assert_raises(Liquid::StackLevelError) do
assert_raises Liquid::StackLevelError do
Template.parse('{% render "loop_render" %}').render!
end
end
@@ -75,7 +75,7 @@ class RenderTagTest < Minitest::Test
def test_dynamically_choosen_templates_are_not_allowed
Liquid::Template.file_system = StubFileSystem.new('snippet' => 'should not be rendered')
assert_raises(Liquid::SyntaxError) do
assert_raises Liquid::SyntaxError do
Liquid::Template.parse("{% assign name = 'snippet' %}{% render name %}")
end
end
@@ -151,7 +151,7 @@ class RenderTagTest < Minitest::Test
)
assert_template_result("Product: Draft 151cm ",
"{% render 'product' with products[0] %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
"{% render 'product' with products[0] %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
end
def test_render_tag_with_alias
@@ -161,7 +161,7 @@ class RenderTagTest < Minitest::Test
)
assert_template_result("Product: Draft 151cm ",
"{% render 'product_alias' with products[0] as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
"{% render 'product_alias' with products[0] as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
end
def test_render_tag_for_alias
@@ -171,7 +171,7 @@ class RenderTagTest < Minitest::Test
)
assert_template_result("Product: Draft 151cm Product: Element 155cm ",
"{% render 'product_alias' for products as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
"{% render 'product_alias' for products as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
end
def test_render_tag_for
@@ -181,7 +181,7 @@ class RenderTagTest < Minitest::Test
)
assert_template_result("Product: Draft 151cm Product: Element 155cm ",
"{% render 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
"{% render 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
end
def test_render_tag_forloop
@@ -190,7 +190,7 @@ class RenderTagTest < Minitest::Test
)
assert_template_result("Product: Draft 151cm first index:1 Product: Element 155cm last index:2 ",
"{% render 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
"{% render 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
end
def test_render_tag_for_drop
@@ -199,7 +199,7 @@ class RenderTagTest < Minitest::Test
)
assert_template_result("123",
"{% render 'loop' for loop as value %}", "loop" => TestEnumerable.new)
"{% render 'loop' for loop as value %}", "loop" => TestEnumerable.new)
end
def test_render_tag_with_drop
@@ -208,6 +208,6 @@ class RenderTagTest < Minitest::Test
)
assert_template_result("TestEnumerable",
"{% render 'loop' with loop as value %}", "loop" => TestEnumerable.new)
"{% render 'loop' with loop as value %}", "loop" => TestEnumerable.new)
end
end
+4 -18
View File
@@ -198,7 +198,7 @@ class TemplateTest < Minitest::Test
context = Context.new('drop' => ErroneousDrop.new)
t = Template.new.parse('{{ drop.bad_method }}')
e = assert_raises(RuntimeError) do
e = assert_raises RuntimeError do
t.render!(context)
end
assert_equal('ruby error in drop', e.message)
@@ -267,7 +267,7 @@ class TemplateTest < Minitest::Test
def test_undefined_variables_raise
t = Template.parse("{{x}} {{y}} {{z.a}} {{z.b}} {{z.c.d}}")
assert_raises(UndefinedVariable) do
assert_raises UndefinedVariable do
t.render!({ 'x' => 33, 'z' => { 'a' => 32, 'c' => { 'e' => 31 } } }, strict_variables: true)
end
end
@@ -286,7 +286,7 @@ class TemplateTest < Minitest::Test
d = DropWithUndefinedMethod.new
t = Template.new.parse('{{ foo }} {{ woot }}')
assert_raises(UndefinedDropMethod) do
assert_raises UndefinedDropMethod do
t.render!(d, strict_variables: true)
end
end
@@ -309,7 +309,7 @@ class TemplateTest < Minitest::Test
def test_undefined_filters_raise
t = Template.parse("{{x | somefilter1 | upcase | somefilter2}}")
assert_raises(UndefinedFilter) do
assert_raises UndefinedFilter do
t.render!({ 'x' => 'foo' }, strict_filters: true)
end
end
@@ -323,18 +323,4 @@ class TemplateTest < Minitest::Test
result = t.render('x' => 1, 'y' => 5)
assert_equal('12345', result)
end
def test_source_string_subclass
string_subclass = Class.new(String) do
# E.g. ActiveSupport::SafeBuffer does this, so don't just rely on to_s to return a String
def to_s
self
end
end
source = string_subclass.new("{% assign x = 2 -%} x= {{- x }}")
assert_instance_of(string_subclass, source)
output = Template.parse(source).render!
assert_equal("x=2", output)
assert_instance_of(String, output)
end
end
-4
View File
@@ -556,8 +556,4 @@ class TrimModeTest < Minitest::Test
template = Liquid::Template.parse("B\n {%- if true %}{% endif %}", bug_compatible_whitespace_trimming: true)
assert_equal("B", template.render)
end
def test_trim_blank
assert_template_result('foobar', 'foo {{-}} bar')
end
end # TrimModeTest
+2 -33
View File
@@ -15,33 +15,6 @@ class VariableTest < Minitest::Test
assert_template_result('foobar', '{{ foo }}', 'foo' => ThingWithToLiquid.new)
end
def test_variable_lookup_calls_to_liquid_value
assert_template_result('1', '{{ foo }}', 'foo' => IntegerDrop.new('1'))
assert_template_result('2', '{{ list[foo] }}', 'foo' => IntegerDrop.new('1'), 'list' => [1, 2, 3])
assert_template_result('one', '{{ list[foo] }}', 'foo' => IntegerDrop.new('1'), 'list' => { 1 => 'one' })
assert_template_result('Yay', '{{ foo }}', 'foo' => BooleanDrop.new(true))
assert_template_result('YAY', '{{ foo | upcase }}', 'foo' => BooleanDrop.new(true))
end
def test_if_tag_calls_to_liquid_value
assert_template_result('one', '{% if foo == 1 %}one{% endif %}', 'foo' => IntegerDrop.new('1'))
assert_template_result('one', '{% if 0 < foo %}one{% endif %}', 'foo' => IntegerDrop.new('1'))
assert_template_result('one', '{% if foo > 0 %}one{% endif %}', 'foo' => IntegerDrop.new('1'))
assert_template_result('true', '{% if foo == true %}true{% endif %}', 'foo' => BooleanDrop.new(true))
assert_template_result('true', '{% if foo %}true{% endif %}', 'foo' => BooleanDrop.new(true))
assert_template_result('', '{% if foo %}true{% endif %}', 'foo' => BooleanDrop.new(false))
assert_template_result('', '{% if foo == true %}True{% endif %}', 'foo' => BooleanDrop.new(false))
end
def test_unless_tag_calls_to_liquid_value
assert_template_result('', '{% unless foo %}true{% endunless %}', 'foo' => BooleanDrop.new(true))
end
def test_case_tag_calls_to_liquid_value
assert_template_result('One', '{% case foo %}{% when 1 %}One{% endcase %}', 'foo' => IntegerDrop.new('1'))
end
def test_simple_with_whitespaces
template = Template.parse(%( {{ test }} ))
assert_equal(' worked ', template.render!('test' => 'worked'))
@@ -69,8 +42,8 @@ class VariableTest < Minitest::Test
end
def test_hash_scoping
assert_template_result('worked', "{{ test.test }}", 'test' => { 'test' => 'worked' })
assert_template_result('worked', "{{ test . test }}", 'test' => { 'test' => 'worked' })
template = Template.parse(%({{ test.test }}))
assert_equal('worked', template.render!('test' => { 'test' => 'worked' }))
end
def test_false_renders_as_false
@@ -131,8 +104,4 @@ class VariableTest < Minitest::Test
def test_dynamic_find_var
assert_template_result('bar', '{{ [key] }}', 'key' => 'foo', 'foo' => 'bar')
end
def test_raw_value_variable
assert_template_result('bar', '{{ [key] }}', 'key' => 'foo', 'foo' => 'bar')
end
end
-38
View File
@@ -119,44 +119,6 @@ class ThingWithToLiquid
end
end
class IntegerDrop < Liquid::Drop
def initialize(value)
super()
@value = value.to_i
end
def ==(other)
@value == other
end
def to_s
@value.to_s
end
def to_liquid_value
@value
end
end
class BooleanDrop < Liquid::Drop
def initialize(value)
super()
@value = value
end
def ==(other)
@value == other
end
def to_liquid_value
@value
end
def to_s
@value ? "Yay" : "Nay"
end
end
class ErrorDrop < Liquid::Drop
def standard_error
raise Liquid::StandardError, 'standard error'
+1 -1
View File
@@ -28,7 +28,7 @@ class I18nUnitTest < Minitest::Test
# end
def test_raises_unknown_translation
assert_raises(I18n::TranslationError) do
assert_raises I18n::TranslationError do
@i18n.translate("doesnt_exist")
end
end
-14
View File
@@ -26,13 +26,6 @@ class ParseTreeVisitorTest < Minitest::Test
)
end
def test_echo
assert_equal(
["test"],
visit(%({% echo test %}))
)
end
def test_if_condition
assert_equal(
["test"],
@@ -159,13 +152,6 @@ class ParseTreeVisitorTest < Minitest::Test
)
end
def test_for_range
assert_equal(
["test"],
visit(%({% for x in (1..test) %}{% endfor %}))
)
end
def test_tablerow_in
assert_equal(
["test"],
+4 -9
View File
@@ -32,26 +32,21 @@ class TokenizerTest < Minitest::Test
private
def new_tokenizer(source, parse_context: Liquid::ParseContext.new, start_line_number: nil)
parse_context.new_tokenizer(source, start_line_number: start_line_number)
end
def tokenize(source)
tokenizer = new_tokenizer(source)
tokenizer = Liquid::Tokenizer.new(source)
tokens = []
# shift is private in Liquid::C::Tokenizer, since it is only for unit testing
while (t = tokenizer.send(:shift))
while (t = tokenizer.shift)
tokens << t
end
tokens
end
def tokenize_line_numbers(source)
tokenizer = new_tokenizer(source, start_line_number: 1)
tokenizer = Liquid::Tokenizer.new(source, true)
line_numbers = []
loop do
line_number = tokenizer.line_number
if tokenizer.send(:shift)
if tokenizer.shift
line_numbers << line_number
else
break
+1 -1
View File
@@ -108,7 +108,7 @@ class VariableUnitTest < Minitest::Test
assert_equal(VariableLookup.new('foo-bar'), create_variable('foo-bar').name)
assert_equal(VariableLookup.new('foo-bar-2'), create_variable('foo-bar-2').name)
with_error_mode(:strict) do
with_error_mode :strict do
assert_raises(Liquid::SyntaxError) { create_variable('foo - bar') }
assert_raises(Liquid::SyntaxError) { create_variable('-foo') }
assert_raises(Liquid::SyntaxError) { create_variable('2foo') }