mirror of
https://github.com/Shopify/liquid.git
synced 2026-09-14 00:10:39 -07:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
177a7e9c23 | ||
|
|
44d93e2c9b |
@@ -5,7 +5,7 @@ require 'rake/testtask'
|
||||
$LOAD_PATH.unshift(File.expand_path("../lib", __FILE__))
|
||||
require "liquid/version"
|
||||
|
||||
task(default: [:test, "test:migrator_integration", :rubocop])
|
||||
task(default: [:test, :rubocop])
|
||||
|
||||
desc('run test suite with default parser')
|
||||
Rake::TestTask.new(:base_test) do |t|
|
||||
@@ -33,23 +33,6 @@ task :rubocop do
|
||||
end
|
||||
end
|
||||
|
||||
namespace :test do
|
||||
task :migrator_integration do
|
||||
ENV['LIQUID_MIGRATOR'] = '1'
|
||||
original_parse_mode = ENV['LIQUID_PARSER_MODE']
|
||||
begin
|
||||
Rake::Task['integration_test'].reenable
|
||||
["lax", "strict"].each do |parse_mode|
|
||||
ENV['LIQUID_PARSER_MODE'] = parse_mode
|
||||
Rake::Task['integration_test'].invoke
|
||||
Rake::Task['integration_test'].reenable
|
||||
end
|
||||
ensure
|
||||
ENV['LIQUID_PARSER_MODE'] = original_parse_mode
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc('runs test suite with both strict and lax parsers')
|
||||
task :test do
|
||||
ENV['LIQUID_PARSER_MODE'] = 'lax'
|
||||
|
||||
+1
-1
@@ -68,7 +68,6 @@ require 'liquid/parser_switching'
|
||||
require 'liquid/tag'
|
||||
require 'liquid/tag/disabler'
|
||||
require 'liquid/tag/disableable'
|
||||
require 'liquid/parse_context'
|
||||
require 'liquid/block'
|
||||
require 'liquid/block_body'
|
||||
require 'liquid/document'
|
||||
@@ -82,6 +81,7 @@ require 'liquid/standardfilters'
|
||||
require 'liquid/condition'
|
||||
require 'liquid/utils'
|
||||
require 'liquid/tokenizer'
|
||||
require 'liquid/parse_context'
|
||||
require 'liquid/partial_cache'
|
||||
require 'liquid/usage'
|
||||
require 'liquid/registers'
|
||||
|
||||
@@ -60,21 +60,6 @@ module Liquid
|
||||
@block_delimiter ||= "end#{block_name}"
|
||||
end
|
||||
|
||||
def self.migrate_body(start_tag_name, tokenizer, parse_context)
|
||||
new_body, unknown_tag = BlockBody.migrate(tokenizer, parse_context)
|
||||
|
||||
raise SyntaxError unless unknown_tag
|
||||
|
||||
block_delimiter = "end#{start_tag_name}"
|
||||
if unknown_tag.tag_name == block_delimiter
|
||||
new_body << unknown_tag.replaced_markup("") # markup was ignored on end tags
|
||||
return [new_body, nil]
|
||||
end
|
||||
|
||||
# handle the delimiter tag in the caller
|
||||
[new_body, unknown_tag]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# @api public
|
||||
|
||||
+2
-143
@@ -30,16 +30,6 @@ module Liquid
|
||||
end
|
||||
end
|
||||
|
||||
def self.migrate(tokenizer, parse_context, &block)
|
||||
parse_context.line_number = tokenizer.line_number
|
||||
|
||||
if tokenizer.for_liquid_tag
|
||||
migrate_for_liquid_tag(tokenizer, parse_context, &block)
|
||||
else
|
||||
migrate_for_document(tokenizer, parse_context, &block)
|
||||
end
|
||||
end
|
||||
|
||||
def freeze
|
||||
@nodelist.freeze
|
||||
super
|
||||
@@ -70,57 +60,6 @@ module Liquid
|
||||
yield nil, nil
|
||||
end
|
||||
|
||||
class UnknownTagMigrator
|
||||
attr_reader :tag_name, :markup
|
||||
|
||||
def initialize(match:, markup_capture_number:, tag_name:, markup:)
|
||||
@match = match
|
||||
@tag_name = tag_name
|
||||
@markup = markup
|
||||
@markup_capture_number = markup_capture_number
|
||||
end
|
||||
|
||||
def original_tag_string
|
||||
@match[0]
|
||||
end
|
||||
|
||||
def replaced_markup(new_markup)
|
||||
Utils.match_capture_replace(@match, @markup_capture_number, new_markup)
|
||||
end
|
||||
end
|
||||
|
||||
private_class_method def self.migrate_for_liquid_tag(tokenizer, parse_context)
|
||||
result = +""
|
||||
while (token = tokenizer.shift)
|
||||
token += "\n" if tokenizer.more?
|
||||
if token.empty? || token.match?(WhitespaceOrNothing)
|
||||
result << token
|
||||
else
|
||||
# modified version of LiquidTagToken with following changes:
|
||||
# * TagName is optional, to continue supporting its absence in the comment tag
|
||||
# * trailing spaces is allowed to support the newline appended above and so the tag
|
||||
# migrate method doesn't have to handle trailing whitespace
|
||||
match = token.match(/\A\s*(#{TagName})?\s*(.*?)\s*\z/o)
|
||||
tag_name = match[1]
|
||||
markup = match[2]
|
||||
unless (tag = Template.tags[tag_name])
|
||||
# delegate handling of unknown tags to the caller, where a block tag may treat
|
||||
# it as an end tag or body delimiter.
|
||||
unknown_tag = UnknownTagMigrator.new(
|
||||
match: match, markup_capture_number: 2, tag_name: tag_name, markup: markup
|
||||
)
|
||||
return [result, unknown_tag]
|
||||
end
|
||||
new_markup, new_tag_body = tag.migrate(tag_name, markup, tokenizer, parse_context)
|
||||
result << Utils.match_capture_replace(match, 2, new_markup)
|
||||
result << new_tag_body.to_s
|
||||
end
|
||||
parse_context.line_number = tokenizer.line_number
|
||||
end
|
||||
|
||||
[result, nil]
|
||||
end
|
||||
|
||||
# @api private
|
||||
def self.unknown_tag_in_liquid_tag(tag, parse_context)
|
||||
Block.raise_unknown_tag(tag, 'liquid', '%}', parse_context)
|
||||
@@ -170,31 +109,14 @@ module Liquid
|
||||
end
|
||||
end
|
||||
|
||||
private_class_method def self.migrate_liquid_tag(markup, parse_context)
|
||||
liquid_tag_tokenizer = parse_context.new_tokenizer(
|
||||
markup, start_line_number: parse_context.line_number, for_liquid_tag: true
|
||||
)
|
||||
result, unknown_tag = migrate_for_liquid_tag(liquid_tag_tokenizer, parse_context)
|
||||
raise SyntaxError if unknown_tag
|
||||
result
|
||||
end
|
||||
|
||||
private def handle_invalid_tag_token(token, parse_context)
|
||||
if token.end_with?('%}')
|
||||
yield token, token
|
||||
else
|
||||
BlockBody.raise_missing_tag_terminator(token, parse_context)
|
||||
end
|
||||
end
|
||||
|
||||
private def parse_for_document(tokenizer, parse_context, &block)
|
||||
private def parse_for_document(tokenizer, parse_context)
|
||||
while (token = tokenizer.shift)
|
||||
next if token.empty?
|
||||
case
|
||||
when token.start_with?(TAGSTART)
|
||||
whitespace_handler(token, parse_context)
|
||||
unless token =~ FullToken
|
||||
return handle_invalid_tag_token(token, parse_context, &block)
|
||||
BlockBody.raise_missing_tag_terminator(token, parse_context)
|
||||
end
|
||||
tag_name = Regexp.last_match(2)
|
||||
markup = Regexp.last_match(4)
|
||||
@@ -236,58 +158,6 @@ module Liquid
|
||||
yield nil, nil
|
||||
end
|
||||
|
||||
private_class_method def self.migrate_for_document(tokenizer, parse_context, &block)
|
||||
result = +""
|
||||
while (token = tokenizer.shift)
|
||||
next if token.empty?
|
||||
|
||||
case
|
||||
when token.start_with?(TAGSTART)
|
||||
raise SyntaxError unless token.end_with?('%}')
|
||||
# modified FullToken regex with optional tag name, to allow its absence in a comment tag
|
||||
match = token.match(/\A#{TagStart}#{WhitespaceControl}?(\s*)(#{TagName})?(\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\z/om)
|
||||
tag_name = match[2]
|
||||
markup = match[4]
|
||||
|
||||
if parse_context.line_number
|
||||
# newlines inside the tag should increase the line number,
|
||||
# particularly important for multiline {% liquid %} tags
|
||||
parse_context.line_number += Regexp.last_match(1).count("\n") + Regexp.last_match(3).count("\n")
|
||||
end
|
||||
|
||||
if tag_name == 'liquid'
|
||||
new_markup = Utils.migrate_stripped(markup) do |stripped_markup|
|
||||
migrate_liquid_tag(stripped_markup, parse_context)
|
||||
end
|
||||
result << Utils.match_capture_replace(match, 4, new_markup)
|
||||
next
|
||||
end
|
||||
|
||||
unless (tag = Template.tags[tag_name])
|
||||
# delegate handling of unknown tags to the caller, where a block tag may treat
|
||||
# it as an end tag or body delimiter.
|
||||
unknown_tag = UnknownTagMigrator.new(
|
||||
match: match, markup_capture_number: 4, tag_name: tag_name, markup: markup
|
||||
)
|
||||
return [result, unknown_tag]
|
||||
end
|
||||
new_tag_body = nil
|
||||
new_markup = Utils.migrate_stripped(markup) do |stripped_markup|
|
||||
new_stripped_markup, new_tag_body = tag.migrate(tag_name, stripped_markup, tokenizer, parse_context)
|
||||
new_stripped_markup
|
||||
end
|
||||
result << Utils.match_capture_replace(match, 4, new_markup) << new_tag_body.to_s
|
||||
when token.start_with?(VARSTART)
|
||||
result << migrate_variable(token, parse_context)
|
||||
else
|
||||
result << token
|
||||
end
|
||||
parse_context.line_number = tokenizer.line_number
|
||||
end
|
||||
|
||||
[result, nil]
|
||||
end
|
||||
|
||||
def whitespace_handler(token, parse_context)
|
||||
if token[2] == WhitespaceControl
|
||||
previous_token = @nodelist.last
|
||||
@@ -368,17 +238,6 @@ module Liquid
|
||||
BlockBody.raise_missing_variable_terminator(token, parse_context)
|
||||
end
|
||||
|
||||
private_class_method def self.migrate_variable(token, parse_context)
|
||||
match = token.match(ContentOfVariable)
|
||||
if match
|
||||
new_markup = Utils.migrate_stripped(match[1]) do |markup|
|
||||
Variable.migrate(markup, parse_context)
|
||||
end
|
||||
return Utils.match_capture_replace(match, 1, new_markup)
|
||||
end
|
||||
BlockBody.raise_missing_variable_terminator(token, parse_context)
|
||||
end
|
||||
|
||||
# @deprecated Use {.raise_missing_tag_terminator} instead
|
||||
def raise_missing_tag_terminator(token, parse_context)
|
||||
BlockBody.raise_missing_tag_terminator(token, parse_context)
|
||||
|
||||
@@ -26,7 +26,7 @@ module Liquid
|
||||
@environments = [environments]
|
||||
@environments.flatten!
|
||||
|
||||
@static_environments = [static_environments].flatten(1).freeze
|
||||
@static_environments = [static_environments].flat_map(&:freeze).freeze
|
||||
@scopes = [(outer_scope || {})]
|
||||
@registers = registers.is_a?(Registers) ? registers : Registers.new(registers)
|
||||
@errors = []
|
||||
|
||||
@@ -28,16 +28,6 @@ module Liquid
|
||||
raise
|
||||
end
|
||||
|
||||
def self.migrate(tokenizer, parse_context)
|
||||
new_body, unknown_tag = BlockBody.migrate(tokenizer, parse_context)
|
||||
raise SyntaxError if unknown_tag
|
||||
|
||||
new_body
|
||||
rescue SyntaxError => e
|
||||
e.line_number ||= parse_context.line_number
|
||||
raise
|
||||
end
|
||||
|
||||
def unknown_tag(tag, _markup, _tokenizer)
|
||||
case tag
|
||||
when 'else', 'end'
|
||||
|
||||
@@ -41,36 +41,5 @@ module Liquid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.lax_migrate(markup)
|
||||
Utils.migrate_stripped(markup) do |markup|
|
||||
raise ArgumentError, "unexpected empty expression" if markup.empty?
|
||||
|
||||
if (markup.start_with?('"') && markup.end_with?('"')) ||
|
||||
(markup.start_with?("'") && markup.end_with?("'"))
|
||||
markup
|
||||
else
|
||||
case markup
|
||||
when INTEGERS_REGEX
|
||||
markup
|
||||
when RANGES_REGEX
|
||||
match = Regexp.last_match
|
||||
new_start, new_end = RangeLookup.lax_migrate(match[1], match[2])
|
||||
Utils.match_captures_replace(match, 1 => new_start, 2 => new_end)
|
||||
when FLOATS_REGEX
|
||||
# lax parser allowed multiple periods, but the second period and following characters were ignored
|
||||
new_markup = markup.slice(/\A(-?\d+\.\d*)/)
|
||||
new_markup << "0" if new_markup.end_with?(".")
|
||||
new_markup
|
||||
else
|
||||
if LITERALS.key?(markup)
|
||||
markup
|
||||
else
|
||||
VariableLookup.lax_migrate(markup)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,7 +5,7 @@ module Liquid
|
||||
# @liquid_type object
|
||||
# @liquid_name forloop
|
||||
# @liquid_summary
|
||||
# Information about a parent [`for` loop](/api/liquid/tags/for).
|
||||
# Information about a parent [`for` loop](/api/liquid/tags#for).
|
||||
class ForloopDrop < Drop
|
||||
def initialize(name, length, parentloop)
|
||||
@name = name
|
||||
@@ -30,7 +30,10 @@ module Liquid
|
||||
# @liquid_return [forloop]
|
||||
attr_reader :parentloop
|
||||
|
||||
attr_reader :name
|
||||
def name
|
||||
Usage.increment('forloop_drop_name')
|
||||
@name
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_summary
|
||||
|
||||
@@ -5,9 +5,6 @@ module Liquid
|
||||
attr_accessor :locale, :line_number, :trim_whitespace, :depth
|
||||
attr_reader :partial, :warnings, :error_mode
|
||||
|
||||
# @api private
|
||||
attr_writer :error_mode
|
||||
|
||||
def initialize(options = {})
|
||||
@template_options = options ? options.dup : {}
|
||||
|
||||
|
||||
@@ -2,30 +2,6 @@
|
||||
|
||||
module Liquid
|
||||
module ParserSwitching
|
||||
module ClassMethods
|
||||
def migrate_with_selected_parser(tag_name, markup, tokenizer, parse_context)
|
||||
case parse_context.error_mode
|
||||
when :strict then strict_migrate(tag_name, markup, tokenizer, parse_context)
|
||||
when :lax then lax_migrate(tag_name, markup, tokenizer, parse_context)
|
||||
when :warn
|
||||
begin
|
||||
parse_context.error_mode = :strict
|
||||
begin
|
||||
# Use exception side effect to conditionally branch to lax migration
|
||||
parse(tag_name, markup, tokenizer, parse_context)
|
||||
ensure
|
||||
parse_context.error_mode = :warn
|
||||
end
|
||||
|
||||
strict_migrate(tag_name, markup, tokenizer, parse_context)
|
||||
rescue SyntaxError => e
|
||||
parse_context.warnings << e
|
||||
lax_migrate(tag_name, markup, tokenizer, parse_context)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def strict_parse_with_error_mode_fallback(markup)
|
||||
strict_parse_with_error_context(markup)
|
||||
rescue SyntaxError => e
|
||||
|
||||
@@ -22,37 +22,6 @@ module Liquid
|
||||
end
|
||||
end
|
||||
|
||||
def self.lax_migrate(start_markup, end_markup)
|
||||
new_start = Expression.lax_migrate(start_markup)
|
||||
new_end = Expression.lax_migrate(end_markup)
|
||||
|
||||
# cast literals
|
||||
start_obj = Expression.parse(new_start)
|
||||
end_obj = Expression.parse(new_end)
|
||||
if start_obj.respond_to?(:evaluate) || end_obj.respond_to?(:evaluate)
|
||||
new_start = lax_migrate_range_expression(new_start, start_obj)
|
||||
new_end = lax_migrate_range_expression(new_end, end_obj)
|
||||
else
|
||||
new_start = start_obj.to_i.to_s unless start_obj.is_a?(Integer)
|
||||
new_end = end_obj.to_i.to_s unless end_obj.is_a?(Integer)
|
||||
end
|
||||
|
||||
[new_start, new_end]
|
||||
end
|
||||
|
||||
def self.lax_migrate_range_expression(markup, expression)
|
||||
return markup if expression.respond_to?(:evaluate)
|
||||
|
||||
case expression
|
||||
when Integer
|
||||
markup
|
||||
when NilClass, String
|
||||
expression.to_i.to_s
|
||||
else
|
||||
Utils.to_integer(input).to_s
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :start_obj, :end_obj
|
||||
|
||||
def initialize(start_obj, end_obj)
|
||||
|
||||
@@ -6,14 +6,7 @@ require 'bigdecimal'
|
||||
|
||||
module Liquid
|
||||
module StandardFilters
|
||||
MAX_I32 = (1 << 31) - 1
|
||||
private_constant :MAX_I32
|
||||
|
||||
MIN_I64 = -(1 << 63)
|
||||
MAX_I64 = (1 << 63) - 1
|
||||
I64_RANGE = MIN_I64..MAX_I64
|
||||
private_constant :MIN_I64, :MAX_I64, :I64_RANGE
|
||||
|
||||
MAX_INT = (1 << 31) - 1
|
||||
HTML_ESCAPE = {
|
||||
'&' => '&',
|
||||
'>' => '>',
|
||||
@@ -80,7 +73,7 @@ module Liquid
|
||||
# @liquid_type filter
|
||||
# @liquid_category string
|
||||
# @liquid_summary
|
||||
# Escapes special characters in HTML, such as `<>`, `'`, and `&`, and converts characters into escape sequences. The filter doesn't effect characters within the string that don’t have a corresponding escape sequence.".
|
||||
# Escapes a string.
|
||||
# @liquid_syntax string | escape
|
||||
# @liquid_return [string]
|
||||
def escape(input)
|
||||
@@ -193,19 +186,10 @@ module Liquid
|
||||
offset = Utils.to_integer(offset)
|
||||
length = length ? Utils.to_integer(length) : 1
|
||||
|
||||
begin
|
||||
if input.is_a?(Array)
|
||||
input.slice(offset, length) || []
|
||||
else
|
||||
input.to_s.slice(offset, length) || ''
|
||||
end
|
||||
rescue RangeError
|
||||
if I64_RANGE.cover?(length) && I64_RANGE.cover?(offset)
|
||||
raise # unexpected error
|
||||
end
|
||||
offset = offset.clamp(I64_RANGE)
|
||||
length = length.clamp(I64_RANGE)
|
||||
retry
|
||||
if input.is_a?(Array)
|
||||
input.slice(offset, length) || []
|
||||
else
|
||||
input.to_s.slice(offset, length) || ''
|
||||
end
|
||||
end
|
||||
|
||||
@@ -255,9 +239,9 @@ module Liquid
|
||||
wordlist = begin
|
||||
input.split(" ", words + 1)
|
||||
rescue RangeError
|
||||
# integer too big for String#split, but we can semantically assume no truncation is needed
|
||||
return input if words + 1 > MAX_I32
|
||||
raise # unexpected error
|
||||
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
|
||||
|
||||
@@ -615,7 +599,7 @@ module Liquid
|
||||
# @liquid_description
|
||||
# > Note:
|
||||
# > The `concat` filter won't filter out duplicates. If you want to remove duplicates, then you need to use the
|
||||
# > [`uniq` filter](/api/liquid/filters/uniq).
|
||||
# > [`uniq` filter](/api/liquid/filters#uniq).
|
||||
# @liquid_syntax array | concat: array
|
||||
# @liquid_return [array[untyped]]
|
||||
def concat(input, array)
|
||||
|
||||
@@ -5,7 +5,7 @@ module Liquid
|
||||
# @liquid_type object
|
||||
# @liquid_name tablerowloop
|
||||
# @liquid_summary
|
||||
# Information about a parent [`tablerow` loop](/api/liquid/tags/tablerow).
|
||||
# Information about a parent [`tablerow` loop](/api/liquid/tags#tablerow).
|
||||
class TablerowloopDrop < Drop
|
||||
def initialize(length, cols)
|
||||
@length = length
|
||||
|
||||
+2
-10
@@ -7,8 +7,6 @@ module Liquid
|
||||
include ParserSwitching
|
||||
|
||||
class << self
|
||||
include ParserSwitching::ClassMethods
|
||||
|
||||
def parse(tag_name, markup, tokenizer, parse_context)
|
||||
tag = new(tag_name, markup, parse_context)
|
||||
tag.parse(tokenizer)
|
||||
@@ -16,18 +14,12 @@ module Liquid
|
||||
end
|
||||
|
||||
def disable_tags(*tag_names)
|
||||
tag_names += disabled_tags
|
||||
define_singleton_method(:disabled_tags) { tag_names }
|
||||
@disabled_tags ||= []
|
||||
@disabled_tags.concat(tag_names)
|
||||
prepend(Disabler)
|
||||
end
|
||||
|
||||
private :new
|
||||
|
||||
protected
|
||||
|
||||
def disabled_tags
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(tag_name, markup, parse_context)
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
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
|
||||
|
||||
@@ -21,18 +21,6 @@ module Liquid
|
||||
raise Liquid::SyntaxError, parse_context.locale.t('errors.syntax.assign')
|
||||
end
|
||||
|
||||
def self.migrate(tag_name, markup, tokenizer, parse_context)
|
||||
match = markup.match(Syntax)
|
||||
new_variable_markup = Variable.migrate(match[2], parse_context)
|
||||
new_markup = Utils.match_captures_replace(match, 2 => new_variable_markup)
|
||||
|
||||
# replace scanned over characters with a space to ensure there is a space
|
||||
# to separate the tag name and the variable name
|
||||
new_markup.prepend(" ") if match.begin(0) > 0
|
||||
|
||||
new_markup
|
||||
end
|
||||
|
||||
attr_reader :to, :from
|
||||
|
||||
def initialize(tag_name, markup, parse_context)
|
||||
|
||||
@@ -15,16 +15,12 @@ module Liquid
|
||||
# @liquid_category iteration
|
||||
# @liquid_name break
|
||||
# @liquid_summary
|
||||
# Stops a [`for` loop](/api/liquid/tags/for) from iterating.
|
||||
# Stops a [`for` loop](/api/liquid/tags#for) from iterating.
|
||||
# @liquid_syntax
|
||||
# {% break %}
|
||||
class Break < Tag
|
||||
INTERRUPT = BreakInterrupt.new.freeze
|
||||
|
||||
def self.migrate(_tag_name, _markup, _tokenizer, _parse_context)
|
||||
"" # markup was ignored
|
||||
end
|
||||
|
||||
def render_to_output_buffer(context, output)
|
||||
context.push_interrupt(INTERRUPT)
|
||||
output
|
||||
|
||||
@@ -18,21 +18,6 @@ module Liquid
|
||||
class Capture < Block
|
||||
Syntax = /(#{VariableSignature}+)/o
|
||||
|
||||
def self.migrate(tag_name, markup, tokenizer, parse_context)
|
||||
match = markup.match(Syntax)
|
||||
|
||||
new_markup = match[1]
|
||||
|
||||
# replace scanned over characters with a space to ensure there is a space
|
||||
# to separate the tag name and the variable name
|
||||
new_markup.prepend(" ") if match.begin(0) > 0
|
||||
|
||||
new_body, unknown_tag = migrate_body(tag_name, tokenizer, parse_context)
|
||||
raise SyntaxError if unknown_tag
|
||||
|
||||
[new_markup, new_body]
|
||||
end
|
||||
|
||||
def initialize(tag_name, markup, options)
|
||||
super
|
||||
if markup =~ Syntax
|
||||
|
||||
@@ -28,20 +28,6 @@ module Liquid
|
||||
|
||||
attr_reader :blocks, :left
|
||||
|
||||
def self.migrate(tag_name, markup, tokenizer, parse_context)
|
||||
match = markup.match(/\s*#{Syntax}\s*/o) || raise(SyntaxError)
|
||||
|
||||
new_expression = Expression.lax_migrate(match[1])
|
||||
new_markup = Utils.match_captures_replace(match, { 1 => new_expression })
|
||||
|
||||
# replace scanned over characters with a space to ensure there is a space
|
||||
# to separate the tag name and the variable name
|
||||
new_markup.prepend(" ") if match.begin(0) > 0
|
||||
|
||||
new_body = migrate_body(tag_name, tokenizer, parse_context)
|
||||
[new_markup, new_body]
|
||||
end
|
||||
|
||||
def initialize(tag_name, markup, options)
|
||||
super
|
||||
@blocks = []
|
||||
@@ -53,37 +39,6 @@ module Liquid
|
||||
end
|
||||
end
|
||||
|
||||
def self.migrate_body(start_tag_name, tokenizer, parse_context)
|
||||
result = +""
|
||||
|
||||
# body before first `when` delimiter tag is ignored
|
||||
unused_body, delimiter_tag = super(start_tag_name, tokenizer, parse_context)
|
||||
unless delimiter_tag
|
||||
raise NotImplementedError, "TODO: migrate `case` tag with no `when` or `else` tags"
|
||||
end
|
||||
|
||||
result << Utils.migrate_stripped(unused_body) { "" } # just keep whitespace (e.g. newline and indent)
|
||||
|
||||
while delimiter_tag
|
||||
break unless delimiter_tag
|
||||
|
||||
case delimiter_tag.tag_name
|
||||
when "when"
|
||||
new_markup = migrate_when_markup(delimiter_tag.markup)
|
||||
result << delimiter_tag.replaced_markup(new_markup)
|
||||
when "else"
|
||||
result << delimiter_tag.original_tag_string
|
||||
else
|
||||
raise SyntaxError
|
||||
end
|
||||
|
||||
new_body, delimiter_tag = super(start_tag_name, tokenizer, parse_context)
|
||||
result << new_body
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def parse(tokens)
|
||||
body = case_body = new_body
|
||||
body = @blocks.last.attachment while parse_body(body, tokens)
|
||||
@@ -136,25 +91,6 @@ module Liquid
|
||||
|
||||
private
|
||||
|
||||
private_class_method def self.migrate_when_markup(unstripped_markup)
|
||||
Utils.migrate_stripped(unstripped_markup) do |markup|
|
||||
match = markup.match(WhenSyntax) || raise(SyntaxError)
|
||||
|
||||
replacements = { 1 => Expression.lax_migrate(match[1]) }
|
||||
if (right = match[2])
|
||||
replacements[2] = migrate_when_markup(right)
|
||||
end
|
||||
|
||||
new_markup = Utils.match_captures_replace(match, replacements)
|
||||
|
||||
# replace scanned over characters with a space to ensure there is a space
|
||||
# to separate the tag name and the variable name
|
||||
new_markup.prepend(" ") if match.begin(0) > 0
|
||||
|
||||
new_markup
|
||||
end
|
||||
end
|
||||
|
||||
def record_when_condition(markup)
|
||||
body = new_body
|
||||
|
||||
|
||||
@@ -8,31 +8,13 @@ module Liquid
|
||||
# @liquid_summary
|
||||
# Prevents an expression from being rendered or output.
|
||||
# @liquid_description
|
||||
# Any text inside `comment` tags won't be output, and any Liquid code will be parsed, but not executed.
|
||||
# Any text inside `comment` tags won't be output, and any Liquid code won't be rendered.
|
||||
# @liquid_syntax
|
||||
# {% comment %}
|
||||
# content
|
||||
# {% endcomment %}
|
||||
# @liquid_syntax_keyword content The content of the comment.
|
||||
class Comment < Block
|
||||
def self.migrate(tag_name, _markup, tokenizer, parse_context)
|
||||
new_markup = "" # markup was ignored
|
||||
new_body = migrate_body(tag_name, tokenizer, parse_context)
|
||||
[new_markup, new_body]
|
||||
end
|
||||
|
||||
def self.migrate_body(start_tag_name, tokenizer, parse_context)
|
||||
result = +""
|
||||
loop do
|
||||
new_body, delimiter_tag = super(start_tag_name, tokenizer, parse_context)
|
||||
result << new_body
|
||||
break unless delimiter_tag
|
||||
|
||||
result << delimiter_tag.original_tag_string # unknown tags allowed
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
def render_to_output_buffer(_context, output)
|
||||
output
|
||||
end
|
||||
|
||||
@@ -6,16 +6,12 @@ module Liquid
|
||||
# @liquid_category iteration
|
||||
# @liquid_name continue
|
||||
# @liquid_summary
|
||||
# Causes a [`for` loop](/api/liquid/tags/for) to skip to the next iteration.
|
||||
# Causes a [`for` loop](/api/liquid/tags#for) to skip to the next iteration.
|
||||
# @liquid_syntax
|
||||
# {% continue %}
|
||||
class Continue < Tag
|
||||
INTERRUPT = ContinueInterrupt.new.freeze
|
||||
|
||||
def self.migrate(_tag_name, _markup, _tokenizer, _parse_context)
|
||||
"" # markup was ignored
|
||||
end
|
||||
|
||||
def render_to_output_buffer(context, output)
|
||||
context.push_interrupt(INTERRUPT)
|
||||
output
|
||||
|
||||
@@ -6,7 +6,7 @@ module Liquid
|
||||
# @liquid_category iteration
|
||||
# @liquid_name cycle
|
||||
# @liquid_summary
|
||||
# Loops through a group of strings and outputs them one at a time for each iteration of a [`for` loop](/api/liquid/tags/for).
|
||||
# Loops through a group of strings and outputs them one at a time for each iteration of a [`for` loop](/api/liquid/tags#for).
|
||||
# @liquid_description
|
||||
# The `cycle` tag must be used inside a `for` loop.
|
||||
#
|
||||
@@ -18,43 +18,8 @@ module Liquid
|
||||
SimpleSyntax = /\A#{QuotedFragment}+/o
|
||||
NamedSyntax = /\A(#{QuotedFragment})\s*\:\s*(.*)/om
|
||||
|
||||
def self.migrate(tag_name, markup, tokenizer, parse_context)
|
||||
new_markup = case markup
|
||||
when NamedSyntax
|
||||
match = Regexp.last_match
|
||||
|
||||
new_name_syntax = Expression.lax_migrate(match[1])
|
||||
new_variables_markup = migrate_variables_from_string(match[2])
|
||||
|
||||
Utils.match_captures_replace(match, 1 => new_name_syntax, 2 => new_variables_markup)
|
||||
when SimpleSyntax
|
||||
match = Regexp.last_match
|
||||
migrate_variables_from_string(markup)
|
||||
else
|
||||
raise SyntaxError
|
||||
end
|
||||
|
||||
# replace scanned over characters with a space to ensure there is a space
|
||||
# to separate the tag name and the variable name
|
||||
new_markup.prepend(" ") if match.begin(0) > 0
|
||||
|
||||
new_markup
|
||||
end
|
||||
|
||||
def self.migrate_variables_from_string(markup)
|
||||
markup.split(',').collect do |var|
|
||||
match = var.match(/\s*(#{QuotedFragment})\s*/o)
|
||||
if match
|
||||
Utils.match_captures_replace(match, 1 => Expression.lax_migrate(match[1]))
|
||||
end
|
||||
end.compact.join(",")
|
||||
end
|
||||
|
||||
attr_reader :variables
|
||||
|
||||
# @api private
|
||||
attr_reader :name
|
||||
|
||||
def initialize(tag_name, markup, options)
|
||||
super
|
||||
case markup
|
||||
|
||||
@@ -12,8 +12,8 @@ module Liquid
|
||||
# or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across
|
||||
# [snippets](/themes/architecture#snippets) included in the file.
|
||||
#
|
||||
# Similarly, variables that are created with `decrement` are independent from those created with [`assign`](/api/liquid/tags/assign)
|
||||
# and [`capture`](/api/liquid/tags/capture). However, `decrement` and [`increment`](/api/liquid/tags/increment) share
|
||||
# Similarly, variables that are created with `decrement` are independent from those created with [`assign`](/api/liquid/tags#assign)
|
||||
# and [`capture`](/api/liquid/tags#capture). However, `decrement` and [`increment`](/api/liquid/tags#increment) share
|
||||
# variables.
|
||||
# @liquid_syntax
|
||||
# {% decrement variable_name %}
|
||||
@@ -21,10 +21,6 @@ module Liquid
|
||||
class Decrement < Tag
|
||||
attr_reader :variable_name
|
||||
|
||||
def self.migrate(_tag_name, markup, _tokenizer, _parse_context)
|
||||
markup # no characters ignored, it just uses anything for the variable name
|
||||
end
|
||||
|
||||
def initialize(tag_name, markup, options)
|
||||
super
|
||||
@variable_name = markup.strip
|
||||
|
||||
@@ -9,7 +9,7 @@ module Liquid
|
||||
# Outputs an expression.
|
||||
# @liquid_description
|
||||
# Using the `echo` tag is the same as wrapping an expression in curly brackets (`{{` and `}}`). However, unlike the curly
|
||||
# bracket method, you can use the `echo` tag inside [`liquid` tags](/api/liquid/tags/liquid).
|
||||
# bracket method, you can use the `echo` tag inside [`liquid` tags](/api/liquid/tags#liquid).
|
||||
#
|
||||
# > Tip:
|
||||
# > You can use [filters](/api/liquid/filters) on expressions inside `echo` tags.
|
||||
@@ -21,10 +21,6 @@ module Liquid
|
||||
class Echo < Tag
|
||||
attr_reader :variable
|
||||
|
||||
def self.migrate(tag_name, markup, tokenizer, parse_context)
|
||||
Variable.migrate(markup, parse_context)
|
||||
end
|
||||
|
||||
def initialize(tag_name, markup, parse_context)
|
||||
super
|
||||
@variable = Variable.new(markup, parse_context)
|
||||
|
||||
+6
-44
@@ -9,10 +9,10 @@ module Liquid
|
||||
# Renders an expression for every item in an array.
|
||||
# @liquid_description
|
||||
# You can do a maximum of 50 iterations with a `for` loop. If you need to iterate over more than 50 items, then use the
|
||||
# [`paginate` tag](/api/liquid/tags/paginate) to split the items over multiple pages.
|
||||
# [`paginate` tag](/api/liquid/tags#paginate) to split the items over multiple pages.
|
||||
#
|
||||
# > Tip:
|
||||
# > Every `for` loop has an associated [`forloop` object](/api/liquid/objects/forloop) with information about the loop.
|
||||
# > Every `for` loop has an associated [`forloop` object](/api/liquid/objects#forloop) with information about the loop.
|
||||
# @liquid_syntax
|
||||
# {% for variable in array %}
|
||||
# expression
|
||||
@@ -25,16 +25,10 @@ module Liquid
|
||||
# @liquid_optional_param range [untyped] A custom numeric range to iterate over.
|
||||
# @liquid_optional_param reversed [untyped] Iterate in reverse order.
|
||||
class For < Block
|
||||
Syntax = /\A(#{VariableSegment}+)\s+in\s+(#{QuotedFragment}+)(\s*reversed)?/o
|
||||
Syntax = /\A(#{VariableSegment}+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
|
||||
|
||||
attr_reader :collection_name, :variable_name, :limit, :from
|
||||
|
||||
def self.migrate(tag_name, markup, tokenizer, parse_context)
|
||||
new_markup = migrate_with_selected_parser(tag_name, markup, tokenizer, parse_context)
|
||||
new_body = migrate_body(tag_name, tokenizer, parse_context)
|
||||
[new_markup, new_body]
|
||||
end
|
||||
|
||||
def initialize(tag_name, markup, options)
|
||||
super
|
||||
@from = @limit = nil
|
||||
@@ -59,27 +53,6 @@ module Liquid
|
||||
@else_block ? [@for_block, @else_block] : [@for_block]
|
||||
end
|
||||
|
||||
def self.migrate_body(start_tag_name, tokenizer, parse_context)
|
||||
result = +""
|
||||
|
||||
new_body, delimiter_tag = super(start_tag_name, tokenizer, parse_context)
|
||||
result << new_body
|
||||
|
||||
else_tag = delimiter_tag
|
||||
else_body = nil
|
||||
while delimiter_tag
|
||||
raise SyntaxError unless delimiter_tag.tag_name == 'else'
|
||||
else_tag = delimiter_tag
|
||||
else_body, delimiter_tag = super(start_tag_name, tokenizer, parse_context)
|
||||
end
|
||||
if else_tag
|
||||
result << else_tag.replaced_markup("") # markup was ignored in else tags
|
||||
result << else_body
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def unknown_tag(tag, markup, tokens)
|
||||
return super unless tag == 'else'
|
||||
@else_block = new_body
|
||||
@@ -99,13 +72,6 @@ module Liquid
|
||||
|
||||
protected
|
||||
|
||||
private_class_method def self.lax_migrate(tag_name, markup, tokenizer, parse_context)
|
||||
match = markup.match(Syntax) || raise(SyntaxError)
|
||||
new_collection_name = Expression.lax_migrate(match[2])
|
||||
new_markup = Utils.match_captures_replace(match, { 2 => new_collection_name }.compact)
|
||||
new_markup << Utils.migrate_tag_attributes(markup)
|
||||
end
|
||||
|
||||
def lax_parse(markup)
|
||||
if markup =~ Syntax
|
||||
@variable_name = Regexp.last_match(1)
|
||||
@@ -121,10 +87,6 @@ module Liquid
|
||||
end
|
||||
end
|
||||
|
||||
private_class_method def self.strict_migrate(tag_name, markup, tokenizer, parse_context)
|
||||
markup
|
||||
end
|
||||
|
||||
def strict_parse(markup)
|
||||
p = Parser.new(markup)
|
||||
@variable_name = p.consume(:id)
|
||||
@@ -136,12 +98,11 @@ module Liquid
|
||||
@name = "#{@variable_name}-#{collection_name}"
|
||||
@reversed = p.id?('reversed')
|
||||
|
||||
while p.look(:comma) || p.look(:id)
|
||||
p.consume?(:comma)
|
||||
while p.look(:id) && p.look(:colon, 1)
|
||||
unless (attribute = p.id?('limit') || p.id?('offset'))
|
||||
raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_attribute")
|
||||
end
|
||||
p.consume(:colon)
|
||||
p.consume
|
||||
set_attribute(attribute, p.expression)
|
||||
end
|
||||
p.consume(:end_of_string)
|
||||
@@ -216,6 +177,7 @@ module Liquid
|
||||
case key
|
||||
when 'offset'
|
||||
@from = if expr == 'continue'
|
||||
Usage.increment('for_offset_continue')
|
||||
:continue
|
||||
else
|
||||
parse_expression(expr)
|
||||
|
||||
@@ -20,39 +20,6 @@ module Liquid
|
||||
|
||||
attr_reader :blocks
|
||||
|
||||
def self.migrate(tag_name, markup, tokenizer, parse_context)
|
||||
new_markup = migrate_with_selected_parser(tag_name, markup, tokenizer, parse_context)
|
||||
new_body = migrate_body(tag_name, tokenizer, parse_context)
|
||||
[new_markup, new_body]
|
||||
end
|
||||
|
||||
def self.migrate_body(start_tag_name, tokenizer, parse_context)
|
||||
result = +""
|
||||
|
||||
loop do
|
||||
new_body, delimiter_tag = super(start_tag_name, tokenizer, parse_context)
|
||||
result << new_body
|
||||
|
||||
break unless delimiter_tag
|
||||
|
||||
case delimiter_tag.tag_name
|
||||
when "else"
|
||||
result << delimiter_tag.replaced_markup("") # markup was ignored on else tags
|
||||
when "elsif"
|
||||
new_markup = migrate_with_selected_parser(delimiter_tag.tag_name, delimiter_tag.markup, tokenizer, parse_context)
|
||||
result << delimiter_tag.replaced_markup(new_markup)
|
||||
else
|
||||
raise SyntaxError
|
||||
end
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
private_class_method def self.strict_migrate(tag_name, markup, tokenizer, parse_context)
|
||||
markup
|
||||
end
|
||||
|
||||
def initialize(tag_name, markup, options)
|
||||
super
|
||||
@blocks = []
|
||||
@@ -114,10 +81,6 @@ module Liquid
|
||||
Condition.parse_expression(parse_context, markup)
|
||||
end
|
||||
|
||||
private_class_method def self.lax_migrate_expression(markup)
|
||||
Expression.lax_migrate(markup)
|
||||
end
|
||||
|
||||
def lax_parse(markup)
|
||||
expressions = markup.scan(ExpressionsAndOperators)
|
||||
raise SyntaxError, options[:locale].t("errors.syntax.if") unless expressions.pop =~ Syntax
|
||||
@@ -138,40 +101,6 @@ module Liquid
|
||||
condition
|
||||
end
|
||||
|
||||
private_class_method def self.lax_migrate(tag_name, markup, tokenizer, parse_context)
|
||||
expressions = markup.scan(ExpressionsAndOperators)
|
||||
|
||||
new_markup = lax_migrate_condition(expressions.pop)
|
||||
until expressions.empty?
|
||||
operator = expressions.pop
|
||||
new_left_markup = lax_migrate_condition(expressions.pop)
|
||||
new_markup = new_left_markup << operator << new_markup
|
||||
end
|
||||
|
||||
new_markup
|
||||
end
|
||||
|
||||
private_class_method def self.lax_migrate_condition(markup)
|
||||
Utils.migrate_stripped(markup) do |markup|
|
||||
match = markup.match(Syntax)
|
||||
left = lax_migrate_expression(match[1])
|
||||
op = match[2]
|
||||
right_capture = match[3]
|
||||
if op
|
||||
right = lax_migrate_expression(right_capture) if right_capture
|
||||
elsif right_capture
|
||||
right = "" # remove right capture, since it is ignored with no operator
|
||||
end
|
||||
new_markup = Utils.match_captures_replace(match, { 1 => left, 2 => op, 3 => right }.compact)
|
||||
new_markup.prepend(' ') if match.begin(0) > 0
|
||||
new_markup << ' ' if match.end(0) < markup.length
|
||||
if op && !right # missing right operand missing
|
||||
new_markup << " nil" # replace with nil, which it was semantically treated as
|
||||
end
|
||||
new_markup
|
||||
end
|
||||
end
|
||||
|
||||
def strict_parse(markup)
|
||||
p = Parser.new(markup)
|
||||
condition = parse_binary_comparisons(p)
|
||||
|
||||
@@ -2,15 +2,6 @@
|
||||
|
||||
module Liquid
|
||||
class Ifchanged < Block
|
||||
def self.migrate(tag_name, _markup, tokenizer, parse_context)
|
||||
new_markup = "" # markup was ignored
|
||||
|
||||
new_body, unknown_tag = migrate_body(tag_name, tokenizer, parse_context)
|
||||
raise SyntaxError if unknown_tag
|
||||
|
||||
[new_markup, new_body]
|
||||
end
|
||||
|
||||
def render_to_output_buffer(context, output)
|
||||
block_output = +''
|
||||
super(context, block_output)
|
||||
|
||||
@@ -8,7 +8,7 @@ module Liquid
|
||||
# @liquid_summary
|
||||
# Renders a [snippet](/themes/architecture#snippets).
|
||||
# @liquid_description
|
||||
# Inside the snippet, you can access and alter variables that are [created](/api/liquid/tags/variable-tags) outside of the
|
||||
# Inside the snippet, you can access and alter variables that are [created](/api/liquid/tags#variable-tags) outside of the
|
||||
# snippet.
|
||||
# @liquid_syntax
|
||||
# {% include 'filename' %}
|
||||
@@ -16,7 +16,7 @@ module Liquid
|
||||
# @liquid_deprecated
|
||||
# Deprecated because the way that variables are handled reduces performance and makes code harder to both read and maintain.
|
||||
#
|
||||
# The `include` tag has been replaced by [`render`](/api/liquid/tags/render).
|
||||
# The `include` tag has been replaced by [`render`](/api/liquid/tags#render).
|
||||
class Include < Tag
|
||||
prepend Tag::Disableable
|
||||
|
||||
@@ -25,22 +25,6 @@ module Liquid
|
||||
|
||||
attr_reader :template_name_expr, :variable_name_expr, :attributes
|
||||
|
||||
def self.migrate(_tag_name, markup, _tokenizer, parse_context)
|
||||
match = markup.match(SYNTAX) || raise(SyntaxError)
|
||||
|
||||
template_name = Expression.lax_migrate(match[1])
|
||||
variable_name = Expression.lax_migrate(match[3]) if match[3]
|
||||
|
||||
new_markup = Utils.match_captures_replace(match, { 1 => template_name, 3 => variable_name }.compact)
|
||||
new_markup << Utils.migrate_tag_attributes(markup)
|
||||
|
||||
# replace scanned over characters with a space to ensure there is a space
|
||||
# to separate the tag name and the variable name
|
||||
new_markup.prepend(" ") if match.begin(0) > 0
|
||||
|
||||
new_markup
|
||||
end
|
||||
|
||||
def initialize(tag_name, markup, options)
|
||||
super
|
||||
|
||||
@@ -68,7 +52,7 @@ module Liquid
|
||||
|
||||
def render_to_output_buffer(context, output)
|
||||
template_name = context.evaluate(@template_name_expr)
|
||||
raise ArgumentError, options[:locale].t("errors.argument.include") unless template_name.is_a?(String)
|
||||
raise ArgumentError, options[:locale].t("errors.argument.include") unless template_name
|
||||
|
||||
partial = PartialCache.load(
|
||||
template_name,
|
||||
|
||||
@@ -12,8 +12,8 @@ module Liquid
|
||||
# or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across
|
||||
# [snippets](/themes/architecture#snippets) included in the file.
|
||||
#
|
||||
# Similarly, variables that are created with `increment` are independent from those created with [`assign`](/api/liquid/tags/assign)
|
||||
# and [`capture`](/api/liquid/tags/capture). However, `increment` and [`decrement`](/api/liquid/tags/decrement) share
|
||||
# Similarly, variables that are created with `increment` are independent from those created with [`assign`](/api/liquid/tags#assign)
|
||||
# and [`capture`](/api/liquid/tags#capture). However, `increment` and [`decrement`](/api/liquid/tags#decrement) share
|
||||
# variables.
|
||||
# @liquid_syntax
|
||||
# {% increment variable_name %}
|
||||
@@ -21,10 +21,6 @@ module Liquid
|
||||
class Increment < Tag
|
||||
attr_reader :variable_name
|
||||
|
||||
def self.migrate(_tag_name, markup, _tokenizer, _parse_context)
|
||||
markup # no characters ignored, it just uses anything for the variable name
|
||||
end
|
||||
|
||||
def initialize(tag_name, markup, options)
|
||||
super
|
||||
@variable_name = markup.strip
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
# @liquid_public_docs
|
||||
# @liquid_type tag
|
||||
# @liquid_category syntax
|
||||
# @liquid_name inline_comment
|
||||
# @liquid_summary
|
||||
# Prevents an expression from being rendered or output.
|
||||
# @liquid_description
|
||||
# Any text inside an `inline_comment` tag won't be rendered or output.
|
||||
#
|
||||
# You can create multi-line inline comments. However, each line must begin with a `#`.
|
||||
# @liquid_syntax
|
||||
# {% # content %}
|
||||
# @liquid_syntax_keyword content The content of the comment.
|
||||
class InlineComment < Tag
|
||||
def self.migrate(_tag_name, markup, _tokenizer, _parse_context)
|
||||
markup
|
||||
end
|
||||
|
||||
def initialize(tag_name, markup, options)
|
||||
super
|
||||
|
||||
|
||||
@@ -16,31 +16,6 @@ module Liquid
|
||||
Syntax = /\A\s*\z/
|
||||
FullTokenPossiblyInvalid = /\A(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
|
||||
|
||||
def self.migrate(tag_name, markup, tokenizer, parse_context)
|
||||
raise SyntaxError unless Syntax.match?(markup)
|
||||
|
||||
new_body = migrate_body(tag_name, tokenizer, parse_context)
|
||||
[markup, new_body]
|
||||
end
|
||||
|
||||
def self.migrate_body(start_tag_name, tokenizer, parse_context)
|
||||
block_delimiter = "end#{start_tag_name}"
|
||||
|
||||
body = +''
|
||||
while (token = tokenizer.shift)
|
||||
match = token.match(FullTokenPossiblyInvalid)
|
||||
if match && block_delimiter == match[2]
|
||||
body << Utils.match_captures_replace(match, { 3 => "" })
|
||||
return body
|
||||
end
|
||||
body << token unless token.empty?
|
||||
end
|
||||
|
||||
raise SyntaxError
|
||||
end
|
||||
|
||||
attr_reader :body
|
||||
|
||||
def initialize(tag_name, markup, parse_context)
|
||||
super
|
||||
|
||||
|
||||
@@ -8,19 +8,19 @@ module Liquid
|
||||
# @liquid_summary
|
||||
# Renders a [snippet](/themes/architecture#snippets) or [app block](/themes/architecture/sections/section-schema#render-app-blocks).
|
||||
# @liquid_description
|
||||
# Inside snippets and app blocks, you can't directly access variables that are [created](/api/liquid/tags/variable-tags) outside
|
||||
# of the snippet or app block. However, you can [specify variables as parameters](/api/liquid/tags/render#render-passing-variables-to-a-snippet)
|
||||
# Inside snippets and app blocks, you can't directly access variables that are [created](/api/liquid/tags#variable-tags) outside
|
||||
# of the snippet or app block. However, you can [specify variables as parameters](/api/liquid/tags#render-passing-variables-to-snippets)
|
||||
# to pass outside variables to snippets.
|
||||
#
|
||||
# While you can't directly access created variables, you can access global objects, as well as any objects that are
|
||||
# directly accessible outside the snippet or app block. For example, a snippet or app block inside the [product template](/themes/architecture/templates/product)
|
||||
# can access the [`product` object](/api/liquid/objects/product), and a snippet or app block inside a [section](/themes/architecture/sections)
|
||||
# can access the [`section` object](/api/liquid/objects/section).
|
||||
# can access the [`product` object](/api/liquid/objects#product), and a snippet or app block inside a [section](/themes/architecture/sections)
|
||||
# can access the [`section` object](/api/liquid/objects#section).
|
||||
#
|
||||
# Outside a snippet or app block, you can't access variables created inside the snippet or app block.
|
||||
#
|
||||
# > Note:
|
||||
# > When you render a snippet using the `render` tag, you can't use the [`include` tag](/api/liquid/tags/include)
|
||||
# > When you render a snippet using the `render` tag, you can't use the [`include` tag](/api/liquid/tags#include)
|
||||
# > inside the snippet.
|
||||
# @liquid_syntax
|
||||
# {% render 'filename' %}
|
||||
@@ -29,26 +29,14 @@ module Liquid
|
||||
FOR = 'for'
|
||||
SYNTAX = /(#{QuotedString}+)(\s+(with|#{FOR})\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o
|
||||
|
||||
def self.migrate(_tag_name, markup, _tokenizer, parse_context)
|
||||
match = markup.match(SYNTAX)
|
||||
|
||||
template_name = Expression.lax_migrate(match[1])
|
||||
variable_name = Expression.lax_migrate(match[4]) if match[4]
|
||||
|
||||
new_markup = Utils.match_captures_replace(match, { 1 => template_name, 4 => variable_name }.compact)
|
||||
new_markup << Utils.migrate_tag_attributes(markup)
|
||||
|
||||
# replace scanned over characters with a space to ensure there is a space
|
||||
# to separate the tag name and the variable name
|
||||
new_markup.prepend(" ") if match.begin(0) > 0
|
||||
|
||||
new_markup
|
||||
end
|
||||
|
||||
disable_tags "include"
|
||||
|
||||
attr_reader :template_name_expr, :variable_name_expr, :attributes, :alias_name
|
||||
|
||||
def for_loop?
|
||||
@is_for_loop
|
||||
end
|
||||
|
||||
def initialize(tag_name, markup, options)
|
||||
super
|
||||
|
||||
@@ -69,10 +57,6 @@ module Liquid
|
||||
end
|
||||
end
|
||||
|
||||
def for_loop?
|
||||
@is_for_loop
|
||||
end
|
||||
|
||||
def render_to_output_buffer(context, output)
|
||||
render_tag(context, output)
|
||||
end
|
||||
|
||||
@@ -11,7 +11,7 @@ module Liquid
|
||||
# The `tablerow` tag must be wrapped in HTML `<table>` and `</table>` tags.
|
||||
#
|
||||
# > Tip:
|
||||
# > Every `tablerow` loop has an associated [`tablerowloop` object](/api/liquid/objects/tablerowloop) with information about the loop.
|
||||
# > Every `tablerow` loop has an associated [`tablerowloop` object](/api/liquid/objects#tablerowloop) with information about the loop.
|
||||
# @liquid_syntax
|
||||
# {% tablerow variable in array %}
|
||||
# expression
|
||||
@@ -26,20 +26,6 @@ module Liquid
|
||||
class TableRow < Block
|
||||
Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o
|
||||
|
||||
def self.migrate(tag_name, markup, tokenizer, parse_context)
|
||||
match = markup.match(Syntax) || raise(SyntaxError)
|
||||
|
||||
new_collection_name = Expression.lax_migrate(match[2])
|
||||
|
||||
new_markup = Utils.match_captures_replace(match, { 2 => new_collection_name }.compact)
|
||||
new_markup << Utils.migrate_tag_attributes(markup)
|
||||
|
||||
new_body, unknown_tag = migrate_body(tag_name, tokenizer, parse_context)
|
||||
raise SyntaxError if unknown_tag
|
||||
|
||||
[new_markup, new_body]
|
||||
end
|
||||
|
||||
attr_reader :variable_name, :collection_name, :attributes
|
||||
|
||||
def initialize(tag_name, markup, options)
|
||||
@@ -65,7 +51,7 @@ module Liquid
|
||||
collection = Utils.slice_collection(collection, from, to)
|
||||
length = collection.length
|
||||
|
||||
cols = @attributes.key?('cols') ? context.evaluate(@attributes['cols']).to_i : length
|
||||
cols = context.evaluate(@attributes['cols']).to_i
|
||||
|
||||
output << "<tr class=\"row1\">\n"
|
||||
context.stack do
|
||||
|
||||
@@ -11,7 +11,7 @@ module Liquid
|
||||
# Renders an expression unless a specific condition is `true`.
|
||||
# @liquid_description
|
||||
# > Tip:
|
||||
# > Similar to the [`if` tag](/api/liquid/tags/if), you can use `elsif` to add more conditions to an `unless` tag.
|
||||
# > Similar to the [`if` tag](/api/liquid/tags#if), you can use `elsif` to add more conditions to an `unless` tag.
|
||||
# @liquid_syntax
|
||||
# {% unless condition %}
|
||||
# expression
|
||||
|
||||
@@ -96,11 +96,6 @@ module Liquid
|
||||
def parse(source, options = {})
|
||||
new.parse(source, options)
|
||||
end
|
||||
|
||||
def migrate(source, parse_options = {})
|
||||
parse(source, parse_options) # raise if source has syntax errors
|
||||
new.migrate(source, parse_options)
|
||||
end
|
||||
end
|
||||
|
||||
def initialize
|
||||
@@ -117,12 +112,6 @@ module Liquid
|
||||
self
|
||||
end
|
||||
|
||||
def migrate(source, parse_options = {})
|
||||
parse_context = configure_options(parse_options.merge(disable_liquid_c_nodes: true))
|
||||
tokenizer = parse_context.new_tokenizer(source, start_line_number: @line_numbers && 1)
|
||||
Document.migrate(tokenizer, parse_context)
|
||||
end
|
||||
|
||||
def registers
|
||||
@registers ||= {}
|
||||
end
|
||||
|
||||
+2
-12
@@ -8,15 +8,11 @@ module Liquid
|
||||
@source = source.to_s.to_str
|
||||
@line_number = line_number || (line_numbers ? 1 : nil)
|
||||
@for_liquid_tag = for_liquid_tag
|
||||
@offset = 0
|
||||
@tokens = tokenize
|
||||
end
|
||||
|
||||
def shift
|
||||
token = @tokens[@offset]
|
||||
return nil unless token
|
||||
|
||||
@offset += 1
|
||||
(token = @tokens.shift) || return
|
||||
|
||||
if @line_number
|
||||
@line_number += @for_liquid_tag ? 1 : token.count("\n")
|
||||
@@ -25,10 +21,6 @@ module Liquid
|
||||
token
|
||||
end
|
||||
|
||||
def more?
|
||||
@offset < @tokens.length
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def tokenize
|
||||
@@ -39,9 +31,7 @@ module Liquid
|
||||
tokens = @source.split(TemplateParser)
|
||||
|
||||
# removes the rogue empty element at the beginning of the array
|
||||
if tokens[0]&.empty?
|
||||
@offset += 1
|
||||
end
|
||||
tokens.shift if tokens[0]&.empty?
|
||||
|
||||
tokens
|
||||
end
|
||||
|
||||
@@ -89,48 +89,5 @@ module Liquid
|
||||
# Otherwise return the object itself
|
||||
obj
|
||||
end
|
||||
|
||||
def self.migrate_stripped(markup)
|
||||
match = markup.match(/\A\s*(.*?)\s*\z/m)
|
||||
new_markup = yield match[1]
|
||||
Utils.match_captures_replace(match, 1 => new_markup)
|
||||
end
|
||||
|
||||
def self.migrate_tag_attributes(markup)
|
||||
attributes = []
|
||||
markup.scan(/\s*,?\s*#{TagAttributes}/) do
|
||||
tag_match = Regexp.last_match
|
||||
new_value_markup = Expression.lax_migrate(tag_match[2])
|
||||
attribute_markup = Utils.match_captures_replace(tag_match, { 2 => new_value_markup })
|
||||
unless attribute_markup.match?(/\A[,\s]/)
|
||||
attribute_markup.prepend(", ")
|
||||
end
|
||||
attributes << attribute_markup
|
||||
end
|
||||
return "" if attributes.empty?
|
||||
attributes.join
|
||||
end
|
||||
|
||||
# @api private
|
||||
def self.match_capture_replace(match, capture_number, replacement_string)
|
||||
match_captures_replace(match, { capture_number => replacement_string })
|
||||
end
|
||||
|
||||
def self.match_captures_replace(match, replacements = {})
|
||||
new_string = match[0].dup
|
||||
capture_numbers = replacements.keys
|
||||
unless capture_numbers.all?(Integer)
|
||||
raise TypeError, "Currently, only numbered captures are supported"
|
||||
end
|
||||
# replace from later captures first, to avoid affecting the position for following replacements
|
||||
match_begin = match.begin(0)
|
||||
capture_numbers.sort.reverse_each do |capture_number|
|
||||
replacement_string = replacements.fetch(capture_number)
|
||||
capture_start = match.begin(capture_number)
|
||||
capture_length = match.end(capture_number) - capture_start
|
||||
new_string[capture_start - match_begin, capture_length] = replacement_string
|
||||
end
|
||||
new_string
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -41,79 +41,6 @@ module Liquid
|
||||
"in \"{{#{markup}}}\""
|
||||
end
|
||||
|
||||
STRICT_PARSE_CONTEXT = ParseContext.new(error_mode: :strict)
|
||||
private_constant :STRICT_PARSE_CONTEXT
|
||||
|
||||
def self.migrate(markup, parse_context)
|
||||
new(markup, STRICT_PARSE_CONTEXT)
|
||||
# TODO: migrate non-integer range expression literals
|
||||
markup
|
||||
rescue Liquid::SyntaxError
|
||||
raise if parse_context.error_mode == :strict
|
||||
lax_migrate(markup, parse_context)
|
||||
end
|
||||
|
||||
def self.lax_migrate(markup, parse_context)
|
||||
# unanchored match that may skip over characters preceding the name expression
|
||||
markup_match = markup.match(MarkupWithQuotedFragment)
|
||||
unless markup_match
|
||||
# Treated as a blank variable (e.g. `{{ -}}`), which outputs nothing
|
||||
# but may still have an effect on whitespace trimming
|
||||
return ""
|
||||
end
|
||||
|
||||
name_markup = markup_match[1]
|
||||
filters_markup = markup_match[2]
|
||||
|
||||
new_name_markup = Expression.lax_migrate(name_markup)
|
||||
|
||||
new_filter_markup = ""
|
||||
# unanchored match that may skip over characters preceding the pipe for the first filter
|
||||
if (filters_match = filters_markup.match(/\s*#{FilterMarkupRegex}/o))
|
||||
filters = filters_match[1].scan(FilterParser) # may skip over unterminated quote characters
|
||||
filters.map! do |f|
|
||||
filter_match = f.match(/\A(\s*)\W*(\w+)(\s*)/)
|
||||
next unless filter_match
|
||||
|
||||
# omit non-word characters preceding the filter name that the lax parser skips over
|
||||
transformed_filter = +"#{filter_match[1]}#{filter_match[2]}#{filter_match[3]}"
|
||||
|
||||
filter_args = []
|
||||
f.scan(/#{FilterArgsRegex}\s*/o) do # may skip over characters before the argument separator
|
||||
filter_arg_match = Regexp.last_match
|
||||
new_filter_arg = lax_migrate_filter_argument(filter_arg_match[1])
|
||||
filter_arg_string = Utils.match_captures_replace(filter_arg_match, 1 => new_filter_arg)
|
||||
filter_arg_string = filter_arg_string[1...] # remove separator character
|
||||
filter_args << filter_arg_string
|
||||
end
|
||||
|
||||
unless filter_args.empty?
|
||||
transformed_filter << ":" << filter_args.join(",")
|
||||
end
|
||||
|
||||
transformed_filter
|
||||
end
|
||||
filters.compact!
|
||||
new_filters_markup = filters.join('|')
|
||||
|
||||
# include pipe separator along with whitespace surrounding it
|
||||
new_filter_markup = Utils.match_captures_replace(filters_match, 1 => new_filters_markup)
|
||||
end
|
||||
|
||||
new_markup = Utils.match_captures_replace(markup_match, 1 => new_name_markup, 2 => new_filter_markup)
|
||||
new_markup.prepend(" ") if markup_match.begin(0) > 0
|
||||
new_markup
|
||||
end
|
||||
|
||||
def self.lax_migrate_filter_argument(unparsed_arg)
|
||||
if (match = unparsed_arg.match(JustTagAttributes))
|
||||
new_value_markup = Expression.lax_migrate(match[2])
|
||||
Utils.match_captures_replace(match, 2 => new_value_markup)
|
||||
else
|
||||
Expression.lax_migrate(unparsed_arg)
|
||||
end
|
||||
end
|
||||
|
||||
def lax_parse(markup)
|
||||
@filters = []
|
||||
return unless markup =~ MarkupWithQuotedFragment
|
||||
|
||||
@@ -10,43 +10,6 @@ module Liquid
|
||||
new(markup)
|
||||
end
|
||||
|
||||
LITERALS = Expression::LITERALS.keys.freeze
|
||||
private_constant :LITERALS
|
||||
|
||||
def self.lax_migrate(markup)
|
||||
new_markup = nil
|
||||
last_match = nil
|
||||
first_match = nil
|
||||
markup.scan(VariableParser) do |lookup|
|
||||
last_match = Regexp.last_match
|
||||
first_match ||= last_match
|
||||
new_markup ||= +""
|
||||
if lookup&.start_with?('[') && lookup&.end_with?(']')
|
||||
new_markup << "[" << Expression.lax_migrate(lookup[1..-2]) << "]"
|
||||
elsif !lookup.match?(/\A#{Liquid::Lexer::IDENTIFIER}\z/)
|
||||
# quote non-strictly valid identifiers
|
||||
new_markup << "['" << lookup << "']"
|
||||
else
|
||||
new_markup << "." unless new_markup.empty?
|
||||
new_markup << lookup
|
||||
end
|
||||
end
|
||||
|
||||
case new_markup
|
||||
when nil
|
||||
# `markup.scan(VariableParser)` may skip over all characters
|
||||
new_markup ||= " nil "
|
||||
when Expression::INTEGERS_REGEX, Expression::RANGES_REGEX, Expression::FLOATS_REGEX, *LITERALS
|
||||
# Quote variable lookups that match literals after characters are skipped by regex scanning
|
||||
new_markup = "['#{new_markup}']"
|
||||
else
|
||||
new_markup.prepend(" ") if first_match.begin(0) > 0
|
||||
new_markup << ' ' if last_match.end(0) < markup.length
|
||||
end
|
||||
|
||||
new_markup
|
||||
end
|
||||
|
||||
def initialize(markup)
|
||||
lookups = markup.scan(VariableParser)
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ class BlankTest < Minitest::Test
|
||||
|
||||
def test_new_tags_are_not_blank_by_default
|
||||
with_custom_tag('foobar', FoobarTag) do
|
||||
assert_equal(" " * N, Liquid::Template.parse(wrap_in_for("{% foobar %}")).render!)
|
||||
assert_template_result(" " * N, wrap_in_for("{% foobar %}"))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -109,10 +109,6 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_raises(Liquid::ArgumentError) do
|
||||
@filters.slice('foobar', 0, "")
|
||||
end
|
||||
assert_equal("", @filters.slice("foobar", 0, -(1 << 64)))
|
||||
assert_equal("foobar", @filters.slice("foobar", 0, 1 << 63))
|
||||
assert_equal("", @filters.slice("foobar", 1 << 63, 6))
|
||||
assert_equal("", @filters.slice("foobar", -(1 << 63), 6))
|
||||
end
|
||||
|
||||
def test_slice_on_arrays
|
||||
@@ -127,10 +123,6 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_equal(%w(r), @filters.slice(input, -1))
|
||||
assert_equal(%w(), @filters.slice(input, 100, 10))
|
||||
assert_equal(%w(), @filters.slice(input, -100, 10))
|
||||
assert_equal([], @filters.slice(input, 0, -(1 << 64)))
|
||||
assert_equal(input, @filters.slice(input, 0, 1 << 63))
|
||||
assert_equal([], @filters.slice(input, 1 << 63, 6))
|
||||
assert_equal([], @filters.slice(input, -(1 << 63), 6))
|
||||
end
|
||||
|
||||
def test_truncate
|
||||
@@ -140,8 +132,6 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_equal('1234567890', @filters.truncate('1234567890'))
|
||||
assert_equal("测试...", @filters.truncate("测试测试测试测试", 5))
|
||||
assert_equal('12341', @filters.truncate("1234567890", 5, 1))
|
||||
assert_equal("foobar", @filters.truncate("foobar", 1 << 63))
|
||||
assert_equal("...", @filters.truncate("foobar", -(1 << 63)))
|
||||
end
|
||||
|
||||
def test_split
|
||||
@@ -237,8 +227,10 @@ class StandardFiltersTest < Minitest::Test
|
||||
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))
|
||||
assert_equal('one two three four', @filters.truncatewords("one two three four", 1 << 31))
|
||||
assert_equal('one...', @filters.truncatewords("one two three four", -(1 << 32)))
|
||||
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
|
||||
@@ -477,8 +469,8 @@ class StandardFiltersTest < Minitest::Test
|
||||
def test_map_over_proc
|
||||
drop = TestDrop.new(value: "testfoo")
|
||||
p = proc { drop }
|
||||
output = Liquid::Template.parse('{{ procs | map: "value" }}').render!({ "procs" => [p] })
|
||||
assert_equal("testfoo", output)
|
||||
templ = '{{ procs | map: "value" }}'
|
||||
assert_template_result("testfoo", templ, { "procs" => [p] })
|
||||
end
|
||||
|
||||
def test_map_over_drops_returning_procs
|
||||
@@ -490,13 +482,12 @@ class StandardFiltersTest < Minitest::Test
|
||||
"proc" => -> { "bar" },
|
||||
},
|
||||
]
|
||||
output = Liquid::Template.parse('{{ drops | map: "proc" }}').render!({ "drops" => drops })
|
||||
assert_equal("foobar", output)
|
||||
templ = '{{ drops | map: "proc" }}'
|
||||
assert_template_result("foobar", templ, { "drops" => drops })
|
||||
end
|
||||
|
||||
def test_map_works_on_enumerables
|
||||
output = Liquid::Template.parse('{{ foo | map: "foo" }}').render!({ "foo" => TestEnumerable.new })
|
||||
assert_equal("123", output)
|
||||
assert_template_result("123", '{{ foo | map: "foo" }}', { "foo" => TestEnumerable.new })
|
||||
end
|
||||
|
||||
def test_map_returns_empty_on_2d_input_array
|
||||
|
||||
@@ -9,8 +9,8 @@ class BreakTagTest < Minitest::Test
|
||||
# block
|
||||
def test_break_with_no_block
|
||||
assigns = { 'i' => 1 }
|
||||
markup = 'before{% break %}after'
|
||||
expected = 'before'
|
||||
markup = '{% break %}'
|
||||
expected = ''
|
||||
|
||||
assert_template_result(expected, markup, assigns)
|
||||
end
|
||||
|
||||
@@ -263,19 +263,6 @@ HERE
|
||||
assert_template_result(expected, markup, assigns)
|
||||
end
|
||||
|
||||
def test_for_with_break_after_nested_loop
|
||||
source = <<~LIQUID.chomp
|
||||
{% for i in (1..2) -%}
|
||||
{% for j in (1..2) -%}
|
||||
{{ i }}-{{ j }},
|
||||
{%- endfor -%}
|
||||
{% break -%}
|
||||
{% endfor -%}
|
||||
after
|
||||
LIQUID
|
||||
assert_template_result("1-1,1-2,after", source)
|
||||
end
|
||||
|
||||
def test_for_with_continue
|
||||
assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5] } }
|
||||
|
||||
@@ -450,4 +437,30 @@ HERE
|
||||
|
||||
assert(context.registers[:for_stack].empty?)
|
||||
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
|
||||
|
||||
@@ -3,13 +3,44 @@
|
||||
require 'test_helper'
|
||||
|
||||
class TestFileSystem
|
||||
PARTIALS = {
|
||||
"nested_template" => "{% include 'header' %} {% include 'body' %} {% include 'footer' %}",
|
||||
"body" => "body {% include 'body_detail' %}",
|
||||
}
|
||||
|
||||
def read_template_file(template_path)
|
||||
PARTIALS[template_path] || template_path
|
||||
case template_path
|
||||
when "product"
|
||||
"Product: {{ product.title }} "
|
||||
|
||||
when "product_alias"
|
||||
"Product: {{ product.title }} "
|
||||
|
||||
when "locale_variables"
|
||||
"Locale: {{echo1}} {{echo2}}"
|
||||
|
||||
when "variant"
|
||||
"Variant: {{ variant.title }}"
|
||||
|
||||
when "nested_template"
|
||||
"{% include 'header' %} {% include 'body' %} {% include 'footer' %}"
|
||||
|
||||
when "body"
|
||||
"body {% include 'body_detail' %}"
|
||||
|
||||
when "nested_product_template"
|
||||
"Product: {{ nested_product_template.title }} {%include 'details'%} "
|
||||
|
||||
when "recursively_nested_template"
|
||||
"-{% include 'recursively_nested_template' %}"
|
||||
|
||||
when "pick_a_source"
|
||||
"from TestFileSystem"
|
||||
|
||||
when 'assignments'
|
||||
"{% assign foo = 'bar' %}"
|
||||
|
||||
when 'break'
|
||||
"{% break %}"
|
||||
|
||||
else
|
||||
template_path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -50,11 +81,7 @@ class IncludeTagTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def setup
|
||||
@default_file_system = Liquid::Template.file_system
|
||||
end
|
||||
|
||||
def teardown
|
||||
Liquid::Template.file_system = @default_file_system
|
||||
Liquid::Template.file_system = TestFileSystem.new
|
||||
end
|
||||
|
||||
def test_include_tag_looks_for_file_system_in_registers_first
|
||||
@@ -65,85 +92,63 @@ class IncludeTagTest < Minitest::Test
|
||||
def test_include_tag_with
|
||||
assert_template_result("Product: Draft 151cm ",
|
||||
"{% include 'product' with products[0] %}",
|
||||
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
|
||||
partials: { "product" => "Product: {{ product.title }} " })
|
||||
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] })
|
||||
end
|
||||
|
||||
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' }] },
|
||||
partials: { "product_alias" => "Product: {{ product.title }} " })
|
||||
{ "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' }] },
|
||||
partials: { "product_alias" => "Product: {{ product.title }} " })
|
||||
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] })
|
||||
end
|
||||
|
||||
def test_include_tag_with_default_name
|
||||
assert_template_result("Product: Draft 151cm ",
|
||||
"{% include 'product' %}", { "product" => { 'title' => 'Draft 151cm' } },
|
||||
partials: { "product" => "Product: {{ product.title }} " })
|
||||
"{% include 'product' %}", { "product" => { 'title' => 'Draft 151cm' } })
|
||||
end
|
||||
|
||||
def test_include_tag_for
|
||||
assert_template_result("Product: Draft 151cm Product: Element 155cm ",
|
||||
"{% include 'product' for products %}",
|
||||
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
|
||||
partials: { "product" => "Product: {{ product.title }} " })
|
||||
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] })
|
||||
end
|
||||
|
||||
def test_include_tag_with_local_variables
|
||||
assert_template_result("Locale: test123 ", "{% include 'locale_variables' echo1: 'test123' %}",
|
||||
partials: { "locale_variables" => "Locale: {{echo1}} {{echo2}}" })
|
||||
assert_template_result("Locale: test123 ", "{% include 'locale_variables' echo1: 'test123' %}")
|
||||
end
|
||||
|
||||
def test_include_tag_with_multiple_local_variables
|
||||
assert_template_result("Locale: test123 test321",
|
||||
"{% include 'locale_variables' echo1: 'test123', echo2: 'test321' %}",
|
||||
partials: { "locale_variables" => "Locale: {{echo1}} {{echo2}}" })
|
||||
"{% include 'locale_variables' echo1: 'test123', echo2: 'test321' %}")
|
||||
end
|
||||
|
||||
def test_include_tag_with_multiple_local_variables_from_context
|
||||
assert_template_result("Locale: test123 test321",
|
||||
"{% include 'locale_variables' echo1: echo1, echo2: more_echos.echo2 %}",
|
||||
{ 'echo1' => 'test123', 'more_echos' => { "echo2" => 'test321' } },
|
||||
partials: { "locale_variables" => "Locale: {{echo1}} {{echo2}}" })
|
||||
{ 'echo1' => 'test123', 'more_echos' => { "echo2" => 'test321' } })
|
||||
end
|
||||
|
||||
def test_included_templates_assigns_variables
|
||||
assert_template_result("bar", "{% include 'assignments' %}{{ foo }}",
|
||||
partials: { 'assignments' => "{% assign foo = 'bar' %}" })
|
||||
assert_template_result("bar", "{% include 'assignments' %}{{ foo }}")
|
||||
end
|
||||
|
||||
def test_nested_include_tag
|
||||
partials = { "body" => "body {% include 'body_detail' %}", "body_detail" => "body_detail" }
|
||||
assert_template_result("body body_detail", "{% include 'body' %}", partials: partials)
|
||||
assert_template_result("body body_detail", "{% include 'body' %}")
|
||||
|
||||
partials = partials.merge({
|
||||
"nested_template" => "{% include 'header' %} {% include 'body' %} {% include 'footer' %}",
|
||||
"header" => "header",
|
||||
"footer" => "footer",
|
||||
})
|
||||
assert_template_result("header body body_detail footer", "{% include 'nested_template' %}", partials: partials)
|
||||
assert_template_result("header body body_detail footer", "{% include 'nested_template' %}")
|
||||
end
|
||||
|
||||
def test_nested_include_with_variable
|
||||
partials = {
|
||||
"nested_product_template" => "Product: {{ nested_product_template.title }} {%include 'details'%} ",
|
||||
"details" => "details",
|
||||
}
|
||||
|
||||
assert_template_result("Product: Draft 151cm details ",
|
||||
"{% include 'nested_product_template' with product %}", { "product" => { "title" => 'Draft 151cm' } },
|
||||
partials: partials)
|
||||
"{% include 'nested_product_template' with product %}", { "product" => { "title" => 'Draft 151cm' } })
|
||||
|
||||
assert_template_result("Product: Draft 151cm details Product: Element 155cm details ",
|
||||
"{% include 'nested_product_template' for products %}", { "products" => [{ "title" => 'Draft 151cm' }, { "title" => 'Element 155cm' }] },
|
||||
partials: partials)
|
||||
"{% include 'nested_product_template' for products %}", { "products" => [{ "title" => 'Draft 151cm' }, { "title" => 'Element 155cm' }] })
|
||||
end
|
||||
|
||||
def test_recursively_included_template_does_not_produce_endless_loop
|
||||
@@ -161,15 +166,11 @@ class IncludeTagTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_dynamically_choosen_template
|
||||
assert_template_result("Test123", "{% include template %}", { "template" => 'Test123' },
|
||||
partials: { "Test123" => "Test123" })
|
||||
|
||||
assert_template_result("Test321", "{% include template %}", { "template" => 'Test321' },
|
||||
partials: { "Test321" => "Test321" })
|
||||
assert_template_result("Test123", "{% include template %}", { "template" => 'Test123' })
|
||||
assert_template_result("Test321", "{% include template %}", { "template" => 'Test321' })
|
||||
|
||||
assert_template_result("Product: Draft 151cm ", "{% include template for product %}",
|
||||
{ "template" => 'product', 'product' => { 'title' => 'Draft 151cm' } },
|
||||
partials: { "product" => "Product: {{ product.title }} " })
|
||||
{ "template" => 'product', 'product' => { 'title' => 'Draft 151cm' } })
|
||||
end
|
||||
|
||||
def test_include_tag_caches_second_read_of_same_partial
|
||||
@@ -191,8 +192,7 @@ class IncludeTagTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_include_tag_within_if_statement
|
||||
assert_template_result("foo_if_true", "{% if true %}{% include 'foo_if_true' %}{% endif %}",
|
||||
partials: { "foo_if_true" => "foo_if_true" })
|
||||
assert_template_result("foo_if_true", "{% if true %}{% include 'foo_if_true' %}{% endif %}")
|
||||
end
|
||||
|
||||
def test_custom_include_tag
|
||||
@@ -226,7 +226,6 @@ class IncludeTagTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_passing_options_to_included_templates
|
||||
Liquid::Template.file_system = TestFileSystem.new
|
||||
assert_raises(Liquid::SyntaxError) do
|
||||
Template.parse("{% include template %}", error_mode: :strict).render!("template" => '{{ "X" || downcase }}')
|
||||
end
|
||||
@@ -242,35 +241,27 @@ class IncludeTagTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_render_raise_argument_error_when_template_is_undefined
|
||||
assert_template_result("Liquid error (line 1): Argument error in tag 'include' - Illegal template name",
|
||||
"{% include undefined_variable %}", render_errors: true)
|
||||
|
||||
assert_template_result("Liquid error (line 1): Argument error in tag 'include' - Illegal template name",
|
||||
"{% include nil %}", render_errors: true)
|
||||
end
|
||||
|
||||
def test_render_raise_argument_error_when_template_is_not_a_string
|
||||
assert_template_result("Liquid error (line 1): Argument error in tag 'include' - Illegal template name",
|
||||
"{% include 123 %}", render_errors: true)
|
||||
assert_raises(Liquid::ArgumentError) do
|
||||
template = Liquid::Template.parse('{% include undefined_variable %}')
|
||||
template.render!
|
||||
end
|
||||
assert_raises(Liquid::ArgumentError) do
|
||||
template = Liquid::Template.parse('{% include nil %}')
|
||||
template.render!
|
||||
end
|
||||
end
|
||||
|
||||
def test_including_via_variable_value
|
||||
assert_template_result("from TestFileSystem", "{% assign page = 'pick_a_source' %}{% include page %}",
|
||||
partials: { "pick_a_source" => "from TestFileSystem" })
|
||||
|
||||
partials = { "product" => "Product: {{ product.title }} " }
|
||||
assert_template_result("from TestFileSystem", "{% assign page = 'pick_a_source' %}{% include page %}")
|
||||
|
||||
assert_template_result("Product: Draft 151cm ", "{% assign page = 'product' %}{% include page %}",
|
||||
{ "product" => { 'title' => 'Draft 151cm' } },
|
||||
partials: partials)
|
||||
{ "product" => { 'title' => 'Draft 151cm' } })
|
||||
|
||||
assert_template_result("Product: Draft 151cm ", "{% assign page = 'product' %}{% include page for foo %}",
|
||||
{ "foo" => { 'title' => 'Draft 151cm' } },
|
||||
partials: partials)
|
||||
{ "foo" => { 'title' => 'Draft 151cm' } })
|
||||
end
|
||||
|
||||
def test_including_with_strict_variables
|
||||
Liquid::Template.file_system = StubFileSystem.new({ "simple" => "simple" })
|
||||
template = Liquid::Template.parse("{% include 'simple' %}", error_mode: :warn)
|
||||
template.render(nil, strict_variables: true)
|
||||
|
||||
@@ -279,7 +270,6 @@ class IncludeTagTest < Minitest::Test
|
||||
|
||||
def test_break_through_include
|
||||
assert_template_result("1", "{% for i in (1..3) %}{{ i }}{% break %}{{ i }}{% endfor %}")
|
||||
assert_template_result("1", "{% for i in (1..3) %}{{ i }}{% include 'break' %}{{ i }}{% endfor %}",
|
||||
partials: { 'break' => "{% break %}" })
|
||||
assert_template_result("1", "{% for i in (1..3) %}{{ i }}{% include 'break' %}{{ i }}{% endfor %}")
|
||||
end
|
||||
end # IncludeTagTest
|
||||
|
||||
@@ -6,21 +6,20 @@ class IncrementTagTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_inc
|
||||
assert_template_result('0 1', '{%increment port %} {{ port }}')
|
||||
assert_template_result(' 0 1 2', '{{port}} {%increment port %} {%increment port%} {{port}}')
|
||||
assert_template_result('0', '{%increment port %}', {})
|
||||
assert_template_result('0 1', '{%increment port %} {%increment port%}', {})
|
||||
assert_template_result('0 0 1 2 1',
|
||||
'{%increment port %} {%increment starboard%} ' \
|
||||
'{%increment port %} {%increment port%} ' \
|
||||
'{%increment starboard %}')
|
||||
'{%increment starboard %}', {})
|
||||
end
|
||||
|
||||
def test_dec
|
||||
assert_template_result('-1 -1', '{%decrement port %} {{ port }}', { 'port' => 10 })
|
||||
assert_template_result(' -1 -2 -2', '{{port}} {%decrement port %} {%decrement port%} {{port}}')
|
||||
assert_template_result('0 1 2 0 3 1 1 3',
|
||||
'{%increment starboard %} {%increment starboard%} {%increment starboard%} ' \
|
||||
assert_template_result('9', '{%decrement port %}', { 'port' => 10 })
|
||||
assert_template_result('-1 -2', '{%decrement port %} {%decrement port%}', {})
|
||||
assert_template_result('1 5 2 2 5',
|
||||
'{%increment port %} {%increment starboard%} ' \
|
||||
'{%increment port %} {%decrement port%} ' \
|
||||
'{%decrement starboard %}')
|
||||
'{%decrement starboard %}', { 'port' => 1, 'starboard' => 5 })
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,52 +6,53 @@ class RenderTagTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_render_with_no_arguments
|
||||
assert_template_result('rendered content', '{% render "source" %}',
|
||||
partials: { 'source' => 'rendered content' })
|
||||
Liquid::Template.file_system = StubFileSystem.new('source' => 'rendered content')
|
||||
assert_template_result('rendered content', '{% render "source" %}')
|
||||
end
|
||||
|
||||
def test_render_tag_looks_for_file_system_in_registers_first
|
||||
assert_template_result('from register file system', '{% render "pick_a_source" %}',
|
||||
partials: { 'pick_a_source' => 'from register file system' })
|
||||
file_system = StubFileSystem.new('pick_a_source' => 'from register file system')
|
||||
assert_equal('from register file system',
|
||||
Template.parse('{% render "pick_a_source" %}').render!({}, registers: { file_system: file_system }))
|
||||
end
|
||||
|
||||
def test_render_passes_named_arguments_into_inner_scope
|
||||
Liquid::Template.file_system = StubFileSystem.new('product' => '{{ inner_product.title }}')
|
||||
assert_template_result('My Product', '{% render "product", inner_product: outer_product %}',
|
||||
{ 'outer_product' => { 'title' => 'My Product' } },
|
||||
partials: { 'product' => '{{ inner_product.title }}' })
|
||||
{ 'outer_product' => { 'title' => 'My Product' } })
|
||||
end
|
||||
|
||||
def test_render_accepts_literals_as_arguments
|
||||
assert_template_result('123', '{% render "snippet", price: 123 %}',
|
||||
partials: { 'snippet' => '{{ price }}' })
|
||||
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ price }}')
|
||||
assert_template_result('123', '{% render "snippet", price: 123 %}')
|
||||
end
|
||||
|
||||
def test_render_accepts_multiple_named_arguments
|
||||
assert_template_result('1 2', '{% render "snippet", one: 1, two: 2 %}',
|
||||
partials: { 'snippet' => '{{ one }} {{ two }}' })
|
||||
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ one }} {{ two }}')
|
||||
assert_template_result('1 2', '{% render "snippet", one: 1, two: 2 %}')
|
||||
end
|
||||
|
||||
def test_render_does_not_inherit_parent_scope_variables
|
||||
assert_template_result('', '{% assign outer_variable = "should not be visible" %}{% render "snippet" %}',
|
||||
partials: { 'snippet' => '{{ outer_variable }}' })
|
||||
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ outer_variable }}')
|
||||
assert_template_result('', '{% assign outer_variable = "should not be visible" %}{% render "snippet" %}')
|
||||
end
|
||||
|
||||
def test_render_does_not_inherit_variable_with_same_name_as_snippet
|
||||
assert_template_result('', "{% assign snippet = 'should not be visible' %}{% render 'snippet' %}",
|
||||
partials: { 'snippet' => '{{ snippet }}' })
|
||||
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ snippet }}')
|
||||
assert_template_result('', "{% assign snippet = 'should not be visible' %}{% render 'snippet' %}")
|
||||
end
|
||||
|
||||
def test_render_does_not_mutate_parent_scope
|
||||
assert_template_result('', "{% render 'snippet' %}{{ inner }}",
|
||||
partials: { 'snippet' => '{% assign inner = 1 %}' })
|
||||
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{% assign inner = 1 %}')
|
||||
assert_template_result('', "{% render 'snippet' %}{{ inner }}")
|
||||
end
|
||||
|
||||
def test_nested_render_tag
|
||||
assert_template_result('one two', "{% render 'one' %}",
|
||||
partials: {
|
||||
'one' => "one {% render 'two' %}",
|
||||
'two' => 'two',
|
||||
})
|
||||
Liquid::Template.file_system = StubFileSystem.new(
|
||||
'one' => "one {% render 'two' %}",
|
||||
'two' => 'two'
|
||||
)
|
||||
assert_template_result('one two', "{% render 'one' %}")
|
||||
end
|
||||
|
||||
def test_recursively_rendered_template_does_not_produce_endless_loop
|
||||
@@ -72,7 +73,11 @@ class RenderTagTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_dynamically_choosen_templates_are_not_allowed
|
||||
assert_syntax_error("{% assign name = 'snippet' %}{% render name %}")
|
||||
Liquid::Template.file_system = StubFileSystem.new('snippet' => 'should not be rendered')
|
||||
|
||||
assert_raises(Liquid::SyntaxError) do
|
||||
Liquid::Template.parse("{% assign name = 'snippet' %}{% render name %}")
|
||||
end
|
||||
end
|
||||
|
||||
def test_include_tag_caches_second_read_of_same_partial
|
||||
@@ -96,36 +101,36 @@ class RenderTagTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_render_tag_within_if_statement
|
||||
assert_template_result('my message', '{% if true %}{% render "snippet" %}{% endif %}',
|
||||
partials: { 'snippet' => 'my message' })
|
||||
Liquid::Template.file_system = StubFileSystem.new('snippet' => 'my message')
|
||||
assert_template_result('my message', '{% if true %}{% render "snippet" %}{% endif %}')
|
||||
end
|
||||
|
||||
def test_break_through_render
|
||||
options = { partials: { 'break' => '{% break %}' } }
|
||||
assert_template_result('1', '{% for i in (1..3) %}{{ i }}{% break %}{{ i }}{% endfor %}', **options)
|
||||
assert_template_result('112233', '{% for i in (1..3) %}{{ i }}{% render "break" %}{{ i }}{% endfor %}', **options)
|
||||
Liquid::Template.file_system = StubFileSystem.new('break' => '{% break %}')
|
||||
assert_template_result('1', '{% for i in (1..3) %}{{ i }}{% break %}{{ i }}{% endfor %}')
|
||||
assert_template_result('112233', '{% for i in (1..3) %}{{ i }}{% render "break" %}{{ i }}{% endfor %}')
|
||||
end
|
||||
|
||||
def test_increment_is_isolated_between_renders
|
||||
assert_template_result('010', '{% increment %}{% increment %}{% render "incr" %}',
|
||||
partials: { 'incr' => '{% increment %}' })
|
||||
Liquid::Template.file_system = StubFileSystem.new('incr' => '{% increment %}')
|
||||
assert_template_result('010', '{% increment %}{% increment %}{% render "incr" %}')
|
||||
end
|
||||
|
||||
def test_decrement_is_isolated_between_renders
|
||||
assert_template_result('-1-2-1', '{% decrement %}{% decrement %}{% render "decr" %}',
|
||||
partials: { 'decr' => '{% decrement %}' })
|
||||
Liquid::Template.file_system = StubFileSystem.new('decr' => '{% decrement %}')
|
||||
assert_template_result('-1-2-1', '{% decrement %}{% decrement %}{% render "decr" %}')
|
||||
end
|
||||
|
||||
def test_includes_will_not_render_inside_render_tag
|
||||
assert_template_result(
|
||||
'Liquid error (test_include line 1): include usage is not allowed in this context',
|
||||
'{% render "test_include" %}',
|
||||
render_errors: true,
|
||||
partials: {
|
||||
'foo' => 'bar',
|
||||
'test_include' => '{% include "foo" %}',
|
||||
}
|
||||
Liquid::Template.file_system = StubFileSystem.new(
|
||||
'foo' => 'bar',
|
||||
'test_include' => '{% include "foo" %}'
|
||||
)
|
||||
|
||||
exc = assert_raises(Liquid::DisabledError) do
|
||||
Liquid::Template.parse('{% render "test_include" %}').render!
|
||||
end
|
||||
assert_equal('Liquid error: include usage is not allowed in this context', exc.message)
|
||||
end
|
||||
|
||||
def test_includes_will_not_render_inside_nested_sibling_tags
|
||||
@@ -143,67 +148,74 @@ class RenderTagTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_render_tag_with
|
||||
Liquid::Template.file_system = StubFileSystem.new(
|
||||
'product' => "Product: {{ product.title }} ",
|
||||
'product_alias' => "Product: {{ product.title }} ",
|
||||
)
|
||||
|
||||
assert_template_result("Product: Draft 151cm ",
|
||||
"{% render 'product' with products[0] %}",
|
||||
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
|
||||
partials: {
|
||||
'product' => "Product: {{ product.title }} ",
|
||||
'product_alias' => "Product: {{ product.title }} ",
|
||||
})
|
||||
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] })
|
||||
end
|
||||
|
||||
def test_render_tag_with_alias
|
||||
Liquid::Template.file_system = StubFileSystem.new(
|
||||
'product' => "Product: {{ product.title }} ",
|
||||
'product_alias' => "Product: {{ product.title }} ",
|
||||
)
|
||||
|
||||
assert_template_result("Product: Draft 151cm ",
|
||||
"{% render 'product_alias' with products[0] as product %}",
|
||||
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
|
||||
partials: {
|
||||
'product' => "Product: {{ product.title }} ",
|
||||
'product_alias' => "Product: {{ product.title }} ",
|
||||
})
|
||||
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] })
|
||||
end
|
||||
|
||||
def test_render_tag_for_alias
|
||||
Liquid::Template.file_system = StubFileSystem.new(
|
||||
'product' => "Product: {{ product.title }} ",
|
||||
'product_alias' => "Product: {{ product.title }} ",
|
||||
)
|
||||
|
||||
assert_template_result("Product: Draft 151cm Product: Element 155cm ",
|
||||
"{% render 'product_alias' for products as product %}",
|
||||
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
|
||||
partials: {
|
||||
'product' => "Product: {{ product.title }} ",
|
||||
'product_alias' => "Product: {{ product.title }} ",
|
||||
})
|
||||
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] })
|
||||
end
|
||||
|
||||
def test_render_tag_for
|
||||
Liquid::Template.file_system = StubFileSystem.new(
|
||||
'product' => "Product: {{ product.title }} ",
|
||||
'product_alias' => "Product: {{ product.title }} ",
|
||||
)
|
||||
|
||||
assert_template_result("Product: Draft 151cm Product: Element 155cm ",
|
||||
"{% render 'product' for products %}",
|
||||
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
|
||||
partials: {
|
||||
'product' => "Product: {{ product.title }} ",
|
||||
'product_alias' => "Product: {{ product.title }} ",
|
||||
})
|
||||
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] })
|
||||
end
|
||||
|
||||
def test_render_tag_forloop
|
||||
Liquid::Template.file_system = StubFileSystem.new(
|
||||
'product' => "Product: {{ product.title }} {% if forloop.first %}first{% endif %} {% if forloop.last %}last{% endif %} index:{{ forloop.index }} ",
|
||||
)
|
||||
|
||||
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' }] },
|
||||
partials: {
|
||||
'product' => "Product: {{ product.title }} {% if forloop.first %}first{% endif %} {% if forloop.last %}last{% endif %} index:{{ forloop.index }} ",
|
||||
})
|
||||
{ "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] })
|
||||
end
|
||||
|
||||
def test_render_tag_for_drop
|
||||
Liquid::Template.file_system = StubFileSystem.new(
|
||||
'loop' => "{{ value.foo }}",
|
||||
)
|
||||
|
||||
assert_template_result("123",
|
||||
"{% render 'loop' for loop as value %}", { "loop" => TestEnumerable.new },
|
||||
partials: {
|
||||
'loop' => "{{ value.foo }}",
|
||||
})
|
||||
"{% render 'loop' for loop as value %}", { "loop" => TestEnumerable.new })
|
||||
end
|
||||
|
||||
def test_render_tag_with_drop
|
||||
Liquid::Template.file_system = StubFileSystem.new(
|
||||
'loop' => "{{ value }}",
|
||||
)
|
||||
|
||||
assert_template_result("TestEnumerable",
|
||||
"{% render 'loop' with loop as value %}", { "loop" => TestEnumerable.new },
|
||||
partials: {
|
||||
'loop' => "{{ value }}",
|
||||
})
|
||||
"{% render 'loop' with loop as value %}", { "loop" => TestEnumerable.new })
|
||||
end
|
||||
end
|
||||
|
||||
@@ -36,8 +36,6 @@ class StandardTagTest < Minitest::Test
|
||||
assert_template_result('', '{%comment%}{% endif %}{%endcomment%}')
|
||||
assert_template_result('', '{% comment %}{% endwhatever %}{% endcomment %}')
|
||||
assert_template_result('', '{% comment %}{% raw %} {{%%%%}} }} { {% endcomment %} {% comment {% endraw %} {% endcomment %}')
|
||||
assert_template_result('', '{% comment %}{% " %}{% endcomment %}')
|
||||
assert_template_result('', '{% comment %}{%%}{% endcomment %}')
|
||||
|
||||
assert_template_result('foobar', 'foo{%comment%}comment{%endcomment%}bar')
|
||||
assert_template_result('foobar', 'foo{% comment %}comment{% endcomment %}bar')
|
||||
|
||||
@@ -65,70 +65,4 @@ class TableRowTest < Minitest::Test
|
||||
"{% tablerow char in characters cols:3 %}I WILL NOT BE OUTPUT{% endtablerow %}",
|
||||
{ 'characters' => '' })
|
||||
end
|
||||
|
||||
def test_cols_nil_constant_same_as_evaluated_nil_expression
|
||||
expect = "<tr class=\"row1\">\n" \
|
||||
"<td class=\"col1\">false</td>" \
|
||||
"<td class=\"col2\">false</td>" \
|
||||
"</tr>\n"
|
||||
|
||||
assert_template_result(expect,
|
||||
"{% tablerow i in (1..2) cols:nil %}{{ tablerowloop.col_last }}{% endtablerow %}")
|
||||
|
||||
assert_template_result(expect,
|
||||
"{% tablerow i in (1..2) cols:var %}{{ tablerowloop.col_last }}{% endtablerow %}",
|
||||
{ "var" => nil })
|
||||
end
|
||||
|
||||
def test_tablerow_loop_drop_attributes
|
||||
template = <<~LIQUID.chomp
|
||||
{% tablerow i in (1...2) %}
|
||||
col: {{ tablerowloop.col }}
|
||||
col0: {{ tablerowloop.col0 }}
|
||||
col_first: {{ tablerowloop.col_first }}
|
||||
col_last: {{ tablerowloop.col_last }}
|
||||
first: {{ tablerowloop.first }}
|
||||
index: {{ tablerowloop.index }}
|
||||
index0: {{ tablerowloop.index0 }}
|
||||
last: {{ tablerowloop.last }}
|
||||
length: {{ tablerowloop.length }}
|
||||
rindex: {{ tablerowloop.rindex }}
|
||||
rindex0: {{ tablerowloop.rindex0 }}
|
||||
row: {{ tablerowloop.row }}
|
||||
{% endtablerow %}
|
||||
LIQUID
|
||||
|
||||
expected_output = <<~OUTPUT
|
||||
<tr class="row1">
|
||||
<td class="col1">
|
||||
col: 1
|
||||
col0: 0
|
||||
col_first: true
|
||||
col_last: false
|
||||
first: true
|
||||
index: 1
|
||||
index0: 0
|
||||
last: false
|
||||
length: 2
|
||||
rindex: 2
|
||||
rindex0: 1
|
||||
row: 1
|
||||
</td><td class="col2">
|
||||
col: 2
|
||||
col0: 1
|
||||
col_first: false
|
||||
col_last: true
|
||||
first: false
|
||||
index: 2
|
||||
index0: 1
|
||||
last: true
|
||||
length: 2
|
||||
rindex: 1
|
||||
rindex0: 0
|
||||
row: 1
|
||||
</td></tr>
|
||||
OUTPUT
|
||||
|
||||
assert_template_result(expected_output, template)
|
||||
end
|
||||
end
|
||||
|
||||
+2
-31
@@ -42,34 +42,13 @@ module Minitest
|
||||
message: nil, partials: nil, error_mode: nil, render_errors: false
|
||||
)
|
||||
template = Liquid::Template.parse(template, line_numbers: true, error_mode: error_mode&.to_sym)
|
||||
file_system = StubFileSystem.new(partials || {})
|
||||
file_system = StubFileSystem.new(partials) if partials
|
||||
registers = Liquid::Registers.new(file_system: file_system)
|
||||
context = Liquid::Context.build(static_environments: assigns, rethrow_errors: !render_errors, registers: registers)
|
||||
context = Liquid::Context.build(environments: assigns, rethrow_errors: !render_errors, registers: registers)
|
||||
output = template.render(context)
|
||||
assert_equal(expected, output, message)
|
||||
end
|
||||
|
||||
if ENV['LIQUID_MIGRATOR']
|
||||
puts "-- Liquid Migrator Enabled"
|
||||
|
||||
alias_method(:assert_template_result_without_migrator, :assert_template_result)
|
||||
|
||||
def assert_template_result(expected, source, assigns = {}, error_mode: nil, partials: nil, **kwargs)
|
||||
migrated_source = Liquid::Template.migrate(source, line_numbers: true, error_mode: error_mode&.to_sym)
|
||||
assert_no_migration(migrated_source)
|
||||
if partials
|
||||
migrated_partials = {}
|
||||
partials.each do |name, partial|
|
||||
new_partial = Liquid::Template.migrate(partial, line_numbers: true, error_mode: error_mode&.to_sym)
|
||||
assert_no_migration(new_partial)
|
||||
migrated_partials[name] = new_partial
|
||||
end
|
||||
end
|
||||
assert_template_result_without_migrator(expected, migrated_source, assigns,
|
||||
error_mode: 'strict', partials: migrated_partials, **kwargs)
|
||||
end
|
||||
end
|
||||
|
||||
def assert_match_syntax_error(match, template, error_mode: nil)
|
||||
exception = assert_raises(Liquid::SyntaxError) do
|
||||
Template.parse(template, line_numbers: true, error_mode: error_mode&.to_sym).render
|
||||
@@ -77,14 +56,6 @@ module Minitest
|
||||
assert_match(match, exception.message)
|
||||
end
|
||||
|
||||
def assert_syntax_error(template, error_mode: nil)
|
||||
assert_match_syntax_error("", template, error_mode: error_mode)
|
||||
end
|
||||
|
||||
def assert_no_migration(source)
|
||||
assert_equal(source, Liquid::Template.migrate(source))
|
||||
end
|
||||
|
||||
def assert_usage_increment(name, times: 1)
|
||||
old_method = Liquid::Usage.method(:increment)
|
||||
calls = 0
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'test_helper'
|
||||
|
||||
class MigrateUnitTest < Minitest::Test
|
||||
def test_migrate_preserves_valid_markup
|
||||
[
|
||||
"{{a}}",
|
||||
" {{- \ta\n -}} ",
|
||||
"{{a.b['c'].d[5]|default:6,allow_false:true|truncate:7,'..'}}",
|
||||
"{{ a . b [ 'c' ] . d [ 5 ] | default : 6 , allow_false : true | truncate : 4 , '..' }}",
|
||||
"{%assign x=a.b['c'].d[5]|default:6,allow_false:true|truncate:7,'..'%}",
|
||||
"{% assign x =\na . b [ 'c' ] . d [ 5 ] | default : 6 , allow_false : true | truncate : 4 , '..' %}",
|
||||
"{% if a and b > c %}A{% elsif d or f contains g %}B{% else %}C{% endif %}",
|
||||
<<~LIQUID,
|
||||
{% liquid
|
||||
if x > 0
|
||||
assign x = x | plus: 1
|
||||
|
||||
endif
|
||||
%}
|
||||
LIQUID
|
||||
].each do |source|
|
||||
assert_no_migration(source)
|
||||
end
|
||||
end
|
||||
|
||||
def test_migrate_variable
|
||||
with_error_mode(:lax) do
|
||||
assert_migration({
|
||||
%({{ ,|"' }}) => "{{ }}", # no MarkupWithQuotedFragment match, skipping characters
|
||||
%({{ ,|"' 123 }}) => "{{ 123 }}", # MarkupWithQuotedFragment skipped characters
|
||||
"{{,-2}}" => "{{ -2}}", # preserve separators when removing ignored characters
|
||||
"{{ 12 34 }}" => "{{ 12 }}", # no FilterMarkupRegex match, skipping characters
|
||||
"{{ -12 34 | abs }}" => "{{ -12 | abs }}", # FilterMarkupRegex skipped characters
|
||||
%({{ -12 | '" abs }}) => "{{ -12 | abs }}", # FilterParser skipped characters
|
||||
"{{ -1 | abs ' plus: 1 }}" => "{{ -1 | abs | plus: 1 }}", # FilterParser unexpected separator
|
||||
"{{ -1 | ! abs }}" => "{{ -1 | abs }}", # ignored non-word characters preceding filter name
|
||||
"{{ 'a' | append WAT: 'b' }}" => "{{ 'a' | append : 'b' }}", # FilterArgsRegex skipped characters
|
||||
"{{ '!' | replace, '!': '?' }}" => "{{ '!' | replace: '!', '?' }}", # FilterArgsRegex unexpected separators
|
||||
"{{ -a.1b }}" => "{{ ['-a']['1b'] }}", # quote separators when removing ignored characters
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
def test_migrate_expression
|
||||
with_error_mode(:lax) do
|
||||
assert_migration({
|
||||
"{{ (1.9...2.8) }}" => "{{ (1..2) }}", # apply constant range coercion
|
||||
"{{ 1.2.3.4 }}" => "{{ 1.2 }}", # multiple periods allowed by FLOATS_REGEX, truncated by to_f
|
||||
"{{ 1. }}" => "{{ 1.0 }}", # FLOATS_REGEX didn't require digits after the period
|
||||
"{{ .empty }}" => "{{ ['empty'] }}", # skipped character prevents exact literal lookup
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
def test_migrate_variable_lookup
|
||||
with_error_mode(:lax) do
|
||||
assert_migration({
|
||||
"{{@a[b].c@}}" => "{{ a[b].c }}", # VariableParser skipped characters
|
||||
"{{ a!b$c }}" => "{{ a.b.c }}", # VariableParser unexpected separators
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
def test_migrate_assign
|
||||
with_error_mode(:lax) do
|
||||
assert_migration({
|
||||
"{% assign!a = b!%}" => "{% assign a = b %}", # Syntax skipped characters
|
||||
"{% assign a = @b ! %}" => "{% assign a = b %}", # Variable skipped characters
|
||||
"{% assign|x=1 %}" => "{% assign x=1 %}", # ensure tag name separated from markup
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
def test_lax_migrate_if
|
||||
with_error_mode(:lax) do
|
||||
assert_migration({
|
||||
"{% if@a@%}Y{% endif %}" => "{% if a %}Y{% endif %}", # Syntax skipped character
|
||||
"{% if &a contains^b and *c %}A{% elsif %d or$e %}B{% endif %}" =>
|
||||
"{% if a contains b and c %}A{% elsif d or e %}B{% endif %}", # test more expressions
|
||||
"{% if b 1 %}Y{% endif %}" => "{% if b %}Y{% endif %}", # missing operator with right operand
|
||||
"{% if c == %}Y{% endif %}" => "{% if c == nil %}Y{% endif %}", # operator with missing right operand
|
||||
"{% if!a!%}T{% endif %}" => "{% if a %}T{% endif %}", # VariableParser skipped characters
|
||||
"{% if!%}T{% endif %}" => "{% if nil %}T{% endif %}", # VariableParser skipping all characters
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
def test_migrate_liquid_tag
|
||||
with_error_mode(:lax) do
|
||||
source = <<~LIQUID
|
||||
{% liquid
|
||||
assign ! a = 1
|
||||
assign a = @b !
|
||||
%}
|
||||
LIQUID
|
||||
expect = <<~LIQUID
|
||||
{% liquid
|
||||
assign a = 1
|
||||
assign a = b
|
||||
%}
|
||||
LIQUID
|
||||
assert_migration({ source => expect })
|
||||
end
|
||||
end
|
||||
|
||||
def test_migrate_for
|
||||
with_error_mode(:lax) do
|
||||
assert_migration({
|
||||
# VariableLookup ignored character
|
||||
"{% for i in !array %}x{% endfor %}" => "{% for i in array %}x{% endfor %}",
|
||||
|
||||
# TagAttributes scanned over separators
|
||||
"{% for i in array|offset: 1|limit: 5 %}x{% endfor %}" => "{% for i in array, offset: 1, limit: 5 %}x{% endfor %}",
|
||||
|
||||
# TagAttributes scans all the markup, which can overlap with what Syntax already matched
|
||||
"{% for i in foo.offset: wat %}x{% endfor %}" => "{% for i in foo.offset , offset: wat %}x{% endfor %}",
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assert_migration(source_to_expected_output_hash)
|
||||
source_to_expected_output_hash.each do |source, expect|
|
||||
message = "source: #{source.inspect}"
|
||||
assert_equal(expect, Liquid::Template.migrate(source), message)
|
||||
assert_no_migration(expect)
|
||||
assert_equal(Liquid::Template.parse(expect, parse_mode: :strict).render!, Liquid::Template.parse(source).render!, message)
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user