Compare commits

...

69 Commits

Author SHA1 Message Date
Michael Go
f686c5dec7 use Utils.to_liquid_value on conditionals 2021-06-14 18:19:37 -03:00
Daniel Insley
017c1b5e83 Base64 Decode & Encode Filters (#1450) 2021-06-03 13:23:11 -04:00
Michael Go
250555c9a8 Merge pull request #1441 from Shopify/to-raw-value
Implement to_liquid_value to Liquid::Drop
2021-05-27 10:21:05 -03:00
Michael Go
e361a4d53c introduce to_liquid_value on variable look and conditional statements 2021-05-26 17:27:18 -03:00
Dylan Thacker-Smith
b9e0d28729 rubocop: Clarify that a config is a rubocop-shopify backport 2021-04-20 09:01:06 -07:00
Dylan Thacker-Smith
020f6b93c5 rubocop: Fix unsupported ruby version in TargetRubyVersion error 2021-04-20 09:00:20 -07:00
Dylan Thacker-Smith
cfe1637bdd Translate RangeError to Liquid::Error for truncatewords with large int (#1431) 2021-04-20 11:48:22 -04:00
Dylan Thacker-Smith
eab13a07d9 Add changelog entry for a recent fix 2021-03-29 13:43:01 -07:00
Dylan Thacker-Smith
ca96ca0fef Fix support for using a String subclass for the liquid source (#1421) 2021-03-29 16:22:05 -04:00
Marc-André Cournoyer
4e7a953e73 Merge pull request #1417 from Shopify/prep-release
Bump to 5.0.1 and add changelog for release
2021-03-24 16:55:10 -04:00
Marc-André Cournoyer
6ac2499f7f Remove internal tokenizer fix from History.md
Co-authored-by: Dylan Thacker-Smith <dylan.smith@shopify.com>
2021-03-24 16:53:45 -04:00
Marc-André Cournoyer
ff70161512 Remove internal fixes from History.md
Co-authored-by: Dylan Thacker-Smith <dylan.smith@shopify.com>
2021-03-24 16:52:57 -04:00
Marc-André Cournoyer
026157e128 Bump to 5.0.1 and add changelog for release 2021-03-24 16:19:19 -04:00
Charles-Philippe Clermont
bf64239ea6 Merge pull request #1414 from Shopify/fix/echo-parse-tree-visitor
Add ParseTreeVisitor to Echo tag
2021-03-24 09:38:48 -04:00
Charles-P. Clermont
c270a6f378 Add ParseTreeVisitor to Echo tag
This fixes theme-check#218, wherein variables used in echo tags are not
considered used by the linter. It is because our visitor doesn't see the
:variable_lookup's in the echo tag since the children array is empty.
But this array is empty because it is swallowed by the @variable.
2021-03-24 09:35:43 -04:00
Dylan Thacker-Smith
4fba61a802 Merge pull request #1402 from Shopify/rubocop-shopify
Use the rubocop-shopify gem and autocorrect
2021-03-16 17:39:09 -04:00
Dylan Thacker-Smith
6b6baece25 Merge pull request #1406 from ADTC/patch-2
Add a line to Sign the CLA as the first step in Workflow
2021-02-26 09:03:40 -05:00
ADTC
15b2d193ec Add a hint to sign CLA only if it's the first time
The CLA isn't signed per repository. They might have done so for another repository. In which case, they won't need to do that again.

Change attributed to @dylanahsmith

Co-authored-by: Dylan Thacker-Smith <dylan.smith@shopify.com>
2021-02-26 16:31:16 +08:00
ADTC
fd712d134a Give info in the Contributing page about doc updates (#1405)
Please see #1399 for more info and discussion.
2021-02-25 09:31:18 -05:00
ADTC
0c2db998cf Add a line to Sign the CLA as the first step in Workflow
If a pull request is created by someone who didn't sign the CLA yet, the tests will fail. It requires the contributor to submit a new PR or add an insignificant commit (like an empty commit) to force the tests to rerun on the same PR. If we gently nudge a new contributor to sign the CLA in advance, we can help them avoid the hassle.
2021-02-25 09:59:51 +08:00
Dylan Thacker-Smith
9dac68cce1 Use the rubocop-shopify gem 2021-02-21 12:22:03 -05:00
Dylan Thacker-Smith
c50509b741 Commit rubocop remote configuration update 2021-02-21 12:02:55 -05:00
Dylan Thacker-Smith
cd66572514 Rubocop autocorrections to prepare for rubocop config updates 2021-02-21 12:01:59 -05:00
Dylan Thacker-Smith
dcb5a67089 performance: Use split limit in truncatewords (#1361) 2021-02-19 13:11:35 -05:00
Justin Li
efe44a7e6a Merge pull request #1391 from Unending/replace_carriage_return
handle carriage return in newlines_to_br
2021-02-19 12:49:13 -05:00
Dylan Thacker-Smith
8625e66453 CI: Test with ruby 3.0 as the latest ruby version (#1398) 2021-02-10 10:10:27 -05:00
Unending
3cae09b968 handle carriage return in newlines_to_br 2021-01-16 18:49:01 +01:00
Peter Zhu
3c499d0241 Merge pull request #1387 from Shopify/pz-serialize-benchmark-refactor
Refactor render_layout method for serialization
2021-01-11 15:51:00 -05:00
Peter Zhu
e71e53ffb5 Refactor render_layout method for serialization 2021-01-11 14:00:39 -05:00
Dylan Thacker-Smith
260c863e23 Build the tokenizer through the parse context for liquid-c (#1386) 2021-01-07 14:51:41 -05:00
Marc-André Cournoyer
42b6c07cd0 Merge pull request #1384 from Shopify/allowed_push_host
Add allowed_push_host to gemspec
2021-01-06 10:18:16 -05:00
Marc-André Cournoyer
c91a6827f2 Add allowed_push_host to gemspec
Required to push gem to rubygems.
2021-01-06 10:03:13 -05:00
Marc-André Cournoyer
5dbc3d5701 Merge pull request #1383 from Shopify/bump-version
Bump version to 5.0.0
2021-01-06 09:38:04 -05:00
Marc-André Cournoyer
22683cbd2a Bump version to 5.0.0
Bump major because of the numerous breaking changes.
2021-01-05 15:02:14 -05:00
Peter Zhu
abfab3bef2 Merge pull request #1380 from Shopify/pz-serialize-compat
Fixes for serialization
2021-01-05 14:10:54 -05:00
Peter Zhu
51e8d6234a Freeze blocks in reverse order 2020-12-16 15:18:34 -05:00
Peter Zhu
7ca2846d9c Move configure options to a method 2020-12-16 15:08:55 -05:00
Dylan Thacker-Smith
7ba0fc7952 Merge pull request #1376 from Shopify/support-app-liquid-context
Use the same context class for isolated subcontexts for the require tag
2020-12-14 12:12:46 -05:00
Marc-André Cournoyer
e2c86d137f Merge pull request #1377 from Shopify/update-history
Fix latest changelog
2020-12-13 12:27:15 -05:00
Marc-André Cournoyer
776a63b61d Update latest changelog to mention Ruby 2.5 req 2020-12-13 12:13:49 -05:00
Marc-André Cournoyer
84f9d6957c Merge pull request #1374 from Shopify/update-history
Update History.md with changes since last release
2020-12-11 17:05:33 -05:00
Marc-André Cournoyer
7d32728e16 Split latest changelog in Features, Fixes, Changes & Perf
Also remove duplicated entries
2020-12-11 16:35:19 -05:00
Dylan Thacker-Smith
40a9b72b3c Allow a block to finish context init before squashing instance assigns
Liquid::Context#squash_instance_assigns_with_environments can result in
Proc objects in the environment to be eagerly evaluated. So it should be
possible to finish initializing the context object before this is done.
Allowing a block to be used for this purpose avoids the need to add
additional parameters for this purpose.
2020-12-11 14:52:09 -05:00
Dylan Thacker-Smith
4ff26cd707 Use the same context class for isolated subcontexts for the require tag 2020-12-11 14:52:09 -05:00
Dylan Thacker-Smith
462919a28f Merge pull request #1375 from Shopify/update-rubocop
Update rubocop and related configuration
2020-12-11 14:47:56 -05:00
Dylan Thacker-Smith
f3e2be9f85 Update rubocop 2020-12-11 14:14:15 -05:00
Dylan Thacker-Smith
4d40f83457 Update .rubocop_todo.yml to ignore Lint/MissingSuper offenses in drops
I don't think we can rely on the application to call `super` in their
drop's initializers at the moment, so doing that consistently in liquid
would prevent this from being properly tested.
2020-12-11 14:14:15 -05:00
Dylan Thacker-Smith
00be1e4dd4 Update inherited rubocop shopify style guide configuration 2020-12-11 14:14:15 -05:00
Dylan Thacker-Smith
f7d67b946e rubocop autocorrect Style/MethodCallWithArgsParentheses 2020-12-11 13:51:00 -05:00
Marc-André Cournoyer
ae9aee896b Update History.md with changes since last release 2020-12-10 08:03:17 -05:00
Dylan Thacker-Smith
6dec172743 Merge pull request #1366 from Shopify/profile-render-node
Create top-level profile timing nodes for multiple template renders
2020-12-09 10:07:31 -05:00
Dylan Thacker-Smith
da581d988a Create top-level profile timing nodes for multiple template renders 2020-12-09 10:06:02 -05:00
Dylan Thacker-Smith
7960826552 Rename render_profiling_test.rb to profiler_test.rb
so it corresponds to the class name being tested
2020-12-09 10:06:02 -05:00
Dylan Thacker-Smith
84059691b8 Merge pull request #1365 from Shopify/profiling-multiple-renders
Support using a profiler for multiple renders
2020-12-09 10:05:48 -05:00
Dylan Thacker-Smith
896288eff1 Move start of profiling to a Document#render_to_output_buffer patch 2020-12-09 10:04:34 -05:00
Dylan Thacker-Smith
b3f132efd1 Fix total_render_time if a Profiler gets used for multiple renders 2020-12-09 10:04:34 -05:00
Dylan Thacker-Smith
60214b957c Store the profiler in the context instead of a thread-local variable (#1364) 2020-12-09 10:04:20 -05:00
Dylan Thacker-Smith
7361220af6 Merge pull request #1363 from Shopify/profiler-context-template-name
Fix template name in profile result for render tag timing objects
2020-12-09 10:03:42 -05:00
Dylan Thacker-Smith
cb2ad71a31 Remove the Profiler#initialize argument which is effectively now unused
The @root_timing Timing object that it was used with never exposed that
name.
2020-12-09 10:01:16 -05:00
Dylan Thacker-Smith
900e3a6491 Fix template name in profile result for render tag timing objects 2020-12-09 10:01:16 -05:00
Dylan Thacker-Smith
f18084203d Use monotonic time to measure durations in Liquid::Profiler (#1362) 2020-12-09 10:00:44 -05:00
Peter Zhu
3358a892f2 Merge pull request #1371 from Shopify/pz-revert-instrument
Revert instrumentation of end_tag_params and range_float
2020-12-01 15:24:52 -05:00
Peter Zhu
bbfcaa2cc0 Revert "Merge pull request #1350 from Shopify/pz-instrument-invalid-end-tag"
This reverts commit e6eef4b2c4, reversing
changes made to c7c21e88f0.
2020-12-01 14:38:04 -05:00
Peter Zhu
ba657871bc Revert "Merge pull request #1359 from Shopify/pz-instrument-range-floats"
This reverts commit 300adfd7ae, reversing
changes made to ed0aebcbc9.
2020-12-01 14:37:23 -05:00
Peter Zhu
29d5d9674a Merge pull request #1370 from Shopify/pz-freeze-case-body
Freeze the body for case tags
2020-11-27 12:03:03 -05:00
Peter Zhu
0a645e72c1 Freeze the body for case 2020-11-27 11:29:17 -05:00
Dylan Thacker-Smith
1850511334 Use an atomic subgroup in range regex to avoid pathological backtracking (#1360) 2020-11-16 10:29:36 -05:00
Peter Zhu
300adfd7ae Merge pull request #1359 from Shopify/pz-instrument-range-floats
Instrument usage of floats in ranges
2020-11-13 16:08:47 -05:00
Peter Zhu
f357662f37 Instrument floats in ranges 2020-11-13 16:05:29 -05:00
50 changed files with 572 additions and 1322 deletions

View File

@@ -6,9 +6,8 @@ jobs:
strategy:
matrix:
entry:
- { ruby: 2.5, allowed-failure: false }
- { ruby: 2.6, allowed-failure: false }
- { ruby: 2.7, allowed-failure: false }
- { 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:

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,7 @@
inherit_gem:
rubocop-shopify: rubocop.yml
inherit_from:
- 'https://shopify.github.io/ruby-style-guide/rubocop.yml'
- .rubocop_todo.yml
require: rubocop-performance
@@ -8,10 +10,15 @@ Performance:
Enabled: true
AllCops:
TargetRubyVersion: 2.4
TargetRubyVersion: 2.5
NewCops: disable
Exclude:
- 'vendor/bundle/**/*'
Naming/MethodName:
Exclude:
- 'example/server/liquid_servlet.rb'
# Backport https://github.com/Shopify/ruby-style-guide/pull/258
Layout/BeginEndAlignment:
Enabled: true

View File

@@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2019-09-11 06:34:25 +1000 using RuboCop version 0.74.0.
# on 2020-12-11 18:53:41 UTC using RuboCop version 1.6.1.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
@@ -14,14 +14,26 @@ Lint/InheritException:
Exclude:
- 'lib/liquid/interrupts.rb'
# Offense count: 98
# Offense count: 113
# Cop supports --auto-correct.
# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
# URISchemes: http, https
Layout/LineLength:
Max: 294
Max: 260
# Offense count: 44
# Offense count: 8
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:
Exclude:
- 'lib/liquid.rb'
@@ -32,7 +44,6 @@ Naming/ConstantName:
- 'lib/liquid/tags/cycle.rb'
- 'lib/liquid/tags/for.rb'
- 'lib/liquid/tags/if.rb'
- 'lib/liquid/tags/include.rb'
- 'lib/liquid/tags/raw.rb'
- 'lib/liquid/tags/table_row.rb'
- 'lib/liquid/variable.rb'
@@ -40,9 +51,7 @@ Naming/ConstantName:
- 'performance/shopify/paginate.rb'
- 'test/integration/tags/include_tag_test.rb'
# Offense count: 5
# Offense count: 2
Style/ClassVars:
Exclude:
- 'lib/liquid/condition.rb'
- 'lib/liquid/strainer.rb'
- 'lib/liquid/template.rb'

View File

@@ -5,6 +5,7 @@
* Bugfixes
* Performance improvements
* 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
@@ -14,12 +15,14 @@
* Features that can easily be implemented on top of Liquid (for example as a custom filter or custom filesystem)
* Code that does not include tests
* Code that breaks existing tests
* Documentation changes that are verbose, incorrect or not important to most people (we want to keep it simple and easy to understand)
## Workflow
* [Sign the CLA](https://cla.shopify.com/) if you haven't already
* Fork the Liquid repository
* 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
* Make sure all tests pass (`bundle exec rake`)
* Create a pull request

View File

@@ -18,7 +18,8 @@ group :benchmark, :test do
end
group :test do
gem 'rubocop', '~> 0.78.0', require: false
gem 'rubocop', '~> 1.4', require: false
gem 'rubocop-shopify', '~> 1.0.7', require: false
gem 'rubocop-performance', require: false
platform :mri, :truffleruby do

View File

@@ -1,10 +1,55 @@
# Liquid Change Log
### Unreleased
## 5.0.2 (unreleased)
* Split Strainer class as a factory and a template (#1208) [Thierry Joyal]
### Features
* Add `base64_encode`, `base64_decode`, `base64_url_safe_encode`, and `base64_url_safe_decode` filters (#1450) [Daniel Insley]
### Fixes
* Fix support for using a String subclass for the liquid source (#1421) [Dylan Thacker-Smith]
## 5.0.1 / 2021-03-24
### Fixes
* Add ParseTreeVisitor to Echo tag (#1414) [CP Clermont]
* Test with ruby 3.0 as the latest ruby version (#1398) [Dylan Thacker-Smith]
* Handle carriage return in newlines_to_br (#1391) [Unending]
### Performance Improvements
* Use split limit in truncatewords (#1361) [Dylan Thacker-Smith]
## 5.0.0 / 2021-01-06
### Features
* 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]
* StaticRegisters#fetch to raise on missing key (#1250) [Thierry Joyal]
* Handle `BlockBody#blank?` at parse time (#1287) [Dylan Thacker-Smith]
* 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

View File

@@ -77,9 +77,6 @@ module Liquid
body.parse(tokens, parse_context) do |end_tag_name, end_tag_params|
@blank &&= body.blank?
# Instrument for bug 1346
Usage.increment("end_tag_params") if end_tag_params && !end_tag_params.empty?
return false if end_tag_name == block_delimiter
raise_tag_never_closed(block_name) unless end_tag_name

View File

@@ -99,7 +99,9 @@ module Liquid
end
private def parse_liquid_tag(markup, parse_context)
liquid_tag_tokenizer = Tokenizer.new(markup, line_number: parse_context.line_number, for_liquid_tag: true)
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)

View File

@@ -134,8 +134,8 @@ module Liquid
# return this as the result.
return context.evaluate(left) if op.nil?
left = context.evaluate(left)
right = context.evaluate(right)
left = Liquid::Utils.to_liquid_value(context.evaluate(left))
right = Liquid::Utils.to_liquid_value(context.evaluate(right))
operation = self.class.operators[op] || raise(Liquid::ArgumentError, "Unknown operator #{op}")

View File

@@ -18,8 +18,8 @@ module Liquid
attr_accessor :exception_renderer, :template_name, :partial, :global_filter, :strict_variables, :strict_filters
# rubocop:disable Metrics/ParameterLists
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)
def self.build(environments: {}, outer_scope: {}, registers: {}, rethrow_errors: false, resource_limits: nil, static_environments: {}, &block)
new(environments, outer_scope, registers, rethrow_errors, resource_limits, static_environments, &block)
end
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_environments = {})
@@ -44,6 +44,8 @@ module Liquid
self.exception_renderer = Liquid::RAISE_EXCEPTION_LAMBDA
end
yield self if block_given?
# Do this last, since it could result in this object being passed to a Proc in the environment
squash_instance_assigns_with_environments
end
@@ -135,7 +137,7 @@ module Liquid
def new_isolated_subcontext
check_overflow
Context.build(
self.class.build(
resource_limits: resource_limits,
static_environments: static_environments,
registers: StaticRegisters.new(registers)

View File

@@ -42,7 +42,7 @@ module Liquid
end
def render(context)
@body.render(context)
render_to_output_buffer(context, +'')
end
private

View File

@@ -14,7 +14,10 @@ module Liquid
DOUBLE_QUOTED_STRING = /\A\s*"(.*)"\s*\z/m
INTEGERS_REGEX = /\A\s*(-?\d+)\s*\z/
FLOATS_REGEX = /\A\s*(-?\d[\d\.]+)\s*\z/
RANGES_REGEX = /\A\s*\(\s*(\S+)\s*\.\.\s*(\S+)\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)
case markup

View File

@@ -23,6 +23,10 @@ module Liquid
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

View File

@@ -25,7 +25,7 @@ module Liquid
# node.code
#
# # Which template and line number of this node.
# # If top level, this will be "<root>".
# # The top-level template name is `nil` by default, but can be set in the Liquid::Context before rendering.
# node.partial
# node.line_number
#
@@ -46,126 +46,94 @@ module Liquid
include Enumerable
class Timing
attr_reader :code, :partial, :line_number, :children, :total_time, :self_time
attr_reader :code, :template_name, :line_number, :children
attr_accessor :total_time
alias_method :render_time, :total_time
alias_method :partial, :template_name
def initialize(node, partial)
@code = node.respond_to?(:raw) ? node.raw : node
@partial = partial
@line_number = node.respond_to?(:line_number) ? node.line_number : nil
@children = []
def initialize(code: nil, template_name: nil, line_number: nil)
@code = code
@template_name = template_name
@line_number = line_number
@children = []
end
def self.start(node, partial)
new(node, partial).tap(&:start)
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
def self_time
@self_time ||= begin
total_children_time = 0.0
@children.each do |child|
total_children_time += child.total_time
end
@self_time = @total_time - total_children_time
@total_time - total_children_time
end
end
end
def render_time
@end_time - @start_time
attr_reader :total_time
alias_method :total_render_time, :total_time
def initialize
@root_children = []
@current_children = nil
@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 self.profile_node_render(node)
if Profiler.current_profile && node.respond_to?(:render)
Profiler.current_profile.start_node(node)
output = yield
Profiler.current_profile.end_node(node)
output
def children
children = @root_children
if children.length == 1
children.first.children
else
yield
children
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)
@root_timing.children.each(&block)
children.each(&block)
end
def [](idx)
@root_timing.children[idx]
children[idx]
end
def length
@root_timing.children.length
children.length
end
def start_node(node)
@timing_stack.push(Timing.start(node, current_partial))
def profile_node(template_name, code: nil, line_number: nil)
timing = Timing.new(code: code, template_name: template_name, line_number: line_number)
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
def end_node(_node)
timing = @timing_stack.pop
timing.finish
private
@timing_stack.last.children << timing
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
def monotonic_time
Process.clock_gettime(Process::CLOCK_MONOTONIC)
end
end
end

View File

@@ -3,19 +3,33 @@
module Liquid
module BlockBodyProfilingHook
def render_node(context, output, node)
Profiler.profile_node_render(node) do
if (profiler = context.profiler)
profiler.profile_node(context.template_name, code: node.raw, line_number: node.line_number) do
super
end
else
super
end
end
end
BlockBody.prepend(BlockBodyProfilingHook)
module IncludeProfilingHook
module DocumentProfilingHook
def render_to_output_buffer(context, output)
Profiler.profile_children(context.evaluate(@template_name_expr).to_s) do
super
end
return super unless context.profiler
context.profiler.profile(context.template_name) { super }
end
end
Include.prepend(IncludeProfilingHook)
Document.prepend(DocumentProfilingHook)
module ContextProfilingHook
attr_accessor :profiler
def new_isolated_subcontext
new_context = super
new_context.profiler = profiler
new_context
end
end
Context.prepend(ContextProfilingHook)
end

View File

@@ -1,10 +1,12 @@
# frozen_string_literal: true
require 'cgi'
require 'base64'
require 'bigdecimal'
module Liquid
module StandardFilters
MAX_INT = (1 << 31) - 1
HTML_ESCAPE = {
'&' => '&amp;',
'>' => '&gt;',
@@ -62,6 +64,26 @@ module Liquid
result
end
def base64_encode(input)
Base64.strict_encode64(input.to_s)
end
def base64_decode(input)
Base64.strict_decode64(input.to_s)
rescue ::ArgumentError
raise Liquid::ArgumentError, "invalid base64 provided to base64_decode"
end
def base64_url_safe_encode(input)
Base64.urlsafe_encode64(input.to_s)
end
def base64_url_safe_decode(input)
Base64.urlsafe_decode64(input.to_s)
rescue ::ArgumentError
raise Liquid::ArgumentError, "invalid base64 provided to base64_url_safe_decode"
end
def slice(input, offset, length = nil)
offset = Utils.to_integer(offset)
length = length ? Utils.to_integer(length) : 1
@@ -89,13 +111,21 @@ module Liquid
def truncatewords(input, words = 15, truncate_string = "...")
return if input.nil?
wordlist = input.to_s.split
words = Utils.to_integer(words)
input = input.to_s
words = Utils.to_integer(words)
words = 1 if words <= 0
l = words - 1
l = 0 if l < 0
wordlist = begin
input.split(" ", words + 1)
rescue RangeError
raise if words + 1 < MAX_INT
# e.g. integer #{words} too big to convert to `int'
raise Liquid::ArgumentError, "integer #{words} too big for truncatewords"
end
return input if wordlist.length <= words
wordlist.length > l ? wordlist[0..l].join(" ").concat(truncate_string.to_s) : input
wordlist.pop
wordlist.join(" ").concat(truncate_string.to_s)
end
# Split input string into an array of substrings separated by given pattern.
@@ -295,7 +325,7 @@ module Liquid
# Add <br /> tags in front of all newlines in input string
def newline_to_br(input)
input.to_s.gsub(/\n/, "<br />\n")
input.to_s.gsub(/\r?\n/, "<br />\n")
end
# Reformat a date using Ruby's core Time#strftime( string ) -> string

View File

@@ -19,15 +19,16 @@ module Liquid
end
def parse(tokens)
body = new_body
body = case_body = new_body
body = @blocks.last.attachment while parse_body(body, tokens)
@blocks.each do |condition|
@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
def nodelist
@@ -51,7 +52,14 @@ module Liquid
@blocks.each do |block|
if block.else?
block.attachment.render_to_output_buffer(context, output) if execute_else_block
elsif block.evaluate(context)
next
end
result = Liquid::Utils.to_liquid_value(
block.evaluate(context)
)
if result
execute_else_block = false
block.attachment.render_to_output_buffer(context, output)
end

View File

@@ -12,6 +12,8 @@ module Liquid
# {% echo user | link %}
#
class Echo < Tag
attr_reader :variable
def initialize(tag_name, markup, parse_context)
super
@variable = Variable.new(markup, parse_context)
@@ -20,6 +22,12 @@ module Liquid
def render(context)
@variable.render_to_output_buffer(context, +'')
end
class ParseTreeVisitor < Liquid::ParseTreeVisitor
def children
[@node.variable]
end
end
end
Template.register_tag('echo', Echo)

View File

@@ -63,11 +63,11 @@ module Liquid
parse_body(@else_block, tokens)
end
if blank?
@for_block.remove_blank_strings
@else_block&.remove_blank_strings
@for_block.remove_blank_strings
end
@for_block.freeze
@else_block&.freeze
@for_block.freeze
end
def nodelist

View File

@@ -31,7 +31,7 @@ module Liquid
def parse(tokens)
while parse_body(@blocks.last.attachment, tokens)
end
@blocks.each do |block|
@blocks.reverse_each do |block|
block.attachment.remove_blank_strings if blank?
block.attachment.freeze
end
@@ -50,7 +50,11 @@ module Liquid
def render_to_output_buffer(context, output)
@blocks.each do |block|
if block.evaluate(context)
result = Liquid::Utils.to_liquid_value(
block.evaluate(context)
)
if result
return block.attachment.render_to_output_buffer(context, output)
end
end

View File

@@ -11,13 +11,20 @@ module Liquid
def render_to_output_buffer(context, output)
# First condition is interpreted backwards ( if not )
first_block = @blocks.first
unless first_block.evaluate(context)
result = Liquid::Utils.to_liquid_value(
first_block.evaluate(context)
)
unless result
return first_block.attachment.render_to_output_buffer(context, output)
end
# After the first condition unless works just like if
@blocks[1..-1].each do |block|
if block.evaluate(context)
result = block.evaluate(context)
result = result.to_liquid_value if result.is_a?(Liquid::Drop)
if result
return block.attachment.render_to_output_buffer(context, output)
end
end

View File

@@ -106,12 +106,9 @@ module Liquid
# Parse source code.
# Returns self for easy chaining
def parse(source, options = {})
@options = options
@profiling = options[:profile]
@line_numbers = options[:line_numbers] || @profiling
parse_context = options.is_a?(ParseContext) ? options : ParseContext.new(options)
@root = Document.parse(tokenize(source), parse_context)
@warnings = parse_context.warnings
parse_context = configure_options(options)
tokenizer = parse_context.new_tokenizer(source, start_line_number: @line_numbers && 1)
@root = Document.parse(tokenizer, parse_context)
self
end
@@ -189,11 +186,13 @@ module Liquid
# Retrying a render resets resource usage
context.resource_limits.reset
if @profiling && context.profiler.nil?
@profiler = context.profiler = Liquid::Profiler.new
end
begin
# render the nodelist.
with_profiling(context) do
@root.render_to_output_buffer(context, output || +'')
end
@root.render_to_output_buffer(context, output || +'')
rescue Liquid::MemoryError => e
context.handle_error(e)
ensure
@@ -212,25 +211,17 @@ module Liquid
private
def tokenize(source)
Tokenizer.new(source, @line_numbers)
end
def with_profiling(context)
if @profiling && !context.partial
def configure_options(options)
if (profiling = options[:profile])
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
@options = options
@profiling = profiling
@line_numbers = options[:line_numbers] || @profiling
parse_context = options.is_a?(ParseContext) ? options : ParseContext.new(options)
@warnings = parse_context.warnings
parse_context
end
def apply_options_to_context(context, options)

View File

@@ -5,7 +5,7 @@ module Liquid
attr_reader :line_number, :for_liquid_tag
def initialize(source, line_numbers = false, line_number: nil, for_liquid_tag: false)
@source = source
@source = source.to_s.to_str
@line_number = line_number || (line_numbers ? 1 : nil)
@for_liquid_tag = for_liquid_tag
@tokens = tokenize
@@ -24,7 +24,7 @@ module Liquid
private
def tokenize
return [] if @source.to_s.empty?
return [] if @source.empty?
return @source.split("\n") if @for_liquid_tag

View File

@@ -81,5 +81,13 @@ module Liquid
rescue ::ArgumentError
nil
end
def self.to_liquid_value(obj)
# Enable "obj" to represent itself as a primitive value like integer, string, or boolean
return obj.to_liquid_value if obj.respond_to?(:to_liquid_value)
# Otherwise return the object itself
obj
end
end
end

View File

@@ -40,6 +40,9 @@ module Liquid
@lookups.each_index do |i|
key = context.evaluate(@lookups[i])
# Cast "key" to its liquid value to enable it to act as a primitive value
key = Liquid::Utils.to_liquid_value(key)
# If object is a hash- or array-like object we look for the
# presence of the key and if its available we return it
if object.respond_to?(:[]) &&

View File

@@ -2,5 +2,5 @@
# frozen_string_literal: true
module Liquid
VERSION = "4.0.3"
VERSION = "5.0.2.alpha"
end

View File

@@ -20,6 +20,8 @@ Gem::Specification.new do |s|
s.required_ruby_version = ">= 2.5.0"
s.required_rubygems_version = ">= 1.3.7"
s.metadata['allowed_push_host'] = 'https://rubygems.org'
s.test_files = Dir.glob("{test}/**/*")
s.files = Dir.glob("{lib}/**/*") + %w(LICENSE README.md)

View File

@@ -73,10 +73,14 @@ class ThemeRunner
private
def render_layout(template, layout, assigns)
assigns['content_for_layout'] = template.render!(assigns)
layout&.render!(assigns)
end
def compile_and_render(template, layout, assigns, page_template, template_file)
compiled_test = compile_test(template, layout, assigns, page_template, template_file)
assigns['content_for_layout'] = compiled_test[:tmpl].render!(assigns)
compiled_test[:layout].render!(assigns) if layout
compiled_test = compile_test(template, layout, assigns, page_template, template_file)
render_layout(compiled_test[:tmpl], compiled_test[:layout], compiled_test[:assigns])
end
def compile_all_tests

View File

@@ -44,7 +44,7 @@ class AssignTest < Minitest::Test
end
end
with_error_mode(:lax) do
assert Template.parse("{% assign foo = ('X' | downcase) %}")
assert(Template.parse("{% assign foo = ('X' | downcase) %}"))
end
end

View File

@@ -14,7 +14,7 @@ class BlockTest < Minitest::Test
def test_with_custom_tag
with_custom_tag('testtag', Block) do
assert Liquid::Template.parse("{% testtag %} {% endtesttag %}")
assert(Liquid::Template.parse("{% testtag %} {% endtesttag %}"))
end
end
@@ -28,13 +28,13 @@ class BlockTest < Minitest::Test
with_custom_tag('blabla', klass1) do
template = Liquid::Template.parse("{% blabla %} bla {% endblabla %}")
assert_equal 'hello', template.render
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
assert_equal('hello', output)
assert_equal('hello', buf)
assert_equal(buf.object_id, output.object_id)
end
klass2 = Class.new(klass1) do
@@ -46,33 +46,13 @@ class BlockTest < Minitest::Test
with_custom_tag('blabla', klass2) do
template = Liquid::Template.parse("{% blabla %} foo {% endblabla %}")
assert_equal 'foohellobar', template.render
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
assert_equal('foohellobar', output)
assert_equal('foohellobar', buf)
assert_equal(buf.object_id, output.object_id)
end
end
def test_instrument_for_bug_1346
calls = []
Liquid::Usage.stub(:increment, ->(name) { calls << name }) do
Liquid::Template.parse("{% for i in (1..2) %}{{ i }}{% endfor {% foo %}")
end
assert_equal(["end_tag_params"], calls)
calls = []
Liquid::Usage.stub(:increment, ->(name) { calls << name }) do
Liquid::Template.parse("{% for i in (1..2) %}{{ i }}{% endfor test %}")
end
assert_equal(["end_tag_params"], calls)
calls = []
Liquid::Usage.stub(:increment, ->(name) { calls << name }) do
Liquid::Template.parse("{% for i in (1..2) %}{{ i }}{% endfor %}")
end
assert_equal([], calls)
end
end

View File

@@ -461,6 +461,7 @@ class ContextTest < Minitest::Test
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
@@ -566,9 +567,9 @@ class ContextTest < Minitest::Test
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")
assert_equal(true, context.tag_disabled?("foo"))
assert_equal(true, context.tag_disabled?("bar"))
assert_equal(false, context.tag_disabled?("unknown"))
end
end
@@ -576,19 +577,19 @@ class ContextTest < Minitest::Test
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")
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")
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")
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")
assert_equal(true, context.tag_disabled?("foo"))
assert_equal(false, context.tag_disabled?("bar"))
end
end
@@ -606,16 +607,16 @@ class ContextTest < Minitest::Test
end
with_global_filter(global) do
assert_equal 'Global test', Template.parse("{{'test' | notice }}").render!
assert_equal 'Local test', Template.parse("{{'test' | notice }}").render!({}, filters: [local])
assert_equal('Global test', Template.parse("{{'test' | notice }}").render!)
assert_equal('Local test', Template.parse("{{'test' | notice }}").render!({}, filters: [local]))
end
end
def test_has_key_will_not_add_an_error_for_missing_keys
with_error_mode :strict do
with_error_mode(:strict) do
context = Context.new
context.key?('unknown')
assert_empty context.errors
assert_empty(context.errors)
end
end

View File

@@ -167,7 +167,7 @@ class DropsTest < Minitest::Test
def test_object_methods_not_allowed
[:dup, :clone, :singleton_class, :eval, :class_eval, :inspect].each do |method|
output = Liquid::Template.parse(" {{ product.#{method} }} ").render!('product' => ProductDrop.new)
assert_equal ' ', output
assert_equal(' ', output)
end
end
@@ -210,28 +210,28 @@ class DropsTest < Minitest::Test
def test_enumerable_drop_will_invoke_liquid_method_missing_for_clashing_method_names
["select", "each", "map", "cycle"].each do |method|
assert_equal method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new)
assert_equal method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new)
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' => 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))
end
end
def test_some_enumerable_methods_still_get_invoked
[: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' => EnumerableDrop.new)
assert_equal "3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.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))
end
assert_equal("yes", Liquid::Template.parse("{% if collection contains 3 %}yes{% endif %}").render!('collection' => RealEnumerableDrop.new))
[: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' => EnumerableDrop.new)
assert_equal "1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.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))
end
end

View File

@@ -193,7 +193,7 @@ class ErrorHandlingTest < Minitest::Test
# Liquid should not catch Exceptions that are not subclasses of StandardError, like Interrupt and NoMemoryError
def test_exceptions_propagate
assert_raises Exception do
assert_raises(Exception) do
template = Liquid::Template.parse('{{ errors.exception }}')
template.render('errors' => ErrorDrop.new)
end

View File

@@ -169,9 +169,9 @@ class FiltersInTemplate < Minitest::Test
def test_local_global
with_global_filter(MoneyFilter) do
assert_equal " 1000$ ", Template.parse("{{1000 | money}}").render!(nil, nil)
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, filters: CanadianMoneyFilter)
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, filters: [CanadianMoneyFilter])
assert_equal(" 1000$ ", 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]))
end
end

View File

@@ -19,7 +19,7 @@ class HashOrderingTest < Minitest::Test
def test_global_register_order
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

View File

@@ -32,7 +32,7 @@ class ParsingQuirksTest < Minitest::Test
assert(Template.parse("{{test}}"))
with_error_mode(:lax) do
assert Template.parse("{{|test}}")
assert(Template.parse("{{|test}}"))
end
with_error_mode(:strict) do

View File

@@ -2,7 +2,7 @@
require 'test_helper'
class RenderProfilingTest < Minitest::Test
class ProfilerTest < Minitest::Test
include Liquid
class ProfilingFileSystem
@@ -62,6 +62,17 @@ class RenderProfilingTest < Minitest::Test
assert_equal(2, included_children[1].line_number)
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
t = Template.parse("{% include 'a_template' %}", profile: true)
t.render!
@@ -77,6 +88,37 @@ class RenderProfilingTest < Minitest::Test
assert(t.profiler.total_render_time >= 0, "Total render time was not calculated")
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
t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}", profile: true)
t.render!
@@ -91,7 +133,7 @@ class RenderProfilingTest < Minitest::Test
include_node = t.profiler[1]
include_node.children.each do |child|
assert_equal "a_template", child.partial
assert_equal("a_template", child.partial)
end
end
@@ -101,12 +143,12 @@ class RenderProfilingTest < Minitest::Test
a_template = t.profiler[1]
a_template.children.each do |child|
assert_equal "a_template", child.partial
assert_equal("a_template", child.partial)
end
b_template = t.profiler[2]
b_template.children.each do |child|
assert_equal "b_template", child.partial
assert_equal("b_template", child.partial)
end
end
@@ -116,12 +158,12 @@ class RenderProfilingTest < Minitest::Test
a_template1 = t.profiler[1]
a_template1.children.each do |child|
assert_equal "a_template", child.partial
assert_equal("a_template", child.partial)
end
a_template2 = t.profiler[2]
a_template2.children.each do |child|
assert_equal "a_template", child.partial
assert_equal("a_template", child.partial)
end
end

View File

@@ -145,6 +145,40 @@ class StandardFiltersTest < Minitest::Test
assert_equal('&lt;strong&gt;Hulk&lt;/strong&gt;', @filters.escape_once('&lt;strong&gt;Hulk</strong>'))
end
def test_base64_encode
assert_equal('b25lIHR3byB0aHJlZQ==', @filters.base64_encode('one two three'))
assert_equal('', @filters.base64_encode(nil))
end
def test_base64_decode
assert_equal('one two three', @filters.base64_decode('b25lIHR3byB0aHJlZQ=='))
exception = assert_raises(Liquid::ArgumentError) do
@filters.base64_decode("invalidbase64")
end
assert_equal('Liquid error: invalid base64 provided to base64_decode', exception.message)
end
def test_base64_url_safe_encode
assert_equal(
'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXogQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVogMTIzNDU2Nzg5MCAhQCMkJV4mKigpLT1fKy8_Ljo7W117fVx8',
@filters.base64_url_safe_encode('abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 !@#$%^&*()-=_+/?.:;[]{}\|')
)
assert_equal('', @filters.base64_url_safe_encode(nil))
end
def test_base64_url_safe_decode
assert_equal(
'abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 !@#$%^&*()-=_+/?.:;[]{}\|',
@filters.base64_url_safe_decode('YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXogQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVogMTIzNDU2Nzg5MCAhQCMkJV4mKigpLT1fKy8_Ljo7W117fVx8')
)
exception = assert_raises(Liquid::ArgumentError) do
@filters.base64_url_safe_decode("invalidbase64")
end
assert_equal('Liquid error: invalid base64 provided to base64_url_safe_decode', exception.message)
end
def test_url_encode
assert_equal('foo%2B1%40example.com', @filters.url_encode('foo+1@example.com'))
assert_equal('1', @filters.url_encode(1))
@@ -159,7 +193,7 @@ class StandardFiltersTest < Minitest::Test
assert_equal('1', @filters.url_decode(1))
assert_equal('2001-02-03', @filters.url_decode(Date.new(2001, 2, 3)))
assert_nil(@filters.url_decode(nil))
exception = assert_raises Liquid::ArgumentError do
exception = assert_raises(Liquid::ArgumentError) do
@filters.url_decode('%ff')
end
assert_equal('Liquid error: invalid byte sequence in UTF-8', exception.message)
@@ -171,10 +205,17 @@ class StandardFiltersTest < Minitest::Test
assert_equal('one two three', @filters.truncatewords('one two three'))
assert_equal(
'Two small (13&#8221; x 5.5&#8221; x 10&#8221; high) baskets fit inside one large basket (13&#8221;...',
@filters.truncatewords('Two small (13&#8221; x 5.5&#8221; x 10&#8221; high) baskets fit inside one large basket (13&#8221; x 16&#8221; x 10.5&#8221; high) with cover.', 15)
@filters.truncatewords('Two small (13&#8221; x 5.5&#8221; x 10&#8221; high) baskets fit inside one large basket (13&#8221; x 16&#8221; x 10.5&#8221; high) with cover.', 15)
)
assert_equal("测试测试测试测试", @filters.truncatewords('测试测试测试测试', 5))
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))
exception = assert_raises(Liquid::ArgumentError) do
@filters.truncatewords("one two three four", 1 << 31)
end
assert_equal("Liquid error: integer #{1 << 31} too big for truncatewords", exception.message)
end
def test_strip_html
@@ -286,7 +327,7 @@ class StandardFiltersTest < Minitest::Test
[3],
]
assert_raises Liquid::ArgumentError do
assert_raises(Liquid::ArgumentError) do
@filters.sort(foo, "bar")
end
end
@@ -302,7 +343,7 @@ class StandardFiltersTest < Minitest::Test
[3],
]
assert_raises Liquid::ArgumentError do
assert_raises(Liquid::ArgumentError) do
@filters.sort_natural(foo, "bar")
end
end
@@ -337,7 +378,7 @@ class StandardFiltersTest < Minitest::Test
[3],
]
assert_raises Liquid::ArgumentError do
assert_raises(Liquid::ArgumentError) do
@filters.uniq(foo, "bar")
end
end
@@ -353,7 +394,7 @@ class StandardFiltersTest < Minitest::Test
[3],
]
assert_raises Liquid::ArgumentError do
assert_raises(Liquid::ArgumentError) do
@filters.compact(foo, "bar")
end
end
@@ -430,7 +471,7 @@ class StandardFiltersTest < Minitest::Test
[3],
]
assert_raises Liquid::ArgumentError do
assert_raises(Liquid::ArgumentError) do
@filters.map(foo, "bar")
end
end
@@ -441,7 +482,7 @@ class StandardFiltersTest < Minitest::Test
[2],
[3],
]
assert_raises Liquid::ArgumentError do
assert_raises(Liquid::ArgumentError) do
@filters.map(foo, nil)
end
end
@@ -485,8 +526,8 @@ class StandardFiltersTest < Minitest::Test
assert_equal('', @filters.date('', "%B"))
with_timezone("UTC") do
assert_equal "07/05/2006", @filters.date(1152098955, "%m/%d/%Y")
assert_equal "07/05/2006", @filters.date("1152098955", "%m/%d/%Y")
assert_equal("07/05/2006", @filters.date(1152098955, "%m/%d/%Y"))
assert_equal("07/05/2006", @filters.date("1152098955", "%m/%d/%Y"))
end
end
@@ -539,6 +580,7 @@ class StandardFiltersTest < Minitest::Test
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\r\nb\nc")
end
def test_plus
@@ -587,7 +629,7 @@ class StandardFiltersTest < Minitest::Test
assert_template_result("0.5", "{{ 2.0 | divided_by:4 }}")
assert_raises(Liquid::ZeroDivisionError) do
assert_template_result "4", "{{ 1 | modulo: 0 }}"
assert_template_result("4", "{{ 1 | modulo: 0 }}")
end
assert_template_result("5", "{{ price | divided_by:2 }}", 'price' => NumberLikeThing.new(10))
@@ -596,7 +638,7 @@ class StandardFiltersTest < Minitest::Test
def test_modulo
assert_template_result("1", "{{ 3 | modulo:2 }}")
assert_raises(Liquid::ZeroDivisionError) do
assert_template_result "4", "{{ 1 | modulo: 0 }}"
assert_template_result("4", "{{ 1 | modulo: 0 }}")
end
assert_template_result("1", "{{ price | modulo:2 }}", 'price' => NumberLikeThing.new(3))
@@ -607,7 +649,7 @@ class StandardFiltersTest < Minitest::Test
assert_template_result("4", "{{ '4.3' | round }}")
assert_template_result("4.56", "{{ input | round: 2 }}", 'input' => 4.5612)
assert_raises(Liquid::FloatDomainError) do
assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | round }}"
assert_template_result("4", "{{ 1.0 | divided_by: 0.0 | round }}")
end
assert_template_result("5", "{{ price | round }}", 'price' => NumberLikeThing.new(4.6))
@@ -618,7 +660,7 @@ class StandardFiltersTest < Minitest::Test
assert_template_result("5", "{{ input | ceil }}", 'input' => 4.6)
assert_template_result("5", "{{ '4.3' | ceil }}")
assert_raises(Liquid::FloatDomainError) do
assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | ceil }}"
assert_template_result("4", "{{ 1.0 | divided_by: 0.0 | ceil }}")
end
assert_template_result("5", "{{ price | ceil }}", 'price' => NumberLikeThing.new(4.6))
@@ -628,7 +670,7 @@ class StandardFiltersTest < Minitest::Test
assert_template_result("4", "{{ input | floor }}", 'input' => 4.6)
assert_template_result("4", "{{ '4.3' | floor }}")
assert_raises(Liquid::FloatDomainError) do
assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | floor }}"
assert_template_result("4", "{{ 1.0 | divided_by: 0.0 | floor }}")
end
assert_template_result("5", "{{ price | floor }}", 'price' => NumberLikeThing.new(5.4))

View File

@@ -15,13 +15,13 @@ class TagTest < Minitest::Test
with_custom_tag('blabla', klass1) do
template = Liquid::Template.parse("{% blabla %}")
assert_equal 'hello', template.render
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
assert_equal('hello', output)
assert_equal('hello', buf)
assert_equal(buf.object_id, output.object_id)
end
klass2 = Class.new(klass1) do
@@ -33,13 +33,13 @@ class TagTest < Minitest::Test
with_custom_tag('blabla', klass2) do
template = Liquid::Template.parse("{% blabla %}")
assert_equal 'foohellobar', template.render
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
assert_equal('foohellobar', output)
assert_equal('foohellobar', buf)
assert_equal(buf.object_id, output.object_id)
end
end
end

View File

@@ -184,7 +184,7 @@ class IfElseTagTest < Minitest::Test
tests.each do |vals, expected|
a, b, c = vals
assigns = { 'a' => a, 'b' => b, 'c' => c }
assert_template_result expected.to_s, tpl, assigns, assigns.to_s
assert_template_result(expected.to_s, tpl, assigns, assigns.to_s)
end
end
end

View File

@@ -96,12 +96,12 @@ class IncludeTagTest < Minitest::Test
def test_include_tag_with_alias
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
def test_include_tag_for_alias
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
def test_include_tag_with_default_name
@@ -226,13 +226,13 @@ class IncludeTagTest < Minitest::Test
Template.parse("{% include template %}", error_mode: :strict).render!("template" => '{{ "X" || downcase }}')
end
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
assert_raises(Liquid::SyntaxError) do
Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: [:locale]).render!("template" => '{{ "X" || downcase }}')
end
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

View File

@@ -58,7 +58,7 @@ class RenderTagTest < Minitest::Test
def test_recursively_rendered_template_does_not_produce_endless_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!
end
end
@@ -67,7 +67,7 @@ class RenderTagTest < Minitest::Test
Liquid::Template.file_system = StubFileSystem.new(
'loop_render' => '{% render "loop_render" %}',
)
assert_raises Liquid::StackLevelError do
assert_raises(Liquid::StackLevelError) do
Template.parse('{% render "loop_render" %}').render!
end
end
@@ -75,7 +75,7 @@ class RenderTagTest < Minitest::Test
def test_dynamically_choosen_templates_are_not_allowed
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 %}")
end
end
@@ -151,7 +151,7 @@ class RenderTagTest < Minitest::Test
)
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
def test_render_tag_with_alias
@@ -161,7 +161,7 @@ class RenderTagTest < Minitest::Test
)
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
def test_render_tag_for_alias
@@ -171,7 +171,7 @@ class RenderTagTest < Minitest::Test
)
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
def test_render_tag_for
@@ -181,7 +181,7 @@ class RenderTagTest < Minitest::Test
)
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
def test_render_tag_forloop
@@ -190,7 +190,7 @@ class RenderTagTest < Minitest::Test
)
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
def test_render_tag_for_drop
@@ -199,7 +199,7 @@ class RenderTagTest < Minitest::Test
)
assert_template_result("123",
"{% render 'loop' for loop as value %}", "loop" => TestEnumerable.new)
"{% render 'loop' for loop as value %}", "loop" => TestEnumerable.new)
end
def test_render_tag_with_drop
@@ -208,6 +208,6 @@ class RenderTagTest < Minitest::Test
)
assert_template_result("TestEnumerable",
"{% render 'loop' with loop as value %}", "loop" => TestEnumerable.new)
"{% render 'loop' with loop as value %}", "loop" => TestEnumerable.new)
end
end

View File

@@ -198,7 +198,7 @@ class TemplateTest < Minitest::Test
context = Context.new('drop' => ErroneousDrop.new)
t = Template.new.parse('{{ drop.bad_method }}')
e = assert_raises RuntimeError do
e = assert_raises(RuntimeError) do
t.render!(context)
end
assert_equal('ruby error in drop', e.message)
@@ -267,7 +267,7 @@ class TemplateTest < Minitest::Test
def test_undefined_variables_raise
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)
end
end
@@ -286,7 +286,7 @@ class TemplateTest < Minitest::Test
d = DropWithUndefinedMethod.new
t = Template.new.parse('{{ foo }} {{ woot }}')
assert_raises UndefinedDropMethod do
assert_raises(UndefinedDropMethod) do
t.render!(d, strict_variables: true)
end
end
@@ -309,7 +309,7 @@ class TemplateTest < Minitest::Test
def test_undefined_filters_raise
t = Template.parse("{{x | somefilter1 | upcase | somefilter2}}")
assert_raises UndefinedFilter do
assert_raises(UndefinedFilter) do
t.render!({ 'x' => 'foo' }, strict_filters: true)
end
end
@@ -323,4 +323,18 @@ class TemplateTest < Minitest::Test
result = t.render('x' => 1, 'y' => 5)
assert_equal('12345', result)
end
def test_source_string_subclass
string_subclass = Class.new(String) do
# E.g. ActiveSupport::SafeBuffer does this, so don't just rely on to_s to return a String
def to_s
self
end
end
source = string_subclass.new("{% assign x = 2 -%} x= {{- x }}")
assert_instance_of(string_subclass, source)
output = Template.parse(source).render!
assert_equal("x=2", output)
assert_instance_of(String, output)
end
end

View File

@@ -15,6 +15,33 @@ class VariableTest < Minitest::Test
assert_template_result('foobar', '{{ foo }}', 'foo' => ThingWithToLiquid.new)
end
def test_variable_lookup_calls_to_liquid_value
assert_template_result('1', '{{ foo }}', 'foo' => IntegerDrop.new('1'))
assert_template_result('2', '{{ list[foo] }}', 'foo' => IntegerDrop.new('1'), 'list' => [1, 2, 3])
assert_template_result('one', '{{ list[foo] }}', 'foo' => IntegerDrop.new('1'), 'list' => { 1 => 'one' })
assert_template_result('Yay', '{{ foo }}', 'foo' => BooleanDrop.new(true))
assert_template_result('YAY', '{{ foo | upcase }}', 'foo' => BooleanDrop.new(true))
end
def test_if_tag_calls_to_liquid_value
assert_template_result('one', '{% if foo == 1 %}one{% endif %}', 'foo' => IntegerDrop.new('1'))
assert_template_result('one', '{% if 0 < foo %}one{% endif %}', 'foo' => IntegerDrop.new('1'))
assert_template_result('one', '{% if foo > 0 %}one{% endif %}', 'foo' => IntegerDrop.new('1'))
assert_template_result('true', '{% if foo == true %}true{% endif %}', 'foo' => BooleanDrop.new(true))
assert_template_result('true', '{% if foo %}true{% endif %}', 'foo' => BooleanDrop.new(true))
assert_template_result('', '{% if foo %}true{% endif %}', 'foo' => BooleanDrop.new(false))
assert_template_result('', '{% if foo == true %}True{% endif %}', 'foo' => BooleanDrop.new(false))
end
def test_unless_tag_calls_to_liquid_value
assert_template_result('', '{% unless foo %}true{% endunless %}', 'foo' => BooleanDrop.new(true))
end
def test_case_tag_calls_to_liquid_value
assert_template_result('One', '{% case foo %}{% when 1 %}One{% endcase %}', 'foo' => IntegerDrop.new('1'))
end
def test_simple_with_whitespaces
template = Template.parse(%( {{ test }} ))
assert_equal(' worked ', template.render!('test' => 'worked'))
@@ -104,4 +131,8 @@ class VariableTest < Minitest::Test
def test_dynamic_find_var
assert_template_result('bar', '{{ [key] }}', 'key' => 'foo', 'foo' => 'bar')
end
def test_raw_value_variable
assert_template_result('bar', '{{ [key] }}', 'key' => 'foo', 'foo' => 'bar')
end
end

View File

@@ -119,6 +119,44 @@ class ThingWithToLiquid
end
end
class IntegerDrop < Liquid::Drop
def initialize(value)
super()
@value = value.to_i
end
def ==(other)
@value == other
end
def to_s
@value.to_s
end
def to_liquid_value
@value
end
end
class BooleanDrop < Liquid::Drop
def initialize(value)
super()
@value = value
end
def ==(other)
@value == other
end
def to_liquid_value
@value
end
def to_s
@value ? "Yay" : "Nay"
end
end
class ErrorDrop < Liquid::Drop
def standard_error
raise Liquid::StandardError, 'standard error'

View File

@@ -28,7 +28,7 @@ class I18nUnitTest < Minitest::Test
# end
def test_raises_unknown_translation
assert_raises I18n::TranslationError do
assert_raises(I18n::TranslationError) do
@i18n.translate("doesnt_exist")
end
end

View File

@@ -26,6 +26,13 @@ class ParseTreeVisitorTest < Minitest::Test
)
end
def test_echo
assert_equal(
["test"],
visit(%({% echo test %}))
)
end
def test_if_condition
assert_equal(
["test"],

View File

@@ -32,21 +32,26 @@ class TokenizerTest < Minitest::Test
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)
tokenizer = Liquid::Tokenizer.new(source)
tokenizer = new_tokenizer(source)
tokens = []
while (t = tokenizer.shift)
# shift is private in Liquid::C::Tokenizer, since it is only for unit testing
while (t = tokenizer.send(:shift))
tokens << t
end
tokens
end
def tokenize_line_numbers(source)
tokenizer = Liquid::Tokenizer.new(source, true)
tokenizer = new_tokenizer(source, start_line_number: 1)
line_numbers = []
loop do
line_number = tokenizer.line_number
if tokenizer.shift
if tokenizer.send(:shift)
line_numbers << line_number
else
break

View File

@@ -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-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') }
assert_raises(Liquid::SyntaxError) { create_variable('2foo') }