liquid 2.6.3 → 3.0.0.rc1

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.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +42 -13
  3. data/README.md +27 -2
  4. data/lib/liquid.rb +11 -11
  5. data/lib/liquid/block.rb +75 -45
  6. data/lib/liquid/condition.rb +15 -11
  7. data/lib/liquid/context.rb +68 -29
  8. data/lib/liquid/document.rb +3 -3
  9. data/lib/liquid/drop.rb +17 -1
  10. data/lib/liquid/file_system.rb +17 -6
  11. data/lib/liquid/i18n.rb +39 -0
  12. data/lib/liquid/interrupts.rb +1 -1
  13. data/lib/liquid/lexer.rb +51 -0
  14. data/lib/liquid/locales/en.yml +22 -0
  15. data/lib/liquid/parser.rb +90 -0
  16. data/lib/liquid/standardfilters.rb +115 -52
  17. data/lib/liquid/strainer.rb +14 -4
  18. data/lib/liquid/tag.rb +42 -7
  19. data/lib/liquid/tags/assign.rb +10 -8
  20. data/lib/liquid/tags/break.rb +1 -1
  21. data/lib/liquid/tags/capture.rb +10 -8
  22. data/lib/liquid/tags/case.rb +13 -13
  23. data/lib/liquid/tags/comment.rb +9 -2
  24. data/lib/liquid/tags/continue.rb +1 -4
  25. data/lib/liquid/tags/cycle.rb +5 -7
  26. data/lib/liquid/tags/decrement.rb +3 -4
  27. data/lib/liquid/tags/for.rb +69 -36
  28. data/lib/liquid/tags/if.rb +52 -25
  29. data/lib/liquid/tags/ifchanged.rb +2 -2
  30. data/lib/liquid/tags/include.rb +8 -7
  31. data/lib/liquid/tags/increment.rb +4 -8
  32. data/lib/liquid/tags/raw.rb +3 -3
  33. data/lib/liquid/tags/table_row.rb +73 -0
  34. data/lib/liquid/tags/unless.rb +2 -4
  35. data/lib/liquid/template.rb +69 -10
  36. data/lib/liquid/utils.rb +13 -4
  37. data/lib/liquid/variable.rb +59 -8
  38. data/lib/liquid/version.rb +1 -1
  39. data/test/fixtures/en_locale.yml +9 -0
  40. data/test/{liquid → integration}/assign_test.rb +6 -0
  41. data/test/integration/blank_test.rb +106 -0
  42. data/test/{liquid → integration}/capture_test.rb +2 -2
  43. data/test/integration/context_test.rb +33 -0
  44. data/test/integration/drop_test.rb +245 -0
  45. data/test/{liquid → integration}/error_handling_test.rb +31 -2
  46. data/test/{liquid → integration}/filter_test.rb +7 -7
  47. data/test/{liquid → integration}/hash_ordering_test.rb +0 -0
  48. data/test/{liquid → integration}/output_test.rb +12 -12
  49. data/test/integration/parsing_quirks_test.rb +94 -0
  50. data/test/{liquid → integration}/security_test.rb +9 -9
  51. data/test/{liquid → integration}/standard_filter_test.rb +103 -33
  52. data/test/{liquid → integration}/tags/break_tag_test.rb +0 -0
  53. data/test/{liquid → integration}/tags/continue_tag_test.rb +0 -0
  54. data/test/{liquid → integration}/tags/for_tag_test.rb +78 -0
  55. data/test/{liquid → integration}/tags/if_else_tag_test.rb +1 -1
  56. data/test/integration/tags/include_tag_test.rb +212 -0
  57. data/test/{liquid → integration}/tags/increment_tag_test.rb +0 -0
  58. data/test/{liquid → integration}/tags/raw_tag_test.rb +1 -0
  59. data/test/{liquid → integration}/tags/standard_tag_test.rb +24 -22
  60. data/test/integration/tags/statements_test.rb +113 -0
  61. data/test/{liquid/tags/html_tag_test.rb → integration/tags/table_row_test.rb} +5 -5
  62. data/test/{liquid → integration}/tags/unless_else_tag_test.rb +0 -0
  63. data/test/{liquid → integration}/template_test.rb +66 -42
  64. data/test/integration/variable_test.rb +72 -0
  65. data/test/test_helper.rb +32 -7
  66. data/test/{liquid/block_test.rb → unit/block_unit_test.rb} +1 -1
  67. data/test/{liquid/condition_test.rb → unit/condition_unit_test.rb} +19 -1
  68. data/test/{liquid/context_test.rb → unit/context_unit_test.rb} +27 -19
  69. data/test/{liquid/file_system_test.rb → unit/file_system_unit_test.rb} +7 -1
  70. data/test/unit/i18n_unit_test.rb +37 -0
  71. data/test/unit/lexer_unit_test.rb +48 -0
  72. data/test/{liquid/module_ex_test.rb → unit/module_ex_unit_test.rb} +7 -7
  73. data/test/unit/parser_unit_test.rb +82 -0
  74. data/test/{liquid/regexp_test.rb → unit/regexp_unit_test.rb} +3 -3
  75. data/test/{liquid/strainer_test.rb → unit/strainer_unit_test.rb} +19 -1
  76. data/test/unit/tag_unit_test.rb +11 -0
  77. data/test/unit/tags/case_tag_unit_test.rb +10 -0
  78. data/test/unit/tags/for_tag_unit_test.rb +13 -0
  79. data/test/unit/tags/if_tag_unit_test.rb +8 -0
  80. data/test/unit/template_unit_test.rb +69 -0
  81. data/test/unit/tokenizer_unit_test.rb +29 -0
  82. data/test/{liquid/variable_test.rb → unit/variable_unit_test.rb} +17 -67
  83. metadata +117 -73
  84. data/lib/extras/liquid_view.rb +0 -51
  85. data/lib/liquid/htmltags.rb +0 -73
  86. data/test/liquid/drop_test.rb +0 -180
  87. data/test/liquid/parsing_quirks_test.rb +0 -52
  88. data/test/liquid/tags/include_tag_test.rb +0 -166
  89. data/test/liquid/tags/statements_test.rb +0 -134
@@ -1,5 +1,4 @@
1
1
  module Liquid
2
-
3
2
  # If is the conditional block
4
3
  #
5
4
  # {% if user.admin %}
@@ -10,23 +9,23 @@ module Liquid
10
9
  #
11
10
  # There are {% if count < 5 %} less {% else %} more {% endif %} items than you need.
12
11
  #
13
- #
14
12
  class If < Block
15
- SyntaxHelp = "Syntax Error in tag 'if' - Valid syntax: if [expression]"
16
13
  Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/o
17
14
  ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/o
18
15
  BOOLEAN_OPERATORS = %w(and or)
19
16
 
20
- def initialize(tag_name, markup, tokens)
17
+ def initialize(tag_name, markup, options)
18
+ super
21
19
  @blocks = []
20
+ push_block('if'.freeze, markup)
21
+ end
22
22
 
23
- push_block('if', markup)
24
-
25
- super
23
+ def nodelist
24
+ @blocks.map(&:attachment).flatten
26
25
  end
27
26
 
28
27
  def unknown_tag(tag, markup, tokens)
29
- if ['elsif', 'else'].include?(tag)
28
+ if ['elsif'.freeze, 'else'.freeze].include?(tag)
30
29
  push_block(tag, markup)
31
30
  else
32
31
  super
@@ -40,40 +39,68 @@ module Liquid
40
39
  return render_all(block.attachment, context)
41
40
  end
42
41
  end
43
- ''
42
+ ''.freeze
44
43
  end
45
44
  end
46
45
 
47
46
  private
48
47
 
49
48
  def push_block(tag, markup)
50
- block = if tag == 'else'
49
+ block = if tag == 'else'.freeze
51
50
  ElseCondition.new
52
51
  else
52
+ parse_with_selected_parser(markup)
53
+ end
53
54
 
54
- expressions = markup.scan(ExpressionsAndOperators).reverse
55
- raise(SyntaxError, SyntaxHelp) unless expressions.shift =~ Syntax
55
+ @blocks.push(block)
56
+ @nodelist = block.attach(Array.new)
57
+ end
56
58
 
57
- condition = Condition.new($1, $2, $3)
59
+ def lax_parse(markup)
60
+ expressions = markup.scan(ExpressionsAndOperators).reverse
61
+ raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless expressions.shift =~ Syntax
58
62
 
59
- while not expressions.empty?
60
- operator = (expressions.shift).to_s.strip
63
+ condition = Condition.new($1, $2, $3)
61
64
 
62
- raise(SyntaxError, SyntaxHelp) unless expressions.shift.to_s =~ Syntax
65
+ while not expressions.empty?
66
+ operator = (expressions.shift).to_s.strip
63
67
 
64
- new_condition = Condition.new($1, $2, $3)
65
- raise SyntaxError, "invalid boolean operator" unless BOOLEAN_OPERATORS.include?(operator)
66
- new_condition.send(operator, condition)
67
- condition = new_condition
68
- end
68
+ raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless expressions.shift.to_s =~ Syntax
69
69
 
70
- condition
70
+ new_condition = Condition.new($1, $2, $3)
71
+ raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless BOOLEAN_OPERATORS.include?(operator)
72
+ new_condition.send(operator, condition)
73
+ condition = new_condition
71
74
  end
72
75
 
73
- @blocks.push(block)
74
- @nodelist = block.attach(Array.new)
76
+ condition
77
+ end
78
+
79
+ def strict_parse(markup)
80
+ p = Parser.new(markup)
81
+
82
+ condition = parse_comparison(p)
83
+
84
+ while op = (p.id?('and'.freeze) || p.id?('or'.freeze))
85
+ new_cond = parse_comparison(p)
86
+ new_cond.send(op, condition)
87
+ condition = new_cond
88
+ end
89
+ p.consume(:end_of_string)
90
+
91
+ condition
92
+ end
93
+
94
+ def parse_comparison(p)
95
+ a = p.expression
96
+ if op = p.consume?(:comparison)
97
+ b = p.expression
98
+ Condition.new(a, op, b)
99
+ else
100
+ Condition.new(a)
101
+ end
75
102
  end
76
103
  end
77
104
 
78
- Template.register_tag('if', If)
105
+ Template.register_tag('if'.freeze, If)
79
106
  end
@@ -10,11 +10,11 @@ module Liquid
10
10
  context.registers[:ifchanged] = output
11
11
  output
12
12
  else
13
- ''
13
+ ''.freeze
14
14
  end
15
15
  end
16
16
  end
17
17
  end
18
18
 
19
- Template.register_tag('ifchanged', Ifchanged)
19
+ Template.register_tag('ifchanged'.freeze, Ifchanged)
20
20
  end
@@ -17,7 +17,9 @@ module Liquid
17
17
  class Include < Tag
18
18
  Syntax = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?/o
19
19
 
20
- def initialize(tag_name, markup, tokens)
20
+ def initialize(tag_name, markup, options)
21
+ super
22
+
21
23
  if markup =~ Syntax
22
24
 
23
25
  @template_name = $1
@@ -29,10 +31,8 @@ module Liquid
29
31
  end
30
32
 
31
33
  else
32
- raise SyntaxError.new("Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]")
34
+ raise SyntaxError.new(options[:locale].t("errors.syntax.include".freeze))
33
35
  end
34
-
35
- super
36
36
  end
37
37
 
38
38
  def parse(tokens)
@@ -47,13 +47,14 @@ module Liquid
47
47
  context[key] = context[value]
48
48
  end
49
49
 
50
+ context_variable_name = @template_name[1..-2].split('/'.freeze).last
50
51
  if variable.is_a?(Array)
51
52
  variable.collect do |var|
52
- context[@template_name[1..-2]] = var
53
+ context[context_variable_name] = var
53
54
  partial.render(context)
54
55
  end
55
56
  else
56
- context[@template_name[1..-2]] = variable
57
+ context[context_variable_name] = variable
57
58
  partial.render(context)
58
59
  end
59
60
  end
@@ -89,5 +90,5 @@ module Liquid
89
90
  end
90
91
  end
91
92
 
92
- Template.register_tag('include', Include)
93
+ Template.register_tag('include'.freeze, Include)
93
94
  end
@@ -1,12 +1,11 @@
1
1
  module Liquid
2
-
3
2
  # increment is used in a place where one needs to insert a counter
4
3
  # into a template, and needs the counter to survive across
5
4
  # multiple instantiations of the template.
6
5
  # (To achieve the survival, the application must keep the context)
7
6
  #
8
7
  # if the variable does not exist, it is created with value 0.
9
-
8
+ #
10
9
  # Hello: {% increment variable %}
11
10
  #
12
11
  # gives you:
@@ -16,10 +15,9 @@ module Liquid
16
15
  # Hello: 2
17
16
  #
18
17
  class Increment < Tag
19
- def initialize(tag_name, markup, tokens)
20
- @variable = markup.strip
21
-
18
+ def initialize(tag_name, markup, options)
22
19
  super
20
+ @variable = markup.strip
23
21
  end
24
22
 
25
23
  def render(context)
@@ -27,9 +25,7 @@ module Liquid
27
25
  context.environments.first[@variable] = value + 1
28
26
  value.to_s
29
27
  end
30
-
31
- private
32
28
  end
33
29
 
34
- Template.register_tag('increment', Increment)
30
+ Template.register_tag('increment'.freeze, Increment)
35
31
  end
@@ -1,13 +1,13 @@
1
1
  module Liquid
2
2
  class Raw < Block
3
- FullTokenPossiblyInvalid = /^(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}$/o
3
+ FullTokenPossiblyInvalid = /\A(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
4
4
 
5
5
  def parse(tokens)
6
6
  @nodelist ||= []
7
7
  @nodelist.clear
8
8
  while token = tokens.shift
9
9
  if token =~ FullTokenPossiblyInvalid
10
- @nodelist << $1 if $1 != ""
10
+ @nodelist << $1 if $1 != "".freeze
11
11
  if block_delimiter == $2
12
12
  end_tag
13
13
  return
@@ -18,5 +18,5 @@ module Liquid
18
18
  end
19
19
  end
20
20
 
21
- Template.register_tag('raw', Raw)
21
+ Template.register_tag('raw'.freeze, Raw)
22
22
  end
@@ -0,0 +1,73 @@
1
+ module Liquid
2
+ class TableRow < Block
3
+ Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o
4
+
5
+ def initialize(tag_name, markup, options)
6
+ super
7
+ if markup =~ Syntax
8
+ @variable_name = $1
9
+ @collection_name = $2
10
+ @attributes = {}
11
+ markup.scan(TagAttributes) do |key, value|
12
+ @attributes[key] = value
13
+ end
14
+ else
15
+ raise SyntaxError.new(options[:locale].t("errors.syntax.table_row".freeze))
16
+ end
17
+ end
18
+
19
+ def render(context)
20
+ collection = context[@collection_name] or return ''.freeze
21
+
22
+ from = @attributes['offset'.freeze] ? context[@attributes['offset'.freeze]].to_i : 0
23
+ to = @attributes['limit'.freeze] ? from + context[@attributes['limit'.freeze]].to_i : nil
24
+
25
+ collection = Utils.slice_collection(collection, from, to)
26
+
27
+ length = collection.length
28
+
29
+ cols = context[@attributes['cols'.freeze]].to_i
30
+
31
+ row = 1
32
+ col = 0
33
+
34
+ result = "<tr class=\"row1\">\n"
35
+ context.stack do
36
+
37
+ collection.each_with_index do |item, index|
38
+ context[@variable_name] = item
39
+ context['tablerowloop'.freeze] = {
40
+ 'length'.freeze => length,
41
+ 'index'.freeze => index + 1,
42
+ 'index0'.freeze => index,
43
+ 'col'.freeze => col + 1,
44
+ 'col0'.freeze => col,
45
+ 'index0'.freeze => index,
46
+ 'rindex'.freeze => length - index,
47
+ 'rindex0'.freeze => length - index - 1,
48
+ 'first'.freeze => (index == 0),
49
+ 'last'.freeze => (index == length - 1),
50
+ 'col_first'.freeze => (col == 0),
51
+ 'col_last'.freeze => (col == cols - 1)
52
+ }
53
+
54
+
55
+ col += 1
56
+
57
+ result << "<td class=\"col#{col}\">" << render_all(@nodelist, context) << '</td>'
58
+
59
+ if col == cols and (index != length - 1)
60
+ col = 0
61
+ row += 1
62
+ result << "</tr>\n<tr class=\"row#{row}\">"
63
+ end
64
+
65
+ end
66
+ end
67
+ result << "</tr>\n"
68
+ result
69
+ end
70
+ end
71
+
72
+ Template.register_tag('tablerow'.freeze, TableRow)
73
+ end
@@ -1,7 +1,6 @@
1
1
  require File.dirname(__FILE__) + '/if'
2
2
 
3
3
  module Liquid
4
-
5
4
  # Unless is a conditional just like 'if' but works on the inverse logic.
6
5
  #
7
6
  # {% unless x < 0 %} x is greater than zero {% end %}
@@ -23,11 +22,10 @@ module Liquid
23
22
  end
24
23
  end
25
24
 
26
- ''
25
+ ''.freeze
27
26
  end
28
27
  end
29
28
  end
30
29
 
31
-
32
- Template.register_tag('unless', Unless)
30
+ Template.register_tag('unless'.freeze, Unless)
33
31
  end
@@ -14,10 +14,50 @@ module Liquid
14
14
  # template.render('user_name' => 'bob')
15
15
  #
16
16
  class Template
17
+ DEFAULT_OPTIONS = {
18
+ :locale => I18n.new
19
+ }
20
+
17
21
  attr_accessor :root, :resource_limits
18
22
  @@file_system = BlankFileSystem.new
19
23
 
24
+ class TagRegistry
25
+ def initialize
26
+ @tags = {}
27
+ @cache = {}
28
+ end
29
+
30
+ def [](tag_name)
31
+ return nil unless @tags.has_key?(tag_name)
32
+ return @cache[tag_name] if Liquid.cache_classes
33
+
34
+ lookup_class(@tags[tag_name]).tap { |o| @cache[tag_name] = o }
35
+ end
36
+
37
+ def []=(tag_name, klass)
38
+ @tags[tag_name] = klass.name
39
+ @cache[tag_name] = klass
40
+ end
41
+
42
+ def delete(tag_name)
43
+ @tags.delete(tag_name)
44
+ @cache.delete(tag_name)
45
+ end
46
+
47
+ private
48
+
49
+ def lookup_class(name)
50
+ name.split("::").reject(&:empty?).reduce(Object) { |scope, const| scope.const_get(const) }
51
+ end
52
+ end
53
+
20
54
  class << self
55
+ # Sets how strict the parser should be.
56
+ # :lax acts like liquid 2.5 and silently ignores malformed tags in most cases.
57
+ # :warn is the default and will give deprecation warnings when invalid syntax is used.
58
+ # :strict will enforce correct syntax.
59
+ attr_writer :error_mode
60
+
21
61
  def file_system
22
62
  @@file_system
23
63
  end
@@ -31,7 +71,11 @@ module Liquid
31
71
  end
32
72
 
33
73
  def tags
34
- @tags ||= {}
74
+ @tags ||= TagRegistry.new
75
+ end
76
+
77
+ def error_mode
78
+ @error_mode || :lax
35
79
  end
36
80
 
37
81
  # Pass a module with filter methods which should be available
@@ -41,10 +85,9 @@ module Liquid
41
85
  end
42
86
 
43
87
  # creates a new <tt>Template</tt> object from liquid source code
44
- def parse(source)
88
+ def parse(source, options = {})
45
89
  template = Template.new
46
- template.parse(source)
47
- template
90
+ template.parse(source, options)
48
91
  end
49
92
  end
50
93
 
@@ -55,11 +98,17 @@ module Liquid
55
98
 
56
99
  # Parse source code.
57
100
  # Returns self for easy chaining
58
- def parse(source)
59
- @root = Document.new(tokenize(source))
101
+ def parse(source, options = {})
102
+ @root = Document.parse(tokenize(source), DEFAULT_OPTIONS.merge(options))
103
+ @warnings = nil
60
104
  self
61
105
  end
62
106
 
107
+ def warnings
108
+ return [] unless @root
109
+ @warnings ||= @root.warnings
110
+ end
111
+
63
112
  def registers
64
113
  @registers ||= {}
65
114
  end
@@ -88,11 +137,17 @@ module Liquid
88
137
  # filters and tags and might be useful to integrate liquid more with its host application
89
138
  #
90
139
  def render(*args)
91
- return '' if @root.nil?
140
+ return ''.freeze if @root.nil?
92
141
 
93
142
  context = case args.first
94
143
  when Liquid::Context
95
- args.shift
144
+ c = args.shift
145
+
146
+ if @rethrow_errors
147
+ c.exception_handler = ->(e) { true }
148
+ end
149
+
150
+ c
96
151
  when Liquid::Drop
97
152
  drop = args.shift
98
153
  drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
@@ -101,7 +156,7 @@ module Liquid
101
156
  when nil
102
157
  Context.new(assigns, instance_assigns, registers, @rethrow_errors, @resource_limits)
103
158
  else
104
- raise ArgumentError, "Expect Hash or Liquid::Context as parameter"
159
+ raise ArgumentError, "Expected Hash or Liquid::Context as parameter"
105
160
  end
106
161
 
107
162
  case args.last
@@ -116,6 +171,9 @@ module Liquid
116
171
  context.add_filters(options[:filters])
117
172
  end
118
173
 
174
+ if options[:exception_handler]
175
+ context.exception_handler = options[:exception_handler]
176
+ end
119
177
  when Module
120
178
  context.add_filters(args.pop)
121
179
  when Array
@@ -135,7 +193,8 @@ module Liquid
135
193
  end
136
194
 
137
195
  def render!(*args)
138
- @rethrow_errors = true; render(*args)
196
+ @rethrow_errors = true
197
+ render(*args)
139
198
  end
140
199
 
141
200
  private