locomotivecms-liquid 2.6.0 → 4.0.0.alpha

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 +4 -4
  2. data/History.md +62 -5
  3. data/README.md +4 -4
  4. data/lib/liquid.rb +16 -12
  5. data/lib/liquid/block.rb +37 -118
  6. data/lib/liquid/block_body.rb +131 -0
  7. data/lib/liquid/condition.rb +28 -17
  8. data/lib/liquid/context.rb +94 -146
  9. data/lib/liquid/document.rb +16 -10
  10. data/lib/liquid/drop.rb +8 -5
  11. data/lib/liquid/drops/inherited_block_drop.rb +24 -0
  12. data/lib/liquid/errors.rb +44 -5
  13. data/lib/liquid/expression.rb +33 -0
  14. data/lib/liquid/file_system.rb +17 -6
  15. data/lib/liquid/i18n.rb +2 -2
  16. data/lib/liquid/interrupts.rb +1 -1
  17. data/lib/liquid/lexer.rb +11 -9
  18. data/lib/liquid/locales/en.yml +2 -4
  19. data/lib/liquid/parser.rb +2 -1
  20. data/lib/liquid/parser_switching.rb +31 -0
  21. data/lib/liquid/profiler.rb +162 -0
  22. data/lib/liquid/profiler/hooks.rb +23 -0
  23. data/lib/liquid/range_lookup.rb +22 -0
  24. data/lib/liquid/resource_limits.rb +23 -0
  25. data/lib/liquid/standardfilters.rb +142 -67
  26. data/lib/liquid/strainer.rb +14 -4
  27. data/lib/liquid/tag.rb +22 -41
  28. data/lib/liquid/tags/assign.rb +15 -10
  29. data/lib/liquid/tags/break.rb +1 -1
  30. data/lib/liquid/tags/capture.rb +7 -9
  31. data/lib/liquid/tags/case.rb +28 -19
  32. data/lib/liquid/tags/comment.rb +2 -2
  33. data/lib/liquid/tags/continue.rb +1 -4
  34. data/lib/liquid/tags/cycle.rb +10 -14
  35. data/lib/liquid/tags/decrement.rb +3 -4
  36. data/lib/liquid/tags/extends.rb +28 -44
  37. data/lib/liquid/tags/for.rb +64 -42
  38. data/lib/liquid/tags/if.rb +30 -19
  39. data/lib/liquid/tags/ifchanged.rb +4 -4
  40. data/lib/liquid/tags/include.rb +30 -20
  41. data/lib/liquid/tags/increment.rb +3 -8
  42. data/lib/liquid/tags/inherited_block.rb +54 -56
  43. data/lib/liquid/tags/raw.rb +18 -10
  44. data/lib/liquid/tags/table_row.rb +72 -0
  45. data/lib/liquid/tags/unless.rb +5 -7
  46. data/lib/liquid/template.rb +113 -53
  47. data/lib/liquid/token.rb +18 -0
  48. data/lib/liquid/utils.rb +13 -4
  49. data/lib/liquid/variable.rb +68 -50
  50. data/lib/liquid/variable_lookup.rb +78 -0
  51. data/lib/liquid/version.rb +1 -1
  52. data/test/fixtures/en_locale.yml +9 -0
  53. data/test/integration/assign_test.rb +48 -0
  54. data/test/integration/blank_test.rb +106 -0
  55. data/test/integration/capture_test.rb +50 -0
  56. data/test/integration/context_test.rb +32 -0
  57. data/test/integration/document_test.rb +19 -0
  58. data/test/integration/drop_test.rb +271 -0
  59. data/test/integration/error_handling_test.rb +207 -0
  60. data/test/integration/filter_test.rb +125 -0
  61. data/test/integration/hash_ordering_test.rb +23 -0
  62. data/test/integration/output_test.rb +116 -0
  63. data/test/integration/parsing_quirks_test.rb +119 -0
  64. data/test/integration/render_profiling_test.rb +154 -0
  65. data/test/integration/security_test.rb +64 -0
  66. data/test/integration/standard_filter_test.rb +379 -0
  67. data/test/integration/tags/break_tag_test.rb +16 -0
  68. data/test/integration/tags/continue_tag_test.rb +16 -0
  69. data/test/integration/tags/extends_tag_test.rb +104 -0
  70. data/test/integration/tags/for_tag_test.rb +375 -0
  71. data/test/integration/tags/if_else_tag_test.rb +169 -0
  72. data/test/integration/tags/include_tag_test.rb +234 -0
  73. data/test/integration/tags/increment_tag_test.rb +24 -0
  74. data/test/integration/tags/raw_tag_test.rb +25 -0
  75. data/test/integration/tags/standard_tag_test.rb +297 -0
  76. data/test/integration/tags/statements_test.rb +113 -0
  77. data/test/integration/tags/table_row_test.rb +63 -0
  78. data/test/integration/tags/unless_else_tag_test.rb +26 -0
  79. data/test/integration/template_test.rb +216 -0
  80. data/test/integration/variable_test.rb +82 -0
  81. data/test/test_helper.rb +83 -0
  82. data/test/unit/block_unit_test.rb +55 -0
  83. data/test/unit/condition_unit_test.rb +149 -0
  84. data/test/unit/context_unit_test.rb +482 -0
  85. data/test/unit/file_system_unit_test.rb +35 -0
  86. data/test/unit/i18n_unit_test.rb +37 -0
  87. data/test/unit/lexer_unit_test.rb +51 -0
  88. data/test/unit/module_ex_unit_test.rb +87 -0
  89. data/test/unit/parser_unit_test.rb +82 -0
  90. data/test/unit/regexp_unit_test.rb +44 -0
  91. data/test/unit/strainer_unit_test.rb +71 -0
  92. data/test/unit/tag_unit_test.rb +16 -0
  93. data/test/unit/tags/case_tag_unit_test.rb +10 -0
  94. data/test/unit/tags/for_tag_unit_test.rb +13 -0
  95. data/test/unit/tags/if_tag_unit_test.rb +8 -0
  96. data/test/unit/template_unit_test.rb +70 -0
  97. data/test/unit/tokenizer_unit_test.rb +38 -0
  98. data/test/unit/variable_unit_test.rb +150 -0
  99. metadata +144 -15
  100. data/lib/extras/liquid_view.rb +0 -51
  101. data/lib/liquid/htmltags.rb +0 -74
  102. data/lib/liquid/tags/default_content.rb +0 -21
  103. data/lib/locomotivecms-liquid.rb +0 -1
@@ -0,0 +1,24 @@
1
+ module Liquid
2
+
3
+ # Used to render the content of the parent block.
4
+ #
5
+ # {% extends home %}
6
+ # {% block content }{{ block.super }}{% endblock %}
7
+ #
8
+ class InheritedBlockDrop < Drop
9
+
10
+ def initialize(block)
11
+ @block = block
12
+ end
13
+
14
+ def name
15
+ @block.name
16
+ end
17
+
18
+ def super
19
+ @block.call_super(@context)
20
+ end
21
+
22
+ end
23
+
24
+ end
data/lib/liquid/errors.rb CHANGED
@@ -1,21 +1,60 @@
1
1
  module Liquid
2
2
  class Error < ::StandardError
3
+ attr_accessor :line_number
4
+ attr_accessor :markup_context
3
5
 
4
- attr_accessor :line
6
+ def to_s(with_prefix=true)
7
+ str = ""
8
+ str << message_prefix if with_prefix
9
+ str << super()
5
10
 
6
- def initialize(message = nil, line = nil)
7
- @line = line
8
- super(message)
11
+ if markup_context
12
+ str << " "
13
+ str << markup_context
14
+ end
15
+
16
+ str
17
+ end
18
+
19
+ def set_line_number_from_token(token)
20
+ return unless token.respond_to?(:line_number)
21
+ return if self.line_number
22
+ self.line_number = token.line_number
23
+ end
24
+
25
+ def self.render(e)
26
+ if e.is_a?(Liquid::Error)
27
+ e.to_s
28
+ else
29
+ "Liquid error: #{e.to_s}"
30
+ end
9
31
  end
10
32
 
33
+ private
34
+
35
+ def message_prefix
36
+ str = ""
37
+ if is_a?(SyntaxError)
38
+ str << "Liquid syntax error"
39
+ else
40
+ str << "Liquid error"
41
+ end
42
+
43
+ if line_number
44
+ str << " (line #{line_number})"
45
+ end
46
+
47
+ str << ": "
48
+ str
49
+ end
11
50
  end
12
51
 
13
52
  class ArgumentError < Error; end
14
53
  class ContextError < Error; end
15
- class FilterNotFound < Error; end
16
54
  class FileSystemError < Error; end
17
55
  class StandardError < Error; end
18
56
  class SyntaxError < Error; end
19
57
  class StackLevelError < Error; end
58
+ class TaintedError < Error; end
20
59
  class MemoryError < Error; end
21
60
  end
@@ -0,0 +1,33 @@
1
+ module Liquid
2
+ class Expression
3
+ LITERALS = {
4
+ nil => nil, 'nil'.freeze => nil, 'null'.freeze => nil, ''.freeze => nil,
5
+ 'true'.freeze => true,
6
+ 'false'.freeze => false,
7
+ 'blank'.freeze => :blank?,
8
+ 'empty'.freeze => :empty?
9
+ }
10
+
11
+ def self.parse(markup)
12
+ if LITERALS.key?(markup)
13
+ LITERALS[markup]
14
+ else
15
+ case markup
16
+ when /\A'(.*)'\z/m # Single quoted strings
17
+ $1
18
+ when /\A"(.*)"\z/m # Double quoted strings
19
+ $1
20
+ when /\A(-?\d+)\z/ # Integer and floats
21
+ $1.to_i
22
+ when /\A\((\S+)\.\.(\S+)\)\z/ # Ranges
23
+ RangeLookup.parse($1, $2)
24
+ when /\A(-?\d[\d\.]+)\z/ # Floats
25
+ $1.to_f
26
+ else
27
+ VariableLookup.parse(markup)
28
+ end
29
+ end
30
+ end
31
+
32
+ end
33
+ end
@@ -31,11 +31,22 @@ module Liquid
31
31
  # file_system.full_path("mypartial") # => "/some/path/_mypartial.liquid"
32
32
  # file_system.full_path("dir/mypartial") # => "/some/path/dir/_mypartial.liquid"
33
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
+ #
34
44
  class LocalFileSystem
35
45
  attr_accessor :root
36
46
 
37
- def initialize(root)
47
+ def initialize(root, pattern = "_%s.liquid".freeze)
38
48
  @root = root
49
+ @pattern = pattern
39
50
  end
40
51
 
41
52
  def read_template_file(template_path, context)
@@ -46,15 +57,15 @@ module Liquid
46
57
  end
47
58
 
48
59
  def full_path(template_path)
49
- raise FileSystemError, "Illegal template name '#{template_path}'" unless template_path =~ /^[^.\/][a-zA-Z0-9_\/]+$/
60
+ raise FileSystemError, "Illegal template name '#{template_path}'" unless template_path =~ /\A[^.\/][a-zA-Z0-9_\/]+\z/
50
61
 
51
- full_path = if template_path.include?('/')
52
- File.join(root, File.dirname(template_path), "_#{File.basename(template_path)}.liquid")
62
+ full_path = if template_path.include?('/'.freeze)
63
+ File.join(root, File.dirname(template_path), @pattern % File.basename(template_path))
53
64
  else
54
- File.join(root, "_#{template_path}.liquid")
65
+ File.join(root, @pattern % template_path)
55
66
  end
56
67
 
57
- raise FileSystemError, "Illegal template path '#{File.expand_path(full_path)}'" unless File.expand_path(full_path) =~ /^#{File.expand_path(root)}/
68
+ raise FileSystemError, "Illegal template path '#{File.expand_path(full_path)}'" unless File.expand_path(full_path) =~ /\A#{File.expand_path(root)}/
58
69
 
59
70
  full_path
60
71
  end
data/lib/liquid/i18n.rb CHANGED
@@ -6,7 +6,7 @@ module Liquid
6
6
 
7
7
  class TranslationError < StandardError
8
8
  end
9
-
9
+
10
10
  attr_reader :path
11
11
 
12
12
  def initialize(path = DEFAULT_LOCALE)
@@ -31,7 +31,7 @@ module Liquid
31
31
  end
32
32
 
33
33
  def deep_fetch_translation(name)
34
- name.split('.').reduce(locale) do |level, cur|
34
+ name.split('.'.freeze).reduce(locale) do |level, cur|
35
35
  level[cur] or raise TranslationError, "Translation for #{name} does not exist in locale #{path}"
36
36
  end
37
37
  end
@@ -5,7 +5,7 @@ module Liquid
5
5
  attr_reader :message
6
6
 
7
7
  def initialize(message=nil)
8
- @message = message || "interrupt"
8
+ @message = message || "interrupt".freeze
9
9
  end
10
10
  end
11
11
 
data/lib/liquid/lexer.rb CHANGED
@@ -2,16 +2,18 @@ require "strscan"
2
2
  module Liquid
3
3
  class Lexer
4
4
  SPECIALS = {
5
- '|' => :pipe,
6
- '.' => :dot,
7
- ':' => :colon,
8
- ',' => :comma,
9
- '[' => :open_square,
10
- ']' => :close_square,
11
- '(' => :open_round,
12
- ')' => :close_round
5
+ '|'.freeze => :pipe,
6
+ '.'.freeze => :dot,
7
+ ':'.freeze => :colon,
8
+ ','.freeze => :comma,
9
+ '['.freeze => :open_square,
10
+ ']'.freeze => :close_square,
11
+ '('.freeze => :open_round,
12
+ ')'.freeze => :close_round,
13
+ '?'.freeze => :question,
14
+ '-'.freeze => :dash
13
15
  }
14
- IDENTIFIER = /[\w\-?!]+/
16
+ IDENTIFIER = /[a-zA-Z_][\w-]*\??/
15
17
  SINGLE_STRING_LITERAL = /'[^\']*'/
16
18
  DOUBLE_STRING_LITERAL = /"[^\"]*"/
17
19
  NUMBER_LITERAL = /-?\d+(\.\d+)?/
@@ -14,12 +14,10 @@
14
14
  include: "Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]"
15
15
  unknown_tag: "Unknown tag '%{tag}'"
16
16
  invalid_delimiter: "'end' is not a valid delimiter for %{block_name} tags. use %{block_delimiter}"
17
- unexpected_else: "%{block_name} tag does not expect else tag"
17
+ unexpected_else: "%{block_name} tag does not expect 'else' tag"
18
+ unexpected_outer_tag: "Unexpected outer '%{tag}' tag"
18
19
  tag_termination: "Tag '%{token}' was not properly terminated with regexp: %{tag_end}"
19
20
  variable_termination: "Variable '%{token}' was not properly terminated with regexp: %{tag_end}"
20
21
  tag_never_closed: "'%{block_name}' tag was never closed"
21
22
  meta_syntax_error: "Liquid syntax error: #{e.message}"
22
23
  table_row: "Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3"
23
-
24
- extends: "Syntax Error in 'extends' - Valid syntax: extends [template]"
25
- block: "Syntax Error in 'block' - Valid syntax: block [name]"
data/lib/liquid/parser.rb CHANGED
@@ -66,10 +66,11 @@ module Liquid
66
66
  str = ""
67
67
  # might be a keyword argument (identifier: expression)
68
68
  if look(:id) && look(:colon, 1)
69
- str << consume << consume << ' '
69
+ str << consume << consume << ' '.freeze
70
70
  end
71
71
 
72
72
  str << expression
73
+ str
73
74
  end
74
75
 
75
76
  def variable_signature
@@ -0,0 +1,31 @@
1
+ module Liquid
2
+ module ParserSwitching
3
+ def parse_with_selected_parser(markup)
4
+ case @options[:error_mode] || Template.error_mode
5
+ when :strict then strict_parse_with_error_context(markup)
6
+ when :lax then lax_parse(markup)
7
+ when :warn
8
+ begin
9
+ return strict_parse_with_error_context(markup)
10
+ rescue SyntaxError => e
11
+ e.set_line_number_from_token(markup)
12
+ @warnings ||= []
13
+ @warnings << e
14
+ return lax_parse(markup)
15
+ end
16
+ end
17
+ end
18
+
19
+ private
20
+ def strict_parse_with_error_context(markup)
21
+ strict_parse(markup)
22
+ rescue SyntaxError => e
23
+ e.markup_context = markup_context(markup)
24
+ raise e
25
+ end
26
+
27
+ def markup_context(markup)
28
+ "in \"#{markup.strip}\""
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,162 @@
1
+ require 'liquid/profiler/hooks'
2
+
3
+ module Liquid
4
+
5
+ # Profiler enables support for profiling template rendering to help track down performance issues.
6
+ #
7
+ # To enable profiling, first require 'liquid/profiler'.
8
+ # Then, to profile a parse/render cycle, pass the <tt>profile: true</tt> option to <tt>Liquid::Template.parse</tt>.
9
+ # After <tt>Liquid::Template#render</tt> is called, the template object makes available an instance of this
10
+ # class via the <tt>Liquid::Template#profiler</tt> method.
11
+ #
12
+ # template = Liquid::Template.parse(template_content, profile: true)
13
+ # output = template.render
14
+ # profile = template.profiler
15
+ #
16
+ # This object contains all profiling information, containing information on what tags were rendered,
17
+ # where in the templates these tags live, and how long each tag took to render.
18
+ #
19
+ # This is a tree structure that is Enumerable all the way down, and keeps track of tags and rendering times
20
+ # inside of <tt>{% include %}</tt> tags.
21
+ #
22
+ # profile.each do |node|
23
+ # # Access to the token itself
24
+ # node.code
25
+ #
26
+ # # Which template and line number of this node.
27
+ # # If top level, this will be "<root>".
28
+ # node.partial
29
+ # node.line_number
30
+ #
31
+ # # Render time in seconds of this node
32
+ # node.render_time
33
+ #
34
+ # # If the template used {% include %}, this node will also have children.
35
+ # node.children.each do |child2|
36
+ # # ...
37
+ # end
38
+ # end
39
+ #
40
+ # Profiler also exposes the total time of the template's render in <tt>Liquid::Profiler#total_render_time</tt>.
41
+ #
42
+ # All render times are in seconds. There is a small performance hit when profiling is enabled.
43
+ #
44
+ class Profiler
45
+ include Enumerable
46
+
47
+ class Timing
48
+ attr_reader :code, :partial, :line_number, :children
49
+
50
+ def initialize(token, partial)
51
+ @code = token.respond_to?(:raw) ? token.raw : token
52
+ @partial = partial
53
+ @line_number = token.respond_to?(:line_number) ? token.line_number : nil
54
+ @children = []
55
+ end
56
+
57
+ def self.start(token, partial)
58
+ new(token, partial).tap do |t|
59
+ t.start
60
+ end
61
+ end
62
+
63
+ def start
64
+ @start_time = Time.now
65
+ end
66
+
67
+ def finish
68
+ @end_time = Time.now
69
+ end
70
+
71
+ def render_time
72
+ @end_time - @start_time
73
+ end
74
+ end
75
+
76
+ def self.profile_token_render(token)
77
+ if Profiler.current_profile && token.respond_to?(:render)
78
+ Profiler.current_profile.start_token(token)
79
+ output = yield
80
+ Profiler.current_profile.end_token(token)
81
+ output
82
+ else
83
+ yield
84
+ end
85
+ end
86
+
87
+ def self.profile_children(template_name)
88
+ if Profiler.current_profile
89
+ Profiler.current_profile.push_partial(template_name)
90
+ output = yield
91
+ Profiler.current_profile.pop_partial
92
+ output
93
+ else
94
+ yield
95
+ end
96
+ end
97
+
98
+ def self.current_profile
99
+ Thread.current[:liquid_profiler]
100
+ end
101
+
102
+ def initialize
103
+ @partial_stack = ["<root>"]
104
+
105
+ @root_timing = Timing.new("", current_partial)
106
+ @timing_stack = [@root_timing]
107
+
108
+ @render_start_at = Time.now
109
+ @render_end_at = @render_start_at
110
+ end
111
+
112
+ def start
113
+ Thread.current[:liquid_profiler] = self
114
+ @render_start_at = Time.now
115
+ end
116
+
117
+ def stop
118
+ Thread.current[:liquid_profiler] = nil
119
+ @render_end_at = Time.now
120
+ end
121
+
122
+ def total_render_time
123
+ @render_end_at - @render_start_at
124
+ end
125
+
126
+ def each(&block)
127
+ @root_timing.children.each(&block)
128
+ end
129
+
130
+ def [](idx)
131
+ @root_timing.children[idx]
132
+ end
133
+
134
+ def length
135
+ @root_timing.children.length
136
+ end
137
+
138
+ def start_token(token)
139
+ @timing_stack.push(Timing.start(token, current_partial))
140
+ end
141
+
142
+ def end_token(token)
143
+ timing = @timing_stack.pop
144
+ timing.finish
145
+
146
+ @timing_stack.last.children << timing
147
+ end
148
+
149
+ def current_partial
150
+ @partial_stack.last
151
+ end
152
+
153
+ def push_partial(partial_name)
154
+ @partial_stack.push(partial_name)
155
+ end
156
+
157
+ def pop_partial
158
+ @partial_stack.pop
159
+ end
160
+
161
+ end
162
+ end