mirror of
https://github.com/kemko/liquid.git
synced 2026-01-02 16:25:42 +03:00
Compare commits
1 Commits
enable-cla
...
stack-leve
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1707980a48 |
2
.github/probots.yml
vendored
2
.github/probots.yml
vendored
@@ -1,2 +0,0 @@
|
|||||||
enabled:
|
|
||||||
- cla
|
|
||||||
27
.rubocop.yml
27
.rubocop.yml
@@ -7,6 +7,8 @@ AllCops:
|
|||||||
|
|
||||||
Metrics/BlockNesting:
|
Metrics/BlockNesting:
|
||||||
Max: 3
|
Max: 3
|
||||||
|
Exclude:
|
||||||
|
- 'lib/liquid/block_body.rb'
|
||||||
|
|
||||||
Metrics/ModuleLength:
|
Metrics/ModuleLength:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
@@ -30,7 +32,7 @@ Lint/UnusedBlockArgument:
|
|||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Lint/EndAlignment:
|
Lint/EndAlignment:
|
||||||
EnforcedStyleAlignWith: variable
|
AlignWith: variable
|
||||||
|
|
||||||
Lint/UnusedMethodArgument:
|
Lint/UnusedMethodArgument:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
@@ -59,10 +61,10 @@ Style/BracesAroundHashParameters:
|
|||||||
Style/NumericLiterals:
|
Style/NumericLiterals:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Layout/SpaceInsideBrackets:
|
Style/SpaceInsideBrackets:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Layout/SpaceBeforeBlockBraces:
|
Style/SpaceBeforeBlockBraces:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Style/Documentation:
|
Style/Documentation:
|
||||||
@@ -71,22 +73,19 @@ Style/Documentation:
|
|||||||
Style/ClassAndModuleChildren:
|
Style/ClassAndModuleChildren:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Style/TrailingCommaInArrayLiteral:
|
Style/TrailingComma:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Style/TrailingCommaInHashLiteral:
|
Style/IndentHash:
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
Layout/IndentHash:
|
|
||||||
EnforcedStyle: consistent
|
EnforcedStyle: consistent
|
||||||
|
|
||||||
Style/FormatString:
|
Style/FormatString:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Layout/AlignParameters:
|
Style/AlignParameters:
|
||||||
EnforcedStyle: with_fixed_indentation
|
EnforcedStyle: with_fixed_indentation
|
||||||
|
|
||||||
Layout/MultilineOperationIndentation:
|
Style/MultilineOperationIndentation:
|
||||||
EnforcedStyle: indented
|
EnforcedStyle: indented
|
||||||
|
|
||||||
Style/IfUnlessModifier:
|
Style/IfUnlessModifier:
|
||||||
@@ -95,7 +94,7 @@ Style/IfUnlessModifier:
|
|||||||
Style/RaiseArgs:
|
Style/RaiseArgs:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Style/PreferredHashMethods:
|
Style/DeprecatedHashMethods:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Style/RegexpLiteral:
|
Style/RegexpLiteral:
|
||||||
@@ -110,7 +109,7 @@ Performance/Count:
|
|||||||
Style/ConstantName:
|
Style/ConstantName:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Layout/CaseIndentation:
|
Style/CaseIndentation:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Style/ClassVars:
|
Style/ClassVars:
|
||||||
@@ -124,7 +123,3 @@ Style/TrivialAccessors:
|
|||||||
|
|
||||||
Style/WordArray:
|
Style/WordArray:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Naming/MethodName:
|
|
||||||
Exclude:
|
|
||||||
- 'example/server/liquid_servlet.rb'
|
|
||||||
|
|||||||
@@ -1,248 +1,72 @@
|
|||||||
# This configuration was generated by
|
# This configuration was generated by `rubocop --auto-gen-config`
|
||||||
# `rubocop --auto-gen-config`
|
# on 2015-06-08 18:16:16 +0000 using RuboCop version 0.32.0.
|
||||||
# on 2017-11-22 11:35:55 -0500 using RuboCop version 0.49.1.
|
|
||||||
# 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: 3
|
|
||||||
# Cop supports --auto-correct.
|
|
||||||
Layout/ClosingParenthesisIndentation:
|
|
||||||
Exclude:
|
|
||||||
- 'test/integration/error_handling_test.rb'
|
|
||||||
|
|
||||||
# Offense count: 1
|
|
||||||
# Cop supports --auto-correct.
|
|
||||||
Layout/EmptyLineAfterMagicComment:
|
|
||||||
Exclude:
|
|
||||||
- 'lib/liquid/version.rb'
|
|
||||||
|
|
||||||
# Offense count: 1
|
|
||||||
# Cop supports --auto-correct.
|
|
||||||
# Configuration parameters: AllowForAlignment, ForceEqualSignAlignment.
|
|
||||||
Layout/ExtraSpacing:
|
|
||||||
Exclude:
|
|
||||||
- 'test/integration/parsing_quirks_test.rb'
|
|
||||||
|
|
||||||
# Offense count: 5
|
# Offense count: 5
|
||||||
# Cop supports --auto-correct.
|
Lint/NestedMethodDefinition:
|
||||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
Enabled: false
|
||||||
# SupportedStyles: auto_detection, squiggly, active_support, powerpack, unindent
|
|
||||||
Layout/IndentHeredoc:
|
|
||||||
Exclude:
|
|
||||||
- 'test/integration/tags/for_tag_test.rb'
|
|
||||||
- 'test/integration/trim_mode_test.rb'
|
|
||||||
|
|
||||||
# Offense count: 6
|
# Offense count: 53
|
||||||
# Cop supports --auto-correct.
|
|
||||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
|
||||||
# SupportedStyles: symmetrical, new_line, same_line
|
|
||||||
Layout/MultilineMethodCallBraceLayout:
|
|
||||||
Exclude:
|
|
||||||
- 'test/integration/error_handling_test.rb'
|
|
||||||
- 'test/unit/strainer_unit_test.rb'
|
|
||||||
|
|
||||||
# Offense count: 2
|
|
||||||
# Cop supports --auto-correct.
|
|
||||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
|
||||||
# SupportedStyles: runtime_error, standard_error
|
|
||||||
Lint/InheritException:
|
|
||||||
Exclude:
|
|
||||||
- 'lib/liquid/interrupts.rb'
|
|
||||||
|
|
||||||
# Offense count: 1
|
|
||||||
Lint/ScriptPermission:
|
|
||||||
Exclude:
|
|
||||||
- 'test/test_helper.rb'
|
|
||||||
|
|
||||||
# Offense count: 52
|
|
||||||
Metrics/AbcSize:
|
Metrics/AbcSize:
|
||||||
Max: 56
|
Max: 58
|
||||||
|
|
||||||
# Offense count: 13
|
# Offense count: 12
|
||||||
Metrics/CyclomaticComplexity:
|
Metrics/CyclomaticComplexity:
|
||||||
Max: 12
|
Max: 15
|
||||||
|
|
||||||
# Offense count: 620
|
# Offense count: 553
|
||||||
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
|
# Configuration parameters: AllowURI, URISchemes.
|
||||||
# URISchemes: http, https
|
|
||||||
Metrics/LineLength:
|
Metrics/LineLength:
|
||||||
Max: 294
|
Max: 294
|
||||||
|
|
||||||
# Offense count: 102
|
# Offense count: 77
|
||||||
# Configuration parameters: CountComments.
|
# Configuration parameters: CountComments.
|
||||||
Metrics/MethodLength:
|
Metrics/MethodLength:
|
||||||
Max: 37
|
Max: 46
|
||||||
|
|
||||||
# Offense count: 9
|
|
||||||
Metrics/PerceivedComplexity:
|
|
||||||
Max: 11
|
|
||||||
|
|
||||||
# Offense count: 10
|
|
||||||
# Cop supports --auto-correct.
|
|
||||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
|
||||||
# SupportedStyles: prefer_alias, prefer_alias_method
|
|
||||||
Style/Alias:
|
|
||||||
Exclude:
|
|
||||||
- 'lib/liquid/drop.rb'
|
|
||||||
- 'lib/liquid/i18n.rb'
|
|
||||||
- 'lib/liquid/profiler/hooks.rb'
|
|
||||||
- 'lib/liquid/standardfilters.rb'
|
|
||||||
- 'lib/liquid/tag.rb'
|
|
||||||
- 'lib/liquid/tags/include.rb'
|
|
||||||
- 'lib/liquid/variable.rb'
|
|
||||||
|
|
||||||
# Offense count: 1
|
|
||||||
# Cop supports --auto-correct.
|
|
||||||
# Configuration parameters: EnforcedStyle, SupportedStyles, SingleLineConditionsOnly, IncludeTernaryExpressions.
|
|
||||||
# SupportedStyles: assign_to_condition, assign_inside_condition
|
|
||||||
Style/ConditionalAssignment:
|
|
||||||
Exclude:
|
|
||||||
- 'lib/liquid/errors.rb'
|
|
||||||
|
|
||||||
# Offense count: 2
|
|
||||||
# Cop supports --auto-correct.
|
|
||||||
Style/EmptyCaseCondition:
|
|
||||||
Exclude:
|
|
||||||
- 'lib/liquid/block_body.rb'
|
|
||||||
- 'lib/liquid/lexer.rb'
|
|
||||||
|
|
||||||
# Offense count: 5
|
|
||||||
# Cop supports --auto-correct.
|
|
||||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
|
||||||
# SupportedStyles: compact, expanded
|
|
||||||
Style/EmptyMethod:
|
|
||||||
Exclude:
|
|
||||||
- 'lib/liquid/tag.rb'
|
|
||||||
- 'lib/liquid/tags/comment.rb'
|
|
||||||
- 'lib/liquid/tags/include.rb'
|
|
||||||
- 'test/integration/tags/include_tag_test.rb'
|
|
||||||
- 'test/unit/context_unit_test.rb'
|
|
||||||
|
|
||||||
# Offense count: 2
|
|
||||||
# Configuration parameters: SupportedStyles.
|
|
||||||
# SupportedStyles: annotated, template
|
|
||||||
Style/FormatStringToken:
|
|
||||||
EnforcedStyle: template
|
|
||||||
|
|
||||||
# Offense count: 14
|
|
||||||
# Configuration parameters: MinBodyLength.
|
|
||||||
Style/GuardClause:
|
|
||||||
Exclude:
|
|
||||||
- 'lib/liquid/condition.rb'
|
|
||||||
- 'lib/liquid/lexer.rb'
|
|
||||||
- 'lib/liquid/strainer.rb'
|
|
||||||
- 'lib/liquid/tags/assign.rb'
|
|
||||||
- 'lib/liquid/tags/capture.rb'
|
|
||||||
- 'lib/liquid/tags/case.rb'
|
|
||||||
- 'lib/liquid/tags/for.rb'
|
|
||||||
- 'lib/liquid/tags/include.rb'
|
|
||||||
- 'lib/liquid/tags/raw.rb'
|
|
||||||
- 'lib/liquid/tags/table_row.rb'
|
|
||||||
- 'lib/liquid/variable.rb'
|
|
||||||
- 'test/unit/tokenizer_unit_test.rb'
|
|
||||||
|
|
||||||
# Offense count: 4
|
|
||||||
# Configuration parameters: SupportedStyles.
|
|
||||||
# SupportedStyles: snake_case, camelCase
|
|
||||||
Style/MethodName:
|
|
||||||
EnforcedStyle: snake_case
|
|
||||||
|
|
||||||
# Offense count: 6
|
# Offense count: 6
|
||||||
# Cop supports --auto-correct.
|
Metrics/PerceivedComplexity:
|
||||||
Style/MutableConstant:
|
Max: 13
|
||||||
Exclude:
|
|
||||||
- 'lib/liquid/expression.rb'
|
# Offense count: 1
|
||||||
- 'lib/liquid/lexer.rb'
|
Style/AccessorMethodName:
|
||||||
- 'lib/liquid/standardfilters.rb'
|
Enabled: false
|
||||||
- 'lib/liquid/tags/if.rb'
|
|
||||||
- 'lib/liquid/variable_lookup.rb'
|
|
||||||
- 'lib/liquid/version.rb'
|
|
||||||
|
|
||||||
# Offense count: 1
|
# Offense count: 1
|
||||||
# Cop supports --auto-correct.
|
# Cop supports --auto-correct.
|
||||||
# Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles.
|
Style/ClosingParenthesisIndentation:
|
||||||
# SupportedStyles: skip_modifier_ifs, always
|
Enabled: false
|
||||||
Style/Next:
|
|
||||||
Exclude:
|
# Offense count: 3
|
||||||
- 'lib/liquid/tags/for.rb'
|
# Configuration parameters: MinBodyLength.
|
||||||
|
Style/GuardClause:
|
||||||
|
Enabled: false
|
||||||
|
|
||||||
# Offense count: 4
|
# Offense count: 4
|
||||||
# Cop supports --auto-correct.
|
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||||
# Configuration parameters: AutoCorrect, EnforcedStyle, SupportedStyles.
|
Style/MethodName:
|
||||||
# SupportedStyles: predicate, comparison
|
Enabled: false
|
||||||
Style/NumericPredicate:
|
|
||||||
Exclude:
|
|
||||||
- 'spec/**/*'
|
|
||||||
- 'lib/liquid/context.rb'
|
|
||||||
- 'lib/liquid/forloop_drop.rb'
|
|
||||||
- 'lib/liquid/standardfilters.rb'
|
|
||||||
- 'lib/liquid/tablerowloop_drop.rb'
|
|
||||||
|
|
||||||
# Offense count: 14
|
|
||||||
# Cop supports --auto-correct.
|
|
||||||
# Configuration parameters: PreferredDelimiters.
|
|
||||||
Style/PercentLiteralDelimiters:
|
|
||||||
Exclude:
|
|
||||||
- 'lib/liquid/tags/if.rb'
|
|
||||||
- 'liquid.gemspec'
|
|
||||||
- 'test/integration/assign_test.rb'
|
|
||||||
- 'test/integration/standard_filter_test.rb'
|
|
||||||
|
|
||||||
# Offense count: 2
|
|
||||||
# Cop supports --auto-correct.
|
|
||||||
Style/RedundantParentheses:
|
|
||||||
Exclude:
|
|
||||||
- 'test/unit/condition_unit_test.rb'
|
|
||||||
|
|
||||||
# Offense count: 1
|
# Offense count: 1
|
||||||
# Cop supports --auto-correct.
|
Style/MultilineBlockChain:
|
||||||
Style/RedundantSelf:
|
Enabled: false
|
||||||
Exclude:
|
|
||||||
- 'lib/liquid/strainer.rb'
|
|
||||||
|
|
||||||
# Offense count: 9
|
# Offense count: 2
|
||||||
# Cop supports --auto-correct.
|
# Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles.
|
||||||
# Configuration parameters: AllowAsExpressionSeparator.
|
Style/Next:
|
||||||
Style/Semicolon:
|
Enabled: false
|
||||||
Exclude:
|
|
||||||
- 'test/integration/error_handling_test.rb'
|
|
||||||
- 'test/integration/template_test.rb'
|
|
||||||
- 'test/unit/context_unit_test.rb'
|
|
||||||
|
|
||||||
# Offense count: 7
|
# Offense count: 7
|
||||||
# Cop supports --auto-correct.
|
# Cop supports --auto-correct.
|
||||||
# Configuration parameters: MinSize, SupportedStyles.
|
# Configuration parameters: AllowAsExpressionSeparator.
|
||||||
# SupportedStyles: percent, brackets
|
Style/Semicolon:
|
||||||
Style/SymbolArray:
|
Enabled: false
|
||||||
EnforcedStyle: brackets
|
|
||||||
|
|
||||||
# Offense count: 2
|
|
||||||
# Cop supports --auto-correct.
|
|
||||||
# Configuration parameters: EnforcedStyle, SupportedStyles, AllowSafeAssignment.
|
|
||||||
# SupportedStyles: require_parentheses, require_no_parentheses, require_parentheses_when_complex
|
|
||||||
Style/TernaryParentheses:
|
|
||||||
Exclude:
|
|
||||||
- 'lib/liquid/context.rb'
|
|
||||||
- 'lib/liquid/utils.rb'
|
|
||||||
|
|
||||||
# Offense count: 4
|
|
||||||
# Cop supports --auto-correct.
|
|
||||||
Style/UnneededInterpolation:
|
|
||||||
Exclude:
|
|
||||||
- 'lib/liquid/i18n.rb'
|
|
||||||
- 'test/integration/standard_filter_test.rb'
|
|
||||||
|
|
||||||
# Offense count: 2
|
|
||||||
# Cop supports --auto-correct.
|
|
||||||
Style/UnneededPercentQ:
|
|
||||||
Exclude:
|
|
||||||
- 'test/integration/error_handling_test.rb'
|
|
||||||
|
|
||||||
# Offense count: 1
|
# Offense count: 1
|
||||||
# Cop supports --auto-correct.
|
# Cop supports --auto-correct.
|
||||||
# Configuration parameters: MaxLineLength.
|
# Configuration parameters: MaxLineLength.
|
||||||
Style/WhileUntilModifier:
|
Style/WhileUntilModifier:
|
||||||
Exclude:
|
Enabled: false
|
||||||
- 'lib/liquid/tags/case.rb'
|
|
||||||
|
|||||||
@@ -3,9 +3,7 @@ language: ruby
|
|||||||
rvm:
|
rvm:
|
||||||
- 2.1
|
- 2.1
|
||||||
- 2.2
|
- 2.2
|
||||||
- 2.3
|
- 2.3.3
|
||||||
- 2.4
|
|
||||||
- 2.5
|
|
||||||
- ruby-head
|
- ruby-head
|
||||||
- jruby-head
|
- jruby-head
|
||||||
# - rbx-2
|
# - rbx-2
|
||||||
@@ -19,14 +17,13 @@ addons:
|
|||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
allow_failures:
|
allow_failures:
|
||||||
- rvm: ruby-head
|
|
||||||
- rvm: jruby-head
|
- rvm: jruby-head
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- gem install rainbow -v 2.2.1
|
- gem install rainbow -v 2.2.1
|
||||||
- bundle install
|
- bundle install
|
||||||
|
|
||||||
script: bundle exec rake
|
script: "bundle exec rake"
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
disable: true
|
disable: true
|
||||||
|
|||||||
11
Gemfile
11
Gemfile
@@ -1,20 +1,17 @@
|
|||||||
source 'https://rubygems.org'
|
source 'https://rubygems.org'
|
||||||
git_source(:github) do |repo_name|
|
|
||||||
"https://github.com/#{repo_name}.git"
|
|
||||||
end
|
|
||||||
|
|
||||||
gemspec
|
gemspec
|
||||||
|
gem 'stackprof', platforms: :mri_21
|
||||||
gem 'stackprof', platforms: :mri
|
|
||||||
|
|
||||||
group :benchmark, :test do
|
group :benchmark, :test do
|
||||||
gem 'benchmark-ips'
|
gem 'benchmark-ips'
|
||||||
end
|
end
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
gem 'rubocop', '~> 0.49.0'
|
gem 'spy', '0.4.1'
|
||||||
|
gem 'rubocop', '0.34.2'
|
||||||
|
|
||||||
platform :mri do
|
platform :mri do
|
||||||
gem 'liquid-c', github: 'Shopify/liquid-c', ref: '9168659de45d6d576fce30c735f857e597fa26f6'
|
gem 'liquid-c', github: 'Shopify/liquid-c', ref: 'bd53db95de3d44d631e7c5a267c3d934e66107dd'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -42,8 +42,6 @@ Liquid is a template engine which was written with very specific requirements:
|
|||||||
|
|
||||||
## How to use Liquid
|
## How to use Liquid
|
||||||
|
|
||||||
Install Liquid by adding `gem 'liquid'` to your gemfile.
|
|
||||||
|
|
||||||
Liquid supports a very simple API based around the Liquid::Template class.
|
Liquid supports a very simple API based around the Liquid::Template class.
|
||||||
For standard use you can just pass it the content of a file and call render with a parameters hash.
|
For standard use you can just pass it the content of a file and call render with a parameters hash.
|
||||||
|
|
||||||
|
|||||||
3
circle.yml
Normal file
3
circle.yml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
machine:
|
||||||
|
ruby:
|
||||||
|
version: ruby-2.1
|
||||||
@@ -45,7 +45,6 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
require "liquid/version"
|
require "liquid/version"
|
||||||
require 'liquid/parse_tree_visitor'
|
|
||||||
require 'liquid/lexer'
|
require 'liquid/lexer'
|
||||||
require 'liquid/parser'
|
require 'liquid/parser'
|
||||||
require 'liquid/i18n'
|
require 'liquid/i18n'
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
class Block < Tag
|
class Block < Tag
|
||||||
MAX_DEPTH = 100
|
|
||||||
|
|
||||||
def initialize(tag_name, markup, options)
|
def initialize(tag_name, markup, options)
|
||||||
super
|
super
|
||||||
@blank = true
|
@blank = true
|
||||||
@@ -26,12 +24,12 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def unknown_tag(tag, _params, _tokens)
|
def unknown_tag(tag, _params, _tokens)
|
||||||
if tag == 'else'.freeze
|
case tag
|
||||||
|
when 'else'.freeze
|
||||||
raise SyntaxError.new(parse_context.locale.t("errors.syntax.unexpected_else".freeze,
|
raise SyntaxError.new(parse_context.locale.t("errors.syntax.unexpected_else".freeze,
|
||||||
block_name: block_name))
|
block_name: block_name))
|
||||||
elsif tag.start_with?('end'.freeze)
|
when 'end'.freeze
|
||||||
raise SyntaxError.new(parse_context.locale.t("errors.syntax.invalid_delimiter".freeze,
|
raise SyntaxError.new(parse_context.locale.t("errors.syntax.invalid_delimiter".freeze,
|
||||||
tag: tag,
|
|
||||||
block_name: block_name,
|
block_name: block_name,
|
||||||
block_delimiter: block_delimiter))
|
block_delimiter: block_delimiter))
|
||||||
else
|
else
|
||||||
@@ -50,25 +48,17 @@ module Liquid
|
|||||||
protected
|
protected
|
||||||
|
|
||||||
def parse_body(body, tokens)
|
def parse_body(body, tokens)
|
||||||
if parse_context.depth >= MAX_DEPTH
|
body.parse(tokens, parse_context) do |end_tag_name, end_tag_params|
|
||||||
raise StackLevelError, "Nesting too deep".freeze
|
@blank &&= body.blank?
|
||||||
end
|
|
||||||
parse_context.depth += 1
|
|
||||||
begin
|
|
||||||
body.parse(tokens, parse_context) do |end_tag_name, end_tag_params|
|
|
||||||
@blank &&= body.blank?
|
|
||||||
|
|
||||||
return false if end_tag_name == block_delimiter
|
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.new(parse_context.locale.t("errors.syntax.tag_never_closed".freeze, block_name: block_name))
|
||||||
end
|
|
||||||
|
|
||||||
# this tag is not registered with the system
|
|
||||||
# pass it to the current block for special handling or error reporting
|
|
||||||
unknown_tag(end_tag_name, end_tag_params, tokens)
|
|
||||||
end
|
end
|
||||||
ensure
|
|
||||||
parse_context.depth -= 1
|
# this tag is not registered with the system
|
||||||
|
# pass it to the current block for special handling or error reporting
|
||||||
|
unknown_tag(end_tag_name, end_tag_params, tokens)
|
||||||
end
|
end
|
||||||
|
|
||||||
true
|
true
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ module Liquid
|
|||||||
class BlockBody
|
class BlockBody
|
||||||
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/
|
|
||||||
TAGSTART = "{%".freeze
|
TAGSTART = "{%".freeze
|
||||||
VARSTART = "{{".freeze
|
VARSTART = "{{".freeze
|
||||||
|
|
||||||
@@ -16,35 +15,38 @@ module Liquid
|
|||||||
def parse(tokenizer, parse_context)
|
def parse(tokenizer, parse_context)
|
||||||
parse_context.line_number = tokenizer.line_number
|
parse_context.line_number = tokenizer.line_number
|
||||||
while token = tokenizer.shift
|
while token = tokenizer.shift
|
||||||
next if token.empty?
|
unless token.empty?
|
||||||
case
|
case
|
||||||
when token.start_with?(TAGSTART)
|
when token.start_with?(TAGSTART)
|
||||||
whitespace_handler(token, parse_context)
|
whitespace_handler(token, parse_context)
|
||||||
unless token =~ FullToken
|
if token =~ FullToken
|
||||||
raise_missing_tag_terminator(token, parse_context)
|
tag_name = $1
|
||||||
|
markup = $2
|
||||||
|
# fetch the tag from registered blocks
|
||||||
|
if tag = registered_tags[tag_name]
|
||||||
|
new_tag = tag.parse(tag_name, markup, tokenizer, parse_context)
|
||||||
|
@blank &&= new_tag.blank?
|
||||||
|
@nodelist << new_tag
|
||||||
|
else
|
||||||
|
# end parsing if we reach an unknown tag and let the caller decide
|
||||||
|
# determine how to proceed
|
||||||
|
return yield tag_name, markup
|
||||||
|
end
|
||||||
|
else
|
||||||
|
raise_missing_tag_terminator(token, parse_context)
|
||||||
|
end
|
||||||
|
when token.start_with?(VARSTART)
|
||||||
|
whitespace_handler(token, parse_context)
|
||||||
|
@nodelist << create_variable(token, parse_context)
|
||||||
|
@blank = false
|
||||||
|
else
|
||||||
|
if parse_context.trim_whitespace
|
||||||
|
token.lstrip!
|
||||||
|
end
|
||||||
|
parse_context.trim_whitespace = false
|
||||||
|
@nodelist << token
|
||||||
|
@blank &&= !!(token =~ /\A\s*\z/)
|
||||||
end
|
end
|
||||||
tag_name = $1
|
|
||||||
markup = $2
|
|
||||||
# fetch the tag from registered blocks
|
|
||||||
unless tag = registered_tags[tag_name]
|
|
||||||
# end parsing if we reach an unknown tag and let the caller decide
|
|
||||||
# determine how to proceed
|
|
||||||
return yield tag_name, markup
|
|
||||||
end
|
|
||||||
new_tag = tag.parse(tag_name, markup, tokenizer, parse_context)
|
|
||||||
@blank &&= new_tag.blank?
|
|
||||||
@nodelist << new_tag
|
|
||||||
when token.start_with?(VARSTART)
|
|
||||||
whitespace_handler(token, parse_context)
|
|
||||||
@nodelist << create_variable(token, parse_context)
|
|
||||||
@blank = false
|
|
||||||
else
|
|
||||||
if parse_context.trim_whitespace
|
|
||||||
token.lstrip!
|
|
||||||
end
|
|
||||||
parse_context.trim_whitespace = false
|
|
||||||
@nodelist << token
|
|
||||||
@blank &&= !!(token =~ WhitespaceOrNothing)
|
|
||||||
end
|
end
|
||||||
parse_context.line_number = tokenizer.line_number
|
parse_context.line_number = tokenizer.line_number
|
||||||
end
|
end
|
||||||
@@ -70,27 +72,33 @@ module Liquid
|
|||||||
output = []
|
output = []
|
||||||
context.resource_limits.render_score += @nodelist.length
|
context.resource_limits.render_score += @nodelist.length
|
||||||
|
|
||||||
idx = 0
|
@nodelist.each do |token|
|
||||||
while node = @nodelist[idx]
|
# Break out if we have any unhanded interrupts.
|
||||||
case node
|
break if context.interrupt?
|
||||||
when String
|
|
||||||
check_resources(context, node)
|
begin
|
||||||
output << node
|
|
||||||
when Variable
|
|
||||||
render_node_to_output(node, output, context)
|
|
||||||
when Block
|
|
||||||
render_node_to_output(node, output, context, node.blank?)
|
|
||||||
break if context.interrupt? # might have happened in a for-block
|
|
||||||
when Continue, Break
|
|
||||||
# If we get an Interrupt that means the block must stop processing. An
|
# If we get an Interrupt that means the block must stop processing. An
|
||||||
# Interrupt is any command that stops block execution such as {% break %}
|
# Interrupt is any command that stops block execution such as {% break %}
|
||||||
# or {% continue %}
|
# or {% continue %}
|
||||||
context.push_interrupt(node.interrupt)
|
if token.is_a?(Continue) || token.is_a?(Break)
|
||||||
break
|
context.push_interrupt(token.interrupt)
|
||||||
else # Other non-Block tags
|
break
|
||||||
render_node_to_output(node, output, context)
|
end
|
||||||
|
|
||||||
|
node_output = render_node(token, context)
|
||||||
|
|
||||||
|
unless token.is_a?(Block) && token.blank?
|
||||||
|
output << node_output
|
||||||
|
end
|
||||||
|
rescue MemoryError => e
|
||||||
|
raise e
|
||||||
|
rescue UndefinedVariable, UndefinedDropMethod, UndefinedFilter => e
|
||||||
|
context.handle_error(e, token.line_number)
|
||||||
|
output << nil
|
||||||
|
rescue ::StandardError => e
|
||||||
|
line_number = token.is_a?(String) ? nil : token.line_number
|
||||||
|
output << context.handle_error(e, line_number)
|
||||||
end
|
end
|
||||||
idx += 1
|
|
||||||
end
|
end
|
||||||
|
|
||||||
output.join
|
output.join
|
||||||
@@ -98,25 +106,15 @@ module Liquid
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def render_node_to_output(node, output, context, skip_output = false)
|
def render_node(node, context)
|
||||||
node_output = node.render(context)
|
node_output = node.is_a?(String) ? node : node.render(context)
|
||||||
node_output = node_output.is_a?(Array) ? node_output.join : node_output.to_s
|
node_output = node_output.is_a?(Array) ? node_output.join : node_output.to_s
|
||||||
check_resources(context, node_output)
|
|
||||||
output << node_output unless skip_output
|
|
||||||
rescue MemoryError => e
|
|
||||||
raise e
|
|
||||||
rescue UndefinedVariable, UndefinedDropMethod, UndefinedFilter => e
|
|
||||||
context.handle_error(e, node.line_number)
|
|
||||||
output << nil
|
|
||||||
rescue ::StandardError => e
|
|
||||||
line_number = node.is_a?(String) ? nil : node.line_number
|
|
||||||
output << context.handle_error(e, line_number)
|
|
||||||
end
|
|
||||||
|
|
||||||
def check_resources(context, node_output)
|
|
||||||
context.resource_limits.render_length += node_output.length
|
context.resource_limits.render_length += node_output.length
|
||||||
return unless context.resource_limits.reached?
|
if context.resource_limits.reached?
|
||||||
raise MemoryError.new("Memory limits exceeded".freeze)
|
raise MemoryError.new("Memory limits exceeded".freeze)
|
||||||
|
end
|
||||||
|
node_output
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_variable(token, parse_context)
|
def create_variable(token, parse_context)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ module Liquid
|
|||||||
# c.evaluate #=> true
|
# c.evaluate #=> true
|
||||||
#
|
#
|
||||||
class Condition #:nodoc:
|
class Condition #:nodoc:
|
||||||
|
@@depth = 0
|
||||||
@@operators = {
|
@@operators = {
|
||||||
'=='.freeze => ->(cond, left, right) { cond.send(:equal_variables, left, right) },
|
'=='.freeze => ->(cond, left, right) { cond.send(:equal_variables, left, right) },
|
||||||
'!='.freeze => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },
|
'!='.freeze => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },
|
||||||
@@ -29,7 +30,7 @@ module Liquid
|
|||||||
@@operators
|
@@operators
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_reader :attachment, :child_condition
|
attr_reader :attachment
|
||||||
attr_accessor :left, :operator, :right
|
attr_accessor :left, :operator, :right
|
||||||
|
|
||||||
def initialize(left = nil, operator = nil, right = nil)
|
def initialize(left = nil, operator = nil, right = nil)
|
||||||
@@ -41,22 +42,21 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def evaluate(context = Context.new)
|
def evaluate(context = Context.new)
|
||||||
condition = self
|
result = interpret_condition(left, right, operator, context)
|
||||||
result = nil
|
|
||||||
loop do
|
|
||||||
result = interpret_condition(condition.left, condition.right, condition.operator, context)
|
|
||||||
|
|
||||||
case condition.child_relation
|
case @child_relation
|
||||||
when :or
|
when :or
|
||||||
break if result
|
result || @child_condition.evaluate(context)
|
||||||
when :and
|
when :and
|
||||||
break unless result
|
@@depth += 1
|
||||||
else
|
if @@depth >= 500
|
||||||
break
|
@@depth = 0
|
||||||
|
raise StackLevelError, "Nesting too deep".freeze
|
||||||
end
|
end
|
||||||
condition = condition.child_condition
|
result && @child_condition.evaluate(context)
|
||||||
|
else
|
||||||
|
result
|
||||||
end
|
end
|
||||||
result
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def or(condition)
|
def or(condition)
|
||||||
@@ -81,10 +81,6 @@ module Liquid
|
|||||||
"#<Condition #{[@left, @operator, @right].compact.join(' '.freeze)}>"
|
"#<Condition #{[@left, @operator, @right].compact.join(' '.freeze)}>"
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
attr_reader :child_relation
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def equal_variables(left, right)
|
def equal_variables(left, right)
|
||||||
@@ -128,15 +124,6 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
|
||||||
def children
|
|
||||||
[
|
|
||||||
@node.left, @node.right,
|
|
||||||
@node.child_condition, @node.attachment
|
|
||||||
].compact
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
class ElseCondition < Condition
|
class ElseCondition < Condition
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ module Liquid
|
|||||||
# Push new local scope on the stack. use <tt>Context#stack</tt> instead
|
# Push new local scope on the stack. use <tt>Context#stack</tt> instead
|
||||||
def push(new_scope = {})
|
def push(new_scope = {})
|
||||||
@scopes.unshift(new_scope)
|
@scopes.unshift(new_scope)
|
||||||
raise StackLevelError, "Nesting too deep".freeze if @scopes.length > Block::MAX_DEPTH
|
raise StackLevelError, "Nesting too deep".freeze if @scopes.length > 100
|
||||||
end
|
end
|
||||||
|
|
||||||
# Merge a hash of variables in the current local scope
|
# Merge a hash of variables in the current local scope
|
||||||
@@ -171,9 +171,7 @@ module Liquid
|
|||||||
if scope.nil?
|
if scope.nil?
|
||||||
@environments.each do |e|
|
@environments.each do |e|
|
||||||
variable = lookup_and_evaluate(e, key, raise_on_not_found: raise_on_not_found)
|
variable = lookup_and_evaluate(e, key, raise_on_not_found: raise_on_not_found)
|
||||||
# When lookup returned a value OR there is no value but the lookup also did not raise
|
unless variable.nil?
|
||||||
# then it is the value we are looking for.
|
|
||||||
if !variable.nil? || @strict_variables && raise_on_not_found
|
|
||||||
scope = e
|
scope = e
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -21,24 +21,20 @@ module Liquid
|
|||||||
'empty'.freeze => MethodLiteral.new(:empty?, '').freeze
|
'empty'.freeze => MethodLiteral.new(:empty?, '').freeze
|
||||||
}
|
}
|
||||||
|
|
||||||
SINGLE_QUOTED_STRING = /\A'(.*)'\z/m
|
|
||||||
DOUBLE_QUOTED_STRING = /\A"(.*)"\z/m
|
|
||||||
INTEGERS_REGEX = /\A(-?\d+)\z/
|
|
||||||
FLOATS_REGEX = /\A(-?\d[\d\.]+)\z/
|
|
||||||
RANGES_REGEX = /\A\((\S+)\.\.(\S+)\)\z/
|
|
||||||
|
|
||||||
def self.parse(markup)
|
def self.parse(markup)
|
||||||
if LITERALS.key?(markup)
|
if LITERALS.key?(markup)
|
||||||
LITERALS[markup]
|
LITERALS[markup]
|
||||||
else
|
else
|
||||||
case markup
|
case markup
|
||||||
when SINGLE_QUOTED_STRING, DOUBLE_QUOTED_STRING
|
when /\A'(.*)'\z/m # Single quoted strings
|
||||||
$1
|
$1
|
||||||
when INTEGERS_REGEX
|
when /\A"(.*)"\z/m # Double quoted strings
|
||||||
|
$1
|
||||||
|
when /\A(-?\d+)\z/ # Integer and floats
|
||||||
$1.to_i
|
$1.to_i
|
||||||
when RANGES_REGEX
|
when /\A\((\S+)\.\.(\S+)\)\z/ # Ranges
|
||||||
RangeLookup.parse($1, $2)
|
RangeLookup.parse($1, $2)
|
||||||
when FLOATS_REGEX
|
when /\A(-?\d[\d\.]+)\z/ # Floats
|
||||||
$1.to_f
|
$1.to_f
|
||||||
else
|
else
|
||||||
VariableLookup.parse(markup)
|
VariableLookup.parse(markup)
|
||||||
|
|||||||
@@ -7,12 +7,6 @@ class String # :nodoc:
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class Symbol # :nodoc:
|
|
||||||
def to_liquid
|
|
||||||
to_s
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Array # :nodoc:
|
class Array # :nodoc:
|
||||||
def to_liquid
|
def to_liquid
|
||||||
self
|
self
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ module Liquid
|
|||||||
NUMBER_LITERAL = /-?\d+(\.\d+)?/
|
NUMBER_LITERAL = /-?\d+(\.\d+)?/
|
||||||
DOTDOT = /\.\./
|
DOTDOT = /\.\./
|
||||||
COMPARISON_OPERATOR = /==|!=|<>|<=?|>=?|contains(?=\s)/
|
COMPARISON_OPERATOR = /==|!=|<>|<=?|>=?|contains(?=\s)/
|
||||||
WHITESPACE_OR_NOTHING = /\s*/
|
|
||||||
|
|
||||||
def initialize(input)
|
def initialize(input)
|
||||||
@ss = StringScanner.new(input)
|
@ss = StringScanner.new(input)
|
||||||
@@ -29,7 +28,7 @@ module Liquid
|
|||||||
@output = []
|
@output = []
|
||||||
|
|
||||||
until @ss.eos?
|
until @ss.eos?
|
||||||
@ss.skip(WHITESPACE_OR_NOTHING)
|
@ss.skip(/\s*/)
|
||||||
break if @ss.eos?
|
break if @ss.eos?
|
||||||
tok = case
|
tok = case
|
||||||
when t = @ss.scan(COMPARISON_OPERATOR) then [:comparison, t]
|
when t = @ss.scan(COMPARISON_OPERATOR) then [:comparison, t]
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
if: "Syntax Error in tag 'if' - Valid syntax: if [expression]"
|
if: "Syntax Error in tag 'if' - Valid syntax: if [expression]"
|
||||||
include: "Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]"
|
include: "Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]"
|
||||||
unknown_tag: "Unknown tag '%{tag}'"
|
unknown_tag: "Unknown tag '%{tag}'"
|
||||||
invalid_delimiter: "'%{tag}' is not a valid delimiter for %{block_name} tags. use %{block_delimiter}"
|
invalid_delimiter: "'end' is not a valid delimiter for %{block_name} tags. use %{block_delimiter}"
|
||||||
unexpected_else: "%{block_name} tag does not expect 'else' tag"
|
unexpected_else: "%{block_name} tag does not expect 'else' tag"
|
||||||
unexpected_outer_tag: "Unexpected outer '%{tag}' tag"
|
unexpected_outer_tag: "Unexpected outer '%{tag}' tag"
|
||||||
tag_termination: "Tag '%{token}' was not properly terminated with regexp: %{tag_end}"
|
tag_termination: "Tag '%{token}' was not properly terminated with regexp: %{tag_end}"
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
class ParseContext
|
class ParseContext
|
||||||
attr_accessor :locale, :line_number, :trim_whitespace, :depth
|
attr_accessor :locale, :line_number, :trim_whitespace
|
||||||
attr_reader :partial, :warnings, :error_mode
|
attr_reader :partial, :warnings, :error_mode
|
||||||
|
|
||||||
def initialize(options = {})
|
def initialize(options = {})
|
||||||
@template_options = options ? options.dup : {}
|
@template_options = options ? options.dup : {}
|
||||||
@locale = @template_options[:locale] ||= I18n.new
|
@locale = @template_options[:locale] ||= I18n.new
|
||||||
@warnings = []
|
@warnings = []
|
||||||
self.depth = 0
|
|
||||||
self.partial = false
|
self.partial = false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module Liquid
|
|
||||||
class ParseTreeVisitor
|
|
||||||
def self.for(node, callbacks = Hash.new(proc {}))
|
|
||||||
if defined?(node.class::ParseTreeVisitor)
|
|
||||||
node.class::ParseTreeVisitor
|
|
||||||
else
|
|
||||||
self
|
|
||||||
end.new(node, callbacks)
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize(node, callbacks)
|
|
||||||
@node = node
|
|
||||||
@callbacks = callbacks
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_callback_for(*classes, &block)
|
|
||||||
callback = block
|
|
||||||
callback = ->(node, _) { yield node } if block.arity.abs == 1
|
|
||||||
callback = ->(_, _) { yield } if block.arity.zero?
|
|
||||||
classes.each { |klass| @callbacks[klass] = callback }
|
|
||||||
self
|
|
||||||
end
|
|
||||||
|
|
||||||
def visit(context = nil)
|
|
||||||
children.map do |node|
|
|
||||||
item, new_context = @callbacks[node.class].call(node, context)
|
|
||||||
[
|
|
||||||
item,
|
|
||||||
ParseTreeVisitor.for(node, @callbacks).visit(new_context || context)
|
|
||||||
]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
def children
|
|
||||||
@node.respond_to?(:nodelist) ? Array(@node.nodelist) : []
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
module Liquid
|
module Liquid
|
||||||
class BlockBody
|
class BlockBody
|
||||||
def render_node_with_profiling(node, output, context, skip_output = false)
|
def render_node_with_profiling(node, context)
|
||||||
Profiler.profile_node_render(node) do
|
Profiler.profile_node_render(node) do
|
||||||
render_node_without_profiling(node, output, context, skip_output)
|
render_node_without_profiling(node, context)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
alias_method :render_node_without_profiling, :render_node_to_output
|
alias_method :render_node_without_profiling, :render_node
|
||||||
alias_method :render_node_to_output, :render_node_with_profiling
|
alias_method :render_node, :render_node_with_profiling
|
||||||
end
|
end
|
||||||
|
|
||||||
class Include < Tag
|
class Include < Tag
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def escape(input)
|
def escape(input)
|
||||||
CGI.escapeHTML(input.to_s).untaint unless input.nil?
|
CGI.escapeHTML(input).untaint unless input.nil?
|
||||||
end
|
end
|
||||||
alias_method :h, :escape
|
alias_method :h, :escape
|
||||||
|
|
||||||
@@ -42,11 +42,11 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def url_encode(input)
|
def url_encode(input)
|
||||||
CGI.escape(input.to_s) unless input.nil?
|
CGI.escape(input) unless input.nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
def url_decode(input)
|
def url_decode(input)
|
||||||
CGI.unescape(input.to_s) unless input.nil?
|
CGI.unescape(input) unless input.nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
def slice(input, offset, length = nil)
|
def slice(input, offset, length = nil)
|
||||||
@@ -121,23 +121,17 @@ module Liquid
|
|||||||
def sort(input, property = nil)
|
def sort(input, property = nil)
|
||||||
ary = InputIterator.new(input)
|
ary = InputIterator.new(input)
|
||||||
if property.nil?
|
if property.nil?
|
||||||
ary.sort do |a, b|
|
ary.sort
|
||||||
if !a.nil? && !b.nil?
|
|
||||||
a <=> b
|
|
||||||
else
|
|
||||||
a.nil? ? 1 : -1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elsif ary.empty? # The next two cases assume a non-empty array.
|
elsif ary.empty? # The next two cases assume a non-empty array.
|
||||||
[]
|
[]
|
||||||
elsif ary.all? { |el| el.respond_to?(:[]) }
|
elsif ary.first.respond_to?(:[]) && !ary.first[property].nil?
|
||||||
ary.sort do |a, b|
|
ary.sort do |a, b|
|
||||||
a = a[property]
|
a = a[property]
|
||||||
b = b[property]
|
b = b[property]
|
||||||
if !a.nil? && !b.nil?
|
if a && b
|
||||||
a <=> b
|
a <=> b
|
||||||
else
|
else
|
||||||
a.nil? ? 1 : -1
|
a ? -1 : 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -149,39 +143,11 @@ module Liquid
|
|||||||
ary = InputIterator.new(input)
|
ary = InputIterator.new(input)
|
||||||
|
|
||||||
if property.nil?
|
if property.nil?
|
||||||
ary.sort do |a, b|
|
ary.sort { |a, b| a.casecmp(b) }
|
||||||
if !a.nil? && !b.nil?
|
|
||||||
a.to_s.casecmp(b.to_s)
|
|
||||||
else
|
|
||||||
a.nil? ? 1 : -1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elsif ary.empty? # The next two cases assume a non-empty array.
|
elsif ary.empty? # The next two cases assume a non-empty array.
|
||||||
[]
|
[]
|
||||||
elsif ary.all? { |el| el.respond_to?(:[]) }
|
elsif ary.first.respond_to?(:[]) && !ary.first[property].nil?
|
||||||
ary.sort do |a, b|
|
ary.sort { |a, b| a[property].casecmp(b[property]) }
|
||||||
a = a[property]
|
|
||||||
b = b[property]
|
|
||||||
if !a.nil? && !b.nil?
|
|
||||||
a.to_s.casecmp(b.to_s)
|
|
||||||
else
|
|
||||||
a.nil? ? 1 : -1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Filter the elements of an array to those with a certain property value.
|
|
||||||
# By default the target is any truthy value.
|
|
||||||
def where(input, property, target_value = nil)
|
|
||||||
ary = InputIterator.new(input)
|
|
||||||
|
|
||||||
if ary.empty?
|
|
||||||
[]
|
|
||||||
elsif ary.first.respond_to?(:[]) && target_value.nil?
|
|
||||||
ary.where_present(property)
|
|
||||||
elsif ary.first.respond_to?(:[])
|
|
||||||
ary.where(property, target_value)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -387,22 +353,6 @@ module Liquid
|
|||||||
raise Liquid::FloatDomainError, e.message
|
raise Liquid::FloatDomainError, e.message
|
||||||
end
|
end
|
||||||
|
|
||||||
def at_least(input, n)
|
|
||||||
min_value = Utils.to_number(n)
|
|
||||||
|
|
||||||
result = Utils.to_number(input)
|
|
||||||
result = min_value if min_value > result
|
|
||||||
result.is_a?(BigDecimal) ? result.to_f : result
|
|
||||||
end
|
|
||||||
|
|
||||||
def at_most(input, n)
|
|
||||||
max_value = Utils.to_number(n)
|
|
||||||
|
|
||||||
result = Utils.to_number(input)
|
|
||||||
result = max_value if max_value < result
|
|
||||||
result.is_a?(BigDecimal) ? result.to_f : result
|
|
||||||
end
|
|
||||||
|
|
||||||
def default(input, default_value = ''.freeze)
|
def default(input, default_value = ''.freeze)
|
||||||
if !input || input.respond_to?(:empty?) && input.empty?
|
if !input || input.respond_to?(:empty?) && input.empty?
|
||||||
default_value
|
default_value
|
||||||
@@ -463,24 +413,6 @@ module Liquid
|
|||||||
yield(e.respond_to?(:to_liquid) ? e.to_liquid : e)
|
yield(e.respond_to?(:to_liquid) ? e.to_liquid : e)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def where(property, target_value)
|
|
||||||
select do |item|
|
|
||||||
item[property] == target_value
|
|
||||||
end
|
|
||||||
rescue TypeError
|
|
||||||
# Cannot index with the given property type (eg. indexing integers with strings
|
|
||||||
# which are only allowed to be indexed by other integers).
|
|
||||||
raise ArgumentError.new("cannot select the property `#{property}`")
|
|
||||||
end
|
|
||||||
|
|
||||||
def where_present(property)
|
|
||||||
select { |item| item[property] }
|
|
||||||
rescue TypeError
|
|
||||||
# Cannot index with the given property type (eg. indexing integers with strings
|
|
||||||
# which are only allowed to be indexed by other integers).
|
|
||||||
raise ArgumentError.new("cannot select the property `#{property}`")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ module Liquid
|
|||||||
class Assign < Tag
|
class Assign < Tag
|
||||||
Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om
|
Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om
|
||||||
|
|
||||||
attr_reader :to, :from
|
|
||||||
|
|
||||||
def initialize(tag_name, markup, options)
|
def initialize(tag_name, markup, options)
|
||||||
super
|
super
|
||||||
if markup =~ Syntax
|
if markup =~ Syntax
|
||||||
@@ -47,12 +45,6 @@ module Liquid
|
|||||||
1
|
1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
|
||||||
def children
|
|
||||||
[@node.from]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('assign'.freeze, Assign)
|
Template.register_tag('assign'.freeze, Assign)
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ module Liquid
|
|||||||
Syntax = /(#{QuotedFragment})/o
|
Syntax = /(#{QuotedFragment})/o
|
||||||
WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/om
|
WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/om
|
||||||
|
|
||||||
attr_reader :blocks, :left
|
|
||||||
|
|
||||||
def initialize(tag_name, markup, options)
|
def initialize(tag_name, markup, options)
|
||||||
super
|
super
|
||||||
@blocks = []
|
@blocks = []
|
||||||
@@ -82,12 +80,6 @@ module Liquid
|
|||||||
block.attach(BlockBody.new)
|
block.attach(BlockBody.new)
|
||||||
@blocks << block
|
@blocks << block
|
||||||
end
|
end
|
||||||
|
|
||||||
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
|
||||||
def children
|
|
||||||
[@node.left] + @node.blocks
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('case'.freeze, Case)
|
Template.register_tag('case'.freeze, Case)
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ module Liquid
|
|||||||
SimpleSyntax = /\A#{QuotedFragment}+/o
|
SimpleSyntax = /\A#{QuotedFragment}+/o
|
||||||
NamedSyntax = /\A(#{QuotedFragment})\s*\:\s*(.*)/om
|
NamedSyntax = /\A(#{QuotedFragment})\s*\:\s*(.*)/om
|
||||||
|
|
||||||
attr_reader :variables
|
|
||||||
|
|
||||||
def initialize(tag_name, markup, options)
|
def initialize(tag_name, markup, options)
|
||||||
super
|
super
|
||||||
case markup
|
case markup
|
||||||
@@ -32,11 +30,11 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
context.registers[:cycle] ||= {}
|
context.registers[:cycle] ||= Hash.new(0)
|
||||||
|
|
||||||
context.stack do
|
context.stack do
|
||||||
key = context.evaluate(@name)
|
key = context.evaluate(@name)
|
||||||
iteration = context.registers[:cycle][key].to_i
|
iteration = context.registers[:cycle][key]
|
||||||
result = context.evaluate(@variables[iteration])
|
result = context.evaluate(@variables[iteration])
|
||||||
iteration += 1
|
iteration += 1
|
||||||
iteration = 0 if iteration >= @variables.size
|
iteration = 0 if iteration >= @variables.size
|
||||||
@@ -53,12 +51,6 @@ module Liquid
|
|||||||
$1 ? Expression.parse($1) : nil
|
$1 ? Expression.parse($1) : nil
|
||||||
end.compact
|
end.compact
|
||||||
end
|
end
|
||||||
|
|
||||||
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
|
||||||
def children
|
|
||||||
Array(@node.variables)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('cycle', Cycle)
|
Template.register_tag('cycle', Cycle)
|
||||||
|
|||||||
@@ -46,8 +46,6 @@ module Liquid
|
|||||||
class For < Block
|
class For < Block
|
||||||
Syntax = /\A(#{VariableSegment}+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
|
Syntax = /\A(#{VariableSegment}+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
|
||||||
|
|
||||||
attr_reader :collection_name, :variable_name, :limit, :from
|
|
||||||
|
|
||||||
def initialize(tag_name, markup, options)
|
def initialize(tag_name, markup, options)
|
||||||
super
|
super
|
||||||
@from = @limit = nil
|
@from = @limit = nil
|
||||||
@@ -119,7 +117,7 @@ module Liquid
|
|||||||
private
|
private
|
||||||
|
|
||||||
def collection_segment(context)
|
def collection_segment(context)
|
||||||
offsets = context.registers[:for] ||= {}
|
offsets = context.registers[:for] ||= Hash.new(0)
|
||||||
|
|
||||||
from = if @from == :continue
|
from = if @from == :continue
|
||||||
offsets[@name].to_i
|
offsets[@name].to_i
|
||||||
@@ -155,7 +153,7 @@ module Liquid
|
|||||||
begin
|
begin
|
||||||
context['forloop'.freeze] = loop_vars
|
context['forloop'.freeze] = loop_vars
|
||||||
|
|
||||||
segment.each do |item|
|
segment.each_with_index do |item, index|
|
||||||
context[@variable_name] = item
|
context[@variable_name] = item
|
||||||
result << @for_block.render(context)
|
result << @for_block.render(context)
|
||||||
loop_vars.send(:increment!)
|
loop_vars.send(:increment!)
|
||||||
@@ -191,12 +189,6 @@ module Liquid
|
|||||||
def render_else(context)
|
def render_else(context)
|
||||||
@else_block ? @else_block.render(context) : ''.freeze
|
@else_block ? @else_block.render(context) : ''.freeze
|
||||||
end
|
end
|
||||||
|
|
||||||
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
|
||||||
def children
|
|
||||||
(super + [@node.limit, @node.from, @node.collection_name]).compact
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('for'.freeze, For)
|
Template.register_tag('for'.freeze, For)
|
||||||
|
|||||||
@@ -14,23 +14,21 @@ module Liquid
|
|||||||
ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/o
|
ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/o
|
||||||
BOOLEAN_OPERATORS = %w(and or)
|
BOOLEAN_OPERATORS = %w(and or)
|
||||||
|
|
||||||
attr_reader :blocks
|
|
||||||
|
|
||||||
def initialize(tag_name, markup, options)
|
def initialize(tag_name, markup, options)
|
||||||
super
|
super
|
||||||
@blocks = []
|
@blocks = []
|
||||||
push_block('if'.freeze, markup)
|
push_block('if'.freeze, markup)
|
||||||
end
|
end
|
||||||
|
|
||||||
def nodelist
|
|
||||||
@blocks.map(&:attachment)
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse(tokens)
|
def parse(tokens)
|
||||||
while parse_body(@blocks.last.attachment, tokens)
|
while parse_body(@blocks.last.attachment, tokens)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def nodelist
|
||||||
|
@blocks.map(&:attachment)
|
||||||
|
end
|
||||||
|
|
||||||
def unknown_tag(tag, markup, tokens)
|
def unknown_tag(tag, markup, tokens)
|
||||||
if ['elsif'.freeze, 'else'.freeze].include?(tag)
|
if ['elsif'.freeze, 'else'.freeze].include?(tag)
|
||||||
push_block(tag, markup)
|
push_block(tag, markup)
|
||||||
@@ -85,20 +83,17 @@ module Liquid
|
|||||||
|
|
||||||
def strict_parse(markup)
|
def strict_parse(markup)
|
||||||
p = Parser.new(markup)
|
p = Parser.new(markup)
|
||||||
condition = parse_binary_comparisons(p)
|
condition = parse_binary_comparison(p)
|
||||||
p.consume(:end_of_string)
|
p.consume(:end_of_string)
|
||||||
condition
|
condition
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse_binary_comparisons(p)
|
def parse_binary_comparison(p)
|
||||||
condition = parse_comparison(p)
|
condition = parse_comparison(p)
|
||||||
first_condition = condition
|
if op = (p.id?('and'.freeze) || p.id?('or'.freeze))
|
||||||
while op = (p.id?('and'.freeze) || p.id?('or'.freeze))
|
condition.send(op, parse_binary_comparison(p))
|
||||||
child_condition = parse_comparison(p)
|
|
||||||
condition.send(op, child_condition)
|
|
||||||
condition = child_condition
|
|
||||||
end
|
end
|
||||||
first_condition
|
condition
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse_comparison(p)
|
def parse_comparison(p)
|
||||||
@@ -110,12 +105,6 @@ module Liquid
|
|||||||
Condition.new(a)
|
Condition.new(a)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
|
||||||
def children
|
|
||||||
@node.blocks
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('if'.freeze, If)
|
Template.register_tag('if'.freeze, If)
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ module Liquid
|
|||||||
class Include < Tag
|
class Include < Tag
|
||||||
Syntax = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?/o
|
Syntax = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?/o
|
||||||
|
|
||||||
attr_reader :template_name_expr, :variable_name_expr, :attributes
|
|
||||||
|
|
||||||
def initialize(tag_name, markup, options)
|
def initialize(tag_name, markup, options)
|
||||||
super
|
super
|
||||||
|
|
||||||
@@ -109,15 +107,6 @@ module Liquid
|
|||||||
|
|
||||||
file_system.read_template_file(context.evaluate(@template_name_expr))
|
file_system.read_template_file(context.evaluate(@template_name_expr))
|
||||||
end
|
end
|
||||||
|
|
||||||
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
|
||||||
def children
|
|
||||||
[
|
|
||||||
@node.template_name_expr,
|
|
||||||
@node.variable_name_expr
|
|
||||||
] + @node.attributes.values
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('include'.freeze, Include)
|
Template.register_tag('include'.freeze, Include)
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ module Liquid
|
|||||||
class TableRow < Block
|
class TableRow < Block
|
||||||
Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o
|
Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o
|
||||||
|
|
||||||
attr_reader :variable_name, :collection_name, :attributes
|
|
||||||
|
|
||||||
def initialize(tag_name, markup, options)
|
def initialize(tag_name, markup, options)
|
||||||
super
|
super
|
||||||
if markup =~ Syntax
|
if markup =~ Syntax
|
||||||
@@ -35,7 +33,7 @@ module Liquid
|
|||||||
tablerowloop = Liquid::TablerowloopDrop.new(length, cols)
|
tablerowloop = Liquid::TablerowloopDrop.new(length, cols)
|
||||||
context['tablerowloop'.freeze] = tablerowloop
|
context['tablerowloop'.freeze] = tablerowloop
|
||||||
|
|
||||||
collection.each do |item|
|
collection.each_with_index do |item, index|
|
||||||
context[@variable_name] = item
|
context[@variable_name] = item
|
||||||
|
|
||||||
result << "<td class=\"col#{tablerowloop.col}\">" << super << '</td>'
|
result << "<td class=\"col#{tablerowloop.col}\">" << super << '</td>'
|
||||||
@@ -50,12 +48,6 @@ module Liquid
|
|||||||
result << "</tr>\n"
|
result << "</tr>\n"
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
|
|
||||||
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
|
||||||
def children
|
|
||||||
super + @node.attributes.values + [@node.collection_name]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('tablerow'.freeze, TableRow)
|
Template.register_tag('tablerow'.freeze, TableRow)
|
||||||
|
|||||||
@@ -46,11 +46,11 @@ module Liquid
|
|||||||
def self.to_number(obj)
|
def self.to_number(obj)
|
||||||
case obj
|
case obj
|
||||||
when Float
|
when Float
|
||||||
BigDecimal(obj.to_s)
|
BigDecimal.new(obj.to_s)
|
||||||
when Numeric
|
when Numeric
|
||||||
obj
|
obj
|
||||||
when String
|
when String
|
||||||
(obj.strip =~ /\A-?\d+\.\d+\z/) ? BigDecimal(obj) : obj.to_i
|
(obj.strip =~ /\A-?\d+\.\d+\z/) ? BigDecimal.new(obj) : obj.to_i
|
||||||
else
|
else
|
||||||
if obj.respond_to?(:to_number)
|
if obj.respond_to?(:to_number)
|
||||||
obj.to_number
|
obj.to_number
|
||||||
|
|||||||
@@ -10,16 +10,10 @@ module Liquid
|
|||||||
# {{ user | link }}
|
# {{ user | link }}
|
||||||
#
|
#
|
||||||
class Variable
|
class Variable
|
||||||
FilterMarkupRegex = /#{FilterSeparator}\s*(.*)/om
|
|
||||||
FilterParser = /(?:\s+|#{QuotedFragment}|#{ArgumentSeparator})+/o
|
FilterParser = /(?:\s+|#{QuotedFragment}|#{ArgumentSeparator})+/o
|
||||||
FilterArgsRegex = /(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o
|
|
||||||
JustTagAttributes = /\A#{TagAttributes}\z/o
|
|
||||||
MarkupWithQuotedFragment = /(#{QuotedFragment})(.*)/om
|
|
||||||
|
|
||||||
attr_accessor :filters, :name, :line_number
|
attr_accessor :filters, :name, :line_number
|
||||||
attr_reader :parse_context
|
attr_reader :parse_context
|
||||||
alias_method :options, :parse_context
|
alias_method :options, :parse_context
|
||||||
|
|
||||||
include ParserSwitching
|
include ParserSwitching
|
||||||
|
|
||||||
def initialize(markup, parse_context)
|
def initialize(markup, parse_context)
|
||||||
@@ -41,17 +35,17 @@ module Liquid
|
|||||||
|
|
||||||
def lax_parse(markup)
|
def lax_parse(markup)
|
||||||
@filters = []
|
@filters = []
|
||||||
return unless markup =~ MarkupWithQuotedFragment
|
return unless markup =~ /(#{QuotedFragment})(.*)/om
|
||||||
|
|
||||||
name_markup = $1
|
name_markup = $1
|
||||||
filter_markup = $2
|
filter_markup = $2
|
||||||
@name = Expression.parse(name_markup)
|
@name = Expression.parse(name_markup)
|
||||||
if filter_markup =~ FilterMarkupRegex
|
if filter_markup =~ /#{FilterSeparator}\s*(.*)/om
|
||||||
filters = $1.scan(FilterParser)
|
filters = $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)
|
||||||
filterargs = f.scan(FilterArgsRegex).flatten
|
filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o).flatten
|
||||||
@filters << parse_filter_expressions(filtername, filterargs)
|
@filters << parse_filter_expressions(filtername, filterargs)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -97,7 +91,7 @@ module Liquid
|
|||||||
filter_args = []
|
filter_args = []
|
||||||
keyword_args = {}
|
keyword_args = {}
|
||||||
unparsed_args.each do |a|
|
unparsed_args.each do |a|
|
||||||
if matches = a.match(JustTagAttributes)
|
if matches = a.match(/\A#{TagAttributes}\z/o)
|
||||||
keyword_args[matches[1]] = Expression.parse(matches[2])
|
keyword_args[matches[1]] = Expression.parse(matches[2])
|
||||||
else
|
else
|
||||||
filter_args << Expression.parse(a)
|
filter_args << Expression.parse(a)
|
||||||
@@ -138,11 +132,5 @@ module Liquid
|
|||||||
raise error
|
raise error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
|
||||||
def children
|
|
||||||
[@node.name] + @node.filters.flatten
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -78,11 +78,5 @@ module Liquid
|
|||||||
def state
|
def state
|
||||||
[@name, @lookups, @command_flags]
|
[@name, @lookups, @command_flags]
|
||||||
end
|
end
|
||||||
|
|
||||||
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
|
||||||
def children
|
|
||||||
@node.lookups
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
module Liquid
|
module Liquid
|
||||||
VERSION = "4.0.1"
|
VERSION = "4.0.0"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
require 'test_helper'
|
|
||||||
|
|
||||||
class BlockTest < Minitest::Test
|
|
||||||
include Liquid
|
|
||||||
|
|
||||||
def test_unexpected_end_tag
|
|
||||||
exc = assert_raises(SyntaxError) do
|
|
||||||
Template.parse("{% if true %}{% endunless %}")
|
|
||||||
end
|
|
||||||
assert_equal exc.message, "Liquid syntax error: 'endunless' is not a valid delimiter for if tags. use endif"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,247 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'test_helper'
|
|
||||||
|
|
||||||
class ParseTreeVisitorTest < Minitest::Test
|
|
||||||
include Liquid
|
|
||||||
|
|
||||||
def test_variable
|
|
||||||
assert_equal(
|
|
||||||
["test"],
|
|
||||||
visit(%({{ test }}))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_varible_with_filter
|
|
||||||
assert_equal(
|
|
||||||
["test", "infilter"],
|
|
||||||
visit(%({{ test | split: infilter }}))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_dynamic_variable
|
|
||||||
assert_equal(
|
|
||||||
["test", "inlookup"],
|
|
||||||
visit(%({{ test[inlookup] }}))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_if_condition
|
|
||||||
assert_equal(
|
|
||||||
["test"],
|
|
||||||
visit(%({% if test %}{% endif %}))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_complex_if_condition
|
|
||||||
assert_equal(
|
|
||||||
["test"],
|
|
||||||
visit(%({% if 1 == 1 and 2 == test %}{% endif %}))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_if_body
|
|
||||||
assert_equal(
|
|
||||||
["test"],
|
|
||||||
visit(%({% if 1 == 1 %}{{ test }}{% endif %}))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_unless_condition
|
|
||||||
assert_equal(
|
|
||||||
["test"],
|
|
||||||
visit(%({% unless test %}{% endunless %}))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_complex_unless_condition
|
|
||||||
assert_equal(
|
|
||||||
["test"],
|
|
||||||
visit(%({% unless 1 == 1 and 2 == test %}{% endunless %}))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_unless_body
|
|
||||||
assert_equal(
|
|
||||||
["test"],
|
|
||||||
visit(%({% unless 1 == 1 %}{{ test }}{% endunless %}))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_elsif_condition
|
|
||||||
assert_equal(
|
|
||||||
["test"],
|
|
||||||
visit(%({% if 1 == 1 %}{% elsif test %}{% endif %}))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_complex_elsif_condition
|
|
||||||
assert_equal(
|
|
||||||
["test"],
|
|
||||||
visit(%({% if 1 == 1 %}{% elsif 1 == 1 and 2 == test %}{% endif %}))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_elsif_body
|
|
||||||
assert_equal(
|
|
||||||
["test"],
|
|
||||||
visit(%({% if 1 == 1 %}{% elsif 2 == 2 %}{{ test }}{% endif %}))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_else_body
|
|
||||||
assert_equal(
|
|
||||||
["test"],
|
|
||||||
visit(%({% if 1 == 1 %}{% else %}{{ test }}{% endif %}))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_case_left
|
|
||||||
assert_equal(
|
|
||||||
["test"],
|
|
||||||
visit(%({% case test %}{% endcase %}))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_case_condition
|
|
||||||
assert_equal(
|
|
||||||
["test"],
|
|
||||||
visit(%({% case 1 %}{% when test %}{% endcase %}))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_case_when_body
|
|
||||||
assert_equal(
|
|
||||||
["test"],
|
|
||||||
visit(%({% case 1 %}{% when 2 %}{{ test }}{% endcase %}))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_case_else_body
|
|
||||||
assert_equal(
|
|
||||||
["test"],
|
|
||||||
visit(%({% case 1 %}{% else %}{{ test }}{% endcase %}))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_for_in
|
|
||||||
assert_equal(
|
|
||||||
["test"],
|
|
||||||
visit(%({% for x in test %}{% endfor %}))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_for_limit
|
|
||||||
assert_equal(
|
|
||||||
["test"],
|
|
||||||
visit(%({% for x in (1..5) limit: test %}{% endfor %}))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_for_offset
|
|
||||||
assert_equal(
|
|
||||||
["test"],
|
|
||||||
visit(%({% for x in (1..5) offset: test %}{% endfor %}))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_for_body
|
|
||||||
assert_equal(
|
|
||||||
["test"],
|
|
||||||
visit(%({% for x in (1..5) %}{{ test }}{% endfor %}))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_tablerow_in
|
|
||||||
assert_equal(
|
|
||||||
["test"],
|
|
||||||
visit(%({% tablerow x in test %}{% endtablerow %}))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_tablerow_limit
|
|
||||||
assert_equal(
|
|
||||||
["test"],
|
|
||||||
visit(%({% tablerow x in (1..5) limit: test %}{% endtablerow %}))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_tablerow_offset
|
|
||||||
assert_equal(
|
|
||||||
["test"],
|
|
||||||
visit(%({% tablerow x in (1..5) offset: test %}{% endtablerow %}))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_tablerow_body
|
|
||||||
assert_equal(
|
|
||||||
["test"],
|
|
||||||
visit(%({% tablerow x in (1..5) %}{{ test }}{% endtablerow %}))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_cycle
|
|
||||||
assert_equal(
|
|
||||||
["test"],
|
|
||||||
visit(%({% cycle test %}))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_assign
|
|
||||||
assert_equal(
|
|
||||||
["test"],
|
|
||||||
visit(%({% assign x = test %}))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_capture
|
|
||||||
assert_equal(
|
|
||||||
["test"],
|
|
||||||
visit(%({% capture x %}{{ test }}{% endcapture %}))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_include
|
|
||||||
assert_equal(
|
|
||||||
["test"],
|
|
||||||
visit(%({% include test %}))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_include_with
|
|
||||||
assert_equal(
|
|
||||||
["test"],
|
|
||||||
visit(%({% include "hai" with test %}))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_include_for
|
|
||||||
assert_equal(
|
|
||||||
["test"],
|
|
||||||
visit(%({% include "hai" for test %}))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_preserve_tree_structure
|
|
||||||
assert_equal(
|
|
||||||
[[nil, [
|
|
||||||
[nil, [[nil, [["other", []]]]]],
|
|
||||||
["test", []],
|
|
||||||
["xs", []]
|
|
||||||
]]],
|
|
||||||
traversal(%({% for x in xs offset: test %}{{ other }}{% endfor %})).visit
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def traversal(template)
|
|
||||||
ParseTreeVisitor
|
|
||||||
.for(Template.parse(template).root)
|
|
||||||
.add_callback_for(VariableLookup, &:name)
|
|
||||||
end
|
|
||||||
|
|
||||||
def visit(template)
|
|
||||||
traversal(template).visit.flatten.compact
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -63,18 +63,4 @@ class SecurityTest < Minitest::Test
|
|||||||
|
|
||||||
assert_equal [], (Symbol.all_symbols - current_symbols)
|
assert_equal [], (Symbol.all_symbols - current_symbols)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_max_depth_nested_blocks_does_not_raise_exception
|
|
||||||
depth = Liquid::Block::MAX_DEPTH
|
|
||||||
code = "{% if true %}" * depth + "rendered" + "{% endif %}" * depth
|
|
||||||
assert_equal "rendered", Template.parse(code).render!
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_more_than_max_depth_nested_blocks_raises_exception
|
|
||||||
depth = Liquid::Block::MAX_DEPTH + 1
|
|
||||||
code = "{% if true %}" * depth + "rendered" + "{% endif %}" * depth
|
|
||||||
assert_raises(Liquid::StackLevelError) do
|
|
||||||
Template.parse(code).render!
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end # SecurityTest
|
end # SecurityTest
|
||||||
|
|||||||
@@ -128,16 +128,8 @@ class StandardFiltersTest < Minitest::Test
|
|||||||
|
|
||||||
def test_escape
|
def test_escape
|
||||||
assert_equal '<strong>', @filters.escape('<strong>')
|
assert_equal '<strong>', @filters.escape('<strong>')
|
||||||
assert_equal '1', @filters.escape(1)
|
assert_equal nil, @filters.escape(nil)
|
||||||
assert_equal '2001-02-03', @filters.escape(Date.new(2001, 2, 3))
|
|
||||||
assert_nil @filters.escape(nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_h
|
|
||||||
assert_equal '<strong>', @filters.h('<strong>')
|
assert_equal '<strong>', @filters.h('<strong>')
|
||||||
assert_equal '1', @filters.h(1)
|
|
||||||
assert_equal '2001-02-03', @filters.h(Date.new(2001, 2, 3))
|
|
||||||
assert_nil @filters.h(nil)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_escape_once
|
def test_escape_once
|
||||||
@@ -146,18 +138,14 @@ class StandardFiltersTest < Minitest::Test
|
|||||||
|
|
||||||
def test_url_encode
|
def test_url_encode
|
||||||
assert_equal 'foo%2B1%40example.com', @filters.url_encode('foo+1@example.com')
|
assert_equal 'foo%2B1%40example.com', @filters.url_encode('foo+1@example.com')
|
||||||
assert_equal '1', @filters.url_encode(1)
|
assert_equal nil, @filters.url_encode(nil)
|
||||||
assert_equal '2001-02-03', @filters.url_encode(Date.new(2001, 2, 3))
|
|
||||||
assert_nil @filters.url_encode(nil)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_url_decode
|
def test_url_decode
|
||||||
assert_equal 'foo bar', @filters.url_decode('foo+bar')
|
assert_equal 'foo bar', @filters.url_decode('foo+bar')
|
||||||
assert_equal 'foo bar', @filters.url_decode('foo%20bar')
|
assert_equal 'foo bar', @filters.url_decode('foo%20bar')
|
||||||
assert_equal 'foo+1@example.com', @filters.url_decode('foo%2B1%40example.com')
|
assert_equal 'foo+1@example.com', @filters.url_decode('foo%2B1%40example.com')
|
||||||
assert_equal '1', @filters.url_decode(1)
|
assert_equal nil, @filters.url_decode(nil)
|
||||||
assert_equal '2001-02-03', @filters.url_decode(Date.new(2001, 2, 3))
|
|
||||||
assert_nil @filters.url_decode(nil)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_truncatewords
|
def test_truncatewords
|
||||||
@@ -190,11 +178,6 @@ 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_with_nils
|
|
||||||
assert_equal [1, 2, 3, 4, nil], @filters.sort([nil, 4, 3, 2, 1])
|
|
||||||
assert_equal [{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }, {}], @filters.sort([{ "a" => 4 }, { "a" => 3 }, {}, { "a" => 1 }, { "a" => 2 }], "a")
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_sort_when_property_is_sometimes_missing_puts_nils_last
|
def test_sort_when_property_is_sometimes_missing_puts_nils_last
|
||||||
input = [
|
input = [
|
||||||
{ "price" => 4, "handle" => "alpha" },
|
{ "price" => 4, "handle" => "alpha" },
|
||||||
@@ -213,57 +196,6 @@ class StandardFiltersTest < Minitest::Test
|
|||||||
assert_equal expectation, @filters.sort(input, "price")
|
assert_equal expectation, @filters.sort(input, "price")
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_sort_natural
|
|
||||||
assert_equal ["a", "B", "c", "D"], @filters.sort_natural(["c", "D", "a", "B"])
|
|
||||||
assert_equal [{ "a" => "a" }, { "a" => "B" }, { "a" => "c" }, { "a" => "D" }], @filters.sort_natural([{ "a" => "D" }, { "a" => "c" }, { "a" => "a" }, { "a" => "B" }], "a")
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_sort_natural_with_nils
|
|
||||||
assert_equal ["a", "B", "c", "D", nil], @filters.sort_natural([nil, "c", "D", "a", "B"])
|
|
||||||
assert_equal [{ "a" => "a" }, { "a" => "B" }, { "a" => "c" }, { "a" => "D" }, {}], @filters.sort_natural([{ "a" => "D" }, { "a" => "c" }, {}, { "a" => "a" }, { "a" => "B" }], "a")
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_sort_natural_when_property_is_sometimes_missing_puts_nils_last
|
|
||||||
input = [
|
|
||||||
{ "price" => "4", "handle" => "alpha" },
|
|
||||||
{ "handle" => "beta" },
|
|
||||||
{ "price" => "1", "handle" => "gamma" },
|
|
||||||
{ "handle" => "delta" },
|
|
||||||
{ "price" => 2, "handle" => "epsilon" }
|
|
||||||
]
|
|
||||||
expectation = [
|
|
||||||
{ "price" => "1", "handle" => "gamma" },
|
|
||||||
{ "price" => 2, "handle" => "epsilon" },
|
|
||||||
{ "price" => "4", "handle" => "alpha" },
|
|
||||||
{ "handle" => "delta" },
|
|
||||||
{ "handle" => "beta" }
|
|
||||||
]
|
|
||||||
assert_equal expectation, @filters.sort_natural(input, "price")
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_sort_natural_case_check
|
|
||||||
input = [
|
|
||||||
{ "key" => "X" },
|
|
||||||
{ "key" => "Y" },
|
|
||||||
{ "key" => "Z" },
|
|
||||||
{ "fake" => "t" },
|
|
||||||
{ "key" => "a" },
|
|
||||||
{ "key" => "b" },
|
|
||||||
{ "key" => "c" }
|
|
||||||
]
|
|
||||||
expectation = [
|
|
||||||
{ "key" => "a" },
|
|
||||||
{ "key" => "b" },
|
|
||||||
{ "key" => "c" },
|
|
||||||
{ "key" => "X" },
|
|
||||||
{ "key" => "Y" },
|
|
||||||
{ "key" => "Z" },
|
|
||||||
{ "fake" => "t" }
|
|
||||||
]
|
|
||||||
assert_equal expectation, @filters.sort_natural(input, "key")
|
|
||||||
assert_equal ["a", "b", "c", "X", "Y", "Z"], @filters.sort_natural(["X", "Y", "Z", "a", "b", "c"])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_sort_empty_array
|
def test_sort_empty_array
|
||||||
assert_equal [], @filters.sort([], "a")
|
assert_equal [], @filters.sort([], "a")
|
||||||
end
|
end
|
||||||
@@ -398,7 +330,7 @@ class StandardFiltersTest < Minitest::Test
|
|||||||
assert_equal "#{Date.today.year}", @filters.date('today', '%Y')
|
assert_equal "#{Date.today.year}", @filters.date('today', '%Y')
|
||||||
assert_equal "#{Date.today.year}", @filters.date('Today', '%Y')
|
assert_equal "#{Date.today.year}", @filters.date('Today', '%Y')
|
||||||
|
|
||||||
assert_nil @filters.date(nil, "%B")
|
assert_equal nil, @filters.date(nil, "%B")
|
||||||
|
|
||||||
assert_equal '', @filters.date('', "%B")
|
assert_equal '', @filters.date('', "%B")
|
||||||
|
|
||||||
@@ -411,8 +343,8 @@ class StandardFiltersTest < Minitest::Test
|
|||||||
def test_first_last
|
def test_first_last
|
||||||
assert_equal 1, @filters.first([1, 2, 3])
|
assert_equal 1, @filters.first([1, 2, 3])
|
||||||
assert_equal 3, @filters.last([1, 2, 3])
|
assert_equal 3, @filters.last([1, 2, 3])
|
||||||
assert_nil @filters.first([])
|
assert_equal nil, @filters.first([])
|
||||||
assert_nil @filters.last([])
|
assert_equal nil, @filters.last([])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_replace
|
def test_replace
|
||||||
@@ -552,28 +484,6 @@ class StandardFiltersTest < Minitest::Test
|
|||||||
assert_template_result "5", "{{ price | floor }}", 'price' => NumberLikeThing.new(5.4)
|
assert_template_result "5", "{{ price | floor }}", 'price' => NumberLikeThing.new(5.4)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_at_most
|
|
||||||
assert_template_result "4", "{{ 5 | at_most:4 }}"
|
|
||||||
assert_template_result "5", "{{ 5 | at_most:5 }}"
|
|
||||||
assert_template_result "5", "{{ 5 | at_most:6 }}"
|
|
||||||
|
|
||||||
assert_template_result "4.5", "{{ 4.5 | at_most:5 }}"
|
|
||||||
assert_template_result "5", "{{ width | at_most:5 }}", 'width' => NumberLikeThing.new(6)
|
|
||||||
assert_template_result "4", "{{ width | at_most:5 }}", 'width' => NumberLikeThing.new(4)
|
|
||||||
assert_template_result "4", "{{ 5 | at_most: width }}", 'width' => NumberLikeThing.new(4)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_at_least
|
|
||||||
assert_template_result "5", "{{ 5 | at_least:4 }}"
|
|
||||||
assert_template_result "5", "{{ 5 | at_least:5 }}"
|
|
||||||
assert_template_result "6", "{{ 5 | at_least:6 }}"
|
|
||||||
|
|
||||||
assert_template_result "5", "{{ 4.5 | at_least:5 }}"
|
|
||||||
assert_template_result "6", "{{ width | at_least:5 }}", 'width' => NumberLikeThing.new(6)
|
|
||||||
assert_template_result "5", "{{ width | at_least:5 }}", 'width' => NumberLikeThing.new(4)
|
|
||||||
assert_template_result "6", "{{ 5 | at_least: width }}", 'width' => NumberLikeThing.new(6)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_append
|
def test_append
|
||||||
assigns = { 'a' => 'bc', 'b' => 'd' }
|
assigns = { 'a' => 'bc', 'b' => 'd' }
|
||||||
assert_template_result('bcd', "{{ a | append: 'd'}}", assigns)
|
assert_template_result('bcd', "{{ a | append: 'd'}}", assigns)
|
||||||
@@ -614,78 +524,6 @@ class StandardFiltersTest < Minitest::Test
|
|||||||
assert_template_result('abc', "{{ 'abc' | date: '%D' }}")
|
assert_template_result('abc', "{{ 'abc' | date: '%D' }}")
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_where
|
|
||||||
input = [
|
|
||||||
{ "handle" => "alpha", "ok" => true },
|
|
||||||
{ "handle" => "beta", "ok" => false },
|
|
||||||
{ "handle" => "gamma", "ok" => false },
|
|
||||||
{ "handle" => "delta", "ok" => true }
|
|
||||||
]
|
|
||||||
|
|
||||||
expectation = [
|
|
||||||
{ "handle" => "alpha", "ok" => true },
|
|
||||||
{ "handle" => "delta", "ok" => true }
|
|
||||||
]
|
|
||||||
|
|
||||||
assert_equal expectation, @filters.where(input, "ok", true)
|
|
||||||
assert_equal expectation, @filters.where(input, "ok")
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_where_no_key_set
|
|
||||||
input = [
|
|
||||||
{ "handle" => "alpha", "ok" => true },
|
|
||||||
{ "handle" => "beta" },
|
|
||||||
{ "handle" => "gamma" },
|
|
||||||
{ "handle" => "delta", "ok" => true }
|
|
||||||
]
|
|
||||||
|
|
||||||
expectation = [
|
|
||||||
{ "handle" => "alpha", "ok" => true },
|
|
||||||
{ "handle" => "delta", "ok" => true }
|
|
||||||
]
|
|
||||||
|
|
||||||
assert_equal expectation, @filters.where(input, "ok", true)
|
|
||||||
assert_equal expectation, @filters.where(input, "ok")
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_where_non_array_map_input
|
|
||||||
assert_equal [{ "a" => "ok" }], @filters.where({ "a" => "ok" }, "a", "ok")
|
|
||||||
assert_equal [], @filters.where({ "a" => "not ok" }, "a", "ok")
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_where_indexable_but_non_map_value
|
|
||||||
assert_raises(Liquid::ArgumentError) { @filters.where(1, "ok", true) }
|
|
||||||
assert_raises(Liquid::ArgumentError) { @filters.where(1, "ok") }
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_where_non_boolean_value
|
|
||||||
input = [
|
|
||||||
{ "message" => "Bonjour!", "language" => "French" },
|
|
||||||
{ "message" => "Hello!", "language" => "English" },
|
|
||||||
{ "message" => "Hallo!", "language" => "German" }
|
|
||||||
]
|
|
||||||
|
|
||||||
assert_equal [{ "message" => "Bonjour!", "language" => "French" }], @filters.where(input, "language", "French")
|
|
||||||
assert_equal [{ "message" => "Hallo!", "language" => "German" }], @filters.where(input, "language", "German")
|
|
||||||
assert_equal [{ "message" => "Hello!", "language" => "English" }], @filters.where(input, "language", "English")
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_where_array_of_only_unindexable_values
|
|
||||||
assert_nil @filters.where([nil], "ok", true)
|
|
||||||
assert_nil @filters.where([nil], "ok")
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_where_no_target_value
|
|
||||||
input = [
|
|
||||||
{ "foo" => false },
|
|
||||||
{ "foo" => true },
|
|
||||||
{ "foo" => "for sure" },
|
|
||||||
{ "bar" => true }
|
|
||||||
]
|
|
||||||
|
|
||||||
assert_equal [{ "foo" => true }, { "foo" => "for sure" }], @filters.where(input, "foo")
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def with_timezone(tz)
|
def with_timezone(tz)
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ HERE
|
|||||||
assert_template_result(expected, markup, assigns)
|
assert_template_result(expected, markup, assigns)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_pause_resume_big_limit
|
def test_pause_resume_BIG_limit
|
||||||
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } }
|
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } }
|
||||||
markup = <<-MKUP
|
markup = <<-MKUP
|
||||||
{%for i in array.items limit:3 %}{{i}}{%endfor%}
|
{%for i in array.items limit:3 %}{{i}}{%endfor%}
|
||||||
@@ -178,7 +178,7 @@ HERE
|
|||||||
assert_template_result(expected, markup, assigns)
|
assert_template_result(expected, markup, assigns)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_pause_resume_big_offset
|
def test_pause_resume_BIG_offset
|
||||||
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } }
|
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] } }
|
||||||
markup = '{%for i in array.items limit:3 %}{{i}}{%endfor%}
|
markup = '{%for i in array.items limit:3 %}{{i}}{%endfor%}
|
||||||
next
|
next
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ class IncludeTagTest < Minitest::Test
|
|||||||
|
|
||||||
Liquid::Template.file_system = infinite_file_system.new
|
Liquid::Template.file_system = infinite_file_system.new
|
||||||
|
|
||||||
assert_raises(Liquid::StackLevelError) do
|
assert_raises(Liquid::StackLevelError, SystemStackError) do
|
||||||
Template.parse("{% include 'loop' %}").render!
|
Template.parse("{% include 'loop' %}").render!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -261,15 +261,6 @@ class TemplateTest < Minitest::Test
|
|||||||
assert_equal 'Liquid error: undefined variable d', t.errors[2].message
|
assert_equal 'Liquid error: undefined variable d', t.errors[2].message
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_nil_value_does_not_raise
|
|
||||||
Liquid::Template.error_mode = :strict
|
|
||||||
t = Template.parse("some{{x}}thing")
|
|
||||||
result = t.render!({ 'x' => nil }, strict_variables: true)
|
|
||||||
|
|
||||||
assert_equal 0, t.errors.count
|
|
||||||
assert_equal 'something', result
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_undefined_variables_raise
|
def test_undefined_variables_raise
|
||||||
t = Template.parse("{{x}} {{y}} {{z.a}} {{z.b}} {{z.c.d}}")
|
t = Template.parse("{{x}} {{y}} {{z.a}} {{z.b}} {{z.c.d}}")
|
||||||
|
|
||||||
|
|||||||
@@ -496,10 +496,6 @@ class TrimModeTest < Minitest::Test
|
|||||||
assert_template_result(expected, text)
|
assert_template_result(expected, text)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_right_trim_followed_by_tag
|
|
||||||
assert_template_result('ab c', '{{ "a" -}}{{ "b" }} c')
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_raw_output
|
def test_raw_output
|
||||||
whitespace = ' '
|
whitespace = ' '
|
||||||
text = <<-END_TEMPLATE
|
text = <<-END_TEMPLATE
|
||||||
|
|||||||
@@ -89,8 +89,4 @@ class VariableTest < Minitest::Test
|
|||||||
def test_multiline_variable
|
def test_multiline_variable
|
||||||
assert_equal 'worked', Template.parse("{{\ntest\n}}").render!('test' => 'worked')
|
assert_equal 'worked', Template.parse("{{\ntest\n}}").render!('test' => 'worked')
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_render_symbol
|
|
||||||
assert_template_result 'bar', '{{ foo }}', 'foo' => :bar
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
ENV["MT_NO_EXPECTATIONS"] = "1"
|
ENV["MT_NO_EXPECTATIONS"] = "1"
|
||||||
require 'minitest/autorun'
|
require 'minitest/autorun'
|
||||||
|
require 'spy/integration'
|
||||||
|
|
||||||
$LOAD_PATH.unshift(File.join(File.expand_path(__dir__), '..', 'lib'))
|
$LOAD_PATH.unshift(File.join(File.expand_path(__dir__), '..', 'lib'))
|
||||||
require 'liquid.rb'
|
require 'liquid.rb'
|
||||||
|
|||||||
@@ -65,8 +65,8 @@ class ConditionUnitTest < Minitest::Test
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_hash_compare_backwards_compatibility
|
def test_hash_compare_backwards_compatibility
|
||||||
assert_nil Condition.new({}, '>', 2).evaluate
|
assert_equal nil, Condition.new({}, '>', 2).evaluate
|
||||||
assert_nil Condition.new(2, '>', {}).evaluate
|
assert_equal nil, Condition.new(2, '>', {}).evaluate
|
||||||
assert_equal false, Condition.new({}, '==', 2).evaluate
|
assert_equal false, Condition.new({}, '==', 2).evaluate
|
||||||
assert_equal true, Condition.new({ 'a' => 1 }, '==', { 'a' => 1 }).evaluate
|
assert_equal true, Condition.new({ 'a' => 1 }, '==', { 'a' => 1 }).evaluate
|
||||||
assert_equal true, Condition.new({ 'a' => 2 }, 'contains', 'a').evaluate
|
assert_equal true, Condition.new({ 'a' => 2 }, 'contains', 'a').evaluate
|
||||||
@@ -130,6 +130,17 @@ class ConditionUnitTest < Minitest::Test
|
|||||||
assert_equal false, condition.evaluate
|
assert_equal false, condition.evaluate
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_maximum_recursion_depth
|
||||||
|
condition = Condition.new(1, '==', 1)
|
||||||
|
|
||||||
|
assert_raises(Liquid::StackLevelError) do
|
||||||
|
(1..510).each do
|
||||||
|
condition.evaluate
|
||||||
|
condition.and Condition.new(2, '==', 2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_should_allow_custom_proc_operator
|
def test_should_allow_custom_proc_operator
|
||||||
Condition.operators['starts_with'] = proc { |cond, left, right| left =~ %r{^#{right}} }
|
Condition.operators['starts_with'] = proc { |cond, left, right| left =~ %r{^#{right}} }
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,10 @@ class ContextUnitTest < Minitest::Test
|
|||||||
@context = Liquid::Context.new
|
@context = Liquid::Context.new
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def teardown
|
||||||
|
Spy.teardown
|
||||||
|
end
|
||||||
|
|
||||||
def test_variables
|
def test_variables
|
||||||
@context['string'] = 'string'
|
@context['string'] = 'string'
|
||||||
assert_equal 'string', @context['string']
|
assert_equal 'string', @context['string']
|
||||||
@@ -94,12 +98,12 @@ class ContextUnitTest < Minitest::Test
|
|||||||
assert_equal false, @context['bool']
|
assert_equal false, @context['bool']
|
||||||
|
|
||||||
@context['nil'] = nil
|
@context['nil'] = nil
|
||||||
assert_nil @context['nil']
|
assert_equal nil, @context['nil']
|
||||||
assert_nil @context['nil']
|
assert_equal nil, @context['nil']
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_variables_not_existing
|
def test_variables_not_existing
|
||||||
assert_nil @context['does_not_exist']
|
assert_equal nil, @context['does_not_exist']
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_scoping
|
def test_scoping
|
||||||
@@ -181,7 +185,7 @@ class ContextUnitTest < Minitest::Test
|
|||||||
@context['test'] = 'test'
|
@context['test'] = 'test'
|
||||||
assert_equal 'test', @context['test']
|
assert_equal 'test', @context['test']
|
||||||
@context.pop
|
@context.pop
|
||||||
assert_nil @context['test']
|
assert_equal nil, @context['test']
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_hierachical_data
|
def test_hierachical_data
|
||||||
@@ -296,7 +300,7 @@ class ContextUnitTest < Minitest::Test
|
|||||||
@context['hash'] = { 'first' => 'Hello' }
|
@context['hash'] = { 'first' => 'Hello' }
|
||||||
|
|
||||||
assert_equal 1, @context['array.first']
|
assert_equal 1, @context['array.first']
|
||||||
assert_nil @context['array["first"]']
|
assert_equal nil, @context['array["first"]']
|
||||||
assert_equal 'Hello', @context['hash["first"]']
|
assert_equal 'Hello', @context['hash["first"]']
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -446,10 +450,14 @@ class ContextUnitTest < Minitest::Test
|
|||||||
assert_equal @context, @context['category'].context
|
assert_equal @context, @context['category'].context
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_interrupt_avoids_object_allocations
|
def test_use_empty_instead_of_any_in_interrupt_handling_to_avoid_lots_of_unnecessary_object_allocations
|
||||||
assert_no_object_allocations do
|
mock_any = Spy.on_instance_method(Array, :any?)
|
||||||
@context.interrupt?
|
mock_empty = Spy.on_instance_method(Array, :empty?)
|
||||||
end
|
|
||||||
|
@context.interrupt?
|
||||||
|
|
||||||
|
refute mock_any.has_been_called?
|
||||||
|
assert mock_empty.has_been_called?
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_context_initialization_with_a_proc_in_environment
|
def test_context_initialization_with_a_proc_in_environment
|
||||||
@@ -472,18 +480,4 @@ class ContextUnitTest < Minitest::Test
|
|||||||
context = Context.new
|
context = Context.new
|
||||||
assert_equal 'hi', context.apply_global_filter('hi')
|
assert_equal 'hi', context.apply_global_filter('hi')
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def assert_no_object_allocations
|
|
||||||
unless RUBY_ENGINE == 'ruby'
|
|
||||||
skip "stackprof needed to count object allocations"
|
|
||||||
end
|
|
||||||
require 'stackprof'
|
|
||||||
|
|
||||||
profile = StackProf.run(mode: :object) do
|
|
||||||
yield
|
|
||||||
end
|
|
||||||
assert_equal 0, profile[:samples]
|
|
||||||
end
|
|
||||||
end # ContextTest
|
end # ContextTest
|
||||||
|
|||||||
Reference in New Issue
Block a user