liquid-4-0-2 4.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +235 -0
  3. data/LICENSE +20 -0
  4. data/README.md +108 -0
  5. data/lib/liquid.rb +80 -0
  6. data/lib/liquid/block.rb +77 -0
  7. data/lib/liquid/block_body.rb +142 -0
  8. data/lib/liquid/condition.rb +151 -0
  9. data/lib/liquid/context.rb +226 -0
  10. data/lib/liquid/document.rb +27 -0
  11. data/lib/liquid/drop.rb +78 -0
  12. data/lib/liquid/errors.rb +56 -0
  13. data/lib/liquid/expression.rb +49 -0
  14. data/lib/liquid/extensions.rb +74 -0
  15. data/lib/liquid/file_system.rb +73 -0
  16. data/lib/liquid/forloop_drop.rb +42 -0
  17. data/lib/liquid/i18n.rb +39 -0
  18. data/lib/liquid/interrupts.rb +16 -0
  19. data/lib/liquid/lexer.rb +55 -0
  20. data/lib/liquid/locales/en.yml +26 -0
  21. data/lib/liquid/parse_context.rb +38 -0
  22. data/lib/liquid/parse_tree_visitor.rb +42 -0
  23. data/lib/liquid/parser.rb +90 -0
  24. data/lib/liquid/parser_switching.rb +31 -0
  25. data/lib/liquid/profiler.rb +158 -0
  26. data/lib/liquid/profiler/hooks.rb +23 -0
  27. data/lib/liquid/range_lookup.rb +37 -0
  28. data/lib/liquid/resource_limits.rb +23 -0
  29. data/lib/liquid/standardfilters.rb +485 -0
  30. data/lib/liquid/strainer.rb +66 -0
  31. data/lib/liquid/tablerowloop_drop.rb +62 -0
  32. data/lib/liquid/tag.rb +43 -0
  33. data/lib/liquid/tags/assign.rb +59 -0
  34. data/lib/liquid/tags/break.rb +18 -0
  35. data/lib/liquid/tags/capture.rb +38 -0
  36. data/lib/liquid/tags/case.rb +94 -0
  37. data/lib/liquid/tags/comment.rb +16 -0
  38. data/lib/liquid/tags/continue.rb +18 -0
  39. data/lib/liquid/tags/cycle.rb +65 -0
  40. data/lib/liquid/tags/decrement.rb +35 -0
  41. data/lib/liquid/tags/for.rb +203 -0
  42. data/lib/liquid/tags/if.rb +122 -0
  43. data/lib/liquid/tags/ifchanged.rb +18 -0
  44. data/lib/liquid/tags/include.rb +124 -0
  45. data/lib/liquid/tags/increment.rb +31 -0
  46. data/lib/liquid/tags/raw.rb +47 -0
  47. data/lib/liquid/tags/table_row.rb +62 -0
  48. data/lib/liquid/tags/unless.rb +30 -0
  49. data/lib/liquid/template.rb +254 -0
  50. data/lib/liquid/tokenizer.rb +31 -0
  51. data/lib/liquid/utils.rb +83 -0
  52. data/lib/liquid/variable.rb +148 -0
  53. data/lib/liquid/variable_lookup.rb +88 -0
  54. data/lib/liquid/version.rb +4 -0
  55. data/test/fixtures/en_locale.yml +9 -0
  56. data/test/integration/assign_test.rb +48 -0
  57. data/test/integration/blank_test.rb +106 -0
  58. data/test/integration/block_test.rb +12 -0
  59. data/test/integration/capture_test.rb +50 -0
  60. data/test/integration/context_test.rb +32 -0
  61. data/test/integration/document_test.rb +19 -0
  62. data/test/integration/drop_test.rb +273 -0
  63. data/test/integration/error_handling_test.rb +260 -0
  64. data/test/integration/filter_test.rb +178 -0
  65. data/test/integration/hash_ordering_test.rb +23 -0
  66. data/test/integration/output_test.rb +123 -0
  67. data/test/integration/parse_tree_visitor_test.rb +247 -0
  68. data/test/integration/parsing_quirks_test.rb +122 -0
  69. data/test/integration/render_profiling_test.rb +154 -0
  70. data/test/integration/security_test.rb +80 -0
  71. data/test/integration/standard_filter_test.rb +698 -0
  72. data/test/integration/tags/break_tag_test.rb +15 -0
  73. data/test/integration/tags/continue_tag_test.rb +15 -0
  74. data/test/integration/tags/for_tag_test.rb +410 -0
  75. data/test/integration/tags/if_else_tag_test.rb +188 -0
  76. data/test/integration/tags/include_tag_test.rb +245 -0
  77. data/test/integration/tags/increment_tag_test.rb +23 -0
  78. data/test/integration/tags/raw_tag_test.rb +31 -0
  79. data/test/integration/tags/standard_tag_test.rb +296 -0
  80. data/test/integration/tags/statements_test.rb +111 -0
  81. data/test/integration/tags/table_row_test.rb +64 -0
  82. data/test/integration/tags/unless_else_tag_test.rb +26 -0
  83. data/test/integration/template_test.rb +332 -0
  84. data/test/integration/trim_mode_test.rb +529 -0
  85. data/test/integration/variable_test.rb +96 -0
  86. data/test/test_helper.rb +116 -0
  87. data/test/unit/block_unit_test.rb +58 -0
  88. data/test/unit/condition_unit_test.rb +166 -0
  89. data/test/unit/context_unit_test.rb +489 -0
  90. data/test/unit/file_system_unit_test.rb +35 -0
  91. data/test/unit/i18n_unit_test.rb +37 -0
  92. data/test/unit/lexer_unit_test.rb +51 -0
  93. data/test/unit/parser_unit_test.rb +82 -0
  94. data/test/unit/regexp_unit_test.rb +44 -0
  95. data/test/unit/strainer_unit_test.rb +164 -0
  96. data/test/unit/tag_unit_test.rb +21 -0
  97. data/test/unit/tags/case_tag_unit_test.rb +10 -0
  98. data/test/unit/tags/for_tag_unit_test.rb +13 -0
  99. data/test/unit/tags/if_tag_unit_test.rb +8 -0
  100. data/test/unit/template_unit_test.rb +78 -0
  101. data/test/unit/tokenizer_unit_test.rb +55 -0
  102. data/test/unit/variable_unit_test.rb +162 -0
  103. metadata +224 -0
@@ -0,0 +1,66 @@
1
+ require 'set'
2
+
3
+ module Liquid
4
+ # Strainer is the parent class for the filters system.
5
+ # New filters are mixed into the strainer class which is then instantiated for each liquid template render run.
6
+ #
7
+ # The Strainer only allows method calls defined in filters given to it via Strainer.global_filter,
8
+ # Context#add_filters or Template.register_filter
9
+ class Strainer #:nodoc:
10
+ @@global_strainer = Class.new(Strainer) do
11
+ @filter_methods = Set.new
12
+ end
13
+ @@strainer_class_cache = Hash.new do |hash, filters|
14
+ hash[filters] = Class.new(@@global_strainer) do
15
+ @filter_methods = @@global_strainer.filter_methods.dup
16
+ filters.each { |f| add_filter(f) }
17
+ end
18
+ end
19
+
20
+ def initialize(context)
21
+ @context = context
22
+ end
23
+
24
+ class << self
25
+ attr_reader :filter_methods
26
+ end
27
+
28
+ def self.add_filter(filter)
29
+ raise ArgumentError, "Expected module but got: #{filter.class}" unless filter.is_a?(Module)
30
+ unless self.include?(filter)
31
+ invokable_non_public_methods = (filter.private_instance_methods + filter.protected_instance_methods).select { |m| invokable?(m) }
32
+ if invokable_non_public_methods.any?
33
+ raise MethodOverrideError, "Filter overrides registered public methods as non public: #{invokable_non_public_methods.join(', ')}"
34
+ else
35
+ send(:include, filter)
36
+ @filter_methods.merge(filter.public_instance_methods.map(&:to_s))
37
+ end
38
+ end
39
+ end
40
+
41
+ def self.global_filter(filter)
42
+ @@strainer_class_cache.clear
43
+ @@global_strainer.add_filter(filter)
44
+ end
45
+
46
+ def self.invokable?(method)
47
+ @filter_methods.include?(method.to_s)
48
+ end
49
+
50
+ def self.create(context, filters = [])
51
+ @@strainer_class_cache[filters].new(context)
52
+ end
53
+
54
+ def invoke(method, *args)
55
+ if self.class.invokable?(method)
56
+ send(method, *args)
57
+ elsif @context && @context.strict_filters
58
+ raise Liquid::UndefinedFilter, "undefined filter #{method}"
59
+ else
60
+ args.first
61
+ end
62
+ rescue ::ArgumentError => e
63
+ raise Liquid::ArgumentError, e.message, e.backtrace
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,62 @@
1
+ module Liquid
2
+ class TablerowloopDrop < Drop
3
+ def initialize(length, cols)
4
+ @length = length
5
+ @row = 1
6
+ @col = 1
7
+ @cols = cols
8
+ @index = 0
9
+ end
10
+
11
+ attr_reader :length, :col, :row
12
+
13
+ def index
14
+ @index + 1
15
+ end
16
+
17
+ def index0
18
+ @index
19
+ end
20
+
21
+ def col0
22
+ @col - 1
23
+ end
24
+
25
+ def rindex
26
+ @length - @index
27
+ end
28
+
29
+ def rindex0
30
+ @length - @index - 1
31
+ end
32
+
33
+ def first
34
+ @index == 0
35
+ end
36
+
37
+ def last
38
+ @index == @length - 1
39
+ end
40
+
41
+ def col_first
42
+ @col == 1
43
+ end
44
+
45
+ def col_last
46
+ @col == @cols
47
+ end
48
+
49
+ protected
50
+
51
+ def increment!
52
+ @index += 1
53
+
54
+ if @col == @cols
55
+ @col = 1
56
+ @row += 1
57
+ else
58
+ @col += 1
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,43 @@
1
+ module Liquid
2
+ class Tag
3
+ attr_reader :nodelist, :tag_name, :line_number, :parse_context
4
+ alias_method :options, :parse_context
5
+ include ParserSwitching
6
+
7
+ class << self
8
+ def parse(tag_name, markup, tokenizer, options)
9
+ tag = new(tag_name, markup, options)
10
+ tag.parse(tokenizer)
11
+ tag
12
+ end
13
+
14
+ private :new
15
+ end
16
+
17
+ def initialize(tag_name, markup, parse_context)
18
+ @tag_name = tag_name
19
+ @markup = markup
20
+ @parse_context = parse_context
21
+ @line_number = parse_context.line_number
22
+ end
23
+
24
+ def parse(_tokens)
25
+ end
26
+
27
+ def raw
28
+ "#{@tag_name} #{@markup}"
29
+ end
30
+
31
+ def name
32
+ self.class.name.downcase
33
+ end
34
+
35
+ def render(_context)
36
+ ''.freeze
37
+ end
38
+
39
+ def blank?
40
+ false
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,59 @@
1
+ module Liquid
2
+ # Assign sets a variable in your template.
3
+ #
4
+ # {% assign foo = 'monkey' %}
5
+ #
6
+ # You can then use the variable later in the page.
7
+ #
8
+ # {{ foo }}
9
+ #
10
+ class Assign < Tag
11
+ Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om
12
+
13
+ attr_reader :to, :from
14
+
15
+ def initialize(tag_name, markup, options)
16
+ super
17
+ if markup =~ Syntax
18
+ @to = $1
19
+ @from = Variable.new($2, options)
20
+ else
21
+ raise SyntaxError.new options[:locale].t("errors.syntax.assign".freeze)
22
+ end
23
+ end
24
+
25
+ def render(context)
26
+ val = @from.render(context)
27
+ context.scopes.last[@to] = val
28
+ context.resource_limits.assign_score += assign_score_of(val)
29
+ ''.freeze
30
+ end
31
+
32
+ def blank?
33
+ true
34
+ end
35
+
36
+ private
37
+
38
+ def assign_score_of(val)
39
+ if val.instance_of?(String)
40
+ val.length
41
+ elsif val.instance_of?(Array) || val.instance_of?(Hash)
42
+ sum = 1
43
+ # Uses #each to avoid extra allocations.
44
+ val.each { |child| sum += assign_score_of(child) }
45
+ sum
46
+ else
47
+ 1
48
+ end
49
+ end
50
+
51
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
52
+ def children
53
+ [@node.from]
54
+ end
55
+ end
56
+ end
57
+
58
+ Template.register_tag('assign'.freeze, Assign)
59
+ end
@@ -0,0 +1,18 @@
1
+ module Liquid
2
+ # Break tag to be used to break out of a for loop.
3
+ #
4
+ # == Basic Usage:
5
+ # {% for item in collection %}
6
+ # {% if item.condition %}
7
+ # {% break %}
8
+ # {% endif %}
9
+ # {% endfor %}
10
+ #
11
+ class Break < Tag
12
+ def interrupt
13
+ BreakInterrupt.new
14
+ end
15
+ end
16
+
17
+ Template.register_tag('break'.freeze, Break)
18
+ end
@@ -0,0 +1,38 @@
1
+ module Liquid
2
+ # Capture stores the result of a block into a variable without rendering it inplace.
3
+ #
4
+ # {% capture heading %}
5
+ # Monkeys!
6
+ # {% endcapture %}
7
+ # ...
8
+ # <h1>{{ heading }}</h1>
9
+ #
10
+ # Capture is useful for saving content for use later in your template, such as
11
+ # in a sidebar or footer.
12
+ #
13
+ class Capture < Block
14
+ Syntax = /(#{VariableSignature}+)/o
15
+
16
+ def initialize(tag_name, markup, options)
17
+ super
18
+ if markup =~ Syntax
19
+ @to = $1
20
+ else
21
+ raise SyntaxError.new(options[:locale].t("errors.syntax.capture"))
22
+ end
23
+ end
24
+
25
+ def render(context)
26
+ output = super
27
+ context.scopes.last[@to] = output
28
+ context.resource_limits.assign_score += output.length
29
+ ''.freeze
30
+ end
31
+
32
+ def blank?
33
+ true
34
+ end
35
+ end
36
+
37
+ Template.register_tag('capture'.freeze, Capture)
38
+ end
@@ -0,0 +1,94 @@
1
+ module Liquid
2
+ class Case < Block
3
+ Syntax = /(#{QuotedFragment})/o
4
+ WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/om
5
+
6
+ attr_reader :blocks, :left
7
+
8
+ def initialize(tag_name, markup, options)
9
+ super
10
+ @blocks = []
11
+
12
+ if markup =~ Syntax
13
+ @left = Expression.parse($1)
14
+ else
15
+ raise SyntaxError.new(options[:locale].t("errors.syntax.case".freeze))
16
+ end
17
+ end
18
+
19
+ def parse(tokens)
20
+ body = BlockBody.new
21
+ while parse_body(body, tokens)
22
+ body = @blocks.last.attachment
23
+ end
24
+ end
25
+
26
+ def nodelist
27
+ @blocks.map(&:attachment)
28
+ end
29
+
30
+ def unknown_tag(tag, markup, tokens)
31
+ case tag
32
+ when 'when'.freeze
33
+ record_when_condition(markup)
34
+ when 'else'.freeze
35
+ record_else_condition(markup)
36
+ else
37
+ super
38
+ end
39
+ end
40
+
41
+ def render(context)
42
+ context.stack do
43
+ execute_else_block = true
44
+
45
+ output = ''
46
+ @blocks.each do |block|
47
+ if block.else?
48
+ return block.attachment.render(context) if execute_else_block
49
+ elsif block.evaluate(context)
50
+ execute_else_block = false
51
+ output << block.attachment.render(context)
52
+ end
53
+ end
54
+ output
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def record_when_condition(markup)
61
+ body = BlockBody.new
62
+
63
+ while markup
64
+ unless markup =~ WhenSyntax
65
+ raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_when".freeze))
66
+ end
67
+
68
+ markup = $2
69
+
70
+ block = Condition.new(@left, '=='.freeze, Expression.parse($1))
71
+ block.attach(body)
72
+ @blocks << block
73
+ end
74
+ end
75
+
76
+ def record_else_condition(markup)
77
+ unless markup.strip.empty?
78
+ raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_else".freeze))
79
+ end
80
+
81
+ block = ElseCondition.new
82
+ block.attach(BlockBody.new)
83
+ @blocks << block
84
+ end
85
+
86
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
87
+ def children
88
+ [@node.left] + @node.blocks
89
+ end
90
+ end
91
+ end
92
+
93
+ Template.register_tag('case'.freeze, Case)
94
+ end
@@ -0,0 +1,16 @@
1
+ module Liquid
2
+ class Comment < Block
3
+ def render(_context)
4
+ ''.freeze
5
+ end
6
+
7
+ def unknown_tag(_tag, _markup, _tokens)
8
+ end
9
+
10
+ def blank?
11
+ true
12
+ end
13
+ end
14
+
15
+ Template.register_tag('comment'.freeze, Comment)
16
+ end
@@ -0,0 +1,18 @@
1
+ module Liquid
2
+ # Continue tag to be used to break out of a for loop.
3
+ #
4
+ # == Basic Usage:
5
+ # {% for item in collection %}
6
+ # {% if item.condition %}
7
+ # {% continue %}
8
+ # {% endif %}
9
+ # {% endfor %}
10
+ #
11
+ class Continue < Tag
12
+ def interrupt
13
+ ContinueInterrupt.new
14
+ end
15
+ end
16
+
17
+ Template.register_tag('continue'.freeze, Continue)
18
+ end
@@ -0,0 +1,65 @@
1
+ module Liquid
2
+ # Cycle is usually used within a loop to alternate between values, like colors or DOM classes.
3
+ #
4
+ # {% for item in items %}
5
+ # <div class="{% cycle 'red', 'green', 'blue' %}"> {{ item }} </div>
6
+ # {% end %}
7
+ #
8
+ # <div class="red"> Item one </div>
9
+ # <div class="green"> Item two </div>
10
+ # <div class="blue"> Item three </div>
11
+ # <div class="red"> Item four </div>
12
+ # <div class="green"> Item five</div>
13
+ #
14
+ class Cycle < Tag
15
+ SimpleSyntax = /\A#{QuotedFragment}+/o
16
+ NamedSyntax = /\A(#{QuotedFragment})\s*\:\s*(.*)/om
17
+
18
+ attr_reader :variables
19
+
20
+ def initialize(tag_name, markup, options)
21
+ super
22
+ case markup
23
+ when NamedSyntax
24
+ @variables = variables_from_string($2)
25
+ @name = Expression.parse($1)
26
+ when SimpleSyntax
27
+ @variables = variables_from_string(markup)
28
+ @name = @variables.to_s
29
+ else
30
+ raise SyntaxError.new(options[:locale].t("errors.syntax.cycle".freeze))
31
+ end
32
+ end
33
+
34
+ def render(context)
35
+ context.registers[:cycle] ||= {}
36
+
37
+ context.stack do
38
+ key = context.evaluate(@name)
39
+ iteration = context.registers[:cycle][key].to_i
40
+ result = context.evaluate(@variables[iteration])
41
+ iteration += 1
42
+ iteration = 0 if iteration >= @variables.size
43
+ context.registers[:cycle][key] = iteration
44
+ result
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def variables_from_string(markup)
51
+ markup.split(',').collect do |var|
52
+ var =~ /\s*(#{QuotedFragment})\s*/o
53
+ $1 ? Expression.parse($1) : nil
54
+ end.compact
55
+ end
56
+
57
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
58
+ def children
59
+ Array(@node.variables)
60
+ end
61
+ end
62
+ end
63
+
64
+ Template.register_tag('cycle', Cycle)
65
+ end