Compare commits

..

4 Commits

Author SHA1 Message Date
Mike Angell
2d0917493d Add performance rubocop and fixes 2019-08-27 22:07:44 +10:00
Mike Angell
ef8a6dcda1 Add option legacy file which will load removed constants 2019-08-27 17:46:09 +10:00
Mike Angell
0dd22af7d6 Use optimised 2.4 syntax 2019-08-27 17:46:09 +10:00
Mike Angell
da9051eb18 Upgrade rubocop 2019-08-27 17:46:09 +10:00
131 changed files with 2262 additions and 3589 deletions

1
.gitignore vendored
View File

@@ -4,6 +4,7 @@
pkg
*.rbc
.rvmrc
.idea
.ruby-version
Gemfile.lock
.bundle

View File

@@ -892,7 +892,7 @@ Lint/FormatParameterMismatch:
Enabled: true
Lint/HandleExceptions:
AllowComments: true
Enabled: true
Lint/ImplicitStringConcatenation:
Description: Checks for adjacent string literals on the same line, which could

View File

@@ -1,17 +1,10 @@
inherit_from:
- 'https://shopify.github.io/ruby-style-guide/rubocop.yml'
- .rubocop_todo.yml
- .rubocop.shopify.yml
require: rubocop-performance
Performance:
Enabled: true
AllCops:
TargetRubyVersion: 2.4
Exclude:
- 'vendor/bundle/**/*'
Naming/MethodName:
Exclude:
- 'example/server/liquid_servlet.rb'
Metrics/LineLength:
Enabled: false

View File

@@ -1,48 +0,0 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# 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
# versions of RuboCop, may require this file to be generated again.
# Offense count: 2
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: runtime_error, standard_error
Lint/InheritException:
Exclude:
- 'lib/liquid/interrupts.rb'
# Offense count: 98
# Cop supports --auto-correct.
# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
# URISchemes: http, https
Metrics/LineLength:
Max: 294
# Offense count: 44
Naming/ConstantName:
Exclude:
- 'lib/liquid.rb'
- 'lib/liquid/block_body.rb'
- 'lib/liquid/tags/assign.rb'
- 'lib/liquid/tags/capture.rb'
- 'lib/liquid/tags/case.rb'
- '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'
- 'performance/shopify/comment_form.rb'
- 'performance/shopify/paginate.rb'
- 'test/integration/tags/include_tag_test.rb'
# Offense count: 5
Style/ClassVars:
Exclude:
- 'lib/liquid/condition.rb'
- 'lib/liquid/strainer.rb'
- 'lib/liquid/template.rb'

View File

@@ -1,26 +1,30 @@
language: ruby
cache: bundler
rvm:
- 2.4
- 2.5
- &latest_ruby 2.6
- 2.6
- 2.7
- ruby-head
- jruby-head
- truffleruby
sudo: false
addons:
apt:
packages:
- libgmp3-dev
matrix:
include:
- rvm: *latest_ruby
script: bundle exec rake memory_profile:run
name: Profiling Memory Usage
allow_failures:
- rvm: ruby-head
- rvm: jruby-head
branches:
only:
- master
- gh-pages
- /.*-stable/
install:
- bundle install
script: bundle exec rake
notifications:
disable: true

View File

@@ -10,7 +10,6 @@ gemspec
group :benchmark, :test do
gem 'benchmark-ips'
gem 'memory_profiler'
gem 'terminal-table'
install_if -> { RUBY_PLATFORM !~ /mingw|mswin|java/ && RUBY_ENGINE != 'truffleruby' } do
gem 'stackprof'
@@ -22,6 +21,6 @@ group :test do
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: '7ba926791ef8411984d0f3e41c6353fd716041c6'
end
end

View File

@@ -274,7 +274,7 @@ Yanked from rubygems, as it contained too many changes that broke compatibility.
* Fixed bad error reporting when a filter called a method which doesn't exist. Liquid told you that it couldn't find the filter which was obviously misleading [Tobias Luetke]
* Removed count helper from standard lib. use size [Tobias Luetke]
* Fixed bug with string filter parameters failing to tolerate commas in strings. [Paul Hammond]
* Improved filter parameters. Filter parameters are now context sensitive; Types are resolved according to the rules of the context. Multiple parameters are now separated by the Liquid::ArgumentSeparator: , by default [Paul Hammond]
* Improved filter parameters. Filter parameters are now context sensitive; Types are resolved according to the rules of the context. Multiple parameters are now separated by the Liquid::ARGUMENT_SEPARATOR: , by default [Paul Hammond]
{{ 'Typo' | link_to: 'http://typo.leetsoft.com', 'Typo - a modern weblog engine' }}
* Added Liquid::Drop. A base class which you can use for exporting proxy objects to liquid which can acquire more data when used in liquid. [Tobias Luetke]

View File

@@ -106,9 +106,3 @@ template = Liquid::Template.parse("{{x}} {{y}}")
template.render!({ 'x' => 1}, { strict_variables: true })
#=> Liquid::UndefinedVariable: Liquid error: undefined variable y
```
### Usage tracking
To help track usages of a feature or code path in production, we have released opt-in usage tracking. To enable this, we provide an empty `Liquid:: Usage.increment` method which you can customize to your needs. The feature is well suited to https://github.com/Shopify/statsd-instrument. However, the choice of implementation is up to you.
Once you have enabled usage tracking, we recommend reporting any events through Github Issues that your system may be logging. It is highly likely this event has been added to consider deprecating or improving code specific to this event, so please raise any concerns.

View File

@@ -2,16 +2,16 @@
require 'rake'
require 'rake/testtask'
$LOAD_PATH.unshift(File.expand_path("../lib", __FILE__))
require "liquid/version"
$LOAD_PATH.unshift(File.expand_path('lib', __dir__))
require 'liquid/version'
task(default: [:test, :rubocop])
task(default: %i[test rubocop])
desc('run test suite with default parser')
Rake::TestTask.new(:base_test) do |t|
t.libs << '.' << 'lib' << 'test'
t.test_files = FileList['test/{integration,unit}/**/*_test.rb']
t.verbose = false
t.verbose = false
end
desc('run test suite with warn error mode')
@@ -37,6 +37,7 @@ task :test do
Rake::Task['base_test'].invoke
if RUBY_ENGINE == 'ruby' || RUBY_ENGINE == 'truffleruby'
ENV['LIQUID_C'] = '1'
ENV['LIQUID_PARSER_MODE'] = 'lax'
@@ -51,7 +52,7 @@ end
task(gem: :build)
task :build do
system "gem build liquid.gemspec"
system 'gem build liquid.gemspec'
end
task install: :build do
@@ -60,45 +61,45 @@ end
task release: :build do
system "git tag -a v#{Liquid::VERSION} -m 'Tagging #{Liquid::VERSION}'"
system "git push --tags"
system 'git push --tags'
system "gem push liquid-#{Liquid::VERSION}.gem"
system "rm liquid-#{Liquid::VERSION}.gem"
end
namespace :benchmark do
desc "Run the liquid benchmark with lax parsing"
desc 'Run the liquid benchmark with lax parsing'
task :run do
ruby "./performance/benchmark.rb lax"
ruby './performance/benchmark.rb lax'
end
desc "Run the liquid benchmark with strict parsing"
desc 'Run the liquid benchmark with strict parsing'
task :strict do
ruby "./performance/benchmark.rb strict"
ruby './performance/benchmark.rb strict'
end
end
namespace :profile do
desc "Run the liquid profile/performance coverage"
desc 'Run the liquid profile/performance coverage'
task :run do
ruby "./performance/profile.rb"
ruby './performance/profile.rb'
end
desc "Run the liquid profile/performance coverage with strict parsing"
desc 'Run the liquid profile/performance coverage with strict parsing'
task :strict do
ruby "./performance/profile.rb strict"
ruby './performance/profile.rb strict'
end
end
namespace :memory_profile do
desc "Run memory profiler"
desc 'Run memory profiler'
task :run do
ruby "./performance/memory_profile.rb"
ruby './performance/memory_profile.rb'
end
end
desc("Run example")
desc('Run example')
task :example do
ruby "-w -d -Ilib example/server/server.rb"
ruby '-w -d -Ilib example/server/server.rb'
end
task :console do

View File

@@ -2,7 +2,7 @@
module ProductsFilter
def price(integer)
format("$%.2d USD", integer / 100.0)
format('$%.2d USD', integer / 100.0)
end
def prettyprint(text)
@@ -30,17 +30,17 @@ class Servlet < LiquidServlet
private
def products_list
[{ 'name' => 'Arbor Draft', 'price' => 39900, 'description' => 'the *arbor draft* is a excellent product' },
{ 'name' => 'Arbor Element', 'price' => 40000, 'description' => 'the *arbor element* rocks for freestyling' },
{ 'name' => 'Arbor Diamond', 'price' => 59900, 'description' => 'the *arbor diamond* is a made up product because im obsessed with arbor and have no creativity' }]
[{ 'name' => 'Arbor Draft', 'price' => 39_900, 'description' => 'the *arbor draft* is a excellent product' },
{ 'name' => 'Arbor Element', 'price' => 40_000, 'description' => 'the *arbor element* rocks for freestyling' },
{ 'name' => 'Arbor Diamond', 'price' => 59_900, 'description' => 'the *arbor diamond* is a made up product because im obsessed with arbor and have no creativity' }]
end
def more_products_list
[{ 'name' => 'Arbor Catalyst', 'price' => 39900, 'description' => 'the *arbor catalyst* is an advanced drop-through for freestyle and flatground performance and versatility' },
{ 'name' => 'Arbor Fish', 'price' => 40000, 'description' => 'the *arbor fish* is a compact pin that features an extended wheelbase and time-honored teardrop shape' }]
[{ 'name' => 'Arbor Catalyst', 'price' => 39_900, 'description' => 'the *arbor catalyst* is an advanced drop-through for freestyle and flatground performance and versatility' },
{ 'name' => 'Arbor Fish', 'price' => 40_000, 'description' => 'the *arbor fish* is a compact pin that features an extended wheelbase and time-honored teardrop shape' }]
end
def description
"List of Products ~ This is a list of products with price and description."
'List of Products ~ This is a list of products with price and description.'
end
end

View File

@@ -1,27 +1,30 @@
# frozen_string_literal: true
class LiquidServlet < WEBrick::HTTPServlet::AbstractServlet
def do_GET(req, res)
def get(req, res)
handle(:get, req, res)
end
def do_POST(req, res)
def post(req, res)
handle(:post, req, res)
end
alias_method :do_GET, :get
alias_method :do_POST, :post
private
def handle(_type, req, res)
@request = req
@request = req
@response = res
@request.path_info =~ /(\w+)\z/
@action = Regexp.last_match(1) || 'index'
@action = Regexp.last_match(1) || 'index'
@assigns = send(@action) if respond_to?(@action)
@response['Content-Type'] = "text/html"
@response['Content-Type'] = 'text/html'
@response.status = 200
@response.body = Liquid::Template.parse(read_template).render(@assigns, filters: [ProductsFilter])
@response.body = Liquid::Template.parse(read_template).render(@assigns, filters: [ProductsFilter])
end
def read_template(filename = @action)

View File

@@ -10,5 +10,5 @@ require_relative 'example_servlet'
# Setup webrick
server = WEBrick::HTTPServer.new(Port: ARGV[1] || 3000)
server.mount('/', Servlet)
trap("INT") { server.shutdown }
trap('INT') { server.shutdown }
server.start

View File

@@ -22,31 +22,31 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
module Liquid
FilterSeparator = /\|/
ArgumentSeparator = ','
FilterArgumentSeparator = ':'
VariableAttributeSeparator = '.'
WhitespaceControl = '-'
TagStart = /\{\%/
TagEnd = /\%\}/
VariableSignature = /\(?[\w\-\.\[\]]\)?/
VariableSegment = /[\w\-]/
VariableStart = /\{\{/
VariableEnd = /\}\}/
VariableIncompleteEnd = /\}\}?/
QuotedString = /"[^"]*"|'[^']*'/
QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o
TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/o
AnyStartingTag = /#{TagStart}|#{VariableStart}/o
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/om
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om
VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o
FILTER_SEPARATOR = /\|/.freeze
ARGUMENT_SEPARATOR = ','
FILTER_ARGUMENT_SEPARATOR = ':'
VARIABLE_ATTRIBUTE_SEPARATOR = '.'
WHITESPACE_CONTROL = '-'
TAG_START = /\{\%/.freeze
TAG_END = /\%\}/.freeze
VARIABLE_SIGNATURE = /\(?[\w\-\.\[\]]\)?/.freeze
VARIABLE_SEGMENT = /[\w\-]/.freeze
VARIABLE_START = /\{\{/.freeze
VARIABLE_END = /\}\}/.freeze
VARIABLE_INCOMPLETE_END = /\}\}?/.freeze
QUOTED_STRING = /"[^"]*"|'[^']*'/.freeze
QUOTED_FRAGMENT = /#{QUOTED_STRING}|(?:[^\s,\|'"]|#{QUOTED_STRING})+/o.freeze
TAG_ATTRIBUTES = /(\w+)\s*\:\s*(#{QUOTED_FRAGMENT})/o.freeze
ANY_STARTING_TAG = /#{TAG_START}|#{VARIABLE_START}/o.freeze
PARTIAL_TEMPLATE_PARSER = /#{TAG_START}.*?#{TAG_END}|#{VARIABLE_START}.*?#{VARIABLE_INCOMPLETE_END}/om.freeze
TEMPLATE_PARSER = /(#{PARTIAL_TEMPLATE_PARSER}|#{ANY_STARTING_TAG})/om.freeze
VARIABLE_PARSER = /\[[^\]]+\]|#{VARIABLE_SEGMENT}+\??/o.freeze
singleton_class.send(:attr_accessor, :cache_classes)
self.cache_classes = true
end
require "liquid/version"
require 'liquid/version'
require 'liquid/parse_tree_visitor'
require 'liquid/lexer'
require 'liquid/parser'
@@ -76,12 +76,7 @@ require 'liquid/condition'
require 'liquid/utils'
require 'liquid/tokenizer'
require 'liquid/parse_context'
require 'liquid/partial_cache'
require 'liquid/usage'
require 'liquid/register'
require 'liquid/static_registers'
# Load all the tags of the standard library
#
Dir["#{__dir__}/liquid/tags/*.rb"].each { |f| require f }
Dir["#{__dir__}/liquid/registers/*.rb"].each { |f| require f }

View File

@@ -30,15 +30,15 @@ module Liquid
def unknown_tag(tag, _params, _tokens)
if tag == 'else'
raise SyntaxError, parse_context.locale.t("errors.syntax.unexpected_else",
block_name: block_name)
raise SyntaxError, parse_context.locale.t('errors.syntax.unexpected_else',
block_name: block_name)
elsif tag.start_with?('end')
raise SyntaxError, parse_context.locale.t("errors.syntax.invalid_delimiter",
tag: tag,
block_name: block_name,
block_delimiter: block_delimiter)
raise SyntaxError, parse_context.locale.t('errors.syntax.invalid_delimiter',
tag: tag,
block_name: block_name,
block_delimiter: block_delimiter)
else
raise SyntaxError, parse_context.locale.t("errors.syntax.unknown_tag", tag: tag)
raise SyntaxError, parse_context.locale.t('errors.syntax.unknown_tag', tag: tag)
end
end
@@ -53,18 +53,15 @@ module Liquid
protected
def parse_body(body, tokens)
if parse_context.depth >= MAX_DEPTH
raise StackLevelError, "Nesting too deep"
end
raise StackLevelError, 'Nesting too deep' if parse_context.depth >= MAX_DEPTH
parse_context.depth += 1
begin
body.parse(tokens, parse_context) do |end_tag_name, end_tag_params|
@blank &&= body.blank?
return false if end_tag_name == block_delimiter
unless end_tag_name
raise SyntaxError, parse_context.locale.t("errors.syntax.tag_never_closed", block_name: block_name)
end
raise SyntaxError, parse_context.locale.t('errors.syntax.tag_never_closed', block_name: block_name) unless end_tag_name
# this tag is not registered with the system
# pass it to the current block for special handling or error reporting

View File

@@ -2,97 +2,49 @@
module Liquid
class BlockBody
LiquidTagToken = /\A\s*(\w+)\s*(.*?)\z/o
FullToken = /\A#{TagStart}#{WhitespaceControl}?(\s*)(\w+)(\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om
WhitespaceOrNothing = /\A\s*\z/
TAGSTART = "{%"
VARSTART = "{{"
FULL_TOKEN = /\A#{TAG_START}#{WHITESPACE_CONTROL}?\s*(\w+)\s*(.*?)#{WHITESPACE_CONTROL}?#{TAG_END}\z/om.freeze
CONTENT_OF_VARIABLE = /\A#{VARIABLE_START}#{WHITESPACE_CONTROL}?(.*?)#{WHITESPACE_CONTROL}?#{VARIABLE_END}\z/om.freeze
WHITESPACE_OR_NOTHING = /\A\s*\z/.freeze
TAG_START = '{%'
VAR_START = '{{'
attr_reader :nodelist
def initialize
@nodelist = []
@blank = true
@blank = true
end
def parse(tokenizer, parse_context, &block)
def parse(tokenizer, parse_context)
parse_context.line_number = tokenizer.line_number
if tokenizer.for_liquid_tag
parse_for_liquid_tag(tokenizer, parse_context, &block)
else
parse_for_document(tokenizer, parse_context, &block)
end
end
private def parse_for_liquid_tag(tokenizer, parse_context)
while (token = tokenizer.shift)
unless token.empty? || token =~ WhitespaceOrNothing
unless token =~ LiquidTagToken
# line isn't empty but didn't match tag syntax, yield and let the
# caller raise a syntax error
return yield token, token
end
tag_name = Regexp.last_match(1)
markup = Regexp.last_match(2)
unless (tag = registered_tags[tag_name])
# end parsing if we reach an unknown tag and let the caller decide
# determine how to proceed
return yield tag_name, markup
end
new_tag = tag.parse(tag_name, markup, tokenizer, parse_context)
@blank &&= new_tag.blank?
@nodelist << new_tag
end
parse_context.line_number = tokenizer.line_number
end
yield nil, nil
end
private def parse_for_document(tokenizer, parse_context, &block)
while (token = tokenizer.shift)
next if token.empty?
case
when token.start_with?(TAGSTART)
when token.start_with?(TAG_START)
whitespace_handler(token, parse_context)
unless token =~ FullToken
raise_missing_tag_terminator(token, parse_context)
end
tag_name = Regexp.last_match(2)
markup = Regexp.last_match(4)
if parse_context.line_number
# newlines inside the tag should increase the line number,
# particularly important for multiline {% liquid %} tags
parse_context.line_number += Regexp.last_match(1).count("\n") + Regexp.last_match(3).count("\n")
end
if tag_name == 'liquid'
liquid_tag_tokenizer = Tokenizer.new(markup, line_number: parse_context.line_number, for_liquid_tag: true)
next parse_for_liquid_tag(liquid_tag_tokenizer, parse_context, &block)
end
raise_missing_tag_terminator(token, parse_context) unless token =~ FULL_TOKEN
tag_name = Regexp.last_match(1)
markup = Regexp.last_match(2)
# fetch the tag from registered blocks
unless (tag = registered_tags[tag_name])
# end parsing if we reach an unknown tag and let the caller decide
# determine how to proceed
return yield tag_name, markup
end
new_tag = tag.parse(tag_name, markup, tokenizer, parse_context)
@blank &&= new_tag.blank?
@nodelist << new_tag
when token.start_with?(VARSTART)
when token.start_with?(VAR_START)
whitespace_handler(token, parse_context)
@nodelist << create_variable(token, parse_context)
@blank = false
else
if parse_context.trim_whitespace
token.lstrip!
end
token.lstrip! if parse_context.trim_whitespace
parse_context.trim_whitespace = false
@nodelist << token
@blank &&= !!(token =~ WhitespaceOrNothing)
@blank &&= token.match?(WHITESPACE_OR_NOTHING)
end
parse_context.line_number = tokenizer.line_number
end
@@ -101,13 +53,11 @@ module Liquid
end
def whitespace_handler(token, parse_context)
if token[2] == WhitespaceControl
if token[2] == WHITESPACE_CONTROL
previous_token = @nodelist.last
if previous_token.is_a?(String)
previous_token.rstrip!
end
previous_token.rstrip! if previous_token.is_a?(String)
end
parse_context.trim_whitespace = (token[-3] == WhitespaceControl)
parse_context.trim_whitespace = (token[-3] == WHITESPACE_CONTROL)
end
def blank?
@@ -154,13 +104,7 @@ module Liquid
private
def render_node(context, output, node)
if node.disabled?(context)
output << node.disabled_error_message
return
end
disable_tags(context, node.disabled_tags) do
node.render_to_output_buffer(context, output)
end
node.render_to_output_buffer(context, output)
rescue UndefinedVariable, UndefinedDropMethod, UndefinedFilter => e
context.handle_error(e, node.line_number)
rescue ::StandardError => e
@@ -168,19 +112,15 @@ module Liquid
output << context.handle_error(e, line_number)
end
def disable_tags(context, tags, &block)
return yield if tags.empty?
context.registers[:disabled_tags].disable(tags, &block)
end
def raise_if_resource_limits_reached(context, length)
context.resource_limits.render_length += length
return unless context.resource_limits.reached?
raise MemoryError, "Memory limits exceeded"
raise MemoryError, 'Memory limits exceeded'
end
def create_variable(token, parse_context)
token.scan(ContentOfVariable) do |content|
token.scan(CONTENT_OF_VARIABLE) do |content|
markup = content.first
return Variable.new(markup, parse_context)
end
@@ -188,11 +128,11 @@ module Liquid
end
def raise_missing_tag_terminator(token, parse_context)
raise SyntaxError, parse_context.locale.t("errors.syntax.tag_termination", token: token, tag_end: TagEnd.inspect)
raise SyntaxError, parse_context.locale.t('errors.syntax.tag_termination', token: token, tag_end: TAG_END.inspect)
end
def raise_missing_variable_terminator(token, parse_context)
raise SyntaxError, parse_context.locale.t("errors.syntax.variable_termination", token: token, tag_end: VariableEnd.inspect)
raise SyntaxError, parse_context.locale.t('errors.syntax.variable_termination', token: token, tag_end: VARIABLE_END.inspect)
end
def registered_tags

View File

@@ -9,7 +9,7 @@ module Liquid
# c.evaluate #=> true
#
class Condition #:nodoc:
@@operators = {
@operators = {
'==' => ->(cond, left, right) { cond.send(:equal_variables, left, right) },
'!=' => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },
'<>' => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },
@@ -27,19 +27,18 @@ module Liquid
end,
}
def self.operators
@@operators
class << self
attr_accessor :operators
end
attr_reader :attachment, :child_condition
attr_accessor :left, :operator, :right
def initialize(left = nil, operator = nil, right = nil)
@left = left
@left = left
@operator = operator
@right = right
@child_relation = nil
@right = right
@child_relation = nil
@child_condition = nil
end
@@ -63,12 +62,12 @@ module Liquid
end
def or(condition)
@child_relation = :or
@child_relation = :or
@child_condition = condition
end
def and(condition)
@child_relation = :and
@child_relation = :and
@child_condition = condition
end
@@ -116,7 +115,7 @@ module Liquid
# return this as the result.
return context.evaluate(left) if op.nil?
left = context.evaluate(left)
left = context.evaluate(left)
right = context.evaluate(right)
operation = self.class.operators[op] || raise(Liquid::ArgumentError, "Unknown operator #{op}")

View File

@@ -14,38 +14,28 @@ module Liquid
#
# context['bob'] #=> nil class Context
class Context
attr_reader :scopes, :errors, :registers, :environments, :resource_limits, :static_registers, :static_environments
attr_reader :scopes, :errors, :registers, :environments, :resource_limits
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: {})
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 = {})
@environments = [environments]
@environments.flatten!
@static_environments = [static_environments].flat_map(&:freeze).freeze
@scopes = [(outer_scope || {})]
@registers = registers
@errors = []
@partial = false
@strict_variables = false
@resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
@base_scope_depth = 0
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil)
@environments = [environments].flatten
@scopes = [(outer_scope || {})]
@registers = registers
@errors = []
@partial = false
@strict_variables = false
@resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
squash_instance_assigns_with_environments
self.exception_renderer = Template.default_exception_renderer
if rethrow_errors
self.exception_renderer = ->(_e) { raise }
end
@this_stack_used = false
@interrupts = []
@filters = []
self.exception_renderer = Template.default_exception_renderer
self.exception_renderer = ->(_e) { raise } if rethrow_errors
@interrupts = []
@filters = []
@global_filter = nil
end
# rubocop:enable Metrics/ParameterLists
def warnings
@warnings ||= []
@@ -87,7 +77,7 @@ module Liquid
def handle_error(e, line_number = nil)
e = internal_error unless e.is_a?(Liquid::Error)
e.template_name ||= template_name
e.line_number ||= line_number
e.line_number ||= line_number
errors.push(e)
exception_renderer.call(e).to_s
end
@@ -99,7 +89,7 @@ module Liquid
# Push new local scope on the stack. use <tt>Context#stack</tt> instead
def push(new_scope = {})
@scopes.unshift(new_scope)
check_overflow
raise StackLevelError, 'Nesting too deep' if @scopes.length > Block::MAX_DEPTH
end
# Merge a hash of variables in the current local scope
@@ -110,6 +100,7 @@ module Liquid
# Pop from the stack. use <tt>Context#stack</tt> instead
def pop
raise ContextError if @scopes.size == 1
@scopes.shift
end
@@ -121,30 +112,19 @@ module Liquid
# end
#
# context['var] #=> nil
def stack(new_scope = {})
push(new_scope)
def stack(new_scope = nil)
old_stack_used = @this_stack_used
if new_scope
push(new_scope)
@this_stack_used = true
else
@this_stack_used = false
end
yield
ensure
pop
end
# Creates a new context inheriting resource limits, filters, environment etc.,
# but with an isolated scope.
def new_isolated_subcontext
check_overflow
Context.build(
resource_limits: resource_limits,
static_environments: static_environments,
registers: StaticRegisters.new(registers)
).tap do |subcontext|
subcontext.base_scope_depth = base_scope_depth + 1
subcontext.exception_renderer = exception_renderer
subcontext.filters = @filters
subcontext.strainer = nil
subcontext.errors = errors
subcontext.warnings = warnings
end
pop if @this_stack_used
@this_stack_used = old_stack_used
end
def clear_instance_assigns
@@ -153,6 +133,10 @@ module Liquid
# Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
def []=(key, value)
unless @this_stack_used
@this_stack_used = true
push({})
end
@scopes[0][key] = value
end
@@ -181,23 +165,33 @@ module Liquid
# This was changed from find() to find_index() because this is a very hot
# path and find_index() is optimized in MRI to reduce object allocation
index = @scopes.find_index { |s| s.key?(key) }
scope = @scopes[index] if index
variable = if index
lookup_and_evaluate(@scopes[index], key, raise_on_not_found: raise_on_not_found)
else
try_variable_find_in_environments(key, raise_on_not_found: raise_on_not_found)
variable = nil
if scope.nil?
@environments.each do |e|
variable = lookup_and_evaluate(e, key, raise_on_not_found: raise_on_not_found)
# When lookup returned a value OR there is no value but the lookup also did not raise
# then it is the value we are looking for.
if !variable.nil? || @strict_variables && raise_on_not_found
scope = e
break
end
end
end
variable = variable.to_liquid
scope ||= @environments.last || @scopes.last
variable ||= lookup_and_evaluate(scope, key, raise_on_not_found: raise_on_not_found)
variable = variable.to_liquid
variable.context = self if variable.respond_to?(:context=)
variable
end
def lookup_and_evaluate(obj, key, raise_on_not_found: true)
if @strict_variables && raise_on_not_found && obj.respond_to?(:key?) && !obj.key?(key)
raise Liquid::UndefinedVariable, "undefined variable #{key}"
end
raise Liquid::UndefinedVariable, "undefined variable #{key}" if @strict_variables && raise_on_not_found && obj.respond_to?(:key?) && !obj.key?(key)
value = obj[key]
@@ -208,43 +202,13 @@ module Liquid
end
end
protected
attr_writer :base_scope_depth, :warnings, :errors, :strainer, :filters
private
attr_reader :base_scope_depth
def try_variable_find_in_environments(key, raise_on_not_found:)
@environments.each do |environment|
found_variable = lookup_and_evaluate(environment, key, raise_on_not_found: raise_on_not_found)
if !found_variable.nil? || @strict_variables && raise_on_not_found
return found_variable
end
end
@static_environments.each do |environment|
found_variable = lookup_and_evaluate(environment, key, raise_on_not_found: raise_on_not_found)
if !found_variable.nil? || @strict_variables && raise_on_not_found
return found_variable
end
end
nil
end
def check_overflow
raise StackLevelError, "Nesting too deep" if overflow?
end
def overflow?
base_scope_depth + @scopes.length > Block::MAX_DEPTH
end
def internal_error
# raise and catch to set backtrace and cause on exception
raise Liquid::InternalError, 'internal'
rescue Liquid::InternalError => exc
exc
rescue Liquid::InternalError => e
e
end
def squash_instance_assigns_with_environments

View File

@@ -20,9 +20,9 @@ module Liquid
def unknown_tag(tag, parse_context)
case tag
when 'else', 'end'
raise SyntaxError, parse_context.locale.t("errors.syntax.unexpected_outer_tag", tag: tag)
raise SyntaxError, parse_context.locale.t('errors.syntax.unexpected_outer_tag', tag: tag)
else
raise SyntaxError, parse_context.locale.t("errors.syntax.unknown_tag", tag: tag)
raise SyntaxError, parse_context.locale.t('errors.syntax.unknown_tag', tag: tag)
end
end
end

View File

@@ -28,6 +28,7 @@ module Liquid
# Catch all for the method
def liquid_method_missing(method)
return nil unless @context&.strict_variables
raise Liquid::UndefinedDropMethod, "undefined method #{method}"
end
@@ -69,7 +70,7 @@ module Liquid
if include?(Enumerable)
blacklist += Enumerable.public_instance_methods
blacklist -= [:sort, :count, :first, :min, :max]
blacklist -= %i[sort count first min max include?]
end
whitelist = [:to_liquid] + (public_instance_methods - blacklist)

View File

@@ -7,12 +7,12 @@ module Liquid
attr_accessor :markup_context
def to_s(with_prefix = true)
str = +""
str = +''
str << message_prefix if with_prefix
str << super()
if markup_context
str << " "
str << ' '
str << markup_context
end
@@ -22,37 +22,37 @@ module Liquid
private
def message_prefix
str = +""
str = +''
str << if is_a?(SyntaxError)
"Liquid syntax error"
'Liquid syntax error'
else
"Liquid error"
'Liquid error'
end
if line_number
str << " ("
str << template_name << " " if template_name
str << "line " << line_number.to_s << ")"
str << ' ('
str << template_name << ' ' if template_name
str << 'line ' << line_number.to_s << ')'
end
str << ": "
str << ': '
str
end
end
ArgumentError = Class.new(Error)
ContextError = Class.new(Error)
FileSystemError = Class.new(Error)
StandardError = Class.new(Error)
SyntaxError = Class.new(Error)
StackLevelError = Class.new(Error)
TaintedError = Class.new(Error)
MemoryError = Class.new(Error)
ZeroDivisionError = Class.new(Error)
FloatDomainError = Class.new(Error)
UndefinedVariable = Class.new(Error)
ArgumentError = Class.new(Error)
ContextError = Class.new(Error)
FileSystemError = Class.new(Error)
StandardError = Class.new(Error)
SyntaxError = Class.new(Error)
StackLevelError = Class.new(Error)
TaintedError = Class.new(Error)
MemoryError = Class.new(Error)
ZeroDivisionError = Class.new(Error)
FloatDomainError = Class.new(Error)
UndefinedVariable = Class.new(Error)
UndefinedDropMethod = Class.new(Error)
UndefinedFilter = Class.new(Error)
UndefinedFilter = Class.new(Error)
MethodOverrideError = Class.new(Error)
InternalError = Class.new(Error)
InternalError = Class.new(Error)
end

View File

@@ -23,11 +23,11 @@ module Liquid
'empty' => MethodLiteral.new(:empty?, '').freeze
}.freeze
SINGLE_QUOTED_STRING = /\A'(.*)'\z/m
DOUBLE_QUOTED_STRING = /\A"(.*)"\z/m
INTEGERS_REGEX = /\A(-?\d+)\z/
FLOATS_REGEX = /\A(-?\d[\d\.]+)\z/
RANGES_REGEX = /\A\((\S+)\.\.(\S+)\)\z/
SINGLE_QUOTED_STRING = /\A'(.*)'\z/m.freeze
DOUBLE_QUOTED_STRING = /\A"(.*)"\z/m.freeze
INTEGERS_REGEX = /\A(-?\d+)\z/.freeze
FLOATS_REGEX = /\A(-?\d[\d\.]+)\z/.freeze
RANGES_REGEX = /\A\((\S+)\.\.(\S+)\)\z/.freeze
def self.parse(markup)
if LITERALS.key?(markup)

View File

@@ -17,7 +17,7 @@ module Liquid
class BlankFileSystem
# Called by Liquid to retrieve a template file
def read_template_file(_template_path)
raise FileSystemError, "This liquid context does not allow includes."
raise FileSystemError, 'This liquid context does not allow includes.'
end
end
@@ -46,8 +46,8 @@ module Liquid
class LocalFileSystem
attr_accessor :root
def initialize(root, pattern = "_%s.liquid")
@root = root
def initialize(root, pattern = '_%s.liquid')
@root = root
@pattern = pattern
end
@@ -59,7 +59,7 @@ module Liquid
end
def full_path(template_path)
raise FileSystemError, "Illegal template name '#{template_path}'" unless %r{\A[^./][a-zA-Z0-9_/]+\z}.match?(template_path)
raise FileSystemError, "Illegal template name '#{template_path}'" unless template_path =~ %r{\A[^./][a-zA-Z0-9_/]+\z}
full_path = if template_path.include?('/')
File.join(root, File.dirname(template_path), @pattern % File.basename(template_path))

View File

@@ -3,10 +3,10 @@
module Liquid
class ForloopDrop < Drop
def initialize(name, length, parentloop)
@name = name
@length = length
@name = name
@length = length
@parentloop = parentloop
@index = 0
@index = 0
end
attr_reader :name, :length, :parentloop

View File

@@ -4,7 +4,7 @@ require 'yaml'
module Liquid
class I18n
DEFAULT_LOCALE = File.join(File.expand_path(__dir__), "locales", "en.yml")
DEFAULT_LOCALE = File.join(File.expand_path(__dir__), 'locales', 'en.yml')
TranslationError = Class.new(StandardError)

View File

@@ -6,13 +6,13 @@ module Liquid
attr_reader :message
def initialize(message = nil)
@message = message || "interrupt"
@message = message || 'interrupt'
end
end
# Interrupt that is thrown whenever a {% break %} is called.
class BreakInterrupt < Interrupt; end
class BreakInterrupt < RuntimeError; end
# Interrupt that is thrown whenever a {% continue %} is called.
class ContinueInterrupt < Interrupt; end
class ContinueInterrupt < RuntimeError; end
end

79
lib/liquid/legacy.rb Normal file
View File

@@ -0,0 +1,79 @@
# frozen_string_literal: true
module Liquid
FilterSeparator = FILTER_SEPARATOR
ArgumentSeparator = ARGUMENT_SEPARATOR
FilterArgumentSeparator = FILTER_ARGUMENT_SEPARATOR
VariableAttributeSeparator = VARIABLE_ATTRIBUTE_SEPARATOR
WhitespaceControl = WHITESPACE_CONTROL
TagStart = TAG_START
TagEnd = TAG_END
VariableSignature = VARIABLE_SIGNATURE
VariableSegment = VARIABLE_SEGMENT
VariableStart = VARIABLE_START
VariableEnd = VARIABLE_END
VariableIncompleteEnd = VARIABLE_INCOMPLETE_END
QuotedString = QUOTED_STRING
QuotedFragment = QUOTED_FRAGMENT
TagAttributes = TAG_ATTRIBUTES
AnyStartingTag = ANY_STARTING_TAG
PartialTemplateParser = PARTIAL_TEMPLATE_PARSER
TemplateParser = TEMPLATE_PARSER
VariableParser = VARIABLE_PARSER
class BlockBody
FullToken = FULL_TOKEN
ContentOfVariable = CONTENT_OF_VARIABLE
WhitespaceOrNothing = WHITESPACE_OR_NOTHING
TAGSTART = TAG_START
VARSTART = VAR_START
end
class Assign < Tag
Syntax = SYNTAX
end
class Capture < Block
Syntax = SYNTAX
end
class Case < Block
Syntax = SYNTAX
WhenSyntax = WHEN_SYNTAX
end
class Cycle < Tag
SimpleSyntax = SIMPLE_SYNTAX
NamedSyntax = NAMED_SYNTAX
end
class For < Block
Syntax = SYNTAX
end
class If < Block
Syntax = SYNTAX
ExpressionsAndOperators = EXPRESSIONS_AND_OPERATORS
end
class Include < Tag
Syntax = SYNTAX
end
class Raw < Block
Syntax = SYNTAX
FullTokenPossiblyInvalid = FULL_TOKEN_POSSIBLY_INVALID
end
class TableRow < Block
Syntax = SYNTAX
end
class Variable
FilterMarkupRegex = FILTER_MARKUP_REGEX
FilterParser = FILTER_PARSER
FilterArgsRegex = FILTER_ARGS_REGEX
JustTagAttributes = JUST_TAG_ATTRIBUTES
MarkupWithQuotedFragment = MARKUP_WITH_QUOTED_FRAGMENT
end
end

View File

@@ -1,6 +1,6 @@
# frozen_string_literal: true
require "strscan"
require 'strscan'
module Liquid
class Lexer
SPECIALS = {
@@ -15,13 +15,13 @@ module Liquid
'?' => :question,
'-' => :dash,
}.freeze
IDENTIFIER = /[a-zA-Z_][\w-]*\??/
SINGLE_STRING_LITERAL = /'[^\']*'/
DOUBLE_STRING_LITERAL = /"[^\"]*"/
NUMBER_LITERAL = /-?\d+(\.\d+)?/
DOTDOT = /\.\./
COMPARISON_OPERATOR = /==|!=|<>|<=?|>=?|contains(?=\s)/
WHITESPACE_OR_NOTHING = /\s*/
IDENTIFIER = /[a-zA-Z_][\w-]*\??/.freeze
SINGLE_STRING_LITERAL = /'[^\']*'/.freeze
DOUBLE_STRING_LITERAL = /"[^\"]*"/.freeze
NUMBER_LITERAL = /-?\d+(\.\d+)?/.freeze
DOTDOT = /\.\./.freeze
COMPARISON_OPERATOR = /==|!=|<>|<=?|>=?|contains(?=\s)/.freeze
WHITESPACE_OR_NOTHING = /\s*/.freeze
def initialize(input)
@ss = StringScanner.new(input)
@@ -33,20 +33,15 @@ module Liquid
until @ss.eos?
@ss.skip(WHITESPACE_OR_NOTHING)
break if @ss.eos?
tok = if (t = @ss.scan(COMPARISON_OPERATOR))
[:comparison, t]
elsif (t = @ss.scan(SINGLE_STRING_LITERAL))
[:string, t]
elsif (t = @ss.scan(DOUBLE_STRING_LITERAL))
[:string, t]
elsif (t = @ss.scan(NUMBER_LITERAL))
[:number, t]
elsif (t = @ss.scan(IDENTIFIER))
[:id, t]
elsif (t = @ss.scan(DOTDOT))
[:dotdot, t]
tok = if (t = @ss.scan(COMPARISON_OPERATOR)) then [:comparison, t]
elsif (t = @ss.scan(SINGLE_STRING_LITERAL)) then [:string, t]
elsif (t = @ss.scan(DOUBLE_STRING_LITERAL)) then [:string, t]
elsif (t = @ss.scan(NUMBER_LITERAL)) then [:number, t]
elsif (t = @ss.scan(IDENTIFIER)) then [:id, t]
elsif (t = @ss.scan(DOTDOT)) then [:dotdot, t]
else
c = @ss.getch
c = @ss.getch
if (s = SPECIALS[c])
[s, c]
else

View File

@@ -22,8 +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"
render: "Syntax error in tag 'render' - Template name must be a quoted string"
argument:
include: "Argument error in tag 'include' - Illegal template name"
disabled:
tag: "usage is not allowed in this context"

View File

@@ -7,11 +7,9 @@ module Liquid
def initialize(options = {})
@template_options = options ? options.dup : {}
@locale = @template_options[:locale] ||= I18n.new
@locale = @template_options[:locale] ||= I18n.new
@warnings = []
self.depth = 0
self.depth = 0
self.partial = false
end
@@ -22,7 +20,6 @@ module Liquid
def partial=(value)
@partial = value
@options = value ? partial_options : @template_options
@error_mode = @options[:error_mode] || Template.error_mode
end

View File

@@ -11,7 +11,7 @@ module Liquid
end
def initialize(node, callbacks)
@node = node
@node = node
@callbacks = callbacks
end

View File

@@ -3,9 +3,9 @@
module Liquid
class Parser
def initialize(input)
l = Lexer.new(input)
l = Lexer.new(input)
@tokens = l.tokenize
@p = 0 # pointer to current location
@p = 0 # pointer to current location
end
def jump(point)
@@ -14,9 +14,8 @@ module Liquid
def consume(type = nil)
token = @tokens[@p]
if type && token[0] != type
raise SyntaxError, "Expected #{type} but found #{@tokens[@p].first}"
end
raise SyntaxError, "Expected #{type} but found #{@tokens[@p].first}" if type && token[0] != type
@p += 1
token[1]
end
@@ -27,6 +26,7 @@ module Liquid
def consume?(type)
token = @tokens[@p]
return false unless token && token[0] == type
@p += 1
token[1]
end
@@ -36,6 +36,7 @@ module Liquid
token = @tokens[@p]
return false unless token && token[0] == :id
return false unless token[1] == str
@p += 1
token[1]
end
@@ -43,10 +44,11 @@ module Liquid
def look(type, ahead = 0)
tok = @tokens[@p + ahead]
return false unless tok
tok[0] == type
end
SINGLE_TOKEN_EXPRESSION_TYPES = [:string, :number].freeze
SINGLE_TOKEN_EXPRESSION_TYPES = %i[string number].freeze
private_constant :SINGLE_TOKEN_EXPRESSION_TYPES
def expression
@@ -68,11 +70,9 @@ module Liquid
end
def argument
str = +""
str = +''
# might be a keyword argument (identifier: expression)
if look(:id) && look(:colon, 1)
str << consume << consume << ' '
end
str << consume << consume << ' ' if look(:id) && look(:colon, 1)
str << expression
str

View File

@@ -21,7 +21,7 @@ module Liquid
def strict_parse_with_error_context(markup)
strict_parse(markup)
rescue SyntaxError => e
e.line_number = line_number
e.line_number = line_number
e.markup_context = markup_context(markup)
raise e
end

View File

@@ -1,21 +0,0 @@
# frozen_string_literal: true
module Liquid
class PartialCache
def self.load(template_name, context:, parse_context:)
cached_partials = (context.registers[:cached_partials] ||= {})
cached = cached_partials[template_name]
return cached if cached
file_system = (context.registers[:file_system] ||= Liquid::Template.file_system)
source = file_system.read_template_file(template_name)
parse_context.partial = true
partial = Liquid::Template.parse(source, parse_context)
cached_partials[template_name] = partial
ensure
parse_context.partial = false
end
end
end

View File

@@ -46,7 +46,7 @@ module Liquid
include Enumerable
class Timing
attr_reader :code, :partial, :line_number, :children, :total_time, :self_time
attr_reader :code, :partial, :line_number, :children
def initialize(node, partial)
@code = node.respond_to?(:raw) ? node.raw : node
@@ -64,18 +64,7 @@ module Liquid
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
@self_time = @total_time - total_children_time
end
@end_time = Time.now
end
def render_time
@@ -109,14 +98,14 @@ module Liquid
Thread.current[:liquid_profiler]
end
def initialize(partial_name = "<root>")
@partial_stack = [partial_name]
def initialize
@partial_stack = ['<root>']
@root_timing = Timing.new("", current_partial)
@root_timing = Timing.new('', current_partial)
@timing_stack = [@root_timing]
@render_start_at = Time.now
@render_end_at = @render_start_at
@render_end_at = @render_start_at
end
def start

View File

@@ -4,7 +4,7 @@ module Liquid
class RangeLookup
def self.parse(start_markup, end_markup)
start_obj = Expression.parse(start_markup)
end_obj = Expression.parse(end_markup)
end_obj = Expression.parse(end_markup)
if start_obj.respond_to?(:evaluate) || end_obj.respond_to?(:evaluate)
new(start_obj, end_obj)
else
@@ -14,12 +14,12 @@ module Liquid
def initialize(start_obj, end_obj)
@start_obj = start_obj
@end_obj = end_obj
@end_obj = end_obj
end
def evaluate(context)
start_int = to_integer(context.evaluate(@start_obj))
end_int = to_integer(context.evaluate(@end_obj))
end_int = to_integer(context.evaluate(@end_obj))
start_int..end_int
end

View File

@@ -1,6 +0,0 @@
# frozen_string_literal: true
module Liquid
class Register
end
end

View File

@@ -1,32 +0,0 @@
# frozen_string_literal: true
module Liquid
class DisabledTags < Register
def initialize
@disabled_tags = {}
end
def disabled?(tag)
@disabled_tags.key?(tag) && @disabled_tags[tag] > 0
end
def disable(tags)
tags.each(&method(:increment))
yield
ensure
tags.each(&method(:decrement))
end
private
def increment(tag)
@disabled_tags[tag] ||= 0
@disabled_tags[tag] += 1
end
def decrement(tag)
@disabled_tags[tag] -= 1
end
end
Template.add_register(:disabled_tags, DisabledTags.new)
end

View File

@@ -3,12 +3,12 @@
module Liquid
class ResourceLimits
attr_accessor :render_length, :render_score, :assign_score,
:render_length_limit, :render_score_limit, :assign_score_limit
:render_length_limit, :render_score_limit, :assign_score_limit
def initialize(limits)
@render_length_limit = limits[:render_length_limit]
@render_score_limit = limits[:render_score_limit]
@assign_score_limit = limits[:assign_score_limit]
@render_score_limit = limits[:render_score_limit]
@assign_score_limit = limits[:assign_score_limit]
reset
end

View File

@@ -12,13 +12,13 @@ module Liquid
'"' => '&quot;',
"'" => '&#39;',
}.freeze
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/
STRIP_HTML_BLOCKS = Regexp.union(
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/.freeze
STRIP_HTML_BLOCKS = Regexp.union(
%r{<script.*?</script>}m,
/<!--.*?-->/m,
%r{<style.*?</style>}m
)
STRIP_HTML_TAGS = /<.*?>/m
STRIP_HTML_TAGS = /<.*?>/m.freeze
# Return the size of an array or of an string
def size(input)
@@ -74,28 +74,25 @@ module Liquid
end
# Truncate a string down to x characters
def truncate(input, length = 50, truncate_string = "...")
def truncate(input, length = 50, truncate_string = '...')
return if input.nil?
input_str = input.to_s
length = Utils.to_integer(length)
length = Utils.to_integer(length)
truncate_string_str = truncate_string.to_s
l = length - truncate_string_str.length
l = 0 if l < 0
input_str.length > length ? input_str[0...l].concat(truncate_string_str) : input_str
end
def truncatewords(input, words = 15, truncate_string = "...")
def truncatewords(input, words = 15, truncate_string = '...')
return if input.nil?
wordlist = input.to_s.split
words = Utils.to_integer(words)
wordlist = input.to_s.split
words = Utils.to_integer(words)
l = words - 1
l = 0 if l < 0
wordlist.length > l ? wordlist[0..l].join(" ").concat(truncate_string.to_s) : input
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.
@@ -120,7 +117,7 @@ module Liquid
end
def strip_html(input)
empty = ''
empty = ''
result = input.to_s.gsub(STRIP_HTML_BLOCKS, empty)
result.gsub!(STRIP_HTML_TAGS, empty)
result
@@ -133,13 +130,13 @@ module Liquid
# Join elements of the array with certain character between them
def join(input, glue = ' ')
InputIterator.new(input, context).join(glue)
InputIterator.new(input).join(glue)
end
# Sort elements of the array
# provide optional property with which to sort an array of hashes or drops
def sort(input, property = nil)
ary = InputIterator.new(input, context)
ary = InputIterator.new(input)
return [] if ary.empty?
@@ -159,7 +156,7 @@ module Liquid
# Sort elements of an array ignoring case if strings
# provide optional property with which to sort an array of hashes or drops
def sort_natural(input, property = nil)
ary = InputIterator.new(input, context)
ary = InputIterator.new(input)
return [] if ary.empty?
@@ -179,7 +176,7 @@ module Liquid
# Filter the elements of an array to those with a certain property value.
# By default the target is any truthy value.
def where(input, property, target_value = nil)
ary = InputIterator.new(input, context)
ary = InputIterator.new(input)
if ary.empty?
[]
@@ -201,7 +198,7 @@ module Liquid
# Remove duplicate elements from an array
# provide optional property with which to determine uniqueness
def uniq(input, property = nil)
ary = InputIterator.new(input, context)
ary = InputIterator.new(input)
if property.nil?
ary.uniq
@@ -218,16 +215,16 @@ module Liquid
# Reverse the elements of an array
def reverse(input)
ary = InputIterator.new(input, context)
ary = InputIterator.new(input)
ary.reverse
end
# map/collect on a given property
def map(input, property)
InputIterator.new(input, context).map do |e|
InputIterator.new(input).map do |e|
e = e.call if e.is_a?(Proc)
if property == "to_liquid"
if property == 'to_liquid'
e
elsif e.respond_to?(:[])
r = e[property]
@@ -241,7 +238,7 @@ module Liquid
# Remove nils within an array
# provide optional property with which to check for nil
def compact(input, property = nil)
ary = InputIterator.new(input, context)
ary = InputIterator.new(input)
if property.nil?
ary.compact
@@ -282,10 +279,9 @@ module Liquid
end
def concat(input, array)
unless array.respond_to?(:to_ary)
raise ArgumentError, "concat filter requires an array argument"
end
InputIterator.new(input, context).concat(array)
raise ArgumentError, 'concat filter requires an array argument' unless array.respond_to?(:to_ary)
InputIterator.new(input).concat(array)
end
# prepend a string to another
@@ -426,26 +422,16 @@ module Liquid
result.is_a?(BigDecimal) ? result.to_f : result
end
# Set a default value when the input is nil, false or empty
#
# Example:
# {{ product.title | default: "No Title" }}
#
# Use `allow_false` when an input should only be tested against nil or empty and not false.
#
# Example:
# {{ product.title | default: "No Title", allow_false: true }}
#
def default(input, default_value = '', options = {})
options = {} unless options.is_a?(Hash)
false_check = options['allow_false'] ? input.nil? : !input
false_check || (input.respond_to?(:empty?) && input.empty?) ? default_value : input
def default(input, default_value = '')
if !input || input.respond_to?(:empty?) && input.empty?
default_value
else
input
end
end
private
attr_reader :context
def raise_property_error(property)
raise Liquid::ArgumentError, "cannot select the property '#{property}'"
end
@@ -474,9 +460,8 @@ module Liquid
class InputIterator
include Enumerable
def initialize(input, context)
@context = context
@input = if input.is_a?(Array)
def initialize(input)
@input = if input.is_a?(Array)
input.flatten
elsif input.is_a?(Hash)
[input]
@@ -514,7 +499,6 @@ module Liquid
def each
@input.each do |e|
e.context = @context if e.respond_to?(:context=)
yield(e.respond_to?(:to_liquid) ? e.to_liquid : e)
end
end

View File

@@ -1,36 +0,0 @@
# frozen_string_literal: true
module Liquid
class StaticRegisters
attr_reader :static, :registers
def initialize(registers = {})
@static = registers.is_a?(StaticRegisters) ? registers.static : registers
@registers = {}
end
def []=(key, value)
@registers[key] = value
end
def [](key)
if @registers.key?(key)
@registers[key]
else
@static[key]
end
end
def delete(key)
@registers.delete(key)
end
def fetch(key, default = nil)
key?(key) ? self[key] : default
end
def key?(key)
@registers.key?(key) || @static.key?(key)
end
end
end

View File

@@ -9,12 +9,12 @@ module Liquid
# The Strainer only allows method calls defined in filters given to it via Strainer.global_filter,
# Context#add_filters or Template.register_filter
class Strainer #:nodoc:
@@global_strainer = Class.new(Strainer) do
@global_strainer = Class.new(Strainer) do
@filter_methods = Set.new
end
@@strainer_class_cache = Hash.new do |hash, filters|
hash[filters] = Class.new(@@global_strainer) do
@filter_methods = @@global_strainer.filter_methods.dup
@strainer_class_cache = Hash.new do |hash, filters|
hash[filters] = Class.new(@global_strainer) do
@filter_methods = Strainer.global_strainer.filter_methods.dup
filters.each { |f| add_filter(f) }
end
end
@@ -24,11 +24,13 @@ module Liquid
end
class << self
attr_accessor :strainer_class_cache, :global_strainer
attr_reader :filter_methods
end
def self.add_filter(filter)
raise ArgumentError, "Expected module but got: #{filter.class}" unless filter.is_a?(Module)
unless include?(filter)
invokable_non_public_methods = (filter.private_instance_methods + filter.protected_instance_methods).select { |m| invokable?(m) }
if invokable_non_public_methods.any?
@@ -41,8 +43,8 @@ module Liquid
end
def self.global_filter(filter)
@@strainer_class_cache.clear
@@global_strainer.add_filter(filter)
@strainer_class_cache.clear
@global_strainer.add_filter(filter)
end
def self.invokable?(method)
@@ -50,7 +52,7 @@ module Liquid
end
def self.create(context, filters = [])
@@strainer_class_cache[filters].new(context)
@strainer_class_cache[filters].new(context)
end
def invoke(method, *args)

View File

@@ -4,10 +4,10 @@ module Liquid
class TablerowloopDrop < Drop
def initialize(length, cols)
@length = length
@row = 1
@col = 1
@cols = cols
@index = 0
@row = 1
@col = 1
@cols = cols
@index = 0
end
attr_reader :length, :col, :row

View File

@@ -7,32 +7,23 @@ module Liquid
include ParserSwitching
class << self
def parse(tag_name, markup, tokenizer, parse_context)
tag = new(tag_name, markup, parse_context)
def parse(tag_name, markup, tokenizer, options)
tag = new(tag_name, markup, options)
tag.parse(tokenizer)
tag
end
def disable_tags(*tags)
disabled_tags.push(*tags)
end
private :new
def disabled_tags
@disabled_tags ||= []
end
end
def initialize(tag_name, markup, parse_context)
@tag_name = tag_name
@markup = markup
@tag_name = tag_name
@markup = markup
@parse_context = parse_context
@line_number = parse_context.line_number
@line_number = parse_context.line_number
end
def parse(_tokens)
end
def parse(_tokens); end
def raw
"#{@tag_name} #{@markup}"
@@ -46,14 +37,6 @@ module Liquid
''
end
def disabled?(context)
context.registers[:disabled_tags].disabled?(tag_name)
end
def disabled_error_message
"#{tag_name} #{options[:locale].t('errors.disabled.tag')}"
end
# For backwards compatibility with custom tags. In a future release, the semantics
# of the `render_to_output_buffer` method will become the default and the `render`
# method will be removed.
@@ -65,9 +48,5 @@ module Liquid
def blank?
false
end
def disabled_tags
self.class.disabled_tags
end
end
end

View File

@@ -10,21 +10,17 @@ module Liquid
# {{ foo }}
#
class Assign < Tag
Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om
def self.syntax_error_translation_key
"errors.syntax.assign"
end
SYNTAX = /(#{VARIABLE_SIGNATURE}+)\s*=\s*(.*)\s*/om.freeze
attr_reader :to, :from
def initialize(tag_name, markup, options)
super
if markup =~ Syntax
@to = Regexp.last_match(1)
if markup =~ SYNTAX
@to = Regexp.last_match(1)
@from = Variable.new(Regexp.last_match(2), options)
else
raise SyntaxError, options[:locale].t(self.class.syntax_error_translation_key)
raise SyntaxError, options[:locale].t('errors.syntax.assign')
end
end

View File

@@ -13,14 +13,14 @@ module Liquid
# in a sidebar or footer.
#
class Capture < Block
Syntax = /(#{VariableSignature}+)/o
SYNTAX = /(#{VARIABLE_SIGNATURE}+)/o.freeze
def initialize(tag_name, markup, options)
super
if markup =~ Syntax
if markup =~ SYNTAX
@to = Regexp.last_match(1)
else
raise SyntaxError, options[:locale].t("errors.syntax.capture")
raise SyntaxError, options[:locale].t('errors.syntax.capture')
end
end

View File

@@ -2,8 +2,8 @@
module Liquid
class Case < Block
Syntax = /(#{QuotedFragment})/o
WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/om
SYNTAX = /(#{QUOTED_FRAGMENT})/o.freeze
WHEN_SYNTAX = /(#{QUOTED_FRAGMENT})(?:(?:\s+or\s+|\s*\,\s*)(#{QUOTED_FRAGMENT}.*))?/om.freeze
attr_reader :blocks, :left
@@ -11,10 +11,10 @@ module Liquid
super
@blocks = []
if markup =~ Syntax
if markup =~ SYNTAX
@left = Expression.parse(Regexp.last_match(1))
else
raise SyntaxError, options[:locale].t("errors.syntax.case")
raise SyntaxError, options[:locale].t('errors.syntax.case')
end
end
@@ -39,14 +39,16 @@ module Liquid
end
def render_to_output_buffer(context, output)
execute_else_block = true
context.stack do
execute_else_block = true
@blocks.each do |block|
if block.else?
block.attachment.render_to_output_buffer(context, output) if execute_else_block
elsif block.evaluate(context)
execute_else_block = false
block.attachment.render_to_output_buffer(context, output)
@blocks.each do |block|
if block.else?
block.attachment.render_to_output_buffer(context, output) if execute_else_block
elsif block.evaluate(context)
execute_else_block = false
block.attachment.render_to_output_buffer(context, output)
end
end
end
@@ -59,9 +61,7 @@ module Liquid
body = BlockBody.new
while markup
unless markup =~ WhenSyntax
raise SyntaxError, options[:locale].t("errors.syntax.case_invalid_when")
end
raise SyntaxError, options[:locale].t('errors.syntax.case_invalid_when') unless markup =~ WHEN_SYNTAX
markup = Regexp.last_match(2)
@@ -72,9 +72,7 @@ module Liquid
end
def record_else_condition(markup)
unless markup.strip.empty?
raise SyntaxError, options[:locale].t("errors.syntax.case_invalid_else")
end
raise SyntaxError, options[:locale].t('errors.syntax.case_invalid_else') unless markup.strip.empty?
block = ElseCondition.new
block.attach(BlockBody.new)

View File

@@ -6,8 +6,7 @@ module Liquid
output
end
def unknown_tag(_tag, _markup, _tokens)
end
def unknown_tag(_tag, _markup, _tokens); end
def blank?
true

View File

@@ -14,45 +14,47 @@ module Liquid
# <div class="green"> Item five</div>
#
class Cycle < Tag
SimpleSyntax = /\A#{QuotedFragment}+/o
NamedSyntax = /\A(#{QuotedFragment})\s*\:\s*(.*)/om
SIMPLE_SYNTAX = /\A#{QUOTED_FRAGMENT}+/o.freeze
NAMED_SYNTAX = /\A(#{QUOTED_FRAGMENT})\s*\:\s*(.*)/om.freeze
attr_reader :variables
def initialize(tag_name, markup, options)
super
case markup
when NamedSyntax
when NAMED_SYNTAX
@variables = variables_from_string(Regexp.last_match(2))
@name = Expression.parse(Regexp.last_match(1))
when SimpleSyntax
@name = Expression.parse(Regexp.last_match(1))
when SIMPLE_SYNTAX
@variables = variables_from_string(markup)
@name = @variables.to_s
@name = @variables.to_s
else
raise SyntaxError, options[:locale].t("errors.syntax.cycle")
raise SyntaxError, options[:locale].t('errors.syntax.cycle')
end
end
def render_to_output_buffer(context, output)
context.registers[:cycle] ||= {}
key = context.evaluate(@name)
iteration = context.registers[:cycle][key].to_i
context.stack do
key = context.evaluate(@name)
iteration = context.registers[:cycle][key].to_i
val = context.evaluate(@variables[iteration])
val = context.evaluate(@variables[iteration])
if val.is_a?(Array)
val = val.join
elsif !val.is_a?(String)
val = val.to_s
if val.is_a?(Array)
val = val.join
elsif !val.is_a?(String)
val = val.to_s
end
output << val
iteration += 1
iteration = 0 if iteration >= @variables.size
context.registers[:cycle][key] = iteration
end
output << val
iteration += 1
iteration = 0 if iteration >= @variables.size
context.registers[:cycle][key] = iteration
output
end
@@ -60,7 +62,7 @@ module Liquid
def variables_from_string(markup)
markup.split(',').collect do |var|
var =~ /\s*(#{QuotedFragment})\s*/o
var =~ /\s*(#{QUOTED_FRAGMENT})\s*/o
Regexp.last_match(1) ? Expression.parse(Regexp.last_match(1)) : nil
end.compact
end

View File

@@ -1,26 +0,0 @@
# frozen_string_literal: true
module Liquid
# Echo outputs an expression
#
# {% echo monkey %}
# {% echo user.name %}
#
# This is identical to variable output syntax, like {{ foo }}, but works
# inside {% liquid %} tags. The full syntax is supported, including filters:
#
# {% echo user | link %}
#
class Echo < Tag
def initialize(tag_name, markup, parse_context)
super
@variable = Variable.new(markup, parse_context)
end
def render(context)
@variable.render_to_output_buffer(context, +'')
end
end
Template.register_tag('echo', Echo)
end

View File

@@ -46,7 +46,7 @@ module Liquid
# forloop.parentloop:: Provides access to the parent loop, if present.
#
class For < Block
Syntax = /\A(#{VariableSegment}+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
SYNTAX = /\A(#{VARIABLE_SEGMENT}+)\s+in\s+(#{QUOTED_FRAGMENT}+)\s*(reversed)?/o.freeze
attr_reader :collection_name, :variable_name, :limit, :from
@@ -60,6 +60,7 @@ module Liquid
def parse(tokens)
return unless parse_body(@for_block, tokens)
parse_body(@else_block, tokens)
end
@@ -69,6 +70,7 @@ module Liquid
def unknown_tag(tag, markup, tokens)
return super unless tag == 'else'
@else_block = BlockBody.new
end
@@ -87,35 +89,35 @@ module Liquid
protected
def lax_parse(markup)
if markup =~ Syntax
@variable_name = Regexp.last_match(1)
collection_name = Regexp.last_match(2)
@reversed = !!Regexp.last_match(3)
@name = "#{@variable_name}-#{collection_name}"
if markup =~ SYNTAX
@variable_name = Regexp.last_match(1)
collection_name = Regexp.last_match(2)
@reversed = !!Regexp.last_match(3)
@name = "#{@variable_name}-#{collection_name}"
@collection_name = Expression.parse(collection_name)
markup.scan(TagAttributes) do |key, value|
markup.scan(TAG_ATTRIBUTES) do |key, value|
set_attribute(key, value)
end
else
raise SyntaxError, options[:locale].t("errors.syntax.for")
raise SyntaxError, options[:locale].t('errors.syntax.for')
end
end
def strict_parse(markup)
p = Parser.new(markup)
@variable_name = p.consume(:id)
raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_in") unless p.id?('in')
raise SyntaxError, options[:locale].t('errors.syntax.for_invalid_in') unless p.id?('in')
collection_name = p.expression
collection_name = p.expression
@name = "#{@variable_name}-#{collection_name}"
@collection_name = Expression.parse(collection_name)
@name = "#{@variable_name}-#{collection_name}"
@reversed = p.id?('reversed')
while p.look(:id) && p.look(:colon, 1)
unless (attribute = p.id?('limit') || p.id?('offset'))
raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_attribute")
raise SyntaxError, options[:locale].t('errors.syntax.for_invalid_attribute')
end
p.consume
set_attribute(attribute, p.expression)
end
@@ -158,7 +160,7 @@ module Liquid
def render_segment(context, output, segment)
for_stack = context.registers[:for_stack] ||= []
length = segment.length
length = segment.length
context.stack do
loop_vars = Liquid::ForloopDrop.new(@name, length, for_stack[-1])
@@ -175,6 +177,7 @@ module Liquid
# Handle any interrupts if they exist.
next unless context.interrupt?
interrupt = context.pop_interrupt
break if interrupt.is_a?(BreakInterrupt)
next if interrupt.is_a?(ContinueInterrupt)

View File

@@ -12,9 +12,9 @@ module Liquid
# There are {% if count < 5 %} less {% else %} more {% endif %} items than you need.
#
class If < Block
Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/o
ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/o
BOOLEAN_OPERATORS = %w(and or).freeze
SYNTAX = /(#{QUOTED_FRAGMENT})\s*([=!<>a-z_]+)?\s*(#{QUOTED_FRAGMENT})?/o.freeze
EXPRESSIONS_AND_OPERATORS = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QUOTED_FRAGMENT}|\S+)\s*)+)/o.freeze
BOOLEAN_OPERATORS = %w[and or].freeze
attr_reader :blocks
@@ -34,7 +34,7 @@ module Liquid
end
def unknown_tag(tag, markup, tokens)
if ['elsif', 'else'].include?(tag)
if %w[elsif else].include?(tag)
push_block(tag, markup)
else
super
@@ -42,9 +42,9 @@ module Liquid
end
def render_to_output_buffer(context, output)
@blocks.each do |block|
if block.evaluate(context)
return block.attachment.render_to_output_buffer(context, output)
context.stack do
@blocks.each do |block|
return block.attachment.render_to_output_buffer(context, output) if block.evaluate(context)
end
end
@@ -65,18 +65,19 @@ module Liquid
end
def lax_parse(markup)
expressions = markup.scan(ExpressionsAndOperators)
raise SyntaxError, options[:locale].t("errors.syntax.if") unless expressions.pop =~ Syntax
expressions = markup.scan(EXPRESSIONS_AND_OPERATORS)
raise SyntaxError, options[:locale].t('errors.syntax.if') unless expressions.pop =~ SYNTAX
condition = Condition.new(Expression.parse(Regexp.last_match(1)), Regexp.last_match(2), Expression.parse(Regexp.last_match(3)))
until expressions.empty?
operator = expressions.pop.to_s.strip
raise SyntaxError, options[:locale].t("errors.syntax.if") unless expressions.pop.to_s =~ Syntax
raise SyntaxError, options[:locale].t('errors.syntax.if') unless expressions.pop.to_s =~ SYNTAX
new_condition = Condition.new(Expression.parse(Regexp.last_match(1)), Regexp.last_match(2), Expression.parse(Regexp.last_match(3)))
raise SyntaxError, options[:locale].t("errors.syntax.if") unless BOOLEAN_OPERATORS.include?(operator)
raise SyntaxError, options[:locale].t('errors.syntax.if') unless BOOLEAN_OPERATORS.include?(operator)
new_condition.send(operator, condition)
condition = new_condition
end

View File

@@ -3,12 +3,14 @@
module Liquid
class Ifchanged < Block
def render_to_output_buffer(context, output)
block_output = +''
super(context, block_output)
context.stack do
block_output = +''
super(context, block_output)
if block_output != context.registers[:ifchanged]
context.registers[:ifchanged] = block_output
output << block_output
if block_output != context.registers[:ifchanged]
context.registers[:ifchanged] = block_output
output << block_output
end
end
output

View File

@@ -16,8 +16,7 @@ module Liquid
# {% include 'product' for products %}
#
class Include < Tag
SYNTAX = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o
Syntax = SYNTAX
SYNTAX = /(#{QUOTED_FRAGMENT}+)(\s+(?:with|for)\s+(#{QUOTED_FRAGMENT}+))?/o.freeze
attr_reader :template_name_expr, :variable_name_expr, :attributes
@@ -29,45 +28,42 @@ module Liquid
template_name = Regexp.last_match(1)
variable_name = Regexp.last_match(3)
@alias_name = Regexp.last_match(5)
@variable_name_expr = variable_name ? Expression.parse(variable_name) : nil
@template_name_expr = Expression.parse(template_name)
@attributes = {}
@attributes = {}
markup.scan(TagAttributes) do |key, value|
markup.scan(TAG_ATTRIBUTES) do |key, value|
@attributes[key] = Expression.parse(value)
end
else
raise SyntaxError, options[:locale].t("errors.syntax.include")
raise SyntaxError, options[:locale].t('errors.syntax.include')
end
end
def parse(_tokens)
end
def parse(_tokens); end
def render_to_output_buffer(context, output)
template_name = context.evaluate(@template_name_expr)
raise ArgumentError, options[:locale].t("errors.argument.include") unless template_name
raise ArgumentError, options[:locale].t('errors.argument.include') unless template_name
partial = load_partial(template_name, context, parse_context)
partial = load_cached_partial(template_name, context)
context_variable_name = template_name.split('/').last
variable = if @variable_name_expr
context.evaluate(@variable_name_expr)
else
context.find_variable(template_name, raise_on_not_found: false)
end
old_template_name = context.template_name
old_partial = context.partial
old_partial = context.partial
begin
context.template_name = template_name
context.partial = true
context.partial = true
context.stack do
@attributes.each do |key, value|
context[key] = evaluate(context, value)
end
context_variable_name = @alias_name || template_name.split('/').last
variable = if @variable_name_expr
evaluate(context, @variable_name_expr)
else
find_variable(context, template_name, raise_on_not_found: false)
context[key] = context.evaluate(value)
end
if variable.is_a?(Array)
@@ -82,15 +78,42 @@ module Liquid
end
ensure
context.template_name = old_template_name
context.partial = old_partial
context.partial = old_partial
end
output
end
private
alias_method :parse_context, :options
private :parse_context
def load_cached_partial(template_name, context)
cached_partials = context.registers[:cached_partials] || {}
if (cached = cached_partials[template_name])
return cached
end
source = read_template_from_file_system(context)
begin
parse_context.partial = true
partial = Liquid::Template.parse(source, parse_context)
ensure
parse_context.partial = false
end
cached_partials[template_name] = partial
context.registers[:cached_partials] = cached_partials
partial
end
def read_template_from_file_system(context)
file_system = context.registers[:file_system] || Liquid::Template.file_system
file_system.read_template_file(context.evaluate(@template_name_expr))
end
class ParseTreeVisitor < Liquid::ParseTreeVisitor
def children
[
@@ -99,24 +122,6 @@ module Liquid
] + @node.attributes.values
end
end
private
def evaluate(context, value)
context.evaluate(value)
end
def find_variable(context, *args)
context.find_variable(*args)
end
def load_partial(template_name, context, parse_context)
PartialCache.load(
template_name,
context: context,
parse_context: parse_context
)
end
end
Template.register_tag('include', Include)

View File

@@ -25,7 +25,6 @@ module Liquid
def render_to_output_buffer(context, output)
value = context.environments.first[@variable] ||= 0
context.environments.first[@variable] = value + 1
output << value.to_s
output
end

View File

@@ -2,8 +2,8 @@
module Liquid
class Raw < Block
Syntax = /\A\s*\z/
FullTokenPossiblyInvalid = /\A(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
SYNTAX = /\A\s*\z/.freeze
FULL_TOKEN_POSSIBLY_INVALID = /\A(.*)#{TAG_START}\s*(\w+)\s*(.*)?#{TAG_END}\z/om.freeze
def initialize(tag_name, markup, parse_context)
super
@@ -14,14 +14,14 @@ module Liquid
def parse(tokens)
@body = +''
while (token = tokens.shift)
if token =~ FullTokenPossiblyInvalid
@body << Regexp.last_match(1) if Regexp.last_match(1) != ""
if token =~ FULL_TOKEN_POSSIBLY_INVALID
@body << Regexp.last_match(1) if Regexp.last_match(1) != ''
return if block_delimiter == Regexp.last_match(2)
end
@body << token unless token.empty?
end
raise SyntaxError, parse_context.locale.t("errors.syntax.tag_never_closed", block_name: block_name)
raise SyntaxError, parse_context.locale.t('errors.syntax.tag_never_closed', block_name: block_name)
end
def render_to_output_buffer(_context, output)
@@ -40,9 +40,7 @@ module Liquid
protected
def ensure_valid_markup(tag_name, markup, parse_context)
unless Syntax.match?(markup)
raise SyntaxError, parse_context.locale.t("errors.syntax.tag_unexpected_args", tag: tag_name)
end
raise SyntaxError, parse_context.locale.t('errors.syntax.tag_unexpected_args', tag: tag_name) unless markup =~ SYNTAX
end
end

View File

@@ -1,94 +0,0 @@
# frozen_string_literal: true
module Liquid
class Render < Tag
FOR = 'for'
SYNTAX = /(#{QuotedString}+)(\s+(with|#{FOR})\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o
disable_tags "include"
attr_reader :template_name_expr, :attributes
def initialize(tag_name, markup, options)
super
raise SyntaxError, options[:locale].t("errors.syntax.render") unless markup =~ SYNTAX
template_name = Regexp.last_match(1)
with_or_for = Regexp.last_match(3)
variable_name = Regexp.last_match(4)
@alias_name = Regexp.last_match(6)
@variable_name_expr = variable_name ? Expression.parse(variable_name) : nil
@template_name_expr = Expression.parse(template_name)
@for = (with_or_for == FOR)
@attributes = {}
markup.scan(TagAttributes) do |key, value|
@attributes[key] = Expression.parse(value)
end
end
def render_to_output_buffer(context, output)
render_tag(context, output)
end
def render_tag(context, output)
# Though we evaluate this here we will only ever parse it as a string literal.
template_name = context.evaluate(@template_name_expr)
raise ArgumentError, options[:locale].t("errors.argument.include") unless template_name
partial = load_partial(template_name, context, parse_context)
context_variable_name = @alias_name || template_name.split('/').last
render_partial_func = ->(var, forloop) {
inner_context = context.new_isolated_subcontext
inner_context.template_name = template_name
inner_context.partial = true
inner_context['forloop'] = forloop if forloop
@attributes.each do |key, value|
inner_context[key] = evaluate(context, value)
end
inner_context[context_variable_name] = var unless var.nil?
partial.render_to_output_buffer(inner_context, output)
forloop&.send(:increment!)
}
variable = @variable_name_expr ? evaluate(context, @variable_name_expr) : nil
if @for && variable.respond_to?(:each) && variable.respond_to?(:count)
forloop = Liquid::ForloopDrop.new(template_name, variable.count, nil)
variable.each { |var| render_partial_func.call(var, forloop) }
else
render_partial_func.call(variable, nil)
end
output
end
class ParseTreeVisitor < Liquid::ParseTreeVisitor
def children
[
@node.template_name_expr,
] + @node.attributes.values
end
end
private
def evaluate(context, value)
context.evaluate(value)
end
def load_partial(template_name, context, parse_context)
PartialCache.load(
template_name,
context: context,
parse_context: parse_context
)
end
end
Template.register_tag('render', Render)
end

View File

@@ -2,21 +2,21 @@
module Liquid
class TableRow < Block
Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o
SYNTAX = /(\w+)\s+in\s+(#{QUOTED_FRAGMENT}+)/o.freeze
attr_reader :variable_name, :collection_name, :attributes
def initialize(tag_name, markup, options)
super
if markup =~ Syntax
@variable_name = Regexp.last_match(1)
if markup =~ SYNTAX
@variable_name = Regexp.last_match(1)
@collection_name = Expression.parse(Regexp.last_match(2))
@attributes = {}
markup.scan(TagAttributes) do |key, value|
@attributes = {}
markup.scan(TAG_ATTRIBUTES) do |key, value|
@attributes[key] = Expression.parse(value)
end
else
raise SyntaxError, options[:locale].t("errors.syntax.table_row")
raise SyntaxError, options[:locale].t('errors.syntax.table_row')
end
end
@@ -24,10 +24,11 @@ module Liquid
(collection = context.evaluate(@collection_name)) || (return '')
from = @attributes.key?('offset') ? context.evaluate(@attributes['offset']).to_i : 0
to = @attributes.key?('limit') ? from + context.evaluate(@attributes['limit']).to_i : nil
to = @attributes.key?('limit') ? from + context.evaluate(@attributes['limit']).to_i : nil
collection = Utils.slice_collection(collection, from, to)
length = collection.length
length = collection.length
cols = context.evaluate(@attributes['cols']).to_i
@@ -43,9 +44,7 @@ module Liquid
super
output << '</td>'
if tablerowloop.col_last && !tablerowloop.last
output << "</tr>\n<tr class=\"row#{tablerowloop.row + 1}\">"
end
output << "</tr>\n<tr class=\"row#{tablerowloop.row + 1}\">" if tablerowloop.col_last && !tablerowloop.last
tablerowloop.send(:increment!)
end

View File

@@ -9,16 +9,14 @@ module Liquid
#
class Unless < If
def render_to_output_buffer(context, output)
# First condition is interpreted backwards ( if not )
first_block = @blocks.first
unless first_block.evaluate(context)
return first_block.attachment.render_to_output_buffer(context, output)
end
context.stack do
# First condition is interpreted backwards ( if not )
first_block = @blocks.first
return first_block.attachment.render_to_output_buffer(context, output) unless first_block.evaluate(context)
# After the first condition unless works just like if
@blocks[1..-1].each do |block|
if block.evaluate(context)
return block.attachment.render_to_output_buffer(context, output)
# After the first condition unless works just like if
@blocks[1..-1].each do |block|
return block.attachment.render_to_output_buffer(context, output) if block.evaluate(context)
end
end

View File

@@ -18,13 +18,17 @@ module Liquid
attr_accessor :root
attr_reader :resource_limits, :warnings
@@file_system = BlankFileSystem.new
@file_system = BlankFileSystem.new
class << self
attr_accessor :file_system
end
class TagRegistry
include Enumerable
def initialize
@tags = {}
@tags = {}
@cache = {}
end
@@ -76,14 +80,6 @@ module Liquid
exception
end
def file_system
@@file_system
end
def file_system=(obj)
@@file_system = obj
end
def register_tag(name, klass)
tags[name.to_s] = klass
end
@@ -92,14 +88,6 @@ module Liquid
@tags ||= TagRegistry.new
end
def add_register(name, klass)
registers[name.to_sym] = klass
end
def registers
@registers ||= {}
end
def error_mode
@error_mode ||= :lax
end
@@ -128,19 +116,19 @@ module Liquid
end
def initialize
@rethrow_errors = false
@rethrow_errors = false
@resource_limits = ResourceLimits.new(self.class.default_resource_limits)
end
# Parse source code.
# Returns self for easy chaining
def parse(source, options = {})
@options = options
@profiling = options[:profile]
@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
@root = Document.parse(tokenize(source), parse_context)
@warnings = parse_context.warnings
self
end
@@ -181,44 +169,34 @@ module Liquid
when Liquid::Context
c = args.shift
if @rethrow_errors
c.exception_renderer = ->(_e) { raise }
end
c.exception_renderer = ->(_e) { raise } if @rethrow_errors
c
when Liquid::Drop
drop = args.shift
drop = args.shift
drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
when Hash
Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
when nil
Context.new(assigns, instance_assigns, registers, @rethrow_errors, @resource_limits)
else
raise ArgumentError, "Expected Hash or Liquid::Context as parameter"
raise ArgumentError, 'Expected Hash or Liquid::Context as parameter'
end
output = nil
context_register = context.registers.is_a?(StaticRegisters) ? context.registers.static : context.registers
case args.last
when Hash
options = args.pop
output = options[:output] if options[:output]
output = options[:output] if options[:output]
options[:registers]&.each do |key, register|
context_register[key] = register
end
registers.merge!(options[:registers]) if options[:registers].is_a?(Hash)
apply_options_to_context(context, options)
when Module, Array
context.add_filters(args.pop)
end
Template.registers.each do |key, register|
context_register[key] = register
end
# Retrying a render resets resource usage
context.resource_limits.reset
@@ -254,7 +232,7 @@ module Liquid
if @profiling && !context.partial
raise "Profiler not loaded, require 'liquid/profiler' first" unless defined?(Liquid::Profiler)
@profiler = Profiler.new(context.template_name)
@profiler = Profiler.new
@profiler.start
begin
@@ -269,10 +247,10 @@ module Liquid
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.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]
context.strict_variables = options[:strict_variables] if options[:strict_variables]
context.strict_filters = options[:strict_filters] if options[:strict_filters]
end
end
end

View File

@@ -2,33 +2,27 @@
module Liquid
class Tokenizer
attr_reader :line_number, :for_liquid_tag
attr_reader :line_number
def initialize(source, line_numbers = false, line_number: nil, for_liquid_tag: false)
@source = source
@line_number = line_number || (line_numbers ? 1 : nil)
@for_liquid_tag = for_liquid_tag
@tokens = tokenize
def initialize(source, line_numbers = false)
@source = source
@line_number = line_numbers ? 1 : nil
@tokens = tokenize
end
def shift
(token = @tokens.shift) || return
if @line_number
@line_number += @for_liquid_tag ? 1 : token.count("\n")
end
token = @tokens.shift
@line_number += token.count("\n") if @line_number && token
token
end
private
def tokenize
@source = @source.source if @source.respond_to?(:source)
return [] if @source.to_s.empty?
return @source.split("\n") if @for_liquid_tag
tokens = @source.split(TemplateParser)
tokens = @source.split(TEMPLATE_PARSER)
# removes the rogue empty element at the beginning of the array
tokens.shift if tokens[0]&.empty?

View File

@@ -1,8 +0,0 @@
# frozen_string_literal: true
module Liquid
module Usage
def self.increment(name)
end
end
end

View File

@@ -12,7 +12,7 @@ module Liquid
def self.slice_collection_using_each(collection, from, to)
segments = []
index = 0
index = 0
# Maintains Ruby 1.8.7 String#each behaviour on 1.9
if collection.is_a?(String)
@@ -21,13 +21,9 @@ module Liquid
return [] unless collection.respond_to?(:each)
collection.each do |item|
if to && to <= index
break
end
break if to && to <= index
if from <= index
segments << item
end
segments << item if from <= index
index += 1
end
@@ -37,11 +33,12 @@ module Liquid
def self.to_integer(num)
return num if num.is_a?(Integer)
num = num.to_s
begin
Integer(num)
rescue ::ArgumentError
raise Liquid::ArgumentError, "invalid integer"
raise Liquid::ArgumentError, 'invalid integer'
end
end
@@ -52,7 +49,7 @@ module Liquid
when Numeric
obj
when String
/\A-?\d+\.\d+\z/.match?(obj.strip) ? BigDecimal(obj) : obj.to_i
obj.strip =~ /\A-?\d+\.\d+\z/ ? BigDecimal(obj) : obj.to_i
else
if obj.respond_to?(:to_number)
obj.to_number
@@ -67,6 +64,7 @@ module Liquid
if obj.is_a?(String)
return nil if obj.empty?
obj = obj.downcase
end

View File

@@ -12,11 +12,11 @@ module Liquid
# {{ user | link }}
#
class Variable
FilterMarkupRegex = /#{FilterSeparator}\s*(.*)/om
FilterParser = /(?:\s+|#{QuotedFragment}|#{ArgumentSeparator})+/o
FilterArgsRegex = /(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o
JustTagAttributes = /\A#{TagAttributes}\z/o
MarkupWithQuotedFragment = /(#{QuotedFragment})(.*)/om
FILTER_MARKUP_REGEX = /#{FILTER_SEPARATOR}\s*(.*)/om.freeze
FILTER_PARSER = /(?:\s+|#{QUOTED_FRAGMENT}|#{ARGUMENT_SEPARATOR})+/o.freeze
FILTER_ARGS_REGEX = /(?:#{FILTER_ARGUMENT_SEPARATOR}|#{ARGUMENT_SEPARATOR})\s*((?:\w+\s*\:\s*)?#{QUOTED_FRAGMENT})/o.freeze
JUST_TAG_ATTRIBUTES = /\A#{TAG_ATTRIBUTES}\z/o.freeze
MARKUP_WITH_QUOTED_FRAGMENT = /(#{QUOTED_FRAGMENT})(.*)/om.freeze
attr_accessor :filters, :name, :line_number
attr_reader :parse_context
@@ -25,10 +25,10 @@ module Liquid
include ParserSwitching
def initialize(markup, parse_context)
@markup = markup
@name = nil
@markup = markup
@name = nil
@parse_context = parse_context
@line_number = parse_context.line_number
@line_number = parse_context.line_number
parse_with_selected_parser(markup)
end
@@ -43,17 +43,18 @@ module Liquid
def lax_parse(markup)
@filters = []
return unless markup =~ MarkupWithQuotedFragment
return unless markup =~ MARKUP_WITH_QUOTED_FRAGMENT
name_markup = Regexp.last_match(1)
name_markup = Regexp.last_match(1)
filter_markup = Regexp.last_match(2)
@name = Expression.parse(name_markup)
if filter_markup =~ FilterMarkupRegex
filters = Regexp.last_match(1).scan(FilterParser)
@name = Expression.parse(name_markup)
if filter_markup =~ FILTER_MARKUP_REGEX
filters = Regexp.last_match(1).scan(FILTER_PARSER)
filters.each do |f|
next unless f =~ /\w+/
filtername = Regexp.last_match(0)
filterargs = f.scan(FilterArgsRegex).flatten
filterargs = f.scan(FILTER_ARGS_REGEX).flatten
@filters << parse_filter_expressions(filtername, filterargs)
end
end
@@ -104,22 +105,14 @@ module Liquid
output
end
def disabled?(_context)
false
end
def disabled_tags
[]
end
private
def parse_filter_expressions(filter_name, unparsed_args)
filter_args = []
filter_args = []
keyword_args = nil
unparsed_args.each do |a|
if (matches = a.match(JustTagAttributes))
keyword_args ||= {}
if (matches = a.match(JUST_TAG_ATTRIBUTES))
keyword_args ||= {}
keyword_args[matches[1]] = Expression.parse(matches[2])
else
filter_args << Expression.parse(a)
@@ -146,11 +139,11 @@ module Liquid
return unless obj.tainted?
return if Template.taint_mode == :lax
@markup =~ QuotedFragment
@markup =~ QUOTED_FRAGMENT
name = Regexp.last_match(0)
error = TaintedError.new("variable '#{name}' is tainted and was not escaped")
error.line_number = line_number
error = TaintedError.new("variable '#{name}' is tainted and was not escaped")
error.line_number = line_number
error.template_name = context.template_name
case Template.taint_mode

View File

@@ -2,8 +2,8 @@
module Liquid
class VariableLookup
SQUARE_BRACKETED = /\A\[(.*)\]\z/m
COMMAND_METHODS = ['size', 'first', 'last'].freeze
SQUARE_BRACKETED = /\A\[(.*)\]\z/m.freeze
COMMAND_METHODS = %w[size first last].freeze
attr_reader :name, :lookups
@@ -12,15 +12,13 @@ module Liquid
end
def initialize(markup)
lookups = markup.scan(VariableParser)
lookups = markup.scan(VARIABLE_PARSER)
name = lookups.shift
if name =~ SQUARE_BRACKETED
name = Expression.parse(Regexp.last_match(1))
end
name = Expression.parse(Regexp.last_match(1)) if name =~ SQUARE_BRACKETED
@name = name
@lookups = lookups
@lookups = lookups
@command_flags = 0
@lookups.each_index do |i|
@@ -34,7 +32,7 @@ module Liquid
end
def evaluate(context)
name = context.evaluate(@name)
name = context.evaluate(@name)
object = context.find_variable(name)
@lookups.each_index do |i|
@@ -43,11 +41,11 @@ module Liquid
# 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?(:[]) &&
((object.respond_to?(:key?) && object.key?(key)) ||
(object.respond_to?(:fetch) && key.is_a?(Integer)))
((object.respond_to?(:key?) && object.key?(key)) ||
(object.respond_to?(:fetch) && key.is_a?(Integer)))
# if its a proc we will replace the entry with the proc
res = context.lookup_and_evaluate(object, key)
res = context.lookup_and_evaluate(object, key)
object = res.to_liquid
# Some special cases. If the part wasn't in square brackets and
@@ -61,6 +59,7 @@ module Liquid
# raise an exception if `strict_variables` option is set to true
else
return nil unless context.strict_variables
raise Liquid::UndefinedVariable, "undefined variable #{key}"
end

View File

@@ -1,6 +1,5 @@
# encoding: utf-8
# frozen_string_literal: true
module Liquid
VERSION = "4.0.3"
VERSION = '4.0.3'
end

View File

@@ -1,32 +1,31 @@
# encoding: utf-8
# frozen_string_literal: true
lib = File.expand_path('../lib/', __FILE__)
lib = File.expand_path('lib', __dir__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require "liquid/version"
require 'liquid/version'
Gem::Specification.new do |s|
s.name = "liquid"
s.name = 'liquid'
s.version = Liquid::VERSION
s.platform = Gem::Platform::RUBY
s.summary = "A secure, non-evaling end user template engine with aesthetic markup."
s.authors = ["Tobias Lütke"]
s.email = ["tobi@leetsoft.com"]
s.homepage = "http://www.liquidmarkup.org"
s.license = "MIT"
s.summary = 'A secure, non-evaling end user template engine with aesthetic markup.'
s.authors = ['Tobias Lütke']
s.email = ['tobi@leetsoft.com']
s.homepage = 'http://www.liquidmarkup.org'
s.license = 'MIT'
# s.description = "A secure, non-evaling end user template engine with aesthetic markup."
s.required_ruby_version = ">= 2.4.0"
s.required_rubygems_version = ">= 1.3.7"
s.required_ruby_version = '>= 2.4.0'
s.required_rubygems_version = '>= 1.3.7'
s.test_files = Dir.glob("{test}/**/*")
s.files = Dir.glob("{lib}/**/*") + %w(LICENSE README.md)
s.test_files = Dir.glob('{test}/**/*')
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.require_path = 'lib'
s.add_development_dependency('rake', '~> 11.3')
s.add_development_dependency('minitest')
s.add_development_dependency('rake', '~> 11.3')
end

View File

@@ -7,14 +7,14 @@ Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
profiler = ThemeRunner.new
Benchmark.ips do |x|
x.time = 10
x.time = 10
x.warmup = 5
puts
puts "Running benchmark for #{x.time} seconds (with #{x.warmup} seconds warmup)."
puts
x.report("parse:") { profiler.compile }
x.report("render:") { profiler.render }
x.report("parse & render:") { profiler.run }
x.report('parse:') { profiler.compile }
x.report('render:') { profiler.render }
x.report('parse & render:') { profiler.run }
end

View File

@@ -2,62 +2,25 @@
require 'benchmark/ips'
require 'memory_profiler'
require 'terminal-table'
require_relative 'theme_runner'
class Profiler
LOG_LABEL = "Profiling: ".rjust(14).freeze
REPORTS_DIR = File.expand_path('.memprof', __dir__).freeze
def profile(phase, &block)
puts
puts "#{phase}:"
puts
def self.run
puts
yield new
end
report = MemoryProfiler.report(&block)
def initialize
@allocated = []
@retained = []
@headings = []
end
def profile(phase, &block)
print(LOG_LABEL)
print("#{phase}.. ".ljust(10))
report = MemoryProfiler.report(&block)
puts 'Done.'
@headings << phase.capitalize
@allocated << "#{report.scale_bytes(report.total_allocated_memsize)} (#{report.total_allocated} objects)"
@retained << "#{report.scale_bytes(report.total_retained_memsize)} (#{report.total_retained} objects)"
return if ENV['CI']
require 'fileutils'
report_file = File.join(REPORTS_DIR, "#{sanitize(phase)}.txt")
FileUtils.mkdir_p(REPORTS_DIR)
report.pretty_print(to_file: report_file, scale_bytes: true)
end
def tabulate
table = Terminal::Table.new(headings: @headings.unshift('Phase')) do |t|
t << @allocated.unshift('Total allocated')
t << @retained.unshift('Total retained')
end
puts
puts table
puts "\nDetailed report(s) saved to #{REPORTS_DIR}/" unless ENV['CI']
end
def sanitize(string)
string.downcase.gsub(/[\W]/, '-').squeeze('-')
end
report.pretty_print(
color_output: true,
scale_bytes: true,
detailed_report: true
)
end
Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
runner = ThemeRunner.new
Profiler.run do |x|
x.profile('parse') { runner.compile }
x.profile('render') { runner.render }
x.tabulate
end
profiler = ThemeRunner.new
profile('Parsing') { profiler.compile }
profile('Rendering') { profiler.render }

View File

@@ -7,7 +7,7 @@ Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
profiler = ThemeRunner.new
profiler.run
[:cpu, :object].each do |profile_type|
%i[cpu object].each do |profile_type|
puts "Profiling in #{profile_type} mode..."
results = StackProf.run(mode: profile_type) do
200.times do
@@ -22,5 +22,5 @@ profiler.run
end
StackProf::Report.new(results).print_text(false, 20)
File.write(ENV['FILENAME'] + "." + profile_type.to_s, Marshal.dump(results)) if ENV['FILENAME']
File.write(ENV['FILENAME'] + '.' + profile_type.to_s, Marshal.dump(results)) if ENV['FILENAME']
end

View File

@@ -1,14 +1,14 @@
# frozen_string_literal: true
class CommentForm < Liquid::Block
Syntax = /(#{Liquid::VariableSignature}+)/
SYNTAX = /(#{Liquid::VARIABLE_SIGNATURE}+)/.freeze
def initialize(tag_name, markup, options)
super
if markup =~ Syntax
if markup =~ SYNTAX
@variable_name = Regexp.last_match(1)
@attributes = {}
@attributes = {}
else
raise SyntaxError, "Syntax Error in 'comment_form' - Valid syntax: comment_form [article]"
end

View File

@@ -21,7 +21,9 @@ module Database
db = db.each_with_object({}) do |(key, values), assigns|
assigns[key] = values.each_with_object({}) do |v, h|
h[v['handle']] = v
h
end
assigns
end
# Some standard direct accessors so that the specialized templates

View File

@@ -4,6 +4,6 @@ require 'json'
module JsonFilter
def json(object)
JSON.dump(object.reject { |k, _v| k == "collections" })
JSON.dump(object.reject { |k, _v| k == 'collections' })
end
end

View File

@@ -3,12 +3,14 @@
module MoneyFilter
def money_with_currency(money)
return '' if money.nil?
format("$ %.2f USD", money / 100.0)
format('$ %.2f USD', money / 100.0)
end
def money(money)
return '' if money.nil?
format("$ %.2f", money / 100.0)
format('$ %.2f', money / 100.0)
end
private

View File

@@ -1,21 +1,21 @@
# frozen_string_literal: true
class Paginate < Liquid::Block
Syntax = /(#{Liquid::QuotedFragment})\s*(by\s*(\d+))?/
SYNTAX = /(#{Liquid::QUOTED_FRAGMENT})\s*(by\s*(\d+))?/.freeze
def initialize(tag_name, markup, options)
super
if markup =~ Syntax
if markup =~ SYNTAX
@collection_name = Regexp.last_match(1)
@page_size = if Regexp.last_match(2)
@page_size = if Regexp.last_match(2)
Regexp.last_match(3).to_i
else
20
end
@attributes = { 'window_size' => 3 }
markup.scan(Liquid::TagAttributes) do |key, value|
markup.scan(Liquid::TAG_ATTRIBUTES) do |key, value|
@attributes[key] = value
end
else
@@ -45,7 +45,7 @@ class Paginate < Liquid::Block
pagination['items'] = collection_size
pagination['pages'] = page_count - 1
pagination['previous'] = link('&laquo; Previous', current_page - 1) unless 1 >= current_page
pagination['previous'] = link('&laquo; Previous', current_page - 1) unless current_page <= 1
pagination['next'] = link('Next &raquo;', current_page + 1) unless page_count <= current_page + 1
pagination['parts'] = []
@@ -61,6 +61,7 @@ class Paginate < Liquid::Block
pagination['parts'] << link(page, page)
elsif page <= current_page - @attributes['window_size'] || page >= current_page + @attributes['window_size']
next if hellip_break
pagination['parts'] << no_link('&hellip;')
hellip_break = true
next
@@ -87,6 +88,6 @@ class Paginate < Liquid::Block
end
def current_url
"/collections/frontpage"
'/collections/frontpage'
end
end

View File

@@ -17,21 +17,21 @@ module ShopFilter
%(<script src="#{url}" type="text/javascript"></script>)
end
def stylesheet_tag(url, media = "all")
def stylesheet_tag(url, media = 'all')
%(<link href="#{url}" rel="stylesheet" type="text/css" media="#{media}" />)
end
def link_to(link, url, title = "")
def link_to(link, url, title = '')
%(<a href="#{url}" title="#{title}">#{link}</a>)
end
def img_tag(url, alt = "")
def img_tag(url, alt = '')
%(<img src="#{url}" alt="#{alt}" />)
end
def link_to_vendor(vendor)
if vendor
link_to(vendor, url_for_vendor(vendor), vendor)
link_to vendor, url_for_vendor(vendor), vendor
else
'Unknown Vendor'
end
@@ -39,7 +39,7 @@ module ShopFilter
def link_to_type(type)
if type
link_to(type, url_for_type(type), type)
link_to type, url_for_type(type), type
else
'Unknown Vendor'
end
@@ -54,9 +54,7 @@ module ShopFilter
end
def product_img_url(url, style = 'small')
unless url =~ %r{\Aproducts/([\w\-\_]+)\.(\w{2,4})}
raise ArgumentError, 'filter "size" can only be called on product images'
end
raise ArgumentError, 'filter "size" can only be called on product images' unless url =~ %r{\Aproducts/([\w\-\_]+)\.(\w{2,4})}
case style
when 'original'

View File

@@ -2,7 +2,7 @@
module WeightFilter
def weight(grams)
format("%.2f", grams / 1000)
format('%.2f', grams / 1000)
end
def weight_with_unit(grams)

View File

@@ -58,9 +58,9 @@ class ThemeRunner
# `render` is called to benchmark just the render portion of liquid
def render
@compiled_tests.each do |test|
tmpl = test[:tmpl]
tmpl = test[:tmpl]
assigns = test[:assigns]
layout = test[:layout]
layout = test[:layout]
if layout
assigns['content_for_layout'] = tmpl.render!(assigns)
@@ -74,7 +74,7 @@ class ThemeRunner
private
def compile_and_render(template, layout, assigns, page_template, template_file)
compiled_test = compile_test(template, layout, assigns, page_template, template_file)
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
@@ -88,7 +88,7 @@ class ThemeRunner
end
def compile_test(template, layout, assigns, page_template, template_file)
tmpl = init_template(page_template, template_file)
tmpl = init_template(page_template, template_file)
parsed_template = tmpl.parse(template).dup
if layout
@@ -113,9 +113,9 @@ class ThemeRunner
# set up a new Liquid::Template object for use in `compile_and_render` and `compile_test`
def init_template(page_template, template_file)
tmpl = Liquid::Template.new
tmpl.assigns['page_title'] = 'Page title'
tmpl.assigns['template'] = page_template
tmpl = Liquid::Template.new
tmpl.assigns['page_title'] = 'Page title'
tmpl.assigns['template'] = page_template
tmpl.registers[:file_system] = ThemeRunner::FileSystem.new(File.dirname(template_file))
tmpl
end

View File

@@ -10,31 +10,31 @@ class AssignTest < Minitest::Test
{% assign this-thing = 'Print this-thing' %}
{{ this-thing }}
END_TEMPLATE
template = Template.parse(template_source)
rendered = template.render!
assert_equal("Print this-thing", rendered.strip)
template = Template.parse(template_source)
rendered = template.render!
assert_equal 'Print this-thing', rendered.strip
end
def test_assigned_variable
assert_template_result('.foo.',
'{% assign foo = values %}.{{ foo[0] }}.',
'values' => %w(foo bar baz))
'{% assign foo = values %}.{{ foo[0] }}.',
'values' => %w[foo bar baz])
assert_template_result('.bar.',
'{% assign foo = values %}.{{ foo[1] }}.',
'values' => %w(foo bar baz))
'{% assign foo = values %}.{{ foo[1] }}.',
'values' => %w[foo bar baz])
end
def test_assign_with_filter
assert_template_result('.bar.',
'{% assign foo = values | split: "," %}.{{ foo[1] }}.',
'values' => "foo,bar,baz")
'{% assign foo = values | split: "," %}.{{ foo[1] }}.',
'values' => 'foo,bar,baz')
end
def test_assign_syntax_error
assert_match_syntax_error(/assign/,
'{% assign foo not values %}.',
'values' => "foo,bar,baz")
'{% assign foo not values %}.',
'values' => 'foo,bar,baz')
end
def test_assign_uses_error_mode

View File

@@ -33,77 +33,77 @@ class BlankTest < Minitest::Test
def test_new_tags_are_not_blank_by_default
with_custom_tag('foobar', FoobarTag) do
assert_template_result(" " * N, wrap_in_for("{% foobar %}"))
assert_template_result(' ' * N, wrap_in_for('{% foobar %}'))
end
end
def test_loops_are_blank
assert_template_result("", wrap_in_for(" "))
assert_template_result('', wrap_in_for(' '))
end
def test_if_else_are_blank
assert_template_result("", "{% if true %} {% elsif false %} {% else %} {% endif %}")
assert_template_result('', '{% if true %} {% elsif false %} {% else %} {% endif %}')
end
def test_unless_is_blank
assert_template_result("", wrap("{% unless true %} {% endunless %}"))
assert_template_result('', wrap('{% unless true %} {% endunless %}'))
end
def test_mark_as_blank_only_during_parsing
assert_template_result(" " * (N + 1), wrap(" {% if false %} this never happens, but still, this block is not blank {% endif %}"))
assert_template_result(' ' * (N + 1), wrap(' {% if false %} this never happens, but still, this block is not blank {% endif %}'))
end
def test_comments_are_blank
assert_template_result("", wrap(" {% comment %} whatever {% endcomment %} "))
assert_template_result('', wrap(' {% comment %} whatever {% endcomment %} '))
end
def test_captures_are_blank
assert_template_result("", wrap(" {% capture foo %} whatever {% endcapture %} "))
assert_template_result('', wrap(' {% capture foo %} whatever {% endcapture %} '))
end
def test_nested_blocks_are_blank_but_only_if_all_children_are
assert_template_result("", wrap(wrap(" ")))
assert_template_result('', wrap(wrap(' ')))
assert_template_result("\n but this is not " * (N + 1),
wrap('{% if true %} {% comment %} this is blank {% endcomment %} {% endif %}
wrap('{% if true %} {% comment %} this is blank {% endcomment %} {% endif %}
{% if true %} but this is not {% endif %}'))
end
def test_assigns_are_blank
assert_template_result("", wrap(' {% assign foo = "bar" %} '))
assert_template_result('', wrap(' {% assign foo = "bar" %} '))
end
def test_whitespace_is_blank
assert_template_result("", wrap(" "))
assert_template_result("", wrap("\t"))
assert_template_result('', wrap(' '))
assert_template_result('', wrap("\t"))
end
def test_whitespace_is_not_blank_if_other_stuff_is_present
body = " x "
body = ' x '
assert_template_result(body * (N + 1), wrap(body))
end
def test_increment_is_not_blank
assert_template_result(" 0" * 2 * (N + 1), wrap("{% assign foo = 0 %} {% increment foo %} {% decrement foo %}"))
assert_template_result(' 0' * 2 * (N + 1), wrap('{% assign foo = 0 %} {% increment foo %} {% decrement foo %}'))
end
def test_cycle_is_not_blank
assert_template_result(" " * ((N + 1) / 2) + " ", wrap("{% cycle ' ', ' ' %}"))
assert_template_result(' ' * ((N + 1) / 2) + ' ', wrap("{% cycle ' ', ' ' %}"))
end
def test_raw_is_not_blank
assert_template_result(" " * (N + 1), wrap(" {% raw %} {% endraw %}"))
assert_template_result(' ' * (N + 1), wrap(' {% raw %} {% endraw %}'))
end
def test_include_is_blank
Liquid::Template.file_system = BlankTestFileSystem.new
assert_template_result("foobar" * (N + 1), wrap("{% include 'foobar' %}"))
assert_template_result(" foobar " * (N + 1), wrap("{% include ' foobar ' %}"))
assert_template_result(" " * (N + 1), wrap(" {% include ' ' %} "))
assert_template_result 'foobar' * (N + 1), wrap("{% include 'foobar' %}")
assert_template_result ' foobar ' * (N + 1), wrap("{% include ' foobar ' %}")
assert_template_result ' ' * (N + 1), wrap(" {% include ' ' %} ")
end
def test_case_is_blank
assert_template_result("", wrap(" {% assign foo = 'bar' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} {% endcase %} "))
assert_template_result("", wrap(" {% assign foo = 'else' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} {% endcase %} "))
assert_template_result(" x " * (N + 1), wrap(" {% assign foo = 'else' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} x {% endcase %} "))
assert_template_result('', wrap(" {% assign foo = 'bar' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} {% endcase %} "))
assert_template_result('', wrap(" {% assign foo = 'else' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} {% endcase %} "))
assert_template_result(' x ' * (N + 1), wrap(" {% assign foo = 'else' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} x {% endcase %} "))
end
end

View File

@@ -7,8 +7,8 @@ class BlockTest < Minitest::Test
def test_unexpected_end_tag
exc = assert_raises(SyntaxError) do
Template.parse("{% if true %}{% endunless %}")
Template.parse('{% if true %}{% endunless %}')
end
assert_equal(exc.message, "Liquid syntax error: 'endunless' is not a valid delimiter for if tags. use endif")
assert_equal exc.message, "Liquid syntax error: 'endunless' is not a valid delimiter for if tags. use endif"
end
end

View File

@@ -6,7 +6,7 @@ class CaptureTest < Minitest::Test
include Liquid
def test_captures_block_content_in_variable
assert_template_result("test string", "{% capture 'var' %}test string{% endcapture %}{{var}}", {})
assert_template_result('test string', "{% capture 'var' %}test string{% endcapture %}{{var}}", {})
end
def test_capture_with_hyphen_in_variable_name
@@ -14,9 +14,9 @@ class CaptureTest < Minitest::Test
{% capture this-thing %}Print this-thing{% endcapture %}
{{ this-thing }}
END_TEMPLATE
template = Template.parse(template_source)
rendered = template.render!
assert_equal("Print this-thing", rendered.strip)
template = Template.parse(template_source)
rendered = template.render!
assert_equal 'Print this-thing', rendered.strip
end
def test_capture_to_variable_from_outer_scope_if_existing
@@ -30,9 +30,9 @@ class CaptureTest < Minitest::Test
{% endif %}
{{var}}
END_TEMPLATE
template = Template.parse(template_source)
rendered = template.render!
assert_equal("test-string", rendered.gsub(/\s/, ''))
template = Template.parse(template_source)
rendered = template.render!
assert_equal 'test-string', rendered.gsub(/\s/, '')
end
def test_assigning_from_capture
@@ -45,8 +45,8 @@ class CaptureTest < Minitest::Test
{% endfor %}
{{ first }}-{{ second }}
END_TEMPLATE
template = Template.parse(template_source)
rendered = template.render!
assert_equal("3-3", rendered.gsub(/\s/, ''))
template = Template.parse(template_source)
rendered = template.render!
assert_equal '3-3', rendered.gsub(/\s/, '')
end
end # CaptureTest

View File

@@ -7,15 +7,15 @@ class DocumentTest < Minitest::Test
def test_unexpected_outer_tag
exc = assert_raises(SyntaxError) do
Template.parse("{% else %}")
Template.parse('{% else %}')
end
assert_equal(exc.message, "Liquid syntax error: Unexpected outer 'else' tag")
assert_equal exc.message, "Liquid syntax error: Unexpected outer 'else' tag"
end
def test_unknown_tag
exc = assert_raises(SyntaxError) do
Template.parse("{% foo %}")
Template.parse('{% foo %}')
end
assert_equal(exc.message, "Liquid syntax error: Unknown tag 'foo'")
assert_equal exc.message, "Liquid syntax error: Unknown tag 'foo'"
end
end

View File

@@ -23,7 +23,7 @@ end
class ProductDrop < Liquid::Drop
class TextDrop < Liquid::Drop
def array
['text1', 'text2']
%w[text1 text2]
end
def text
@@ -33,7 +33,7 @@ class ProductDrop < Liquid::Drop
class CatchallDrop < Liquid::Drop
def liquid_method_missing(method)
"catchall_method: #{method}"
+'catchall_method: ' << method.to_s
end
end
@@ -50,13 +50,13 @@ class ProductDrop < Liquid::Drop
end
def user_input
(+"foo").taint
(+'foo').taint
end
protected
def callmenot
"protected"
'protected'
end
end
@@ -111,7 +111,7 @@ class DropsTest < Minitest::Test
def test_product_drop
tpl = Liquid::Template.parse(' ')
assert_equal(' ', tpl.render!('product' => ProductDrop.new))
assert_equal ' ', tpl.render!('product' => ProductDrop.new)
end
def test_rendering_raises_on_tainted_attr
@@ -125,7 +125,7 @@ class DropsTest < Minitest::Test
def test_rendering_warns_on_tainted_attr
with_taint_mode(:warn) do
tpl = Liquid::Template.parse('{{ product.user_input }}')
tpl = Liquid::Template.parse('{{ product.user_input }}')
context = Context.new('product' => ProductDrop.new)
tpl.render!(context)
assert_equal [Liquid::TaintedError], context.warnings.map(&:class)
@@ -141,105 +141,100 @@ class DropsTest < Minitest::Test
end
def test_drop_does_only_respond_to_whitelisted_methods
assert_equal("", Liquid::Template.parse("{{ product.inspect }}").render!('product' => ProductDrop.new))
assert_equal("", Liquid::Template.parse("{{ product.pretty_inspect }}").render!('product' => ProductDrop.new))
assert_equal("", Liquid::Template.parse("{{ product.whatever }}").render!('product' => ProductDrop.new))
assert_equal("", Liquid::Template.parse('{{ product | map: "inspect" }}').render!('product' => ProductDrop.new))
assert_equal("", Liquid::Template.parse('{{ product | map: "pretty_inspect" }}').render!('product' => ProductDrop.new))
assert_equal("", Liquid::Template.parse('{{ product | map: "whatever" }}').render!('product' => ProductDrop.new))
assert_equal '', Liquid::Template.parse('{{ product.inspect }}').render!('product' => ProductDrop.new)
assert_equal '', Liquid::Template.parse('{{ product.pretty_inspect }}').render!('product' => ProductDrop.new)
assert_equal '', Liquid::Template.parse('{{ product.whatever }}').render!('product' => ProductDrop.new)
assert_equal '', Liquid::Template.parse('{{ product | map: "inspect" }}').render!('product' => ProductDrop.new)
assert_equal '', Liquid::Template.parse('{{ product | map: "pretty_inspect" }}').render!('product' => ProductDrop.new)
assert_equal '', Liquid::Template.parse('{{ product | map: "whatever" }}').render!('product' => ProductDrop.new)
end
def test_drops_respond_to_to_liquid
assert_equal("text1", Liquid::Template.parse("{{ product.to_liquid.texts.text }}").render!('product' => ProductDrop.new))
assert_equal("text1", Liquid::Template.parse('{{ product | map: "to_liquid" | map: "texts" | map: "text" }}').render!('product' => ProductDrop.new))
assert_equal 'text1', Liquid::Template.parse('{{ product.to_liquid.texts.text }}').render!('product' => ProductDrop.new)
assert_equal 'text1', Liquid::Template.parse('{{ product | map: "to_liquid" | map: "texts" | map: "text" }}').render!('product' => ProductDrop.new)
end
def test_text_drop
output = Liquid::Template.parse(' {{ product.texts.text }} ').render!('product' => ProductDrop.new)
assert_equal(' text1 ', output)
assert_equal ' text1 ', output
end
def test_catchall_unknown_method
output = Liquid::Template.parse(' {{ product.catchall.unknown }} ').render!('product' => ProductDrop.new)
assert_equal(' catchall_method: unknown ', output)
assert_equal ' catchall_method: unknown ', output
end
def test_catchall_integer_argument_drop
output = Liquid::Template.parse(' {{ product.catchall[8] }} ').render!('product' => ProductDrop.new)
assert_equal(' catchall_method: 8 ', output)
assert_equal ' catchall_method: 8 ', output
end
def test_text_array_drop
output = Liquid::Template.parse('{% for text in product.texts.array %} {{text}} {% endfor %}').render!('product' => ProductDrop.new)
assert_equal(' text1 text2 ', output)
assert_equal ' text1 text2 ', output
end
def test_context_drop
output = Liquid::Template.parse(' {{ context.bar }} ').render!('context' => ContextDrop.new, 'bar' => "carrot")
assert_equal(' carrot ', output)
end
def test_context_drop_array_with_map
output = Liquid::Template.parse(' {{ contexts | map: "bar" }} ').render!('contexts' => [ContextDrop.new, ContextDrop.new], 'bar' => "carrot")
assert_equal(' carrotcarrot ', output)
output = Liquid::Template.parse(' {{ context.bar }} ').render!('context' => ContextDrop.new, 'bar' => 'carrot')
assert_equal ' carrot ', output
end
def test_nested_context_drop
output = Liquid::Template.parse(' {{ product.context.foo }} ').render!('product' => ProductDrop.new, 'foo' => "monkey")
assert_equal(' monkey ', output)
output = Liquid::Template.parse(' {{ product.context.foo }} ').render!('product' => ProductDrop.new, 'foo' => 'monkey')
assert_equal ' monkey ', output
end
def test_protected
output = Liquid::Template.parse(' {{ product.callmenot }} ').render!('product' => ProductDrop.new)
assert_equal(' ', output)
assert_equal ' ', output
end
def test_object_methods_not_allowed
[:dup, :clone, :singleton_class, :eval, :class_eval, :inspect].each do |method|
%i[dup clone singleton_class eval class_eval inspect].each do |method|
output = Liquid::Template.parse(" {{ product.#{method} }} ").render!('product' => ProductDrop.new)
assert_equal ' ', output
end
end
def test_scope
assert_equal('1', Liquid::Template.parse('{{ context.scopes }}').render!('context' => ContextDrop.new))
assert_equal('2', Liquid::Template.parse('{%for i in dummy%}{{ context.scopes }}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1]))
assert_equal('3', Liquid::Template.parse('{%for i in dummy%}{%for i in dummy%}{{ context.scopes }}{%endfor%}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1]))
assert_equal '1', Liquid::Template.parse('{{ context.scopes }}').render!('context' => ContextDrop.new)
assert_equal '2', Liquid::Template.parse('{%for i in dummy%}{{ context.scopes }}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1])
assert_equal '3', Liquid::Template.parse('{%for i in dummy%}{%for i in dummy%}{{ context.scopes }}{%endfor%}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1])
end
def test_scope_though_proc
assert_equal('1', Liquid::Template.parse('{{ s }}').render!('context' => ContextDrop.new, 's' => proc { |c| c['context.scopes'] }))
assert_equal('2', Liquid::Template.parse('{%for i in dummy%}{{ s }}{%endfor%}').render!('context' => ContextDrop.new, 's' => proc { |c| c['context.scopes'] }, 'dummy' => [1]))
assert_equal('3', Liquid::Template.parse('{%for i in dummy%}{%for i in dummy%}{{ s }}{%endfor%}{%endfor%}').render!('context' => ContextDrop.new, 's' => proc { |c| c['context.scopes'] }, 'dummy' => [1]))
assert_equal '1', Liquid::Template.parse('{{ s }}').render!('context' => ContextDrop.new, 's' => proc { |c| c['context.scopes'] })
assert_equal '2', Liquid::Template.parse('{%for i in dummy%}{{ s }}{%endfor%}').render!('context' => ContextDrop.new, 's' => proc { |c| c['context.scopes'] }, 'dummy' => [1])
assert_equal '3', Liquid::Template.parse('{%for i in dummy%}{%for i in dummy%}{{ s }}{%endfor%}{%endfor%}').render!('context' => ContextDrop.new, 's' => proc { |c| c['context.scopes'] }, 'dummy' => [1])
end
def test_scope_with_assigns
assert_equal('variable', Liquid::Template.parse('{% assign a = "variable"%}{{a}}').render!('context' => ContextDrop.new))
assert_equal('variable', Liquid::Template.parse('{% assign a = "variable"%}{%for i in dummy%}{{a}}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1]))
assert_equal('test', Liquid::Template.parse('{% assign header_gif = "test"%}{{header_gif}}').render!('context' => ContextDrop.new))
assert_equal('test', Liquid::Template.parse("{% assign header_gif = 'test'%}{{header_gif}}").render!('context' => ContextDrop.new))
assert_equal 'variable', Liquid::Template.parse('{% assign a = "variable"%}{{a}}').render!('context' => ContextDrop.new)
assert_equal 'variable', Liquid::Template.parse('{% assign a = "variable"%}{%for i in dummy%}{{a}}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1])
assert_equal 'test', Liquid::Template.parse('{% assign header_gif = "test"%}{{header_gif}}').render!('context' => ContextDrop.new)
assert_equal 'test', Liquid::Template.parse("{% assign header_gif = 'test'%}{{header_gif}}").render!('context' => ContextDrop.new)
end
def test_scope_from_tags
assert_equal('1', Liquid::Template.parse('{% for i in context.scopes_as_array %}{{i}}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1]))
assert_equal('12', Liquid::Template.parse('{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1]))
assert_equal('123', Liquid::Template.parse('{%for a in dummy%}{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1]))
assert_equal '1', Liquid::Template.parse('{% for i in context.scopes_as_array %}{{i}}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1])
assert_equal '12', Liquid::Template.parse('{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1])
assert_equal '123', Liquid::Template.parse('{%for a in dummy%}{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1])
end
def test_access_context_from_drop
assert_equal('123', Liquid::Template.parse('{%for a in dummy%}{{ context.loop_pos }}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1, 2, 3]))
assert_equal '123', Liquid::Template.parse('{%for a in dummy%}{{ context.loop_pos }}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1, 2, 3])
end
def test_enumerable_drop
assert_equal('123', Liquid::Template.parse('{% for c in collection %}{{c}}{% endfor %}').render!('collection' => EnumerableDrop.new))
assert_equal '123', Liquid::Template.parse('{% for c in collection %}{{c}}{% endfor %}').render!('collection' => EnumerableDrop.new)
end
def test_enumerable_drop_size
assert_equal('3', Liquid::Template.parse('{{collection.size}}').render!('collection' => EnumerableDrop.new))
assert_equal '3', Liquid::Template.parse('{{collection.size}}').render!('collection' => EnumerableDrop.new)
end
def test_enumerable_drop_will_invoke_liquid_method_missing_for_clashing_method_names
["select", "each", "map", "cycle"].each do |method|
%w[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)
@@ -248,40 +243,33 @@ class DropsTest < Minitest::Test
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)
%i[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)
end
assert_equal("yes", Liquid::Template.parse("{% if collection contains 3 %}yes{% endif %}").render!('collection' => RealEnumerableDrop.new))
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)
%i[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)
end
end
def test_empty_string_value_access
assert_equal('', Liquid::Template.parse('{{ product[value] }}').render!('product' => ProductDrop.new, 'value' => ''))
assert_equal '', Liquid::Template.parse('{{ product[value] }}').render!('product' => ProductDrop.new, 'value' => '')
end
def test_nil_value_access
assert_equal('', Liquid::Template.parse('{{ product[value] }}').render!('product' => ProductDrop.new, 'value' => nil))
assert_equal '', Liquid::Template.parse('{{ product[value] }}').render!('product' => ProductDrop.new, 'value' => nil)
end
def test_default_to_s_on_drops
assert_equal('ProductDrop', Liquid::Template.parse("{{ product }}").render!('product' => ProductDrop.new))
assert_equal('EnumerableDrop', Liquid::Template.parse('{{ collection }}').render!('collection' => EnumerableDrop.new))
end
def test_invokable_methods
assert_equal(%w(to_liquid catchall user_input context texts).to_set, ProductDrop.invokable_methods)
assert_equal(%w(to_liquid scopes_as_array loop_pos scopes).to_set, ContextDrop.invokable_methods)
assert_equal(%w(to_liquid size max min first count).to_set, EnumerableDrop.invokable_methods)
assert_equal(%w(to_liquid max min sort count first).to_set, RealEnumerableDrop.invokable_methods)
assert_equal 'ProductDrop', Liquid::Template.parse('{{ product }}').render!('product' => ProductDrop.new)
assert_equal 'EnumerableDrop', Liquid::Template.parse('{{ collection }}').render!('collection' => EnumerableDrop.new)
end
end # DropsTest

View File

@@ -35,31 +35,31 @@ class ErrorHandlingTest < Minitest::Test
TEXT
output = Liquid::Template.parse(template, line_numbers: true).render('errors' => ErrorDrop.new)
assert_equal(expected, output)
assert_equal expected, output
end
def test_standard_error
template = Liquid::Template.parse(' {{ errors.standard_error }} ')
assert_equal(' Liquid error: standard error ', template.render('errors' => ErrorDrop.new))
assert_equal ' Liquid error: standard error ', template.render('errors' => ErrorDrop.new)
assert_equal(1, template.errors.size)
assert_equal(StandardError, template.errors.first.class)
assert_equal 1, template.errors.size
assert_equal StandardError, template.errors.first.class
end
def test_syntax
template = Liquid::Template.parse(' {{ errors.syntax_error }} ')
assert_equal(' Liquid syntax error: syntax error ', template.render('errors' => ErrorDrop.new))
assert_equal ' Liquid syntax error: syntax error ', template.render('errors' => ErrorDrop.new)
assert_equal(1, template.errors.size)
assert_equal(SyntaxError, template.errors.first.class)
assert_equal 1, template.errors.size
assert_equal SyntaxError, template.errors.first.class
end
def test_argument
template = Liquid::Template.parse(' {{ errors.argument_error }} ')
assert_equal(' Liquid error: argument error ', template.render('errors' => ErrorDrop.new))
assert_equal ' Liquid error: argument error ', template.render('errors' => ErrorDrop.new)
assert_equal(1, template.errors.size)
assert_equal(ArgumentError, template.errors.first.class)
assert_equal 1, template.errors.size
assert_equal ArgumentError, template.errors.first.class
end
def test_missing_endtag_parse_time_error
@@ -78,9 +78,9 @@ class ErrorHandlingTest < Minitest::Test
def test_lax_unrecognized_operator
template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', error_mode: :lax)
assert_equal(' Liquid error: Unknown operator =! ', template.render)
assert_equal(1, template.errors.size)
assert_equal(Liquid::ArgumentError, template.errors.first.class)
assert_equal ' Liquid error: Unknown operator =! ', template.render
assert_equal 1, template.errors.size
assert_equal Liquid::ArgumentError, template.errors.first.class
end
def test_with_line_numbers_adds_numbers_to_parser_errors
@@ -92,7 +92,7 @@ class ErrorHandlingTest < Minitest::Test
bla
',
line_numbers: true)
line_numbers: true)
end
assert_match(/Liquid syntax error \(line 4\)/, err.message)
@@ -107,7 +107,7 @@ class ErrorHandlingTest < Minitest::Test
bla
',
line_numbers: true)
line_numbers: true)
end
assert_match(/Liquid syntax error \(line 4\)/, err.message)
@@ -121,11 +121,11 @@ class ErrorHandlingTest < Minitest::Test
bla
',
error_mode: :warn,
line_numbers: true)
error_mode: :warn,
line_numbers: true)
assert_equal(['Liquid syntax error (line 4): Unexpected character = in "1 =! 2"'],
template.warnings.map(&:message))
assert_equal ['Liquid syntax error (line 4): Unexpected character = in "1 =! 2"'],
template.warnings.map(&:message)
end
def test_parsing_strict_with_line_numbers_adds_numbers_to_lexer_errors
@@ -137,11 +137,11 @@ class ErrorHandlingTest < Minitest::Test
bla
',
error_mode: :strict,
line_numbers: true)
error_mode: :strict,
line_numbers: true)
end
assert_equal('Liquid syntax error (line 4): Unexpected character = in "1 =! 2"', err.message)
assert_equal 'Liquid syntax error (line 4): Unexpected character = in "1 =! 2"', err.message
end
def test_syntax_errors_in_nested_blocks_have_correct_line_number
@@ -155,40 +155,40 @@ class ErrorHandlingTest < Minitest::Test
bla
',
line_numbers: true)
line_numbers: true)
end
assert_equal("Liquid syntax error (line 5): Unknown tag 'foo'", err.message)
assert_equal "Liquid syntax error (line 5): Unknown tag 'foo'", err.message
end
def test_strict_error_messages
err = assert_raises(SyntaxError) do
Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', error_mode: :strict)
end
assert_equal('Liquid syntax error: Unexpected character = in "1 =! 2"', err.message)
assert_equal 'Liquid syntax error: Unexpected character = in "1 =! 2"', err.message
err = assert_raises(SyntaxError) do
Liquid::Template.parse('{{%%%}}', error_mode: :strict)
end
assert_equal('Liquid syntax error: Unexpected character % in "{{%%%}}"', err.message)
assert_equal 'Liquid syntax error: Unexpected character % in "{{%%%}}"', err.message
end
def test_warnings
template = Liquid::Template.parse('{% if ~~~ %}{{%%%}}{% else %}{{ hello. }}{% endif %}', error_mode: :warn)
assert_equal(3, template.warnings.size)
assert_equal('Unexpected character ~ in "~~~"', template.warnings[0].to_s(false))
assert_equal('Unexpected character % in "{{%%%}}"', template.warnings[1].to_s(false))
assert_equal('Expected id but found end_of_string in "{{ hello. }}"', template.warnings[2].to_s(false))
assert_equal('', template.render)
assert_equal 3, template.warnings.size
assert_equal 'Unexpected character ~ in "~~~"', template.warnings[0].to_s(false)
assert_equal 'Unexpected character % in "{{%%%}}"', template.warnings[1].to_s(false)
assert_equal 'Expected id but found end_of_string in "{{ hello. }}"', template.warnings[2].to_s(false)
assert_equal '', template.render
end
def test_warning_line_numbers
template = Liquid::Template.parse("{% if ~~~ %}\n{{%%%}}{% else %}\n{{ hello. }}{% endif %}", error_mode: :warn, line_numbers: true)
assert_equal('Liquid syntax error (line 1): Unexpected character ~ in "~~~"', template.warnings[0].message)
assert_equal('Liquid syntax error (line 2): Unexpected character % in "{{%%%}}"', template.warnings[1].message)
assert_equal('Liquid syntax error (line 3): Expected id but found end_of_string in "{{ hello. }}"', template.warnings[2].message)
assert_equal(3, template.warnings.size)
assert_equal([1, 2, 3], template.warnings.map(&:line_number))
assert_equal 'Liquid syntax error (line 1): Unexpected character ~ in "~~~"', template.warnings[0].message
assert_equal 'Liquid syntax error (line 2): Unexpected character % in "{{%%%}}"', template.warnings[1].message
assert_equal 'Liquid syntax error (line 3): Expected id but found end_of_string in "{{ hello. }}"', template.warnings[2].message
assert_equal 3, template.warnings.size
assert_equal [1, 2, 3], template.warnings.map(&:line_number)
end
# Liquid should not catch Exceptions that are not subclasses of StandardError, like Interrupt and NoMemoryError
@@ -204,14 +204,14 @@ class ErrorHandlingTest < Minitest::Test
output = template.render('errors' => ErrorDrop.new)
assert_equal('This is a runtime error: Liquid error (line 1): internal', output)
assert_equal([Liquid::InternalError], template.errors.map(&:class))
assert_equal 'This is a runtime error: Liquid error (line 1): internal', output
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) {
Liquid::Template.default_exception_renderer = lambda { |e|
exceptions << e
''
}
@@ -219,31 +219,31 @@ class ErrorHandlingTest < Minitest::Test
output = template.render('errors' => ErrorDrop.new)
assert_equal('This is a runtime error: ', output)
assert_equal([Liquid::ArgumentError], template.errors.map(&:class))
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)
template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true)
exceptions = []
handler = ->(e) {
handler = lambda { |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)
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
def read_template_file(_template_path)
"{{ errors.argument_error }}"
'{{ errors.argument_error }}'
end
end
@@ -252,13 +252,12 @@ class ErrorHandlingTest < Minitest::Test
begin
Liquid::Template.file_system = TestFileSystem.new
template = Liquid::Template.parse("Argument error:\n{% include 'product' %}", line_numbers: true)
page = template.render('errors' => ErrorDrop.new)
page = template.render('errors' => ErrorDrop.new)
ensure
Liquid::Template.file_system = old_file_system
end
assert_equal("Argument error:\nLiquid error (product line 1): argument error", page)
assert_equal("product", template.errors.first.template_name)
assert_equal "Argument error:\nLiquid error (product line 1): argument error", page
assert_equal 'product', template.errors.first.template_name
end
end

View File

@@ -29,7 +29,7 @@ class FiltersTest < Minitest::Test
module OverrideObjectMethodFilter
def tap(_input)
"tap overridden"
'tap overridden'
end
end
@@ -41,13 +41,13 @@ class FiltersTest < Minitest::Test
@context['var'] = 1000
@context.add_filters(MoneyFilter)
assert_equal(' 1000$ ', Template.parse("{{var | money}}").render(@context))
assert_equal ' 1000$ ', Template.parse('{{var | money}}').render(@context)
end
def test_underscore_in_filter_name
@context['var'] = 1000
@context.add_filters(MoneyFilter)
assert_equal(' 1000$ ', Template.parse("{{var | money_with_underscore}}").render(@context))
assert_equal ' 1000$ ', Template.parse('{{var | money_with_underscore}}').render(@context)
end
def test_second_filter_overwrites_first
@@ -55,103 +55,103 @@ class FiltersTest < Minitest::Test
@context.add_filters(MoneyFilter)
@context.add_filters(CanadianMoneyFilter)
assert_equal(' 1000$ CAD ', Template.parse("{{var | money}}").render(@context))
assert_equal ' 1000$ CAD ', Template.parse('{{var | money}}').render(@context)
end
def test_size
@context['var'] = 'abcd'
@context.add_filters(MoneyFilter)
assert_equal('4', Template.parse("{{var | size}}").render(@context))
assert_equal '4', Template.parse('{{var | size}}').render(@context)
end
def test_join
@context['var'] = [1, 2, 3, 4]
assert_equal("1 2 3 4", Template.parse("{{var | join}}").render(@context))
assert_equal '1 2 3 4', Template.parse('{{var | join}}').render(@context)
end
def test_sort
@context['value'] = 3
@context['value'] = 3
@context['numbers'] = [2, 1, 4, 3]
@context['words'] = ['expected', 'as', 'alphabetic']
@context['arrays'] = ['flower', 'are']
@context['case_sensitive'] = ['sensitive', 'Expected', 'case']
@context['words'] = %w[expected as alphabetic]
@context['arrays'] = %w[flower are]
@context['case_sensitive'] = %w[sensitive Expected case]
assert_equal('1 2 3 4', Template.parse("{{numbers | sort | join}}").render(@context))
assert_equal('alphabetic as expected', Template.parse("{{words | sort | join}}").render(@context))
assert_equal('3', Template.parse("{{value | sort}}").render(@context))
assert_equal('are flower', Template.parse("{{arrays | sort | join}}").render(@context))
assert_equal('Expected case sensitive', Template.parse("{{case_sensitive | sort | join}}").render(@context))
assert_equal '1 2 3 4', Template.parse('{{numbers | sort | join}}').render(@context)
assert_equal 'alphabetic as expected', Template.parse('{{words | sort | join}}').render(@context)
assert_equal '3', Template.parse('{{value | sort}}').render(@context)
assert_equal 'are flower', Template.parse('{{arrays | sort | join}}').render(@context)
assert_equal 'Expected case sensitive', Template.parse('{{case_sensitive | sort | join}}').render(@context)
end
def test_sort_natural
@context['words'] = ['case', 'Assert', 'Insensitive']
@context['hashes'] = [{ 'a' => 'A' }, { 'a' => 'b' }, { 'a' => 'C' }]
@context['words'] = %w[case Assert Insensitive]
@context['hashes'] = [{ 'a' => 'A' }, { 'a' => 'b' }, { 'a' => 'C' }]
@context['objects'] = [TestObject.new('A'), TestObject.new('b'), TestObject.new('C')]
# Test strings
assert_equal('Assert case Insensitive', Template.parse("{{words | sort_natural | join}}").render(@context))
assert_equal 'Assert case Insensitive', Template.parse('{{words | sort_natural | join}}').render(@context)
# Test hashes
assert_equal('A b C', Template.parse("{{hashes | sort_natural: 'a' | map: 'a' | join}}").render(@context))
assert_equal 'A b C', Template.parse("{{hashes | sort_natural: 'a' | map: 'a' | join}}").render(@context)
# Test objects
assert_equal('A b C', Template.parse("{{objects | sort_natural: 'a' | map: 'a' | join}}").render(@context))
assert_equal 'A b C', Template.parse("{{objects | sort_natural: 'a' | map: 'a' | join}}").render(@context)
end
def test_compact
@context['words'] = ['a', nil, 'b', nil, 'c']
@context['hashes'] = [{ 'a' => 'A' }, { 'a' => nil }, { 'a' => 'C' }]
@context['words'] = ['a', nil, 'b', nil, 'c']
@context['hashes'] = [{ 'a' => 'A' }, { 'a' => nil }, { 'a' => 'C' }]
@context['objects'] = [TestObject.new('A'), TestObject.new(nil), TestObject.new('C')]
# Test strings
assert_equal('a b c', Template.parse("{{words | compact | join}}").render(@context))
assert_equal 'a b c', Template.parse('{{words | compact | join}}').render(@context)
# Test hashes
assert_equal('A C', Template.parse("{{hashes | compact: 'a' | map: 'a' | join}}").render(@context))
assert_equal 'A C', Template.parse("{{hashes | compact: 'a' | map: 'a' | join}}").render(@context)
# Test objects
assert_equal('A C', Template.parse("{{objects | compact: 'a' | map: 'a' | join}}").render(@context))
assert_equal 'A C', Template.parse("{{objects | compact: 'a' | map: 'a' | join}}").render(@context)
end
def test_strip_html
@context['var'] = "<b>bla blub</a>"
@context['var'] = '<b>bla blub</a>'
assert_equal("bla blub", Template.parse("{{ var | strip_html }}").render(@context))
assert_equal 'bla blub', Template.parse('{{ var | strip_html }}').render(@context)
end
def test_strip_html_ignore_comments_with_html
@context['var'] = "<!-- split and some <ul> tag --><b>bla blub</a>"
@context['var'] = '<!-- split and some <ul> tag --><b>bla blub</a>'
assert_equal("bla blub", Template.parse("{{ var | strip_html }}").render(@context))
assert_equal 'bla blub', Template.parse('{{ var | strip_html }}').render(@context)
end
def test_capitalize
@context['var'] = "blub"
@context['var'] = 'blub'
assert_equal("Blub", Template.parse("{{ var | capitalize }}").render(@context))
assert_equal 'Blub', Template.parse('{{ var | capitalize }}').render(@context)
end
def test_nonexistent_filter_is_ignored
@context['var'] = 1000
assert_equal('1000', Template.parse("{{ var | xyzzy }}").render(@context))
assert_equal '1000', Template.parse('{{ var | xyzzy }}').render(@context)
end
def test_filter_with_keyword_arguments
@context['surname'] = 'john'
@context['input'] = 'hello %{first_name}, %{last_name}'
@context['input'] = 'hello %{first_name}, %{last_name}'
@context.add_filters(SubstituteFilter)
output = Template.parse(%({{ input | substitute: first_name: surname, last_name: 'doe' }})).render(@context)
assert_equal('hello john, doe', output)
output = Template.parse(%({{ input | substitute: first_name: surname, last_name: 'doe' }})).render(@context)
assert_equal 'hello john, doe', output
end
def test_override_object_method_in_filter
assert_equal("tap overridden", Template.parse("{{var | tap}}").render!({ 'var' => 1000 }, filters: [OverrideObjectMethodFilter]))
assert_equal 'tap overridden', Template.parse('{{var | tap}}').render!({ 'var' => 1000 }, filters: [OverrideObjectMethodFilter])
# tap still treated as a non-existent filter
assert_equal("1000", Template.parse("{{var | tap}}").render!('var' => 1000))
assert_equal '1000', Template.parse('{{var | tap}}').render!('var' => 1000)
end
end
@@ -160,15 +160,15 @@ 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
def test_local_filter_with_deprecated_syntax
assert_equal(" 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, CanadianMoneyFilter))
assert_equal(" 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, [CanadianMoneyFilter]))
assert_equal ' 1000$ CAD ', Template.parse('{{1000 | money}}').render!(nil, CanadianMoneyFilter)
assert_equal ' 1000$ CAD ', Template.parse('{{1000 | money}}').render!(nil, [CanadianMoneyFilter])
end
end # FiltersTest

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

View File

@@ -0,0 +1,13 @@
# frozen_string_literal: true
require 'test_helper'
require 'liquid/legacy'
class FiltersTest < Minitest::Test
include Liquid
def test_constants
assert_equal Liquid::FilterSeparator, Liquid::FILTER_SEPARATOR
assert_equal Liquid::BlockBody::ContentOfVariable, Liquid::BlockBody::CONTENT_OF_VARIABLE
end
end

View File

@@ -11,11 +11,11 @@ module FunnyFilter
"LOL: #{input}"
end
def add_smiley(input, smiley = ":-)")
def add_smiley(input, smiley = ':-)')
"#{input} #{smiley}"
end
def add_tag(input, tag = "p", id = "foo")
def add_tag(input, tag = 'p', id = 'foo')
%(<#{tag} id="#{id}">#{input}</#{tag}>)
end
@@ -42,84 +42,84 @@ class OutputTest < Minitest::Test
text = %( {{best_cars}} )
expected = %( bmw )
assert_equal(expected, Template.parse(text).render!(@assigns))
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" }
))
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}} )
expected = %( good bad good )
assert_equal(expected, Template.parse(text).render!(@assigns))
assert_equal expected, Template.parse(text).render!(@assigns)
end
def test_variable_piping
text = %( {{ car.gm | make_funny }} )
text = %( {{ car.gm | make_funny }} )
expected = %( LOL )
assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))
assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
end
def test_variable_piping_with_input
text = %( {{ car.gm | cite_funny }} )
text = %( {{ car.gm | cite_funny }} )
expected = %( LOL: bad )
assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))
assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
end
def test_variable_piping_with_args
text = %! {{ car.gm | add_smiley : ':-(' }} !
text = %! {{ car.gm | add_smiley : ':-(' }} !
expected = %| bad :-( |
assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))
assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
end
def test_variable_piping_with_no_args
text = %( {{ car.gm | add_smiley }} )
text = %( {{ car.gm | add_smiley }} )
expected = %| bad :-) |
assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))
assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
end
def test_multiple_variable_piping_with_args
text = %! {{ car.gm | add_smiley : ':-(' | add_smiley : ':-('}} !
text = %! {{ car.gm | add_smiley : ':-(' | add_smiley : ':-('}} !
expected = %| bad :-( :-( |
assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))
assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
end
def test_variable_piping_with_multiple_args
text = %( {{ car.gm | add_tag : 'span', 'bar'}} )
text = %( {{ car.gm | add_tag : 'span', 'bar'}} )
expected = %( <span id="bar">bad</span> )
assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))
assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
end
def test_variable_piping_with_variable_args
text = %( {{ car.gm | add_tag : 'span', car.bmw}} )
text = %( {{ car.gm | add_tag : 'span', car.bmw}} )
expected = %( <span id="good">bad</span> )
assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))
assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
end
def test_multiple_pipings
text = %( {{ best_cars | cite_funny | paragraph }} )
text = %( {{ best_cars | cite_funny | paragraph }} )
expected = %( <p>LOL: bmw</p> )
assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))
assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
end
def test_link_to
text = %( {{ 'Typo' | link_to: 'http://typo.leetsoft.com' }} )
text = %( {{ 'Typo' | link_to: 'http://typo.leetsoft.com' }} )
expected = %( <a href="http://typo.leetsoft.com">Typo</a> )
assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))
assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
end
end # OutputTest

View File

@@ -7,217 +7,217 @@ class ParseTreeVisitorTest < Minitest::Test
def test_variable
assert_equal(
["test"],
['test'],
visit(%({{ test }}))
)
end
def test_varible_with_filter
assert_equal(
["test", "infilter"],
%w[test infilter],
visit(%({{ test | split: infilter }}))
)
end
def test_dynamic_variable
assert_equal(
["test", "inlookup"],
%w[test inlookup],
visit(%({{ test[inlookup] }}))
)
end
def test_if_condition
assert_equal(
["test"],
['test'],
visit(%({% if test %}{% endif %}))
)
end
def test_complex_if_condition
assert_equal(
["test"],
['test'],
visit(%({% if 1 == 1 and 2 == test %}{% endif %}))
)
end
def test_if_body
assert_equal(
["test"],
['test'],
visit(%({% if 1 == 1 %}{{ test }}{% endif %}))
)
end
def test_unless_condition
assert_equal(
["test"],
['test'],
visit(%({% unless test %}{% endunless %}))
)
end
def test_complex_unless_condition
assert_equal(
["test"],
['test'],
visit(%({% unless 1 == 1 and 2 == test %}{% endunless %}))
)
end
def test_unless_body
assert_equal(
["test"],
['test'],
visit(%({% unless 1 == 1 %}{{ test }}{% endunless %}))
)
end
def test_elsif_condition
assert_equal(
["test"],
['test'],
visit(%({% if 1 == 1 %}{% elsif test %}{% endif %}))
)
end
def test_complex_elsif_condition
assert_equal(
["test"],
['test'],
visit(%({% if 1 == 1 %}{% elsif 1 == 1 and 2 == test %}{% endif %}))
)
end
def test_elsif_body
assert_equal(
["test"],
['test'],
visit(%({% if 1 == 1 %}{% elsif 2 == 2 %}{{ test }}{% endif %}))
)
end
def test_else_body
assert_equal(
["test"],
['test'],
visit(%({% if 1 == 1 %}{% else %}{{ test }}{% endif %}))
)
end
def test_case_left
assert_equal(
["test"],
['test'],
visit(%({% case test %}{% endcase %}))
)
end
def test_case_condition
assert_equal(
["test"],
['test'],
visit(%({% case 1 %}{% when test %}{% endcase %}))
)
end
def test_case_when_body
assert_equal(
["test"],
['test'],
visit(%({% case 1 %}{% when 2 %}{{ test }}{% endcase %}))
)
end
def test_case_else_body
assert_equal(
["test"],
['test'],
visit(%({% case 1 %}{% else %}{{ test }}{% endcase %}))
)
end
def test_for_in
assert_equal(
["test"],
['test'],
visit(%({% for x in test %}{% endfor %}))
)
end
def test_for_limit
assert_equal(
["test"],
['test'],
visit(%({% for x in (1..5) limit: test %}{% endfor %}))
)
end
def test_for_offset
assert_equal(
["test"],
['test'],
visit(%({% for x in (1..5) offset: test %}{% endfor %}))
)
end
def test_for_body
assert_equal(
["test"],
['test'],
visit(%({% for x in (1..5) %}{{ test }}{% endfor %}))
)
end
def test_tablerow_in
assert_equal(
["test"],
['test'],
visit(%({% tablerow x in test %}{% endtablerow %}))
)
end
def test_tablerow_limit
assert_equal(
["test"],
['test'],
visit(%({% tablerow x in (1..5) limit: test %}{% endtablerow %}))
)
end
def test_tablerow_offset
assert_equal(
["test"],
['test'],
visit(%({% tablerow x in (1..5) offset: test %}{% endtablerow %}))
)
end
def test_tablerow_body
assert_equal(
["test"],
['test'],
visit(%({% tablerow x in (1..5) %}{{ test }}{% endtablerow %}))
)
end
def test_cycle
assert_equal(
["test"],
['test'],
visit(%({% cycle test %}))
)
end
def test_assign
assert_equal(
["test"],
['test'],
visit(%({% assign x = test %}))
)
end
def test_capture
assert_equal(
["test"],
['test'],
visit(%({% capture x %}{{ test }}{% endcapture %}))
)
end
def test_include
assert_equal(
["test"],
['test'],
visit(%({% include test %}))
)
end
def test_include_with
assert_equal(
["test"],
['test'],
visit(%({% include "hai" with test %}))
)
end
def test_include_for
assert_equal(
["test"],
['test'],
visit(%({% include "hai" for test %}))
)
end
@@ -225,9 +225,9 @@ class ParseTreeVisitorTest < Minitest::Test
def test_preserve_tree_structure
assert_equal(
[[nil, [
[nil, [[nil, [["other", []]]]]],
["test", []],
["xs", []],
[nil, [[nil, [['other', []]]]]],
['test', []],
['xs', []],
]]],
traversal(%({% for x in xs offset: test %}{{ other }}{% endfor %})).visit
)

View File

@@ -6,38 +6,38 @@ class ParsingQuirksTest < Minitest::Test
include Liquid
def test_parsing_css
text = " div { font-weight: bold; } "
assert_equal(text, Template.parse(text).render!)
text = ' div { font-weight: bold; } '
assert_equal text, Template.parse(text).render!
end
def test_raise_on_single_close_bracet
assert_raises(SyntaxError) do
Template.parse("text {{method} oh nos!")
Template.parse('text {{method} oh nos!')
end
end
def test_raise_on_label_and_no_close_bracets
assert_raises(SyntaxError) do
Template.parse("TEST {{ ")
Template.parse('TEST {{ ')
end
end
def test_raise_on_label_and_no_close_bracets_percent
assert_raises(SyntaxError) do
Template.parse("TEST {% ")
Template.parse('TEST {% ')
end
end
def test_error_on_empty_filter
assert(Template.parse("{{test}}"))
assert Template.parse('{{test}}')
with_error_mode(:lax) do
assert Template.parse("{{|test}}")
assert Template.parse('{{|test}}')
end
with_error_mode(:strict) do
assert_raises(SyntaxError) { Template.parse("{{|test}}") }
assert_raises(SyntaxError) { Template.parse("{{test |a|b|}}") }
assert_raises(SyntaxError) { Template.parse('{{|test}}') }
assert_raises(SyntaxError) { Template.parse('{{test |a|b|}}') }
end
end
@@ -53,35 +53,35 @@ class ParsingQuirksTest < Minitest::Test
def test_unexpected_characters_syntax_error
with_error_mode(:strict) do
assert_raises(SyntaxError) do
markup = "true && false"
markup = 'true && false'
Template.parse("{% if #{markup} %} YES {% endif %}")
end
assert_raises(SyntaxError) do
markup = "false || true"
markup = 'false || true'
Template.parse("{% if #{markup} %} YES {% endif %}")
end
end
end
def test_no_error_on_lax_empty_filter
assert(Template.parse("{{test |a|b|}}", error_mode: :lax))
assert(Template.parse("{{test}}", error_mode: :lax))
assert(Template.parse("{{|test|}}", error_mode: :lax))
assert Template.parse('{{test |a|b|}}', error_mode: :lax)
assert Template.parse('{{test}}', error_mode: :lax)
assert Template.parse('{{|test|}}', error_mode: :lax)
end
def test_meaningless_parens_lax
with_error_mode(:lax) do
assigns = { 'b' => 'bar', 'c' => 'baz' }
markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false"
markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false"
assert_template_result(' YES ', "{% if #{markup} %} YES {% endif %}", assigns)
end
end
def test_unexpected_characters_silently_eat_logic_lax
with_error_mode(:lax) do
markup = "true && false"
markup = 'true && false'
assert_template_result(' YES ', "{% if #{markup} %} YES {% endif %}")
markup = "false || true"
markup = 'false || true'
assert_template_result('', "{% if #{markup} %} YES {% endif %}")
end
end
@@ -114,7 +114,7 @@ class ParsingQuirksTest < Minitest::Test
def test_extra_dots_in_ranges
with_error_mode(:lax) do
assert_template_result('12345', "{% for i in (1...5) %}{{ i }}{% endfor %}")
assert_template_result('12345', '{% for i in (1...5) %}{{ i }}{% endfor %}')
end
end

View File

@@ -1,27 +0,0 @@
# frozen_string_literal: true
require 'test_helper'
class DisabledTagsTest < Minitest::Test
include Liquid
class DisableRaw < Block
disable_tags "raw"
end
class DisableRawEcho < Block
disable_tags "raw", "echo"
end
def test_disables_raw
with_custom_tag('disable', DisableRaw) do
assert_template_result 'raw usage is not allowed in this contextfoo', '{% disable %}{% raw %}Foobar{% endraw %}{% echo "foo" %}{% enddisable %}'
end
end
def test_disables_echo_and_raw
with_custom_tag('disable', DisableRawEcho) do
assert_template_result 'raw usage is not allowed in this contextecho usage is not allowed in this context', '{% disable %}{% raw %}Foobar{% endraw %}{% echo "foo" %}{% enddisable %}'
end
end
end

View File

@@ -19,35 +19,35 @@ class RenderProfilingTest < Minitest::Test
t = Template.parse("{{ 'a string' | upcase }}")
t.render!
assert_nil(t.profiler)
assert_nil t.profiler
end
def test_parse_makes_available_simple_profiling
t = Template.parse("{{ 'a string' | upcase }}", profile: true)
t.render!
assert_equal(1, t.profiler.length)
assert_equal 1, t.profiler.length
node = t.profiler[0]
assert_equal(" 'a string' | upcase ", node.code)
assert_equal " 'a string' | upcase ", node.code
end
def test_render_ignores_raw_strings_when_profiling
t = Template.parse("This is raw string\nstuff\nNewline", profile: true)
t.render!
assert_equal(0, t.profiler.length)
assert_equal 0, t.profiler.length
end
def test_profiling_includes_line_numbers_of_liquid_nodes
t = Template.parse("{{ 'a string' | upcase }}\n{% increment test %}", profile: true)
t.render!
assert_equal(2, t.profiler.length)
assert_equal 2, t.profiler.length
# {{ 'a string' | upcase }}
assert_equal(1, t.profiler[0].line_number)
assert_equal 1, t.profiler[0].line_number
# {{ increment test }}
assert_equal(2, t.profiler[1].line_number)
assert_equal 2, t.profiler[1].line_number
end
def test_profiling_includes_line_numbers_of_included_partials
@@ -57,9 +57,9 @@ class RenderProfilingTest < Minitest::Test
included_children = t.profiler[0].children
# {% assign template_name = 'a_template' %}
assert_equal(1, included_children[0].line_number)
assert_equal 1, included_children[0].line_number
# {{ template_name }}
assert_equal(2, included_children[1].line_number)
assert_equal 2, included_children[1].line_number
end
def test_profiling_times_the_rendering_of_tokens
@@ -67,14 +67,14 @@ class RenderProfilingTest < Minitest::Test
t.render!
node = t.profiler[0]
refute_nil(node.render_time)
refute_nil node.render_time
end
def test_profiling_times_the_entire_render
t = Template.parse("{% include 'a_template' %}", profile: true)
t.render!
assert(t.profiler.total_render_time >= 0, "Total render time was not calculated")
assert t.profiler.total_render_time >= 0, 'Total render time was not calculated'
end
def test_profiling_uses_include_to_mark_children
@@ -82,7 +82,7 @@ class RenderProfilingTest < Minitest::Test
t.render!
include_node = t.profiler[1]
assert_equal(2, include_node.children.length)
assert_equal 2, include_node.children.length
end
def test_profiling_marks_children_with_the_name_of_included_partial
@@ -91,7 +91,7 @@ class RenderProfilingTest < 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
@@ -101,12 +101,12 @@ class RenderProfilingTest < 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
@@ -116,12 +116,12 @@ class RenderProfilingTest < 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
@@ -134,38 +134,23 @@ class RenderProfilingTest < Minitest::Test
timing_count += 1
end
assert_equal(2, timing_count)
assert_equal 2, timing_count
end
def test_profiling_marks_children_of_if_blocks
t = Template.parse("{% if true %} {% increment test %} {{ test }} {% endif %}", profile: true)
t = Template.parse('{% if true %} {% increment test %} {{ test }} {% endif %}', profile: true)
t.render!
assert_equal(1, t.profiler.length)
assert_equal(2, t.profiler[0].children.length)
assert_equal 1, t.profiler.length
assert_equal 2, t.profiler[0].children.length
end
def test_profiling_marks_children_of_for_blocks
t = Template.parse("{% for item in collection %} {{ item }} {% endfor %}", profile: true)
t.render!("collection" => ["one", "two"])
t = Template.parse('{% for item in collection %} {{ item }} {% endfor %}', profile: true)
t.render!('collection' => %w[one two])
assert_equal(1, t.profiler.length)
assert_equal 1, t.profiler.length
# Will profile each invocation of the for block
assert_equal(2, t.profiler[0].children.length)
end
def test_profiling_supports_self_time
t = Template.parse("{% for item in collection %} {{ item }} {% endfor %}", profile: true)
t.render!("collection" => ["one", "two"])
leaf = t.profiler[0].children[0]
assert_operator leaf.self_time, :>, 0
end
def test_profiling_supports_total_time
t = Template.parse("{% if true %} {% increment test %} {{ test }} {% endif %}", profile: true)
t.render!
assert_operator t.profiler[0].total_time, :>, 0
assert_equal 2, t.profiler[0].children.length
end
end

View File

@@ -16,31 +16,31 @@ class SecurityTest < Minitest::Test
end
def test_no_instance_eval
text = %( {{ '1+1' | instance_eval }} )
text = %( {{ '1+1' | instance_eval }} )
expected = %( 1+1 )
assert_equal(expected, Template.parse(text).render!(@assigns))
assert_equal expected, Template.parse(text).render!(@assigns)
end
def test_no_existing_instance_eval
text = %( {{ '1+1' | __instance_eval__ }} )
text = %( {{ '1+1' | __instance_eval__ }} )
expected = %( 1+1 )
assert_equal(expected, Template.parse(text).render!(@assigns))
assert_equal expected, Template.parse(text).render!(@assigns)
end
def test_no_instance_eval_after_mixing_in_new_filter
text = %( {{ '1+1' | instance_eval }} )
text = %( {{ '1+1' | instance_eval }} )
expected = %( 1+1 )
assert_equal(expected, Template.parse(text).render!(@assigns))
assert_equal expected, Template.parse(text).render!(@assigns)
end
def test_no_instance_eval_later_in_chain
text = %( {{ '1+1' | add_one | instance_eval }} )
text = %( {{ '1+1' | add_one | instance_eval }} )
expected = %( 1+1 + 1 )
assert_equal(expected, Template.parse(text).render!(@assigns, filters: SecurityFilter))
assert_equal expected, Template.parse(text).render!(@assigns, filters: SecurityFilter)
end
def test_does_not_add_filters_to_symbol_table
@@ -49,32 +49,32 @@ class SecurityTest < Minitest::Test
test = %( {{ "some_string" | a_bad_filter }} )
template = Template.parse(test)
assert_equal([], (Symbol.all_symbols - current_symbols))
assert_equal [], (Symbol.all_symbols - current_symbols)
template.render!
assert_equal([], (Symbol.all_symbols - current_symbols))
assert_equal [], (Symbol.all_symbols - current_symbols)
end
def test_does_not_add_drop_methods_to_symbol_table
current_symbols = Symbol.all_symbols
assigns = { 'drop' => Drop.new }
assert_equal("", Template.parse("{{ drop.custom_method_1 }}", assigns).render!)
assert_equal("", Template.parse("{{ drop.custom_method_2 }}", assigns).render!)
assert_equal("", Template.parse("{{ drop.custom_method_3 }}", assigns).render!)
assert_equal '', Template.parse('{{ drop.custom_method_1 }}', assigns).render!
assert_equal '', Template.parse('{{ drop.custom_method_2 }}', assigns).render!
assert_equal '', Template.parse('{{ drop.custom_method_3 }}', assigns).render!
assert_equal([], (Symbol.all_symbols - current_symbols))
assert_equal [], (Symbol.all_symbols - current_symbols)
end
def test_max_depth_nested_blocks_does_not_raise_exception
depth = Liquid::Block::MAX_DEPTH
code = "{% if true %}" * depth + "rendered" + "{% endif %}" * depth
assert_equal("rendered", Template.parse(code).render!)
code = '{% if true %}' * depth + 'rendered' + '{% endif %}' * depth
assert_equal 'rendered', Template.parse(code).render!
end
def test_more_than_max_depth_nested_blocks_raises_exception
depth = Liquid::Block::MAX_DEPTH + 1
code = "{% if true %}" * depth + "rendered" + "{% endif %}" * depth
code = '{% if true %}' * depth + 'rendered' + '{% endif %}' * depth
assert_raises(Liquid::StackLevelError) do
Template.parse(code).render!
end

File diff suppressed because it is too large Load Diff

View File

@@ -8,8 +8,8 @@ class BreakTagTest < Minitest::Test
# tests that no weird errors are raised if break is called outside of a
# block
def test_break_with_no_block
assigns = { 'i' => 1 }
markup = '{% break %}'
assigns = { 'i' => 1 }
markup = '{% break %}'
expected = ''
assert_template_result(expected, markup, assigns)

View File

@@ -8,8 +8,8 @@ class ContinueTagTest < Minitest::Test
# tests that no weird errors are raised if continue is called outside of a
# block
def test_continue_with_no_block
assigns = {}
markup = '{% continue %}'
assigns = {}
markup = '{% continue %}'
expected = ''
assert_template_result(expected, markup, assigns)

View File

@@ -1,13 +0,0 @@
# frozen_string_literal: true
require 'test_helper'
class EchoTest < Minitest::Test
include Liquid
def test_echo_outputs_its_input
assert_template_result('BAR', <<~LIQUID, 'variable-name' => 'bar')
{%- echo variable-name | upcase -%}
LIQUID
end
end

View File

@@ -42,31 +42,31 @@ HERE
assert_template_result(' 1 2 3 ', '{%for item in (1..3) %} {{item}} {%endfor%}')
assert_raises(Liquid::ArgumentError) do
Template.parse('{% for i in (a..2) %}{% endfor %}').render!("a" => [1, 2])
Template.parse('{% for i in (a..2) %}{% endfor %}').render!('a' => [1, 2])
end
assert_template_result(' 0 1 2 3 ', '{% for item in (a..3) %} {{item}} {% endfor %}', "a" => "invalid integer")
assert_template_result(' 0 1 2 3 ', '{% for item in (a..3) %} {{item}} {% endfor %}', 'a' => 'invalid integer')
end
def test_for_with_variable_range
assert_template_result(' 1 2 3 ', '{%for item in (1..foobar) %} {{item}} {%endfor%}', "foobar" => 3)
assert_template_result(' 1 2 3 ', '{%for item in (1..foobar) %} {{item}} {%endfor%}', 'foobar' => 3)
end
def test_for_with_hash_value_range
foobar = { "value" => 3 }
assert_template_result(' 1 2 3 ', '{%for item in (1..foobar.value) %} {{item}} {%endfor%}', "foobar" => foobar)
foobar = { 'value' => 3 }
assert_template_result(' 1 2 3 ', '{%for item in (1..foobar.value) %} {{item}} {%endfor%}', 'foobar' => foobar)
end
def test_for_with_drop_value_range
foobar = ThingWithValue.new
assert_template_result(' 1 2 3 ', '{%for item in (1..foobar.value) %} {{item}} {%endfor%}', "foobar" => foobar)
assert_template_result(' 1 2 3 ', '{%for item in (1..foobar.value) %} {{item}} {%endfor%}', 'foobar' => foobar)
end
def test_for_with_variable
assert_template_result(' 1 2 3 ', '{%for item in array%} {{item}} {%endfor%}', 'array' => [1, 2, 3])
assert_template_result('123', '{%for item in array%}{{item}}{%endfor%}', 'array' => [1, 2, 3])
assert_template_result('123', '{% for item in array %}{{item}}{% endfor %}', 'array' => [1, 2, 3])
assert_template_result('abcd', '{%for item in array%}{{item}}{%endfor%}', 'array' => ['a', 'b', 'c', 'd'])
assert_template_result('abcd', '{%for item in array%}{{item}}{%endfor%}', 'array' => %w[a b c d])
assert_template_result('a b c', '{%for item in array%}{{item}}{%endfor%}', 'array' => ['a', ' ', 'b', ' ', 'c'])
assert_template_result('abc', '{%for item in array%}{{item}}{%endfor%}', 'array' => ['a', '', 'b', '', 'c'])
end
@@ -74,8 +74,8 @@ HERE
def test_for_helpers
assigns = { 'array' => [1, 2, 3] }
assert_template_result(' 1/3 2/3 3/3 ',
'{%for item in array%} {{forloop.index}}/{{forloop.length}} {%endfor%}',
assigns)
'{%for item in array%} {{forloop.index}}/{{forloop.length}} {%endfor%}',
assigns)
assert_template_result(' 1 2 3 ', '{%for item in array%} {{forloop.index}} {%endfor%}', assigns)
assert_template_result(' 0 1 2 ', '{%for item in array%} {{forloop.index0}} {%endfor%}', assigns)
assert_template_result(' 2 1 0 ', '{%for item in array%} {{forloop.rindex0}} {%endfor%}', assigns)
@@ -87,8 +87,8 @@ HERE
def test_for_and_if
assigns = { 'array' => [1, 2, 3] }
assert_template_result('+--',
'{%for item in array%}{% if forloop.first %}+{% else %}-{% endif %}{%endfor%}',
assigns)
'{%for item in array%}{% if forloop.first %}+{% else %}-{% endif %}{%endfor%}',
assigns)
end
def test_for_else
@@ -106,7 +106,7 @@ HERE
end
def test_limiting_with_invalid_limit
assigns = { 'array' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] }
assigns = { 'array' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] }
template = <<-MKUP
{% for i in array limit: true offset: 1 %}
{{ i }}
@@ -116,11 +116,11 @@ HERE
exception = assert_raises(Liquid::ArgumentError) do
Template.parse(template).render!(assigns)
end
assert_equal("Liquid error: invalid integer", exception.message)
assert_equal('Liquid error: invalid integer', exception.message)
end
def test_limiting_with_invalid_offset
assigns = { 'array' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] }
assigns = { 'array' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] }
template = <<-MKUP
{% for i in array limit: 1 offset: true %}
{{ i }}
@@ -130,12 +130,12 @@ HERE
exception = assert_raises(Liquid::ArgumentError) do
Template.parse(template).render!(assigns)
end
assert_equal("Liquid error: invalid integer", exception.message)
assert_equal('Liquid error: invalid integer', exception.message)
end
def test_dynamic_variable_limiting
assigns = { 'array' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] }
assigns['limit'] = 2
assigns = { 'array' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] }
assigns['limit'] = 2
assigns['offset'] = 2
assert_template_result('34', '{%for i in array limit: limit offset: offset %}{{ i }}{%endfor%}', assigns)
@@ -152,65 +152,65 @@ HERE
end
def test_pause_resume
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } }
markup = <<-MKUP
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } }
markup = <<-MKUP
{%for i in array.items limit: 3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit: 3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit: 3 %}{{i}}{%endfor%}
MKUP
MKUP
expected = <<-XPCTD
123
next
456
next
789
XPCTD
XPCTD
assert_template_result(expected, markup, assigns)
end
def test_pause_resume_limit
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } }
markup = <<-MKUP
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } }
markup = <<-MKUP
{%for i in array.items limit:3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit:3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit:1 %}{{i}}{%endfor%}
MKUP
MKUP
expected = <<-XPCTD
123
next
456
next
7
XPCTD
XPCTD
assert_template_result(expected, markup, assigns)
end
def test_pause_resume_big_limit
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } }
markup = <<-MKUP
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } }
markup = <<-MKUP
{%for i in array.items limit:3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit:3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit:1000 %}{{i}}{%endfor%}
MKUP
MKUP
expected = <<-XPCTD
123
next
456
next
7890
XPCTD
XPCTD
assert_template_result(expected, markup, assigns)
end
def test_pause_resume_big_offset
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } }
markup = '{%for i in array.items limit:3 %}{{i}}{%endfor%}
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } }
markup = '{%for i in array.items limit:3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit:3 %}{{i}}{%endfor%}
next
@@ -226,26 +226,26 @@ HERE
def test_for_with_break
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] } }
markup = '{% for i in array.items %}{% break %}{% endfor %}'
expected = ""
markup = '{% for i in array.items %}{% break %}{% endfor %}'
expected = ''
assert_template_result(expected, markup, assigns)
markup = '{% for i in array.items %}{{ i }}{% break %}{% endfor %}'
expected = "1"
markup = '{% for i in array.items %}{{ i }}{% break %}{% endfor %}'
expected = '1'
assert_template_result(expected, markup, assigns)
markup = '{% for i in array.items %}{% break %}{{ i }}{% endfor %}'
expected = ""
markup = '{% for i in array.items %}{% break %}{{ i }}{% endfor %}'
expected = ''
assert_template_result(expected, markup, assigns)
markup = '{% for i in array.items %}{{ i }}{% if i > 3 %}{% break %}{% endif %}{% endfor %}'
expected = "1234"
markup = '{% for i in array.items %}{{ i }}{% if i > 3 %}{% break %}{% endif %}{% endfor %}'
expected = '1234'
assert_template_result(expected, markup, assigns)
# tests to ensure it only breaks out of the local for loop
# and not all of them.
assigns = { 'array' => [[1, 2], [3, 4], [5, 6]] }
markup = '{% for item in array %}' \
assigns = { 'array' => [[1, 2], [3, 4], [5, 6]] }
markup = '{% for item in array %}' \
'{% for i in item %}' \
'{% if i == 1 %}' \
'{% break %}' \
@@ -257,8 +257,8 @@ HERE
assert_template_result(expected, markup, assigns)
# test break does nothing when unreached
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5] } }
markup = '{% for i in array.items %}{% if i == 9999 %}{% break %}{% endif %}{{ i }}{% endfor %}'
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5] } }
markup = '{% for i in array.items %}{% if i == 9999 %}{% break %}{% endif %}{{ i }}{% endfor %}'
expected = '12345'
assert_template_result(expected, markup, assigns)
end
@@ -266,29 +266,29 @@ HERE
def test_for_with_continue
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5] } }
markup = '{% for i in array.items %}{% continue %}{% endfor %}'
expected = ""
markup = '{% for i in array.items %}{% continue %}{% endfor %}'
expected = ''
assert_template_result(expected, markup, assigns)
markup = '{% for i in array.items %}{{ i }}{% continue %}{% endfor %}'
expected = "12345"
markup = '{% for i in array.items %}{{ i }}{% continue %}{% endfor %}'
expected = '12345'
assert_template_result(expected, markup, assigns)
markup = '{% for i in array.items %}{% continue %}{{ i }}{% endfor %}'
expected = ""
markup = '{% for i in array.items %}{% continue %}{{ i }}{% endfor %}'
expected = ''
assert_template_result(expected, markup, assigns)
markup = '{% for i in array.items %}{% if i > 3 %}{% continue %}{% endif %}{{ i }}{% endfor %}'
expected = "123"
markup = '{% for i in array.items %}{% if i > 3 %}{% continue %}{% endif %}{{ i }}{% endfor %}'
expected = '123'
assert_template_result(expected, markup, assigns)
markup = '{% for i in array.items %}{% if i == 3 %}{% continue %}{% else %}{{ i }}{% endif %}{% endfor %}'
expected = "1245"
markup = '{% for i in array.items %}{% if i == 3 %}{% continue %}{% else %}{{ i }}{% endif %}{% endfor %}'
expected = '1245'
assert_template_result(expected, markup, assigns)
# tests to ensure it only continues the local for loop and not all of them.
assigns = { 'array' => [[1, 2], [3, 4], [5, 6]] }
markup = '{% for item in array %}' \
assigns = { 'array' => [[1, 2], [3, 4], [5, 6]] }
markup = '{% for item in array %}' \
'{% for i in item %}' \
'{% if i == 1 %}' \
'{% continue %}' \
@@ -300,8 +300,8 @@ HERE
assert_template_result(expected, markup, assigns)
# test continue does nothing when unreached
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5] } }
markup = '{% for i in array.items %}{% if i == 9999 %}{% continue %}{% endif %}{{ i }}{% endfor %}'
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5] } }
markup = '{% for i in array.items %}{% if i == 9999 %}{% continue %}{% endif %}{{ i }}{% endfor %}'
expected = '12345'
assert_template_result(expected, markup, assigns)
end
@@ -312,49 +312,49 @@ HERE
# the functionality for backwards compatibility
assert_template_result('test string',
'{%for val in string%}{{val}}{%endfor%}',
'string' => "test string")
'{%for val in string%}{{val}}{%endfor%}',
'string' => 'test string')
assert_template_result('test string',
'{%for val in string limit:1%}{{val}}{%endfor%}',
'string' => "test string")
'{%for val in string limit:1%}{{val}}{%endfor%}',
'string' => 'test string')
assert_template_result('val-string-1-1-0-1-0-true-true-test string',
'{%for val in string%}' \
'{{forloop.name}}-' \
'{{forloop.index}}-' \
'{{forloop.length}}-' \
'{{forloop.index0}}-' \
'{{forloop.rindex}}-' \
'{{forloop.rindex0}}-' \
'{{forloop.first}}-' \
'{{forloop.last}}-' \
'{{val}}{%endfor%}',
'string' => "test string")
'{%for val in string%}' \
'{{forloop.name}}-' \
'{{forloop.index}}-' \
'{{forloop.length}}-' \
'{{forloop.index0}}-' \
'{{forloop.rindex}}-' \
'{{forloop.rindex0}}-' \
'{{forloop.first}}-' \
'{{forloop.last}}-' \
'{{val}}{%endfor%}',
'string' => 'test string')
end
def test_for_parentloop_references_parent_loop
assert_template_result('1.1 1.2 1.3 2.1 2.2 2.3 ',
'{% for inner in outer %}{% for k in inner %}' \
'{{ forloop.parentloop.index }}.{{ forloop.index }} ' \
'{% endfor %}{% endfor %}',
'outer' => [[1, 1, 1], [1, 1, 1]])
'{% for inner in outer %}{% for k in inner %}' \
'{{ forloop.parentloop.index }}.{{ forloop.index }} ' \
'{% endfor %}{% endfor %}',
'outer' => [[1, 1, 1], [1, 1, 1]])
end
def test_for_parentloop_nil_when_not_present
assert_template_result('.1 .2 ',
'{% for inner in outer %}' \
'{{ forloop.parentloop.index }}.{{ forloop.index }} ' \
'{% endfor %}',
'outer' => [[1, 1, 1], [1, 1, 1]])
'{% for inner in outer %}' \
'{{ forloop.parentloop.index }}.{{ forloop.index }} ' \
'{% endfor %}',
'outer' => [[1, 1, 1], [1, 1, 1]])
end
def test_inner_for_over_empty_input
assert_template_result('oo', '{% for a in (1..2) %}o{% for b in empty %}{% endfor %}{% endfor %}')
assert_template_result 'oo', '{% for a in (1..2) %}o{% for b in empty %}{% endfor %}{% endfor %}'
end
def test_blank_string_not_iterable
assert_template_result('', "{% for char in characters %}I WILL NOT BE OUTPUT{% endfor %}", 'characters' => '')
assert_template_result('', '{% for char in characters %}I WILL NOT BE OUTPUT{% endfor %}', 'characters' => '')
end
def test_bad_variable_naming_in_for_loop
@@ -389,41 +389,41 @@ HERE
end
def test_iterate_with_each_when_no_limit_applied
loader = LoaderDrop.new([1, 2, 3, 4, 5])
assigns = { 'items' => loader }
loader = LoaderDrop.new([1, 2, 3, 4, 5])
assigns = { 'items' => loader }
expected = '12345'
template = '{% for item in items %}{{item}}{% endfor %}'
assert_template_result(expected, template, assigns)
assert(loader.each_called)
assert(!loader.load_slice_called)
assert loader.each_called
assert !loader.load_slice_called
end
def test_iterate_with_load_slice_when_limit_applied
loader = LoaderDrop.new([1, 2, 3, 4, 5])
assigns = { 'items' => loader }
loader = LoaderDrop.new([1, 2, 3, 4, 5])
assigns = { 'items' => loader }
expected = '1'
template = '{% for item in items limit:1 %}{{item}}{% endfor %}'
assert_template_result(expected, template, assigns)
assert(!loader.each_called)
assert(loader.load_slice_called)
assert !loader.each_called
assert loader.load_slice_called
end
def test_iterate_with_load_slice_when_limit_and_offset_applied
loader = LoaderDrop.new([1, 2, 3, 4, 5])
assigns = { 'items' => loader }
loader = LoaderDrop.new([1, 2, 3, 4, 5])
assigns = { 'items' => loader }
expected = '34'
template = '{% for item in items offset:2 limit:2 %}{{item}}{% endfor %}'
assert_template_result(expected, template, assigns)
assert(!loader.each_called)
assert(loader.load_slice_called)
assert !loader.each_called
assert loader.load_slice_called
end
def test_iterate_with_load_slice_returns_same_results_as_without
loader = LoaderDrop.new([1, 2, 3, 4, 5])
loader = LoaderDrop.new([1, 2, 3, 4, 5])
loader_assigns = { 'items' => loader }
array_assigns = { 'items' => [1, 2, 3, 4, 5] }
expected = '34'
template = '{% for item in items offset:2 limit:2 %}{{item}}{% endfor %}'
array_assigns = { 'items' => [1, 2, 3, 4, 5] }
expected = '34'
template = '{% for item in items offset:2 limit:2 %}{{item}}{% endfor %}'
assert_template_result(expected, template, loader_assigns)
assert_template_result(expected, template, array_assigns)
end
@@ -435,6 +435,6 @@ HERE
Liquid::Template.parse('{% for i in (1..2) %}{{ standard_error }}{% endfor %}').render!(context)
end
assert(context.registers[:for_stack].empty?)
assert context.registers[:for_stack].empty?
end
end

View File

@@ -8,7 +8,7 @@ class IfElseTagTest < Minitest::Test
def test_if
assert_template_result(' ', ' {% if false %} this text should not go into the output {% endif %} ')
assert_template_result(' this text should go into the output ',
' {% if true %} this text should go into the output {% endif %} ')
' {% if true %} this text should go into the output {% endif %} ')
assert_template_result(' you rock ?', '{% if false %} you suck {% endif %} {% if true %} you rock {% endif %}?')
end
@@ -45,18 +45,18 @@ class IfElseTagTest < Minitest::Test
def test_comparison_of_strings_containing_and_or_or
awful_markup = "a == 'and' and b == 'or' and c == 'foo and bar' and d == 'bar or baz' and e == 'foo' and foo and bar"
assigns = { 'a' => 'and', 'b' => 'or', 'c' => 'foo and bar', 'd' => 'bar or baz', 'e' => 'foo', 'foo' => true, 'bar' => true }
assigns = { 'a' => 'and', 'b' => 'or', 'c' => 'foo and bar', 'd' => 'bar or baz', 'e' => 'foo', 'foo' => true, 'bar' => true }
assert_template_result(' YES ', "{% if #{awful_markup} %} YES {% endif %}", assigns)
end
def test_comparison_of_expressions_starting_with_and_or_or
assigns = { 'order' => { 'items_count' => 0 }, 'android' => { 'name' => 'Roy' } }
assert_template_result("YES",
"{% if android.name == 'Roy' %}YES{% endif %}",
assigns)
assert_template_result("YES",
"{% if order.items_count == 0 %}YES{% endif %}",
assigns)
assert_template_result('YES',
"{% if android.name == 'Roy' %}YES{% endif %}",
assigns)
assert_template_result('YES',
'{% if order.items_count == 0 %}YES{% endif %}',
assigns)
end
def test_if_and
@@ -77,14 +77,14 @@ class IfElseTagTest < Minitest::Test
assert_template_result('', '{% if foo.bar %} NO {% endif %}', 'foo' => nil)
assert_template_result('', '{% if foo.bar %} NO {% endif %}', 'foo' => true)
assert_template_result(' YES ', '{% if var %} YES {% endif %}', 'var' => "text")
assert_template_result(' YES ', '{% if var %} YES {% endif %}', 'var' => 'text')
assert_template_result(' YES ', '{% if var %} YES {% endif %}', 'var' => true)
assert_template_result(' YES ', '{% if var %} YES {% endif %}', 'var' => 1)
assert_template_result(' YES ', '{% if var %} YES {% endif %}', 'var' => {})
assert_template_result(' YES ', '{% if var %} YES {% endif %}', 'var' => [])
assert_template_result(' YES ', '{% if "foo" %} YES {% endif %}')
assert_template_result(' YES ', '{% if foo.bar %} YES {% endif %}', 'foo' => { 'bar' => true })
assert_template_result(' YES ', '{% if foo.bar %} YES {% endif %}', 'foo' => { 'bar' => "text" })
assert_template_result(' YES ', '{% if foo.bar %} YES {% endif %}', 'foo' => { 'bar' => 'text' })
assert_template_result(' YES ', '{% if foo.bar %} YES {% endif %}', 'foo' => { 'bar' => 1 })
assert_template_result(' YES ', '{% if foo.bar %} YES {% endif %}', 'foo' => { 'bar' => {} })
assert_template_result(' YES ', '{% if foo.bar %} YES {% endif %}', 'foo' => { 'bar' => [] })
@@ -92,11 +92,11 @@ class IfElseTagTest < Minitest::Test
assert_template_result(' YES ', '{% if var %} NO {% else %} YES {% endif %}', 'var' => false)
assert_template_result(' YES ', '{% if var %} NO {% else %} YES {% endif %}', 'var' => nil)
assert_template_result(' YES ', '{% if var %} YES {% else %} NO {% endif %}', 'var' => true)
assert_template_result(' YES ', '{% if "foo" %} YES {% else %} NO {% endif %}', 'var' => "text")
assert_template_result(' YES ', '{% if "foo" %} YES {% else %} NO {% endif %}', 'var' => 'text')
assert_template_result(' YES ', '{% if foo.bar %} NO {% else %} YES {% endif %}', 'foo' => { 'bar' => false })
assert_template_result(' YES ', '{% if foo.bar %} YES {% else %} NO {% endif %}', 'foo' => { 'bar' => true })
assert_template_result(' YES ', '{% if foo.bar %} YES {% else %} NO {% endif %}', 'foo' => { 'bar' => "text" })
assert_template_result(' YES ', '{% if foo.bar %} YES {% else %} NO {% endif %}', 'foo' => { 'bar' => 'text' })
assert_template_result(' YES ', '{% if foo.bar %} NO {% else %} YES {% endif %}', 'foo' => { 'notbar' => true })
assert_template_result(' YES ', '{% if foo.bar %} NO {% else %} YES {% endif %}', 'foo' => {})
assert_template_result(' YES ', '{% if foo.bar %} NO {% else %} YES {% endif %}', 'notfoo' => { 'bar' => true })
@@ -156,7 +156,7 @@ class IfElseTagTest < Minitest::Test
Condition.operators['contains'] = :[]
assert_template_result('yes',
%({% if 'gnomeslab-and-or-liquid' contains 'gnomeslab-and-or-liquid' %}yes{% endif %}))
%({% if 'gnomeslab-and-or-liquid' contains 'gnomeslab-and-or-liquid' %}yes{% endif %}))
ensure
Condition.operators['contains'] = original_op
end
@@ -168,7 +168,7 @@ class IfElseTagTest < Minitest::Test
end
def test_multiple_conditions
tpl = "{% if a or b and c %}true{% else %}false{% endif %}"
tpl = '{% if a or b and c %}true{% else %}false{% endif %}'
tests = {
[true, true, true] => true,

View File

@@ -5,38 +5,35 @@ require 'test_helper'
class TestFileSystem
def read_template_file(template_path)
case template_path
when "product"
"Product: {{ product.title }} "
when 'product'
'Product: {{ product.title }} '
when "product_alias"
"Product: {{ product.title }} "
when 'locale_variables'
'Locale: {{echo1}} {{echo2}}'
when "locale_variables"
"Locale: {{echo1}} {{echo2}}"
when 'variant'
'Variant: {{ variant.title }}'
when "variant"
"Variant: {{ variant.title }}"
when "nested_template"
when 'nested_template'
"{% include 'header' %} {% include 'body' %} {% include 'footer' %}"
when "body"
when 'body'
"body {% include 'body_detail' %}"
when "nested_product_template"
when 'nested_product_template'
"Product: {{ nested_product_template.title }} {%include 'details'%} "
when "recursively_nested_template"
when 'recursively_nested_template'
"-{% include 'recursively_nested_template' %}"
when "pick_a_source"
"from TestFileSystem"
when 'pick_a_source'
'from TestFileSystem'
when 'assignments'
"{% assign foo = 'bar' %}"
when 'break'
"{% break %}"
'{% break %}'
else
template_path
@@ -54,22 +51,21 @@ class CountingFileSystem
attr_reader :count
def read_template_file(_template_path)
@count ||= 0
@count += 1
@count += 1
'from CountingFileSystem'
end
end
class CustomInclude < Liquid::Tag
Syntax = /(#{Liquid::QuotedFragment}+)(\s+(?:with|for)\s+(#{Liquid::QuotedFragment}+))?/o
SYNTAX = /(#{Liquid::QUOTED_FRAGMENT}+)(\s+(?:with|for)\s+(#{Liquid::QUOTED_FRAGMENT}+))?/o.freeze
def initialize(tag_name, markup, tokens)
markup =~ Syntax
markup =~ SYNTAX
@template_name = Regexp.last_match(1)
super
end
def parse(tokens)
end
def parse(tokens); end
def render_to_output_buffer(_context, output)
output << @template_name[1..-2]
@@ -85,66 +81,56 @@ class IncludeTagTest < Minitest::Test
end
def test_include_tag_looks_for_file_system_in_registers_first
assert_equal('from OtherFileSystem',
Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: OtherFileSystem.new }))
assert_equal 'from OtherFileSystem',
Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: OtherFileSystem.new })
end
def test_include_tag_with
assert_template_result("Product: Draft 151cm ",
"{% include 'product' with products[0] %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
end
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' }])
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' }])
assert_template_result 'Product: Draft 151cm ',
"{% include 'product' with products[0] %}", 'products' => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }]
end
def test_include_tag_with_default_name
assert_template_result("Product: Draft 151cm ",
"{% include 'product' %}", "product" => { 'title' => 'Draft 151cm' })
assert_template_result 'Product: Draft 151cm ',
"{% include 'product' %}", 'product' => { 'title' => 'Draft 151cm' }
end
def test_include_tag_for
assert_template_result("Product: Draft 151cm Product: Element 155cm ",
"{% include 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
assert_template_result 'Product: Draft 151cm Product: Element 155cm ',
"{% include 'product' for products %}", 'products' => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }]
end
def test_include_tag_with_local_variables
assert_template_result("Locale: test123 ", "{% include 'locale_variables' echo1: 'test123' %}")
assert_template_result 'Locale: test123 ', "{% include 'locale_variables' echo1: 'test123' %}"
end
def test_include_tag_with_multiple_local_variables
assert_template_result("Locale: test123 test321",
"{% include 'locale_variables' echo1: 'test123', echo2: 'test321' %}")
assert_template_result 'Locale: test123 test321',
"{% include 'locale_variables' echo1: 'test123', echo2: 'test321' %}"
end
def test_include_tag_with_multiple_local_variables_from_context
assert_template_result("Locale: test123 test321",
"{% include 'locale_variables' echo1: echo1, echo2: more_echos.echo2 %}",
'echo1' => 'test123', 'more_echos' => { "echo2" => 'test321' })
assert_template_result 'Locale: test123 test321',
"{% include 'locale_variables' echo1: echo1, echo2: more_echos.echo2 %}",
'echo1' => 'test123', 'more_echos' => { 'echo2' => 'test321' }
end
def test_included_templates_assigns_variables
assert_template_result("bar", "{% include 'assignments' %}{{ foo }}")
assert_template_result 'bar', "{% include 'assignments' %}{{ foo }}"
end
def test_nested_include_tag
assert_template_result("body body_detail", "{% include 'body' %}")
assert_template_result 'body body_detail', "{% include 'body' %}"
assert_template_result("header body body_detail footer", "{% include 'nested_template' %}")
assert_template_result 'header body body_detail footer', "{% include 'nested_template' %}"
end
def test_nested_include_with_variable
assert_template_result("Product: Draft 151cm details ",
"{% include 'nested_product_template' with product %}", "product" => { "title" => 'Draft 151cm' })
assert_template_result 'Product: Draft 151cm details ',
"{% include 'nested_product_template' with product %}", 'product' => { 'title' => 'Draft 151cm' }
assert_template_result("Product: Draft 151cm details Product: Element 155cm details ",
"{% include 'nested_product_template' for products %}", "products" => [{ "title" => 'Draft 151cm' }, { "title" => 'Element 155cm' }])
assert_template_result 'Product: Draft 151cm details Product: Element 155cm details ',
"{% include 'nested_product_template' for products %}", 'products' => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }]
end
def test_recursively_included_template_does_not_produce_endless_loop
@@ -162,41 +148,41 @@ class IncludeTagTest < Minitest::Test
end
def test_dynamically_choosen_template
assert_template_result("Test123", "{% include template %}", "template" => 'Test123')
assert_template_result("Test321", "{% include template %}", "template" => 'Test321')
assert_template_result 'Test123', '{% include template %}', 'template' => 'Test123'
assert_template_result 'Test321', '{% include template %}', 'template' => 'Test321'
assert_template_result("Product: Draft 151cm ", "{% include template for product %}",
"template" => 'product', 'product' => { 'title' => 'Draft 151cm' })
assert_template_result 'Product: Draft 151cm ', '{% include template for product %}',
'template' => 'product', 'product' => { 'title' => 'Draft 151cm' }
end
def test_include_tag_caches_second_read_of_same_partial
file_system = CountingFileSystem.new
assert_equal('from CountingFileSystemfrom CountingFileSystem',
Template.parse("{% include 'pick_a_source' %}{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system }))
assert_equal(1, file_system.count)
assert_equal 'from CountingFileSystemfrom CountingFileSystem',
Template.parse("{% include 'pick_a_source' %}{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system })
assert_equal 1, file_system.count
end
def test_include_tag_doesnt_cache_partials_across_renders
file_system = CountingFileSystem.new
assert_equal('from CountingFileSystem',
Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system }))
assert_equal(1, file_system.count)
assert_equal 'from CountingFileSystem',
Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system })
assert_equal 1, file_system.count
assert_equal('from CountingFileSystem',
Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system }))
assert_equal(2, file_system.count)
assert_equal 'from CountingFileSystem',
Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system })
assert_equal 2, file_system.count
end
def test_include_tag_within_if_statement
assert_template_result("foo_if_true", "{% if true %}{% include 'foo_if_true' %}{% endif %}")
assert_template_result 'foo_if_true', "{% if true %}{% include 'foo_if_true' %}{% endif %}"
end
def test_custom_include_tag
original_tag = Liquid::Template.tags['include']
Liquid::Template.tags['include'] = CustomInclude
begin
assert_equal("custom_foo",
Template.parse("{% include 'custom_foo' %}").render!)
assert_equal 'custom_foo',
Template.parse("{% include 'custom_foo' %}").render!
ensure
Liquid::Template.tags['include'] = original_tag
end
@@ -206,8 +192,8 @@ class IncludeTagTest < Minitest::Test
original_tag = Liquid::Template.tags['include']
Liquid::Template.tags['include'] = CustomInclude
begin
assert_equal("custom_foo_if_true",
Template.parse("{% if true %}{% include 'custom_foo_if_true' %}{% endif %}").render!)
assert_equal 'custom_foo_if_true',
Template.parse("{% if true %}{% include 'custom_foo_if_true' %}{% endif %}").render!
ensure
Liquid::Template.tags['include'] = original_tag
end
@@ -218,21 +204,21 @@ class IncludeTagTest < Minitest::Test
a = Liquid::Template.parse(' {% include "nested_template" %}')
a.render!
assert_empty(a.errors)
assert_empty a.errors
end
def test_passing_options_to_included_templates
assert_raises(Liquid::SyntaxError) do
Template.parse("{% include template %}", error_mode: :strict).render!("template" => '{{ "X" || downcase }}')
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 }}')
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
@@ -248,22 +234,22 @@ class IncludeTagTest < Minitest::Test
end
def test_including_via_variable_value
assert_template_result("from TestFileSystem", "{% assign page = 'pick_a_source' %}{% include page %}")
assert_template_result 'from TestFileSystem', "{% assign page = 'pick_a_source' %}{% include page %}"
assert_template_result("Product: Draft 151cm ", "{% assign page = 'product' %}{% include page %}", "product" => { 'title' => 'Draft 151cm' })
assert_template_result 'Product: Draft 151cm ', "{% assign page = 'product' %}{% include page %}", 'product' => { 'title' => 'Draft 151cm' }
assert_template_result("Product: Draft 151cm ", "{% assign page = 'product' %}{% include page for foo %}", "foo" => { 'title' => 'Draft 151cm' })
assert_template_result 'Product: Draft 151cm ', "{% assign page = 'product' %}{% include page for foo %}", 'foo' => { 'title' => 'Draft 151cm' }
end
def test_including_with_strict_variables
template = Liquid::Template.parse("{% include 'simple' %}", error_mode: :warn)
template.render(nil, strict_variables: true)
assert_equal([], template.errors)
assert_equal [], template.errors
end
def test_break_through_include
assert_template_result("1", "{% for i in (1..3) %}{{ i }}{% break %}{{ i }}{% endfor %}")
assert_template_result("1", "{% for i in (1..3) %}{{ i }}{% include 'break' %}{{ i }}{% endfor %}")
assert_template_result '1', '{% for i in (1..3) %}{{ i }}{% break %}{{ i }}{% endfor %}'
assert_template_result '1', "{% for i in (1..3) %}{{ i }}{% include 'break' %}{{ i }}{% endfor %}"
end
end # IncludeTagTest

Some files were not shown because too many files have changed in this diff Show More