liquid-4-0-2 4.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|