mirror of
https://github.com/kemko/liquid.git
synced 2026-01-06 18:25:41 +03:00
Add {% liquid %}, {% echo %}, and {% local %} tags
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
module Liquid
|
||||
class BlockBody
|
||||
FullToken = /\A#{TagStart}#{WhitespaceControl}?\s*(\w+)\s*(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
|
||||
LiquidTagToken = /\A\s*(\w+)\s*(.*?)\z/o
|
||||
FullToken = /\A#{TagStart}#{WhitespaceControl}?(\s*)(\w+)(\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
|
||||
ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om
|
||||
WhitespaceOrNothing = /\A\s*\z/
|
||||
TAGSTART = "{%".freeze
|
||||
@@ -13,8 +14,46 @@ module Liquid
|
||||
@blank = true
|
||||
end
|
||||
|
||||
def parse(tokenizer, parse_context)
|
||||
def parse(tokenizer, parse_context, &block)
|
||||
parse_context.line_number = tokenizer.line_number
|
||||
|
||||
if tokenizer.for_liquid_tag
|
||||
parse_for_liquid_tag(tokenizer, parse_context, &block)
|
||||
else
|
||||
parse_for_document(tokenizer, parse_context, &block)
|
||||
end
|
||||
end
|
||||
|
||||
private def parse_for_liquid_tag(tokenizer, parse_context)
|
||||
while token = tokenizer.shift
|
||||
case
|
||||
when token.empty?
|
||||
# pass
|
||||
else
|
||||
unless token =~ LiquidTagToken
|
||||
next if token =~ WhitespaceOrNothing
|
||||
# line isn't empty but didn't match tag syntax, yield and let the
|
||||
# caller raise a syntax error
|
||||
return yield token, token
|
||||
end
|
||||
tag_name = $1
|
||||
markup = $2
|
||||
unless tag = registered_tags[tag_name]
|
||||
# end parsing if we reach an unknown tag and let the caller decide
|
||||
# determine how to proceed
|
||||
return yield tag_name, markup
|
||||
end
|
||||
new_tag = tag.parse(tag_name, markup, tokenizer, parse_context)
|
||||
@blank &&= new_tag.blank?
|
||||
@nodelist << new_tag
|
||||
end
|
||||
parse_context.line_number = tokenizer.line_number
|
||||
end
|
||||
|
||||
yield nil, nil
|
||||
end
|
||||
|
||||
private def parse_for_document(tokenizer, parse_context, &block)
|
||||
while token = tokenizer.shift
|
||||
next if token.empty?
|
||||
case
|
||||
@@ -23,9 +62,20 @@ module Liquid
|
||||
unless token =~ FullToken
|
||||
raise_missing_tag_terminator(token, parse_context)
|
||||
end
|
||||
tag_name = $1
|
||||
markup = $2
|
||||
# fetch the tag from registered blocks
|
||||
tag_name = $2
|
||||
markup = $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 += $1.count("\n".freeze) + $3.count("\n".freeze)
|
||||
end
|
||||
|
||||
if tag_name == 'liquid'.freeze
|
||||
liquid_tag_tokenizer = Tokenizer.new(markup, line_number: parse_context.line_number, for_liquid_tag: true)
|
||||
next parse(liquid_tag_tokenizer, parse_context, &block)
|
||||
end
|
||||
|
||||
unless tag = registered_tags[tag_name]
|
||||
# end parsing if we reach an unknown tag and let the caller decide
|
||||
# determine how to proceed
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
syntax:
|
||||
tag_unexpected_args: "Syntax Error in '%{tag}' - Valid syntax: %{tag}"
|
||||
assign: "Syntax Error in 'assign' - Valid syntax: assign [var] = [source]"
|
||||
local: "Syntax Error in 'local' - Valid syntax: local [var] = [source]"
|
||||
capture: "Syntax Error in 'capture' - Valid syntax: capture [var]"
|
||||
case: "Syntax Error in 'case' - Valid syntax: case [condition]"
|
||||
case_invalid_when: "Syntax Error in tag 'case' - Valid when condition: {% when [condition] [or condition2...] %}"
|
||||
|
||||
@@ -5,8 +5,8 @@ module Liquid
|
||||
include ParserSwitching
|
||||
|
||||
class << self
|
||||
def parse(tag_name, markup, tokenizer, options)
|
||||
tag = new(tag_name, markup, options)
|
||||
def parse(tag_name, markup, tokenizer, parse_context)
|
||||
tag = new(tag_name, markup, parse_context)
|
||||
tag.parse(tokenizer)
|
||||
tag
|
||||
end
|
||||
|
||||
@@ -10,6 +10,10 @@ module Liquid
|
||||
class Assign < Tag
|
||||
Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om
|
||||
|
||||
def self.syntax_error_translation_key
|
||||
"errors.syntax.assign".freeze
|
||||
end
|
||||
|
||||
attr_reader :to, :from
|
||||
|
||||
def initialize(tag_name, markup, options)
|
||||
@@ -18,7 +22,7 @@ module Liquid
|
||||
@to = $1
|
||||
@from = Variable.new($2, options)
|
||||
else
|
||||
raise SyntaxError.new options[:locale].t("errors.syntax.assign".freeze)
|
||||
raise SyntaxError.new(options[:locale].t(self.class.syntax_error_translation_key))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
24
lib/liquid/tags/echo.rb
Normal file
24
lib/liquid/tags/echo.rb
Normal file
@@ -0,0 +1,24 @@
|
||||
module Liquid
|
||||
# Echo outputs an expression
|
||||
#
|
||||
# {% echo monkey %}
|
||||
# {% echo user.name %}
|
||||
#
|
||||
# This is identical to variable output syntax, like {{ foo }}, but works
|
||||
# inside {% liquid %} tags. The full syntax is supported, including filters:
|
||||
#
|
||||
# {% echo user | link %}
|
||||
#
|
||||
class Echo < Tag
|
||||
def initialize(tag_name, markup, parse_context)
|
||||
super
|
||||
@variable = Variable.new(markup, parse_context)
|
||||
end
|
||||
|
||||
def render(context)
|
||||
@variable.render(context)
|
||||
end
|
||||
end
|
||||
|
||||
Template.register_tag('echo'.freeze, Echo)
|
||||
end
|
||||
30
lib/liquid/tags/local.rb
Normal file
30
lib/liquid/tags/local.rb
Normal file
@@ -0,0 +1,30 @@
|
||||
require_relative 'assign'
|
||||
|
||||
module Liquid
|
||||
# Local sets a variable in the current scope.
|
||||
#
|
||||
# {% local foo = 'monkey' %}
|
||||
#
|
||||
# You can then use the variable later in the scope.
|
||||
#
|
||||
# {% if true %}
|
||||
# {% local foo = 'monkey' %}
|
||||
# {{ foo }} outputs monkey
|
||||
# {% endif %}
|
||||
# {{ foo }} outputs nothing
|
||||
#
|
||||
class Local < Assign
|
||||
def self.syntax_error_translation_key
|
||||
"errors.syntax.local".freeze
|
||||
end
|
||||
|
||||
def render(context)
|
||||
val = @from.render(context)
|
||||
context[@to] = val
|
||||
context.resource_limits.assign_score += assign_score_of(val)
|
||||
''.freeze
|
||||
end
|
||||
end
|
||||
|
||||
Template.register_tag('local'.freeze, Local)
|
||||
end
|
||||
@@ -1,25 +1,31 @@
|
||||
module Liquid
|
||||
class Tokenizer
|
||||
attr_reader :line_number
|
||||
attr_reader :line_number, :for_liquid_tag
|
||||
|
||||
def initialize(source, line_numbers = false)
|
||||
def initialize(source, line_numbers = false, line_number: nil, for_liquid_tag: false)
|
||||
@source = source
|
||||
@line_number = line_numbers ? 1 : nil
|
||||
@line_number = line_number || (line_numbers ? 1 : nil)
|
||||
@for_liquid_tag = for_liquid_tag
|
||||
@tokens = tokenize
|
||||
end
|
||||
|
||||
def shift
|
||||
token = @tokens.shift
|
||||
@line_number += token.count("\n") if @line_number && token
|
||||
token = @tokens.shift or return
|
||||
|
||||
if @line_number
|
||||
@line_number += @for_liquid_tag ? 1 : token.count("\n")
|
||||
end
|
||||
|
||||
token
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def tokenize
|
||||
@source = @source.source if @source.respond_to?(:source)
|
||||
return [] if @source.to_s.empty?
|
||||
|
||||
return @source.split("\n") if @for_liquid_tag
|
||||
|
||||
tokens = @source.split(TemplateParser)
|
||||
|
||||
# removes the rogue empty element at the beginning of the array
|
||||
|
||||
Reference in New Issue
Block a user