mirror of
https://github.com/kemko/liquid.git
synced 2026-01-04 17:25:41 +03:00
Compare commits
3 Commits
fix/echo-p
...
no-templat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5c31861f9 | ||
|
|
895e63e40a | ||
|
|
219168e89f |
39
.github/workflows/liquid.yml
vendored
39
.github/workflows/liquid.yml
vendored
@@ -1,39 +0,0 @@
|
|||||||
name: Liquid
|
|
||||||
on: [push, pull_request]
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
entry:
|
|
||||||
- { ruby: 2.5, allowed-failure: false } # minimum supported
|
|
||||||
- { ruby: 3.0, allowed-failure: false } # latest
|
|
||||||
- { ruby: ruby-head, allowed-failure: true }
|
|
||||||
name: test (${{ matrix.entry.ruby }})
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: ruby/setup-ruby@v1
|
|
||||||
with:
|
|
||||||
ruby-version: ${{ matrix.entry.ruby }}
|
|
||||||
- uses: actions/cache@v1
|
|
||||||
with:
|
|
||||||
path: vendor/bundle
|
|
||||||
key: ${{ runner.os }}-gems-${{ hashFiles('Gemfile') }}
|
|
||||||
restore-keys: ${{ runner.os }}-gems-
|
|
||||||
- run: bundle install --jobs=3 --retry=3 --path=vendor/bundle
|
|
||||||
- run: bundle exec rake
|
|
||||||
continue-on-error: ${{ matrix.entry.allowed-failure }}
|
|
||||||
memory_profile:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: ruby/setup-ruby@v1
|
|
||||||
with:
|
|
||||||
ruby-version: 2.7
|
|
||||||
- uses: actions/cache@v1
|
|
||||||
with:
|
|
||||||
path: vendor/bundle
|
|
||||||
key: ${{ runner.os }}-gems-${{ hashFiles('Gemfile') }}
|
|
||||||
restore-keys: ${{ runner.os }}-gems-
|
|
||||||
- run: bundle install --jobs=3 --retry=3 --path=vendor/bundle
|
|
||||||
- run: bundle exec rake memory_profile:run
|
|
||||||
1029
.rubocop-https---shopify-github-io-ruby-style-guide-rubocop-yml
Normal file
1029
.rubocop-https---shopify-github-io-ruby-style-guide-rubocop-yml
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,5 @@
|
|||||||
inherit_gem:
|
|
||||||
rubocop-shopify: rubocop.yml
|
|
||||||
|
|
||||||
inherit_from:
|
inherit_from:
|
||||||
|
- 'https://shopify.github.io/ruby-style-guide/rubocop.yml'
|
||||||
- .rubocop_todo.yml
|
- .rubocop_todo.yml
|
||||||
|
|
||||||
require: rubocop-performance
|
require: rubocop-performance
|
||||||
@@ -11,7 +9,6 @@ Performance:
|
|||||||
|
|
||||||
AllCops:
|
AllCops:
|
||||||
TargetRubyVersion: 2.4
|
TargetRubyVersion: 2.4
|
||||||
NewCops: disable
|
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'vendor/bundle/**/*'
|
- 'vendor/bundle/**/*'
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# This configuration was generated by
|
# This configuration was generated by
|
||||||
# `rubocop --auto-gen-config`
|
# `rubocop --auto-gen-config`
|
||||||
# on 2020-12-11 18:53:41 UTC using RuboCop version 1.6.1.
|
# on 2019-09-11 06:34:25 +1000 using RuboCop version 0.74.0.
|
||||||
# The point is for the user to remove these configuration records
|
# The point is for the user to remove these configuration records
|
||||||
# one by one as the offenses are removed from the code base.
|
# one by one as the offenses are removed from the code base.
|
||||||
# Note that changes in the inspected code, or installation of new
|
# Note that changes in the inspected code, or installation of new
|
||||||
@@ -14,26 +14,14 @@ Lint/InheritException:
|
|||||||
Exclude:
|
Exclude:
|
||||||
- 'lib/liquid/interrupts.rb'
|
- 'lib/liquid/interrupts.rb'
|
||||||
|
|
||||||
# Offense count: 113
|
# Offense count: 98
|
||||||
# Cop supports --auto-correct.
|
# Cop supports --auto-correct.
|
||||||
# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
|
# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
|
||||||
# URISchemes: http, https
|
# URISchemes: http, https
|
||||||
Layout/LineLength:
|
Layout/LineLength:
|
||||||
Max: 260
|
Max: 294
|
||||||
|
|
||||||
# Offense count: 8
|
# Offense count: 44
|
||||||
Lint/MissingSuper:
|
|
||||||
Exclude:
|
|
||||||
- 'lib/liquid/forloop_drop.rb'
|
|
||||||
- 'lib/liquid/tablerowloop_drop.rb'
|
|
||||||
- 'test/integration/assign_test.rb'
|
|
||||||
- 'test/integration/context_test.rb'
|
|
||||||
- 'test/integration/filter_test.rb'
|
|
||||||
- 'test/integration/standard_filter_test.rb'
|
|
||||||
- 'test/integration/tags/for_tag_test.rb'
|
|
||||||
- 'test/integration/tags/table_row_test.rb'
|
|
||||||
|
|
||||||
# Offense count: 43
|
|
||||||
Naming/ConstantName:
|
Naming/ConstantName:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'lib/liquid.rb'
|
- 'lib/liquid.rb'
|
||||||
@@ -44,6 +32,7 @@ Naming/ConstantName:
|
|||||||
- 'lib/liquid/tags/cycle.rb'
|
- 'lib/liquid/tags/cycle.rb'
|
||||||
- 'lib/liquid/tags/for.rb'
|
- 'lib/liquid/tags/for.rb'
|
||||||
- 'lib/liquid/tags/if.rb'
|
- 'lib/liquid/tags/if.rb'
|
||||||
|
- 'lib/liquid/tags/include.rb'
|
||||||
- 'lib/liquid/tags/raw.rb'
|
- 'lib/liquid/tags/raw.rb'
|
||||||
- 'lib/liquid/tags/table_row.rb'
|
- 'lib/liquid/tags/table_row.rb'
|
||||||
- 'lib/liquid/variable.rb'
|
- 'lib/liquid/variable.rb'
|
||||||
@@ -51,7 +40,9 @@ Naming/ConstantName:
|
|||||||
- 'performance/shopify/paginate.rb'
|
- 'performance/shopify/paginate.rb'
|
||||||
- 'test/integration/tags/include_tag_test.rb'
|
- 'test/integration/tags/include_tag_test.rb'
|
||||||
|
|
||||||
# Offense count: 2
|
# Offense count: 5
|
||||||
Style/ClassVars:
|
Style/ClassVars:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'lib/liquid/condition.rb'
|
- 'lib/liquid/condition.rb'
|
||||||
|
- 'lib/liquid/strainer.rb'
|
||||||
|
- 'lib/liquid/template.rb'
|
||||||
26
.travis.yml
Normal file
26
.travis.yml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
language: ruby
|
||||||
|
cache: bundler
|
||||||
|
|
||||||
|
rvm:
|
||||||
|
- 2.4
|
||||||
|
- 2.5
|
||||||
|
- 2.6
|
||||||
|
- &latest_ruby 2.7
|
||||||
|
- ruby-head
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- rvm: *latest_ruby
|
||||||
|
script: bundle exec rake memory_profile:run
|
||||||
|
name: Profiling Memory Usage
|
||||||
|
allow_failures:
|
||||||
|
- rvm: ruby-head
|
||||||
|
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
- gh-pages
|
||||||
|
- /.*-stable/
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
disable: true
|
||||||
@@ -5,7 +5,6 @@
|
|||||||
* Bugfixes
|
* Bugfixes
|
||||||
* Performance improvements
|
* Performance improvements
|
||||||
* Features that are likely to be useful to the majority of Liquid users
|
* Features that are likely to be useful to the majority of Liquid users
|
||||||
* Documentation updates that are concise and likely to be useful to the majority of Liquid users
|
|
||||||
|
|
||||||
## Things we won't merge
|
## Things we won't merge
|
||||||
|
|
||||||
@@ -15,14 +14,12 @@
|
|||||||
* Features that can easily be implemented on top of Liquid (for example as a custom filter or custom filesystem)
|
* 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 does not include tests
|
||||||
* Code that breaks existing tests
|
* Code that breaks existing tests
|
||||||
* Documentation changes that are verbose, incorrect or not important to most people (we want to keep it simple and easy to understand)
|
|
||||||
|
|
||||||
## Workflow
|
## Workflow
|
||||||
|
|
||||||
* [Sign the CLA](https://cla.shopify.com/) if you haven't already
|
|
||||||
* Fork the Liquid repository
|
* Fork the Liquid repository
|
||||||
* Create a new branch in your fork
|
* Create a new branch in your fork
|
||||||
* For updating [Liquid documentation](https://shopify.github.io/liquid/), create it from `gh-pages` branch. (You can skip tests.)
|
|
||||||
* If it makes sense, add tests for your code and/or run a performance benchmark
|
* If it makes sense, add tests for your code and/or run a performance benchmark
|
||||||
* Make sure all tests pass (`bundle exec rake`)
|
* Make sure all tests pass (`bundle exec rake`)
|
||||||
* Create a pull request
|
* Create a pull request
|
||||||
|
|
||||||
|
|||||||
3
Gemfile
3
Gemfile
@@ -18,8 +18,7 @@ group :benchmark, :test do
|
|||||||
end
|
end
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
gem 'rubocop', '~> 1.4', require: false
|
gem 'rubocop', '~> 0.78.0', require: false
|
||||||
gem 'rubocop-shopify', '~> 1.0.7', require: false
|
|
||||||
gem 'rubocop-performance', require: false
|
gem 'rubocop-performance', require: false
|
||||||
|
|
||||||
platform :mri, :truffleruby do
|
platform :mri, :truffleruby do
|
||||||
|
|||||||
33
History.md
33
History.md
@@ -1,37 +1,10 @@
|
|||||||
# Liquid Change Log
|
# Liquid Change Log
|
||||||
|
|
||||||
## 5.0.0 / 2021-01-06
|
### Unreleased
|
||||||
|
|
||||||
### Features
|
* Split Strainer class as a factory and a template (#1208) [Thierry Joyal]
|
||||||
* Add new `{% render %}` tag (#1122) [Samuel Doiron]
|
|
||||||
* Add support for `as` in `{% render %}` and `{% include %}` (#1181) [Mike Angell]
|
|
||||||
* Add `{% liquid %}` and `{% echo %}` tags (#1086) [Justin Li]
|
|
||||||
* Add [usage tracking](README.md#usage-tracking) [Mike Angell]
|
|
||||||
* Add `Tag.disable_tags` for disabling tags that prepend `Tag::Disableable` at render time (#1162, #1274, #1275) [Mike Angell]
|
|
||||||
* Support using a profiler for multiple renders (#1365, #1366) [Dylan Thacker-Smith]
|
|
||||||
|
|
||||||
### Fixes
|
|
||||||
* Fix catastrophic backtracking in `RANGES_REGEX` regular expression (#1357) [Dylan Thacker-Smith]
|
|
||||||
* Make sure the for tag's limit and offset are integers (#1094) [David Cornu]
|
|
||||||
* Invokable methods for enumerable reject include (#1151) [Thierry Joyal]
|
|
||||||
* Allow `default` filter to handle `false` as value (#1144) [Mike Angell]
|
|
||||||
* Fix render length resource limit so it doesn't multiply nested output (#1285) [Dylan Thacker-Smith]
|
|
||||||
* Fix duplication of text in raw tags (#1304) [Peter Zhu]
|
|
||||||
* Fix strict parsing of find variable with a name expression (#1317) [Dylan Thacker-Smith]
|
|
||||||
* Use monotonic time to measure durations in Liquid::Profiler (#1362) [Dylan Thacker-Smith]
|
|
||||||
|
|
||||||
### Breaking Changes
|
|
||||||
* Require Ruby >= 2.5 (#1131, #1310) [Mike Angell, Dylan Thacker-Smith]
|
|
||||||
* Remove support for taint checking (#1268) [Dylan Thacker-Smith]
|
|
||||||
* Split Strainer class into StrainerFactory and StrainerTemplate (#1208) [Thierry Joyal]
|
|
||||||
* Remove handling of a nil context in the Strainer class (#1218) [Thierry Joyal]
|
* Remove handling of a nil context in the Strainer class (#1218) [Thierry Joyal]
|
||||||
* Handle `BlockBody#blank?` at parse time (#1287) [Dylan Thacker-Smith]
|
* StaticRegisters#fetch to raise on missing key (#1250) [Thierry Joyal]
|
||||||
* Pass the tag markup and tokenizer to `Document#unknown_tag` (#1290) [Dylan Thacker-Smith]
|
|
||||||
* And several internal changes
|
|
||||||
|
|
||||||
### Performance Improvements
|
|
||||||
* Reduce allocations (#1073, #1091, #1115, #1099, #1117, #1141, #1322, #1341) [Richard Monette, Florian Weingarten, Ashwin Maroli]
|
|
||||||
* Improve resources limits performance (#1093, #1323) [Florian Weingarten, Dylan Thacker-Smith]
|
|
||||||
|
|
||||||
## 4.0.3 / 2019-03-12
|
## 4.0.3 / 2019-03-12
|
||||||
|
|
||||||
|
|||||||
14
README.md
14
README.md
@@ -80,22 +80,24 @@ It is also recommended that you use it in the template editors of existing apps
|
|||||||
|
|
||||||
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.
|
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.
|
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.
|
When one of these options is set to true, all errors about undefined variables and undefined filters will be stored in an `errors` array on the `Liquid::Context` instance used for rendering.
|
||||||
Here are some examples:
|
Here are some examples:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
template = Liquid::Template.parse("{{x}} {{y}} {{z.a}} {{z.b}}")
|
template = Liquid::Template.parse("{{x}} {{y}} {{z.a}} {{z.b}}")
|
||||||
template.render({ 'x' => 1, 'z' => { 'a' => 2 } }, { strict_variables: true })
|
context = Liquid::Context.new({ 'x' => 1, 'z' => { 'a' => 2 } })
|
||||||
|
template.render(context, { strict_variables: true })
|
||||||
#=> '1 2 ' # when a variable is undefined, it's rendered as nil
|
#=> '1 2 ' # when a variable is undefined, it's rendered as nil
|
||||||
template.errors
|
context.errors
|
||||||
#=> [#<Liquid::UndefinedVariable: Liquid error: undefined variable y>, #<Liquid::UndefinedVariable: Liquid error: undefined variable b>]
|
#=> [#<Liquid::UndefinedVariable: Liquid error: undefined variable y>, #<Liquid::UndefinedVariable: Liquid error: undefined variable b>]
|
||||||
```
|
```
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
template = Liquid::Template.parse("{{x | filter1 | upcase}}")
|
template = Liquid::Template.parse("{{x | filter1 | upcase}}")
|
||||||
template.render({ 'x' => 'foo' }, { strict_filters: true })
|
context = Liquid::Context.new({ 'x' => 'foo' })
|
||||||
|
template.render(context, { strict_filters: true })
|
||||||
#=> '' # when at least one filter in the filter chain is undefined, a whole expression is rendered as nil
|
#=> '' # when at least one filter in the filter chain is undefined, a whole expression is rendered as nil
|
||||||
template.errors
|
context.errors
|
||||||
#=> [#<Liquid::UndefinedFilter: Liquid error: undefined filter filter1>]
|
#=> [#<Liquid::UndefinedFilter: Liquid error: undefined filter filter1>]
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -111,4 +113,4 @@ template.render!({ 'x' => 1}, { strict_variables: true })
|
|||||||
|
|
||||||
To help track usages of a feature or code path in production, we have released opt-in usage tracking. To enable this, we provide an empty `Liquid:: Usage.increment` method which you can customize to your needs. The feature is well suited to https://github.com/Shopify/statsd-instrument. However, the choice of implementation is up to you.
|
To help track usages of a feature or code path in production, we have released opt-in usage tracking. To enable this, we provide an empty `Liquid:: Usage.increment` method which you can customize to your needs. The feature is well suited to https://github.com/Shopify/statsd-instrument. However, the choice of implementation is up to you.
|
||||||
|
|
||||||
Once you have enabled usage tracking, we recommend reporting any events through Github Issues that your system may be logging. It is highly likely this event has been added to consider deprecating or improving code specific to this event, so please raise any concerns.
|
Once you have enabled usage tracking, we recommend reporting any events through Github Issues that your system may be logging. It is highly likely this event has been added to consider deprecating or improving code specific to this event, so please raise any concerns.
|
||||||
|
|||||||
16
Rakefile
16
Rakefile
@@ -9,17 +9,11 @@ task(default: [:test, :rubocop])
|
|||||||
|
|
||||||
desc('run test suite with default parser')
|
desc('run test suite with default parser')
|
||||||
Rake::TestTask.new(:base_test) do |t|
|
Rake::TestTask.new(:base_test) do |t|
|
||||||
t.libs << 'lib' << 'test'
|
t.libs << '.' << 'lib' << 'test'
|
||||||
t.test_files = FileList['test/{integration,unit}/**/*_test.rb']
|
t.test_files = FileList['test/{integration,unit}/**/*_test.rb']
|
||||||
t.verbose = false
|
t.verbose = false
|
||||||
end
|
end
|
||||||
|
|
||||||
Rake::TestTask.new(:integration_test) do |t|
|
|
||||||
t.libs << 'lib' << 'test'
|
|
||||||
t.test_files = FileList['test/integration/**/*_test.rb']
|
|
||||||
t.verbose = false
|
|
||||||
end
|
|
||||||
|
|
||||||
desc('run test suite with warn error mode')
|
desc('run test suite with warn error mode')
|
||||||
task :warn_test do
|
task :warn_test do
|
||||||
ENV['LIQUID_PARSER_MODE'] = 'warn'
|
ENV['LIQUID_PARSER_MODE'] = 'warn'
|
||||||
@@ -46,12 +40,12 @@ task :test do
|
|||||||
ENV['LIQUID_C'] = '1'
|
ENV['LIQUID_C'] = '1'
|
||||||
|
|
||||||
ENV['LIQUID_PARSER_MODE'] = 'lax'
|
ENV['LIQUID_PARSER_MODE'] = 'lax'
|
||||||
Rake::Task['integration_test'].reenable
|
Rake::Task['base_test'].reenable
|
||||||
Rake::Task['integration_test'].invoke
|
Rake::Task['base_test'].invoke
|
||||||
|
|
||||||
ENV['LIQUID_PARSER_MODE'] = 'strict'
|
ENV['LIQUID_PARSER_MODE'] = 'strict'
|
||||||
Rake::Task['integration_test'].reenable
|
Rake::Task['base_test'].reenable
|
||||||
Rake::Task['integration_test'].invoke
|
Rake::Task['base_test'].invoke
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -42,8 +42,6 @@ module Liquid
|
|||||||
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om
|
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om
|
||||||
VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o
|
VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o
|
||||||
|
|
||||||
RAISE_EXCEPTION_LAMBDA = ->(_e) { raise }
|
|
||||||
|
|
||||||
singleton_class.send(:attr_accessor, :cache_classes)
|
singleton_class.send(:attr_accessor, :cache_classes)
|
||||||
self.cache_classes = true
|
self.cache_classes = true
|
||||||
end
|
end
|
||||||
@@ -65,8 +63,6 @@ require 'liquid/expression'
|
|||||||
require 'liquid/context'
|
require 'liquid/context'
|
||||||
require 'liquid/parser_switching'
|
require 'liquid/parser_switching'
|
||||||
require 'liquid/tag'
|
require 'liquid/tag'
|
||||||
require 'liquid/tag/disabler'
|
|
||||||
require 'liquid/tag/disableable'
|
|
||||||
require 'liquid/block'
|
require 'liquid/block'
|
||||||
require 'liquid/block_body'
|
require 'liquid/block_body'
|
||||||
require 'liquid/document'
|
require 'liquid/document'
|
||||||
@@ -90,3 +86,4 @@ require 'liquid/template_factory'
|
|||||||
# Load all the tags of the standard library
|
# Load all the tags of the standard library
|
||||||
#
|
#
|
||||||
Dir["#{__dir__}/liquid/tags/*.rb"].each { |f| require f }
|
Dir["#{__dir__}/liquid/tags/*.rb"].each { |f| require f }
|
||||||
|
Dir["#{__dir__}/liquid/registers/*.rb"].each { |f| require f }
|
||||||
|
|||||||
@@ -10,10 +10,9 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def parse(tokens)
|
def parse(tokens)
|
||||||
@body = new_body
|
@body = BlockBody.new
|
||||||
while parse_body(@body, tokens)
|
while parse_body(@body, tokens)
|
||||||
end
|
end
|
||||||
@body.freeze
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# For backwards compatibility
|
# For backwards compatibility
|
||||||
@@ -29,12 +28,7 @@ module Liquid
|
|||||||
@body.nodelist
|
@body.nodelist
|
||||||
end
|
end
|
||||||
|
|
||||||
def unknown_tag(tag_name, _markup, _tokenizer)
|
def unknown_tag(tag, _params, _tokens)
|
||||||
Block.raise_unknown_tag(tag_name, block_name, block_delimiter, parse_context)
|
|
||||||
end
|
|
||||||
|
|
||||||
# @api private
|
|
||||||
def self.raise_unknown_tag(tag, block_name, block_delimiter, parse_context)
|
|
||||||
if tag == 'else'
|
if tag == 'else'
|
||||||
raise SyntaxError, parse_context.locale.t("errors.syntax.unexpected_else",
|
raise SyntaxError, parse_context.locale.t("errors.syntax.unexpected_else",
|
||||||
block_name: block_name)
|
block_name: block_name)
|
||||||
@@ -48,10 +42,6 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def raise_tag_never_closed(block_name)
|
|
||||||
raise SyntaxError, parse_context.locale.t("errors.syntax.tag_never_closed", block_name: block_name)
|
|
||||||
end
|
|
||||||
|
|
||||||
def block_name
|
def block_name
|
||||||
@tag_name
|
@tag_name
|
||||||
end
|
end
|
||||||
@@ -60,14 +50,8 @@ module Liquid
|
|||||||
@block_delimiter ||= "end#{block_name}"
|
@block_delimiter ||= "end#{block_name}"
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
protected
|
||||||
|
|
||||||
# @api public
|
|
||||||
def new_body
|
|
||||||
parse_context.new_block_body
|
|
||||||
end
|
|
||||||
|
|
||||||
# @api public
|
|
||||||
def parse_body(body, tokens)
|
def parse_body(body, tokens)
|
||||||
if parse_context.depth >= MAX_DEPTH
|
if parse_context.depth >= MAX_DEPTH
|
||||||
raise StackLevelError, "Nesting too deep"
|
raise StackLevelError, "Nesting too deep"
|
||||||
@@ -78,7 +62,9 @@ module Liquid
|
|||||||
@blank &&= body.blank?
|
@blank &&= body.blank?
|
||||||
|
|
||||||
return false if end_tag_name == block_delimiter
|
return false if end_tag_name == block_delimiter
|
||||||
raise_tag_never_closed(block_name) unless end_tag_name
|
unless end_tag_name
|
||||||
|
raise SyntaxError, parse_context.locale.t("errors.syntax.tag_never_closed", block_name: block_name)
|
||||||
|
end
|
||||||
|
|
||||||
# this tag is not registered with the system
|
# this tag is not registered with the system
|
||||||
# pass it to the current block for special handling or error reporting
|
# pass it to the current block for special handling or error reporting
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def parse(tokenizer, parse_context, &block)
|
def parse(tokenizer, parse_context, &block)
|
||||||
raise FrozenError, "can't modify frozen Liquid::BlockBody" if frozen?
|
|
||||||
|
|
||||||
parse_context.line_number = tokenizer.line_number
|
parse_context.line_number = tokenizer.line_number
|
||||||
|
|
||||||
if tokenizer.for_liquid_tag
|
if tokenizer.for_liquid_tag
|
||||||
@@ -30,11 +28,6 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def freeze
|
|
||||||
@nodelist.freeze
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
private def parse_for_liquid_tag(tokenizer, parse_context)
|
private def parse_for_liquid_tag(tokenizer, parse_context)
|
||||||
while (token = tokenizer.shift)
|
while (token = tokenizer.shift)
|
||||||
unless token.empty? || token =~ WhitespaceOrNothing
|
unless token.empty? || token =~ WhitespaceOrNothing
|
||||||
@@ -61,62 +54,28 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
# @api private
|
# @api private
|
||||||
def self.unknown_tag_in_liquid_tag(tag, parse_context)
|
def self.unknown_tag_in_liquid_tag(end_tag_name, end_tag_markup)
|
||||||
Block.raise_unknown_tag(tag, 'liquid', '%}', parse_context)
|
yield end_tag_name, end_tag_markup
|
||||||
|
ensure
|
||||||
|
Usage.increment("liquid_tag_contains_outer_tag") unless $ERROR_INFO.is_a?(SyntaxError)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @api private
|
private def parse_liquid_tag(markup, parse_context, &block)
|
||||||
def self.raise_missing_tag_terminator(token, parse_context)
|
liquid_tag_tokenizer = Tokenizer.new(markup, line_number: parse_context.line_number, for_liquid_tag: true)
|
||||||
raise SyntaxError, parse_context.locale.t("errors.syntax.tag_termination", token: token, tag_end: TagEnd.inspect)
|
parse_for_liquid_tag(liquid_tag_tokenizer, parse_context) do |end_tag_name, end_tag_markup|
|
||||||
end
|
next unless end_tag_name
|
||||||
|
self.class.unknown_tag_in_liquid_tag(end_tag_name, end_tag_markup, &block)
|
||||||
# @api private
|
|
||||||
def self.raise_missing_variable_terminator(token, parse_context)
|
|
||||||
raise SyntaxError, parse_context.locale.t("errors.syntax.variable_termination", token: token, tag_end: VariableEnd.inspect)
|
|
||||||
end
|
|
||||||
|
|
||||||
# @api private
|
|
||||||
def self.render_node(context, output, node)
|
|
||||||
node.render_to_output_buffer(context, output)
|
|
||||||
rescue => exc
|
|
||||||
blank_tag = !node.instance_of?(Variable) && node.blank?
|
|
||||||
rescue_render_node(context, output, node.line_number, exc, blank_tag)
|
|
||||||
end
|
|
||||||
|
|
||||||
# @api private
|
|
||||||
def self.rescue_render_node(context, output, line_number, exc, blank_tag)
|
|
||||||
case exc
|
|
||||||
when MemoryError
|
|
||||||
raise
|
|
||||||
when UndefinedVariable, UndefinedDropMethod, UndefinedFilter
|
|
||||||
context.handle_error(exc, line_number)
|
|
||||||
else
|
|
||||||
error_message = context.handle_error(exc, line_number)
|
|
||||||
unless blank_tag # conditional for backwards compatibility
|
|
||||||
output << error_message
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private def parse_liquid_tag(markup, parse_context)
|
private def parse_for_document(tokenizer, parse_context, &block)
|
||||||
liquid_tag_tokenizer = parse_context.new_tokenizer(
|
|
||||||
markup, start_line_number: parse_context.line_number, for_liquid_tag: true
|
|
||||||
)
|
|
||||||
parse_for_liquid_tag(liquid_tag_tokenizer, parse_context) do |end_tag_name, _end_tag_markup|
|
|
||||||
if end_tag_name
|
|
||||||
BlockBody.unknown_tag_in_liquid_tag(end_tag_name, parse_context)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private def parse_for_document(tokenizer, parse_context)
|
|
||||||
while (token = tokenizer.shift)
|
while (token = tokenizer.shift)
|
||||||
next if token.empty?
|
next if token.empty?
|
||||||
case
|
case
|
||||||
when token.start_with?(TAGSTART)
|
when token.start_with?(TAGSTART)
|
||||||
whitespace_handler(token, parse_context)
|
whitespace_handler(token, parse_context)
|
||||||
unless token =~ FullToken
|
unless token =~ FullToken
|
||||||
BlockBody.raise_missing_tag_terminator(token, parse_context)
|
raise_missing_tag_terminator(token, parse_context)
|
||||||
end
|
end
|
||||||
tag_name = Regexp.last_match(2)
|
tag_name = Regexp.last_match(2)
|
||||||
markup = Regexp.last_match(4)
|
markup = Regexp.last_match(4)
|
||||||
@@ -128,7 +87,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
if tag_name == 'liquid'
|
if tag_name == 'liquid'
|
||||||
parse_liquid_tag(markup, parse_context)
|
parse_liquid_tag(markup, parse_context, &block)
|
||||||
next
|
next
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -162,11 +121,7 @@ module Liquid
|
|||||||
if token[2] == WhitespaceControl
|
if token[2] == WhitespaceControl
|
||||||
previous_token = @nodelist.last
|
previous_token = @nodelist.last
|
||||||
if previous_token.is_a?(String)
|
if previous_token.is_a?(String)
|
||||||
first_byte = previous_token.getbyte(0)
|
|
||||||
previous_token.rstrip!
|
previous_token.rstrip!
|
||||||
if previous_token.empty? && parse_context[:bug_compatible_whitespace_trimming] && first_byte
|
|
||||||
previous_token << first_byte
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
parse_context.trim_whitespace = (token[-3] == WhitespaceControl)
|
parse_context.trim_whitespace = (token[-3] == WhitespaceControl)
|
||||||
@@ -176,49 +131,38 @@ module Liquid
|
|||||||
@blank
|
@blank
|
||||||
end
|
end
|
||||||
|
|
||||||
# Remove blank strings in the block body for a control flow tag (e.g. `if`, `for`, `case`, `unless`)
|
|
||||||
# with a blank body.
|
|
||||||
#
|
|
||||||
# For example, in a conditional assignment like the following
|
|
||||||
#
|
|
||||||
# ```
|
|
||||||
# {% if size > max_size %}
|
|
||||||
# {% assign size = max_size %}
|
|
||||||
# {% endif %}
|
|
||||||
# ```
|
|
||||||
#
|
|
||||||
# we assume the intention wasn't to output the blank spaces in the `if` tag's block body, so this method
|
|
||||||
# will remove them to reduce the render output size.
|
|
||||||
#
|
|
||||||
# Note that it is now preferred to use the `liquid` tag for this use case.
|
|
||||||
def remove_blank_strings
|
|
||||||
raise "remove_blank_strings only support being called on a blank block body" unless @blank
|
|
||||||
@nodelist.reject! { |node| node.instance_of?(String) }
|
|
||||||
end
|
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
render_to_output_buffer(context, +'')
|
render_to_output_buffer(context, +'')
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_to_output_buffer(context, output)
|
def render_to_output_buffer(context, output)
|
||||||
freeze unless frozen?
|
context.resource_limits.render_score += @nodelist.length
|
||||||
|
|
||||||
context.resource_limits.increment_render_score(@nodelist.length)
|
|
||||||
|
|
||||||
idx = 0
|
idx = 0
|
||||||
while (node = @nodelist[idx])
|
while (node = @nodelist[idx])
|
||||||
if node.instance_of?(String)
|
previous_output_size = output.bytesize
|
||||||
|
|
||||||
|
case node
|
||||||
|
when String
|
||||||
output << node
|
output << node
|
||||||
else
|
when Variable
|
||||||
render_node(context, output, node)
|
render_node(context, output, node)
|
||||||
|
when Block
|
||||||
|
render_node(context, node.blank? ? +'' : output, node)
|
||||||
|
break if context.interrupt? # might have happened in a for-block
|
||||||
|
when Continue, Break
|
||||||
# If we get an Interrupt that means the block must stop processing. An
|
# If we get an Interrupt that means the block must stop processing. An
|
||||||
# Interrupt is any command that stops block execution such as {% break %}
|
# Interrupt is any command that stops block execution such as {% break %}
|
||||||
# or {% continue %}. These tags may also occur through Block or Include tags.
|
# or {% continue %}
|
||||||
break if context.interrupt? # might have happened in a for-block
|
context.push_interrupt(node.interrupt)
|
||||||
|
break
|
||||||
|
else # Other non-Block tags
|
||||||
|
render_node(context, output, node)
|
||||||
|
break if context.interrupt? # might have happened through an include
|
||||||
end
|
end
|
||||||
idx += 1
|
idx += 1
|
||||||
|
|
||||||
context.resource_limits.increment_write_score(output)
|
raise_if_resource_limits_reached(context, output.bytesize - previous_output_size)
|
||||||
end
|
end
|
||||||
|
|
||||||
output
|
output
|
||||||
@@ -227,7 +171,29 @@ module Liquid
|
|||||||
private
|
private
|
||||||
|
|
||||||
def render_node(context, output, node)
|
def render_node(context, output, node)
|
||||||
BlockBody.render_node(context, output, node)
|
if node.disabled?(context)
|
||||||
|
output << node.disabled_error_message
|
||||||
|
return
|
||||||
|
end
|
||||||
|
disable_tags(context, node.disabled_tags) do
|
||||||
|
node.render_to_output_buffer(context, output)
|
||||||
|
end
|
||||||
|
rescue UndefinedVariable, UndefinedDropMethod, UndefinedFilter => e
|
||||||
|
context.handle_error(e, node.line_number)
|
||||||
|
rescue ::StandardError => e
|
||||||
|
line_number = node.is_a?(String) ? nil : node.line_number
|
||||||
|
output << context.handle_error(e, line_number)
|
||||||
|
end
|
||||||
|
|
||||||
|
def disable_tags(context, tags, &block)
|
||||||
|
return yield if tags.empty?
|
||||||
|
context.registers[:disabled_tags].disable(tags, &block)
|
||||||
|
end
|
||||||
|
|
||||||
|
def raise_if_resource_limits_reached(context, length)
|
||||||
|
context.resource_limits.render_length += length
|
||||||
|
return unless context.resource_limits.reached?
|
||||||
|
raise MemoryError, "Memory limits exceeded"
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_variable(token, parse_context)
|
def create_variable(token, parse_context)
|
||||||
@@ -235,17 +201,15 @@ module Liquid
|
|||||||
markup = content.first
|
markup = content.first
|
||||||
return Variable.new(markup, parse_context)
|
return Variable.new(markup, parse_context)
|
||||||
end
|
end
|
||||||
BlockBody.raise_missing_variable_terminator(token, parse_context)
|
raise_missing_variable_terminator(token, parse_context)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @deprecated Use {.raise_missing_tag_terminator} instead
|
|
||||||
def raise_missing_tag_terminator(token, parse_context)
|
def raise_missing_tag_terminator(token, parse_context)
|
||||||
BlockBody.raise_missing_tag_terminator(token, parse_context)
|
raise SyntaxError, parse_context.locale.t("errors.syntax.tag_termination", token: token, tag_end: TagEnd.inspect)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @deprecated Use {.raise_missing_variable_terminator} instead
|
|
||||||
def raise_missing_variable_terminator(token, parse_context)
|
def raise_missing_variable_terminator(token, parse_context)
|
||||||
BlockBody.raise_missing_variable_terminator(token, parse_context)
|
raise SyntaxError, parse_context.locale.t("errors.syntax.variable_termination", token: token, tag_end: VariableEnd.inspect)
|
||||||
end
|
end
|
||||||
|
|
||||||
def registered_tags
|
def registered_tags
|
||||||
|
|||||||
@@ -27,28 +27,10 @@ module Liquid
|
|||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
class MethodLiteral
|
|
||||||
attr_reader :method_name, :to_s
|
|
||||||
|
|
||||||
def initialize(method_name, to_s)
|
|
||||||
@method_name = method_name
|
|
||||||
@to_s = to_s
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@@method_literals = {
|
|
||||||
'blank' => MethodLiteral.new(:blank?, '').freeze,
|
|
||||||
'empty' => MethodLiteral.new(:empty?, '').freeze,
|
|
||||||
}
|
|
||||||
|
|
||||||
def self.operators
|
def self.operators
|
||||||
@@operators
|
@@operators
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.parse_expression(parse_context, markup)
|
|
||||||
@@method_literals[markup] || parse_context.parse_expression(markup)
|
|
||||||
end
|
|
||||||
|
|
||||||
attr_reader :attachment, :child_condition
|
attr_reader :attachment, :child_condition
|
||||||
attr_accessor :left, :operator, :right
|
attr_accessor :left, :operator, :right
|
||||||
|
|
||||||
@@ -109,7 +91,7 @@ module Liquid
|
|||||||
private
|
private
|
||||||
|
|
||||||
def equal_variables(left, right)
|
def equal_variables(left, right)
|
||||||
if left.is_a?(MethodLiteral)
|
if left.is_a?(Liquid::Expression::MethodLiteral)
|
||||||
if right.respond_to?(left.method_name)
|
if right.respond_to?(left.method_name)
|
||||||
return right.send(left.method_name)
|
return right.send(left.method_name)
|
||||||
else
|
else
|
||||||
@@ -117,7 +99,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if right.is_a?(MethodLiteral)
|
if right.is_a?(Liquid::Expression::MethodLiteral)
|
||||||
if left.respond_to?(right.method_name)
|
if left.respond_to?(right.method_name)
|
||||||
return left.send(right.method_name)
|
return left.send(right.method_name)
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ module Liquid
|
|||||||
attr_accessor :exception_renderer, :template_name, :partial, :global_filter, :strict_variables, :strict_filters
|
attr_accessor :exception_renderer, :template_name, :partial, :global_filter, :strict_variables, :strict_filters
|
||||||
|
|
||||||
# rubocop:disable Metrics/ParameterLists
|
# rubocop:disable Metrics/ParameterLists
|
||||||
def self.build(environments: {}, outer_scope: {}, registers: {}, rethrow_errors: false, resource_limits: nil, static_environments: {}, &block)
|
def self.build(environments: {}, outer_scope: {}, registers: {}, rethrow_errors: false, resource_limits: nil, static_environments: {})
|
||||||
new(environments, outer_scope, registers, rethrow_errors, resource_limits, static_environments, &block)
|
new(environments, outer_scope, registers, rethrow_errors, resource_limits, static_environments)
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_environments = {})
|
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_environments = {})
|
||||||
@@ -34,20 +34,15 @@ module Liquid
|
|||||||
@strict_variables = false
|
@strict_variables = false
|
||||||
@resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
|
@resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
|
||||||
@base_scope_depth = 0
|
@base_scope_depth = 0
|
||||||
@interrupts = []
|
|
||||||
@filters = []
|
|
||||||
@global_filter = nil
|
|
||||||
@disabled_tags = {}
|
|
||||||
|
|
||||||
self.exception_renderer = Template.default_exception_renderer
|
self.exception_renderer = Template.default_exception_renderer
|
||||||
if rethrow_errors
|
if rethrow_errors
|
||||||
self.exception_renderer = Liquid::RAISE_EXCEPTION_LAMBDA
|
self.exception_renderer = ->(_e) { raise }
|
||||||
end
|
end
|
||||||
|
|
||||||
yield self if block_given?
|
@interrupts = []
|
||||||
|
@filters = []
|
||||||
# Do this last, since it could result in this object being passed to a Proc in the environment
|
@global_filter = nil
|
||||||
squash_instance_assigns_with_environments
|
|
||||||
end
|
end
|
||||||
# rubocop:enable Metrics/ParameterLists
|
# rubocop:enable Metrics/ParameterLists
|
||||||
|
|
||||||
@@ -137,7 +132,7 @@ module Liquid
|
|||||||
def new_isolated_subcontext
|
def new_isolated_subcontext
|
||||||
check_overflow
|
check_overflow
|
||||||
|
|
||||||
self.class.build(
|
Context.build(
|
||||||
resource_limits: resource_limits,
|
resource_limits: resource_limits,
|
||||||
static_environments: static_environments,
|
static_environments: static_environments,
|
||||||
registers: StaticRegisters.new(registers)
|
registers: StaticRegisters.new(registers)
|
||||||
@@ -148,7 +143,6 @@ module Liquid
|
|||||||
subcontext.strainer = nil
|
subcontext.strainer = nil
|
||||||
subcontext.errors = errors
|
subcontext.errors = errors
|
||||||
subcontext.warnings = warnings
|
subcontext.warnings = warnings
|
||||||
subcontext.disabled_tags = @disabled_tags
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -213,24 +207,9 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def with_disabled_tags(tag_names)
|
|
||||||
tag_names.each do |name|
|
|
||||||
@disabled_tags[name] = @disabled_tags.fetch(name, 0) + 1
|
|
||||||
end
|
|
||||||
yield
|
|
||||||
ensure
|
|
||||||
tag_names.each do |name|
|
|
||||||
@disabled_tags[name] -= 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def tag_disabled?(tag_name)
|
|
||||||
@disabled_tags.fetch(tag_name, 0) > 0
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
attr_writer :base_scope_depth, :warnings, :errors, :strainer, :filters, :disabled_tags
|
attr_writer :base_scope_depth, :warnings, :errors, :strainer, :filters
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
@@ -266,16 +245,5 @@ module Liquid
|
|||||||
rescue Liquid::InternalError => exc
|
rescue Liquid::InternalError => exc
|
||||||
exc
|
exc
|
||||||
end
|
end
|
||||||
|
|
||||||
def squash_instance_assigns_with_environments
|
|
||||||
@scopes.last.each_key do |k|
|
|
||||||
@environments.each do |env|
|
|
||||||
if env.key?(k)
|
|
||||||
scopes.last[k] = lookup_and_evaluate(env, k)
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end # squash_instance_assigns_with_environments
|
|
||||||
end # Context
|
end # Context
|
||||||
end # Liquid
|
end # Liquid
|
||||||
|
|||||||
@@ -1,34 +1,23 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module Liquid
|
module Liquid
|
||||||
class Document
|
class Document < BlockBody
|
||||||
def self.parse(tokens, parse_context)
|
def self.parse(tokens, parse_context)
|
||||||
doc = new(parse_context)
|
doc = new
|
||||||
doc.parse(tokens, parse_context)
|
doc.parse(tokens, parse_context)
|
||||||
doc
|
doc
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_reader :parse_context, :body
|
def parse(tokens, parse_context)
|
||||||
|
super do |end_tag_name, _end_tag_params|
|
||||||
def initialize(parse_context)
|
unknown_tag(end_tag_name, parse_context) if end_tag_name
|
||||||
@parse_context = parse_context
|
|
||||||
@body = new_body
|
|
||||||
end
|
|
||||||
|
|
||||||
def nodelist
|
|
||||||
@body.nodelist
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse(tokenizer, parse_context)
|
|
||||||
while parse_body(tokenizer)
|
|
||||||
end
|
end
|
||||||
@body.freeze
|
|
||||||
rescue SyntaxError => e
|
rescue SyntaxError => e
|
||||||
e.line_number ||= parse_context.line_number
|
e.line_number ||= parse_context.line_number
|
||||||
raise
|
raise
|
||||||
end
|
end
|
||||||
|
|
||||||
def unknown_tag(tag, _markup, _tokenizer)
|
def unknown_tag(tag, parse_context)
|
||||||
case tag
|
case tag
|
||||||
when 'else', 'end'
|
when 'else', 'end'
|
||||||
raise SyntaxError, parse_context.locale.t("errors.syntax.unexpected_outer_tag", tag: tag)
|
raise SyntaxError, parse_context.locale.t("errors.syntax.unexpected_outer_tag", tag: tag)
|
||||||
@@ -36,30 +25,5 @@ module Liquid
|
|||||||
raise SyntaxError, parse_context.locale.t("errors.syntax.unknown_tag", tag: tag)
|
raise SyntaxError, parse_context.locale.t("errors.syntax.unknown_tag", tag: tag)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_to_output_buffer(context, output)
|
|
||||||
@body.render_to_output_buffer(context, output)
|
|
||||||
end
|
|
||||||
|
|
||||||
def render(context)
|
|
||||||
render_to_output_buffer(context, +'')
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def new_body
|
|
||||||
parse_context.new_block_body
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_body(tokenizer)
|
|
||||||
@body.parse(tokenizer, parse_context) do |unknown_tag_name, unknown_tag_markup|
|
|
||||||
if unknown_tag_name
|
|
||||||
unknown_tag(unknown_tag_name, unknown_tag_markup, tokenizer)
|
|
||||||
true
|
|
||||||
else
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -53,6 +53,5 @@ module Liquid
|
|||||||
UndefinedDropMethod = Class.new(Error)
|
UndefinedDropMethod = Class.new(Error)
|
||||||
UndefinedFilter = Class.new(Error)
|
UndefinedFilter = Class.new(Error)
|
||||||
MethodOverrideError = Class.new(Error)
|
MethodOverrideError = Class.new(Error)
|
||||||
DisabledError = Class.new(Error)
|
|
||||||
InternalError = Class.new(Error)
|
InternalError = Class.new(Error)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,39 +2,46 @@
|
|||||||
|
|
||||||
module Liquid
|
module Liquid
|
||||||
class Expression
|
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 = {
|
LITERALS = {
|
||||||
nil => nil, 'nil' => nil, 'null' => nil, '' => nil,
|
nil => nil, 'nil' => nil, 'null' => nil, '' => nil,
|
||||||
'true' => true,
|
'true' => true,
|
||||||
'false' => false,
|
'false' => false,
|
||||||
'blank' => '',
|
'blank' => MethodLiteral.new(:blank?, '').freeze,
|
||||||
'empty' => ''
|
'empty' => MethodLiteral.new(:empty?, '').freeze
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
SINGLE_QUOTED_STRING = /\A\s*'(.*)'\s*\z/m
|
SINGLE_QUOTED_STRING = /\A'(.*)'\z/m
|
||||||
DOUBLE_QUOTED_STRING = /\A\s*"(.*)"\s*\z/m
|
DOUBLE_QUOTED_STRING = /\A"(.*)"\z/m
|
||||||
INTEGERS_REGEX = /\A\s*(-?\d+)\s*\z/
|
INTEGERS_REGEX = /\A(-?\d+)\z/
|
||||||
FLOATS_REGEX = /\A\s*(-?\d[\d\.]+)\s*\z/
|
FLOATS_REGEX = /\A(-?\d[\d\.]+)\z/
|
||||||
|
RANGES_REGEX = /\A\((\S+)\.\.(\S+)\)\z/
|
||||||
# Use an atomic group (?>...) to avoid pathological backtracing from
|
|
||||||
# malicious input as described in https://github.com/Shopify/liquid/issues/1357
|
|
||||||
RANGES_REGEX = /\A\s*\(\s*(?>(\S+)\s*\.\.)\s*(\S+)\s*\)\s*\z/
|
|
||||||
|
|
||||||
def self.parse(markup)
|
def self.parse(markup)
|
||||||
case markup
|
if LITERALS.key?(markup)
|
||||||
when nil
|
LITERALS[markup]
|
||||||
nil
|
|
||||||
when SINGLE_QUOTED_STRING, DOUBLE_QUOTED_STRING
|
|
||||||
Regexp.last_match(1)
|
|
||||||
when INTEGERS_REGEX
|
|
||||||
Regexp.last_match(1).to_i
|
|
||||||
when RANGES_REGEX
|
|
||||||
RangeLookup.parse(Regexp.last_match(1), Regexp.last_match(2))
|
|
||||||
when FLOATS_REGEX
|
|
||||||
Regexp.last_match(1).to_f
|
|
||||||
else
|
else
|
||||||
markup = markup.strip
|
case markup
|
||||||
if LITERALS.key?(markup)
|
when SINGLE_QUOTED_STRING, DOUBLE_QUOTED_STRING
|
||||||
LITERALS[markup]
|
Regexp.last_match(1)
|
||||||
|
when INTEGERS_REGEX
|
||||||
|
Regexp.last_match(1).to_i
|
||||||
|
when RANGES_REGEX
|
||||||
|
RangeLookup.parse(Regexp.last_match(1), Regexp.last_match(2))
|
||||||
|
when FLOATS_REGEX
|
||||||
|
Regexp.last_match(1).to_f
|
||||||
else
|
else
|
||||||
VariableLookup.parse(markup)
|
VariableLookup.parse(markup)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9,12 +9,7 @@ module Liquid
|
|||||||
@index = 0
|
@index = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_reader :length, :parentloop
|
attr_reader :name, :length, :parentloop
|
||||||
|
|
||||||
def name
|
|
||||||
Usage.increment('forloop_drop_name')
|
|
||||||
@name
|
|
||||||
end
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@index + 1
|
@index + 1
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
tag_termination: "Tag '%{token}' was not properly terminated with regexp: %{tag_end}"
|
tag_termination: "Tag '%{token}' was not properly terminated with regexp: %{tag_end}"
|
||||||
variable_termination: "Variable '%{token}' was not properly terminated with regexp: %{tag_end}"
|
variable_termination: "Variable '%{token}' was not properly terminated with regexp: %{tag_end}"
|
||||||
tag_never_closed: "'%{block_name}' tag was never closed"
|
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"
|
table_row: "Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3"
|
||||||
render: "Syntax error in tag 'render' - Template name must be a quoted string"
|
render: "Syntax error in tag 'render' - Template name must be a quoted string"
|
||||||
argument:
|
argument:
|
||||||
|
|||||||
@@ -19,18 +19,6 @@ module Liquid
|
|||||||
@options[option_key]
|
@options[option_key]
|
||||||
end
|
end
|
||||||
|
|
||||||
def new_block_body
|
|
||||||
Liquid::BlockBody.new
|
|
||||||
end
|
|
||||||
|
|
||||||
def new_tokenizer(markup, start_line_number: nil, for_liquid_tag: false)
|
|
||||||
Tokenizer.new(markup, line_number: start_line_number, for_liquid_tag: for_liquid_tag)
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_expression(markup)
|
|
||||||
Expression.parse(markup)
|
|
||||||
end
|
|
||||||
|
|
||||||
def partial=(value)
|
def partial=(value)
|
||||||
@partial = value
|
@partial = value
|
||||||
@options = value ? partial_options : @template_options
|
@options = value ? partial_options : @template_options
|
||||||
|
|||||||
@@ -46,20 +46,16 @@ module Liquid
|
|||||||
tok[0] == type
|
tok[0] == type
|
||||||
end
|
end
|
||||||
|
|
||||||
|
SINGLE_TOKEN_EXPRESSION_TYPES = [:string, :number].freeze
|
||||||
|
private_constant :SINGLE_TOKEN_EXPRESSION_TYPES
|
||||||
|
|
||||||
def expression
|
def expression
|
||||||
token = @tokens[@p]
|
token = @tokens[@p]
|
||||||
case token[0]
|
if token[0] == :id
|
||||||
when :id
|
variable_signature
|
||||||
str = consume
|
elsif SINGLE_TOKEN_EXPRESSION_TYPES.include?(token[0])
|
||||||
str << variable_lookups
|
|
||||||
when :open_square
|
|
||||||
str = consume
|
|
||||||
str << expression
|
|
||||||
str << consume(:close_square)
|
|
||||||
str << variable_lookups
|
|
||||||
when :string, :number
|
|
||||||
consume
|
consume
|
||||||
when :open_round
|
elsif token.first == :open_round
|
||||||
consume
|
consume
|
||||||
first = expression
|
first = expression
|
||||||
consume(:dotdot)
|
consume(:dotdot)
|
||||||
@@ -82,19 +78,16 @@ module Liquid
|
|||||||
str
|
str
|
||||||
end
|
end
|
||||||
|
|
||||||
def variable_lookups
|
def variable_signature
|
||||||
str = +""
|
str = consume(:id)
|
||||||
loop do
|
while look(:open_square)
|
||||||
if look(:open_square)
|
str << consume
|
||||||
str << consume
|
str << expression
|
||||||
str << expression
|
str << consume(:close_square)
|
||||||
str << consume(:close_square)
|
end
|
||||||
elsif look(:dot)
|
if look(:dot)
|
||||||
str << consume
|
str << consume
|
||||||
str << consume(:id)
|
str << variable_signature
|
||||||
else
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
str
|
str
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,18 +2,6 @@
|
|||||||
|
|
||||||
module Liquid
|
module Liquid
|
||||||
module ParserSwitching
|
module ParserSwitching
|
||||||
def strict_parse_with_error_mode_fallback(markup)
|
|
||||||
strict_parse_with_error_context(markup)
|
|
||||||
rescue SyntaxError => e
|
|
||||||
case parse_context.error_mode
|
|
||||||
when :strict
|
|
||||||
raise
|
|
||||||
when :warn
|
|
||||||
parse_context.warnings << e
|
|
||||||
end
|
|
||||||
lax_parse(markup)
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_with_selected_parser(markup)
|
def parse_with_selected_parser(markup)
|
||||||
case parse_context.error_mode
|
case parse_context.error_mode
|
||||||
when :strict then strict_parse_with_error_context(markup)
|
when :strict then strict_parse_with_error_context(markup)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ module Liquid
|
|||||||
# node.code
|
# node.code
|
||||||
#
|
#
|
||||||
# # Which template and line number of this node.
|
# # Which template and line number of this node.
|
||||||
# # The top-level template name is `nil` by default, but can be set in the Liquid::Context before rendering.
|
# # If top level, this will be "<root>".
|
||||||
# node.partial
|
# node.partial
|
||||||
# node.line_number
|
# node.line_number
|
||||||
#
|
#
|
||||||
@@ -46,94 +46,126 @@ module Liquid
|
|||||||
include Enumerable
|
include Enumerable
|
||||||
|
|
||||||
class Timing
|
class Timing
|
||||||
attr_reader :code, :template_name, :line_number, :children
|
attr_reader :code, :partial, :line_number, :children, :total_time, :self_time
|
||||||
attr_accessor :total_time
|
|
||||||
alias_method :render_time, :total_time
|
|
||||||
alias_method :partial, :template_name
|
|
||||||
|
|
||||||
def initialize(code: nil, template_name: nil, line_number: nil)
|
def initialize(node, partial)
|
||||||
@code = code
|
@code = node.respond_to?(:raw) ? node.raw : node
|
||||||
@template_name = template_name
|
@partial = partial
|
||||||
@line_number = line_number
|
@line_number = node.respond_to?(:line_number) ? node.line_number : nil
|
||||||
@children = []
|
@children = []
|
||||||
end
|
end
|
||||||
|
|
||||||
def self_time
|
def self.start(node, partial)
|
||||||
@self_time ||= begin
|
new(node, partial).tap(&:start)
|
||||||
total_children_time = 0.0
|
end
|
||||||
|
|
||||||
|
def start
|
||||||
|
@start_time = Time.now
|
||||||
|
end
|
||||||
|
|
||||||
|
def finish
|
||||||
|
@end_time = Time.now
|
||||||
|
@total_time = @end_time - @start_time
|
||||||
|
|
||||||
|
if @children.empty?
|
||||||
|
@self_time = @total_time
|
||||||
|
else
|
||||||
|
total_children_time = 0
|
||||||
@children.each do |child|
|
@children.each do |child|
|
||||||
total_children_time += child.total_time
|
total_children_time += child.total_time
|
||||||
end
|
end
|
||||||
@total_time - total_children_time
|
@self_time = @total_time - total_children_time
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def render_time
|
||||||
|
@end_time - @start_time
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_reader :total_time
|
def self.profile_node_render(node)
|
||||||
alias_method :total_render_time, :total_time
|
if Profiler.current_profile && node.respond_to?(:render)
|
||||||
|
Profiler.current_profile.start_node(node)
|
||||||
def initialize
|
output = yield
|
||||||
@root_children = []
|
Profiler.current_profile.end_node(node)
|
||||||
@current_children = nil
|
output
|
||||||
@total_time = 0.0
|
|
||||||
end
|
|
||||||
|
|
||||||
def profile(template_name, &block)
|
|
||||||
# nested renders are done from a tag that already has a timing node
|
|
||||||
return yield if @current_children
|
|
||||||
|
|
||||||
root_children = @root_children
|
|
||||||
render_idx = root_children.length
|
|
||||||
begin
|
|
||||||
@current_children = root_children
|
|
||||||
profile_node(template_name, &block)
|
|
||||||
ensure
|
|
||||||
@current_children = nil
|
|
||||||
if (timing = root_children[render_idx])
|
|
||||||
@total_time += timing.total_time
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def children
|
|
||||||
children = @root_children
|
|
||||||
if children.length == 1
|
|
||||||
children.first.children
|
|
||||||
else
|
else
|
||||||
children
|
yield
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.profile_children(template_name)
|
||||||
|
if Profiler.current_profile
|
||||||
|
Profiler.current_profile.push_partial(template_name)
|
||||||
|
output = yield
|
||||||
|
Profiler.current_profile.pop_partial
|
||||||
|
output
|
||||||
|
else
|
||||||
|
yield
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.current_profile
|
||||||
|
Thread.current[:liquid_profiler]
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(partial_name = "<root>")
|
||||||
|
@partial_stack = [partial_name]
|
||||||
|
|
||||||
|
@root_timing = Timing.new("", current_partial)
|
||||||
|
@timing_stack = [@root_timing]
|
||||||
|
|
||||||
|
@render_start_at = Time.now
|
||||||
|
@render_end_at = @render_start_at
|
||||||
|
end
|
||||||
|
|
||||||
|
def start
|
||||||
|
Thread.current[:liquid_profiler] = self
|
||||||
|
@render_start_at = Time.now
|
||||||
|
end
|
||||||
|
|
||||||
|
def stop
|
||||||
|
Thread.current[:liquid_profiler] = nil
|
||||||
|
@render_end_at = Time.now
|
||||||
|
end
|
||||||
|
|
||||||
|
def total_render_time
|
||||||
|
@render_end_at - @render_start_at
|
||||||
|
end
|
||||||
|
|
||||||
def each(&block)
|
def each(&block)
|
||||||
children.each(&block)
|
@root_timing.children.each(&block)
|
||||||
end
|
end
|
||||||
|
|
||||||
def [](idx)
|
def [](idx)
|
||||||
children[idx]
|
@root_timing.children[idx]
|
||||||
end
|
end
|
||||||
|
|
||||||
def length
|
def length
|
||||||
children.length
|
@root_timing.children.length
|
||||||
end
|
end
|
||||||
|
|
||||||
def profile_node(template_name, code: nil, line_number: nil)
|
def start_node(node)
|
||||||
timing = Timing.new(code: code, template_name: template_name, line_number: line_number)
|
@timing_stack.push(Timing.start(node, current_partial))
|
||||||
parent_children = @current_children
|
|
||||||
start_time = monotonic_time
|
|
||||||
begin
|
|
||||||
@current_children = timing.children
|
|
||||||
yield
|
|
||||||
ensure
|
|
||||||
@current_children = parent_children
|
|
||||||
timing.total_time = monotonic_time - start_time
|
|
||||||
parent_children << timing
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
def end_node(_node)
|
||||||
|
timing = @timing_stack.pop
|
||||||
|
timing.finish
|
||||||
|
|
||||||
def monotonic_time
|
@timing_stack.last.children << timing
|
||||||
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
end
|
||||||
|
|
||||||
|
def current_partial
|
||||||
|
@partial_stack.last
|
||||||
|
end
|
||||||
|
|
||||||
|
def push_partial(partial_name)
|
||||||
|
@partial_stack.push(partial_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def pop_partial
|
||||||
|
@partial_stack.pop
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,35 +1,25 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module Liquid
|
module Liquid
|
||||||
module BlockBodyProfilingHook
|
class BlockBody
|
||||||
def render_node(context, output, node)
|
def render_node_with_profiling(context, output, node)
|
||||||
if (profiler = context.profiler)
|
Profiler.profile_node_render(node) do
|
||||||
profiler.profile_node(context.template_name, code: node.raw, line_number: node.line_number) do
|
render_node_without_profiling(context, output, node)
|
||||||
super
|
|
||||||
end
|
|
||||||
else
|
|
||||||
super
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
BlockBody.prepend(BlockBodyProfilingHook)
|
|
||||||
|
|
||||||
module DocumentProfilingHook
|
alias_method :render_node_without_profiling, :render_node
|
||||||
def render_to_output_buffer(context, output)
|
alias_method :render_node, :render_node_with_profiling
|
||||||
return super unless context.profiler
|
end
|
||||||
context.profiler.profile(context.template_name) { super }
|
|
||||||
|
class Include < Tag
|
||||||
|
def render_to_output_buffer_with_profiling(context, output)
|
||||||
|
Profiler.profile_children(context.evaluate(@template_name_expr).to_s) do
|
||||||
|
render_to_output_buffer_without_profiling(context, output)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
Document.prepend(DocumentProfilingHook)
|
|
||||||
|
|
||||||
module ContextProfilingHook
|
alias_method :render_to_output_buffer_without_profiling, :render_to_output_buffer
|
||||||
attr_accessor :profiler
|
alias_method :render_to_output_buffer, :render_to_output_buffer_with_profiling
|
||||||
|
|
||||||
def new_isolated_subcontext
|
|
||||||
new_context = super
|
|
||||||
new_context.profiler = profiler
|
|
||||||
new_context
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
Context.prepend(ContextProfilingHook)
|
|
||||||
end
|
end
|
||||||
|
|||||||
32
lib/liquid/registers/disabled_tags.rb
Normal file
32
lib/liquid/registers/disabled_tags.rb
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
module Liquid
|
||||||
|
class DisabledTags < Register
|
||||||
|
def initialize
|
||||||
|
@disabled_tags = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def disabled?(tag)
|
||||||
|
@disabled_tags.key?(tag) && @disabled_tags[tag] > 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def disable(tags)
|
||||||
|
tags.each(&method(:increment))
|
||||||
|
yield
|
||||||
|
ensure
|
||||||
|
tags.each(&method(:decrement))
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def increment(tag)
|
||||||
|
@disabled_tags[tag] ||= 0
|
||||||
|
@disabled_tags[tag] += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def decrement(tag)
|
||||||
|
@disabled_tags[tag] -= 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Template.add_register(:disabled_tags, DisabledTags.new)
|
||||||
|
end
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
module Liquid
|
module Liquid
|
||||||
class ResourceLimits
|
class ResourceLimits
|
||||||
attr_accessor :render_length_limit, :render_score_limit, :assign_score_limit
|
attr_accessor :render_length, :render_score, :assign_score,
|
||||||
attr_reader :render_score, :assign_score
|
:render_length_limit, :render_score_limit, :assign_score_limit
|
||||||
|
|
||||||
def initialize(limits)
|
def initialize(limits)
|
||||||
@render_length_limit = limits[:render_length_limit]
|
@render_length_limit = limits[:render_length_limit]
|
||||||
@@ -12,51 +12,14 @@ module Liquid
|
|||||||
reset
|
reset
|
||||||
end
|
end
|
||||||
|
|
||||||
def increment_render_score(amount)
|
|
||||||
@render_score += amount
|
|
||||||
raise_limits_reached if @render_score_limit && @render_score > @render_score_limit
|
|
||||||
end
|
|
||||||
|
|
||||||
def increment_assign_score(amount)
|
|
||||||
@assign_score += amount
|
|
||||||
raise_limits_reached if @assign_score_limit && @assign_score > @assign_score_limit
|
|
||||||
end
|
|
||||||
|
|
||||||
# update either render_length or assign_score based on whether or not the writes are captured
|
|
||||||
def increment_write_score(output)
|
|
||||||
if (last_captured = @last_capture_length)
|
|
||||||
captured = output.bytesize
|
|
||||||
increment = captured - last_captured
|
|
||||||
@last_capture_length = captured
|
|
||||||
increment_assign_score(increment)
|
|
||||||
elsif @render_length_limit && output.bytesize > @render_length_limit
|
|
||||||
raise_limits_reached
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def raise_limits_reached
|
|
||||||
@reached_limit = true
|
|
||||||
raise MemoryError, "Memory limits exceeded"
|
|
||||||
end
|
|
||||||
|
|
||||||
def reached?
|
def reached?
|
||||||
@reached_limit
|
(@render_length_limit && @render_length > @render_length_limit) ||
|
||||||
|
(@render_score_limit && @render_score > @render_score_limit) ||
|
||||||
|
(@assign_score_limit && @assign_score > @assign_score_limit)
|
||||||
end
|
end
|
||||||
|
|
||||||
def reset
|
def reset
|
||||||
@reached_limit = false
|
@render_length = @render_score = @assign_score = 0
|
||||||
@last_capture_length = nil
|
|
||||||
@render_score = @assign_score = 0
|
|
||||||
end
|
|
||||||
|
|
||||||
def with_capture
|
|
||||||
old_capture_length = @last_capture_length
|
|
||||||
begin
|
|
||||||
@last_capture_length = 0
|
|
||||||
yield
|
|
||||||
ensure
|
|
||||||
@last_capture_length = old_capture_length
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -89,15 +89,13 @@ module Liquid
|
|||||||
|
|
||||||
def truncatewords(input, words = 15, truncate_string = "...")
|
def truncatewords(input, words = 15, truncate_string = "...")
|
||||||
return if input.nil?
|
return if input.nil?
|
||||||
input = input.to_s
|
wordlist = input.to_s.split
|
||||||
words = Utils.to_integer(words)
|
words = Utils.to_integer(words)
|
||||||
words = 1 if words <= 0
|
|
||||||
|
|
||||||
wordlist = input.split(" ", words + 1)
|
l = words - 1
|
||||||
return input if wordlist.length <= words
|
l = 0 if l < 0
|
||||||
|
|
||||||
wordlist.pop
|
wordlist.length > l ? wordlist[0..l].join(" ").concat(truncate_string.to_s) : input
|
||||||
wordlist.join(" ").concat(truncate_string.to_s)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Split input string into an array of substrings separated by given pattern.
|
# Split input string into an array of substrings separated by given pattern.
|
||||||
@@ -297,7 +295,7 @@ module Liquid
|
|||||||
|
|
||||||
# Add <br /> tags in front of all newlines in input string
|
# Add <br /> tags in front of all newlines in input string
|
||||||
def newline_to_br(input)
|
def newline_to_br(input)
|
||||||
input.to_s.gsub(/\r?\n/, "<br />\n")
|
input.to_s.gsub(/\n/, "<br />\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
# Reformat a date using Ruby's core Time#strftime( string ) -> string
|
# Reformat a date using Ruby's core Time#strftime( string ) -> string
|
||||||
|
|||||||
@@ -13,13 +13,15 @@ module Liquid
|
|||||||
tag
|
tag
|
||||||
end
|
end
|
||||||
|
|
||||||
def disable_tags(*tag_names)
|
def disable_tags(*tags)
|
||||||
@disabled_tags ||= []
|
disabled_tags.push(*tags)
|
||||||
@disabled_tags.concat(tag_names)
|
|
||||||
prepend(Disabler)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private :new
|
private :new
|
||||||
|
|
||||||
|
def disabled_tags
|
||||||
|
@disabled_tags ||= []
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(tag_name, markup, parse_context)
|
def initialize(tag_name, markup, parse_context)
|
||||||
@@ -44,6 +46,14 @@ module Liquid
|
|||||||
''
|
''
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def disabled?(context)
|
||||||
|
context.registers[:disabled_tags].disabled?(tag_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def disabled_error_message
|
||||||
|
"#{tag_name} #{options[:locale].t('errors.disabled.tag')}"
|
||||||
|
end
|
||||||
|
|
||||||
# For backwards compatibility with custom tags. In a future release, the semantics
|
# For backwards compatibility with custom tags. In a future release, the semantics
|
||||||
# of the `render_to_output_buffer` method will become the default and the `render`
|
# of the `render_to_output_buffer` method will become the default and the `render`
|
||||||
# method will be removed.
|
# method will be removed.
|
||||||
@@ -56,10 +66,8 @@ module Liquid
|
|||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
def disabled_tags
|
||||||
|
self.class.disabled_tags
|
||||||
def parse_expression(markup)
|
|
||||||
parse_context.parse_expression(markup)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module Liquid
|
|
||||||
class Tag
|
|
||||||
module Disableable
|
|
||||||
def render_to_output_buffer(context, output)
|
|
||||||
if context.tag_disabled?(tag_name)
|
|
||||||
output << disabled_error(context)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
def disabled_error(context)
|
|
||||||
# raise then rescue the exception so that the Context#exception_renderer can re-raise it
|
|
||||||
raise DisabledError, "#{tag_name} #{parse_context[:locale].t('errors.disabled.tag')}"
|
|
||||||
rescue DisabledError => exc
|
|
||||||
context.handle_error(exc, line_number)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module Liquid
|
|
||||||
class Tag
|
|
||||||
module Disabler
|
|
||||||
module ClassMethods
|
|
||||||
attr_reader :disabled_tags
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.prepended(base)
|
|
||||||
base.extend(ClassMethods)
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_to_output_buffer(context, output)
|
|
||||||
context.with_disabled_tags(self.class.disabled_tags) do
|
|
||||||
super
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -12,27 +12,22 @@ module Liquid
|
|||||||
class Assign < Tag
|
class Assign < Tag
|
||||||
Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om
|
Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om
|
||||||
|
|
||||||
# @api private
|
|
||||||
def self.raise_syntax_error(parse_context)
|
|
||||||
raise Liquid::SyntaxError, parse_context.locale.t('errors.syntax.assign')
|
|
||||||
end
|
|
||||||
|
|
||||||
attr_reader :to, :from
|
attr_reader :to, :from
|
||||||
|
|
||||||
def initialize(tag_name, markup, parse_context)
|
def initialize(tag_name, markup, options)
|
||||||
super
|
super
|
||||||
if markup =~ Syntax
|
if markup =~ Syntax
|
||||||
@to = Regexp.last_match(1)
|
@to = Regexp.last_match(1)
|
||||||
@from = Variable.new(Regexp.last_match(2), parse_context)
|
@from = Variable.new(Regexp.last_match(2), options)
|
||||||
else
|
else
|
||||||
self.class.raise_syntax_error(parse_context)
|
raise SyntaxError, options[:locale].t('errors.syntax.assign')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_to_output_buffer(context, output)
|
def render_to_output_buffer(context, output)
|
||||||
val = @from.render(context)
|
val = @from.render(context)
|
||||||
context.scopes.last[@to] = val
|
context.scopes.last[@to] = val
|
||||||
context.resource_limits.increment_assign_score(assign_score_of(val))
|
context.resource_limits.assign_score += assign_score_of(val)
|
||||||
output
|
output
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -45,18 +40,11 @@ module Liquid
|
|||||||
def assign_score_of(val)
|
def assign_score_of(val)
|
||||||
if val.instance_of?(String)
|
if val.instance_of?(String)
|
||||||
val.bytesize
|
val.bytesize
|
||||||
elsif val.instance_of?(Array)
|
elsif val.instance_of?(Array) || val.instance_of?(Hash)
|
||||||
sum = 1
|
sum = 1
|
||||||
# Uses #each to avoid extra allocations.
|
# Uses #each to avoid extra allocations.
|
||||||
val.each { |child| sum += assign_score_of(child) }
|
val.each { |child| sum += assign_score_of(child) }
|
||||||
sum
|
sum
|
||||||
elsif val.instance_of?(Hash)
|
|
||||||
sum = 1
|
|
||||||
val.each do |key, entry_value|
|
|
||||||
sum += assign_score_of(key)
|
|
||||||
sum += assign_score_of(entry_value)
|
|
||||||
end
|
|
||||||
sum
|
|
||||||
else
|
else
|
||||||
1
|
1
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -11,11 +11,8 @@ module Liquid
|
|||||||
# {% endfor %}
|
# {% endfor %}
|
||||||
#
|
#
|
||||||
class Break < Tag
|
class Break < Tag
|
||||||
INTERRUPT = BreakInterrupt.new.freeze
|
def interrupt
|
||||||
|
BreakInterrupt.new
|
||||||
def render_to_output_buffer(context, output)
|
|
||||||
context.push_interrupt(INTERRUPT)
|
|
||||||
output
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -25,10 +25,10 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def render_to_output_buffer(context, output)
|
def render_to_output_buffer(context, output)
|
||||||
context.resource_limits.with_capture do
|
previous_output_size = output.bytesize
|
||||||
capture_output = render(context)
|
super
|
||||||
context.scopes.last[@to] = capture_output
|
context.scopes.last[@to] = output
|
||||||
end
|
context.resource_limits.assign_score += (output.bytesize - previous_output_size)
|
||||||
output
|
output
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -12,23 +12,15 @@ module Liquid
|
|||||||
@blocks = []
|
@blocks = []
|
||||||
|
|
||||||
if markup =~ Syntax
|
if markup =~ Syntax
|
||||||
@left = parse_expression(Regexp.last_match(1))
|
@left = Expression.parse(Regexp.last_match(1))
|
||||||
else
|
else
|
||||||
raise SyntaxError, options[:locale].t("errors.syntax.case")
|
raise SyntaxError, options[:locale].t("errors.syntax.case")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse(tokens)
|
def parse(tokens)
|
||||||
body = case_body = new_body
|
body = BlockBody.new
|
||||||
body = @blocks.last.attachment while parse_body(body, tokens)
|
body = @blocks.last.attachment while parse_body(body, tokens)
|
||||||
@blocks.reverse_each do |condition|
|
|
||||||
body = condition.attachment
|
|
||||||
unless body.frozen?
|
|
||||||
body.remove_blank_strings if blank?
|
|
||||||
body.freeze
|
|
||||||
end
|
|
||||||
end
|
|
||||||
case_body.freeze
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def nodelist
|
def nodelist
|
||||||
@@ -64,7 +56,7 @@ module Liquid
|
|||||||
private
|
private
|
||||||
|
|
||||||
def record_when_condition(markup)
|
def record_when_condition(markup)
|
||||||
body = new_body
|
body = BlockBody.new
|
||||||
|
|
||||||
while markup
|
while markup
|
||||||
unless markup =~ WhenSyntax
|
unless markup =~ WhenSyntax
|
||||||
@@ -73,7 +65,7 @@ module Liquid
|
|||||||
|
|
||||||
markup = Regexp.last_match(2)
|
markup = Regexp.last_match(2)
|
||||||
|
|
||||||
block = Condition.new(@left, '==', Condition.parse_expression(parse_context, Regexp.last_match(1)))
|
block = Condition.new(@left, '==', Expression.parse(Regexp.last_match(1)))
|
||||||
block.attach(body)
|
block.attach(body)
|
||||||
@blocks << block
|
@blocks << block
|
||||||
end
|
end
|
||||||
@@ -85,7 +77,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
block = ElseCondition.new
|
block = ElseCondition.new
|
||||||
block.attach(new_body)
|
block.attach(BlockBody.new)
|
||||||
@blocks << block
|
@blocks << block
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -11,11 +11,8 @@ module Liquid
|
|||||||
# {% endfor %}
|
# {% endfor %}
|
||||||
#
|
#
|
||||||
class Continue < Tag
|
class Continue < Tag
|
||||||
INTERRUPT = ContinueInterrupt.new.freeze
|
def interrupt
|
||||||
|
ContinueInterrupt.new
|
||||||
def render_to_output_buffer(context, output)
|
|
||||||
context.push_interrupt(INTERRUPT)
|
|
||||||
output
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ module Liquid
|
|||||||
case markup
|
case markup
|
||||||
when NamedSyntax
|
when NamedSyntax
|
||||||
@variables = variables_from_string(Regexp.last_match(2))
|
@variables = variables_from_string(Regexp.last_match(2))
|
||||||
@name = parse_expression(Regexp.last_match(1))
|
@name = Expression.parse(Regexp.last_match(1))
|
||||||
when SimpleSyntax
|
when SimpleSyntax
|
||||||
@variables = variables_from_string(markup)
|
@variables = variables_from_string(markup)
|
||||||
@name = @variables.to_s
|
@name = @variables.to_s
|
||||||
@@ -61,7 +61,7 @@ module Liquid
|
|||||||
def variables_from_string(markup)
|
def variables_from_string(markup)
|
||||||
markup.split(',').collect do |var|
|
markup.split(',').collect do |var|
|
||||||
var =~ /\s*(#{QuotedFragment})\s*/o
|
var =~ /\s*(#{QuotedFragment})\s*/o
|
||||||
Regexp.last_match(1) ? parse_expression(Regexp.last_match(1)) : nil
|
Regexp.last_match(1) ? Expression.parse(Regexp.last_match(1)) : nil
|
||||||
end.compact
|
end.compact
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ module Liquid
|
|||||||
# {% echo user | link %}
|
# {% echo user | link %}
|
||||||
#
|
#
|
||||||
class Echo < Tag
|
class Echo < Tag
|
||||||
attr_reader :variable
|
|
||||||
|
|
||||||
def initialize(tag_name, markup, parse_context)
|
def initialize(tag_name, markup, parse_context)
|
||||||
super
|
super
|
||||||
@variable = Variable.new(markup, parse_context)
|
@variable = Variable.new(markup, parse_context)
|
||||||
@@ -22,12 +20,6 @@ module Liquid
|
|||||||
def render(context)
|
def render(context)
|
||||||
@variable.render_to_output_buffer(context, +'')
|
@variable.render_to_output_buffer(context, +'')
|
||||||
end
|
end
|
||||||
|
|
||||||
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
|
||||||
def children
|
|
||||||
[@node.variable]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('echo', Echo)
|
Template.register_tag('echo', Echo)
|
||||||
|
|||||||
@@ -54,20 +54,13 @@ module Liquid
|
|||||||
super
|
super
|
||||||
@from = @limit = nil
|
@from = @limit = nil
|
||||||
parse_with_selected_parser(markup)
|
parse_with_selected_parser(markup)
|
||||||
@for_block = new_body
|
@for_block = BlockBody.new
|
||||||
@else_block = nil
|
@else_block = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse(tokens)
|
def parse(tokens)
|
||||||
if parse_body(@for_block, tokens)
|
return unless parse_body(@for_block, tokens)
|
||||||
parse_body(@else_block, tokens)
|
parse_body(@else_block, tokens)
|
||||||
end
|
|
||||||
if blank?
|
|
||||||
@else_block&.remove_blank_strings
|
|
||||||
@for_block.remove_blank_strings
|
|
||||||
end
|
|
||||||
@else_block&.freeze
|
|
||||||
@for_block.freeze
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def nodelist
|
def nodelist
|
||||||
@@ -76,7 +69,7 @@ module Liquid
|
|||||||
|
|
||||||
def unknown_tag(tag, markup, tokens)
|
def unknown_tag(tag, markup, tokens)
|
||||||
return super unless tag == 'else'
|
return super unless tag == 'else'
|
||||||
@else_block = new_body
|
@else_block = BlockBody.new
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_to_output_buffer(context, output)
|
def render_to_output_buffer(context, output)
|
||||||
@@ -99,7 +92,7 @@ module Liquid
|
|||||||
collection_name = Regexp.last_match(2)
|
collection_name = Regexp.last_match(2)
|
||||||
@reversed = !!Regexp.last_match(3)
|
@reversed = !!Regexp.last_match(3)
|
||||||
@name = "#{@variable_name}-#{collection_name}"
|
@name = "#{@variable_name}-#{collection_name}"
|
||||||
@collection_name = parse_expression(collection_name)
|
@collection_name = Expression.parse(collection_name)
|
||||||
markup.scan(TagAttributes) do |key, value|
|
markup.scan(TagAttributes) do |key, value|
|
||||||
set_attribute(key, value)
|
set_attribute(key, value)
|
||||||
end
|
end
|
||||||
@@ -114,7 +107,7 @@ module Liquid
|
|||||||
raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_in") unless p.id?('in')
|
raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_in") unless p.id?('in')
|
||||||
|
|
||||||
collection_name = p.expression
|
collection_name = p.expression
|
||||||
@collection_name = parse_expression(collection_name)
|
@collection_name = Expression.parse(collection_name)
|
||||||
|
|
||||||
@name = "#{@variable_name}-#{collection_name}"
|
@name = "#{@variable_name}-#{collection_name}"
|
||||||
@reversed = p.id?('reversed')
|
@reversed = p.id?('reversed')
|
||||||
@@ -198,13 +191,12 @@ module Liquid
|
|||||||
case key
|
case key
|
||||||
when 'offset'
|
when 'offset'
|
||||||
@from = if expr == 'continue'
|
@from = if expr == 'continue'
|
||||||
Usage.increment('for_offset_continue')
|
|
||||||
:continue
|
:continue
|
||||||
else
|
else
|
||||||
parse_expression(expr)
|
Expression.parse(expr)
|
||||||
end
|
end
|
||||||
when 'limit'
|
when 'limit'
|
||||||
@limit = parse_expression(expr)
|
@limit = Expression.parse(expr)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -31,17 +31,10 @@ module Liquid
|
|||||||
def parse(tokens)
|
def parse(tokens)
|
||||||
while parse_body(@blocks.last.attachment, tokens)
|
while parse_body(@blocks.last.attachment, tokens)
|
||||||
end
|
end
|
||||||
@blocks.reverse_each do |block|
|
|
||||||
block.attachment.remove_blank_strings if blank?
|
|
||||||
block.attachment.freeze
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
ELSE_TAG_NAMES = ['elsif', 'else'].freeze
|
|
||||||
private_constant :ELSE_TAG_NAMES
|
|
||||||
|
|
||||||
def unknown_tag(tag, markup, tokens)
|
def unknown_tag(tag, markup, tokens)
|
||||||
if ELSE_TAG_NAMES.include?(tag)
|
if ['elsif', 'else'].include?(tag)
|
||||||
push_block(tag, markup)
|
push_block(tag, markup)
|
||||||
else
|
else
|
||||||
super
|
super
|
||||||
@@ -68,25 +61,21 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
@blocks.push(block)
|
@blocks.push(block)
|
||||||
block.attach(new_body)
|
block.attach(BlockBody.new)
|
||||||
end
|
|
||||||
|
|
||||||
def parse_expression(markup)
|
|
||||||
Condition.parse_expression(parse_context, markup)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def lax_parse(markup)
|
def lax_parse(markup)
|
||||||
expressions = markup.scan(ExpressionsAndOperators)
|
expressions = markup.scan(ExpressionsAndOperators)
|
||||||
raise SyntaxError, options[:locale].t("errors.syntax.if") unless expressions.pop =~ Syntax
|
raise SyntaxError, options[:locale].t("errors.syntax.if") unless expressions.pop =~ Syntax
|
||||||
|
|
||||||
condition = Condition.new(parse_expression(Regexp.last_match(1)), Regexp.last_match(2), parse_expression(Regexp.last_match(3)))
|
condition = Condition.new(Expression.parse(Regexp.last_match(1)), Regexp.last_match(2), Expression.parse(Regexp.last_match(3)))
|
||||||
|
|
||||||
until expressions.empty?
|
until expressions.empty?
|
||||||
operator = expressions.pop.to_s.strip
|
operator = expressions.pop.to_s.strip
|
||||||
|
|
||||||
raise SyntaxError, options[:locale].t("errors.syntax.if") unless expressions.pop.to_s =~ Syntax
|
raise SyntaxError, options[:locale].t("errors.syntax.if") unless expressions.pop.to_s =~ Syntax
|
||||||
|
|
||||||
new_condition = Condition.new(parse_expression(Regexp.last_match(1)), Regexp.last_match(2), parse_expression(Regexp.last_match(3)))
|
new_condition = Condition.new(Expression.parse(Regexp.last_match(1)), Regexp.last_match(2), Expression.parse(Regexp.last_match(3)))
|
||||||
raise SyntaxError, options[:locale].t("errors.syntax.if") unless BOOLEAN_OPERATORS.include?(operator)
|
raise SyntaxError, options[:locale].t("errors.syntax.if") unless BOOLEAN_OPERATORS.include?(operator)
|
||||||
new_condition.send(operator, condition)
|
new_condition.send(operator, condition)
|
||||||
condition = new_condition
|
condition = new_condition
|
||||||
@@ -114,9 +103,9 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def parse_comparison(p)
|
def parse_comparison(p)
|
||||||
a = parse_expression(p.expression)
|
a = Expression.parse(p.expression)
|
||||||
if (op = p.consume?(:comparison))
|
if (op = p.consume?(:comparison))
|
||||||
b = parse_expression(p.expression)
|
b = Expression.parse(p.expression)
|
||||||
Condition.new(a, op, b)
|
Condition.new(a, op, b)
|
||||||
else
|
else
|
||||||
Condition.new(a)
|
Condition.new(a)
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ module Liquid
|
|||||||
# {% include 'product' for products %}
|
# {% include 'product' for products %}
|
||||||
#
|
#
|
||||||
class Include < Tag
|
class Include < Tag
|
||||||
prepend Tag::Disableable
|
|
||||||
|
|
||||||
SYNTAX = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o
|
SYNTAX = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o
|
||||||
Syntax = SYNTAX
|
Syntax = SYNTAX
|
||||||
|
|
||||||
@@ -32,12 +30,12 @@ module Liquid
|
|||||||
variable_name = Regexp.last_match(3)
|
variable_name = Regexp.last_match(3)
|
||||||
|
|
||||||
@alias_name = Regexp.last_match(5)
|
@alias_name = Regexp.last_match(5)
|
||||||
@variable_name_expr = variable_name ? parse_expression(variable_name) : nil
|
@variable_name_expr = variable_name ? Expression.parse(variable_name) : nil
|
||||||
@template_name_expr = parse_expression(template_name)
|
@template_name_expr = Expression.parse(template_name)
|
||||||
@attributes = {}
|
@attributes = {}
|
||||||
|
|
||||||
markup.scan(TagAttributes) do |key, value|
|
markup.scan(TagAttributes) do |key, value|
|
||||||
@attributes[key] = parse_expression(value)
|
@attributes[key] = Expression.parse(value)
|
||||||
end
|
end
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -14,14 +14,14 @@ module Liquid
|
|||||||
def parse(tokens)
|
def parse(tokens)
|
||||||
@body = +''
|
@body = +''
|
||||||
while (token = tokens.shift)
|
while (token = tokens.shift)
|
||||||
if token =~ FullTokenPossiblyInvalid && block_delimiter == Regexp.last_match(2)
|
if token =~ FullTokenPossiblyInvalid
|
||||||
@body << Regexp.last_match(1) if Regexp.last_match(1) != ""
|
@body << Regexp.last_match(1) if Regexp.last_match(1) != ""
|
||||||
return
|
return if block_delimiter == Regexp.last_match(2)
|
||||||
end
|
end
|
||||||
@body << token unless token.empty?
|
@body << token unless token.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
raise_tag_never_closed(block_name)
|
raise SyntaxError, parse_context.locale.t("errors.syntax.tag_never_closed", block_name: block_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_to_output_buffer(_context, output)
|
def render_to_output_buffer(_context, output)
|
||||||
|
|||||||
@@ -19,13 +19,13 @@ module Liquid
|
|||||||
variable_name = Regexp.last_match(4)
|
variable_name = Regexp.last_match(4)
|
||||||
|
|
||||||
@alias_name = Regexp.last_match(6)
|
@alias_name = Regexp.last_match(6)
|
||||||
@variable_name_expr = variable_name ? parse_expression(variable_name) : nil
|
@variable_name_expr = variable_name ? Expression.parse(variable_name) : nil
|
||||||
@template_name_expr = parse_expression(template_name)
|
@template_name_expr = Expression.parse(template_name)
|
||||||
@for = (with_or_for == FOR)
|
@for = (with_or_for == FOR)
|
||||||
|
|
||||||
@attributes = {}
|
@attributes = {}
|
||||||
markup.scan(TagAttributes) do |key, value|
|
markup.scan(TagAttributes) do |key, value|
|
||||||
@attributes[key] = parse_expression(value)
|
@attributes[key] = Expression.parse(value)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ module Liquid
|
|||||||
super
|
super
|
||||||
if markup =~ Syntax
|
if markup =~ Syntax
|
||||||
@variable_name = Regexp.last_match(1)
|
@variable_name = Regexp.last_match(1)
|
||||||
@collection_name = parse_expression(Regexp.last_match(2))
|
@collection_name = Expression.parse(Regexp.last_match(2))
|
||||||
@attributes = {}
|
@attributes = {}
|
||||||
markup.scan(TagAttributes) do |key, value|
|
markup.scan(TagAttributes) do |key, value|
|
||||||
@attributes[key] = parse_expression(value)
|
@attributes[key] = Expression.parse(value)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
raise SyntaxError, options[:locale].t("errors.syntax.table_row")
|
raise SyntaxError, options[:locale].t("errors.syntax.table_row")
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ module Liquid
|
|||||||
#
|
#
|
||||||
class Template
|
class Template
|
||||||
attr_accessor :root
|
attr_accessor :root
|
||||||
attr_reader :resource_limits, :warnings
|
attr_reader :warnings
|
||||||
|
|
||||||
class TagRegistry
|
class TagRegistry
|
||||||
include Enumerable
|
include Enumerable
|
||||||
@@ -80,6 +80,14 @@ module Liquid
|
|||||||
tags[name.to_s] = klass
|
tags[name.to_s] = klass
|
||||||
end
|
end
|
||||||
|
|
||||||
|
attr_accessor :registers
|
||||||
|
Template.registers = {}
|
||||||
|
private :registers=
|
||||||
|
|
||||||
|
def add_register(name, klass)
|
||||||
|
registers[name.to_sym] = klass
|
||||||
|
end
|
||||||
|
|
||||||
# Pass a module with filter methods which should be available
|
# Pass a module with filter methods which should be available
|
||||||
# to all liquid views. Good for registering the standard library
|
# to all liquid views. Good for registering the standard library
|
||||||
def register_filter(mod)
|
def register_filter(mod)
|
||||||
@@ -98,36 +106,18 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize
|
|
||||||
@rethrow_errors = false
|
|
||||||
@resource_limits = ResourceLimits.new(Template.default_resource_limits)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Parse source code.
|
# Parse source code.
|
||||||
# Returns self for easy chaining
|
# Returns self for easy chaining
|
||||||
def parse(source, options = {})
|
def parse(source, options = {})
|
||||||
parse_context = configure_options(options)
|
@options = options
|
||||||
tokenizer = parse_context.new_tokenizer(source, start_line_number: @line_numbers && 1)
|
@profiling = options[:profile]
|
||||||
@root = Document.parse(tokenizer, parse_context)
|
@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
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
def registers
|
|
||||||
@registers ||= {}
|
|
||||||
end
|
|
||||||
|
|
||||||
def assigns
|
|
||||||
@assigns ||= {}
|
|
||||||
end
|
|
||||||
|
|
||||||
def instance_assigns
|
|
||||||
@instance_assigns ||= {}
|
|
||||||
end
|
|
||||||
|
|
||||||
def errors
|
|
||||||
@errors ||= []
|
|
||||||
end
|
|
||||||
|
|
||||||
# Render takes a hash with local variables.
|
# Render takes a hash with local variables.
|
||||||
#
|
#
|
||||||
# if you use the same filters over and over again consider registering them globally
|
# if you use the same filters over and over again consider registering them globally
|
||||||
@@ -142,37 +132,18 @@ module Liquid
|
|||||||
# * <tt>registers</tt> : hash with register variables. Those can be accessed from
|
# * <tt>registers</tt> : hash with register variables. Those can be accessed from
|
||||||
# filters and tags and might be useful to integrate liquid more with its host application
|
# filters and tags and might be useful to integrate liquid more with its host application
|
||||||
#
|
#
|
||||||
def render(*args)
|
def render(assigns_or_context = nil, options = nil)
|
||||||
return '' if @root.nil?
|
return '' if @root.nil?
|
||||||
|
|
||||||
context = case args.first
|
context = coerce_context(assigns_or_context)
|
||||||
when Liquid::Context
|
|
||||||
c = args.shift
|
|
||||||
|
|
||||||
if @rethrow_errors
|
|
||||||
c.exception_renderer = Liquid::RAISE_EXCEPTION_LAMBDA
|
|
||||||
end
|
|
||||||
|
|
||||||
c
|
|
||||||
when Liquid::Drop
|
|
||||||
drop = args.shift
|
|
||||||
drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
|
|
||||||
when Hash
|
|
||||||
Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
|
|
||||||
when nil
|
|
||||||
Context.new(assigns, instance_assigns, registers, @rethrow_errors, @resource_limits)
|
|
||||||
else
|
|
||||||
raise ArgumentError, "Expected Hash or Liquid::Context as parameter"
|
|
||||||
end
|
|
||||||
|
|
||||||
output = nil
|
output = nil
|
||||||
|
|
||||||
context_register = context.registers.is_a?(StaticRegisters) ? context.registers.static : context.registers
|
context_register = context.registers.is_a?(StaticRegisters) ? context.registers.static : context.registers
|
||||||
|
|
||||||
case args.last
|
case options
|
||||||
when Hash
|
when Hash
|
||||||
options = args.pop
|
output = options[:output] if options[:output]
|
||||||
output = options[:output] if options[:output]
|
|
||||||
|
|
||||||
options[:registers]&.each do |key, register|
|
options[:registers]&.each do |key, register|
|
||||||
context_register[key] = register
|
context_register[key] = register
|
||||||
@@ -180,29 +151,33 @@ module Liquid
|
|||||||
|
|
||||||
apply_options_to_context(context, options)
|
apply_options_to_context(context, options)
|
||||||
when Module, Array
|
when Module, Array
|
||||||
context.add_filters(args.pop)
|
context.add_filters(options)
|
||||||
|
end
|
||||||
|
|
||||||
|
Template.registers.each do |key, register|
|
||||||
|
context_register[key] = register unless context_register.key?(key)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Retrying a render resets resource usage
|
# Retrying a render resets resource usage
|
||||||
context.resource_limits.reset
|
context.resource_limits.reset
|
||||||
|
|
||||||
if @profiling && context.profiler.nil?
|
|
||||||
@profiler = context.profiler = Liquid::Profiler.new
|
|
||||||
end
|
|
||||||
|
|
||||||
begin
|
begin
|
||||||
# render the nodelist.
|
# render the nodelist.
|
||||||
@root.render_to_output_buffer(context, output || +'')
|
# for performance reasons we get an array back here. join will make a string out of it.
|
||||||
|
with_profiling(context) do
|
||||||
|
@root.render_to_output_buffer(context, output || +'')
|
||||||
|
end
|
||||||
rescue Liquid::MemoryError => e
|
rescue Liquid::MemoryError => e
|
||||||
context.handle_error(e)
|
context.handle_error(e)
|
||||||
ensure
|
|
||||||
@errors = context.errors
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def render!(*args)
|
def render!(assigns_or_context = nil, options = nil)
|
||||||
@rethrow_errors = true
|
context = coerce_context(assigns_or_context)
|
||||||
render(*args)
|
# rethrow errors
|
||||||
|
context.exception_renderer = ->(_e) { raise }
|
||||||
|
|
||||||
|
render(context, options)
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_to_output_buffer(context, output)
|
def render_to_output_buffer(context, output)
|
||||||
@@ -211,17 +186,41 @@ module Liquid
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def configure_options(options)
|
def coerce_context(assigns_or_context)
|
||||||
if (profiling = options[:profile])
|
case assigns_or_context
|
||||||
raise "Profiler not loaded, require 'liquid/profiler' first" unless defined?(Liquid::Profiler)
|
when Liquid::Context
|
||||||
|
assigns_or_context
|
||||||
|
when Liquid::Drop
|
||||||
|
drop = assigns_or_context
|
||||||
|
drop.context = Context.build(environments: [drop])
|
||||||
|
when Hash
|
||||||
|
Context.build(environments: [assigns_or_context])
|
||||||
|
when nil
|
||||||
|
Context.build
|
||||||
|
else
|
||||||
|
raise ArgumentError, "Expected Hash or Liquid::Context as parameter"
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@options = options
|
def tokenize(source)
|
||||||
@profiling = profiling
|
Tokenizer.new(source, @line_numbers)
|
||||||
@line_numbers = options[:line_numbers] || @profiling
|
end
|
||||||
parse_context = options.is_a?(ParseContext) ? options : ParseContext.new(options)
|
|
||||||
@warnings = parse_context.warnings
|
def with_profiling(context)
|
||||||
parse_context
|
if @profiling && !context.partial
|
||||||
|
raise "Profiler not loaded, require 'liquid/profiler' first" unless defined?(Liquid::Profiler)
|
||||||
|
|
||||||
|
@profiler = Profiler.new(context.template_name)
|
||||||
|
@profiler.start
|
||||||
|
|
||||||
|
begin
|
||||||
|
yield
|
||||||
|
ensure
|
||||||
|
@profiler.stop
|
||||||
|
end
|
||||||
|
else
|
||||||
|
yield
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def apply_options_to_context(context, options)
|
def apply_options_to_context(context, options)
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ module Liquid
|
|||||||
@parse_context = parse_context
|
@parse_context = parse_context
|
||||||
@line_number = parse_context.line_number
|
@line_number = parse_context.line_number
|
||||||
|
|
||||||
strict_parse_with_error_mode_fallback(markup)
|
parse_with_selected_parser(markup)
|
||||||
end
|
end
|
||||||
|
|
||||||
def raw
|
def raw
|
||||||
@@ -63,8 +63,6 @@ module Liquid
|
|||||||
@filters = []
|
@filters = []
|
||||||
p = Parser.new(markup)
|
p = Parser.new(markup)
|
||||||
|
|
||||||
return if p.look(:end_of_string)
|
|
||||||
|
|
||||||
@name = Expression.parse(p.expression)
|
@name = Expression.parse(p.expression)
|
||||||
while p.consume?(:pipe)
|
while p.consume?(:pipe)
|
||||||
filtername = p.consume(:id)
|
filtername = p.consume(:id)
|
||||||
@@ -83,11 +81,9 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
obj = context.evaluate(@name)
|
obj = @filters.inject(context.evaluate(@name)) do |output, (filter_name, filter_args, filter_kwargs)|
|
||||||
|
|
||||||
@filters.each do |filter_name, filter_args, filter_kwargs|
|
|
||||||
filter_args = evaluate_filter_expressions(context, filter_args, filter_kwargs)
|
filter_args = evaluate_filter_expressions(context, filter_args, filter_kwargs)
|
||||||
obj = context.invoke(filter_name, obj, *filter_args)
|
context.invoke(filter_name, output, *filter_args)
|
||||||
end
|
end
|
||||||
|
|
||||||
context.apply_global_filter(obj)
|
context.apply_global_filter(obj)
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module Liquid
|
module Liquid
|
||||||
VERSION = "5.0.0"
|
VERSION = "4.0.3"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -17,11 +17,9 @@ Gem::Specification.new do |s|
|
|||||||
s.license = "MIT"
|
s.license = "MIT"
|
||||||
# s.description = "A secure, non-evaling end user template engine with aesthetic markup."
|
# s.description = "A secure, non-evaling end user template engine with aesthetic markup."
|
||||||
|
|
||||||
s.required_ruby_version = ">= 2.5.0"
|
s.required_ruby_version = ">= 2.4.0"
|
||||||
s.required_rubygems_version = ">= 1.3.7"
|
s.required_rubygems_version = ">= 1.3.7"
|
||||||
|
|
||||||
s.metadata['allowed_push_host'] = 'https://rubygems.org'
|
|
||||||
|
|
||||||
s.test_files = Dir.glob("{test}/**/*")
|
s.test_files = Dir.glob("{test}/**/*")
|
||||||
s.files = Dir.glob("{lib}/**/*") + %w(LICENSE README.md)
|
s.files = Dir.glob("{lib}/**/*") + %w(LICENSE README.md)
|
||||||
|
|
||||||
|
|||||||
@@ -73,30 +73,29 @@ class ThemeRunner
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def render_layout(template, layout, assigns)
|
def compile_and_render(template, layout, assigns, page_template)
|
||||||
assigns['content_for_layout'] = template.render!(assigns)
|
assigns = assigns.merge(
|
||||||
layout&.render!(assigns)
|
'page_title' => 'Page title',
|
||||||
end
|
'template' => page_template,
|
||||||
|
)
|
||||||
def compile_and_render(template, layout, assigns, page_template, template_file)
|
compiled_test = compile_test(template, layout, assigns)
|
||||||
compiled_test = compile_test(template, layout, assigns, page_template, template_file)
|
assigns['content_for_layout'] = compiled_test[:tmpl].render!(assigns)
|
||||||
render_layout(compiled_test[:tmpl], compiled_test[:layout], compiled_test[:assigns])
|
compiled_test[:layout].render!(assigns) if layout
|
||||||
end
|
end
|
||||||
|
|
||||||
def compile_all_tests
|
def compile_all_tests
|
||||||
@compiled_tests = []
|
@compiled_tests = []
|
||||||
each_test do |liquid, layout, assigns, page_template, template_name|
|
each_test do |liquid, layout, assigns, _page_template, _template_name|
|
||||||
@compiled_tests << compile_test(liquid, layout, assigns, page_template, template_name)
|
@compiled_tests << compile_test(liquid, layout, assigns)
|
||||||
end
|
end
|
||||||
@compiled_tests
|
@compiled_tests
|
||||||
end
|
end
|
||||||
|
|
||||||
def compile_test(template, layout, assigns, page_template, template_file)
|
def compile_test(template, layout, assigns)
|
||||||
tmpl = init_template(page_template, template_file)
|
parsed_template = Liquid::Template.parse(template).dup
|
||||||
parsed_template = tmpl.parse(template).dup
|
|
||||||
|
|
||||||
if layout
|
if layout
|
||||||
parsed_layout = tmpl.parse(layout)
|
parsed_layout = Liquid::Template.parse(layout)
|
||||||
{ tmpl: parsed_template, assigns: assigns, layout: parsed_layout }
|
{ tmpl: parsed_template, assigns: assigns, layout: parsed_layout }
|
||||||
else
|
else
|
||||||
{ tmpl: parsed_template, assigns: assigns }
|
{ tmpl: parsed_template, assigns: assigns }
|
||||||
@@ -111,16 +110,7 @@ class ThemeRunner
|
|||||||
@tests.each do |test_hash|
|
@tests.each do |test_hash|
|
||||||
# Compute page_template outside of profiler run, uninteresting to profiler
|
# Compute page_template outside of profiler run, uninteresting to profiler
|
||||||
page_template = File.basename(test_hash[:template_name], File.extname(test_hash[:template_name]))
|
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])
|
yield(test_hash[:liquid], test_hash[:layout], assigns, page_template)
|
||||||
end
|
end
|
||||||
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
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -44,74 +44,7 @@ class AssignTest < Minitest::Test
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
with_error_mode(:lax) do
|
with_error_mode(:lax) do
|
||||||
assert(Template.parse("{% assign foo = ('X' | downcase) %}"))
|
assert Template.parse("{% assign foo = ('X' | downcase) %}")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end # AssignTest
|
||||||
def test_expression_with_whitespace_in_square_brackets
|
|
||||||
source = "{% assign r = a[ 'b' ] %}{{ r }}"
|
|
||||||
assert_template_result('result', source, 'a' => { 'b' => 'result' })
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_assign_score_exceeding_resource_limit
|
|
||||||
t = Template.parse("{% assign foo = 42 %}{% assign bar = 23 %}")
|
|
||||||
t.resource_limits.assign_score_limit = 1
|
|
||||||
assert_equal("Liquid error: Memory limits exceeded", t.render)
|
|
||||||
assert(t.resource_limits.reached?)
|
|
||||||
|
|
||||||
t.resource_limits.assign_score_limit = 2
|
|
||||||
assert_equal("", t.render!)
|
|
||||||
refute_nil(t.resource_limits.assign_score)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_assign_score_exceeding_limit_from_composite_object
|
|
||||||
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_assign_score_of_int
|
|
||||||
assert_equal(1, assign_score_of(123))
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_assign_score_of_string_counts_bytes
|
|
||||||
assert_equal(3, assign_score_of('123'))
|
|
||||||
assert_equal(5, assign_score_of('12345'))
|
|
||||||
assert_equal(9, assign_score_of('すごい'))
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_assign_score_of_array
|
|
||||||
assert_equal(1, assign_score_of([]))
|
|
||||||
assert_equal(2, assign_score_of([123]))
|
|
||||||
assert_equal(6, assign_score_of([123, 'abcd']))
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_assign_score_of_hash
|
|
||||||
assert_equal(1, assign_score_of({}))
|
|
||||||
assert_equal(5, assign_score_of('int' => 123))
|
|
||||||
assert_equal(12, assign_score_of('int' => 123, 'str' => 'abcd'))
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
class ObjectWrapperDrop < Liquid::Drop
|
|
||||||
def initialize(obj)
|
|
||||||
@obj = obj
|
|
||||||
end
|
|
||||||
|
|
||||||
def value
|
|
||||||
@obj
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def assign_score_of(obj)
|
|
||||||
context = Liquid::Context.new('drop' => ObjectWrapperDrop.new(obj))
|
|
||||||
Liquid::Template.parse('{% assign obj = drop.value %}').render!(context)
|
|
||||||
context.resource_limits.assign_score
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|||||||
@@ -11,48 +11,4 @@ class BlockTest < Minitest::Test
|
|||||||
end
|
end
|
||||||
assert_equal(exc.message, "Liquid syntax error: 'endunless' is not a valid delimiter for if tags. use endif")
|
assert_equal(exc.message, "Liquid syntax error: 'endunless' is not a valid delimiter for if tags. use endif")
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_with_custom_tag
|
|
||||||
with_custom_tag('testtag', Block) do
|
|
||||||
assert(Liquid::Template.parse("{% testtag %} {% endtesttag %}"))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_custom_block_tags_have_a_default_render_to_output_buffer_method_for_backwards_compatibility
|
|
||||||
klass1 = Class.new(Block) do
|
|
||||||
def render(*)
|
|
||||||
'hello'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
with_custom_tag('blabla', klass1) do
|
|
||||||
template = Liquid::Template.parse("{% blabla %} bla {% endblabla %}")
|
|
||||||
|
|
||||||
assert_equal('hello', template.render)
|
|
||||||
|
|
||||||
buf = +''
|
|
||||||
output = template.render({}, output: buf)
|
|
||||||
assert_equal('hello', output)
|
|
||||||
assert_equal('hello', buf)
|
|
||||||
assert_equal(buf.object_id, output.object_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
klass2 = Class.new(klass1) do
|
|
||||||
def render(*)
|
|
||||||
'foo' + super + 'bar'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
with_custom_tag('blabla', klass2) do
|
|
||||||
template = Liquid::Template.parse("{% blabla %} foo {% endblabla %}")
|
|
||||||
|
|
||||||
assert_equal('foohellobar', template.render)
|
|
||||||
|
|
||||||
buf = +''
|
|
||||||
output = template.render({}, output: buf)
|
|
||||||
assert_equal('foohellobar', output)
|
|
||||||
assert_equal('foohellobar', buf)
|
|
||||||
assert_equal(buf.object_id, output.object_id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -49,10 +49,4 @@ class CaptureTest < Minitest::Test
|
|||||||
rendered = template.render!
|
rendered = template.render!
|
||||||
assert_equal("3-3", rendered.gsub(/\s/, ''))
|
assert_equal("3-3", rendered.gsub(/\s/, ''))
|
||||||
end
|
end
|
||||||
|
end # CaptureTest
|
||||||
def test_increment_assign_score_by_bytes_not_characters
|
|
||||||
t = Template.parse("{% capture foo %}すごい{% endcapture %}")
|
|
||||||
t.render!
|
|
||||||
assert_equal(9, t.resource_limits.assign_score)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|||||||
@@ -2,597 +2,9 @@
|
|||||||
|
|
||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class HundredCentes
|
|
||||||
def to_liquid
|
|
||||||
100
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class CentsDrop < Liquid::Drop
|
|
||||||
def amount
|
|
||||||
HundredCentes.new
|
|
||||||
end
|
|
||||||
|
|
||||||
def non_zero?
|
|
||||||
true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class ContextSensitiveDrop < Liquid::Drop
|
|
||||||
def test
|
|
||||||
@context['test']
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Category < Liquid::Drop
|
|
||||||
attr_accessor :name
|
|
||||||
|
|
||||||
def initialize(name)
|
|
||||||
@name = name
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_liquid
|
|
||||||
CategoryDrop.new(self)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class CategoryDrop
|
|
||||||
attr_accessor :category, :context
|
|
||||||
def initialize(category)
|
|
||||||
@category = category
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class CounterDrop < Liquid::Drop
|
|
||||||
def count
|
|
||||||
@count ||= 0
|
|
||||||
@count += 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class ArrayLike
|
|
||||||
def fetch(index)
|
|
||||||
end
|
|
||||||
|
|
||||||
def [](index)
|
|
||||||
@counts ||= []
|
|
||||||
@counts[index] ||= 0
|
|
||||||
@counts[index] += 1
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_liquid
|
|
||||||
self
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class ContextTest < Minitest::Test
|
class ContextTest < Minitest::Test
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def setup
|
|
||||||
@context = Liquid::Context.new
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_variables
|
|
||||||
@context['string'] = 'string'
|
|
||||||
assert_equal('string', @context['string'])
|
|
||||||
|
|
||||||
@context['num'] = 5
|
|
||||||
assert_equal(5, @context['num'])
|
|
||||||
|
|
||||||
@context['time'] = Time.parse('2006-06-06 12:00:00')
|
|
||||||
assert_equal(Time.parse('2006-06-06 12:00:00'), @context['time'])
|
|
||||||
|
|
||||||
@context['date'] = Date.today
|
|
||||||
assert_equal(Date.today, @context['date'])
|
|
||||||
|
|
||||||
now = Time.now
|
|
||||||
@context['datetime'] = now
|
|
||||||
assert_equal(now, @context['datetime'])
|
|
||||||
|
|
||||||
@context['bool'] = true
|
|
||||||
assert_equal(true, @context['bool'])
|
|
||||||
|
|
||||||
@context['bool'] = false
|
|
||||||
assert_equal(false, @context['bool'])
|
|
||||||
|
|
||||||
@context['nil'] = nil
|
|
||||||
assert_nil(@context['nil'])
|
|
||||||
assert_nil(@context['nil'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_variables_not_existing
|
|
||||||
assert_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
|
|
||||||
@context['numbers'] = [1, 2, 3, 4]
|
|
||||||
|
|
||||||
assert_equal(4, @context['numbers.size'])
|
|
||||||
|
|
||||||
@context['numbers'] = { 1 => 1, 2 => 2, 3 => 3, 4 => 4 }
|
|
||||||
|
|
||||||
assert_equal(4, @context['numbers.size'])
|
|
||||||
|
|
||||||
@context['numbers'] = { 1 => 1, 2 => 2, 3 => 3, 4 => 4, 'size' => 1000 }
|
|
||||||
|
|
||||||
assert_equal(1000, @context['numbers.size'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_hyphenated_variable
|
|
||||||
@context['oh-my'] = 'godz'
|
|
||||||
assert_equal('godz', @context['oh-my'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_add_filter
|
|
||||||
filter = Module.new do
|
|
||||||
def hi(output)
|
|
||||||
output + ' hi!'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context = Context.new
|
|
||||||
context.add_filters(filter)
|
|
||||||
assert_equal('hi? hi!', context.invoke(:hi, 'hi?'))
|
|
||||||
|
|
||||||
context = Context.new
|
|
||||||
assert_equal('hi?', context.invoke(:hi, 'hi?'))
|
|
||||||
|
|
||||||
context.add_filters(filter)
|
|
||||||
assert_equal('hi? hi!', context.invoke(:hi, 'hi?'))
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_only_intended_filters_make_it_there
|
|
||||||
filter = Module.new do
|
|
||||||
def hi(output)
|
|
||||||
output + ' hi!'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context = Context.new
|
|
||||||
assert_equal("Wookie", context.invoke("hi", "Wookie"))
|
|
||||||
|
|
||||||
context.add_filters(filter)
|
|
||||||
assert_equal("Wookie hi!", context.invoke("hi", "Wookie"))
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_add_item_in_outer_scope
|
|
||||||
@context['test'] = 'test'
|
|
||||||
@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_nil(@context['test'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_hierachical_data
|
|
||||||
@context['hash'] = { "name" => 'tobi' }
|
|
||||||
assert_equal('tobi', @context['hash.name'])
|
|
||||||
assert_equal('tobi', @context['hash["name"]'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_keywords
|
|
||||||
assert_equal(true, @context['true'])
|
|
||||||
assert_equal(false, @context['false'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_digits
|
|
||||||
assert_equal(100, @context['100'])
|
|
||||||
assert_equal(100.00, @context['100.00'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_strings
|
|
||||||
assert_equal("hello!", @context['"hello!"'])
|
|
||||||
assert_equal("hello!", @context["'hello!'"])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_merge
|
|
||||||
@context.merge("test" => "test")
|
|
||||||
assert_equal('test', @context['test'])
|
|
||||||
@context.merge("test" => "newvalue", "foo" => "bar")
|
|
||||||
assert_equal('newvalue', @context['test'])
|
|
||||||
assert_equal('bar', @context['foo'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_array_notation
|
|
||||||
@context['test'] = [1, 2, 3, 4, 5]
|
|
||||||
|
|
||||||
assert_equal(1, @context['test[0]'])
|
|
||||||
assert_equal(2, @context['test[1]'])
|
|
||||||
assert_equal(3, @context['test[2]'])
|
|
||||||
assert_equal(4, @context['test[3]'])
|
|
||||||
assert_equal(5, @context['test[4]'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_recoursive_array_notation
|
|
||||||
@context['test'] = { 'test' => [1, 2, 3, 4, 5] }
|
|
||||||
|
|
||||||
assert_equal(1, @context['test.test[0]'])
|
|
||||||
|
|
||||||
@context['test'] = [{ 'test' => 'worked' }]
|
|
||||||
|
|
||||||
assert_equal('worked', @context['test[0].test'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_hash_to_array_transition
|
|
||||||
@context['colors'] = {
|
|
||||||
'Blue' => ['003366', '336699', '6699CC', '99CCFF'],
|
|
||||||
'Green' => ['003300', '336633', '669966', '99CC99'],
|
|
||||||
'Yellow' => ['CC9900', 'FFCC00', 'FFFF99', 'FFFFCC'],
|
|
||||||
'Red' => ['660000', '993333', 'CC6666', 'FF9999'],
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_equal('003366', @context['colors.Blue[0]'])
|
|
||||||
assert_equal('FF9999', @context['colors.Red[3]'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_try_first
|
|
||||||
@context['test'] = [1, 2, 3, 4, 5]
|
|
||||||
|
|
||||||
assert_equal(1, @context['test.first'])
|
|
||||||
assert_equal(5, @context['test.last'])
|
|
||||||
|
|
||||||
@context['test'] = { 'test' => [1, 2, 3, 4, 5] }
|
|
||||||
|
|
||||||
assert_equal(1, @context['test.test.first'])
|
|
||||||
assert_equal(5, @context['test.test.last'])
|
|
||||||
|
|
||||||
@context['test'] = [1]
|
|
||||||
assert_equal(1, @context['test.first'])
|
|
||||||
assert_equal(1, @context['test.last'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_access_hashes_with_hash_notation
|
|
||||||
@context['products'] = { 'count' => 5, 'tags' => ['deepsnow', 'freestyle'] }
|
|
||||||
@context['product'] = { 'variants' => [{ 'title' => 'draft151cm' }, { 'title' => 'element151cm' }] }
|
|
||||||
|
|
||||||
assert_equal(5, @context['products["count"]'])
|
|
||||||
assert_equal('deepsnow', @context['products["tags"][0]'])
|
|
||||||
assert_equal('deepsnow', @context['products["tags"].first'])
|
|
||||||
assert_equal('draft151cm', @context['product["variants"][0]["title"]'])
|
|
||||||
assert_equal('element151cm', @context['product["variants"][1]["title"]'])
|
|
||||||
assert_equal('draft151cm', @context['product["variants"][0]["title"]'])
|
|
||||||
assert_equal('element151cm', @context['product["variants"].last["title"]'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_access_variable_with_hash_notation
|
|
||||||
@context['foo'] = 'baz'
|
|
||||||
@context['bar'] = 'foo'
|
|
||||||
|
|
||||||
assert_equal('baz', @context['["foo"]'])
|
|
||||||
assert_equal('baz', @context['[bar]'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_access_hashes_with_hash_access_variables
|
|
||||||
@context['var'] = 'tags'
|
|
||||||
@context['nested'] = { 'var' => 'tags' }
|
|
||||||
@context['products'] = { 'count' => 5, 'tags' => ['deepsnow', 'freestyle'] }
|
|
||||||
|
|
||||||
assert_equal('deepsnow', @context['products[var].first'])
|
|
||||||
assert_equal('freestyle', @context['products[nested.var].last'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_hash_notation_only_for_hash_access
|
|
||||||
@context['array'] = [1, 2, 3, 4, 5]
|
|
||||||
@context['hash'] = { 'first' => 'Hello' }
|
|
||||||
|
|
||||||
assert_equal(1, @context['array.first'])
|
|
||||||
assert_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' }] }
|
|
||||||
|
|
||||||
assert_equal('draft151cm', @context['product.variants[0].title'])
|
|
||||||
assert_equal('element151cm', @context['product.variants[1].title'])
|
|
||||||
assert_equal('draft151cm', @context['product.variants.first.title'])
|
|
||||||
assert_equal('element151cm', @context['product.variants.last.title'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_cents
|
|
||||||
@context.merge("cents" => HundredCentes.new)
|
|
||||||
assert_equal(100, @context['cents'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_nested_cents
|
|
||||||
@context.merge("cents" => { 'amount' => HundredCentes.new })
|
|
||||||
assert_equal(100, @context['cents.amount'])
|
|
||||||
|
|
||||||
@context.merge("cents" => { 'cents' => { 'amount' => HundredCentes.new } })
|
|
||||||
assert_equal(100, @context['cents.cents.amount'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_cents_through_drop
|
|
||||||
@context.merge("cents" => CentsDrop.new)
|
|
||||||
assert_equal(100, @context['cents.amount'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_nested_cents_through_drop
|
|
||||||
@context.merge("vars" => { "cents" => CentsDrop.new })
|
|
||||||
assert_equal(100, @context['vars.cents.amount'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_drop_methods_with_question_marks
|
|
||||||
@context.merge("cents" => CentsDrop.new)
|
|
||||||
assert(@context['cents.non_zero?'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_context_from_within_drop
|
|
||||||
@context.merge("test" => '123', "vars" => ContextSensitiveDrop.new)
|
|
||||||
assert_equal('123', @context['vars.test'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_nested_context_from_within_drop
|
|
||||||
@context.merge("test" => '123', "vars" => { "local" => ContextSensitiveDrop.new })
|
|
||||||
assert_equal('123', @context['vars.local.test'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_ranges
|
|
||||||
@context.merge("test" => '5')
|
|
||||||
assert_equal((1..5), @context['(1..5)'])
|
|
||||||
assert_equal((1..5), @context['(1..test)'])
|
|
||||||
assert_equal((5..5), @context['(test..test)'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_cents_through_drop_nestedly
|
|
||||||
@context.merge("cents" => { "cents" => CentsDrop.new })
|
|
||||||
assert_equal(100, @context['cents.cents.amount'])
|
|
||||||
|
|
||||||
@context.merge("cents" => { "cents" => { "cents" => CentsDrop.new } })
|
|
||||||
assert_equal(100, @context['cents.cents.cents.amount'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_drop_with_variable_called_only_once
|
|
||||||
@context['counter'] = CounterDrop.new
|
|
||||||
|
|
||||||
assert_equal(1, @context['counter.count'])
|
|
||||||
assert_equal(2, @context['counter.count'])
|
|
||||||
assert_equal(3, @context['counter.count'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_drop_with_key_called_only_once
|
|
||||||
@context['counter'] = CounterDrop.new
|
|
||||||
|
|
||||||
assert_equal(1, @context['counter["count"]'])
|
|
||||||
assert_equal(2, @context['counter["count"]'])
|
|
||||||
assert_equal(3, @context['counter["count"]'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_proc_as_variable
|
|
||||||
@context['dynamic'] = proc { 'Hello' }
|
|
||||||
|
|
||||||
assert_equal('Hello', @context['dynamic'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_lambda_as_variable
|
|
||||||
@context['dynamic'] = proc { 'Hello' }
|
|
||||||
|
|
||||||
assert_equal('Hello', @context['dynamic'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_nested_lambda_as_variable
|
|
||||||
@context['dynamic'] = { "lambda" => proc { 'Hello' } }
|
|
||||||
|
|
||||||
assert_equal('Hello', @context['dynamic.lambda'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_array_containing_lambda_as_variable
|
|
||||||
@context['dynamic'] = [1, 2, proc { 'Hello' }, 4, 5]
|
|
||||||
|
|
||||||
assert_equal('Hello', @context['dynamic[2]'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_lambda_is_called_once
|
|
||||||
@context['callcount'] = proc {
|
|
||||||
@global ||= 0
|
|
||||||
@global += 1
|
|
||||||
@global.to_s
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_equal('1', @context['callcount'])
|
|
||||||
assert_equal('1', @context['callcount'])
|
|
||||||
assert_equal('1', @context['callcount'])
|
|
||||||
|
|
||||||
@global = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_nested_lambda_is_called_once
|
|
||||||
@context['callcount'] = { "lambda" => proc {
|
|
||||||
@global ||= 0
|
|
||||||
@global += 1
|
|
||||||
@global.to_s
|
|
||||||
} }
|
|
||||||
|
|
||||||
assert_equal('1', @context['callcount.lambda'])
|
|
||||||
assert_equal('1', @context['callcount.lambda'])
|
|
||||||
assert_equal('1', @context['callcount.lambda'])
|
|
||||||
|
|
||||||
@global = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_lambda_in_array_is_called_once
|
|
||||||
@context['callcount'] = [1, 2, proc {
|
|
||||||
@global ||= 0
|
|
||||||
@global += 1
|
|
||||||
@global.to_s
|
|
||||||
}, 4, 5]
|
|
||||||
|
|
||||||
assert_equal('1', @context['callcount[2]'])
|
|
||||||
assert_equal('1', @context['callcount[2]'])
|
|
||||||
assert_equal('1', @context['callcount[2]'])
|
|
||||||
|
|
||||||
@global = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_access_to_context_from_proc
|
|
||||||
@context.registers[:magic] = 345392
|
|
||||||
|
|
||||||
@context['magic'] = proc { @context.registers[:magic] }
|
|
||||||
|
|
||||||
assert_equal(345392, @context['magic'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_to_liquid_and_context_at_first_level
|
|
||||||
@context['category'] = Category.new("foobar")
|
|
||||||
assert_kind_of(CategoryDrop, @context['category'])
|
|
||||||
assert_equal(@context, @context['category'].context)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_interrupt_avoids_object_allocations
|
|
||||||
@context.interrupt? # ruby 3.0.0 allocates on the first call
|
|
||||||
assert_no_object_allocations do
|
|
||||||
@context.interrupt?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_context_initialization_with_a_proc_in_environment
|
|
||||||
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_static_environments_are_read_with_lower_priority_than_environments
|
|
||||||
context = Context.build(
|
|
||||||
static_environments: { 'shadowed' => 'static', 'unshadowed' => 'static' },
|
|
||||||
environments: { 'shadowed' => 'dynamic' }
|
|
||||||
)
|
|
||||||
|
|
||||||
assert_equal('dynamic', context['shadowed'])
|
|
||||||
assert_equal('static', context['unshadowed'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_apply_global_filter_when_no_global_filter_exist
|
|
||||||
context = Context.new
|
|
||||||
assert_equal('hi', context.apply_global_filter('hi'))
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_new_isolated_subcontext_does_not_inherit_variables
|
|
||||||
super_context = Context.new
|
|
||||||
super_context['my_variable'] = 'some value'
|
|
||||||
subcontext = super_context.new_isolated_subcontext
|
|
||||||
|
|
||||||
assert_nil(subcontext['my_variable'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_new_isolated_subcontext_inherits_static_environment
|
|
||||||
super_context = Context.build(static_environments: { 'my_environment_value' => 'my value' })
|
|
||||||
subcontext = super_context.new_isolated_subcontext
|
|
||||||
|
|
||||||
assert_equal('my value', subcontext['my_environment_value'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_new_isolated_subcontext_inherits_resource_limits
|
|
||||||
resource_limits = ResourceLimits.new({})
|
|
||||||
super_context = Context.new({}, {}, {}, false, resource_limits)
|
|
||||||
subcontext = super_context.new_isolated_subcontext
|
|
||||||
assert_equal(resource_limits, subcontext.resource_limits)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_new_isolated_subcontext_inherits_exception_renderer
|
|
||||||
super_context = Context.new
|
|
||||||
super_context.exception_renderer = ->(_e) { 'my exception message' }
|
|
||||||
subcontext = super_context.new_isolated_subcontext
|
|
||||||
assert_equal('my exception message', subcontext.handle_error(Liquid::Error.new))
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_new_isolated_subcontext_does_not_inherit_non_static_registers
|
|
||||||
registers = {
|
|
||||||
my_register: :my_value,
|
|
||||||
}
|
|
||||||
super_context = Context.new({}, {}, StaticRegisters.new(registers))
|
|
||||||
super_context.registers[:my_register] = :my_alt_value
|
|
||||||
subcontext = super_context.new_isolated_subcontext
|
|
||||||
assert_equal(:my_value, subcontext.registers[:my_register])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_new_isolated_subcontext_inherits_static_registers
|
|
||||||
super_context = Context.build(registers: { my_register: :my_value })
|
|
||||||
subcontext = super_context.new_isolated_subcontext
|
|
||||||
assert_equal(:my_value, subcontext.registers[:my_register])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_new_isolated_subcontext_registers_do_not_pollute_context
|
|
||||||
super_context = Context.build(registers: { my_register: :my_value })
|
|
||||||
subcontext = super_context.new_isolated_subcontext
|
|
||||||
subcontext.registers[:my_register] = :my_alt_value
|
|
||||||
assert_equal(:my_value, super_context.registers[:my_register])
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_new_isolated_subcontext_inherits_filters
|
|
||||||
my_filter = Module.new do
|
|
||||||
def my_filter(*)
|
|
||||||
'my filter result'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
super_context = Context.new
|
|
||||||
super_context.add_filters([my_filter])
|
|
||||||
subcontext = super_context.new_isolated_subcontext
|
|
||||||
template = Template.parse('{{ 123 | my_filter }}')
|
|
||||||
assert_equal('my filter result', template.render(subcontext))
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_disables_tag_specified
|
|
||||||
context = Context.new
|
|
||||||
context.with_disabled_tags(%w(foo bar)) do
|
|
||||||
assert_equal(true, context.tag_disabled?("foo"))
|
|
||||||
assert_equal(true, context.tag_disabled?("bar"))
|
|
||||||
assert_equal(false, context.tag_disabled?("unknown"))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_disables_nested_tags
|
|
||||||
context = Context.new
|
|
||||||
context.with_disabled_tags(["foo"]) do
|
|
||||||
context.with_disabled_tags(["foo"]) do
|
|
||||||
assert_equal(true, context.tag_disabled?("foo"))
|
|
||||||
assert_equal(false, context.tag_disabled?("bar"))
|
|
||||||
end
|
|
||||||
context.with_disabled_tags(["bar"]) do
|
|
||||||
assert_equal(true, context.tag_disabled?("foo"))
|
|
||||||
assert_equal(true, context.tag_disabled?("bar"))
|
|
||||||
context.with_disabled_tags(["foo"]) do
|
|
||||||
assert_equal(true, context.tag_disabled?("foo"))
|
|
||||||
assert_equal(true, context.tag_disabled?("bar"))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
assert_equal(true, context.tag_disabled?("foo"))
|
|
||||||
assert_equal(false, context.tag_disabled?("bar"))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_override_global_filter
|
def test_override_global_filter
|
||||||
global = Module.new do
|
global = Module.new do
|
||||||
def notice(output)
|
def notice(output)
|
||||||
@@ -607,30 +19,16 @@ class ContextTest < Minitest::Test
|
|||||||
end
|
end
|
||||||
|
|
||||||
with_global_filter(global) do
|
with_global_filter(global) do
|
||||||
assert_equal('Global test', Template.parse("{{'test' | notice }}").render!)
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_has_key_will_not_add_an_error_for_missing_keys
|
def test_has_key_will_not_add_an_error_for_missing_keys
|
||||||
with_error_mode(:strict) do
|
with_error_mode :strict do
|
||||||
context = Context.new
|
context = Context.new
|
||||||
context.key?('unknown')
|
context.key?('unknown')
|
||||||
assert_empty(context.errors)
|
assert_empty context.errors
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
private
|
|
||||||
|
|
||||||
def assert_no_object_allocations
|
|
||||||
unless RUBY_ENGINE == 'ruby'
|
|
||||||
skip("stackprof needed to count object allocations")
|
|
||||||
end
|
|
||||||
require 'stackprof'
|
|
||||||
|
|
||||||
profile = StackProf.run(mode: :object) do
|
|
||||||
yield
|
|
||||||
end
|
|
||||||
assert_equal(0, profile[:samples])
|
|
||||||
end
|
|
||||||
end # ContextTest
|
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ class DropsTest < Minitest::Test
|
|||||||
def test_object_methods_not_allowed
|
def test_object_methods_not_allowed
|
||||||
[:dup, :clone, :singleton_class, :eval, :class_eval, :inspect].each do |method|
|
[:dup, :clone, :singleton_class, :eval, :class_eval, :inspect].each do |method|
|
||||||
output = Liquid::Template.parse(" {{ product.#{method} }} ").render!('product' => ProductDrop.new)
|
output = Liquid::Template.parse(" {{ product.#{method} }} ").render!('product' => ProductDrop.new)
|
||||||
assert_equal(' ', output)
|
assert_equal ' ', output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -210,28 +210,28 @@ class DropsTest < Minitest::Test
|
|||||||
|
|
||||||
def test_enumerable_drop_will_invoke_liquid_method_missing_for_clashing_method_names
|
def test_enumerable_drop_will_invoke_liquid_method_missing_for_clashing_method_names
|
||||||
["select", "each", "map", "cycle"].each do |method|
|
["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)
|
||||||
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)
|
||||||
assert_equal(method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new))
|
assert_equal method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new)
|
||||||
assert_equal(method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new))
|
assert_equal method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_some_enumerable_methods_still_get_invoked
|
def test_some_enumerable_methods_still_get_invoked
|
||||||
[:count, :max].each do |method|
|
[:count, :max].each do |method|
|
||||||
assert_equal("3", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new))
|
assert_equal "3", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new)
|
||||||
assert_equal("3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new))
|
assert_equal "3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new)
|
||||||
assert_equal("3", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new))
|
assert_equal "3", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new)
|
||||||
assert_equal("3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new))
|
assert_equal "3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new)
|
||||||
end
|
end
|
||||||
|
|
||||||
assert_equal("yes", Liquid::Template.parse("{% if collection contains 3 %}yes{% endif %}").render!('collection' => RealEnumerableDrop.new))
|
assert_equal("yes", Liquid::Template.parse("{% if collection contains 3 %}yes{% endif %}").render!('collection' => RealEnumerableDrop.new))
|
||||||
|
|
||||||
[:min, :first].each do |method|
|
[:min, :first].each do |method|
|
||||||
assert_equal("1", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new))
|
assert_equal "1", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new)
|
||||||
assert_equal("1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new))
|
assert_equal "1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new)
|
||||||
assert_equal("1", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new))
|
assert_equal "1", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new)
|
||||||
assert_equal("1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new))
|
assert_equal "1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -40,26 +40,29 @@ class ErrorHandlingTest < Minitest::Test
|
|||||||
|
|
||||||
def test_standard_error
|
def test_standard_error
|
||||||
template = Liquid::Template.parse(' {{ errors.standard_error }} ')
|
template = Liquid::Template.parse(' {{ errors.standard_error }} ')
|
||||||
assert_equal(' Liquid error: standard error ', template.render('errors' => ErrorDrop.new))
|
context = Liquid::Context.new('errors' => ErrorDrop.new)
|
||||||
|
assert_equal(' Liquid error: standard error ', template.render(context))
|
||||||
|
|
||||||
assert_equal(1, template.errors.size)
|
assert_equal(1, context.errors.size)
|
||||||
assert_equal(StandardError, template.errors.first.class)
|
assert_equal(StandardError, context.errors.first.class)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_syntax
|
def test_syntax
|
||||||
template = Liquid::Template.parse(' {{ errors.syntax_error }} ')
|
template = Liquid::Template.parse(' {{ errors.syntax_error }} ')
|
||||||
assert_equal(' Liquid syntax error: syntax error ', template.render('errors' => ErrorDrop.new))
|
context = Liquid::Context.new('errors' => ErrorDrop.new)
|
||||||
|
assert_equal(' Liquid syntax error: syntax error ', template.render(context))
|
||||||
|
|
||||||
assert_equal(1, template.errors.size)
|
assert_equal(1, context.errors.size)
|
||||||
assert_equal(SyntaxError, template.errors.first.class)
|
assert_equal(SyntaxError, context.errors.first.class)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_argument
|
def test_argument
|
||||||
template = Liquid::Template.parse(' {{ errors.argument_error }} ')
|
template = Liquid::Template.parse(' {{ errors.argument_error }} ')
|
||||||
assert_equal(' Liquid error: argument error ', template.render('errors' => ErrorDrop.new))
|
context = Liquid::Context.new('errors' => ErrorDrop.new)
|
||||||
|
assert_equal(' Liquid error: argument error ', template.render(context))
|
||||||
|
|
||||||
assert_equal(1, template.errors.size)
|
assert_equal(1, context.errors.size)
|
||||||
assert_equal(ArgumentError, template.errors.first.class)
|
assert_equal(ArgumentError, context.errors.first.class)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_missing_endtag_parse_time_error
|
def test_missing_endtag_parse_time_error
|
||||||
@@ -78,9 +81,10 @@ class ErrorHandlingTest < Minitest::Test
|
|||||||
|
|
||||||
def test_lax_unrecognized_operator
|
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)
|
context = Liquid::Context.new('errors' => ErrorDrop.new)
|
||||||
assert_equal(1, template.errors.size)
|
assert_equal(' Liquid error: Unknown operator =! ', template.render(context))
|
||||||
assert_equal(Liquid::ArgumentError, template.errors.first.class)
|
assert_equal(1, context.errors.size)
|
||||||
|
assert_equal(Liquid::ArgumentError, context.errors.first.class)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_with_line_numbers_adds_numbers_to_parser_errors
|
def test_with_line_numbers_adds_numbers_to_parser_errors
|
||||||
@@ -193,7 +197,7 @@ class ErrorHandlingTest < Minitest::Test
|
|||||||
|
|
||||||
# Liquid should not catch Exceptions that are not subclasses of StandardError, like Interrupt and NoMemoryError
|
# Liquid should not catch Exceptions that are not subclasses of StandardError, like Interrupt and NoMemoryError
|
||||||
def test_exceptions_propagate
|
def test_exceptions_propagate
|
||||||
assert_raises(Exception) do
|
assert_raises Exception do
|
||||||
template = Liquid::Template.parse('{{ errors.exception }}')
|
template = Liquid::Template.parse('{{ errors.exception }}')
|
||||||
template.render('errors' => ErrorDrop.new)
|
template.render('errors' => ErrorDrop.new)
|
||||||
end
|
end
|
||||||
@@ -202,10 +206,11 @@ class ErrorHandlingTest < Minitest::Test
|
|||||||
def test_default_exception_renderer_with_internal_error
|
def test_default_exception_renderer_with_internal_error
|
||||||
template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true)
|
template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true)
|
||||||
|
|
||||||
output = template.render('errors' => ErrorDrop.new)
|
context = Liquid::Context.new('errors' => ErrorDrop.new)
|
||||||
|
output = template.render(context)
|
||||||
|
|
||||||
assert_equal('This is a runtime error: Liquid error (line 1): internal', output)
|
assert_equal('This is a runtime error: Liquid error (line 1): internal', output)
|
||||||
assert_equal([Liquid::InternalError], template.errors.map(&:class))
|
assert_equal([Liquid::InternalError], context.errors.map(&:class))
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_setting_default_exception_renderer
|
def test_setting_default_exception_renderer
|
||||||
@@ -217,10 +222,11 @@ class ErrorHandlingTest < Minitest::Test
|
|||||||
}
|
}
|
||||||
template = Liquid::Template.parse('This is a runtime error: {{ errors.argument_error }}')
|
template = Liquid::Template.parse('This is a runtime error: {{ errors.argument_error }}')
|
||||||
|
|
||||||
output = template.render('errors' => ErrorDrop.new)
|
context = Liquid::Context.new('errors' => ErrorDrop.new)
|
||||||
|
output = template.render(context)
|
||||||
|
|
||||||
assert_equal('This is a runtime error: ', output)
|
assert_equal('This is a runtime error: ', output)
|
||||||
assert_equal([Liquid::ArgumentError], template.errors.map(&:class))
|
assert_equal([Liquid::ArgumentError], context.errors.map(&:class))
|
||||||
ensure
|
ensure
|
||||||
Liquid::Template.default_exception_renderer = old_exception_renderer if old_exception_renderer
|
Liquid::Template.default_exception_renderer = old_exception_renderer if old_exception_renderer
|
||||||
end
|
end
|
||||||
@@ -233,11 +239,12 @@ class ErrorHandlingTest < Minitest::Test
|
|||||||
e.cause
|
e.cause
|
||||||
}
|
}
|
||||||
|
|
||||||
output = template.render({ 'errors' => ErrorDrop.new }, exception_renderer: handler)
|
context = Liquid::Context.new('errors' => ErrorDrop.new)
|
||||||
|
output = template.render(context, exception_renderer: handler)
|
||||||
|
|
||||||
assert_equal('This is a runtime error: runtime error', output)
|
assert_equal('This is a runtime error: runtime error', output)
|
||||||
assert_equal([Liquid::InternalError], exceptions.map(&:class))
|
assert_equal([Liquid::InternalError], exceptions.map(&:class))
|
||||||
assert_equal(exceptions, template.errors)
|
assert_equal(exceptions, context.errors)
|
||||||
assert_equal('#<RuntimeError: runtime error>', exceptions.first.cause.inspect)
|
assert_equal('#<RuntimeError: runtime error>', exceptions.first.cause.inspect)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -250,23 +257,16 @@ class ErrorHandlingTest < Minitest::Test
|
|||||||
def test_included_template_name_with_line_numbers
|
def test_included_template_name_with_line_numbers
|
||||||
old_file_system = Liquid::Template.file_system
|
old_file_system = Liquid::Template.file_system
|
||||||
|
|
||||||
|
context = Liquid::Context.new('errors' => ErrorDrop.new)
|
||||||
begin
|
begin
|
||||||
Liquid::Template.file_system = TestFileSystem.new
|
Liquid::Template.file_system = TestFileSystem.new
|
||||||
|
|
||||||
template = Liquid::Template.parse("Argument error:\n{% include 'product' %}", line_numbers: true)
|
template = Liquid::Template.parse("Argument error:\n{% include 'product' %}", line_numbers: true)
|
||||||
page = template.render('errors' => ErrorDrop.new)
|
page = template.render(context)
|
||||||
ensure
|
ensure
|
||||||
Liquid::Template.file_system = old_file_system
|
Liquid::Template.file_system = old_file_system
|
||||||
end
|
end
|
||||||
assert_equal("Argument error:\nLiquid error (product line 1): argument error", page)
|
assert_equal("Argument error:\nLiquid error (product line 1): argument error", page)
|
||||||
assert_equal("product", template.errors.first.template_name)
|
assert_equal("product", context.errors.first.template_name)
|
||||||
end
|
|
||||||
|
|
||||||
def test_bug_compatible_silencing_of_errors_in_blank_nodes
|
|
||||||
output = Liquid::Template.parse("{% assign x = 0 %}{% if 1 < '2' %}not blank{% assign x = 3 %}{% endif %}{{ x }}").render
|
|
||||||
assert_equal("Liquid error: comparison of Integer with String failed0", output)
|
|
||||||
|
|
||||||
output = Liquid::Template.parse("{% assign x = 0 %}{% if 1 < '2' %}{% assign x = 3 %}{% endif %}{{ x }}").render
|
|
||||||
assert_equal("0", output)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'test_helper'
|
|
||||||
|
|
||||||
class ExpressionTest < Minitest::Test
|
|
||||||
def test_keyword_literals
|
|
||||||
assert_equal(true, parse_and_eval("true"))
|
|
||||||
assert_equal(true, parse_and_eval(" true "))
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_string
|
|
||||||
assert_equal("single quoted", parse_and_eval("'single quoted'"))
|
|
||||||
assert_equal("double quoted", parse_and_eval('"double quoted"'))
|
|
||||||
assert_equal("spaced", parse_and_eval(" 'spaced' "))
|
|
||||||
assert_equal("spaced2", parse_and_eval(' "spaced2" '))
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_int
|
|
||||||
assert_equal(123, parse_and_eval("123"))
|
|
||||||
assert_equal(456, parse_and_eval(" 456 "))
|
|
||||||
assert_equal(12, parse_and_eval("012"))
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_float
|
|
||||||
assert_equal(1.5, parse_and_eval("1.5"))
|
|
||||||
assert_equal(2.5, parse_and_eval(" 2.5 "))
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_range
|
|
||||||
assert_equal(1..2, parse_and_eval("(1..2)"))
|
|
||||||
assert_equal(3..4, parse_and_eval(" ( 3 .. 4 ) "))
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def parse_and_eval(markup, **assigns)
|
|
||||||
if Liquid::Template.error_mode == :strict
|
|
||||||
p = Liquid::Parser.new(markup)
|
|
||||||
markup = p.expression
|
|
||||||
p.consume(:end_of_string)
|
|
||||||
end
|
|
||||||
expression = Liquid::Expression.parse(markup)
|
|
||||||
context = Liquid::Context.new(assigns)
|
|
||||||
context.evaluate(expression)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -153,15 +153,6 @@ class FiltersTest < Minitest::Test
|
|||||||
# tap still treated as a non-existent filter
|
# tap still treated as a non-existent filter
|
||||||
assert_equal("1000", Template.parse("{{var | tap}}").render!('var' => 1000))
|
assert_equal("1000", Template.parse("{{var | tap}}").render!('var' => 1000))
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_liquid_argument_error
|
|
||||||
source = "{{ '' | size: 'too many args' }}"
|
|
||||||
exc = assert_raises(Liquid::ArgumentError) do
|
|
||||||
Template.parse(source).render!
|
|
||||||
end
|
|
||||||
assert_match(/\ALiquid error: wrong number of arguments /, exc.message)
|
|
||||||
assert_equal(exc.message, Template.parse(source).render)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
class FiltersInTemplate < Minitest::Test
|
class FiltersInTemplate < Minitest::Test
|
||||||
@@ -169,9 +160,9 @@ class FiltersInTemplate < Minitest::Test
|
|||||||
|
|
||||||
def test_local_global
|
def test_local_global
|
||||||
with_global_filter(MoneyFilter) do
|
with_global_filter(MoneyFilter) do
|
||||||
assert_equal(" 1000$ ", Template.parse("{{1000 | money}}").render!(nil, nil))
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class HashOrderingTest < Minitest::Test
|
|||||||
|
|
||||||
def test_global_register_order
|
def test_global_register_order
|
||||||
with_global_filter(MoneyFilter, CanadianMoneyFilter) do
|
with_global_filter(MoneyFilter, CanadianMoneyFilter) do
|
||||||
assert_equal(" 1000$ CAD ", Template.parse("{{1000 | money}}").render(nil, nil))
|
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render(nil, nil)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -26,13 +26,6 @@ class ParseTreeVisitorTest < Minitest::Test
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_echo
|
|
||||||
assert_equal(
|
|
||||||
["test"],
|
|
||||||
visit(%({% echo test %}))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_if_condition
|
def test_if_condition
|
||||||
assert_equal(
|
assert_equal(
|
||||||
["test"],
|
["test"],
|
||||||
@@ -32,7 +32,7 @@ class ParsingQuirksTest < Minitest::Test
|
|||||||
assert(Template.parse("{{test}}"))
|
assert(Template.parse("{{test}}"))
|
||||||
|
|
||||||
with_error_mode(:lax) do
|
with_error_mode(:lax) do
|
||||||
assert(Template.parse("{{|test}}"))
|
assert Template.parse("{{|test}}")
|
||||||
end
|
end
|
||||||
|
|
||||||
with_error_mode(:strict) do
|
with_error_mode(:strict) do
|
||||||
@@ -118,16 +118,6 @@ class ParsingQuirksTest < Minitest::Test
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_blank_variable_markup
|
|
||||||
assert_template_result('', "{{}}")
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_lookup_on_var_with_literal_name
|
|
||||||
assigns = { "blank" => { "x" => "result" } }
|
|
||||||
assert_template_result('result', "{{ blank.x }}", assigns)
|
|
||||||
assert_template_result('result', "{{ blank['x'] }}", assigns)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_contains_in_id
|
def test_contains_in_id
|
||||||
assert_template_result(' YES ', '{% if containsallshipments == true %} YES {% endif %}', 'containsallshipments' => true)
|
assert_template_result(' YES ', '{% if containsallshipments == true %} YES {% endif %}', 'containsallshipments' => true)
|
||||||
end
|
end
|
||||||
|
|||||||
27
test/integration/registers/disabled_tags_test.rb
Normal file
27
test/integration/registers/disabled_tags_test.rb
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class DisabledTagsTest < Minitest::Test
|
||||||
|
include Liquid
|
||||||
|
|
||||||
|
class DisableRaw < Block
|
||||||
|
disable_tags "raw"
|
||||||
|
end
|
||||||
|
|
||||||
|
class DisableRawEcho < Block
|
||||||
|
disable_tags "raw", "echo"
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_disables_raw
|
||||||
|
with_custom_tag('disable', DisableRaw) do
|
||||||
|
assert_template_result 'raw usage is not allowed in this contextfoo', '{% disable %}{% raw %}Foobar{% endraw %}{% echo "foo" %}{% enddisable %}'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_disables_echo_and_raw
|
||||||
|
with_custom_tag('disable', DisableRawEcho) do
|
||||||
|
assert_template_result 'raw usage is not allowed in this contextecho usage is not allowed in this context', '{% disable %}{% raw %}Foobar{% endraw %}{% echo "foo" %}{% enddisable %}'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class ProfilerTest < Minitest::Test
|
class RenderProfilingTest < Minitest::Test
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
class ProfilingFileSystem
|
class ProfilingFileSystem
|
||||||
@@ -62,17 +62,6 @@ class ProfilerTest < Minitest::Test
|
|||||||
assert_equal(2, included_children[1].line_number)
|
assert_equal(2, included_children[1].line_number)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_profiling_render_tag
|
|
||||||
t = Template.parse("{% render 'a_template' %}", profile: true)
|
|
||||||
t.render!
|
|
||||||
|
|
||||||
render_children = t.profiler[0].children
|
|
||||||
render_children.each do |timing|
|
|
||||||
assert_equal('a_template', timing.partial)
|
|
||||||
end
|
|
||||||
assert_equal([1, 2], render_children.map(&:line_number))
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_profiling_times_the_rendering_of_tokens
|
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!
|
t.render!
|
||||||
@@ -88,37 +77,6 @@ class ProfilerTest < Minitest::Test
|
|||||||
assert(t.profiler.total_render_time >= 0, "Total render time was not calculated")
|
assert(t.profiler.total_render_time >= 0, "Total render time was not calculated")
|
||||||
end
|
end
|
||||||
|
|
||||||
class SleepTag < Liquid::Tag
|
|
||||||
def initialize(tag_name, markup, parse_context)
|
|
||||||
super
|
|
||||||
@duration = Float(markup)
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_to_output_buffer(_context, _output)
|
|
||||||
sleep(@duration)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_profiling_multiple_renders
|
|
||||||
with_custom_tag('sleep', SleepTag) do
|
|
||||||
context = Liquid::Context.new
|
|
||||||
t = Liquid::Template.parse("{% sleep 0.001 %}", profile: true)
|
|
||||||
context.template_name = 'index'
|
|
||||||
t.render!(context)
|
|
||||||
context.template_name = 'layout'
|
|
||||||
first_render_time = context.profiler.total_time
|
|
||||||
t.render!(context)
|
|
||||||
|
|
||||||
profiler = context.profiler
|
|
||||||
children = profiler.children
|
|
||||||
assert_operator(first_render_time, :>=, 0.001)
|
|
||||||
assert_operator(profiler.total_time, :>=, 0.001 + first_render_time)
|
|
||||||
assert_equal(["index", "layout"], children.map(&:template_name))
|
|
||||||
assert_equal([nil, nil], children.map(&:code))
|
|
||||||
assert_equal(profiler.total_time, children.map(&:total_time).reduce(&:+))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_profiling_uses_include_to_mark_children
|
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!
|
t.render!
|
||||||
@@ -133,7 +91,7 @@ class ProfilerTest < Minitest::Test
|
|||||||
|
|
||||||
include_node = t.profiler[1]
|
include_node = t.profiler[1]
|
||||||
include_node.children.each do |child|
|
include_node.children.each do |child|
|
||||||
assert_equal("a_template", child.partial)
|
assert_equal "a_template", child.partial
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -143,12 +101,12 @@ class ProfilerTest < Minitest::Test
|
|||||||
|
|
||||||
a_template = t.profiler[1]
|
a_template = t.profiler[1]
|
||||||
a_template.children.each do |child|
|
a_template.children.each do |child|
|
||||||
assert_equal("a_template", child.partial)
|
assert_equal "a_template", child.partial
|
||||||
end
|
end
|
||||||
|
|
||||||
b_template = t.profiler[2]
|
b_template = t.profiler[2]
|
||||||
b_template.children.each do |child|
|
b_template.children.each do |child|
|
||||||
assert_equal("b_template", child.partial)
|
assert_equal "b_template", child.partial
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -158,12 +116,12 @@ class ProfilerTest < Minitest::Test
|
|||||||
|
|
||||||
a_template1 = t.profiler[1]
|
a_template1 = t.profiler[1]
|
||||||
a_template1.children.each do |child|
|
a_template1.children.each do |child|
|
||||||
assert_equal("a_template", child.partial)
|
assert_equal "a_template", child.partial
|
||||||
end
|
end
|
||||||
|
|
||||||
a_template2 = t.profiler[2]
|
a_template2 = t.profiler[2]
|
||||||
a_template2.children.each do |child|
|
a_template2.children.each do |child|
|
||||||
assert_equal("a_template", child.partial)
|
assert_equal "a_template", child.partial
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -43,22 +43,15 @@ class SecurityTest < Minitest::Test
|
|||||||
assert_equal(expected, Template.parse(text).render!(@assigns, filters: SecurityFilter))
|
assert_equal(expected, Template.parse(text).render!(@assigns, filters: SecurityFilter))
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_does_not_permanently_add_filters_to_symbol_table
|
def test_does_not_add_filters_to_symbol_table
|
||||||
current_symbols = Symbol.all_symbols
|
current_symbols = Symbol.all_symbols
|
||||||
|
|
||||||
# MRI imprecisely marks objects found on the C stack, which can result
|
test = %( {{ "some_string" | a_bad_filter }} )
|
||||||
# in uninitialized memory being marked. This can even result in the test failing
|
|
||||||
# deterministically for a given compilation of ruby. Using a separate thread will
|
|
||||||
# keep these writes of the symbol pointer on a separate stack that will be garbage
|
|
||||||
# collected after Thread#join.
|
|
||||||
Thread.new do
|
|
||||||
test = %( {{ "some_string" | a_bad_filter }} )
|
|
||||||
Template.parse(test).render!
|
|
||||||
nil
|
|
||||||
end.join
|
|
||||||
|
|
||||||
GC.start
|
template = Template.parse(test)
|
||||||
|
assert_equal([], (Symbol.all_symbols - current_symbols))
|
||||||
|
|
||||||
|
template.render!
|
||||||
assert_equal([], (Symbol.all_symbols - current_symbols))
|
assert_equal([], (Symbol.all_symbols - current_symbols))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ class StandardFiltersTest < Minitest::Test
|
|||||||
assert_equal('1', @filters.url_decode(1))
|
assert_equal('1', @filters.url_decode(1))
|
||||||
assert_equal('2001-02-03', @filters.url_decode(Date.new(2001, 2, 3)))
|
assert_equal('2001-02-03', @filters.url_decode(Date.new(2001, 2, 3)))
|
||||||
assert_nil(@filters.url_decode(nil))
|
assert_nil(@filters.url_decode(nil))
|
||||||
exception = assert_raises(Liquid::ArgumentError) do
|
exception = assert_raises Liquid::ArgumentError do
|
||||||
@filters.url_decode('%ff')
|
@filters.url_decode('%ff')
|
||||||
end
|
end
|
||||||
assert_equal('Liquid error: invalid byte sequence in UTF-8', exception.message)
|
assert_equal('Liquid error: invalid byte sequence in UTF-8', exception.message)
|
||||||
@@ -171,13 +171,10 @@ class StandardFiltersTest < Minitest::Test
|
|||||||
assert_equal('one two three', @filters.truncatewords('one two three'))
|
assert_equal('one two three', @filters.truncatewords('one two three'))
|
||||||
assert_equal(
|
assert_equal(
|
||||||
'Two small (13” x 5.5” x 10” high) baskets fit inside one large basket (13”...',
|
'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)
|
@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("测试测试测试测试", @filters.truncatewords('测试测试测试测试', 5))
|
||||||
assert_equal('one two1', @filters.truncatewords("one two three", 2, 1))
|
assert_equal('one two1', @filters.truncatewords("one two three", 2, 1))
|
||||||
assert_equal('one two three...', @filters.truncatewords("one two\tthree\nfour", 3))
|
|
||||||
assert_equal('one two...', @filters.truncatewords("one two three four", 2))
|
|
||||||
assert_equal('one...', @filters.truncatewords("one two three four", 0))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_strip_html
|
def test_strip_html
|
||||||
@@ -289,7 +286,7 @@ class StandardFiltersTest < Minitest::Test
|
|||||||
[3],
|
[3],
|
||||||
]
|
]
|
||||||
|
|
||||||
assert_raises(Liquid::ArgumentError) do
|
assert_raises Liquid::ArgumentError do
|
||||||
@filters.sort(foo, "bar")
|
@filters.sort(foo, "bar")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -305,7 +302,7 @@ class StandardFiltersTest < Minitest::Test
|
|||||||
[3],
|
[3],
|
||||||
]
|
]
|
||||||
|
|
||||||
assert_raises(Liquid::ArgumentError) do
|
assert_raises Liquid::ArgumentError do
|
||||||
@filters.sort_natural(foo, "bar")
|
@filters.sort_natural(foo, "bar")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -340,7 +337,7 @@ class StandardFiltersTest < Minitest::Test
|
|||||||
[3],
|
[3],
|
||||||
]
|
]
|
||||||
|
|
||||||
assert_raises(Liquid::ArgumentError) do
|
assert_raises Liquid::ArgumentError do
|
||||||
@filters.uniq(foo, "bar")
|
@filters.uniq(foo, "bar")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -356,7 +353,7 @@ class StandardFiltersTest < Minitest::Test
|
|||||||
[3],
|
[3],
|
||||||
]
|
]
|
||||||
|
|
||||||
assert_raises(Liquid::ArgumentError) do
|
assert_raises Liquid::ArgumentError do
|
||||||
@filters.compact(foo, "bar")
|
@filters.compact(foo, "bar")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -433,7 +430,7 @@ class StandardFiltersTest < Minitest::Test
|
|||||||
[3],
|
[3],
|
||||||
]
|
]
|
||||||
|
|
||||||
assert_raises(Liquid::ArgumentError) do
|
assert_raises Liquid::ArgumentError do
|
||||||
@filters.map(foo, "bar")
|
@filters.map(foo, "bar")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -444,7 +441,7 @@ class StandardFiltersTest < Minitest::Test
|
|||||||
[2],
|
[2],
|
||||||
[3],
|
[3],
|
||||||
]
|
]
|
||||||
assert_raises(Liquid::ArgumentError) do
|
assert_raises Liquid::ArgumentError do
|
||||||
@filters.map(foo, nil)
|
@filters.map(foo, nil)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -488,8 +485,8 @@ class StandardFiltersTest < Minitest::Test
|
|||||||
assert_equal('', @filters.date('', "%B"))
|
assert_equal('', @filters.date('', "%B"))
|
||||||
|
|
||||||
with_timezone("UTC") do
|
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")
|
||||||
assert_equal("07/05/2006", @filters.date("1152098955", "%m/%d/%Y"))
|
assert_equal "07/05/2006", @filters.date("1152098955", "%m/%d/%Y")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -542,7 +539,6 @@ class StandardFiltersTest < Minitest::Test
|
|||||||
|
|
||||||
def test_newlines_to_br
|
def test_newlines_to_br
|
||||||
assert_template_result("a<br />\nb<br />\nc", "{{ source | newline_to_br }}", 'source' => "a\nb\nc")
|
assert_template_result("a<br />\nb<br />\nc", "{{ source | newline_to_br }}", 'source' => "a\nb\nc")
|
||||||
assert_template_result("a<br />\nb<br />\nc", "{{ source | newline_to_br }}", 'source' => "a\r\nb\nc")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_plus
|
def test_plus
|
||||||
@@ -591,7 +587,7 @@ class StandardFiltersTest < Minitest::Test
|
|||||||
|
|
||||||
assert_template_result("0.5", "{{ 2.0 | divided_by:4 }}")
|
assert_template_result("0.5", "{{ 2.0 | divided_by:4 }}")
|
||||||
assert_raises(Liquid::ZeroDivisionError) do
|
assert_raises(Liquid::ZeroDivisionError) do
|
||||||
assert_template_result("4", "{{ 1 | modulo: 0 }}")
|
assert_template_result "4", "{{ 1 | modulo: 0 }}"
|
||||||
end
|
end
|
||||||
|
|
||||||
assert_template_result("5", "{{ price | divided_by:2 }}", 'price' => NumberLikeThing.new(10))
|
assert_template_result("5", "{{ price | divided_by:2 }}", 'price' => NumberLikeThing.new(10))
|
||||||
@@ -600,7 +596,7 @@ class StandardFiltersTest < Minitest::Test
|
|||||||
def test_modulo
|
def test_modulo
|
||||||
assert_template_result("1", "{{ 3 | modulo:2 }}")
|
assert_template_result("1", "{{ 3 | modulo:2 }}")
|
||||||
assert_raises(Liquid::ZeroDivisionError) do
|
assert_raises(Liquid::ZeroDivisionError) do
|
||||||
assert_template_result("4", "{{ 1 | modulo: 0 }}")
|
assert_template_result "4", "{{ 1 | modulo: 0 }}"
|
||||||
end
|
end
|
||||||
|
|
||||||
assert_template_result("1", "{{ price | modulo:2 }}", 'price' => NumberLikeThing.new(3))
|
assert_template_result("1", "{{ price | modulo:2 }}", 'price' => NumberLikeThing.new(3))
|
||||||
@@ -611,7 +607,7 @@ class StandardFiltersTest < Minitest::Test
|
|||||||
assert_template_result("4", "{{ '4.3' | round }}")
|
assert_template_result("4", "{{ '4.3' | round }}")
|
||||||
assert_template_result("4.56", "{{ input | round: 2 }}", 'input' => 4.5612)
|
assert_template_result("4.56", "{{ input | round: 2 }}", 'input' => 4.5612)
|
||||||
assert_raises(Liquid::FloatDomainError) do
|
assert_raises(Liquid::FloatDomainError) do
|
||||||
assert_template_result("4", "{{ 1.0 | divided_by: 0.0 | round }}")
|
assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | round }}"
|
||||||
end
|
end
|
||||||
|
|
||||||
assert_template_result("5", "{{ price | round }}", 'price' => NumberLikeThing.new(4.6))
|
assert_template_result("5", "{{ price | round }}", 'price' => NumberLikeThing.new(4.6))
|
||||||
@@ -622,7 +618,7 @@ class StandardFiltersTest < Minitest::Test
|
|||||||
assert_template_result("5", "{{ input | ceil }}", 'input' => 4.6)
|
assert_template_result("5", "{{ input | ceil }}", 'input' => 4.6)
|
||||||
assert_template_result("5", "{{ '4.3' | ceil }}")
|
assert_template_result("5", "{{ '4.3' | ceil }}")
|
||||||
assert_raises(Liquid::FloatDomainError) do
|
assert_raises(Liquid::FloatDomainError) do
|
||||||
assert_template_result("4", "{{ 1.0 | divided_by: 0.0 | ceil }}")
|
assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | ceil }}"
|
||||||
end
|
end
|
||||||
|
|
||||||
assert_template_result("5", "{{ price | ceil }}", 'price' => NumberLikeThing.new(4.6))
|
assert_template_result("5", "{{ price | ceil }}", 'price' => NumberLikeThing.new(4.6))
|
||||||
@@ -632,7 +628,7 @@ class StandardFiltersTest < Minitest::Test
|
|||||||
assert_template_result("4", "{{ input | floor }}", 'input' => 4.6)
|
assert_template_result("4", "{{ input | floor }}", 'input' => 4.6)
|
||||||
assert_template_result("4", "{{ '4.3' | floor }}")
|
assert_template_result("4", "{{ '4.3' | floor }}")
|
||||||
assert_raises(Liquid::FloatDomainError) do
|
assert_raises(Liquid::FloatDomainError) do
|
||||||
assert_template_result("4", "{{ 1.0 | divided_by: 0.0 | floor }}")
|
assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | floor }}"
|
||||||
end
|
end
|
||||||
|
|
||||||
assert_template_result("5", "{{ price | floor }}", 'price' => NumberLikeThing.new(5.4))
|
assert_template_result("5", "{{ price | floor }}", 'price' => NumberLikeThing.new(5.4))
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'test_helper'
|
|
||||||
|
|
||||||
class TagDisableableTest < Minitest::Test
|
|
||||||
include Liquid
|
|
||||||
|
|
||||||
module RenderTagName
|
|
||||||
def render(_context)
|
|
||||||
tag_name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Custom < Tag
|
|
||||||
prepend Liquid::Tag::Disableable
|
|
||||||
include RenderTagName
|
|
||||||
end
|
|
||||||
|
|
||||||
class Custom2 < Tag
|
|
||||||
prepend Liquid::Tag::Disableable
|
|
||||||
include RenderTagName
|
|
||||||
end
|
|
||||||
|
|
||||||
class DisableCustom < Block
|
|
||||||
disable_tags "custom"
|
|
||||||
end
|
|
||||||
|
|
||||||
class DisableBoth < Block
|
|
||||||
disable_tags "custom", "custom2"
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_block_tag_disabling_nested_tag
|
|
||||||
with_disableable_tags do
|
|
||||||
with_custom_tag('disable', DisableCustom) do
|
|
||||||
output = Template.parse('{% disable %}{% custom %};{% custom2 %}{% enddisable %}').render
|
|
||||||
assert_equal('Liquid error: custom usage is not allowed in this context;custom2', output)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_block_tag_disabling_multiple_nested_tags
|
|
||||||
with_disableable_tags do
|
|
||||||
with_custom_tag('disable', DisableBoth) do
|
|
||||||
output = Template.parse('{% disable %}{% custom %};{% custom2 %}{% enddisable %}').render
|
|
||||||
assert_equal('Liquid error: custom usage is not allowed in this context;Liquid error: custom2 usage is not allowed in this context', output)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def with_disableable_tags
|
|
||||||
with_custom_tag('custom', Custom) do
|
|
||||||
with_custom_tag('custom2', Custom2) do
|
|
||||||
yield
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'test_helper'
|
|
||||||
|
|
||||||
class TagTest < Minitest::Test
|
|
||||||
include Liquid
|
|
||||||
|
|
||||||
def test_custom_tags_have_a_default_render_to_output_buffer_method_for_backwards_compatibility
|
|
||||||
klass1 = Class.new(Tag) do
|
|
||||||
def render(*)
|
|
||||||
'hello'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
with_custom_tag('blabla', klass1) do
|
|
||||||
template = Liquid::Template.parse("{% blabla %}")
|
|
||||||
|
|
||||||
assert_equal('hello', template.render)
|
|
||||||
|
|
||||||
buf = +''
|
|
||||||
output = template.render({}, output: buf)
|
|
||||||
assert_equal('hello', output)
|
|
||||||
assert_equal('hello', buf)
|
|
||||||
assert_equal(buf.object_id, output.object_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
klass2 = Class.new(klass1) do
|
|
||||||
def render(*)
|
|
||||||
'foo' + super + 'bar'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
with_custom_tag('blabla', klass2) do
|
|
||||||
template = Liquid::Template.parse("{% blabla %}")
|
|
||||||
|
|
||||||
assert_equal('foohellobar', template.render)
|
|
||||||
|
|
||||||
buf = +''
|
|
||||||
output = template.render({}, output: buf)
|
|
||||||
assert_equal('foohellobar', output)
|
|
||||||
assert_equal('foohellobar', buf)
|
|
||||||
assert_equal(buf.object_id, output.object_id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -437,30 +437,4 @@ HERE
|
|||||||
|
|
||||||
assert(context.registers[:for_stack].empty?)
|
assert(context.registers[:for_stack].empty?)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_instrument_for_offset_continue
|
|
||||||
assert_usage_increment('for_offset_continue') do
|
|
||||||
Template.parse('{% for item in items offset:continue %}{{item}}{% endfor %}')
|
|
||||||
end
|
|
||||||
|
|
||||||
assert_usage_increment('for_offset_continue', times: 0) do
|
|
||||||
Template.parse('{% for item in items offset:2 %}{{item}}{% endfor %}')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_instrument_forloop_drop_name
|
|
||||||
assigns = { 'items' => [1, 2, 3, 4, 5] }
|
|
||||||
|
|
||||||
assert_usage_increment('forloop_drop_name', times: 5) do
|
|
||||||
Template.parse('{% for item in items %}{{forloop.name}}{% endfor %}').render!(assigns)
|
|
||||||
end
|
|
||||||
|
|
||||||
assert_usage_increment('forloop_drop_name', times: 0) do
|
|
||||||
Template.parse('{% for item in items %}{{forloop.index}}{% endfor %}').render!(assigns)
|
|
||||||
end
|
|
||||||
|
|
||||||
assert_usage_increment('forloop_drop_name', times: 0) do
|
|
||||||
Template.parse('{% for item in items %}{{item}}{% endfor %}').render!(assigns)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ class IfElseTagTest < Minitest::Test
|
|||||||
tests.each do |vals, expected|
|
tests.each do |vals, expected|
|
||||||
a, b, c = vals
|
a, b, c = vals
|
||||||
assigns = { 'a' => a, 'b' => b, 'c' => c }
|
assigns = { 'a' => a, 'b' => b, 'c' => c }
|
||||||
assert_template_result(expected.to_s, tpl, assigns, assigns.to_s)
|
assert_template_result expected.to_s, tpl, assigns, assigns.to_s
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -96,12 +96,12 @@ class IncludeTagTest < Minitest::Test
|
|||||||
|
|
||||||
def test_include_tag_with_alias
|
def test_include_tag_with_alias
|
||||||
assert_template_result("Product: Draft 151cm ",
|
assert_template_result("Product: Draft 151cm ",
|
||||||
"{% include 'product_alias' with products[0] as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
|
"{% include 'product_alias' with products[0] as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_include_tag_for_alias
|
def test_include_tag_for_alias
|
||||||
assert_template_result("Product: Draft 151cm Product: Element 155cm ",
|
assert_template_result("Product: Draft 151cm Product: Element 155cm ",
|
||||||
"{% include 'product_alias' for products as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
|
"{% include 'product_alias' for products as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_include_tag_with_default_name
|
def test_include_tag_with_default_name
|
||||||
@@ -217,8 +217,9 @@ class IncludeTagTest < Minitest::Test
|
|||||||
Liquid::Template.file_system = TestFileSystem.new
|
Liquid::Template.file_system = TestFileSystem.new
|
||||||
|
|
||||||
a = Liquid::Template.parse(' {% include "nested_template" %}')
|
a = Liquid::Template.parse(' {% include "nested_template" %}')
|
||||||
a.render!
|
context = Liquid::Context.new
|
||||||
assert_empty(a.errors)
|
a.render!(context)
|
||||||
|
assert_empty(context.errors)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_passing_options_to_included_templates
|
def test_passing_options_to_included_templates
|
||||||
@@ -226,13 +227,13 @@ class IncludeTagTest < Minitest::Test
|
|||||||
Template.parse("{% include template %}", error_mode: :strict).render!("template" => '{{ "X" || downcase }}')
|
Template.parse("{% include template %}", error_mode: :strict).render!("template" => '{{ "X" || downcase }}')
|
||||||
end
|
end
|
||||||
with_error_mode(:lax) do
|
with_error_mode(:lax) do
|
||||||
assert_equal('x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: true).render!("template" => '{{ "X" || downcase }}'))
|
assert_equal 'x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: true).render!("template" => '{{ "X" || downcase }}')
|
||||||
end
|
end
|
||||||
assert_raises(Liquid::SyntaxError) do
|
assert_raises(Liquid::SyntaxError) do
|
||||||
Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: [:locale]).render!("template" => '{{ "X" || downcase }}')
|
Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: [:locale]).render!("template" => '{{ "X" || downcase }}')
|
||||||
end
|
end
|
||||||
with_error_mode(:lax) do
|
with_error_mode(:lax) do
|
||||||
assert_equal('x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: [:error_mode]).render!("template" => '{{ "X" || downcase }}'))
|
assert_equal 'x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: [:error_mode]).render!("template" => '{{ "X" || downcase }}')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -257,9 +258,10 @@ class IncludeTagTest < Minitest::Test
|
|||||||
|
|
||||||
def test_including_with_strict_variables
|
def test_including_with_strict_variables
|
||||||
template = Liquid::Template.parse("{% include 'simple' %}", error_mode: :warn)
|
template = Liquid::Template.parse("{% include 'simple' %}", error_mode: :warn)
|
||||||
template.render(nil, strict_variables: true)
|
context = Liquid::Context.new
|
||||||
|
template.render(context, strict_variables: true)
|
||||||
|
|
||||||
assert_equal([], template.errors)
|
assert_equal([], context.errors)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_break_through_include
|
def test_break_through_include
|
||||||
|
|||||||
@@ -82,13 +82,15 @@ class LiquidTagTest < Minitest::Test
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_nested_liquid_tag
|
def test_nested_liquid_tag
|
||||||
assert_template_result('good', <<~LIQUID)
|
assert_usage_increment("liquid_tag_contains_outer_tag", times: 0) do
|
||||||
{%- if true %}
|
assert_template_result('good', <<~LIQUID)
|
||||||
{%- liquid
|
{%- if true %}
|
||||||
echo "good"
|
{%- liquid
|
||||||
%}
|
echo "good"
|
||||||
{%- endif -%}
|
%}
|
||||||
LIQUID
|
{%- endif -%}
|
||||||
|
LIQUID
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_cannot_open_blocks_living_past_a_liquid_tag
|
def test_cannot_open_blocks_living_past_a_liquid_tag
|
||||||
@@ -100,12 +102,14 @@ class LiquidTagTest < Minitest::Test
|
|||||||
LIQUID
|
LIQUID
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_cannot_close_blocks_created_before_a_liquid_tag
|
def test_quirk_can_close_blocks_created_before_a_liquid_tag
|
||||||
assert_match_syntax_error("syntax error (line 3): 'endif' is not a valid delimiter for liquid tags. use %}", <<~LIQUID)
|
assert_usage_increment("liquid_tag_contains_outer_tag") do
|
||||||
{%- if true -%}
|
assert_template_result("42", <<~LIQUID)
|
||||||
42
|
{%- if true -%}
|
||||||
{%- liquid endif -%}
|
42
|
||||||
LIQUID
|
{%- liquid endif -%}
|
||||||
|
LIQUID
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_liquid_tag_in_raw
|
def test_liquid_tag_in_raw
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ class RawTagTest < Minitest::Test
|
|||||||
assert_template_result(' Foobar {% {% {% ', '{% raw %} Foobar {% {% {% {% endraw %}')
|
assert_template_result(' Foobar {% {% {% ', '{% raw %} Foobar {% {% {% {% endraw %}')
|
||||||
assert_template_result(' test {% raw %} {% endraw %}', '{% raw %} test {% raw %} {% {% endraw %}endraw %}')
|
assert_template_result(' test {% raw %} {% endraw %}', '{% raw %} test {% raw %} {% {% endraw %}endraw %}')
|
||||||
assert_template_result(' Foobar {{ invalid 1', '{% raw %} Foobar {{ invalid {% endraw %}{{ 1 }}')
|
assert_template_result(' Foobar {{ invalid 1', '{% raw %} Foobar {{ invalid {% endraw %}{{ 1 }}')
|
||||||
assert_template_result(' Foobar {% foo {% bar %}', '{% raw %} Foobar {% foo {% bar %}{% endraw %}')
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_invalid_raw
|
def test_invalid_raw
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class RenderTagTest < Minitest::Test
|
|||||||
def test_recursively_rendered_template_does_not_produce_endless_loop
|
def test_recursively_rendered_template_does_not_produce_endless_loop
|
||||||
Liquid::Template.file_system = StubFileSystem.new('loop' => '{% render "loop" %}')
|
Liquid::Template.file_system = StubFileSystem.new('loop' => '{% render "loop" %}')
|
||||||
|
|
||||||
assert_raises(Liquid::StackLevelError) do
|
assert_raises Liquid::StackLevelError do
|
||||||
Template.parse('{% render "loop" %}').render!
|
Template.parse('{% render "loop" %}').render!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -67,7 +67,7 @@ class RenderTagTest < Minitest::Test
|
|||||||
Liquid::Template.file_system = StubFileSystem.new(
|
Liquid::Template.file_system = StubFileSystem.new(
|
||||||
'loop_render' => '{% render "loop_render" %}',
|
'loop_render' => '{% render "loop_render" %}',
|
||||||
)
|
)
|
||||||
assert_raises(Liquid::StackLevelError) do
|
assert_raises Liquid::StackLevelError do
|
||||||
Template.parse('{% render "loop_render" %}').render!
|
Template.parse('{% render "loop_render" %}').render!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -75,7 +75,7 @@ class RenderTagTest < Minitest::Test
|
|||||||
def test_dynamically_choosen_templates_are_not_allowed
|
def test_dynamically_choosen_templates_are_not_allowed
|
||||||
Liquid::Template.file_system = StubFileSystem.new('snippet' => 'should not be rendered')
|
Liquid::Template.file_system = StubFileSystem.new('snippet' => 'should not be rendered')
|
||||||
|
|
||||||
assert_raises(Liquid::SyntaxError) do
|
assert_raises Liquid::SyntaxError do
|
||||||
Liquid::Template.parse("{% assign name = 'snippet' %}{% render name %}")
|
Liquid::Template.parse("{% assign name = 'snippet' %}{% render name %}")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -127,10 +127,7 @@ class RenderTagTest < Minitest::Test
|
|||||||
'test_include' => '{% include "foo" %}'
|
'test_include' => '{% include "foo" %}'
|
||||||
)
|
)
|
||||||
|
|
||||||
exc = assert_raises(Liquid::DisabledError) do
|
assert_template_result('include usage is not allowed in this context', '{% render "test_include" %}')
|
||||||
Liquid::Template.parse('{% render "test_include" %}').render!
|
|
||||||
end
|
|
||||||
assert_equal('Liquid error: include usage is not allowed in this context', exc.message)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_includes_will_not_render_inside_nested_sibling_tags
|
def test_includes_will_not_render_inside_nested_sibling_tags
|
||||||
@@ -140,8 +137,7 @@ class RenderTagTest < Minitest::Test
|
|||||||
'test_include' => '{% include "foo" %}'
|
'test_include' => '{% include "foo" %}'
|
||||||
)
|
)
|
||||||
|
|
||||||
output = Liquid::Template.parse('{% render "nested_render_with_sibling_include" %}').render
|
assert_template_result('include usage is not allowed in this contextinclude usage is not allowed in this context', '{% render "nested_render_with_sibling_include" %}')
|
||||||
assert_equal('Liquid error: include usage is not allowed in this contextLiquid error: include usage is not allowed in this context', output)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_render_tag_with
|
def test_render_tag_with
|
||||||
@@ -151,7 +147,7 @@ class RenderTagTest < Minitest::Test
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert_template_result("Product: Draft 151cm ",
|
assert_template_result("Product: Draft 151cm ",
|
||||||
"{% render 'product' with products[0] %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
|
"{% render 'product' with products[0] %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_render_tag_with_alias
|
def test_render_tag_with_alias
|
||||||
@@ -161,7 +157,7 @@ class RenderTagTest < Minitest::Test
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert_template_result("Product: Draft 151cm ",
|
assert_template_result("Product: Draft 151cm ",
|
||||||
"{% render 'product_alias' with products[0] as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
|
"{% render 'product_alias' with products[0] as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_render_tag_for_alias
|
def test_render_tag_for_alias
|
||||||
@@ -171,7 +167,7 @@ class RenderTagTest < Minitest::Test
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert_template_result("Product: Draft 151cm Product: Element 155cm ",
|
assert_template_result("Product: Draft 151cm Product: Element 155cm ",
|
||||||
"{% render 'product_alias' for products as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
|
"{% render 'product_alias' for products as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_render_tag_for
|
def test_render_tag_for
|
||||||
@@ -181,7 +177,7 @@ class RenderTagTest < Minitest::Test
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert_template_result("Product: Draft 151cm Product: Element 155cm ",
|
assert_template_result("Product: Draft 151cm Product: Element 155cm ",
|
||||||
"{% render 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
|
"{% render 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_render_tag_forloop
|
def test_render_tag_forloop
|
||||||
@@ -190,7 +186,7 @@ class RenderTagTest < Minitest::Test
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert_template_result("Product: Draft 151cm first index:1 Product: Element 155cm last index:2 ",
|
assert_template_result("Product: Draft 151cm first index:1 Product: Element 155cm last index:2 ",
|
||||||
"{% render 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
|
"{% render 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_render_tag_for_drop
|
def test_render_tag_for_drop
|
||||||
@@ -199,7 +195,7 @@ class RenderTagTest < Minitest::Test
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert_template_result("123",
|
assert_template_result("123",
|
||||||
"{% render 'loop' for loop as value %}", "loop" => TestEnumerable.new)
|
"{% render 'loop' for loop as value %}", "loop" => TestEnumerable.new)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_render_tag_with_drop
|
def test_render_tag_with_drop
|
||||||
@@ -208,6 +204,6 @@ class RenderTagTest < Minitest::Test
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert_template_result("TestEnumerable",
|
assert_template_result("TestEnumerable",
|
||||||
"{% render 'loop' with loop as value %}", "loop" => TestEnumerable.new)
|
"{% render 'loop' with loop as value %}", "loop" => TestEnumerable.new)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -213,11 +213,6 @@ class StandardTagTest < Minitest::Test
|
|||||||
assert_template_result('', code, 'condition' => 'something else')
|
assert_template_result('', code, 'condition' => 'something else')
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_case_when_comma_and_blank_body
|
|
||||||
code = '{% case condition %}{% when 1, 2 %} {% assign r = "result" %} {% endcase %}{{ r }}'
|
|
||||||
assert_template_result('result', code, 'condition' => 2)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_assign
|
def test_assign
|
||||||
assert_template_result('variable', '{% assign a = "variable"%}{{a}}')
|
assert_template_result('variable', '{% assign a = "variable"%}{{a}}')
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -38,12 +38,6 @@ end
|
|||||||
class TemplateTest < Minitest::Test
|
class TemplateTest < Minitest::Test
|
||||||
include Liquid
|
include Liquid
|
||||||
|
|
||||||
def test_instance_assigns_persist_on_same_template_object_between_parses
|
|
||||||
t = Template.new
|
|
||||||
assert_equal('from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render!)
|
|
||||||
assert_equal('from instance assigns', t.parse("{{ foo }}").render!)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_warnings_is_not_exponential_time
|
def test_warnings_is_not_exponential_time
|
||||||
str = "false"
|
str = "false"
|
||||||
100.times do
|
100.times do
|
||||||
@@ -54,43 +48,15 @@ class TemplateTest < Minitest::Test
|
|||||||
assert_equal([], Timeout.timeout(1) { t.warnings })
|
assert_equal([], Timeout.timeout(1) { t.warnings })
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_instance_assigns_persist_on_same_template_parsing_between_renders
|
|
||||||
t = Template.new.parse("{{ foo }}{% assign foo = 'foo' %}{{ foo }}")
|
|
||||||
assert_equal('foo', t.render!)
|
|
||||||
assert_equal('foofoo', t.render!)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_custom_assigns_do_not_persist_on_same_template
|
def test_custom_assigns_do_not_persist_on_same_template
|
||||||
t = Template.new
|
t = Template.new
|
||||||
assert_equal('from custom assigns', t.parse("{{ foo }}").render!('foo' => 'from custom assigns'))
|
assert_equal('from custom assigns', t.parse("{{ foo }}").render!('foo' => 'from custom assigns'))
|
||||||
assert_equal('', t.parse("{{ foo }}").render!)
|
assert_equal('', t.parse("{{ foo }}").render!)
|
||||||
end
|
|
||||||
|
|
||||||
def test_custom_assigns_squash_instance_assigns
|
|
||||||
t = Template.new
|
|
||||||
assert_equal('from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render!)
|
assert_equal('from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render!)
|
||||||
assert_equal('from custom assigns', t.parse("{{ foo }}").render!('foo' => 'from custom assigns'))
|
assert_equal('from custom assigns', t.parse("{{ foo }}").render!('foo' => 'from custom assigns'))
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_persistent_assigns_squash_instance_assigns
|
|
||||||
t = Template.new
|
|
||||||
assert_equal('from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render!)
|
|
||||||
t.assigns['foo'] = 'from persistent assigns'
|
|
||||||
assert_equal('from persistent assigns', t.parse("{{ foo }}").render!)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_lambda_is_called_once_from_persistent_assigns_over_multiple_parses_and_renders
|
|
||||||
t = Template.new
|
|
||||||
t.assigns['number'] = -> {
|
|
||||||
@global ||= 0
|
|
||||||
@global += 1
|
|
||||||
}
|
|
||||||
assert_equal('1', t.parse("{{number}}").render!)
|
|
||||||
assert_equal('1', t.parse("{{number}}").render!)
|
|
||||||
assert_equal('1', t.render!)
|
|
||||||
@global = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_lambda_is_called_once_from_custom_assigns_over_multiple_parses_and_renders
|
def test_lambda_is_called_once_from_custom_assigns_over_multiple_parses_and_renders
|
||||||
t = Template.new
|
t = Template.new
|
||||||
assigns = { 'number' => -> {
|
assigns = { 'number' => -> {
|
||||||
@@ -105,100 +71,150 @@ class TemplateTest < Minitest::Test
|
|||||||
|
|
||||||
def test_resource_limits_works_with_custom_length_method
|
def test_resource_limits_works_with_custom_length_method
|
||||||
t = Template.parse("{% assign foo = bar %}")
|
t = Template.parse("{% assign foo = bar %}")
|
||||||
t.resource_limits.render_length_limit = 42
|
context = Liquid::Context.new("bar" => SomethingWithLength.new)
|
||||||
assert_equal("", t.render!("bar" => SomethingWithLength.new))
|
context.resource_limits.render_length_limit = 42
|
||||||
|
assert_equal("", t.render!(context))
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_resource_limits_render_length
|
def test_resource_limits_render_length
|
||||||
t = Template.parse("0123456789")
|
t = Template.parse("0123456789")
|
||||||
t.resource_limits.render_length_limit = 9
|
context = Liquid::Context.new
|
||||||
assert_equal("Liquid error: Memory limits exceeded", t.render)
|
context.resource_limits.render_length_limit = 5
|
||||||
assert(t.resource_limits.reached?)
|
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
|
||||||
|
assert(context.resource_limits.reached?)
|
||||||
|
|
||||||
t.resource_limits.render_length_limit = 10
|
context.resource_limits.render_length_limit = 10
|
||||||
assert_equal("0123456789", t.render!)
|
assert_equal("0123456789", t.render!)
|
||||||
|
refute_nil(context.resource_limits.render_length)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_resource_limits_render_score
|
def test_resource_limits_render_score
|
||||||
t = Template.parse("{% for a in (1..10) %} {% for a in (1..10) %} foo {% endfor %} {% endfor %}")
|
t = Template.parse("{% for a in (1..10) %} {% for a in (1..10) %} foo {% endfor %} {% endfor %}")
|
||||||
t.resource_limits.render_score_limit = 50
|
context = Liquid::Context.new
|
||||||
assert_equal("Liquid error: Memory limits exceeded", t.render)
|
context.resource_limits.render_score_limit = 50
|
||||||
assert(t.resource_limits.reached?)
|
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
|
||||||
|
assert(context.resource_limits.reached?)
|
||||||
|
|
||||||
t = Template.parse("{% for a in (1..100) %} foo {% endfor %}")
|
t = Template.parse("{% for a in (1..100) %} foo {% endfor %}")
|
||||||
t.resource_limits.render_score_limit = 50
|
context.resource_limits.render_score_limit = 50
|
||||||
assert_equal("Liquid error: Memory limits exceeded", t.render)
|
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
|
||||||
assert(t.resource_limits.reached?)
|
assert(context.resource_limits.reached?)
|
||||||
|
|
||||||
t.resource_limits.render_score_limit = 200
|
context.resource_limits.render_score_limit = 200
|
||||||
assert_equal((" foo " * 100), t.render!)
|
assert_equal((" foo " * 100), t.render!(context))
|
||||||
refute_nil(t.resource_limits.render_score)
|
refute_nil(context.resource_limits.render_score)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_resource_limits_assign_score
|
||||||
|
t = Template.parse("{% assign foo = 42 %}{% assign bar = 23 %}")
|
||||||
|
context = Liquid::Context.new
|
||||||
|
context.resource_limits.assign_score_limit = 1
|
||||||
|
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
|
||||||
|
assert(context.resource_limits.reached?)
|
||||||
|
|
||||||
|
context.resource_limits.assign_score_limit = 2
|
||||||
|
assert_equal("", t.render!(context))
|
||||||
|
refute_nil(context.resource_limits.assign_score)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_resource_limits_assign_score_counts_bytes_not_characters
|
||||||
|
t = Template.parse("{% assign foo = 'すごい' %}")
|
||||||
|
context = Liquid::Context.new
|
||||||
|
t.render(context)
|
||||||
|
assert_equal(9, context.resource_limits.assign_score)
|
||||||
|
|
||||||
|
t = Template.parse("{% capture foo %}すごい{% endcapture %}")
|
||||||
|
t.render(context)
|
||||||
|
assert_equal(9, context.resource_limits.assign_score)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_resource_limits_assign_score_nested
|
||||||
|
t = Template.parse("{% assign foo = 'aaaa' | reverse %}")
|
||||||
|
|
||||||
|
context = Liquid::Context.new
|
||||||
|
context.resource_limits.assign_score_limit = 3
|
||||||
|
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
|
||||||
|
assert(context.resource_limits.reached?)
|
||||||
|
|
||||||
|
context.resource_limits.assign_score_limit = 5
|
||||||
|
assert_equal("", t.render!(context))
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_resource_limits_aborts_rendering_after_first_error
|
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 = Template.parse("{% for a in (1..100) %} foo1 {% endfor %} bar {% for a in (1..100) %} foo2 {% endfor %}")
|
||||||
t.resource_limits.render_score_limit = 50
|
context = Liquid::Context.new
|
||||||
assert_equal("Liquid error: Memory limits exceeded", t.render)
|
context.resource_limits.render_score_limit = 50
|
||||||
assert(t.resource_limits.reached?)
|
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
|
||||||
|
assert(context.resource_limits.reached?)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_resource_limits_hash_in_template_gets_updated_even_if_no_limits_are_set
|
def test_resource_limits_hash_in_template_gets_updated_even_if_no_limits_are_set
|
||||||
t = Template.parse("{% for a in (1..100) %}x{% assign foo = 1 %} {% endfor %}")
|
t = Template.parse("{% for a in (1..100) %} {% assign foo = 1 %} {% endfor %}")
|
||||||
t.render!
|
context = Liquid::Context.new
|
||||||
assert(t.resource_limits.assign_score > 0)
|
t.render!(context)
|
||||||
assert(t.resource_limits.render_score > 0)
|
assert(context.resource_limits.assign_score > 0)
|
||||||
|
assert(context.resource_limits.render_score > 0)
|
||||||
|
assert(context.resource_limits.render_length > 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_render_length_persists_between_blocks
|
def test_render_length_persists_between_blocks
|
||||||
t = Template.parse("{% if true %}aaaa{% endif %}")
|
t = Template.parse("{% if true %}aaaa{% endif %}")
|
||||||
t.resource_limits.render_length_limit = 3
|
context = Liquid::Context.new
|
||||||
assert_equal("Liquid error: Memory limits exceeded", t.render)
|
context.resource_limits.render_length_limit = 7
|
||||||
t.resource_limits.render_length_limit = 4
|
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
|
||||||
assert_equal("aaaa", t.render)
|
context.resource_limits.render_length_limit = 8
|
||||||
|
assert_equal("aaaa", t.render(context))
|
||||||
|
|
||||||
t = Template.parse("{% if true %}aaaa{% endif %}{% if true %}bbb{% endif %}")
|
t = Template.parse("{% if true %}aaaa{% endif %}{% if true %}bbb{% endif %}")
|
||||||
t.resource_limits.render_length_limit = 6
|
context = Liquid::Context.new
|
||||||
assert_equal("Liquid error: Memory limits exceeded", t.render)
|
context.resource_limits.render_length_limit = 13
|
||||||
t.resource_limits.render_length_limit = 7
|
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
|
||||||
assert_equal("aaaabbb", t.render)
|
context.resource_limits.render_length_limit = 14
|
||||||
|
assert_equal("aaaabbb", t.render(context))
|
||||||
|
|
||||||
t = Template.parse("{% if true %}a{% endif %}{% if true %}b{% endif %}{% if true %}a{% endif %}{% if true %}b{% endif %}{% if true %}a{% endif %}{% if true %}b{% endif %}")
|
t = Template.parse("{% if true %}a{% endif %}{% if true %}b{% endif %}{% if true %}a{% endif %}{% if true %}b{% endif %}{% if true %}a{% endif %}{% if true %}b{% endif %}")
|
||||||
t.resource_limits.render_length_limit = 5
|
context = Liquid::Context.new
|
||||||
assert_equal("Liquid error: Memory limits exceeded", t.render)
|
context.resource_limits.render_length_limit = 5
|
||||||
t.resource_limits.render_length_limit = 6
|
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
|
||||||
assert_equal("ababab", t.render)
|
context.resource_limits.render_length_limit = 11
|
||||||
|
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
|
||||||
|
context.resource_limits.render_length_limit = 12
|
||||||
|
assert_equal("ababab", t.render(context))
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_render_length_uses_number_of_bytes_not_characters
|
def test_render_length_uses_number_of_bytes_not_characters
|
||||||
t = Template.parse("{% if true %}すごい{% endif %}")
|
t = Template.parse("{% if true %}すごい{% endif %}")
|
||||||
t.resource_limits.render_length_limit = 8
|
context = Liquid::Context.new
|
||||||
assert_equal("Liquid error: Memory limits exceeded", t.render)
|
context.resource_limits.render_length_limit = 10
|
||||||
t.resource_limits.render_length_limit = 9
|
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
|
||||||
assert_equal("すごい", t.render)
|
context.resource_limits.render_length_limit = 18
|
||||||
|
assert_equal("すごい", t.render(context))
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_default_resource_limits_unaffected_by_render_with_context
|
def test_default_resource_limits_unaffected_by_render_with_context
|
||||||
context = Context.new
|
context = Context.new
|
||||||
t = Template.parse("{% for a in (1..100) %}x{% assign foo = 1 %} {% endfor %}")
|
t = Template.parse("{% for a in (1..100) %} {% assign foo = 1 %} {% endfor %}")
|
||||||
t.render!(context)
|
t.render!(context)
|
||||||
assert(context.resource_limits.assign_score > 0)
|
assert(context.resource_limits.assign_score > 0)
|
||||||
assert(context.resource_limits.render_score > 0)
|
assert(context.resource_limits.render_score > 0)
|
||||||
|
assert(context.resource_limits.render_length > 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_can_use_drop_as_context
|
def test_can_use_drop_as_context
|
||||||
t = Template.new
|
t = Template.new
|
||||||
t.registers['lulz'] = 'haha'
|
|
||||||
drop = TemplateContextDrop.new
|
drop = TemplateContextDrop.new
|
||||||
assert_equal('fizzbuzz', t.parse('{{foo}}').render!(drop))
|
context = Liquid::Context.build(environments: drop, registers: { 'lulz' => 'haha' })
|
||||||
assert_equal('bar', t.parse('{{bar}}').render!(drop))
|
drop.context = context
|
||||||
assert_equal('haha', t.parse("{{baz}}").render!(drop))
|
assert_equal('fizzbuzz', t.parse('{{foo}}').render!(context))
|
||||||
|
assert_equal('bar', t.parse('{{bar}}').render!(context))
|
||||||
|
assert_equal('haha', t.parse("{{baz}}").render!(context))
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_render_bang_force_rethrow_errors_on_passed_context
|
def test_render_bang_force_rethrow_errors_on_passed_context
|
||||||
context = Context.new('drop' => ErroneousDrop.new)
|
context = Context.new('drop' => ErroneousDrop.new)
|
||||||
t = Template.new.parse('{{ drop.bad_method }}')
|
t = Template.new.parse('{{ drop.bad_method }}')
|
||||||
|
|
||||||
e = assert_raises(RuntimeError) do
|
e = assert_raises RuntimeError do
|
||||||
t.render!(context)
|
t.render!(context)
|
||||||
end
|
end
|
||||||
assert_equal('ruby error in drop', e.message)
|
assert_equal('ruby error in drop', e.message)
|
||||||
@@ -243,31 +259,34 @@ class TemplateTest < Minitest::Test
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_undefined_variables
|
def test_undefined_variables
|
||||||
t = Template.parse("{{x}} {{y}} {{z.a}} {{z.b}} {{z.c.d}}")
|
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)
|
context = Liquid::Context.new('x' => 33, 'z' => { 'a' => 32, 'c' => { 'e' => 31 } })
|
||||||
|
result = t.render(context, strict_variables: true)
|
||||||
|
|
||||||
assert_equal('33 32 ', result)
|
assert_equal('33 32 ', result)
|
||||||
assert_equal(3, t.errors.count)
|
assert_equal(3, context.errors.count)
|
||||||
assert_instance_of(Liquid::UndefinedVariable, t.errors[0])
|
assert_instance_of(Liquid::UndefinedVariable, context.errors[0])
|
||||||
assert_equal('Liquid error: undefined variable y', t.errors[0].message)
|
assert_equal('Liquid error: undefined variable y', context.errors[0].message)
|
||||||
assert_instance_of(Liquid::UndefinedVariable, t.errors[1])
|
assert_instance_of(Liquid::UndefinedVariable, context.errors[1])
|
||||||
assert_equal('Liquid error: undefined variable b', t.errors[1].message)
|
assert_equal('Liquid error: undefined variable b', context.errors[1].message)
|
||||||
assert_instance_of(Liquid::UndefinedVariable, t.errors[2])
|
assert_instance_of(Liquid::UndefinedVariable, context.errors[2])
|
||||||
assert_equal('Liquid error: undefined variable d', t.errors[2].message)
|
assert_equal('Liquid error: undefined variable d', context.errors[2].message)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_nil_value_does_not_raise
|
def test_nil_value_does_not_raise
|
||||||
t = Template.parse("some{{x}}thing", error_mode: :strict)
|
Liquid::Template.error_mode = :strict
|
||||||
result = t.render!({ 'x' => nil }, strict_variables: true)
|
t = Template.parse("some{{x}}thing")
|
||||||
|
context = Liquid::Context.new('x' => nil)
|
||||||
|
result = t.render!(context, strict_variables: true)
|
||||||
|
|
||||||
assert_equal(0, t.errors.count)
|
assert_equal(0, context.errors.count)
|
||||||
assert_equal('something', result)
|
assert_equal('something', result)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_undefined_variables_raise
|
def test_undefined_variables_raise
|
||||||
t = Template.parse("{{x}} {{y}} {{z.a}} {{z.b}} {{z.c.d}}")
|
t = Template.parse("{{x}} {{y}} {{z.a}} {{z.b}} {{z.c.d}}")
|
||||||
|
|
||||||
assert_raises(UndefinedVariable) do
|
assert_raises UndefinedVariable do
|
||||||
t.render!({ 'x' => 33, 'z' => { 'a' => 32, 'c' => { 'e' => 31 } } }, strict_variables: true)
|
t.render!({ 'x' => 33, 'z' => { 'a' => 32, 'c' => { 'e' => 31 } } }, strict_variables: true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -275,18 +294,20 @@ class TemplateTest < Minitest::Test
|
|||||||
def test_undefined_drop_methods
|
def test_undefined_drop_methods
|
||||||
d = DropWithUndefinedMethod.new
|
d = DropWithUndefinedMethod.new
|
||||||
t = Template.new.parse('{{ foo }} {{ woot }}')
|
t = Template.new.parse('{{ foo }} {{ woot }}')
|
||||||
result = t.render(d, strict_variables: true)
|
context = Liquid::Context.new(d)
|
||||||
|
d.context = context
|
||||||
|
result = t.render(context, strict_variables: true)
|
||||||
|
|
||||||
assert_equal('foo ', result)
|
assert_equal('foo ', result)
|
||||||
assert_equal(1, t.errors.count)
|
assert_equal(1, context.errors.count)
|
||||||
assert_instance_of(Liquid::UndefinedDropMethod, t.errors[0])
|
assert_instance_of(Liquid::UndefinedDropMethod, context.errors[0])
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_undefined_drop_methods_raise
|
def test_undefined_drop_methods_raise
|
||||||
d = DropWithUndefinedMethod.new
|
d = DropWithUndefinedMethod.new
|
||||||
t = Template.new.parse('{{ foo }} {{ woot }}')
|
t = Template.new.parse('{{ foo }} {{ woot }}')
|
||||||
|
|
||||||
assert_raises(UndefinedDropMethod) do
|
assert_raises UndefinedDropMethod do
|
||||||
t.render!(d, strict_variables: true)
|
t.render!(d, strict_variables: true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -298,18 +319,19 @@ class TemplateTest < Minitest::Test
|
|||||||
"-#{v}-"
|
"-#{v}-"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
result = t.render({ 'a' => 123, 'x' => 'foo' }, filters: [filters], strict_filters: true)
|
context = Liquid::Context.new('a' => 123, 'x' => 'foo')
|
||||||
|
result = t.render(context, filters: [filters], strict_filters: true)
|
||||||
|
|
||||||
assert_equal('123 ', result)
|
assert_equal('123 ', result)
|
||||||
assert_equal(1, t.errors.count)
|
assert_equal(1, context.errors.count)
|
||||||
assert_instance_of(Liquid::UndefinedFilter, t.errors[0])
|
assert_instance_of(Liquid::UndefinedFilter, context.errors[0])
|
||||||
assert_equal('Liquid error: undefined filter somefilter1', t.errors[0].message)
|
assert_equal('Liquid error: undefined filter somefilter1', context.errors[0].message)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_undefined_filters_raise
|
def test_undefined_filters_raise
|
||||||
t = Template.parse("{{x | somefilter1 | upcase | somefilter2}}")
|
t = Template.parse("{{x | somefilter1 | upcase | somefilter2}}")
|
||||||
|
|
||||||
assert_raises(UndefinedFilter) do
|
assert_raises UndefinedFilter do
|
||||||
t.render!({ 'x' => 'foo' }, strict_filters: true)
|
t.render!({ 'x' => 'foo' }, strict_filters: true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -323,4 +345,48 @@ class TemplateTest < Minitest::Test
|
|||||||
result = t.render('x' => 1, 'y' => 5)
|
result = t.render('x' => 1, 'y' => 5)
|
||||||
assert_equal('12345', result)
|
assert_equal('12345', result)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_render_uses_correct_disabled_tags_instance
|
||||||
|
Liquid::Template.file_system = StubFileSystem.new(
|
||||||
|
'foo' => 'bar',
|
||||||
|
'test_include' => '{% include "foo" %}'
|
||||||
|
)
|
||||||
|
|
||||||
|
disabled_tags = DisabledTags.new
|
||||||
|
context = Context.build(registers: { disabled_tags: disabled_tags })
|
||||||
|
|
||||||
|
source = "{% render 'test_include' %}"
|
||||||
|
parse_context = Liquid::ParseContext.new(line_numbers: true, error_mode: :strict)
|
||||||
|
document = Document.parse(Liquid::Tokenizer.new(source, true), parse_context)
|
||||||
|
|
||||||
|
assert_equal("include usage is not allowed in this context", document.render(context))
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_render_sets_context_static_register_when_register_key_does_exist
|
||||||
|
disabled_tags_for_test = DisabledTags.new
|
||||||
|
Template.add_register(:disabled_tags, disabled_tags_for_test)
|
||||||
|
|
||||||
|
t = Template.parse("{% if true %} Test Template {% endif %}")
|
||||||
|
|
||||||
|
context = Context.new
|
||||||
|
refute(context.registers.key?(:disabled_tags))
|
||||||
|
|
||||||
|
t.render(context)
|
||||||
|
|
||||||
|
assert(context.registers.key?(:disabled_tags))
|
||||||
|
assert_equal(disabled_tags_for_test, context.registers[:disabled_tags])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_render_does_not_override_context_static_register_when_register_key_exists
|
||||||
|
context = Context.new
|
||||||
|
context.registers[:random_register] = nil
|
||||||
|
Template.add_register(:random_register, {})
|
||||||
|
|
||||||
|
t = Template.parse("{% if true %} Test Template {% endif %}")
|
||||||
|
|
||||||
|
t.render(context)
|
||||||
|
|
||||||
|
assert_nil(context.registers[:random_register])
|
||||||
|
assert(context.registers.key?(:random_register))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -528,36 +528,4 @@ class TrimModeTest < Minitest::Test
|
|||||||
END_EXPECTED
|
END_EXPECTED
|
||||||
assert_template_result(expected, text)
|
assert_template_result(expected, text)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_pre_trim_blank_preceding_text
|
|
||||||
template = Liquid::Template.parse("\n{%- raw %}{% endraw %}")
|
|
||||||
assert_equal("", template.render)
|
|
||||||
|
|
||||||
template = Liquid::Template.parse("\n{%- if true %}{% endif %}")
|
|
||||||
assert_equal("", template.render)
|
|
||||||
|
|
||||||
template = Liquid::Template.parse("{{ 'B' }} \n{%- if true %}C{% endif %}")
|
|
||||||
assert_equal("BC", template.render)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_bug_compatible_pre_trim
|
|
||||||
template = Liquid::Template.parse("\n {%- raw %}{% endraw %}", bug_compatible_whitespace_trimming: true)
|
|
||||||
assert_equal("\n", template.render)
|
|
||||||
|
|
||||||
template = Liquid::Template.parse("\n {%- if true %}{% endif %}", bug_compatible_whitespace_trimming: true)
|
|
||||||
assert_equal("\n", template.render)
|
|
||||||
|
|
||||||
template = Liquid::Template.parse("{{ 'B' }} \n{%- if true %}C{% endif %}", bug_compatible_whitespace_trimming: true)
|
|
||||||
assert_equal("B C", template.render)
|
|
||||||
|
|
||||||
template = Liquid::Template.parse("B\n {%- raw %}{% endraw %}", bug_compatible_whitespace_trimming: true)
|
|
||||||
assert_equal("B", template.render)
|
|
||||||
|
|
||||||
template = Liquid::Template.parse("B\n {%- if true %}{% endif %}", bug_compatible_whitespace_trimming: true)
|
|
||||||
assert_equal("B", template.render)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_trim_blank
|
|
||||||
assert_template_result('foobar', 'foo {{-}} bar')
|
|
||||||
end
|
|
||||||
end # TrimModeTest
|
end # TrimModeTest
|
||||||
|
|||||||
@@ -21,11 +21,6 @@ class VariableTest < Minitest::Test
|
|||||||
assert_equal(' worked wonderfully ', template.render!('test' => 'worked wonderfully'))
|
assert_equal(' worked wonderfully ', template.render!('test' => 'worked wonderfully'))
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_expression_with_whitespace_in_square_brackets
|
|
||||||
assert_template_result('result', "{{ a[ 'b' ] }}", 'a' => { 'b' => 'result' })
|
|
||||||
assert_template_result('result', "{{ a[ [ 'b' ] ] }}", 'b' => 'c', 'a' => { 'c' => 'result' })
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_ignore_unknown
|
def test_ignore_unknown
|
||||||
template = Template.parse(%({{ test }}))
|
template = Template.parse(%({{ test }}))
|
||||||
assert_equal('', template.render!)
|
assert_equal('', template.render!)
|
||||||
@@ -42,8 +37,8 @@ class VariableTest < Minitest::Test
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_hash_scoping
|
def test_hash_scoping
|
||||||
assert_template_result('worked', "{{ test.test }}", 'test' => { 'test' => 'worked' })
|
template = Template.parse(%({{ test.test }}))
|
||||||
assert_template_result('worked', "{{ test . test }}", 'test' => { 'test' => 'worked' })
|
assert_equal('worked', template.render!('test' => { 'test' => 'worked' }))
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_false_renders_as_false
|
def test_false_renders_as_false
|
||||||
@@ -56,29 +51,10 @@ class VariableTest < Minitest::Test
|
|||||||
assert_equal('cat', Template.parse("{{ nil | append: 'cat' }}").render!)
|
assert_equal('cat', Template.parse("{{ nil | append: 'cat' }}").render!)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_preset_assigns
|
|
||||||
template = Template.parse(%({{ test }}))
|
|
||||||
template.assigns['test'] = 'worked'
|
|
||||||
assert_equal('worked', template.render!)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_reuse_parsed_template
|
def test_reuse_parsed_template
|
||||||
template = Template.parse(%({{ greeting }} {{ name }}))
|
template = Template.parse(%({{ greeting }} {{ name }}))
|
||||||
template.assigns['greeting'] = 'Goodbye'
|
|
||||||
assert_equal('Hello Tobi', template.render!('greeting' => 'Hello', 'name' => 'Tobi'))
|
assert_equal('Hello Tobi', template.render!('greeting' => 'Hello', 'name' => 'Tobi'))
|
||||||
assert_equal('Hello ', template.render!('greeting' => 'Hello', 'unknown' => 'Tobi'))
|
assert_equal('Goodbye Brian', template.render!('greeting' => 'Goodbye', 'name' => 'Brian'))
|
||||||
assert_equal('Hello Brian', template.render!('greeting' => 'Hello', 'name' => 'Brian'))
|
|
||||||
assert_equal('Goodbye Brian', template.render!('name' => 'Brian'))
|
|
||||||
assert_equal({ 'greeting' => 'Goodbye' }, template.assigns)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_assigns_not_polluted_from_template
|
|
||||||
template = Template.parse(%({{ test }}{% assign test = 'bar' %}{{ test }}))
|
|
||||||
template.assigns['test'] = 'baz'
|
|
||||||
assert_equal('bazbar', template.render!)
|
|
||||||
assert_equal('bazbar', template.render!)
|
|
||||||
assert_equal('foobar', template.render!('test' => 'foo'))
|
|
||||||
assert_equal('bazbar', template.render!)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_hash_with_default_proc
|
def test_hash_with_default_proc
|
||||||
@@ -100,8 +76,4 @@ class VariableTest < Minitest::Test
|
|||||||
def test_render_symbol
|
def test_render_symbol
|
||||||
assert_template_result('bar', '{{ foo }}', 'foo' => :bar)
|
assert_template_result('bar', '{{ foo }}', 'foo' => :bar)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_dynamic_find_var
|
|
||||||
assert_template_result('bar', '{{ [key] }}', 'key' => 'foo', 'foo' => 'bar')
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -98,17 +98,10 @@ module Minitest
|
|||||||
end
|
end
|
||||||
|
|
||||||
def with_custom_tag(tag_name, tag_class)
|
def with_custom_tag(tag_name, tag_class)
|
||||||
old_tag = Liquid::Template.tags[tag_name]
|
Liquid::Template.register_tag(tag_name, tag_class)
|
||||||
begin
|
yield
|
||||||
Liquid::Template.register_tag(tag_name, tag_class)
|
ensure
|
||||||
yield
|
Liquid::Template.tags.delete(tag_name)
|
||||||
ensure
|
|
||||||
if old_tag
|
|
||||||
Liquid::Template.tags[tag_name] = old_tag
|
|
||||||
else
|
|
||||||
Liquid::Template.tags.delete(tag_name)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -45,9 +45,53 @@ class BlockUnitTest < Minitest::Test
|
|||||||
assert_equal(3, template.root.nodelist.size)
|
assert_equal(3, template.root.nodelist.size)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_with_custom_tag
|
||||||
|
with_custom_tag('testtag', Block) do
|
||||||
|
assert Liquid::Template.parse("{% testtag %} {% endtesttag %}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_custom_block_tags_have_a_default_render_to_output_buffer_method_for_backwards_compatibility
|
||||||
|
klass1 = Class.new(Block) do
|
||||||
|
def render(*)
|
||||||
|
'hello'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
with_custom_tag('blabla', klass1) do
|
||||||
|
template = Liquid::Template.parse("{% blabla %} bla {% endblabla %}")
|
||||||
|
|
||||||
|
assert_equal 'hello', template.render
|
||||||
|
|
||||||
|
buf = +''
|
||||||
|
output = template.render({}, output: buf)
|
||||||
|
assert_equal 'hello', output
|
||||||
|
assert_equal 'hello', buf
|
||||||
|
assert_equal buf.object_id, output.object_id
|
||||||
|
end
|
||||||
|
|
||||||
|
klass2 = Class.new(klass1) do
|
||||||
|
def render(*)
|
||||||
|
'foo' + super + 'bar'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
with_custom_tag('blabla', klass2) do
|
||||||
|
template = Liquid::Template.parse("{% blabla %} foo {% endblabla %}")
|
||||||
|
|
||||||
|
assert_equal 'foohellobar', template.render
|
||||||
|
|
||||||
|
buf = +''
|
||||||
|
output = template.render({}, output: buf)
|
||||||
|
assert_equal 'foohellobar', output
|
||||||
|
assert_equal 'foohellobar', buf
|
||||||
|
assert_equal buf.object_id, output.object_id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def block_types(nodelist)
|
def block_types(nodelist)
|
||||||
nodelist.collect(&:class)
|
nodelist.collect(&:class)
|
||||||
end
|
end
|
||||||
end
|
end # VariableTest
|
||||||
|
|||||||
579
test/unit/context_unit_test.rb
Normal file
579
test/unit/context_unit_test.rb
Normal file
@@ -0,0 +1,579 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class HundredCentes
|
||||||
|
def to_liquid
|
||||||
|
100
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class CentsDrop < Liquid::Drop
|
||||||
|
def amount
|
||||||
|
HundredCentes.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def non_zero?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class ContextSensitiveDrop < Liquid::Drop
|
||||||
|
def test
|
||||||
|
@context['test']
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Category < Liquid::Drop
|
||||||
|
attr_accessor :name
|
||||||
|
|
||||||
|
def initialize(name)
|
||||||
|
@name = name
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_liquid
|
||||||
|
CategoryDrop.new(self)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class CategoryDrop
|
||||||
|
attr_accessor :category, :context
|
||||||
|
def initialize(category)
|
||||||
|
@category = category
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class CounterDrop < Liquid::Drop
|
||||||
|
def count
|
||||||
|
@count ||= 0
|
||||||
|
@count += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class ArrayLike
|
||||||
|
def fetch(index)
|
||||||
|
end
|
||||||
|
|
||||||
|
def [](index)
|
||||||
|
@counts ||= []
|
||||||
|
@counts[index] ||= 0
|
||||||
|
@counts[index] += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_liquid
|
||||||
|
self
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class ContextUnitTest < Minitest::Test
|
||||||
|
include Liquid
|
||||||
|
|
||||||
|
def setup
|
||||||
|
@context = Liquid::Context.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_variables
|
||||||
|
@context['string'] = 'string'
|
||||||
|
assert_equal('string', @context['string'])
|
||||||
|
|
||||||
|
@context['num'] = 5
|
||||||
|
assert_equal(5, @context['num'])
|
||||||
|
|
||||||
|
@context['time'] = Time.parse('2006-06-06 12:00:00')
|
||||||
|
assert_equal(Time.parse('2006-06-06 12:00:00'), @context['time'])
|
||||||
|
|
||||||
|
@context['date'] = Date.today
|
||||||
|
assert_equal(Date.today, @context['date'])
|
||||||
|
|
||||||
|
now = Time.now
|
||||||
|
@context['datetime'] = now
|
||||||
|
assert_equal(now, @context['datetime'])
|
||||||
|
|
||||||
|
@context['bool'] = true
|
||||||
|
assert_equal(true, @context['bool'])
|
||||||
|
|
||||||
|
@context['bool'] = false
|
||||||
|
assert_equal(false, @context['bool'])
|
||||||
|
|
||||||
|
@context['nil'] = nil
|
||||||
|
assert_nil(@context['nil'])
|
||||||
|
assert_nil(@context['nil'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_variables_not_existing
|
||||||
|
assert_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
|
||||||
|
@context['numbers'] = [1, 2, 3, 4]
|
||||||
|
|
||||||
|
assert_equal(4, @context['numbers.size'])
|
||||||
|
|
||||||
|
@context['numbers'] = { 1 => 1, 2 => 2, 3 => 3, 4 => 4 }
|
||||||
|
|
||||||
|
assert_equal(4, @context['numbers.size'])
|
||||||
|
|
||||||
|
@context['numbers'] = { 1 => 1, 2 => 2, 3 => 3, 4 => 4, 'size' => 1000 }
|
||||||
|
|
||||||
|
assert_equal(1000, @context['numbers.size'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_hyphenated_variable
|
||||||
|
@context['oh-my'] = 'godz'
|
||||||
|
assert_equal('godz', @context['oh-my'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_add_filter
|
||||||
|
filter = Module.new do
|
||||||
|
def hi(output)
|
||||||
|
output + ' hi!'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context = Context.new
|
||||||
|
context.add_filters(filter)
|
||||||
|
assert_equal('hi? hi!', context.invoke(:hi, 'hi?'))
|
||||||
|
|
||||||
|
context = Context.new
|
||||||
|
assert_equal('hi?', context.invoke(:hi, 'hi?'))
|
||||||
|
|
||||||
|
context.add_filters(filter)
|
||||||
|
assert_equal('hi? hi!', context.invoke(:hi, 'hi?'))
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_only_intended_filters_make_it_there
|
||||||
|
filter = Module.new do
|
||||||
|
def hi(output)
|
||||||
|
output + ' hi!'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context = Context.new
|
||||||
|
assert_equal("Wookie", context.invoke("hi", "Wookie"))
|
||||||
|
|
||||||
|
context.add_filters(filter)
|
||||||
|
assert_equal("Wookie hi!", context.invoke("hi", "Wookie"))
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_add_item_in_outer_scope
|
||||||
|
@context['test'] = 'test'
|
||||||
|
@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_nil(@context['test'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_hierachical_data
|
||||||
|
@context['hash'] = { "name" => 'tobi' }
|
||||||
|
assert_equal('tobi', @context['hash.name'])
|
||||||
|
assert_equal('tobi', @context['hash["name"]'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_keywords
|
||||||
|
assert_equal(true, @context['true'])
|
||||||
|
assert_equal(false, @context['false'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_digits
|
||||||
|
assert_equal(100, @context['100'])
|
||||||
|
assert_equal(100.00, @context['100.00'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_strings
|
||||||
|
assert_equal("hello!", @context['"hello!"'])
|
||||||
|
assert_equal("hello!", @context["'hello!'"])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_merge
|
||||||
|
@context.merge("test" => "test")
|
||||||
|
assert_equal('test', @context['test'])
|
||||||
|
@context.merge("test" => "newvalue", "foo" => "bar")
|
||||||
|
assert_equal('newvalue', @context['test'])
|
||||||
|
assert_equal('bar', @context['foo'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_array_notation
|
||||||
|
@context['test'] = [1, 2, 3, 4, 5]
|
||||||
|
|
||||||
|
assert_equal(1, @context['test[0]'])
|
||||||
|
assert_equal(2, @context['test[1]'])
|
||||||
|
assert_equal(3, @context['test[2]'])
|
||||||
|
assert_equal(4, @context['test[3]'])
|
||||||
|
assert_equal(5, @context['test[4]'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_recoursive_array_notation
|
||||||
|
@context['test'] = { 'test' => [1, 2, 3, 4, 5] }
|
||||||
|
|
||||||
|
assert_equal(1, @context['test.test[0]'])
|
||||||
|
|
||||||
|
@context['test'] = [{ 'test' => 'worked' }]
|
||||||
|
|
||||||
|
assert_equal('worked', @context['test[0].test'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_hash_to_array_transition
|
||||||
|
@context['colors'] = {
|
||||||
|
'Blue' => ['003366', '336699', '6699CC', '99CCFF'],
|
||||||
|
'Green' => ['003300', '336633', '669966', '99CC99'],
|
||||||
|
'Yellow' => ['CC9900', 'FFCC00', 'FFFF99', 'FFFFCC'],
|
||||||
|
'Red' => ['660000', '993333', 'CC6666', 'FF9999'],
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equal('003366', @context['colors.Blue[0]'])
|
||||||
|
assert_equal('FF9999', @context['colors.Red[3]'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_try_first
|
||||||
|
@context['test'] = [1, 2, 3, 4, 5]
|
||||||
|
|
||||||
|
assert_equal(1, @context['test.first'])
|
||||||
|
assert_equal(5, @context['test.last'])
|
||||||
|
|
||||||
|
@context['test'] = { 'test' => [1, 2, 3, 4, 5] }
|
||||||
|
|
||||||
|
assert_equal(1, @context['test.test.first'])
|
||||||
|
assert_equal(5, @context['test.test.last'])
|
||||||
|
|
||||||
|
@context['test'] = [1]
|
||||||
|
assert_equal(1, @context['test.first'])
|
||||||
|
assert_equal(1, @context['test.last'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_access_hashes_with_hash_notation
|
||||||
|
@context['products'] = { 'count' => 5, 'tags' => ['deepsnow', 'freestyle'] }
|
||||||
|
@context['product'] = { 'variants' => [{ 'title' => 'draft151cm' }, { 'title' => 'element151cm' }] }
|
||||||
|
|
||||||
|
assert_equal(5, @context['products["count"]'])
|
||||||
|
assert_equal('deepsnow', @context['products["tags"][0]'])
|
||||||
|
assert_equal('deepsnow', @context['products["tags"].first'])
|
||||||
|
assert_equal('draft151cm', @context['product["variants"][0]["title"]'])
|
||||||
|
assert_equal('element151cm', @context['product["variants"][1]["title"]'])
|
||||||
|
assert_equal('draft151cm', @context['product["variants"][0]["title"]'])
|
||||||
|
assert_equal('element151cm', @context['product["variants"].last["title"]'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_access_variable_with_hash_notation
|
||||||
|
@context['foo'] = 'baz'
|
||||||
|
@context['bar'] = 'foo'
|
||||||
|
|
||||||
|
assert_equal('baz', @context['["foo"]'])
|
||||||
|
assert_equal('baz', @context['[bar]'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_access_hashes_with_hash_access_variables
|
||||||
|
@context['var'] = 'tags'
|
||||||
|
@context['nested'] = { 'var' => 'tags' }
|
||||||
|
@context['products'] = { 'count' => 5, 'tags' => ['deepsnow', 'freestyle'] }
|
||||||
|
|
||||||
|
assert_equal('deepsnow', @context['products[var].first'])
|
||||||
|
assert_equal('freestyle', @context['products[nested.var].last'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_hash_notation_only_for_hash_access
|
||||||
|
@context['array'] = [1, 2, 3, 4, 5]
|
||||||
|
@context['hash'] = { 'first' => 'Hello' }
|
||||||
|
|
||||||
|
assert_equal(1, @context['array.first'])
|
||||||
|
assert_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' }] }
|
||||||
|
|
||||||
|
assert_equal('draft151cm', @context['product.variants[0].title'])
|
||||||
|
assert_equal('element151cm', @context['product.variants[1].title'])
|
||||||
|
assert_equal('draft151cm', @context['product.variants.first.title'])
|
||||||
|
assert_equal('element151cm', @context['product.variants.last.title'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_cents
|
||||||
|
@context.merge("cents" => HundredCentes.new)
|
||||||
|
assert_equal(100, @context['cents'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_nested_cents
|
||||||
|
@context.merge("cents" => { 'amount' => HundredCentes.new })
|
||||||
|
assert_equal(100, @context['cents.amount'])
|
||||||
|
|
||||||
|
@context.merge("cents" => { 'cents' => { 'amount' => HundredCentes.new } })
|
||||||
|
assert_equal(100, @context['cents.cents.amount'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_cents_through_drop
|
||||||
|
@context.merge("cents" => CentsDrop.new)
|
||||||
|
assert_equal(100, @context['cents.amount'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_nested_cents_through_drop
|
||||||
|
@context.merge("vars" => { "cents" => CentsDrop.new })
|
||||||
|
assert_equal(100, @context['vars.cents.amount'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_drop_methods_with_question_marks
|
||||||
|
@context.merge("cents" => CentsDrop.new)
|
||||||
|
assert(@context['cents.non_zero?'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_context_from_within_drop
|
||||||
|
@context.merge("test" => '123', "vars" => ContextSensitiveDrop.new)
|
||||||
|
assert_equal('123', @context['vars.test'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_nested_context_from_within_drop
|
||||||
|
@context.merge("test" => '123', "vars" => { "local" => ContextSensitiveDrop.new })
|
||||||
|
assert_equal('123', @context['vars.local.test'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_ranges
|
||||||
|
@context.merge("test" => '5')
|
||||||
|
assert_equal((1..5), @context['(1..5)'])
|
||||||
|
assert_equal((1..5), @context['(1..test)'])
|
||||||
|
assert_equal((5..5), @context['(test..test)'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_cents_through_drop_nestedly
|
||||||
|
@context.merge("cents" => { "cents" => CentsDrop.new })
|
||||||
|
assert_equal(100, @context['cents.cents.amount'])
|
||||||
|
|
||||||
|
@context.merge("cents" => { "cents" => { "cents" => CentsDrop.new } })
|
||||||
|
assert_equal(100, @context['cents.cents.cents.amount'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_drop_with_variable_called_only_once
|
||||||
|
@context['counter'] = CounterDrop.new
|
||||||
|
|
||||||
|
assert_equal(1, @context['counter.count'])
|
||||||
|
assert_equal(2, @context['counter.count'])
|
||||||
|
assert_equal(3, @context['counter.count'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_drop_with_key_called_only_once
|
||||||
|
@context['counter'] = CounterDrop.new
|
||||||
|
|
||||||
|
assert_equal(1, @context['counter["count"]'])
|
||||||
|
assert_equal(2, @context['counter["count"]'])
|
||||||
|
assert_equal(3, @context['counter["count"]'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_proc_as_variable
|
||||||
|
@context['dynamic'] = proc { 'Hello' }
|
||||||
|
|
||||||
|
assert_equal('Hello', @context['dynamic'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_lambda_as_variable
|
||||||
|
@context['dynamic'] = proc { 'Hello' }
|
||||||
|
|
||||||
|
assert_equal('Hello', @context['dynamic'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_nested_lambda_as_variable
|
||||||
|
@context['dynamic'] = { "lambda" => proc { 'Hello' } }
|
||||||
|
|
||||||
|
assert_equal('Hello', @context['dynamic.lambda'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_array_containing_lambda_as_variable
|
||||||
|
@context['dynamic'] = [1, 2, proc { 'Hello' }, 4, 5]
|
||||||
|
|
||||||
|
assert_equal('Hello', @context['dynamic[2]'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_lambda_is_called_once
|
||||||
|
@context['callcount'] = proc {
|
||||||
|
@global ||= 0
|
||||||
|
@global += 1
|
||||||
|
@global.to_s
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equal('1', @context['callcount'])
|
||||||
|
assert_equal('1', @context['callcount'])
|
||||||
|
assert_equal('1', @context['callcount'])
|
||||||
|
|
||||||
|
@global = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_nested_lambda_is_called_once
|
||||||
|
@context['callcount'] = { "lambda" => proc {
|
||||||
|
@global ||= 0
|
||||||
|
@global += 1
|
||||||
|
@global.to_s
|
||||||
|
} }
|
||||||
|
|
||||||
|
assert_equal('1', @context['callcount.lambda'])
|
||||||
|
assert_equal('1', @context['callcount.lambda'])
|
||||||
|
assert_equal('1', @context['callcount.lambda'])
|
||||||
|
|
||||||
|
@global = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_lambda_in_array_is_called_once
|
||||||
|
@context['callcount'] = [1, 2, proc {
|
||||||
|
@global ||= 0
|
||||||
|
@global += 1
|
||||||
|
@global.to_s
|
||||||
|
}, 4, 5]
|
||||||
|
|
||||||
|
assert_equal('1', @context['callcount[2]'])
|
||||||
|
assert_equal('1', @context['callcount[2]'])
|
||||||
|
assert_equal('1', @context['callcount[2]'])
|
||||||
|
|
||||||
|
@global = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_access_to_context_from_proc
|
||||||
|
@context.registers[:magic] = 345392
|
||||||
|
|
||||||
|
@context['magic'] = proc { @context.registers[:magic] }
|
||||||
|
|
||||||
|
assert_equal(345392, @context['magic'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_to_liquid_and_context_at_first_level
|
||||||
|
@context['category'] = Category.new("foobar")
|
||||||
|
assert_kind_of(CategoryDrop, @context['category'])
|
||||||
|
assert_equal(@context, @context['category'].context)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_interrupt_avoids_object_allocations
|
||||||
|
assert_no_object_allocations do
|
||||||
|
@context.interrupt?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_context_initialization_with_a_proc_in_environment
|
||||||
|
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_static_environments_are_read_with_lower_priority_than_environments
|
||||||
|
context = Context.build(
|
||||||
|
static_environments: { 'shadowed' => 'static', 'unshadowed' => 'static' },
|
||||||
|
environments: { 'shadowed' => 'dynamic' }
|
||||||
|
)
|
||||||
|
|
||||||
|
assert_equal('dynamic', context['shadowed'])
|
||||||
|
assert_equal('static', context['unshadowed'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_apply_global_filter_when_no_global_filter_exist
|
||||||
|
context = Context.new
|
||||||
|
assert_equal('hi', context.apply_global_filter('hi'))
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_new_isolated_subcontext_does_not_inherit_variables
|
||||||
|
super_context = Context.new
|
||||||
|
super_context['my_variable'] = 'some value'
|
||||||
|
subcontext = super_context.new_isolated_subcontext
|
||||||
|
|
||||||
|
assert_nil(subcontext['my_variable'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_new_isolated_subcontext_inherits_static_environment
|
||||||
|
super_context = Context.build(static_environments: { 'my_environment_value' => 'my value' })
|
||||||
|
subcontext = super_context.new_isolated_subcontext
|
||||||
|
|
||||||
|
assert_equal('my value', subcontext['my_environment_value'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_new_isolated_subcontext_inherits_resource_limits
|
||||||
|
resource_limits = ResourceLimits.new({})
|
||||||
|
super_context = Context.new({}, {}, {}, false, resource_limits)
|
||||||
|
subcontext = super_context.new_isolated_subcontext
|
||||||
|
assert_equal(resource_limits, subcontext.resource_limits)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_new_isolated_subcontext_inherits_exception_renderer
|
||||||
|
super_context = Context.new
|
||||||
|
super_context.exception_renderer = ->(_e) { 'my exception message' }
|
||||||
|
subcontext = super_context.new_isolated_subcontext
|
||||||
|
assert_equal('my exception message', subcontext.handle_error(Liquid::Error.new))
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_new_isolated_subcontext_does_not_inherit_non_static_registers
|
||||||
|
registers = {
|
||||||
|
my_register: :my_value,
|
||||||
|
}
|
||||||
|
super_context = Context.new({}, {}, StaticRegisters.new(registers))
|
||||||
|
super_context.registers[:my_register] = :my_alt_value
|
||||||
|
subcontext = super_context.new_isolated_subcontext
|
||||||
|
assert_equal(:my_value, subcontext.registers[:my_register])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_new_isolated_subcontext_inherits_static_registers
|
||||||
|
super_context = Context.build(registers: { my_register: :my_value })
|
||||||
|
subcontext = super_context.new_isolated_subcontext
|
||||||
|
assert_equal(:my_value, subcontext.registers[:my_register])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_new_isolated_subcontext_registers_do_not_pollute_context
|
||||||
|
super_context = Context.build(registers: { my_register: :my_value })
|
||||||
|
subcontext = super_context.new_isolated_subcontext
|
||||||
|
subcontext.registers[:my_register] = :my_alt_value
|
||||||
|
assert_equal(:my_value, super_context.registers[:my_register])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_new_isolated_subcontext_inherits_filters
|
||||||
|
my_filter = Module.new do
|
||||||
|
def my_filter(*)
|
||||||
|
'my filter result'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
super_context = Context.new
|
||||||
|
super_context.add_filters([my_filter])
|
||||||
|
subcontext = super_context.new_isolated_subcontext
|
||||||
|
template = Template.parse('{{ 123 | my_filter }}')
|
||||||
|
assert_equal('my filter result', template.render(subcontext))
|
||||||
|
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
|
||||||
@@ -28,7 +28,7 @@ class I18nUnitTest < Minitest::Test
|
|||||||
# end
|
# end
|
||||||
|
|
||||||
def test_raises_unknown_translation
|
def test_raises_unknown_translation
|
||||||
assert_raises(I18n::TranslationError) do
|
assert_raises I18n::TranslationError do
|
||||||
@i18n.translate("doesnt_exist")
|
@i18n.translate("doesnt_exist")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
36
test/unit/registers/disabled_tags_unit_test.rb
Normal file
36
test/unit/registers/disabled_tags_unit_test.rb
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class DisabledTagsUnitTest < Minitest::Test
|
||||||
|
include Liquid
|
||||||
|
|
||||||
|
def test_disables_tag_specified
|
||||||
|
register = DisabledTags.new
|
||||||
|
register.disable(%w(foo bar)) do
|
||||||
|
assert_equal true, register.disabled?("foo")
|
||||||
|
assert_equal true, register.disabled?("bar")
|
||||||
|
assert_equal false, register.disabled?("unknown")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_disables_nested_tags
|
||||||
|
register = DisabledTags.new
|
||||||
|
register.disable(["foo"]) do
|
||||||
|
register.disable(["foo"]) do
|
||||||
|
assert_equal true, register.disabled?("foo")
|
||||||
|
assert_equal false, register.disabled?("bar")
|
||||||
|
end
|
||||||
|
register.disable(["bar"]) do
|
||||||
|
assert_equal true, register.disabled?("foo")
|
||||||
|
assert_equal true, register.disabled?("bar")
|
||||||
|
register.disable(["foo"]) do
|
||||||
|
assert_equal true, register.disabled?("foo")
|
||||||
|
assert_equal true, register.disabled?("bar")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
assert_equal true, register.disabled?("foo")
|
||||||
|
assert_equal false, register.disabled?("bar")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -20,4 +20,42 @@ class TagUnitTest < Minitest::Test
|
|||||||
tag = Tag.parse("some_tag", "", Tokenizer.new(""), ParseContext.new)
|
tag = Tag.parse("some_tag", "", Tokenizer.new(""), ParseContext.new)
|
||||||
assert_equal('some_tag', tag.tag_name)
|
assert_equal('some_tag', tag.tag_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_custom_tags_have_a_default_render_to_output_buffer_method_for_backwards_compatibility
|
||||||
|
klass1 = Class.new(Tag) do
|
||||||
|
def render(*)
|
||||||
|
'hello'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
with_custom_tag('blabla', klass1) do
|
||||||
|
template = Liquid::Template.parse("{% blabla %}")
|
||||||
|
|
||||||
|
assert_equal 'hello', template.render
|
||||||
|
|
||||||
|
buf = +''
|
||||||
|
output = template.render({}, output: buf)
|
||||||
|
assert_equal 'hello', output
|
||||||
|
assert_equal 'hello', buf
|
||||||
|
assert_equal buf.object_id, output.object_id
|
||||||
|
end
|
||||||
|
|
||||||
|
klass2 = Class.new(klass1) do
|
||||||
|
def render(*)
|
||||||
|
'foo' + super + 'bar'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
with_custom_tag('blabla', klass2) do
|
||||||
|
template = Liquid::Template.parse("{% blabla %}")
|
||||||
|
|
||||||
|
assert_equal 'foohellobar', template.render
|
||||||
|
|
||||||
|
buf = +''
|
||||||
|
output = template.render({}, output: buf)
|
||||||
|
assert_equal 'foohellobar', output
|
||||||
|
assert_equal 'foohellobar', buf
|
||||||
|
assert_equal buf.object_id, output.object_id
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -32,26 +32,21 @@ class TokenizerTest < Minitest::Test
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def new_tokenizer(source, parse_context: Liquid::ParseContext.new, start_line_number: nil)
|
|
||||||
parse_context.new_tokenizer(source, start_line_number: start_line_number)
|
|
||||||
end
|
|
||||||
|
|
||||||
def tokenize(source)
|
def tokenize(source)
|
||||||
tokenizer = new_tokenizer(source)
|
tokenizer = Liquid::Tokenizer.new(source)
|
||||||
tokens = []
|
tokens = []
|
||||||
# shift is private in Liquid::C::Tokenizer, since it is only for unit testing
|
while (t = tokenizer.shift)
|
||||||
while (t = tokenizer.send(:shift))
|
|
||||||
tokens << t
|
tokens << t
|
||||||
end
|
end
|
||||||
tokens
|
tokens
|
||||||
end
|
end
|
||||||
|
|
||||||
def tokenize_line_numbers(source)
|
def tokenize_line_numbers(source)
|
||||||
tokenizer = new_tokenizer(source, start_line_number: 1)
|
tokenizer = Liquid::Tokenizer.new(source, true)
|
||||||
line_numbers = []
|
line_numbers = []
|
||||||
loop do
|
loop do
|
||||||
line_number = tokenizer.line_number
|
line_number = tokenizer.line_number
|
||||||
if tokenizer.send(:shift)
|
if tokenizer.shift
|
||||||
line_numbers << line_number
|
line_numbers << line_number
|
||||||
else
|
else
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ class VariableUnitTest < Minitest::Test
|
|||||||
assert_equal(VariableLookup.new('foo-bar'), create_variable('foo-bar').name)
|
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-2'), create_variable('foo-bar-2').name)
|
||||||
|
|
||||||
with_error_mode(:strict) do
|
with_error_mode :strict do
|
||||||
assert_raises(Liquid::SyntaxError) { create_variable('foo - bar') }
|
assert_raises(Liquid::SyntaxError) { create_variable('foo - bar') }
|
||||||
assert_raises(Liquid::SyntaxError) { create_variable('-foo') }
|
assert_raises(Liquid::SyntaxError) { create_variable('-foo') }
|
||||||
assert_raises(Liquid::SyntaxError) { create_variable('2foo') }
|
assert_raises(Liquid::SyntaxError) { create_variable('2foo') }
|
||||||
|
|||||||
Reference in New Issue
Block a user