mirror of
https://github.com/kemko/liquid.git
synced 2026-01-02 00:05:42 +03:00
Compare commits
36 Commits
reduce-blo
...
v4.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4661700a97 | ||
|
|
cd5a6dd225 | ||
|
|
53b8babf52 | ||
|
|
76b4920d3e | ||
|
|
8dcc319128 | ||
|
|
0b36461d80 | ||
|
|
70e75719de | ||
|
|
b037b19688 | ||
|
|
d0f77f6cf4 | ||
|
|
0be260bc97 | ||
|
|
5f0b64cebc | ||
|
|
c086017bc9 | ||
|
|
4369fe6c85 | ||
|
|
c118e6b435 | ||
|
|
0fbaf873d9 | ||
|
|
5980ddbfae | ||
|
|
193fc0fb7a | ||
|
|
e4da4d49d2 | ||
|
|
a0bec1f873 | ||
|
|
4aa3261518 | ||
|
|
04d552fabb | ||
|
|
5106466a2d | ||
|
|
5d6c1ed7c6 | ||
|
|
a594653a0c | ||
|
|
0c802aba17 | ||
|
|
147d7ae24d | ||
|
|
282d42f98d | ||
|
|
e6ba6ee87b | ||
|
|
2ad7a37d44 | ||
|
|
4bdaaf069f | ||
|
|
85b1e91aed | ||
|
|
a7c5e247c8 | ||
|
|
6c117fd7dd | ||
|
|
deb10ebc7a | ||
|
|
cfe1844de9 | ||
|
|
59950bff87 |
27
.rubocop.yml
27
.rubocop.yml
@@ -7,8 +7,6 @@ AllCops:
|
||||
|
||||
Metrics/BlockNesting:
|
||||
Max: 3
|
||||
Exclude:
|
||||
- 'lib/liquid/block_body.rb'
|
||||
|
||||
Metrics/ModuleLength:
|
||||
Enabled: false
|
||||
@@ -32,7 +30,7 @@ Lint/UnusedBlockArgument:
|
||||
Enabled: false
|
||||
|
||||
Lint/EndAlignment:
|
||||
AlignWith: variable
|
||||
EnforcedStyleAlignWith: variable
|
||||
|
||||
Lint/UnusedMethodArgument:
|
||||
Enabled: false
|
||||
@@ -61,10 +59,10 @@ Style/BracesAroundHashParameters:
|
||||
Style/NumericLiterals:
|
||||
Enabled: false
|
||||
|
||||
Style/SpaceInsideBrackets:
|
||||
Layout/SpaceInsideBrackets:
|
||||
Enabled: false
|
||||
|
||||
Style/SpaceBeforeBlockBraces:
|
||||
Layout/SpaceBeforeBlockBraces:
|
||||
Enabled: false
|
||||
|
||||
Style/Documentation:
|
||||
@@ -73,19 +71,22 @@ Style/Documentation:
|
||||
Style/ClassAndModuleChildren:
|
||||
Enabled: false
|
||||
|
||||
Style/TrailingComma:
|
||||
Style/TrailingCommaInArrayLiteral:
|
||||
Enabled: false
|
||||
|
||||
Style/IndentHash:
|
||||
Style/TrailingCommaInHashLiteral:
|
||||
Enabled: false
|
||||
|
||||
Layout/IndentHash:
|
||||
EnforcedStyle: consistent
|
||||
|
||||
Style/FormatString:
|
||||
Enabled: false
|
||||
|
||||
Style/AlignParameters:
|
||||
Layout/AlignParameters:
|
||||
EnforcedStyle: with_fixed_indentation
|
||||
|
||||
Style/MultilineOperationIndentation:
|
||||
Layout/MultilineOperationIndentation:
|
||||
EnforcedStyle: indented
|
||||
|
||||
Style/IfUnlessModifier:
|
||||
@@ -94,7 +95,7 @@ Style/IfUnlessModifier:
|
||||
Style/RaiseArgs:
|
||||
Enabled: false
|
||||
|
||||
Style/DeprecatedHashMethods:
|
||||
Style/PreferredHashMethods:
|
||||
Enabled: false
|
||||
|
||||
Style/RegexpLiteral:
|
||||
@@ -109,7 +110,7 @@ Performance/Count:
|
||||
Style/ConstantName:
|
||||
Enabled: false
|
||||
|
||||
Style/CaseIndentation:
|
||||
Layout/CaseIndentation:
|
||||
Enabled: false
|
||||
|
||||
Style/ClassVars:
|
||||
@@ -123,3 +124,7 @@ Style/TrivialAccessors:
|
||||
|
||||
Style/WordArray:
|
||||
Enabled: false
|
||||
|
||||
Naming/MethodName:
|
||||
Exclude:
|
||||
- 'example/server/liquid_servlet.rb'
|
||||
|
||||
@@ -1,72 +1,248 @@
|
||||
# This configuration was generated by `rubocop --auto-gen-config`
|
||||
# on 2015-06-08 18:16:16 +0000 using RuboCop version 0.32.0.
|
||||
# This configuration was generated by
|
||||
# `rubocop --auto-gen-config`
|
||||
# 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
|
||||
# one by one as the offenses are removed from the code base.
|
||||
# Note that changes in the inspected code, or installation of new
|
||||
# versions of RuboCop, may require this file to be generated again.
|
||||
|
||||
# Offense count: 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
|
||||
Lint/NestedMethodDefinition:
|
||||
Enabled: false
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
# 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: 53
|
||||
# Offense count: 6
|
||||
# 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:
|
||||
Max: 58
|
||||
Max: 56
|
||||
|
||||
# Offense count: 12
|
||||
# Offense count: 13
|
||||
Metrics/CyclomaticComplexity:
|
||||
Max: 15
|
||||
Max: 12
|
||||
|
||||
# Offense count: 553
|
||||
# Configuration parameters: AllowURI, URISchemes.
|
||||
# Offense count: 620
|
||||
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
|
||||
# URISchemes: http, https
|
||||
Metrics/LineLength:
|
||||
Max: 294
|
||||
|
||||
# Offense count: 77
|
||||
# Offense count: 102
|
||||
# Configuration parameters: CountComments.
|
||||
Metrics/MethodLength:
|
||||
Max: 46
|
||||
Max: 37
|
||||
|
||||
# Offense count: 6
|
||||
# Offense count: 9
|
||||
Metrics/PerceivedComplexity:
|
||||
Max: 13
|
||||
Max: 11
|
||||
|
||||
# Offense count: 1
|
||||
Style/AccessorMethodName:
|
||||
Enabled: false
|
||||
# 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.
|
||||
Style/ClosingParenthesisIndentation:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 3
|
||||
# Configuration parameters: MinBodyLength.
|
||||
Style/GuardClause:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 4
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
Style/MethodName:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 1
|
||||
Style/MultilineBlockChain:
|
||||
Enabled: false
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles, SingleLineConditionsOnly, IncludeTernaryExpressions.
|
||||
# SupportedStyles: assign_to_condition, assign_inside_condition
|
||||
Style/ConditionalAssignment:
|
||||
Exclude:
|
||||
- 'lib/liquid/errors.rb'
|
||||
|
||||
# Offense count: 2
|
||||
# Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles.
|
||||
Style/Next:
|
||||
Enabled: false
|
||||
# Cop supports --auto-correct.
|
||||
Style/EmptyCaseCondition:
|
||||
Exclude:
|
||||
- 'lib/liquid/block_body.rb'
|
||||
- 'lib/liquid/lexer.rb'
|
||||
|
||||
# Offense count: 7
|
||||
# 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
|
||||
# Cop supports --auto-correct.
|
||||
Style/MutableConstant:
|
||||
Exclude:
|
||||
- 'lib/liquid/expression.rb'
|
||||
- 'lib/liquid/lexer.rb'
|
||||
- 'lib/liquid/standardfilters.rb'
|
||||
- 'lib/liquid/tags/if.rb'
|
||||
- 'lib/liquid/variable_lookup.rb'
|
||||
- 'lib/liquid/version.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles.
|
||||
# SupportedStyles: skip_modifier_ifs, always
|
||||
Style/Next:
|
||||
Exclude:
|
||||
- 'lib/liquid/tags/for.rb'
|
||||
|
||||
# Offense count: 4
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: AutoCorrect, EnforcedStyle, SupportedStyles.
|
||||
# SupportedStyles: predicate, comparison
|
||||
Style/NumericPredicate:
|
||||
Exclude:
|
||||
- 'spec/**/*'
|
||||
- 'lib/liquid/context.rb'
|
||||
- 'lib/liquid/forloop_drop.rb'
|
||||
- 'lib/liquid/standardfilters.rb'
|
||||
- 'lib/liquid/tablerowloop_drop.rb'
|
||||
|
||||
# Offense count: 14
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: PreferredDelimiters.
|
||||
Style/PercentLiteralDelimiters:
|
||||
Exclude:
|
||||
- 'lib/liquid/tags/if.rb'
|
||||
- 'liquid.gemspec'
|
||||
- 'test/integration/assign_test.rb'
|
||||
- 'test/integration/standard_filter_test.rb'
|
||||
|
||||
# Offense count: 2
|
||||
# Cop supports --auto-correct.
|
||||
Style/RedundantParentheses:
|
||||
Exclude:
|
||||
- 'test/unit/condition_unit_test.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# Cop supports --auto-correct.
|
||||
Style/RedundantSelf:
|
||||
Exclude:
|
||||
- 'lib/liquid/strainer.rb'
|
||||
|
||||
# Offense count: 9
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: AllowAsExpressionSeparator.
|
||||
Style/Semicolon:
|
||||
Enabled: false
|
||||
Exclude:
|
||||
- 'test/integration/error_handling_test.rb'
|
||||
- 'test/integration/template_test.rb'
|
||||
- 'test/unit/context_unit_test.rb'
|
||||
|
||||
# Offense count: 7
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: MinSize, SupportedStyles.
|
||||
# SupportedStyles: percent, brackets
|
||||
Style/SymbolArray:
|
||||
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
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: MaxLineLength.
|
||||
Style/WhileUntilModifier:
|
||||
Enabled: false
|
||||
Exclude:
|
||||
- 'lib/liquid/tags/case.rb'
|
||||
|
||||
@@ -3,7 +3,9 @@ language: ruby
|
||||
rvm:
|
||||
- 2.1
|
||||
- 2.2
|
||||
- 2.3.3
|
||||
- 2.3
|
||||
- 2.4
|
||||
- 2.5
|
||||
- ruby-head
|
||||
- jruby-head
|
||||
# - rbx-2
|
||||
@@ -17,13 +19,14 @@ addons:
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- rvm: ruby-head
|
||||
- rvm: jruby-head
|
||||
|
||||
install:
|
||||
- gem install rainbow -v 2.2.1
|
||||
- bundle install
|
||||
|
||||
script: "bundle exec rake"
|
||||
script: bundle exec rake
|
||||
|
||||
notifications:
|
||||
disable: true
|
||||
|
||||
7
Gemfile
7
Gemfile
@@ -1,4 +1,7 @@
|
||||
source 'https://rubygems.org'
|
||||
git_source(:github) do |repo_name|
|
||||
"https://github.com/#{repo_name}.git"
|
||||
end
|
||||
|
||||
gemspec
|
||||
|
||||
@@ -9,9 +12,9 @@ group :benchmark, :test do
|
||||
end
|
||||
|
||||
group :test do
|
||||
gem 'rubocop', '0.34.2'
|
||||
gem 'rubocop', '~> 0.49.0'
|
||||
|
||||
platform :mri do
|
||||
gem 'liquid-c', github: 'Shopify/liquid-c', ref: 'bd53db95de3d44d631e7c5a267c3d934e66107dd'
|
||||
gem 'liquid-c', github: 'Shopify/liquid-c', ref: '9168659de45d6d576fce30c735f857e597fa26f6'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -42,6 +42,8 @@ Liquid is a template engine which was written with very specific requirements:
|
||||
|
||||
## 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.
|
||||
For standard use you can just pass it the content of a file and call render with a parameters hash.
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
machine:
|
||||
ruby:
|
||||
version: ruby-2.1
|
||||
@@ -26,12 +26,12 @@ module Liquid
|
||||
end
|
||||
|
||||
def unknown_tag(tag, _params, _tokens)
|
||||
case tag
|
||||
when 'else'.freeze
|
||||
if tag == 'else'.freeze
|
||||
raise SyntaxError.new(parse_context.locale.t("errors.syntax.unexpected_else".freeze,
|
||||
block_name: block_name))
|
||||
when 'end'.freeze
|
||||
elsif tag.start_with?('end'.freeze)
|
||||
raise SyntaxError.new(parse_context.locale.t("errors.syntax.invalid_delimiter".freeze,
|
||||
tag: tag,
|
||||
block_name: block_name,
|
||||
block_delimiter: block_delimiter))
|
||||
else
|
||||
|
||||
@@ -2,6 +2,7 @@ module Liquid
|
||||
class BlockBody
|
||||
FullToken = /\A#{TagStart}#{WhitespaceControl}?\s*(\w+)\s*(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
|
||||
ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om
|
||||
WhitespaceOrNothing = /\A\s*\z/
|
||||
TAGSTART = "{%".freeze
|
||||
VARSTART = "{{".freeze
|
||||
|
||||
@@ -15,38 +16,35 @@ module Liquid
|
||||
def parse(tokenizer, parse_context)
|
||||
parse_context.line_number = tokenizer.line_number
|
||||
while token = tokenizer.shift
|
||||
unless token.empty?
|
||||
case
|
||||
when token.start_with?(TAGSTART)
|
||||
whitespace_handler(token, parse_context)
|
||||
if token =~ FullToken
|
||||
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/)
|
||||
next if token.empty?
|
||||
case
|
||||
when token.start_with?(TAGSTART)
|
||||
whitespace_handler(token, parse_context)
|
||||
unless token =~ FullToken
|
||||
raise_missing_tag_terminator(token, parse_context)
|
||||
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
|
||||
parse_context.line_number = tokenizer.line_number
|
||||
end
|
||||
@@ -72,33 +70,27 @@ module Liquid
|
||||
output = []
|
||||
context.resource_limits.render_score += @nodelist.length
|
||||
|
||||
@nodelist.each do |token|
|
||||
# Break out if we have any unhanded interrupts.
|
||||
break if context.interrupt?
|
||||
|
||||
begin
|
||||
idx = 0
|
||||
while node = @nodelist[idx]
|
||||
case node
|
||||
when String
|
||||
check_resources(context, node)
|
||||
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
|
||||
# Interrupt is any command that stops block execution such as {% break %}
|
||||
# or {% continue %}
|
||||
if token.is_a?(Continue) || token.is_a?(Break)
|
||||
context.push_interrupt(token.interrupt)
|
||||
break
|
||||
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)
|
||||
context.push_interrupt(node.interrupt)
|
||||
break
|
||||
else # Other non-Block tags
|
||||
render_node_to_output(node, output, context)
|
||||
end
|
||||
idx += 1
|
||||
end
|
||||
|
||||
output.join
|
||||
@@ -106,15 +98,25 @@ module Liquid
|
||||
|
||||
private
|
||||
|
||||
def render_node(node, context)
|
||||
node_output = node.is_a?(String) ? node : node.render(context)
|
||||
def render_node_to_output(node, output, context, skip_output = false)
|
||||
node_output = node.render(context)
|
||||
node_output = node_output.is_a?(Array) ? node_output.join : node_output.to_s
|
||||
check_resources(context, node_output)
|
||||
output << node_output unless skip_output
|
||||
rescue MemoryError => e
|
||||
raise e
|
||||
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
|
||||
if context.resource_limits.reached?
|
||||
raise MemoryError.new("Memory limits exceeded".freeze)
|
||||
end
|
||||
node_output
|
||||
return unless context.resource_limits.reached?
|
||||
raise MemoryError.new("Memory limits exceeded".freeze)
|
||||
end
|
||||
|
||||
def create_variable(token, parse_context)
|
||||
|
||||
@@ -21,20 +21,24 @@ module Liquid
|
||||
'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)
|
||||
if LITERALS.key?(markup)
|
||||
LITERALS[markup]
|
||||
else
|
||||
case markup
|
||||
when /\A'(.*)'\z/m # Single quoted strings
|
||||
when SINGLE_QUOTED_STRING, DOUBLE_QUOTED_STRING
|
||||
$1
|
||||
when /\A"(.*)"\z/m # Double quoted strings
|
||||
$1
|
||||
when /\A(-?\d+)\z/ # Integer and floats
|
||||
when INTEGERS_REGEX
|
||||
$1.to_i
|
||||
when /\A\((\S+)\.\.(\S+)\)\z/ # Ranges
|
||||
when RANGES_REGEX
|
||||
RangeLookup.parse($1, $2)
|
||||
when /\A(-?\d[\d\.]+)\z/ # Floats
|
||||
when FLOATS_REGEX
|
||||
$1.to_f
|
||||
else
|
||||
VariableLookup.parse(markup)
|
||||
|
||||
@@ -19,6 +19,7 @@ module Liquid
|
||||
NUMBER_LITERAL = /-?\d+(\.\d+)?/
|
||||
DOTDOT = /\.\./
|
||||
COMPARISON_OPERATOR = /==|!=|<>|<=?|>=?|contains(?=\s)/
|
||||
WHITESPACE_OR_NOTHING = /\s*/
|
||||
|
||||
def initialize(input)
|
||||
@ss = StringScanner.new(input)
|
||||
@@ -28,7 +29,7 @@ module Liquid
|
||||
@output = []
|
||||
|
||||
until @ss.eos?
|
||||
@ss.skip(/\s*/)
|
||||
@ss.skip(WHITESPACE_OR_NOTHING)
|
||||
break if @ss.eos?
|
||||
tok = case
|
||||
when t = @ss.scan(COMPARISON_OPERATOR) then [:comparison, t]
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
if: "Syntax Error in tag 'if' - Valid syntax: if [expression]"
|
||||
include: "Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]"
|
||||
unknown_tag: "Unknown tag '%{tag}'"
|
||||
invalid_delimiter: "'end' is not a valid delimiter for %{block_name} tags. use %{block_delimiter}"
|
||||
invalid_delimiter: "'%{tag}' is not a valid delimiter for %{block_name} tags. use %{block_delimiter}"
|
||||
unexpected_else: "%{block_name} tag does not expect 'else' tag"
|
||||
unexpected_outer_tag: "Unexpected outer '%{tag}' tag"
|
||||
tag_termination: "Tag '%{token}' was not properly terminated with regexp: %{tag_end}"
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
module Liquid
|
||||
class BlockBody
|
||||
def render_node_with_profiling(node, context)
|
||||
def render_node_with_profiling(node, output, context, skip_output = false)
|
||||
Profiler.profile_node_render(node) do
|
||||
render_node_without_profiling(node, context)
|
||||
render_node_without_profiling(node, output, context, skip_output)
|
||||
end
|
||||
end
|
||||
|
||||
alias_method :render_node_without_profiling, :render_node
|
||||
alias_method :render_node, :render_node_with_profiling
|
||||
alias_method :render_node_without_profiling, :render_node_to_output
|
||||
alias_method :render_node_to_output, :render_node_with_profiling
|
||||
end
|
||||
|
||||
class Include < Tag
|
||||
|
||||
@@ -121,17 +121,23 @@ module Liquid
|
||||
def sort(input, property = nil)
|
||||
ary = InputIterator.new(input)
|
||||
if property.nil?
|
||||
ary.sort
|
||||
ary.sort do |a, b|
|
||||
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.first.respond_to?(:[]) && !ary.first[property].nil?
|
||||
elsif ary.all? { |el| el.respond_to?(:[]) }
|
||||
ary.sort do |a, b|
|
||||
a = a[property]
|
||||
b = b[property]
|
||||
if a && b
|
||||
if !a.nil? && !b.nil?
|
||||
a <=> b
|
||||
else
|
||||
a ? -1 : 1
|
||||
a.nil? ? 1 : -1
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -143,11 +149,25 @@ module Liquid
|
||||
ary = InputIterator.new(input)
|
||||
|
||||
if property.nil?
|
||||
ary.sort { |a, b| a.casecmp(b) }
|
||||
ary.sort do |a, 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.first.respond_to?(:[]) && !ary.first[property].nil?
|
||||
ary.sort { |a, b| a[property].casecmp(b[property]) }
|
||||
elsif ary.all? { |el| el.respond_to?(:[]) }
|
||||
ary.sort do |a, b|
|
||||
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
|
||||
|
||||
@@ -201,14 +221,12 @@ module Liquid
|
||||
|
||||
# Replace occurrences of a string with another
|
||||
def replace(input, string, replacement = ''.freeze)
|
||||
replacement = replacement.to_s
|
||||
input.to_s.gsub(string.to_s) { replacement }
|
||||
input.to_s.gsub(string.to_s, replacement.to_s)
|
||||
end
|
||||
|
||||
# Replace the first occurrences of a string with another
|
||||
def replace_first(input, string, replacement = ''.freeze)
|
||||
replacement = replacement.to_s
|
||||
input.to_s.sub(string.to_s) { replacement }
|
||||
input.to_s.sub(string.to_s, replacement.to_s)
|
||||
end
|
||||
|
||||
# remove a substring
|
||||
@@ -355,6 +373,22 @@ module Liquid
|
||||
raise Liquid::FloatDomainError, e.message
|
||||
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)
|
||||
if !input || input.respond_to?(:empty?) && input.empty?
|
||||
default_value
|
||||
|
||||
@@ -156,7 +156,7 @@ module Liquid
|
||||
begin
|
||||
context['forloop'.freeze] = loop_vars
|
||||
|
||||
segment.each_with_index do |item, index|
|
||||
segment.each do |item|
|
||||
context[@variable_name] = item
|
||||
result << @for_block.render(context)
|
||||
loop_vars.send(:increment!)
|
||||
|
||||
@@ -33,7 +33,7 @@ module Liquid
|
||||
tablerowloop = Liquid::TablerowloopDrop.new(length, cols)
|
||||
context['tablerowloop'.freeze] = tablerowloop
|
||||
|
||||
collection.each_with_index do |item, index|
|
||||
collection.each do |item|
|
||||
context[@variable_name] = item
|
||||
|
||||
result << "<td class=\"col#{tablerowloop.col}\">" << super << '</td>'
|
||||
|
||||
@@ -46,11 +46,11 @@ module Liquid
|
||||
def self.to_number(obj)
|
||||
case obj
|
||||
when Float
|
||||
BigDecimal.new(obj.to_s)
|
||||
BigDecimal(obj.to_s)
|
||||
when Numeric
|
||||
obj
|
||||
when String
|
||||
(obj.strip =~ /\A-?\d+\.\d+\z/) ? BigDecimal.new(obj) : obj.to_i
|
||||
(obj.strip =~ /\A-?\d+\.\d+\z/) ? BigDecimal(obj) : obj.to_i
|
||||
else
|
||||
if obj.respond_to?(:to_number)
|
||||
obj.to_number
|
||||
|
||||
@@ -10,10 +10,16 @@ module Liquid
|
||||
# {{ user | link }}
|
||||
#
|
||||
class Variable
|
||||
FilterMarkupRegex = /#{FilterSeparator}\s*(.*)/om
|
||||
FilterParser = /(?:\s+|#{QuotedFragment}|#{ArgumentSeparator})+/o
|
||||
FilterArgsRegex = /(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o
|
||||
JustTagAttributes = /\A#{TagAttributes}\z/o
|
||||
MarkupWithQuotedFragment = /(#{QuotedFragment})(.*)/om
|
||||
|
||||
attr_accessor :filters, :name, :line_number
|
||||
attr_reader :parse_context
|
||||
alias_method :options, :parse_context
|
||||
|
||||
include ParserSwitching
|
||||
|
||||
def initialize(markup, parse_context)
|
||||
@@ -35,17 +41,17 @@ module Liquid
|
||||
|
||||
def lax_parse(markup)
|
||||
@filters = []
|
||||
return unless markup =~ /(#{QuotedFragment})(.*)/om
|
||||
return unless markup =~ MarkupWithQuotedFragment
|
||||
|
||||
name_markup = $1
|
||||
filter_markup = $2
|
||||
@name = Expression.parse(name_markup)
|
||||
if filter_markup =~ /#{FilterSeparator}\s*(.*)/om
|
||||
if filter_markup =~ FilterMarkupRegex
|
||||
filters = $1.scan(FilterParser)
|
||||
filters.each do |f|
|
||||
next unless f =~ /\w+/
|
||||
filtername = Regexp.last_match(0)
|
||||
filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o).flatten
|
||||
filterargs = f.scan(FilterArgsRegex).flatten
|
||||
@filters << parse_filter_expressions(filtername, filterargs)
|
||||
end
|
||||
end
|
||||
@@ -91,7 +97,7 @@ module Liquid
|
||||
filter_args = []
|
||||
keyword_args = {}
|
||||
unparsed_args.each do |a|
|
||||
if matches = a.match(/\A#{TagAttributes}\z/o)
|
||||
if matches = a.match(JustTagAttributes)
|
||||
keyword_args[matches[1]] = Expression.parse(matches[2])
|
||||
else
|
||||
filter_args << Expression.parse(a)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# encoding: utf-8
|
||||
module Liquid
|
||||
VERSION = "4.0.0"
|
||||
VERSION = "4.0.1"
|
||||
end
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# encoding: utf-8
|
||||
|
||||
lib = File.expand_path('../lib/', __FILE__)
|
||||
$LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib)
|
||||
|
||||
|
||||
12
test/integration/block_test.rb
Normal file
12
test/integration/block_test.rb
Normal file
@@ -0,0 +1,12 @@
|
||||
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
|
||||
@@ -190,6 +190,11 @@ 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")
|
||||
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
|
||||
input = [
|
||||
{ "price" => 4, "handle" => "alpha" },
|
||||
@@ -208,6 +213,57 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_equal expectation, @filters.sort(input, "price")
|
||||
end
|
||||
|
||||
def test_sort_natural
|
||||
assert_equal ["a", "B", "c", "D"], @filters.sort_natural(["c", "D", "a", "B"])
|
||||
assert_equal [{ "a" => "a" }, { "a" => "B" }, { "a" => "c" }, { "a" => "D" }], @filters.sort_natural([{ "a" => "D" }, { "a" => "c" }, { "a" => "a" }, { "a" => "B" }], "a")
|
||||
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
|
||||
assert_equal [], @filters.sort([], "a")
|
||||
end
|
||||
@@ -362,10 +418,8 @@ class StandardFiltersTest < Minitest::Test
|
||||
def test_replace
|
||||
assert_equal '2 2 2 2', @filters.replace('1 1 1 1', '1', 2)
|
||||
assert_equal '2 2 2 2', @filters.replace('1 1 1 1', 1, 2)
|
||||
assert_equal "\\& \\& \\& \\&", @filters.replace('1 1 1 1', '1', "\\&")
|
||||
assert_equal '2 1 1 1', @filters.replace_first('1 1 1 1', '1', 2)
|
||||
assert_equal '2 1 1 1', @filters.replace_first('1 1 1 1', 1, 2)
|
||||
assert_equal '\\& 1 1 1', @filters.replace_first("1 1 1 1", '1', "\\&")
|
||||
assert_template_result '2 1 1 1', "{{ '1 1 1 1' | replace_first: '1', 2 }}"
|
||||
end
|
||||
|
||||
@@ -498,6 +552,28 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_template_result "5", "{{ price | floor }}", 'price' => NumberLikeThing.new(5.4)
|
||||
end
|
||||
|
||||
def test_at_most
|
||||
assert_template_result "4", "{{ 5 | at_most:4 }}"
|
||||
assert_template_result "5", "{{ 5 | at_most:5 }}"
|
||||
assert_template_result "5", "{{ 5 | at_most:6 }}"
|
||||
|
||||
assert_template_result "4.5", "{{ 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
|
||||
assigns = { 'a' => 'bc', 'b' => 'd' }
|
||||
assert_template_result('bcd', "{{ a | append: 'd'}}", assigns)
|
||||
|
||||
@@ -159,7 +159,7 @@ HERE
|
||||
assert_template_result(expected, markup, assigns)
|
||||
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] } }
|
||||
markup = <<-MKUP
|
||||
{%for i in array.items limit:3 %}{{i}}{%endfor%}
|
||||
@@ -178,7 +178,7 @@ HERE
|
||||
assert_template_result(expected, markup, assigns)
|
||||
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] } }
|
||||
markup = '{%for i in array.items limit:3 %}{{i}}{%endfor%}
|
||||
next
|
||||
|
||||
@@ -496,6 +496,10 @@ class TrimModeTest < Minitest::Test
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_right_trim_followed_by_tag
|
||||
assert_template_result('ab c', '{{ "a" -}}{{ "b" }} c')
|
||||
end
|
||||
|
||||
def test_raw_output
|
||||
whitespace = ' '
|
||||
text = <<-END_TEMPLATE
|
||||
|
||||
Reference in New Issue
Block a user