liquid-4-0-2 4.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/History.md +235 -0
- data/LICENSE +20 -0
- data/README.md +108 -0
- data/lib/liquid.rb +80 -0
- data/lib/liquid/block.rb +77 -0
- data/lib/liquid/block_body.rb +142 -0
- data/lib/liquid/condition.rb +151 -0
- data/lib/liquid/context.rb +226 -0
- data/lib/liquid/document.rb +27 -0
- data/lib/liquid/drop.rb +78 -0
- data/lib/liquid/errors.rb +56 -0
- data/lib/liquid/expression.rb +49 -0
- data/lib/liquid/extensions.rb +74 -0
- data/lib/liquid/file_system.rb +73 -0
- data/lib/liquid/forloop_drop.rb +42 -0
- data/lib/liquid/i18n.rb +39 -0
- data/lib/liquid/interrupts.rb +16 -0
- data/lib/liquid/lexer.rb +55 -0
- data/lib/liquid/locales/en.yml +26 -0
- data/lib/liquid/parse_context.rb +38 -0
- data/lib/liquid/parse_tree_visitor.rb +42 -0
- data/lib/liquid/parser.rb +90 -0
- data/lib/liquid/parser_switching.rb +31 -0
- data/lib/liquid/profiler.rb +158 -0
- data/lib/liquid/profiler/hooks.rb +23 -0
- data/lib/liquid/range_lookup.rb +37 -0
- data/lib/liquid/resource_limits.rb +23 -0
- data/lib/liquid/standardfilters.rb +485 -0
- data/lib/liquid/strainer.rb +66 -0
- data/lib/liquid/tablerowloop_drop.rb +62 -0
- data/lib/liquid/tag.rb +43 -0
- data/lib/liquid/tags/assign.rb +59 -0
- data/lib/liquid/tags/break.rb +18 -0
- data/lib/liquid/tags/capture.rb +38 -0
- data/lib/liquid/tags/case.rb +94 -0
- data/lib/liquid/tags/comment.rb +16 -0
- data/lib/liquid/tags/continue.rb +18 -0
- data/lib/liquid/tags/cycle.rb +65 -0
- data/lib/liquid/tags/decrement.rb +35 -0
- data/lib/liquid/tags/for.rb +203 -0
- data/lib/liquid/tags/if.rb +122 -0
- data/lib/liquid/tags/ifchanged.rb +18 -0
- data/lib/liquid/tags/include.rb +124 -0
- data/lib/liquid/tags/increment.rb +31 -0
- data/lib/liquid/tags/raw.rb +47 -0
- data/lib/liquid/tags/table_row.rb +62 -0
- data/lib/liquid/tags/unless.rb +30 -0
- data/lib/liquid/template.rb +254 -0
- data/lib/liquid/tokenizer.rb +31 -0
- data/lib/liquid/utils.rb +83 -0
- data/lib/liquid/variable.rb +148 -0
- data/lib/liquid/variable_lookup.rb +88 -0
- data/lib/liquid/version.rb +4 -0
- data/test/fixtures/en_locale.yml +9 -0
- data/test/integration/assign_test.rb +48 -0
- data/test/integration/blank_test.rb +106 -0
- data/test/integration/block_test.rb +12 -0
- data/test/integration/capture_test.rb +50 -0
- data/test/integration/context_test.rb +32 -0
- data/test/integration/document_test.rb +19 -0
- data/test/integration/drop_test.rb +273 -0
- data/test/integration/error_handling_test.rb +260 -0
- data/test/integration/filter_test.rb +178 -0
- data/test/integration/hash_ordering_test.rb +23 -0
- data/test/integration/output_test.rb +123 -0
- data/test/integration/parse_tree_visitor_test.rb +247 -0
- data/test/integration/parsing_quirks_test.rb +122 -0
- data/test/integration/render_profiling_test.rb +154 -0
- data/test/integration/security_test.rb +80 -0
- data/test/integration/standard_filter_test.rb +698 -0
- data/test/integration/tags/break_tag_test.rb +15 -0
- data/test/integration/tags/continue_tag_test.rb +15 -0
- data/test/integration/tags/for_tag_test.rb +410 -0
- data/test/integration/tags/if_else_tag_test.rb +188 -0
- data/test/integration/tags/include_tag_test.rb +245 -0
- data/test/integration/tags/increment_tag_test.rb +23 -0
- data/test/integration/tags/raw_tag_test.rb +31 -0
- data/test/integration/tags/standard_tag_test.rb +296 -0
- data/test/integration/tags/statements_test.rb +111 -0
- data/test/integration/tags/table_row_test.rb +64 -0
- data/test/integration/tags/unless_else_tag_test.rb +26 -0
- data/test/integration/template_test.rb +332 -0
- data/test/integration/trim_mode_test.rb +529 -0
- data/test/integration/variable_test.rb +96 -0
- data/test/test_helper.rb +116 -0
- data/test/unit/block_unit_test.rb +58 -0
- data/test/unit/condition_unit_test.rb +166 -0
- data/test/unit/context_unit_test.rb +489 -0
- data/test/unit/file_system_unit_test.rb +35 -0
- data/test/unit/i18n_unit_test.rb +37 -0
- data/test/unit/lexer_unit_test.rb +51 -0
- data/test/unit/parser_unit_test.rb +82 -0
- data/test/unit/regexp_unit_test.rb +44 -0
- data/test/unit/strainer_unit_test.rb +164 -0
- data/test/unit/tag_unit_test.rb +21 -0
- data/test/unit/tags/case_tag_unit_test.rb +10 -0
- data/test/unit/tags/for_tag_unit_test.rb +13 -0
- data/test/unit/tags/if_tag_unit_test.rb +8 -0
- data/test/unit/template_unit_test.rb +78 -0
- data/test/unit/tokenizer_unit_test.rb +55 -0
- data/test/unit/variable_unit_test.rb +162 -0
- metadata +224 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
module Liquid
|
2
|
+
class Document < BlockBody
|
3
|
+
def self.parse(tokens, parse_context)
|
4
|
+
doc = new
|
5
|
+
doc.parse(tokens, parse_context)
|
6
|
+
doc
|
7
|
+
end
|
8
|
+
|
9
|
+
def parse(tokens, parse_context)
|
10
|
+
super do |end_tag_name, end_tag_params|
|
11
|
+
unknown_tag(end_tag_name, parse_context) if end_tag_name
|
12
|
+
end
|
13
|
+
rescue SyntaxError => e
|
14
|
+
e.line_number ||= parse_context.line_number
|
15
|
+
raise
|
16
|
+
end
|
17
|
+
|
18
|
+
def unknown_tag(tag, parse_context)
|
19
|
+
case tag
|
20
|
+
when 'else'.freeze, 'end'.freeze
|
21
|
+
raise SyntaxError.new(parse_context.locale.t("errors.syntax.unexpected_outer_tag".freeze, tag: tag))
|
22
|
+
else
|
23
|
+
raise SyntaxError.new(parse_context.locale.t("errors.syntax.unknown_tag".freeze, tag: tag))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/liquid/drop.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Liquid
|
4
|
+
# A drop in liquid is a class which allows you to export DOM like things to liquid.
|
5
|
+
# Methods of drops are callable.
|
6
|
+
# The main use for liquid drops is to implement lazy loaded objects.
|
7
|
+
# If you would like to make data available to the web designers which you don't want loaded unless needed then
|
8
|
+
# a drop is a great way to do that.
|
9
|
+
#
|
10
|
+
# Example:
|
11
|
+
#
|
12
|
+
# class ProductDrop < Liquid::Drop
|
13
|
+
# def top_sales
|
14
|
+
# Shop.current.products.find(:all, :order => 'sales', :limit => 10 )
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# tmpl = Liquid::Template.parse( ' {% for product in product.top_sales %} {{ product.name }} {%endfor%} ' )
|
19
|
+
# tmpl.render('product' => ProductDrop.new ) # will invoke top_sales query.
|
20
|
+
#
|
21
|
+
# Your drop can either implement the methods sans any parameters
|
22
|
+
# or implement the liquid_method_missing(name) method which is a catch all.
|
23
|
+
class Drop
|
24
|
+
attr_writer :context
|
25
|
+
|
26
|
+
# Catch all for the method
|
27
|
+
def liquid_method_missing(method)
|
28
|
+
return nil unless @context && @context.strict_variables
|
29
|
+
raise Liquid::UndefinedDropMethod, "undefined method #{method}"
|
30
|
+
end
|
31
|
+
|
32
|
+
# called by liquid to invoke a drop
|
33
|
+
def invoke_drop(method_or_key)
|
34
|
+
if self.class.invokable?(method_or_key)
|
35
|
+
send(method_or_key)
|
36
|
+
else
|
37
|
+
liquid_method_missing(method_or_key)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def key?(_name)
|
42
|
+
true
|
43
|
+
end
|
44
|
+
|
45
|
+
def inspect
|
46
|
+
self.class.to_s
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_liquid
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_s
|
54
|
+
self.class.name
|
55
|
+
end
|
56
|
+
|
57
|
+
alias_method :[], :invoke_drop
|
58
|
+
|
59
|
+
# Check for method existence without invoking respond_to?, which creates symbols
|
60
|
+
def self.invokable?(method_name)
|
61
|
+
invokable_methods.include?(method_name.to_s)
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.invokable_methods
|
65
|
+
@invokable_methods ||= begin
|
66
|
+
blacklist = Liquid::Drop.public_instance_methods + [:each]
|
67
|
+
|
68
|
+
if include?(Enumerable)
|
69
|
+
blacklist += Enumerable.public_instance_methods
|
70
|
+
blacklist -= [:sort, :count, :first, :min, :max, :include?]
|
71
|
+
end
|
72
|
+
|
73
|
+
whitelist = [:to_liquid] + (public_instance_methods - blacklist)
|
74
|
+
Set.new(whitelist.map(&:to_s))
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Liquid
|
2
|
+
class Error < ::StandardError
|
3
|
+
attr_accessor :line_number
|
4
|
+
attr_accessor :template_name
|
5
|
+
attr_accessor :markup_context
|
6
|
+
|
7
|
+
def to_s(with_prefix = true)
|
8
|
+
str = ""
|
9
|
+
str << message_prefix if with_prefix
|
10
|
+
str << super()
|
11
|
+
|
12
|
+
if markup_context
|
13
|
+
str << " "
|
14
|
+
str << markup_context
|
15
|
+
end
|
16
|
+
|
17
|
+
str
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def message_prefix
|
23
|
+
str = ""
|
24
|
+
if is_a?(SyntaxError)
|
25
|
+
str << "Liquid syntax error"
|
26
|
+
else
|
27
|
+
str << "Liquid error"
|
28
|
+
end
|
29
|
+
|
30
|
+
if line_number
|
31
|
+
str << " ("
|
32
|
+
str << template_name << " " if template_name
|
33
|
+
str << "line " << line_number.to_s << ")"
|
34
|
+
end
|
35
|
+
|
36
|
+
str << ": "
|
37
|
+
str
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
ArgumentError = Class.new(Error)
|
42
|
+
ContextError = Class.new(Error)
|
43
|
+
FileSystemError = Class.new(Error)
|
44
|
+
StandardError = Class.new(Error)
|
45
|
+
SyntaxError = Class.new(Error)
|
46
|
+
StackLevelError = Class.new(Error)
|
47
|
+
TaintedError = Class.new(Error)
|
48
|
+
MemoryError = Class.new(Error)
|
49
|
+
ZeroDivisionError = Class.new(Error)
|
50
|
+
FloatDomainError = Class.new(Error)
|
51
|
+
UndefinedVariable = Class.new(Error)
|
52
|
+
UndefinedDropMethod = Class.new(Error)
|
53
|
+
UndefinedFilter = Class.new(Error)
|
54
|
+
MethodOverrideError = Class.new(Error)
|
55
|
+
InternalError = Class.new(Error)
|
56
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Liquid
|
2
|
+
class Expression
|
3
|
+
class MethodLiteral
|
4
|
+
attr_reader :method_name, :to_s
|
5
|
+
|
6
|
+
def initialize(method_name, to_s)
|
7
|
+
@method_name = method_name
|
8
|
+
@to_s = to_s
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_liquid
|
12
|
+
to_s
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
LITERALS = {
|
17
|
+
nil => nil, 'nil'.freeze => nil, 'null'.freeze => nil, ''.freeze => nil,
|
18
|
+
'true'.freeze => true,
|
19
|
+
'false'.freeze => false,
|
20
|
+
'blank'.freeze => MethodLiteral.new(:blank?, '').freeze,
|
21
|
+
'empty'.freeze => MethodLiteral.new(:empty?, '').freeze
|
22
|
+
}
|
23
|
+
|
24
|
+
SINGLE_QUOTED_STRING = /\A'(.*)'\z/m
|
25
|
+
DOUBLE_QUOTED_STRING = /\A"(.*)"\z/m
|
26
|
+
INTEGERS_REGEX = /\A(-?\d+)\z/
|
27
|
+
FLOATS_REGEX = /\A(-?\d[\d\.]+)\z/
|
28
|
+
RANGES_REGEX = /\A\((\S+)\.\.(\S+)\)\z/
|
29
|
+
|
30
|
+
def self.parse(markup)
|
31
|
+
if LITERALS.key?(markup)
|
32
|
+
LITERALS[markup]
|
33
|
+
else
|
34
|
+
case markup
|
35
|
+
when SINGLE_QUOTED_STRING, DOUBLE_QUOTED_STRING
|
36
|
+
$1
|
37
|
+
when INTEGERS_REGEX
|
38
|
+
$1.to_i
|
39
|
+
when RANGES_REGEX
|
40
|
+
RangeLookup.parse($1, $2)
|
41
|
+
when FLOATS_REGEX
|
42
|
+
$1.to_f
|
43
|
+
else
|
44
|
+
VariableLookup.parse(markup)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
class String # :nodoc:
|
5
|
+
def to_liquid
|
6
|
+
self
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class Symbol # :nodoc:
|
11
|
+
def to_liquid
|
12
|
+
to_s
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Array # :nodoc:
|
17
|
+
def to_liquid
|
18
|
+
self
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Hash # :nodoc:
|
23
|
+
def to_liquid
|
24
|
+
self
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Numeric # :nodoc:
|
29
|
+
def to_liquid
|
30
|
+
self
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Range # :nodoc:
|
35
|
+
def to_liquid
|
36
|
+
self
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Time # :nodoc:
|
41
|
+
def to_liquid
|
42
|
+
self
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class DateTime < Date # :nodoc:
|
47
|
+
def to_liquid
|
48
|
+
self
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class Date # :nodoc:
|
53
|
+
def to_liquid
|
54
|
+
self
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class TrueClass
|
59
|
+
def to_liquid # :nodoc:
|
60
|
+
self
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class FalseClass
|
65
|
+
def to_liquid # :nodoc:
|
66
|
+
self
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class NilClass
|
71
|
+
def to_liquid # :nodoc:
|
72
|
+
self
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Liquid
|
2
|
+
# A Liquid file system is a way to let your templates retrieve other templates for use with the include tag.
|
3
|
+
#
|
4
|
+
# You can implement subclasses that retrieve templates from the database, from the file system using a different
|
5
|
+
# path structure, you can provide them as hard-coded inline strings, or any manner that you see fit.
|
6
|
+
#
|
7
|
+
# You can add additional instance variables, arguments, or methods as needed.
|
8
|
+
#
|
9
|
+
# Example:
|
10
|
+
#
|
11
|
+
# Liquid::Template.file_system = Liquid::LocalFileSystem.new(template_path)
|
12
|
+
# liquid = Liquid::Template.parse(template)
|
13
|
+
#
|
14
|
+
# This will parse the template with a LocalFileSystem implementation rooted at 'template_path'.
|
15
|
+
class BlankFileSystem
|
16
|
+
# Called by Liquid to retrieve a template file
|
17
|
+
def read_template_file(_template_path)
|
18
|
+
raise FileSystemError, "This liquid context does not allow includes."
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# This implements an abstract file system which retrieves template files named in a manner similar to Rails partials,
|
23
|
+
# ie. with the template name prefixed with an underscore. The extension ".liquid" is also added.
|
24
|
+
#
|
25
|
+
# For security reasons, template paths are only allowed to contain letters, numbers, and underscore.
|
26
|
+
#
|
27
|
+
# Example:
|
28
|
+
#
|
29
|
+
# file_system = Liquid::LocalFileSystem.new("/some/path")
|
30
|
+
#
|
31
|
+
# file_system.full_path("mypartial") # => "/some/path/_mypartial.liquid"
|
32
|
+
# file_system.full_path("dir/mypartial") # => "/some/path/dir/_mypartial.liquid"
|
33
|
+
#
|
34
|
+
# Optionally in the second argument you can specify a custom pattern for template filenames.
|
35
|
+
# The Kernel::sprintf format specification is used.
|
36
|
+
# Default pattern is "_%s.liquid".
|
37
|
+
#
|
38
|
+
# Example:
|
39
|
+
#
|
40
|
+
# file_system = Liquid::LocalFileSystem.new("/some/path", "%s.html")
|
41
|
+
#
|
42
|
+
# file_system.full_path("index") # => "/some/path/index.html"
|
43
|
+
#
|
44
|
+
class LocalFileSystem
|
45
|
+
attr_accessor :root
|
46
|
+
|
47
|
+
def initialize(root, pattern = "_%s.liquid".freeze)
|
48
|
+
@root = root
|
49
|
+
@pattern = pattern
|
50
|
+
end
|
51
|
+
|
52
|
+
def read_template_file(template_path)
|
53
|
+
full_path = full_path(template_path)
|
54
|
+
raise FileSystemError, "No such template '#{template_path}'" unless File.exist?(full_path)
|
55
|
+
|
56
|
+
File.read(full_path)
|
57
|
+
end
|
58
|
+
|
59
|
+
def full_path(template_path)
|
60
|
+
raise FileSystemError, "Illegal template name '#{template_path}'" unless template_path =~ /\A[^.\/][a-zA-Z0-9_\/]+\z/
|
61
|
+
|
62
|
+
full_path = if template_path.include?('/'.freeze)
|
63
|
+
File.join(root, File.dirname(template_path), @pattern % File.basename(template_path))
|
64
|
+
else
|
65
|
+
File.join(root, @pattern % template_path)
|
66
|
+
end
|
67
|
+
|
68
|
+
raise FileSystemError, "Illegal template path '#{File.expand_path(full_path)}'" unless File.expand_path(full_path).start_with?(File.expand_path(root))
|
69
|
+
|
70
|
+
full_path
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Liquid
|
2
|
+
class ForloopDrop < Drop
|
3
|
+
def initialize(name, length, parentloop)
|
4
|
+
@name = name
|
5
|
+
@length = length
|
6
|
+
@parentloop = parentloop
|
7
|
+
@index = 0
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :name, :length, :parentloop
|
11
|
+
|
12
|
+
def index
|
13
|
+
@index + 1
|
14
|
+
end
|
15
|
+
|
16
|
+
def index0
|
17
|
+
@index
|
18
|
+
end
|
19
|
+
|
20
|
+
def rindex
|
21
|
+
@length - @index
|
22
|
+
end
|
23
|
+
|
24
|
+
def rindex0
|
25
|
+
@length - @index - 1
|
26
|
+
end
|
27
|
+
|
28
|
+
def first
|
29
|
+
@index == 0
|
30
|
+
end
|
31
|
+
|
32
|
+
def last
|
33
|
+
@index == @length - 1
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
def increment!
|
39
|
+
@index += 1
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/liquid/i18n.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Liquid
|
4
|
+
class I18n
|
5
|
+
DEFAULT_LOCALE = File.join(File.expand_path(__dir__), "locales", "en.yml")
|
6
|
+
|
7
|
+
TranslationError = Class.new(StandardError)
|
8
|
+
|
9
|
+
attr_reader :path
|
10
|
+
|
11
|
+
def initialize(path = DEFAULT_LOCALE)
|
12
|
+
@path = path
|
13
|
+
end
|
14
|
+
|
15
|
+
def translate(name, vars = {})
|
16
|
+
interpolate(deep_fetch_translation(name), vars)
|
17
|
+
end
|
18
|
+
alias_method :t, :translate
|
19
|
+
|
20
|
+
def locale
|
21
|
+
@locale ||= YAML.load_file(@path)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def interpolate(name, vars)
|
27
|
+
name.gsub(/%\{(\w+)\}/) do
|
28
|
+
# raise TranslationError, "Undefined key #{$1} for interpolation in translation #{name}" unless vars[$1.to_sym]
|
29
|
+
"#{vars[$1.to_sym]}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def deep_fetch_translation(name)
|
34
|
+
name.split('.'.freeze).reduce(locale) do |level, cur|
|
35
|
+
level[cur] or raise TranslationError, "Translation for #{name} does not exist in locale #{path}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|