Compare commits

...

16 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
Justin Li
831355dfbd Merge pull request #1117 from ashmaroli/reduce-allocations-template-lookup-class
Reduce allocations while registering Liquid tags
2019-08-07 16:37:39 -04:00
Ashwin Maroli
00702d8e63 Use Object.const_get directly 2019-08-07 11:44:53 +05:30
Justin Li
197c058208 Merge pull request #1099 from ashmaroli/stash-types-private-constant
Use a private constant to stash token-types
2019-08-06 17:56:56 -04:00
Justin Li
98dfe198e1 Merge pull request #1115 from ashmaroli/reduce-allocations-from-truncate-filters
Reduce string allocations from truncate filters
2019-08-06 17:48:43 -04:00
Ashwin Maroli
c2c1497ca8 Reduce allocations while registering Liquid tags 2019-07-22 20:42:37 +05:30
Ashwin Maroli
d19967a79d Reduce string allocations from truncate filters 2019-07-22 17:35:45 +05:30
Florian Weingarten
248c54a386 Merge pull request #1091 from Shopify/rendering-with-less-garbage
Rendering with less garbage
2019-07-19 15:53:22 +01:00
Ashwin Maroli
2c42447659 Rename constant to SINGLE_TOKEN_EXPRESSION_TYPES 2019-05-17 23:30:24 +05:30
Ashwin Maroli
9ef6f9b642 Freeze mutable object assigned to constant 2019-04-29 23:50:49 +05:30
Ashwin Maroli
4684478e94 Use a private constant to stash token-types 2019-04-29 23:45:45 +05:30
Florian Weingarten
9640e77805 render_to_output_buffer 2019-04-23 17:06:29 -04:00
Florian Weingarten
2a1ca3152d liquid without the garbage 2019-04-22 16:34:31 -04:00
123 changed files with 3051 additions and 1951 deletions

2
.gitignore vendored
View File

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

1027
.rubocop.shopify.yml Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,132 +1,10 @@
inherit_from:
- .rubocop_todo.yml
- ./.rubocop_todo.yml
- .rubocop.shopify.yml
AllCops:
Exclude:
- 'performance/shopify/*'
- 'pkg/**'
require: rubocop-performance
Metrics/BlockNesting:
Max: 3
Performance:
Enabled: true
Metrics/ModuleLength:
Enabled: false
Metrics/ClassLength:
Enabled: false
Lint/AssignmentInCondition:
Enabled: false
Lint/AmbiguousOperator:
Enabled: false
Lint/AmbiguousRegexpLiteral:
Enabled: false
Lint/ParenthesesAsGroupedExpression:
Enabled: false
Lint/UnusedBlockArgument:
Enabled: false
Layout/EndAlignment:
EnforcedStyleAlignWith: variable
Lint/UnusedMethodArgument:
Enabled: false
Style/SingleLineBlockParams:
Enabled: false
Style/DoubleNegation:
Enabled: false
Style/StringLiteralsInInterpolation:
Enabled: false
Style/AndOr:
Enabled: false
Style/SignalException:
Enabled: false
Style/StringLiterals:
Enabled: false
Style/BracesAroundHashParameters:
Enabled: false
Style/NumericLiterals:
Enabled: false
Layout/SpaceInsideArrayLiteralBrackets:
Enabled: false
Layout/SpaceBeforeBlockBraces:
Enabled: false
Style/Documentation:
Enabled: false
Style/ClassAndModuleChildren:
Enabled: false
Style/TrailingCommaInArrayLiteral:
Enabled: false
Style/TrailingCommaInHashLiteral:
Enabled: false
Layout/IndentHash:
EnforcedStyle: consistent
Style/FormatString:
Enabled: false
Layout/AlignParameters:
EnforcedStyle: with_fixed_indentation
Layout/MultilineOperationIndentation:
EnforcedStyle: indented
Style/IfUnlessModifier:
Enabled: false
Style/RaiseArgs:
Enabled: false
Style/PreferredHashMethods:
Enabled: false
Style/RegexpLiteral:
Enabled: false
Style/SymbolLiteral:
Enabled: false
Performance/Count:
Enabled: false
Naming/ConstantName:
Enabled: false
Layout/CaseIndentation:
Enabled: false
Style/ClassVars:
Enabled: false
Style/PerlBackrefs:
Enabled: false
Style/TrivialAccessors:
AllowPredicates: true
Style/WordArray:
Enabled: false
Naming/MethodName:
Exclude:
- 'example/server/liquid_servlet.rb'
Metrics/LineLength:
Enabled: false

View File

@@ -1,260 +0,0 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2019-03-19 11:04:37 -0400 using RuboCop version 0.53.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: 1
# Cop supports --auto-correct.
# Configuration parameters: Include, TreatCommentsAsGroupSeparators.
# Include: **/*.gemspec
Gemspec/OrderedDependencies:
Exclude:
- 'liquid.gemspec'
# Offense count: 5
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: auto_detection, squiggly, active_support, powerpack, unindent
Layout/IndentHeredoc:
Exclude:
- 'test/integration/tags/for_tag_test.rb'
- 'test/integration/trim_mode_test.rb'
# Offense count: 6
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: symmetrical, new_line, same_line
Layout/MultilineMethodCallBraceLayout:
Exclude:
- 'test/integration/error_handling_test.rb'
- 'test/unit/strainer_unit_test.rb'
# Offense count: 2
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: runtime_error, standard_error
Lint/InheritException:
Exclude:
- 'lib/liquid/interrupts.rb'
# Offense count: 1
# Configuration parameters: CheckForMethodsWithNoSideEffects.
Lint/Void:
Exclude:
- 'lib/liquid/parse_context.rb'
# Offense count: 54
Metrics/AbcSize:
Max: 56
# Offense count: 12
Metrics/CyclomaticComplexity:
Max: 12
# Offense count: 112
# Configuration parameters: CountComments.
Metrics/MethodLength:
Max: 37
# Offense count: 8
Metrics/PerceivedComplexity:
Max: 11
# Offense count: 52
# Configuration parameters: Blacklist.
# Blacklist: END, (?-mix:EO[A-Z]{1})
Naming/HeredocDelimiterNaming:
Exclude:
- 'test/integration/assign_test.rb'
- 'test/integration/capture_test.rb'
- 'test/integration/trim_mode_test.rb'
# Offense count: 23
# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
# AllowedNames: io, id
Naming/UncommunicativeMethodParamName:
Exclude:
- 'example/server/example_servlet.rb'
- 'lib/liquid/condition.rb'
- 'lib/liquid/context.rb'
- 'lib/liquid/standardfilters.rb'
- 'lib/liquid/tags/if.rb'
- 'lib/liquid/utils.rb'
- 'lib/liquid/variable.rb'
- 'test/integration/filter_test.rb'
- 'test/integration/standard_filter_test.rb'
- 'test/integration/tags/for_tag_test.rb'
- 'test/integration/template_test.rb'
- 'test/unit/condition_unit_test.rb'
# Offense count: 10
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: prefer_alias, prefer_alias_method
Style/Alias:
Exclude:
- 'lib/liquid/drop.rb'
- 'lib/liquid/i18n.rb'
- 'lib/liquid/profiler/hooks.rb'
- 'lib/liquid/standardfilters.rb'
- 'lib/liquid/tag.rb'
- 'lib/liquid/tags/include.rb'
- 'lib/liquid/variable.rb'
# Offense count: 22
Style/CommentedKeyword:
Enabled: false
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions.
# SupportedStyles: assign_to_condition, assign_inside_condition
Style/ConditionalAssignment:
Exclude:
- 'lib/liquid/errors.rb'
# Offense count: 1
Style/DateTime:
Exclude:
- 'test/unit/context_unit_test.rb'
# Offense count: 2
# Cop supports --auto-correct.
Style/EmptyCaseCondition:
Exclude:
- 'lib/liquid/block_body.rb'
- 'lib/liquid/lexer.rb'
# Offense count: 5
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: compact, expanded
Style/EmptyMethod:
Exclude:
- 'lib/liquid/tag.rb'
- 'lib/liquid/tags/comment.rb'
- 'lib/liquid/tags/include.rb'
- 'test/integration/tags/include_tag_test.rb'
- 'test/unit/context_unit_test.rb'
# Offense count: 3
# Cop supports --auto-correct.
Style/Encoding:
Exclude:
- 'lib/liquid/version.rb'
- 'liquid.gemspec'
- 'test/integration/standard_filter_test.rb'
# Offense count: 2
# Cop supports --auto-correct.
Style/ExpandPathArguments:
Exclude:
- 'Rakefile'
- 'liquid.gemspec'
# Offense count: 7
# Configuration parameters: EnforcedStyle.
# SupportedStyles: annotated, template, unannotated
Style/FormatStringToken:
Exclude:
- 'test/integration/filter_test.rb'
- 'test/integration/hash_ordering_test.rb'
# Offense count: 14
# Configuration parameters: MinBodyLength.
Style/GuardClause:
Exclude:
- 'lib/liquid/condition.rb'
- 'lib/liquid/lexer.rb'
- 'lib/liquid/strainer.rb'
- 'lib/liquid/tags/assign.rb'
- 'lib/liquid/tags/capture.rb'
- 'lib/liquid/tags/case.rb'
- 'lib/liquid/tags/for.rb'
- 'lib/liquid/tags/include.rb'
- 'lib/liquid/tags/raw.rb'
- 'lib/liquid/tags/table_row.rb'
- 'lib/liquid/variable.rb'
- 'test/unit/tokenizer_unit_test.rb'
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, MinBodyLength.
# SupportedStyles: skip_modifier_ifs, always
Style/Next:
Exclude:
- 'lib/liquid/tags/for.rb'
# Offense count: 4
# Cop supports --auto-correct.
# Configuration parameters: AutoCorrect, EnforcedStyle.
# SupportedStyles: predicate, comparison
Style/NumericPredicate:
Exclude:
- 'spec/**/*'
- 'lib/liquid/context.rb'
- 'lib/liquid/forloop_drop.rb'
- 'lib/liquid/standardfilters.rb'
- 'lib/liquid/tablerowloop_drop.rb'
# Offense count: 14
# Cop supports --auto-correct.
# Configuration parameters: PreferredDelimiters.
Style/PercentLiteralDelimiters:
Exclude:
- 'lib/liquid/tags/if.rb'
- 'liquid.gemspec'
- 'test/integration/assign_test.rb'
- 'test/integration/standard_filter_test.rb'
# Offense count: 1
# Cop supports --auto-correct.
Style/RedundantSelf:
Exclude:
- 'lib/liquid/strainer.rb'
# Offense count: 9
# Cop supports --auto-correct.
# Configuration parameters: AllowAsExpressionSeparator.
Style/Semicolon:
Exclude:
- 'test/integration/error_handling_test.rb'
- 'test/integration/template_test.rb'
- 'test/unit/context_unit_test.rb'
# Offense count: 7
# Cop supports --auto-correct.
# Configuration parameters: MinSize.
# SupportedStyles: percent, brackets
Style/SymbolArray:
EnforcedStyle: brackets
# Offense count: 2
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, AllowSafeAssignment.
# SupportedStyles: require_parentheses, require_no_parentheses, require_parentheses_when_complex
Style/TernaryParentheses:
Exclude:
- 'lib/liquid/context.rb'
- 'lib/liquid/utils.rb'
# Offense count: 2
# Cop supports --auto-correct.
Style/UnneededPercentQ:
Exclude:
- 'test/integration/error_handling_test.rb'
# Offense count: 1
# Cop supports --auto-correct.
Style/WhileUntilModifier:
Exclude:
- 'lib/liquid/tags/case.rb'
# Offense count: 640
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
# URISchemes: http, https
Metrics/LineLength:
Max: 294

View File

@@ -1,14 +1,13 @@
language: ruby
rvm:
- 2.1
- 2.2
- 2.3
- 2.4
- 2.5
- 2.6
- 2.7
- ruby-head
- jruby-head
# - rbx-2
- truffleruby
sudo: false

11
Gemfile
View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
source 'https://rubygems.org'
git_source(:github) do |repo_name|
"https://github.com/#{repo_name}.git"
@@ -9,15 +11,16 @@ group :benchmark, :test do
gem 'benchmark-ips'
gem 'memory_profiler'
install_if -> { RUBY_PLATFORM !~ /mingw|mswin|java/ } do
install_if -> { RUBY_PLATFORM !~ /mingw|mswin|java/ && RUBY_ENGINE != 'truffleruby' } do
gem 'stackprof'
end
end
group :test do
gem 'rubocop', '~> 0.53.0'
gem 'rubocop', '~> 0.74.0', require: false
gem 'rubocop-performance', require: false
platform :mri do
gem 'liquid-c', github: 'Shopify/liquid-c', ref: '9168659de45d6d576fce30c735f857e597fa26f6'
platform :mri, :truffleruby do
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

@@ -1,29 +1,33 @@
# frozen_string_literal: true
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'
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
end
desc 'run test suite with warn error mode'
desc('run test suite with warn error mode')
task :warn_test do
ENV['LIQUID_PARSER_MODE'] = 'warn'
Rake::Task['base_test'].invoke
end
task :rubocop do
require 'rubocop/rake_task'
RuboCop::RakeTask.new
if RUBY_ENGINE == 'ruby'
require 'rubocop/rake_task'
RuboCop::RakeTask.new
end
end
desc 'runs test suite with both strict and lax parsers'
desc('runs test suite with both strict and lax parsers')
task :test do
ENV['LIQUID_PARSER_MODE'] = 'lax'
Rake::Task['base_test'].invoke
@@ -32,8 +36,9 @@ task :test do
Rake::Task['base_test'].reenable
Rake::Task['base_test'].invoke
if RUBY_ENGINE == 'ruby'
ENV['LIQUID-C'] = '1'
if RUBY_ENGINE == 'ruby' || RUBY_ENGINE == 'truffleruby'
ENV['LIQUID_C'] = '1'
ENV['LIQUID_PARSER_MODE'] = 'lax'
Rake::Task['base_test'].reenable
@@ -45,9 +50,9 @@ task :test do
end
end
task gem: :build
task(gem: :build)
task :build do
system "gem build liquid.gemspec"
system 'gem build liquid.gemspec'
end
task install: :build do
@@ -56,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

@@ -1,6 +1,8 @@
# frozen_string_literal: true
module ProductsFilter
def price(integer)
sprintf("$%.2d USD", integer / 100.0)
format('$%.2d USD', integer / 100.0)
end
def prettyprint(text)
@@ -28,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,23 +1,28 @@
# 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)
def handle(_type, req, res)
@request = req
@response = res
@request.path_info =~ /(\w+)\z/
@action = $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])
end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'webrick'
require 'rexml/document'
@@ -8,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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
# Copyright (c) 2005 Tobias Luetke
#
# Permission is hereby granted, free of charge, to any person obtaining
@@ -20,31 +22,31 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
module Liquid
FilterSeparator = /\|/
ArgumentSeparator = ','.freeze
FilterArgumentSeparator = ':'.freeze
VariableAttributeSeparator = '.'.freeze
WhitespaceControl = '-'.freeze
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'

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
class Block < Tag
MAX_DEPTH = 100
@@ -13,6 +15,7 @@ module Liquid
end
end
# For backwards compatibility
def render(context)
@body.render(context)
end
@@ -26,16 +29,16 @@ module Liquid
end
def unknown_tag(tag, _params, _tokens)
if tag == 'else'.freeze
raise SyntaxError.new(parse_context.locale.t("errors.syntax.unexpected_else".freeze,
block_name: block_name))
elsif tag.start_with?('end'.freeze)
raise SyntaxError.new(parse_context.locale.t("errors.syntax.invalid_delimiter".freeze,
tag: tag,
block_name: block_name,
block_delimiter: block_delimiter))
if tag == 'else'
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)
else
raise SyntaxError.new(parse_context.locale.t("errors.syntax.unknown_tag".freeze, tag: tag))
raise SyntaxError, parse_context.locale.t('errors.syntax.unknown_tag', tag: tag)
end
end
@@ -50,18 +53,15 @@ module Liquid
protected
def parse_body(body, tokens)
if parse_context.depth >= MAX_DEPTH
raise StackLevelError, "Nesting too deep".freeze
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.new(parse_context.locale.t("errors.syntax.tag_never_closed".freeze, 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

@@ -1,10 +1,12 @@
# frozen_string_literal: true
module Liquid
class BlockBody
FullToken = /\A#{TagStart}#{WhitespaceControl}?\s*(\w+)\s*(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om
WhitespaceOrNothing = /\A\s*\z/
TAGSTART = "{%".freeze
VARSTART = "{{".freeze
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
@@ -15,36 +17,34 @@ module Liquid
def parse(tokenizer, parse_context)
parse_context.line_number = tokenizer.line_number
while token = tokenizer.shift
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 = $1
markup = $2
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]
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
@@ -53,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?
@@ -67,19 +65,23 @@ module Liquid
end
def render(context)
output = []
render_to_output_buffer(context, +'')
end
def render_to_output_buffer(context, output)
context.resource_limits.render_score += @nodelist.length
idx = 0
while node = @nodelist[idx]
while (node = @nodelist[idx])
previous_output_size = output.bytesize
case node
when String
check_resources(context, node)
output << node
when Variable
render_node_to_output(node, output, context)
render_node(context, output, node)
when Block
render_node_to_output(node, output, context, node.blank?)
render_node(context, node.blank? ? +'' : output, node)
break if context.interrupt? # might have happened in a for-block
when Continue, Break
# If we get an Interrupt that means the block must stop processing. An
@@ -88,40 +90,37 @@ module Liquid
context.push_interrupt(node.interrupt)
break
else # Other non-Block tags
render_node_to_output(node, output, context)
render_node(context, output, node)
break if context.interrupt? # might have happened through an include
end
idx += 1
raise_if_resource_limits_reached(context, output.bytesize - previous_output_size)
end
output.join
output
end
private
def render_node_to_output(node, output, context, skip_output = false)
node_output = node.render(context)
node_output = node_output.is_a?(Array) ? node_output.join : node_output.to_s
check_resources(context, node_output)
output << node_output unless skip_output
rescue MemoryError => e
raise e
def render_node(context, output, node)
node.render_to_output_buffer(context, output)
rescue UndefinedVariable, UndefinedDropMethod, UndefinedFilter => e
context.handle_error(e, node.line_number)
output << nil
rescue ::StandardError => e
line_number = node.is_a?(String) ? nil : node.line_number
output << context.handle_error(e, line_number)
end
def check_resources(context, node_output)
context.resource_limits.render_length += node_output.bytesize
def raise_if_resource_limits_reached(context, length)
context.resource_limits.render_length += length
return unless context.resource_limits.reached?
raise MemoryError.new("Memory limits exceeded".freeze)
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
@@ -129,11 +128,11 @@ module Liquid
end
def raise_missing_tag_terminator(token, parse_context)
raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_termination".freeze, 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.new(parse_context.locale.t("errors.syntax.variable_termination".freeze, 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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
# Container for liquid nodes which conveniently wraps decision making logic
#
@@ -7,26 +9,26 @@ module Liquid
# c.evaluate #=> true
#
class Condition #:nodoc:
@@operators = {
'=='.freeze => ->(cond, left, right) { cond.send(:equal_variables, left, right) },
'!='.freeze => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },
'<>'.freeze => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },
'<'.freeze => :<,
'>'.freeze => :>,
'>='.freeze => :>=,
'<='.freeze => :<=,
'contains'.freeze => lambda do |cond, left, right|
@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) },
'<' => :<,
'>' => :>,
'>=' => :>=,
'<=' => :<=,
'contains' => lambda do |_cond, left, right|
if left && right && left.respond_to?(:include?)
right = right.to_s if left.is_a?(String)
left.include?(right)
else
false
end
end
end,
}
def self.operators
@@operators
class << self
attr_accessor :operators
end
attr_reader :attachment, :child_condition
@@ -36,7 +38,7 @@ module Liquid
@left = left
@operator = operator
@right = right
@child_relation = nil
@child_relation = nil
@child_condition = nil
end
@@ -78,7 +80,7 @@ module Liquid
end
def inspect
"#<Condition #{[@left, @operator, @right].compact.join(' '.freeze)}>"
"#<Condition #{[@left, @operator, @right].compact.join(' ')}>"
end
protected
@@ -116,7 +118,7 @@ module Liquid
left = context.evaluate(left)
right = context.evaluate(right)
operation = self.class.operators[op] || raise(Liquid::ArgumentError.new("Unknown operator #{op}"))
operation = self.class.operators[op] || raise(Liquid::ArgumentError, "Unknown operator #{op}")
if operation.respond_to?(:call)
operation.call(self, left, right)
@@ -124,7 +126,7 @@ module Liquid
begin
left.send(operation, right)
rescue ::ArgumentError => e
raise Liquid::ArgumentError.new(e.message)
raise Liquid::ArgumentError, e.message
end
end
end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
# Context keeps the variable stack and resolves variables, as well as keywords
#
@@ -28,9 +30,7 @@ module Liquid
@this_stack_used = false
self.exception_renderer = Template.default_exception_renderer
if rethrow_errors
self.exception_renderer = ->(e) { raise }
end
self.exception_renderer = ->(_e) { raise } if rethrow_errors
@interrupts = []
@filters = []
@@ -89,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)
raise StackLevelError, "Nesting too deep".freeze if @scopes.length > Block::MAX_DEPTH
raise StackLevelError, 'Nesting too deep' if @scopes.length > Block::MAX_DEPTH
end
# Merge a hash of variables in the current local scope
@@ -100,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
@@ -190,14 +191,12 @@ module Liquid
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]
if value.is_a?(Proc) && obj.respond_to?(:[]=)
obj[key] = (value.arity == 0) ? value.call : value.call(self)
obj[key] = value.arity == 0 ? value.call : value.call(self)
else
value
end
@@ -208,8 +207,8 @@ module Liquid
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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
class Document < BlockBody
def self.parse(tokens, parse_context)
@@ -7,7 +9,7 @@ module Liquid
end
def parse(tokens, parse_context)
super do |end_tag_name, end_tag_params|
super do |end_tag_name, _end_tag_params|
unknown_tag(end_tag_name, parse_context) if end_tag_name
end
rescue SyntaxError => e
@@ -17,10 +19,10 @@ module Liquid
def unknown_tag(tag, parse_context)
case tag
when 'else'.freeze, 'end'.freeze
raise SyntaxError.new(parse_context.locale.t("errors.syntax.unexpected_outer_tag".freeze, tag: tag))
when 'else', 'end'
raise SyntaxError, parse_context.locale.t('errors.syntax.unexpected_outer_tag', tag: tag)
else
raise SyntaxError.new(parse_context.locale.t("errors.syntax.unknown_tag".freeze, tag: tag))
raise SyntaxError, parse_context.locale.t('errors.syntax.unknown_tag', tag: tag)
end
end
end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'set'
module Liquid
@@ -25,7 +27,8 @@ module Liquid
# Catch all for the method
def liquid_method_missing(method)
return nil unless @context && @context.strict_variables
return nil unless @context&.strict_variables
raise Liquid::UndefinedDropMethod, "undefined method #{method}"
end
@@ -67,7 +70,7 @@ module Liquid
if include?(Enumerable)
blacklist += Enumerable.public_instance_methods
blacklist -= [:sort, :count, :first, :min, :max, :include?]
blacklist -= %i[sort count first min max include?]
end
whitelist = [:to_liquid] + (public_instance_methods - blacklist)

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
class Error < ::StandardError
attr_accessor :line_number
@@ -5,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
@@ -20,20 +22,20 @@ module Liquid
private
def message_prefix
str = ""
if is_a?(SyntaxError)
str << "Liquid syntax error"
str = +''
str << if is_a?(SyntaxError)
'Liquid syntax error'
else
str << "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

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
class Expression
class MethodLiteral
@@ -14,18 +16,18 @@ module Liquid
end
LITERALS = {
nil => nil, 'nil'.freeze => nil, 'null'.freeze => nil, ''.freeze => nil,
'true'.freeze => true,
'false'.freeze => false,
'blank'.freeze => MethodLiteral.new(:blank?, '').freeze,
'empty'.freeze => MethodLiteral.new(:empty?, '').freeze
nil => nil, 'nil' => nil, 'null' => nil, '' => nil,
'true' => true,
'false' => false,
'blank' => MethodLiteral.new(:blank?, '').freeze,
'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)
@@ -33,13 +35,13 @@ module Liquid
else
case markup
when SINGLE_QUOTED_STRING, DOUBLE_QUOTED_STRING
$1
Regexp.last_match(1)
when INTEGERS_REGEX
$1.to_i
Regexp.last_match(1).to_i
when RANGES_REGEX
RangeLookup.parse($1, $2)
RangeLookup.parse(Regexp.last_match(1), Regexp.last_match(2))
when FLOATS_REGEX
$1.to_f
Regexp.last_match(1).to_f
else
VariableLookup.parse(markup)
end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'time'
require 'date'

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
# A Liquid file system is a way to let your templates retrieve other templates for use with the include tag.
#
@@ -15,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
@@ -44,7 +46,7 @@ module Liquid
class LocalFileSystem
attr_accessor :root
def initialize(root, pattern = "_%s.liquid".freeze)
def initialize(root, pattern = '_%s.liquid')
@root = root
@pattern = pattern
end
@@ -57,9 +59,9 @@ module Liquid
end
def full_path(template_path)
raise FileSystemError, "Illegal template name '#{template_path}'" unless template_path =~ /\A[^.\/][a-zA-Z0-9_\/]+\z/
raise FileSystemError, "Illegal template name '#{template_path}'" unless template_path =~ %r{\A[^./][a-zA-Z0-9_/]+\z}
full_path = if template_path.include?('/'.freeze)
full_path = if template_path.include?('/')
File.join(root, File.dirname(template_path), @pattern % File.basename(template_path))
else
File.join(root, @pattern % template_path)

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
class ForloopDrop < Drop
def initialize(name, length, parentloop)

View File

@@ -1,8 +1,10 @@
# frozen_string_literal: true
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)
@@ -26,13 +28,13 @@ module Liquid
def interpolate(name, vars)
name.gsub(/%\{(\w+)\}/) do
# raise TranslationError, "Undefined key #{$1} for interpolation in translation #{name}" unless vars[$1.to_sym]
(vars[$1.to_sym]).to_s
(vars[Regexp.last_match(1).to_sym]).to_s
end
end
def deep_fetch_translation(name)
name.split('.'.freeze).reduce(locale) do |level, cur|
level[cur] or raise TranslationError, "Translation for #{name} does not exist in locale #{path}"
name.split('.').reduce(locale) do |level, cur|
level[cur] || raise(TranslationError, "Translation for #{name} does not exist in locale #{path}")
end
end
end

View File

@@ -1,16 +1,18 @@
# frozen_string_literal: true
module Liquid
# An interrupt is any command that breaks processing of a block (ex: a for loop).
class Interrupt
attr_reader :message
def initialize(message = nil)
@message = message || "interrupt".freeze
@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,25 +1,27 @@
require "strscan"
# frozen_string_literal: true
require 'strscan'
module Liquid
class Lexer
SPECIALS = {
'|'.freeze => :pipe,
'.'.freeze => :dot,
':'.freeze => :colon,
','.freeze => :comma,
'['.freeze => :open_square,
']'.freeze => :close_square,
'('.freeze => :open_round,
')'.freeze => :close_round,
'?'.freeze => :question,
'-'.freeze => :dash
'|' => :pipe,
'.' => :dot,
':' => :colon,
',' => :comma,
'[' => :open_square,
']' => :close_square,
'(' => :open_round,
')' => :close_round,
'?' => :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)
@@ -31,16 +33,16 @@ module Liquid
until @ss.eos?
@ss.skip(WHITESPACE_OR_NOTHING)
break if @ss.eos?
tok = case
when t = @ss.scan(COMPARISON_OPERATOR) then [:comparison, t]
when t = @ss.scan(SINGLE_STRING_LITERAL) then [:string, t]
when t = @ss.scan(DOUBLE_STRING_LITERAL) then [:string, t]
when t = @ss.scan(NUMBER_LITERAL) then [:number, t]
when t = @ss.scan(IDENTIFIER) then [:id, t]
when t = @ss.scan(DOTDOT) then [: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
if s = SPECIALS[c]
if (s = SPECIALS[c])
[s, c]
else
raise SyntaxError, "Unexpected character #{c}"

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
class ParseContext
attr_accessor :locale, :line_number, :trim_whitespace, :depth
@@ -19,7 +21,6 @@ module Liquid
@partial = value
@options = value ? partial_options : @template_options
@error_mode = @options[:error_mode] || Template.error_mode
value
end
def partial_options
@@ -28,7 +29,7 @@ module Liquid
if dont_pass == true
{ locale: locale }
elsif dont_pass.is_a?(Array)
@template_options.reject { |k, v| dont_pass.include?(k) }
@template_options.reject { |k, _v| dont_pass.include?(k) }
else
@template_options
end

View File

@@ -28,7 +28,7 @@ module Liquid
item, new_context = @callbacks[node.class].call(node, context)
[
item,
ParseTreeVisitor.for(node, @callbacks).visit(new_context || context)
ParseTreeVisitor.for(node, @callbacks).visit(new_context || context),
]
end
end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
class Parser
def initialize(input)
@@ -12,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
@@ -25,6 +26,7 @@ module Liquid
def consume?(type)
token = @tokens[@p]
return false unless token && token[0] == type
@p += 1
token[1]
end
@@ -34,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
@@ -41,14 +44,18 @@ module Liquid
def look(type, ahead = 0)
tok = @tokens[@p + ahead]
return false unless tok
tok[0] == type
end
SINGLE_TOKEN_EXPRESSION_TYPES = %i[string number].freeze
private_constant :SINGLE_TOKEN_EXPRESSION_TYPES
def expression
token = @tokens[@p]
if token[0] == :id
variable_signature
elsif [:string, :number].include? token[0]
elsif SINGLE_TOKEN_EXPRESSION_TYPES.include?(token[0])
consume
elsif token.first == :open_round
consume
@@ -63,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 << ' '.freeze
end
str << consume << consume << ' ' if look(:id) && look(:colon, 1)
str << expression
str

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
module ParserSwitching
def parse_with_selected_parser(markup)

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'liquid/profiler/hooks'
module Liquid
@@ -97,9 +99,9 @@ module Liquid
end
def initialize
@partial_stack = ["<root>"]
@partial_stack = ['<root>']
@root_timing = Timing.new("", current_partial)
@root_timing = Timing.new('', current_partial)
@timing_stack = [@root_timing]
@render_start_at = Time.now

View File

@@ -1,23 +1,25 @@
# frozen_string_literal: true
module Liquid
class BlockBody
def render_node_with_profiling(node, output, context, skip_output = false)
def render_node_with_profiling(context, output, node)
Profiler.profile_node_render(node) do
render_node_without_profiling(node, output, context, skip_output)
render_node_without_profiling(context, output, node)
end
end
alias_method :render_node_without_profiling, :render_node_to_output
alias_method :render_node_to_output, :render_node_with_profiling
alias_method :render_node_without_profiling, :render_node
alias_method :render_node, :render_node_with_profiling
end
class Include < Tag
def render_with_profiling(context)
def render_to_output_buffer_with_profiling(context, output)
Profiler.profile_children(context.evaluate(@template_name_expr).to_s) do
render_without_profiling(context)
render_to_output_buffer_without_profiling(context, output)
end
end
alias_method :render_without_profiling, :render
alias_method :render, :render_with_profiling
alias_method :render_to_output_buffer_without_profiling, :render_to_output_buffer
alias_method :render_to_output_buffer, :render_to_output_buffer_with_profiling
end
end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
class RangeLookup
def self.parse(start_markup, end_markup)

View File

@@ -1,7 +1,9 @@
# frozen_string_literal: true
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]

View File

@@ -1,22 +1,24 @@
# frozen_string_literal: true
require 'cgi'
require 'bigdecimal'
module Liquid
module StandardFilters
HTML_ESCAPE = {
'&'.freeze => '&amp;'.freeze,
'>'.freeze => '&gt;'.freeze,
'<'.freeze => '&lt;'.freeze,
'"'.freeze => '&quot;'.freeze,
"'".freeze => '&#39;'.freeze
'&' => '&amp;',
'>' => '&gt;',
'<' => '&lt;',
'"' => '&quot;',
"'" => '&#39;',
}.freeze
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/.freeze
STRIP_HTML_BLOCKS = Regexp.union(
/<script.*?<\/script>/m,
%r{<script.*?</script>}m,
/<!--.*?-->/m,
/<style.*?<\/style>/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)
@@ -72,23 +74,25 @@ module Liquid
end
# Truncate a string down to x characters
def truncate(input, length = 50, truncate_string = "...".freeze)
def truncate(input, length = 50, truncate_string = '...')
return if input.nil?
input_str = input.to_s
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] + truncate_string_str : input_str
input_str.length > length ? input_str[0...l].concat(truncate_string_str) : input_str
end
def truncatewords(input, words = 15, truncate_string = "...".freeze)
def truncatewords(input, words = 15, truncate_string = '...')
return if input.nil?
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(" ".freeze) + 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.
@@ -113,7 +117,7 @@ module Liquid
end
def strip_html(input)
empty = ''.freeze
empty = ''
result = input.to_s.gsub(STRIP_HTML_BLOCKS, empty)
result.gsub!(STRIP_HTML_TAGS, empty)
result
@@ -121,11 +125,11 @@ module Liquid
# Remove all newlines from the string
def strip_newlines(input)
input.to_s.gsub(/\r?\n/, ''.freeze)
input.to_s.gsub(/\r?\n/, '')
end
# Join elements of the array with certain character between them
def join(input, glue = ' '.freeze)
def join(input, glue = ' ')
InputIterator.new(input).join(glue)
end
@@ -220,7 +224,7 @@ module Liquid
InputIterator.new(input).map do |e|
e = e.call if e.is_a?(Proc)
if property == "to_liquid".freeze
if property == 'to_liquid'
e
elsif e.respond_to?(:[])
r = e[property]
@@ -250,23 +254,23 @@ module Liquid
end
# Replace occurrences of a string with another
def replace(input, string, replacement = ''.freeze)
def replace(input, string, replacement = '')
input.to_s.gsub(string.to_s, replacement.to_s)
end
# Replace the first occurrences of a string with another
def replace_first(input, string, replacement = ''.freeze)
def replace_first(input, string, replacement = '')
input.to_s.sub(string.to_s, replacement.to_s)
end
# remove a substring
def remove(input, string)
input.to_s.gsub(string.to_s, ''.freeze)
input.to_s.gsub(string.to_s, '')
end
# remove the first occurrences of a substring
def remove_first(input, string)
input.to_s.sub(string.to_s, ''.freeze)
input.to_s.sub(string.to_s, '')
end
# add one string to another
@@ -275,9 +279,8 @@ module Liquid
end
def concat(input, array)
unless array.respond_to?(:to_ary)
raise ArgumentError.new("concat filter requires an array argument")
end
raise ArgumentError, 'concat filter requires an array argument' unless array.respond_to?(:to_ary)
InputIterator.new(input).concat(array)
end
@@ -288,7 +291,7 @@ module Liquid
# Add <br /> tags in front of all newlines in input string
def newline_to_br(input)
input.to_s.gsub(/\n/, "<br />\n".freeze)
input.to_s.gsub(/\n/, "<br />\n")
end
# Reformat a date using Ruby's core Time#strftime( string ) -> string
@@ -325,7 +328,7 @@ module Liquid
def date(input, format)
return input if format.to_s.empty?
return input unless date = Utils.to_date(input)
return input unless (date = Utils.to_date(input))
date.strftime(format.to_s)
end
@@ -419,7 +422,7 @@ module Liquid
result.is_a?(BigDecimal) ? result.to_f : result
end
def default(input, default_value = ''.freeze)
def default(input, default_value = '')
if !input || input.respond_to?(:empty?) && input.empty?
default_value
else
@@ -430,7 +433,7 @@ module Liquid
private
def raise_property_error(property)
raise Liquid::ArgumentError.new("cannot select the property '#{property}'")
raise Liquid::ArgumentError, "cannot select the property '#{property}'"
end
def apply_operation(input, operand, operation)

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'set'
module Liquid
@@ -7,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
@@ -22,12 +24,14 @@ 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 self.include?(filter)
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?
raise MethodOverrideError, "Filter overrides registered public methods as non public: #{invokable_non_public_methods.join(', ')}"
@@ -39,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)
@@ -48,13 +52,13 @@ 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)
if self.class.invokable?(method)
send(method, *args)
elsif @context && @context.strict_filters
elsif @context&.strict_filters
raise Liquid::UndefinedFilter, "undefined filter #{method}"
else
args.first

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
class TablerowloopDrop < Drop
def initialize(length, cols)

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
class Tag
attr_reader :nodelist, :tag_name, :line_number, :parse_context
@@ -21,8 +23,7 @@ module Liquid
@line_number = parse_context.line_number
end
def parse(_tokens)
end
def parse(_tokens); end
def raw
"#{@tag_name} #{@markup}"
@@ -33,7 +34,15 @@ module Liquid
end
def render(_context)
''.freeze
''
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.
def render_to_output_buffer(context, output)
output << render(context)
output
end
def blank?

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
# Assign sets a variable in your template.
#
@@ -8,25 +10,25 @@ module Liquid
# {{ foo }}
#
class Assign < Tag
Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om
SYNTAX = /(#{VARIABLE_SIGNATURE}+)\s*=\s*(.*)\s*/om.freeze
attr_reader :to, :from
def initialize(tag_name, markup, options)
super
if markup =~ Syntax
@to = $1
@from = Variable.new($2, options)
if markup =~ SYNTAX
@to = Regexp.last_match(1)
@from = Variable.new(Regexp.last_match(2), options)
else
raise SyntaxError.new options[:locale].t("errors.syntax.assign".freeze)
raise SyntaxError, options[:locale].t('errors.syntax.assign')
end
end
def render(context)
def render_to_output_buffer(context, output)
val = @from.render(context)
context.scopes.last[@to] = val
context.resource_limits.assign_score += assign_score_of(val)
''.freeze
output
end
def blank?
@@ -55,5 +57,5 @@ module Liquid
end
end
Template.register_tag('assign'.freeze, Assign)
Template.register_tag('assign', Assign)
end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
# Break tag to be used to break out of a for loop.
#
@@ -14,5 +16,5 @@ module Liquid
end
end
Template.register_tag('break'.freeze, Break)
Template.register_tag('break', Break)
end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
# Capture stores the result of a block into a variable without rendering it inplace.
#
@@ -11,22 +13,23 @@ 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
@to = $1
if markup =~ SYNTAX
@to = Regexp.last_match(1)
else
raise SyntaxError.new(options[:locale].t("errors.syntax.capture"))
raise SyntaxError, options[:locale].t('errors.syntax.capture')
end
end
def render(context)
output = super
def render_to_output_buffer(context, output)
previous_output_size = output.bytesize
super
context.scopes.last[@to] = output
context.resource_limits.assign_score += output.bytesize
''.freeze
context.resource_limits.assign_score += (output.bytesize - previous_output_size)
output
end
def blank?
@@ -34,5 +37,5 @@ module Liquid
end
end
Template.register_tag('capture'.freeze, Capture)
Template.register_tag('capture', Capture)
end

View File

@@ -1,7 +1,9 @@
# frozen_string_literal: true
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
@@ -9,18 +11,16 @@ module Liquid
super
@blocks = []
if markup =~ Syntax
@left = Expression.parse($1)
if markup =~ SYNTAX
@left = Expression.parse(Regexp.last_match(1))
else
raise SyntaxError.new(options[:locale].t("errors.syntax.case".freeze))
raise SyntaxError, options[:locale].t('errors.syntax.case')
end
end
def parse(tokens)
body = BlockBody.new
while parse_body(body, tokens)
body = @blocks.last.attachment
end
body = @blocks.last.attachment while parse_body(body, tokens)
end
def nodelist
@@ -29,30 +29,30 @@ module Liquid
def unknown_tag(tag, markup, tokens)
case tag
when 'when'.freeze
when 'when'
record_when_condition(markup)
when 'else'.freeze
when 'else'
record_else_condition(markup)
else
super
end
end
def render(context)
def render_to_output_buffer(context, output)
context.stack do
execute_else_block = true
output = ''
@blocks.each do |block|
if block.else?
return block.attachment.render(context) if execute_else_block
block.attachment.render_to_output_buffer(context, output) if execute_else_block
elsif block.evaluate(context)
execute_else_block = false
output << block.attachment.render(context)
block.attachment.render_to_output_buffer(context, output)
end
end
output
end
output
end
private
@@ -61,22 +61,18 @@ module Liquid
body = BlockBody.new
while markup
unless markup =~ WhenSyntax
raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_when".freeze))
end
raise SyntaxError, options[:locale].t('errors.syntax.case_invalid_when') unless markup =~ WHEN_SYNTAX
markup = $2
markup = Regexp.last_match(2)
block = Condition.new(@left, '=='.freeze, Expression.parse($1))
block = Condition.new(@left, '==', Expression.parse(Regexp.last_match(1)))
block.attach(body)
@blocks << block
end
end
def record_else_condition(markup)
unless markup.strip.empty?
raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_else".freeze))
end
raise SyntaxError, options[:locale].t('errors.syntax.case_invalid_else') unless markup.strip.empty?
block = ElseCondition.new
block.attach(BlockBody.new)
@@ -90,5 +86,5 @@ module Liquid
end
end
Template.register_tag('case'.freeze, Case)
Template.register_tag('case', Case)
end

View File

@@ -1,16 +1,17 @@
# frozen_string_literal: true
module Liquid
class Comment < Block
def render(_context)
''.freeze
def render_to_output_buffer(_context, output)
output
end
def unknown_tag(_tag, _markup, _tokens)
end
def unknown_tag(_tag, _markup, _tokens); end
def blank?
true
end
end
Template.register_tag('comment'.freeze, Comment)
Template.register_tag('comment', Comment)
end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
# Continue tag to be used to break out of a for loop.
#
@@ -14,5 +16,5 @@ module Liquid
end
end
Template.register_tag('continue'.freeze, Continue)
Template.register_tag('continue', Continue)
end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
# Cycle is usually used within a loop to alternate between values, like colors or DOM classes.
#
@@ -12,45 +14,56 @@ 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
@variables = variables_from_string($2)
@name = Expression.parse($1)
when SimpleSyntax
when NAMED_SYNTAX
@variables = variables_from_string(Regexp.last_match(2))
@name = Expression.parse(Regexp.last_match(1))
when SIMPLE_SYNTAX
@variables = variables_from_string(markup)
@name = @variables.to_s
else
raise SyntaxError.new(options[:locale].t("errors.syntax.cycle".freeze))
raise SyntaxError, options[:locale].t('errors.syntax.cycle')
end
end
def render(context)
def render_to_output_buffer(context, output)
context.registers[:cycle] ||= {}
context.stack do
key = context.evaluate(@name)
iteration = context.registers[:cycle][key].to_i
result = 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
end
output << val
iteration += 1
iteration = 0 if iteration >= @variables.size
context.registers[:cycle][key] = iteration
result
end
output
end
private
def variables_from_string(markup)
markup.split(',').collect do |var|
var =~ /\s*(#{QuotedFragment})\s*/o
$1 ? Expression.parse($1) : nil
var =~ /\s*(#{QUOTED_FRAGMENT})\s*/o
Regexp.last_match(1) ? Expression.parse(Regexp.last_match(1)) : nil
end.compact
end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
# decrement is used in a place where one needs to insert a counter
# into a template, and needs the counter to survive across
@@ -23,13 +25,14 @@ module Liquid
@variable = markup.strip
end
def render(context)
def render_to_output_buffer(context, output)
value = context.environments.first[@variable] ||= 0
value -= 1
context.environments.first[@variable] = value
value.to_s
output << value.to_s
output
end
end
Template.register_tag('decrement'.freeze, Decrement)
Template.register_tag('decrement', Decrement)
end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
# "For" iterates over an array or collection.
# Several useful variables are available to you within the loop.
@@ -44,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
@@ -58,6 +60,7 @@ module Liquid
def parse(tokens)
return unless parse_body(@for_block, tokens)
parse_body(@else_block, tokens)
end
@@ -66,50 +69,55 @@ module Liquid
end
def unknown_tag(tag, markup, tokens)
return super unless tag == 'else'.freeze
return super unless tag == 'else'
@else_block = BlockBody.new
end
def render(context)
def render_to_output_buffer(context, output)
segment = collection_segment(context)
if segment.empty?
render_else(context)
render_else(context, output)
else
render_segment(context, segment)
render_segment(context, output, segment)
end
output
end
protected
def lax_parse(markup)
if markup =~ Syntax
@variable_name = $1
collection_name = $2
@reversed = !!$3
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.new(options[:locale].t("errors.syntax.for".freeze))
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.new(options[:locale].t("errors.syntax.for_invalid_in".freeze)) unless p.id?('in'.freeze)
raise SyntaxError, options[:locale].t('errors.syntax.for_invalid_in') unless p.id?('in')
collection_name = p.expression
@name = "#{@variable_name}-#{collection_name}"
@collection_name = Expression.parse(collection_name)
@reversed = p.id?('reversed'.freeze)
@reversed = p.id?('reversed')
while p.look(:id) && p.look(:colon, 1)
unless attribute = p.id?('limit'.freeze) || p.id?('offset'.freeze)
raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_attribute".freeze))
unless (attribute = p.id?('limit') || p.id?('offset'))
raise SyntaxError, options[:locale].t('errors.syntax.for_invalid_attribute')
end
p.consume
set_attribute(attribute, p.expression)
end
@@ -150,55 +158,57 @@ module Liquid
segment
end
def render_segment(context, segment)
def render_segment(context, output, segment)
for_stack = context.registers[:for_stack] ||= []
length = segment.length
result = ''
context.stack do
loop_vars = Liquid::ForloopDrop.new(@name, length, for_stack[-1])
for_stack.push(loop_vars)
begin
context['forloop'.freeze] = loop_vars
context['forloop'] = loop_vars
segment.each do |item|
context[@variable_name] = item
result << @for_block.render(context)
@for_block.render_to_output_buffer(context, output)
loop_vars.send(:increment!)
# Handle any interrupts if they exist.
if context.interrupt?
interrupt = context.pop_interrupt
break if interrupt.is_a? BreakInterrupt
next if interrupt.is_a? ContinueInterrupt
end
next unless context.interrupt?
interrupt = context.pop_interrupt
break if interrupt.is_a?(BreakInterrupt)
next if interrupt.is_a?(ContinueInterrupt)
end
ensure
for_stack.pop
end
end
result
output
end
def set_attribute(key, expr)
case key
when 'offset'.freeze
@from = if expr == 'continue'.freeze
when 'offset'
@from = if expr == 'continue'
:continue
else
Expression.parse(expr)
end
when 'limit'.freeze
when 'limit'
@limit = Expression.parse(expr)
end
end
def render_else(context)
@else_block ? @else_block.render(context) : ''.freeze
def render_else(context, output)
if @else_block
@else_block.render_to_output_buffer(context, output)
else
output
end
end
class ParseTreeVisitor < Liquid::ParseTreeVisitor
@@ -208,5 +218,5 @@ module Liquid
end
end
Template.register_tag('for'.freeze, For)
Template.register_tag('for', For)
end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
# If is the conditional block
#
@@ -10,16 +12,16 @@ 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
def initialize(tag_name, markup, options)
super
@blocks = []
push_block('if'.freeze, markup)
push_block('if', markup)
end
def nodelist
@@ -32,28 +34,27 @@ module Liquid
end
def unknown_tag(tag, markup, tokens)
if ['elsif'.freeze, 'else'.freeze].include?(tag)
if %w[elsif else].include?(tag)
push_block(tag, markup)
else
super
end
end
def render(context)
def render_to_output_buffer(context, output)
context.stack do
@blocks.each do |block|
if block.evaluate(context)
return block.attachment.render(context)
end
return block.attachment.render_to_output_buffer(context, output) if block.evaluate(context)
end
''.freeze
end
output
end
private
def push_block(tag, markup)
block = if tag == 'else'.freeze
block = if tag == 'else'
ElseCondition.new
else
parse_with_selected_parser(markup)
@@ -64,18 +65,19 @@ module Liquid
end
def lax_parse(markup)
expressions = markup.scan(ExpressionsAndOperators)
raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) 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($1), $2, Expression.parse($3))
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.new(options[:locale].t("errors.syntax.if".freeze))) 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)
new_condition = Condition.new(Expression.parse($1), $2, Expression.parse($3))
raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless BOOLEAN_OPERATORS.include?(operator)
new_condition.send(operator, condition)
condition = new_condition
end
@@ -93,7 +95,7 @@ module Liquid
def parse_binary_comparisons(p)
condition = parse_comparison(p)
first_condition = condition
while op = (p.id?('and'.freeze) || p.id?('or'.freeze))
while (op = (p.id?('and') || p.id?('or')))
child_condition = parse_comparison(p)
condition.send(op, child_condition)
condition = child_condition
@@ -103,7 +105,7 @@ module Liquid
def parse_comparison(p)
a = Expression.parse(p.expression)
if op = p.consume?(:comparison)
if (op = p.consume?(:comparison))
b = Expression.parse(p.expression)
Condition.new(a, op, b)
else
@@ -118,5 +120,5 @@ module Liquid
end
end
Template.register_tag('if'.freeze, If)
Template.register_tag('if', If)
end

View File

@@ -1,18 +1,21 @@
# frozen_string_literal: true
module Liquid
class Ifchanged < Block
def render(context)
def render_to_output_buffer(context, output)
context.stack do
output = super
block_output = +''
super(context, block_output)
if output != context.registers[:ifchanged]
context.registers[:ifchanged] = output
output
else
''.freeze
if block_output != context.registers[:ifchanged]
context.registers[:ifchanged] = block_output
output << block_output
end
end
output
end
end
Template.register_tag('ifchanged'.freeze, Ifchanged)
Template.register_tag('ifchanged', Ifchanged)
end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
# Include allows templates to relate with other templates
#
@@ -14,40 +16,39 @@ module Liquid
# {% include 'product' for products %}
#
class Include < Tag
Syntax = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?/o
SYNTAX = /(#{QUOTED_FRAGMENT}+)(\s+(?:with|for)\s+(#{QUOTED_FRAGMENT}+))?/o.freeze
attr_reader :template_name_expr, :variable_name_expr, :attributes
def initialize(tag_name, markup, options)
super
if markup =~ Syntax
if markup =~ SYNTAX
template_name = $1
variable_name = $3
template_name = Regexp.last_match(1)
variable_name = Regexp.last_match(3)
@variable_name_expr = variable_name ? Expression.parse(variable_name) : nil
@template_name_expr = Expression.parse(template_name)
@attributes = {}
markup.scan(TagAttributes) do |key, value|
markup.scan(TAG_ATTRIBUTES) do |key, value|
@attributes[key] = Expression.parse(value)
end
else
raise SyntaxError.new(options[:locale].t("errors.syntax.include".freeze))
raise SyntaxError, options[:locale].t('errors.syntax.include')
end
end
def parse(_tokens)
end
def parse(_tokens); end
def render(context)
def render_to_output_buffer(context, output)
template_name = context.evaluate(@template_name_expr)
raise ArgumentError.new(options[:locale].t("errors.argument.include")) unless template_name
raise ArgumentError, options[:locale].t('errors.argument.include') unless template_name
partial = load_cached_partial(template_name, context)
context_variable_name = template_name.split('/'.freeze).last
context_variable_name = template_name.split('/').last
variable = if @variable_name_expr
context.evaluate(@variable_name_expr)
@@ -66,19 +67,21 @@ module Liquid
end
if variable.is_a?(Array)
variable.collect do |var|
variable.each do |var|
context[context_variable_name] = var
partial.render(context)
partial.render_to_output_buffer(context, output)
end
else
context[context_variable_name] = variable
partial.render(context)
partial.render_to_output_buffer(context, output)
end
end
ensure
context.template_name = old_template_name
context.partial = old_partial
end
output
end
private
@@ -89,9 +92,10 @@ module Liquid
def load_cached_partial(template_name, context)
cached_partials = context.registers[:cached_partials] || {}
if cached = cached_partials[template_name]
if (cached = cached_partials[template_name])
return cached
end
source = read_template_from_file_system(context)
begin
parse_context.partial = true
@@ -114,11 +118,11 @@ module Liquid
def children
[
@node.template_name_expr,
@node.variable_name_expr
@node.variable_name_expr,
] + @node.attributes.values
end
end
end
Template.register_tag('include'.freeze, Include)
Template.register_tag('include', Include)
end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
# increment is used in a place where one needs to insert a counter
# into a template, and needs the counter to survive across
@@ -20,12 +22,13 @@ module Liquid
@variable = markup.strip
end
def render(context)
def render_to_output_buffer(context, output)
value = context.environments.first[@variable] ||= 0
context.environments.first[@variable] = value + 1
value.to_s
output << value.to_s
output
end
end
Template.register_tag('increment'.freeze, Increment)
Template.register_tag('increment', Increment)
end

View File

@@ -1,7 +1,9 @@
# frozen_string_literal: true
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
@@ -10,20 +12,21 @@ module Liquid
end
def parse(tokens)
@body = ''
while token = tokens.shift
if token =~ FullTokenPossiblyInvalid
@body << $1 if $1 != "".freeze
return if block_delimiter == $2
@body = +''
while (token = tokens.shift)
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.new(parse_context.locale.t("errors.syntax.tag_never_closed".freeze, block_name: block_name))
raise SyntaxError, parse_context.locale.t('errors.syntax.tag_never_closed', block_name: block_name)
end
def render(_context)
@body
def render_to_output_buffer(_context, output)
output << @body
output
end
def nodelist
@@ -37,11 +40,9 @@ module Liquid
protected
def ensure_valid_markup(tag_name, markup, parse_context)
unless markup =~ Syntax
raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_unexpected_args".freeze, tag: tag_name))
end
raise SyntaxError, parse_context.locale.t('errors.syntax.tag_unexpected_args', tag: tag_name) unless markup =~ SYNTAX
end
end
Template.register_tag('raw'.freeze, Raw)
Template.register_tag('raw', Raw)
end

View File

@@ -1,54 +1,57 @@
# frozen_string_literal: true
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 = $1
@collection_name = Expression.parse($2)
if markup =~ SYNTAX
@variable_name = Regexp.last_match(1)
@collection_name = Expression.parse(Regexp.last_match(2))
@attributes = {}
markup.scan(TagAttributes) do |key, value|
markup.scan(TAG_ATTRIBUTES) do |key, value|
@attributes[key] = Expression.parse(value)
end
else
raise SyntaxError.new(options[:locale].t("errors.syntax.table_row".freeze))
raise SyntaxError, options[:locale].t('errors.syntax.table_row')
end
end
def render(context)
collection = context.evaluate(@collection_name) or return ''.freeze
def render_to_output_buffer(context, output)
(collection = context.evaluate(@collection_name)) || (return '')
from = @attributes.key?('offset'.freeze) ? context.evaluate(@attributes['offset'.freeze]).to_i : 0
to = @attributes.key?('limit'.freeze) ? from + context.evaluate(@attributes['limit'.freeze]).to_i : nil
from = @attributes.key?('offset') ? context.evaluate(@attributes['offset']).to_i : 0
to = @attributes.key?('limit') ? from + context.evaluate(@attributes['limit']).to_i : nil
collection = Utils.slice_collection(collection, from, to)
length = collection.length
cols = context.evaluate(@attributes['cols'.freeze]).to_i
cols = context.evaluate(@attributes['cols']).to_i
result = "<tr class=\"row1\">\n"
output << "<tr class=\"row1\">\n"
context.stack do
tablerowloop = Liquid::TablerowloopDrop.new(length, cols)
context['tablerowloop'.freeze] = tablerowloop
context['tablerowloop'] = tablerowloop
collection.each do |item|
context[@variable_name] = item
result << "<td class=\"col#{tablerowloop.col}\">" << super << '</td>'
output << "<td class=\"col#{tablerowloop.col}\">"
super
output << '</td>'
if tablerowloop.col_last && !tablerowloop.last
result << "</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
end
result << "</tr>\n"
result
output << "</tr>\n"
output
end
class ParseTreeVisitor < Liquid::ParseTreeVisitor
@@ -58,5 +61,5 @@ module Liquid
end
end
Template.register_tag('tablerow'.freeze, TableRow)
Template.register_tag('tablerow', TableRow)
end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require_relative 'if'
module Liquid
@@ -6,25 +8,21 @@ module Liquid
# {% unless x < 0 %} x is greater than zero {% endunless %}
#
class Unless < If
def render(context)
def render_to_output_buffer(context, output)
context.stack do
# First condition is interpreted backwards ( if not )
first_block = @blocks.first
unless first_block.evaluate(context)
return first_block.attachment.render(context)
end
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(context)
end
return block.attachment.render_to_output_buffer(context, output) if block.evaluate(context)
end
''.freeze
end
output
end
end
Template.register_tag('unless'.freeze, Unless)
Template.register_tag('unless', Unless)
end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
# Templates are central to liquid.
# Interpretating templates is a two step process. First you compile the
@@ -16,7 +18,11 @@ 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
@@ -50,7 +56,7 @@ module Liquid
private
def lookup_class(name)
name.split("::").reject(&:empty?).reduce(Object) { |scope, const| scope.const_get(const) }
Object.const_get(name)
end
end
@@ -74,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
@@ -165,15 +163,13 @@ module Liquid
# filters and tags and might be useful to integrate liquid more with its host application
#
def render(*args)
return ''.freeze if @root.nil?
return '' if @root.nil?
context = case args.first
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
@@ -184,12 +180,15 @@ module Liquid
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
case args.last
when Hash
options = args.pop
output = options[:output] if options[:output]
registers.merge!(options[:registers]) if options[:registers].is_a?(Hash)
@@ -204,10 +203,9 @@ module Liquid
begin
# render the nodelist.
# for performance reasons we get an array back here. join will make a string out of it.
result = with_profiling(context) do
@root.render(context)
with_profiling(context) do
@root.render_to_output_buffer(context, output || +'')
end
result.respond_to?(:join) ? result.join : result
rescue Liquid::MemoryError => e
context.handle_error(e)
ensure
@@ -220,6 +218,10 @@ module Liquid
render(*args)
end
def render_to_output_buffer(context, output)
render(context, output: output)
end
private
def tokenize(source)

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
class Tokenizer
attr_reader :line_number
@@ -20,10 +22,10 @@ module Liquid
@source = @source.source if @source.respond_to?(:source)
return [] if @source.to_s.empty?
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] && tokens[0].empty?
tokens.shift if tokens[0]&.empty?
tokens
end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
module Utils
def self.slice_collection(collection, from, to)
@@ -19,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
@@ -35,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
@@ -50,7 +49,7 @@ module Liquid
when Numeric
obj
when String
(obj.strip =~ /\A-?\d+\.\d+\z/) ? 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
@@ -65,11 +64,12 @@ module Liquid
if obj.is_a?(String)
return nil if obj.empty?
obj = obj.downcase
end
case obj
when 'now'.freeze, 'today'.freeze
when 'now', 'today'
Time.now
when /\A\d+\z/, Integer
Time.at(obj.to_i)

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
# Holds variables. Variables are only loaded "just in time"
# and are not evaluated as part of the render stage
@@ -10,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
@@ -41,17 +43,18 @@ module Liquid
def lax_parse(markup)
@filters = []
return unless markup =~ MarkupWithQuotedFragment
return unless markup =~ MARKUP_WITH_QUOTED_FRAGMENT
name_markup = $1
filter_markup = $2
name_markup = Regexp.last_match(1)
filter_markup = Regexp.last_match(2)
@name = Expression.parse(name_markup)
if filter_markup =~ FilterMarkupRegex
filters = $1.scan(FilterParser)
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
@@ -85,19 +88,30 @@ module Liquid
end
obj = context.apply_global_filter(obj)
taint_check(context, obj)
obj
end
def render_to_output_buffer(context, output)
obj = render(context)
if obj.is_a?(Array)
output << obj.join
elsif obj.nil?
else
output << obj.to_s
end
output
end
private
def parse_filter_expressions(filter_name, unparsed_args)
filter_args = []
keyword_args = nil
unparsed_args.each do |a|
if matches = a.match(JustTagAttributes)
if (matches = a.match(JUST_TAG_ATTRIBUTES))
keyword_args ||= {}
keyword_args[matches[1]] = Expression.parse(matches[2])
else
@@ -110,7 +124,7 @@ module Liquid
end
def evaluate_filter_expressions(context, filter_args, filter_kwargs)
parsed_args = filter_args.map{ |expr| context.evaluate(expr) }
parsed_args = filter_args.map { |expr| context.evaluate(expr) }
if filter_kwargs
parsed_kwargs = {}
filter_kwargs.each do |key, expr|
@@ -125,7 +139,7 @@ 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")

View File

@@ -1,7 +1,9 @@
# frozen_string_literal: true
module Liquid
class VariableLookup
SQUARE_BRACKETED = /\A\[(.*)\]\z/m
COMMAND_METHODS = ['size'.freeze, 'first'.freeze, 'last'.freeze].freeze
SQUARE_BRACKETED = /\A\[(.*)\]\z/m.freeze
COMMAND_METHODS = %w[size first last].freeze
attr_reader :name, :lookups
@@ -10,12 +12,10 @@ 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($1)
end
name = Expression.parse(Regexp.last_match(1)) if name =~ SQUARE_BRACKETED
@name = name
@lookups = lookups
@@ -24,7 +24,7 @@ module Liquid
@lookups.each_index do |i|
lookup = lookups[i]
if lookup =~ SQUARE_BRACKETED
lookups[i] = Expression.parse($1)
lookups[i] = Expression.parse(Regexp.last_match(1))
elsif COMMAND_METHODS.include?(lookup)
@command_flags |= 1 << i
end
@@ -41,8 +41,8 @@ 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)
@@ -59,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,5 +1,5 @@
# encoding: utf-8
# frozen_string_literal: true
module Liquid
VERSION = "4.0.3".freeze
VERSION = '4.0.3'
end

View File

@@ -1,31 +1,31 @@
# encoding: utf-8
# frozen_string_literal: true
lib = File.expand_path('../lib/', __FILE__)
$LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib)
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.1.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('minitest')
s.add_development_dependency('rake', '~> 11.3')
end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'benchmark/ips'
require_relative 'theme_runner'
@@ -12,7 +14,7 @@ Benchmark.ips do |x|
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

@@ -22,5 +22,5 @@ Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
profiler = ThemeRunner.new
profile("Parsing") { profiler.compile }
profile("Rendering") { profiler.render }
profile('Parsing') { profiler.compile }
profile('Rendering') { profiler.render }

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'stackprof'
require_relative 'theme_runner'
@@ -5,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
@@ -13,12 +15,12 @@ profiler.run
end
end
if profile_type == :cpu && graph_filename = ENV['GRAPH_FILENAME']
if profile_type == :cpu && (graph_filename = ENV['GRAPH_FILENAME'])
File.open(graph_filename, 'w') do |f|
StackProf::Report.new(results).print_graphviz(nil, f)
end
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,18 +1,20 @@
# 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
@variable_name = $1
if markup =~ SYNTAX
@variable_name = Regexp.last_match(1)
@attributes = {}
else
raise SyntaxError.new("Syntax Error in 'comment_form' - Valid syntax: comment_form [article]")
raise SyntaxError, "Syntax Error in 'comment_form' - Valid syntax: comment_form [article]"
end
end
def render(context)
def render_to_output_buffer(context, output)
article = context[@variable_name]
context.stack do
@@ -20,10 +22,12 @@ class CommentForm < Liquid::Block
'posted_successfully?' => context.registers[:posted_successfully],
'errors' => context['comment.errors'],
'author' => context['comment.author'],
'email' => context['comment.email'],
'body' => context['comment.body']
'email' => context['comment.email'],
'body' => context['comment.body'],
}
wrap_in_form(article, render_all(@nodelist, context))
output << wrap_in_form(article, render_all(@nodelist, context, output))
output
end
end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'yaml'
module Database
@@ -16,8 +18,11 @@ module Database
end
# key the tables by handles, as this is how liquid expects it.
db = db.inject({}) do |assigns, (key, values)|
assigns[key] = values.inject({}) { |h, v| h[v['handle']] = v; h; }
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
@@ -29,9 +34,9 @@ module Database
db['article'] = db['blog']['articles'].first
db['cart'] = {
'total_price' => db['line_items'].values.inject(0) { |sum, item| sum += item['line_price'] * item['quantity'] },
'item_count' => db['line_items'].values.inject(0) { |sum, item| sum += item['quantity'] },
'items' => db['line_items'].values
'total_price' => db['line_items'].values.inject(0) { |sum, item| sum + item['line_price'] * item['quantity'] },
'item_count' => db['line_items'].values.inject(0) { |sum, item| sum + item['quantity'] },
'items' => db['line_items'].values,
}
db
@@ -40,6 +45,6 @@ module Database
end
if __FILE__ == $PROGRAM_NAME
p Database.tables['collections']['frontpage'].keys
p(Database.tables['collections']['frontpage'].keys)
# p Database.tables['blog']['articles']
end

View File

@@ -1,7 +1,9 @@
# frozen_string_literal: true
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

@@ -1,4 +1,6 @@
$:.unshift __dir__ + '/../../lib'
# frozen_string_literal: true
$LOAD_PATH.unshift(__dir__ + '/../../lib')
require_relative '../../lib/liquid'
require_relative 'comment_form'
@@ -9,11 +11,11 @@ require_relative 'shop_filter'
require_relative 'tag_filter'
require_relative 'weight_filter'
Liquid::Template.register_tag 'paginate', Paginate
Liquid::Template.register_tag 'form', CommentForm
Liquid::Template.register_tag('paginate', Paginate)
Liquid::Template.register_tag('form', CommentForm)
Liquid::Template.register_filter JsonFilter
Liquid::Template.register_filter MoneyFilter
Liquid::Template.register_filter WeightFilter
Liquid::Template.register_filter ShopFilter
Liquid::Template.register_filter TagFilter
Liquid::Template.register_filter(JsonFilter)
Liquid::Template.register_filter(MoneyFilter)
Liquid::Template.register_filter(WeightFilter)
Liquid::Template.register_filter(ShopFilter)
Liquid::Template.register_filter(TagFilter)

View File

@@ -1,12 +1,16 @@
# frozen_string_literal: true
module MoneyFilter
def money_with_currency(money)
return '' if money.nil?
sprintf("$ %.2f USD", money / 100.0)
format('$ %.2f USD', money / 100.0)
end
def money(money)
return '' if money.nil?
sprintf("$ %.2f", money / 100.0)
format('$ %.2f', money / 100.0)
end
private

View File

@@ -1,49 +1,51 @@
# 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
@collection_name = $1
@page_size = if $2
$3.to_i
if markup =~ SYNTAX
@collection_name = Regexp.last_match(1)
@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
raise SyntaxError.new("Syntax Error in tag 'paginate' - Valid syntax: paginate [collection] by number")
raise SyntaxError, "Syntax Error in tag 'paginate' - Valid syntax: paginate [collection] by number"
end
end
def render(context)
def render_to_output_buffer(context, output)
@context = context
context.stack do
current_page = context['current_page'].to_i
current_page = context['current_page'].to_i
pagination = {
'page_size' => @page_size,
'current_page' => 5,
'current_offset' => @page_size * 5
'page_size' => @page_size,
'current_page' => 5,
'current_offset' => @page_size * 5,
}
context['paginate'] = pagination
collection_size = context[@collection_name].size
collection_size = context[@collection_name].size
raise ArgumentError.new("Cannot paginate array '#{@collection_name}'. Not found.") if collection_size.nil?
raise ArgumentError, "Cannot paginate array '#{@collection_name}'. Not found." if collection_size.nil?
page_count = (collection_size.to_f / @page_size.to_f).to_f.ceil + 1
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'] = []
@@ -59,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
@@ -85,6 +88,6 @@ class Paginate < Liquid::Block
end
def current_url
"/collections/frontpage"
'/collections/frontpage'
end
end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module ShopFilter
def asset_url(input)
"/files/1/[shop_id]/[shop_id]/assets/#{input}"
@@ -15,15 +17,15 @@ 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
@@ -52,15 +54,13 @@ module ShopFilter
end
def product_img_url(url, style = 'small')
unless url =~ /\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'
return '/files/shops/random_number/' + url
when 'grande', 'large', 'medium', 'compact', 'small', 'thumb', 'icon'
"/files/shops/random_number/products/#{$1}_#{style}.#{$2}"
"/files/shops/random_number/products/#{Regexp.last_match(1)}_#{style}.#{Regexp.last_match(2)}"
else
raise ArgumentError, 'valid parameters for filter "size" are: original, grande, large, medium, compact, small, thumb and icon '
end
@@ -70,16 +70,14 @@ module ShopFilter
html = []
html << %(<span class="prev">#{link_to(paginate['previous']['title'], paginate['previous']['url'])}</span>) if paginate['previous']
for part in paginate['parts']
if part['is_link']
html << %(<span class="page">#{link_to(part['title'], part['url'])}</span>)
paginate['parts'].each do |part|
html << if part['is_link']
%(<span class="page">#{link_to(part['title'], part['url'])}</span>)
elsif part['title'].to_i == paginate['current_page'].to_i
html << %(<span class="page current">#{part['title']}</span>)
%(<span class="page current">#{part['title']}</span>)
else
html << %(<span class="deco">#{part['title']}</span>)
%(<span class="deco">#{part['title']}</span>)
end
end
html << %(<span class="next">#{link_to(paginate['next']['title'], paginate['next']['url'])}</span>) if paginate['next']

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module TagFilter
def link_to_tag(label, tag)
"<a title=\"Show tag #{tag}\" href=\"/collections/#{@context['handle']}/#{tag}\">#{label}</a>"
@@ -13,11 +15,11 @@ module TagFilter
def link_to_add_tag(label, tag)
tags = (@context['current_tags'] + [tag]).uniq
"<a title=\"Show tag #{tag}\" href=\"/collections/#{@context['handle']}/#{tags.join("+")}\">#{label}</a>"
"<a title=\"Show tag #{tag}\" href=\"/collections/#{@context['handle']}/#{tags.join('+')}\">#{label}</a>"
end
def link_to_remove_tag(label, tag)
tags = (@context['current_tags'] - [tag]).uniq
"<a title=\"Show tag #{tag}\" href=\"/collections/#{@context['handle']}/#{tags.join("+")}\">#{label}</a>"
"<a title=\"Show tag #{tag}\" href=\"/collections/#{@context['handle']}/#{tags.join('+')}\">#{label}</a>"
end
end

View File

@@ -1,6 +1,8 @@
# frozen_string_literal: true
module WeightFilter
def weight(grams)
sprintf("%.2f", grams / 1000)
format('%.2f', grams / 1000)
end
def weight_with_unit(grams)

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
# This profiler run simulates Shopify.
# We are looking in the tests directory for liquid files and render them within the designated layout file.
# We will also export a substantial database to liquid which the templates can render values of.
@@ -31,7 +33,7 @@ class ThemeRunner
{
liquid: File.read(test),
layout: (File.file?(theme_path) ? File.read(theme_path) : nil),
template_name: test
template_name: test,
}
end.compact

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'test_helper'
class AssignTest < Minitest::Test
@@ -10,29 +12,29 @@ class AssignTest < Minitest::Test
END_TEMPLATE
template = Template.parse(template_source)
rendered = template.render!
assert_equal "Print this-thing", rendered.strip
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

@@ -1,11 +1,12 @@
# frozen_string_literal: true
require 'test_helper'
class FoobarTag < Liquid::Tag
def render(*args)
" "
def render_to_output_buffer(_context, output)
output << ' '
output
end
Liquid::Template.register_tag('foobar', FoobarTag)
end
class BlankTestFileSystem
@@ -31,76 +32,78 @@ class BlankTest < Minitest::Test
end
def test_new_tags_are_not_blank_by_default
assert_template_result(" " * N, wrap_in_for("{% foobar %}"))
with_custom_tag('foobar', FoobarTag) do
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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'test_helper'
class BlockTest < Minitest::Test
@@ -5,7 +7,7 @@ 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"
end

View File

@@ -1,10 +1,12 @@
# frozen_string_literal: true
require 'test_helper'
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,7 +16,7 @@ class CaptureTest < Minitest::Test
END_TEMPLATE
template = Template.parse(template_source)
rendered = template.render!
assert_equal "Print this-thing", rendered.strip
assert_equal 'Print this-thing', rendered.strip
end
def test_capture_to_variable_from_outer_scope_if_existing
@@ -30,7 +32,7 @@ class CaptureTest < Minitest::Test
END_TEMPLATE
template = Template.parse(template_source)
rendered = template.render!
assert_equal "test-string", rendered.gsub(/\s/, '')
assert_equal 'test-string', rendered.gsub(/\s/, '')
end
def test_assigning_from_capture
@@ -45,6 +47,6 @@ class CaptureTest < Minitest::Test
END_TEMPLATE
template = Template.parse(template_source)
rendered = template.render!
assert_equal "3-3", rendered.gsub(/\s/, '')
assert_equal '3-3', rendered.gsub(/\s/, '')
end
end # CaptureTest

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'test_helper'
class ContextTest < Minitest::Test

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'test_helper'
class DocumentTest < Minitest::Test
@@ -5,14 +7,14 @@ 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"
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'"
end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'test_helper'
class ContextDrop < Liquid::Drop
@@ -21,7 +23,7 @@ end
class ProductDrop < Liquid::Drop
class TextDrop < Liquid::Drop
def array
['text1', 'text2']
%w[text1 text2]
end
def text
@@ -31,7 +33,7 @@ class ProductDrop < Liquid::Drop
class CatchallDrop < Liquid::Drop
def liquid_method_missing(method)
'catchall_method: ' << method.to_s
+'catchall_method: ' << method.to_s
end
end
@@ -48,13 +50,13 @@ class ProductDrop < Liquid::Drop
end
def user_input
"foo".taint
(+'foo').taint
end
protected
def callmenot
"protected"
'protected'
end
end
@@ -139,17 +141,17 @@ 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
@@ -173,12 +175,12 @@ class DropsTest < Minitest::Test
end
def test_context_drop
output = Liquid::Template.parse(' {{ context.bar }} ').render!('context' => ContextDrop.new, 'bar' => "carrot")
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")
output = Liquid::Template.parse(' {{ product.context.foo }} ').render!('product' => ProductDrop.new, 'foo' => 'monkey')
assert_equal ' monkey ', output
end
@@ -188,7 +190,7 @@ class DropsTest < Minitest::Test
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
@@ -201,9 +203,9 @@ class DropsTest < Minitest::Test
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
@@ -232,7 +234,7 @@ class DropsTest < Minitest::Test
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)
@@ -241,20 +243,20 @@ 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
@@ -267,7 +269,7 @@ class DropsTest < Minitest::Test
end
def test_default_to_s_on_drops
assert_equal 'ProductDrop', Liquid::Template.parse("{{ product }}").render!('product' => ProductDrop.new)
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

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'test_helper'
class ErrorHandlingTest < Minitest::Test
@@ -83,15 +85,14 @@ class ErrorHandlingTest < Minitest::Test
def test_with_line_numbers_adds_numbers_to_parser_errors
err = assert_raises(SyntaxError) do
Liquid::Template.parse(%q(
Liquid::Template.parse('
foobar
{% "cat" | foobar %}
bla
),
line_numbers: true
)
',
line_numbers: true)
end
assert_match(/Liquid syntax error \(line 4\)/, err.message)
@@ -99,15 +100,14 @@ class ErrorHandlingTest < Minitest::Test
def test_with_line_numbers_adds_numbers_to_parser_errors_with_whitespace_trim
err = assert_raises(SyntaxError) do
Liquid::Template.parse(%q(
Liquid::Template.parse('
foobar
{%- "cat" | foobar -%}
bla
),
line_numbers: true
)
',
line_numbers: true)
end
assert_match(/Liquid syntax error \(line 4\)/, err.message)
@@ -121,12 +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)
template.warnings.map(&:message)
end
def test_parsing_strict_with_line_numbers_adds_numbers_to_lexer_errors
@@ -138,9 +137,8 @@ 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
@@ -157,8 +155,7 @@ class ErrorHandlingTest < Minitest::Test
bla
',
line_numbers: true
)
line_numbers: true)
end
assert_equal "Liquid syntax error (line 5): Unknown tag 'foo'", err.message
@@ -205,7 +202,7 @@ class ErrorHandlingTest < Minitest::Test
def test_default_exception_renderer_with_internal_error
template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true)
output = template.render({ 'errors' => ErrorDrop.new })
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)
@@ -214,10 +211,13 @@ class ErrorHandlingTest < Minitest::Test
def test_setting_default_exception_renderer
old_exception_renderer = Liquid::Template.default_exception_renderer
exceptions = []
Liquid::Template.default_exception_renderer = ->(e) { exceptions << e; '' }
Liquid::Template.default_exception_renderer = lambda { |e|
exceptions << e
''
}
template = Liquid::Template.parse('This is a runtime error: {{ errors.argument_error }}')
output = template.render({ 'errors' => ErrorDrop.new })
output = template.render('errors' => ErrorDrop.new)
assert_equal 'This is a runtime error: ', output
assert_equal [Liquid::ArgumentError], template.errors.map(&:class)
@@ -228,7 +228,10 @@ class ErrorHandlingTest < Minitest::Test
def test_exception_renderer_exposing_non_liquid_error
template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true)
exceptions = []
handler = ->(e) { exceptions << e; e.cause }
handler = lambda { |e|
exceptions << e
e.cause
}
output = template.render({ 'errors' => ErrorDrop.new }, exception_renderer: handler)
@@ -239,8 +242,8 @@ class ErrorHandlingTest < Minitest::Test
end
class TestFileSystem
def read_template_file(template_path)
"{{ errors.argument_error }}"
def read_template_file(_template_path)
'{{ errors.argument_error }}'
end
end
@@ -255,6 +258,6 @@ class ErrorHandlingTest < Minitest::Test
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 'product', template.errors.first.template_name
end
end

View File

@@ -1,24 +1,26 @@
# frozen_string_literal: true
require 'test_helper'
module MoneyFilter
def money(input)
sprintf(' %d$ ', input)
format(' %d$ ', input)
end
def money_with_underscore(input)
sprintf(' %d$ ', input)
format(' %d$ ', input)
end
end
module CanadianMoneyFilter
def money(input)
sprintf(' %d$ CAD ', input)
format(' %d$ CAD ', input)
end
end
module SubstituteFilter
def substitute(input, params = {})
input.gsub(/%\{(\w+)\}/) { |match| params[$1] }
input.gsub(/%\{(\w+)\}/) { |_match| params[Regexp.last_match(1)] }
end
end
@@ -26,8 +28,8 @@ class FiltersTest < Minitest::Test
include Liquid
module OverrideObjectMethodFilter
def tap(input)
"tap overridden"
def tap(_input)
'tap overridden'
end
end
@@ -39,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
@@ -53,43 +55,43 @@ 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['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['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)
@@ -104,7 +106,7 @@ class FiltersTest < Minitest::Test
@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)
@@ -114,27 +116,27 @@ class FiltersTest < Minitest::Test
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
@@ -146,10 +148,10 @@ class FiltersTest < Minitest::Test
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
@@ -158,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

@@ -1,15 +1,17 @@
# frozen_string_literal: true
require 'test_helper'
class HashOrderingTest < Minitest::Test
module MoneyFilter
def money(input)
sprintf(' %d$ ', input)
format(' %d$ ', input)
end
end
module CanadianMoneyFilter
def money(input)
sprintf(' %d$ CAD ', input)
format(' %d$ CAD ', input)
end
end
@@ -17,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

@@ -1,7 +1,9 @@
# frozen_string_literal: true
require 'test_helper'
module FunnyFilter
def make_funny(input)
def make_funny(_input)
'LOL'
end
@@ -9,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
@@ -32,7 +34,7 @@ class OutputTest < Minitest::Test
def setup
@assigns = {
'best_cars' => 'bmw',
'car' => { 'bmw' => 'good', 'gm' => 'bad' }
'car' => { 'bmw' => 'good', 'gm' => 'bad' },
}
end
@@ -45,9 +47,9 @@ class OutputTest < Minitest::Test
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

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

@@ -1,41 +1,43 @@
# frozen_string_literal: true
require 'test_helper'
class ParsingQuirksTest < Minitest::Test
include Liquid
def test_parsing_css
text = " div { font-weight: bold; } "
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
@@ -51,20 +53,20 @@ 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
@@ -77,9 +79,9 @@ class ParsingQuirksTest < Minitest::Test
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
@@ -112,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,3 +1,5 @@
# frozen_string_literal: true
require 'test_helper'
class RenderProfilingTest < Minitest::Test
@@ -72,7 +74,7 @@ class RenderProfilingTest < Minitest::Test
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
@@ -89,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
@@ -99,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
@@ -114,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
@@ -128,7 +130,7 @@ class RenderProfilingTest < Minitest::Test
t.render!
timing_count = 0
t.profiler.each do |timing|
t.profiler.each do |_timing|
timing_count += 1
end
@@ -136,7 +138,7 @@ class RenderProfilingTest < Minitest::Test
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
@@ -144,8 +146,8 @@ class RenderProfilingTest < Minitest::Test
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
# Will profile each invocation of the for block

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'test_helper'
module SecurityFilter
@@ -57,22 +59,22 @@ class SecurityTest < Minitest::Test
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)
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

View File

@@ -1,4 +1,4 @@
# encoding: utf-8
# frozen_string_literal: true
require 'test_helper'
@@ -17,7 +17,7 @@ class TestThing
"woot: #{@foo}"
end
def [](whatever)
def [](_whatever)
to_s
end
@@ -29,7 +29,7 @@ end
class TestDrop < Liquid::Drop
def test
"testfoo"
'testfoo'
end
end
@@ -37,7 +37,7 @@ class TestEnumerable < Liquid::Drop
include Enumerable
def each(&block)
[ { "foo" => 1, "bar" => 2 }, { "foo" => 2, "bar" => 1 }, { "foo" => 3, "bar" => 3 } ].each(&block)
[{ 'foo' => 1, 'bar' => 2 }, { 'foo' => 2, 'bar' => 1 }, { 'foo' => 3, 'bar' => 3 }].each(&block)
end
end
@@ -65,12 +65,12 @@ class StandardFiltersTest < Minitest::Test
end
def test_downcase
assert_equal 'testing', @filters.downcase("Testing")
assert_equal 'testing', @filters.downcase('Testing')
assert_equal '', @filters.downcase(nil)
end
def test_upcase
assert_equal 'TESTING', @filters.upcase("Testing")
assert_equal 'TESTING', @filters.upcase('Testing')
assert_equal '', @filters.upcase(nil)
end
@@ -91,22 +91,22 @@ class StandardFiltersTest < Minitest::Test
@filters.slice('foobar', nil)
end
assert_raises(Liquid::ArgumentError) do
@filters.slice('foobar', 0, "")
@filters.slice('foobar', 0, '')
end
end
def test_slice_on_arrays
input = 'foobar'.split(//)
assert_equal %w(o o b), @filters.slice(input, 1, 3)
assert_equal %w(o o b a r), @filters.slice(input, 1, 1000)
assert_equal %w(), @filters.slice(input, 1, 0)
assert_equal %w(o), @filters.slice(input, 1, 1)
assert_equal %w(b a r), @filters.slice(input, 3, 3)
assert_equal %w(a r), @filters.slice(input, -2, 2)
assert_equal %w(a r), @filters.slice(input, -2, 1000)
assert_equal %w(r), @filters.slice(input, -1)
assert_equal %w(), @filters.slice(input, 100, 10)
assert_equal %w(), @filters.slice(input, -100, 10)
assert_equal %w[o o b], @filters.slice(input, 1, 3)
assert_equal %w[o o b a r], @filters.slice(input, 1, 1000)
assert_equal %w[], @filters.slice(input, 1, 0)
assert_equal %w[o], @filters.slice(input, 1, 1)
assert_equal %w[b a r], @filters.slice(input, 3, 3)
assert_equal %w[a r], @filters.slice(input, -2, 2)
assert_equal %w[a r], @filters.slice(input, -2, 1000)
assert_equal %w[r], @filters.slice(input, -1)
assert_equal %w[], @filters.slice(input, 100, 10)
assert_equal %w[], @filters.slice(input, -100, 10)
end
def test_truncate
@@ -114,16 +114,16 @@ class StandardFiltersTest < Minitest::Test
assert_equal '1234567890', @filters.truncate('1234567890', 20)
assert_equal '...', @filters.truncate('1234567890', 0)
assert_equal '1234567890', @filters.truncate('1234567890')
assert_equal "测试...", @filters.truncate("测试测试测试测试", 5)
assert_equal '12341', @filters.truncate("1234567890", 5, 1)
assert_equal '测试...', @filters.truncate('测试测试测试测试', 5)
assert_equal '12341', @filters.truncate('1234567890', 5, 1)
end
def test_split
assert_equal ['12', '34'], @filters.split('12~34', '~')
assert_equal %w[12 34], @filters.split('12~34', '~')
assert_equal ['A? ', ' ,Z'], @filters.split('A? ~ ~ ~ ,Z', '~ ~ ~')
assert_equal ['A?Z'], @filters.split('A?Z', '~')
assert_equal [], @filters.split(nil, ' ')
assert_equal ['A', 'Z'], @filters.split('A1Z', 1)
assert_equal %w[A Z], @filters.split('A1Z', 1)
end
def test_escape
@@ -169,12 +169,12 @@ class StandardFiltersTest < Minitest::Test
assert_equal 'one two...', @filters.truncatewords('one two three', 2)
assert_equal 'one two three', @filters.truncatewords('one two three')
assert_equal 'Two small (13&#8221; x 5.5&#8221; x 10&#8221; high) baskets fit inside one large basket (13&#8221;...', @filters.truncatewords('Two small (13&#8221; x 5.5&#8221; x 10&#8221; high) baskets fit inside one large basket (13&#8221; x 16&#8221; x 10.5&#8221; high) with cover.', 15)
assert_equal "测试测试测试测试", @filters.truncatewords('测试测试测试测试', 5)
assert_equal 'one two1', @filters.truncatewords("one two three", 2, 1)
assert_equal '测试测试测试测试', @filters.truncatewords('测试测试测试测试', 5)
assert_equal 'one two1', @filters.truncatewords('one two three', 2, 1)
end
def test_strip_html
assert_equal 'test', @filters.strip_html("<div>test</div>")
assert_equal 'test', @filters.strip_html('<div>test</div>')
assert_equal 'test', @filters.strip_html("<div id='test'>test</div>")
assert_equal '', @filters.strip_html("<script type='text/javascript'>document.write('some stuff');</script>")
assert_equal '', @filters.strip_html("<style type='text/css'>foo bar</style>")
@@ -183,7 +183,7 @@ class StandardFiltersTest < Minitest::Test
assert_equal '', @filters.strip_html(nil)
# Quirk of the existing implementation
assert_equal 'foo;', @filters.strip_html("<<<script </script>script>foo;</script>")
assert_equal 'foo;', @filters.strip_html('<<<script </script>script>foo;</script>')
end
def test_join
@@ -194,163 +194,163 @@ class StandardFiltersTest < Minitest::Test
def test_sort
assert_equal [1, 2, 3, 4], @filters.sort([4, 3, 2, 1])
assert_equal [{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }], @filters.sort([{ "a" => 4 }, { "a" => 3 }, { "a" => 1 }, { "a" => 2 }], "a")
assert_equal [{ 'a' => 1 }, { 'a' => 2 }, { 'a' => 3 }, { 'a' => 4 }], @filters.sort([{ 'a' => 4 }, { 'a' => 3 }, { 'a' => 1 }, { 'a' => 2 }], 'a')
end
def test_sort_with_nils
assert_equal [1, 2, 3, 4, nil], @filters.sort([nil, 4, 3, 2, 1])
assert_equal [{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }, {}], @filters.sort([{ "a" => 4 }, { "a" => 3 }, {}, { "a" => 1 }, { "a" => 2 }], "a")
assert_equal [{ 'a' => 1 }, { 'a' => 2 }, { 'a' => 3 }, { 'a' => 4 }, {}], @filters.sort([{ 'a' => 4 }, { 'a' => 3 }, {}, { 'a' => 1 }, { 'a' => 2 }], 'a')
end
def test_sort_when_property_is_sometimes_missing_puts_nils_last
input = [
{ "price" => 4, "handle" => "alpha" },
{ "handle" => "beta" },
{ "price" => 1, "handle" => "gamma" },
{ "handle" => "delta" },
{ "price" => 2, "handle" => "epsilon" }
{ 'price' => 4, 'handle' => 'alpha' },
{ 'handle' => 'beta' },
{ 'price' => 1, 'handle' => 'gamma' },
{ 'handle' => 'delta' },
{ 'price' => 2, 'handle' => 'epsilon' },
]
expectation = [
{ "price" => 1, "handle" => "gamma" },
{ "price" => 2, "handle" => "epsilon" },
{ "price" => 4, "handle" => "alpha" },
{ "handle" => "delta" },
{ "handle" => "beta" }
{ 'price' => 1, 'handle' => 'gamma' },
{ 'price' => 2, 'handle' => 'epsilon' },
{ 'price' => 4, 'handle' => 'alpha' },
{ 'handle' => 'delta' },
{ 'handle' => 'beta' },
]
assert_equal expectation, @filters.sort(input, "price")
assert_equal expectation, @filters.sort(input, 'price')
end
def test_sort_natural
assert_equal ["a", "B", "c", "D"], @filters.sort_natural(["c", "D", "a", "B"])
assert_equal [{ "a" => "a" }, { "a" => "B" }, { "a" => "c" }, { "a" => "D" }], @filters.sort_natural([{ "a" => "D" }, { "a" => "c" }, { "a" => "a" }, { "a" => "B" }], "a")
assert_equal %w[a B c D], @filters.sort_natural(%w[c D a B])
assert_equal [{ 'a' => 'a' }, { 'a' => 'B' }, { 'a' => 'c' }, { 'a' => 'D' }], @filters.sort_natural([{ 'a' => 'D' }, { 'a' => 'c' }, { 'a' => 'a' }, { 'a' => 'B' }], 'a')
end
def test_sort_natural_with_nils
assert_equal ["a", "B", "c", "D", nil], @filters.sort_natural([nil, "c", "D", "a", "B"])
assert_equal [{ "a" => "a" }, { "a" => "B" }, { "a" => "c" }, { "a" => "D" }, {}], @filters.sort_natural([{ "a" => "D" }, { "a" => "c" }, {}, { "a" => "a" }, { "a" => "B" }], "a")
assert_equal ['a', 'B', 'c', 'D', nil], @filters.sort_natural([nil, 'c', 'D', 'a', 'B'])
assert_equal [{ 'a' => 'a' }, { 'a' => 'B' }, { 'a' => 'c' }, { 'a' => 'D' }, {}], @filters.sort_natural([{ 'a' => 'D' }, { 'a' => 'c' }, {}, { 'a' => 'a' }, { 'a' => 'B' }], 'a')
end
def test_sort_natural_when_property_is_sometimes_missing_puts_nils_last
input = [
{ "price" => "4", "handle" => "alpha" },
{ "handle" => "beta" },
{ "price" => "1", "handle" => "gamma" },
{ "handle" => "delta" },
{ "price" => 2, "handle" => "epsilon" }
{ 'price' => '4', 'handle' => 'alpha' },
{ 'handle' => 'beta' },
{ 'price' => '1', 'handle' => 'gamma' },
{ 'handle' => 'delta' },
{ 'price' => 2, 'handle' => 'epsilon' },
]
expectation = [
{ "price" => "1", "handle" => "gamma" },
{ "price" => 2, "handle" => "epsilon" },
{ "price" => "4", "handle" => "alpha" },
{ "handle" => "delta" },
{ "handle" => "beta" }
{ 'price' => '1', 'handle' => 'gamma' },
{ 'price' => 2, 'handle' => 'epsilon' },
{ 'price' => '4', 'handle' => 'alpha' },
{ 'handle' => 'delta' },
{ 'handle' => 'beta' },
]
assert_equal expectation, @filters.sort_natural(input, "price")
assert_equal expectation, @filters.sort_natural(input, 'price')
end
def test_sort_natural_case_check
input = [
{ "key" => "X" },
{ "key" => "Y" },
{ "key" => "Z" },
{ "fake" => "t" },
{ "key" => "a" },
{ "key" => "b" },
{ "key" => "c" }
{ 'key' => 'X' },
{ 'key' => 'Y' },
{ 'key' => 'Z' },
{ 'fake' => 't' },
{ 'key' => 'a' },
{ 'key' => 'b' },
{ 'key' => 'c' },
]
expectation = [
{ "key" => "a" },
{ "key" => "b" },
{ "key" => "c" },
{ "key" => "X" },
{ "key" => "Y" },
{ "key" => "Z" },
{ "fake" => "t" }
{ 'key' => 'a' },
{ 'key' => 'b' },
{ 'key' => 'c' },
{ 'key' => 'X' },
{ 'key' => 'Y' },
{ 'key' => 'Z' },
{ 'fake' => 't' },
]
assert_equal expectation, @filters.sort_natural(input, "key")
assert_equal ["a", "b", "c", "X", "Y", "Z"], @filters.sort_natural(["X", "Y", "Z", "a", "b", "c"])
assert_equal expectation, @filters.sort_natural(input, 'key')
assert_equal %w[a b c X Y Z], @filters.sort_natural(%w[X Y Z a b c])
end
def test_sort_empty_array
assert_equal [], @filters.sort([], "a")
assert_equal [], @filters.sort([], 'a')
end
def test_sort_invalid_property
foo = [
[1],
[2],
[3]
[3],
]
assert_raises Liquid::ArgumentError do
@filters.sort(foo, "bar")
@filters.sort(foo, 'bar')
end
end
def test_sort_natural_empty_array
assert_equal [], @filters.sort_natural([], "a")
assert_equal [], @filters.sort_natural([], 'a')
end
def test_sort_natural_invalid_property
foo = [
[1],
[2],
[3]
[3],
]
assert_raises Liquid::ArgumentError do
@filters.sort_natural(foo, "bar")
@filters.sort_natural(foo, 'bar')
end
end
def test_legacy_sort_hash
assert_equal [{ a: 1, b: 2 }], @filters.sort({ a: 1, b: 2 })
assert_equal [{ a: 1, b: 2 }], @filters.sort(a: 1, b: 2)
end
def test_numerical_vs_lexicographical_sort
assert_equal [2, 10], @filters.sort([10, 2])
assert_equal [{ "a" => 2 }, { "a" => 10 }], @filters.sort([{ "a" => 10 }, { "a" => 2 }], "a")
assert_equal ["10", "2"], @filters.sort(["10", "2"])
assert_equal [{ "a" => "10" }, { "a" => "2" }], @filters.sort([{ "a" => "10" }, { "a" => "2" }], "a")
assert_equal [{ 'a' => 2 }, { 'a' => 10 }], @filters.sort([{ 'a' => 10 }, { 'a' => 2 }], 'a')
assert_equal %w[10 2], @filters.sort(%w[10 2])
assert_equal [{ 'a' => '10' }, { 'a' => '2' }], @filters.sort([{ 'a' => '10' }, { 'a' => '2' }], 'a')
end
def test_uniq
assert_equal ["foo"], @filters.uniq("foo")
assert_equal ['foo'], @filters.uniq('foo')
assert_equal [1, 3, 2, 4], @filters.uniq([1, 1, 3, 2, 3, 1, 4, 3, 2, 1])
assert_equal [{ "a" => 1 }, { "a" => 3 }, { "a" => 2 }], @filters.uniq([{ "a" => 1 }, { "a" => 3 }, { "a" => 1 }, { "a" => 2 }], "a")
assert_equal [{ 'a' => 1 }, { 'a' => 3 }, { 'a' => 2 }], @filters.uniq([{ 'a' => 1 }, { 'a' => 3 }, { 'a' => 1 }, { 'a' => 2 }], 'a')
testdrop = TestDrop.new
assert_equal [testdrop], @filters.uniq([testdrop, TestDrop.new], 'test')
end
def test_uniq_empty_array
assert_equal [], @filters.uniq([], "a")
assert_equal [], @filters.uniq([], 'a')
end
def test_uniq_invalid_property
foo = [
[1],
[2],
[3]
[3],
]
assert_raises Liquid::ArgumentError do
@filters.uniq(foo, "bar")
@filters.uniq(foo, 'bar')
end
end
def test_compact_empty_array
assert_equal [], @filters.compact([], "a")
assert_equal [], @filters.compact([], 'a')
end
def test_compact_invalid_property
foo = [
[1],
[2],
[3]
[3],
]
assert_raises Liquid::ArgumentError do
@filters.compact(foo, "bar")
@filters.compact(foo, 'bar')
end
end
@@ -363,71 +363,71 @@ class StandardFiltersTest < Minitest::Test
end
def test_map
assert_equal [1, 2, 3, 4], @filters.map([{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }], 'a')
assert_equal [1, 2, 3, 4], @filters.map([{ 'a' => 1 }, { 'a' => 2 }, { 'a' => 3 }, { 'a' => 4 }], 'a')
assert_template_result 'abc', "{{ ary | map:'foo' | map:'bar' }}",
'ary' => [{ 'foo' => { 'bar' => 'a' } }, { 'foo' => { 'bar' => 'b' } }, { 'foo' => { 'bar' => 'c' } }]
'ary' => [{ 'foo' => { 'bar' => 'a' } }, { 'foo' => { 'bar' => 'b' } }, { 'foo' => { 'bar' => 'c' } }]
end
def test_map_doesnt_call_arbitrary_stuff
assert_template_result "", '{{ "foo" | map: "__id__" }}'
assert_template_result "", '{{ "foo" | map: "inspect" }}'
assert_template_result '', '{{ "foo" | map: "__id__" }}'
assert_template_result '', '{{ "foo" | map: "inspect" }}'
end
def test_map_calls_to_liquid
t = TestThing.new
assert_template_result "woot: 1", '{{ foo | map: "whatever" }}', "foo" => [t]
assert_template_result 'woot: 1', '{{ foo | map: "whatever" }}', 'foo' => [t]
end
def test_map_on_hashes
assert_template_result "4217", '{{ thing | map: "foo" | map: "bar" }}',
"thing" => { "foo" => [ { "bar" => 42 }, { "bar" => 17 } ] }
assert_template_result '4217', '{{ thing | map: "foo" | map: "bar" }}',
'thing' => { 'foo' => [{ 'bar' => 42 }, { 'bar' => 17 }] }
end
def test_legacy_map_on_hashes_with_dynamic_key
template = "{% assign key = 'foo' %}{{ thing | map: key | map: 'bar' }}"
hash = { "foo" => { "bar" => 42 } }
assert_template_result "42", template, "thing" => hash
hash = { 'foo' => { 'bar' => 42 } }
assert_template_result '42', template, 'thing' => hash
end
def test_sort_calls_to_liquid
t = TestThing.new
Liquid::Template.parse('{{ foo | sort: "whatever" }}').render("foo" => [t])
assert t.foo > 0
Liquid::Template.parse('{{ foo | sort: "whatever" }}').render('foo' => [t])
assert t.foo.positive?
end
def test_map_over_proc
drop = TestDrop.new
p = proc{ drop }
p = proc { drop }
templ = '{{ procs | map: "test" }}'
assert_template_result "testfoo", templ, "procs" => [p]
assert_template_result 'testfoo', templ, 'procs' => [p]
end
def test_map_over_drops_returning_procs
drops = [
{
"proc" => ->{ "foo" },
'proc' => -> { 'foo' },
},
{
"proc" => ->{ "bar" },
'proc' => -> { 'bar' },
},
]
templ = '{{ drops | map: "proc" }}'
assert_template_result "foobar", templ, "drops" => drops
assert_template_result 'foobar', templ, 'drops' => drops
end
def test_map_works_on_enumerables
assert_template_result "123", '{{ foo | map: "foo" }}', "foo" => TestEnumerable.new
assert_template_result '123', '{{ foo | map: "foo" }}', 'foo' => TestEnumerable.new
end
def test_map_returns_empty_on_2d_input_array
foo = [
[1],
[2],
[3]
[3],
]
assert_raises Liquid::ArgumentError do
@filters.map(foo, "bar")
@filters.map(foo, 'bar')
end
end
@@ -435,7 +435,7 @@ class StandardFiltersTest < Minitest::Test
foo = [
[1],
[2],
[3]
[3],
]
assert_raises Liquid::ArgumentError do
@filters.map(foo, nil)
@@ -443,7 +443,7 @@ class StandardFiltersTest < Minitest::Test
end
def test_sort_works_on_enumerables
assert_template_result "213", '{{ foo | sort: "bar" | map: "foo" }}', "foo" => TestEnumerable.new
assert_template_result '213', '{{ foo | sort: "bar" | map: "foo" }}', 'foo' => TestEnumerable.new
end
def test_first_and_last_call_to_liquid
@@ -452,37 +452,37 @@ class StandardFiltersTest < Minitest::Test
end
def test_truncate_calls_to_liquid
assert_template_result "wo...", '{{ foo | truncate: 5 }}', "foo" => TestThing.new
assert_template_result 'wo...', '{{ foo | truncate: 5 }}', 'foo' => TestThing.new
end
def test_date
assert_equal 'May', @filters.date(Time.parse("2006-05-05 10:00:00"), "%B")
assert_equal 'June', @filters.date(Time.parse("2006-06-05 10:00:00"), "%B")
assert_equal 'July', @filters.date(Time.parse("2006-07-05 10:00:00"), "%B")
assert_equal 'May', @filters.date(Time.parse('2006-05-05 10:00:00'), '%B')
assert_equal 'June', @filters.date(Time.parse('2006-06-05 10:00:00'), '%B')
assert_equal 'July', @filters.date(Time.parse('2006-07-05 10:00:00'), '%B')
assert_equal 'May', @filters.date("2006-05-05 10:00:00", "%B")
assert_equal 'June', @filters.date("2006-06-05 10:00:00", "%B")
assert_equal 'July', @filters.date("2006-07-05 10:00:00", "%B")
assert_equal 'May', @filters.date('2006-05-05 10:00:00', '%B')
assert_equal 'June', @filters.date('2006-06-05 10:00:00', '%B')
assert_equal 'July', @filters.date('2006-07-05 10:00:00', '%B')
assert_equal '2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", "")
assert_equal '2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", "")
assert_equal '2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", "")
assert_equal '2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", nil)
assert_equal '2006-07-05 10:00:00', @filters.date('2006-07-05 10:00:00', '')
assert_equal '2006-07-05 10:00:00', @filters.date('2006-07-05 10:00:00', '')
assert_equal '2006-07-05 10:00:00', @filters.date('2006-07-05 10:00:00', '')
assert_equal '2006-07-05 10:00:00', @filters.date('2006-07-05 10:00:00', nil)
assert_equal '07/05/2006', @filters.date("2006-07-05 10:00:00", "%m/%d/%Y")
assert_equal '07/05/2006', @filters.date('2006-07-05 10:00:00', '%m/%d/%Y')
assert_equal "07/16/2004", @filters.date("Fri Jul 16 01:00:00 2004", "%m/%d/%Y")
assert_equal '07/16/2004', @filters.date('Fri Jul 16 01:00:00 2004', '%m/%d/%Y')
assert_equal Date.today.year.to_s, @filters.date('now', '%Y')
assert_equal Date.today.year.to_s, @filters.date('today', '%Y')
assert_equal Date.today.year.to_s, @filters.date('Today', '%Y')
assert_nil @filters.date(nil, "%B")
assert_nil @filters.date(nil, '%B')
assert_equal '', @filters.date('', "%B")
assert_equal '', @filters.date('', '%B')
with_timezone("UTC") do
assert_equal "07/05/2006", @filters.date(1152098955, "%m/%d/%Y")
assert_equal "07/05/2006", @filters.date("1152098955", "%m/%d/%Y")
with_timezone('UTC') do
assert_equal '07/05/2006', @filters.date(1_152_098_955, '%m/%d/%Y')
assert_equal '07/05/2006', @filters.date('1152098955', '%m/%d/%Y')
end
end
@@ -502,10 +502,10 @@ class StandardFiltersTest < Minitest::Test
end
def test_remove
assert_equal ' ', @filters.remove("a a a a", 'a')
assert_equal ' ', @filters.remove("1 1 1 1", 1)
assert_equal 'a a a', @filters.remove_first("a a a a", 'a ')
assert_equal ' 1 1 1', @filters.remove_first("1 1 1 1", 1)
assert_equal ' ', @filters.remove('a a a a', 'a')
assert_equal ' ', @filters.remove('1 1 1 1', 1)
assert_equal 'a a a', @filters.remove_first('a a a a', 'a ')
assert_equal ' 1 1 1', @filters.remove_first('1 1 1 1', 1)
assert_template_result 'a a a', "{{ 'a a a a' | remove_first: 'a ' }}"
end
@@ -514,148 +514,148 @@ class StandardFiltersTest < Minitest::Test
end
def test_strip
assert_template_result 'ab c', "{{ source | strip }}", 'source' => " ab c "
assert_template_result 'ab c', "{{ source | strip }}", 'source' => " \tab c \n \t"
assert_template_result 'ab c', '{{ source | strip }}', 'source' => ' ab c '
assert_template_result 'ab c', '{{ source | strip }}', 'source' => " \tab c \n \t"
end
def test_lstrip
assert_template_result 'ab c ', "{{ source | lstrip }}", 'source' => " ab c "
assert_template_result "ab c \n \t", "{{ source | lstrip }}", 'source' => " \tab c \n \t"
assert_template_result 'ab c ', '{{ source | lstrip }}', 'source' => ' ab c '
assert_template_result "ab c \n \t", '{{ source | lstrip }}', 'source' => " \tab c \n \t"
end
def test_rstrip
assert_template_result " ab c", "{{ source | rstrip }}", 'source' => " ab c "
assert_template_result " \tab c", "{{ source | rstrip }}", 'source' => " \tab c \n \t"
assert_template_result ' ab c', '{{ source | rstrip }}', 'source' => ' ab c '
assert_template_result " \tab c", '{{ source | rstrip }}', 'source' => " \tab c \n \t"
end
def test_strip_newlines
assert_template_result 'abc', "{{ source | strip_newlines }}", 'source' => "a\nb\nc"
assert_template_result 'abc', "{{ source | strip_newlines }}", 'source' => "a\r\nb\nc"
assert_template_result 'abc', '{{ source | strip_newlines }}', 'source' => "a\nb\nc"
assert_template_result 'abc', '{{ source | strip_newlines }}', 'source' => "a\r\nb\nc"
end
def test_newlines_to_br
assert_template_result "a<br />\nb<br />\nc", "{{ source | newline_to_br }}", 'source' => "a\nb\nc"
assert_template_result "a<br />\nb<br />\nc", '{{ source | newline_to_br }}', 'source' => "a\nb\nc"
end
def test_plus
assert_template_result "2", "{{ 1 | plus:1 }}"
assert_template_result "2.0", "{{ '1' | plus:'1.0' }}"
assert_template_result '2', '{{ 1 | plus:1 }}'
assert_template_result '2.0', "{{ '1' | plus:'1.0' }}"
assert_template_result "5", "{{ price | plus:'2' }}", 'price' => NumberLikeThing.new(3)
assert_template_result '5', "{{ price | plus:'2' }}", 'price' => NumberLikeThing.new(3)
end
def test_minus
assert_template_result "4", "{{ input | minus:operand }}", 'input' => 5, 'operand' => 1
assert_template_result "2.3", "{{ '4.3' | minus:'2' }}"
assert_template_result '4', '{{ input | minus:operand }}', 'input' => 5, 'operand' => 1
assert_template_result '2.3', "{{ '4.3' | minus:'2' }}"
assert_template_result "5", "{{ price | minus:'2' }}", 'price' => NumberLikeThing.new(7)
assert_template_result '5', "{{ price | minus:'2' }}", 'price' => NumberLikeThing.new(7)
end
def test_abs
assert_template_result "17", "{{ 17 | abs }}"
assert_template_result "17", "{{ -17 | abs }}"
assert_template_result "17", "{{ '17' | abs }}"
assert_template_result "17", "{{ '-17' | abs }}"
assert_template_result "0", "{{ 0 | abs }}"
assert_template_result "0", "{{ '0' | abs }}"
assert_template_result "17.42", "{{ 17.42 | abs }}"
assert_template_result "17.42", "{{ -17.42 | abs }}"
assert_template_result "17.42", "{{ '17.42' | abs }}"
assert_template_result "17.42", "{{ '-17.42' | abs }}"
assert_template_result '17', '{{ 17 | abs }}'
assert_template_result '17', '{{ -17 | abs }}'
assert_template_result '17', "{{ '17' | abs }}"
assert_template_result '17', "{{ '-17' | abs }}"
assert_template_result '0', '{{ 0 | abs }}'
assert_template_result '0', "{{ '0' | abs }}"
assert_template_result '17.42', '{{ 17.42 | abs }}'
assert_template_result '17.42', '{{ -17.42 | abs }}'
assert_template_result '17.42', "{{ '17.42' | abs }}"
assert_template_result '17.42', "{{ '-17.42' | abs }}"
end
def test_times
assert_template_result "12", "{{ 3 | times:4 }}"
assert_template_result "0", "{{ 'foo' | times:4 }}"
assert_template_result "6", "{{ '2.1' | times:3 | replace: '.','-' | plus:0}}"
assert_template_result "7.25", "{{ 0.0725 | times:100 }}"
assert_template_result "-7.25", '{{ "-0.0725" | times:100 }}'
assert_template_result "7.25", '{{ "-0.0725" | times: -100 }}'
assert_template_result "4", "{{ price | times:2 }}", 'price' => NumberLikeThing.new(2)
assert_template_result '12', '{{ 3 | times:4 }}'
assert_template_result '0', "{{ 'foo' | times:4 }}"
assert_template_result '6', "{{ '2.1' | times:3 | replace: '.','-' | plus:0}}"
assert_template_result '7.25', '{{ 0.0725 | times:100 }}'
assert_template_result '-7.25', '{{ "-0.0725" | times:100 }}'
assert_template_result '7.25', '{{ "-0.0725" | times: -100 }}'
assert_template_result '4', '{{ price | times:2 }}', 'price' => NumberLikeThing.new(2)
end
def test_divided_by
assert_template_result "4", "{{ 12 | divided_by:3 }}"
assert_template_result "4", "{{ 14 | divided_by:3 }}"
assert_template_result '4', '{{ 12 | divided_by:3 }}'
assert_template_result '4', '{{ 14 | divided_by:3 }}'
assert_template_result "5", "{{ 15 | divided_by:3 }}"
assert_equal "Liquid error: divided by 0", Template.parse("{{ 5 | divided_by:0 }}").render
assert_template_result '5', '{{ 15 | divided_by:3 }}'
assert_equal 'Liquid error: divided by 0', Template.parse('{{ 5 | divided_by:0 }}').render
assert_template_result "0.5", "{{ 2.0 | divided_by:4 }}"
assert_template_result '0.5', '{{ 2.0 | divided_by:4 }}'
assert_raises(Liquid::ZeroDivisionError) do
assert_template_result "4", "{{ 1 | modulo: 0 }}"
assert_template_result '4', '{{ 1 | modulo: 0 }}'
end
assert_template_result "5", "{{ price | divided_by:2 }}", 'price' => NumberLikeThing.new(10)
assert_template_result '5', '{{ price | divided_by:2 }}', 'price' => NumberLikeThing.new(10)
end
def test_modulo
assert_template_result "1", "{{ 3 | modulo:2 }}"
assert_template_result '1', '{{ 3 | modulo:2 }}'
assert_raises(Liquid::ZeroDivisionError) do
assert_template_result "4", "{{ 1 | modulo: 0 }}"
assert_template_result '4', '{{ 1 | modulo: 0 }}'
end
assert_template_result "1", "{{ price | modulo:2 }}", 'price' => NumberLikeThing.new(3)
assert_template_result '1', '{{ price | modulo:2 }}', 'price' => NumberLikeThing.new(3)
end
def test_round
assert_template_result "5", "{{ input | round }}", 'input' => 4.6
assert_template_result "4", "{{ '4.3' | round }}"
assert_template_result "4.56", "{{ input | round: 2 }}", 'input' => 4.5612
assert_template_result '5', '{{ input | round }}', 'input' => 4.6
assert_template_result '4', "{{ '4.3' | round }}"
assert_template_result '4.56', '{{ input | round: 2 }}', 'input' => 4.5612
assert_raises(Liquid::FloatDomainError) do
assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | round }}"
assert_template_result '4', '{{ 1.0 | divided_by: 0.0 | round }}'
end
assert_template_result "5", "{{ price | round }}", 'price' => NumberLikeThing.new(4.6)
assert_template_result "4", "{{ price | round }}", 'price' => NumberLikeThing.new(4.3)
assert_template_result '5', '{{ price | round }}', 'price' => NumberLikeThing.new(4.6)
assert_template_result '4', '{{ price | round }}', 'price' => NumberLikeThing.new(4.3)
end
def test_ceil
assert_template_result "5", "{{ input | ceil }}", 'input' => 4.6
assert_template_result "5", "{{ '4.3' | ceil }}"
assert_template_result '5', '{{ input | ceil }}', 'input' => 4.6
assert_template_result '5', "{{ '4.3' | ceil }}"
assert_raises(Liquid::FloatDomainError) do
assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | ceil }}"
assert_template_result '4', '{{ 1.0 | divided_by: 0.0 | ceil }}'
end
assert_template_result "5", "{{ price | ceil }}", 'price' => NumberLikeThing.new(4.6)
assert_template_result '5', '{{ price | ceil }}', 'price' => NumberLikeThing.new(4.6)
end
def test_floor
assert_template_result "4", "{{ input | floor }}", 'input' => 4.6
assert_template_result "4", "{{ '4.3' | floor }}"
assert_template_result '4', '{{ input | floor }}', 'input' => 4.6
assert_template_result '4', "{{ '4.3' | floor }}"
assert_raises(Liquid::FloatDomainError) do
assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | floor }}"
assert_template_result '4', '{{ 1.0 | divided_by: 0.0 | floor }}'
end
assert_template_result "5", "{{ price | floor }}", 'price' => NumberLikeThing.new(5.4)
assert_template_result '5', '{{ price | floor }}', 'price' => NumberLikeThing.new(5.4)
end
def test_at_most
assert_template_result "4", "{{ 5 | at_most:4 }}"
assert_template_result "5", "{{ 5 | at_most:5 }}"
assert_template_result "5", "{{ 5 | at_most:6 }}"
assert_template_result '4', '{{ 5 | at_most:4 }}'
assert_template_result '5', '{{ 5 | at_most:5 }}'
assert_template_result '5', '{{ 5 | at_most:6 }}'
assert_template_result "4.5", "{{ 4.5 | at_most:5 }}"
assert_template_result "5", "{{ width | at_most:5 }}", 'width' => NumberLikeThing.new(6)
assert_template_result "4", "{{ width | at_most:5 }}", 'width' => NumberLikeThing.new(4)
assert_template_result "4", "{{ 5 | at_most: width }}", 'width' => NumberLikeThing.new(4)
assert_template_result '4.5', '{{ 4.5 | at_most:5 }}'
assert_template_result '5', '{{ width | at_most:5 }}', 'width' => NumberLikeThing.new(6)
assert_template_result '4', '{{ width | at_most:5 }}', 'width' => NumberLikeThing.new(4)
assert_template_result '4', '{{ 5 | at_most: width }}', 'width' => NumberLikeThing.new(4)
end
def test_at_least
assert_template_result "5", "{{ 5 | at_least:4 }}"
assert_template_result "5", "{{ 5 | at_least:5 }}"
assert_template_result "6", "{{ 5 | at_least:6 }}"
assert_template_result '5', '{{ 5 | at_least:4 }}'
assert_template_result '5', '{{ 5 | at_least:5 }}'
assert_template_result '6', '{{ 5 | at_least:6 }}'
assert_template_result "5", "{{ 4.5 | at_least:5 }}"
assert_template_result "6", "{{ width | at_least:5 }}", 'width' => NumberLikeThing.new(6)
assert_template_result "5", "{{ width | at_least:5 }}", 'width' => NumberLikeThing.new(4)
assert_template_result "6", "{{ 5 | at_least: width }}", 'width' => NumberLikeThing.new(6)
assert_template_result '5', '{{ 4.5 | at_least:5 }}'
assert_template_result '6', '{{ width | at_least:5 }}', 'width' => NumberLikeThing.new(6)
assert_template_result '5', '{{ width | at_least:5 }}', 'width' => NumberLikeThing.new(4)
assert_template_result '6', '{{ 5 | at_least: width }}', 'width' => NumberLikeThing.new(6)
end
def test_append
assigns = { 'a' => 'bc', 'b' => 'd' }
assert_template_result('bcd', "{{ a | append: 'd'}}", assigns)
assert_template_result('bcd', "{{ a | append: b}}", assigns)
assert_template_result('bcd', '{{ a | append: b}}', assigns)
end
def test_concat
@@ -663,7 +663,7 @@ class StandardFiltersTest < Minitest::Test
assert_equal [1, 2, 'a'], @filters.concat([1, 2], ['a'])
assert_equal [1, 2, 10], @filters.concat([1, 2], [10])
assert_raises(Liquid::ArgumentError, "concat filter requires an array argument") do
assert_raises(Liquid::ArgumentError, 'concat filter requires an array argument') do
@filters.concat([1, 2], 10)
end
end
@@ -671,16 +671,16 @@ class StandardFiltersTest < Minitest::Test
def test_prepend
assigns = { 'a' => 'bc', 'b' => 'a' }
assert_template_result('abc', "{{ a | prepend: 'a'}}", assigns)
assert_template_result('abc', "{{ a | prepend: b}}", assigns)
assert_template_result('abc', '{{ a | prepend: b}}', assigns)
end
def test_default
assert_equal "foo", @filters.default("foo", "bar")
assert_equal "bar", @filters.default(nil, "bar")
assert_equal "bar", @filters.default("", "bar")
assert_equal "bar", @filters.default(false, "bar")
assert_equal "bar", @filters.default([], "bar")
assert_equal "bar", @filters.default({}, "bar")
assert_equal 'foo', @filters.default('foo', 'bar')
assert_equal 'bar', @filters.default(nil, 'bar')
assert_equal 'bar', @filters.default('', 'bar')
assert_equal 'bar', @filters.default(false, 'bar')
assert_equal 'bar', @filters.default([], 'bar')
assert_equal 'bar', @filters.default({}, 'bar')
end
def test_cannot_access_private_methods
@@ -694,74 +694,74 @@ class StandardFiltersTest < Minitest::Test
def test_where
input = [
{ "handle" => "alpha", "ok" => true },
{ "handle" => "beta", "ok" => false },
{ "handle" => "gamma", "ok" => false },
{ "handle" => "delta", "ok" => true }
{ 'handle' => 'alpha', 'ok' => true },
{ 'handle' => 'beta', 'ok' => false },
{ 'handle' => 'gamma', 'ok' => false },
{ 'handle' => 'delta', 'ok' => true },
]
expectation = [
{ "handle" => "alpha", "ok" => true },
{ "handle" => "delta", "ok" => true }
{ 'handle' => 'alpha', 'ok' => true },
{ 'handle' => 'delta', 'ok' => true },
]
assert_equal expectation, @filters.where(input, "ok", true)
assert_equal expectation, @filters.where(input, "ok")
assert_equal expectation, @filters.where(input, 'ok', true)
assert_equal expectation, @filters.where(input, 'ok')
end
def test_where_no_key_set
input = [
{ "handle" => "alpha", "ok" => true },
{ "handle" => "beta" },
{ "handle" => "gamma" },
{ "handle" => "delta", "ok" => true }
{ 'handle' => 'alpha', 'ok' => true },
{ 'handle' => 'beta' },
{ 'handle' => 'gamma' },
{ 'handle' => 'delta', 'ok' => true },
]
expectation = [
{ "handle" => "alpha", "ok" => true },
{ "handle" => "delta", "ok" => true }
{ 'handle' => 'alpha', 'ok' => true },
{ 'handle' => 'delta', 'ok' => true },
]
assert_equal expectation, @filters.where(input, "ok", true)
assert_equal expectation, @filters.where(input, "ok")
assert_equal expectation, @filters.where(input, 'ok', true)
assert_equal expectation, @filters.where(input, 'ok')
end
def test_where_non_array_map_input
assert_equal [{ "a" => "ok" }], @filters.where({ "a" => "ok" }, "a", "ok")
assert_equal [], @filters.where({ "a" => "not ok" }, "a", "ok")
assert_equal [{ 'a' => 'ok' }], @filters.where({ 'a' => 'ok' }, 'a', 'ok')
assert_equal [], @filters.where({ 'a' => 'not ok' }, 'a', 'ok')
end
def test_where_indexable_but_non_map_value
assert_raises(Liquid::ArgumentError) { @filters.where(1, "ok", true) }
assert_raises(Liquid::ArgumentError) { @filters.where(1, "ok") }
assert_raises(Liquid::ArgumentError) { @filters.where(1, 'ok', true) }
assert_raises(Liquid::ArgumentError) { @filters.where(1, 'ok') }
end
def test_where_non_boolean_value
input = [
{ "message" => "Bonjour!", "language" => "French" },
{ "message" => "Hello!", "language" => "English" },
{ "message" => "Hallo!", "language" => "German" }
{ 'message' => 'Bonjour!', 'language' => 'French' },
{ 'message' => 'Hello!', 'language' => 'English' },
{ 'message' => 'Hallo!', 'language' => 'German' },
]
assert_equal [{ "message" => "Bonjour!", "language" => "French" }], @filters.where(input, "language", "French")
assert_equal [{ "message" => "Hallo!", "language" => "German" }], @filters.where(input, "language", "German")
assert_equal [{ "message" => "Hello!", "language" => "English" }], @filters.where(input, "language", "English")
assert_equal [{ 'message' => 'Bonjour!', 'language' => 'French' }], @filters.where(input, 'language', 'French')
assert_equal [{ 'message' => 'Hallo!', 'language' => 'German' }], @filters.where(input, 'language', 'German')
assert_equal [{ 'message' => 'Hello!', 'language' => 'English' }], @filters.where(input, 'language', 'English')
end
def test_where_array_of_only_unindexable_values
assert_nil @filters.where([nil], "ok", true)
assert_nil @filters.where([nil], "ok")
assert_nil @filters.where([nil], 'ok', true)
assert_nil @filters.where([nil], 'ok')
end
def test_where_no_target_value
input = [
{ "foo" => false },
{ "foo" => true },
{ "foo" => "for sure" },
{ "bar" => true }
{ 'foo' => false },
{ 'foo' => true },
{ 'foo' => 'for sure' },
{ 'bar' => true },
]
assert_equal [{ "foo" => true }, { "foo" => "for sure" }], @filters.where(input, "foo")
assert_equal [{ 'foo' => true }, { 'foo' => 'for sure' }], @filters.where(input, 'foo')
end
private

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'test_helper'
class BreakTagTest < Minitest::Test

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'test_helper'
class ContinueTagTest < Minitest::Test

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'test_helper'
class ThingWithValue < Liquid::Drop
@@ -23,16 +25,16 @@ class ForTagTest < Minitest::Test
yo
HERE
template = <<HERE
{%for item in array%}
yo
{%endfor%}
HERE
template = <<~HERE
{%for item in array%}
yo
{%endfor%}
HERE
assert_template_result(expected, template, 'array' => [1, 2, 3])
end
def test_for_reversed
assigns = { 'array' => [ 1, 2, 3] }
assigns = { 'array' => [1, 2, 3] }
assert_template_result('321', '{%for item in array reversed %}{{item}}{%endfor%}', assigns)
end
@@ -40,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
@@ -72,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)
@@ -85,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
@@ -114,7 +116,7 @@ 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
@@ -128,7 +130,7 @@ 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
@@ -157,14 +159,14 @@ HERE
{%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
@@ -176,14 +178,14 @@ HERE
{%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
@@ -195,14 +197,14 @@ HERE
{%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
@@ -225,19 +227,19 @@ HERE
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] } }
markup = '{% for i in array.items %}{% break %}{% endfor %}'
expected = ""
expected = ''
assert_template_result(expected, markup, assigns)
markup = '{% for i in array.items %}{{ i }}{% break %}{% endfor %}'
expected = "1"
expected = '1'
assert_template_result(expected, markup, assigns)
markup = '{% for i in array.items %}{% break %}{{ i }}{% endfor %}'
expected = ""
expected = ''
assert_template_result(expected, markup, assigns)
markup = '{% for i in array.items %}{{ i }}{% if i > 3 %}{% break %}{% endif %}{% endfor %}'
expected = "1234"
expected = '1234'
assert_template_result(expected, markup, assigns)
# tests to ensure it only breaks out of the local for loop
@@ -265,23 +267,23 @@ HERE
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5] } }
markup = '{% for i in array.items %}{% continue %}{% endfor %}'
expected = ""
expected = ''
assert_template_result(expected, markup, assigns)
markup = '{% for i in array.items %}{{ i }}{% continue %}{% endfor %}'
expected = "12345"
expected = '12345'
assert_template_result(expected, markup, assigns)
markup = '{% for i in array.items %}{% continue %}{{ i }}{% endfor %}'
expected = ""
expected = ''
assert_template_result(expected, markup, assigns)
markup = '{% for i in array.items %}{% if i > 3 %}{% continue %}{% endif %}{{ i }}{% endfor %}'
expected = "123"
expected = '123'
assert_template_result(expected, markup, assigns)
markup = '{% for i in array.items %}{% if i == 3 %}{% continue %}{% else %}{{ i }}{% endif %}{% endfor %}'
expected = "1245"
expected = '1245'
assert_template_result(expected, markup, assigns)
# tests to ensure it only continues the local for loop and not all of them.
@@ -310,41 +312,41 @@ 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
@@ -352,7 +354,7 @@ HERE
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

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'test_helper'
class IfElseTagTest < Minitest::Test
@@ -6,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
@@ -49,12 +51,12 @@ class IfElseTagTest < Minitest::Test
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
@@ -75,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' => [] })
@@ -90,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 })
@@ -132,7 +134,7 @@ class IfElseTagTest < Minitest::Test
end
def test_syntax_error_no_variable
assert_raises(SyntaxError){ assert_template_result('', '{% if jerry == 1 %}') }
assert_raises(SyntaxError) { assert_template_result('', '{% if jerry == 1 %}') }
end
def test_syntax_error_no_expression
@@ -154,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
@@ -166,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

@@ -1,37 +1,39 @@
# frozen_string_literal: true
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 "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
@@ -40,14 +42,14 @@ class TestFileSystem
end
class OtherFileSystem
def read_template_file(template_path)
def read_template_file(_template_path)
'from OtherFileSystem'
end
end
class CountingFileSystem
attr_reader :count
def read_template_file(template_path)
def read_template_file(_template_path)
@count ||= 0
@count += 1
'from CountingFileSystem'
@@ -55,19 +57,19 @@ class CountingFileSystem
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
@template_name = $1
markup =~ SYNTAX
@template_name = Regexp.last_match(1)
super
end
def parse(tokens)
end
def parse(tokens); end
def render(context)
@template_name[1..-2]
def render_to_output_buffer(_context, output)
output << @template_name[1..-2]
output
end
end
@@ -80,60 +82,60 @@ class IncludeTagTest < Minitest::Test
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 })
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' } ]
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
infinite_file_system = Class.new do
def read_template_file(template_path)
def read_template_file(_template_path)
"-{% include 'loop' %}"
end
end
@@ -146,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 })
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 })
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 })
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
@@ -190,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
@@ -207,16 +209,16 @@ class IncludeTagTest < Minitest::Test
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
@@ -232,11 +234,11 @@ 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
@@ -247,7 +249,7 @@ class IncludeTagTest < Minitest::Test
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

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'test_helper'
class IncrementTagTest < Minitest::Test
@@ -7,17 +9,17 @@ class IncrementTagTest < Minitest::Test
assert_template_result('0', '{%increment port %}', {})
assert_template_result('0 1', '{%increment port %} {%increment port%}', {})
assert_template_result('0 0 1 2 1',
'{%increment port %} {%increment starboard%} ' \
'{%increment port %} {%increment port%} ' \
'{%increment starboard %}', {})
'{%increment port %} {%increment starboard%} ' \
'{%increment port %} {%increment port%} ' \
'{%increment starboard %}', {})
end
def test_dec
assert_template_result('9', '{%decrement port %}', { 'port' => 10 })
assert_template_result('9', '{%decrement port %}', 'port' => 10)
assert_template_result('-1 -2', '{%decrement port %} {%decrement port%}', {})
assert_template_result('1 5 2 2 5',
'{%increment port %} {%increment starboard%} ' \
'{%increment port %} {%decrement port%} ' \
'{%decrement starboard %}', { 'port' => 1, 'starboard' => 5 })
'{%increment port %} {%increment starboard%} ' \
'{%increment port %} {%decrement port%} ' \
'{%decrement starboard %}', 'port' => 1, 'starboard' => 5)
end
end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'test_helper'
class RawTagTest < Minitest::Test
@@ -5,7 +7,7 @@ class RawTagTest < Minitest::Test
def test_tag_in_raw
assert_template_result '{% comment %} test {% endcomment %}',
'{% raw %}{% comment %} test {% endcomment %}{% endraw %}'
'{% raw %}{% comment %} test {% endcomment %}{% endraw %}'
end
def test_output_in_raw

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'test_helper'
class StandardTagTest < Minitest::Test
@@ -5,7 +7,7 @@ class StandardTagTest < Minitest::Test
def test_no_transform
assert_template_result('this text should come out of the template without change...',
'this text should come out of the template without change...')
'this text should come out of the template without change...')
assert_template_result('blah', 'blah')
assert_template_result('<blah>', '<blah>')
@@ -19,7 +21,7 @@ class StandardTagTest < Minitest::Test
def test_has_a_block_which_does_nothing
assert_template_result(%(the comment block should be removed .. right?),
%(the comment block should be removed {%comment%} be gone.. {%endcomment%} .. right?))
%(the comment block should be removed {%comment%} be gone.. {%endcomment%} .. right?))
assert_template_result('', '{%comment%}{%endcomment%}')
assert_template_result('', '{%comment%}{% endcomment %}')
@@ -61,60 +63,60 @@ class StandardTagTest < Minitest::Test
def test_capture
assigns = { 'var' => 'content' }
assert_template_result('content foo content foo ',
'{{ var2 }}{% capture var2 %}{{ var }} foo {% endcapture %}{{ var2 }}{{ var2 }}',
assigns)
'{{ var2 }}{% capture var2 %}{{ var }} foo {% endcapture %}{{ var2 }}{{ var2 }}',
assigns)
end
def test_capture_detects_bad_syntax
assert_raises(SyntaxError) do
assert_template_result('content foo content foo ',
'{{ var2 }}{% capture %}{{ var }} foo {% endcapture %}{{ var2 }}{{ var2 }}',
{ 'var' => 'content' })
'{{ var2 }}{% capture %}{{ var }} foo {% endcapture %}{{ var2 }}{{ var2 }}',
'var' => 'content')
end
end
def test_case
assigns = { 'condition' => 2 }
assert_template_result(' its 2 ',
'{% case condition %}{% when 1 %} its 1 {% when 2 %} its 2 {% endcase %}',
assigns)
'{% case condition %}{% when 1 %} its 1 {% when 2 %} its 2 {% endcase %}',
assigns)
assigns = { 'condition' => 1 }
assert_template_result(' its 1 ',
'{% case condition %}{% when 1 %} its 1 {% when 2 %} its 2 {% endcase %}',
assigns)
'{% case condition %}{% when 1 %} its 1 {% when 2 %} its 2 {% endcase %}',
assigns)
assigns = { 'condition' => 3 }
assert_template_result('',
'{% case condition %}{% when 1 %} its 1 {% when 2 %} its 2 {% endcase %}',
assigns)
'{% case condition %}{% when 1 %} its 1 {% when 2 %} its 2 {% endcase %}',
assigns)
assigns = { 'condition' => "string here" }
assigns = { 'condition' => 'string here' }
assert_template_result(' hit ',
'{% case condition %}{% when "string here" %} hit {% endcase %}',
assigns)
'{% case condition %}{% when "string here" %} hit {% endcase %}',
assigns)
assigns = { 'condition' => "bad string here" }
assigns = { 'condition' => 'bad string here' }
assert_template_result('',
'{% case condition %}{% when "string here" %} hit {% endcase %}',\
assigns)
'{% case condition %}{% when "string here" %} hit {% endcase %}',\
assigns)
end
def test_case_with_else
assigns = { 'condition' => 5 }
assert_template_result(' hit ',
'{% case condition %}{% when 5 %} hit {% else %} else {% endcase %}',
assigns)
'{% case condition %}{% when 5 %} hit {% else %} else {% endcase %}',
assigns)
assigns = { 'condition' => 6 }
assert_template_result(' else ',
'{% case condition %}{% when 5 %} hit {% else %} else {% endcase %}',
assigns)
'{% case condition %}{% when 5 %} hit {% else %} else {% endcase %}',
assigns)
assigns = { 'condition' => 6 }
assert_template_result(' else ',
'{% case condition %} {% when 5 %} hit {% else %} else {% endcase %}',
assigns)
'{% case condition %} {% when 5 %} hit {% else %} else {% endcase %}',
assigns)
end
def test_case_on_size
@@ -128,87 +130,87 @@ class StandardTagTest < Minitest::Test
def test_case_on_size_with_else
assert_template_result('else',
'{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
'a' => [])
'{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
'a' => [])
assert_template_result('1',
'{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
'a' => [1])
'{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
'a' => [1])
assert_template_result('2',
'{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
'a' => [1, 1])
'{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
'a' => [1, 1])
assert_template_result('else',
'{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
'a' => [1, 1, 1])
'{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
'a' => [1, 1, 1])
assert_template_result('else',
'{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
'a' => [1, 1, 1, 1])
'{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
'a' => [1, 1, 1, 1])
assert_template_result('else',
'{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
'a' => [1, 1, 1, 1, 1])
'{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
'a' => [1, 1, 1, 1, 1])
end
def test_case_on_length_with_else
assert_template_result('else',
'{% case a.empty? %}{% when true %}true{% when false %}false{% else %}else{% endcase %}',
{})
'{% case a.empty? %}{% when true %}true{% when false %}false{% else %}else{% endcase %}',
{})
assert_template_result('false',
'{% case false %}{% when true %}true{% when false %}false{% else %}else{% endcase %}',
{})
'{% case false %}{% when true %}true{% when false %}false{% else %}else{% endcase %}',
{})
assert_template_result('true',
'{% case true %}{% when true %}true{% when false %}false{% else %}else{% endcase %}',
{})
'{% case true %}{% when true %}true{% when false %}false{% else %}else{% endcase %}',
{})
assert_template_result('else',
'{% case NULL %}{% when true %}true{% when false %}false{% else %}else{% endcase %}',
{})
'{% case NULL %}{% when true %}true{% when false %}false{% else %}else{% endcase %}',
{})
end
def test_assign_from_case
# Example from the shopify forums
code = "{% case collection.handle %}{% when 'menswear-jackets' %}{% assign ptitle = 'menswear' %}{% when 'menswear-t-shirts' %}{% assign ptitle = 'menswear' %}{% else %}{% assign ptitle = 'womenswear' %}{% endcase %}{{ ptitle }}"
template = Liquid::Template.parse(code)
assert_equal "menswear", template.render!("collection" => { 'handle' => 'menswear-jackets' })
assert_equal "menswear", template.render!("collection" => { 'handle' => 'menswear-t-shirts' })
assert_equal "womenswear", template.render!("collection" => { 'handle' => 'x' })
assert_equal "womenswear", template.render!("collection" => { 'handle' => 'y' })
assert_equal "womenswear", template.render!("collection" => { 'handle' => 'z' })
assert_equal 'menswear', template.render!('collection' => { 'handle' => 'menswear-jackets' })
assert_equal 'menswear', template.render!('collection' => { 'handle' => 'menswear-t-shirts' })
assert_equal 'womenswear', template.render!('collection' => { 'handle' => 'x' })
assert_equal 'womenswear', template.render!('collection' => { 'handle' => 'y' })
assert_equal 'womenswear', template.render!('collection' => { 'handle' => 'z' })
end
def test_case_when_or
code = '{% case condition %}{% when 1 or 2 or 3 %} its 1 or 2 or 3 {% when 4 %} its 4 {% endcase %}'
assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 1 })
assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 2 })
assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 3 })
assert_template_result(' its 4 ', code, { 'condition' => 4 })
assert_template_result('', code, { 'condition' => 5 })
assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 1)
assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 2)
assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 3)
assert_template_result(' its 4 ', code, 'condition' => 4)
assert_template_result('', code, 'condition' => 5)
code = '{% case condition %}{% when 1 or "string" or null %} its 1 or 2 or 3 {% when 4 %} its 4 {% endcase %}'
assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 1 })
assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 'string' })
assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => nil })
assert_template_result('', code, { 'condition' => 'something else' })
assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 1)
assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 'string')
assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => nil)
assert_template_result('', code, 'condition' => 'something else')
end
def test_case_when_comma
code = '{% case condition %}{% when 1, 2, 3 %} its 1 or 2 or 3 {% when 4 %} its 4 {% endcase %}'
assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 1 })
assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 2 })
assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 3 })
assert_template_result(' its 4 ', code, { 'condition' => 4 })
assert_template_result('', code, { 'condition' => 5 })
assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 1)
assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 2)
assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 3)
assert_template_result(' its 4 ', code, 'condition' => 4)
assert_template_result('', code, 'condition' => 5)
code = '{% case condition %}{% when 1, "string", null %} its 1 or 2 or 3 {% when 4 %} its 4 {% endcase %}'
assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 1 })
assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 'string' })
assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => nil })
assert_template_result('', code, { 'condition' => 'something else' })
assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 1)
assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 'string')
assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => nil)
assert_template_result('', code, 'condition' => 'something else')
end
def test_assign
@@ -246,33 +248,33 @@ class StandardTagTest < Minitest::Test
assert_template_result('one two one', '{%cycle "one", "two"%} {%cycle "one", "two"%} {%cycle "one", "two"%}')
assert_template_result('text-align: left text-align: right',
'{%cycle "text-align: left", "text-align: right" %} {%cycle "text-align: left", "text-align: right"%}')
'{%cycle "text-align: left", "text-align: right" %} {%cycle "text-align: left", "text-align: right"%}')
end
def test_multiple_cycles
assert_template_result('1 2 1 1 2 3 1',
'{%cycle 1,2%} {%cycle 1,2%} {%cycle 1,2%} {%cycle 1,2,3%} {%cycle 1,2,3%} {%cycle 1,2,3%} {%cycle 1,2,3%}')
'{%cycle 1,2%} {%cycle 1,2%} {%cycle 1,2%} {%cycle 1,2,3%} {%cycle 1,2,3%} {%cycle 1,2,3%} {%cycle 1,2,3%}')
end
def test_multiple_named_cycles
assert_template_result('one one two two one one',
'{%cycle 1: "one", "two" %} {%cycle 2: "one", "two" %} {%cycle 1: "one", "two" %} {%cycle 2: "one", "two" %} {%cycle 1: "one", "two" %} {%cycle 2: "one", "two" %}')
'{%cycle 1: "one", "two" %} {%cycle 2: "one", "two" %} {%cycle 1: "one", "two" %} {%cycle 2: "one", "two" %} {%cycle 1: "one", "two" %} {%cycle 2: "one", "two" %}')
end
def test_multiple_named_cycles_with_names_from_context
assigns = { "var1" => 1, "var2" => 2 }
assigns = { 'var1' => 1, 'var2' => 2 }
assert_template_result('one one two two one one',
'{%cycle var1: "one", "two" %} {%cycle var2: "one", "two" %} {%cycle var1: "one", "two" %} {%cycle var2: "one", "two" %} {%cycle var1: "one", "two" %} {%cycle var2: "one", "two" %}', assigns)
'{%cycle var1: "one", "two" %} {%cycle var2: "one", "two" %} {%cycle var1: "one", "two" %} {%cycle var2: "one", "two" %} {%cycle var1: "one", "two" %} {%cycle var2: "one", "two" %}', assigns)
end
def test_size_of_array
assigns = { "array" => [1, 2, 3, 4] }
assert_template_result('array has 4 elements', "array has {{ array.size }} elements", assigns)
assigns = { 'array' => [1, 2, 3, 4] }
assert_template_result('array has 4 elements', 'array has {{ array.size }} elements', assigns)
end
def test_size_of_hash
assigns = { "hash" => { a: 1, b: 2, c: 3, d: 4 } }
assert_template_result('hash has 4 elements', "hash has {{ hash.size }} elements", assigns)
assigns = { 'hash' => { a: 1, b: 2, c: 3, d: 4 } }
assert_template_result('hash has 4 elements', 'hash has {{ hash.size }} elements', assigns)
end
def test_illegal_symbols
@@ -283,10 +285,10 @@ class StandardTagTest < Minitest::Test
end
def test_ifchanged
assigns = { 'array' => [ 1, 1, 2, 2, 3, 3] }
assigns = { 'array' => [1, 1, 2, 2, 3, 3] }
assert_template_result('123', '{%for item in array%}{%ifchanged%}{{item}}{% endifchanged %}{%endfor%}', assigns)
assigns = { 'array' => [ 1, 1, 1, 1] }
assigns = { 'array' => [1, 1, 1, 1] }
assert_template_result('1', '{%for item in array%}{%ifchanged%}{{item}}{% endifchanged %}{%endfor%}', assigns)
end

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