Compare commits

...

33 Commits

Author SHA1 Message Date
Thierry Joyal
3f439f73ba Add osx 2019-12-13 09:47:29 -05:00
Thierry Joyal
5face68cc8 Experiment with ruby 2.7 2019-12-13 09:13:32 -05:00
Mike Angell
57c9cf64eb Allow render to handle with and for correctly (#1193)
* Allow render to handle with and for correctly

* code improvements
2019-10-23 04:12:46 +10:00
Alessandro Diogo Brückheimer
e83b1e4159 Add ForceEqualSignAlignment to .rubocop.yml (#1190)
* Add ForceEqualSignAlignment to .rubocop.yml

* Revert ForceEqualSignAlignment cop

* Update method alignment

* Undo addition of whitespace to improve readability

* Fix missing alignment
2019-10-21 21:18:48 +10:00
Mike Angell
3784020a8d [New Feature] Add forloop inside render tag when using for syntax (#1191)
* Add forloop to render for syntax

* Remove forloop guard
2019-10-17 23:06:13 +10:00
uchoudh
1223444738 Fix flaky tests (#1186) 2019-10-12 02:52:07 +11:00
Mike Angell
2bfeed2b00 Resolve InputIterator dropping context (#1184)
* Resolve InputIterator dropping context

* Prefer attr_reader
2019-10-09 08:00:16 +11:00
Mike Angell
04b800d768 Add support for as in Render and Include tags (#1181)
* Add support for alias

* Remove duplicate code

* Default to template name

* Improve variable matching

* Extract render_partial

* remove method
2019-10-09 07:59:52 +11:00
Mike Angell
f1d62978ef Allow default function to handle false as value (#1144)
* Allow default function to handle false as value

* Change to named parameter

* Remove redundant freeze

* add brackets to make intention clearer

* Use named param format from liquid

* Update syntax

* document default filter
2019-10-09 04:03:33 +11:00
uchoudh
ffadc64f28 Merge pull request #1172 from Shopify/add-liquid-profiling-attr
Add liquid profile attributes
2019-10-08 10:49:54 -04:00
Mike Angell
5302f40342 Rubocop fixes (#1182) 2019-10-07 17:06:47 +11:00
uchoudh
fefee4c675 Add liquid profile attributes
Attribute testing

Add partial name support
2019-10-03 10:12:39 -04:00
Mike Angell
1aa7d3d2ba Change registers to by symbols (#1178) 2019-09-27 04:32:24 +10:00
Mike Angell
0db9c56f34 Disable rendering of tag based on register (#1162)
* Disable rendering of tag based on register

* Improvements to disable tag

* Resolve disbale tag tests

* Test disable_tags register

* disabled_tags is now always avaiable

* Allow multiple tags to be disabled at once

* Move disabled check to block_body

* Code improvements

* Remove redundant nil check

* Improve disabled tag error output

* Improve disable tag API

* Code improvements

* Switch disabled? to not mutate output

* Fix array handling shortcut in disable_tags
2019-09-26 00:18:30 +10:00
Mike Angell
f4d134cd5c Remove jruby and truffleruby testing (#1167) 2019-09-20 02:28:43 +10:00
Mike Angell
b667bcb48b Shopify stye guide fixes (#1160) 2019-09-20 02:08:11 +10:00
Ashwin Maroli
2c14e0b2ba Use Regexp#match? when MatchData is not used (#1165)
* Use `Regexp#match?` when `MatchData` is not used

* Add `TargetRubyVersion: 2.4` to RuboCop config
2019-09-20 02:07:52 +10:00
Ashwin Maroli
ca207ed93f Cleanup RuboCop configuration file (#1161) 2019-09-20 00:55:01 +10:00
Mike Angell
ef13343591 Changes static registers to not be frozen (#1163)
* Changes static registers to not be frozen

* Add frozen test to static registers
2019-09-20 00:24:48 +10:00
Mike Angell
adb40c41b7 Enable frozen_string_literal 2019-09-18 13:40:07 +10:00
Mike Angell
d8403af515 Reimplementation of Static Registers (#1157) 2019-09-18 13:25:55 +10:00
Mike Angell
0d26f05bb8 Enabled frozen string literals (#1154)
* Enabled frozen string literals

* Update rubocop config

* Prefer string interpolation in simple cases

Co-Authored-By: Dylan Thacker-Smith <dylan.smith@shopify.com>
2019-09-18 13:19:45 +10:00
Thierry Joyal
1dcad34b06 Merge pull request #1151 from Shopify/invokable-methods-for-enumerable-reject-include
Invokable methods for enumerable reject include?
2019-09-16 09:49:40 -04:00
Mike Angell
9a42c8c8b2 Merge pull request #1149 from Shopify/liquid-usage
Add usage tracking
2019-09-16 12:14:50 +10:00
Mike Angell
1fcef2133f Merge pull request #1143 from Shopify/styling-fixes-1
Apply simple rubocop fixes
2019-09-16 12:14:32 +10:00
Mike Angell
d7514b1305 Merge pull request #1137 from Shopify/remove-lazy-stacks
Remove lazy load stacks
2019-09-16 12:14:14 +10:00
Thierry Joyal
c0ffee5919 Invokable methods for enumerable reject include? 2019-09-12 12:58:51 +00:00
Mike Angell
724d02e9b3 Disable interrupt fix in this round 2019-09-11 06:35:08 +10:00
Mike Angell
a5b387cdd4 Remove reserved word Interrupt to avoid confusion
Also resolves rubocop conflicts
2019-09-11 06:32:31 +10:00
Mike Angell
1f90a37b63 Merge branch 'master' into styling-fixes-1 2019-09-04 14:26:27 +10:00
Mike Angell
799da202df Apply simple rubocop fixes 2019-08-31 21:58:33 +10:00
Mike Angell
dafbb4ae90 Remove hasnling false scopes 2019-08-31 20:03:54 +10:00
Mike Angell
2324564743 Remove lazy load stacks
Remove lazy load stacks and instead only create a new scope when a tag is known to need one
2019-08-29 09:09:32 +10:00
134 changed files with 2737 additions and 2204 deletions

View File

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

View File

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

View File

@@ -1,107 +1,11 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2019-08-29 12:16:25 +1000 using RuboCop version 0.74.0.
# on 2019-09-11 06:34:25 +1000 using RuboCop version 0.74.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
# Offense count: 13
# Cop supports --auto-correct.
# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle.
# SupportedHashRocketStyles: key, separator, table
# SupportedColonStyles: key, separator, table
# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit
Layout/AlignHash:
Exclude:
- 'lib/liquid/condition.rb'
- 'lib/liquid/expression.rb'
- 'performance/shopify/comment_form.rb'
- 'performance/shopify/database.rb'
- 'performance/shopify/paginate.rb'
- 'test/unit/context_unit_test.rb'
# Offense count: 3
# Cop supports --auto-correct.
# Configuration parameters: AllowForAlignment, AllowBeforeTrailingComments, ForceEqualSignAlignment.
Layout/ExtraSpacing:
Exclude:
- 'performance/shopify/paginate.rb'
# Offense count: 5
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: 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: 4
# Cop supports --auto-correct.
# Configuration parameters: AllowForAlignment.
Layout/SpaceAroundOperators:
Exclude:
- 'lib/liquid/condition.rb'
- 'performance/shopify/paginate.rb'
# Offense count: 9
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces.
# SupportedStyles: space, no_space
# SupportedStylesForEmptyBraces: space, no_space
Layout/SpaceBeforeBlockBraces:
Exclude:
- 'example/server/server.rb'
- 'lib/liquid/variable.rb'
- 'test/integration/drop_test.rb'
- 'test/integration/standard_filter_test.rb'
- 'test/integration/tags/if_else_tag_test.rb'
# Offense count: 19
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBrackets.
# SupportedStyles: space, no_space, compact
# SupportedStylesForEmptyBrackets: space, no_space
Layout/SpaceInsideArrayLiteralBrackets:
Exclude:
- 'test/integration/drop_test.rb'
- 'test/integration/standard_filter_test.rb'
- 'test/integration/tags/for_tag_test.rb'
- 'test/integration/tags/include_tag_test.rb'
- 'test/integration/tags/standard_tag_test.rb'
- 'test/unit/context_unit_test.rb'
# Offense count: 2
Lint/AmbiguousOperator:
Exclude:
- 'test/unit/condition_unit_test.rb'
# Offense count: 16
# Configuration parameters: AllowSafeAssignment.
Lint/AssignmentInCondition:
Exclude:
- 'lib/liquid/block_body.rb'
- 'lib/liquid/lexer.rb'
- 'lib/liquid/standardfilters.rb'
- 'lib/liquid/tags/for.rb'
- 'lib/liquid/tags/if.rb'
- 'lib/liquid/tags/include.rb'
- 'lib/liquid/tags/raw.rb'
- 'lib/liquid/variable.rb'
- 'performance/profile.rb'
- 'test/test_helper.rb'
- 'test/unit/tokenizer_unit_test.rb'
# Offense count: 2
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
@@ -110,48 +14,7 @@ Lint/InheritException:
Exclude:
- 'lib/liquid/interrupts.rb'
# Offense count: 10
# Cop supports --auto-correct.
# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments.
Lint/UnusedBlockArgument:
Exclude:
- 'lib/liquid/condition.rb'
- 'lib/liquid/context.rb'
- 'lib/liquid/document.rb'
- 'lib/liquid/parse_context.rb'
- 'lib/liquid/template.rb'
- 'performance/shopify/json_filter.rb'
- 'test/integration/filter_test.rb'
- 'test/integration/render_profiling_test.rb'
- 'test/integration/variable_test.rb'
- 'test/unit/condition_unit_test.rb'
# Offense count: 12
# Cop supports --auto-correct.
# Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods.
Lint/UnusedMethodArgument:
Exclude:
- 'example/server/liquid_servlet.rb'
- 'test/integration/blank_test.rb'
- 'test/integration/error_handling_test.rb'
- 'test/integration/filter_test.rb'
- 'test/integration/output_test.rb'
- 'test/integration/standard_filter_test.rb'
- 'test/integration/tags/include_tag_test.rb'
- 'test/unit/strainer_unit_test.rb'
# Offense count: 2
Lint/UselessAssignment:
Exclude:
- 'performance/shopify/database.rb'
# Offense count: 1
# Configuration parameters: CheckForMethodsWithNoSideEffects.
Lint/Void:
Exclude:
- 'lib/liquid/parse_context.rb'
# Offense count: 95
# Offense count: 98
# Cop supports --auto-correct.
# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
# URISchemes: http, https
@@ -177,235 +40,9 @@ Naming/ConstantName:
- 'performance/shopify/paginate.rb'
- 'test/integration/tags/include_tag_test.rb'
# Offense count: 2
# Configuration parameters: .
# SupportedStyles: snake_case, camelCase
Naming/MethodName:
EnforcedStyle: snake_case
# Offense count: 3
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: always, conditionals
Style/AndOr:
Exclude:
- 'lib/liquid/i18n.rb'
- 'lib/liquid/tags/table_row.rb'
- 'lib/liquid/tokenizer.rb'
# Offense count: 40
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: braces, no_braces, context_dependent
Style/BracesAroundHashParameters:
Exclude:
- 'test/integration/error_handling_test.rb'
- 'test/integration/filter_test.rb'
- 'test/integration/render_profiling_test.rb'
- 'test/integration/standard_filter_test.rb'
- 'test/integration/tags/echo_test.rb'
- 'test/integration/tags/increment_tag_test.rb'
- 'test/integration/tags/standard_tag_test.rb'
- 'test/integration/template_test.rb'
- 'test/unit/condition_unit_test.rb'
- 'test/unit/context_unit_test.rb'
# Offense count: 5
Style/ClassVars:
Exclude:
- 'lib/liquid/condition.rb'
- 'lib/liquid/strainer.rb'
- 'lib/liquid/template.rb'
# Offense count: 2
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions.
# SupportedStyles: assign_to_condition, assign_inside_condition
Style/ConditionalAssignment:
Exclude:
- 'lib/liquid/errors.rb'
- 'performance/shopify/shop_filter.rb'
# Offense count: 1
# Configuration parameters: AllowCoercion.
Style/DateTime:
Exclude:
- 'test/unit/context_unit_test.rb'
# Offense count: 2
# Cop supports --auto-correct.
Style/EachWithObject:
Exclude:
- 'performance/shopify/database.rb'
# Offense count: 1
# Cop supports --auto-correct.
Style/EmptyCaseCondition:
Exclude:
- 'lib/liquid/lexer.rb'
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: each, for
Style/For:
Exclude:
- 'performance/shopify/shop_filter.rb'
# Offense count: 9
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: format, sprintf, percent
Style/FormatString:
Exclude:
- 'example/server/example_servlet.rb'
- 'performance/shopify/money_filter.rb'
- 'performance/shopify/weight_filter.rb'
- 'test/integration/filter_test.rb'
- 'test/integration/hash_ordering_test.rb'
# Offense count: 115
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: always, never
Style/FrozenStringLiteralComment:
Enabled: false
# Offense count: 30
# Cop supports --auto-correct.
# Configuration parameters: IgnoreMacros, IgnoredMethods, IncludedMacros, AllowParenthesesInMultilineCall, AllowParenthesesInChaining, AllowParenthesesInCamelCaseMethod, EnforcedStyle.
# SupportedStyles: require_parentheses, omit_parentheses
Style/MethodCallWithArgsParentheses:
Exclude:
- 'Gemfile'
- 'Rakefile'
- 'lib/liquid/block_body.rb'
- 'lib/liquid/parser.rb'
- 'lib/liquid/tags/for.rb'
- 'liquid.gemspec'
- 'performance/shopify/database.rb'
- 'performance/shopify/liquid.rb'
- 'test/test_helper.rb'
- 'test/unit/condition_unit_test.rb'
- 'test/unit/tags/if_tag_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: 52
# Cop supports --auto-correct.
Style/PerlBackrefs:
Enabled: false
# Offense count: 33
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: compact, exploded
Style/RaiseArgs:
Enabled: false
# Offense count: 1
# Cop supports --auto-correct.
Style/RedundantSelf:
Exclude:
- 'lib/liquid/strainer.rb'
# Offense count: 5
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, AllowInnerSlashes.
# SupportedStyles: slashes, percent_r, mixed
Style/RegexpLiteral:
Exclude:
- 'lib/liquid/file_system.rb'
- 'lib/liquid/standardfilters.rb'
- 'performance/shopify/shop_filter.rb'
- 'test/unit/condition_unit_test.rb'
# Offense count: 3
# Cop supports --auto-correct.
# Configuration parameters: ConvertCodeThatCanStartToReturnNil, Whitelist.
# Whitelist: present?, blank?, presence, try, try!
Style/SafeNavigation:
Exclude:
- 'lib/liquid/drop.rb'
- 'lib/liquid/strainer.rb'
- 'lib/liquid/tokenizer.rb'
# Offense count: 10
# Cop supports --auto-correct.
# Configuration parameters: AllowAsExpressionSeparator.
Style/Semicolon:
Exclude:
- 'performance/shopify/database.rb'
- 'test/integration/error_handling_test.rb'
- 'test/integration/template_test.rb'
- 'test/unit/context_unit_test.rb'
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: use_perl_names, use_english_names
Style/SpecialGlobalVars:
Exclude:
- 'performance/shopify/liquid.rb'
# Offense count: 2
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: single_quotes, double_quotes
Style/StringLiteralsInInterpolation:
Exclude:
- 'performance/shopify/tag_filter.rb'
# 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: 21
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyleForMultiline.
# SupportedStylesForMultiline: comma, consistent_comma, no_comma
Style/TrailingCommaInArrayLiteral:
Exclude:
- 'lib/liquid/parse_tree_visitor.rb'
- 'lib/liquid/tags/include.rb'
- 'test/integration/parse_tree_visitor_test.rb'
- 'test/integration/standard_filter_test.rb'
# Offense count: 9
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyleForMultiline.
# SupportedStylesForMultiline: comma, consistent_comma, no_comma
Style/TrailingCommaInHashLiteral:
Exclude:
- 'lib/liquid/condition.rb'
- 'lib/liquid/lexer.rb'
- 'lib/liquid/standardfilters.rb'
- 'performance/shopify/comment_form.rb'
- 'performance/shopify/database.rb'
- 'performance/shopify/paginate.rb'
- 'performance/theme_runner.rb'
- 'test/integration/output_test.rb'
- 'test/unit/context_unit_test.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'
- 'lib/liquid/template.rb'

View File

@@ -1,14 +1,16 @@
language: ruby
cache: bundler
os: [osx, linux]
rvm:
- 2.4
- 2.5
- &latest_ruby 2.6
- 2.7
- 2.7.0-preview1
- 2.7.0-preview2
- 2.7.0-preview3
- ruby-head
- jruby-head
- truffleruby
matrix:
include:
@@ -17,8 +19,9 @@ matrix:
name: Profiling Memory Usage
allow_failures:
- rvm: ruby-head
- rvm: jruby-head
- rvm: truffleruby
- rvm: 2.7
- rvm: 2.7.0-preview2
- rvm: 2.7.0-preview3
branches:
only:

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"

View File

@@ -1,18 +1,20 @@
# frozen_string_literal: true
require 'rake'
require 'rake/testtask'
$LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
$LOAD_PATH.unshift(File.expand_path("../lib", __FILE__))
require "liquid/version"
task default: [:test, :rubocop]
task(default: [: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
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
@@ -25,7 +27,7 @@ task :rubocop do
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
@@ -47,7 +49,7 @@ task :test do
end
end
task gem: :build
task(gem: :build)
task :build do
system "gem build liquid.gemspec"
end
@@ -94,7 +96,7 @@ namespace :memory_profile do
end
end
desc "Run example"
desc("Run example")
task :example do
ruby "-w -d -Ilib example/server/server.rb"
end

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)

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
class LiquidServlet < WEBrick::HTTPServlet::AbstractServlet
def do_GET(req, res)
handle(:get, req, res)
@@ -9,17 +11,17 @@ class LiquidServlet < WEBrick::HTTPServlet::AbstractServlet
private
def handle(type, req, res)
@request = req
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.status = 200
@response.body = Liquid::Template.parse(read_template).render(@assigns, filters: [ProductsFilter])
@response.body = Liquid::Template.parse(read_template).render(@assigns, filters: [ProductsFilter])
end
def read_template(filename = @action)

View File

@@ -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
@@ -21,10 +23,10 @@
module Liquid
FilterSeparator = /\|/
ArgumentSeparator = ','.freeze
FilterArgumentSeparator = ':'.freeze
VariableAttributeSeparator = '.'.freeze
WhitespaceControl = '-'.freeze
ArgumentSeparator = ','
FilterArgumentSeparator = ':'
VariableAttributeSeparator = '.'
WhitespaceControl = '-'
TagStart = /\{\%/
TagEnd = /\%\}/
VariableSignature = /\(?[\w\-\.\[\]]\)?/
@@ -76,7 +78,10 @@ require 'liquid/tokenizer'
require 'liquid/parse_context'
require 'liquid/partial_cache'
require 'liquid/usage'
require 'liquid/register'
require 'liquid/static_registers'
# Load all the tags of the standard library
#
Dir["#{__dir__}/liquid/tags/*.rb"].each { |f| require f }
Dir["#{__dir__}/liquid/registers/*.rb"].each { |f| require f }

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
class Block < Tag
MAX_DEPTH = 100
@@ -27,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,
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))
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
@@ -52,7 +54,7 @@ module Liquid
def parse_body(body, tokens)
if parse_context.depth >= MAX_DEPTH
raise StackLevelError, "Nesting too deep".freeze
raise StackLevelError, "Nesting too deep"
end
parse_context.depth += 1
begin
@@ -61,7 +63,7 @@ module Liquid
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))
raise SyntaxError, parse_context.locale.t("errors.syntax.tag_never_closed", block_name: block_name)
end
# this tag is not registered with the system

View File

@@ -1,17 +1,19 @@
# frozen_string_literal: true
module Liquid
class BlockBody
LiquidTagToken = /\A\s*(\w+)\s*(.*?)\z/o
FullToken = /\A#{TagStart}#{WhitespaceControl}?(\s*)(\w+)(\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om
LiquidTagToken = /\A\s*(\w+)\s*(.*?)\z/o
FullToken = /\A#{TagStart}#{WhitespaceControl}?(\s*)(\w+)(\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om
WhitespaceOrNothing = /\A\s*\z/
TAGSTART = "{%".freeze
VARSTART = "{{".freeze
TAGSTART = "{%"
VARSTART = "{{"
attr_reader :nodelist
def initialize
@nodelist = []
@blank = true
@blank = true
end
def parse(tokenizer, parse_context, &block)
@@ -25,16 +27,16 @@ module Liquid
end
private def parse_for_liquid_tag(tokenizer, parse_context)
while token = tokenizer.shift
while (token = tokenizer.shift)
unless token.empty? || token =~ WhitespaceOrNothing
unless token =~ LiquidTagToken
# line isn't empty but didn't match tag syntax, yield and let the
# caller raise a syntax error
return yield token, token
end
tag_name = $1
markup = $2
unless tag = registered_tags[tag_name]
tag_name = Regexp.last_match(1)
markup = Regexp.last_match(2)
unless (tag = registered_tags[tag_name])
# end parsing if we reach an unknown tag and let the caller decide
# determine how to proceed
return yield tag_name, markup
@@ -50,7 +52,7 @@ module Liquid
end
private def parse_for_document(tokenizer, parse_context, &block)
while token = tokenizer.shift
while (token = tokenizer.shift)
next if token.empty?
case
when token.start_with?(TAGSTART)
@@ -58,21 +60,21 @@ module Liquid
unless token =~ FullToken
raise_missing_tag_terminator(token, parse_context)
end
tag_name = $2
markup = $4
tag_name = Regexp.last_match(2)
markup = Regexp.last_match(4)
if parse_context.line_number
# newlines inside the tag should increase the line number,
# particularly important for multiline {% liquid %} tags
parse_context.line_number += $1.count("\n".freeze) + $3.count("\n".freeze)
parse_context.line_number += Regexp.last_match(1).count("\n") + Regexp.last_match(3).count("\n")
end
if tag_name == 'liquid'.freeze
if tag_name == 'liquid'
liquid_tag_tokenizer = Tokenizer.new(markup, line_number: parse_context.line_number, for_liquid_tag: true)
next parse_for_liquid_tag(liquid_tag_tokenizer, parse_context, &block)
end
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
@@ -101,7 +103,7 @@ module Liquid
def whitespace_handler(token, parse_context)
if token[2] == WhitespaceControl
previous_token = @nodelist.last
if previous_token.is_a? String
if previous_token.is_a?(String)
previous_token.rstrip!
end
end
@@ -113,14 +115,14 @@ module Liquid
end
def render(context)
render_to_output_buffer(context, '')
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
@@ -129,7 +131,7 @@ module Liquid
when Variable
render_node(context, output, node)
when Block
render_node(context, node.blank? ? '' : output, node)
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
@@ -152,7 +154,13 @@ module Liquid
private
def render_node(context, output, node)
node.render_to_output_buffer(context, output)
if node.disabled?(context)
output << node.disabled_error_message
return
end
disable_tags(context, node.disabled_tags) do
node.render_to_output_buffer(context, output)
end
rescue UndefinedVariable, UndefinedDropMethod, UndefinedFilter => e
context.handle_error(e, node.line_number)
rescue ::StandardError => e
@@ -160,10 +168,15 @@ module Liquid
output << context.handle_error(e, line_number)
end
def disable_tags(context, tags, &block)
return yield if tags.empty?
context.registers[:disabled_tags].disable(tags, &block)
end
def raise_if_resource_limits_reached(context, length)
context.resource_limits.render_length += length
return unless context.resource_limits.reached?
raise MemoryError.new("Memory limits exceeded".freeze)
raise MemoryError, "Memory limits exceeded"
end
def create_variable(token, parse_context)
@@ -175,11 +188,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: TagEnd.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: VariableEnd.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
#
@@ -8,21 +10,21 @@ module Liquid
#
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|
'==' => ->(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
@@ -33,9 +35,10 @@ module Liquid
attr_accessor :left, :operator, :right
def initialize(left = nil, operator = nil, right = nil)
@left = left
@left = left
@operator = operator
@right = right
@right = right
@child_relation = nil
@child_condition = nil
end
@@ -60,12 +63,12 @@ module Liquid
end
def or(condition)
@child_relation = :or
@child_relation = :or
@child_condition = condition
end
def and(condition)
@child_relation = :and
@child_relation = :and
@child_condition = condition
end
@@ -78,7 +81,7 @@ module Liquid
end
def inspect
"#<Condition #{[@left, @operator, @right].compact.join(' '.freeze)}>"
"#<Condition #{[@left, @operator, @right].compact.join(' ')}>"
end
protected
@@ -113,10 +116,10 @@ module Liquid
# return this as the result.
return context.evaluate(left) if op.nil?
left = context.evaluate(left)
left = context.evaluate(left)
right = context.evaluate(right)
operation = self.class.operators[op] || raise(Liquid::ArgumentError.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 +127,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
#
@@ -16,18 +18,17 @@ module Liquid
attr_accessor :exception_renderer, :template_name, :partial, :global_filter, :strict_variables, :strict_filters
# rubocop:disable Metrics/ParameterLists
def self.build(environments: {}, outer_scope: {}, registers: {}, rethrow_errors: false, resource_limits: nil, static_registers: {}, static_environments: {})
new(environments, outer_scope, registers, rethrow_errors, resource_limits, static_registers, static_environments)
def self.build(environments: {}, outer_scope: {}, registers: {}, rethrow_errors: false, resource_limits: nil, static_environments: {})
new(environments, outer_scope, registers, rethrow_errors, resource_limits, static_environments)
end
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_registers = {}, static_environments = {})
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_environments = {})
@environments = [environments]
@environments.flatten!
@static_environments = [static_environments].flat_map(&:freeze).freeze
@scopes = [(outer_scope || {})]
@registers = registers
@static_registers = static_registers.freeze
@errors = []
@partial = false
@strict_variables = false
@@ -35,15 +36,13 @@ module Liquid
@base_scope_depth = 0
squash_instance_assigns_with_environments
@this_stack_used = false
self.exception_renderer = Template.default_exception_renderer
if rethrow_errors
self.exception_renderer = ->(e) { raise }
self.exception_renderer = ->(_e) { raise }
end
@interrupts = []
@filters = []
@interrupts = []
@filters = []
@global_filter = nil
end
# rubocop:enable Metrics/ParameterLists
@@ -88,7 +87,7 @@ module Liquid
def handle_error(e, line_number = nil)
e = internal_error unless e.is_a?(Liquid::Error)
e.template_name ||= template_name
e.line_number ||= line_number
e.line_number ||= line_number
errors.push(e)
exception_renderer.call(e).to_s
end
@@ -122,19 +121,11 @@ module Liquid
# end
#
# context['var] #=> nil
def stack(new_scope = nil)
old_stack_used = @this_stack_used
if new_scope
push(new_scope)
@this_stack_used = true
else
@this_stack_used = false
end
def stack(new_scope = {})
push(new_scope)
yield
ensure
pop if @this_stack_used
@this_stack_used = old_stack_used
pop
end
# Creates a new context inheriting resource limits, filters, environment etc.,
@@ -145,13 +136,13 @@ module Liquid
Context.build(
resource_limits: resource_limits,
static_environments: static_environments,
static_registers: static_registers
registers: StaticRegisters.new(registers)
).tap do |subcontext|
subcontext.base_scope_depth = base_scope_depth + 1
subcontext.base_scope_depth = base_scope_depth + 1
subcontext.exception_renderer = exception_renderer
subcontext.filters = @filters
subcontext.filters = @filters
subcontext.strainer = nil
subcontext.errors = errors
subcontext.errors = errors
subcontext.warnings = warnings
end
end
@@ -162,10 +153,6 @@ module Liquid
# Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
def []=(key, value)
unless @this_stack_used
@this_stack_used = true
push({})
end
@scopes[0][key] = value
end
@@ -201,7 +188,7 @@ module Liquid
try_variable_find_in_environments(key, raise_on_not_found: raise_on_not_found)
end
variable = variable.to_liquid
variable = variable.to_liquid
variable.context = self if variable.respond_to?(:context=)
variable
@@ -215,7 +202,7 @@ module Liquid
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
@@ -246,7 +233,7 @@ module Liquid
end
def check_overflow
raise StackLevelError, "Nesting too deep".freeze if overflow?
raise StackLevelError, "Nesting too deep" if overflow?
end
def overflow?

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,7 @@ 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 +69,7 @@ module Liquid
if include?(Enumerable)
blacklist += Enumerable.public_instance_methods
blacklist -= [:sort, :count, :first, :min, :max, :include?]
blacklist -= [:sort, :count, :first, :min, :max]
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,7 +7,7 @@ module Liquid
attr_accessor :markup_context
def to_s(with_prefix = true)
str = ""
str = +""
str << message_prefix if with_prefix
str << super()
@@ -20,11 +22,11 @@ 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
@@ -38,19 +40,19 @@ module Liquid
end
end
ArgumentError = Class.new(Error)
ContextError = Class.new(Error)
FileSystemError = Class.new(Error)
StandardError = Class.new(Error)
SyntaxError = Class.new(Error)
StackLevelError = Class.new(Error)
TaintedError = Class.new(Error)
MemoryError = Class.new(Error)
ZeroDivisionError = Class.new(Error)
FloatDomainError = Class.new(Error)
UndefinedVariable = Class.new(Error)
ArgumentError = Class.new(Error)
ContextError = Class.new(Error)
FileSystemError = Class.new(Error)
StandardError = Class.new(Error)
SyntaxError = Class.new(Error)
StackLevelError = Class.new(Error)
TaintedError = Class.new(Error)
MemoryError = Class.new(Error)
ZeroDivisionError = Class.new(Error)
FloatDomainError = Class.new(Error)
UndefinedVariable = Class.new(Error)
UndefinedDropMethod = Class.new(Error)
UndefinedFilter = Class.new(Error)
UndefinedFilter = Class.new(Error)
MethodOverrideError = Class.new(Error)
InternalError = Class.new(Error)
InternalError = Class.new(Error)
end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
class Expression
class MethodLiteral
@@ -14,11 +16,11 @@ 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
@@ -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.
#
@@ -44,8 +46,8 @@ module Liquid
class LocalFileSystem
attr_accessor :root
def initialize(root, pattern = "_%s.liquid".freeze)
@root = root
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 %r{\A[^./][a-zA-Z0-9_/]+\z}.match?(template_path)
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,10 +1,12 @@
# frozen_string_literal: true
module Liquid
class ForloopDrop < Drop
def initialize(name, length, parentloop)
@name = name
@length = length
@name = name
@length = length
@parentloop = parentloop
@index = 0
@index = 0
end
attr_reader :name, :length, :parentloop

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'yaml'
module Liquid
@@ -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,10 +1,12 @@
# 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

View File

@@ -1,24 +1,26 @@
# 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-]*\??/
IDENTIFIER = /[a-zA-Z_][\w-]*\??/
SINGLE_STRING_LITERAL = /'[^\']*'/
DOUBLE_STRING_LITERAL = /"[^\"]*"/
NUMBER_LITERAL = /-?\d+(\.\d+)?/
DOTDOT = /\.\./
COMPARISON_OPERATOR = /==|!=|<>|<=?|>=?|contains(?=\s)/
NUMBER_LITERAL = /-?\d+(\.\d+)?/
DOTDOT = /\.\./
COMPARISON_OPERATOR = /==|!=|<>|<=?|>=?|contains(?=\s)/
WHITESPACE_OR_NOTHING = /\s*/
def initialize(input)
@@ -31,16 +33,21 @@ 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))
[:comparison, t]
elsif (t = @ss.scan(SINGLE_STRING_LITERAL))
[:string, t]
elsif (t = @ss.scan(DOUBLE_STRING_LITERAL))
[:string, t]
elsif (t = @ss.scan(NUMBER_LITERAL))
[:number, t]
elsif (t = @ss.scan(IDENTIFIER))
[:id, t]
elsif (t = @ss.scan(DOTDOT))
[:dotdot, t]
else
c = @ss.getch
if s = SPECIALS[c]
c = @ss.getch
if (s = SPECIALS[c])
[s, c]
else
raise SyntaxError, "Unexpected character #{c}"

View File

@@ -25,3 +25,5 @@
render: "Syntax error in tag 'render' - Template name must be a quoted string"
argument:
include: "Argument error in tag 'include' - Illegal template name"
disabled:
tag: "usage is not allowed in this context"

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
class ParseContext
attr_accessor :locale, :line_number, :trim_whitespace, :depth
@@ -5,9 +7,11 @@ module Liquid
def initialize(options = {})
@template_options = options ? options.dup : {}
@locale = @template_options[:locale] ||= I18n.new
@locale = @template_options[:locale] ||= I18n.new
@warnings = []
self.depth = 0
self.depth = 0
self.partial = false
end
@@ -18,8 +22,8 @@ module Liquid
def partial=(value)
@partial = value
@options = value ? partial_options : @template_options
@error_mode = @options[:error_mode] || Template.error_mode
value
end
def partial_options
@@ -28,7 +32,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

@@ -11,7 +11,7 @@ module Liquid
end
def initialize(node, callbacks)
@node = node
@node = node
@callbacks = callbacks
end
@@ -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,9 +1,11 @@
# frozen_string_literal: true
module Liquid
class Parser
def initialize(input)
l = Lexer.new(input)
l = Lexer.new(input)
@tokens = l.tokenize
@p = 0 # pointer to current location
@p = 0 # pointer to current location
end
def jump(point)
@@ -51,7 +53,7 @@ module Liquid
token = @tokens[@p]
if token[0] == :id
variable_signature
elsif SINGLE_TOKEN_EXPRESSION_TYPES.include? token[0]
elsif SINGLE_TOKEN_EXPRESSION_TYPES.include?(token[0])
consume
elsif token.first == :open_round
consume
@@ -66,10 +68,10 @@ module Liquid
end
def argument
str = ""
str = +""
# might be a keyword argument (identifier: expression)
if look(:id) && look(:colon, 1)
str << consume << consume << ' '.freeze
str << consume << consume << ' '
end
str << expression

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
module ParserSwitching
def parse_with_selected_parser(markup)
@@ -19,7 +21,7 @@ module Liquid
def strict_parse_with_error_context(markup)
strict_parse(markup)
rescue SyntaxError => e
e.line_number = line_number
e.line_number = line_number
e.markup_context = markup_context(markup)
raise e
end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
class PartialCache
def self.load(template_name, context:, parse_context:)
@@ -6,7 +8,8 @@ module Liquid
return cached if cached
file_system = (context.registers[:file_system] ||= Liquid::Template.file_system)
source = file_system.read_template_file(template_name)
source = file_system.read_template_file(template_name)
parse_context.partial = true
partial = Liquid::Template.parse(source, parse_context)

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'liquid/profiler/hooks'
module Liquid
@@ -44,7 +46,7 @@ module Liquid
include Enumerable
class Timing
attr_reader :code, :partial, :line_number, :children
attr_reader :code, :partial, :line_number, :children, :total_time, :self_time
def initialize(node, partial)
@code = node.respond_to?(:raw) ? node.raw : node
@@ -62,7 +64,18 @@ module Liquid
end
def finish
@end_time = Time.now
@end_time = Time.now
@total_time = @end_time - @start_time
if @children.empty?
@self_time = @total_time
else
total_children_time = 0
@children.each do |child|
total_children_time += child.total_time
end
@self_time = @total_time - total_children_time
end
end
def render_time
@@ -96,14 +109,14 @@ module Liquid
Thread.current[:liquid_profiler]
end
def initialize
@partial_stack = ["<root>"]
def initialize(partial_name = "<root>")
@partial_stack = [partial_name]
@root_timing = Timing.new("", current_partial)
@root_timing = Timing.new("", current_partial)
@timing_stack = [@root_timing]
@render_start_at = Time.now
@render_end_at = @render_start_at
@render_end_at = @render_start_at
end
def start

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
class BlockBody
def render_node_with_profiling(context, output, node)

View File

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

6
lib/liquid/register.rb Normal file
View File

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

View File

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

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
class ResourceLimits
attr_accessor :render_length, :render_score, :assign_score,
@@ -5,8 +7,8 @@ module Liquid
def initialize(limits)
@render_length_limit = limits[:render_length_limit]
@render_score_limit = limits[:render_score_limit]
@assign_score_limit = limits[:assign_score_limit]
@render_score_limit = limits[:render_score_limit]
@assign_score_limit = limits[:assign_score_limit]
reset
end

View File

@@ -1,20 +1,22 @@
# 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+));)/
STRIP_HTML_BLOCKS = Regexp.union(
/<script.*?<\/script>/m,
STRIP_HTML_BLOCKS = Regexp.union(
%r{<script.*?</script>}m,
/<!--.*?-->/m,
/<style.*?<\/style>/m
%r{<style.*?</style>}m
)
STRIP_HTML_TAGS = /<.*?>/m
@@ -72,23 +74,28 @@ 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)
length = Utils.to_integer(length)
truncate_string_str = truncate_string.to_s
l = length - truncate_string_str.length
l = 0 if l < 0
input_str.length > length ? input_str[0...l].concat(truncate_string_str) : input_str
end
def truncatewords(input, words = 15, truncate_string = "...".freeze)
def truncatewords(input, words = 15, truncate_string = "...")
return if input.nil?
wordlist = input.to_s.split
words = Utils.to_integer(words)
words = Utils.to_integer(words)
l = words - 1
l = 0 if l < 0
wordlist.length > l ? wordlist[0..l].join(" ".freeze).concat(truncate_string.to_s) : input
wordlist.length > l ? wordlist[0..l].join(" ").concat(truncate_string.to_s) : input
end
# Split input string into an array of substrings separated by given pattern.
@@ -113,7 +120,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,18 +128,18 @@ 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)
InputIterator.new(input).join(glue)
def join(input, glue = ' ')
InputIterator.new(input, context).join(glue)
end
# Sort elements of the array
# provide optional property with which to sort an array of hashes or drops
def sort(input, property = nil)
ary = InputIterator.new(input)
ary = InputIterator.new(input, context)
return [] if ary.empty?
@@ -152,7 +159,7 @@ module Liquid
# Sort elements of an array ignoring case if strings
# provide optional property with which to sort an array of hashes or drops
def sort_natural(input, property = nil)
ary = InputIterator.new(input)
ary = InputIterator.new(input, context)
return [] if ary.empty?
@@ -172,7 +179,7 @@ module Liquid
# Filter the elements of an array to those with a certain property value.
# By default the target is any truthy value.
def where(input, property, target_value = nil)
ary = InputIterator.new(input)
ary = InputIterator.new(input, context)
if ary.empty?
[]
@@ -194,7 +201,7 @@ module Liquid
# Remove duplicate elements from an array
# provide optional property with which to determine uniqueness
def uniq(input, property = nil)
ary = InputIterator.new(input)
ary = InputIterator.new(input, context)
if property.nil?
ary.uniq
@@ -211,16 +218,16 @@ module Liquid
# Reverse the elements of an array
def reverse(input)
ary = InputIterator.new(input)
ary = InputIterator.new(input, context)
ary.reverse
end
# map/collect on a given property
def map(input, property)
InputIterator.new(input).map do |e|
InputIterator.new(input, context).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]
@@ -234,7 +241,7 @@ module Liquid
# Remove nils within an array
# provide optional property with which to check for nil
def compact(input, property = nil)
ary = InputIterator.new(input)
ary = InputIterator.new(input, context)
if property.nil?
ary.compact
@@ -250,23 +257,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
@@ -276,9 +283,9 @@ module Liquid
def concat(input, array)
unless array.respond_to?(:to_ary)
raise ArgumentError.new("concat filter requires an array argument")
raise ArgumentError, "concat filter requires an array argument"
end
InputIterator.new(input).concat(array)
InputIterator.new(input, context).concat(array)
end
# prepend a string to another
@@ -288,7 +295,7 @@ module Liquid
# Add <br /> tags in front of all newlines in input string
def newline_to_br(input)
input.to_s.gsub(/\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 +332,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,19 +426,28 @@ module Liquid
result.is_a?(BigDecimal) ? result.to_f : result
end
def default(input, default_value = ''.freeze)
if !input || input.respond_to?(:empty?) && input.empty?
Usage.increment("default_filter_received_false_value") if input == false # See https://github.com/Shopify/liquid/issues/1127
default_value
else
input
end
# Set a default value when the input is nil, false or empty
#
# Example:
# {{ product.title | default: "No Title" }}
#
# Use `allow_false` when an input should only be tested against nil or empty and not false.
#
# Example:
# {{ product.title | default: "No Title", allow_false: true }}
#
def default(input, default_value = '', options = {})
options = {} unless options.is_a?(Hash)
false_check = options['allow_false'] ? input.nil? : !input
false_check || (input.respond_to?(:empty?) && input.empty?) ? default_value : input
end
private
attr_reader :context
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)
@@ -458,8 +474,9 @@ module Liquid
class InputIterator
include Enumerable
def initialize(input)
@input = if input.is_a?(Array)
def initialize(input, context)
@context = context
@input = if input.is_a?(Array)
input.flatten
elsif input.is_a?(Hash)
[input]
@@ -497,6 +514,7 @@ module Liquid
def each
@input.each do |e|
e.context = @context if e.respond_to?(:context=)
yield(e.respond_to?(:to_liquid) ? e.to_liquid : e)
end
end

View File

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

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'set'
module Liquid
@@ -27,7 +29,7 @@ module Liquid
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(', ')}"
@@ -54,7 +56,7 @@ module Liquid
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,11 +1,13 @@
# frozen_string_literal: true
module Liquid
class TablerowloopDrop < Drop
def initialize(length, cols)
@length = length
@row = 1
@col = 1
@cols = cols
@index = 0
@row = 1
@col = 1
@cols = cols
@index = 0
end
attr_reader :length, :col, :row

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
class Tag
attr_reader :nodelist, :tag_name, :line_number, :parse_context
@@ -11,14 +13,22 @@ module Liquid
tag
end
def disable_tags(*tags)
disabled_tags.push(*tags)
end
private :new
def disabled_tags
@disabled_tags ||= []
end
end
def initialize(tag_name, markup, parse_context)
@tag_name = tag_name
@markup = markup
@tag_name = tag_name
@markup = markup
@parse_context = parse_context
@line_number = parse_context.line_number
@line_number = parse_context.line_number
end
def parse(_tokens)
@@ -33,7 +43,15 @@ module Liquid
end
def render(_context)
''.freeze
''
end
def disabled?(context)
context.registers[:disabled_tags].disabled?(tag_name)
end
def disabled_error_message
"#{tag_name} #{options[:locale].t('errors.disabled.tag')}"
end
# For backwards compatibility with custom tags. In a future release, the semantics
@@ -47,5 +65,9 @@ module Liquid
def blank?
false
end
def disabled_tags
self.class.disabled_tags
end
end
end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
# Assign sets a variable in your template.
#
@@ -11,7 +13,7 @@ module Liquid
Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om
def self.syntax_error_translation_key
"errors.syntax.assign".freeze
"errors.syntax.assign"
end
attr_reader :to, :from
@@ -19,10 +21,10 @@ module Liquid
def initialize(tag_name, markup, options)
super
if markup =~ Syntax
@to = $1
@from = Variable.new($2, options)
@to = Regexp.last_match(1)
@from = Variable.new(Regexp.last_match(2), options)
else
raise SyntaxError.new(options[:locale].t(self.class.syntax_error_translation_key))
raise SyntaxError, options[:locale].t(self.class.syntax_error_translation_key)
end
end
@@ -59,5 +61,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.
#
@@ -16,9 +18,9 @@ module Liquid
def initialize(tag_name, markup, options)
super
if markup =~ Syntax
@to = $1
@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
@@ -35,5 +37,5 @@ module Liquid
end
end
Template.register_tag('capture'.freeze, Capture)
Template.register_tag('capture', Capture)
end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
class Case < Block
Syntax = /(#{QuotedFragment})/o
@@ -10,17 +12,15 @@ module Liquid
@blocks = []
if markup =~ Syntax
@left = Expression.parse($1)
@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,9 +29,9 @@ 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
@@ -39,16 +39,14 @@ module Liquid
end
def render_to_output_buffer(context, output)
context.stack do
execute_else_block = true
execute_else_block = true
@blocks.each do |block|
if block.else?
block.attachment.render_to_output_buffer(context, output) if execute_else_block
elsif block.evaluate(context)
execute_else_block = false
block.attachment.render_to_output_buffer(context, output)
end
@blocks.each do |block|
if block.else?
block.attachment.render_to_output_buffer(context, output) if execute_else_block
elsif block.evaluate(context)
execute_else_block = false
block.attachment.render_to_output_buffer(context, output)
end
end
@@ -62,12 +60,12 @@ module Liquid
while markup
unless markup =~ WhenSyntax
raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_when".freeze))
raise SyntaxError, options[:locale].t("errors.syntax.case_invalid_when")
end
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
@@ -75,7 +73,7 @@ module Liquid
def record_else_condition(markup)
unless markup.strip.empty?
raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_else".freeze))
raise SyntaxError, options[:locale].t("errors.syntax.case_invalid_else")
end
block = ElseCondition.new
@@ -90,5 +88,5 @@ module Liquid
end
end
Template.register_tag('case'.freeze, Case)
Template.register_tag('case', Case)
end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
class Comment < Block
def render_to_output_buffer(_context, output)
@@ -12,5 +14,5 @@ module Liquid
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.
#
@@ -21,38 +23,36 @@ module Liquid
super
case markup
when NamedSyntax
@variables = variables_from_string($2)
@name = Expression.parse($1)
@variables = variables_from_string(Regexp.last_match(2))
@name = Expression.parse(Regexp.last_match(1))
when SimpleSyntax
@variables = variables_from_string(markup)
@name = @variables.to_s
@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_to_output_buffer(context, output)
context.registers[:cycle] ||= {}
context.stack do
key = context.evaluate(@name)
iteration = context.registers[:cycle][key].to_i
key = context.evaluate(@name)
iteration = context.registers[:cycle][key].to_i
val = context.evaluate(@variables[iteration])
val = context.evaluate(@variables[iteration])
if val.is_a?(Array)
val = val.join
elsif !val.is_a?(String)
val = val.to_s
end
output << val
iteration += 1
iteration = 0 if iteration >= @variables.size
context.registers[:cycle][key] = 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
output
end
@@ -61,7 +61,7 @@ module Liquid
def variables_from_string(markup)
markup.split(',').collect do |var|
var =~ /\s*(#{QuotedFragment})\s*/o
$1 ? Expression.parse($1) : nil
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
@@ -32,5 +34,5 @@ module Liquid
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
# Echo outputs an expression
#
@@ -16,9 +18,9 @@ module Liquid
end
def render(context)
@variable.render_to_output_buffer(context, '')
@variable.render_to_output_buffer(context, +'')
end
end
Template.register_tag('echo'.freeze, Echo)
Template.register_tag('echo', Echo)
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.
@@ -66,7 +68,7 @@ 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
@@ -86,31 +88,33 @@ module Liquid
def lax_parse(markup)
if markup =~ Syntax
@variable_name = $1
collection_name = $2
@reversed = !!$3
@name = "#{@variable_name}-#{collection_name}"
@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|
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)
collection_name = p.expression
@name = "#{@variable_name}-#{collection_name}"
raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_in") unless p.id?('in')
collection_name = p.expression
@collection_name = Expression.parse(collection_name)
@reversed = p.id?('reversed'.freeze)
@name = "#{@variable_name}-#{collection_name}"
@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)
@@ -154,7 +158,7 @@ module Liquid
def render_segment(context, output, segment)
for_stack = context.registers[:for_stack] ||= []
length = segment.length
length = segment.length
context.stack do
loop_vars = Liquid::ForloopDrop.new(@name, length, for_stack[-1])
@@ -162,7 +166,7 @@ module Liquid
for_stack.push(loop_vars)
begin
context['forloop'.freeze] = loop_vars
context['forloop'] = loop_vars
segment.each do |item|
context[@variable_name] = item
@@ -170,11 +174,10 @@ module Liquid
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
@@ -186,13 +189,13 @@ module Liquid
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
@@ -212,5 +215,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
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
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,7 +34,7 @@ module Liquid
end
def unknown_tag(tag, markup, tokens)
if ['elsif'.freeze, 'else'.freeze].include?(tag)
if ['elsif', 'else'].include?(tag)
push_block(tag, markup)
else
super
@@ -40,11 +42,9 @@ module Liquid
end
def render_to_output_buffer(context, output)
context.stack do
@blocks.each do |block|
if block.evaluate(context)
return block.attachment.render_to_output_buffer(context, output)
end
@blocks.each do |block|
if block.evaluate(context)
return block.attachment.render_to_output_buffer(context, output)
end
end
@@ -54,7 +54,7 @@ module Liquid
private
def push_block(tag, markup)
block = if tag == 'else'.freeze
block = if tag == 'else'
ElseCondition.new
else
parse_with_selected_parser(markup)
@@ -66,17 +66,17 @@ module Liquid
def lax_parse(markup)
expressions = markup.scan(ExpressionsAndOperators)
raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless expressions.pop =~ Syntax
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($1), $2, Expression.parse($3))
raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless BOOLEAN_OPERATORS.include?(operator)
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.send(operator, condition)
condition = new_condition
end
@@ -94,7 +94,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
@@ -104,7 +104,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
@@ -119,5 +119,5 @@ module Liquid
end
end
Template.register_tag('if'.freeze, If)
Template.register_tag('if', If)
end

View File

@@ -1,19 +1,19 @@
# frozen_string_literal: true
module Liquid
class Ifchanged < Block
def render_to_output_buffer(context, output)
context.stack do
block_output = ''
super(context, block_output)
block_output = +''
super(context, block_output)
if block_output != context.registers[:ifchanged]
context.registers[:ifchanged] = block_output
output << block_output
end
if block_output != context.registers[:ifchanged]
context.registers[:ifchanged] = block_output
output << block_output
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,28 +16,30 @@ module Liquid
# {% include 'product' for products %}
#
class Include < Tag
Syntax = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?/o
SYNTAX = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o
Syntax = SYNTAX
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)
@alias_name = Regexp.last_match(5)
@variable_name_expr = variable_name ? Expression.parse(variable_name) : nil
@template_name_expr = Expression.parse(template_name)
@attributes = {}
@attributes = {}
markup.scan(TagAttributes) do |key, value|
@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
@@ -44,7 +48,7 @@ module Liquid
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 = PartialCache.load(
template_name,
@@ -52,7 +56,7 @@ module Liquid
parse_context: parse_context
)
context_variable_name = template_name.split('/'.freeze).last
context_variable_name = @alias_name || template_name.split('/').last
variable = if @variable_name_expr
context.evaluate(@variable_name_expr)
@@ -61,10 +65,10 @@ module Liquid
end
old_template_name = context.template_name
old_partial = context.partial
old_partial = context.partial
begin
context.template_name = template_name
context.partial = true
context.partial = true
context.stack do
@attributes.each do |key, value|
context[key] = context.evaluate(value)
@@ -82,7 +86,7 @@ module Liquid
end
ensure
context.template_name = old_template_name
context.partial = old_partial
context.partial = old_partial
end
output
@@ -95,11 +99,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
@@ -29,5 +31,5 @@ module Liquid
end
end
Template.register_tag('increment'.freeze, Increment)
Template.register_tag('increment', Increment)
end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
class Raw < Block
Syntax = /\A\s*\z/
@@ -10,16 +12,16 @@ module Liquid
end
def parse(tokens)
@body = ''
while token = tokens.shift
@body = +''
while (token = tokens.shift)
if token =~ FullTokenPossiblyInvalid
@body << $1 if $1 != "".freeze
return if block_delimiter == $2
@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_to_output_buffer(_context, output)
@@ -38,11 +40,11 @@ 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))
unless Syntax.match?(markup)
raise SyntaxError, parse_context.locale.t("errors.syntax.tag_unexpected_args", tag: tag_name)
end
end
end
Template.register_tag('raw'.freeze, Raw)
Template.register_tag('raw', Raw)
end

View File

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

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
class TableRow < Block
Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o
@@ -7,33 +9,32 @@ module Liquid
def initialize(tag_name, markup, options)
super
if markup =~ Syntax
@variable_name = $1
@collection_name = Expression.parse($2)
@attributes = {}
@variable_name = Regexp.last_match(1)
@collection_name = Expression.parse(Regexp.last_match(2))
@attributes = {}
markup.scan(TagAttributes) 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_to_output_buffer(context, output)
collection = context.evaluate(@collection_name) or return ''.freeze
(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
length = collection.length
cols = context.evaluate(@attributes['cols'.freeze]).to_i
cols = context.evaluate(@attributes['cols']).to_i
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
@@ -61,5 +62,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
@@ -7,18 +9,16 @@ module Liquid
#
class Unless < If
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_to_output_buffer(context, output)
end
# First condition is interpreted backwards ( if not )
first_block = @blocks.first
unless first_block.evaluate(context)
return first_block.attachment.render_to_output_buffer(context, output)
end
# After the first condition unless works just like if
@blocks[1..-1].each do |block|
if block.evaluate(context)
return block.attachment.render_to_output_buffer(context, output)
end
# After the first condition unless works just like if
@blocks[1..-1].each do |block|
if block.evaluate(context)
return block.attachment.render_to_output_buffer(context, output)
end
end
@@ -26,5 +26,5 @@ module Liquid
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
@@ -22,7 +24,7 @@ module Liquid
include Enumerable
def initialize
@tags = {}
@tags = {}
@cache = {}
end
@@ -90,6 +92,14 @@ module Liquid
@tags ||= TagRegistry.new
end
def add_register(name, klass)
registers[name.to_sym] = klass
end
def registers
@registers ||= {}
end
def error_mode
@error_mode ||= :lax
end
@@ -118,19 +128,19 @@ module Liquid
end
def initialize
@rethrow_errors = false
@rethrow_errors = false
@resource_limits = ResourceLimits.new(self.class.default_resource_limits)
end
# Parse source code.
# Returns self for easy chaining
def parse(source, options = {})
@options = options
@profiling = options[:profile]
@options = options
@profiling = options[:profile]
@line_numbers = options[:line_numbers] || @profiling
parse_context = options.is_a?(ParseContext) ? options : ParseContext.new(options)
@root = Document.parse(tokenize(source), parse_context)
@warnings = parse_context.warnings
@root = Document.parse(tokenize(source), parse_context)
@warnings = parse_context.warnings
self
end
@@ -165,19 +175,19 @@ 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 }
c.exception_renderer = ->(_e) { raise }
end
c
when Liquid::Drop
drop = args.shift
drop = args.shift
drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
when Hash
Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
@@ -189,18 +199,26 @@ module Liquid
output = nil
context_register = context.registers.is_a?(StaticRegisters) ? context.registers.static : context.registers
case args.last
when Hash
options = args.pop
output = options[:output] if options[:output]
output = options[:output] if options[:output]
registers.merge!(options[:registers]) if options[:registers].is_a?(Hash)
options[:registers]&.each do |key, register|
context_register[key] = register
end
apply_options_to_context(context, options)
when Module, Array
context.add_filters(args.pop)
end
Template.registers.each do |key, register|
context_register[key] = register
end
# Retrying a render resets resource usage
context.resource_limits.reset
@@ -208,7 +226,7 @@ module Liquid
# render the nodelist.
# for performance reasons we get an array back here. join will make a string out of it.
with_profiling(context) do
@root.render_to_output_buffer(context, output || '')
@root.render_to_output_buffer(context, output || +'')
end
rescue Liquid::MemoryError => e
context.handle_error(e)
@@ -236,7 +254,7 @@ module Liquid
if @profiling && !context.partial
raise "Profiler not loaded, require 'liquid/profiler' first" unless defined?(Liquid::Profiler)
@profiler = Profiler.new
@profiler = Profiler.new(context.template_name)
@profiler.start
begin
@@ -251,10 +269,10 @@ module Liquid
def apply_options_to_context(context, options)
context.add_filters(options[:filters]) if options[:filters]
context.global_filter = options[:global_filter] if options[:global_filter]
context.global_filter = options[:global_filter] if options[:global_filter]
context.exception_renderer = options[:exception_renderer] if options[:exception_renderer]
context.strict_variables = options[:strict_variables] if options[:strict_variables]
context.strict_filters = options[:strict_filters] if options[:strict_filters]
context.strict_variables = options[:strict_variables] if options[:strict_variables]
context.strict_filters = options[:strict_filters] if options[:strict_filters]
end
end
end

View File

@@ -1,16 +1,18 @@
# frozen_string_literal: true
module Liquid
class Tokenizer
attr_reader :line_number, :for_liquid_tag
def initialize(source, line_numbers = false, line_number: nil, for_liquid_tag: false)
@source = source
@line_number = line_number || (line_numbers ? 1 : nil)
@source = source
@line_number = line_number || (line_numbers ? 1 : nil)
@for_liquid_tag = for_liquid_tag
@tokens = tokenize
@tokens = tokenize
end
def shift
token = @tokens.shift or return
(token = @tokens.shift) || return
if @line_number
@line_number += @for_liquid_tag ? 1 : token.count("\n")
@@ -29,7 +31,7 @@ module Liquid
tokens = @source.split(TemplateParser)
# 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 Usage
def self.increment(name)

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid
module Utils
def self.slice_collection(collection, from, to)
@@ -10,7 +12,7 @@ module Liquid
def self.slice_collection_using_each(collection, from, to)
segments = []
index = 0
index = 0
# Maintains Ruby 1.8.7 String#each behaviour on 1.9
if collection.is_a?(String)
@@ -50,7 +52,7 @@ module Liquid
when Numeric
obj
when String
(obj.strip =~ /\A-?\d+\.\d+\z/) ? BigDecimal(obj) : obj.to_i
/\A-?\d+\.\d+\z/.match?(obj.strip) ? BigDecimal(obj) : obj.to_i
else
if obj.respond_to?(:to_number)
obj.to_number
@@ -69,7 +71,7 @@ module Liquid
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,10 +12,10 @@ 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
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
attr_accessor :filters, :name, :line_number
@@ -23,10 +25,10 @@ module Liquid
include ParserSwitching
def initialize(markup, parse_context)
@markup = markup
@name = nil
@markup = markup
@name = nil
@parse_context = parse_context
@line_number = parse_context.line_number
@line_number = parse_context.line_number
parse_with_selected_parser(markup)
end
@@ -43,11 +45,11 @@ module Liquid
@filters = []
return unless markup =~ MarkupWithQuotedFragment
name_markup = $1
filter_markup = $2
@name = Expression.parse(name_markup)
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)
filters = Regexp.last_match(1).scan(FilterParser)
filters.each do |f|
next unless f =~ /\w+/
filtername = Regexp.last_match(0)
@@ -102,14 +104,22 @@ module Liquid
output
end
def disabled?(_context)
false
end
def disabled_tags
[]
end
private
def parse_filter_expressions(filter_name, unparsed_args)
filter_args = []
filter_args = []
keyword_args = nil
unparsed_args.each do |a|
if matches = a.match(JustTagAttributes)
keyword_args ||= {}
if (matches = a.match(JustTagAttributes))
keyword_args ||= {}
keyword_args[matches[1]] = Expression.parse(matches[2])
else
filter_args << Expression.parse(a)
@@ -121,7 +131,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|
@@ -139,8 +149,8 @@ module Liquid
@markup =~ QuotedFragment
name = Regexp.last_match(0)
error = TaintedError.new("variable '#{name}' is tainted and was not escaped")
error.line_number = line_number
error = TaintedError.new("variable '#{name}' is tainted and was not escaped")
error.line_number = line_number
error.template_name = context.template_name
case Template.taint_mode

View File

@@ -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
COMMAND_METHODS = ['size', 'first', 'last'].freeze
attr_reader :name, :lookups
@@ -14,17 +16,17 @@ module Liquid
name = lookups.shift
if name =~ SQUARE_BRACKETED
name = Expression.parse($1)
name = Expression.parse(Regexp.last_match(1))
end
@name = name
@lookups = lookups
@lookups = lookups
@command_flags = 0
@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
@@ -32,7 +34,7 @@ module Liquid
end
def evaluate(context)
name = context.evaluate(@name)
name = context.evaluate(@name)
object = context.find_variable(name)
@lookups.each_index do |i|
@@ -45,7 +47,7 @@ module Liquid
(object.respond_to?(:fetch) && key.is_a?(Integer)))
# if its a proc we will replace the entry with the proc
res = context.lookup_and_evaluate(object, key)
res = context.lookup_and_evaluate(object, key)
object = res.to_liquid
# Some special cases. If the part wasn't in square brackets and

View File

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

View File

@@ -1,7 +1,8 @@
# encoding: utf-8
# frozen_string_literal: true
lib = File.expand_path('../lib/', __FILE__)
$LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require "liquid/version"
@@ -26,6 +27,6 @@ Gem::Specification.new do |s|
s.require_path = "lib"
s.add_development_dependency 'rake', '~> 11.3'
s.add_development_dependency 'minitest'
s.add_development_dependency('rake', '~> 11.3')
s.add_development_dependency('minitest')
end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'benchmark/ips'
require_relative 'theme_runner'
@@ -5,7 +7,7 @@ Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
profiler = ThemeRunner.new
Benchmark.ips do |x|
x.time = 10
x.time = 10
x.warmup = 5
puts

View File

@@ -21,8 +21,8 @@ class Profiler
end
def profile(phase, &block)
print LOG_LABEL
print "#{phase}.. ".ljust(10)
print(LOG_LABEL)
print("#{phase}.. ".ljust(10))
report = MemoryProfiler.report(&block)
puts 'Done.'
@headings << phase.capitalize

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'stackprof'
require_relative 'theme_runner'
@@ -13,7 +15,7 @@ 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

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
class CommentForm < Liquid::Block
Syntax = /(#{Liquid::VariableSignature}+)/
@@ -5,10 +7,10 @@ class CommentForm < Liquid::Block
super
if markup =~ Syntax
@variable_name = $1
@attributes = {}
@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
@@ -20,8 +22,8 @@ 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'],
}
output << wrap_in_form(article, render_all(@nodelist, context, output))

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'yaml'
module Database
@@ -16,9 +18,10 @@ 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; }
assigns
db = db.each_with_object({}) do |(key, values), assigns|
assigns[key] = values.each_with_object({}) do |v, h|
h[v['handle']] = v
end
end
# Some standard direct accessors so that the specialized templates
@@ -29,9 +32,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 +43,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,14 @@
# 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,13 +1,15 @@
# frozen_string_literal: true
class Paginate < Liquid::Block
Syntax = /(#{Liquid::QuotedFragment})\s*(by\s*(\d+))?/
Syntax = /(#{Liquid::QuotedFragment})\s*(by\s*(\d+))?/
def initialize(tag_name, markup, options)
super
if markup =~ Syntax
@collection_name = $1
@page_size = if $2
$3.to_i
@collection_name = Regexp.last_match(1)
@page_size = if Regexp.last_match(2)
Regexp.last_match(3).to_i
else
20
end
@@ -17,7 +19,7 @@ class Paginate < Liquid::Block
@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
@@ -25,19 +27,19 @@ class Paginate < Liquid::Block
@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

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module ShopFilter
def asset_url(input)
"/files/1/[shop_id]/[shop_id]/assets/#{input}"
@@ -29,7 +31,7 @@ module ShopFilter
def link_to_vendor(vendor)
if vendor
link_to vendor, url_for_vendor(vendor), vendor
link_to(vendor, url_for_vendor(vendor), vendor)
else
'Unknown Vendor'
end
@@ -37,7 +39,7 @@ module ShopFilter
def link_to_type(type)
if type
link_to type, url_for_type(type), type
link_to(type, url_for_type(type), type)
else
'Unknown Vendor'
end
@@ -52,7 +54,7 @@ module ShopFilter
end
def product_img_url(url, style = 'small')
unless url =~ /\Aproducts\/([\w\-\_]+)\.(\w{2,4})/
unless url =~ %r{\Aproducts/([\w\-\_]+)\.(\w{2,4})}
raise ArgumentError, 'filter "size" can only be called on product images'
end
@@ -60,7 +62,7 @@ module ShopFilter
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 +72,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
@@ -56,9 +58,9 @@ class ThemeRunner
# `render` is called to benchmark just the render portion of liquid
def render
@compiled_tests.each do |test|
tmpl = test[:tmpl]
tmpl = test[:tmpl]
assigns = test[:assigns]
layout = test[:layout]
layout = test[:layout]
if layout
assigns['content_for_layout'] = tmpl.render!(assigns)
@@ -72,7 +74,7 @@ class ThemeRunner
private
def compile_and_render(template, layout, assigns, page_template, template_file)
compiled_test = compile_test(template, layout, assigns, page_template, template_file)
compiled_test = compile_test(template, layout, assigns, page_template, template_file)
assigns['content_for_layout'] = compiled_test[:tmpl].render!(assigns)
compiled_test[:layout].render!(assigns) if layout
end
@@ -86,7 +88,7 @@ class ThemeRunner
end
def compile_test(template, layout, assigns, page_template, template_file)
tmpl = init_template(page_template, template_file)
tmpl = init_template(page_template, template_file)
parsed_template = tmpl.parse(template).dup
if layout
@@ -111,9 +113,9 @@ class ThemeRunner
# set up a new Liquid::Template object for use in `compile_and_render` and `compile_test`
def init_template(page_template, template_file)
tmpl = Liquid::Template.new
tmpl.assigns['page_title'] = 'Page title'
tmpl.assigns['template'] = page_template
tmpl = Liquid::Template.new
tmpl.assigns['page_title'] = 'Page title'
tmpl.assigns['template'] = page_template
tmpl.registers[:file_system] = ThemeRunner::FileSystem.new(File.dirname(template_file))
tmpl
end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'test_helper'
class AssignTest < Minitest::Test
@@ -8,9 +10,9 @@ class AssignTest < Minitest::Test
{% assign this-thing = 'Print this-thing' %}
{{ this-thing }}
END_TEMPLATE
template = Template.parse(template_source)
rendered = template.render!
assert_equal "Print this-thing", rendered.strip
template = Template.parse(template_source)
rendered = template.render!
assert_equal("Print this-thing", rendered.strip)
end
def test_assigned_variable

View File

@@ -1,7 +1,9 @@
# frozen_string_literal: true
require 'test_helper'
class FoobarTag < Liquid::Tag
def render_to_output_buffer(context, output)
def render_to_output_buffer(_context, output)
output << ' '
output
end
@@ -94,9 +96,9 @@ class BlankTest < Minitest::Test
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

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'test_helper'
class BlockTest < Minitest::Test
@@ -7,6 +9,6 @@ class BlockTest < Minitest::Test
exc = assert_raises(SyntaxError) do
Template.parse("{% if true %}{% endunless %}")
end
assert_equal exc.message, "Liquid syntax error: 'endunless' is not a valid delimiter for if tags. use endif"
assert_equal(exc.message, "Liquid syntax error: 'endunless' is not a valid delimiter for if tags. use endif")
end
end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'test_helper'
class CaptureTest < Minitest::Test
@@ -12,9 +14,9 @@ class CaptureTest < Minitest::Test
{% capture this-thing %}Print this-thing{% endcapture %}
{{ this-thing }}
END_TEMPLATE
template = Template.parse(template_source)
rendered = template.render!
assert_equal "Print this-thing", rendered.strip
template = Template.parse(template_source)
rendered = template.render!
assert_equal("Print this-thing", rendered.strip)
end
def test_capture_to_variable_from_outer_scope_if_existing
@@ -28,9 +30,9 @@ class CaptureTest < Minitest::Test
{% endif %}
{{var}}
END_TEMPLATE
template = Template.parse(template_source)
rendered = template.render!
assert_equal "test-string", rendered.gsub(/\s/, '')
template = Template.parse(template_source)
rendered = template.render!
assert_equal("test-string", rendered.gsub(/\s/, ''))
end
def test_assigning_from_capture
@@ -43,8 +45,8 @@ class CaptureTest < Minitest::Test
{% endfor %}
{{ first }}-{{ second }}
END_TEMPLATE
template = Template.parse(template_source)
rendered = template.render!
assert_equal "3-3", rendered.gsub(/\s/, '')
template = Template.parse(template_source)
rendered = template.render!
assert_equal("3-3", rendered.gsub(/\s/, ''))
end
end # CaptureTest

View File

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

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'test_helper'
class ContextDrop < Liquid::Drop
@@ -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}"
end
end
@@ -48,7 +50,7 @@ class ProductDrop < Liquid::Drop
end
def user_input
"foo".taint
(+"foo").taint
end
protected
@@ -109,7 +111,7 @@ class DropsTest < Minitest::Test
def test_product_drop
tpl = Liquid::Template.parse(' ')
assert_equal ' ', tpl.render!('product' => ProductDrop.new)
assert_equal(' ', tpl.render!('product' => ProductDrop.new))
end
def test_rendering_raises_on_tainted_attr
@@ -123,7 +125,7 @@ class DropsTest < Minitest::Test
def test_rendering_warns_on_tainted_attr
with_taint_mode(:warn) do
tpl = Liquid::Template.parse('{{ product.user_input }}')
tpl = Liquid::Template.parse('{{ product.user_input }}')
context = Context.new('product' => ProductDrop.new)
tpl.render!(context)
assert_equal [Liquid::TaintedError], context.warnings.map(&:class)
@@ -139,52 +141,57 @@ class DropsTest < Minitest::Test
end
def test_drop_does_only_respond_to_whitelisted_methods
assert_equal "", Liquid::Template.parse("{{ product.inspect }}").render!('product' => ProductDrop.new)
assert_equal "", Liquid::Template.parse("{{ product.pretty_inspect }}").render!('product' => ProductDrop.new)
assert_equal "", Liquid::Template.parse("{{ product.whatever }}").render!('product' => ProductDrop.new)
assert_equal "", Liquid::Template.parse('{{ product | map: "inspect" }}').render!('product' => ProductDrop.new)
assert_equal "", Liquid::Template.parse('{{ product | map: "pretty_inspect" }}').render!('product' => ProductDrop.new)
assert_equal "", Liquid::Template.parse('{{ product | map: "whatever" }}').render!('product' => ProductDrop.new)
assert_equal("", Liquid::Template.parse("{{ product.inspect }}").render!('product' => ProductDrop.new))
assert_equal("", Liquid::Template.parse("{{ product.pretty_inspect }}").render!('product' => ProductDrop.new))
assert_equal("", Liquid::Template.parse("{{ product.whatever }}").render!('product' => ProductDrop.new))
assert_equal("", Liquid::Template.parse('{{ product | map: "inspect" }}').render!('product' => ProductDrop.new))
assert_equal("", Liquid::Template.parse('{{ product | map: "pretty_inspect" }}').render!('product' => ProductDrop.new))
assert_equal("", Liquid::Template.parse('{{ product | map: "whatever" }}').render!('product' => ProductDrop.new))
end
def test_drops_respond_to_to_liquid
assert_equal "text1", Liquid::Template.parse("{{ product.to_liquid.texts.text }}").render!('product' => ProductDrop.new)
assert_equal "text1", Liquid::Template.parse('{{ product | map: "to_liquid" | map: "texts" | map: "text" }}').render!('product' => ProductDrop.new)
assert_equal("text1", Liquid::Template.parse("{{ product.to_liquid.texts.text }}").render!('product' => ProductDrop.new))
assert_equal("text1", Liquid::Template.parse('{{ product | map: "to_liquid" | map: "texts" | map: "text" }}').render!('product' => ProductDrop.new))
end
def test_text_drop
output = Liquid::Template.parse(' {{ product.texts.text }} ').render!('product' => ProductDrop.new)
assert_equal ' text1 ', output
assert_equal(' text1 ', output)
end
def test_catchall_unknown_method
output = Liquid::Template.parse(' {{ product.catchall.unknown }} ').render!('product' => ProductDrop.new)
assert_equal ' catchall_method: unknown ', output
assert_equal(' catchall_method: unknown ', output)
end
def test_catchall_integer_argument_drop
output = Liquid::Template.parse(' {{ product.catchall[8] }} ').render!('product' => ProductDrop.new)
assert_equal ' catchall_method: 8 ', output
assert_equal(' catchall_method: 8 ', output)
end
def test_text_array_drop
output = Liquid::Template.parse('{% for text in product.texts.array %} {{text}} {% endfor %}').render!('product' => ProductDrop.new)
assert_equal ' text1 text2 ', output
assert_equal(' text1 text2 ', output)
end
def test_context_drop
output = Liquid::Template.parse(' {{ context.bar }} ').render!('context' => ContextDrop.new, 'bar' => "carrot")
assert_equal ' carrot ', output
assert_equal(' carrot ', output)
end
def test_context_drop_array_with_map
output = Liquid::Template.parse(' {{ contexts | map: "bar" }} ').render!('contexts' => [ContextDrop.new, ContextDrop.new], 'bar' => "carrot")
assert_equal(' carrotcarrot ', output)
end
def test_nested_context_drop
output = Liquid::Template.parse(' {{ product.context.foo }} ').render!('product' => ProductDrop.new, 'foo' => "monkey")
assert_equal ' monkey ', output
assert_equal(' monkey ', output)
end
def test_protected
output = Liquid::Template.parse(' {{ product.callmenot }} ').render!('product' => ProductDrop.new)
assert_equal ' ', output
assert_equal(' ', output)
end
def test_object_methods_not_allowed
@@ -195,40 +202,40 @@ class DropsTest < Minitest::Test
end
def test_scope
assert_equal '1', Liquid::Template.parse('{{ context.scopes }}').render!('context' => ContextDrop.new)
assert_equal '2', Liquid::Template.parse('{%for i in dummy%}{{ context.scopes }}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1])
assert_equal '3', Liquid::Template.parse('{%for i in dummy%}{%for i in dummy%}{{ context.scopes }}{%endfor%}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1])
assert_equal('1', Liquid::Template.parse('{{ context.scopes }}').render!('context' => ContextDrop.new))
assert_equal('2', Liquid::Template.parse('{%for i in dummy%}{{ context.scopes }}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1]))
assert_equal('3', Liquid::Template.parse('{%for i in dummy%}{%for i in dummy%}{{ context.scopes }}{%endfor%}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1]))
end
def test_scope_though_proc
assert_equal '1', Liquid::Template.parse('{{ s }}').render!('context' => ContextDrop.new, 's' => proc{ |c| c['context.scopes'] })
assert_equal '2', Liquid::Template.parse('{%for i in dummy%}{{ s }}{%endfor%}').render!('context' => ContextDrop.new, 's' => proc{ |c| c['context.scopes'] }, 'dummy' => [1])
assert_equal '3', Liquid::Template.parse('{%for i in dummy%}{%for i in dummy%}{{ s }}{%endfor%}{%endfor%}').render!('context' => ContextDrop.new, 's' => proc{ |c| c['context.scopes'] }, 'dummy' => [1])
assert_equal('1', Liquid::Template.parse('{{ s }}').render!('context' => ContextDrop.new, 's' => proc { |c| c['context.scopes'] }))
assert_equal('2', Liquid::Template.parse('{%for i in dummy%}{{ s }}{%endfor%}').render!('context' => ContextDrop.new, 's' => proc { |c| c['context.scopes'] }, 'dummy' => [1]))
assert_equal('3', Liquid::Template.parse('{%for i in dummy%}{%for i in dummy%}{{ s }}{%endfor%}{%endfor%}').render!('context' => ContextDrop.new, 's' => proc { |c| c['context.scopes'] }, 'dummy' => [1]))
end
def test_scope_with_assigns
assert_equal 'variable', Liquid::Template.parse('{% assign a = "variable"%}{{a}}').render!('context' => ContextDrop.new)
assert_equal 'variable', Liquid::Template.parse('{% assign a = "variable"%}{%for i in dummy%}{{a}}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1])
assert_equal 'test', Liquid::Template.parse('{% assign header_gif = "test"%}{{header_gif}}').render!('context' => ContextDrop.new)
assert_equal 'test', Liquid::Template.parse("{% assign header_gif = 'test'%}{{header_gif}}").render!('context' => ContextDrop.new)
assert_equal('variable', Liquid::Template.parse('{% assign a = "variable"%}{{a}}').render!('context' => ContextDrop.new))
assert_equal('variable', Liquid::Template.parse('{% assign a = "variable"%}{%for i in dummy%}{{a}}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1]))
assert_equal('test', Liquid::Template.parse('{% assign header_gif = "test"%}{{header_gif}}').render!('context' => ContextDrop.new))
assert_equal('test', Liquid::Template.parse("{% assign header_gif = 'test'%}{{header_gif}}").render!('context' => ContextDrop.new))
end
def test_scope_from_tags
assert_equal '1', Liquid::Template.parse('{% for i in context.scopes_as_array %}{{i}}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1])
assert_equal '12', Liquid::Template.parse('{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1])
assert_equal '123', Liquid::Template.parse('{%for a in dummy%}{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1])
assert_equal('1', Liquid::Template.parse('{% for i in context.scopes_as_array %}{{i}}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1]))
assert_equal('12', Liquid::Template.parse('{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1]))
assert_equal('123', Liquid::Template.parse('{%for a in dummy%}{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1]))
end
def test_access_context_from_drop
assert_equal '123', Liquid::Template.parse('{%for a in dummy%}{{ context.loop_pos }}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1, 2, 3])
assert_equal('123', Liquid::Template.parse('{%for a in dummy%}{{ context.loop_pos }}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1, 2, 3]))
end
def test_enumerable_drop
assert_equal '123', Liquid::Template.parse('{% for c in collection %}{{c}}{% endfor %}').render!('collection' => EnumerableDrop.new)
assert_equal('123', Liquid::Template.parse('{% for c in collection %}{{c}}{% endfor %}').render!('collection' => EnumerableDrop.new))
end
def test_enumerable_drop_size
assert_equal '3', Liquid::Template.parse('{{collection.size}}').render!('collection' => EnumerableDrop.new)
assert_equal('3', Liquid::Template.parse('{{collection.size}}').render!('collection' => EnumerableDrop.new))
end
def test_enumerable_drop_will_invoke_liquid_method_missing_for_clashing_method_names
@@ -241,16 +248,16 @@ class DropsTest < Minitest::Test
end
def test_some_enumerable_methods_still_get_invoked
[ :count, :max ].each do |method|
[: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|
[: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)
@@ -259,15 +266,22 @@ class DropsTest < Minitest::Test
end
def test_empty_string_value_access
assert_equal '', Liquid::Template.parse('{{ product[value] }}').render!('product' => ProductDrop.new, 'value' => '')
assert_equal('', Liquid::Template.parse('{{ product[value] }}').render!('product' => ProductDrop.new, 'value' => ''))
end
def test_nil_value_access
assert_equal '', Liquid::Template.parse('{{ product[value] }}').render!('product' => ProductDrop.new, 'value' => nil)
assert_equal('', Liquid::Template.parse('{{ product[value] }}').render!('product' => ProductDrop.new, 'value' => nil))
end
def test_default_to_s_on_drops
assert_equal 'ProductDrop', Liquid::Template.parse("{{ product }}").render!('product' => ProductDrop.new)
assert_equal 'EnumerableDrop', Liquid::Template.parse('{{ collection }}').render!('collection' => EnumerableDrop.new)
assert_equal('ProductDrop', Liquid::Template.parse("{{ product }}").render!('product' => ProductDrop.new))
assert_equal('EnumerableDrop', Liquid::Template.parse('{{ collection }}').render!('collection' => EnumerableDrop.new))
end
def test_invokable_methods
assert_equal(%w(to_liquid catchall user_input context texts).to_set, ProductDrop.invokable_methods)
assert_equal(%w(to_liquid scopes_as_array loop_pos scopes).to_set, ContextDrop.invokable_methods)
assert_equal(%w(to_liquid size max min first count).to_set, EnumerableDrop.invokable_methods)
assert_equal(%w(to_liquid max min sort count first).to_set, RealEnumerableDrop.invokable_methods)
end
end # DropsTest

View File

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

View File

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

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

View File

@@ -227,7 +227,7 @@ class ParseTreeVisitorTest < Minitest::Test
[[nil, [
[nil, [[nil, [["other", []]]]]],
["test", []],
["xs", []]
["xs", []],
]]],
traversal(%({% for x in xs offset: test %}{{ other }}{% endfor %})).visit
)

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'test_helper'
class ParsingQuirksTest < Minitest::Test
@@ -5,7 +7,7 @@ class ParsingQuirksTest < Minitest::Test
def test_parsing_css
text = " div { font-weight: bold; } "
assert_equal text, Template.parse(text).render!
assert_equal(text, Template.parse(text).render!)
end
def test_raise_on_single_close_bracet
@@ -27,7 +29,7 @@ class ParsingQuirksTest < Minitest::Test
end
def test_error_on_empty_filter
assert Template.parse("{{test}}")
assert(Template.parse("{{test}}"))
with_error_mode(:lax) do
assert Template.parse("{{|test}}")
@@ -62,15 +64,15 @@ class ParsingQuirksTest < Minitest::Test
end
def test_no_error_on_lax_empty_filter
assert Template.parse("{{test |a|b|}}", error_mode: :lax)
assert Template.parse("{{test}}", error_mode: :lax)
assert Template.parse("{{|test|}}", error_mode: :lax)
assert(Template.parse("{{test |a|b|}}", error_mode: :lax))
assert(Template.parse("{{test}}", error_mode: :lax))
assert(Template.parse("{{|test|}}", error_mode: :lax))
end
def test_meaningless_parens_lax
with_error_mode(:lax) do
assigns = { 'b' => 'bar', 'c' => 'baz' }
markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false"
markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false"
assert_template_result(' YES ', "{% if #{markup} %} YES {% endif %}", assigns)
end
end

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,5 @@
# encoding: utf-8
# frozen_string_literal: true
require 'test_helper'
@@ -17,7 +18,7 @@ class TestThing
"woot: #{@foo}"
end
def [](whatever)
def [](_whatever)
to_s
end
@@ -37,7 +38,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
@@ -59,34 +60,34 @@ class StandardFiltersTest < Minitest::Test
end
def test_size
assert_equal 3, @filters.size([1, 2, 3])
assert_equal 0, @filters.size([])
assert_equal 0, @filters.size(nil)
assert_equal(3, @filters.size([1, 2, 3]))
assert_equal(0, @filters.size([]))
assert_equal(0, @filters.size(nil))
end
def test_downcase
assert_equal 'testing', @filters.downcase("Testing")
assert_equal '', @filters.downcase(nil)
assert_equal('testing', @filters.downcase("Testing"))
assert_equal('', @filters.downcase(nil))
end
def test_upcase
assert_equal 'TESTING', @filters.upcase("Testing")
assert_equal '', @filters.upcase(nil)
assert_equal('TESTING', @filters.upcase("Testing"))
assert_equal('', @filters.upcase(nil))
end
def test_slice
assert_equal 'oob', @filters.slice('foobar', 1, 3)
assert_equal 'oobar', @filters.slice('foobar', 1, 1000)
assert_equal '', @filters.slice('foobar', 1, 0)
assert_equal 'o', @filters.slice('foobar', 1, 1)
assert_equal 'bar', @filters.slice('foobar', 3, 3)
assert_equal 'ar', @filters.slice('foobar', -2, 2)
assert_equal 'ar', @filters.slice('foobar', -2, 1000)
assert_equal 'r', @filters.slice('foobar', -1)
assert_equal '', @filters.slice(nil, 0)
assert_equal '', @filters.slice('foobar', 100, 10)
assert_equal '', @filters.slice('foobar', -100, 10)
assert_equal 'oob', @filters.slice('foobar', '1', '3')
assert_equal('oob', @filters.slice('foobar', 1, 3))
assert_equal('oobar', @filters.slice('foobar', 1, 1000))
assert_equal('', @filters.slice('foobar', 1, 0))
assert_equal('o', @filters.slice('foobar', 1, 1))
assert_equal('bar', @filters.slice('foobar', 3, 3))
assert_equal('ar', @filters.slice('foobar', -2, 2))
assert_equal('ar', @filters.slice('foobar', -2, 1000))
assert_equal('r', @filters.slice('foobar', -1))
assert_equal('', @filters.slice(nil, 0))
assert_equal('', @filters.slice('foobar', 100, 10))
assert_equal('', @filters.slice('foobar', -100, 10))
assert_equal('oob', @filters.slice('foobar', '1', '3'))
assert_raises(Liquid::ArgumentError) do
@filters.slice('foobar', nil)
end
@@ -97,155 +98,158 @@ class StandardFiltersTest < Minitest::Test
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
assert_equal '1234...', @filters.truncate('1234567890', 7)
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('1234...', @filters.truncate('1234567890', 7))
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))
end
def test_split
assert_equal ['12', '34'], @filters.split('12~34', '~')
assert_equal ['A? ', ' ,Z'], @filters.split('A? ~ ~ ~ ,Z', '~ ~ ~')
assert_equal ['A?Z'], @filters.split('A?Z', '~')
assert_equal [], @filters.split(nil, ' ')
assert_equal ['A', 'Z'], @filters.split('A1Z', 1)
assert_equal(['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))
end
def test_escape
assert_equal '&lt;strong&gt;', @filters.escape('<strong>')
assert_equal '1', @filters.escape(1)
assert_equal '2001-02-03', @filters.escape(Date.new(2001, 2, 3))
assert_nil @filters.escape(nil)
assert_equal('&lt;strong&gt;', @filters.escape('<strong>'))
assert_equal('1', @filters.escape(1))
assert_equal('2001-02-03', @filters.escape(Date.new(2001, 2, 3)))
assert_nil(@filters.escape(nil))
end
def test_h
assert_equal '&lt;strong&gt;', @filters.h('<strong>')
assert_equal '1', @filters.h(1)
assert_equal '2001-02-03', @filters.h(Date.new(2001, 2, 3))
assert_nil @filters.h(nil)
assert_equal('&lt;strong&gt;', @filters.h('<strong>'))
assert_equal('1', @filters.h(1))
assert_equal('2001-02-03', @filters.h(Date.new(2001, 2, 3)))
assert_nil(@filters.h(nil))
end
def test_escape_once
assert_equal '&lt;strong&gt;Hulk&lt;/strong&gt;', @filters.escape_once('&lt;strong&gt;Hulk</strong>')
assert_equal('&lt;strong&gt;Hulk&lt;/strong&gt;', @filters.escape_once('&lt;strong&gt;Hulk</strong>'))
end
def test_url_encode
assert_equal 'foo%2B1%40example.com', @filters.url_encode('foo+1@example.com')
assert_equal '1', @filters.url_encode(1)
assert_equal '2001-02-03', @filters.url_encode(Date.new(2001, 2, 3))
assert_nil @filters.url_encode(nil)
assert_equal('foo%2B1%40example.com', @filters.url_encode('foo+1@example.com'))
assert_equal('1', @filters.url_encode(1))
assert_equal('2001-02-03', @filters.url_encode(Date.new(2001, 2, 3)))
assert_nil(@filters.url_encode(nil))
end
def test_url_decode
assert_equal 'foo bar', @filters.url_decode('foo+bar')
assert_equal 'foo bar', @filters.url_decode('foo%20bar')
assert_equal 'foo+1@example.com', @filters.url_decode('foo%2B1%40example.com')
assert_equal '1', @filters.url_decode(1)
assert_equal '2001-02-03', @filters.url_decode(Date.new(2001, 2, 3))
assert_nil @filters.url_decode(nil)
assert_equal('foo bar', @filters.url_decode('foo+bar'))
assert_equal('foo bar', @filters.url_decode('foo%20bar'))
assert_equal('foo+1@example.com', @filters.url_decode('foo%2B1%40example.com'))
assert_equal('1', @filters.url_decode(1))
assert_equal('2001-02-03', @filters.url_decode(Date.new(2001, 2, 3)))
assert_nil(@filters.url_decode(nil))
exception = assert_raises Liquid::ArgumentError do
@filters.url_decode('%ff')
end
assert_equal 'Liquid error: invalid byte sequence in UTF-8', exception.message
assert_equal('Liquid error: invalid byte sequence in UTF-8', exception.message)
end
def test_truncatewords
assert_equal 'one two three', @filters.truncatewords('one two three', 4)
assert_equal 'one two...', @filters.truncatewords('one two three', 2)
assert_equal 'one two three', @filters.truncatewords('one two three')
assert_equal 'Two small (13&#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('one two three', @filters.truncatewords('one two three', 4))
assert_equal('one two...', @filters.truncatewords('one two three', 2))
assert_equal('one two three', @filters.truncatewords('one two three'))
assert_equal(
'Two small (13&#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))
end
def test_strip_html
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>")
assert_equal 'test', @filters.strip_html("<div\nclass='multiline'>test</div>")
assert_equal 'test', @filters.strip_html("<!-- foo bar \n test -->test")
assert_equal '', @filters.strip_html(nil)
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>"))
assert_equal('test', @filters.strip_html("<div\nclass='multiline'>test</div>"))
assert_equal('test', @filters.strip_html("<!-- foo bar \n test -->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
assert_equal '1 2 3 4', @filters.join([1, 2, 3, 4])
assert_equal '1 - 2 - 3 - 4', @filters.join([1, 2, 3, 4], ' - ')
assert_equal '1121314', @filters.join([1, 2, 3, 4], 1)
assert_equal('1 2 3 4', @filters.join([1, 2, 3, 4]))
assert_equal('1 - 2 - 3 - 4', @filters.join([1, 2, 3, 4], ' - '))
assert_equal('1121314', @filters.join([1, 2, 3, 4], 1))
end
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([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"))
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([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"))
end
def test_sort_when_property_is_sometimes_missing_puts_nils_last
input = [
input = [
{ "price" => 4, "handle" => "alpha" },
{ "handle" => "beta" },
{ "price" => 1, "handle" => "gamma" },
{ "handle" => "delta" },
{ "price" => 2, "handle" => "epsilon" }
{ "price" => 2, "handle" => "epsilon" },
]
expectation = [
{ "price" => 1, "handle" => "gamma" },
{ "price" => 2, "handle" => "epsilon" },
{ "price" => 4, "handle" => "alpha" },
{ "handle" => "delta" },
{ "handle" => "beta" }
{ "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(["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"))
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 = [
input = [
{ "price" => "4", "handle" => "alpha" },
{ "handle" => "beta" },
{ "price" => "1", "handle" => "gamma" },
{ "handle" => "delta" },
{ "price" => 2, "handle" => "epsilon" }
{ "price" => 2, "handle" => "epsilon" },
]
expectation = [
{ "price" => "1", "handle" => "gamma" },
{ "price" => 2, "handle" => "epsilon" },
{ "price" => "4", "handle" => "alpha" },
{ "handle" => "delta" },
{ "handle" => "beta" }
{ "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
@@ -256,7 +260,7 @@ class StandardFiltersTest < Minitest::Test
{ "fake" => "t" },
{ "key" => "a" },
{ "key" => "b" },
{ "key" => "c" }
{ "key" => "c" },
]
expectation = [
{ "key" => "a" },
@@ -265,21 +269,21 @@ class StandardFiltersTest < Minitest::Test
{ "key" => "X" },
{ "key" => "Y" },
{ "key" => "Z" },
{ "fake" => "t" }
{ "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(["a", "b", "c", "X", "Y", "Z"], @filters.sort_natural(["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
@@ -288,14 +292,14 @@ class StandardFiltersTest < Minitest::Test
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
@@ -304,33 +308,33 @@ class StandardFiltersTest < Minitest::Test
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([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"))
end
def test_uniq
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(["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"))
testdrop = TestDrop.new
assert_equal [testdrop], @filters.uniq([testdrop, TestDrop.new], 'test')
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
@@ -339,14 +343,14 @@ class StandardFiltersTest < Minitest::Test
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
@@ -355,75 +359,75 @@ class StandardFiltersTest < Minitest::Test
end
def test_reverse
assert_equal [4, 3, 2, 1], @filters.reverse([1, 2, 3, 4])
assert_equal([4, 3, 2, 1], @filters.reverse([1, 2, 3, 4]))
end
def test_legacy_reverse_hash
assert_equal [{ a: 1, b: 2 }], @filters.reverse(a: 1, b: 2)
assert_equal([{ a: 1, b: 2 }], @filters.reverse(a: 1, b: 2))
end
def test_map
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' } }]
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' } }])
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
assert(t.foo > 0)
end
def test_map_over_proc
drop = TestDrop.new
p = proc{ drop }
drop = TestDrop.new
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
@@ -435,7 +439,7 @@ class StandardFiltersTest < Minitest::Test
foo = [
[1],
[2],
[3]
[3],
]
assert_raises Liquid::ArgumentError do
@filters.map(foo, nil)
@@ -443,42 +447,42 @@ 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
assert_template_result 'foobar', '{{ foo | first }}', 'foo' => [ThingWithToLiquid.new]
assert_template_result 'foobar', '{{ foo | last }}', 'foo' => [ThingWithToLiquid.new]
assert_template_result('foobar', '{{ foo | first }}', 'foo' => [ThingWithToLiquid.new])
assert_template_result('foobar', '{{ foo | last }}', 'foo' => [ThingWithToLiquid.new])
end
def test_truncate_calls_to_liquid
assert_template_result "wo...", '{{ foo | truncate: 5 }}', "foo" => TestThing.new
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 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_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")
@@ -487,169 +491,169 @@ class StandardFiltersTest < Minitest::Test
end
def test_first_last
assert_equal 1, @filters.first([1, 2, 3])
assert_equal 3, @filters.last([1, 2, 3])
assert_nil @filters.first([])
assert_nil @filters.last([])
assert_equal(1, @filters.first([1, 2, 3]))
assert_equal(3, @filters.last([1, 2, 3]))
assert_nil(@filters.first([]))
assert_nil(@filters.last([]))
end
def test_replace
assert_equal '2 2 2 2', @filters.replace('1 1 1 1', '1', 2)
assert_equal '2 2 2 2', @filters.replace('1 1 1 1', 1, 2)
assert_equal '2 1 1 1', @filters.replace_first('1 1 1 1', '1', 2)
assert_equal '2 1 1 1', @filters.replace_first('1 1 1 1', 1, 2)
assert_template_result '2 1 1 1', "{{ '1 1 1 1' | replace_first: '1', 2 }}"
assert_equal('2 2 2 2', @filters.replace('1 1 1 1', '1', 2))
assert_equal('2 2 2 2', @filters.replace('1 1 1 1', 1, 2))
assert_equal('2 1 1 1', @filters.replace_first('1 1 1 1', '1', 2))
assert_equal('2 1 1 1', @filters.replace_first('1 1 1 1', 1, 2))
assert_template_result('2 1 1 1', "{{ '1 1 1 1' | replace_first: '1', 2 }}")
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_template_result 'a a a', "{{ 'a a a a' | remove_first: 'a ' }}"
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
def test_pipes_in_string_arguments
assert_template_result 'foobar', "{{ 'foo|bar' | remove: '|' }}"
assert_template_result('foobar', "{{ 'foo|bar' | remove: '|' }}")
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 }}"
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 }}"
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 }}"
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 }}"
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 }}"
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
@@ -659,9 +663,9 @@ class StandardFiltersTest < Minitest::Test
end
def test_concat
assert_equal [1, 2, 3, 4], @filters.concat([1, 2], [3, 4])
assert_equal [1, 2, 'a'], @filters.concat([1, 2], ['a'])
assert_equal [1, 2, 10], @filters.concat([1, 2], [10])
assert_equal([1, 2, 3, 4], @filters.concat([1, 2], [3, 4]))
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
@filters.concat([1, 2], 10)
@@ -675,12 +679,23 @@ class StandardFiltersTest < Minitest::Test
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"))
assert_template_result('bar', "{{ false | default: 'bar' }}")
end
def test_default_handle_false
assert_equal("foo", @filters.default("foo", "bar", "allow_false" => true))
assert_equal("bar", @filters.default(nil, "bar", "allow_false" => true))
assert_equal("bar", @filters.default("", "bar", "allow_false" => true))
assert_equal(false, @filters.default(false, "bar", "allow_false" => true))
assert_equal("bar", @filters.default([], "bar", "allow_false" => true))
assert_equal("bar", @filters.default({}, "bar", "allow_false" => true))
assert_template_result('false', "{{ false | default: 'bar', allow_false: true }}")
end
def test_cannot_access_private_methods
@@ -697,16 +712,16 @@ class StandardFiltersTest < Minitest::Test
{ "handle" => "alpha", "ok" => true },
{ "handle" => "beta", "ok" => false },
{ "handle" => "gamma", "ok" => false },
{ "handle" => "delta", "ok" => true }
{ "handle" => "delta", "ok" => true },
]
expectation = [
{ "handle" => "alpha", "ok" => true },
{ "handle" => "delta", "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
@@ -714,21 +729,21 @@ class StandardFiltersTest < Minitest::Test
{ "handle" => "alpha", "ok" => true },
{ "handle" => "beta" },
{ "handle" => "gamma" },
{ "handle" => "delta", "ok" => true }
{ "handle" => "delta", "ok" => true },
]
expectation = [
{ "handle" => "alpha", "ok" => true },
{ "handle" => "delta", "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
@@ -740,17 +755,17 @@ class StandardFiltersTest < Minitest::Test
input = [
{ "message" => "Bonjour!", "language" => "French" },
{ "message" => "Hello!", "language" => "English" },
{ "message" => "Hallo!", "language" => "German" }
{ "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
@@ -758,16 +773,16 @@ class StandardFiltersTest < Minitest::Test
{ "foo" => false },
{ "foo" => true },
{ "foo" => "for sure" },
{ "bar" => true }
{ "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
def with_timezone(tz)
old_tz = ENV['TZ']
old_tz = ENV['TZ']
ENV['TZ'] = tz
yield
ensure

View File

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

View File

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

View File

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

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