Compare commits

...

24 Commits

Author SHA1 Message Date
Mike Angell
9bb533985e styling fixes 2019-10-04 15:25:06 +10:00
Mike Angell
1c6a5d91fe Merge branch 'master' into sort-numeric 2019-10-04 15:20:00 +10: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
Justin Li
2f0a2c66f3 Add sort_numeric filter 2018-09-13 17:16:50 -04:00
132 changed files with 1309 additions and 928 deletions

View File

@@ -1,5 +1,5 @@
inherit_from: inherit_from:
- https://shopify.github.io/ruby-style-guide/rubocop.yml - 'https://shopify.github.io/ruby-style-guide/rubocop.yml'
- .rubocop_todo.yml - .rubocop_todo.yml
require: rubocop-performance require: rubocop-performance
@@ -8,6 +8,7 @@ Performance:
Enabled: true Enabled: true
AllCops: AllCops:
TargetRubyVersion: 2.4
Exclude: Exclude:
- 'vendor/bundle/**/*' - 'vendor/bundle/**/*'

View File

@@ -1,107 +1,11 @@
# This configuration was generated by # This configuration was generated by
# `rubocop --auto-gen-config` # `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 # The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base. # one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new # Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again. # 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 # Offense count: 2
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle. # Configuration parameters: EnforcedStyle.
@@ -110,48 +14,7 @@ Lint/InheritException:
Exclude: Exclude:
- 'lib/liquid/interrupts.rb' - 'lib/liquid/interrupts.rb'
# Offense count: 10 # Offense count: 98
# 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
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. # Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
# URISchemes: http, https # URISchemes: http, https
@@ -177,235 +40,9 @@ Naming/ConstantName:
- 'performance/shopify/paginate.rb' - 'performance/shopify/paginate.rb'
- 'test/integration/tags/include_tag_test.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 # Offense count: 5
Style/ClassVars: Style/ClassVars:
Exclude: Exclude:
- 'lib/liquid/condition.rb' - 'lib/liquid/condition.rb'
- 'lib/liquid/strainer.rb' - 'lib/liquid/strainer.rb'
- 'lib/liquid/template.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'

View File

@@ -7,8 +7,6 @@ rvm:
- &latest_ruby 2.6 - &latest_ruby 2.6
- 2.7 - 2.7
- ruby-head - ruby-head
- jruby-head
- truffleruby
matrix: matrix:
include: include:
@@ -17,8 +15,6 @@ matrix:
name: Profiling Memory Usage name: Profiling Memory Usage
allow_failures: allow_failures:
- rvm: ruby-head - rvm: ruby-head
- rvm: jruby-head
- rvm: truffleruby
branches: branches:
only: only:

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
source 'https://rubygems.org' source 'https://rubygems.org'
git_source(:github) do |repo_name| git_source(:github) do |repo_name|
"https://github.com/#{repo_name}.git" "https://github.com/#{repo_name}.git"

View File

@@ -1,18 +1,20 @@
# frozen_string_literal: true
require 'rake' require 'rake'
require 'rake/testtask' require 'rake/testtask'
$LOAD_PATH.unshift File.expand_path("../lib", __FILE__) $LOAD_PATH.unshift(File.expand_path("../lib", __FILE__))
require "liquid/version" 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| Rake::TestTask.new(:base_test) do |t|
t.libs << '.' << 'lib' << 'test' t.libs << '.' << 'lib' << 'test'
t.test_files = FileList['test/{integration,unit}/**/*_test.rb'] t.test_files = FileList['test/{integration,unit}/**/*_test.rb']
t.verbose = false t.verbose = false
end end
desc 'run test suite with warn error mode' desc('run test suite with warn error mode')
task :warn_test do task :warn_test do
ENV['LIQUID_PARSER_MODE'] = 'warn' ENV['LIQUID_PARSER_MODE'] = 'warn'
Rake::Task['base_test'].invoke Rake::Task['base_test'].invoke
@@ -25,7 +27,7 @@ task :rubocop do
end end
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 task :test do
ENV['LIQUID_PARSER_MODE'] = 'lax' ENV['LIQUID_PARSER_MODE'] = 'lax'
Rake::Task['base_test'].invoke Rake::Task['base_test'].invoke
@@ -47,7 +49,7 @@ task :test do
end end
end end
task gem: :build task(gem: :build)
task :build do task :build do
system "gem build liquid.gemspec" system "gem build liquid.gemspec"
end end
@@ -94,7 +96,7 @@ namespace :memory_profile do
end end
end end
desc "Run example" desc("Run example")
task :example do task :example do
ruby "-w -d -Ilib example/server/server.rb" ruby "-w -d -Ilib example/server/server.rb"
end end

View File

@@ -1,6 +1,8 @@
# frozen_string_literal: true
module ProductsFilter module ProductsFilter
def price(integer) def price(integer)
sprintf("$%.2d USD", integer / 100.0) format("$%.2d USD", integer / 100.0)
end end
def prettyprint(text) def prettyprint(text)

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
class LiquidServlet < WEBrick::HTTPServlet::AbstractServlet class LiquidServlet < WEBrick::HTTPServlet::AbstractServlet
def do_GET(req, res) def do_GET(req, res)
handle(:get, req, res) handle(:get, req, res)
@@ -9,12 +11,12 @@ class LiquidServlet < WEBrick::HTTPServlet::AbstractServlet
private private
def handle(type, req, res) def handle(_type, req, res)
@request = req @request = req
@response = res @response = res
@request.path_info =~ /(\w+)\z/ @request.path_info =~ /(\w+)\z/
@action = $1 || 'index' @action = Regexp.last_match(1) || 'index'
@assigns = send(@action) if respond_to?(@action) @assigns = send(@action) if respond_to?(@action)
@response['Content-Type'] = "text/html" @response['Content-Type'] = "text/html"

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'webrick' require 'webrick'
require 'rexml/document' require 'rexml/document'

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
# Copyright (c) 2005 Tobias Luetke # Copyright (c) 2005 Tobias Luetke
# #
# Permission is hereby granted, free of charge, to any person obtaining # Permission is hereby granted, free of charge, to any person obtaining
@@ -21,10 +23,10 @@
module Liquid module Liquid
FilterSeparator = /\|/ FilterSeparator = /\|/
ArgumentSeparator = ','.freeze ArgumentSeparator = ','
FilterArgumentSeparator = ':'.freeze FilterArgumentSeparator = ':'
VariableAttributeSeparator = '.'.freeze VariableAttributeSeparator = '.'
WhitespaceControl = '-'.freeze WhitespaceControl = '-'
TagStart = /\{\%/ TagStart = /\{\%/
TagEnd = /\%\}/ TagEnd = /\%\}/
VariableSignature = /\(?[\w\-\.\[\]]\)?/ VariableSignature = /\(?[\w\-\.\[\]]\)?/
@@ -76,7 +78,10 @@ require 'liquid/tokenizer'
require 'liquid/parse_context' require 'liquid/parse_context'
require 'liquid/partial_cache' require 'liquid/partial_cache'
require 'liquid/usage' require 'liquid/usage'
require 'liquid/register'
require 'liquid/static_registers'
# Load all the tags of the standard library # Load all the tags of the standard library
# #
Dir["#{__dir__}/liquid/tags/*.rb"].each { |f| require f } 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 module Liquid
class Block < Tag class Block < Tag
MAX_DEPTH = 100 MAX_DEPTH = 100
@@ -27,16 +29,16 @@ module Liquid
end end
def unknown_tag(tag, _params, _tokens) def unknown_tag(tag, _params, _tokens)
if tag == 'else'.freeze if tag == 'else'
raise SyntaxError.new(parse_context.locale.t("errors.syntax.unexpected_else".freeze, raise SyntaxError, parse_context.locale.t("errors.syntax.unexpected_else",
block_name: block_name)) block_name: block_name)
elsif tag.start_with?('end'.freeze) elsif tag.start_with?('end')
raise SyntaxError.new(parse_context.locale.t("errors.syntax.invalid_delimiter".freeze, raise SyntaxError, parse_context.locale.t("errors.syntax.invalid_delimiter",
tag: tag, tag: tag,
block_name: block_name, block_name: block_name,
block_delimiter: block_delimiter)) block_delimiter: block_delimiter)
else 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 end
@@ -52,7 +54,7 @@ module Liquid
def parse_body(body, tokens) def parse_body(body, tokens)
if parse_context.depth >= MAX_DEPTH if parse_context.depth >= MAX_DEPTH
raise StackLevelError, "Nesting too deep".freeze raise StackLevelError, "Nesting too deep"
end end
parse_context.depth += 1 parse_context.depth += 1
begin begin
@@ -61,7 +63,7 @@ module Liquid
return false if end_tag_name == block_delimiter return false if end_tag_name == block_delimiter
unless end_tag_name 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 end
# this tag is not registered with the system # this tag is not registered with the system

View File

@@ -1,11 +1,13 @@
# frozen_string_literal: true
module Liquid module Liquid
class BlockBody class BlockBody
LiquidTagToken = /\A\s*(\w+)\s*(.*?)\z/o LiquidTagToken = /\A\s*(\w+)\s*(.*?)\z/o
FullToken = /\A#{TagStart}#{WhitespaceControl}?(\s*)(\w+)(\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\z/om FullToken = /\A#{TagStart}#{WhitespaceControl}?(\s*)(\w+)(\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om
WhitespaceOrNothing = /\A\s*\z/ WhitespaceOrNothing = /\A\s*\z/
TAGSTART = "{%".freeze TAGSTART = "{%"
VARSTART = "{{".freeze VARSTART = "{{"
attr_reader :nodelist attr_reader :nodelist
@@ -25,16 +27,16 @@ module Liquid
end end
private def parse_for_liquid_tag(tokenizer, parse_context) private def parse_for_liquid_tag(tokenizer, parse_context)
while token = tokenizer.shift while (token = tokenizer.shift)
unless token.empty? || token =~ WhitespaceOrNothing unless token.empty? || token =~ WhitespaceOrNothing
unless token =~ LiquidTagToken unless token =~ LiquidTagToken
# line isn't empty but didn't match tag syntax, yield and let the # line isn't empty but didn't match tag syntax, yield and let the
# caller raise a syntax error # caller raise a syntax error
return yield token, token return yield token, token
end end
tag_name = $1 tag_name = Regexp.last_match(1)
markup = $2 markup = Regexp.last_match(2)
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 # end parsing if we reach an unknown tag and let the caller decide
# determine how to proceed # determine how to proceed
return yield tag_name, markup return yield tag_name, markup
@@ -50,7 +52,7 @@ module Liquid
end end
private def parse_for_document(tokenizer, parse_context, &block) private def parse_for_document(tokenizer, parse_context, &block)
while token = tokenizer.shift while (token = tokenizer.shift)
next if token.empty? next if token.empty?
case case
when token.start_with?(TAGSTART) when token.start_with?(TAGSTART)
@@ -58,21 +60,21 @@ module Liquid
unless token =~ FullToken unless token =~ FullToken
raise_missing_tag_terminator(token, parse_context) raise_missing_tag_terminator(token, parse_context)
end end
tag_name = $2 tag_name = Regexp.last_match(2)
markup = $4 markup = Regexp.last_match(4)
if parse_context.line_number if parse_context.line_number
# newlines inside the tag should increase the line number, # newlines inside the tag should increase the line number,
# particularly important for multiline {% liquid %} tags # 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 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) 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) next parse_for_liquid_tag(liquid_tag_tokenizer, parse_context, &block)
end 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 # end parsing if we reach an unknown tag and let the caller decide
# determine how to proceed # determine how to proceed
return yield tag_name, markup return yield tag_name, markup
@@ -101,7 +103,7 @@ module Liquid
def whitespace_handler(token, parse_context) def whitespace_handler(token, parse_context)
if token[2] == WhitespaceControl if token[2] == WhitespaceControl
previous_token = @nodelist.last previous_token = @nodelist.last
if previous_token.is_a? String if previous_token.is_a?(String)
previous_token.rstrip! previous_token.rstrip!
end end
end end
@@ -113,14 +115,14 @@ module Liquid
end end
def render(context) def render(context)
render_to_output_buffer(context, '') render_to_output_buffer(context, +'')
end end
def render_to_output_buffer(context, output) def render_to_output_buffer(context, output)
context.resource_limits.render_score += @nodelist.length context.resource_limits.render_score += @nodelist.length
idx = 0 idx = 0
while node = @nodelist[idx] while (node = @nodelist[idx])
previous_output_size = output.bytesize previous_output_size = output.bytesize
case node case node
@@ -129,7 +131,7 @@ module Liquid
when Variable when Variable
render_node(context, output, node) render_node(context, output, node)
when Block 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 break if context.interrupt? # might have happened in a for-block
when Continue, Break when Continue, Break
# If we get an Interrupt that means the block must stop processing. An # If we get an Interrupt that means the block must stop processing. An
@@ -152,7 +154,13 @@ module Liquid
private private
def render_node(context, output, node) def render_node(context, output, node)
if node.disabled?(context)
output << node.disabled_error_message
return
end
disable_tags(context, node.disabled_tags) do
node.render_to_output_buffer(context, output) node.render_to_output_buffer(context, output)
end
rescue UndefinedVariable, UndefinedDropMethod, UndefinedFilter => e rescue UndefinedVariable, UndefinedDropMethod, UndefinedFilter => e
context.handle_error(e, node.line_number) context.handle_error(e, node.line_number)
rescue ::StandardError => e rescue ::StandardError => e
@@ -160,10 +168,15 @@ module Liquid
output << context.handle_error(e, line_number) output << context.handle_error(e, line_number)
end 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) def raise_if_resource_limits_reached(context, length)
context.resource_limits.render_length += length context.resource_limits.render_length += length
return unless context.resource_limits.reached? return unless context.resource_limits.reached?
raise MemoryError.new("Memory limits exceeded".freeze) raise MemoryError, "Memory limits exceeded"
end end
def create_variable(token, parse_context) def create_variable(token, parse_context)
@@ -175,11 +188,11 @@ module Liquid
end end
def raise_missing_tag_terminator(token, parse_context) 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 end
def raise_missing_variable_terminator(token, parse_context) 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 end
def registered_tags def registered_tags

View File

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

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid module Liquid
# Context keeps the variable stack and resolves variables, as well as keywords # 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 attr_accessor :exception_renderer, :template_name, :partial, :global_filter, :strict_variables, :strict_filters
# rubocop:disable Metrics/ParameterLists # rubocop:disable Metrics/ParameterLists
def self.build(environments: {}, outer_scope: {}, registers: {}, rethrow_errors: false, resource_limits: nil, 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_registers, static_environments) new(environments, outer_scope, registers, rethrow_errors, resource_limits, static_environments)
end 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 = [environments]
@environments.flatten! @environments.flatten!
@static_environments = [static_environments].flat_map(&:freeze).freeze @static_environments = [static_environments].flat_map(&:freeze).freeze
@scopes = [(outer_scope || {})] @scopes = [(outer_scope || {})]
@registers = registers @registers = registers
@static_registers = static_registers.freeze
@errors = [] @errors = []
@partial = false @partial = false
@strict_variables = false @strict_variables = false
@@ -35,11 +36,9 @@ module Liquid
@base_scope_depth = 0 @base_scope_depth = 0
squash_instance_assigns_with_environments squash_instance_assigns_with_environments
@this_stack_used = false
self.exception_renderer = Template.default_exception_renderer self.exception_renderer = Template.default_exception_renderer
if rethrow_errors if rethrow_errors
self.exception_renderer = ->(e) { raise } self.exception_renderer = ->(_e) { raise }
end end
@interrupts = [] @interrupts = []
@@ -122,19 +121,11 @@ module Liquid
# end # end
# #
# context['var] #=> nil # context['var] #=> nil
def stack(new_scope = nil) def stack(new_scope = {})
old_stack_used = @this_stack_used
if new_scope
push(new_scope) push(new_scope)
@this_stack_used = true
else
@this_stack_used = false
end
yield yield
ensure ensure
pop if @this_stack_used pop
@this_stack_used = old_stack_used
end end
# Creates a new context inheriting resource limits, filters, environment etc., # Creates a new context inheriting resource limits, filters, environment etc.,
@@ -145,7 +136,7 @@ module Liquid
Context.build( Context.build(
resource_limits: resource_limits, resource_limits: resource_limits,
static_environments: static_environments, static_environments: static_environments,
static_registers: static_registers registers: StaticRegisters.new(registers)
).tap do |subcontext| ).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.exception_renderer = exception_renderer
@@ -162,10 +153,6 @@ module Liquid
# Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt> # Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
def []=(key, value) def []=(key, value)
unless @this_stack_used
@this_stack_used = true
push({})
end
@scopes[0][key] = value @scopes[0][key] = value
end end
@@ -215,7 +202,7 @@ module Liquid
value = obj[key] value = obj[key]
if value.is_a?(Proc) && obj.respond_to?(:[]=) 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 else
value value
end end
@@ -246,7 +233,7 @@ module Liquid
end end
def check_overflow def check_overflow
raise StackLevelError, "Nesting too deep".freeze if overflow? raise StackLevelError, "Nesting too deep" if overflow?
end end
def overflow? def overflow?

View File

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

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'set' require 'set'
module Liquid module Liquid
@@ -25,7 +27,7 @@ module Liquid
# Catch all for the method # Catch all for the method
def liquid_method_missing(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}" raise Liquid::UndefinedDropMethod, "undefined method #{method}"
end end
@@ -67,7 +69,7 @@ module Liquid
if include?(Enumerable) if include?(Enumerable)
blacklist += Enumerable.public_instance_methods blacklist += Enumerable.public_instance_methods
blacklist -= [:sort, :count, :first, :min, :max, :include?] blacklist -= [:sort, :count, :first, :min, :max]
end end
whitelist = [:to_liquid] + (public_instance_methods - blacklist) whitelist = [:to_liquid] + (public_instance_methods - blacklist)

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid module Liquid
class Error < ::StandardError class Error < ::StandardError
attr_accessor :line_number attr_accessor :line_number
@@ -5,7 +7,7 @@ module Liquid
attr_accessor :markup_context attr_accessor :markup_context
def to_s(with_prefix = true) def to_s(with_prefix = true)
str = "" str = +""
str << message_prefix if with_prefix str << message_prefix if with_prefix
str << super() str << super()
@@ -20,11 +22,11 @@ module Liquid
private private
def message_prefix def message_prefix
str = "" str = +""
if is_a?(SyntaxError) str << if is_a?(SyntaxError)
str << "Liquid syntax error" "Liquid syntax error"
else else
str << "Liquid error" "Liquid error"
end end
if line_number if line_number

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid module Liquid
class Expression class Expression
class MethodLiteral class MethodLiteral
@@ -14,11 +16,11 @@ module Liquid
end end
LITERALS = { LITERALS = {
nil => nil, 'nil'.freeze => nil, 'null'.freeze => nil, ''.freeze => nil, nil => nil, 'nil' => nil, 'null' => nil, '' => nil,
'true'.freeze => true, 'true' => true,
'false'.freeze => false, 'false' => false,
'blank'.freeze => MethodLiteral.new(:blank?, '').freeze, 'blank' => MethodLiteral.new(:blank?, '').freeze,
'empty'.freeze => MethodLiteral.new(:empty?, '').freeze 'empty' => MethodLiteral.new(:empty?, '').freeze
}.freeze }.freeze
SINGLE_QUOTED_STRING = /\A'(.*)'\z/m SINGLE_QUOTED_STRING = /\A'(.*)'\z/m
@@ -33,13 +35,13 @@ module Liquid
else else
case markup case markup
when SINGLE_QUOTED_STRING, DOUBLE_QUOTED_STRING when SINGLE_QUOTED_STRING, DOUBLE_QUOTED_STRING
$1 Regexp.last_match(1)
when INTEGERS_REGEX when INTEGERS_REGEX
$1.to_i Regexp.last_match(1).to_i
when RANGES_REGEX when RANGES_REGEX
RangeLookup.parse($1, $2) RangeLookup.parse(Regexp.last_match(1), Regexp.last_match(2))
when FLOATS_REGEX when FLOATS_REGEX
$1.to_f Regexp.last_match(1).to_f
else else
VariableLookup.parse(markup) VariableLookup.parse(markup)
end end

View File

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

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid module Liquid
# A Liquid file system is a way to let your templates retrieve other templates for use with the include tag. # A Liquid file system is a way to let your templates retrieve other templates for use with the include tag.
# #
@@ -44,7 +46,7 @@ module Liquid
class LocalFileSystem class LocalFileSystem
attr_accessor :root attr_accessor :root
def initialize(root, pattern = "_%s.liquid".freeze) def initialize(root, pattern = "_%s.liquid")
@root = root @root = root
@pattern = pattern @pattern = pattern
end end
@@ -57,9 +59,9 @@ module Liquid
end end
def full_path(template_path) 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)) File.join(root, File.dirname(template_path), @pattern % File.basename(template_path))
else else
File.join(root, @pattern % template_path) File.join(root, @pattern % template_path)

View File

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

View File

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

View File

@@ -1,10 +1,12 @@
# frozen_string_literal: true
module Liquid module Liquid
# An interrupt is any command that breaks processing of a block (ex: a for loop). # An interrupt is any command that breaks processing of a block (ex: a for loop).
class Interrupt class Interrupt
attr_reader :message attr_reader :message
def initialize(message = nil) def initialize(message = nil)
@message = message || "interrupt".freeze @message = message || "interrupt"
end end
end end

View File

@@ -1,17 +1,19 @@
# frozen_string_literal: true
require "strscan" require "strscan"
module Liquid module Liquid
class Lexer class Lexer
SPECIALS = { SPECIALS = {
'|'.freeze => :pipe, '|' => :pipe,
'.'.freeze => :dot, '.' => :dot,
':'.freeze => :colon, ':' => :colon,
','.freeze => :comma, ',' => :comma,
'['.freeze => :open_square, '[' => :open_square,
']'.freeze => :close_square, ']' => :close_square,
'('.freeze => :open_round, '(' => :open_round,
')'.freeze => :close_round, ')' => :close_round,
'?'.freeze => :question, '?' => :question,
'-'.freeze => :dash '-' => :dash,
}.freeze }.freeze
IDENTIFIER = /[a-zA-Z_][\w-]*\??/ IDENTIFIER = /[a-zA-Z_][\w-]*\??/
SINGLE_STRING_LITERAL = /'[^\']*'/ SINGLE_STRING_LITERAL = /'[^\']*'/
@@ -31,16 +33,21 @@ module Liquid
until @ss.eos? until @ss.eos?
@ss.skip(WHITESPACE_OR_NOTHING) @ss.skip(WHITESPACE_OR_NOTHING)
break if @ss.eos? break if @ss.eos?
tok = case tok = if (t = @ss.scan(COMPARISON_OPERATOR))
when t = @ss.scan(COMPARISON_OPERATOR) then [:comparison, t] [:comparison, t]
when t = @ss.scan(SINGLE_STRING_LITERAL) then [:string, t] elsif (t = @ss.scan(SINGLE_STRING_LITERAL))
when t = @ss.scan(DOUBLE_STRING_LITERAL) then [:string, t] [:string, t]
when t = @ss.scan(NUMBER_LITERAL) then [:number, t] elsif (t = @ss.scan(DOUBLE_STRING_LITERAL))
when t = @ss.scan(IDENTIFIER) then [:id, t] [:string, t]
when t = @ss.scan(DOTDOT) then [:dotdot, t] elsif (t = @ss.scan(NUMBER_LITERAL))
[:number, t]
elsif (t = @ss.scan(IDENTIFIER))
[:id, t]
elsif (t = @ss.scan(DOTDOT))
[:dotdot, t]
else else
c = @ss.getch c = @ss.getch
if s = SPECIALS[c] if (s = SPECIALS[c])
[s, c] [s, c]
else else
raise SyntaxError, "Unexpected character #{c}" raise SyntaxError, "Unexpected character #{c}"

View File

@@ -25,3 +25,5 @@
render: "Syntax error in tag 'render' - Template name must be a quoted string" render: "Syntax error in tag 'render' - Template name must be a quoted string"
argument: argument:
include: "Argument error in tag 'include' - Illegal template name" 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 module Liquid
class ParseContext class ParseContext
attr_accessor :locale, :line_number, :trim_whitespace, :depth attr_accessor :locale, :line_number, :trim_whitespace, :depth
@@ -19,7 +21,6 @@ module Liquid
@partial = value @partial = value
@options = value ? partial_options : @template_options @options = value ? partial_options : @template_options
@error_mode = @options[:error_mode] || Template.error_mode @error_mode = @options[:error_mode] || Template.error_mode
value
end end
def partial_options def partial_options
@@ -28,7 +29,7 @@ module Liquid
if dont_pass == true if dont_pass == true
{ locale: locale } { locale: locale }
elsif dont_pass.is_a?(Array) 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 else
@template_options @template_options
end end

View File

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

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid module Liquid
class Parser class Parser
def initialize(input) def initialize(input)
@@ -51,7 +53,7 @@ module Liquid
token = @tokens[@p] token = @tokens[@p]
if token[0] == :id if token[0] == :id
variable_signature variable_signature
elsif SINGLE_TOKEN_EXPRESSION_TYPES.include? token[0] elsif SINGLE_TOKEN_EXPRESSION_TYPES.include?(token[0])
consume consume
elsif token.first == :open_round elsif token.first == :open_round
consume consume
@@ -66,10 +68,10 @@ module Liquid
end end
def argument def argument
str = "" str = +""
# might be a keyword argument (identifier: expression) # might be a keyword argument (identifier: expression)
if look(:id) && look(:colon, 1) if look(:id) && look(:colon, 1)
str << consume << consume << ' '.freeze str << consume << consume << ' '
end end
str << expression str << expression

View File

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

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid module Liquid
class PartialCache class PartialCache
def self.load(template_name, context:, parse_context:) def self.load(template_name, context:, parse_context:)

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'liquid/profiler/hooks' require 'liquid/profiler/hooks'
module Liquid module Liquid

View File

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

View File

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

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 module Liquid
class ResourceLimits class ResourceLimits
attr_accessor :render_length, :render_score, :assign_score, attr_accessor :render_length, :render_score, :assign_score,

View File

@@ -1,20 +1,22 @@
# frozen_string_literal: true
require 'cgi' require 'cgi'
require 'bigdecimal' require 'bigdecimal'
module Liquid module Liquid
module StandardFilters module StandardFilters
HTML_ESCAPE = { HTML_ESCAPE = {
'&'.freeze => '&amp;'.freeze, '&' => '&amp;',
'>'.freeze => '&gt;'.freeze, '>' => '&gt;',
'<'.freeze => '&lt;'.freeze, '<' => '&lt;',
'"'.freeze => '&quot;'.freeze, '"' => '&quot;',
"'".freeze => '&#39;'.freeze "'" => '&#39;',
}.freeze }.freeze
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/ HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/
STRIP_HTML_BLOCKS = Regexp.union( STRIP_HTML_BLOCKS = Regexp.union(
/<script.*?<\/script>/m, %r{<script.*?</script>}m,
/<!--.*?-->/m, /<!--.*?-->/m,
/<style.*?<\/style>/m %r{<style.*?</style>}m
) )
STRIP_HTML_TAGS = /<.*?>/m STRIP_HTML_TAGS = /<.*?>/m
@@ -72,7 +74,7 @@ module Liquid
end end
# Truncate a string down to x characters # 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? return if input.nil?
input_str = input.to_s input_str = input.to_s
length = Utils.to_integer(length) length = Utils.to_integer(length)
@@ -82,13 +84,13 @@ module Liquid
input_str.length > length ? input_str[0...l].concat(truncate_string_str) : input_str input_str.length > length ? input_str[0...l].concat(truncate_string_str) : input_str
end end
def truncatewords(input, words = 15, truncate_string = "...".freeze) def truncatewords(input, words = 15, truncate_string = "...")
return if input.nil? return if input.nil?
wordlist = input.to_s.split wordlist = input.to_s.split
words = Utils.to_integer(words) words = Utils.to_integer(words)
l = words - 1 l = words - 1
l = 0 if l < 0 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 end
# Split input string into an array of substrings separated by given pattern. # Split input string into an array of substrings separated by given pattern.
@@ -113,7 +115,7 @@ module Liquid
end end
def strip_html(input) def strip_html(input)
empty = ''.freeze empty = ''
result = input.to_s.gsub(STRIP_HTML_BLOCKS, empty) result = input.to_s.gsub(STRIP_HTML_BLOCKS, empty)
result.gsub!(STRIP_HTML_TAGS, empty) result.gsub!(STRIP_HTML_TAGS, empty)
result result
@@ -121,11 +123,11 @@ module Liquid
# Remove all newlines from the string # Remove all newlines from the string
def strip_newlines(input) def strip_newlines(input)
input.to_s.gsub(/\r?\n/, ''.freeze) input.to_s.gsub(/\r?\n/, '')
end end
# Join elements of the array with certain character between them # Join elements of the array with certain character between them
def join(input, glue = ' '.freeze) def join(input, glue = ' ')
InputIterator.new(input).join(glue) InputIterator.new(input).join(glue)
end end
@@ -191,6 +193,23 @@ module Liquid
end end
end end
# Sort elements of an array in numeric order
# provide optional property with which to sort an array of hashes or drops
def sort_numeric(input, property = nil)
ary = InputIterator.new(input)
if property.nil?
ary.sort do |a, b|
Utils.to_number(a) <=> Utils.to_number(b)
end
elsif ary.empty? # The next two cases assume a non-empty array.
[]
elsif ary.first.respond_to?(:[]) && !ary.first[property].nil?
ary.sort do |a, b|
Utils.to_number(a[property]) <=> Utils.to_number(b[property])
end
end
end
# Remove duplicate elements from an array # Remove duplicate elements from an array
# provide optional property with which to determine uniqueness # provide optional property with which to determine uniqueness
def uniq(input, property = nil) def uniq(input, property = nil)
@@ -220,7 +239,7 @@ module Liquid
InputIterator.new(input).map do |e| InputIterator.new(input).map do |e|
e = e.call if e.is_a?(Proc) e = e.call if e.is_a?(Proc)
if property == "to_liquid".freeze if property == "to_liquid"
e e
elsif e.respond_to?(:[]) elsif e.respond_to?(:[])
r = e[property] r = e[property]
@@ -250,23 +269,23 @@ module Liquid
end end
# Replace occurrences of a string with another # 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) input.to_s.gsub(string.to_s, replacement.to_s)
end end
# Replace the first occurrences of a string with another # 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) input.to_s.sub(string.to_s, replacement.to_s)
end end
# remove a substring # remove a substring
def remove(input, string) def remove(input, string)
input.to_s.gsub(string.to_s, ''.freeze) input.to_s.gsub(string.to_s, '')
end end
# remove the first occurrences of a substring # remove the first occurrences of a substring
def remove_first(input, string) def remove_first(input, string)
input.to_s.sub(string.to_s, ''.freeze) input.to_s.sub(string.to_s, '')
end end
# add one string to another # add one string to another
@@ -276,7 +295,7 @@ module Liquid
def concat(input, array) def concat(input, array)
unless array.respond_to?(:to_ary) unless array.respond_to?(:to_ary)
raise ArgumentError.new("concat filter requires an array argument") raise ArgumentError, "concat filter requires an array argument"
end end
InputIterator.new(input).concat(array) InputIterator.new(input).concat(array)
end end
@@ -288,7 +307,7 @@ module Liquid
# Add <br /> tags in front of all newlines in input string # Add <br /> tags in front of all newlines in input string
def newline_to_br(input) def newline_to_br(input)
input.to_s.gsub(/\n/, "<br />\n".freeze) input.to_s.gsub(/\n/, "<br />\n")
end end
# Reformat a date using Ruby's core Time#strftime( string ) -> string # Reformat a date using Ruby's core Time#strftime( string ) -> string
@@ -325,7 +344,7 @@ module Liquid
def date(input, format) def date(input, format)
return input if format.to_s.empty? 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) date.strftime(format.to_s)
end end
@@ -419,7 +438,7 @@ module Liquid
result.is_a?(BigDecimal) ? result.to_f : result result.is_a?(BigDecimal) ? result.to_f : result
end end
def default(input, default_value = ''.freeze) def default(input, default_value = '')
if !input || input.respond_to?(:empty?) && input.empty? 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 Usage.increment("default_filter_received_false_value") if input == false # See https://github.com/Shopify/liquid/issues/1127
default_value default_value
@@ -431,7 +450,7 @@ module Liquid
private private
def raise_property_error(property) def raise_property_error(property)
raise Liquid::ArgumentError.new("cannot select the property '#{property}'") raise Liquid::ArgumentError, "cannot select the property '#{property}'"
end end
def apply_operation(input, operand, operation) def apply_operation(input, operand, operation)

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' require 'set'
module Liquid module Liquid
@@ -27,7 +29,7 @@ module Liquid
def self.add_filter(filter) def self.add_filter(filter)
raise ArgumentError, "Expected module but got: #{filter.class}" unless filter.is_a?(Module) 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) } invokable_non_public_methods = (filter.private_instance_methods + filter.protected_instance_methods).select { |m| invokable?(m) }
if invokable_non_public_methods.any? if invokable_non_public_methods.any?
raise MethodOverrideError, "Filter overrides registered public methods as non public: #{invokable_non_public_methods.join(', ')}" 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) def invoke(method, *args)
if self.class.invokable?(method) if self.class.invokable?(method)
send(method, *args) send(method, *args)
elsif @context && @context.strict_filters elsif @context&.strict_filters
raise Liquid::UndefinedFilter, "undefined filter #{method}" raise Liquid::UndefinedFilter, "undefined filter #{method}"
else else
args.first args.first

View File

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

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid module Liquid
class Tag class Tag
attr_reader :nodelist, :tag_name, :line_number, :parse_context attr_reader :nodelist, :tag_name, :line_number, :parse_context
@@ -11,7 +13,15 @@ module Liquid
tag tag
end end
def disable_tags(*tags)
disabled_tags.push(*tags)
end
private :new private :new
def disabled_tags
@disabled_tags ||= []
end
end end
def initialize(tag_name, markup, parse_context) def initialize(tag_name, markup, parse_context)
@@ -33,7 +43,15 @@ module Liquid
end end
def render(_context) 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 end
# For backwards compatibility with custom tags. In a future release, the semantics # For backwards compatibility with custom tags. In a future release, the semantics
@@ -47,5 +65,9 @@ module Liquid
def blank? def blank?
false false
end end
def disabled_tags
self.class.disabled_tags
end
end end
end end

View File

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

View File

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

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid module Liquid
# Capture stores the result of a block into a variable without rendering it inplace. # 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) def initialize(tag_name, markup, options)
super super
if markup =~ Syntax if markup =~ Syntax
@to = $1 @to = Regexp.last_match(1)
else else
raise SyntaxError.new(options[:locale].t("errors.syntax.capture")) raise SyntaxError, options[:locale].t("errors.syntax.capture")
end end
end end
@@ -35,5 +37,5 @@ module Liquid
end end
end end
Template.register_tag('capture'.freeze, Capture) Template.register_tag('capture', Capture)
end end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid module Liquid
class Case < Block class Case < Block
Syntax = /(#{QuotedFragment})/o Syntax = /(#{QuotedFragment})/o
@@ -10,17 +12,15 @@ module Liquid
@blocks = [] @blocks = []
if markup =~ Syntax if markup =~ Syntax
@left = Expression.parse($1) @left = Expression.parse(Regexp.last_match(1))
else else
raise SyntaxError.new(options[:locale].t("errors.syntax.case".freeze)) raise SyntaxError, options[:locale].t("errors.syntax.case")
end end
end end
def parse(tokens) def parse(tokens)
body = BlockBody.new body = BlockBody.new
while parse_body(body, tokens) body = @blocks.last.attachment while parse_body(body, tokens)
body = @blocks.last.attachment
end
end end
def nodelist def nodelist
@@ -29,9 +29,9 @@ module Liquid
def unknown_tag(tag, markup, tokens) def unknown_tag(tag, markup, tokens)
case tag case tag
when 'when'.freeze when 'when'
record_when_condition(markup) record_when_condition(markup)
when 'else'.freeze when 'else'
record_else_condition(markup) record_else_condition(markup)
else else
super super
@@ -39,7 +39,6 @@ module Liquid
end end
def render_to_output_buffer(context, output) def render_to_output_buffer(context, output)
context.stack do
execute_else_block = true execute_else_block = true
@blocks.each do |block| @blocks.each do |block|
@@ -50,7 +49,6 @@ module Liquid
block.attachment.render_to_output_buffer(context, output) block.attachment.render_to_output_buffer(context, output)
end end
end end
end
output output
end end
@@ -62,12 +60,12 @@ module Liquid
while markup while markup
unless markup =~ WhenSyntax 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 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) block.attach(body)
@blocks << block @blocks << block
end end
@@ -75,7 +73,7 @@ module Liquid
def record_else_condition(markup) def record_else_condition(markup)
unless markup.strip.empty? 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 end
block = ElseCondition.new block = ElseCondition.new
@@ -90,5 +88,5 @@ module Liquid
end end
end end
Template.register_tag('case'.freeze, Case) Template.register_tag('case', Case)
end end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid module Liquid
class Comment < Block class Comment < Block
def render_to_output_buffer(_context, output) def render_to_output_buffer(_context, output)
@@ -12,5 +14,5 @@ module Liquid
end end
end end
Template.register_tag('comment'.freeze, Comment) Template.register_tag('comment', Comment)
end end

View File

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

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid module Liquid
# Cycle is usually used within a loop to alternate between values, like colors or DOM classes. # Cycle is usually used within a loop to alternate between values, like colors or DOM classes.
# #
@@ -21,20 +23,19 @@ module Liquid
super super
case markup case markup
when NamedSyntax when NamedSyntax
@variables = variables_from_string($2) @variables = variables_from_string(Regexp.last_match(2))
@name = Expression.parse($1) @name = Expression.parse(Regexp.last_match(1))
when SimpleSyntax when SimpleSyntax
@variables = variables_from_string(markup) @variables = variables_from_string(markup)
@name = @variables.to_s @name = @variables.to_s
else else
raise SyntaxError.new(options[:locale].t("errors.syntax.cycle".freeze)) raise SyntaxError, options[:locale].t("errors.syntax.cycle")
end end
end end
def render_to_output_buffer(context, output) def render_to_output_buffer(context, output)
context.registers[:cycle] ||= {} context.registers[:cycle] ||= {}
context.stack do
key = context.evaluate(@name) key = context.evaluate(@name)
iteration = context.registers[:cycle][key].to_i iteration = context.registers[:cycle][key].to_i
@@ -51,7 +52,6 @@ module Liquid
iteration += 1 iteration += 1
iteration = 0 if iteration >= @variables.size iteration = 0 if iteration >= @variables.size
context.registers[:cycle][key] = iteration context.registers[:cycle][key] = iteration
end
output output
end end
@@ -61,7 +61,7 @@ module Liquid
def variables_from_string(markup) def variables_from_string(markup)
markup.split(',').collect do |var| markup.split(',').collect do |var|
var =~ /\s*(#{QuotedFragment})\s*/o var =~ /\s*(#{QuotedFragment})\s*/o
$1 ? Expression.parse($1) : nil Regexp.last_match(1) ? Expression.parse(Regexp.last_match(1)) : nil
end.compact end.compact
end end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid module Liquid
# decrement is used in a place where one needs to insert a counter # decrement is used in a place where one needs to insert a counter
# into a template, and needs the counter to survive across # into a template, and needs the counter to survive across
@@ -32,5 +34,5 @@ module Liquid
end end
end end
Template.register_tag('decrement'.freeze, Decrement) Template.register_tag('decrement', Decrement)
end end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid module Liquid
# Echo outputs an expression # Echo outputs an expression
# #
@@ -16,9 +18,9 @@ module Liquid
end end
def render(context) def render(context)
@variable.render_to_output_buffer(context, '') @variable.render_to_output_buffer(context, +'')
end end
end end
Template.register_tag('echo'.freeze, Echo) Template.register_tag('echo', Echo)
end end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid module Liquid
# "For" iterates over an array or collection. # "For" iterates over an array or collection.
# Several useful variables are available to you within the loop. # Several useful variables are available to you within the loop.
@@ -66,7 +68,7 @@ module Liquid
end end
def unknown_tag(tag, markup, tokens) def unknown_tag(tag, markup, tokens)
return super unless tag == 'else'.freeze return super unless tag == 'else'
@else_block = BlockBody.new @else_block = BlockBody.new
end end
@@ -86,31 +88,31 @@ module Liquid
def lax_parse(markup) def lax_parse(markup)
if markup =~ Syntax if markup =~ Syntax
@variable_name = $1 @variable_name = Regexp.last_match(1)
collection_name = $2 collection_name = Regexp.last_match(2)
@reversed = !!$3 @reversed = !!Regexp.last_match(3)
@name = "#{@variable_name}-#{collection_name}" @name = "#{@variable_name}-#{collection_name}"
@collection_name = Expression.parse(collection_name) @collection_name = Expression.parse(collection_name)
markup.scan(TagAttributes) do |key, value| markup.scan(TagAttributes) do |key, value|
set_attribute(key, value) set_attribute(key, value)
end end
else else
raise SyntaxError.new(options[:locale].t("errors.syntax.for".freeze)) raise SyntaxError, options[:locale].t("errors.syntax.for")
end end
end end
def strict_parse(markup) def strict_parse(markup)
p = Parser.new(markup) p = Parser.new(markup)
@variable_name = p.consume(:id) @variable_name = p.consume(:id)
raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_in".freeze)) unless p.id?('in'.freeze) raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_in") unless p.id?('in')
collection_name = p.expression collection_name = p.expression
@name = "#{@variable_name}-#{collection_name}" @name = "#{@variable_name}-#{collection_name}"
@collection_name = Expression.parse(collection_name) @collection_name = Expression.parse(collection_name)
@reversed = p.id?('reversed'.freeze) @reversed = p.id?('reversed')
while p.look(:id) && p.look(:colon, 1) while p.look(:id) && p.look(:colon, 1)
unless attribute = p.id?('limit'.freeze) || p.id?('offset'.freeze) unless (attribute = p.id?('limit') || p.id?('offset'))
raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_attribute".freeze)) raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_attribute")
end end
p.consume p.consume
set_attribute(attribute, p.expression) set_attribute(attribute, p.expression)
@@ -162,7 +164,7 @@ module Liquid
for_stack.push(loop_vars) for_stack.push(loop_vars)
begin begin
context['forloop'.freeze] = loop_vars context['forloop'] = loop_vars
segment.each do |item| segment.each do |item|
context[@variable_name] = item context[@variable_name] = item
@@ -170,11 +172,10 @@ module Liquid
loop_vars.send(:increment!) loop_vars.send(:increment!)
# Handle any interrupts if they exist. # Handle any interrupts if they exist.
if context.interrupt? next unless context.interrupt?
interrupt = context.pop_interrupt interrupt = context.pop_interrupt
break if interrupt.is_a? BreakInterrupt break if interrupt.is_a?(BreakInterrupt)
next if interrupt.is_a? ContinueInterrupt next if interrupt.is_a?(ContinueInterrupt)
end
end end
ensure ensure
for_stack.pop for_stack.pop
@@ -186,13 +187,13 @@ module Liquid
def set_attribute(key, expr) def set_attribute(key, expr)
case key case key
when 'offset'.freeze when 'offset'
@from = if expr == 'continue'.freeze @from = if expr == 'continue'
:continue :continue
else else
Expression.parse(expr) Expression.parse(expr)
end end
when 'limit'.freeze when 'limit'
@limit = Expression.parse(expr) @limit = Expression.parse(expr)
end end
end end
@@ -212,5 +213,5 @@ module Liquid
end end
end end
Template.register_tag('for'.freeze, For) Template.register_tag('for', For)
end end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid module Liquid
# If is the conditional block # If is the conditional block
# #
@@ -19,7 +21,7 @@ module Liquid
def initialize(tag_name, markup, options) def initialize(tag_name, markup, options)
super super
@blocks = [] @blocks = []
push_block('if'.freeze, markup) push_block('if', markup)
end end
def nodelist def nodelist
@@ -32,7 +34,7 @@ module Liquid
end end
def unknown_tag(tag, markup, tokens) def unknown_tag(tag, markup, tokens)
if ['elsif'.freeze, 'else'.freeze].include?(tag) if ['elsif', 'else'].include?(tag)
push_block(tag, markup) push_block(tag, markup)
else else
super super
@@ -40,13 +42,11 @@ module Liquid
end end
def render_to_output_buffer(context, output) def render_to_output_buffer(context, output)
context.stack do
@blocks.each do |block| @blocks.each do |block|
if block.evaluate(context) if block.evaluate(context)
return block.attachment.render_to_output_buffer(context, output) return block.attachment.render_to_output_buffer(context, output)
end end
end end
end
output output
end end
@@ -54,7 +54,7 @@ module Liquid
private private
def push_block(tag, markup) def push_block(tag, markup)
block = if tag == 'else'.freeze block = if tag == 'else'
ElseCondition.new ElseCondition.new
else else
parse_with_selected_parser(markup) parse_with_selected_parser(markup)
@@ -66,17 +66,17 @@ module Liquid
def lax_parse(markup) def lax_parse(markup)
expressions = markup.scan(ExpressionsAndOperators) 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? until expressions.empty?
operator = expressions.pop.to_s.strip 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)) new_condition = Condition.new(Expression.parse(Regexp.last_match(1)), Regexp.last_match(2), Expression.parse(Regexp.last_match(3)))
raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless BOOLEAN_OPERATORS.include?(operator) raise SyntaxError, options[:locale].t("errors.syntax.if") unless BOOLEAN_OPERATORS.include?(operator)
new_condition.send(operator, condition) new_condition.send(operator, condition)
condition = new_condition condition = new_condition
end end
@@ -94,7 +94,7 @@ module Liquid
def parse_binary_comparisons(p) def parse_binary_comparisons(p)
condition = parse_comparison(p) condition = parse_comparison(p)
first_condition = condition 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) child_condition = parse_comparison(p)
condition.send(op, child_condition) condition.send(op, child_condition)
condition = child_condition condition = child_condition
@@ -104,7 +104,7 @@ module Liquid
def parse_comparison(p) def parse_comparison(p)
a = Expression.parse(p.expression) a = Expression.parse(p.expression)
if op = p.consume?(:comparison) if (op = p.consume?(:comparison))
b = Expression.parse(p.expression) b = Expression.parse(p.expression)
Condition.new(a, op, b) Condition.new(a, op, b)
else else
@@ -119,5 +119,5 @@ module Liquid
end end
end end
Template.register_tag('if'.freeze, If) Template.register_tag('if', If)
end end

View File

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

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid module Liquid
# Include allows templates to relate with other templates # Include allows templates to relate with other templates
# #
@@ -23,8 +25,8 @@ module Liquid
if markup =~ Syntax if markup =~ Syntax
template_name = $1 template_name = Regexp.last_match(1)
variable_name = $3 variable_name = Regexp.last_match(3)
@variable_name_expr = variable_name ? Expression.parse(variable_name) : nil @variable_name_expr = variable_name ? Expression.parse(variable_name) : nil
@template_name_expr = Expression.parse(template_name) @template_name_expr = Expression.parse(template_name)
@@ -35,7 +37,7 @@ module Liquid
end end
else else
raise SyntaxError.new(options[:locale].t("errors.syntax.include".freeze)) raise SyntaxError, options[:locale].t("errors.syntax.include")
end end
end end
@@ -44,7 +46,7 @@ module Liquid
def render_to_output_buffer(context, output) def render_to_output_buffer(context, output)
template_name = context.evaluate(@template_name_expr) 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( partial = PartialCache.load(
template_name, template_name,
@@ -52,7 +54,7 @@ module Liquid
parse_context: parse_context parse_context: parse_context
) )
context_variable_name = template_name.split('/'.freeze).last context_variable_name = template_name.split('/').last
variable = if @variable_name_expr variable = if @variable_name_expr
context.evaluate(@variable_name_expr) context.evaluate(@variable_name_expr)
@@ -95,11 +97,11 @@ module Liquid
def children def children
[ [
@node.template_name_expr, @node.template_name_expr,
@node.variable_name_expr @node.variable_name_expr,
] + @node.attributes.values ] + @node.attributes.values
end end
end end
end end
Template.register_tag('include'.freeze, Include) Template.register_tag('include', Include)
end end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid module Liquid
# increment is used in a place where one needs to insert a counter # increment is used in a place where one needs to insert a counter
# into a template, and needs the counter to survive across # into a template, and needs the counter to survive across
@@ -29,5 +31,5 @@ module Liquid
end end
end end
Template.register_tag('increment'.freeze, Increment) Template.register_tag('increment', Increment)
end end

View File

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

View File

@@ -1,15 +1,19 @@
# frozen_string_literal: true
module Liquid module Liquid
class Render < Tag class Render < Tag
SYNTAX = /(#{QuotedString})#{QuotedFragment}*/o SYNTAX = /(#{QuotedString})#{QuotedFragment}*/o
disable_tags "include"
attr_reader :template_name_expr, :attributes attr_reader :template_name_expr, :attributes
def initialize(tag_name, markup, options) def initialize(tag_name, markup, options)
super 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)
@template_name_expr = Expression.parse(template_name) @template_name_expr = Expression.parse(template_name)
@@ -20,9 +24,13 @@ module Liquid
end end
def render_to_output_buffer(context, output) 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. # Though we evaluate this here we will only ever parse it as a string literal.
template_name = context.evaluate(@template_name_expr) 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( partial = PartialCache.load(
template_name, template_name,
@@ -50,5 +58,5 @@ module Liquid
end end
end end
Template.register_tag('render'.freeze, Render) Template.register_tag('render', Render)
end end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid module Liquid
class TableRow < Block class TableRow < Block
Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o
@@ -7,33 +9,33 @@ module Liquid
def initialize(tag_name, markup, options) def initialize(tag_name, markup, options)
super super
if markup =~ Syntax if markup =~ Syntax
@variable_name = $1 @variable_name = Regexp.last_match(1)
@collection_name = Expression.parse($2) @collection_name = Expression.parse(Regexp.last_match(2))
@attributes = {} @attributes = {}
markup.scan(TagAttributes) do |key, value| markup.scan(TagAttributes) do |key, value|
@attributes[key] = Expression.parse(value) @attributes[key] = Expression.parse(value)
end end
else else
raise SyntaxError.new(options[:locale].t("errors.syntax.table_row".freeze)) raise SyntaxError, options[:locale].t("errors.syntax.table_row")
end end
end end
def render_to_output_buffer(context, output) 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 from = @attributes.key?('offset') ? context.evaluate(@attributes['offset']).to_i : 0
to = @attributes.key?('limit'.freeze) ? from + context.evaluate(@attributes['limit'.freeze]).to_i : nil to = @attributes.key?('limit') ? from + context.evaluate(@attributes['limit']).to_i : nil
collection = Utils.slice_collection(collection, from, to) 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" output << "<tr class=\"row1\">\n"
context.stack do context.stack do
tablerowloop = Liquid::TablerowloopDrop.new(length, cols) tablerowloop = Liquid::TablerowloopDrop.new(length, cols)
context['tablerowloop'.freeze] = tablerowloop context['tablerowloop'] = tablerowloop
collection.each do |item| collection.each do |item|
context[@variable_name] = item context[@variable_name] = item
@@ -61,5 +63,5 @@ module Liquid
end end
end end
Template.register_tag('tablerow'.freeze, TableRow) Template.register_tag('tablerow', TableRow)
end end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require_relative 'if' require_relative 'if'
module Liquid module Liquid
@@ -7,7 +9,6 @@ module Liquid
# #
class Unless < If class Unless < If
def render_to_output_buffer(context, output) def render_to_output_buffer(context, output)
context.stack do
# First condition is interpreted backwards ( if not ) # First condition is interpreted backwards ( if not )
first_block = @blocks.first first_block = @blocks.first
unless first_block.evaluate(context) unless first_block.evaluate(context)
@@ -20,11 +21,10 @@ module Liquid
return block.attachment.render_to_output_buffer(context, output) return block.attachment.render_to_output_buffer(context, output)
end end
end end
end
output output
end end
end end
Template.register_tag('unless'.freeze, Unless) Template.register_tag('unless', Unless)
end end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid module Liquid
# Templates are central to liquid. # Templates are central to liquid.
# Interpretating templates is a two step process. First you compile the # Interpretating templates is a two step process. First you compile the
@@ -90,6 +92,14 @@ module Liquid
@tags ||= TagRegistry.new @tags ||= TagRegistry.new
end end
def add_register(name, klass)
registers[name.to_sym] = klass
end
def registers
@registers ||= {}
end
def error_mode def error_mode
@error_mode ||= :lax @error_mode ||= :lax
end end
@@ -165,14 +175,14 @@ module Liquid
# filters and tags and might be useful to integrate liquid more with its host application # filters and tags and might be useful to integrate liquid more with its host application
# #
def render(*args) def render(*args)
return ''.freeze if @root.nil? return '' if @root.nil?
context = case args.first context = case args.first
when Liquid::Context when Liquid::Context
c = args.shift c = args.shift
if @rethrow_errors if @rethrow_errors
c.exception_renderer = ->(e) { raise } c.exception_renderer = ->(_e) { raise }
end end
c c
@@ -189,18 +199,26 @@ module Liquid
output = nil output = nil
context_register = context.registers.is_a?(StaticRegisters) ? context.registers.static : context.registers
case args.last case args.last
when Hash when Hash
options = args.pop 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) apply_options_to_context(context, options)
when Module, Array when Module, Array
context.add_filters(args.pop) context.add_filters(args.pop)
end end
Template.registers.each do |key, register|
context_register[key] = register
end
# Retrying a render resets resource usage # Retrying a render resets resource usage
context.resource_limits.reset context.resource_limits.reset
@@ -208,7 +226,7 @@ module Liquid
# render the nodelist. # render the nodelist.
# for performance reasons we get an array back here. join will make a string out of it. # for performance reasons we get an array back here. join will make a string out of it.
with_profiling(context) do with_profiling(context) do
@root.render_to_output_buffer(context, output || '') @root.render_to_output_buffer(context, output || +'')
end end
rescue Liquid::MemoryError => e rescue Liquid::MemoryError => e
context.handle_error(e) context.handle_error(e)

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid module Liquid
class Tokenizer class Tokenizer
attr_reader :line_number, :for_liquid_tag attr_reader :line_number, :for_liquid_tag
@@ -10,7 +12,7 @@ module Liquid
end end
def shift def shift
token = @tokens.shift or return (token = @tokens.shift) || return
if @line_number if @line_number
@line_number += @for_liquid_tag ? 1 : token.count("\n") @line_number += @for_liquid_tag ? 1 : token.count("\n")
@@ -29,7 +31,7 @@ module Liquid
tokens = @source.split(TemplateParser) tokens = @source.split(TemplateParser)
# removes the rogue empty element at the beginning of the array # 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 tokens
end end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid module Liquid
module Usage module Usage
def self.increment(name) def self.increment(name)

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid module Liquid
module Utils module Utils
def self.slice_collection(collection, from, to) def self.slice_collection(collection, from, to)
@@ -50,7 +52,7 @@ module Liquid
when Numeric when Numeric
obj obj
when String 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 else
if obj.respond_to?(:to_number) if obj.respond_to?(:to_number)
obj.to_number obj.to_number
@@ -69,7 +71,7 @@ module Liquid
end end
case obj case obj
when 'now'.freeze, 'today'.freeze when 'now', 'today'
Time.now Time.now
when /\A\d+\z/, Integer when /\A\d+\z/, Integer
Time.at(obj.to_i) Time.at(obj.to_i)

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Liquid module Liquid
# Holds variables. Variables are only loaded "just in time" # Holds variables. Variables are only loaded "just in time"
# and are not evaluated as part of the render stage # and are not evaluated as part of the render stage
@@ -43,11 +45,11 @@ module Liquid
@filters = [] @filters = []
return unless markup =~ MarkupWithQuotedFragment return unless markup =~ MarkupWithQuotedFragment
name_markup = $1 name_markup = Regexp.last_match(1)
filter_markup = $2 filter_markup = Regexp.last_match(2)
@name = Expression.parse(name_markup) @name = Expression.parse(name_markup)
if filter_markup =~ FilterMarkupRegex if filter_markup =~ FilterMarkupRegex
filters = $1.scan(FilterParser) filters = Regexp.last_match(1).scan(FilterParser)
filters.each do |f| filters.each do |f|
next unless f =~ /\w+/ next unless f =~ /\w+/
filtername = Regexp.last_match(0) filtername = Regexp.last_match(0)
@@ -102,13 +104,21 @@ module Liquid
output output
end end
def disabled?(_context)
false
end
def disabled_tags
[]
end
private private
def parse_filter_expressions(filter_name, unparsed_args) def parse_filter_expressions(filter_name, unparsed_args)
filter_args = [] filter_args = []
keyword_args = nil keyword_args = nil
unparsed_args.each do |a| unparsed_args.each do |a|
if matches = a.match(JustTagAttributes) if (matches = a.match(JustTagAttributes))
keyword_args ||= {} keyword_args ||= {}
keyword_args[matches[1]] = Expression.parse(matches[2]) keyword_args[matches[1]] = Expression.parse(matches[2])
else else

View File

@@ -1,7 +1,9 @@
# frozen_string_literal: true
module Liquid module Liquid
class VariableLookup class VariableLookup
SQUARE_BRACKETED = /\A\[(.*)\]\z/m SQUARE_BRACKETED = /\A\[(.*)\]\z/m
COMMAND_METHODS = ['size'.freeze, 'first'.freeze, 'last'.freeze].freeze COMMAND_METHODS = ['size', 'first', 'last'].freeze
attr_reader :name, :lookups attr_reader :name, :lookups
@@ -14,7 +16,7 @@ module Liquid
name = lookups.shift name = lookups.shift
if name =~ SQUARE_BRACKETED if name =~ SQUARE_BRACKETED
name = Expression.parse($1) name = Expression.parse(Regexp.last_match(1))
end end
@name = name @name = name
@@ -24,7 +26,7 @@ module Liquid
@lookups.each_index do |i| @lookups.each_index do |i|
lookup = lookups[i] lookup = lookups[i]
if lookup =~ SQUARE_BRACKETED if lookup =~ SQUARE_BRACKETED
lookups[i] = Expression.parse($1) lookups[i] = Expression.parse(Regexp.last_match(1))
elsif COMMAND_METHODS.include?(lookup) elsif COMMAND_METHODS.include?(lookup)
@command_flags |= 1 << i @command_flags |= 1 << i
end end

View File

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

View File

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

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'benchmark/ips' require 'benchmark/ips'
require_relative 'theme_runner' require_relative 'theme_runner'

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'stackprof' require 'stackprof'
require_relative 'theme_runner' require_relative 'theme_runner'
@@ -13,7 +15,7 @@ profiler.run
end end
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| File.open(graph_filename, 'w') do |f|
StackProf::Report.new(results).print_graphviz(nil, f) StackProf::Report.new(results).print_graphviz(nil, f)
end end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
class CommentForm < Liquid::Block class CommentForm < Liquid::Block
Syntax = /(#{Liquid::VariableSignature}+)/ Syntax = /(#{Liquid::VariableSignature}+)/
@@ -5,10 +7,10 @@ class CommentForm < Liquid::Block
super super
if markup =~ Syntax if markup =~ Syntax
@variable_name = $1 @variable_name = Regexp.last_match(1)
@attributes = {} @attributes = {}
else 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
end end
@@ -21,7 +23,7 @@ class CommentForm < Liquid::Block
'errors' => context['comment.errors'], 'errors' => context['comment.errors'],
'author' => context['comment.author'], 'author' => context['comment.author'],
'email' => context['comment.email'], 'email' => context['comment.email'],
'body' => context['comment.body'] 'body' => context['comment.body'],
} }
output << wrap_in_form(article, render_all(@nodelist, context, output)) output << wrap_in_form(article, render_all(@nodelist, context, output))

View File

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

View File

@@ -1,7 +1,9 @@
# frozen_string_literal: true
require 'json' require 'json'
module JsonFilter module JsonFilter
def json(object) def json(object)
JSON.dump(object.reject { |k, v| k == "collections" }) JSON.dump(object.reject { |k, _v| k == "collections" })
end end
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 '../../lib/liquid'
require_relative 'comment_form' require_relative 'comment_form'
@@ -9,11 +11,11 @@ require_relative 'shop_filter'
require_relative 'tag_filter' require_relative 'tag_filter'
require_relative 'weight_filter' require_relative 'weight_filter'
Liquid::Template.register_tag 'paginate', Paginate Liquid::Template.register_tag('paginate', Paginate)
Liquid::Template.register_tag 'form', CommentForm Liquid::Template.register_tag('form', CommentForm)
Liquid::Template.register_filter JsonFilter Liquid::Template.register_filter(JsonFilter)
Liquid::Template.register_filter MoneyFilter Liquid::Template.register_filter(MoneyFilter)
Liquid::Template.register_filter WeightFilter Liquid::Template.register_filter(WeightFilter)
Liquid::Template.register_filter ShopFilter Liquid::Template.register_filter(ShopFilter)
Liquid::Template.register_filter TagFilter Liquid::Template.register_filter(TagFilter)

View File

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

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
class Paginate < Liquid::Block class Paginate < Liquid::Block
Syntax = /(#{Liquid::QuotedFragment})\s*(by\s*(\d+))?/ Syntax = /(#{Liquid::QuotedFragment})\s*(by\s*(\d+))?/
@@ -5,9 +7,9 @@ class Paginate < Liquid::Block
super super
if markup =~ Syntax if markup =~ Syntax
@collection_name = $1 @collection_name = Regexp.last_match(1)
@page_size = if $2 @page_size = if Regexp.last_match(2)
$3.to_i Regexp.last_match(3).to_i
else else
20 20
end end
@@ -17,7 +19,7 @@ class Paginate < Liquid::Block
@attributes[key] = value @attributes[key] = value
end end
else 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
end end
@@ -30,14 +32,14 @@ class Paginate < Liquid::Block
pagination = { pagination = {
'page_size' => @page_size, 'page_size' => @page_size,
'current_page' => 5, 'current_page' => 5,
'current_offset' => @page_size * 5 'current_offset' => @page_size * 5,
} }
context['paginate'] = pagination 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 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 module ShopFilter
def asset_url(input) def asset_url(input)
"/files/1/[shop_id]/[shop_id]/assets/#{input}" "/files/1/[shop_id]/[shop_id]/assets/#{input}"
@@ -52,7 +54,7 @@ module ShopFilter
end end
def product_img_url(url, style = 'small') 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' raise ArgumentError, 'filter "size" can only be called on product images'
end end
@@ -60,7 +62,7 @@ module ShopFilter
when 'original' when 'original'
return '/files/shops/random_number/' + url return '/files/shops/random_number/' + url
when 'grande', 'large', 'medium', 'compact', 'small', 'thumb', 'icon' 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 else
raise ArgumentError, 'valid parameters for filter "size" are: original, grande, large, medium, compact, small, thumb and icon ' raise ArgumentError, 'valid parameters for filter "size" are: original, grande, large, medium, compact, small, thumb and icon '
end end
@@ -70,16 +72,14 @@ module ShopFilter
html = [] html = []
html << %(<span class="prev">#{link_to(paginate['previous']['title'], paginate['previous']['url'])}</span>) if paginate['previous'] html << %(<span class="prev">#{link_to(paginate['previous']['title'], paginate['previous']['url'])}</span>) if paginate['previous']
for part in paginate['parts'] paginate['parts'].each do |part|
html << if part['is_link']
if part['is_link'] %(<span class="page">#{link_to(part['title'], part['url'])}</span>)
html << %(<span class="page">#{link_to(part['title'], part['url'])}</span>)
elsif part['title'].to_i == paginate['current_page'].to_i 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 else
html << %(<span class="deco">#{part['title']}</span>) %(<span class="deco">#{part['title']}</span>)
end end
end end
html << %(<span class="next">#{link_to(paginate['next']['title'], paginate['next']['url'])}</span>) if paginate['next'] 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 module TagFilter
def link_to_tag(label, tag) def link_to_tag(label, tag)
"<a title=\"Show tag #{tag}\" href=\"/collections/#{@context['handle']}/#{tag}\">#{label}</a>" "<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) def link_to_add_tag(label, tag)
tags = (@context['current_tags'] + [tag]).uniq 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
def link_to_remove_tag(label, tag) def link_to_remove_tag(label, tag)
tags = (@context['current_tags'] - [tag]).uniq 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
end end

View File

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

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
# This profiler run simulates Shopify. # This profiler run simulates Shopify.
# We are looking in the tests directory for liquid files and render them within the designated layout file. # 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. # 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), liquid: File.read(test),
layout: (File.file?(theme_path) ? File.read(theme_path) : nil), layout: (File.file?(theme_path) ? File.read(theme_path) : nil),
template_name: test template_name: test,
} }
end.compact end.compact

View File

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

View File

@@ -1,7 +1,9 @@
# frozen_string_literal: true
require 'test_helper' require 'test_helper'
class FoobarTag < Liquid::Tag class FoobarTag < Liquid::Tag
def render_to_output_buffer(context, output) def render_to_output_buffer(_context, output)
output << ' ' output << ' '
output output
end end

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'test_helper' require 'test_helper'
class ContextDrop < Liquid::Drop class ContextDrop < Liquid::Drop
@@ -31,7 +33,7 @@ class ProductDrop < Liquid::Drop
class CatchallDrop < Liquid::Drop class CatchallDrop < Liquid::Drop
def liquid_method_missing(method) def liquid_method_missing(method)
'catchall_method: ' << method.to_s "catchall_method: #{method}"
end end
end end
@@ -48,7 +50,7 @@ class ProductDrop < Liquid::Drop
end end
def user_input def user_input
"foo".taint (+"foo").taint
end end
protected protected
@@ -270,4 +272,11 @@ class DropsTest < Minitest::Test
assert_equal 'ProductDrop', Liquid::Template.parse("{{ product }}").render!('product' => ProductDrop.new) assert_equal 'ProductDrop', Liquid::Template.parse("{{ product }}").render!('product' => ProductDrop.new)
assert_equal 'EnumerableDrop', Liquid::Template.parse('{{ collection }}').render!('collection' => EnumerableDrop.new) assert_equal 'EnumerableDrop', Liquid::Template.parse('{{ collection }}').render!('collection' => EnumerableDrop.new)
end 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 end # DropsTest

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'test_helper' require 'test_helper'
class ErrorHandlingTest < Minitest::Test class ErrorHandlingTest < Minitest::Test
@@ -83,15 +85,14 @@ class ErrorHandlingTest < Minitest::Test
def test_with_line_numbers_adds_numbers_to_parser_errors def test_with_line_numbers_adds_numbers_to_parser_errors
err = assert_raises(SyntaxError) do err = assert_raises(SyntaxError) do
Liquid::Template.parse(%q( Liquid::Template.parse('
foobar foobar
{% "cat" | foobar %} {% "cat" | foobar %}
bla bla
), ',
line_numbers: true line_numbers: true)
)
end end
assert_match(/Liquid syntax error \(line 4\)/, err.message) 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 def test_with_line_numbers_adds_numbers_to_parser_errors_with_whitespace_trim
err = assert_raises(SyntaxError) do err = assert_raises(SyntaxError) do
Liquid::Template.parse(%q( Liquid::Template.parse('
foobar foobar
{%- "cat" | foobar -%} {%- "cat" | foobar -%}
bla bla
), ',
line_numbers: true line_numbers: true)
)
end end
assert_match(/Liquid syntax error \(line 4\)/, err.message) assert_match(/Liquid syntax error \(line 4\)/, err.message)
@@ -122,8 +122,7 @@ class ErrorHandlingTest < Minitest::Test
bla bla
', ',
error_mode: :warn, error_mode: :warn,
line_numbers: true line_numbers: true)
)
assert_equal ['Liquid syntax error (line 4): Unexpected character = in "1 =! 2"'], assert_equal ['Liquid syntax error (line 4): Unexpected character = in "1 =! 2"'],
template.warnings.map(&:message) template.warnings.map(&:message)
@@ -139,8 +138,7 @@ class ErrorHandlingTest < Minitest::Test
bla bla
', ',
error_mode: :strict, error_mode: :strict,
line_numbers: true line_numbers: true)
)
end 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
@@ -157,8 +155,7 @@ class ErrorHandlingTest < Minitest::Test
bla bla
', ',
line_numbers: true line_numbers: true)
)
end 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
@@ -205,7 +202,7 @@ class ErrorHandlingTest < Minitest::Test
def test_default_exception_renderer_with_internal_error def test_default_exception_renderer_with_internal_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)
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 'This is a runtime error: Liquid error (line 1): internal', output
assert_equal [Liquid::InternalError], template.errors.map(&:class) assert_equal [Liquid::InternalError], template.errors.map(&:class)
@@ -214,10 +211,13 @@ class ErrorHandlingTest < Minitest::Test
def test_setting_default_exception_renderer def test_setting_default_exception_renderer
old_exception_renderer = Liquid::Template.default_exception_renderer old_exception_renderer = Liquid::Template.default_exception_renderer
exceptions = [] 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 }}') 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 'This is a runtime error: ', output
assert_equal [Liquid::ArgumentError], template.errors.map(&:class) assert_equal [Liquid::ArgumentError], template.errors.map(&:class)
@@ -228,7 +228,10 @@ class ErrorHandlingTest < Minitest::Test
def test_exception_renderer_exposing_non_liquid_error 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 = [] exceptions = []
handler = ->(e) { exceptions << e; e.cause } handler = ->(e) {
exceptions << e
e.cause
}
output = template.render({ 'errors' => ErrorDrop.new }, exception_renderer: handler) output = template.render({ 'errors' => ErrorDrop.new }, exception_renderer: handler)
@@ -239,7 +242,7 @@ class ErrorHandlingTest < Minitest::Test
end end
class TestFileSystem class TestFileSystem
def read_template_file(template_path) def read_template_file(_template_path)
"{{ errors.argument_error }}" "{{ errors.argument_error }}"
end end
end end

View File

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

View File

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

View File

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

View File

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

View File

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

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' require 'test_helper'
class RenderProfilingTest < Minitest::Test class RenderProfilingTest < Minitest::Test
@@ -128,7 +130,7 @@ class RenderProfilingTest < Minitest::Test
t.render! t.render!
timing_count = 0 timing_count = 0
t.profiler.each do |timing| t.profiler.each do |_timing|
timing_count += 1 timing_count += 1
end end
@@ -145,7 +147,7 @@ class RenderProfilingTest < Minitest::Test
def test_profiling_marks_children_of_for_blocks def test_profiling_marks_children_of_for_blocks
t = Template.parse("{% for item in collection %} {{ item }} {% endfor %}", profile: true) 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 # Will profile each invocation of the for block

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'test_helper' require 'test_helper'
module SecurityFilter module SecurityFilter

View File

@@ -1,4 +1,5 @@
# encoding: utf-8 # encoding: utf-8
# frozen_string_literal: true
require 'test_helper' require 'test_helper'
@@ -17,7 +18,7 @@ class TestThing
"woot: #{@foo}" "woot: #{@foo}"
end end
def [](whatever) def [](_whatever)
to_s to_s
end end
@@ -197,6 +198,12 @@ class StandardFiltersTest < Minitest::Test
assert_equal [{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }], @filters.sort([{ "a" => 4 }, { "a" => 3 }, { "a" => 1 }, { "a" => 2 }], "a") assert_equal [{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }], @filters.sort([{ "a" => 4 }, { "a" => 3 }, { "a" => 1 }, { "a" => 2 }], "a")
end end
def test_sort_numeric
assert_equal ['1', '2', '3', '10'], @filters.sort_numeric(['10', '3', '2', '1'])
assert_equal [{ "a" => '1' }, { "a" => '2' }, { "a" => '3' }, { "a" => '10' }],
@filters.sort_numeric([{ "a" => '10' }, { "a" => '3' }, { "a" => '1' }, { "a" => '2' }], "a")
end
def test_sort_with_nils def test_sort_with_nils
assert_equal [1, 2, 3, 4, nil], @filters.sort([nil, 4, 3, 2, 1]) assert_equal [1, 2, 3, 4, nil], @filters.sort([nil, 4, 3, 2, 1])
assert_equal [{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }, {}], @filters.sort([{ "a" => 4 }, { "a" => 3 }, {}, { "a" => 1 }, { "a" => 2 }], "a") assert_equal [{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }, {}], @filters.sort([{ "a" => 4 }, { "a" => 3 }, {}, { "a" => 1 }, { "a" => 2 }], "a")
@@ -208,14 +215,14 @@ class StandardFiltersTest < Minitest::Test
{ "handle" => "beta" }, { "handle" => "beta" },
{ "price" => 1, "handle" => "gamma" }, { "price" => 1, "handle" => "gamma" },
{ "handle" => "delta" }, { "handle" => "delta" },
{ "price" => 2, "handle" => "epsilon" } { "price" => 2, "handle" => "epsilon" },
] ]
expectation = [ expectation = [
{ "price" => 1, "handle" => "gamma" }, { "price" => 1, "handle" => "gamma" },
{ "price" => 2, "handle" => "epsilon" }, { "price" => 2, "handle" => "epsilon" },
{ "price" => 4, "handle" => "alpha" }, { "price" => 4, "handle" => "alpha" },
{ "handle" => "delta" }, { "handle" => "delta" },
{ "handle" => "beta" } { "handle" => "beta" },
] ]
assert_equal expectation, @filters.sort(input, "price") assert_equal expectation, @filters.sort(input, "price")
end end
@@ -236,14 +243,14 @@ class StandardFiltersTest < Minitest::Test
{ "handle" => "beta" }, { "handle" => "beta" },
{ "price" => "1", "handle" => "gamma" }, { "price" => "1", "handle" => "gamma" },
{ "handle" => "delta" }, { "handle" => "delta" },
{ "price" => 2, "handle" => "epsilon" } { "price" => 2, "handle" => "epsilon" },
] ]
expectation = [ expectation = [
{ "price" => "1", "handle" => "gamma" }, { "price" => "1", "handle" => "gamma" },
{ "price" => 2, "handle" => "epsilon" }, { "price" => 2, "handle" => "epsilon" },
{ "price" => "4", "handle" => "alpha" }, { "price" => "4", "handle" => "alpha" },
{ "handle" => "delta" }, { "handle" => "delta" },
{ "handle" => "beta" } { "handle" => "beta" },
] ]
assert_equal expectation, @filters.sort_natural(input, "price") assert_equal expectation, @filters.sort_natural(input, "price")
end end
@@ -256,7 +263,7 @@ class StandardFiltersTest < Minitest::Test
{ "fake" => "t" }, { "fake" => "t" },
{ "key" => "a" }, { "key" => "a" },
{ "key" => "b" }, { "key" => "b" },
{ "key" => "c" } { "key" => "c" },
] ]
expectation = [ expectation = [
{ "key" => "a" }, { "key" => "a" },
@@ -265,7 +272,7 @@ class StandardFiltersTest < Minitest::Test
{ "key" => "X" }, { "key" => "X" },
{ "key" => "Y" }, { "key" => "Y" },
{ "key" => "Z" }, { "key" => "Z" },
{ "fake" => "t" } { "fake" => "t" },
] ]
assert_equal expectation, @filters.sort_natural(input, "key") 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 ["a", "b", "c", "X", "Y", "Z"], @filters.sort_natural(["X", "Y", "Z", "a", "b", "c"])
@@ -279,7 +286,7 @@ class StandardFiltersTest < Minitest::Test
foo = [ foo = [
[1], [1],
[2], [2],
[3] [3],
] ]
assert_raises Liquid::ArgumentError do assert_raises Liquid::ArgumentError do
@@ -291,11 +298,15 @@ class StandardFiltersTest < Minitest::Test
assert_equal [], @filters.sort_natural([], "a") assert_equal [], @filters.sort_natural([], "a")
end end
def test_sort_numeric_empty_array
assert_equal [], @filters.sort_numeric([], "a")
end
def test_sort_natural_invalid_property def test_sort_natural_invalid_property
foo = [ foo = [
[1], [1],
[2], [2],
[3] [3],
] ]
assert_raises Liquid::ArgumentError do assert_raises Liquid::ArgumentError do
@@ -304,7 +315,7 @@ class StandardFiltersTest < Minitest::Test
end end
def test_legacy_sort_hash 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 end
def test_numerical_vs_lexicographical_sort def test_numerical_vs_lexicographical_sort
@@ -330,7 +341,7 @@ class StandardFiltersTest < Minitest::Test
foo = [ foo = [
[1], [1],
[2], [2],
[3] [3],
] ]
assert_raises Liquid::ArgumentError do assert_raises Liquid::ArgumentError do
@@ -346,7 +357,7 @@ class StandardFiltersTest < Minitest::Test
foo = [ foo = [
[1], [1],
[2], [2],
[3] [3],
] ]
assert_raises Liquid::ArgumentError do assert_raises Liquid::ArgumentError do
@@ -423,7 +434,7 @@ class StandardFiltersTest < Minitest::Test
foo = [ foo = [
[1], [1],
[2], [2],
[3] [3],
] ]
assert_raises Liquid::ArgumentError do assert_raises Liquid::ArgumentError do
@@ -435,7 +446,7 @@ class StandardFiltersTest < Minitest::Test
foo = [ foo = [
[1], [1],
[2], [2],
[3] [3],
] ]
assert_raises Liquid::ArgumentError do assert_raises Liquid::ArgumentError do
@filters.map(foo, nil) @filters.map(foo, nil)
@@ -697,12 +708,12 @@ class StandardFiltersTest < Minitest::Test
{ "handle" => "alpha", "ok" => true }, { "handle" => "alpha", "ok" => true },
{ "handle" => "beta", "ok" => false }, { "handle" => "beta", "ok" => false },
{ "handle" => "gamma", "ok" => false }, { "handle" => "gamma", "ok" => false },
{ "handle" => "delta", "ok" => true } { "handle" => "delta", "ok" => true },
] ]
expectation = [ expectation = [
{ "handle" => "alpha", "ok" => true }, { "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", true)
@@ -714,12 +725,12 @@ class StandardFiltersTest < Minitest::Test
{ "handle" => "alpha", "ok" => true }, { "handle" => "alpha", "ok" => true },
{ "handle" => "beta" }, { "handle" => "beta" },
{ "handle" => "gamma" }, { "handle" => "gamma" },
{ "handle" => "delta", "ok" => true } { "handle" => "delta", "ok" => true },
] ]
expectation = [ expectation = [
{ "handle" => "alpha", "ok" => true }, { "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", true)
@@ -740,7 +751,7 @@ class StandardFiltersTest < Minitest::Test
input = [ input = [
{ "message" => "Bonjour!", "language" => "French" }, { "message" => "Bonjour!", "language" => "French" },
{ "message" => "Hello!", "language" => "English" }, { "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" => "Bonjour!", "language" => "French" }], @filters.where(input, "language", "French")
@@ -758,7 +769,7 @@ class StandardFiltersTest < Minitest::Test
{ "foo" => false }, { "foo" => false },
{ "foo" => true }, { "foo" => true },
{ "foo" => "for sure" }, { "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")

View File

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

View File

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

View File

@@ -1,10 +1,12 @@
# frozen_string_literal: true
require 'test_helper' require 'test_helper'
class EchoTest < Minitest::Test class EchoTest < Minitest::Test
include Liquid include Liquid
def test_echo_outputs_its_input 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 -%} {%- echo variable-name | upcase -%}
LIQUID LIQUID
end end

View File

@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'test_helper' require 'test_helper'
class ThingWithValue < Liquid::Drop class ThingWithValue < Liquid::Drop
@@ -23,7 +25,7 @@ class ForTagTest < Minitest::Test
yo yo
HERE HERE
template = <<HERE template = <<~HERE
{%for item in array%} {%for item in array%}
yo yo
{%endfor%} {%endfor%}

View File

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

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