liquid 5.6.0 → 5.8.7

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.
@@ -1,20 +1,43 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "strscan"
4
+
3
5
  module Liquid
4
6
  class Tokenizer
5
7
  attr_reader :line_number, :for_liquid_tag
6
8
 
7
- def initialize(source, line_numbers = false, line_number: nil, for_liquid_tag: false)
8
- @source = source.to_s.to_str
9
- @line_number = line_number || (line_numbers ? 1 : nil)
9
+ TAG_END = /%\}/
10
+ TAG_OR_VARIABLE_START = /\{[\{\%]/
11
+ NEWLINE = /\n/
12
+
13
+ OPEN_CURLEY = "{".ord
14
+ CLOSE_CURLEY = "}".ord
15
+ PERCENTAGE = "%".ord
16
+
17
+ def initialize(
18
+ source:,
19
+ string_scanner:,
20
+ line_numbers: false,
21
+ line_number: nil,
22
+ for_liquid_tag: false
23
+ )
24
+ @line_number = line_number || (line_numbers ? 1 : nil)
10
25
  @for_liquid_tag = for_liquid_tag
11
- @offset = 0
12
- @tokens = tokenize
26
+ @source = source.to_s.to_str
27
+ @offset = 0
28
+ @tokens = []
29
+
30
+ if @source
31
+ @ss = string_scanner
32
+ @ss.string = @source
33
+ tokenize
34
+ end
13
35
  end
14
36
 
15
37
  def shift
16
38
  token = @tokens[@offset]
17
- return nil unless token
39
+
40
+ return unless token
18
41
 
19
42
  @offset += 1
20
43
 
@@ -28,18 +51,111 @@ module Liquid
28
51
  private
29
52
 
30
53
  def tokenize
31
- return [] if @source.empty?
54
+ if @for_liquid_tag
55
+ @tokens = @source.split("\n")
56
+ else
57
+ @tokens << shift_normal until @ss.eos?
58
+ end
59
+
60
+ @source = nil
61
+ @ss = nil
62
+ end
63
+
64
+ def shift_normal
65
+ token = next_token
66
+
67
+ return unless token
68
+
69
+ token
70
+ end
71
+
72
+ def next_token
73
+ # possible states: :text, :tag, :variable
74
+ byte_a = @ss.peek_byte
75
+
76
+ if byte_a == OPEN_CURLEY
77
+ @ss.scan_byte
78
+
79
+ byte_b = @ss.peek_byte
80
+
81
+ if byte_b == PERCENTAGE
82
+ @ss.scan_byte
83
+ return next_tag_token
84
+ elsif byte_b == OPEN_CURLEY
85
+ @ss.scan_byte
86
+ return next_variable_token
87
+ end
88
+
89
+ @ss.pos -= 1
90
+ end
32
91
 
33
- return @source.split("\n") if @for_liquid_tag
92
+ next_text_token
93
+ end
34
94
 
35
- tokens = @source.split(TemplateParser)
95
+ def next_text_token
96
+ start = @ss.pos
36
97
 
37
- # removes the rogue empty element at the beginning of the array
38
- if tokens[0]&.empty?
39
- @offset += 1
98
+ unless @ss.skip_until(TAG_OR_VARIABLE_START)
99
+ token = @ss.rest
100
+ @ss.terminate
101
+ return token
40
102
  end
41
103
 
42
- tokens
104
+ pos = @ss.pos -= 2
105
+ @source.byteslice(start, pos - start)
106
+ rescue ::ArgumentError => e
107
+ if e.message == "invalid byte sequence in #{@ss.string.encoding}"
108
+ raise SyntaxError, "Invalid byte sequence in #{@ss.string.encoding}"
109
+ else
110
+ raise
111
+ end
112
+ end
113
+
114
+ def next_variable_token
115
+ start = @ss.pos - 2
116
+
117
+ byte_a = byte_b = @ss.scan_byte
118
+
119
+ while byte_b
120
+ byte_a = @ss.scan_byte while byte_a && (byte_a != CLOSE_CURLEY && byte_a != OPEN_CURLEY)
121
+
122
+ break unless byte_a
123
+
124
+ if @ss.eos?
125
+ return byte_a == CLOSE_CURLEY ? @source.byteslice(start, @ss.pos - start) : "{{"
126
+ end
127
+
128
+ byte_b = @ss.scan_byte
129
+
130
+ if byte_a == CLOSE_CURLEY
131
+ if byte_b == CLOSE_CURLEY
132
+ return @source.byteslice(start, @ss.pos - start)
133
+ elsif byte_b != CLOSE_CURLEY
134
+ @ss.pos -= 1
135
+ return @source.byteslice(start, @ss.pos - start)
136
+ end
137
+ elsif byte_a == OPEN_CURLEY && byte_b == PERCENTAGE
138
+ return next_tag_token_with_start(start)
139
+ end
140
+
141
+ byte_a = byte_b
142
+ end
143
+
144
+ "{{"
145
+ end
146
+
147
+ def next_tag_token
148
+ start = @ss.pos - 2
149
+ if (len = @ss.skip_until(TAG_END))
150
+ @source.byteslice(start, len + 2)
151
+ else
152
+ "{%"
153
+ end
154
+ end
155
+
156
+ def next_tag_token_with_start(start)
157
+ @ss.skip_until(TAG_END)
158
+ @source.byteslice(start, @ss.pos - start)
43
159
  end
44
160
  end
45
161
  end
data/lib/liquid/utils.rb CHANGED
@@ -89,5 +89,101 @@ module Liquid
89
89
  # Otherwise return the object itself
90
90
  obj
91
91
  end
92
+
93
+ def self.to_s(obj, seen = {})
94
+ case obj
95
+ when Hash
96
+ # If the custom hash implementation overrides `#to_s`, use their
97
+ # custom implementation. Otherwise we use Liquid's default
98
+ # implementation.
99
+ if obj.class.instance_method(:to_s) == HASH_TO_S_METHOD
100
+ hash_inspect(obj, seen)
101
+ else
102
+ obj.to_s
103
+ end
104
+ when Array
105
+ array_inspect(obj, seen)
106
+ else
107
+ obj.to_s
108
+ end
109
+ end
110
+
111
+ def self.inspect(obj, seen = {})
112
+ case obj
113
+ when Hash
114
+ # If the custom hash implementation overrides `#inspect`, use their
115
+ # custom implementation. Otherwise we use Liquid's default
116
+ # implementation.
117
+ if obj.class.instance_method(:inspect) == HASH_INSPECT_METHOD
118
+ hash_inspect(obj, seen)
119
+ else
120
+ obj.inspect
121
+ end
122
+ when Array
123
+ array_inspect(obj, seen)
124
+ else
125
+ obj.inspect
126
+ end
127
+ end
128
+
129
+ def self.array_inspect(arr, seen = {})
130
+ if seen[arr.object_id]
131
+ return "[...]"
132
+ end
133
+
134
+ seen[arr.object_id] = true
135
+ str = +"["
136
+ cursor = 0
137
+ len = arr.length
138
+
139
+ while cursor < len
140
+ if cursor > 0
141
+ str << ", "
142
+ end
143
+
144
+ item_str = inspect(arr[cursor], seen)
145
+ str << item_str
146
+ cursor += 1
147
+ end
148
+
149
+ str << "]"
150
+ str
151
+ ensure
152
+ seen.delete(arr.object_id)
153
+ end
154
+
155
+ def self.hash_inspect(hash, seen = {})
156
+ if seen[hash.object_id]
157
+ return "{...}"
158
+ end
159
+ seen[hash.object_id] = true
160
+
161
+ str = +"{"
162
+ first = true
163
+ hash.each do |key, value|
164
+ if first
165
+ first = false
166
+ else
167
+ str << ", "
168
+ end
169
+
170
+ key_str = inspect(key, seen)
171
+ str << key_str
172
+ str << "=>"
173
+
174
+ value_str = inspect(value, seen)
175
+ str << value_str
176
+ end
177
+ str << "}"
178
+ str
179
+ ensure
180
+ seen.delete(hash.object_id)
181
+ end
182
+
183
+ HASH_TO_S_METHOD = Hash.instance_method(:to_s)
184
+ private_constant :HASH_TO_S_METHOD
185
+
186
+ HASH_INSPECT_METHOD = Hash.instance_method(:inspect)
187
+ private_constant :HASH_INSPECT_METHOD
92
188
  end
93
189
  end
@@ -61,7 +61,7 @@ module Liquid
61
61
 
62
62
  def strict_parse(markup)
63
63
  @filters = []
64
- p = Parser.new(markup)
64
+ p = @parse_context.new_parser(markup)
65
65
 
66
66
  return if p.look(:end_of_string)
67
67
 
@@ -107,8 +107,8 @@ module Liquid
107
107
  obj.each do |o|
108
108
  render_obj_to_output(o, output)
109
109
  end
110
- when
111
- output << obj.to_s
110
+ else
111
+ output << Liquid::Utils.to_s(obj)
112
112
  end
113
113
  end
114
114
 
@@ -6,16 +6,20 @@ module Liquid
6
6
 
7
7
  attr_reader :name, :lookups
8
8
 
9
- def self.parse(markup)
10
- new(markup)
9
+ def self.parse(markup, string_scanner = StringScanner.new(""), cache = nil)
10
+ new(markup, string_scanner, cache)
11
11
  end
12
12
 
13
- def initialize(markup)
13
+ def initialize(markup, string_scanner = StringScanner.new(""), cache = nil)
14
14
  lookups = markup.scan(VariableParser)
15
15
 
16
16
  name = lookups.shift
17
17
  if name&.start_with?('[') && name&.end_with?(']')
18
- name = Expression.parse(name[1..-2])
18
+ name = Expression.parse(
19
+ name[1..-2],
20
+ string_scanner,
21
+ cache,
22
+ )
19
23
  end
20
24
  @name = name
21
25
 
@@ -25,7 +29,11 @@ module Liquid
25
29
  @lookups.each_index do |i|
26
30
  lookup = lookups[i]
27
31
  if lookup&.start_with?('[') && lookup&.end_with?(']')
28
- lookups[i] = Expression.parse(lookup[1..-2])
32
+ lookups[i] = Expression.parse(
33
+ lookup[1..-2],
34
+ string_scanner,
35
+ cache,
36
+ )
29
37
  elsif COMMAND_METHODS.include?(lookup)
30
38
  @command_flags |= 1 << i
31
39
  end
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Liquid
5
- VERSION = "5.6.0"
5
+ VERSION = "5.8.7"
6
6
  end
data/lib/liquid.rb CHANGED
@@ -21,6 +21,8 @@
21
21
  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
22
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
23
 
24
+ require "strscan"
25
+
24
26
  module Liquid
25
27
  FilterSeparator = /\|/
26
28
  ArgumentSeparator = ','
@@ -44,6 +46,7 @@ module Liquid
44
46
  VariableParser = /\[(?>[^\[\]]+|\g<0>)*\]|#{VariableSegment}+\??/o
45
47
 
46
48
  RAISE_EXCEPTION_LAMBDA = ->(_e) { raise }
49
+ HAS_STRING_SCANNER_SCAN_BYTE = StringScanner.instance_methods.include?(:scan_byte)
47
50
  end
48
51
 
49
52
  require "liquid/version"
@@ -68,7 +71,6 @@ require 'liquid/extensions'
68
71
  require 'liquid/errors'
69
72
  require 'liquid/interrupts'
70
73
  require 'liquid/strainer_template'
71
- require 'liquid/expression'
72
74
  require 'liquid/context'
73
75
  require 'liquid/tag'
74
76
  require 'liquid/block_body'
@@ -77,6 +79,7 @@ require 'liquid/variable'
77
79
  require 'liquid/variable_lookup'
78
80
  require 'liquid/range_lookup'
79
81
  require 'liquid/resource_limits'
82
+ require 'liquid/expression'
80
83
  require 'liquid/template'
81
84
  require 'liquid/condition'
82
85
  require 'liquid/utils'
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: liquid
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.6.0
4
+ version: 5.8.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tobias Lütke
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2024-12-19 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: strscan
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: '0'
18
+ version: 3.1.1
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
- version: '0'
25
+ version: 3.1.1
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: bigdecimal
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -120,6 +120,7 @@ files:
120
120
  - lib/liquid/tags/continue.rb
121
121
  - lib/liquid/tags/cycle.rb
122
122
  - lib/liquid/tags/decrement.rb
123
+ - lib/liquid/tags/doc.rb
123
124
  - lib/liquid/tags/echo.rb
124
125
  - lib/liquid/tags/for.rb
125
126
  - lib/liquid/tags/if.rb
@@ -158,7 +159,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
158
159
  - !ruby/object:Gem::Version
159
160
  version: 1.3.7
160
161
  requirements: []
161
- rubygems_version: 3.6.1
162
+ rubygems_version: 3.6.9
162
163
  specification_version: 4
163
164
  summary: A secure, non-evaling end user template engine with aesthetic markup.
164
165
  test_files: []