mirror of
https://github.com/kemko/liquid.git
synced 2026-01-02 00:05:42 +03:00
Compare commits
1 Commits
implicit-v
...
filter-err
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
169dfe5af3 |
2
.github/probots.yml
vendored
2
.github/probots.yml
vendored
@@ -1,2 +0,0 @@
|
||||
enabled:
|
||||
- cla
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,4 +6,3 @@ pkg
|
||||
.rvmrc
|
||||
.ruby-version
|
||||
Gemfile.lock
|
||||
.bundle
|
||||
|
||||
64
.rubocop.yml
64
.rubocop.yml
@@ -1,54 +1,24 @@
|
||||
inherit_from:
|
||||
- .rubocop_todo.yml
|
||||
- ./.rubocop_todo.yml
|
||||
inherit_from: ./.rubocop_todo.yml
|
||||
|
||||
AllCops:
|
||||
Exclude:
|
||||
- 'performance/shopify/*'
|
||||
- 'pkg/**'
|
||||
|
||||
Metrics/BlockNesting:
|
||||
Max: 3
|
||||
|
||||
Metrics/ModuleLength:
|
||||
Enabled: false
|
||||
|
||||
Metrics/ClassLength:
|
||||
Enabled: false
|
||||
|
||||
Lint/AssignmentInCondition:
|
||||
Enabled: false
|
||||
|
||||
Lint/AmbiguousOperator:
|
||||
Enabled: false
|
||||
|
||||
Lint/AmbiguousRegexpLiteral:
|
||||
Enabled: false
|
||||
|
||||
Lint/ParenthesesAsGroupedExpression:
|
||||
Enabled: false
|
||||
|
||||
Lint/UnusedBlockArgument:
|
||||
Enabled: false
|
||||
|
||||
Layout/EndAlignment:
|
||||
EnforcedStyleAlignWith: variable
|
||||
|
||||
Lint/UnusedMethodArgument:
|
||||
Enabled: false
|
||||
Lint/EndAlignment:
|
||||
AlignWith: variable
|
||||
|
||||
Style/SingleLineBlockParams:
|
||||
Enabled: false
|
||||
|
||||
Style/DoubleNegation:
|
||||
Enabled: false
|
||||
|
||||
Style/StringLiteralsInInterpolation:
|
||||
Enabled: false
|
||||
|
||||
Style/AndOr:
|
||||
Enabled: false
|
||||
|
||||
Style/SignalException:
|
||||
Enabled: false
|
||||
|
||||
@@ -61,10 +31,10 @@ Style/BracesAroundHashParameters:
|
||||
Style/NumericLiterals:
|
||||
Enabled: false
|
||||
|
||||
Layout/SpaceInsideArrayLiteralBrackets:
|
||||
Style/SpaceInsideBrackets:
|
||||
Enabled: false
|
||||
|
||||
Layout/SpaceBeforeBlockBraces:
|
||||
Style/SpaceBeforeBlockBraces:
|
||||
Enabled: false
|
||||
|
||||
Style/Documentation:
|
||||
@@ -73,22 +43,19 @@ Style/Documentation:
|
||||
Style/ClassAndModuleChildren:
|
||||
Enabled: false
|
||||
|
||||
Style/TrailingCommaInArrayLiteral:
|
||||
Style/TrailingComma:
|
||||
Enabled: false
|
||||
|
||||
Style/TrailingCommaInHashLiteral:
|
||||
Enabled: false
|
||||
|
||||
Layout/IndentHash:
|
||||
Style/IndentHash:
|
||||
EnforcedStyle: consistent
|
||||
|
||||
Style/FormatString:
|
||||
Enabled: false
|
||||
|
||||
Layout/AlignParameters:
|
||||
Style/AlignParameters:
|
||||
EnforcedStyle: with_fixed_indentation
|
||||
|
||||
Layout/MultilineOperationIndentation:
|
||||
Style/MultilineOperationIndentation:
|
||||
EnforcedStyle: indented
|
||||
|
||||
Style/IfUnlessModifier:
|
||||
@@ -97,7 +64,7 @@ Style/IfUnlessModifier:
|
||||
Style/RaiseArgs:
|
||||
Enabled: false
|
||||
|
||||
Style/PreferredHashMethods:
|
||||
Style/DeprecatedHashMethods:
|
||||
Enabled: false
|
||||
|
||||
Style/RegexpLiteral:
|
||||
@@ -109,10 +76,10 @@ Style/SymbolLiteral:
|
||||
Performance/Count:
|
||||
Enabled: false
|
||||
|
||||
Naming/ConstantName:
|
||||
Style/ConstantName:
|
||||
Enabled: false
|
||||
|
||||
Layout/CaseIndentation:
|
||||
Style/CaseIndentation:
|
||||
Enabled: false
|
||||
|
||||
Style/ClassVars:
|
||||
@@ -121,12 +88,5 @@ Style/ClassVars:
|
||||
Style/PerlBackrefs:
|
||||
Enabled: false
|
||||
|
||||
Style/TrivialAccessors:
|
||||
AllowPredicates: true
|
||||
|
||||
Style/WordArray:
|
||||
Enabled: false
|
||||
|
||||
Naming/MethodName:
|
||||
Exclude:
|
||||
- 'example/server/liquid_servlet.rb'
|
||||
|
||||
@@ -1,260 +1,142 @@
|
||||
# This configuration was generated by
|
||||
# `rubocop --auto-gen-config`
|
||||
# on 2019-03-19 11:04:37 -0400 using RuboCop version 0.53.0.
|
||||
# This configuration was generated by `rubocop --auto-gen-config`
|
||||
# on 2015-05-13 19:54:01 +0000 using RuboCop version 0.31.0.
|
||||
# The point is for the user to remove these configuration records
|
||||
# one by one as the offenses are removed from the code base.
|
||||
# Note that changes in the inspected code, or installation of new
|
||||
# versions of RuboCop, may require this file to be generated again.
|
||||
|
||||
# Offense count: 1
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: Include, TreatCommentsAsGroupSeparators.
|
||||
# Include: **/*.gemspec
|
||||
Gemspec/OrderedDependencies:
|
||||
Exclude:
|
||||
- 'liquid.gemspec'
|
||||
# Offense count: 2
|
||||
Lint/AmbiguousOperator:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 5
|
||||
# Offense count: 2
|
||||
Lint/AmbiguousRegexpLiteral:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 1
|
||||
Lint/Eval:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 4
|
||||
Lint/ParenthesesAsGroupedExpression:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 3
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
# SupportedStyles: auto_detection, squiggly, active_support, powerpack, unindent
|
||||
Layout/IndentHeredoc:
|
||||
Exclude:
|
||||
- 'test/integration/tags/for_tag_test.rb'
|
||||
- 'test/integration/trim_mode_test.rb'
|
||||
Lint/UnusedMethodArgument:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 1
|
||||
Lint/UselessAccessModifier:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 6
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
# SupportedStyles: symmetrical, new_line, same_line
|
||||
Layout/MultilineMethodCallBraceLayout:
|
||||
Exclude:
|
||||
- 'test/integration/error_handling_test.rb'
|
||||
- 'test/unit/strainer_unit_test.rb'
|
||||
Lint/UselessAssignment:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 51
|
||||
Metrics/AbcSize:
|
||||
Max: 59
|
||||
|
||||
# Offense count: 2
|
||||
Metrics/BlockNesting:
|
||||
Max: 6
|
||||
|
||||
# Offense count: 16
|
||||
# Configuration parameters: CountComments.
|
||||
Metrics/ClassLength:
|
||||
Max: 304
|
||||
|
||||
# Offense count: 10
|
||||
Metrics/CyclomaticComplexity:
|
||||
Max: 15
|
||||
|
||||
# Offense count: 547
|
||||
# Configuration parameters: AllowURI, URISchemes.
|
||||
Metrics/LineLength:
|
||||
Max: 294
|
||||
|
||||
# Offense count: 76
|
||||
# Configuration parameters: CountComments.
|
||||
Metrics/MethodLength:
|
||||
Max: 46
|
||||
|
||||
# Offense count: 1
|
||||
# Configuration parameters: CountComments.
|
||||
Metrics/ModuleLength:
|
||||
Max: 216
|
||||
|
||||
# Offense count: 6
|
||||
Metrics/PerceivedComplexity:
|
||||
Max: 13
|
||||
|
||||
# Offense count: 1
|
||||
Style/AccessorMethodName:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 2
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
# SupportedStyles: runtime_error, standard_error
|
||||
Lint/InheritException:
|
||||
Exclude:
|
||||
- 'lib/liquid/interrupts.rb'
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
Style/AndOr:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 2
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles, ProceduralMethods, FunctionalMethods, IgnoredMethods.
|
||||
Style/BlockDelimiters:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 1
|
||||
# Configuration parameters: CheckForMethodsWithNoSideEffects.
|
||||
Lint/Void:
|
||||
Exclude:
|
||||
- 'lib/liquid/parse_context.rb'
|
||||
|
||||
# Offense count: 54
|
||||
Metrics/AbcSize:
|
||||
Max: 56
|
||||
|
||||
# Offense count: 12
|
||||
Metrics/CyclomaticComplexity:
|
||||
Max: 12
|
||||
|
||||
# Offense count: 112
|
||||
# Configuration parameters: CountComments.
|
||||
Metrics/MethodLength:
|
||||
Max: 37
|
||||
|
||||
# Offense count: 8
|
||||
Metrics/PerceivedComplexity:
|
||||
Max: 11
|
||||
|
||||
# Offense count: 52
|
||||
# Configuration parameters: Blacklist.
|
||||
# Blacklist: END, (?-mix:EO[A-Z]{1})
|
||||
Naming/HeredocDelimiterNaming:
|
||||
Exclude:
|
||||
- 'test/integration/assign_test.rb'
|
||||
- 'test/integration/capture_test.rb'
|
||||
- 'test/integration/trim_mode_test.rb'
|
||||
|
||||
# Offense count: 23
|
||||
# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
|
||||
# AllowedNames: io, id
|
||||
Naming/UncommunicativeMethodParamName:
|
||||
Exclude:
|
||||
- 'example/server/example_servlet.rb'
|
||||
- 'lib/liquid/condition.rb'
|
||||
- 'lib/liquid/context.rb'
|
||||
- 'lib/liquid/standardfilters.rb'
|
||||
- 'lib/liquid/tags/if.rb'
|
||||
- 'lib/liquid/utils.rb'
|
||||
- 'lib/liquid/variable.rb'
|
||||
- 'test/integration/filter_test.rb'
|
||||
- 'test/integration/standard_filter_test.rb'
|
||||
- 'test/integration/tags/for_tag_test.rb'
|
||||
- 'test/integration/template_test.rb'
|
||||
- 'test/unit/condition_unit_test.rb'
|
||||
|
||||
# Offense count: 10
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
# SupportedStyles: prefer_alias, prefer_alias_method
|
||||
Style/Alias:
|
||||
Exclude:
|
||||
- 'lib/liquid/drop.rb'
|
||||
- 'lib/liquid/i18n.rb'
|
||||
- 'lib/liquid/profiler/hooks.rb'
|
||||
- 'lib/liquid/standardfilters.rb'
|
||||
- 'lib/liquid/tag.rb'
|
||||
- 'lib/liquid/tags/include.rb'
|
||||
- 'lib/liquid/variable.rb'
|
||||
|
||||
# Offense count: 22
|
||||
Style/CommentedKeyword:
|
||||
Style/DoubleNegation:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 1
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions.
|
||||
# SupportedStyles: assign_to_condition, assign_inside_condition
|
||||
Style/ConditionalAssignment:
|
||||
Exclude:
|
||||
- 'lib/liquid/errors.rb'
|
||||
|
||||
# Offense count: 1
|
||||
Style/DateTime:
|
||||
Exclude:
|
||||
- 'test/unit/context_unit_test.rb'
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
Style/EmptyLinesAroundClassBody:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 2
|
||||
# Cop supports --auto-correct.
|
||||
Style/EmptyCaseCondition:
|
||||
Exclude:
|
||||
- 'lib/liquid/block_body.rb'
|
||||
- 'lib/liquid/lexer.rb'
|
||||
|
||||
# Offense count: 5
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
# SupportedStyles: compact, expanded
|
||||
Style/EmptyMethod:
|
||||
Exclude:
|
||||
- 'lib/liquid/tag.rb'
|
||||
- 'lib/liquid/tags/comment.rb'
|
||||
- 'lib/liquid/tags/include.rb'
|
||||
- 'test/integration/tags/include_tag_test.rb'
|
||||
- 'test/unit/context_unit_test.rb'
|
||||
|
||||
# Offense count: 3
|
||||
# Cop supports --auto-correct.
|
||||
Style/Encoding:
|
||||
Exclude:
|
||||
- 'lib/liquid/version.rb'
|
||||
- 'liquid.gemspec'
|
||||
- 'test/integration/standard_filter_test.rb'
|
||||
|
||||
# Offense count: 2
|
||||
# Cop supports --auto-correct.
|
||||
Style/ExpandPathArguments:
|
||||
Exclude:
|
||||
- 'Rakefile'
|
||||
- 'liquid.gemspec'
|
||||
|
||||
# Offense count: 7
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
# SupportedStyles: annotated, template, unannotated
|
||||
Style/FormatStringToken:
|
||||
Exclude:
|
||||
- 'test/integration/filter_test.rb'
|
||||
- 'test/integration/hash_ordering_test.rb'
|
||||
|
||||
# Offense count: 14
|
||||
# Configuration parameters: MinBodyLength.
|
||||
Style/GuardClause:
|
||||
Exclude:
|
||||
- 'lib/liquid/condition.rb'
|
||||
- 'lib/liquid/lexer.rb'
|
||||
- 'lib/liquid/strainer.rb'
|
||||
- 'lib/liquid/tags/assign.rb'
|
||||
- 'lib/liquid/tags/capture.rb'
|
||||
- 'lib/liquid/tags/case.rb'
|
||||
- 'lib/liquid/tags/for.rb'
|
||||
- 'lib/liquid/tags/include.rb'
|
||||
- 'lib/liquid/tags/raw.rb'
|
||||
- 'lib/liquid/tags/table_row.rb'
|
||||
- 'lib/liquid/variable.rb'
|
||||
- 'test/unit/tokenizer_unit_test.rb'
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 1
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, MinBodyLength.
|
||||
# SupportedStyles: skip_modifier_ifs, always
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles, UseHashRocketsWithSymbolValues.
|
||||
Style/HashSyntax:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 13
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
Style/MethodName:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 1
|
||||
Style/MultilineBlockChain:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 3
|
||||
# Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles.
|
||||
Style/Next:
|
||||
Exclude:
|
||||
- 'lib/liquid/tags/for.rb'
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 4
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: AutoCorrect, EnforcedStyle.
|
||||
# SupportedStyles: predicate, comparison
|
||||
Style/NumericPredicate:
|
||||
Exclude:
|
||||
- 'spec/**/*'
|
||||
- 'lib/liquid/context.rb'
|
||||
- 'lib/liquid/forloop_drop.rb'
|
||||
- 'lib/liquid/standardfilters.rb'
|
||||
- 'lib/liquid/tablerowloop_drop.rb'
|
||||
|
||||
# Offense count: 14
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: PreferredDelimiters.
|
||||
Style/PercentLiteralDelimiters:
|
||||
Exclude:
|
||||
- 'lib/liquid/tags/if.rb'
|
||||
- 'liquid.gemspec'
|
||||
- 'test/integration/assign_test.rb'
|
||||
- 'test/integration/standard_filter_test.rb'
|
||||
# Offense count: 3
|
||||
# Configuration parameters: NamePrefix, NamePrefixBlacklist.
|
||||
Style/PredicateName:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 1
|
||||
# Cop supports --auto-correct.
|
||||
Style/RedundantSelf:
|
||||
Exclude:
|
||||
- 'lib/liquid/strainer.rb'
|
||||
Style/RescueModifier:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 9
|
||||
# Offense count: 7
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: AllowAsExpressionSeparator.
|
||||
Style/Semicolon:
|
||||
Exclude:
|
||||
- 'test/integration/error_handling_test.rb'
|
||||
- 'test/integration/template_test.rb'
|
||||
- 'test/unit/context_unit_test.rb'
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 7
|
||||
# Offense count: 3
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: MinSize.
|
||||
# SupportedStyles: percent, brackets
|
||||
Style/SymbolArray:
|
||||
EnforcedStyle: brackets
|
||||
|
||||
# Offense count: 2
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, AllowSafeAssignment.
|
||||
# SupportedStyles: require_parentheses, require_no_parentheses, require_parentheses_when_complex
|
||||
Style/TernaryParentheses:
|
||||
Exclude:
|
||||
- 'lib/liquid/context.rb'
|
||||
- 'lib/liquid/utils.rb'
|
||||
|
||||
# Offense count: 2
|
||||
# Cop supports --auto-correct.
|
||||
Style/UnneededPercentQ:
|
||||
Exclude:
|
||||
- 'test/integration/error_handling_test.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# Cop supports --auto-correct.
|
||||
Style/WhileUntilModifier:
|
||||
Exclude:
|
||||
- 'lib/liquid/tags/case.rb'
|
||||
|
||||
# Offense count: 640
|
||||
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
|
||||
# URISchemes: http, https
|
||||
Metrics/LineLength:
|
||||
Max: 294
|
||||
Style/SpecialGlobalVars:
|
||||
Enabled: false
|
||||
|
||||
17
.travis.yml
17
.travis.yml
@@ -1,31 +1,20 @@
|
||||
language: ruby
|
||||
|
||||
rvm:
|
||||
- 2.0
|
||||
- 2.1
|
||||
- 2.2
|
||||
- 2.3
|
||||
- 2.4
|
||||
- 2.5
|
||||
- ruby-head
|
||||
- jruby-head
|
||||
# - rbx-2
|
||||
- rbx-2
|
||||
|
||||
sudo: false
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libgmp3-dev
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- rvm: ruby-head
|
||||
- rvm: jruby-head
|
||||
|
||||
install:
|
||||
- bundle install
|
||||
|
||||
script: bundle exec rake
|
||||
script: "rake test"
|
||||
|
||||
notifications:
|
||||
disable: true
|
||||
|
||||
@@ -4,22 +4,23 @@
|
||||
|
||||
* Bugfixes
|
||||
* Performance improvements
|
||||
* Features that are likely to be useful to the majority of Liquid users
|
||||
* Features which are likely to be useful to the majority of Liquid users
|
||||
|
||||
## Things we won't merge
|
||||
|
||||
* Code that introduces considerable performance degrations
|
||||
* Code that touches performance-critical parts of Liquid and comes without benchmarks
|
||||
* Features that are not important for most people (we want to keep the core Liquid code small and tidy)
|
||||
* Features that can easily be implemented on top of Liquid (for example as a custom filter or custom filesystem)
|
||||
* Code that does not include tests
|
||||
* Code that breaks existing tests
|
||||
* Code which introduces considerable performance degrations
|
||||
* Code which touches performance critical parts of Liquid and comes without benchmarks
|
||||
* Features which are not important for most people (we want to keep the core Liquid code small and tidy)
|
||||
* Features which can easily be implemented on top of Liquid (for example as a custom filter or custom filesystem)
|
||||
* Code which comes without tests
|
||||
* Code which breaks existing tests
|
||||
|
||||
## Workflow
|
||||
|
||||
* Fork the Liquid repository
|
||||
* Create a new branch in your fork
|
||||
* If it makes sense, add tests for your code and/or run a performance benchmark
|
||||
* Make sure all tests pass (`bundle exec rake`)
|
||||
* If it makes sense, add tests for your code and run a performance benchmark
|
||||
* Make sure all tests pass
|
||||
* Create a pull request
|
||||
* In the description, ping one of [@boourns](https://github.com/boourns), [@fw42](https://github.com/fw42), [@camilo](https://github.com/camilo), [@dylanahsmith](https://github.com/dylanahsmith), or [@arthurnn](https://github.com/arthurnn) and ask for a code review.
|
||||
|
||||
|
||||
21
Gemfile
21
Gemfile
@@ -1,23 +1,10 @@
|
||||
source 'https://rubygems.org'
|
||||
git_source(:github) do |repo_name|
|
||||
"https://github.com/#{repo_name}.git"
|
||||
end
|
||||
|
||||
gemspec
|
||||
|
||||
group :benchmark, :test do
|
||||
gem 'benchmark-ips'
|
||||
gem 'memory_profiler'
|
||||
|
||||
install_if -> { RUBY_PLATFORM !~ /mingw|mswin|java/ } do
|
||||
gem 'stackprof'
|
||||
end
|
||||
end
|
||||
gem 'stackprof', platforms: :mri_21
|
||||
|
||||
group :test do
|
||||
gem 'rubocop', '~> 0.53.0'
|
||||
|
||||
platform :mri do
|
||||
gem 'liquid-c', github: 'Shopify/liquid-c', ref: '9168659de45d6d576fce30c735f857e597fa26f6'
|
||||
end
|
||||
gem 'spy', '0.4.1'
|
||||
gem 'benchmark-ips'
|
||||
gem 'rubocop'
|
||||
end
|
||||
|
||||
238
History.md
238
History.md
@@ -1,116 +1,28 @@
|
||||
# Liquid Change Log
|
||||
|
||||
## 4.0.3 / 2019-03-12
|
||||
|
||||
### Fixed
|
||||
* Fix break and continue tags inside included templates in loops (#1072) [Justin Li]
|
||||
|
||||
## 4.0.2 / 2019-03-08
|
||||
## 4.0.0 / not yet released / branch "master"
|
||||
|
||||
### Changed
|
||||
* Add `where` filter (#1026) [Samuel Doiron]
|
||||
* Add `ParseTreeVisitor` to iterate the Liquid AST (#1025) [Stephen Paul Weber]
|
||||
* Improve `strip_html` performance (#1032) [printercu]
|
||||
* Add sort_natural filter (#554) [Martin Hanzel, arthanzel]
|
||||
* Add forloop.parentloop as a reference to the parent loop (#520) [Justin Li, pushrax]
|
||||
* Block parsing moved to BlockBody class (#458) [Dylan Thacker-Smith, dylanahsmith]
|
||||
* Add concat filter to concatenate arrays (#429) [Diogo Beato, dvbeato]
|
||||
* Ruby 1.9 support dropped (#491) [Justin Li, pushrax]
|
||||
* Liquid::Template.file_system's read_template_file method is no longer passed the context. (#441) [James Reid-Smith, sunblaze]
|
||||
* Remove support for `liquid_methods`
|
||||
|
||||
### Fixed
|
||||
* Add error checking for invalid combinations of inputs to sort, sort_natural, where, uniq, map, compact filters (#1059) [Garland Zhang]
|
||||
* Validate the character encoding in url_decode (#1070) [Clayton Smith]
|
||||
* Fix naming of the "context variable" when dynamically including a template (#559) [Justin Li, pushrax]
|
||||
* Gracefully accept empty strings in the date filter (#555) [Loren Hale, boobooninja]
|
||||
* Fix capturing into variables with a hyphen in the name (#505) [Florian Weingarten, fw42]
|
||||
* Fix case sensitivity regression in date standard filter (#499) [Kelley Reynolds, kreynolds]
|
||||
* Disallow filters with no variable in strict mode (#475) [Justin Li, pushrax]
|
||||
* Disallow variable names in the strict parser that are not valid in the lax parser (#463) [Justin Li, pushrax]
|
||||
* Fix BlockBody#warnings taking exponential time to compute (#486) [Justin Li, pushrax]
|
||||
|
||||
## 4.0.1 / 2018-10-09
|
||||
## 3.0.2 / 2015-04-24 / branch "3-0-stable"
|
||||
|
||||
### Changed
|
||||
* Add benchmark group in Gemfile (#855) [Jerry Liu]
|
||||
* Allow benchmarks to benchmark render by itself (#851) [Jerry Liu]
|
||||
* Avoid calling `line_number` on String node when rescuing a render error. (#860) [Dylan Thacker-Smith]
|
||||
* Avoid duck typing to detect whether to call render on a node. [Dylan Thacker-Smith]
|
||||
* Clarify spelling of `reversed` on `for` block tag (#843) [Mark Crossfield]
|
||||
* Replace recursion with loop to avoid potential stack overflow from malicious input (#891, #892) [Dylan Thacker-Smith]
|
||||
* Limit block tag nesting to 100 (#894) [Dylan Thacker-Smith]
|
||||
* Replace `assert_equal nil` with `assert_nil` (#895) [Dylan Thacker-Smith]
|
||||
* Remove Spy Gem (#896) [Dylan Thacker-Smith]
|
||||
* Add `collection_name` and `variable_name` reader to `For` block (#909)
|
||||
* Symbols render as strings (#920) [Justin Li]
|
||||
* Remove default value from Hash objects (#932) [Maxime Bedard]
|
||||
* Remove one level of nesting (#944) [Dylan Thacker-Smith]
|
||||
* Update Rubocop version (#952) [Justin Li]
|
||||
* Add `at_least` and `at_most` filters (#954, #958) [Nithin Bekal]
|
||||
* Add a regression test for a liquid-c trim mode bug (#972) [Dylan Thacker-Smith]
|
||||
* Use https rather than git protocol to fetch liquid-c [Dylan Thacker-Smith]
|
||||
* Add tests against Ruby 2.4 (#963) and 2.5 (#981)
|
||||
* Replace RegExp literals with constants (#988) [Ashwin Maroli]
|
||||
* Replace unnecessary `#each_with_index` with `#each` (#992) [Ashwin Maroli]
|
||||
* Improve the unexpected end delimiter message for block tags. (#1003) [Dylan Thacker-Smith]
|
||||
* Refactor and optimize rendering (#1005) [Christopher Aue]
|
||||
* Add installation instruction (#1006) [Ben Gift]
|
||||
* Remove Circle CI (#1010)
|
||||
* Rename deprecated `BigDecimal.new` to `BigDecimal` (#1024) [Koichi ITO]
|
||||
* Rename deprecated Rubocop name (#1027) [Justin Li]
|
||||
|
||||
### Fixed
|
||||
* Handle `join` filter on non String joiners (#857) [Richard Monette]
|
||||
* Fix duplicate inclusion condition logic error of `Liquid::Strainer.add_filter` method (#861)
|
||||
* Fix `escape`, `url_encode`, `url_decode` not handling non-string values (#898) [Thierry Joyal]
|
||||
* Fix raise when variable is defined but nil when using `strict_variables` [Pascal Betz]
|
||||
* Fix `sort` and `sort_natural` to handle arrays with nils (#930) [Eric Chan]
|
||||
|
||||
## 4.0.0 / 2016-12-14 / branch "4-0-stable"
|
||||
|
||||
### Changed
|
||||
* Render an opaque internal error by default for non-Liquid::Error (#835) [Dylan Thacker-Smith]
|
||||
* Ruby 2.0 support dropped (#832) [Dylan Thacker-Smith]
|
||||
* Add to_number Drop method to allow custom drops to work with number filters (#731)
|
||||
* Add strict_variables and strict_filters options to detect undefined references (#691)
|
||||
* Improve loop performance (#681) [Florian Weingarten]
|
||||
* Rename Drop method `before_method` to `liquid_method_missing` (#661) [Thierry Joyal]
|
||||
* Add url_decode filter to invert url_encode (#645) [Larry Archer]
|
||||
* Add global_filter to apply a filter to all output (#610) [Loren Hale]
|
||||
* Add compact filter (#600) [Carson Reinke]
|
||||
* Rename deprecated "has_key?" and "has_interrupt?" methods (#593) [Florian Weingarten]
|
||||
* Include template name with line numbers in render errors (574) [Dylan Thacker-Smith]
|
||||
* Add sort_natural filter (#554) [Martin Hanzel]
|
||||
* Add forloop.parentloop as a reference to the parent loop (#520) [Justin Li]
|
||||
* Block parsing moved to BlockBody class (#458) [Dylan Thacker-Smith]
|
||||
* Add concat filter to concatenate arrays (#429) [Diogo Beato]
|
||||
* Ruby 1.9 support dropped (#491) [Justin Li]
|
||||
* Liquid::Template.file_system's read_template_file method is no longer passed the context. (#441) [James Reid-Smith]
|
||||
* Remove `liquid_methods` (See https://github.com/Shopify/liquid/pull/568 for replacement)
|
||||
* Liquid::Template.register_filter raises when the module overrides registered public methods as private or protected (#705) [Gaurav Chande]
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix variable names being detected as an operator when starting with contains (#788) [Michael Angell]
|
||||
* Fix include tag used with strict_variables (#828) [QuickPay]
|
||||
* Fix map filter when value is a Proc (#672) [Guillaume Malette]
|
||||
* Fix truncate filter when value is not a string (#672) [Guillaume Malette]
|
||||
* Fix behaviour of escape filter when input is nil (#665) [Tanel Jakobsoo]
|
||||
* Fix sort filter behaviour with empty array input (#652) [Marcel Cary]
|
||||
* Fix test failure under certain timezones (#631) [Dylan Thacker-Smith]
|
||||
* Fix bug in uniq filter (#595) [Florian Weingarten]
|
||||
* Fix bug when "blank" and "empty" are used as variable names (#592) [Florian Weingarten]
|
||||
* Fix condition parse order in strict mode (#569) [Justin Li]
|
||||
* Fix naming of the "context variable" when dynamically including a template (#559) [Justin Li]
|
||||
* Gracefully accept empty strings in the date filter (#555) [Loren Hale]
|
||||
* Fix capturing into variables with a hyphen in the name (#505) [Florian Weingarten]
|
||||
* Fix case sensitivity regression in date standard filter (#499) [Kelley Reynolds]
|
||||
* Disallow filters with no variable in strict mode (#475) [Justin Li]
|
||||
* Disallow variable names in the strict parser that are not valid in the lax parser (#463) [Justin Li]
|
||||
* Fix BlockBody#warnings taking exponential time to compute (#486) [Justin Li]
|
||||
|
||||
## 3.0.5 / 2015-07-23 / branch "3-0-stable"
|
||||
|
||||
* Fix test failure under certain timezones [Dylan Thacker-Smith]
|
||||
|
||||
## 3.0.4 / 2015-07-17
|
||||
|
||||
* Fix chained access to multi-dimensional hashes [Florian Weingarten]
|
||||
|
||||
## 3.0.3 / 2015-05-28
|
||||
|
||||
* Fix condition parse order in strict mode (#569) [Justin Li]
|
||||
|
||||
## 3.0.2 / 2015-04-24
|
||||
|
||||
* Expose VariableLookup private members (#551) [Justin Li]
|
||||
* Expose VariableLookup private members (#551) [Justin Li, pushrax]
|
||||
* Documentation fixes
|
||||
|
||||
## 3.0.1 / 2015-01-23
|
||||
@@ -119,52 +31,44 @@
|
||||
|
||||
## 3.0.0 / 2014-11-12
|
||||
|
||||
* Removed Block#end_tag. Instead, override parse with `super` followed by your code. See #446 [Dylan Thacker-Smith]
|
||||
* Removed Block#end_tag. Instead, override parse with `super` followed by your code. See #446 [Dylan Thacker-Smith, dylanahsmith]
|
||||
* Fixed condition with wrong data types (#423) [Bogdan Gusiev]
|
||||
* Add url_encode to standard filters (#421) [Derrick Reimer]
|
||||
* Add uniq to standard filters [Florian Weingarten]
|
||||
* Add exception_handler feature (#397) and #254 [Bogdan Gusiev, Florian Weingarten]
|
||||
* Optimize variable parsing to avoid repeated regex evaluation during template rendering #383 [Jason Hiltz-Laforge]
|
||||
* Optimize checking for block interrupts to reduce object allocation #380 [Jason Hiltz-Laforge]
|
||||
* Properly set context rethrow_errors on render! #349 [Thierry Joyal]
|
||||
* Fix broken rendering of variables which are equal to false (#345) [Florian Weingarten]
|
||||
* Remove ActionView template handler [Dylan Thacker-Smith]
|
||||
* Freeze lots of string literals for new Ruby 2.1 optimization (#297) [Florian Weingarten]
|
||||
* Allow newlines in tags and variables (#324) [Dylan Thacker-Smith]
|
||||
* Tag#parse is called after initialize, which now takes options instead of tokens as the 3rd argument. See #321 [Dylan Thacker-Smith]
|
||||
* Raise `Liquid::ArgumentError` instead of `::ArgumentError` when filter has wrong number of arguments #309 [Bogdan Gusiev]
|
||||
* Add a to_s default for liquid drops (#306) [Adam Doeler]
|
||||
* Add strip, lstrip, and rstrip to standard filters [Florian Weingarten]
|
||||
* Make if, for & case tags return complete and consistent nodelists (#250) [Nick Jones]
|
||||
* Prevent arbitrary method invocation on condition objects (#274) [Dylan Thacker-Smith]
|
||||
* Don't call to_sym when creating conditions for security reasons (#273) [Bouke van der Bijl]
|
||||
* Fix resource counting bug with respond_to?(:length) (#263) [Florian Weingarten]
|
||||
* Allow specifying custom patterns for template filenames (#284) [Andrei Gladkyi]
|
||||
* Allow drops to optimize loading a slice of elements (#282) [Tom Burns]
|
||||
* Support for passing variables to snippets in subdirs (#271) [Joost Hietbrink]
|
||||
* Add a class cache to avoid runtime extend calls (#249) [James Tucker]
|
||||
* Remove some legacy Ruby 1.8 compatibility code (#276) [Florian Weingarten]
|
||||
* Add default filter to standard filters (#267) [Derrick Reimer]
|
||||
* Add optional strict parsing and warn parsing (#235) [Tristan Hume]
|
||||
* Add url_encode to standard filters (#421) [Derrick Reimer, djreimer]
|
||||
* Add uniq to standard filters [Florian Weingarten, fw42]
|
||||
* Add exception_handler feature (#397) and #254 [Bogdan Gusiev, bogdan and Florian Weingarten, fw42]
|
||||
* Optimize variable parsing to avoid repeated regex evaluation during template rendering #383 [Jason Hiltz-Laforge, jasonhl]
|
||||
* Optimize checking for block interrupts to reduce object allocation #380 [Jason Hiltz-Laforge, jasonhl]
|
||||
* Properly set context rethrow_errors on render! #349 [Thierry Joyal, tjoyal]
|
||||
* Fix broken rendering of variables which are equal to false (#345) [Florian Weingarten, fw42]
|
||||
* Remove ActionView template handler [Dylan Thacker-Smith, dylanahsmith]
|
||||
* Freeze lots of string literals for new Ruby 2.1 optimization (#297) [Florian Weingarten, fw42]
|
||||
* Allow newlines in tags and variables (#324) [Dylan Thacker-Smith, dylanahsmith]
|
||||
* Tag#parse is called after initialize, which now takes options instead of tokens as the 3rd argument. See #321 [Dylan Thacker-Smith, dylanahsmith]
|
||||
* Raise `Liquid::ArgumentError` instead of `::ArgumentError` when filter has wrong number of arguments #309 [Bogdan Gusiev, bogdan]
|
||||
* Add a to_s default for liquid drops (#306) [Adam Doeler, releod]
|
||||
* Add strip, lstrip, and rstrip to standard filters [Florian Weingarten, fw42]
|
||||
* Make if, for & case tags return complete and consistent nodelists (#250) [Nick Jones, dntj]
|
||||
* Prevent arbitrary method invocation on condition objects (#274) [Dylan Thacker-Smith, dylanahsmith]
|
||||
* Don't call to_sym when creating conditions for security reasons (#273) [Bouke van der Bijl, bouk]
|
||||
* Fix resource counting bug with respond_to?(:length) (#263) [Florian Weingarten, fw42]
|
||||
* Allow specifying custom patterns for template filenames (#284) [Andrei Gladkyi, agladkyi]
|
||||
* Allow drops to optimize loading a slice of elements (#282) [Tom Burns, boourns]
|
||||
* Support for passing variables to snippets in subdirs (#271) [Joost Hietbrink, joost]
|
||||
* Add a class cache to avoid runtime extend calls (#249) [James Tucker, raggi]
|
||||
* Remove some legacy Ruby 1.8 compatibility code (#276) [Florian Weingarten, fw42]
|
||||
* Add default filter to standard filters (#267) [Derrick Reimer, djreimer]
|
||||
* Add optional strict parsing and warn parsing (#235) [Tristan Hume, trishume]
|
||||
* Add I18n syntax error translation (#241) [Simon Hørup Eskildsen, Sirupsen]
|
||||
* Make sort filter work on enumerable drops (#239) [Florian Weingarten]
|
||||
* Fix clashing method names in enumerable drops (#238) [Florian Weingarten]
|
||||
* Make map filter work on enumerable drops (#233) [Florian Weingarten]
|
||||
* Improved whitespace stripping for blank blocks, related to #216 [Florian Weingarten]
|
||||
* Make sort filter work on enumerable drops (#239) [Florian Weingarten, fw42]
|
||||
* Fix clashing method names in enumerable drops (#238) [Florian Weingarten, fw42]
|
||||
* Make map filter work on enumerable drops (#233) [Florian Weingarten, fw42]
|
||||
* Improved whitespace stripping for blank blocks, related to #216 [Florian Weingarten, fw42]
|
||||
|
||||
## 2.6.3 / 2015-07-23 / branch "2-6-stable"
|
||||
|
||||
* Fix test failure under certain timezones [Dylan Thacker-Smith]
|
||||
|
||||
## 2.6.2 / 2015-01-23
|
||||
|
||||
* Remove duplicate hash key [Parker Moore]
|
||||
|
||||
## 2.6.1 / 2014-01-10
|
||||
## 2.6.1 / 2014-01-10 / branch "2-6-stable"
|
||||
|
||||
Security fix, cherry-picked from master (4e14a65):
|
||||
* Don't call to_sym when creating conditions for security reasons (#273) [Bouke van der Bijl]
|
||||
* Prevent arbitrary method invocation on condition objects (#274) [Dylan Thacker-Smith]
|
||||
* Don't call to_sym when creating conditions for security reasons (#273) [Bouke van der Bijl, bouk]
|
||||
* Prevent arbitrary method invocation on condition objects (#274) [Dylan Thacker-Smith, dylanahsmith]
|
||||
|
||||
## 2.6.0 / 2013-11-25
|
||||
|
||||
@@ -172,37 +76,37 @@ IMPORTANT: Liquid 2.6 is going to be the last version of Liquid which maintains
|
||||
The following releases will only be tested against Ruby 1.9 and Ruby 2.0 and are likely to break on Ruby 1.8.
|
||||
|
||||
* Bugfix for #106: fix example servlet [gnowoel]
|
||||
* Bugfix for #97: strip_html filter supports multi-line tags [Jo Liss]
|
||||
* Bugfix for #114: strip_html filter supports style tags [James Allardice]
|
||||
* Bugfix for #117: 'now' support for date filter in Ruby 1.9 [Notre Dame Webgroup]
|
||||
* Bugfix for #166: truncate filter on UTF-8 strings with Ruby 1.8 [Florian Weingarten]
|
||||
* Bugfix for #204: 'raw' parsing bug [Florian Weingarten]
|
||||
* Bugfix for #150: 'for' parsing bug [Peter Schröder]
|
||||
* Bugfix for #126: Strip CRLF in strip_newline [Peter Schröder]
|
||||
* Bugfix for #174, "can't convert Fixnum into String" for "replace" [jsw0528]
|
||||
* Allow a Liquid::Drop to be passed into Template#render [Daniel Huckstep]
|
||||
* Resource limits [Florian Weingarten]
|
||||
* Add reverse filter [Jay Strybis]
|
||||
* Bugfix for #97: strip_html filter supports multi-line tags [Jo Liss, joliss]
|
||||
* Bugfix for #114: strip_html filter supports style tags [James Allardice, jamesallardice]
|
||||
* Bugfix for #117: 'now' support for date filter in Ruby 1.9 [Notre Dame Webgroup, ndwebgroup]
|
||||
* Bugfix for #166: truncate filter on UTF-8 strings with Ruby 1.8 [Florian Weingarten, fw42]
|
||||
* Bugfix for #204: 'raw' parsing bug [Florian Weingarten, fw42]
|
||||
* Bugfix for #150: 'for' parsing bug [Peter Schröder, phoet]
|
||||
* Bugfix for #126: Strip CRLF in strip_newline [Peter Schröder, phoet]
|
||||
* Bugfix for #174, "can't convert Fixnum into String" for "replace" [wǒ_is神仙, jsw0528]
|
||||
* Allow a Liquid::Drop to be passed into Template#render [Daniel Huckstep, darkhelmet]
|
||||
* Resource limits [Florian Weingarten, fw42]
|
||||
* Add reverse filter [Jay Strybis, unreal]
|
||||
* Add utf-8 support
|
||||
* Use array instead of Hash to keep the registered filters [Tasos Stathopoulos]
|
||||
* Cache tokenized partial templates [Tom Burns]
|
||||
* Avoid warnings in Ruby 1.9.3 [Marcus Stollsteimer]
|
||||
* Better documentation for 'include' tag (closes #163) [Peter Schröder]
|
||||
* Use of BigDecimal on filters to have better precision (closes #155) [Arthur Nogueira Neves]
|
||||
* Use array instead of Hash to keep the registered filters [Tasos Stathopoulos, astathopoulos]
|
||||
* Cache tokenized partial templates [Tom Burns, boourns]
|
||||
* Avoid warnings in Ruby 1.9.3 [Marcus Stollsteimer, stomar]
|
||||
* Better documentation for 'include' tag (closes #163) [Peter Schröder, phoet]
|
||||
* Use of BigDecimal on filters to have better precision (closes #155) [Arthur Nogueira Neves, arthurnn]
|
||||
|
||||
## 2.5.5 / 2014-01-10 / branch "2-5-stable"
|
||||
|
||||
Security fix, cherry-picked from master (4e14a65):
|
||||
* Don't call to_sym when creating conditions for security reasons (#273) [Bouke van der Bijl]
|
||||
* Prevent arbitrary method invocation on condition objects (#274) [Dylan Thacker-Smith]
|
||||
* Don't call to_sym when creating conditions for security reasons (#273) [Bouke van der Bijl, bouk]
|
||||
* Prevent arbitrary method invocation on condition objects (#274) [Dylan Thacker-Smith, dylanahsmith]
|
||||
|
||||
## 2.5.4 / 2013-11-11
|
||||
|
||||
* Fix "can't convert Fixnum into String" for "replace" (#173), [jsw0528]
|
||||
* Fix "can't convert Fixnum into String" for "replace" (#173), [wǒ_is神仙, jsw0528]
|
||||
|
||||
## 2.5.3 / 2013-10-09
|
||||
|
||||
* #232, #234, #237: Fix map filter bugs [Florian Weingarten]
|
||||
* #232, #234, #237: Fix map filter bugs [Florian Weingarten, fw42]
|
||||
|
||||
## 2.5.2 / 2013-09-03 / deleted
|
||||
|
||||
@@ -210,7 +114,7 @@ Yanked from rubygems, as it contained too many changes that broke compatibility.
|
||||
|
||||
## 2.5.1 / 2013-07-24
|
||||
|
||||
* #230: Fix security issue with map filter, Use invoke_drop in map filter [Florian Weingarten]
|
||||
* #230: Fix security issue with map filter, Use invoke_drop in map filter [Florian Weingarten, fw42]
|
||||
|
||||
## 2.5.0 / 2013-03-06
|
||||
|
||||
|
||||
33
README.md
33
README.md
@@ -42,8 +42,6 @@ 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.
|
||||
|
||||
@@ -75,34 +73,3 @@ This is useful for doing things like enabling strict mode only in the theme edit
|
||||
|
||||
It is recommended that you enable `:strict` or `:warn` mode on new apps to stop invalid templates from being created.
|
||||
It is also recommended that you use it in the template editors of existing apps to give editors better error messages.
|
||||
|
||||
### Undefined variables and filters
|
||||
|
||||
By default, the renderer doesn't raise or in any other way notify you if some variables or filters are missing, i.e. not passed to the `render` method.
|
||||
You can improve this situation by passing `strict_variables: true` and/or `strict_filters: true` options to the `render` method.
|
||||
When one of these options is set to true, all errors about undefined variables and undefined filters will be stored in `errors` array of a `Liquid::Template` instance.
|
||||
Here are some examples:
|
||||
|
||||
```ruby
|
||||
template = Liquid::Template.parse("{{x}} {{y}} {{z.a}} {{z.b}}")
|
||||
template.render({ 'x' => 1, 'z' => { 'a' => 2 } }, { strict_variables: true })
|
||||
#=> '1 2 ' # when a variable is undefined, it's rendered as nil
|
||||
template.errors
|
||||
#=> [#<Liquid::UndefinedVariable: Liquid error: undefined variable y>, #<Liquid::UndefinedVariable: Liquid error: undefined variable b>]
|
||||
```
|
||||
|
||||
```ruby
|
||||
template = Liquid::Template.parse("{{x | filter1 | upcase}}")
|
||||
template.render({ 'x' => 'foo' }, { strict_filters: true })
|
||||
#=> '' # when at least one filter in the filter chain is undefined, a whole expression is rendered as nil
|
||||
template.errors
|
||||
#=> [#<Liquid::UndefinedFilter: Liquid error: undefined filter filter1>]
|
||||
```
|
||||
|
||||
If you want to raise on a first exception instead of pushing all of them in `errors`, you can use `render!` method:
|
||||
|
||||
```ruby
|
||||
template = Liquid::Template.parse("{{x}} {{y}}")
|
||||
template.render!({ 'x' => 1}, { strict_variables: true })
|
||||
#=> Liquid::UndefinedVariable: Liquid error: undefined variable y
|
||||
```
|
||||
|
||||
26
Rakefile
26
Rakefile
@@ -3,7 +3,7 @@ require 'rake/testtask'
|
||||
$LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
|
||||
require "liquid/version"
|
||||
|
||||
task default: [:test, :rubocop]
|
||||
task default: [:rubocop, :test]
|
||||
|
||||
desc 'run test suite with default parser'
|
||||
Rake::TestTask.new(:base_test) do |t|
|
||||
@@ -27,22 +27,9 @@ desc 'runs test suite with both strict and lax parsers'
|
||||
task :test do
|
||||
ENV['LIQUID_PARSER_MODE'] = 'lax'
|
||||
Rake::Task['base_test'].invoke
|
||||
|
||||
ENV['LIQUID_PARSER_MODE'] = 'strict'
|
||||
Rake::Task['base_test'].reenable
|
||||
Rake::Task['base_test'].invoke
|
||||
|
||||
if RUBY_ENGINE == 'ruby'
|
||||
ENV['LIQUID-C'] = '1'
|
||||
|
||||
ENV['LIQUID_PARSER_MODE'] = 'lax'
|
||||
Rake::Task['base_test'].reenable
|
||||
Rake::Task['base_test'].invoke
|
||||
|
||||
ENV['LIQUID_PARSER_MODE'] = 'strict'
|
||||
Rake::Task['base_test'].reenable
|
||||
Rake::Task['base_test'].invoke
|
||||
end
|
||||
end
|
||||
|
||||
task gem: :build
|
||||
@@ -85,18 +72,7 @@ namespace :profile do
|
||||
end
|
||||
end
|
||||
|
||||
namespace :memory_profile do
|
||||
desc "Run memory profiler"
|
||||
task :run do
|
||||
ruby "./performance/memory_profile.rb"
|
||||
end
|
||||
end
|
||||
|
||||
desc "Run example"
|
||||
task :example do
|
||||
ruby "-w -d -Ilib example/server/server.rb"
|
||||
end
|
||||
|
||||
task :console do
|
||||
exec 'irb -I lib -r liquid'
|
||||
end
|
||||
|
||||
3
circle.yml
Normal file
3
circle.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
machine:
|
||||
ruby:
|
||||
version: ruby-2.0
|
||||
@@ -1,4 +1,5 @@
|
||||
class LiquidServlet < WEBrick::HTTPServlet::AbstractServlet
|
||||
|
||||
def do_GET(req, res)
|
||||
handle(:get, req, res)
|
||||
end
|
||||
@@ -19,7 +20,7 @@ class LiquidServlet < WEBrick::HTTPServlet::AbstractServlet
|
||||
|
||||
@response['Content-Type'] = "text/html"
|
||||
@response.status = 200
|
||||
@response.body = Liquid::Template.parse(read_template).render(@assigns, filters: [ProductsFilter])
|
||||
@response.body = Liquid::Template.parse(read_template).render(@assigns, :filters => [ProductsFilter])
|
||||
end
|
||||
|
||||
def read_template(filename = @action)
|
||||
|
||||
@@ -24,7 +24,6 @@ module Liquid
|
||||
ArgumentSeparator = ','.freeze
|
||||
FilterArgumentSeparator = ':'.freeze
|
||||
VariableAttributeSeparator = '.'.freeze
|
||||
WhitespaceControl = '-'.freeze
|
||||
TagStart = /\{\%/
|
||||
TagEnd = /\%\}/
|
||||
VariableSignature = /\(?[\w\-\.\[\]]\)?/
|
||||
@@ -35,7 +34,7 @@ module Liquid
|
||||
QuotedString = /"[^"]*"|'[^']*'/
|
||||
QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o
|
||||
TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/o
|
||||
AnyStartingTag = /#{TagStart}|#{VariableStart}/o
|
||||
AnyStartingTag = /\{\{|\{\%/
|
||||
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/om
|
||||
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om
|
||||
VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o
|
||||
@@ -45,13 +44,10 @@ module Liquid
|
||||
end
|
||||
|
||||
require "liquid/version"
|
||||
require 'liquid/parse_tree_visitor'
|
||||
require 'liquid/lexer'
|
||||
require 'liquid/parser'
|
||||
require 'liquid/i18n'
|
||||
require 'liquid/drop'
|
||||
require 'liquid/tablerowloop_drop'
|
||||
require 'liquid/forloop_drop'
|
||||
require 'liquid/extensions'
|
||||
require 'liquid/errors'
|
||||
require 'liquid/interrupts'
|
||||
@@ -72,8 +68,7 @@ require 'liquid/template'
|
||||
require 'liquid/standardfilters'
|
||||
require 'liquid/condition'
|
||||
require 'liquid/utils'
|
||||
require 'liquid/tokenizer'
|
||||
require 'liquid/parse_context'
|
||||
require 'liquid/token'
|
||||
|
||||
# Load all the tags of the standard library
|
||||
#
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
module Liquid
|
||||
class Block < Tag
|
||||
MAX_DEPTH = 100
|
||||
|
||||
def initialize(tag_name, markup, options)
|
||||
super
|
||||
@blank = true
|
||||
@@ -9,7 +7,7 @@ module Liquid
|
||||
|
||||
def parse(tokens)
|
||||
@body = BlockBody.new
|
||||
while parse_body(@body, tokens)
|
||||
while more = parse_body(@body, tokens)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -25,17 +23,29 @@ module Liquid
|
||||
@body.nodelist
|
||||
end
|
||||
|
||||
# warnings of this block and all sub-tags
|
||||
def warnings
|
||||
all_warnings = []
|
||||
all_warnings.concat(@warnings) if @warnings
|
||||
|
||||
(nodelist || []).each do |node|
|
||||
all_warnings.concat(node.warnings || []) if node.respond_to?(:warnings)
|
||||
end
|
||||
|
||||
all_warnings
|
||||
end
|
||||
|
||||
def unknown_tag(tag, _params, _tokens)
|
||||
if tag == 'else'.freeze
|
||||
raise SyntaxError.new(parse_context.locale.t("errors.syntax.unexpected_else".freeze,
|
||||
case tag
|
||||
when 'else'.freeze
|
||||
raise SyntaxError.new(options[:locale].t("errors.syntax.unexpected_else".freeze,
|
||||
block_name: block_name))
|
||||
elsif tag.start_with?('end'.freeze)
|
||||
raise SyntaxError.new(parse_context.locale.t("errors.syntax.invalid_delimiter".freeze,
|
||||
tag: tag,
|
||||
when 'end'.freeze
|
||||
raise SyntaxError.new(options[:locale].t("errors.syntax.invalid_delimiter".freeze,
|
||||
block_name: block_name,
|
||||
block_delimiter: block_delimiter))
|
||||
else
|
||||
raise SyntaxError.new(parse_context.locale.t("errors.syntax.unknown_tag".freeze, tag: tag))
|
||||
raise SyntaxError.new(options[:locale].t("errors.syntax.unknown_tag".freeze, tag: tag))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -50,25 +60,17 @@ module Liquid
|
||||
protected
|
||||
|
||||
def parse_body(body, tokens)
|
||||
if parse_context.depth >= MAX_DEPTH
|
||||
raise StackLevelError, "Nesting too deep".freeze
|
||||
end
|
||||
parse_context.depth += 1
|
||||
begin
|
||||
body.parse(tokens, parse_context) do |end_tag_name, end_tag_params|
|
||||
@blank &&= body.blank?
|
||||
body.parse(tokens, options) do |end_tag_name, end_tag_params|
|
||||
@blank &&= body.blank?
|
||||
|
||||
return false if end_tag_name == block_delimiter
|
||||
unless end_tag_name
|
||||
raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_never_closed".freeze, block_name: block_name))
|
||||
end
|
||||
|
||||
# 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)
|
||||
return false if end_tag_name == block_delimiter
|
||||
unless end_tag_name
|
||||
raise SyntaxError.new(@options[:locale].t("errors.syntax.tag_never_closed".freeze, block_name: block_name))
|
||||
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
|
||||
|
||||
true
|
||||
|
||||
@@ -1,8 +1,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/
|
||||
FullToken = /\A#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
|
||||
ContentOfVariable = /\A#{VariableStart}(.*)#{VariableEnd}\z/om
|
||||
TAGSTART = "{%".freeze
|
||||
VARSTART = "{{".freeze
|
||||
|
||||
@@ -13,85 +12,88 @@ module Liquid
|
||||
@blank = true
|
||||
end
|
||||
|
||||
def parse(tokenizer, parse_context)
|
||||
parse_context.line_number = tokenizer.line_number
|
||||
while token = tokenizer.shift
|
||||
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)
|
||||
def parse(tokens, options)
|
||||
while token = tokens.shift
|
||||
begin
|
||||
unless token.empty?
|
||||
case
|
||||
when token.start_with?(TAGSTART)
|
||||
if token =~ FullToken
|
||||
tag_name = $1
|
||||
markup = $2
|
||||
# fetch the tag from registered blocks
|
||||
if tag = Template.tags[tag_name]
|
||||
markup = token.child(markup) if token.is_a?(Token)
|
||||
new_tag = tag.parse(tag_name, markup, tokens, options)
|
||||
new_tag.line_number = token.line_number if token.is_a?(Token)
|
||||
@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, options)
|
||||
end
|
||||
when token.start_with?(VARSTART)
|
||||
new_var = create_variable(token, options)
|
||||
new_var.line_number = token.line_number if token.is_a?(Token)
|
||||
@nodelist << new_var
|
||||
@blank = false
|
||||
else
|
||||
@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)
|
||||
rescue SyntaxError => e
|
||||
e.set_line_number_from_token(token)
|
||||
raise
|
||||
end
|
||||
parse_context.line_number = tokenizer.line_number
|
||||
end
|
||||
|
||||
yield nil, nil
|
||||
end
|
||||
|
||||
def whitespace_handler(token, parse_context)
|
||||
if token[2] == WhitespaceControl
|
||||
previous_token = @nodelist.last
|
||||
if previous_token.is_a? String
|
||||
previous_token.rstrip!
|
||||
end
|
||||
end
|
||||
parse_context.trim_whitespace = (token[-3] == WhitespaceControl)
|
||||
end
|
||||
|
||||
def blank?
|
||||
@blank
|
||||
end
|
||||
|
||||
def warnings
|
||||
all_warnings = []
|
||||
nodelist.each do |node|
|
||||
all_warnings.concat(node.warnings || []) if node.respond_to?(:warnings)
|
||||
end
|
||||
all_warnings
|
||||
end
|
||||
|
||||
def render(context)
|
||||
output = []
|
||||
context.resource_limits.render_score += @nodelist.length
|
||||
|
||||
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
|
||||
@nodelist.each do |token|
|
||||
# Break out if we have any unhanded interrupts.
|
||||
break if context.has_interrupt?
|
||||
|
||||
begin
|
||||
# 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 %}
|
||||
context.push_interrupt(node.interrupt)
|
||||
break
|
||||
else # Other non-Block tags
|
||||
render_node_to_output(node, output, context)
|
||||
break if context.interrupt? # might have happened through an include
|
||||
if token.is_a?(Continue) || token.is_a?(Break)
|
||||
context.push_interrupt(token.interrupt)
|
||||
break
|
||||
end
|
||||
|
||||
token_output = render_token(token, context)
|
||||
|
||||
unless token.is_a?(Block) && token.blank?
|
||||
output << token_output
|
||||
end
|
||||
rescue MemoryError => e
|
||||
raise e
|
||||
rescue ::StandardError => e
|
||||
output << context.handle_error(e, token)
|
||||
end
|
||||
idx += 1
|
||||
end
|
||||
|
||||
output.join
|
||||
@@ -99,45 +101,31 @@ module Liquid
|
||||
|
||||
private
|
||||
|
||||
def render_node_to_output(node, output, context, skip_output = false)
|
||||
node_output = node.render(context)
|
||||
node_output = node_output.is_a?(Array) ? node_output.join : node_output.to_s
|
||||
check_resources(context, node_output)
|
||||
output << node_output unless skip_output
|
||||
rescue MemoryError => e
|
||||
raise e
|
||||
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 render_token(token, context)
|
||||
token_output = (token.respond_to?(:render) ? token.render(context) : token)
|
||||
token_str = token_output.is_a?(Array) ? token_output.join : token_output.to_s
|
||||
|
||||
def check_resources(context, node_output)
|
||||
context.resource_limits.render_length += node_output.bytesize
|
||||
return unless context.resource_limits.reached?
|
||||
raise MemoryError.new("Memory limits exceeded".freeze)
|
||||
end
|
||||
|
||||
def create_variable(token, parse_context)
|
||||
token.scan(ContentOfVariable) do |content|
|
||||
markup = content.first
|
||||
return Variable.new(markup, parse_context)
|
||||
context.resource_limits.render_length += token_str.length
|
||||
if context.resource_limits.reached?
|
||||
raise MemoryError.new("Memory limits exceeded".freeze)
|
||||
end
|
||||
raise_missing_variable_terminator(token, parse_context)
|
||||
token_str
|
||||
end
|
||||
|
||||
def raise_missing_tag_terminator(token, parse_context)
|
||||
raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_termination".freeze, token: token, tag_end: TagEnd.inspect))
|
||||
def create_variable(token, options)
|
||||
token.scan(ContentOfVariable) do |content|
|
||||
markup = token.is_a?(Token) ? token.child(content.first) : content.first
|
||||
return Variable.new(markup, options)
|
||||
end
|
||||
raise_missing_variable_terminator(token, options)
|
||||
end
|
||||
|
||||
def raise_missing_variable_terminator(token, parse_context)
|
||||
raise SyntaxError.new(parse_context.locale.t("errors.syntax.variable_termination".freeze, token: token, tag_end: VariableEnd.inspect))
|
||||
def raise_missing_tag_terminator(token, options)
|
||||
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_termination".freeze, token: token, tag_end: TagEnd.inspect))
|
||||
end
|
||||
|
||||
def registered_tags
|
||||
Template.tags
|
||||
def raise_missing_variable_terminator(token, options)
|
||||
raise SyntaxError.new(options[:locale].t("errors.syntax.variable_termination".freeze, token: token, tag_end: VariableEnd.inspect))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -16,12 +16,7 @@ module Liquid
|
||||
'>='.freeze => :>=,
|
||||
'<='.freeze => :<=,
|
||||
'contains'.freeze => lambda do |cond, left, right|
|
||||
if left && right && left.respond_to?(:include?)
|
||||
right = right.to_s if left.is_a?(String)
|
||||
left.include?(right)
|
||||
else
|
||||
false
|
||||
end
|
||||
left && right && left.respond_to?(:include?) ? left.include?(right) : false
|
||||
end
|
||||
}
|
||||
|
||||
@@ -29,7 +24,7 @@ module Liquid
|
||||
@@operators
|
||||
end
|
||||
|
||||
attr_reader :attachment, :child_condition
|
||||
attr_reader :attachment
|
||||
attr_accessor :left, :operator, :right
|
||||
|
||||
def initialize(left = nil, operator = nil, right = nil)
|
||||
@@ -41,22 +36,16 @@ module Liquid
|
||||
end
|
||||
|
||||
def evaluate(context = Context.new)
|
||||
condition = self
|
||||
result = nil
|
||||
loop do
|
||||
result = interpret_condition(condition.left, condition.right, condition.operator, context)
|
||||
result = interpret_condition(left, right, operator, context)
|
||||
|
||||
case condition.child_relation
|
||||
when :or
|
||||
break if result
|
||||
when :and
|
||||
break unless result
|
||||
else
|
||||
break
|
||||
end
|
||||
condition = condition.child_condition
|
||||
case @child_relation
|
||||
when :or
|
||||
result || @child_condition.evaluate(context)
|
||||
when :and
|
||||
result && @child_condition.evaluate(context)
|
||||
else
|
||||
result
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
def or(condition)
|
||||
@@ -81,24 +70,20 @@ module Liquid
|
||||
"#<Condition #{[@left, @operator, @right].compact.join(' '.freeze)}>"
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
attr_reader :child_relation
|
||||
|
||||
private
|
||||
|
||||
def equal_variables(left, right)
|
||||
if left.is_a?(Liquid::Expression::MethodLiteral)
|
||||
if right.respond_to?(left.method_name)
|
||||
return right.send(left.method_name)
|
||||
if left.is_a?(Symbol)
|
||||
if right.respond_to?(left)
|
||||
return right.send(left.to_s)
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
if right.is_a?(Liquid::Expression::MethodLiteral)
|
||||
if left.respond_to?(right.method_name)
|
||||
return left.send(right.method_name)
|
||||
if right.is_a?(Symbol)
|
||||
if left.respond_to?(right)
|
||||
return left.send(right.to_s)
|
||||
else
|
||||
return nil
|
||||
end
|
||||
@@ -120,7 +105,7 @@ module Liquid
|
||||
|
||||
if operation.respond_to?(:call)
|
||||
operation.call(self, left, right)
|
||||
elsif left.respond_to?(operation) && right.respond_to?(operation) && !left.is_a?(Hash) && !right.is_a?(Hash)
|
||||
elsif left.respond_to?(operation) && right.respond_to?(operation)
|
||||
begin
|
||||
left.send(operation, right)
|
||||
rescue ::ArgumentError => e
|
||||
@@ -128,15 +113,6 @@ module Liquid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
||||
def children
|
||||
[
|
||||
@node.left, @node.right,
|
||||
@node.child_condition, @node.attachment
|
||||
].compact
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class ElseCondition < Condition
|
||||
|
||||
@@ -12,33 +12,26 @@ module Liquid
|
||||
#
|
||||
# context['bob'] #=> nil class Context
|
||||
class Context
|
||||
attr_reader :scope, :errors, :registers, :environments, :resource_limits
|
||||
attr_accessor :exception_renderer, :template_name, :partial, :global_filter, :strict_variables, :strict_filters
|
||||
attr_reader :scopes, :errors, :registers, :environments, :resource_limits
|
||||
attr_accessor :exception_handler, :render_errors
|
||||
|
||||
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil)
|
||||
@environments = [environments].flatten
|
||||
@scope = outer_scope || {}
|
||||
@scopes = [(outer_scope || {})]
|
||||
@registers = registers
|
||||
@errors = []
|
||||
@partial = false
|
||||
@strict_variables = false
|
||||
@resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
|
||||
squash_instance_assigns_with_environments
|
||||
@render_errors = true
|
||||
|
||||
@this_stack_used = false
|
||||
|
||||
self.exception_renderer = Template.default_exception_renderer
|
||||
if rethrow_errors
|
||||
self.exception_renderer = ->(e) { raise }
|
||||
self.exception_handler = ->(e) { true }
|
||||
end
|
||||
|
||||
@interrupts = []
|
||||
@filters = []
|
||||
@global_filter = nil
|
||||
|
||||
@stack_level = 0
|
||||
end
|
||||
|
||||
def warnings
|
||||
@warnings ||= []
|
||||
end
|
||||
|
||||
def strainer
|
||||
@@ -55,12 +48,8 @@ module Liquid
|
||||
@strainer = nil
|
||||
end
|
||||
|
||||
def apply_global_filter(obj)
|
||||
global_filter.nil? ? obj : global_filter.call(obj)
|
||||
end
|
||||
|
||||
# are there any not handled interrupts?
|
||||
def interrupt?
|
||||
def has_interrupt?
|
||||
!@interrupts.empty?
|
||||
end
|
||||
|
||||
@@ -74,21 +63,35 @@ module Liquid
|
||||
@interrupts.pop
|
||||
end
|
||||
|
||||
def handle_error(e, line_number = nil)
|
||||
e = internal_error unless e.is_a?(Liquid::Error)
|
||||
e.template_name ||= template_name
|
||||
e.line_number ||= line_number
|
||||
def handle_error(e, token = nil)
|
||||
if e.is_a?(Liquid::Error)
|
||||
e.set_line_number_from_token(token)
|
||||
end
|
||||
|
||||
errors.push(e)
|
||||
exception_renderer.call(e).to_s
|
||||
raise if exception_handler && exception_handler.call(e)
|
||||
render_errors ? Liquid::Error.render(e) : ''
|
||||
end
|
||||
|
||||
def invoke(method, *args)
|
||||
strainer.invoke(method, *args).to_liquid
|
||||
end
|
||||
|
||||
# Push new local scope on the stack. use <tt>Context#stack</tt> instead
|
||||
def push(new_scope = {})
|
||||
@scopes.unshift(new_scope)
|
||||
raise StackLevelError, "Nesting too deep".freeze if @scopes.length > 100
|
||||
end
|
||||
|
||||
# Merge a hash of variables in the current local scope
|
||||
def merge(new_scopes)
|
||||
new_scopes.each { |k, v| self[k] = v }
|
||||
@scopes[0].merge!(new_scopes)
|
||||
end
|
||||
|
||||
# Pop from the stack. use <tt>Context#stack</tt> instead
|
||||
def pop
|
||||
raise ContextError if @scopes.size == 1
|
||||
@scopes.shift
|
||||
end
|
||||
|
||||
# Pushes a new local scope on the stack, pops it at the end of the block
|
||||
@@ -99,20 +102,32 @@ module Liquid
|
||||
# end
|
||||
#
|
||||
# context['var] #=> nil
|
||||
def stack(*variable_names)
|
||||
@stack_level += 1
|
||||
raise StackLevelError, "Nesting too deep".freeze if @stack_level > Block::MAX_DEPTH
|
||||
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
@stack_level -= 1
|
||||
def stack(new_scope = nil)
|
||||
old_stack_used = @this_stack_used
|
||||
if new_scope
|
||||
push(new_scope)
|
||||
@this_stack_used = true
|
||||
else
|
||||
@this_stack_used = false
|
||||
end
|
||||
|
||||
yield
|
||||
ensure
|
||||
pop if @this_stack_used
|
||||
@this_stack_used = old_stack_used
|
||||
end
|
||||
|
||||
def clear_instance_assigns
|
||||
@scopes[0] = {}
|
||||
end
|
||||
|
||||
# Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
|
||||
def []=(key, value)
|
||||
(@scope[key] ||= [nil]) << value
|
||||
unless @this_stack_used
|
||||
@this_stack_used = true
|
||||
push({})
|
||||
end
|
||||
@scopes[0][key] = value
|
||||
end
|
||||
|
||||
# Look up variable, either resolve directly after considering the name. We can directly handle
|
||||
@@ -127,30 +142,7 @@ module Liquid
|
||||
evaluate(Expression.parse(expression))
|
||||
end
|
||||
|
||||
def unset(key)
|
||||
if @scope[key].size <= 1
|
||||
@scope.delete(key)
|
||||
else
|
||||
@scope[key].pop
|
||||
end
|
||||
end
|
||||
|
||||
def set_root(key, val)
|
||||
@scope[key] ||= []
|
||||
@scope[key][0] = val
|
||||
end
|
||||
|
||||
def set_level(key, val, int)
|
||||
@scope[key] ||= []
|
||||
@scope[key][int] = val
|
||||
end
|
||||
|
||||
def create_level(key)
|
||||
(@scope[key] ||= [nil]) << nil
|
||||
@scope[key].size - 1
|
||||
end
|
||||
|
||||
def key?(key)
|
||||
def has_key?(key)
|
||||
self[key] != nil
|
||||
end
|
||||
|
||||
@@ -159,24 +151,26 @@ module Liquid
|
||||
end
|
||||
|
||||
# Fetches an object starting at the local scope and then moving up the hierachy
|
||||
def find_variable(key, raise_on_not_found: true)
|
||||
trigger = false
|
||||
value = @scope[key]
|
||||
scope = @scope unless value.nil?
|
||||
trigger = true unless value.nil?
|
||||
def find_variable(key)
|
||||
# This was changed from find() to find_index() because this is a very hot
|
||||
# path and find_index() is optimized in MRI to reduce object allocation
|
||||
index = @scopes.find_index { |s| s.has_key?(key) }
|
||||
scope = @scopes[index] if index
|
||||
|
||||
variable = nil
|
||||
|
||||
if scope.nil?
|
||||
index = @environments.find_index do |e|
|
||||
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
|
||||
# then it is the value we are looking for.
|
||||
!variable.nil? || @strict_variables && raise_on_not_found
|
||||
@environments.each do |e|
|
||||
variable = lookup_and_evaluate(e, key)
|
||||
unless variable.nil?
|
||||
scope = e
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
scope = @environments[index || -1]
|
||||
end
|
||||
|
||||
variable ||= lookup_and_evaluate(scope, key, trigger, raise_on_not_found: raise_on_not_found)
|
||||
scope ||= @environments.last || @scopes.last
|
||||
variable ||= lookup_and_evaluate(scope, key)
|
||||
|
||||
variable = variable.to_liquid
|
||||
variable.context = self if variable.respond_to?(:context=)
|
||||
@@ -184,18 +178,8 @@ module Liquid
|
||||
variable
|
||||
end
|
||||
|
||||
def lookup_and_evaluate(obj, key, trigger = false, raise_on_not_found: true)
|
||||
if @strict_variables && raise_on_not_found && obj.respond_to?(:key?) && !obj.key?(key)
|
||||
raise Liquid::UndefinedVariable, "undefined variable #{key}"
|
||||
end
|
||||
|
||||
value = if trigger == true
|
||||
obj[key][-1]
|
||||
else
|
||||
obj[key]
|
||||
end
|
||||
|
||||
if value.is_a?(Proc) && obj.respond_to?(:[]=)
|
||||
def lookup_and_evaluate(obj, key)
|
||||
if (value = obj[key]).is_a?(Proc) && obj.respond_to?(:[]=)
|
||||
obj[key] = (value.arity == 0) ? value.call : value.call(self)
|
||||
else
|
||||
value
|
||||
@@ -204,18 +188,11 @@ module Liquid
|
||||
|
||||
private
|
||||
|
||||
def internal_error
|
||||
# raise and catch to set backtrace and cause on exception
|
||||
raise Liquid::InternalError, 'internal'
|
||||
rescue Liquid::InternalError => exc
|
||||
exc
|
||||
end
|
||||
|
||||
def squash_instance_assigns_with_environments
|
||||
@scope.each_key do |k|
|
||||
@scopes.last.each_key do |k|
|
||||
@environments.each do |env|
|
||||
if env.key?(k)
|
||||
@scope[k] = [lookup_and_evaluate(env, k)]
|
||||
if env.has_key?(k)
|
||||
scopes.last[k] = lookup_and_evaluate(env, k)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,26 +1,23 @@
|
||||
module Liquid
|
||||
class Document < BlockBody
|
||||
def self.parse(tokens, parse_context)
|
||||
def self.parse(tokens, options)
|
||||
doc = new
|
||||
doc.parse(tokens, parse_context)
|
||||
doc.parse(tokens, options)
|
||||
doc
|
||||
end
|
||||
|
||||
def parse(tokens, parse_context)
|
||||
def parse(tokens, options)
|
||||
super do |end_tag_name, end_tag_params|
|
||||
unknown_tag(end_tag_name, parse_context) if end_tag_name
|
||||
unknown_tag(end_tag_name, options) if end_tag_name
|
||||
end
|
||||
rescue SyntaxError => e
|
||||
e.line_number ||= parse_context.line_number
|
||||
raise
|
||||
end
|
||||
|
||||
def unknown_tag(tag, parse_context)
|
||||
def unknown_tag(tag, options)
|
||||
case tag
|
||||
when 'else'.freeze, 'end'.freeze
|
||||
raise SyntaxError.new(parse_context.locale.t("errors.syntax.unexpected_outer_tag".freeze, tag: tag))
|
||||
raise SyntaxError.new(options[:locale].t("errors.syntax.unexpected_outer_tag".freeze, tag: tag))
|
||||
else
|
||||
raise SyntaxError.new(parse_context.locale.t("errors.syntax.unknown_tag".freeze, tag: tag))
|
||||
raise SyntaxError.new(options[:locale].t("errors.syntax.unknown_tag".freeze, tag: tag))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -18,27 +18,28 @@ module Liquid
|
||||
# tmpl = Liquid::Template.parse( ' {% for product in product.top_sales %} {{ product.name }} {%endfor%} ' )
|
||||
# tmpl.render('product' => ProductDrop.new ) # will invoke top_sales query.
|
||||
#
|
||||
# Your drop can either implement the methods sans any parameters
|
||||
# or implement the liquid_method_missing(name) method which is a catch all.
|
||||
# Your drop can either implement the methods sans any parameters or implement the before_method(name) method which is a
|
||||
# catch all.
|
||||
class Drop
|
||||
attr_writer :context
|
||||
|
||||
EMPTY_STRING = ''.freeze
|
||||
|
||||
# Catch all for the method
|
||||
def liquid_method_missing(method)
|
||||
return nil unless @context && @context.strict_variables
|
||||
raise Liquid::UndefinedDropMethod, "undefined method #{method}"
|
||||
def before_method(_method)
|
||||
nil
|
||||
end
|
||||
|
||||
# called by liquid to invoke a drop
|
||||
def invoke_drop(method_or_key)
|
||||
if self.class.invokable?(method_or_key)
|
||||
if method_or_key && method_or_key != EMPTY_STRING && self.class.invokable?(method_or_key)
|
||||
send(method_or_key)
|
||||
else
|
||||
liquid_method_missing(method_or_key)
|
||||
before_method(method_or_key)
|
||||
end
|
||||
end
|
||||
|
||||
def key?(_name)
|
||||
def has_key?(_name)
|
||||
true
|
||||
end
|
||||
|
||||
@@ -56,23 +57,24 @@ module Liquid
|
||||
|
||||
alias_method :[], :invoke_drop
|
||||
|
||||
private
|
||||
|
||||
# Check for method existence without invoking respond_to?, which creates symbols
|
||||
def self.invokable?(method_name)
|
||||
invokable_methods.include?(method_name.to_s)
|
||||
end
|
||||
|
||||
def self.invokable_methods
|
||||
@invokable_methods ||= begin
|
||||
unless @invokable_methods
|
||||
blacklist = Liquid::Drop.public_instance_methods + [:each]
|
||||
|
||||
if include?(Enumerable)
|
||||
blacklist += Enumerable.public_instance_methods
|
||||
blacklist -= [:sort, :count, :first, :min, :max, :include?]
|
||||
end
|
||||
|
||||
whitelist = [:to_liquid] + (public_instance_methods - blacklist)
|
||||
Set.new(whitelist.map(&:to_s))
|
||||
@invokable_methods = Set.new(whitelist.map(&:to_s))
|
||||
end
|
||||
@invokable_methods
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
module Liquid
|
||||
class Error < ::StandardError
|
||||
attr_accessor :line_number
|
||||
attr_accessor :template_name
|
||||
attr_accessor :markup_context
|
||||
|
||||
def to_s(with_prefix = true)
|
||||
@@ -17,6 +16,20 @@ module Liquid
|
||||
str
|
||||
end
|
||||
|
||||
def set_line_number_from_token(token)
|
||||
return unless token.respond_to?(:line_number)
|
||||
return if line_number
|
||||
self.line_number = token.line_number
|
||||
end
|
||||
|
||||
def self.render(e)
|
||||
if e.is_a?(Liquid::Error)
|
||||
e.to_s
|
||||
else
|
||||
"Liquid error: #{e}"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def message_prefix
|
||||
@@ -28,9 +41,7 @@ module Liquid
|
||||
end
|
||||
|
||||
if line_number
|
||||
str << " ("
|
||||
str << template_name << " " if template_name
|
||||
str << "line " << line_number.to_s << ")"
|
||||
str << " (line #{line_number})"
|
||||
end
|
||||
|
||||
str << ": "
|
||||
@@ -38,6 +49,10 @@ module Liquid
|
||||
end
|
||||
end
|
||||
|
||||
class FilterError < Error
|
||||
attr_accessor :original_exception
|
||||
end
|
||||
|
||||
ArgumentError = Class.new(Error)
|
||||
ContextError = Class.new(Error)
|
||||
FileSystemError = Class.new(Error)
|
||||
@@ -46,11 +61,4 @@ module Liquid
|
||||
StackLevelError = Class.new(Error)
|
||||
TaintedError = Class.new(Error)
|
||||
MemoryError = Class.new(Error)
|
||||
ZeroDivisionError = Class.new(Error)
|
||||
FloatDomainError = Class.new(Error)
|
||||
UndefinedVariable = Class.new(Error)
|
||||
UndefinedDropMethod = Class.new(Error)
|
||||
UndefinedFilter = Class.new(Error)
|
||||
MethodOverrideError = Class.new(Error)
|
||||
InternalError = Class.new(Error)
|
||||
end
|
||||
|
||||
@@ -1,44 +1,27 @@
|
||||
module Liquid
|
||||
class Expression
|
||||
class MethodLiteral
|
||||
attr_reader :method_name, :to_s
|
||||
|
||||
def initialize(method_name, to_s)
|
||||
@method_name = method_name
|
||||
@to_s = to_s
|
||||
end
|
||||
|
||||
def to_liquid
|
||||
to_s
|
||||
end
|
||||
end
|
||||
|
||||
LITERALS = {
|
||||
nil => nil, 'nil'.freeze => nil, 'null'.freeze => nil, ''.freeze => nil,
|
||||
'true'.freeze => true,
|
||||
'false'.freeze => false,
|
||||
'blank'.freeze => MethodLiteral.new(:blank?, '').freeze,
|
||||
'empty'.freeze => MethodLiteral.new(:empty?, '').freeze
|
||||
}.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/
|
||||
'blank'.freeze => :blank?,
|
||||
'empty'.freeze => :empty?
|
||||
}
|
||||
|
||||
def self.parse(markup)
|
||||
if LITERALS.key?(markup)
|
||||
LITERALS[markup]
|
||||
else
|
||||
case markup
|
||||
when SINGLE_QUOTED_STRING, DOUBLE_QUOTED_STRING
|
||||
when /\A'(.*)'\z/m # Single quoted strings
|
||||
$1
|
||||
when INTEGERS_REGEX
|
||||
when /\A"(.*)"\z/m # Double quoted strings
|
||||
$1
|
||||
when /\A(-?\d+)\z/ # Integer and floats
|
||||
$1.to_i
|
||||
when RANGES_REGEX
|
||||
when /\A\((\S+)\.\.(\S+)\)\z/ # Ranges
|
||||
RangeLookup.parse($1, $2)
|
||||
when FLOATS_REGEX
|
||||
when /\A(-?\d[\d\.]+)\z/ # Floats
|
||||
$1.to_f
|
||||
else
|
||||
VariableLookup.parse(markup)
|
||||
|
||||
@@ -7,56 +7,44 @@ class String # :nodoc:
|
||||
end
|
||||
end
|
||||
|
||||
class Symbol # :nodoc:
|
||||
def to_liquid
|
||||
to_s
|
||||
end
|
||||
end
|
||||
|
||||
class Array # :nodoc:
|
||||
class Array # :nodoc:
|
||||
def to_liquid
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
class Hash # :nodoc:
|
||||
class Hash # :nodoc:
|
||||
def to_liquid
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
class Numeric # :nodoc:
|
||||
class Numeric # :nodoc:
|
||||
def to_liquid
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
class Range # :nodoc:
|
||||
class Time # :nodoc:
|
||||
def to_liquid
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
class Time # :nodoc:
|
||||
class DateTime < Date # :nodoc:
|
||||
def to_liquid
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
class DateTime < Date # :nodoc:
|
||||
def to_liquid
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
class Date # :nodoc:
|
||||
class Date # :nodoc:
|
||||
def to_liquid
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
class TrueClass
|
||||
def to_liquid # :nodoc:
|
||||
def to_liquid # :nodoc:
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,8 +8,8 @@ module Liquid
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# Liquid::Template.file_system = Liquid::LocalFileSystem.new(template_path)
|
||||
# liquid = Liquid::Template.parse(template)
|
||||
# Liquid::Template.file_system = Liquid::LocalFileSystem.new(template_path)
|
||||
# liquid = Liquid::Template.parse(template)
|
||||
#
|
||||
# This will parse the template with a LocalFileSystem implementation rooted at 'template_path'.
|
||||
class BlankFileSystem
|
||||
@@ -26,10 +26,10 @@ module Liquid
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# file_system = Liquid::LocalFileSystem.new("/some/path")
|
||||
# file_system = Liquid::LocalFileSystem.new("/some/path")
|
||||
#
|
||||
# file_system.full_path("mypartial") # => "/some/path/_mypartial.liquid"
|
||||
# file_system.full_path("dir/mypartial") # => "/some/path/dir/_mypartial.liquid"
|
||||
# file_system.full_path("mypartial") # => "/some/path/_mypartial.liquid"
|
||||
# file_system.full_path("dir/mypartial") # => "/some/path/dir/_mypartial.liquid"
|
||||
#
|
||||
# Optionally in the second argument you can specify a custom pattern for template filenames.
|
||||
# The Kernel::sprintf format specification is used.
|
||||
@@ -37,9 +37,9 @@ module Liquid
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# file_system = Liquid::LocalFileSystem.new("/some/path", "%s.html")
|
||||
# file_system = Liquid::LocalFileSystem.new("/some/path", "%s.html")
|
||||
#
|
||||
# file_system.full_path("index") # => "/some/path/index.html"
|
||||
# file_system.full_path("index") # => "/some/path/index.html"
|
||||
#
|
||||
class LocalFileSystem
|
||||
attr_accessor :root
|
||||
@@ -65,7 +65,7 @@ module Liquid
|
||||
File.join(root, @pattern % template_path)
|
||||
end
|
||||
|
||||
raise FileSystemError, "Illegal template path '#{File.expand_path(full_path)}'" unless File.expand_path(full_path).start_with?(File.expand_path(root))
|
||||
raise FileSystemError, "Illegal template path '#{File.expand_path(full_path)}'" unless File.expand_path(full_path) =~ /\A#{File.expand_path(root)}/
|
||||
|
||||
full_path
|
||||
end
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
module Liquid
|
||||
class ForloopDrop < Drop
|
||||
def initialize(name, length, parentloop)
|
||||
@name = name
|
||||
@length = length
|
||||
@parentloop = parentloop
|
||||
@index = 0
|
||||
end
|
||||
|
||||
attr_reader :name, :length, :parentloop
|
||||
|
||||
def index
|
||||
@index + 1
|
||||
end
|
||||
|
||||
def index0
|
||||
@index
|
||||
end
|
||||
|
||||
def rindex
|
||||
@length - @index
|
||||
end
|
||||
|
||||
def rindex0
|
||||
@length - @index - 1
|
||||
end
|
||||
|
||||
def first
|
||||
@index == 0
|
||||
end
|
||||
|
||||
def last
|
||||
@index == @length - 1
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def increment!
|
||||
@index += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -24,10 +24,10 @@ module Liquid
|
||||
private
|
||||
|
||||
def interpolate(name, vars)
|
||||
name.gsub(/%\{(\w+)\}/) do
|
||||
name.gsub(/%\{(\w+)\}/) {
|
||||
# raise TranslationError, "Undefined key #{$1} for interpolation in translation #{name}" unless vars[$1.to_sym]
|
||||
(vars[$1.to_sym]).to_s
|
||||
end
|
||||
"#{vars[$1.to_sym]}"
|
||||
}
|
||||
end
|
||||
|
||||
def deep_fetch_translation(name)
|
||||
|
||||
@@ -12,25 +12,23 @@ module Liquid
|
||||
')'.freeze => :close_round,
|
||||
'?'.freeze => :question,
|
||||
'-'.freeze => :dash
|
||||
}.freeze
|
||||
}
|
||||
IDENTIFIER = /[a-zA-Z_][\w-]*\??/
|
||||
SINGLE_STRING_LITERAL = /'[^\']*'/
|
||||
DOUBLE_STRING_LITERAL = /"[^\"]*"/
|
||||
NUMBER_LITERAL = /-?\d+(\.\d+)?/
|
||||
DOTDOT = /\.\./
|
||||
COMPARISON_OPERATOR = /==|!=|<>|<=?|>=?|contains(?=\s)/
|
||||
WHITESPACE_OR_NOTHING = /\s*/
|
||||
COMPARISON_OPERATOR = /==|!=|<>|<=?|>=?|contains/
|
||||
|
||||
def initialize(input)
|
||||
@ss = StringScanner.new(input)
|
||||
@ss = StringScanner.new(input.rstrip)
|
||||
end
|
||||
|
||||
def tokenize
|
||||
@output = []
|
||||
|
||||
until @ss.eos?
|
||||
@ss.skip(WHITESPACE_OR_NOTHING)
|
||||
break if @ss.eos?
|
||||
@ss.skip(/\s*/)
|
||||
tok = case
|
||||
when t = @ss.scan(COMPARISON_OPERATOR) then [:comparison, t]
|
||||
when t = @ss.scan(SINGLE_STRING_LITERAL) then [:string, t]
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
errors:
|
||||
syntax:
|
||||
tag_unexpected_args: "Syntax Error in '%{tag}' - Valid syntax: %{tag}"
|
||||
assign: "Syntax Error in 'assign' - Valid syntax: assign [var] = [source]"
|
||||
capture: "Syntax Error in 'capture' - Valid syntax: capture [var]"
|
||||
case: "Syntax Error in 'case' - Valid syntax: case [condition]"
|
||||
@@ -14,7 +13,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: "'%{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_outer_tag: "Unexpected outer '%{tag}' tag"
|
||||
tag_termination: "Tag '%{token}' was not properly terminated with regexp: %{tag_end}"
|
||||
@@ -22,5 +21,3 @@
|
||||
tag_never_closed: "'%{block_name}' tag was never closed"
|
||||
meta_syntax_error: "Liquid syntax error: #{e.message}"
|
||||
table_row: "Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3"
|
||||
argument:
|
||||
include: "Argument error in tag 'include' - Illegal template name"
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
module Liquid
|
||||
class ParseContext
|
||||
attr_accessor :locale, :line_number, :trim_whitespace, :depth
|
||||
attr_reader :partial, :warnings, :error_mode
|
||||
|
||||
def initialize(options = {})
|
||||
@template_options = options ? options.dup : {}
|
||||
@locale = @template_options[:locale] ||= I18n.new
|
||||
@warnings = []
|
||||
self.depth = 0
|
||||
self.partial = false
|
||||
end
|
||||
|
||||
def [](option_key)
|
||||
@options[option_key]
|
||||
end
|
||||
|
||||
def partial=(value)
|
||||
@partial = value
|
||||
@options = value ? partial_options : @template_options
|
||||
@error_mode = @options[:error_mode] || Template.error_mode
|
||||
value
|
||||
end
|
||||
|
||||
def partial_options
|
||||
@partial_options ||= begin
|
||||
dont_pass = @template_options[:include_options_blacklist]
|
||||
if dont_pass == true
|
||||
{ locale: locale }
|
||||
elsif dont_pass.is_a?(Array)
|
||||
@template_options.reject { |k, v| dont_pass.include?(k) }
|
||||
else
|
||||
@template_options
|
||||
end
|
||||
end
|
||||
end
|
||||
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
|
||||
@@ -75,7 +75,7 @@ module Liquid
|
||||
|
||||
def variable_signature
|
||||
str = consume(:id)
|
||||
while look(:open_square)
|
||||
if look(:open_square)
|
||||
str << consume
|
||||
str << expression
|
||||
str << consume(:close_square)
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
module Liquid
|
||||
module ParserSwitching
|
||||
def parse_with_selected_parser(markup)
|
||||
case parse_context.error_mode
|
||||
case @options[:error_mode] || Template.error_mode
|
||||
when :strict then strict_parse_with_error_context(markup)
|
||||
when :lax then lax_parse(markup)
|
||||
when :warn
|
||||
begin
|
||||
return strict_parse_with_error_context(markup)
|
||||
rescue SyntaxError => e
|
||||
parse_context.warnings << e
|
||||
e.set_line_number_from_token(markup)
|
||||
@warnings ||= []
|
||||
@warnings << e
|
||||
return lax_parse(markup)
|
||||
end
|
||||
end
|
||||
@@ -19,7 +21,6 @@ module Liquid
|
||||
def strict_parse_with_error_context(markup)
|
||||
strict_parse(markup)
|
||||
rescue SyntaxError => e
|
||||
e.line_number = line_number
|
||||
e.markup_context = markup_context(markup)
|
||||
raise e
|
||||
end
|
||||
|
||||
@@ -19,7 +19,7 @@ module Liquid
|
||||
# inside of <tt>{% include %}</tt> tags.
|
||||
#
|
||||
# profile.each do |node|
|
||||
# # Access to the node itself
|
||||
# # Access to the token itself
|
||||
# node.code
|
||||
#
|
||||
# # Which template and line number of this node.
|
||||
@@ -46,15 +46,15 @@ module Liquid
|
||||
class Timing
|
||||
attr_reader :code, :partial, :line_number, :children
|
||||
|
||||
def initialize(node, partial)
|
||||
@code = node.respond_to?(:raw) ? node.raw : node
|
||||
def initialize(token, partial)
|
||||
@code = token.respond_to?(:raw) ? token.raw : token
|
||||
@partial = partial
|
||||
@line_number = node.respond_to?(:line_number) ? node.line_number : nil
|
||||
@line_number = token.respond_to?(:line_number) ? token.line_number : nil
|
||||
@children = []
|
||||
end
|
||||
|
||||
def self.start(node, partial)
|
||||
new(node, partial).tap(&:start)
|
||||
def self.start(token, partial)
|
||||
new(token, partial).tap(&:start)
|
||||
end
|
||||
|
||||
def start
|
||||
@@ -70,11 +70,11 @@ module Liquid
|
||||
end
|
||||
end
|
||||
|
||||
def self.profile_node_render(node)
|
||||
if Profiler.current_profile && node.respond_to?(:render)
|
||||
Profiler.current_profile.start_node(node)
|
||||
def self.profile_token_render(token)
|
||||
if Profiler.current_profile && token.respond_to?(:render)
|
||||
Profiler.current_profile.start_token(token)
|
||||
output = yield
|
||||
Profiler.current_profile.end_node(node)
|
||||
Profiler.current_profile.end_token(token)
|
||||
output
|
||||
else
|
||||
yield
|
||||
@@ -132,11 +132,11 @@ module Liquid
|
||||
@root_timing.children.length
|
||||
end
|
||||
|
||||
def start_node(node)
|
||||
@timing_stack.push(Timing.start(node, current_partial))
|
||||
def start_token(token)
|
||||
@timing_stack.push(Timing.start(token, current_partial))
|
||||
end
|
||||
|
||||
def end_node(_node)
|
||||
def end_token(_token)
|
||||
timing = @timing_stack.pop
|
||||
timing.finish
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
module Liquid
|
||||
class BlockBody
|
||||
def render_node_with_profiling(node, output, context, skip_output = false)
|
||||
Profiler.profile_node_render(node) do
|
||||
render_node_without_profiling(node, output, context, skip_output)
|
||||
def render_token_with_profiling(token, context)
|
||||
Profiler.profile_token_render(token) do
|
||||
render_token_without_profiling(token, context)
|
||||
end
|
||||
end
|
||||
|
||||
alias_method :render_node_without_profiling, :render_node_to_output
|
||||
alias_method :render_node_to_output, :render_node_with_profiling
|
||||
alias_method :render_token_without_profiling, :render_token
|
||||
alias_method :render_token, :render_token_with_profiling
|
||||
end
|
||||
|
||||
class Include < Tag
|
||||
|
||||
@@ -16,22 +16,7 @@ module Liquid
|
||||
end
|
||||
|
||||
def evaluate(context)
|
||||
start_int = to_integer(context.evaluate(@start_obj))
|
||||
end_int = to_integer(context.evaluate(@end_obj))
|
||||
start_int..end_int
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def to_integer(input)
|
||||
case input
|
||||
when Integer
|
||||
input
|
||||
when NilClass, String
|
||||
input.to_i
|
||||
else
|
||||
Utils.to_integer(input)
|
||||
end
|
||||
context.evaluate(@start_obj).to_i..context.evaluate(@end_obj).to_i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,14 +9,8 @@ module Liquid
|
||||
'<'.freeze => '<'.freeze,
|
||||
'"'.freeze => '"'.freeze,
|
||||
"'".freeze => '''.freeze
|
||||
}.freeze
|
||||
}
|
||||
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/
|
||||
STRIP_HTML_BLOCKS = Regexp.union(
|
||||
/<script.*?<\/script>/m,
|
||||
/<!--.*?-->/m,
|
||||
/<style.*?<\/style>/m
|
||||
)
|
||||
STRIP_HTML_TAGS = /<.*?>/m
|
||||
|
||||
# Return the size of an array or of an string
|
||||
def size(input)
|
||||
@@ -39,7 +33,7 @@ module Liquid
|
||||
end
|
||||
|
||||
def escape(input)
|
||||
CGI.escapeHTML(input.to_s).untaint unless input.nil?
|
||||
CGI.escapeHTML(input).untaint rescue input
|
||||
end
|
||||
alias_method :h, :escape
|
||||
|
||||
@@ -48,21 +42,12 @@ module Liquid
|
||||
end
|
||||
|
||||
def url_encode(input)
|
||||
CGI.escape(input.to_s) unless input.nil?
|
||||
end
|
||||
|
||||
def url_decode(input)
|
||||
return if input.nil?
|
||||
|
||||
result = CGI.unescape(input.to_s)
|
||||
raise Liquid::ArgumentError, "invalid byte sequence in #{result.encoding}" unless result.valid_encoding?
|
||||
|
||||
result
|
||||
CGI.escape(input) rescue input
|
||||
end
|
||||
|
||||
def slice(input, offset, length = nil)
|
||||
offset = Utils.to_integer(offset)
|
||||
length = length ? Utils.to_integer(length) : 1
|
||||
offset = Integer(offset)
|
||||
length = length ? Integer(length) : 1
|
||||
|
||||
if input.is_a?(Array)
|
||||
input.slice(offset, length) || []
|
||||
@@ -74,21 +59,17 @@ module Liquid
|
||||
# Truncate a string down to x characters
|
||||
def truncate(input, length = 50, truncate_string = "...".freeze)
|
||||
return if input.nil?
|
||||
input_str = input.to_s
|
||||
length = Utils.to_integer(length)
|
||||
truncate_string_str = truncate_string.to_s
|
||||
l = length - truncate_string_str.length
|
||||
l = length.to_i - truncate_string.length
|
||||
l = 0 if l < 0
|
||||
input_str.length > length ? input_str[0...l] + truncate_string_str : input_str
|
||||
input.length > length.to_i ? input[0...l] + truncate_string : input
|
||||
end
|
||||
|
||||
def truncatewords(input, words = 15, truncate_string = "...".freeze)
|
||||
return if input.nil?
|
||||
wordlist = input.to_s.split
|
||||
words = Utils.to_integer(words)
|
||||
l = words - 1
|
||||
l = words.to_i - 1
|
||||
l = 0 if l < 0
|
||||
wordlist.length > l ? wordlist[0..l].join(" ".freeze) + truncate_string.to_s : input
|
||||
wordlist.length > l ? wordlist[0..l].join(" ".freeze) + truncate_string : input
|
||||
end
|
||||
|
||||
# Split input string into an array of substrings separated by given pattern.
|
||||
@@ -97,7 +78,7 @@ module Liquid
|
||||
# <div class="summary">{{ post | split '//' | first }}</div>
|
||||
#
|
||||
def split(input, pattern)
|
||||
input.to_s.split(pattern.to_s)
|
||||
input.to_s.split(pattern)
|
||||
end
|
||||
|
||||
def strip(input)
|
||||
@@ -114,9 +95,7 @@ module Liquid
|
||||
|
||||
def strip_html(input)
|
||||
empty = ''.freeze
|
||||
result = input.to_s.gsub(STRIP_HTML_BLOCKS, empty)
|
||||
result.gsub!(STRIP_HTML_TAGS, empty)
|
||||
result
|
||||
input.to_s.gsub(/<script.*?<\/script>/m, empty).gsub(/<!--.*?-->/m, empty).gsub(/<style.*?<\/style>/m, empty).gsub(/<.*?>/m, empty)
|
||||
end
|
||||
|
||||
# Remove all newlines from the string
|
||||
@@ -133,19 +112,12 @@ module Liquid
|
||||
# provide optional property with which to sort an array of hashes or drops
|
||||
def sort(input, property = nil)
|
||||
ary = InputIterator.new(input)
|
||||
|
||||
return [] if ary.empty?
|
||||
|
||||
if property.nil?
|
||||
ary.sort do |a, b|
|
||||
nil_safe_compare(a, b)
|
||||
end
|
||||
elsif ary.all? { |el| el.respond_to?(:[]) }
|
||||
begin
|
||||
ary.sort { |a, b| nil_safe_compare(a[property], b[property]) }
|
||||
rescue TypeError
|
||||
raise_property_error(property)
|
||||
end
|
||||
ary.sort
|
||||
elsif ary.first.respond_to?(:[]) && !ary.first[property].nil?
|
||||
ary.sort { |a, b| a[property] <=> b[property] }
|
||||
elsif ary.first.respond_to?(property)
|
||||
ary.sort { |a, b| a.send(property) <=> b.send(property) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -154,40 +126,12 @@ module Liquid
|
||||
def sort_natural(input, property = nil)
|
||||
ary = InputIterator.new(input)
|
||||
|
||||
return [] if ary.empty?
|
||||
|
||||
if property.nil?
|
||||
ary.sort do |a, b|
|
||||
nil_safe_casecmp(a, b)
|
||||
end
|
||||
elsif ary.all? { |el| el.respond_to?(:[]) }
|
||||
begin
|
||||
ary.sort { |a, b| nil_safe_casecmp(a[property], b[property]) }
|
||||
rescue TypeError
|
||||
raise_property_error(property)
|
||||
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?
|
||||
begin
|
||||
ary.select { |item| item[property] }
|
||||
rescue TypeError
|
||||
raise_property_error(property)
|
||||
end
|
||||
elsif ary.first.respond_to?(:[])
|
||||
begin
|
||||
ary.select { |item| item[property] == target_value }
|
||||
rescue TypeError
|
||||
raise_property_error(property)
|
||||
end
|
||||
ary.sort { |a, b| a.casecmp(b) }
|
||||
elsif ary.first.respond_to?(:[]) && !ary.first[property].nil?
|
||||
ary.sort { |a, b| a[property].casecmp(b[property]) }
|
||||
elsif ary.first.respond_to?(property)
|
||||
ary.sort { |a, b| a.send(property).casecmp(b.send(property)) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -195,17 +139,10 @@ module Liquid
|
||||
# provide optional property with which to determine uniqueness
|
||||
def uniq(input, property = nil)
|
||||
ary = InputIterator.new(input)
|
||||
|
||||
if property.nil?
|
||||
ary.uniq
|
||||
elsif ary.empty? # The next two cases assume a non-empty array.
|
||||
[]
|
||||
elsif ary.first.respond_to?(:[])
|
||||
begin
|
||||
ary.uniq { |a| a[property] }
|
||||
rescue TypeError
|
||||
raise_property_error(property)
|
||||
end
|
||||
input.uniq
|
||||
elsif input.first.respond_to?(:[])
|
||||
input.uniq{ |a| a[property] }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -223,50 +160,29 @@ module Liquid
|
||||
if property == "to_liquid".freeze
|
||||
e
|
||||
elsif e.respond_to?(:[])
|
||||
r = e[property]
|
||||
r.is_a?(Proc) ? r.call : r
|
||||
end
|
||||
end
|
||||
rescue TypeError
|
||||
raise_property_error(property)
|
||||
end
|
||||
|
||||
# Remove nils within an array
|
||||
# provide optional property with which to check for nil
|
||||
def compact(input, property = nil)
|
||||
ary = InputIterator.new(input)
|
||||
|
||||
if property.nil?
|
||||
ary.compact
|
||||
elsif ary.empty? # The next two cases assume a non-empty array.
|
||||
[]
|
||||
elsif ary.first.respond_to?(:[])
|
||||
begin
|
||||
ary.reject { |a| a[property].nil? }
|
||||
rescue TypeError
|
||||
raise_property_error(property)
|
||||
e[property]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Replace occurrences of a string with another
|
||||
def replace(input, string, replacement = ''.freeze)
|
||||
input.to_s.gsub(string.to_s, replacement.to_s)
|
||||
input.to_s.gsub(string, replacement.to_s)
|
||||
end
|
||||
|
||||
# Replace the first occurrences of a string with another
|
||||
def replace_first(input, string, replacement = ''.freeze)
|
||||
input.to_s.sub(string.to_s, replacement.to_s)
|
||||
input.to_s.sub(string, replacement.to_s)
|
||||
end
|
||||
|
||||
# remove a substring
|
||||
def remove(input, string)
|
||||
input.to_s.gsub(string.to_s, ''.freeze)
|
||||
input.to_s.gsub(string, ''.freeze)
|
||||
end
|
||||
|
||||
# remove the first occurrences of a substring
|
||||
def remove_first(input, string)
|
||||
input.to_s.sub(string.to_s, ''.freeze)
|
||||
input.to_s.sub(string, ''.freeze)
|
||||
end
|
||||
|
||||
# add one string to another
|
||||
@@ -275,9 +191,6 @@ module Liquid
|
||||
end
|
||||
|
||||
def concat(input, array)
|
||||
unless array.respond_to?(:to_ary)
|
||||
raise ArgumentError.new("concat filter requires an array argument")
|
||||
end
|
||||
InputIterator.new(input).concat(array)
|
||||
end
|
||||
|
||||
@@ -325,7 +238,7 @@ module Liquid
|
||||
def date(input, format)
|
||||
return input if format.to_s.empty?
|
||||
|
||||
return input unless date = Utils.to_date(input)
|
||||
return input unless date = to_date(input)
|
||||
|
||||
date.strftime(format.to_s)
|
||||
end
|
||||
@@ -348,12 +261,6 @@ module Liquid
|
||||
array.last if array.respond_to?(:last)
|
||||
end
|
||||
|
||||
# absolute value
|
||||
def abs(input)
|
||||
result = Utils.to_number(input).abs
|
||||
result.is_a?(BigDecimal) ? result.to_f : result
|
||||
end
|
||||
|
||||
# addition
|
||||
def plus(input, operand)
|
||||
apply_operation(input, operand, :+)
|
||||
@@ -372,88 +279,72 @@ module Liquid
|
||||
# division
|
||||
def divided_by(input, operand)
|
||||
apply_operation(input, operand, :/)
|
||||
rescue ::ZeroDivisionError => e
|
||||
raise Liquid::ZeroDivisionError, e.message
|
||||
end
|
||||
|
||||
def modulo(input, operand)
|
||||
apply_operation(input, operand, :%)
|
||||
rescue ::ZeroDivisionError => e
|
||||
raise Liquid::ZeroDivisionError, e.message
|
||||
end
|
||||
|
||||
def round(input, n = 0)
|
||||
result = Utils.to_number(input).round(Utils.to_number(n))
|
||||
result = to_number(input).round(to_number(n))
|
||||
result = result.to_f if result.is_a?(BigDecimal)
|
||||
result = result.to_i if n == 0
|
||||
result
|
||||
rescue ::FloatDomainError => e
|
||||
raise Liquid::FloatDomainError, e.message
|
||||
end
|
||||
|
||||
def ceil(input)
|
||||
Utils.to_number(input).ceil.to_i
|
||||
rescue ::FloatDomainError => e
|
||||
raise Liquid::FloatDomainError, e.message
|
||||
to_number(input).ceil.to_i
|
||||
end
|
||||
|
||||
def floor(input)
|
||||
Utils.to_number(input).floor.to_i
|
||||
rescue ::FloatDomainError => e
|
||||
raise Liquid::FloatDomainError, e.message
|
||||
to_number(input).floor.to_i
|
||||
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
|
||||
else
|
||||
input
|
||||
end
|
||||
def default(input, default_value = "".freeze)
|
||||
is_blank = input.respond_to?(:empty?) ? input.empty? : !input
|
||||
is_blank ? default_value : input
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def raise_property_error(property)
|
||||
raise Liquid::ArgumentError.new("cannot select the property '#{property}'")
|
||||
def to_number(obj)
|
||||
case obj
|
||||
when Float
|
||||
BigDecimal.new(obj.to_s)
|
||||
when Numeric
|
||||
obj
|
||||
when String
|
||||
(obj.strip =~ /\A\d+\.\d+\z/) ? BigDecimal.new(obj) : obj.to_i
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def to_date(obj)
|
||||
return obj if obj.respond_to?(:strftime)
|
||||
|
||||
if obj.is_a?(String)
|
||||
return nil if obj.empty?
|
||||
obj = obj.downcase
|
||||
end
|
||||
|
||||
case obj
|
||||
when 'now'.freeze, 'today'.freeze
|
||||
Time.now
|
||||
when /\A\d+\z/, Integer
|
||||
Time.at(obj.to_i)
|
||||
when String
|
||||
Time.parse(obj)
|
||||
end
|
||||
rescue ArgumentError
|
||||
nil
|
||||
end
|
||||
|
||||
def apply_operation(input, operand, operation)
|
||||
result = Utils.to_number(input).send(operation, Utils.to_number(operand))
|
||||
result = to_number(input).send(operation, to_number(operand))
|
||||
result.is_a?(BigDecimal) ? result.to_f : result
|
||||
end
|
||||
|
||||
def nil_safe_compare(a, b)
|
||||
if !a.nil? && !b.nil?
|
||||
a <=> b
|
||||
else
|
||||
a.nil? ? 1 : -1
|
||||
end
|
||||
end
|
||||
|
||||
def nil_safe_casecmp(a, b)
|
||||
if !a.nil? && !b.nil?
|
||||
a.to_s.casecmp(b.to_s)
|
||||
else
|
||||
a.nil? ? 1 : -1
|
||||
end
|
||||
end
|
||||
|
||||
class InputIterator
|
||||
include Enumerable
|
||||
|
||||
@@ -470,30 +361,17 @@ module Liquid
|
||||
end
|
||||
|
||||
def join(glue)
|
||||
to_a.join(glue.to_s)
|
||||
to_a.join(glue)
|
||||
end
|
||||
|
||||
def concat(args)
|
||||
to_a.concat(args)
|
||||
to_a.concat args
|
||||
end
|
||||
|
||||
def reverse
|
||||
reverse_each.to_a
|
||||
end
|
||||
|
||||
def uniq(&block)
|
||||
to_a.uniq(&block)
|
||||
end
|
||||
|
||||
def compact
|
||||
to_a.compact
|
||||
end
|
||||
|
||||
def empty?
|
||||
@input.each { return false }
|
||||
true
|
||||
end
|
||||
|
||||
def each
|
||||
@input.each do |e|
|
||||
yield(e.respond_to?(:to_liquid) ? e.to_liquid : e)
|
||||
|
||||
@@ -26,20 +26,14 @@ module Liquid
|
||||
end
|
||||
|
||||
def self.add_filter(filter)
|
||||
raise ArgumentError, "Expected module but got: #{filter.class}" unless filter.is_a?(Module)
|
||||
unless self.include?(filter)
|
||||
invokable_non_public_methods = (filter.private_instance_methods + filter.protected_instance_methods).select { |m| invokable?(m) }
|
||||
if invokable_non_public_methods.any?
|
||||
raise MethodOverrideError, "Filter overrides registered public methods as non public: #{invokable_non_public_methods.join(', ')}"
|
||||
else
|
||||
send(:include, filter)
|
||||
@filter_methods.merge(filter.public_instance_methods.map(&:to_s))
|
||||
end
|
||||
raise ArgumentError, "Expected module but got: #{f.class}" unless filter.is_a?(Module)
|
||||
unless self.class.include?(filter)
|
||||
send(:include, filter)
|
||||
@filter_methods.merge(filter.public_instance_methods.map(&:to_s))
|
||||
end
|
||||
end
|
||||
|
||||
def self.global_filter(filter)
|
||||
@@strainer_class_cache.clear
|
||||
@@global_strainer.add_filter(filter)
|
||||
end
|
||||
|
||||
@@ -53,14 +47,20 @@ module Liquid
|
||||
|
||||
def invoke(method, *args)
|
||||
if self.class.invokable?(method)
|
||||
send(method, *args)
|
||||
elsif @context && @context.strict_filters
|
||||
raise Liquid::UndefinedFilter, "undefined filter #{method}"
|
||||
begin
|
||||
send(method, *args)
|
||||
rescue ::ArgumentError => e
|
||||
raise Liquid::ArgumentError.new(e.message)
|
||||
rescue Liquid::Error
|
||||
raise
|
||||
rescue ::StandardError => exception
|
||||
error = Liquid::FilterError.new(exception.message)
|
||||
error.original_exception = exception
|
||||
raise error
|
||||
end
|
||||
else
|
||||
args.first
|
||||
end
|
||||
rescue ::ArgumentError => e
|
||||
raise Liquid::ArgumentError, e.message, e.backtrace
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
module Liquid
|
||||
class TablerowloopDrop < Drop
|
||||
def initialize(length, cols)
|
||||
@length = length
|
||||
@row = 1
|
||||
@col = 1
|
||||
@cols = cols
|
||||
@index = 0
|
||||
end
|
||||
|
||||
attr_reader :length, :col, :row
|
||||
|
||||
def index
|
||||
@index + 1
|
||||
end
|
||||
|
||||
def index0
|
||||
@index
|
||||
end
|
||||
|
||||
def col0
|
||||
@col - 1
|
||||
end
|
||||
|
||||
def rindex
|
||||
@length - @index
|
||||
end
|
||||
|
||||
def rindex0
|
||||
@length - @index - 1
|
||||
end
|
||||
|
||||
def first
|
||||
@index == 0
|
||||
end
|
||||
|
||||
def last
|
||||
@index == @length - 1
|
||||
end
|
||||
|
||||
def col_first
|
||||
@col == 1
|
||||
end
|
||||
|
||||
def col_last
|
||||
@col == @cols
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def increment!
|
||||
@index += 1
|
||||
|
||||
if @col == @cols
|
||||
@col = 1
|
||||
@row += 1
|
||||
else
|
||||
@col += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,24 +1,23 @@
|
||||
module Liquid
|
||||
class Tag
|
||||
attr_reader :nodelist, :tag_name, :line_number, :parse_context
|
||||
alias_method :options, :parse_context
|
||||
attr_accessor :options, :line_number
|
||||
attr_reader :nodelist, :warnings
|
||||
include ParserSwitching
|
||||
|
||||
class << self
|
||||
def parse(tag_name, markup, tokenizer, options)
|
||||
def parse(tag_name, markup, tokens, options)
|
||||
tag = new(tag_name, markup, options)
|
||||
tag.parse(tokenizer)
|
||||
tag.parse(tokens)
|
||||
tag
|
||||
end
|
||||
|
||||
private :new
|
||||
end
|
||||
|
||||
def initialize(tag_name, markup, parse_context)
|
||||
def initialize(tag_name, markup, options)
|
||||
@tag_name = tag_name
|
||||
@markup = markup
|
||||
@parse_context = parse_context
|
||||
@line_number = parse_context.line_number
|
||||
@options = options
|
||||
end
|
||||
|
||||
def parse(_tokens)
|
||||
|
||||
@@ -10,13 +10,12 @@ module Liquid
|
||||
class Assign < Tag
|
||||
Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om
|
||||
|
||||
attr_reader :to, :from
|
||||
|
||||
def initialize(tag_name, markup, options)
|
||||
super
|
||||
if markup =~ Syntax
|
||||
@to = $1
|
||||
@from = Variable.new($2, options)
|
||||
@from.line_number = line_number
|
||||
else
|
||||
raise SyntaxError.new options[:locale].t("errors.syntax.assign".freeze)
|
||||
end
|
||||
@@ -24,35 +23,17 @@ module Liquid
|
||||
|
||||
def render(context)
|
||||
val = @from.render(context)
|
||||
context.set_root(@to, val)
|
||||
context.resource_limits.assign_score += assign_score_of(val)
|
||||
context.scopes.last[@to] = val
|
||||
|
||||
inc = val.instance_of?(String) || val.instance_of?(Array) || val.instance_of?(Hash) ? val.length : 1
|
||||
context.resource_limits.assign_score += inc
|
||||
|
||||
''.freeze
|
||||
end
|
||||
|
||||
def blank?
|
||||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assign_score_of(val)
|
||||
if val.instance_of?(String)
|
||||
val.bytesize
|
||||
elsif val.instance_of?(Array) || val.instance_of?(Hash)
|
||||
sum = 1
|
||||
# Uses #each to avoid extra allocations.
|
||||
val.each { |child| sum += assign_score_of(child) }
|
||||
sum
|
||||
else
|
||||
1
|
||||
end
|
||||
end
|
||||
|
||||
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
||||
def children
|
||||
[@node.from]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Template.register_tag('assign'.freeze, Assign)
|
||||
|
||||
@@ -24,8 +24,8 @@ module Liquid
|
||||
|
||||
def render(context)
|
||||
output = super
|
||||
context.set_root(@to, output)
|
||||
context.resource_limits.assign_score += output.bytesize
|
||||
context.scopes.last[@to] = output
|
||||
context.resource_limits.assign_score += output.length
|
||||
''.freeze
|
||||
end
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@ module Liquid
|
||||
Syntax = /(#{QuotedFragment})/o
|
||||
WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/om
|
||||
|
||||
attr_reader :blocks, :left
|
||||
|
||||
def initialize(tag_name, markup, options)
|
||||
super
|
||||
@blocks = []
|
||||
@@ -18,7 +16,7 @@ module Liquid
|
||||
|
||||
def parse(tokens)
|
||||
body = BlockBody.new
|
||||
while parse_body(body, tokens)
|
||||
while more = parse_body(body, tokens)
|
||||
body = @blocks.last.attachment
|
||||
end
|
||||
end
|
||||
@@ -39,19 +37,20 @@ module Liquid
|
||||
end
|
||||
|
||||
def render(context)
|
||||
execute_else_block = true
|
||||
context.stack do
|
||||
execute_else_block = true
|
||||
|
||||
output = ''
|
||||
@blocks.each do |block|
|
||||
if block.else?
|
||||
return block.attachment.render(context) if execute_else_block
|
||||
elsif block.evaluate(context)
|
||||
execute_else_block = false
|
||||
output << block.attachment.render(context)
|
||||
output = ''
|
||||
@blocks.each do |block|
|
||||
if block.else?
|
||||
return block.attachment.render(context) if execute_else_block
|
||||
elsif block.evaluate(context)
|
||||
execute_else_block = false
|
||||
output << block.attachment.render(context)
|
||||
end
|
||||
end
|
||||
output
|
||||
end
|
||||
|
||||
output
|
||||
end
|
||||
|
||||
private
|
||||
@@ -81,12 +80,6 @@ module Liquid
|
||||
block.attach(BlockBody.new)
|
||||
@blocks << block
|
||||
end
|
||||
|
||||
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
||||
def children
|
||||
[@node.left] + @node.blocks
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Template.register_tag('case'.freeze, Case)
|
||||
|
||||
@@ -15,8 +15,6 @@ module Liquid
|
||||
SimpleSyntax = /\A#{QuotedFragment}+/o
|
||||
NamedSyntax = /\A(#{QuotedFragment})\s*\:\s*(.*)/om
|
||||
|
||||
attr_reader :variables
|
||||
|
||||
def initialize(tag_name, markup, options)
|
||||
super
|
||||
case markup
|
||||
@@ -32,15 +30,17 @@ module Liquid
|
||||
end
|
||||
|
||||
def render(context)
|
||||
context.registers[:cycle] ||= {}
|
||||
context.registers[:cycle] ||= Hash.new(0)
|
||||
|
||||
key = context.evaluate(@name)
|
||||
iteration = context.registers[:cycle][key].to_i
|
||||
result = context.evaluate(@variables[iteration])
|
||||
iteration += 1
|
||||
iteration = 0 if iteration >= @variables.size
|
||||
context.registers[:cycle][key] = iteration
|
||||
result
|
||||
context.stack do
|
||||
key = context.evaluate(@name)
|
||||
iteration = context.registers[:cycle][key]
|
||||
result = context.evaluate(@variables[iteration])
|
||||
iteration += 1
|
||||
iteration = 0 if iteration >= @variables.size
|
||||
context.registers[:cycle][key] = iteration
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
@@ -51,12 +51,6 @@ module Liquid
|
||||
$1 ? Expression.parse($1) : nil
|
||||
end.compact
|
||||
end
|
||||
|
||||
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
||||
def children
|
||||
Array(@node.variables)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Template.register_tag('cycle', Cycle)
|
||||
|
||||
@@ -23,7 +23,7 @@ module Liquid
|
||||
# {{ item.name }}
|
||||
# {% end %}
|
||||
#
|
||||
# To reverse the for loop simply use {% for item in collection reversed %} (note that the flag's spelling is different to the filter `reverse`)
|
||||
# To reverse the for loop simply use {% for item in collection reversed %}
|
||||
#
|
||||
# == Available variables:
|
||||
#
|
||||
@@ -46,14 +46,10 @@ module Liquid
|
||||
class For < Block
|
||||
Syntax = /\A(#{VariableSegment}+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
|
||||
|
||||
attr_reader :collection_name, :variable_name, :limit, :from
|
||||
|
||||
def initialize(tag_name, markup, options)
|
||||
super
|
||||
@from = @limit = nil
|
||||
parse_with_selected_parser(markup)
|
||||
@for_block = BlockBody.new
|
||||
@else_block = nil
|
||||
end
|
||||
|
||||
def parse(tokens)
|
||||
@@ -71,13 +67,72 @@ module Liquid
|
||||
end
|
||||
|
||||
def render(context)
|
||||
segment = collection_segment(context)
|
||||
for_offsets = context.registers[:for] ||= Hash.new(0)
|
||||
for_stack = context.registers[:for_stack] ||= []
|
||||
|
||||
if segment.empty?
|
||||
render_else(context)
|
||||
parent_loop = for_stack.last
|
||||
for_stack.push(nil)
|
||||
|
||||
collection = context.evaluate(@collection_name)
|
||||
collection = collection.to_a if collection.is_a?(Range)
|
||||
|
||||
# Maintains Ruby 1.8.7 String#each behaviour on 1.9
|
||||
return render_else(context) unless iterable?(collection)
|
||||
|
||||
from = if @from == :continue
|
||||
for_offsets[@name].to_i
|
||||
else
|
||||
render_segment(context, segment)
|
||||
context.evaluate(@from).to_i
|
||||
end
|
||||
|
||||
limit = context.evaluate(@limit)
|
||||
to = limit ? limit.to_i + from : nil
|
||||
|
||||
segment = Utils.slice_collection(collection, from, to)
|
||||
|
||||
return render_else(context) if segment.empty?
|
||||
|
||||
segment.reverse! if @reversed
|
||||
|
||||
result = ''
|
||||
|
||||
length = segment.length
|
||||
|
||||
# Store our progress through the collection for the continue flag
|
||||
for_offsets[@name] = from + segment.length
|
||||
|
||||
context.stack do
|
||||
segment.each_with_index do |item, index|
|
||||
context[@variable_name] = item
|
||||
loop_vars = {
|
||||
'name'.freeze => @name,
|
||||
'length'.freeze => length,
|
||||
'index'.freeze => index + 1,
|
||||
'index0'.freeze => index,
|
||||
'rindex'.freeze => length - index,
|
||||
'rindex0'.freeze => length - index - 1,
|
||||
'first'.freeze => (index == 0),
|
||||
'last'.freeze => (index == length - 1),
|
||||
'parentloop'.freeze => parent_loop
|
||||
}
|
||||
|
||||
context['forloop'.freeze] = loop_vars
|
||||
for_stack[-1] = loop_vars
|
||||
|
||||
result << @for_block.render(context)
|
||||
|
||||
# Handle any interrupts if they exist.
|
||||
if context.has_interrupt?
|
||||
interrupt = context.pop_interrupt
|
||||
break if interrupt.is_a? BreakInterrupt
|
||||
next if interrupt.is_a? ContinueInterrupt
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
result
|
||||
ensure
|
||||
for_stack.pop
|
||||
end
|
||||
|
||||
protected
|
||||
@@ -86,7 +141,7 @@ module Liquid
|
||||
if markup =~ Syntax
|
||||
@variable_name = $1
|
||||
collection_name = $2
|
||||
@reversed = !!$3
|
||||
@reversed = $3
|
||||
@name = "#{@variable_name}-#{collection_name}"
|
||||
@collection_name = Expression.parse(collection_name)
|
||||
markup.scan(TagAttributes) do |key, value|
|
||||
@@ -100,7 +155,7 @@ module Liquid
|
||||
def strict_parse(markup)
|
||||
p = Parser.new(markup)
|
||||
@variable_name = p.consume(:id)
|
||||
raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_in".freeze)) unless p.id?('in'.freeze)
|
||||
raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_in".freeze)) unless p.id?('in'.freeze)
|
||||
collection_name = p.expression
|
||||
@name = "#{@variable_name}-#{collection_name}"
|
||||
@collection_name = Expression.parse(collection_name)
|
||||
@@ -118,75 +173,6 @@ module Liquid
|
||||
|
||||
private
|
||||
|
||||
def collection_segment(context)
|
||||
offsets = context.registers[:for] ||= {}
|
||||
|
||||
from = if @from == :continue
|
||||
offsets[@name].to_i
|
||||
else
|
||||
from_value = context.evaluate(@from)
|
||||
if from_value.nil?
|
||||
0
|
||||
else
|
||||
Utils.to_integer(from_value)
|
||||
end
|
||||
end
|
||||
|
||||
collection = context.evaluate(@collection_name)
|
||||
collection = collection.to_a if collection.is_a?(Range)
|
||||
|
||||
limit_value = context.evaluate(@limit)
|
||||
to = if limit_value.nil?
|
||||
nil
|
||||
else
|
||||
Utils.to_integer(limit_value) + from
|
||||
end
|
||||
|
||||
segment = Utils.slice_collection(collection, from, to)
|
||||
segment.reverse! if @reversed
|
||||
|
||||
offsets[@name] = from + segment.length
|
||||
|
||||
segment
|
||||
end
|
||||
|
||||
def render_segment(context, segment)
|
||||
for_stack = context.registers[:for_stack] ||= []
|
||||
length = segment.length
|
||||
|
||||
result = ''
|
||||
|
||||
context.stack('forloop', @variable_name) do
|
||||
loop_vars = Liquid::ForloopDrop.new(@name, length, for_stack[-1])
|
||||
|
||||
for_stack.push(loop_vars)
|
||||
|
||||
begin
|
||||
context['forloop'.freeze] = loop_vars
|
||||
level = context.create_level(@variable_name)
|
||||
|
||||
segment.each do |item|
|
||||
context.set_level(@variable_name, item, level)
|
||||
result << @for_block.render(context)
|
||||
loop_vars.send(:increment!)
|
||||
# Handle any interrupts if they exist.
|
||||
if context.interrupt?
|
||||
interrupt = context.pop_interrupt
|
||||
break if interrupt.is_a? BreakInterrupt
|
||||
next if interrupt.is_a? ContinueInterrupt
|
||||
end
|
||||
end
|
||||
context.unset(@variable_name)
|
||||
context.unset('forloop'.freeze)
|
||||
ensure
|
||||
|
||||
for_stack.pop
|
||||
end
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def set_attribute(key, expr)
|
||||
case key
|
||||
when 'offset'.freeze
|
||||
@@ -204,10 +190,8 @@ module Liquid
|
||||
@else_block ? @else_block.render(context) : ''.freeze
|
||||
end
|
||||
|
||||
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
||||
def children
|
||||
(super + [@node.limit, @node.from, @node.collection_name]).compact
|
||||
end
|
||||
def iterable?(collection)
|
||||
collection.respond_to?(:each) || Utils.non_blank_string?(collection)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -12,9 +12,7 @@ module Liquid
|
||||
class If < Block
|
||||
Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/o
|
||||
ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/o
|
||||
BOOLEAN_OPERATORS = %w(and or).freeze
|
||||
|
||||
attr_reader :blocks
|
||||
BOOLEAN_OPERATORS = %w(and or)
|
||||
|
||||
def initialize(tag_name, markup, options)
|
||||
super
|
||||
@@ -22,15 +20,15 @@ module Liquid
|
||||
push_block('if'.freeze, markup)
|
||||
end
|
||||
|
||||
def nodelist
|
||||
@blocks.map(&:attachment)
|
||||
end
|
||||
|
||||
def parse(tokens)
|
||||
while parse_body(@blocks.last.attachment, tokens)
|
||||
end
|
||||
end
|
||||
|
||||
def nodelist
|
||||
@blocks.map(&:attachment)
|
||||
end
|
||||
|
||||
def unknown_tag(tag, markup, tokens)
|
||||
if ['elsif'.freeze, 'else'.freeze].include?(tag)
|
||||
push_block(tag, markup)
|
||||
@@ -40,12 +38,14 @@ module Liquid
|
||||
end
|
||||
|
||||
def render(context)
|
||||
@blocks.each do |block|
|
||||
if block.evaluate(context)
|
||||
return block.attachment.render(context)
|
||||
context.stack do
|
||||
@blocks.each do |block|
|
||||
if block.evaluate(context)
|
||||
return block.attachment.render(context)
|
||||
end
|
||||
end
|
||||
''.freeze
|
||||
end
|
||||
''.freeze
|
||||
end
|
||||
|
||||
private
|
||||
@@ -83,20 +83,17 @@ module Liquid
|
||||
|
||||
def strict_parse(markup)
|
||||
p = Parser.new(markup)
|
||||
condition = parse_binary_comparisons(p)
|
||||
p.consume(:end_of_string)
|
||||
condition
|
||||
end
|
||||
|
||||
def parse_binary_comparisons(p)
|
||||
condition = parse_comparison(p)
|
||||
first_condition = condition
|
||||
|
||||
while op = (p.id?('and'.freeze) || p.id?('or'.freeze))
|
||||
child_condition = parse_comparison(p)
|
||||
condition.send(op, child_condition)
|
||||
condition = child_condition
|
||||
new_cond = parse_comparison(p)
|
||||
new_cond.send(op, condition)
|
||||
condition = new_cond
|
||||
end
|
||||
first_condition
|
||||
p.consume(:end_of_string)
|
||||
|
||||
condition
|
||||
end
|
||||
|
||||
def parse_comparison(p)
|
||||
@@ -108,12 +105,6 @@ module Liquid
|
||||
Condition.new(a)
|
||||
end
|
||||
end
|
||||
|
||||
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
||||
def children
|
||||
@node.blocks
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Template.register_tag('if'.freeze, If)
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
module Liquid
|
||||
class Ifchanged < Block
|
||||
def render(context)
|
||||
output = super
|
||||
context.stack do
|
||||
output = super
|
||||
|
||||
if output != context.registers[:ifchanged]
|
||||
context.registers[:ifchanged] = output
|
||||
output
|
||||
else
|
||||
''.freeze
|
||||
if output != context.registers[:ifchanged]
|
||||
context.registers[:ifchanged] = output
|
||||
output
|
||||
else
|
||||
''.freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -16,8 +16,6 @@ module Liquid
|
||||
class Include < Tag
|
||||
Syntax = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?/o
|
||||
|
||||
attr_reader :template_name_expr, :variable_name_expr, :attributes
|
||||
|
||||
def initialize(tag_name, markup, options)
|
||||
super
|
||||
|
||||
@@ -43,62 +41,45 @@ module Liquid
|
||||
end
|
||||
|
||||
def render(context)
|
||||
template_name = context.evaluate(@template_name_expr)
|
||||
raise ArgumentError.new(options[:locale].t("errors.argument.include")) unless template_name
|
||||
partial = load_cached_partial(context)
|
||||
|
||||
partial = load_cached_partial(template_name, context)
|
||||
template_name = context.evaluate(@template_name_expr)
|
||||
context_variable_name = template_name.split('/'.freeze).last
|
||||
|
||||
variable = if @variable_name_expr
|
||||
context.evaluate(@variable_name_expr)
|
||||
else
|
||||
context.find_variable(template_name, raise_on_not_found: false)
|
||||
context.find_variable(template_name)
|
||||
end
|
||||
|
||||
old_template_name = context.template_name
|
||||
old_partial = context.partial
|
||||
begin
|
||||
context.template_name = template_name
|
||||
context.partial = true
|
||||
context.stack(context_variable_name, *@attributes.keys) do
|
||||
@attributes.each do |key, value|
|
||||
context[key] = context.evaluate(value)
|
||||
end
|
||||
context.stack do
|
||||
@attributes.each do |key, value|
|
||||
context[key] = context.evaluate(value)
|
||||
end
|
||||
|
||||
if variable.is_a?(Array)
|
||||
variable.collect do |var|
|
||||
context[context_variable_name] = var
|
||||
partial.render(context)
|
||||
end
|
||||
else
|
||||
context[context_variable_name] = variable
|
||||
if variable.is_a?(Array)
|
||||
variable.collect do |var|
|
||||
context[context_variable_name] = var
|
||||
partial.render(context)
|
||||
end
|
||||
else
|
||||
context[context_variable_name] = variable
|
||||
partial.render(context)
|
||||
end
|
||||
ensure
|
||||
context.template_name = old_template_name
|
||||
context.partial = old_partial
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
alias_method :parse_context, :options
|
||||
private :parse_context
|
||||
|
||||
def load_cached_partial(template_name, context)
|
||||
def load_cached_partial(context)
|
||||
cached_partials = context.registers[:cached_partials] || {}
|
||||
template_name = context.evaluate(@template_name_expr)
|
||||
|
||||
if cached = cached_partials[template_name]
|
||||
return cached
|
||||
end
|
||||
source = read_template_from_file_system(context)
|
||||
begin
|
||||
parse_context.partial = true
|
||||
partial = Liquid::Template.parse(source, parse_context)
|
||||
ensure
|
||||
parse_context.partial = false
|
||||
end
|
||||
partial = Liquid::Template.parse(source, pass_options)
|
||||
cached_partials[template_name] = partial
|
||||
context.registers[:cached_partials] = cached_partials
|
||||
partial
|
||||
@@ -110,13 +91,14 @@ module Liquid
|
||||
file_system.read_template_file(context.evaluate(@template_name_expr))
|
||||
end
|
||||
|
||||
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
||||
def children
|
||||
[
|
||||
@node.template_name_expr,
|
||||
@node.variable_name_expr
|
||||
] + @node.attributes.values
|
||||
def pass_options
|
||||
dont_pass = @options[:include_options_blacklist]
|
||||
return { locale: @options[:locale] } if dont_pass == true
|
||||
opts = @options.merge(included: true, include_options_blacklist: false)
|
||||
if dont_pass.is_a?(Array)
|
||||
dont_pass.each { |o| opts.delete(o) }
|
||||
end
|
||||
opts
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
module Liquid
|
||||
class Raw < Block
|
||||
Syntax = /\A\s*\z/
|
||||
FullTokenPossiblyInvalid = /\A(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
|
||||
|
||||
def initialize(tag_name, markup, parse_context)
|
||||
super
|
||||
|
||||
ensure_valid_markup(tag_name, markup, parse_context)
|
||||
end
|
||||
|
||||
def parse(tokens)
|
||||
@body = ''
|
||||
while token = tokens.shift
|
||||
@@ -18,8 +11,6 @@ module Liquid
|
||||
end
|
||||
@body << token unless token.empty?
|
||||
end
|
||||
|
||||
raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_never_closed".freeze, block_name: block_name))
|
||||
end
|
||||
|
||||
def render(_context)
|
||||
@@ -33,14 +24,6 @@ module Liquid
|
||||
def blank?
|
||||
@body.empty?
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def ensure_valid_markup(tag_name, markup, parse_context)
|
||||
unless markup =~ Syntax
|
||||
raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_unexpected_args".freeze, tag: tag_name))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Template.register_tag('raw'.freeze, Raw)
|
||||
|
||||
@@ -2,8 +2,6 @@ module Liquid
|
||||
class TableRow < Block
|
||||
Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o
|
||||
|
||||
attr_reader :variable_name, :collection_name, :attributes
|
||||
|
||||
def initialize(tag_name, markup, options)
|
||||
super
|
||||
if markup =~ Syntax
|
||||
@@ -30,32 +28,41 @@ module Liquid
|
||||
|
||||
cols = context.evaluate(@attributes['cols'.freeze]).to_i
|
||||
|
||||
row = 1
|
||||
col = 0
|
||||
|
||||
result = "<tr class=\"row1\">\n"
|
||||
context.stack('tablerowloop', @variable_name) do
|
||||
tablerowloop = Liquid::TablerowloopDrop.new(length, cols)
|
||||
context['tablerowloop'.freeze] = tablerowloop
|
||||
|
||||
collection.each do |item|
|
||||
context.stack do
|
||||
collection.each_with_index do |item, index|
|
||||
context[@variable_name] = item
|
||||
context['tablerowloop'.freeze] = {
|
||||
'length'.freeze => length,
|
||||
'index'.freeze => index + 1,
|
||||
'index0'.freeze => index,
|
||||
'col'.freeze => col + 1,
|
||||
'col0'.freeze => col,
|
||||
'rindex'.freeze => length - index,
|
||||
'rindex0'.freeze => length - index - 1,
|
||||
'first'.freeze => (index == 0),
|
||||
'last'.freeze => (index == length - 1),
|
||||
'col_first'.freeze => (col == 0),
|
||||
'col_last'.freeze => (col == cols - 1)
|
||||
}
|
||||
|
||||
result << "<td class=\"col#{tablerowloop.col}\">" << super << '</td>'
|
||||
col += 1
|
||||
|
||||
if tablerowloop.col_last && !tablerowloop.last
|
||||
result << "</tr>\n<tr class=\"row#{tablerowloop.row + 1}\">"
|
||||
result << "<td class=\"col#{col}\">" << super << '</td>'
|
||||
|
||||
if col == cols && (index != length - 1)
|
||||
col = 0
|
||||
row += 1
|
||||
result << "</tr>\n<tr class=\"row#{row}\">"
|
||||
end
|
||||
|
||||
tablerowloop.send(:increment!)
|
||||
end
|
||||
end
|
||||
result << "</tr>\n"
|
||||
result
|
||||
end
|
||||
|
||||
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
||||
def children
|
||||
super + @node.attributes.values + [@node.collection_name]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Template.register_tag('tablerow'.freeze, TableRow)
|
||||
|
||||
@@ -7,20 +7,22 @@ module Liquid
|
||||
#
|
||||
class Unless < If
|
||||
def render(context)
|
||||
# First condition is interpreted backwards ( if not )
|
||||
first_block = @blocks.first
|
||||
unless first_block.evaluate(context)
|
||||
return first_block.attachment.render(context)
|
||||
end
|
||||
|
||||
# After the first condition unless works just like if
|
||||
@blocks[1..-1].each do |block|
|
||||
if block.evaluate(context)
|
||||
return block.attachment.render(context)
|
||||
context.stack do
|
||||
# First condition is interpreted backwards ( if not )
|
||||
first_block = @blocks.first
|
||||
unless first_block.evaluate(context)
|
||||
return first_block.attachment.render(context)
|
||||
end
|
||||
end
|
||||
|
||||
''.freeze
|
||||
# After the first condition unless works just like if
|
||||
@blocks[1..-1].each do |block|
|
||||
if block.evaluate(context)
|
||||
return block.attachment.render(context)
|
||||
end
|
||||
end
|
||||
|
||||
''.freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -13,21 +13,23 @@ module Liquid
|
||||
# template.render('user_name' => 'bob')
|
||||
#
|
||||
class Template
|
||||
attr_accessor :root
|
||||
attr_reader :resource_limits, :warnings
|
||||
DEFAULT_OPTIONS = {
|
||||
locale: I18n.new
|
||||
}
|
||||
|
||||
attr_accessor :root, :render_errors
|
||||
attr_reader :resource_limits
|
||||
|
||||
@@file_system = BlankFileSystem.new
|
||||
|
||||
class TagRegistry
|
||||
include Enumerable
|
||||
|
||||
def initialize
|
||||
@tags = {}
|
||||
@tags = {}
|
||||
@cache = {}
|
||||
end
|
||||
|
||||
def [](tag_name)
|
||||
return nil unless @tags.key?(tag_name)
|
||||
return nil unless @tags.has_key?(tag_name)
|
||||
return @cache[tag_name] if Liquid.cache_classes
|
||||
|
||||
lookup_class(@tags[tag_name]).tap { |o| @cache[tag_name] = o }
|
||||
@@ -43,10 +45,6 @@ module Liquid
|
||||
@cache.delete(tag_name)
|
||||
end
|
||||
|
||||
def each(&block)
|
||||
@tags.each(&block)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def lookup_class(name)
|
||||
@@ -69,11 +67,6 @@ module Liquid
|
||||
# :error raises an error when tainted output is used
|
||||
attr_writer :taint_mode
|
||||
|
||||
attr_accessor :default_exception_renderer
|
||||
Template.default_exception_renderer = lambda do |exception|
|
||||
exception
|
||||
end
|
||||
|
||||
def file_system
|
||||
@@file_system
|
||||
end
|
||||
@@ -91,11 +84,11 @@ module Liquid
|
||||
end
|
||||
|
||||
def error_mode
|
||||
@error_mode ||= :lax
|
||||
@error_mode || :lax
|
||||
end
|
||||
|
||||
def taint_mode
|
||||
@taint_mode ||= :lax
|
||||
@taint_mode || :lax
|
||||
end
|
||||
|
||||
# Pass a module with filter methods which should be available
|
||||
@@ -118,7 +111,6 @@ module Liquid
|
||||
end
|
||||
|
||||
def initialize
|
||||
@rethrow_errors = false
|
||||
@resource_limits = ResourceLimits.new(self.class.default_resource_limits)
|
||||
end
|
||||
|
||||
@@ -128,12 +120,16 @@ module Liquid
|
||||
@options = options
|
||||
@profiling = options[:profile]
|
||||
@line_numbers = options[:line_numbers] || @profiling
|
||||
parse_context = options.is_a?(ParseContext) ? options : ParseContext.new(options)
|
||||
@root = Document.parse(tokenize(source), parse_context)
|
||||
@warnings = parse_context.warnings
|
||||
@root = Document.parse(tokenize(source), DEFAULT_OPTIONS.merge(options))
|
||||
@warnings = nil
|
||||
self
|
||||
end
|
||||
|
||||
def warnings
|
||||
return [] unless @root
|
||||
@warnings ||= @root.warnings
|
||||
end
|
||||
|
||||
def registers
|
||||
@registers ||= {}
|
||||
end
|
||||
@@ -172,7 +168,7 @@ module Liquid
|
||||
c = args.shift
|
||||
|
||||
if @rethrow_errors
|
||||
c.exception_renderer = ->(e) { raise }
|
||||
c.exception_handler = ->(e) { true }
|
||||
end
|
||||
|
||||
c
|
||||
@@ -187,14 +183,26 @@ module Liquid
|
||||
raise ArgumentError, "Expected Hash or Liquid::Context as parameter"
|
||||
end
|
||||
|
||||
context.render_errors = self.render_errors unless self.render_errors.nil?
|
||||
|
||||
case args.last
|
||||
when Hash
|
||||
options = args.pop
|
||||
|
||||
registers.merge!(options[:registers]) if options[:registers].is_a?(Hash)
|
||||
if options[:registers].is_a?(Hash)
|
||||
registers.merge!(options[:registers])
|
||||
end
|
||||
|
||||
apply_options_to_context(context, options)
|
||||
when Module, Array
|
||||
if options[:filters]
|
||||
context.add_filters(options[:filters])
|
||||
end
|
||||
|
||||
if options[:exception_handler]
|
||||
context.exception_handler = options[:exception_handler]
|
||||
end
|
||||
when Module
|
||||
context.add_filters(args.pop)
|
||||
when Array
|
||||
context.add_filters(args.pop)
|
||||
end
|
||||
|
||||
@@ -204,7 +212,7 @@ module Liquid
|
||||
begin
|
||||
# render the nodelist.
|
||||
# for performance reasons we get an array back here. join will make a string out of it.
|
||||
result = with_profiling(context) do
|
||||
result = with_profiling do
|
||||
@root.render(context)
|
||||
end
|
||||
result.respond_to?(:join) ? result.join : result
|
||||
@@ -222,12 +230,32 @@ module Liquid
|
||||
|
||||
private
|
||||
|
||||
# Uses the <tt>Liquid::TemplateParser</tt> regexp to tokenize the passed source
|
||||
def tokenize(source)
|
||||
Tokenizer.new(source, @line_numbers)
|
||||
source = source.source if source.respond_to?(:source)
|
||||
return [] if source.to_s.empty?
|
||||
|
||||
tokens = calculate_line_numbers(source.split(TemplateParser))
|
||||
|
||||
# removes the rogue empty element at the beginning of the array
|
||||
tokens.shift if tokens[0] && tokens[0].empty?
|
||||
|
||||
tokens
|
||||
end
|
||||
|
||||
def with_profiling(context)
|
||||
if @profiling && !context.partial
|
||||
def calculate_line_numbers(raw_tokens)
|
||||
return raw_tokens unless @line_numbers
|
||||
|
||||
current_line = 1
|
||||
raw_tokens.map do |token|
|
||||
Token.new(token, current_line).tap do
|
||||
current_line += token.count("\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def with_profiling
|
||||
if @profiling && !@options[:included]
|
||||
raise "Profiler not loaded, require 'liquid/profiler' first" unless defined?(Liquid::Profiler)
|
||||
|
||||
@profiler = Profiler.new
|
||||
@@ -242,13 +270,5 @@ module Liquid
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
def apply_options_to_context(context, options)
|
||||
context.add_filters(options[:filters]) if options[:filters]
|
||||
context.global_filter = options[:global_filter] if options[:global_filter]
|
||||
context.exception_renderer = options[:exception_renderer] if options[:exception_renderer]
|
||||
context.strict_variables = options[:strict_variables] if options[:strict_variables]
|
||||
context.strict_filters = options[:strict_filters] if options[:strict_filters]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
18
lib/liquid/token.rb
Normal file
18
lib/liquid/token.rb
Normal file
@@ -0,0 +1,18 @@
|
||||
module Liquid
|
||||
class Token < String
|
||||
attr_reader :line_number
|
||||
|
||||
def initialize(content, line_number)
|
||||
super(content)
|
||||
@line_number = line_number
|
||||
end
|
||||
|
||||
def raw
|
||||
"<raw>"
|
||||
end
|
||||
|
||||
def child(string)
|
||||
Token.new(string, @line_number)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,31 +0,0 @@
|
||||
module Liquid
|
||||
class Tokenizer
|
||||
attr_reader :line_number
|
||||
|
||||
def initialize(source, line_numbers = false)
|
||||
@source = source
|
||||
@line_number = line_numbers ? 1 : nil
|
||||
@tokens = tokenize
|
||||
end
|
||||
|
||||
def shift
|
||||
token = @tokens.shift
|
||||
@line_number += token.count("\n") if @line_number && token
|
||||
token
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def tokenize
|
||||
@source = @source.source if @source.respond_to?(:source)
|
||||
return [] if @source.to_s.empty?
|
||||
|
||||
tokens = @source.split(TemplateParser)
|
||||
|
||||
# removes the rogue empty element at the beginning of the array
|
||||
tokens.shift if tokens[0] && tokens[0].empty?
|
||||
|
||||
tokens
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -8,15 +8,16 @@ module Liquid
|
||||
end
|
||||
end
|
||||
|
||||
def self.non_blank_string?(collection)
|
||||
collection.is_a?(String) && collection != ''.freeze
|
||||
end
|
||||
|
||||
def self.slice_collection_using_each(collection, from, to)
|
||||
segments = []
|
||||
index = 0
|
||||
|
||||
# Maintains Ruby 1.8.7 String#each behaviour on 1.9
|
||||
if collection.is_a?(String)
|
||||
return collection.empty? ? [] : [collection]
|
||||
end
|
||||
return [] unless collection.respond_to?(:each)
|
||||
return [collection] if non_blank_string?(collection)
|
||||
|
||||
collection.each do |item|
|
||||
if to && to <= index
|
||||
@@ -32,52 +33,5 @@ module Liquid
|
||||
|
||||
segments
|
||||
end
|
||||
|
||||
def self.to_integer(num)
|
||||
return num if num.is_a?(Integer)
|
||||
num = num.to_s
|
||||
begin
|
||||
Integer(num)
|
||||
rescue ::ArgumentError
|
||||
raise Liquid::ArgumentError, "invalid integer"
|
||||
end
|
||||
end
|
||||
|
||||
def self.to_number(obj)
|
||||
case obj
|
||||
when Float
|
||||
BigDecimal(obj.to_s)
|
||||
when Numeric
|
||||
obj
|
||||
when String
|
||||
(obj.strip =~ /\A-?\d+\.\d+\z/) ? BigDecimal(obj) : obj.to_i
|
||||
else
|
||||
if obj.respond_to?(:to_number)
|
||||
obj.to_number
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.to_date(obj)
|
||||
return obj if obj.respond_to?(:strftime)
|
||||
|
||||
if obj.is_a?(String)
|
||||
return nil if obj.empty?
|
||||
obj = obj.downcase
|
||||
end
|
||||
|
||||
case obj
|
||||
when 'now'.freeze, 'today'.freeze
|
||||
Time.now
|
||||
when /\A\d+\z/, Integer
|
||||
Time.at(obj.to_i)
|
||||
when String
|
||||
Time.parse(obj)
|
||||
end
|
||||
rescue ::ArgumentError
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,23 +10,15 @@ 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
|
||||
|
||||
attr_accessor :filters, :name, :warnings
|
||||
attr_accessor :line_number
|
||||
include ParserSwitching
|
||||
|
||||
def initialize(markup, parse_context)
|
||||
def initialize(markup, options = {})
|
||||
@markup = markup
|
||||
@name = nil
|
||||
@parse_context = parse_context
|
||||
@line_number = parse_context.line_number
|
||||
@options = options || {}
|
||||
|
||||
parse_with_selected_parser(markup)
|
||||
end
|
||||
@@ -41,18 +33,19 @@ module Liquid
|
||||
|
||||
def lax_parse(markup)
|
||||
@filters = []
|
||||
return unless markup =~ MarkupWithQuotedFragment
|
||||
return unless markup =~ /(#{QuotedFragment})(.*)/om
|
||||
|
||||
name_markup = $1
|
||||
filter_markup = $2
|
||||
@name = Expression.parse(name_markup)
|
||||
if filter_markup =~ FilterMarkupRegex
|
||||
if filter_markup =~ /#{FilterSeparator}\s*(.*)/om
|
||||
filters = $1.scan(FilterParser)
|
||||
filters.each do |f|
|
||||
next unless f =~ /\w+/
|
||||
filtername = Regexp.last_match(0)
|
||||
filterargs = f.scan(FilterArgsRegex).flatten
|
||||
@filters << parse_filter_expressions(filtername, filterargs)
|
||||
if f =~ /\w+/
|
||||
filtername = Regexp.last_match(0)
|
||||
filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o).flatten
|
||||
@filters << parse_filter_expressions(filtername, filterargs)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -79,33 +72,26 @@ module Liquid
|
||||
end
|
||||
|
||||
def render(context)
|
||||
obj = @filters.inject(context.evaluate(@name)) do |output, (filter_name, filter_args, filter_kwargs)|
|
||||
@filters.inject(context.evaluate(@name)) do |output, (filter_name, filter_args, filter_kwargs)|
|
||||
filter_args = evaluate_filter_expressions(context, filter_args, filter_kwargs)
|
||||
context.invoke(filter_name, output, *filter_args)
|
||||
end
|
||||
|
||||
obj = context.apply_global_filter(obj)
|
||||
|
||||
taint_check(context, obj)
|
||||
|
||||
obj
|
||||
output = context.invoke(filter_name, output, *filter_args)
|
||||
end.tap{ |obj| taint_check(obj) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_filter_expressions(filter_name, unparsed_args)
|
||||
filter_args = []
|
||||
keyword_args = nil
|
||||
keyword_args = {}
|
||||
unparsed_args.each do |a|
|
||||
if matches = a.match(JustTagAttributes)
|
||||
keyword_args ||= {}
|
||||
if matches = a.match(/\A#{TagAttributes}\z/o)
|
||||
keyword_args[matches[1]] = Expression.parse(matches[2])
|
||||
else
|
||||
filter_args << Expression.parse(a)
|
||||
end
|
||||
end
|
||||
result = [filter_name, filter_args]
|
||||
result << keyword_args if keyword_args
|
||||
result << keyword_args unless keyword_args.empty?
|
||||
result
|
||||
end
|
||||
|
||||
@@ -121,28 +107,17 @@ module Liquid
|
||||
parsed_args
|
||||
end
|
||||
|
||||
def taint_check(context, obj)
|
||||
return unless obj.tainted?
|
||||
return if Template.taint_mode == :lax
|
||||
|
||||
@markup =~ QuotedFragment
|
||||
name = Regexp.last_match(0)
|
||||
|
||||
error = TaintedError.new("variable '#{name}' is tainted and was not escaped")
|
||||
error.line_number = line_number
|
||||
error.template_name = context.template_name
|
||||
|
||||
case Template.taint_mode
|
||||
when :warn
|
||||
context.warnings << error
|
||||
when :error
|
||||
raise error
|
||||
end
|
||||
end
|
||||
|
||||
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
||||
def children
|
||||
[@node.name] + @node.filters.flatten
|
||||
def taint_check(obj)
|
||||
if obj.tainted?
|
||||
@markup =~ QuotedFragment
|
||||
name = Regexp.last_match(0)
|
||||
case Template.taint_mode
|
||||
when :warn
|
||||
@warnings ||= []
|
||||
@warnings << "variable '#{name}' is tainted and was not escaped"
|
||||
when :error
|
||||
raise TaintedError, "Error - variable '#{name}' is tainted and was not escaped"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module Liquid
|
||||
class VariableLookup
|
||||
SQUARE_BRACKETED = /\A\[(.*)\]\z/m
|
||||
COMMAND_METHODS = ['size'.freeze, 'first'.freeze, 'last'.freeze].freeze
|
||||
COMMAND_METHODS = ['size'.freeze, 'first'.freeze, 'last'.freeze]
|
||||
|
||||
attr_reader :name, :lookups
|
||||
|
||||
@@ -41,7 +41,7 @@ module Liquid
|
||||
# If object is a hash- or array-like object we look for the
|
||||
# presence of the key and if its available we return it
|
||||
if object.respond_to?(:[]) &&
|
||||
((object.respond_to?(:key?) && object.key?(key)) ||
|
||||
((object.respond_to?(:has_key?) && object.has_key?(key)) ||
|
||||
(object.respond_to?(:fetch) && key.is_a?(Integer)))
|
||||
|
||||
# if its a proc we will replace the entry with the proc
|
||||
@@ -55,11 +55,9 @@ module Liquid
|
||||
object = object.send(key).to_liquid
|
||||
|
||||
# No key was present with the desired value and it wasn't one of the directly supported
|
||||
# keywords either. The only thing we got left is to return nil or
|
||||
# raise an exception if `strict_variables` option is set to true
|
||||
# keywords either. The only thing we got left is to return nil
|
||||
else
|
||||
return nil unless context.strict_variables
|
||||
raise Liquid::UndefinedVariable, "undefined variable #{key}"
|
||||
return nil
|
||||
end
|
||||
|
||||
# If we are dealing with a drop here we have to
|
||||
@@ -78,11 +76,5 @@ module Liquid
|
||||
def state
|
||||
[@name, @lookups, @command_flags]
|
||||
end
|
||||
|
||||
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
||||
def children
|
||||
@node.lookups
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# encoding: utf-8
|
||||
|
||||
module Liquid
|
||||
VERSION = "4.0.3".freeze
|
||||
VERSION = "4.0.0.alpha"
|
||||
end
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# encoding: utf-8
|
||||
|
||||
lib = File.expand_path('../lib/', __FILE__)
|
||||
$LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib)
|
||||
|
||||
@@ -16,16 +15,15 @@ Gem::Specification.new do |s|
|
||||
s.license = "MIT"
|
||||
# s.description = "A secure, non-evaling end user template engine with aesthetic markup."
|
||||
|
||||
s.required_ruby_version = ">= 2.1.0"
|
||||
s.required_rubygems_version = ">= 1.3.7"
|
||||
|
||||
s.test_files = Dir.glob("{test}/**/*")
|
||||
s.files = Dir.glob("{lib}/**/*") + %w(LICENSE README.md)
|
||||
s.files = Dir.glob("{lib}/**/*") + %w(MIT-LICENSE README.md)
|
||||
|
||||
s.extra_rdoc_files = ["History.md", "README.md"]
|
||||
s.extra_rdoc_files = ["History.md", "README.md"]
|
||||
|
||||
s.require_path = "lib"
|
||||
|
||||
s.add_development_dependency 'rake', '~> 11.3'
|
||||
s.add_development_dependency 'rake'
|
||||
s.add_development_dependency 'minitest'
|
||||
end
|
||||
|
||||
@@ -5,7 +5,7 @@ Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
|
||||
profiler = ThemeRunner.new
|
||||
|
||||
Benchmark.ips do |x|
|
||||
x.time = 10
|
||||
x.time = 60
|
||||
x.warmup = 5
|
||||
|
||||
puts
|
||||
@@ -13,6 +13,5 @@ Benchmark.ips do |x|
|
||||
puts
|
||||
|
||||
x.report("parse:") { profiler.compile }
|
||||
x.report("render:") { profiler.render }
|
||||
x.report("parse & render:") { profiler.run }
|
||||
x.report("parse & run:") { profiler.run }
|
||||
end
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'benchmark/ips'
|
||||
require 'memory_profiler'
|
||||
require_relative 'theme_runner'
|
||||
|
||||
def profile(phase, &block)
|
||||
puts
|
||||
puts "#{phase}:"
|
||||
puts
|
||||
|
||||
report = MemoryProfiler.report(&block)
|
||||
|
||||
report.pretty_print(
|
||||
color_output: true,
|
||||
scale_bytes: true,
|
||||
detailed_report: true
|
||||
)
|
||||
end
|
||||
|
||||
Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
|
||||
|
||||
profiler = ThemeRunner.new
|
||||
|
||||
profile("Parsing") { profiler.compile }
|
||||
profile("Rendering") { profiler.render }
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'stackprof'
|
||||
require 'stackprof' rescue fail("install stackprof extension/gem")
|
||||
require_relative 'theme_runner'
|
||||
|
||||
Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
|
||||
|
||||
@@ -15,7 +15,7 @@ class CommentForm < Liquid::Block
|
||||
def render(context)
|
||||
article = context[@variable_name]
|
||||
|
||||
context.stack('form') do
|
||||
context.stack do
|
||||
context['form'] = {
|
||||
'posted_successfully?' => context.registers[:posted_successfully],
|
||||
'errors' => context['comment.errors'],
|
||||
|
||||
@@ -24,7 +24,7 @@ class Paginate < Liquid::Block
|
||||
def render(context)
|
||||
@context = context
|
||||
|
||||
context.stack('paginate') do
|
||||
context.stack do
|
||||
current_page = context['current_page'].to_i
|
||||
|
||||
pagination = {
|
||||
|
||||
@@ -21,100 +21,53 @@ class ThemeRunner
|
||||
end
|
||||
end
|
||||
|
||||
# Initialize a new liquid ThemeRunner instance
|
||||
# Will load all templates into memory, do this now so that we don't profile IO.
|
||||
# Load all templates into memory, do this now so that
|
||||
# we don't profile IO.
|
||||
def initialize
|
||||
@tests = Dir[__dir__ + '/tests/**/*.liquid'].collect do |test|
|
||||
next if File.basename(test) == 'theme.liquid'
|
||||
|
||||
theme_path = File.dirname(test) + '/theme.liquid'
|
||||
{
|
||||
liquid: File.read(test),
|
||||
layout: (File.file?(theme_path) ? File.read(theme_path) : nil),
|
||||
template_name: test
|
||||
}
|
||||
end.compact
|
||||
|
||||
compile_all_tests
|
||||
[File.read(test), (File.file?(theme_path) ? File.read(theme_path) : nil), test]
|
||||
end.compact
|
||||
end
|
||||
|
||||
# `compile` will test just the compilation portion of liquid without any templates
|
||||
def compile
|
||||
@tests.each do |test_hash|
|
||||
Liquid::Template.new.parse(test_hash[:liquid])
|
||||
Liquid::Template.new.parse(test_hash[:layout])
|
||||
# Dup assigns because will make some changes to them
|
||||
|
||||
@tests.each do |liquid, layout, template_name|
|
||||
tmpl = Liquid::Template.new
|
||||
tmpl.parse(liquid)
|
||||
tmpl = Liquid::Template.new
|
||||
tmpl.parse(layout)
|
||||
end
|
||||
end
|
||||
|
||||
# `run` is called to benchmark rendering and compiling at the same time
|
||||
def run
|
||||
each_test do |liquid, layout, assigns, page_template, template_name|
|
||||
# Dup assigns because will make some changes to them
|
||||
assigns = Database.tables.dup
|
||||
|
||||
@tests.each do |liquid, layout, template_name|
|
||||
# Compute page_tempalte outside of profiler run, uninteresting to profiler
|
||||
page_template = File.basename(template_name, File.extname(template_name))
|
||||
compile_and_render(liquid, layout, assigns, page_template, template_name)
|
||||
end
|
||||
end
|
||||
|
||||
# `render` is called to benchmark just the render portion of liquid
|
||||
def render
|
||||
@compiled_tests.each do |test|
|
||||
tmpl = test[:tmpl]
|
||||
assigns = test[:assigns]
|
||||
layout = test[:layout]
|
||||
|
||||
if layout
|
||||
assigns['content_for_layout'] = tmpl.render!(assigns)
|
||||
layout.render!(assigns)
|
||||
else
|
||||
tmpl.render!(assigns)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def compile_and_render(template, layout, assigns, page_template, template_file)
|
||||
compiled_test = compile_test(template, layout, assigns, page_template, template_file)
|
||||
assigns['content_for_layout'] = compiled_test[:tmpl].render!(assigns)
|
||||
compiled_test[:layout].render!(assigns) if layout
|
||||
end
|
||||
|
||||
def compile_all_tests
|
||||
@compiled_tests = []
|
||||
each_test do |liquid, layout, assigns, page_template, template_name|
|
||||
@compiled_tests << compile_test(liquid, layout, assigns, page_template, template_name)
|
||||
end
|
||||
@compiled_tests
|
||||
end
|
||||
|
||||
def compile_test(template, layout, assigns, page_template, template_file)
|
||||
tmpl = init_template(page_template, template_file)
|
||||
parsed_template = tmpl.parse(template).dup
|
||||
|
||||
if layout
|
||||
parsed_layout = tmpl.parse(layout)
|
||||
{ tmpl: parsed_template, assigns: assigns, layout: parsed_layout }
|
||||
else
|
||||
{ tmpl: parsed_template, assigns: assigns }
|
||||
end
|
||||
end
|
||||
|
||||
# utility method with similar functionality needed in `compile_all_tests` and `run`
|
||||
def each_test
|
||||
# Dup assigns because will make some changes to them
|
||||
assigns = Database.tables.dup
|
||||
|
||||
@tests.each do |test_hash|
|
||||
# Compute page_template outside of profiler run, uninteresting to profiler
|
||||
page_template = File.basename(test_hash[:template_name], File.extname(test_hash[:template_name]))
|
||||
yield(test_hash[:liquid], test_hash[:layout], assigns, page_template, test_hash[:template_name])
|
||||
end
|
||||
end
|
||||
|
||||
# set up a new Liquid::Template object for use in `compile_and_render` and `compile_test`
|
||||
def init_template(page_template, template_file)
|
||||
tmpl = Liquid::Template.new
|
||||
tmpl.assigns['page_title'] = 'Page title'
|
||||
tmpl.assigns['template'] = page_template
|
||||
tmpl.registers[:file_system] = ThemeRunner::FileSystem.new(File.dirname(template_file))
|
||||
tmpl
|
||||
|
||||
content_for_layout = tmpl.parse(template).render!(assigns)
|
||||
|
||||
if layout
|
||||
assigns['content_for_layout'] = content_for_layout
|
||||
tmpl.parse(layout).render!(assigns)
|
||||
else
|
||||
content_for_layout
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
@@ -18,14 +18,14 @@ class ContextTest < Minitest::Test
|
||||
|
||||
with_global_filter(global) do
|
||||
assert_equal 'Global test', Template.parse("{{'test' | notice }}").render!
|
||||
assert_equal 'Local test', Template.parse("{{'test' | notice }}").render!({}, filters: [local])
|
||||
assert_equal 'Local test', Template.parse("{{'test' | notice }}").render!({}, :filters => [local])
|
||||
end
|
||||
end
|
||||
|
||||
def test_has_key_will_not_add_an_error_for_missing_keys
|
||||
with_error_mode :strict do
|
||||
context = Context.new
|
||||
context.key?('unknown')
|
||||
context.has_key?('unknown')
|
||||
assert_empty context.errors
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
require 'test_helper'
|
||||
|
||||
class ContextDrop < Liquid::Drop
|
||||
def scopes
|
||||
@context.scopes.size
|
||||
end
|
||||
|
||||
def scopes_as_array
|
||||
(1..@context.scopes.size).to_a
|
||||
end
|
||||
|
||||
def loop_pos
|
||||
@context['forloop.index']
|
||||
end
|
||||
|
||||
def liquid_method_missing(method)
|
||||
def before_method(method)
|
||||
@context[method]
|
||||
end
|
||||
end
|
||||
|
||||
class ProductDrop < Liquid::Drop
|
||||
|
||||
class TextDrop < Liquid::Drop
|
||||
def array
|
||||
['text1', 'text2']
|
||||
@@ -22,8 +31,8 @@ class ProductDrop < Liquid::Drop
|
||||
end
|
||||
|
||||
class CatchallDrop < Liquid::Drop
|
||||
def liquid_method_missing(method)
|
||||
'catchall_method: ' << method.to_s
|
||||
def before_method(method)
|
||||
'method: ' << method.to_s
|
||||
end
|
||||
end
|
||||
|
||||
@@ -51,7 +60,7 @@ class ProductDrop < Liquid::Drop
|
||||
end
|
||||
|
||||
class EnumerableDrop < Liquid::Drop
|
||||
def liquid_method_missing(method)
|
||||
def before_method(method)
|
||||
method
|
||||
end
|
||||
|
||||
@@ -85,7 +94,7 @@ end
|
||||
class RealEnumerableDrop < Liquid::Drop
|
||||
include Enumerable
|
||||
|
||||
def liquid_method_missing(method)
|
||||
def before_method(method)
|
||||
method
|
||||
end
|
||||
|
||||
@@ -116,10 +125,8 @@ class DropsTest < Minitest::Test
|
||||
def test_rendering_warns_on_tainted_attr
|
||||
with_taint_mode(:warn) do
|
||||
tpl = Liquid::Template.parse('{{ product.user_input }}')
|
||||
context = Context.new('product' => ProductDrop.new)
|
||||
tpl.render!(context)
|
||||
assert_equal [Liquid::TaintedError], context.warnings.map(&:class)
|
||||
assert_equal "variable 'product.user_input' is tainted and was not escaped", context.warnings.first.to_s(false)
|
||||
tpl.render!('product' => ProductDrop.new)
|
||||
assert_match /tainted/, tpl.warnings.first
|
||||
end
|
||||
end
|
||||
|
||||
@@ -149,14 +156,14 @@ class DropsTest < Minitest::Test
|
||||
assert_equal ' text1 ', output
|
||||
end
|
||||
|
||||
def test_catchall_unknown_method
|
||||
def test_unknown_method
|
||||
output = Liquid::Template.parse(' {{ product.catchall.unknown }} ').render!('product' => ProductDrop.new)
|
||||
assert_equal ' catchall_method: unknown ', output
|
||||
assert_equal ' method: unknown ', output
|
||||
end
|
||||
|
||||
def test_catchall_integer_argument_drop
|
||||
def test_integer_argument_drop
|
||||
output = Liquid::Template.parse(' {{ product.catchall[8] }} ').render!('product' => ProductDrop.new)
|
||||
assert_equal ' catchall_method: 8 ', output
|
||||
assert_equal ' method: 8 ', output
|
||||
end
|
||||
|
||||
def test_text_array_drop
|
||||
@@ -186,6 +193,31 @@ class DropsTest < Minitest::Test
|
||||
end
|
||||
end
|
||||
|
||||
def test_scope
|
||||
assert_equal '1', Liquid::Template.parse('{{ context.scopes }}').render!('context' => ContextDrop.new)
|
||||
assert_equal '2', Liquid::Template.parse('{%for i in dummy%}{{ context.scopes }}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1])
|
||||
assert_equal '3', Liquid::Template.parse('{%for i in dummy%}{%for i in dummy%}{{ context.scopes }}{%endfor%}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1])
|
||||
end
|
||||
|
||||
def test_scope_though_proc
|
||||
assert_equal '1', Liquid::Template.parse('{{ s }}').render!('context' => ContextDrop.new, 's' => proc{ |c| c['context.scopes'] })
|
||||
assert_equal '2', Liquid::Template.parse('{%for i in dummy%}{{ s }}{%endfor%}').render!('context' => ContextDrop.new, 's' => proc{ |c| c['context.scopes'] }, 'dummy' => [1])
|
||||
assert_equal '3', Liquid::Template.parse('{%for i in dummy%}{%for i in dummy%}{{ s }}{%endfor%}{%endfor%}').render!('context' => ContextDrop.new, 's' => proc{ |c| c['context.scopes'] }, 'dummy' => [1])
|
||||
end
|
||||
|
||||
def test_scope_with_assigns
|
||||
assert_equal 'variable', Liquid::Template.parse('{% assign a = "variable"%}{{a}}').render!('context' => ContextDrop.new)
|
||||
assert_equal 'variable', Liquid::Template.parse('{% assign a = "variable"%}{%for i in dummy%}{{a}}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1])
|
||||
assert_equal 'test', Liquid::Template.parse('{% assign header_gif = "test"%}{{header_gif}}').render!('context' => ContextDrop.new)
|
||||
assert_equal 'test', Liquid::Template.parse("{% assign header_gif = 'test'%}{{header_gif}}").render!('context' => ContextDrop.new)
|
||||
end
|
||||
|
||||
def test_scope_from_tags
|
||||
assert_equal '1', Liquid::Template.parse('{% for i in context.scopes_as_array %}{{i}}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1])
|
||||
assert_equal '12', Liquid::Template.parse('{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1])
|
||||
assert_equal '123', Liquid::Template.parse('{%for a in dummy%}{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1])
|
||||
end
|
||||
|
||||
def test_access_context_from_drop
|
||||
assert_equal '123', Liquid::Template.parse('{%for a in dummy%}{{ context.loop_pos }}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1, 2, 3])
|
||||
end
|
||||
@@ -198,7 +230,7 @@ class DropsTest < Minitest::Test
|
||||
assert_equal '3', Liquid::Template.parse('{{collection.size}}').render!('collection' => EnumerableDrop.new)
|
||||
end
|
||||
|
||||
def test_enumerable_drop_will_invoke_liquid_method_missing_for_clashing_method_names
|
||||
def test_enumerable_drop_will_invoke_before_method_for_clashing_method_names
|
||||
["select", "each", "map", "cycle"].each do |method|
|
||||
assert_equal method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new)
|
||||
assert_equal method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new)
|
||||
|
||||
@@ -75,7 +75,7 @@ class ErrorHandlingTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_lax_unrecognized_operator
|
||||
template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', error_mode: :lax)
|
||||
template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', :error_mode => :lax)
|
||||
assert_equal ' Liquid error: Unknown operator =! ', template.render
|
||||
assert_equal 1, template.errors.size
|
||||
assert_equal Liquid::ArgumentError, template.errors.first.class
|
||||
@@ -83,34 +83,18 @@ class ErrorHandlingTest < Minitest::Test
|
||||
|
||||
def test_with_line_numbers_adds_numbers_to_parser_errors
|
||||
err = assert_raises(SyntaxError) do
|
||||
Liquid::Template.parse(%q(
|
||||
template = Liquid::Template.parse(%q(
|
||||
foobar
|
||||
|
||||
{% "cat" | foobar %}
|
||||
|
||||
bla
|
||||
),
|
||||
line_numbers: true
|
||||
)
|
||||
),
|
||||
:line_numbers => true
|
||||
)
|
||||
end
|
||||
|
||||
assert_match(/Liquid syntax error \(line 4\)/, err.message)
|
||||
end
|
||||
|
||||
def test_with_line_numbers_adds_numbers_to_parser_errors_with_whitespace_trim
|
||||
err = assert_raises(SyntaxError) do
|
||||
Liquid::Template.parse(%q(
|
||||
foobar
|
||||
|
||||
{%- "cat" | foobar -%}
|
||||
|
||||
bla
|
||||
),
|
||||
line_numbers: true
|
||||
)
|
||||
end
|
||||
|
||||
assert_match(/Liquid syntax error \(line 4\)/, err.message)
|
||||
assert_match /Liquid syntax error \(line 4\)/, err.message
|
||||
end
|
||||
|
||||
def test_parsing_warn_with_line_numbers_adds_numbers_to_lexer_errors
|
||||
@@ -121,9 +105,9 @@ class ErrorHandlingTest < Minitest::Test
|
||||
|
||||
bla
|
||||
',
|
||||
error_mode: :warn,
|
||||
line_numbers: true
|
||||
)
|
||||
:error_mode => :warn,
|
||||
:line_numbers => true
|
||||
)
|
||||
|
||||
assert_equal ['Liquid syntax error (line 4): Unexpected character = in "1 =! 2"'],
|
||||
template.warnings.map(&:message)
|
||||
@@ -138,9 +122,9 @@ class ErrorHandlingTest < Minitest::Test
|
||||
|
||||
bla
|
||||
',
|
||||
error_mode: :strict,
|
||||
line_numbers: true
|
||||
)
|
||||
:error_mode => :strict,
|
||||
:line_numbers => true
|
||||
)
|
||||
end
|
||||
|
||||
assert_equal 'Liquid syntax error (line 4): Unexpected character = in "1 =! 2"', err.message
|
||||
@@ -157,8 +141,8 @@ class ErrorHandlingTest < Minitest::Test
|
||||
|
||||
bla
|
||||
',
|
||||
line_numbers: true
|
||||
)
|
||||
:line_numbers => true
|
||||
)
|
||||
end
|
||||
|
||||
assert_equal "Liquid syntax error (line 5): Unknown tag 'foo'", err.message
|
||||
@@ -166,18 +150,18 @@ class ErrorHandlingTest < Minitest::Test
|
||||
|
||||
def test_strict_error_messages
|
||||
err = assert_raises(SyntaxError) do
|
||||
Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', error_mode: :strict)
|
||||
Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', :error_mode => :strict)
|
||||
end
|
||||
assert_equal 'Liquid syntax error: Unexpected character = in "1 =! 2"', err.message
|
||||
|
||||
err = assert_raises(SyntaxError) do
|
||||
Liquid::Template.parse('{{%%%}}', error_mode: :strict)
|
||||
Liquid::Template.parse('{{%%%}}', :error_mode => :strict)
|
||||
end
|
||||
assert_equal 'Liquid syntax error: Unexpected character % in "{{%%%}}"', err.message
|
||||
end
|
||||
|
||||
def test_warnings
|
||||
template = Liquid::Template.parse('{% if ~~~ %}{{%%%}}{% else %}{{ hello. }}{% endif %}', error_mode: :warn)
|
||||
template = Liquid::Template.parse('{% if ~~~ %}{{%%%}}{% else %}{{ hello. }}{% endif %}', :error_mode => :warn)
|
||||
assert_equal 3, template.warnings.size
|
||||
assert_equal 'Unexpected character ~ in "~~~"', template.warnings[0].to_s(false)
|
||||
assert_equal 'Unexpected character % in "{{%%%}}"', template.warnings[1].to_s(false)
|
||||
@@ -186,7 +170,7 @@ class ErrorHandlingTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_warning_line_numbers
|
||||
template = Liquid::Template.parse("{% if ~~~ %}\n{{%%%}}{% else %}\n{{ hello. }}{% endif %}", error_mode: :warn, line_numbers: true)
|
||||
template = Liquid::Template.parse("{% if ~~~ %}\n{{%%%}}{% else %}\n{{ hello. }}{% endif %}", :error_mode => :warn, :line_numbers => true)
|
||||
assert_equal 'Liquid syntax error (line 1): Unexpected character ~ in "~~~"', template.warnings[0].message
|
||||
assert_equal 'Liquid syntax error (line 2): Unexpected character % in "{{%%%}}"', template.warnings[1].message
|
||||
assert_equal 'Liquid syntax error (line 3): Expected id but found end_of_string in "{{ hello. }}"', template.warnings[2].message
|
||||
@@ -202,59 +186,10 @@ class ErrorHandlingTest < Minitest::Test
|
||||
end
|
||||
end
|
||||
|
||||
def test_default_exception_renderer_with_internal_error
|
||||
template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true)
|
||||
|
||||
output = template.render({ 'errors' => ErrorDrop.new })
|
||||
|
||||
assert_equal 'This is a runtime error: Liquid error (line 1): internal', output
|
||||
assert_equal [Liquid::InternalError], template.errors.map(&:class)
|
||||
end
|
||||
|
||||
def test_setting_default_exception_renderer
|
||||
old_exception_renderer = Liquid::Template.default_exception_renderer
|
||||
exceptions = []
|
||||
Liquid::Template.default_exception_renderer = ->(e) { exceptions << e; '' }
|
||||
template = Liquid::Template.parse('This is a runtime error: {{ errors.argument_error }}')
|
||||
|
||||
output = template.render({ 'errors' => ErrorDrop.new })
|
||||
|
||||
assert_equal 'This is a runtime error: ', output
|
||||
assert_equal [Liquid::ArgumentError], template.errors.map(&:class)
|
||||
ensure
|
||||
Liquid::Template.default_exception_renderer = old_exception_renderer if old_exception_renderer
|
||||
end
|
||||
|
||||
def test_exception_renderer_exposing_non_liquid_error
|
||||
template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true)
|
||||
exceptions = []
|
||||
handler = ->(e) { exceptions << e; e.cause }
|
||||
|
||||
output = template.render({ 'errors' => ErrorDrop.new }, exception_renderer: handler)
|
||||
|
||||
assert_equal 'This is a runtime error: runtime error', output
|
||||
assert_equal [Liquid::InternalError], exceptions.map(&:class)
|
||||
assert_equal exceptions, template.errors
|
||||
assert_equal '#<RuntimeError: runtime error>', exceptions.first.cause.inspect
|
||||
end
|
||||
|
||||
class TestFileSystem
|
||||
def read_template_file(template_path)
|
||||
"{{ errors.argument_error }}"
|
||||
end
|
||||
end
|
||||
|
||||
def test_included_template_name_with_line_numbers
|
||||
old_file_system = Liquid::Template.file_system
|
||||
|
||||
begin
|
||||
Liquid::Template.file_system = TestFileSystem.new
|
||||
template = Liquid::Template.parse("Argument error:\n{% include 'product' %}", line_numbers: true)
|
||||
page = template.render('errors' => ErrorDrop.new)
|
||||
ensure
|
||||
Liquid::Template.file_system = old_file_system
|
||||
end
|
||||
assert_equal "Argument error:\nLiquid error (product line 1): argument error", page
|
||||
assert_equal "product", template.errors.first.template_name
|
||||
def test_disabling_error_rendering
|
||||
template = Liquid::Template.parse('This is an argument error: {{ errors.argument_error }}')
|
||||
template.render_errors = false
|
||||
assert_equal 'This is an argument error: ', template.render('errors' => ErrorDrop.new)
|
||||
assert_equal [ArgumentError], template.errors.map(&:class)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -22,6 +22,12 @@ module SubstituteFilter
|
||||
end
|
||||
end
|
||||
|
||||
module ErrorFilter
|
||||
def standard_error(input)
|
||||
raise ::StandardError, 'standard error'
|
||||
end
|
||||
end
|
||||
|
||||
class FiltersTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
@@ -39,13 +45,13 @@ class FiltersTest < Minitest::Test
|
||||
@context['var'] = 1000
|
||||
@context.add_filters(MoneyFilter)
|
||||
|
||||
assert_equal ' 1000$ ', Template.parse("{{var | money}}").render(@context)
|
||||
assert_equal ' 1000$ ', Variable.new("var | money").render(@context)
|
||||
end
|
||||
|
||||
def test_underscore_in_filter_name
|
||||
@context['var'] = 1000
|
||||
@context.add_filters(MoneyFilter)
|
||||
assert_equal ' 1000$ ', Template.parse("{{var | money_with_underscore}}").render(@context)
|
||||
assert_equal ' 1000$ ', Variable.new("var | money_with_underscore").render(@context)
|
||||
end
|
||||
|
||||
def test_second_filter_overwrites_first
|
||||
@@ -53,20 +59,20 @@ class FiltersTest < Minitest::Test
|
||||
@context.add_filters(MoneyFilter)
|
||||
@context.add_filters(CanadianMoneyFilter)
|
||||
|
||||
assert_equal ' 1000$ CAD ', Template.parse("{{var | money}}").render(@context)
|
||||
assert_equal ' 1000$ CAD ', Variable.new("var | money").render(@context)
|
||||
end
|
||||
|
||||
def test_size
|
||||
@context['var'] = 'abcd'
|
||||
@context.add_filters(MoneyFilter)
|
||||
|
||||
assert_equal '4', Template.parse("{{var | size}}").render(@context)
|
||||
assert_equal 4, Variable.new("var | size").render(@context)
|
||||
end
|
||||
|
||||
def test_join
|
||||
@context['var'] = [1, 2, 3, 4]
|
||||
|
||||
assert_equal "1 2 3 4", Template.parse("{{var | join}}").render(@context)
|
||||
assert_equal "1 2 3 4", Variable.new("var | join").render(@context)
|
||||
end
|
||||
|
||||
def test_sort
|
||||
@@ -76,11 +82,11 @@ class FiltersTest < Minitest::Test
|
||||
@context['arrays'] = ['flower', 'are']
|
||||
@context['case_sensitive'] = ['sensitive', 'Expected', 'case']
|
||||
|
||||
assert_equal '1 2 3 4', Template.parse("{{numbers | sort | join}}").render(@context)
|
||||
assert_equal 'alphabetic as expected', Template.parse("{{words | sort | join}}").render(@context)
|
||||
assert_equal '3', Template.parse("{{value | sort}}").render(@context)
|
||||
assert_equal 'are flower', Template.parse("{{arrays | sort | join}}").render(@context)
|
||||
assert_equal 'Expected case sensitive', Template.parse("{{case_sensitive | sort | join}}").render(@context)
|
||||
assert_equal [1, 2, 3, 4], Variable.new("numbers | sort").render(@context)
|
||||
assert_equal ['alphabetic', 'as', 'expected'], Variable.new("words | sort").render(@context)
|
||||
assert_equal [3], Variable.new("value | sort").render(@context)
|
||||
assert_equal ['are', 'flower'], Variable.new("arrays | sort").render(@context)
|
||||
assert_equal ['Expected', 'case', 'sensitive'], Variable.new("case_sensitive | sort").render(@context)
|
||||
end
|
||||
|
||||
def test_sort_natural
|
||||
@@ -89,64 +95,54 @@ class FiltersTest < Minitest::Test
|
||||
@context['objects'] = [TestObject.new('A'), TestObject.new('b'), TestObject.new('C')]
|
||||
|
||||
# Test strings
|
||||
assert_equal 'Assert case Insensitive', Template.parse("{{words | sort_natural | join}}").render(@context)
|
||||
assert_equal ['Assert', 'case', 'Insensitive'], Variable.new("words | sort_natural").render(@context)
|
||||
|
||||
# Test hashes
|
||||
assert_equal 'A b C', Template.parse("{{hashes | sort_natural: 'a' | map: 'a' | join}}").render(@context)
|
||||
sorted = Variable.new("hashes | sort_natural: 'a'").render(@context)
|
||||
assert_equal sorted[0]['a'], 'A'
|
||||
assert_equal sorted[1]['a'], 'b'
|
||||
assert_equal sorted[2]['a'], 'C'
|
||||
|
||||
# Test objects
|
||||
assert_equal 'A b C', Template.parse("{{objects | sort_natural: 'a' | map: 'a' | join}}").render(@context)
|
||||
end
|
||||
|
||||
def test_compact
|
||||
@context['words'] = ['a', nil, 'b', nil, 'c']
|
||||
@context['hashes'] = [{ 'a' => 'A' }, { 'a' => nil }, { 'a' => 'C' }]
|
||||
@context['objects'] = [TestObject.new('A'), TestObject.new(nil), TestObject.new('C')]
|
||||
|
||||
# Test strings
|
||||
assert_equal 'a b c', Template.parse("{{words | compact | join}}").render(@context)
|
||||
|
||||
# Test hashes
|
||||
assert_equal 'A C', Template.parse("{{hashes | compact: 'a' | map: 'a' | join}}").render(@context)
|
||||
|
||||
# Test objects
|
||||
assert_equal 'A C', Template.parse("{{objects | compact: 'a' | map: 'a' | join}}").render(@context)
|
||||
sorted = Variable.new("objects | sort_natural: 'a'").render(@context)
|
||||
assert_equal sorted[0].a, 'A'
|
||||
assert_equal sorted[1].a, 'b'
|
||||
assert_equal sorted[2].a, 'C'
|
||||
end
|
||||
|
||||
def test_strip_html
|
||||
@context['var'] = "<b>bla blub</a>"
|
||||
|
||||
assert_equal "bla blub", Template.parse("{{ var | strip_html }}").render(@context)
|
||||
assert_equal "bla blub", Variable.new("var | strip_html").render(@context)
|
||||
end
|
||||
|
||||
def test_strip_html_ignore_comments_with_html
|
||||
@context['var'] = "<!-- split and some <ul> tag --><b>bla blub</a>"
|
||||
|
||||
assert_equal "bla blub", Template.parse("{{ var | strip_html }}").render(@context)
|
||||
assert_equal "bla blub", Variable.new("var | strip_html").render(@context)
|
||||
end
|
||||
|
||||
def test_capitalize
|
||||
@context['var'] = "blub"
|
||||
|
||||
assert_equal "Blub", Template.parse("{{ var | capitalize }}").render(@context)
|
||||
assert_equal "Blub", Variable.new("var | capitalize").render(@context)
|
||||
end
|
||||
|
||||
def test_nonexistent_filter_is_ignored
|
||||
@context['var'] = 1000
|
||||
|
||||
assert_equal '1000', Template.parse("{{ var | xyzzy }}").render(@context)
|
||||
assert_equal 1000, Variable.new("var | xyzzy").render(@context)
|
||||
end
|
||||
|
||||
def test_filter_with_keyword_arguments
|
||||
@context['surname'] = 'john'
|
||||
@context['input'] = 'hello %{first_name}, %{last_name}'
|
||||
@context.add_filters(SubstituteFilter)
|
||||
output = Template.parse(%({{ input | substitute: first_name: surname, last_name: 'doe' }})).render(@context)
|
||||
output = Variable.new(%( 'hello %{first_name}, %{last_name}' | substitute: first_name: surname, last_name: 'doe' )).render(@context)
|
||||
assert_equal 'hello john, doe', output
|
||||
end
|
||||
|
||||
def test_override_object_method_in_filter
|
||||
assert_equal "tap overridden", Template.parse("{{var | tap}}").render!({ 'var' => 1000 }, filters: [OverrideObjectMethodFilter])
|
||||
assert_equal "tap overridden", Template.parse("{{var | tap}}").render!({ 'var' => 1000 }, :filters => [OverrideObjectMethodFilter])
|
||||
|
||||
# tap still treated as a non-existent filter
|
||||
assert_equal "1000", Template.parse("{{var | tap}}").render!({ 'var' => 1000 })
|
||||
@@ -159,8 +155,8 @@ class FiltersInTemplate < Minitest::Test
|
||||
def test_local_global
|
||||
with_global_filter(MoneyFilter) do
|
||||
assert_equal " 1000$ ", Template.parse("{{1000 | money}}").render!(nil, nil)
|
||||
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, filters: CanadianMoneyFilter)
|
||||
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, filters: [CanadianMoneyFilter])
|
||||
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, :filters => CanadianMoneyFilter)
|
||||
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, :filters => [CanadianMoneyFilter])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -168,9 +164,16 @@ class FiltersInTemplate < Minitest::Test
|
||||
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, CanadianMoneyFilter)
|
||||
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, [CanadianMoneyFilter])
|
||||
end
|
||||
end # FiltersTest
|
||||
|
||||
class TestObject < Liquid::Drop
|
||||
def test_filter_error
|
||||
context = Context.new
|
||||
context.add_filters(ErrorFilter)
|
||||
assert_equal "Liquid error: standard error", Template.parse("{{'var' | standard_error}}").render(context)
|
||||
assert_equal [Liquid::FilterError], context.errors.map(&:class)
|
||||
end
|
||||
end
|
||||
|
||||
class TestObject
|
||||
attr_accessor :a
|
||||
def initialize(a)
|
||||
@a = a
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
require 'test_helper'
|
||||
|
||||
module MoneyFilter
|
||||
def money(input)
|
||||
sprintf(' %d$ ', input)
|
||||
end
|
||||
end
|
||||
|
||||
module CanadianMoneyFilter
|
||||
def money(input)
|
||||
sprintf(' %d$ CAD ', input)
|
||||
end
|
||||
end
|
||||
|
||||
class HashOrderingTest < Minitest::Test
|
||||
module MoneyFilter
|
||||
def money(input)
|
||||
sprintf(' %d$ ', input)
|
||||
end
|
||||
end
|
||||
|
||||
module CanadianMoneyFilter
|
||||
def money(input)
|
||||
sprintf(' %d$ CAD ', input)
|
||||
end
|
||||
end
|
||||
|
||||
include Liquid
|
||||
|
||||
def test_global_register_order
|
||||
|
||||
@@ -43,14 +43,6 @@ class OutputTest < Minitest::Test
|
||||
assert_equal expected, Template.parse(text).render!(@assigns)
|
||||
end
|
||||
|
||||
def test_variable_traversing_with_two_brackets
|
||||
text = %({{ site.data.menu[include.menu][include.locale] }})
|
||||
assert_equal "it works!", Template.parse(text).render!(
|
||||
"site" => { "data" => { "menu" => { "foo" => { "bar" => "it works!" } } } },
|
||||
"include" => { "menu" => "foo", "locale" => "bar" }
|
||||
)
|
||||
end
|
||||
|
||||
def test_variable_traversing
|
||||
text = %( {{car.bmw}} {{car.gm}} {{car.bmw}} )
|
||||
|
||||
@@ -62,62 +54,62 @@ class OutputTest < Minitest::Test
|
||||
text = %( {{ car.gm | make_funny }} )
|
||||
expected = %( LOL )
|
||||
|
||||
assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
|
||||
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
|
||||
end
|
||||
|
||||
def test_variable_piping_with_input
|
||||
text = %( {{ car.gm | cite_funny }} )
|
||||
expected = %( LOL: bad )
|
||||
|
||||
assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
|
||||
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
|
||||
end
|
||||
|
||||
def test_variable_piping_with_args
|
||||
text = %! {{ car.gm | add_smiley : ':-(' }} !
|
||||
expected = %| bad :-( |
|
||||
|
||||
assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
|
||||
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
|
||||
end
|
||||
|
||||
def test_variable_piping_with_no_args
|
||||
text = %( {{ car.gm | add_smiley }} )
|
||||
expected = %| bad :-) |
|
||||
|
||||
assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
|
||||
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
|
||||
end
|
||||
|
||||
def test_multiple_variable_piping_with_args
|
||||
text = %! {{ car.gm | add_smiley : ':-(' | add_smiley : ':-('}} !
|
||||
expected = %| bad :-( :-( |
|
||||
|
||||
assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
|
||||
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
|
||||
end
|
||||
|
||||
def test_variable_piping_with_multiple_args
|
||||
text = %( {{ car.gm | add_tag : 'span', 'bar'}} )
|
||||
expected = %( <span id="bar">bad</span> )
|
||||
|
||||
assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
|
||||
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
|
||||
end
|
||||
|
||||
def test_variable_piping_with_variable_args
|
||||
text = %( {{ car.gm | add_tag : 'span', car.bmw}} )
|
||||
expected = %( <span id="good">bad</span> )
|
||||
|
||||
assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
|
||||
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
|
||||
end
|
||||
|
||||
def test_multiple_pipings
|
||||
text = %( {{ best_cars | cite_funny | paragraph }} )
|
||||
expected = %( <p>LOL: bmw</p> )
|
||||
|
||||
assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
|
||||
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
|
||||
end
|
||||
|
||||
def test_link_to
|
||||
text = %( {{ 'Typo' | link_to: 'http://typo.leetsoft.com' }} )
|
||||
expected = %( <a href="http://typo.leetsoft.com">Typo</a> )
|
||||
|
||||
assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
|
||||
assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
|
||||
end
|
||||
end # OutputTest
|
||||
|
||||
@@ -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
|
||||
@@ -62,9 +62,9 @@ class ParsingQuirksTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_no_error_on_lax_empty_filter
|
||||
assert Template.parse("{{test |a|b|}}", error_mode: :lax)
|
||||
assert Template.parse("{{test}}", error_mode: :lax)
|
||||
assert Template.parse("{{|test|}}", error_mode: :lax)
|
||||
assert Template.parse("{{test |a|b|}}", :error_mode => :lax)
|
||||
assert Template.parse("{{test}}", :error_mode => :lax)
|
||||
assert Template.parse("{{|test|}}", :error_mode => :lax)
|
||||
end
|
||||
|
||||
def test_meaningless_parens_lax
|
||||
@@ -99,7 +99,7 @@ class ParsingQuirksTest < Minitest::Test
|
||||
# After the messed up quotes a filter without parameters (reverse) should work
|
||||
# but one with parameters (remove) shouldn't be detected.
|
||||
assert_template_result('here', "{{ 'hi there' | split:\"t\"\" | reverse | first}}")
|
||||
assert_template_result('hi ', "{{ 'hi there' | split:\"t\"\" | remove:\"i\" | first}}")
|
||||
assert_template_result('hi ', "{{ 'hi there' | split:\"t\"\" | remove:\"i\" | first}}")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -116,7 +116,4 @@ class ParsingQuirksTest < Minitest::Test
|
||||
end
|
||||
end
|
||||
|
||||
def test_contains_in_id
|
||||
assert_template_result(' YES ', '{% if containsallshipments == true %} YES {% endif %}', 'containsallshipments' => true)
|
||||
end
|
||||
end # ParsingQuirksTest
|
||||
|
||||
@@ -21,7 +21,7 @@ class RenderProfilingTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_parse_makes_available_simple_profiling
|
||||
t = Template.parse("{{ 'a string' | upcase }}", profile: true)
|
||||
t = Template.parse("{{ 'a string' | upcase }}", :profile => true)
|
||||
t.render!
|
||||
|
||||
assert_equal 1, t.profiler.length
|
||||
@@ -31,14 +31,14 @@ class RenderProfilingTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_render_ignores_raw_strings_when_profiling
|
||||
t = Template.parse("This is raw string\nstuff\nNewline", profile: true)
|
||||
t = Template.parse("This is raw string\nstuff\nNewline", :profile => true)
|
||||
t.render!
|
||||
|
||||
assert_equal 0, t.profiler.length
|
||||
end
|
||||
|
||||
def test_profiling_includes_line_numbers_of_liquid_nodes
|
||||
t = Template.parse("{{ 'a string' | upcase }}\n{% increment test %}", profile: true)
|
||||
t = Template.parse("{{ 'a string' | upcase }}\n{% increment test %}", :profile => true)
|
||||
t.render!
|
||||
assert_equal 2, t.profiler.length
|
||||
|
||||
@@ -49,7 +49,7 @@ class RenderProfilingTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_profiling_includes_line_numbers_of_included_partials
|
||||
t = Template.parse("{% include 'a_template' %}", profile: true)
|
||||
t = Template.parse("{% include 'a_template' %}", :profile => true)
|
||||
t.render!
|
||||
|
||||
included_children = t.profiler[0].children
|
||||
@@ -61,7 +61,7 @@ class RenderProfilingTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_profiling_times_the_rendering_of_tokens
|
||||
t = Template.parse("{% include 'a_template' %}", profile: true)
|
||||
t = Template.parse("{% include 'a_template' %}", :profile => true)
|
||||
t.render!
|
||||
|
||||
node = t.profiler[0]
|
||||
@@ -69,14 +69,14 @@ class RenderProfilingTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_profiling_times_the_entire_render
|
||||
t = Template.parse("{% include 'a_template' %}", profile: true)
|
||||
t = Template.parse("{% include 'a_template' %}", :profile => true)
|
||||
t.render!
|
||||
|
||||
assert t.profiler.total_render_time >= 0, "Total render time was not calculated"
|
||||
end
|
||||
|
||||
def test_profiling_uses_include_to_mark_children
|
||||
t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}", profile: true)
|
||||
t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}", :profile => true)
|
||||
t.render!
|
||||
|
||||
include_node = t.profiler[1]
|
||||
@@ -84,7 +84,7 @@ class RenderProfilingTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_profiling_marks_children_with_the_name_of_included_partial
|
||||
t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}", profile: true)
|
||||
t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}", :profile => true)
|
||||
t.render!
|
||||
|
||||
include_node = t.profiler[1]
|
||||
@@ -94,7 +94,7 @@ class RenderProfilingTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_profiling_supports_multiple_templates
|
||||
t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}\n{% include 'b_template' %}", profile: true)
|
||||
t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}\n{% include 'b_template' %}", :profile => true)
|
||||
t.render!
|
||||
|
||||
a_template = t.profiler[1]
|
||||
@@ -109,7 +109,7 @@ class RenderProfilingTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_profiling_supports_rendering_the_same_partial_multiple_times
|
||||
t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}\n{% include 'a_template' %}", profile: true)
|
||||
t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}\n{% include 'a_template' %}", :profile => true)
|
||||
t.render!
|
||||
|
||||
a_template1 = t.profiler[1]
|
||||
@@ -124,7 +124,7 @@ class RenderProfilingTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_can_iterate_over_each_profiling_entry
|
||||
t = Template.parse("{{ 'a string' | upcase }}\n{% increment test %}", profile: true)
|
||||
t = Template.parse("{{ 'a string' | upcase }}\n{% increment test %}", :profile => true)
|
||||
t.render!
|
||||
|
||||
timing_count = 0
|
||||
@@ -136,7 +136,7 @@ class RenderProfilingTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_profiling_marks_children_of_if_blocks
|
||||
t = Template.parse("{% if true %} {% increment test %} {{ test }} {% endif %}", profile: true)
|
||||
t = Template.parse("{% if true %} {% increment test %} {{ test }} {% endif %}", :profile => true)
|
||||
t.render!
|
||||
|
||||
assert_equal 1, t.profiler.length
|
||||
@@ -144,7 +144,7 @@ class RenderProfilingTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_profiling_marks_children_of_for_blocks
|
||||
t = Template.parse("{% for item in collection %} {{ item }} {% endfor %}", profile: true)
|
||||
t = Template.parse("{% for item in collection %} {{ item }} {% endfor %}", :profile => true)
|
||||
t.render!({ "collection" => ["one", "two"] })
|
||||
|
||||
assert_equal 1, t.profiler.length
|
||||
|
||||
@@ -9,10 +9,6 @@ end
|
||||
class SecurityTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def setup
|
||||
@assigns = {}
|
||||
end
|
||||
|
||||
def test_no_instance_eval
|
||||
text = %( {{ '1+1' | instance_eval }} )
|
||||
expected = %( 1+1 )
|
||||
@@ -38,7 +34,7 @@ class SecurityTest < Minitest::Test
|
||||
text = %( {{ '1+1' | add_one | instance_eval }} )
|
||||
expected = %( 1+1 + 1 )
|
||||
|
||||
assert_equal expected, Template.parse(text).render!(@assigns, filters: SecurityFilter)
|
||||
assert_equal expected, Template.parse(text).render!(@assigns, :filters => SecurityFilter)
|
||||
end
|
||||
|
||||
def test_does_not_add_filters_to_symbol_table
|
||||
@@ -63,18 +59,4 @@ class SecurityTest < Minitest::Test
|
||||
|
||||
assert_equal [], (Symbol.all_symbols - current_symbols)
|
||||
end
|
||||
|
||||
def test_max_depth_nested_blocks_does_not_raise_exception
|
||||
depth = Liquid::Block::MAX_DEPTH
|
||||
code = "{% if true %}" * depth + "rendered" + "{% endif %}" * depth
|
||||
assert_equal "rendered", Template.parse(code).render!
|
||||
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
|
||||
|
||||
@@ -41,16 +41,6 @@ class TestEnumerable < Liquid::Drop
|
||||
end
|
||||
end
|
||||
|
||||
class NumberLikeThing < Liquid::Drop
|
||||
def initialize(amount)
|
||||
@amount = amount
|
||||
end
|
||||
|
||||
def to_number
|
||||
@amount
|
||||
end
|
||||
end
|
||||
|
||||
class StandardFiltersTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
@@ -86,13 +76,6 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_equal '', @filters.slice(nil, 0)
|
||||
assert_equal '', @filters.slice('foobar', 100, 10)
|
||||
assert_equal '', @filters.slice('foobar', -100, 10)
|
||||
assert_equal 'oob', @filters.slice('foobar', '1', '3')
|
||||
assert_raises(Liquid::ArgumentError) do
|
||||
@filters.slice('foobar', nil)
|
||||
end
|
||||
assert_raises(Liquid::ArgumentError) do
|
||||
@filters.slice('foobar', 0, "")
|
||||
end
|
||||
end
|
||||
|
||||
def test_slice_on_arrays
|
||||
@@ -115,29 +98,20 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_equal '...', @filters.truncate('1234567890', 0)
|
||||
assert_equal '1234567890', @filters.truncate('1234567890')
|
||||
assert_equal "测试...", @filters.truncate("测试测试测试测试", 5)
|
||||
assert_equal '12341', @filters.truncate("1234567890", 5, 1)
|
||||
end
|
||||
|
||||
def test_split
|
||||
assert_equal ['12', '34'], @filters.split('12~34', '~')
|
||||
assert_equal ['A? ', ' ,Z'], @filters.split('A? ~ ~ ~ ,Z', '~ ~ ~')
|
||||
assert_equal ['A?Z'], @filters.split('A?Z', '~')
|
||||
# Regexp works although Liquid does not support.
|
||||
assert_equal ['A', 'Z'], @filters.split('AxZ', /x/)
|
||||
assert_equal [], @filters.split(nil, ' ')
|
||||
assert_equal ['A', 'Z'], @filters.split('A1Z', 1)
|
||||
end
|
||||
|
||||
def test_escape
|
||||
assert_equal '<strong>', @filters.escape('<strong>')
|
||||
assert_equal '1', @filters.escape(1)
|
||||
assert_equal '2001-02-03', @filters.escape(Date.new(2001, 2, 3))
|
||||
assert_nil @filters.escape(nil)
|
||||
end
|
||||
|
||||
def test_h
|
||||
assert_equal '<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
|
||||
|
||||
def test_escape_once
|
||||
@@ -146,22 +120,7 @@ class StandardFiltersTest < Minitest::Test
|
||||
|
||||
def test_url_encode
|
||||
assert_equal 'foo%2B1%40example.com', @filters.url_encode('foo+1@example.com')
|
||||
assert_equal '1', @filters.url_encode(1)
|
||||
assert_equal '2001-02-03', @filters.url_encode(Date.new(2001, 2, 3))
|
||||
assert_nil @filters.url_encode(nil)
|
||||
end
|
||||
|
||||
def test_url_decode
|
||||
assert_equal 'foo bar', @filters.url_decode('foo+bar')
|
||||
assert_equal 'foo bar', @filters.url_decode('foo%20bar')
|
||||
assert_equal 'foo+1@example.com', @filters.url_decode('foo%2B1%40example.com')
|
||||
assert_equal '1', @filters.url_decode(1)
|
||||
assert_equal '2001-02-03', @filters.url_decode(Date.new(2001, 2, 3))
|
||||
assert_nil @filters.url_decode(nil)
|
||||
exception = assert_raises Liquid::ArgumentError do
|
||||
@filters.url_decode('%ff')
|
||||
end
|
||||
assert_equal 'Liquid error: invalid byte sequence in UTF-8', exception.message
|
||||
assert_equal nil, @filters.url_encode(nil)
|
||||
end
|
||||
|
||||
def test_truncatewords
|
||||
@@ -170,7 +129,6 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_equal 'one two three', @filters.truncatewords('one two three')
|
||||
assert_equal 'Two small (13” x 5.5” x 10” high) baskets fit inside one large basket (13”...', @filters.truncatewords('Two small (13” x 5.5” x 10” high) baskets fit inside one large basket (13” x 16” x 10.5” high) with cover.', 15)
|
||||
assert_equal "测试测试测试测试", @filters.truncatewords('测试测试测试测试', 5)
|
||||
assert_equal 'one two1', @filters.truncatewords("one two three", 2, 1)
|
||||
end
|
||||
|
||||
def test_strip_html
|
||||
@@ -181,15 +139,11 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_equal 'test', @filters.strip_html("<div\nclass='multiline'>test</div>")
|
||||
assert_equal 'test', @filters.strip_html("<!-- foo bar \n test -->test")
|
||||
assert_equal '', @filters.strip_html(nil)
|
||||
|
||||
# Quirk of the existing implementation
|
||||
assert_equal 'foo;', @filters.strip_html("<<<script </script>script>foo;</script>")
|
||||
end
|
||||
|
||||
def test_join
|
||||
assert_equal '1 2 3 4', @filters.join([1, 2, 3, 4])
|
||||
assert_equal '1 - 2 - 3 - 4', @filters.join([1, 2, 3, 4], ' - ')
|
||||
assert_equal '1121314', @filters.join([1, 2, 3, 4], 1)
|
||||
end
|
||||
|
||||
def test_sort
|
||||
@@ -197,112 +151,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")
|
||||
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" },
|
||||
{ "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(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
|
||||
|
||||
def test_sort_invalid_property
|
||||
foo = [
|
||||
[1],
|
||||
[2],
|
||||
[3]
|
||||
]
|
||||
|
||||
assert_raises Liquid::ArgumentError do
|
||||
@filters.sort(foo, "bar")
|
||||
end
|
||||
end
|
||||
|
||||
def test_sort_natural_empty_array
|
||||
assert_equal [], @filters.sort_natural([], "a")
|
||||
end
|
||||
|
||||
def test_sort_natural_invalid_property
|
||||
foo = [
|
||||
[1],
|
||||
[2],
|
||||
[3]
|
||||
]
|
||||
|
||||
assert_raises Liquid::ArgumentError do
|
||||
@filters.sort_natural(foo, "bar")
|
||||
end
|
||||
end
|
||||
|
||||
def test_legacy_sort_hash
|
||||
assert_equal [{ a: 1, b: 2 }], @filters.sort({ a: 1, b: 2 })
|
||||
end
|
||||
@@ -315,45 +163,12 @@ class StandardFiltersTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_uniq
|
||||
assert_equal ["foo"], @filters.uniq("foo")
|
||||
assert_equal [1, 3, 2, 4], @filters.uniq([1, 1, 3, 2, 3, 1, 4, 3, 2, 1])
|
||||
assert_equal [{ "a" => 1 }, { "a" => 3 }, { "a" => 2 }], @filters.uniq([{ "a" => 1 }, { "a" => 3 }, { "a" => 1 }, { "a" => 2 }], "a")
|
||||
testdrop = TestDrop.new
|
||||
assert_equal [testdrop], @filters.uniq([testdrop, TestDrop.new], 'test')
|
||||
end
|
||||
|
||||
def test_uniq_empty_array
|
||||
assert_equal [], @filters.uniq([], "a")
|
||||
end
|
||||
|
||||
def test_uniq_invalid_property
|
||||
foo = [
|
||||
[1],
|
||||
[2],
|
||||
[3]
|
||||
]
|
||||
|
||||
assert_raises Liquid::ArgumentError do
|
||||
@filters.uniq(foo, "bar")
|
||||
end
|
||||
end
|
||||
|
||||
def test_compact_empty_array
|
||||
assert_equal [], @filters.compact([], "a")
|
||||
end
|
||||
|
||||
def test_compact_invalid_property
|
||||
foo = [
|
||||
[1],
|
||||
[2],
|
||||
[3]
|
||||
]
|
||||
|
||||
assert_raises Liquid::ArgumentError do
|
||||
@filters.compact(foo, "bar")
|
||||
end
|
||||
end
|
||||
|
||||
def test_reverse
|
||||
assert_equal [4, 3, 2, 1], @filters.reverse([1, 2, 3, 4])
|
||||
end
|
||||
@@ -402,46 +217,10 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_template_result "testfoo", templ, "procs" => [p]
|
||||
end
|
||||
|
||||
def test_map_over_drops_returning_procs
|
||||
drops = [
|
||||
{
|
||||
"proc" => ->{ "foo" }
|
||||
},
|
||||
{
|
||||
"proc" => ->{ "bar" }
|
||||
}
|
||||
]
|
||||
templ = '{{ drops | map: "proc" }}'
|
||||
assert_template_result "foobar", templ, "drops" => drops
|
||||
end
|
||||
|
||||
def test_map_works_on_enumerables
|
||||
assert_template_result "123", '{{ foo | map: "foo" }}', "foo" => TestEnumerable.new
|
||||
end
|
||||
|
||||
def test_map_returns_empty_on_2d_input_array
|
||||
foo = [
|
||||
[1],
|
||||
[2],
|
||||
[3]
|
||||
]
|
||||
|
||||
assert_raises Liquid::ArgumentError do
|
||||
@filters.map(foo, "bar")
|
||||
end
|
||||
end
|
||||
|
||||
def test_map_returns_empty_with_no_property
|
||||
foo = [
|
||||
[1],
|
||||
[2],
|
||||
[3]
|
||||
]
|
||||
assert_raises Liquid::ArgumentError do
|
||||
@filters.map(foo, nil)
|
||||
end
|
||||
end
|
||||
|
||||
def test_sort_works_on_enumerables
|
||||
assert_template_result "213", '{{ foo | sort: "bar" | map: "foo" }}', "foo" => TestEnumerable.new
|
||||
end
|
||||
@@ -451,10 +230,6 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_template_result 'foobar', '{{ foo | last }}', 'foo' => [ThingWithToLiquid.new]
|
||||
end
|
||||
|
||||
def test_truncate_calls_to_liquid
|
||||
assert_template_result "wo...", '{{ foo | truncate: 5 }}', "foo" => TestThing.new
|
||||
end
|
||||
|
||||
def test_date
|
||||
assert_equal 'May', @filters.date(Time.parse("2006-05-05 10:00:00"), "%B")
|
||||
assert_equal 'June', @filters.date(Time.parse("2006-06-05 10:00:00"), "%B")
|
||||
@@ -472,40 +247,34 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_equal '07/05/2006', @filters.date("2006-07-05 10:00:00", "%m/%d/%Y")
|
||||
|
||||
assert_equal "07/16/2004", @filters.date("Fri Jul 16 01:00:00 2004", "%m/%d/%Y")
|
||||
assert_equal Date.today.year.to_s, @filters.date('now', '%Y')
|
||||
assert_equal Date.today.year.to_s, @filters.date('today', '%Y')
|
||||
assert_equal Date.today.year.to_s, @filters.date('Today', '%Y')
|
||||
assert_equal "#{Date.today.year}", @filters.date('now', '%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")
|
||||
|
||||
with_timezone("UTC") do
|
||||
assert_equal "07/05/2006", @filters.date(1152098955, "%m/%d/%Y")
|
||||
assert_equal "07/05/2006", @filters.date("1152098955", "%m/%d/%Y")
|
||||
end
|
||||
assert_equal "07/05/2006", @filters.date(1152098955, "%m/%d/%Y")
|
||||
assert_equal "07/05/2006", @filters.date("1152098955", "%m/%d/%Y")
|
||||
end
|
||||
|
||||
def test_first_last
|
||||
assert_equal 1, @filters.first([1, 2, 3])
|
||||
assert_equal 3, @filters.last([1, 2, 3])
|
||||
assert_nil @filters.first([])
|
||||
assert_nil @filters.last([])
|
||||
assert_equal nil, @filters.first([])
|
||||
assert_equal nil, @filters.last([])
|
||||
end
|
||||
|
||||
def test_replace
|
||||
assert_equal '2 2 2 2', @filters.replace('1 1 1 1', '1', 2)
|
||||
assert_equal '2 2 2 2', @filters.replace('1 1 1 1', 1, 2)
|
||||
assert_equal '2 1 1 1', @filters.replace_first('1 1 1 1', '1', 2)
|
||||
assert_equal '2 1 1 1', @filters.replace_first('1 1 1 1', 1, 2)
|
||||
assert_template_result '2 1 1 1', "{{ '1 1 1 1' | replace_first: '1', 2 }}"
|
||||
end
|
||||
|
||||
def test_remove
|
||||
assert_equal ' ', @filters.remove("a a a a", 'a')
|
||||
assert_equal ' ', @filters.remove("1 1 1 1", 1)
|
||||
assert_equal 'a a a', @filters.remove_first("a a a a", 'a ')
|
||||
assert_equal ' 1 1 1', @filters.remove_first("1 1 1 1", 1)
|
||||
assert_template_result 'a a a', "{{ 'a a a a' | remove_first: 'a ' }}"
|
||||
end
|
||||
|
||||
@@ -540,38 +309,20 @@ class StandardFiltersTest < Minitest::Test
|
||||
def test_plus
|
||||
assert_template_result "2", "{{ 1 | plus:1 }}"
|
||||
assert_template_result "2.0", "{{ '1' | plus:'1.0' }}"
|
||||
|
||||
assert_template_result "5", "{{ price | plus:'2' }}", 'price' => NumberLikeThing.new(3)
|
||||
end
|
||||
|
||||
def test_minus
|
||||
assert_template_result "4", "{{ input | minus:operand }}", 'input' => 5, 'operand' => 1
|
||||
assert_template_result "2.3", "{{ '4.3' | minus:'2' }}"
|
||||
|
||||
assert_template_result "5", "{{ price | minus:'2' }}", 'price' => NumberLikeThing.new(7)
|
||||
end
|
||||
|
||||
def test_abs
|
||||
assert_template_result "17", "{{ 17 | abs }}"
|
||||
assert_template_result "17", "{{ -17 | abs }}"
|
||||
assert_template_result "17", "{{ '17' | abs }}"
|
||||
assert_template_result "17", "{{ '-17' | abs }}"
|
||||
assert_template_result "0", "{{ 0 | abs }}"
|
||||
assert_template_result "0", "{{ '0' | abs }}"
|
||||
assert_template_result "17.42", "{{ 17.42 | abs }}"
|
||||
assert_template_result "17.42", "{{ -17.42 | abs }}"
|
||||
assert_template_result "17.42", "{{ '17.42' | abs }}"
|
||||
assert_template_result "17.42", "{{ '-17.42' | abs }}"
|
||||
end
|
||||
|
||||
def test_times
|
||||
assert_template_result "12", "{{ 3 | times:4 }}"
|
||||
assert_template_result "0", "{{ 'foo' | times:4 }}"
|
||||
|
||||
assert_template_result "6", "{{ '2.1' | times:3 | replace: '.','-' | plus:0}}"
|
||||
|
||||
assert_template_result "7.25", "{{ 0.0725 | times:100 }}"
|
||||
assert_template_result "-7.25", '{{ "-0.0725" | times:100 }}'
|
||||
assert_template_result "7.25", '{{ "-0.0725" | times: -100 }}'
|
||||
assert_template_result "4", "{{ price | times:2 }}", 'price' => NumberLikeThing.new(2)
|
||||
end
|
||||
|
||||
def test_divided_by
|
||||
@@ -582,74 +333,26 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_equal "Liquid error: divided by 0", Template.parse("{{ 5 | divided_by:0 }}").render
|
||||
|
||||
assert_template_result "0.5", "{{ 2.0 | divided_by:4 }}"
|
||||
assert_raises(Liquid::ZeroDivisionError) do
|
||||
assert_template_result "4", "{{ 1 | modulo: 0 }}"
|
||||
end
|
||||
|
||||
assert_template_result "5", "{{ price | divided_by:2 }}", 'price' => NumberLikeThing.new(10)
|
||||
end
|
||||
|
||||
def test_modulo
|
||||
assert_template_result "1", "{{ 3 | modulo:2 }}"
|
||||
assert_raises(Liquid::ZeroDivisionError) do
|
||||
assert_template_result "4", "{{ 1 | modulo: 0 }}"
|
||||
end
|
||||
|
||||
assert_template_result "1", "{{ price | modulo:2 }}", 'price' => NumberLikeThing.new(3)
|
||||
end
|
||||
|
||||
def test_round
|
||||
assert_template_result "5", "{{ input | round }}", 'input' => 4.6
|
||||
assert_template_result "4", "{{ '4.3' | round }}"
|
||||
assert_template_result "4.56", "{{ input | round: 2 }}", 'input' => 4.5612
|
||||
assert_raises(Liquid::FloatDomainError) do
|
||||
assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | round }}"
|
||||
end
|
||||
|
||||
assert_template_result "5", "{{ price | round }}", 'price' => NumberLikeThing.new(4.6)
|
||||
assert_template_result "4", "{{ price | round }}", 'price' => NumberLikeThing.new(4.3)
|
||||
end
|
||||
|
||||
def test_ceil
|
||||
assert_template_result "5", "{{ input | ceil }}", 'input' => 4.6
|
||||
assert_template_result "5", "{{ '4.3' | ceil }}"
|
||||
assert_raises(Liquid::FloatDomainError) do
|
||||
assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | ceil }}"
|
||||
end
|
||||
|
||||
assert_template_result "5", "{{ price | ceil }}", 'price' => NumberLikeThing.new(4.6)
|
||||
end
|
||||
|
||||
def test_floor
|
||||
assert_template_result "4", "{{ input | floor }}", 'input' => 4.6
|
||||
assert_template_result "4", "{{ '4.3' | floor }}"
|
||||
assert_raises(Liquid::FloatDomainError) do
|
||||
assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | floor }}"
|
||||
end
|
||||
|
||||
assert_template_result "5", "{{ price | floor }}", 'price' => NumberLikeThing.new(5.4)
|
||||
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
|
||||
@@ -663,7 +366,8 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_equal [1, 2, 'a'], @filters.concat([1, 2], ['a'])
|
||||
assert_equal [1, 2, 10], @filters.concat([1, 2], [10])
|
||||
|
||||
assert_raises(Liquid::ArgumentError, "concat filter requires an array argument") do
|
||||
assert_raises(TypeError) do
|
||||
# no implicit conversion of Fixnum into Array
|
||||
@filters.concat([1, 2], 10)
|
||||
end
|
||||
end
|
||||
@@ -686,91 +390,4 @@ class StandardFiltersTest < Minitest::Test
|
||||
def test_cannot_access_private_methods
|
||||
assert_template_result('a', "{{ 'a' | to_number }}")
|
||||
end
|
||||
|
||||
def test_date_raises_nothing
|
||||
assert_template_result('', "{{ '' | date: '%D' }}")
|
||||
assert_template_result('abc', "{{ 'abc' | date: '%D' }}")
|
||||
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
|
||||
|
||||
def with_timezone(tz)
|
||||
old_tz = ENV['TZ']
|
||||
ENV['TZ'] = tz
|
||||
yield
|
||||
ensure
|
||||
ENV['TZ'] = old_tz
|
||||
end
|
||||
end # StandardFiltersTest
|
||||
|
||||
@@ -12,4 +12,5 @@ class BreakTagTest < Minitest::Test
|
||||
|
||||
assert_template_result(expected, markup, assigns)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -12,4 +12,5 @@ class ContinueTagTest < Minitest::Test
|
||||
|
||||
assert_template_result(expected, markup, assigns)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -38,12 +38,6 @@ HERE
|
||||
|
||||
def test_for_with_range
|
||||
assert_template_result(' 1 2 3 ', '{%for item in (1..3) %} {{item}} {%endfor%}')
|
||||
|
||||
assert_raises(Liquid::ArgumentError) do
|
||||
Template.parse('{% for i in (a..2) %}{% endfor %}').render!("a" => [1, 2])
|
||||
end
|
||||
|
||||
assert_template_result(' 0 1 2 3 ', '{% for item in (a..3) %} {{item}} {% endfor %}', "a" => "invalid integer")
|
||||
end
|
||||
|
||||
def test_for_with_variable_range
|
||||
@@ -103,34 +97,6 @@ HERE
|
||||
assert_template_result('3456', '{%for i in array limit: 4 offset: 2 %}{{ i }}{%endfor%}', assigns)
|
||||
end
|
||||
|
||||
def test_limiting_with_invalid_limit
|
||||
assigns = { 'array' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] }
|
||||
template = <<-MKUP
|
||||
{% for i in array limit: true offset: 1 %}
|
||||
{{ i }}
|
||||
{% endfor %}
|
||||
MKUP
|
||||
|
||||
exception = assert_raises(Liquid::ArgumentError) do
|
||||
Template.parse(template).render!(assigns)
|
||||
end
|
||||
assert_equal("Liquid error: invalid integer", exception.message)
|
||||
end
|
||||
|
||||
def test_limiting_with_invalid_offset
|
||||
assigns = { 'array' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] }
|
||||
template = <<-MKUP
|
||||
{% for i in array limit: 1 offset: true %}
|
||||
{{ i }}
|
||||
{% endfor %}
|
||||
MKUP
|
||||
|
||||
exception = assert_raises(Liquid::ArgumentError) do
|
||||
Template.parse(template).render!(assigns)
|
||||
end
|
||||
assert_equal("Liquid error: invalid integer", exception.message)
|
||||
end
|
||||
|
||||
def test_dynamic_variable_limiting
|
||||
assigns = { 'array' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] }
|
||||
assigns['limit'] = 2
|
||||
@@ -187,7 +153,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%}
|
||||
@@ -206,7 +172,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
|
||||
@@ -368,23 +334,6 @@ HERE
|
||||
assert_template_result(expected, template, assigns)
|
||||
end
|
||||
|
||||
def test_overwriting_internal_variable
|
||||
template = <<-HEREDOC
|
||||
{% assign forloop = 'first' %}
|
||||
|
||||
{% for item in items %}
|
||||
{{ forloop }}
|
||||
{% assign forloop = 'second' %}
|
||||
{{ forloop }}
|
||||
{% endfor %}
|
||||
|
||||
{{ forloop }}
|
||||
HEREDOC
|
||||
|
||||
result = Liquid::Template.parse(template).render('items' => '1')
|
||||
assert_equal 'Liquid::ForloopDrop Liquid::ForloopDrop second', result.split.map(&:strip).join(' ')
|
||||
end
|
||||
|
||||
class LoaderDrop < Liquid::Drop
|
||||
attr_accessor :each_called, :load_slice_called
|
||||
|
||||
|
||||
@@ -29,10 +29,10 @@ class IfElseTagTest < Minitest::Test
|
||||
assert_template_result(' YES ', '{% if a or b %} YES {% endif %}', 'a' => true, 'b' => true)
|
||||
assert_template_result(' YES ', '{% if a or b %} YES {% endif %}', 'a' => true, 'b' => false)
|
||||
assert_template_result(' YES ', '{% if a or b %} YES {% endif %}', 'a' => false, 'b' => true)
|
||||
assert_template_result('', '{% if a or b %} YES {% endif %}', 'a' => false, 'b' => false)
|
||||
assert_template_result('', '{% if a or b %} YES {% endif %}', 'a' => false, 'b' => false)
|
||||
|
||||
assert_template_result(' YES ', '{% if a or b or c %} YES {% endif %}', 'a' => false, 'b' => false, 'c' => true)
|
||||
assert_template_result('', '{% if a or b or c %} YES {% endif %}', 'a' => false, 'b' => false, 'c' => false)
|
||||
assert_template_result('', '{% if a or b or c %} YES {% endif %}', 'a' => false, 'b' => false, 'c' => false)
|
||||
end
|
||||
|
||||
def test_if_or_with_operators
|
||||
@@ -164,25 +164,4 @@ class IfElseTagTest < Minitest::Test
|
||||
assert_template_result('', %({% if 1 or throw or or 1 %}yes{% endif %}))
|
||||
end
|
||||
end
|
||||
|
||||
def test_multiple_conditions
|
||||
tpl = "{% if a or b and c %}true{% else %}false{% endif %}"
|
||||
|
||||
tests = {
|
||||
[true, true, true] => true,
|
||||
[true, true, false] => true,
|
||||
[true, false, true] => true,
|
||||
[true, false, false] => true,
|
||||
[false, true, true] => true,
|
||||
[false, true, false] => false,
|
||||
[false, false, true] => false,
|
||||
[false, false, false] => false
|
||||
}
|
||||
|
||||
tests.each do |vals, expected|
|
||||
a, b, c = vals
|
||||
assigns = { 'a' => a, 'b' => b, 'c' => c }
|
||||
assert_template_result expected.to_s, tpl, assigns, assigns.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -30,9 +30,6 @@ class TestFileSystem
|
||||
when 'assignments'
|
||||
"{% assign foo = 'bar' %}"
|
||||
|
||||
when 'break'
|
||||
"{% break %}"
|
||||
|
||||
else
|
||||
template_path
|
||||
end
|
||||
@@ -80,7 +77,7 @@ class IncludeTagTest < Minitest::Test
|
||||
|
||||
def test_include_tag_looks_for_file_system_in_registers_first
|
||||
assert_equal 'from OtherFileSystem',
|
||||
Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: OtherFileSystem.new })
|
||||
Template.parse("{% include 'pick_a_source' %}").render!({}, :registers => { :file_system => OtherFileSystem.new })
|
||||
end
|
||||
|
||||
def test_include_tag_with
|
||||
@@ -140,7 +137,7 @@ class IncludeTagTest < Minitest::Test
|
||||
|
||||
Liquid::Template.file_system = infinite_file_system.new
|
||||
|
||||
assert_raises(Liquid::StackLevelError) do
|
||||
assert_raises(Liquid::StackLevelError, SystemStackError) do
|
||||
Template.parse("{% include 'loop' %}").render!
|
||||
end
|
||||
end
|
||||
@@ -156,18 +153,18 @@ class IncludeTagTest < Minitest::Test
|
||||
def test_include_tag_caches_second_read_of_same_partial
|
||||
file_system = CountingFileSystem.new
|
||||
assert_equal 'from CountingFileSystemfrom CountingFileSystem',
|
||||
Template.parse("{% include 'pick_a_source' %}{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system })
|
||||
Template.parse("{% include 'pick_a_source' %}{% include 'pick_a_source' %}").render!({}, :registers => { :file_system => file_system })
|
||||
assert_equal 1, file_system.count
|
||||
end
|
||||
|
||||
def test_include_tag_doesnt_cache_partials_across_renders
|
||||
file_system = CountingFileSystem.new
|
||||
assert_equal 'from CountingFileSystem',
|
||||
Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system })
|
||||
Template.parse("{% include 'pick_a_source' %}").render!({}, :registers => { :file_system => file_system })
|
||||
assert_equal 1, file_system.count
|
||||
|
||||
assert_equal 'from CountingFileSystem',
|
||||
Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system })
|
||||
Template.parse("{% include 'pick_a_source' %}").render!({}, :registers => { :file_system => file_system })
|
||||
assert_equal 2, file_system.count
|
||||
end
|
||||
|
||||
@@ -220,17 +217,6 @@ class IncludeTagTest < Minitest::Test
|
||||
end
|
||||
end
|
||||
|
||||
def test_render_raise_argument_error_when_template_is_undefined
|
||||
assert_raises(Liquid::ArgumentError) do
|
||||
template = Liquid::Template.parse('{% include undefined_variable %}')
|
||||
template.render!
|
||||
end
|
||||
assert_raises(Liquid::ArgumentError) do
|
||||
template = Liquid::Template.parse('{% include nil %}')
|
||||
template.render!
|
||||
end
|
||||
end
|
||||
|
||||
def test_including_via_variable_value
|
||||
assert_template_result "from TestFileSystem", "{% assign page = 'pick_a_source' %}{% include page %}"
|
||||
|
||||
@@ -238,16 +224,4 @@ class IncludeTagTest < Minitest::Test
|
||||
|
||||
assert_template_result "Product: Draft 151cm ", "{% assign page = 'product' %}{% include page for foo %}", "foo" => { 'title' => 'Draft 151cm' }
|
||||
end
|
||||
|
||||
def test_including_with_strict_variables
|
||||
template = Liquid::Template.parse("{% include 'simple' %}", error_mode: :warn)
|
||||
template.render(nil, strict_variables: true)
|
||||
|
||||
assert_equal [], template.errors
|
||||
end
|
||||
|
||||
def test_break_through_include
|
||||
assert_template_result "1", "{% for i in (1..3) %}{{ i }}{% break %}{{ i }}{% endfor %}"
|
||||
assert_template_result "1", "{% for i in (1..3) %}{{ i }}{% include 'break' %}{{ i }}{% endfor %}"
|
||||
end
|
||||
end # IncludeTagTest
|
||||
|
||||
@@ -20,4 +20,5 @@ class IncrementTagTest < Minitest::Test
|
||||
'{%increment port %} {%decrement port%} ' \
|
||||
'{%decrement starboard %}', { 'port' => 1, 'starboard' => 5 })
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -22,10 +22,4 @@ class RawTagTest < Minitest::Test
|
||||
assert_template_result ' test {% raw %} {% endraw %}', '{% raw %} test {% raw %} {% {% endraw %}endraw %}'
|
||||
assert_template_result ' Foobar {{ invalid 1', '{% raw %} Foobar {{ invalid {% endraw %}{{ 1 }}'
|
||||
end
|
||||
|
||||
def test_invalid_raw
|
||||
assert_match_syntax_error(/tag was never closed/, '{% raw %} foo')
|
||||
assert_match_syntax_error(/Valid syntax/, '{% raw } foo {% endraw %}')
|
||||
assert_match_syntax_error(/Valid syntax/, '{% raw } foo %}{% endraw %}')
|
||||
end
|
||||
end
|
||||
|
||||
@@ -271,7 +271,7 @@ class StandardTagTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_size_of_hash
|
||||
assigns = { "hash" => { a: 1, b: 2, c: 3, d: 4 } }
|
||||
assigns = { "hash" => { :a => 1, :b => 2, :c => 3, :d => 4 } }
|
||||
assert_template_result('hash has 4 elements', "hash has {{ hash.size }} elements", assigns)
|
||||
end
|
||||
|
||||
|
||||
@@ -57,8 +57,4 @@ class TableRowTest < Minitest::Test
|
||||
'{% tablerow n in numbers cols:3 offset:1 limit:6%} {{n}} {% endtablerow %}',
|
||||
'numbers' => [0, 1, 2, 3, 4, 5, 6, 7])
|
||||
end
|
||||
|
||||
def test_blank_string_not_iterable
|
||||
assert_template_result("<tr class=\"row1\">\n</tr>\n", "{% tablerow char in characters cols:3 %}I WILL NOT BE OUTPUT{% endtablerow %}", 'characters' => '')
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,7 +2,7 @@ require 'test_helper'
|
||||
require 'timeout'
|
||||
|
||||
class TemplateContextDrop < Liquid::Drop
|
||||
def liquid_method_missing(method)
|
||||
def before_method(method)
|
||||
method
|
||||
end
|
||||
|
||||
@@ -27,12 +27,6 @@ class ErroneousDrop < Liquid::Drop
|
||||
end
|
||||
end
|
||||
|
||||
class DropWithUndefinedMethod < Liquid::Drop
|
||||
def foo
|
||||
'foo'
|
||||
end
|
||||
end
|
||||
|
||||
class TemplateTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
@@ -139,27 +133,6 @@ class TemplateTest < Minitest::Test
|
||||
refute_nil t.resource_limits.assign_score
|
||||
end
|
||||
|
||||
def test_resource_limits_assign_score_counts_bytes_not_characters
|
||||
t = Template.parse("{% assign foo = 'すごい' %}")
|
||||
t.render
|
||||
assert_equal 9, t.resource_limits.assign_score
|
||||
|
||||
t = Template.parse("{% capture foo %}すごい{% endcapture %}")
|
||||
t.render
|
||||
assert_equal 9, t.resource_limits.assign_score
|
||||
end
|
||||
|
||||
def test_resource_limits_assign_score_nested
|
||||
t = Template.parse("{% assign foo = 'aaaa' | reverse %}")
|
||||
|
||||
t.resource_limits.assign_score_limit = 3
|
||||
assert_equal "Liquid error: Memory limits exceeded", t.render
|
||||
assert t.resource_limits.reached?
|
||||
|
||||
t.resource_limits.assign_score_limit = 5
|
||||
assert_equal "", t.render!
|
||||
end
|
||||
|
||||
def test_resource_limits_aborts_rendering_after_first_error
|
||||
t = Template.parse("{% for a in (1..100) %} foo1 {% endfor %} bar {% for a in (1..100) %} foo2 {% endfor %}")
|
||||
t.resource_limits.render_score_limit = 50
|
||||
@@ -197,14 +170,6 @@ class TemplateTest < Minitest::Test
|
||||
assert_equal "ababab", t.render
|
||||
end
|
||||
|
||||
def test_render_length_uses_number_of_bytes_not_characters
|
||||
t = Template.parse("{% if true %}すごい{% endif %}")
|
||||
t.resource_limits.render_length_limit = 10
|
||||
assert_equal "Liquid error: Memory limits exceeded", t.render
|
||||
t.resource_limits.render_length_limit = 18
|
||||
assert_equal "すごい", t.render
|
||||
end
|
||||
|
||||
def test_default_resource_limits_unaffected_by_render_with_context
|
||||
context = Context.new
|
||||
t = Template.parse("{% for a in (1..100) %} {% assign foo = 1 %} {% endfor %}")
|
||||
@@ -233,118 +198,19 @@ class TemplateTest < Minitest::Test
|
||||
assert_equal 'ruby error in drop', e.message
|
||||
end
|
||||
|
||||
def test_exception_renderer_that_returns_string
|
||||
def test_exception_handler_doesnt_reraise_if_it_returns_false
|
||||
exception = nil
|
||||
handler = ->(e) { exception = e; '<!-- error -->' }
|
||||
|
||||
output = Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_renderer: handler)
|
||||
|
||||
assert exception.is_a?(Liquid::ZeroDivisionError)
|
||||
assert_equal '<!-- error -->', output
|
||||
Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_handler: ->(e) { exception = e; false })
|
||||
assert exception.is_a?(Liquid::FilterError)
|
||||
assert exception.original_exception.is_a?(ZeroDivisionError)
|
||||
end
|
||||
|
||||
def test_exception_renderer_that_raises
|
||||
def test_exception_handler_does_reraise_if_it_returns_true
|
||||
exception = nil
|
||||
assert_raises(Liquid::ZeroDivisionError) do
|
||||
Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_renderer: ->(e) { exception = e; raise })
|
||||
assert_raises(Liquid::FilterError) do
|
||||
Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_handler: ->(e) { exception = e; true })
|
||||
end
|
||||
assert exception.is_a?(Liquid::ZeroDivisionError)
|
||||
end
|
||||
|
||||
def test_global_filter_option_on_render
|
||||
global_filter_proc = ->(output) { "#{output} filtered" }
|
||||
rendered_template = Template.parse("{{name}}").render({ "name" => "bob" }, global_filter: global_filter_proc)
|
||||
|
||||
assert_equal 'bob filtered', rendered_template
|
||||
end
|
||||
|
||||
def test_global_filter_option_when_native_filters_exist
|
||||
global_filter_proc = ->(output) { "#{output} filtered" }
|
||||
rendered_template = Template.parse("{{name | upcase}}").render({ "name" => "bob" }, global_filter: global_filter_proc)
|
||||
|
||||
assert_equal 'BOB filtered', rendered_template
|
||||
end
|
||||
|
||||
def test_undefined_variables
|
||||
t = Template.parse("{{x}} {{y}} {{z.a}} {{z.b}} {{z.c.d}}")
|
||||
result = t.render({ 'x' => 33, 'z' => { 'a' => 32, 'c' => { 'e' => 31 } } }, { strict_variables: true })
|
||||
|
||||
assert_equal '33 32 ', result
|
||||
assert_equal 3, t.errors.count
|
||||
assert_instance_of Liquid::UndefinedVariable, t.errors[0]
|
||||
assert_equal 'Liquid error: undefined variable y', t.errors[0].message
|
||||
assert_instance_of Liquid::UndefinedVariable, t.errors[1]
|
||||
assert_equal 'Liquid error: undefined variable b', t.errors[1].message
|
||||
assert_instance_of Liquid::UndefinedVariable, t.errors[2]
|
||||
assert_equal 'Liquid error: undefined variable d', t.errors[2].message
|
||||
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
|
||||
t = Template.parse("{{x}} {{y}} {{z.a}} {{z.b}} {{z.c.d}}")
|
||||
|
||||
assert_raises UndefinedVariable do
|
||||
t.render!({ 'x' => 33, 'z' => { 'a' => 32, 'c' => { 'e' => 31 } } }, { strict_variables: true })
|
||||
end
|
||||
end
|
||||
|
||||
def test_undefined_drop_methods
|
||||
d = DropWithUndefinedMethod.new
|
||||
t = Template.new.parse('{{ foo }} {{ woot }}')
|
||||
result = t.render(d, { strict_variables: true })
|
||||
|
||||
assert_equal 'foo ', result
|
||||
assert_equal 1, t.errors.count
|
||||
assert_instance_of Liquid::UndefinedDropMethod, t.errors[0]
|
||||
end
|
||||
|
||||
def test_undefined_drop_methods_raise
|
||||
d = DropWithUndefinedMethod.new
|
||||
t = Template.new.parse('{{ foo }} {{ woot }}')
|
||||
|
||||
assert_raises UndefinedDropMethod do
|
||||
t.render!(d, { strict_variables: true })
|
||||
end
|
||||
end
|
||||
|
||||
def test_undefined_filters
|
||||
t = Template.parse("{{a}} {{x | upcase | somefilter1 | somefilter2 | somefilter3}}")
|
||||
filters = Module.new do
|
||||
def somefilter3(v)
|
||||
"-#{v}-"
|
||||
end
|
||||
end
|
||||
result = t.render({ 'a' => 123, 'x' => 'foo' }, { filters: [filters], strict_filters: true })
|
||||
|
||||
assert_equal '123 ', result
|
||||
assert_equal 1, t.errors.count
|
||||
assert_instance_of Liquid::UndefinedFilter, t.errors[0]
|
||||
assert_equal 'Liquid error: undefined filter somefilter1', t.errors[0].message
|
||||
end
|
||||
|
||||
def test_undefined_filters_raise
|
||||
t = Template.parse("{{x | somefilter1 | upcase | somefilter2}}")
|
||||
|
||||
assert_raises UndefinedFilter do
|
||||
t.render!({ 'x' => 'foo' }, { strict_filters: true })
|
||||
end
|
||||
end
|
||||
|
||||
def test_using_range_literal_works_as_expected
|
||||
t = Template.parse("{% assign foo = (x..y) %}{{ foo }}")
|
||||
result = t.render({ 'x' => 1, 'y' => 5 })
|
||||
assert_equal '1..5', result
|
||||
|
||||
t = Template.parse("{% assign nums = (x..y) %}{% for num in nums %}{{ num }}{% endfor %}")
|
||||
result = t.render({ 'x' => 1, 'y' => 5 })
|
||||
assert_equal '12345', result
|
||||
assert exception.is_a?(Liquid::FilterError)
|
||||
assert exception.original_exception.is_a?(ZeroDivisionError)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,529 +0,0 @@
|
||||
require 'test_helper'
|
||||
|
||||
class TrimModeTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
# Make sure the trim isn't applied to standard output
|
||||
def test_standard_output
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{{ 'John' }}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>
|
||||
John
|
||||
</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_variable_output_with_multiple_blank_lines
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
|
||||
|
||||
{{- 'John' -}}
|
||||
|
||||
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>John</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_tag_output_with_multiple_blank_lines
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
|
||||
|
||||
{%- if true -%}
|
||||
yes
|
||||
{%- endif -%}
|
||||
|
||||
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>yes</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
# Make sure the trim isn't applied to standard tags
|
||||
def test_standard_tags
|
||||
whitespace = ' '
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{% if true %}
|
||||
yes
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>
|
||||
#{whitespace}
|
||||
yes
|
||||
#{whitespace}
|
||||
</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{% if false %}
|
||||
no
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>
|
||||
#{whitespace}
|
||||
</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
# Make sure the trim isn't too agressive
|
||||
def test_no_trim_output
|
||||
text = '<p>{{- \'John\' -}}</p>'
|
||||
expected = '<p>John</p>'
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
# Make sure the trim isn't too agressive
|
||||
def test_no_trim_tags
|
||||
text = '<p>{%- if true -%}yes{%- endif -%}</p>'
|
||||
expected = '<p>yes</p>'
|
||||
assert_template_result(expected, text)
|
||||
|
||||
text = '<p>{%- if false -%}no{%- endif -%}</p>'
|
||||
expected = '<p></p>'
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_single_line_outer_tag
|
||||
text = '<p> {%- if true %} yes {% endif -%} </p>'
|
||||
expected = '<p> yes </p>'
|
||||
assert_template_result(expected, text)
|
||||
|
||||
text = '<p> {%- if false %} no {% endif -%} </p>'
|
||||
expected = '<p></p>'
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_single_line_inner_tag
|
||||
text = '<p> {% if true -%} yes {%- endif %} </p>'
|
||||
expected = '<p> yes </p>'
|
||||
assert_template_result(expected, text)
|
||||
|
||||
text = '<p> {% if false -%} no {%- endif %} </p>'
|
||||
expected = '<p> </p>'
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_single_line_post_tag
|
||||
text = '<p> {% if true -%} yes {% endif -%} </p>'
|
||||
expected = '<p> yes </p>'
|
||||
assert_template_result(expected, text)
|
||||
|
||||
text = '<p> {% if false -%} no {% endif -%} </p>'
|
||||
expected = '<p> </p>'
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_single_line_pre_tag
|
||||
text = '<p> {%- if true %} yes {%- endif %} </p>'
|
||||
expected = '<p> yes </p>'
|
||||
assert_template_result(expected, text)
|
||||
|
||||
text = '<p> {%- if false %} no {%- endif %} </p>'
|
||||
expected = '<p> </p>'
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_pre_trim_output
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{{- 'John' }}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>John
|
||||
</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_pre_trim_tags
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{%- if true %}
|
||||
yes
|
||||
{%- endif %}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>
|
||||
yes
|
||||
</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{%- if false %}
|
||||
no
|
||||
{%- endif %}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>
|
||||
</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_post_trim_output
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{{ 'John' -}}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>
|
||||
John</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_post_trim_tags
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{% if true -%}
|
||||
yes
|
||||
{% endif -%}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>
|
||||
yes
|
||||
</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{% if false -%}
|
||||
no
|
||||
{% endif -%}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>
|
||||
</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_pre_and_post_trim_tags
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{%- if true %}
|
||||
yes
|
||||
{% endif -%}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>
|
||||
yes
|
||||
</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{%- if false %}
|
||||
no
|
||||
{% endif -%}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p></p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_post_and_pre_trim_tags
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{% if true -%}
|
||||
yes
|
||||
{%- endif %}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>
|
||||
yes
|
||||
</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
|
||||
whitespace = ' '
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{% if false -%}
|
||||
no
|
||||
{%- endif %}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>
|
||||
#{whitespace}
|
||||
</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_trim_output
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{{- 'John' -}}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>John</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_trim_tags
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{%- if true -%}
|
||||
yes
|
||||
{%- endif -%}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>yes</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{%- if false -%}
|
||||
no
|
||||
{%- endif -%}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p></p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_whitespace_trim_output
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{{- 'John' -}},
|
||||
{{- '30' -}}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>John,30</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_whitespace_trim_tags
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{%- if true -%}
|
||||
yes
|
||||
{%- endif -%}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>yes</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{%- if false -%}
|
||||
no
|
||||
{%- endif -%}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p></p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_complex_trim_output
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{{- 'John' -}}
|
||||
{{- '30' -}}
|
||||
</p>
|
||||
<b>
|
||||
{{ 'John' -}}
|
||||
{{- '30' }}
|
||||
</b>
|
||||
<i>
|
||||
{{- 'John' }}
|
||||
{{ '30' -}}
|
||||
</i>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>John30</p>
|
||||
<b>
|
||||
John30
|
||||
</b>
|
||||
<i>John
|
||||
30</i>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_complex_trim
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
{%- if true -%}
|
||||
{%- if true -%}
|
||||
<p>
|
||||
{{- 'John' -}}
|
||||
</p>
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div><p>John</p></div>
|
||||
END_EXPECTED
|
||||
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
|
||||
<div>
|
||||
{% raw %}
|
||||
{%- if true -%}
|
||||
<p>
|
||||
{{- 'John' -}}
|
||||
</p>
|
||||
{%- endif -%}
|
||||
{% endraw %}
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
#{whitespace}
|
||||
{%- if true -%}
|
||||
<p>
|
||||
{{- 'John' -}}
|
||||
</p>
|
||||
{%- endif -%}
|
||||
#{whitespace}
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
end # TrimModeTest
|
||||
@@ -24,16 +24,6 @@ class VariableTest < Minitest::Test
|
||||
assert_equal '', template.render!
|
||||
end
|
||||
|
||||
def test_using_blank_as_variable_name
|
||||
template = Template.parse("{% assign foo = blank %}{{ foo }}")
|
||||
assert_equal '', template.render!
|
||||
end
|
||||
|
||||
def test_using_empty_as_variable_name
|
||||
template = Template.parse("{% assign foo = empty %}{{ foo }}")
|
||||
assert_equal '', template.render!
|
||||
end
|
||||
|
||||
def test_hash_scoping
|
||||
template = Template.parse(%({{ test.test }}))
|
||||
assert_equal 'worked', template.render!('test' => { 'test' => 'worked' })
|
||||
@@ -80,17 +70,13 @@ class VariableTest < Minitest::Test
|
||||
assigns['test'] = 'Tobi'
|
||||
assert_equal 'Hello Tobi', template.render!(assigns)
|
||||
assigns.delete('test')
|
||||
e = assert_raises(RuntimeError) do
|
||||
e = assert_raises(RuntimeError) {
|
||||
template.render!(assigns)
|
||||
end
|
||||
}
|
||||
assert_equal "Unknown variable 'test'", e.message
|
||||
end
|
||||
|
||||
def test_multiline_variable
|
||||
assert_equal 'worked', Template.parse("{{\ntest\n}}").render!('test' => 'worked')
|
||||
end
|
||||
|
||||
def test_render_symbol
|
||||
assert_template_result 'bar', '{{ foo }}', 'foo' => :bar
|
||||
end
|
||||
end
|
||||
|
||||
23
test/test_helper.rb
Executable file → Normal file
23
test/test_helper.rb
Executable file → Normal file
@@ -2,8 +2,9 @@
|
||||
|
||||
ENV["MT_NO_EXPECTATIONS"] = "1"
|
||||
require 'minitest/autorun'
|
||||
require 'spy/integration'
|
||||
|
||||
$LOAD_PATH.unshift(File.join(File.expand_path(__dir__), '..', 'lib'))
|
||||
$:.unshift(File.join(File.expand_path(__dir__), '..', 'lib'))
|
||||
require 'liquid.rb'
|
||||
require 'liquid/profiler'
|
||||
|
||||
@@ -14,11 +15,6 @@ if env_mode = ENV['LIQUID_PARSER_MODE']
|
||||
end
|
||||
Liquid::Template.error_mode = mode
|
||||
|
||||
if ENV['LIQUID-C'] == '1'
|
||||
puts "-- LIQUID C"
|
||||
require 'liquid/c'
|
||||
end
|
||||
|
||||
if Minitest.const_defined?('Test')
|
||||
# We're on Minitest 5+. Nothing to do here.
|
||||
else
|
||||
@@ -37,19 +33,19 @@ module Minitest
|
||||
include Liquid
|
||||
|
||||
def assert_template_result(expected, template, assigns = {}, message = nil)
|
||||
assert_equal expected, Template.parse(template).render!(assigns), message
|
||||
assert_equal expected, Template.parse(template).render!(assigns)
|
||||
end
|
||||
|
||||
def assert_template_result_matches(expected, template, assigns = {}, message = nil)
|
||||
return assert_template_result(expected, template, assigns, message) unless expected.is_a? Regexp
|
||||
|
||||
assert_match expected, Template.parse(template).render!(assigns), message
|
||||
assert_match expected, Template.parse(template).render!(assigns)
|
||||
end
|
||||
|
||||
def assert_match_syntax_error(match, template, assigns = {})
|
||||
exception = assert_raises(Liquid::SyntaxError) do
|
||||
def assert_match_syntax_error(match, template, registers = {})
|
||||
exception = assert_raises(Liquid::SyntaxError) {
|
||||
Template.parse(template).render(assigns)
|
||||
end
|
||||
}
|
||||
assert_match match, exception.message
|
||||
end
|
||||
|
||||
@@ -106,11 +102,8 @@ class ErrorDrop < Liquid::Drop
|
||||
raise Liquid::SyntaxError, 'syntax error'
|
||||
end
|
||||
|
||||
def runtime_error
|
||||
raise 'runtime error'
|
||||
end
|
||||
|
||||
def exception
|
||||
raise Exception, 'exception'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -46,8 +46,6 @@ class BlockUnitTest < Minitest::Test
|
||||
def test_with_custom_tag
|
||||
Liquid::Template.register_tag("testtag", Block)
|
||||
assert Liquid::Template.parse("{% testtag %} {% endtesttag %}")
|
||||
ensure
|
||||
Liquid::Template.tags.delete('testtag')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -3,54 +3,50 @@ require 'test_helper'
|
||||
class ConditionUnitTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def setup
|
||||
@context = Liquid::Context.new
|
||||
end
|
||||
|
||||
def test_basic_condition
|
||||
assert_equal false, Condition.new(1, '==', 2).evaluate
|
||||
assert_equal true, Condition.new(1, '==', 1).evaluate
|
||||
end
|
||||
|
||||
def test_default_operators_evalute_true
|
||||
assert_evaluates_true 1, '==', 1
|
||||
assert_evaluates_true 1, '!=', 2
|
||||
assert_evaluates_true 1, '<>', 2
|
||||
assert_evaluates_true 1, '<', 2
|
||||
assert_evaluates_true 2, '>', 1
|
||||
assert_evaluates_true 1, '>=', 1
|
||||
assert_evaluates_true 2, '>=', 1
|
||||
assert_evaluates_true 1, '<=', 2
|
||||
assert_evaluates_true 1, '<=', 1
|
||||
assert_evalutes_true 1, '==', 1
|
||||
assert_evalutes_true 1, '!=', 2
|
||||
assert_evalutes_true 1, '<>', 2
|
||||
assert_evalutes_true 1, '<', 2
|
||||
assert_evalutes_true 2, '>', 1
|
||||
assert_evalutes_true 1, '>=', 1
|
||||
assert_evalutes_true 2, '>=', 1
|
||||
assert_evalutes_true 1, '<=', 2
|
||||
assert_evalutes_true 1, '<=', 1
|
||||
# negative numbers
|
||||
assert_evaluates_true 1, '>', -1
|
||||
assert_evaluates_true -1, '<', 1
|
||||
assert_evaluates_true 1.0, '>', -1.0
|
||||
assert_evaluates_true -1.0, '<', 1.0
|
||||
assert_evalutes_true 1, '>', -1
|
||||
assert_evalutes_true -1, '<', 1
|
||||
assert_evalutes_true 1.0, '>', -1.0
|
||||
assert_evalutes_true -1.0, '<', 1.0
|
||||
end
|
||||
|
||||
def test_default_operators_evalute_false
|
||||
assert_evaluates_false 1, '==', 2
|
||||
assert_evaluates_false 1, '!=', 1
|
||||
assert_evaluates_false 1, '<>', 1
|
||||
assert_evaluates_false 1, '<', 0
|
||||
assert_evaluates_false 2, '>', 4
|
||||
assert_evaluates_false 1, '>=', 3
|
||||
assert_evaluates_false 2, '>=', 4
|
||||
assert_evaluates_false 1, '<=', 0
|
||||
assert_evaluates_false 1, '<=', 0
|
||||
assert_evalutes_false 1, '==', 2
|
||||
assert_evalutes_false 1, '!=', 1
|
||||
assert_evalutes_false 1, '<>', 1
|
||||
assert_evalutes_false 1, '<', 0
|
||||
assert_evalutes_false 2, '>', 4
|
||||
assert_evalutes_false 1, '>=', 3
|
||||
assert_evalutes_false 2, '>=', 4
|
||||
assert_evalutes_false 1, '<=', 0
|
||||
assert_evalutes_false 1, '<=', 0
|
||||
end
|
||||
|
||||
def test_contains_works_on_strings
|
||||
assert_evaluates_true 'bob', 'contains', 'o'
|
||||
assert_evaluates_true 'bob', 'contains', 'b'
|
||||
assert_evaluates_true 'bob', 'contains', 'bo'
|
||||
assert_evaluates_true 'bob', 'contains', 'ob'
|
||||
assert_evaluates_true 'bob', 'contains', 'bob'
|
||||
assert_evalutes_true 'bob', 'contains', 'o'
|
||||
assert_evalutes_true 'bob', 'contains', 'b'
|
||||
assert_evalutes_true 'bob', 'contains', 'bo'
|
||||
assert_evalutes_true 'bob', 'contains', 'ob'
|
||||
assert_evalutes_true 'bob', 'contains', 'bob'
|
||||
|
||||
assert_evaluates_false 'bob', 'contains', 'bob2'
|
||||
assert_evaluates_false 'bob', 'contains', 'a'
|
||||
assert_evaluates_false 'bob', 'contains', '---'
|
||||
assert_evalutes_false 'bob', 'contains', 'bob2'
|
||||
assert_evalutes_false 'bob', 'contains', 'a'
|
||||
assert_evalutes_false 'bob', 'contains', '---'
|
||||
end
|
||||
|
||||
def test_invalid_comparation_operator
|
||||
@@ -64,42 +60,29 @@ class ConditionUnitTest < Minitest::Test
|
||||
assert_evaluates_argument_error '1', '<=', 0
|
||||
end
|
||||
|
||||
def test_hash_compare_backwards_compatibility
|
||||
assert_nil Condition.new({}, '>', 2).evaluate
|
||||
assert_nil 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' => 2 }, 'contains', 'a').evaluate
|
||||
end
|
||||
|
||||
def test_contains_works_on_arrays
|
||||
@context = Liquid::Context.new
|
||||
@context['array'] = [1, 2, 3, 4, 5]
|
||||
array_expr = VariableLookup.new("array")
|
||||
|
||||
assert_evaluates_false array_expr, 'contains', 0
|
||||
assert_evaluates_true array_expr, 'contains', 1
|
||||
assert_evaluates_true array_expr, 'contains', 2
|
||||
assert_evaluates_true array_expr, 'contains', 3
|
||||
assert_evaluates_true array_expr, 'contains', 4
|
||||
assert_evaluates_true array_expr, 'contains', 5
|
||||
assert_evaluates_false array_expr, 'contains', 6
|
||||
assert_evaluates_false array_expr, 'contains', "1"
|
||||
assert_evalutes_false array_expr, 'contains', 0
|
||||
assert_evalutes_true array_expr, 'contains', 1
|
||||
assert_evalutes_true array_expr, 'contains', 2
|
||||
assert_evalutes_true array_expr, 'contains', 3
|
||||
assert_evalutes_true array_expr, 'contains', 4
|
||||
assert_evalutes_true array_expr, 'contains', 5
|
||||
assert_evalutes_false array_expr, 'contains', 6
|
||||
assert_evalutes_false array_expr, 'contains', "1"
|
||||
end
|
||||
|
||||
def test_contains_returns_false_for_nil_operands
|
||||
@context = Liquid::Context.new
|
||||
assert_evaluates_false VariableLookup.new('not_assigned'), 'contains', '0'
|
||||
assert_evaluates_false 0, 'contains', VariableLookup.new('not_assigned')
|
||||
assert_evalutes_false VariableLookup.new('not_assigned'), 'contains', '0'
|
||||
assert_evalutes_false 0, 'contains', VariableLookup.new('not_assigned')
|
||||
end
|
||||
|
||||
def test_contains_return_false_on_wrong_data_type
|
||||
assert_evaluates_false 1, 'contains', 0
|
||||
end
|
||||
|
||||
def test_contains_with_string_left_operand_coerces_right_operand_to_string
|
||||
assert_evaluates_true ' 1 ', 'contains', 1
|
||||
assert_evaluates_false ' 1 ', 'contains', 2
|
||||
assert_evalutes_false 1, 'contains', 0
|
||||
end
|
||||
|
||||
def test_or_condition
|
||||
@@ -133,8 +116,8 @@ class ConditionUnitTest < Minitest::Test
|
||||
def test_should_allow_custom_proc_operator
|
||||
Condition.operators['starts_with'] = proc { |cond, left, right| left =~ %r{^#{right}} }
|
||||
|
||||
assert_evaluates_true 'bob', 'starts_with', 'b'
|
||||
assert_evaluates_false 'bob', 'starts_with', 'o'
|
||||
assert_evalutes_true 'bob', 'starts_with', 'b'
|
||||
assert_evalutes_false 'bob', 'starts_with', 'o'
|
||||
ensure
|
||||
Condition.operators.delete 'starts_with'
|
||||
end
|
||||
@@ -143,24 +126,25 @@ class ConditionUnitTest < Minitest::Test
|
||||
@context = Liquid::Context.new
|
||||
@context['one'] = @context['another'] = "gnomeslab-and-or-liquid"
|
||||
|
||||
assert_evaluates_true VariableLookup.new("one"), '==', VariableLookup.new("another")
|
||||
assert_evalutes_true VariableLookup.new("one"), '==', VariableLookup.new("another")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assert_evaluates_true(left, op, right)
|
||||
assert Condition.new(left, op, right).evaluate(@context),
|
||||
def assert_evalutes_true(left, op, right)
|
||||
assert Condition.new(left, op, right).evaluate(@context || Liquid::Context.new),
|
||||
"Evaluated false: #{left} #{op} #{right}"
|
||||
end
|
||||
|
||||
def assert_evaluates_false(left, op, right)
|
||||
assert !Condition.new(left, op, right).evaluate(@context),
|
||||
def assert_evalutes_false(left, op, right)
|
||||
assert !Condition.new(left, op, right).evaluate(@context || Liquid::Context.new),
|
||||
"Evaluated true: #{left} #{op} #{right}"
|
||||
end
|
||||
|
||||
def assert_evaluates_argument_error(left, op, right)
|
||||
assert_raises(Liquid::ArgumentError) do
|
||||
Condition.new(left, op, right).evaluate(@context)
|
||||
Condition.new(left, op, right).evaluate(@context || Liquid::Context.new)
|
||||
end
|
||||
end
|
||||
|
||||
end # ConditionTest
|
||||
|
||||
@@ -70,6 +70,10 @@ class ContextUnitTest < Minitest::Test
|
||||
@context = Liquid::Context.new
|
||||
end
|
||||
|
||||
def teardown
|
||||
Spy.teardown
|
||||
end
|
||||
|
||||
def test_variables
|
||||
@context['string'] = 'string'
|
||||
assert_equal 'string', @context['string']
|
||||
@@ -94,12 +98,27 @@ class ContextUnitTest < Minitest::Test
|
||||
assert_equal false, @context['bool']
|
||||
|
||||
@context['nil'] = nil
|
||||
assert_nil @context['nil']
|
||||
assert_nil @context['nil']
|
||||
assert_equal nil, @context['nil']
|
||||
assert_equal nil, @context['nil']
|
||||
end
|
||||
|
||||
def test_variables_not_existing
|
||||
assert_nil @context['does_not_exist']
|
||||
assert_equal nil, @context['does_not_exist']
|
||||
end
|
||||
|
||||
def test_scoping
|
||||
@context.push
|
||||
@context.pop
|
||||
|
||||
assert_raises(Liquid::ContextError) do
|
||||
@context.pop
|
||||
end
|
||||
|
||||
assert_raises(Liquid::ContextError) do
|
||||
@context.push
|
||||
@context.pop
|
||||
@context.pop
|
||||
end
|
||||
end
|
||||
|
||||
def test_length_query
|
||||
@@ -155,12 +174,18 @@ class ContextUnitTest < Minitest::Test
|
||||
|
||||
def test_add_item_in_outer_scope
|
||||
@context['test'] = 'test'
|
||||
|
||||
@context.stack('test') do
|
||||
assert_equal 'test', @context['test']
|
||||
end
|
||||
|
||||
@context.push
|
||||
assert_equal 'test', @context['test']
|
||||
@context.pop
|
||||
assert_equal 'test', @context['test']
|
||||
end
|
||||
|
||||
def test_add_item_in_inner_scope
|
||||
@context.push
|
||||
@context['test'] = 'test'
|
||||
assert_equal 'test', @context['test']
|
||||
@context.pop
|
||||
assert_equal nil, @context['test']
|
||||
end
|
||||
|
||||
def test_hierachical_data
|
||||
@@ -242,7 +267,7 @@ class ContextUnitTest < Minitest::Test
|
||||
|
||||
def test_access_hashes_with_hash_notation
|
||||
@context['products'] = { 'count' => 5, 'tags' => ['deepsnow', 'freestyle'] }
|
||||
@context['product'] = { 'variants' => [ { 'title' => 'draft151cm' }, { 'title' => 'element151cm' } ] }
|
||||
@context['product'] = { 'variants' => [ { 'title' => 'draft151cm' }, { 'title' => 'element151cm' } ] }
|
||||
|
||||
assert_equal 5, @context['products["count"]']
|
||||
assert_equal 'deepsnow', @context['products["tags"][0]']
|
||||
@@ -275,12 +300,12 @@ class ContextUnitTest < Minitest::Test
|
||||
@context['hash'] = { 'first' => 'Hello' }
|
||||
|
||||
assert_equal 1, @context['array.first']
|
||||
assert_nil @context['array["first"]']
|
||||
assert_equal nil, @context['array["first"]']
|
||||
assert_equal 'Hello', @context['hash["first"]']
|
||||
end
|
||||
|
||||
def test_first_can_appear_in_middle_of_callchain
|
||||
@context['product'] = { 'variants' => [ { 'title' => 'draft151cm' }, { 'title' => 'element151cm' } ] }
|
||||
@context['product'] = { 'variants' => [ { 'title' => 'draft151cm' }, { 'title' => 'element151cm' } ] }
|
||||
|
||||
assert_equal 'draft151cm', @context['product.variants[0].title']
|
||||
assert_equal 'element151cm', @context['product.variants[1].title']
|
||||
@@ -425,44 +450,22 @@ class ContextUnitTest < Minitest::Test
|
||||
assert_equal @context, @context['category'].context
|
||||
end
|
||||
|
||||
def test_interrupt_avoids_object_allocations
|
||||
assert_no_object_allocations do
|
||||
@context.interrupt?
|
||||
end
|
||||
def test_use_empty_instead_of_any_in_interrupt_handling_to_avoid_lots_of_unnecessary_object_allocations
|
||||
mock_any = Spy.on_instance_method(Array, :any?)
|
||||
mock_empty = Spy.on_instance_method(Array, :empty?)
|
||||
mock_has_interrupt = Spy.on(@context, :has_interrupt?).and_call_through
|
||||
|
||||
@context.has_interrupt?
|
||||
|
||||
refute mock_any.has_been_called?
|
||||
assert mock_empty.has_been_called?
|
||||
end
|
||||
|
||||
def test_context_initialization_with_a_proc_in_environment
|
||||
contx = Context.new([test: ->(c) { c['poutine'] }], { test: :foo })
|
||||
contx = Context.new([:test => ->(c) { c['poutine'] }], { :test => :foo })
|
||||
|
||||
assert contx
|
||||
assert_nil contx['poutine']
|
||||
end
|
||||
|
||||
def test_apply_global_filter
|
||||
global_filter_proc = ->(output) { "#{output} filtered" }
|
||||
|
||||
context = Context.new
|
||||
context.global_filter = global_filter_proc
|
||||
|
||||
assert_equal 'hi filtered', context.apply_global_filter('hi')
|
||||
end
|
||||
|
||||
def test_apply_global_filter_when_no_global_filter_exist
|
||||
context = Context.new
|
||||
assert_equal 'hi', context.apply_global_filter('hi')
|
||||
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
|
||||
|
||||
@@ -16,7 +16,7 @@ class I18nUnitTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_single_string_interpolation
|
||||
assert_equal "something different", @i18n.translate("whatever", something: "different")
|
||||
assert_equal "something different", @i18n.translate("whatever", :something => "different")
|
||||
end
|
||||
|
||||
# def test_raises_translation_error_on_undefined_interpolation_key
|
||||
|
||||
@@ -19,7 +19,7 @@ class LexerUnitTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_comparison
|
||||
tokens = Lexer.new('== <> contains ').tokenize
|
||||
tokens = Lexer.new('== <> contains').tokenize
|
||||
assert_equal [[:comparison, '=='], [:comparison, '<>'], [:comparison, 'contains'], [:end_of_string]], tokens
|
||||
end
|
||||
|
||||
|
||||
@@ -29,18 +29,6 @@ class StrainerUnitTest < Minitest::Test
|
||||
end
|
||||
end
|
||||
|
||||
def test_stainer_argument_error_contains_backtrace
|
||||
strainer = Strainer.create(nil)
|
||||
begin
|
||||
strainer.invoke("public_filter", 1)
|
||||
rescue Liquid::ArgumentError => e
|
||||
assert_match(
|
||||
/\ALiquid error: wrong number of arguments \((1 for 0|given 1, expected 0)\)\z/,
|
||||
e.message)
|
||||
assert_equal e.backtrace[0].split(':')[0], __FILE__
|
||||
end
|
||||
end
|
||||
|
||||
def test_strainer_only_invokes_public_filter_methods
|
||||
strainer = Strainer.create(nil)
|
||||
assert_equal false, strainer.class.invokable?('__test__')
|
||||
@@ -78,87 +66,4 @@ class StrainerUnitTest < Minitest::Test
|
||||
assert_kind_of Liquid::StandardFilters, strainer
|
||||
end
|
||||
|
||||
def test_add_filter_when_wrong_filter_class
|
||||
c = Context.new
|
||||
s = c.strainer
|
||||
wrong_filter = ->(v) { v.reverse }
|
||||
|
||||
assert_raises ArgumentError do
|
||||
s.class.add_filter(wrong_filter)
|
||||
end
|
||||
end
|
||||
|
||||
module PrivateMethodOverrideFilter
|
||||
private
|
||||
|
||||
def public_filter
|
||||
"overriden as private"
|
||||
end
|
||||
end
|
||||
|
||||
def test_add_filter_raises_when_module_privately_overrides_registered_public_methods
|
||||
strainer = Context.new.strainer
|
||||
|
||||
error = assert_raises(Liquid::MethodOverrideError) do
|
||||
strainer.class.add_filter(PrivateMethodOverrideFilter)
|
||||
end
|
||||
assert_equal 'Liquid error: Filter overrides registered public methods as non public: public_filter', error.message
|
||||
end
|
||||
|
||||
module ProtectedMethodOverrideFilter
|
||||
protected
|
||||
|
||||
def public_filter
|
||||
"overriden as protected"
|
||||
end
|
||||
end
|
||||
|
||||
def test_add_filter_raises_when_module_overrides_registered_public_method_as_protected
|
||||
strainer = Context.new.strainer
|
||||
|
||||
error = assert_raises(Liquid::MethodOverrideError) do
|
||||
strainer.class.add_filter(ProtectedMethodOverrideFilter)
|
||||
end
|
||||
assert_equal 'Liquid error: Filter overrides registered public methods as non public: public_filter', error.message
|
||||
end
|
||||
|
||||
module PublicMethodOverrideFilter
|
||||
def public_filter
|
||||
"public"
|
||||
end
|
||||
end
|
||||
|
||||
def test_add_filter_does_not_raise_when_module_overrides_previously_registered_method
|
||||
strainer = Context.new.strainer
|
||||
strainer.class.add_filter(PublicMethodOverrideFilter)
|
||||
assert strainer.class.filter_methods.include?('public_filter')
|
||||
end
|
||||
|
||||
module LateAddedFilter
|
||||
def late_added_filter(input)
|
||||
"filtered"
|
||||
end
|
||||
end
|
||||
|
||||
def test_global_filter_clears_cache
|
||||
assert_equal 'input', Strainer.create(nil).invoke('late_added_filter', 'input')
|
||||
Strainer.global_filter(LateAddedFilter)
|
||||
assert_equal 'filtered', Strainer.create(nil).invoke('late_added_filter', 'input')
|
||||
end
|
||||
|
||||
def test_add_filter_does_not_include_already_included_module
|
||||
mod = Module.new do
|
||||
class << self
|
||||
attr_accessor :include_count
|
||||
def included(mod)
|
||||
self.include_count += 1
|
||||
end
|
||||
end
|
||||
self.include_count = 0
|
||||
end
|
||||
strainer = Context.new.strainer
|
||||
strainer.class.add_filter(mod)
|
||||
strainer.class.add_filter(mod)
|
||||
assert_equal 1, mod.include_count
|
||||
end
|
||||
end # StrainerTest
|
||||
|
||||
@@ -4,18 +4,13 @@ class TagUnitTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_tag
|
||||
tag = Tag.parse('tag', "", Tokenizer.new(""), ParseContext.new)
|
||||
tag = Tag.parse('tag', [], [], {})
|
||||
assert_equal 'liquid::tag', tag.name
|
||||
assert_equal '', tag.render(Context.new)
|
||||
end
|
||||
|
||||
def test_return_raw_text_of_tag
|
||||
tag = Tag.parse("long_tag", "param1, param2, param3", Tokenizer.new(""), ParseContext.new)
|
||||
tag = Tag.parse("long_tag", "param1, param2, param3", [], {})
|
||||
assert_equal("long_tag param1, param2, param3", tag.raw)
|
||||
end
|
||||
|
||||
def test_tag_name_should_return_name_of_the_tag
|
||||
tag = Tag.parse("some_tag", "", Tokenizer.new(""), ParseContext.new)
|
||||
assert_equal 'some_tag', tag.tag_name
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,7 +11,7 @@ class TemplateUnitTest < Minitest::Test
|
||||
|
||||
def test_sets_default_localization_in_context_with_quick_initialization
|
||||
t = Template.new
|
||||
t.parse('{%comment%}{%endcomment%}', locale: I18n.new(fixture("en_locale.yml")))
|
||||
t.parse('{%comment%}{%endcomment%}', :locale => I18n.new(fixture("en_locale.yml")))
|
||||
|
||||
locale = t.root.nodelist[0].options[:locale]
|
||||
assert_instance_of I18n, locale
|
||||
@@ -67,12 +67,4 @@ class TemplateUnitTest < Minitest::Test
|
||||
Template.tags.delete('fake')
|
||||
assert_nil Template.tags['fake']
|
||||
end
|
||||
|
||||
def test_tags_can_be_looped_over
|
||||
Template.register_tag('fake', FakeTag)
|
||||
result = Template.tags.map { |name, klass| [name, klass] }
|
||||
assert result.include?(["fake", "TemplateUnitTest::FakeTag"])
|
||||
ensure
|
||||
Template.tags.delete('fake')
|
||||
end
|
||||
end
|
||||
|
||||
@@ -22,34 +22,17 @@ class TokenizerTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_calculate_line_numbers_per_token_with_profiling
|
||||
assert_equal [1], tokenize_line_numbers("{{funk}}")
|
||||
assert_equal [1, 1, 1], tokenize_line_numbers(" {{funk}} ")
|
||||
assert_equal [1, 2, 2], tokenize_line_numbers("\n{{funk}}\n")
|
||||
assert_equal [1, 1, 3], tokenize_line_numbers(" {{\n funk \n}} ")
|
||||
template = Liquid::Template.parse("", :profile => true)
|
||||
|
||||
assert_equal [1], template.send(:tokenize, "{{funk}}").map(&:line_number)
|
||||
assert_equal [1, 1, 1], template.send(:tokenize, " {{funk}} ").map(&:line_number)
|
||||
assert_equal [1, 2, 2], template.send(:tokenize, "\n{{funk}}\n").map(&:line_number)
|
||||
assert_equal [1, 1, 3], template.send(:tokenize, " {{\n funk \n}} ").map(&:line_number)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def tokenize(source)
|
||||
tokenizer = Liquid::Tokenizer.new(source)
|
||||
tokens = []
|
||||
while t = tokenizer.shift
|
||||
tokens << t
|
||||
end
|
||||
tokens
|
||||
end
|
||||
|
||||
def tokenize_line_numbers(source)
|
||||
tokenizer = Liquid::Tokenizer.new(source, true)
|
||||
line_numbers = []
|
||||
loop do
|
||||
line_number = tokenizer.line_number
|
||||
if tokenizer.shift
|
||||
line_numbers << line_number
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
line_numbers
|
||||
Liquid::Template.new.send(:tokenize, source)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,133 +4,133 @@ class VariableUnitTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_variable
|
||||
var = create_variable('hello')
|
||||
var = Variable.new('hello')
|
||||
assert_equal VariableLookup.new('hello'), var.name
|
||||
end
|
||||
|
||||
def test_filters
|
||||
var = create_variable('hello | textileze')
|
||||
var = Variable.new('hello | textileze')
|
||||
assert_equal VariableLookup.new('hello'), var.name
|
||||
assert_equal [['textileze', []]], var.filters
|
||||
|
||||
var = create_variable('hello | textileze | paragraph')
|
||||
var = Variable.new('hello | textileze | paragraph')
|
||||
assert_equal VariableLookup.new('hello'), var.name
|
||||
assert_equal [['textileze', []], ['paragraph', []]], var.filters
|
||||
|
||||
var = create_variable(%( hello | strftime: '%Y'))
|
||||
var = Variable.new(%( hello | strftime: '%Y'))
|
||||
assert_equal VariableLookup.new('hello'), var.name
|
||||
assert_equal [['strftime', ['%Y']]], var.filters
|
||||
|
||||
var = create_variable(%( 'typo' | link_to: 'Typo', true ))
|
||||
var = Variable.new(%( 'typo' | link_to: 'Typo', true ))
|
||||
assert_equal 'typo', var.name
|
||||
assert_equal [['link_to', ['Typo', true]]], var.filters
|
||||
|
||||
var = create_variable(%( 'typo' | link_to: 'Typo', false ))
|
||||
var = Variable.new(%( 'typo' | link_to: 'Typo', false ))
|
||||
assert_equal 'typo', var.name
|
||||
assert_equal [['link_to', ['Typo', false]]], var.filters
|
||||
|
||||
var = create_variable(%( 'foo' | repeat: 3 ))
|
||||
var = Variable.new(%( 'foo' | repeat: 3 ))
|
||||
assert_equal 'foo', var.name
|
||||
assert_equal [['repeat', [3]]], var.filters
|
||||
|
||||
var = create_variable(%( 'foo' | repeat: 3, 3 ))
|
||||
var = Variable.new(%( 'foo' | repeat: 3, 3 ))
|
||||
assert_equal 'foo', var.name
|
||||
assert_equal [['repeat', [3, 3]]], var.filters
|
||||
|
||||
var = create_variable(%( 'foo' | repeat: 3, 3, 3 ))
|
||||
var = Variable.new(%( 'foo' | repeat: 3, 3, 3 ))
|
||||
assert_equal 'foo', var.name
|
||||
assert_equal [['repeat', [3, 3, 3]]], var.filters
|
||||
|
||||
var = create_variable(%( hello | strftime: '%Y, okay?'))
|
||||
var = Variable.new(%( hello | strftime: '%Y, okay?'))
|
||||
assert_equal VariableLookup.new('hello'), var.name
|
||||
assert_equal [['strftime', ['%Y, okay?']]], var.filters
|
||||
|
||||
var = create_variable(%( hello | things: "%Y, okay?", 'the other one'))
|
||||
var = Variable.new(%( hello | things: "%Y, okay?", 'the other one'))
|
||||
assert_equal VariableLookup.new('hello'), var.name
|
||||
assert_equal [['things', ['%Y, okay?', 'the other one']]], var.filters
|
||||
end
|
||||
|
||||
def test_filter_with_date_parameter
|
||||
var = create_variable(%( '2006-06-06' | date: "%m/%d/%Y"))
|
||||
var = Variable.new(%( '2006-06-06' | date: "%m/%d/%Y"))
|
||||
assert_equal '2006-06-06', var.name
|
||||
assert_equal [['date', ['%m/%d/%Y']]], var.filters
|
||||
end
|
||||
|
||||
def test_filters_without_whitespace
|
||||
var = create_variable('hello | textileze | paragraph')
|
||||
var = Variable.new('hello | textileze | paragraph')
|
||||
assert_equal VariableLookup.new('hello'), var.name
|
||||
assert_equal [['textileze', []], ['paragraph', []]], var.filters
|
||||
|
||||
var = create_variable('hello|textileze|paragraph')
|
||||
var = Variable.new('hello|textileze|paragraph')
|
||||
assert_equal VariableLookup.new('hello'), var.name
|
||||
assert_equal [['textileze', []], ['paragraph', []]], var.filters
|
||||
|
||||
var = create_variable("hello|replace:'foo','bar'|textileze")
|
||||
var = Variable.new("hello|replace:'foo','bar'|textileze")
|
||||
assert_equal VariableLookup.new('hello'), var.name
|
||||
assert_equal [['replace', ['foo', 'bar']], ['textileze', []]], var.filters
|
||||
end
|
||||
|
||||
def test_symbol
|
||||
var = create_variable("http://disney.com/logo.gif | image: 'med' ", error_mode: :lax)
|
||||
var = Variable.new("http://disney.com/logo.gif | image: 'med' ", :error_mode => :lax)
|
||||
assert_equal VariableLookup.new('http://disney.com/logo.gif'), var.name
|
||||
assert_equal [['image', ['med']]], var.filters
|
||||
end
|
||||
|
||||
def test_string_to_filter
|
||||
var = create_variable("'http://disney.com/logo.gif' | image: 'med' ")
|
||||
var = Variable.new("'http://disney.com/logo.gif' | image: 'med' ")
|
||||
assert_equal 'http://disney.com/logo.gif', var.name
|
||||
assert_equal [['image', ['med']]], var.filters
|
||||
end
|
||||
|
||||
def test_string_single_quoted
|
||||
var = create_variable(%( "hello" ))
|
||||
var = Variable.new(%( "hello" ))
|
||||
assert_equal 'hello', var.name
|
||||
end
|
||||
|
||||
def test_string_double_quoted
|
||||
var = create_variable(%( 'hello' ))
|
||||
var = Variable.new(%( 'hello' ))
|
||||
assert_equal 'hello', var.name
|
||||
end
|
||||
|
||||
def test_integer
|
||||
var = create_variable(%( 1000 ))
|
||||
var = Variable.new(%( 1000 ))
|
||||
assert_equal 1000, var.name
|
||||
end
|
||||
|
||||
def test_float
|
||||
var = create_variable(%( 1000.01 ))
|
||||
var = Variable.new(%( 1000.01 ))
|
||||
assert_equal 1000.01, var.name
|
||||
end
|
||||
|
||||
def test_dashes
|
||||
assert_equal VariableLookup.new('foo-bar'), create_variable('foo-bar').name
|
||||
assert_equal VariableLookup.new('foo-bar-2'), create_variable('foo-bar-2').name
|
||||
assert_equal VariableLookup.new('foo-bar'), Variable.new('foo-bar').name
|
||||
assert_equal VariableLookup.new('foo-bar-2'), Variable.new('foo-bar-2').name
|
||||
|
||||
with_error_mode :strict do
|
||||
assert_raises(Liquid::SyntaxError) { create_variable('foo - bar') }
|
||||
assert_raises(Liquid::SyntaxError) { create_variable('-foo') }
|
||||
assert_raises(Liquid::SyntaxError) { create_variable('2foo') }
|
||||
assert_raises(Liquid::SyntaxError) { Variable.new('foo - bar') }
|
||||
assert_raises(Liquid::SyntaxError) { Variable.new('-foo') }
|
||||
assert_raises(Liquid::SyntaxError) { Variable.new('2foo') }
|
||||
end
|
||||
end
|
||||
|
||||
def test_string_with_special_chars
|
||||
var = create_variable(%( 'hello! $!@.;"ddasd" ' ))
|
||||
var = Variable.new(%( 'hello! $!@.;"ddasd" ' ))
|
||||
assert_equal 'hello! $!@.;"ddasd" ', var.name
|
||||
end
|
||||
|
||||
def test_string_dot
|
||||
var = create_variable(%( test.test ))
|
||||
var = Variable.new(%( test.test ))
|
||||
assert_equal VariableLookup.new('test.test'), var.name
|
||||
end
|
||||
|
||||
def test_filter_with_keyword_arguments
|
||||
var = create_variable(%( hello | things: greeting: "world", farewell: 'goodbye'))
|
||||
var = Variable.new(%( hello | things: greeting: "world", farewell: 'goodbye'))
|
||||
assert_equal VariableLookup.new('hello'), var.name
|
||||
assert_equal [['things', [], { 'greeting' => 'world', 'farewell' => 'goodbye' }]], var.filters
|
||||
end
|
||||
|
||||
def test_lax_filter_argument_parsing
|
||||
var = create_variable(%( number_of_comments | pluralize: 'comment': 'comments' ), error_mode: :lax)
|
||||
var = Variable.new(%( number_of_comments | pluralize: 'comment': 'comments' ), :error_mode => :lax)
|
||||
assert_equal VariableLookup.new('number_of_comments'), var.name
|
||||
assert_equal [['pluralize', ['comment', 'comments']]], var.filters
|
||||
end
|
||||
@@ -138,13 +138,13 @@ class VariableUnitTest < Minitest::Test
|
||||
def test_strict_filter_argument_parsing
|
||||
with_error_mode(:strict) do
|
||||
assert_raises(SyntaxError) do
|
||||
create_variable(%( number_of_comments | pluralize: 'comment': 'comments' ))
|
||||
Variable.new(%( number_of_comments | pluralize: 'comment': 'comments' ))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_output_raw_source_of_variable
|
||||
var = create_variable(%( name_of_variable | upcase ))
|
||||
var = Variable.new(%( name_of_variable | upcase ))
|
||||
assert_equal " name_of_variable | upcase ", var.raw
|
||||
end
|
||||
|
||||
@@ -153,10 +153,4 @@ class VariableUnitTest < Minitest::Test
|
||||
assert_equal 'a', lookup.name
|
||||
assert_equal ['b', 'c'], lookup.lookups
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_variable(markup, options = {})
|
||||
Variable.new(markup, ParseContext.new(options))
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user