locomotive_liquid 2.1.3 → 2.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,16 @@
1
+ 2.2.2
2
+
3
+ * Added support for template inheritance {% extends %}
4
+
5
+ 2.2.1 / 2010-08-23
6
+
7
+ * Added support for literal tags
8
+
9
+ 2.2.0 / 2010-08-22
10
+
11
+ * Compatible with Ruby 1.8.7, 1.9.1 and 1.9.2-p0
12
+ * Merged some changed made by the community
13
+
1
14
  1.9.0 / 2008-03-04
2
15
 
3
16
  * Fixed gem install rake task
@@ -7,7 +20,7 @@ Before 1.9.0
7
20
 
8
21
  * Added If with or / and expressions
9
22
 
10
- * Implemented .to_liquid for all objects which can be passed to liquid like Strings Arrays Hashes Numerics and Booleans. To export new objects to liquid just implement .to_liquid on them and return objects which themselves have .to_liquid methods.
23
+ * Implemented .to_liquid for all objects which can be passed to liquid like Strings Arrays Hashes Numerics and Booleans. To export new objects to liquid just implement .to_liquid on them and return objects which themselves have .to_liquid methods.
11
24
 
12
25
  * Added more tags to standard library
13
26
 
@@ -26,17 +39,17 @@ Before 1.9.0
26
39
  * Fixed bug with string filter parameters failing to tolerate commas in strings. [Paul Hammond]
27
40
 
28
41
  * Improved filter parameters. Filter parameters are now context sensitive; Types are resolved according to the rules of the context. Multiple parameters are now separated by the Liquid::ArgumentSeparator: , by default [Paul Hammond]
29
-
30
- {{ 'Typo' | link_to: 'http://typo.leetsoft.com', 'Typo - a modern weblog engine' }}
31
-
32
42
 
33
- * Added Liquid::Drop. A base class which you can use for exporting proxy objects to liquid which can acquire more data when used in liquid. [Tobias Luetke]
43
+ {{ 'Typo' | link_to: 'http://typo.leetsoft.com', 'Typo - a modern weblog engine' }}
44
+
45
+
46
+ * Added Liquid::Drop. A base class which you can use for exporting proxy objects to liquid which can acquire more data when used in liquid. [Tobias Luetke]
34
47
 
35
48
  class ProductDrop < Liquid::Drop
36
49
  def top_sales
37
50
  Shop.current.products.find(:all, :order => 'sales', :limit => 10 )
38
51
  end
39
- end
52
+ end
40
53
  t = Liquid::Template.parse( ' {% for product in product.top_sales %} {{ product.name }} {% endfor %} ' )
41
54
  t.render('product' => ProductDrop.new )
42
55
 
@@ -0,0 +1,39 @@
1
+ # Liquid template engine
2
+
3
+ ## Introduction
4
+
5
+ Liquid is a template engine which I wrote for very specific requirements
6
+
7
+ * It has to have beautiful and simple markup. Template engines which don't produce good looking markup are no fun to use.
8
+ * It needs to be non evaling and secure. Liquid templates are made so that users can edit them. You don't want to run code on your server which your users wrote.
9
+ * It has to be stateless. Compile and render steps have to be seperate so that the expensive parsing and compiling can be done once and later on you can just render it passing in a hash with local variables and objects.
10
+
11
+ ## Why should I use Liquid
12
+
13
+ * You want to allow your users to edit the appearance of your application but don't want them to run **insecure code on your server**.
14
+ * You want to render templates directly from the database
15
+ * You like smarty (PHP) style template engines
16
+ * You need a template engine which does HTML just as well as emails
17
+ * You don't like the markup of your current templating engine
18
+
19
+ ## What does it look like?
20
+
21
+ <ul id="products">
22
+ {% for product in products %}
23
+ <li>
24
+ <h2>{{product.name}}</h2>
25
+ Only {{product.price | price }}
26
+
27
+ {{product.description | prettyprint | paragraph }}
28
+ </li>
29
+ {% endfor %}
30
+ </ul>
31
+
32
+ ## Howto use Liquid
33
+
34
+ Liquid supports a very simple API based around the Liquid::Template class.
35
+ For standard use you can just pass it the content of a file and call render with a parameters hash.
36
+
37
+
38
+ @template = Liquid::Template.parse("hi {{name}}") # Parses and compiles the template
39
+ @template.render( 'name' => 'tobi' ) # => "hi tobi"
data/Rakefile CHANGED
@@ -1,26 +1,37 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'rubygems'
3
+
4
+ require "bundler"
5
+ Bundler.setup
6
+
3
7
  require 'rake'
4
- require 'rake/testtask'
5
8
  require 'rake/gempackagetask'
6
9
 
7
- task :default => 'test'
10
+ require "rspec"
11
+ require "rspec/core/rake_task"
12
+
13
+ Rspec::Core::RakeTask.new("spec") do |spec|
14
+ spec.pattern = "spec/**/*_spec.rb"
15
+ end
8
16
 
9
- Rake::TestTask.new(:test) do |t|
10
- t.libs << "lib"
11
- t.libs << "test"
12
- t.pattern = 'test/*_test.rb'
13
- t.verbose = false
17
+ desc "Run the Integration Specs (rendering)"
18
+ Rspec::Core::RakeTask.new("spec:integration") do |spec|
19
+ spec.pattern = "spec/unit/*_spec.rb"
14
20
  end
15
21
 
16
- Rake::TestTask.new(:ti) do |t|
17
- t.libs << "lib"
18
- t.libs << "test"
19
- t.test_files = ['test/test_helper.rb', 'test/extends_test.rb', 'test/inherited_block_test.rb']
20
- # t.test_files = ['test/test_helper.rb', 'test/inherited_block_test.rb', 'test/inherited_block_test.rb']
21
- t.verbose = false
22
+ desc "Run the Unit Specs"
23
+ Rspec::Core::RakeTask.new("spec:unit") do |spec|
24
+ spec.pattern = "spec/unit/*_spec.rb"
22
25
  end
23
26
 
27
+ desc "Run all the specs without all the verbose spec output"
28
+ Rspec::Core::RakeTask.new('spec:progress') do |spec|
29
+ spec.rspec_opts = %w(--format progress)
30
+ spec.pattern = "spec/**/*_spec.rb"
31
+ end
32
+
33
+ task :default => :spec
34
+
24
35
  gemspec = eval(File.read('locomotive_liquid.gemspec'))
25
36
  Rake::GemPackageTask.new(gemspec) do |pkg|
26
37
  pkg.gem_spec = gemspec
@@ -28,7 +39,13 @@ end
28
39
 
29
40
  desc "build the gem and release it to rubygems.org"
30
41
  task :release => :gem do
31
- sh "gem push pkg/locomotive_liquid-#{gemspec.version}.gem"
42
+ puts "Tagging #{gemspec.version}..."
43
+ require 'ruby-debug';debugger
44
+ system "git tag -a #{gemspec.version} -m 'Tagging #{gemspec.version}'"
45
+ puts "Pushing to Github..."
46
+ system "git push --tags"
47
+ puts "Pushing to rubygems.org..."
48
+ system "gem push pkg/locomotive_liquid-#{gemspec.version}.gem"
32
49
  end
33
50
 
34
51
  namespace :profile do
@@ -2,15 +2,15 @@
2
2
  # and use liquid as an template system for .liquid files
3
3
  #
4
4
  # Example
5
- #
5
+ #
6
6
  # ActionView::Base::register_template_handler :liquid, LiquidView
7
7
  class LiquidView
8
8
  PROTECTED_ASSIGNS = %w( template_root response _session template_class action_name request_origin session template
9
9
  _response url _request _cookies variables_added _flash params _headers request cookies
10
10
  ignore_missing_templates flash _params logger before_filter_chain_aborted headers )
11
- PROTECTED_INSTANCE_VARIABLES = %w( @_request @controller @_first_render @_memoized__pick_template @view_paths
11
+ PROTECTED_INSTANCE_VARIABLES = %w( @_request @controller @_first_render @_memoized__pick_template @view_paths
12
12
  @helpers @assigns_added @template @_render_stack @template_format @assigns )
13
-
13
+
14
14
  def self.call(template)
15
15
  "LiquidView.new(self).render(template, local_assigns)"
16
16
  end
@@ -18,10 +18,10 @@ class LiquidView
18
18
  def initialize(view)
19
19
  @view = view
20
20
  end
21
-
21
+
22
22
  def render(template, local_assigns = nil)
23
23
  @view.controller.headers["Content-Type"] ||= 'text/html; charset=utf-8'
24
-
24
+
25
25
  # Rails 2.2 Template has source, but not locals
26
26
  if template.respond_to?(:source) && !template.respond_to?(:locals)
27
27
  assigns = (@view.instance_variables - PROTECTED_INSTANCE_VARIABLES).inject({}) do |hash, ivar|
@@ -31,15 +31,15 @@ class LiquidView
31
31
  else
32
32
  assigns = @view.assigns.reject{ |k,v| PROTECTED_ASSIGNS.include?(k) }
33
33
  end
34
-
34
+
35
35
  source = template.respond_to?(:source) ? template.source : template
36
36
  local_assigns = (template.respond_to?(:locals) ? template.locals : local_assigns) || {}
37
-
37
+
38
38
  if content_for_layout = @view.instance_variable_get("@content_for_layout")
39
39
  assigns['content_for_layout'] = content_for_layout
40
40
  end
41
41
  assigns.merge!(local_assigns.stringify_keys)
42
-
42
+
43
43
  liquid = Liquid::Template.parse(source)
44
44
  liquid.render(assigns, :filters => [@view.controller.master_helper_module], :registers => {:action_view => @view, :controller => @view.controller})
45
45
  end
@@ -38,13 +38,14 @@ module Liquid
38
38
  StrictQuotedFragment = /"[^"]+"|'[^']+'|[^\s,\|,\:,\,]+/
39
39
  FirstFilterArgument = /#{FilterArgumentSeparator}(?:#{StrictQuotedFragment})/
40
40
  OtherFilterArgument = /#{ArgumentSeparator}(?:#{StrictQuotedFragment})/
41
- SpacelessFilter = /#{FilterSeparator}(?:#{StrictQuotedFragment})(?:#{FirstFilterArgument}(?:#{OtherFilterArgument})*)?/
41
+ SpacelessFilter = /^(?:'[^']+'|"[^"]+"|[^'"])*#{FilterSeparator}(?:#{StrictQuotedFragment})(?:#{FirstFilterArgument}(?:#{OtherFilterArgument})*)?/
42
42
  Expression = /(?:#{QuotedFragment}(?:#{SpacelessFilter})*)/
43
43
  TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/
44
44
  AnyStartingTag = /\{\{|\{\%/
45
45
  PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/
46
46
  TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/
47
47
  VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/
48
+ LiteralShorthand = /^(?:\{\{\{\s?)(.*?)(?:\s*\}\}\})$/
48
49
  end
49
50
 
50
51
  require 'liquid/drop'
@@ -28,8 +28,9 @@ module Liquid
28
28
  @strainer ||= Strainer.create(self)
29
29
  end
30
30
 
31
- # adds filters to this context.
32
- # this does not register the filters with the main Template object. see <tt>Template.register_filter</tt>
31
+ # Adds filters to this context.
32
+ #
33
+ # Note that this does not register the filters with the main Template object. see <tt>Template.register_filter</tt>
33
34
  # for that
34
35
  def add_filters(filters)
35
36
  filters = [filters].flatten.compact
@@ -52,7 +53,6 @@ module Liquid
52
53
  end
53
54
  end
54
55
 
55
-
56
56
  def invoke(method, *args)
57
57
  if strainer.respond_to?(method)
58
58
  strainer.__send__(method, *args)
@@ -61,43 +61,44 @@ module Liquid
61
61
  end
62
62
  end
63
63
 
64
- # push new local scope on the stack. use <tt>Context#stack</tt> instead
64
+ # Push new local scope on the stack. use <tt>Context#stack</tt> instead
65
65
  def push(new_scope={})
66
66
  raise StackLevelError, "Nesting too deep" if @scopes.length > 100
67
67
  @scopes.unshift(new_scope)
68
68
  end
69
69
 
70
- # merge a hash of variables in the current local scope
70
+ # Merge a hash of variables in the current local scope
71
71
  def merge(new_scopes)
72
72
  @scopes[0].merge!(new_scopes)
73
73
  end
74
74
 
75
- # pop from the stack. use <tt>Context#stack</tt> instead
75
+ # Pop from the stack. use <tt>Context#stack</tt> instead
76
76
  def pop
77
77
  raise ContextError if @scopes.size == 1
78
78
  @scopes.shift
79
79
  end
80
80
 
81
- # pushes a new local scope on the stack, pops it at the end of the block
81
+ # Pushes a new local scope on the stack, pops it at the end of the block
82
82
  #
83
83
  # Example:
84
- #
85
84
  # context.stack do
86
85
  # context['var'] = 'hi'
87
86
  # end
88
- # context['var] #=> nil
89
87
  #
88
+ # context['var] #=> nil
90
89
  def stack(new_scope={},&block)
91
90
  result = nil
92
91
  push(new_scope)
92
+
93
93
  begin
94
94
  result = yield
95
95
  ensure
96
96
  pop
97
97
  end
98
+
98
99
  result
99
100
  end
100
-
101
+
101
102
  def clear_instance_assigns
102
103
  @scopes[0] = {}
103
104
  end
@@ -116,139 +117,133 @@ module Liquid
116
117
  end
117
118
 
118
119
  private
119
-
120
- # Look up variable, either resolve directly after considering the name. We can directly handle
121
- # Strings, digits, floats and booleans (true,false). If no match is made we lookup the variable in the current scope and
122
- # later move up to the parent blocks to see if we can resolve the variable somewhere up the tree.
123
- # Some special keywords return symbols. Those symbols are to be called on the rhs object in expressions
124
- #
125
- # Example:
126
- #
127
- # products == empty #=> products.empty?
128
- #
129
- def resolve(key)
130
- case key
131
- when nil, 'nil', 'null', ''
132
- nil
133
- when 'true'
134
- true
135
- when 'false'
136
- false
137
- when 'blank'
138
- :blank?
139
- when 'empty'
140
- :empty?
141
- # Single quoted strings
142
- when /^'(.*)'$/
143
- $1.to_s
144
- # Double quoted strings
145
- when /^"(.*)"$/
146
- $1.to_s
147
- # Integer and floats
148
- when /^(\d+)$/
149
- $1.to_i
150
- # Ranges
151
- when /^\((\S+)\.\.(\S+)\)$/
152
- (resolve($1).to_i..resolve($2).to_i)
153
- # Floats
154
- when /^(\d[\d\.]+)$/
155
- $1.to_f
156
- else
157
- variable(key)
120
+ # Look up variable, either resolve directly after considering the name. We can directly handle
121
+ # Strings, digits, floats and booleans (true,false).
122
+ # If no match is made we lookup the variable in the current scope and
123
+ # later move up to the parent blocks to see if we can resolve the variable somewhere up the tree.
124
+ # Some special keywords return symbols. Those symbols are to be called on the rhs object in expressions
125
+ #
126
+ # Example:
127
+ # products == empty #=> products.empty?
128
+ def resolve(key)
129
+ case key
130
+ when nil, 'nil', 'null', ''
131
+ nil
132
+ when 'true'
133
+ true
134
+ when 'false'
135
+ false
136
+ when 'blank'
137
+ :blank?
138
+ when 'empty' # Single quoted strings
139
+ :empty?
140
+ when /^'(.*)'$/ # Double quoted strings
141
+ $1.to_s
142
+ when /^"(.*)"$/ # Integer and floats
143
+ $1.to_s
144
+ when /^(\d+)$/ # Ranges
145
+ $1.to_i
146
+ when /^\((\S+)\.\.(\S+)\)$/ # Floats
147
+ (resolve($1).to_i..resolve($2).to_i)
148
+ when /^(\d[\d\.]+)$/
149
+ $1.to_f
150
+ else
151
+ variable(key)
152
+ end
158
153
  end
159
- end
160
154
 
161
- # fetches an object starting at the local scope and then moving up
162
- # the hierachy
163
- def find_variable(key)
164
- scope = @scopes.find { |s| s.has_key?(key) }
165
- if scope.nil?
166
- @environments.each do |e|
167
- if variable = lookup_and_evaluate(e, key)
168
- scope = e
169
- break
155
+ # Fetches an object starting at the local scope and then moving up the hierachy
156
+ def find_variable(key)
157
+ scope = @scopes.find { |s| s.has_key?(key) }
158
+
159
+ if scope.nil?
160
+ @environments.each do |e|
161
+ if variable = lookup_and_evaluate(e, key)
162
+ scope = e
163
+ break
164
+ end
170
165
  end
171
166
  end
172
- end
173
- scope ||= @environments.last || @scopes.last
174
- variable ||= lookup_and_evaluate(scope, key)
175
-
176
- variable = variable.to_liquid
177
- variable.context = self if variable.respond_to?(:context=)
178
- return variable
179
- end
180
167
 
181
- # resolves namespaced queries gracefully.
182
- #
183
- # Example
184
- #
185
- # @context['hash'] = {"name" => 'tobi'}
186
- # assert_equal 'tobi', @context['hash.name']
187
- # assert_equal 'tobi', @context['hash["name"]']
188
- #
189
- def variable(markup)
190
- parts = markup.scan(VariableParser)
191
- square_bracketed = /^\[(.*)\]$/
168
+ scope ||= @environments.last || @scopes.last
169
+ variable ||= lookup_and_evaluate(scope, key)
170
+
171
+ variable = variable.to_liquid
172
+ variable.context = self if variable.respond_to?(:context=)
192
173
 
193
- first_part = parts.shift
194
- if first_part =~ square_bracketed
195
- first_part = resolve($1)
174
+ return variable
196
175
  end
197
176
 
198
- if object = find_variable(first_part)
177
+ # Resolves namespaced queries gracefully.
178
+ #
179
+ # Example
180
+ # @context['hash'] = {"name" => 'tobi'}
181
+ # assert_equal 'tobi', @context['hash.name']
182
+ # assert_equal 'tobi', @context['hash["name"]']
183
+ def variable(markup)
184
+ parts = markup.scan(VariableParser)
185
+ square_bracketed = /^\[(.*)\]$/
199
186
 
200
- parts.each do |part|
201
- part = resolve($1) if part_resolved = (part =~ square_bracketed)
187
+ first_part = parts.shift
202
188
 
203
- # If object is a hash- or array-like object we look for the
204
- # presence of the key and if its available we return it
205
- if object.respond_to?(:[]) and
206
- ((object.respond_to?(:has_key?) and object.has_key?(part)) or
207
- (object.respond_to?(:fetch) and part.is_a?(Integer)))
189
+ if first_part =~ square_bracketed
190
+ first_part = resolve($1)
191
+ end
208
192
 
209
- # if its a proc we will replace the entry with the proc
210
- res = lookup_and_evaluate(object, part)
211
- object = res.to_liquid
193
+ if object = find_variable(first_part)
212
194
 
213
- # Some special cases. If the part wasn't in square brackets and
214
- # no key with the same name was found we interpret following calls
215
- # as commands and call them on the current object
216
- elsif !part_resolved and object.respond_to?(part) and ['size', 'first', 'last'].include?(part)
195
+ parts.each do |part|
196
+ part = resolve($1) if part_resolved = (part =~ square_bracketed)
217
197
 
218
- object = object.send(part.intern).to_liquid
198
+ # If object is a hash- or array-like object we look for the
199
+ # presence of the key and if its available we return it
200
+ if object.respond_to?(:[]) and
201
+ ((object.respond_to?(:has_key?) and object.has_key?(part)) or
202
+ (object.respond_to?(:fetch) and part.is_a?(Integer)))
219
203
 
220
- # No key was present with the desired value and it wasn't one of the directly supported
221
- # keywords either. The only thing we got left is to return nil
222
- else
223
- return nil
224
- end
204
+ # if its a proc we will replace the entry with the proc
205
+ res = lookup_and_evaluate(object, part)
206
+ object = res.to_liquid
225
207
 
226
- # If we are dealing with a drop here we have to
227
- object.context = self if object.respond_to?(:context=)
208
+ # Some special cases. If the part wasn't in square brackets and
209
+ # no key with the same name was found we interpret following calls
210
+ # as commands and call them on the current object
211
+ elsif !part_resolved and object.respond_to?(part) and ['size', 'first', 'last'].include?(part)
212
+
213
+ object = object.send(part.intern).to_liquid
214
+
215
+ # No key was present with the desired value and it wasn't one of the directly supported
216
+ # keywords either. The only thing we got left is to return nil
217
+ else
218
+ return nil
219
+ end
220
+
221
+ # If we are dealing with a drop here we have to
222
+ object.context = self if object.respond_to?(:context=)
223
+ end
228
224
  end
229
- end
230
225
 
231
- object
232
- end
233
-
234
- def lookup_and_evaluate(obj, key)
235
- if (value = obj[key]).is_a?(Proc) && obj.respond_to?(:[]=)
236
- obj[key] = value.call(self)
237
- else
238
- value
239
- end
240
- end
241
-
242
- def squash_instance_assigns_with_environments
243
- @scopes.last.each_key do |k|
244
- @environments.each do |env|
245
- if env.has_key?(k)
246
- scopes.last[k] = lookup_and_evaluate(env, k)
247
- break
226
+ object
227
+ end # variable
228
+
229
+ def lookup_and_evaluate(obj, key)
230
+ if (value = obj[key]).is_a?(Proc) && obj.respond_to?(:[]=)
231
+ obj[key] = (value.arity == 0) ? value.call : value.call(self)
232
+ else
233
+ value
234
+ end
235
+ end # lookup_and_evaluate
236
+
237
+ def squash_instance_assigns_with_environments
238
+ @scopes.last.each_key do |k|
239
+ @environments.each do |env|
240
+ if env.has_key?(k)
241
+ scopes.last[k] = lookup_and_evaluate(env, k)
242
+ break
243
+ end
248
244
  end
249
245
  end
250
- end
251
- end
252
-
253
- end
254
- end
246
+ end # squash_instance_assigns_with_environments
247
+ end # Context
248
+
249
+ end # Liquid
@@ -1,6 +1,6 @@
1
1
  module Liquid
2
2
  class Error < ::StandardError; end
3
-
3
+
4
4
  class ArgumentError < Error; end
5
5
  class ContextError < Error; end
6
6
  class FilterNotFound < Error; end
@@ -1,7 +1,7 @@
1
1
  module Liquid
2
2
  # A Liquid file system is way to let your templates retrieve other templates for use with the include tag.
3
3
  #
4
- # You can implement subclasses that retrieve templates from the database, from the file system using a different
4
+ # You can implement subclasses that retrieve templates from the database, from the file system using a different
5
5
  # path structure, you can provide them as hard-coded inline strings, or any manner that you see fit.
6
6
  #
7
7
  # You can add additional instance variables, arguments, or methods as needed.
@@ -18,7 +18,7 @@ module Liquid
18
18
  raise FileSystemError, "This liquid context does not allow includes."
19
19
  end
20
20
  end
21
-
21
+
22
22
  # This implements an abstract file system which retrieves template files named in a manner similar to Rails partials,
23
23
  # ie. with the template name prefixed with an underscore. The extension ".liquid" is also added.
24
24
  #
@@ -27,35 +27,35 @@ module Liquid
27
27
  # Example:
28
28
  #
29
29
  # file_system = Liquid::LocalFileSystem.new("/some/path")
30
- #
30
+ #
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
34
  class LocalFileSystem
35
35
  attr_accessor :root
36
-
36
+
37
37
  def initialize(root)
38
38
  @root = root
39
39
  end
40
-
40
+
41
41
  def read_template_file(template_path)
42
42
  full_path = full_path(template_path)
43
43
  raise FileSystemError, "No such template '#{template_path}'" unless File.exists?(full_path)
44
-
44
+
45
45
  File.read(full_path)
46
46
  end
47
-
47
+
48
48
  def full_path(template_path)
49
49
  raise FileSystemError, "Illegal template name '#{template_path}'" unless template_path =~ /^[^.\/][a-zA-Z0-9_\/]+$/
50
-
50
+
51
51
  full_path = if template_path.include?('/')
52
52
  File.join(root, File.dirname(template_path), "_#{File.basename(template_path)}.liquid")
53
53
  else
54
54
  File.join(root, "_#{template_path}.liquid")
55
55
  end
56
-
56
+
57
57
  raise FileSystemError, "Illegal template path '#{File.expand_path(full_path)}'" unless File.expand_path(full_path) =~ /^#{File.expand_path(root)}/
58
-
58
+
59
59
  full_path
60
60
  end
61
61
  end
@@ -1,36 +1,36 @@
1
1
  require 'cgi'
2
2
 
3
3
  module Liquid
4
-
4
+
5
5
  module StandardFilters
6
-
6
+
7
7
  # Return the size of an array or of an string
8
8
  def size(input)
9
-
9
+
10
10
  input.respond_to?(:size) ? input.size : 0
11
- end
12
-
11
+ end
12
+
13
13
  # convert a input string to DOWNCASE
14
14
  def downcase(input)
15
15
  input.to_s.downcase
16
- end
16
+ end
17
17
 
18
18
  # convert a input string to UPCASE
19
19
  def upcase(input)
20
20
  input.to_s.upcase
21
21
  end
22
-
22
+
23
23
  # capitalize words in the input centence
24
24
  def capitalize(input)
25
25
  input.to_s.capitalize
26
26
  end
27
-
27
+
28
28
  def escape(input)
29
29
  CGI.escapeHTML(input) rescue input
30
30
  end
31
-
31
+
32
32
  alias_method :h, :escape
33
-
33
+
34
34
  # Truncate a string down to x characters
35
35
  def truncate(input, length = 50, truncate_string = "...")
36
36
  if input.nil? then return end
@@ -44,22 +44,26 @@ module Liquid
44
44
  wordlist = input.to_s.split
45
45
  l = words.to_i - 1
46
46
  l = 0 if l < 0
47
- wordlist.length > l ? wordlist[0..l].join(" ") + truncate_string : input
47
+ wordlist.length > l ? wordlist[0..l].join(" ") + truncate_string : input
48
48
  end
49
-
49
+
50
50
  def strip_html(input)
51
51
  input.to_s.gsub(/<script.*?<\/script>/, '').gsub(/<.*?>/, '')
52
- end
53
-
52
+ end
53
+
54
54
  # Remove all newlines from the string
55
- def strip_newlines(input)
56
- input.to_s.gsub(/\n/, '')
55
+ def strip_newlines(input)
56
+ input.to_s.gsub(/\n/, '')
57
57
  end
58
-
59
-
58
+
60
59
  # Join elements of the array with certain character between them
61
- def join(input, glue = ' ')
62
- [input].flatten.join(glue)
60
+ def join(input, array_glue = ' ', hash_glue = nil)
61
+ hash_glue ||= array_glue
62
+
63
+ # translate from hash to array if needed
64
+ input = input.map{|k,v| "#{k}#{hash_glue}#{v}" } if input.is_a?(Hash)
65
+
66
+ [input].flatten.join(array_glue)
63
67
  end
64
68
 
65
69
  # Sort elements of the array
@@ -73,8 +77,8 @@ module Liquid
73
77
  elsif ary.first.respond_to?(property)
74
78
  ary.sort {|a,b| a.send(property) <=> b.send(property) }
75
79
  end
76
- end
77
-
80
+ end
81
+
78
82
  # map/collect on a given property
79
83
  def map(input, property)
80
84
  ary = [input].flatten
@@ -84,42 +88,42 @@ module Liquid
84
88
  ary.map {|e| e.send(property) }
85
89
  end
86
90
  end
87
-
91
+
88
92
  # Replace occurrences of a string with another
89
93
  def replace(input, string, replacement = '')
90
94
  input.to_s.gsub(string, replacement)
91
95
  end
92
-
96
+
93
97
  # Replace the first occurrences of a string with another
94
98
  def replace_first(input, string, replacement = '')
95
99
  input.to_s.sub(string, replacement)
96
- end
97
-
100
+ end
101
+
98
102
  # remove a substring
99
103
  def remove(input, string)
100
- input.to_s.gsub(string, '')
104
+ input.to_s.gsub(string, '')
101
105
  end
102
-
106
+
103
107
  # remove the first occurrences of a substring
104
108
  def remove_first(input, string)
105
- input.to_s.sub(string, '')
106
- end
107
-
109
+ input.to_s.sub(string, '')
110
+ end
111
+
108
112
  # add one string to another
109
113
  def append(input, string)
110
114
  input.to_s + string.to_s
111
115
  end
112
-
116
+
113
117
  # prepend a string to another
114
118
  def prepend(input, string)
115
119
  string.to_s + input.to_s
116
120
  end
117
-
121
+
118
122
  # Add <br /> tags in front of all newlines in input string
119
- def newline_to_br(input)
120
- input.to_s.gsub(/\n/, "<br />\n")
123
+ def newline_to_br(input)
124
+ input.to_s.gsub(/\n/, "<br />\n")
121
125
  end
122
-
126
+
123
127
  # Reformat a date
124
128
  #
125
129
  # %a - The abbreviated weekday name (``Sun'')
@@ -149,62 +153,62 @@ module Liquid
149
153
  # %Z - Time zone name
150
154
  # %% - Literal ``%'' character
151
155
  def date(input, format)
152
-
156
+
153
157
  if format.to_s.empty?
154
158
  return input.to_s
155
159
  end
156
-
160
+
157
161
  date = input.is_a?(String) ? Time.parse(input) : input
158
-
162
+
159
163
  if date.respond_to?(:strftime)
160
164
  date.strftime(format.to_s)
161
165
  else
162
166
  input
163
167
  end
164
- rescue => e
168
+ rescue => e
165
169
  input
166
170
  end
167
-
168
- # Get the first element of the passed in array
169
- #
171
+
172
+ # Get the first element of the passed in array
173
+ #
170
174
  # Example:
171
175
  # {{ product.images | first | to_img }}
172
- #
176
+ #
173
177
  def first(array)
174
178
  array.first if array.respond_to?(:first)
175
179
  end
176
180
 
177
- # Get the last element of the passed in array
178
- #
181
+ # Get the last element of the passed in array
182
+ #
179
183
  # Example:
180
184
  # {{ product.images | last | to_img }}
181
- #
185
+ #
182
186
  def last(array)
183
187
  array.last if array.respond_to?(:last)
184
188
  end
185
-
189
+
186
190
  # addition
187
191
  def plus(input, operand)
188
192
  to_number(input) + to_number(operand)
189
193
  end
190
-
194
+
191
195
  # subtraction
192
196
  def minus(input, operand)
193
197
  to_number(input) - to_number(operand)
194
198
  end
195
-
199
+
196
200
  # multiplication
197
201
  def times(input, operand)
198
202
  to_number(input) * to_number(operand)
199
203
  end
200
-
204
+
201
205
  # division
202
206
  def divided_by(input, operand)
203
207
  to_number(input) / to_number(operand)
204
208
  end
205
-
209
+
206
210
  private
207
-
211
+
208
212
  def to_number(obj)
209
213
  case obj
210
214
  when Numeric
@@ -215,8 +219,8 @@ module Liquid
215
219
  0
216
220
  end
217
221
  end
218
-
222
+
219
223
  end
220
-
224
+
221
225
  Template.register_filter(StandardFilters)
222
226
  end
@@ -16,6 +16,9 @@ module Liquid
16
16
  INTERNAL_METHOD = /^__/
17
17
  @@required_methods = Set.new([:__id__, :__send__, :respond_to?, :extend, :methods, :class, :object_id])
18
18
 
19
+ # Ruby 1.9.2 introduces Object#respond_to_missing?, which is invoked by Object#respond_to?
20
+ @@required_methods << :respond_to_missing? if Object.respond_to? :respond_to_missing?
21
+
19
22
  @@filters = {}
20
23
 
21
24
  def initialize(context)
@@ -1,9 +1,9 @@
1
1
  module Liquid
2
- class Comment < Block
2
+ class Comment < Block
3
3
  def render(context)
4
4
  ''
5
- end
5
+ end
6
6
  end
7
-
8
- Template.register_tag('comment', Comment)
7
+
8
+ Template.register_tag('comment', Comment)
9
9
  end
@@ -14,7 +14,7 @@ module Liquid
14
14
  class If < Block
15
15
  SyntaxHelp = "Syntax Error in tag 'if' - Valid syntax: if [expression]"
16
16
  Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/
17
- ExpressionsAndOperators = /(?:\b(?:and|or)\b|(?:\s*(?!\b(?:and|or)\b)(?:#{QuotedFragment}|\S+)\s*)+)/
17
+ ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/
18
18
 
19
19
  def initialize(tag_name, markup, tokens, context)
20
20
 
@@ -57,7 +57,7 @@ module Liquid
57
57
  condition = Condition.new($1, $2, $3)
58
58
 
59
59
  while not expressions.empty?
60
- operator = expressions.shift
60
+ operator = (expressions.shift).to_s.strip
61
61
 
62
62
  raise(SyntaxError, SyntaxHelp) unless expressions.shift.to_s =~ Syntax
63
63
 
@@ -1,20 +1,20 @@
1
1
  module Liquid
2
2
  class Ifchanged < Block
3
-
3
+
4
4
  def render(context)
5
- context.stack do
6
-
5
+ context.stack do
6
+
7
7
  output = render_all(@nodelist, context)
8
-
8
+
9
9
  if output != context.registers[:ifchanged]
10
10
  context.registers[:ifchanged] = output
11
11
  output
12
12
  else
13
13
  ''
14
- end
14
+ end
15
15
  end
16
16
  end
17
- end
18
-
19
- Template.register_tag('ifchanged', Ifchanged)
17
+ end
18
+
19
+ Template.register_tag('ifchanged', Ifchanged)
20
20
  end
@@ -0,0 +1,42 @@
1
+ module Liquid
2
+
3
+ class Literal < Block
4
+
5
+ # Class methods
6
+
7
+ # Converts a shorthand Liquid literal into its long representation.
8
+ #
9
+ # Currently the Template parser only knows how to handle the long version.
10
+ # So, it always checks if it is in the presence of a literal, in which case it gets converted through this method.
11
+ #
12
+ # Example:
13
+ # Liquid::Literal "{{{ hello world }}}" #=> "{% literal %} hello world {% endliteral %}"
14
+ def self.from_shorthand(literal)
15
+ literal =~ LiteralShorthand ? "{% literal %}#{$1}{% endliteral %}" : literal
16
+ end
17
+
18
+ # Public instance methods
19
+
20
+ def parse(tokens) # :nodoc:
21
+ @nodelist ||= []
22
+ @nodelist.clear
23
+
24
+ while token = tokens.shift
25
+ if token =~ FullToken && block_delimiter == $1
26
+ end_tag
27
+ return
28
+ else
29
+ @nodelist << token
30
+ end
31
+ end
32
+
33
+ # Make sure that its ok to end parsing in the current block.
34
+ # Effectively this method will throw and exception unless the current block is
35
+ # of type Document
36
+ assert_missing_delimitation!
37
+ end # parse
38
+
39
+ end
40
+
41
+ Template.register_tag('literal', Literal)
42
+ end
@@ -9,25 +9,25 @@ module Liquid
9
9
  class Unless < If
10
10
  def render(context)
11
11
  context.stack do
12
-
12
+
13
13
  # First condition is interpreted backwards ( if not )
14
14
  block = @blocks.first
15
15
  unless block.evaluate(context)
16
- return render_all(block.attachment, context)
16
+ return render_all(block.attachment, context)
17
17
  end
18
-
18
+
19
19
  # After the first condition unless works just like if
20
20
  @blocks[1..-1].each do |block|
21
- if block.evaluate(context)
22
- return render_all(block.attachment, context)
21
+ if block.evaluate(context)
22
+ return render_all(block.attachment, context)
23
23
  end
24
- end
25
-
24
+ end
25
+
26
26
  ''
27
27
  end
28
- end
28
+ end
29
29
  end
30
-
30
+
31
31
 
32
32
  Template.register_tag('unless', Unless)
33
33
  end
@@ -55,7 +55,7 @@ module Liquid
55
55
  # Parse source code.
56
56
  # Returns self for easy chaining
57
57
  def parse(source, context = {})
58
- @root = Document.new(tokenize(source), context.merge!(:template => self))
58
+ @root = Document.new(tokenize(Liquid::Literal.from_shorthand(source)), context.merge!(:template => self))
59
59
  self
60
60
  end
61
61
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: locomotive_liquid
3
3
  version: !ruby/object:Gem::Version
4
- hash: 13
4
+ hash: 3
5
5
  prerelease: false
6
6
  segments:
7
7
  - 2
8
- - 1
9
- - 3
10
- version: 2.1.3
8
+ - 2
9
+ - 2
10
+ version: 2.2.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Tobias Luetke
@@ -17,7 +17,7 @@ autorequire:
17
17
  bindir: bin
18
18
  cert_chain: []
19
19
 
20
- date: 2010-09-27 00:00:00 -07:00
20
+ date: 2010-10-01 00:00:00 -07:00
21
21
  default_executable:
22
22
  dependencies: []
23
23
 
@@ -32,12 +32,12 @@ extensions: []
32
32
 
33
33
  extra_rdoc_files:
34
34
  - History.txt
35
- - README.txt
35
+ - README.md
36
36
  files:
37
37
  - CHANGELOG
38
38
  - History.txt
39
39
  - MIT-LICENSE
40
- - README.txt
40
+ - README.md
41
41
  - Rakefile
42
42
  - init.rb
43
43
  - lib/extras/liquid_view.rb
@@ -65,6 +65,7 @@ files:
65
65
  - lib/liquid/tags/ifchanged.rb
66
66
  - lib/liquid/tags/include.rb
67
67
  - lib/liquid/tags/inherited_block.rb
68
+ - lib/liquid/tags/literal.rb
68
69
  - lib/liquid/tags/unless.rb
69
70
  - lib/liquid/template.rb
70
71
  - lib/liquid/variable.rb
@@ -77,7 +78,7 @@ licenses: []
77
78
  post_install_message:
78
79
  rdoc_options:
79
80
  - --main
80
- - README.txt
81
+ - README.md
81
82
  require_paths:
82
83
  - lib
83
84
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -105,7 +106,7 @@ requirements: []
105
106
  rubyforge_project: locomotive_liquid
106
107
  rubygems_version: 1.3.7
107
108
  signing_key:
108
- specification_version: 2
109
+ specification_version: 3
109
110
  summary: A secure, non-evaling end user template engine with aesthetic markup.
110
111
  test_files: []
111
112
 
data/README.txt DELETED
@@ -1,38 +0,0 @@
1
- = Liquid template engine
2
-
3
- Liquid is a template engine which I wrote for very specific requirements
4
-
5
- * It has to have beautiful and simple markup.
6
- Template engines which don't produce good looking markup are no fun to use.
7
- * It needs to be non evaling and secure. Liquid templates are made so that users can edit them. You don't want to run code on your server which your users wrote.
8
- * It has to be stateless. Compile and render steps have to be seperate so that the expensive parsing and compiling can be done once and later on you can
9
- just render it passing in a hash with local variables and objects.
10
-
11
- == Why should i use Liquid
12
-
13
- * You want to allow your users to edit the appearance of your application but don't want them to run insecure code on your server.
14
- * You want to render templates directly from the database
15
- * You like smarty style template engines
16
- * You need a template engine which does HTML just as well as Emails
17
- * You don't like the markup of your current one
18
-
19
- == What does it look like?
20
-
21
- <ul id="products">
22
- {% for product in products %}
23
- <li>
24
- <h2>{{product.name}}</h2>
25
- Only {{product.price | price }}
26
-
27
- {{product.description | prettyprint | paragraph }}
28
- </li>
29
- {% endfor %}
30
- </ul>
31
-
32
- == Howto use Liquid
33
-
34
- Liquid supports a very simple API based around the Liquid::Template class.
35
- For standard use you can just pass it the content of a file and call render with a parameters hash.
36
-
37
- @template = Liquid::Template.parse("hi {{name}}") # Parses and compiles the template
38
- @template.render( 'name' => 'tobi' ) # => "hi tobi"