Add snippet support and unpack liquid plugin

This commit is contained in:
Wen-Tien Chang 2009-05-08 02:13:27 +08:00
parent 8dcde174cf
commit 438c508ea1
79 changed files with 5708 additions and 1 deletions

View File

@ -1,4 +1,7 @@
class Admin::LayoutsController < ApplicationController class Admin::LayoutsController < ApplicationController
layout "admin"
# GET /layouts # GET /layouts
# GET /layouts.xml # GET /layouts.xml
def index def index

View File

@ -1,4 +1,7 @@
class Admin::PagesController < ApplicationController class Admin::PagesController < ApplicationController
layout "admin"
# GET /pages # GET /pages
# GET /pages.xml # GET /pages.xml
def index def index

View File

@ -0,0 +1,85 @@
class Admin::SnippetsController < ApplicationController
layout "admin"
# GET /snippets
# GET /snippets.xml
def index
@snippets = Snippet.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @snippets }
end
end
# GET /snippets/1
# GET /snippets/1.xml
def show
@snippet = Snippet.find(params[:id])
redirect_to "/#{@snippet.name}"
end
# GET /snippets/new
# GET /snippets/new.xml
def new
@snippet = Snippet.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @snippets }
end
end
# GET /snippets/1/edit
def edit
@snippet = Snippet.find(params[:id])
end
# POST /snippets
# POST /snippets.xml
def create
@snippet = Snippet.new(params[:snippet])
respond_to do |format|
if @snippet.save
flash[:notice] = 'Snippet was successfully created.'
format.html { redirect_to admin_snippets_url }
format.xml { render :xml => @snippet, :status => :created, :location => @snippets }
else
format.html { render :action => "new" }
format.xml { render :xml => @snippet.errors, :status => :unprocessable_entity }
end
end
end
# PUT /snippets/1
# PUT /snippets/1.xml
def update
@snippet = Snippet.find(params[:id])
respond_to do |format|
if @snippet.update_attributes(params[:snippet])
flash[:notice] = 'Snippet was successfully updated.'
format.html { redirect_to admin_snippets_url }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @snippet.errors, :status => :unprocessable_entity }
end
end
end
# DELETE /snippets/1
# DELETE /snippets/1.xml
def destroy
@snippet = Snippet.find(params[:id])
@snippet.destroy
respond_to do |format|
format.html { redirect_to admin_snippets_url }
format.xml { head :ok }
end
end
end

View File

@ -1,5 +1,7 @@
class PagesController < ApplicationController class PagesController < ApplicationController
Liquid::Template.register_filter(SnippetFilter)
def show def show
@page = Page.find_by_name(params[:page_name]) @page = Page.find_by_name(params[:page_name])
@layout = @page.layout @layout = @page.layout

8
app/models/snippet.rb Normal file
View File

@ -0,0 +1,8 @@
class Snippet < CouchFoo::Base
property :name, String
property :content, String
validates_presence_of :name
end

View File

@ -0,0 +1,13 @@
module SnippetFilter
def render_snippet(snippet_name)
snippet = Snippet.find_by_name(snippet_name)
if snippet
return Liquid::Template.parse(snippet.content).render
else
return "nothing"
end
end
end

View File

@ -0,0 +1,21 @@
<h1>Editing snippets</h1>
<% form_for @snippet, :url => admin_snippet_path(@snippet) do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :name, "Name" %>
<%= f.text_field :name %>
</p>
<p>
<%= f.label :content, "Content" %>
<%= f.text_area :content, :size => '100x30' %>
</p>
<p>
<%= f.submit 'Update' %>
</p>
<% end %>
<%= link_to 'Back', admin_snippets_path %>

View File

@ -0,0 +1,18 @@
<h1>Listing snippets</h1>
<table>
<tr>
</tr>
<% @snippets.each do |snippet| %>
<tr>
<td><%= snippet.name %></td>
<td><%= link_to 'Edit', edit_admin_snippet_path(snippet) %></td>
<td><%= link_to 'Destroy', admin_snippet_path(snippet), :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<% end %>
</table>
<br />
<%= link_to 'New snippets', new_admin_snippet_path %>

View File

@ -0,0 +1,21 @@
<h1>New snippets</h1>
<% form_for :snippet, :url => admin_snippets_path do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :name, "Name" %>
<%= f.text_field :name %>
</p>
<p>
<%= f.label :content, "Content" %>
<%= f.text_area :content, :size => '100x30' %>
</p>
<p>
<%= f.submit 'Create' %>
</p>
<% end %>
<%= link_to 'Back', admin_snippets_path %>

View File

@ -38,7 +38,6 @@ Rails::Initializer.run do |config|
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}')] # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}')]
# config.i18n.default_locale = :de # config.i18n.default_locale = :de
config.gem "liquid"
end end

View File

@ -3,6 +3,7 @@ ActionController::Routing::Routes.draw do |map|
map.namespace :admin do |admin| map.namespace :admin do |admin|
admin.resources :pages admin.resources :pages
admin.resources :layouts admin.resources :layouts
admin.resources :snippets
end end
# The priority is based upon order of creation: first created -> highest priority. # The priority is based upon order of creation: first created -> highest priority.

44
vendor/plugins/liquid/CHANGELOG vendored Normal file
View File

@ -0,0 +1,44 @@
* Ruby 1.9.1 bugfixes
* Fix LiquidView for Rails 2.2. Fix local assigns for all versions of Rails
* Fixed gem install rake task
* Improve Error encapsulation in liquid by maintaining a own set of exceptions instead of relying on ruby build ins
* Added If with or / and expressions
* 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.
* Added more tags to standard library
* Added include tag ( like partials in rails )
* [...] Gazillion of detail improvements
* Added strainers as filter hosts for better security [Tobias Luetke]
* Fixed that rails integration would call filter with the wrong "self" [Michael Geary]
* Fixed bad error reporting when a filter called a method which doesn't exist. Liquid told you that it couldn't find the filter which was obviously misleading [Tobias Luetke]
* Removed count helper from standard lib. use size [Tobias Luetke]
* Fixed bug with string filter parameters failing to tolerate commas in strings. [Paul Hammond]
* 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]
{{ 'Typo' | link_to: 'http://typo.leetsoft.com', 'Typo - a modern weblog engine' }}
* 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]
class ProductDrop < Liquid::Drop
def top_sales
Shop.current.products.find(:all, :order => 'sales', :limit => 10 )
end
end
t = Liquid::Template.parse( ' {% for product in product.top_sales %} {{ product.name }} {% endfor %} ' )
t.render('product' => ProductDrop.new )
* Added filter parameters support. Example: {{ date | format_date: "%Y" }} [Paul Hammond]

44
vendor/plugins/liquid/History.txt vendored Normal file
View File

@ -0,0 +1,44 @@
1.9.0 / 2008-03-04
* Fixed gem install rake task
* Improve Error encapsulation in liquid by maintaining a own set of exceptions instead of relying on ruby build ins
Before 1.9.0
* Added If with or / and expressions
* 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.
* Added more tags to standard library
* Added include tag ( like partials in rails )
* [...] Gazillion of detail improvements
* Added strainers as filter hosts for better security [Tobias Luetke]
* Fixed that rails integration would call filter with the wrong "self" [Michael Geary]
* Fixed bad error reporting when a filter called a method which doesn't exist. Liquid told you that it couldn't find the filter which was obviously misleading [Tobias Luetke]
* Removed count helper from standard lib. use size [Tobias Luetke]
* Fixed bug with string filter parameters failing to tolerate commas in strings. [Paul Hammond]
* 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]
{{ 'Typo' | link_to: 'http://typo.leetsoft.com', 'Typo - a modern weblog engine' }}
* 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]
class ProductDrop < Liquid::Drop
def top_sales
Shop.current.products.find(:all, :order => 'sales', :limit => 10 )
end
end
t = Liquid::Template.parse( ' {% for product in product.top_sales %} {{ product.name }} {% endfor %} ' )
t.render('product' => ProductDrop.new )
* Added filter parameters support. Example: {{ date | format_date: "%Y" }} [Paul Hammond]

20
vendor/plugins/liquid/MIT-LICENSE vendored Normal file
View File

@ -0,0 +1,20 @@
Copyright (c) 2005, 2006 Tobias Luetke
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

34
vendor/plugins/liquid/Manifest.txt vendored Normal file
View File

@ -0,0 +1,34 @@
CHANGELOG
History.txt
MIT-LICENSE
Manifest.txt
README.txt
Rakefile
init.rb
lib/extras/liquid_view.rb
lib/liquid.rb
lib/liquid/block.rb
lib/liquid/condition.rb
lib/liquid/context.rb
lib/liquid/document.rb
lib/liquid/drop.rb
lib/liquid/errors.rb
lib/liquid/extensions.rb
lib/liquid/file_system.rb
lib/liquid/htmltags.rb
lib/liquid/module_ex.rb
lib/liquid/standardfilters.rb
lib/liquid/strainer.rb
lib/liquid/tag.rb
lib/liquid/tags/assign.rb
lib/liquid/tags/capture.rb
lib/liquid/tags/case.rb
lib/liquid/tags/comment.rb
lib/liquid/tags/cycle.rb
lib/liquid/tags/for.rb
lib/liquid/tags/if.rb
lib/liquid/tags/ifchanged.rb
lib/liquid/tags/include.rb
lib/liquid/tags/unless.rb
lib/liquid/template.rb
lib/liquid/variable.rb

38
vendor/plugins/liquid/README.txt vendored Normal file
View File

@ -0,0 +1,38 @@
= Liquid template engine
Liquid is a template engine which I wrote for very specific requirements
* It has to have beautiful and simple markup.
Template engines which don't produce good looking markup are no fun to use.
* 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.
* 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.
== Why should i use Liquid
* 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.
* You want to render templates directly from the database
* You like smarty style template engines
* You need a template engine which does HTML just as well as Emails
* You don't like the markup of your current one
== What does it look like?
<ul id="products">
{% for product in products %}
<li>
<h2>{{product.name}}</h2>
Only {{product.price | price }}
{{product.description | prettyprint | paragraph }}
</li>
{% endfor %}
</ul>
== Howto use Liquid
Liquid supports a very simple API based around the Liquid::Template class.
For standard use you can just pass it the content of a file and call render with a parameters hash.
@template = Liquid::Template.parse("hi {{name}}") # Parses and compiles the template
@template.render( 'name' => 'tobi' ) # => "hi tobi"

24
vendor/plugins/liquid/Rakefile vendored Executable file
View File

@ -0,0 +1,24 @@
#!/usr/bin/env ruby
require 'rubygems'
require 'rake'
require 'hoe'
PKG_VERSION = "1.9.0"
PKG_NAME = "liquid"
PKG_DESC = "A secure non evaling end user template engine with aesthetic markup."
Rake::TestTask.new(:test) do |t|
t.libs << "lib"
t.libs << "test"
t.pattern = 'test/*_test.rb'
t.verbose = false
end
Hoe.new(PKG_NAME, PKG_VERSION) do |p|
p.rubyforge_name = PKG_NAME
p.summary = PKG_DESC
p.description = PKG_DESC
p.author = "Tobias Luetke"
p.email = "tobi@leetsoft.com"
p.url = "http://www.liquidmarkup.org"
end

View File

@ -0,0 +1,37 @@
module ProductsFilter
def price(integer)
sprintf("$%.2d USD", integer / 100.0)
end
def prettyprint(text)
text.gsub( /\*(.*)\*/, '<b>\1</b>' )
end
def count(array)
array.size
end
def paragraph(p)
"<p>#{p}</p>"
end
end
class Servlet < LiquidServlet
def index
{ 'date' => Time.now }
end
def products
{ 'products' => products_list, 'section' => 'Snowboards', 'cool_products' => true}
end
private
def products_list
[{'name' => 'Arbor Draft', 'price' => 39900, 'description' => 'the *arbor draft* is a excellent product' },
{'name' => 'Arbor Element', 'price' => 40000, 'description' => 'the *arbor element* rocks for freestyling'},
{'name' => 'Arbor Diamond', 'price' => 59900, 'description' => 'the *arbor diamond* is a made up product because im obsessed with arbor and have no creativity'}]
end
end

View File

@ -0,0 +1,28 @@
class LiquidServlet < WEBrick::HTTPServlet::AbstractServlet
def do_GET(req, res)
handle(:get, req, res)
end
def do_POST(req, res)
handle(:post, req, res)
end
private
def handle(type, req, res)
@request, @response = req, res
@request.path_info =~ /(\w+)$/
@action = $1 || 'index'
@assigns = send(@action) if respond_to?(@action)
@response['Content-Type'] = "text/html"
@response.status = 200
@response.body = Liquid::Template.parse(read_template).render(@assigns, :filters => [ProductsFilter])
end
def read_template(filename = @action)
File.read( File.dirname(__FILE__) + "/templates/#{filename}.liquid" )
end
end

View File

@ -0,0 +1,12 @@
require 'webrick'
require 'rexml/document'
require File.dirname(__FILE__) + '/../../lib/liquid'
require File.dirname(__FILE__) + '/liquid_servlet'
require File.dirname(__FILE__) + '/example_servlet'
# Setup webrick
server = WEBrick::HTTPServer.new( :Port => ARGV[1] || 3000 )
server.mount('/', Servlet)
trap("INT"){ server.shutdown }
server.start

View File

@ -0,0 +1,6 @@
<p>Hello world!</p>
<p>It is {{date}}</p>
<p>Check out the <a href="http://localhost:3000/products">Products</a> screen </p>

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="en-us" />
<title>products</title>
<meta name="ROBOTS" content="ALL" />
<meta http-equiv="imagetoolbar" content="no" />
<meta name="MSSmartTagsPreventParsing" content="true" />
<meta name="Copyright" content="(c) 2005 Copyright content: Copyright design: Tobias Luetke" />
<!-- (c) Copyright 2005 by Tobias Luetke All Rights Reserved. -->
</head>
<body>
<h1>There are currently {{products | count}} products in the {{section}} catalog</h1>
{% if cool_products %}
Cool products :)
{% else %}
Uncool products :(
{% endif %}
<ul id="products">
{% for product in products %}
<li>
<h2>{{product.name}}</h2>
Only {{product.price | price }}
{{product.description | prettyprint | paragraph }}
{{ 'it rocks!' | paragraph }}
</li>
{% endfor %}
</ul>
</body>
</html>

8
vendor/plugins/liquid/init.rb vendored Normal file
View File

@ -0,0 +1,8 @@
require 'liquid'
require 'extras/liquid_view'
if defined? ActionView::Template and ActionView::Template.respond_to? :register_template_handler
ActionView::Template
else
ActionView::Base
end.register_template_handler(:liquid, LiquidView)

View File

@ -0,0 +1,51 @@
# LiquidView is a action view extension class. You can register it with rails
# and use liquid as an template system for .liquid files
#
# Example
#
# ActionView::Base::register_template_handler :liquid, LiquidView
class LiquidView
PROTECTED_ASSIGNS = %w( template_root response _session template_class action_name request_origin session template
_response url _request _cookies variables_added _flash params _headers request cookies
ignore_missing_templates flash _params logger before_filter_chain_aborted headers )
PROTECTED_INSTANCE_VARIABLES = %w( @_request @controller @_first_render @_memoized__pick_template @view_paths
@helpers @assigns_added @template @_render_stack @template_format @assigns )
def self.call(template)
"LiquidView.new(self).render(template, local_assigns)"
end
def initialize(view)
@view = view
end
def render(template, local_assigns = nil)
@view.controller.headers["Content-Type"] ||= 'text/html; charset=utf-8'
# Rails 2.2 Template has source, but not locals
if template.respond_to?(:source) && !template.respond_to?(:locals)
assigns = (@view.instance_variables - PROTECTED_INSTANCE_VARIABLES).inject({}) do |hash, ivar|
hash[ivar[1..-1]] = @view.instance_variable_get(ivar)
hash
end
else
assigns = @view.assigns.reject{ |k,v| PROTECTED_ASSIGNS.include?(k) }
end
source = template.respond_to?(:source) ? template.source : template
local_assigns = (template.respond_to?(:locals) ? template.locals : local_assigns) || {}
if content_for_layout = @view.instance_variable_get("@content_for_layout")
assigns['content_for_layout'] = content_for_layout
end
assigns.merge!(local_assigns.stringify_keys)
liquid = Liquid::Template.parse(source)
liquid.render(assigns, :filters => [@view.controller.master_helper_module], :registers => {:action_view => @view, :controller => @view.controller})
end
def compilable?
false
end
end

68
vendor/plugins/liquid/lib/liquid.rb vendored Normal file
View File

@ -0,0 +1,68 @@
# Copyright (c) 2005 Tobias Luetke
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
$LOAD_PATH.unshift(File.dirname(__FILE__))
module Liquid
FilterSeparator = /\|/
ArgumentSeparator = ','
FilterArgumentSeparator = ':'
VariableAttributeSeparator = '.'
TagStart = /\{\%/
TagEnd = /\%\}/
VariableSignature = /\(?[\w\-\.\[\]]\)?/
VariableSegment = /[\w\-]\??/
VariableStart = /\{\{/
VariableEnd = /\}\}/
VariableIncompleteEnd = /\}\}?/
QuotedString = /"[^"]+"|'[^']+'/
QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/
StrictQuotedFragment = /"[^"]+"|'[^']+'|[^\s,\|,\:,\,]+/
FirstFilterArgument = /#{FilterArgumentSeparator}(?:#{StrictQuotedFragment})/
OtherFilterArgument = /#{ArgumentSeparator}(?:#{StrictQuotedFragment})/
SpacelessFilter = /#{FilterSeparator}(?:#{StrictQuotedFragment})(?:#{FirstFilterArgument}(?:#{OtherFilterArgument})*)?/
Expression = /(?:#{QuotedFragment}(?:#{SpacelessFilter})*)/
TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/
AnyStartingTag = /\{\{|\{\%/
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/
VariableParser = /\[[^\]]+\]|#{VariableSegment}+/
end
require 'liquid/drop'
require 'liquid/extensions'
require 'liquid/errors'
require 'liquid/strainer'
require 'liquid/context'
require 'liquid/tag'
require 'liquid/block'
require 'liquid/document'
require 'liquid/variable'
require 'liquid/file_system'
require 'liquid/template'
require 'liquid/htmltags'
require 'liquid/standardfilters'
require 'liquid/condition'
require 'liquid/module_ex'
# Load all the tags of the standard library
#
Dir[File.dirname(__FILE__) + '/liquid/tags/*.rb'].each { |f| require f }

View File

@ -0,0 +1,97 @@
module Liquid
class Block < Tag
def parse(tokens)
@nodelist ||= []
@nodelist.clear
while token = tokens.shift
case token
when /^#{TagStart}/
if token =~ /^#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}$/
# if we found the proper block delimitor just end parsing here and let the outer block
# proceed
if block_delimiter == $1
end_tag
return
end
# fetch the tag from registered blocks
if tag = Template.tags[$1]
@nodelist << tag.new($1, $2, tokens)
else
# this tag is not registered with the system
# pass it to the current block for special handling or error reporting
unknown_tag($1, $2, tokens)
end
else
raise SyntaxError, "Tag '#{token}' was not properly terminated with regexp: #{TagEnd.inspect} "
end
when /^#{VariableStart}/
@nodelist << create_variable(token)
when ''
# pass
else
@nodelist << token
end
end
# Make sure that its ok to end parsing in the current block.
# Effectively this method will throw and exception unless the current block is
# of type Document
assert_missing_delimitation!
end
def end_tag
end
def unknown_tag(tag, params, tokens)
case tag
when 'else'
raise SyntaxError, "#{block_name} tag does not expect else tag"
when 'end'
raise SyntaxError, "'end' is not a valid delimiter for #{block_name} tags. use #{block_delimiter}"
else
raise SyntaxError, "Unknown tag '#{tag}'"
end
end
def block_delimiter
"end#{block_name}"
end
def block_name
@tag_name
end
def create_variable(token)
token.scan(/^#{VariableStart}(.*)#{VariableEnd}$/) do |content|
return Variable.new(content.first)
end
raise SyntaxError.new("Variable '#{token}' was not properly terminated with regexp: #{VariableEnd.inspect} ")
end
def render(context)
render_all(@nodelist, context)
end
protected
def assert_missing_delimitation!
raise SyntaxError.new("#{block_name} tag was never closed")
end
def render_all(list, context)
list.collect do |token|
begin
token.respond_to?(:render) ? token.render(context) : token
rescue Exception => e
context.handle_error(e)
end
end
end
end
end

View File

@ -0,0 +1,120 @@
module Liquid
# Container for liquid nodes which conveniently wraps decision making logic
#
# Example:
#
# c = Condition.new('1', '==', '1')
# c.evaluate #=> true
#
class Condition #:nodoc:
@@operators = {
'==' => lambda { |cond, left, right| cond.send(:equal_variables, left, right) },
'!=' => lambda { |cond, left, right| !cond.send(:equal_variables, left, right) },
'<>' => lambda { |cond, left, right| !cond.send(:equal_variables, left, right) },
'<' => :<,
'>' => :>,
'>=' => :>=,
'<=' => :<=,
'contains' => lambda { |cond, left, right| left.include?(right) },
}
def self.operators
@@operators
end
attr_reader :attachment
attr_accessor :left, :operator, :right
def initialize(left = nil, operator = nil, right = nil)
@left, @operator, @right = left, operator, right
@child_relation = nil
@child_condition = nil
end
def evaluate(context = Context.new)
result = interpret_condition(left, right, operator, context)
case @child_relation
when :or
result || @child_condition.evaluate(context)
when :and
result && @child_condition.evaluate(context)
else
result
end
end
def or(condition)
@child_relation, @child_condition = :or, condition
end
def and(condition)
@child_relation, @child_condition = :and, condition
end
def attach(attachment)
@attachment = attachment
end
def else?
false
end
def inspect
"#<Condition #{[@left, @operator, @right].compact.join(' ')}>"
end
private
def equal_variables(left, right)
if left.is_a?(Symbol)
if right.respond_to?(left)
return right.send(left.to_s)
else
return nil
end
end
if right.is_a?(Symbol)
if left.respond_to?(right)
return left.send(right.to_s)
else
return nil
end
end
left == right
end
def interpret_condition(left, right, op, context)
# If the operator is empty this means that the decision statement is just
# a single variable. We can just poll this variable from the context and
# return this as the result.
return context[left] if op == nil
left, right = context[left], context[right]
operation = self.class.operators[op] || raise(ArgumentError.new("Unknown operator #{op}"))
if operation.respond_to?(:call)
operation.call(self, left, right)
elsif left.respond_to?(operation) and right.respond_to?(operation)
left.send(operation, right)
else
nil
end
end
end
class ElseCondition < Condition
def else?
true
end
def evaluate(context)
true
end
end
end

View File

@ -0,0 +1,232 @@
module Liquid
# Context keeps the variable stack and resolves variables, as well as keywords
#
# context['variable'] = 'testing'
# context['variable'] #=> 'testing'
# context['true'] #=> true
# context['10.2232'] #=> 10.2232
#
# context.stack do
# context['bob'] = 'bobsen'
# end
#
# context['bob'] #=> nil class Context
class Context
attr_reader :scopes
attr_reader :errors, :registers
def initialize(assigns = {}, registers = {}, rethrow_errors = false)
@scopes = [(assigns || {})]
@registers = registers
@errors = []
@rethrow_errors = rethrow_errors
end
def strainer
@strainer ||= Strainer.create(self)
end
# adds filters to this context.
# this does not register the filters with the main Template object. see <tt>Template.register_filter</tt>
# for that
def add_filters(filters)
filters = [filters].flatten.compact
filters.each do |f|
raise ArgumentError, "Expected module but got: #{f.class}" unless f.is_a?(Module)
strainer.extend(f)
end
end
def handle_error(e)
errors.push(e)
raise if @rethrow_errors
case e
when SyntaxError
"Liquid syntax error: #{e.message}"
else
"Liquid error: #{e.message}"
end
end
def invoke(method, *args)
if strainer.respond_to?(method)
strainer.__send__(method, *args)
else
args.first
end
end
# push new local scope on the stack. use <tt>Context#stack</tt> instead
def push
raise StackLevelError, "Nesting too deep" if @scopes.length > 100
@scopes.unshift({})
end
# merge a hash of variables in the current local scope
def merge(new_scopes)
@scopes[0].merge!(new_scopes)
end
# pop from the stack. use <tt>Context#stack</tt> instead
def pop
raise ContextError if @scopes.size == 1
@scopes.shift
end
# pushes a new local scope on the stack, pops it at the end of the block
#
# Example:
#
# context.stack do
# context['var'] = 'hi'
# end
# context['var] #=> nil
#
def stack(&block)
result = nil
push
begin
result = yield
ensure
pop
end
result
end
# Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
def []=(key, value)
@scopes[0][key] = value
end
def [](key)
resolve(key)
end
def has_key?(key)
resolve(key) != nil
end
private
# Look up variable, either resolve directly after considering the name. We can directly handle
# Strings, digits, floats and booleans (true,false). If no match is made we lookup the variable in the current scope and
# later move up to the parent blocks to see if we can resolve the variable somewhere up the tree.
# Some special keywords return symbols. Those symbols are to be called on the rhs object in expressions
#
# Example:
#
# products == empty #=> products.empty?
#
def resolve(key)
case key
when nil, 'nil', 'null', ''
nil
when 'true'
true
when 'false'
false
when 'blank'
:blank?
when 'empty'
:empty?
# filtered variables
when SpacelessFilter
filtered_variable(key)
# Single quoted strings
when /^'(.*)'$/
$1.to_s
# Double quoted strings
when /^"(.*)"$/
$1.to_s
# Integer and floats
when /^(\d+)$/
$1.to_i
# Ranges
when /^\((\S+)\.\.(\S+)\)$/
(resolve($1).to_i..resolve($2).to_i)
# Floats
when /^(\d[\d\.]+)$/
$1.to_f
else
variable(key)
end
end
# fetches an object starting at the local scope and then moving up
# the hierachy
def find_variable(key)
@scopes.each do |scope|
if scope.has_key?(key)
variable = scope[key]
variable = scope[key] = variable.call(self) if variable.is_a?(Proc)
variable = variable.to_liquid
variable.context = self if variable.respond_to?(:context=)
return variable
end
end
nil
end
# resolves namespaced queries gracefully.
#
# Example
#
# @context['hash'] = {"name" => 'tobi'}
# assert_equal 'tobi', @context['hash.name']
# assert_equal 'tobi', @context['hash["name"]']
#
def variable(markup)
parts = markup.scan(VariableParser)
square_bracketed = /^\[(.*)\]$/
first_part = parts.shift
if first_part =~ square_bracketed
first_part = resolve($1)
end
if object = find_variable(first_part)
parts.each do |part|
part = resolve($1) if part_resolved = (part =~ square_bracketed)
# If object is a hash- or array-like object we look for the
# presence of the key and if its available we return it
if object.respond_to?(:[]) and
((object.respond_to?(:has_key?) and object.has_key?(part)) or
(object.respond_to?(:fetch) and part.is_a?(Integer)))
# if its a proc we will replace the entry with the proc
res = object[part]
res = object[part] = res.call(self) if res.is_a?(Proc) and object.respond_to?(:[]=)
object = res.to_liquid
# Some special cases. If the part wasn't in square brackets and
# no key with the same name was found we interpret following calls
# as commands and call them on the current object
elsif !part_resolved and object.respond_to?(part) and ['size', 'first', 'last'].include?(part)
object = object.send(part.intern).to_liquid
# No key was present with the desired value and it wasn't one of the directly supported
# keywords either. The only thing we got left is to return nil
else
return nil
end
# If we are dealing with a drop here we have to
object.context = self if object.respond_to?(:context=)
end
end
object
end
def filtered_variable(markup)
Variable.new(markup).render(self)
end
end
end

View File

@ -0,0 +1,17 @@
module Liquid
class Document < Block
# we don't need markup to open this block
def initialize(tokens)
parse(tokens)
end
# There isn't a real delimter
def block_delimiter
[]
end
# Document blocks don't need to be terminated since they are not actually opened
def assert_missing_delimitation!
end
end
end

View File

@ -0,0 +1,51 @@
module Liquid
# A drop in liquid is a class which allows you to to export DOM like things to liquid
# Methods of drops are callable.
# The main use for liquid drops is the implement lazy loaded objects.
# If you would like to make data available to the web designers which you don't want loaded unless needed then
# a drop is a great way to do that
#
# Example:
#
# class ProductDrop < Liquid::Drop
# def top_sales
# Shop.current.products.find(:all, :order => 'sales', :limit => 10 )
# end
# end
#
# tmpl = Liquid::Template.parse( ' {% for product in product.top_sales %} {{ product.name }} {%endfor%} ' )
# tmpl.render('product' => ProductDrop.new ) # will invoke top_sales query.
#
# Your drop can either implement the methods sans any parameters or implement the before_method(name) method which is a
# catch all
class Drop
attr_writer :context
# Catch all for the method
def before_method(method)
nil
end
# called by liquid to invoke a drop
def invoke_drop(method)
# for backward compatibility with Ruby 1.8
methods = self.class.public_instance_methods.map { |m| m.to_s }
if methods.include?(method.to_s)
send(method.to_sym)
else
before_method(method)
end
end
def has_key?(name)
true
end
def to_liquid
self
end
alias :[] :invoke_drop
end
end

View File

@ -0,0 +1,11 @@
module Liquid
class Error < ::StandardError; end
class ArgumentError < Error; end
class ContextError < Error; end
class FilterNotFound < Error; end
class FileSystemError < Error; end
class StandardError < Error; end
class SyntaxError < Error; end
class StackLevelError < Error; end
end

View File

@ -0,0 +1,56 @@
require 'time'
require 'date'
class String # :nodoc:
def to_liquid
self
end
end
class Array # :nodoc:
def to_liquid
self
end
end
class Hash # :nodoc:
def to_liquid
self
end
end
class Numeric # :nodoc:
def to_liquid
self
end
end
class Time # :nodoc:
def to_liquid
self
end
end
class DateTime < Date # :nodoc:
def to_liquid
self
end
end
class Date # :nodoc:
def to_liquid
self
end
end
def true.to_liquid # :nodoc:
self
end
def false.to_liquid # :nodoc:
self
end
def nil.to_liquid # :nodoc:
self
end

View File

@ -0,0 +1,62 @@
module Liquid
# A Liquid file system is way to let your templates retrieve other templates for use with the include tag.
#
# You can implement subclasses that retrieve templates from the database, from the file system using a different
# path structure, you can provide them as hard-coded inline strings, or any manner that you see fit.
#
# You can add additional instance variables, arguments, or methods as needed.
#
# Example:
#
# Liquid::Template.file_system = Liquid::LocalFileSystem.new(template_path)
# liquid = Liquid::Template.parse(template)
#
# This will parse the template with a LocalFileSystem implementation rooted at 'template_path'.
class BlankFileSystem
# Called by Liquid to retrieve a template file
def read_template_file(template_path)
raise FileSystemError, "This liquid context does not allow includes."
end
end
# This implements an abstract file system which retrieves template files named in a manner similar to Rails partials,
# ie. with the template name prefixed with an underscore. The extension ".liquid" is also added.
#
# For security reasons, template paths are only allowed to contain letters, numbers, and underscore.
#
# Example:
#
# file_system = Liquid::LocalFileSystem.new("/some/path")
#
# file_system.full_path("mypartial") # => "/some/path/_mypartial.liquid"
# file_system.full_path("dir/mypartial") # => "/some/path/dir/_mypartial.liquid"
#
class LocalFileSystem
attr_accessor :root
def initialize(root)
@root = root
end
def read_template_file(template_path)
full_path = full_path(template_path)
raise FileSystemError, "No such template '#{template_path}'" unless File.exists?(full_path)
File.read(full_path)
end
def full_path(template_path)
raise FileSystemError, "Illegal template name '#{template_path}'" unless template_path =~ /^[^.\/][a-zA-Z0-9_\/]+$/
full_path = if template_path.include?('/')
File.join(root, File.dirname(template_path), "_#{File.basename(template_path)}.liquid")
else
File.join(root, "_#{template_path}.liquid")
end
raise FileSystemError, "Illegal template path '#{File.expand_path(full_path)}'" unless File.expand_path(full_path) =~ /^#{File.expand_path(root)}/
full_path
end
end
end

View File

@ -0,0 +1,74 @@
module Liquid
class TableRow < Block
Syntax = /(\w+)\s+in\s+(#{VariableSignature}+)/
def initialize(tag_name, markup, tokens)
if markup =~ Syntax
@variable_name = $1
@collection_name = $2
@attributes = {}
markup.scan(TagAttributes) do |key, value|
@attributes[key] = value
end
else
raise SyntaxError.new("Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3")
end
super
end
def render(context)
collection = context[@collection_name] or return ''
if @attributes['limit'] or @attributes['offset']
limit = context[@attributes['limit']] || -1
offset = context[@attributes['offset']] || 0
collection = collection[offset.to_i..(limit.to_i + offset.to_i - 1)]
end
length = collection.length
cols = context[@attributes['cols']].to_i
row = 1
col = 0
result = ["<tr class=\"row1\">\n"]
context.stack do
collection.each_with_index do |item, index|
context[@variable_name] = item
context['tablerowloop'] = {
'length' => length,
'index' => index + 1,
'index0' => index,
'col' => col + 1,
'col0' => col,
'index0' => index,
'rindex' => length - index,
'rindex0' => length - index -1,
'first' => (index == 0),
'last' => (index == length - 1),
'col_first' => (col == 0),
'col_last' => (col == cols - 1)
}
col += 1
result << ["<td class=\"col#{col}\">"] + render_all(@nodelist, context) + ['</td>']
if col == cols and not (index == length - 1)
col = 0
row += 1
result << ["</tr>\n<tr class=\"row#{row}\">"]
end
end
end
result + ["</tr>\n"]
end
end
Template.register_tag('tablerow', TableRow)
end

View File

@ -0,0 +1,62 @@
# Copyright 2007 by Domizio Demichelis
# This library is free software. It may be used, redistributed and/or modified
# under the same terms as Ruby itself
#
# This extension is usesd in order to expose the object of the implementing class
# to liquid as it were a Drop. It also limits the liquid-callable methods of the instance
# to the allowed method passed with the liquid_methods call
# Example:
#
# class SomeClass
# liquid_methods :an_allowed_method
#
# def an_allowed_method
# 'this comes from an allowed method'
# end
# def unallowed_method
# 'this will never be an output'
# end
# end
#
# if you want to extend the drop to other methods you can defines more methods
# in the class <YourClass>::LiquidDropClass
#
# class SomeClass::LiquidDropClass
# def another_allowed_method
# 'and this from another allowed method'
# end
# end
# end
#
# usage:
# @something = SomeClass.new
#
# template:
# {{something.an_allowed_method}}{{something.unallowed_method}} {{something.another_allowed_method}}
#
# output:
# 'this comes from an allowed method and this from another allowed method'
#
# You can also chain associations, by adding the liquid_method call in the
# association models.
#
class Module
def liquid_methods(*allowed_methods)
drop_class = eval "class #{self.to_s}::LiquidDropClass < Liquid::Drop; self; end"
define_method :to_liquid do
drop_class.new(self)
end
drop_class.class_eval do
def initialize(object)
@object = object
end
allowed_methods.each do |sym|
define_method sym do
@object.send sym
end
end
end
end
end

View File

@ -0,0 +1,209 @@
require 'cgi'
module Liquid
module StandardFilters
# Return the size of an array or of an string
def size(input)
input.respond_to?(:size) ? input.size : 0
end
# convert a input string to DOWNCASE
def downcase(input)
input.to_s.downcase
end
# convert a input string to UPCASE
def upcase(input)
input.to_s.upcase
end
# capitalize words in the input centence
def capitalize(input)
input.to_s.capitalize
end
def escape(input)
CGI.escapeHTML(input) rescue input
end
alias_method :h, :escape
# Truncate a string down to x characters
def truncate(input, length = 50, truncate_string = "...")
if input.nil? then return end
l = length.to_i - truncate_string.length
l = 0 if l < 0
input.length > length.to_i ? input[0...l] + truncate_string : input
end
def truncatewords(input, words = 15, truncate_string = "...")
if input.nil? then return end
wordlist = input.to_s.split
l = words.to_i - 1
l = 0 if l < 0
wordlist.length > l ? wordlist[0..l].join(" ") + truncate_string : input
end
def strip_html(input)
input.to_s.gsub(/<.*?>/, '')
end
# Remove all newlines from the string
def strip_newlines(input)
input.to_s.gsub(/\n/, '')
end
# Join elements of the array with certain character between them
def join(input, glue = ' ')
[input].flatten.join(glue)
end
# Sort elements of the array
# provide optional property with which to sort an array of hashes or drops
def sort(input, property = nil)
ary = [input].flatten
if property.nil?
ary.sort
elsif ary.first.respond_to?('[]') and !ary.first[property].nil?
ary.sort {|a,b| a[property] <=> b[property] }
elsif ary.first.respond_to?(property)
ary.sort {|a,b| a.send(property) <=> b.send(property) }
end
end
# map/collect on a given property
def map(input, property)
ary = [input].flatten
if ary.first.respond_to?('[]') and !ary.first[property].nil?
ary.map {|e| e[property] }
elsif ary.first.respond_to?(property)
ary.map {|e| e.send(property) }
end
end
# Replace occurrences of a string with another
def replace(input, string, replacement = '')
input.to_s.gsub(string, replacement)
end
# Replace the first occurrences of a string with another
def replace_first(input, string, replacement = '')
input.to_s.sub(string, replacement)
end
# remove a substring
def remove(input, string)
input.to_s.gsub(string, '')
end
# remove the first occurrences of a substring
def remove_first(input, string)
input.to_s.sub(string, '')
end
# add one string to another
def append(input, string)
input.to_s + string.to_s
end
# prepend a string to another
def prepend(input, string)
string.to_s + input.to_s
end
# Add <br /> tags in front of all newlines in input string
def newline_to_br(input)
input.to_s.gsub(/\n/, "<br />\n")
end
# Reformat a date
#
# %a - The abbreviated weekday name (``Sun'')
# %A - The full weekday name (``Sunday'')
# %b - The abbreviated month name (``Jan'')
# %B - The full month name (``January'')
# %c - The preferred local date and time representation
# %d - Day of the month (01..31)
# %H - Hour of the day, 24-hour clock (00..23)
# %I - Hour of the day, 12-hour clock (01..12)
# %j - Day of the year (001..366)
# %m - Month of the year (01..12)
# %M - Minute of the hour (00..59)
# %p - Meridian indicator (``AM'' or ``PM'')
# %S - Second of the minute (00..60)
# %U - Week number of the current year,
# starting with the first Sunday as the first
# day of the first week (00..53)
# %W - Week number of the current year,
# starting with the first Monday as the first
# day of the first week (00..53)
# %w - Day of the week (Sunday is 0, 0..6)
# %x - Preferred representation for the date alone, no time
# %X - Preferred representation for the time alone, no date
# %y - Year without a century (00..99)
# %Y - Year with century
# %Z - Time zone name
# %% - Literal ``%'' character
def date(input, format)
if format.to_s.empty?
return input.to_s
end
date = input.is_a?(String) ? Time.parse(input) : input
if date.respond_to?(:strftime)
date.strftime(format.to_s)
else
input
end
rescue => e
input
end
# Get the first element of the passed in array
#
# Example:
# {{ product.images | first | to_img }}
#
def first(array)
array.first if array.respond_to?(:first)
end
# Get the last element of the passed in array
#
# Example:
# {{ product.images | last | to_img }}
#
def last(array)
array.last if array.respond_to?(:last)
end
# addition
def plus(input, operand)
input + operand if input.respond_to?('+')
end
# subtraction
def minus(input, operand)
input - operand if input.respond_to?('-')
end
# multiplication
def times(input, operand)
input * operand if input.respond_to?('*')
end
# division
def divided_by(input, operand)
input / operand if input.respond_to?('/')
end
end
Template.register_filter(StandardFilters)
end

View File

@ -0,0 +1,51 @@
require 'set'
module Liquid
parent_object = if defined? BlankObject
BlankObject
else
Object
end
# Strainer is the parent class for the filters system.
# New filters are mixed into the strainer class which is then instanciated for each liquid template render run.
#
# One of the strainer's responsibilities is to keep malicious method calls out
class Strainer < parent_object #:nodoc:
INTERNAL_METHOD = /^__/
@@required_methods = Set.new([:__id__, :__send__, :respond_to?, :extend, :methods, :class, :object_id])
@@filters = {}
def initialize(context)
@context = context
end
def self.global_filter(filter)
raise ArgumentError, "Passed filter is not a module" unless filter.is_a?(Module)
@@filters[filter.name] = filter
end
def self.create(context)
strainer = Strainer.new(context)
@@filters.each { |k,m| strainer.extend(m) }
strainer
end
def respond_to?(method, include_private = false)
method_name = method.to_s
return false if method_name =~ INTERNAL_METHOD
return false if @@required_methods.include?(method_name)
super
end
# remove all standard methods from the bucket so circumvent security
# problems
instance_methods.each do |m|
unless @@required_methods.include?(m.to_sym)
undef_method m
end
end
end
end

26
vendor/plugins/liquid/lib/liquid/tag.rb vendored Normal file
View File

@ -0,0 +1,26 @@
module Liquid
class Tag
attr_accessor :nodelist
def initialize(tag_name, markup, tokens)
@tag_name = tag_name
@markup = markup
parse(tokens)
end
def parse(tokens)
end
def name
self.class.name.downcase
end
def render(context)
''
end
end
end

View File

@ -0,0 +1,33 @@
module Liquid
# Assign sets a variable in your template.
#
# {% assign foo = 'monkey' %}
#
# You can then use the variable later in the page.
#
# {{ monkey }}
#
class Assign < Tag
Syntax = /(#{VariableSignature}+)\s*=\s*(#{Expression}+)/
def initialize(tag_name, markup, tokens)
if markup =~ Syntax
@to = $1
@from = $2
else
raise SyntaxError.new("Syntax Error in 'assign' - Valid syntax: assign [var] = [source]")
end
super
end
def render(context)
context.scopes.last[@to.to_s] = context[@from]
''
end
end
Template.register_tag('assign', Assign)
end

View File

@ -0,0 +1,35 @@
module Liquid
# Capture stores the result of a block into a variable without rendering it inplace.
#
# {% capture heading %}
# Monkeys!
# {% endcapture %}
# ...
# <h1>{{ monkeys }}</h1>
#
# Capture is useful for saving content for use later in your template, such as
# in a sidebar or footer.
#
class Capture < Block
Syntax = /(\w+)/
def initialize(tag_name, markup, tokens)
if markup =~ Syntax
@to = $1
else
raise SyntaxError.new("Syntax Error in 'capture' - Valid syntax: capture [var]")
end
super
end
def render(context)
output = super
context[@to] = output.join
''
end
end
Template.register_tag('capture', Capture)
end

View File

@ -0,0 +1,83 @@
module Liquid
class Case < Block
Syntax = /(#{Expression})/
WhenSyntax = /(#{Expression})(?:(?:\s+or\s+|\s*\,\s*)(#{Expression}.*))?/
def initialize(tag_name, markup, tokens)
@blocks = []
if markup =~ Syntax
@left = $1
else
raise SyntaxError.new("Syntax Error in tag 'case' - Valid syntax: case [condition]")
end
super
end
def unknown_tag(tag, markup, tokens)
@nodelist = []
case tag
when 'when'
record_when_condition(markup)
when 'else'
record_else_condition(markup)
else
super
end
end
def render(context)
context.stack do
execute_else_block = true
@blocks.inject([]) do |output, block|
if block.else?
return render_all(block.attachment, context) if execute_else_block
elsif block.evaluate(context)
execute_else_block = false
output += render_all(block.attachment, context)
end
output
end
end
end
private
def record_when_condition(markup)
while markup
# Create a new nodelist and assign it to the new block
if not markup =~ WhenSyntax
raise SyntaxError.new("Syntax Error in tag 'case' - Valid when condition: {% when [condition] [or condition2...] %} ")
end
markup = $2
block = Condition.new(@left, '==', $1)
block.attach(@nodelist)
@blocks.push(block)
end
end
def record_else_condition(markup)
if not markup.strip.empty?
raise SyntaxError.new("Syntax Error in tag 'case' - Valid else condition: {% else %} (no parameters) ")
end
block = ElseCondition.new
block.attach(@nodelist)
@blocks << block
end
end
Template.register_tag('case', Case)
end

View File

@ -0,0 +1,9 @@
module Liquid
class Comment < Block
def render(context)
''
end
end
Template.register_tag('comment', Comment)
end

View File

@ -0,0 +1,59 @@
module Liquid
# Cycle is usually used within a loop to alternate between values, like colors or DOM classes.
#
# {% for item in items %}
# <div class="{% cycle 'red', 'green', 'blue' %}"> {{ item }} </div>
# {% end %}
#
# <div class="red"> Item one </div>
# <div class="green"> Item two </div>
# <div class="blue"> Item three </div>
# <div class="red"> Item four </div>
# <div class="green"> Item five</div>
#
class Cycle < Tag
SimpleSyntax = /^#{Expression}/
NamedSyntax = /^(#{Expression})\s*\:\s*(.*)/
def initialize(tag_name, markup, tokens)
case markup
when NamedSyntax
@variables = variables_from_string($2)
@name = $1
when SimpleSyntax
@variables = variables_from_string(markup)
@name = "'#{@variables.to_s}'"
else
raise SyntaxError.new("Syntax Error in 'cycle' - Valid syntax: cycle [name :] var [, var2, var3 ...]")
end
super
end
def render(context)
context.registers[:cycle] ||= Hash.new(0)
context.stack do
key = context[@name]
iteration = context.registers[:cycle][key]
result = context[@variables[iteration]]
iteration += 1
iteration = 0 if iteration >= @variables.size
context.registers[:cycle][key] = iteration
result
end
end
private
def variables_from_string(markup)
markup.split(',').collect do |var|
var =~ /\s*(#{Expression})\s*/
$1 ? $1 : nil
end.compact
end
end
Template.register_tag('cycle', Cycle)
end

View File

@ -0,0 +1,136 @@
module Liquid
# "For" iterates over an array or collection.
# Several useful variables are available to you within the loop.
#
# == Basic usage:
# {% for item in collection %}
# {{ forloop.index }}: {{ item.name }}
# {% endfor %}
#
# == Advanced usage:
# {% for item in collection %}
# <div {% if forloop.first %}class="first"{% endif %}>
# Item {{ forloop.index }}: {{ item.name }}
# </div>
# {% endfor %}
#
# You can also define a limit and offset much like SQL. Remember
# that offset starts at 0 for the first item.
#
# {% for item in collection limit:5 offset:10 %}
# {{ item.name }}
# {% end %}
#
# To reverse the for loop simply use {% for item in collection reversed %}
#
# == Available variables:
#
# forloop.name:: 'item-collection'
# forloop.length:: Length of the loop
# forloop.index:: The current item's position in the collection;
# forloop.index starts at 1.
# This is helpful for non-programmers who start believe
# the first item in an array is 1, not 0.
# forloop.index0:: The current item's position in the collection
# where the first item is 0
# forloop.rindex:: Number of items remaining in the loop
# (length - index) where 1 is the last item.
# forloop.rindex0:: Number of items remaining in the loop
# where 0 is the last item.
# forloop.first:: Returns true if the item is the first item.
# forloop.last:: Returns true if the item is the last item.
#
class For < Block
Syntax = /(\w+)\s+in\s+(#{Expression}+)\s*(reversed)?/
def initialize(tag_name, markup, tokens)
if markup =~ Syntax
@variable_name = $1
@collection_name = $2
@name = "#{$1}-#{$2}"
@reversed = $3
@attributes = {}
markup.scan(TagAttributes) do |key, value|
@attributes[key] = value
end
else
raise SyntaxError.new("Syntax Error in 'for loop' - Valid syntax: for [item] in [collection]")
end
super
end
def render(context)
context.registers[:for] ||= Hash.new(0)
collection = context[@collection_name]
collection = collection.to_a if collection.is_a?(Range)
return '' unless collection.respond_to?(:each)
from = if @attributes['offset'] == 'continue'
context.registers[:for][@name].to_i
else
context[@attributes['offset']].to_i
end
limit = context[@attributes['limit']]
to = limit ? limit.to_i + from : nil
segment = slice_collection_using_each(collection, from, to)
return '' if segment.empty?
segment.reverse! if @reversed
result = []
length = segment.length
# Store our progress through the collection for the continue flag
context.registers[:for][@name] = from + segment.length
context.stack do
segment.each_with_index do |item, index|
context[@variable_name] = item
context['forloop'] = {
'name' => @name,
'length' => length,
'index' => index + 1,
'index0' => index,
'rindex' => length - index,
'rindex0' => length - index -1,
'first' => (index == 0),
'last' => (index == length - 1) }
result << render_all(@nodelist, context)
end
end
result
end
def slice_collection_using_each(collection, from, to)
segments = []
index = 0
yielded = 0
collection.each do |item|
if to && to <= index
break
end
if from <= index
segments << item
end
index += 1
end
segments
end
end
Template.register_tag('for', For)
end

View File

@ -0,0 +1,79 @@
module Liquid
# If is the conditional block
#
# {% if user.admin %}
# Admin user!
# {% else %}
# Not admin user
# {% endif %}
#
# There are {% if count < 5 %} less {% else %} more {% endif %} items than you need.
#
#
class If < Block
SyntaxHelp = "Syntax Error in tag 'if' - Valid syntax: if [expression]"
Syntax = /(#{Expression})\s*([=!<>a-z_]+)?\s*(#{Expression})?/
def initialize(tag_name, markup, tokens)
@blocks = []
push_block('if', markup)
super
end
def unknown_tag(tag, markup, tokens)
if ['elsif', 'else'].include?(tag)
push_block(tag, markup)
else
super
end
end
def render(context)
context.stack do
@blocks.each do |block|
if block.evaluate(context)
return render_all(block.attachment, context)
end
end
''
end
end
private
def push_block(tag, markup)
block = if tag == 'else'
ElseCondition.new
else
expressions = markup.split(/\b(and|or)\b/).reverse
raise(SyntaxError, SyntaxHelp) unless expressions.shift =~ Syntax
condition = Condition.new($1, $2, $3)
while not expressions.empty?
operator = expressions.shift
raise(SyntaxError, SyntaxHelp) unless expressions.shift.to_s =~ Syntax
new_condition = Condition.new($1, $2, $3)
new_condition.send(operator.to_sym, condition)
condition = new_condition
end
condition
end
@blocks.push(block)
@nodelist = block.attach(Array.new)
end
end
Template.register_tag('if', If)
end

View File

@ -0,0 +1,20 @@
module Liquid
class Ifchanged < Block
def render(context)
context.stack do
output = render_all(@nodelist, context)
if output != context.registers[:ifchanged]
context.registers[:ifchanged] = output
output
else
''
end
end
end
end
Template.register_tag('ifchanged', Ifchanged)
end

View File

@ -0,0 +1,55 @@
module Liquid
class Include < Tag
Syntax = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?/
def initialize(tag_name, markup, tokens)
if markup =~ Syntax
@template_name = $1
@variable_name = $3
@attributes = {}
markup.scan(TagAttributes) do |key, value|
@attributes[key] = value
end
else
raise SyntaxError.new("Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]")
end
super
end
def parse(tokens)
end
def render(context)
source = Liquid::Template.file_system.read_template_file(context[@template_name])
partial = Liquid::Template.parse(source)
variable = context[@variable_name || @template_name[1..-2]]
context.stack do
@attributes.each do |key, value|
context[key] = context[value]
end
if variable.is_a?(Array)
variable.collect do |variable|
context[@template_name[1..-2]] = variable
partial.render(context)
end
else
context[@template_name[1..-2]] = variable
partial.render(context)
end
end
end
end
Template.register_tag('include', Include)
end

View File

@ -0,0 +1,33 @@
require File.dirname(__FILE__) + '/if'
module Liquid
# Unless is a conditional just like 'if' but works on the inverse logic.
#
# {% unless x < 0 %} x is greater than zero {% end %}
#
class Unless < If
def render(context)
context.stack do
# First condition is interpreted backwards ( if not )
block = @blocks.first
unless block.evaluate(context)
return render_all(block.attachment, context)
end
# After the first condition unless works just like if
@blocks[1..-1].each do |block|
if block.evaluate(context)
return render_all(block.attachment, context)
end
end
''
end
end
end
Template.register_tag('unless', Unless)
end

View File

@ -0,0 +1,146 @@
module Liquid
# Templates are central to liquid.
# Interpretating templates is a two step process. First you compile the
# source code you got. During compile time some extensive error checking is performed.
# your code should expect to get some SyntaxErrors.
#
# After you have a compiled template you can then <tt>render</tt> it.
# You can use a compiled template over and over again and keep it cached.
#
# Example:
#
# template = Liquid::Template.parse(source)
# template.render('user_name' => 'bob')
#
class Template
attr_accessor :root
@@file_system = BlankFileSystem.new
class << self
def file_system
@@file_system
end
def file_system=(obj)
@@file_system = obj
end
def register_tag(name, klass)
tags[name.to_s] = klass
end
def tags
@tags ||= {}
end
# Pass a module with filter methods which should be available
# to all liquid views. Good for registering the standard library
def register_filter(mod)
Strainer.global_filter(mod)
end
# creates a new <tt>Template</tt> object from liquid source code
def parse(source)
template = Template.new
template.parse(source)
template
end
end
# creates a new <tt>Template</tt> from an array of tokens. Use <tt>Template.parse</tt> instead
def initialize
end
# Parse source code.
# Returns self for easy chaining
def parse(source)
@root = Document.new(tokenize(source))
self
end
def registers
@registers ||= {}
end
def assigns
@assigns ||= {}
end
def errors
@errors ||= []
end
# Render takes a hash with local variables.
#
# if you use the same filters over and over again consider registering them globally
# with <tt>Template.register_filter</tt>
#
# Following options can be passed:
#
# * <tt>filters</tt> : array with local filters
# * <tt>registers</tt> : hash with register variables. Those can be accessed from
# filters and tags and might be useful to integrate liquid more with its host application
#
def render(*args)
return '' if @root.nil?
context = case args.first
when Liquid::Context
args.shift
when Hash
self.assigns.merge!(args.shift)
Context.new(assigns, registers, @rethrow_errors)
when nil
Context.new(assigns, registers, @rethrow_errors)
else
raise ArgumentError, "Expect Hash or Liquid::Context as parameter"
end
case args.last
when Hash
options = args.pop
if options[:registers].is_a?(Hash)
self.registers.merge!(options[:registers])
end
if options[:filters]
context.add_filters(options[:filters])
end
when Module
context.add_filters(args.pop)
when Array
context.add_filters(args.pop)
end
begin
# render the nodelist.
# for performance reasons we get a array back here. join will make a string out of it
@root.render(context).join
ensure
@errors = context.errors
end
end
def render!(*args)
@rethrow_errors = true; render(*args)
end
private
# Uses the <tt>Liquid::TemplateParser</tt> regexp to tokenize the passed source
def tokenize(source)
source = source.source if source.respond_to?(:source)
return [] if source.to_s.empty?
tokens = source.split(TemplateParser)
# removes the rogue empty element at the beginning of the array
tokens.shift if tokens[0] and tokens[0].empty?
tokens
end
end
end

View File

@ -0,0 +1,49 @@
module Liquid
# Holds variables. Variables are only loaded "just in time"
# and are not evaluated as part of the render stage
#
# {{ monkey }}
# {{ user.name }}
#
# Variables can be combined with filters:
#
# {{ user | link }}
#
class Variable
attr_accessor :filters, :name
def initialize(markup)
@markup = markup
@name = nil
@filters = []
if match = markup.match(/\s*(#{QuotedFragment})/)
@name = match[1]
if markup.match(/#{FilterSeparator}\s*(.*)/)
filters = Regexp.last_match(1).split(/#{FilterSeparator}/)
filters.each do |f|
if matches = f.match(/\s*(\w+)/)
filtername = matches[1]
filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*(#{QuotedFragment})/).flatten
@filters << [filtername.to_sym, filterargs]
end
end
end
end
end
def render(context)
return '' if @name.nil?
@filters.inject(context[@name]) do |output, filter|
filterargs = filter[1].to_a.collect do |a|
context[a]
end
begin
output = context.invoke(filter[0], output, *filterargs)
rescue FilterNotFound
raise FilterNotFound, "Error - filter '#{filter[0]}' in '#{@markup.strip}' could not be found."
end
end
end
end
end

29
vendor/plugins/liquid/liquid.gemspec vendored Normal file
View File

@ -0,0 +1,29 @@
Gem::Specification.new do |s|
s.name = %q{liquid}
s.version = "2.0.1"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Tobias Luetke"]
s.date = %q{2009-04-13}
s.description = %q{A secure non evaling end user template engine with aesthetic markup.}
s.email = %q{tobi@leetsoft.com}
s.extra_rdoc_files = ["History.txt", "Manifest.txt", "README.txt"]
s.files = ["CHANGELOG", "History.txt", "MIT-LICENSE", "Manifest.txt", "README.txt", "Rakefile", "lib/extras/liquid_view.rb", "lib/liquid.rb", "lib/liquid/block.rb", "lib/liquid/condition.rb", "lib/liquid/context.rb", "lib/liquid/document.rb", "lib/liquid/drop.rb", "lib/liquid/errors.rb", "lib/liquid/extensions.rb", "lib/liquid/file_system.rb", "lib/liquid/htmltags.rb", "lib/liquid/module_ex.rb", "lib/liquid/standardfilters.rb", "lib/liquid/strainer.rb", "lib/liquid/tag.rb", "lib/liquid/tags/assign.rb", "lib/liquid/tags/capture.rb", "lib/liquid/tags/case.rb", "lib/liquid/tags/comment.rb", "lib/liquid/tags/cycle.rb", "lib/liquid/tags/for.rb", "lib/liquid/tags/if.rb", "lib/liquid/tags/ifchanged.rb", "lib/liquid/tags/include.rb", "lib/liquid/tags/unless.rb", "lib/liquid/template.rb", "lib/liquid/variable.rb"]
s.has_rdoc = true
s.homepage = %q{http://www.liquidmarkup.org}
s.rdoc_options = ["--main", "README.txt"]
s.require_paths = ["lib"]
s.rubyforge_project = %q{liquid}
s.rubygems_version = %q{1.3.1}
s.summary = %q{A secure non evaling end user template engine with aesthetic markup.}
if s.respond_to? :specification_version then
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
s.specification_version = 2
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
else
end
else
end
end

View File

@ -0,0 +1,11 @@
require File.dirname(__FILE__) + '/helper'
class IfElseTest < Test::Unit::TestCase
include Liquid
def test_with_filtered_expressions
assert_template_result('foo','{% assign foo = values|sort|last %}{{ foo }}', 'values' => %w{foo bar baz})
assert_template_result('foo','{% assign sorted = values|sort %}{{ sorted | last }}', 'values' => %w{foo bar baz})
end
end

View File

@ -0,0 +1,58 @@
require File.dirname(__FILE__) + '/helper'
class VariableTest < Test::Unit::TestCase
include Liquid
def test_blankspace
template = Liquid::Template.parse(" ")
assert_equal [" "], template.root.nodelist
end
def test_variable_beginning
template = Liquid::Template.parse("{{funk}} ")
assert_equal 2, template.root.nodelist.size
assert_equal Variable, template.root.nodelist[0].class
assert_equal String, template.root.nodelist[1].class
end
def test_variable_end
template = Liquid::Template.parse(" {{funk}}")
assert_equal 2, template.root.nodelist.size
assert_equal String, template.root.nodelist[0].class
assert_equal Variable, template.root.nodelist[1].class
end
def test_variable_middle
template = Liquid::Template.parse(" {{funk}} ")
assert_equal 3, template.root.nodelist.size
assert_equal String, template.root.nodelist[0].class
assert_equal Variable, template.root.nodelist[1].class
assert_equal String, template.root.nodelist[2].class
end
def test_variable_many_embedded_fragments
template = Liquid::Template.parse(" {{funk}} {{so}} {{brother}} ")
assert_equal 7, template.root.nodelist.size
assert_equal [String, Variable, String, Variable, String, Variable, String], block_types(template.root.nodelist)
end
def test_with_block
template = Liquid::Template.parse(" {% comment %} {% endcomment %} ")
assert_equal [String, Comment, String], block_types(template.root.nodelist)
assert_equal 3, template.root.nodelist.size
end
def test_with_custom_tag
Liquid::Template.register_tag("testtag", Block)
assert_nothing_thrown do
template = Liquid::Template.parse( "{% testtag %} {% endtesttag %}")
end
end
private
def block_types(nodelist)
nodelist.collect { |node| node.class }
end
end

View File

@ -0,0 +1,109 @@
require File.dirname(__FILE__) + '/helper'
class ConditionTest < Test::Unit::TestCase
include Liquid
def test_basic_condition
assert_equal false, Condition.new('1', '==', '2').evaluate
assert_equal true, Condition.new('1', '==', '1').evaluate
end
def test_default_operators_evalute_true
assert_evalutes_true '1', '==', '1'
assert_evalutes_true '1', '!=', '2'
assert_evalutes_true '1', '<>', '2'
assert_evalutes_true '1', '<', '2'
assert_evalutes_true '2', '>', '1'
assert_evalutes_true '1', '>=', '1'
assert_evalutes_true '2', '>=', '1'
assert_evalutes_true '1', '<=', '2'
assert_evalutes_true '1', '<=', '1'
end
def test_default_operators_evalute_false
assert_evalutes_false '1', '==', '2'
assert_evalutes_false '1', '!=', '1'
assert_evalutes_false '1', '<>', '1'
assert_evalutes_false '1', '<', '0'
assert_evalutes_false '2', '>', '4'
assert_evalutes_false '1', '>=', '3'
assert_evalutes_false '2', '>=', '4'
assert_evalutes_false '1', '<=', '0'
assert_evalutes_false '1', '<=', '0'
end
def test_contains_works_on_strings
assert_evalutes_true "'bob'", 'contains', "'o'"
assert_evalutes_true "'bob'", 'contains', "'b'"
assert_evalutes_true "'bob'", 'contains', "'bo'"
assert_evalutes_true "'bob'", 'contains', "'ob'"
assert_evalutes_true "'bob'", 'contains', "'bob'"
assert_evalutes_false "'bob'", 'contains', "'bob2'"
assert_evalutes_false "'bob'", 'contains', "'a'"
assert_evalutes_false "'bob'", 'contains', "'---'"
end
def test_contains_works_on_arrays
@context = Liquid::Context.new
@context['array'] = [1,2,3,4,5]
assert_evalutes_false "array", 'contains', '0'
assert_evalutes_true "array", 'contains', '1'
assert_evalutes_true "array", 'contains', '2'
assert_evalutes_true "array", 'contains', '3'
assert_evalutes_true "array", 'contains', '4'
assert_evalutes_true "array", 'contains', '5'
assert_evalutes_false "array", 'contains', '6'
assert_evalutes_false "array", 'contains', '"1"'
end
def test_or_condition
condition = Condition.new('1', '==', '2')
assert_equal false, condition.evaluate
condition.or Condition.new('2', '==', '1')
assert_equal false, condition.evaluate
condition.or Condition.new('1', '==', '1')
assert_equal true, condition.evaluate
end
def test_and_condition
condition = Condition.new('1', '==', '1')
assert_equal true, condition.evaluate
condition.and Condition.new('2', '==', '2')
assert_equal true, condition.evaluate
condition.and Condition.new('2', '==', '1')
assert_equal false, condition.evaluate
end
def test_should_allow_custom_proc_operator
Condition.operators['starts_with'] = Proc.new { |cond, left, right| left =~ %r{^#{right}}}
assert_evalutes_true "'bob'", 'starts_with', "'b'"
assert_evalutes_false "'bob'", 'starts_with', "'o'"
ensure
Condition.operators.delete 'starts_with'
end
private
def assert_evalutes_true(left, op, right)
assert Condition.new(left, op, right).evaluate(@context || Liquid::Context.new), "Evaluated false: #{left} #{op} #{right}"
end
def assert_evalutes_false(left, op, right)
assert !Condition.new(left, op, right).evaluate(@context || Liquid::Context.new), "Evaluated true: #{left} #{op} #{right}"
end
end

View File

@ -0,0 +1,480 @@
require File.dirname(__FILE__) + '/helper'
class HundredCentes
def to_liquid
100
end
end
class CentsDrop < Liquid::Drop
def amount
HundredCentes.new
end
def non_zero?
true
end
end
class ContextSensitiveDrop < Liquid::Drop
def test
@context['test']
end
end
class Category < Liquid::Drop
attr_accessor :name
def initialize(name)
@name = name
end
def to_liquid
CategoryDrop.new(self)
end
end
class CategoryDrop
attr_accessor :category, :context
def initialize(category)
@category = category
end
end
class CounterDrop < Liquid::Drop
def count
@count ||= 0
@count += 1
end
end
class ArrayLike
def fetch(index)
end
def [](index)
@counts ||= []
@counts[index] ||= 0
@counts[index] += 1
end
def to_liquid
self
end
end
class ContextTest < Test::Unit::TestCase
include Liquid
def setup
@template = Liquid::Template.new
@context = Liquid::Context.new(@template.assigns, @template.registers)
end
def test_variables
@context['string'] = 'string'
assert_equal 'string', @context['string']
@context['num'] = 5
assert_equal 5, @context['num']
@context['time'] = Time.parse('2006-06-06 12:00:00')
assert_equal Time.parse('2006-06-06 12:00:00'), @context['time']
@context['date'] = Date.today
assert_equal Date.today, @context['date']
now = DateTime.now
@context['datetime'] = now
assert_equal now, @context['datetime']
@context['bool'] = true
assert_equal true, @context['bool']
@context['bool'] = false
assert_equal false, @context['bool']
@context['nil'] = nil
assert_equal nil, @context['nil']
assert_equal nil, @context['nil']
end
def test_variables_not_existing
assert_equal nil, @context['does_not_exist']
end
def test_scoping
assert_nothing_raised do
@context.push
@context.pop
end
assert_raise(Liquid::ContextError) do
@context.pop
end
assert_raise(Liquid::ContextError) do
@context.push
@context.pop
@context.pop
end
end
def test_length_query
@context['numbers'] = [1,2,3,4]
assert_equal 4, @context['numbers.size']
@context['numbers'] = {1 => 1,2 => 2,3 => 3,4 => 4}
assert_equal 4, @context['numbers.size']
@context['numbers'] = {1 => 1,2 => 2,3 => 3,4 => 4, 'size' => 1000}
assert_equal 1000, @context['numbers.size']
end
def test_hyphenated_variable
@context['oh-my'] = 'godz'
assert_equal 'godz', @context['oh-my']
end
def test_add_filter
filter = Module.new do
def hi(output)
output + ' hi!'
end
end
context = Context.new(@template)
context.add_filters(filter)
assert_equal 'hi? hi!', context.invoke(:hi, 'hi?')
context = Context.new(@template)
assert_equal 'hi?', context.invoke(:hi, 'hi?')
context.add_filters(filter)
assert_equal 'hi? hi!', context.invoke(:hi, 'hi?')
end
def test_override_global_filter
global = Module.new do
def notice(output)
"Global #{output}"
end
end
local = Module.new do
def notice(output)
"Local #{output}"
end
end
Template.register_filter(global)
assert_equal 'Global test', Template.parse("{{'test' | notice }}").render
assert_equal 'Local test', Template.parse("{{'test' | notice }}").render({}, :filters => [local])
end
def test_only_intended_filters_make_it_there
filter = Module.new do
def hi(output)
output + ' hi!'
end
end
context = Context.new(@template)
methods_before = context.strainer.methods.map { |method| method.to_s }
context.add_filters(filter)
methods_after = context.strainer.methods.map { |method| method.to_s }
assert_equal (methods_before + ["hi"]).sort, methods_after.sort
end
def test_add_item_in_outer_scope
@context['test'] = 'test'
@context.push
assert_equal 'test', @context['test']
@context.pop
assert_equal 'test', @context['test']
end
def test_add_item_in_inner_scope
@context.push
@context['test'] = 'test'
assert_equal 'test', @context['test']
@context.pop
assert_equal nil, @context['test']
end
def test_hierachical_data
@context['hash'] = {"name" => 'tobi'}
assert_equal 'tobi', @context['hash.name']
assert_equal 'tobi', @context['hash["name"]']
end
def test_keywords
assert_equal true, @context['true']
assert_equal false, @context['false']
end
def test_digits
assert_equal 100, @context['100']
assert_equal 100.00, @context['100.00']
end
def test_strings
assert_equal "hello!", @context['"hello!"']
assert_equal "hello!", @context["'hello!'"]
end
def test_merge
@context.merge({ "test" => "test" })
assert_equal 'test', @context['test']
@context.merge({ "test" => "newvalue", "foo" => "bar" })
assert_equal 'newvalue', @context['test']
assert_equal 'bar', @context['foo']
end
def test_array_notation
@context['test'] = [1,2,3,4,5]
assert_equal 1, @context['test[0]']
assert_equal 2, @context['test[1]']
assert_equal 3, @context['test[2]']
assert_equal 4, @context['test[3]']
assert_equal 5, @context['test[4]']
end
def test_recoursive_array_notation
@context['test'] = {'test' => [1,2,3,4,5]}
assert_equal 1, @context['test.test[0]']
@context['test'] = [{'test' => 'worked'}]
assert_equal 'worked', @context['test[0].test']
end
def test_hash_to_array_transition
@context['colors'] = {
'Blue' => ['003366','336699', '6699CC', '99CCFF'],
'Green' => ['003300','336633', '669966', '99CC99'],
'Yellow' => ['CC9900','FFCC00', 'FFFF99', 'FFFFCC'],
'Red' => ['660000','993333', 'CC6666', 'FF9999']
}
assert_equal '003366', @context['colors.Blue[0]']
assert_equal 'FF9999', @context['colors.Red[3]']
end
def test_try_first
@context['test'] = [1,2,3,4,5]
assert_equal 1, @context['test.first']
assert_equal 5, @context['test.last']
@context['test'] = {'test' => [1,2,3,4,5]}
assert_equal 1, @context['test.test.first']
assert_equal 5, @context['test.test.last']
@context['test'] = [1]
assert_equal 1, @context['test.first']
assert_equal 1, @context['test.last']
end
def test_access_hashes_with_hash_notation
@context['products'] = {'count' => 5, 'tags' => ['deepsnow', 'freestyle'] }
@context['product'] = {'variants' => [ {'title' => 'draft151cm'}, {'title' => 'element151cm'} ]}
assert_equal 5, @context['products["count"]']
assert_equal 'deepsnow', @context['products["tags"][0]']
assert_equal 'deepsnow', @context['products["tags"].first']
assert_equal 'draft151cm', @context['product["variants"][0]["title"]']
assert_equal 'element151cm', @context['product["variants"][1]["title"]']
assert_equal 'draft151cm', @context['product["variants"][0]["title"]']
assert_equal 'element151cm', @context['product["variants"].last["title"]']
end
def test_access_variable_with_hash_notation
@context['foo'] = 'baz'
@context['bar'] = 'foo'
assert_equal 'baz', @context['["foo"]']
assert_equal 'baz', @context['[bar]']
end
def test_access_hashes_with_hash_access_variables
@context['var'] = 'tags'
@context['nested'] = {'var' => 'tags'}
@context['products'] = {'count' => 5, 'tags' => ['deepsnow', 'freestyle'] }
assert_equal 'deepsnow', @context['products[var].first']
assert_equal 'freestyle', @context['products[nested.var].last']
end
def test_hash_notation_only_for_hash_access
@context['array'] = [1,2,3,4,5]
@context['hash'] = {'first' => 'Hello'}
assert_equal 1, @context['array.first']
assert_equal nil, @context['array["first"]']
assert_equal 'Hello', @context['hash["first"]']
end
def test_first_can_appear_in_middle_of_callchain
@context['product'] = {'variants' => [ {'title' => 'draft151cm'}, {'title' => 'element151cm'} ]}
assert_equal 'draft151cm', @context['product.variants[0].title']
assert_equal 'element151cm', @context['product.variants[1].title']
assert_equal 'draft151cm', @context['product.variants.first.title']
assert_equal 'element151cm', @context['product.variants.last.title']
end
def test_cents
@context.merge( "cents" => HundredCentes.new )
assert_equal 100, @context['cents']
end
def test_nested_cents
@context.merge( "cents" => { 'amount' => HundredCentes.new} )
assert_equal 100, @context['cents.amount']
@context.merge( "cents" => { 'cents' => { 'amount' => HundredCentes.new} } )
assert_equal 100, @context['cents.cents.amount']
end
def test_cents_through_drop
@context.merge( "cents" => CentsDrop.new )
assert_equal 100, @context['cents.amount']
end
def test_nested_cents_through_drop
@context.merge( "vars" => {"cents" => CentsDrop.new} )
assert_equal 100, @context['vars.cents.amount']
end
def test_drop_methods_with_question_marks
@context.merge( "cents" => CentsDrop.new )
assert @context['cents.non_zero?']
end
def test_context_from_within_drop
@context.merge( "test" => '123', "vars" => ContextSensitiveDrop.new )
assert_equal '123', @context['vars.test']
end
def test_nested_context_from_within_drop
@context.merge( "test" => '123', "vars" => {"local" => ContextSensitiveDrop.new } )
assert_equal '123', @context['vars.local.test']
end
def test_ranges
@context.merge( "test" => '5' )
assert_equal (1..5), @context['(1..5)']
assert_equal (1..5), @context['(1..test)']
assert_equal (5..5), @context['(test..test)']
end
def test_cents_through_drop_nestedly
@context.merge( "cents" => {"cents" => CentsDrop.new} )
assert_equal 100, @context['cents.cents.amount']
@context.merge( "cents" => { "cents" => {"cents" => CentsDrop.new}} )
assert_equal 100, @context['cents.cents.cents.amount']
end
def test_drop_with_variable_called_only_once
@context['counter'] = CounterDrop.new
assert_equal 1, @context['counter.count']
assert_equal 2, @context['counter.count']
assert_equal 3, @context['counter.count']
end
def test_drop_with_key_called_only_once
@context['counter'] = CounterDrop.new
assert_equal 1, @context['counter["count"]']
assert_equal 2, @context['counter["count"]']
assert_equal 3, @context['counter["count"]']
end
def test_proc_as_variable
@context['dynamic'] = Proc.new { 'Hello' }
assert_equal 'Hello', @context['dynamic']
end
def test_lambda_as_variable
@context['dynamic'] = proc { 'Hello' }
assert_equal 'Hello', @context['dynamic']
end
def test_nested_lambda_as_variable
@context['dynamic'] = { "lambda" => proc { 'Hello' } }
assert_equal 'Hello', @context['dynamic.lambda']
end
def test_array_containing_lambda_as_variable
@context['dynamic'] = [1,2, proc { 'Hello' } ,4,5]
assert_equal 'Hello', @context['dynamic[2]']
end
def test_lambda_is_called_once
@context['callcount'] = proc { @global ||= 0; @global += 1; @global.to_s }
assert_equal '1', @context['callcount']
assert_equal '1', @context['callcount']
assert_equal '1', @context['callcount']
@global = nil
end
def test_nested_lambda_is_called_once
@context['callcount'] = { "lambda" => proc { @global ||= 0; @global += 1; @global.to_s } }
assert_equal '1', @context['callcount.lambda']
assert_equal '1', @context['callcount.lambda']
assert_equal '1', @context['callcount.lambda']
@global = nil
end
def test_lambda_in_array_is_called_once
@context['callcount'] = [1,2, proc { @global ||= 0; @global += 1; @global.to_s } ,4,5]
assert_equal '1', @context['callcount[2]']
assert_equal '1', @context['callcount[2]']
assert_equal '1', @context['callcount[2]']
@global = nil
end
def test_access_to_context_from_proc
@context.registers[:magic] = 345392
@context['magic'] = proc { @context.registers[:magic] }
assert_equal 345392, @context['magic']
end
def test_to_liquid_and_context_at_first_level
@context['category'] = Category.new("foobar")
assert_kind_of CategoryDrop, @context['category']
assert_equal @context, @context['category'].context
end
end

162
vendor/plugins/liquid/test/drop_test.rb vendored Normal file
View File

@ -0,0 +1,162 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/helper'
class ContextDrop < Liquid::Drop
def scopes
@context.scopes.size
end
def scopes_as_array
(1..@context.scopes.size).to_a
end
def loop_pos
@context['forloop.index']
end
def break
Breakpoint.breakpoint
end
def before_method(method)
return @context[method]
end
end
class ProductDrop < Liquid::Drop
class TextDrop < Liquid::Drop
def array
['text1', 'text2']
end
def text
'text1'
end
end
class CatchallDrop < Liquid::Drop
def before_method(method)
return 'method: ' << method
end
end
def texts
TextDrop.new
end
def catchall
CatchallDrop.new
end
def context
ContextDrop.new
end
protected
def callmenot
"protected"
end
end
class EnumerableDrop < Liquid::Drop
def size
3
end
def each
yield 1
yield 2
yield 3
end
end
class DropsTest < Test::Unit::TestCase
include Liquid
def test_product_drop
assert_nothing_raised do
tpl = Liquid::Template.parse( ' ' )
tpl.render('product' => ProductDrop.new)
end
end
def test_text_drop
output = Liquid::Template.parse( ' {{ product.texts.text }} ' ).render('product' => ProductDrop.new)
assert_equal ' text1 ', output
end
def test_text_drop
output = Liquid::Template.parse( ' {{ product.catchall.unknown }} ' ).render('product' => ProductDrop.new)
assert_equal ' method: unknown ', output
end
def test_text_array_drop
output = Liquid::Template.parse( '{% for text in product.texts.array %} {{text}} {% endfor %}' ).render('product' => ProductDrop.new)
assert_equal ' text1 text2 ', output
end
def test_context_drop
output = Liquid::Template.parse( ' {{ context.bar }} ' ).render('context' => ContextDrop.new, 'bar' => "carrot")
assert_equal ' carrot ', output
end
def test_nested_context_drop
output = Liquid::Template.parse( ' {{ product.context.foo }} ' ).render('product' => ProductDrop.new, 'foo' => "monkey")
assert_equal ' monkey ', output
end
def test_protected
output = Liquid::Template.parse( ' {{ product.callmenot }} ' ).render('product' => ProductDrop.new)
assert_equal ' ', output
end
def test_scope
assert_equal '1', Liquid::Template.parse( '{{ context.scopes }}' ).render('context' => ContextDrop.new)
assert_equal '2', Liquid::Template.parse( '{%for i in dummy%}{{ context.scopes }}{%endfor%}' ).render('context' => ContextDrop.new, 'dummy' => [1])
assert_equal '3', Liquid::Template.parse( '{%for i in dummy%}{%for i in dummy%}{{ context.scopes }}{%endfor%}{%endfor%}' ).render('context' => ContextDrop.new, 'dummy' => [1])
end
def test_scope_though_proc
assert_equal '1', Liquid::Template.parse( '{{ s }}' ).render('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] })
assert_equal '2', Liquid::Template.parse( '{%for i in dummy%}{{ s }}{%endfor%}' ).render('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] }, 'dummy' => [1])
assert_equal '3', Liquid::Template.parse( '{%for i in dummy%}{%for i in dummy%}{{ s }}{%endfor%}{%endfor%}' ).render('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] }, 'dummy' => [1])
end
def test_scope_with_assigns
assert_equal 'variable', Liquid::Template.parse( '{% assign a = "variable"%}{{a}}' ).render('context' => ContextDrop.new)
assert_equal 'variable', Liquid::Template.parse( '{% assign a = "variable"%}{%for i in dummy%}{{a}}{%endfor%}' ).render('context' => ContextDrop.new, 'dummy' => [1])
assert_equal 'test', Liquid::Template.parse( '{% assign header_gif = "test"%}{{header_gif}}' ).render('context' => ContextDrop.new)
assert_equal 'test', Liquid::Template.parse( "{% assign header_gif = 'test'%}{{header_gif}}" ).render('context' => ContextDrop.new)
end
def test_scope_from_tags
assert_equal '1', Liquid::Template.parse( '{% for i in context.scopes_as_array %}{{i}}{% endfor %}' ).render('context' => ContextDrop.new, 'dummy' => [1])
assert_equal '12', Liquid::Template.parse( '{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}' ).render('context' => ContextDrop.new, 'dummy' => [1])
assert_equal '123', Liquid::Template.parse( '{%for a in dummy%}{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}{% endfor %}' ).render('context' => ContextDrop.new, 'dummy' => [1])
end
def test_access_context_from_drop
assert_equal '123', Liquid::Template.parse( '{%for a in dummy%}{{ context.loop_pos }}{% endfor %}' ).render('context' => ContextDrop.new, 'dummy' => [1,2,3])
end
def test_enumerable_drop
assert_equal '123', Liquid::Template.parse( '{% for c in collection %}{{c}}{% endfor %}').render('collection' => EnumerableDrop.new)
end
def test_enumerable_drop_size
assert_equal '3', Liquid::Template.parse( '{{collection.size}}').render('collection' => EnumerableDrop.new)
end
end

View File

@ -0,0 +1,89 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/helper'
class ErrorDrop < Liquid::Drop
def standard_error
raise Liquid::StandardError, 'standard error'
end
def argument_error
raise Liquid::ArgumentError, 'argument error'
end
def syntax_error
raise Liquid::SyntaxError, 'syntax error'
end
end
class ErrorHandlingTest < Test::Unit::TestCase
include Liquid
def test_standard_error
assert_nothing_raised do
template = Liquid::Template.parse( ' {{ errors.standard_error }} ' )
assert_equal ' Liquid error: standard error ', template.render('errors' => ErrorDrop.new)
assert_equal 1, template.errors.size
assert_equal StandardError, template.errors.first.class
end
end
def test_syntax
assert_nothing_raised do
template = Liquid::Template.parse( ' {{ errors.syntax_error }} ' )
assert_equal ' Liquid syntax error: syntax error ', template.render('errors' => ErrorDrop.new)
assert_equal 1, template.errors.size
assert_equal SyntaxError, template.errors.first.class
end
end
def test_argument
assert_nothing_raised do
template = Liquid::Template.parse( ' {{ errors.argument_error }} ' )
assert_equal ' Liquid error: argument error ', template.render('errors' => ErrorDrop.new)
assert_equal 1, template.errors.size
assert_equal ArgumentError, template.errors.first.class
end
end
def test_missing_endtag_parse_time_error
assert_raise(Liquid::SyntaxError) do
template = Liquid::Template.parse(' {% for a in b %} ... ')
end
end
def test_unrecognized_operator
assert_nothing_raised do
template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ')
assert_equal ' Liquid error: Unknown operator =! ', template.render
assert_equal 1, template.errors.size
assert_equal Liquid::ArgumentError, template.errors.first.class
end
end
end

547
vendor/plugins/liquid/test/extra/breakpoint.rb vendored Executable file
View File

@ -0,0 +1,547 @@
# The Breakpoint library provides the convenience of
# being able to inspect and modify state, diagnose
# bugs all via IRB by simply setting breakpoints in
# your applications by the call of a method.
#
# This library was written and is supported by me,
# Florian Gross. I can be reached at flgr@ccan.de
# and enjoy getting feedback about my libraries.
#
# The whole library (including breakpoint_client.rb
# and binding_of_caller.rb) is licensed under the
# same license that Ruby uses. (Which is currently
# either the GNU General Public License or a custom
# one that allows for commercial usage.) If you for
# some good reason need to use this under another
# license please contact me.
require 'irb'
require 'caller'
require 'drb'
require 'drb/acl'
require 'thread'
module Breakpoint
id = %q$Id: breakpoint.rb 52 2005-02-26 19:43:19Z flgr $
current_version = id.split(" ")[2]
unless defined?(Version)
# The Version of ruby-breakpoint you are using as String of the
# 1.2.3 form where the digits stand for release, major and minor
# version respectively.
Version = "0.5.0"
end
extend self
# This will pop up an interactive ruby session at a
# pre-defined break point in a Ruby application. In
# this session you can examine the environment of
# the break point.
#
# You can get a list of variables in the context using
# local_variables via +local_variables+. You can then
# examine their values by typing their names.
#
# You can have a look at the call stack via +caller+.
#
# The source code around the location where the breakpoint
# was executed can be examined via +source_lines+. Its
# argument specifies how much lines of context to display.
# The default amount of context is 5 lines. Note that
# the call to +source_lines+ can raise an exception when
# it isn't able to read in the source code.
#
# breakpoints can also return a value. They will execute
# a supplied block for getting a default return value.
# A custom value can be returned from the session by doing
# +throw(:debug_return, value)+.
#
# You can also give names to break points which will be
# used in the message that is displayed upon execution
# of them.
#
# Here's a sample of how breakpoints should be placed:
#
# class Person
# def initialize(name, age)
# @name, @age = name, age
# breakpoint("Person#initialize")
# end
#
# attr_reader :age
# def name
# breakpoint("Person#name") { @name }
# end
# end
#
# person = Person.new("Random Person", 23)
# puts "Name: #{person.name}"
#
# And here is a sample debug session:
#
# Executing break point "Person#initialize" at file.rb:4 in `initialize'
# irb(#<Person:0x292fbe8>):001:0> local_variables
# => ["name", "age", "_", "__"]
# irb(#<Person:0x292fbe8>):002:0> [name, age]
# => ["Random Person", 23]
# irb(#<Person:0x292fbe8>):003:0> [@name, @age]
# => ["Random Person", 23]
# irb(#<Person:0x292fbe8>):004:0> self
# => #<Person:0x292fbe8 @age=23, @name="Random Person">
# irb(#<Person:0x292fbe8>):005:0> @age += 1; self
# => #<Person:0x292fbe8 @age=24, @name="Random Person">
# irb(#<Person:0x292fbe8>):006:0> exit
# Executing break point "Person#name" at file.rb:9 in `name'
# irb(#<Person:0x292fbe8>):001:0> throw(:debug_return, "Overriden name")
# Name: Overriden name
#
# Breakpoint sessions will automatically have a few
# convenience methods available. See Breakpoint::CommandBundle
# for a list of them.
#
# Breakpoints can also be used remotely over sockets.
# This is implemented by running part of the IRB session
# in the application and part of it in a special client.
# You have to call Breakpoint.activate_drb to enable
# support for remote breakpoints and then run
# breakpoint_client.rb which is distributed with this
# library. See the documentation of Breakpoint.activate_drb
# for details.
def breakpoint(id = nil, context = nil, &block)
callstack = caller
callstack.slice!(0, 3) if callstack.first["breakpoint"]
file, line, method = *callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/).captures
message = "Executing break point " + (id ? "#{id.inspect} " : "") +
"at #{file}:#{line}" + (method ? " in `#{method}'" : "")
if context then
return handle_breakpoint(context, message, file, line, &block)
end
Binding.of_caller do |binding_context|
handle_breakpoint(binding_context, message, file, line, &block)
end
end
# These commands are automatically available in all breakpoint shells.
module CommandBundle
# Proxy to a Breakpoint client. Lets you directly execute code
# in the context of the client.
class Client
def initialize(eval_handler) # :nodoc:
eval_handler.untaint
@eval_handler = eval_handler
end
instance_methods.each do |method|
next if method[/^__.+__$/]
undef_method method
end
# Executes the specified code at the client.
def eval(code)
@eval_handler.call(code)
end
# Will execute the specified statement at the client.
def method_missing(method, *args, &block)
if args.empty? and not block
result = eval "#{method}"
else
# This is a bit ugly. The alternative would be using an
# eval context instead of an eval handler for executing
# the code at the client. The problem with that approach
# is that we would have to handle special expressions
# like "self", "nil" or constants ourself which is hard.
remote = eval %{
result = lambda { |block, *args| #{method}(*args, &block) }
def result.call_with_block(*args, &block)
call(block, *args)
end
result
}
remote.call_with_block(*args, &block)
end
return result
end
end
# Returns the source code surrounding the location where the
# breakpoint was issued.
def source_lines(context = 5, return_line_numbers = false)
lines = File.readlines(@__bp_file).map { |line| line.chomp }
break_line = @__bp_line
start_line = [break_line - context, 1].max
end_line = break_line + context
result = lines[(start_line - 1) .. (end_line - 1)]
if return_line_numbers then
return [start_line, break_line, result]
else
return result
end
end
# Lets an object that will forward method calls to the breakpoint
# client. This is useful for outputting longer things at the client
# and so on. You can for example do these things:
#
# client.puts "Hello" # outputs "Hello" at client console
# # outputs "Hello" into the file temp.txt at the client
# client.File.open("temp.txt", "w") { |f| f.puts "Hello" }
def client()
if Breakpoint.use_drb? then
sleep(0.5) until Breakpoint.drb_service.eval_handler
Client.new(Breakpoint.drb_service.eval_handler)
else
Client.new(lambda { |code| eval(code, TOPLEVEL_BINDING) })
end
end
end
def handle_breakpoint(context, message, file = "", line = "", &block) # :nodoc:
catch(:debug_return) do |value|
eval(%{
@__bp_file = #{file.inspect}
@__bp_line = #{line}
extend Breakpoint::CommandBundle
extend DRbUndumped if self
}, context) rescue nil
if not use_drb? then
puts message
IRB.start(nil, IRB::WorkSpace.new(context))
else
@drb_service.add_breakpoint(context, message)
end
block.call if block
end
end
# These exceptions will be raised on failed asserts
# if Breakpoint.asserts_cause_exceptions is set to
# true.
class FailedAssertError < RuntimeError
end
# This asserts that the block evaluates to true.
# If it doesn't evaluate to true a breakpoint will
# automatically be created at that execution point.
#
# You can disable assert checking in production
# code by setting Breakpoint.optimize_asserts to
# true. (It will still be enabled when Ruby is run
# via the -d argument.)
#
# Example:
# person_name = "Foobar"
# assert { not person_name.nil? }
#
# Note: If you want to use this method from an
# unit test, you will have to call it by its full
# name, Breakpoint.assert.
def assert(context = nil, &condition)
return if Breakpoint.optimize_asserts and not $DEBUG
return if yield
callstack = caller
callstack.slice!(0, 3) if callstack.first["assert"]
file, line, method = *callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/).captures
message = "Assert failed at #{file}:#{line}#{" in `#{method}'" if method}."
if Breakpoint.asserts_cause_exceptions and not $DEBUG then
raise(Breakpoint::FailedAssertError, message)
end
message += " Executing implicit breakpoint."
if context then
return handle_breakpoint(context, message, file, line)
end
Binding.of_caller do |context|
handle_breakpoint(context, message, file, line)
end
end
# Whether asserts should be ignored if not in debug mode.
# Debug mode can be enabled by running ruby with the -d
# switch or by setting $DEBUG to true.
attr_accessor :optimize_asserts
self.optimize_asserts = false
# Whether an Exception should be raised on failed asserts
# in non-$DEBUG code or not. By default this is disabled.
attr_accessor :asserts_cause_exceptions
self.asserts_cause_exceptions = false
@use_drb = false
attr_reader :drb_service # :nodoc:
class DRbService # :nodoc:
include DRbUndumped
def initialize
@handler = @eval_handler = @collision_handler = nil
IRB.instance_eval { @CONF[:RC] = true }
IRB.run_config
end
def collision
sleep(0.5) until @collision_handler
@collision_handler.untaint
@collision_handler.call
end
def ping() end
def add_breakpoint(context, message)
workspace = IRB::WorkSpace.new(context)
workspace.extend(DRbUndumped)
sleep(0.5) until @handler
@handler.untaint
@handler.call(workspace, message)
rescue Errno::ECONNREFUSED, DRb::DRbConnError
raise if Breakpoint.use_drb?
end
attr_accessor :handler, :eval_handler, :collision_handler
end
# Will run Breakpoint in DRb mode. This will spawn a server
# that can be attached to via the breakpoint-client command
# whenever a breakpoint is executed. This is useful when you
# are debugging CGI applications or other applications where
# you can't access debug sessions via the standard input and
# output of your application.
#
# You can specify an URI where the DRb server will run at.
# This way you can specify the port the server runs on. The
# default URI is druby://localhost:42531.
#
# Please note that breakpoints will be skipped silently in
# case the DRb server can not spawned. (This can happen if
# the port is already used by another instance of your
# application on CGI or another application.)
#
# Also note that by default this will only allow access
# from localhost. You can however specify a list of
# allowed hosts or nil (to allow access from everywhere).
# But that will still not protect you from somebody
# reading the data as it goes through the net.
#
# A good approach for getting security and remote access
# is setting up an SSH tunnel between the DRb service
# and the client. This is usually done like this:
#
# $ ssh -L20000:127.0.0.1:20000 -R10000:127.0.0.1:10000 example.com
# (This will connect port 20000 at the client side to port
# 20000 at the server side, and port 10000 at the server
# side to port 10000 at the client side.)
#
# After that do this on the server side: (the code being debugged)
# Breakpoint.activate_drb("druby://127.0.0.1:20000", "localhost")
#
# And at the client side:
# ruby breakpoint_client.rb -c druby://127.0.0.1:10000 -s druby://127.0.0.1:20000
#
# Running through such a SSH proxy will also let you use
# breakpoint.rb in case you are behind a firewall.
#
# Detailed information about running DRb through firewalls is
# available at http://www.rubygarden.org/ruby?DrbTutorial
#
# == Security considerations
# Usually you will be fine when using the default druby:// URI and the default
# access control list. However, if you are sitting on a machine where there are
# local users that you likely can not trust (this is the case for example on
# most web hosts which have multiple users sitting on the same physical machine)
# you will be better off by doing client/server communication through a unix
# socket. This can be accomplished by calling with a drbunix:/ style URI, e.g.
# <code>Breakpoint.activate_drb('drbunix:/tmp/breakpoint_server')</code>. This
# will only work on Unix based platforms.
def activate_drb(uri = nil, allowed_hosts = ['localhost', '127.0.0.1', '::1'],
ignore_collisions = false)
return false if @use_drb
uri ||= 'druby://localhost:42531'
if allowed_hosts then
acl = ["deny", "all"]
Array(allowed_hosts).each do |host|
acl += ["allow", host]
end
DRb.install_acl(ACL.new(acl))
end
@use_drb = true
@drb_service = DRbService.new
did_collision = false
begin
@service = DRb.start_service(uri, @drb_service)
rescue Errno::EADDRINUSE
if ignore_collisions then
nil
else
# The port is already occupied by another
# Breakpoint service. We will try to tell
# the old service that we want its port.
# It will then forward that request to the
# user and retry.
unless did_collision then
DRbObject.new(nil, uri).collision
did_collision = true
end
sleep(10)
retry
end
end
return true
end
# Deactivates a running Breakpoint service.
def deactivate_drb
Thread.exclusive do
@service.stop_service unless @service.nil?
@service = nil
@use_drb = false
@drb_service = nil
end
end
# Returns true when Breakpoints are used over DRb.
# Breakpoint.activate_drb causes this to be true.
def use_drb?
@use_drb == true
end
end
module IRB # :nodoc:
class << self; remove_method :start; end
def self.start(ap_path = nil, main_context = nil, workspace = nil)
$0 = File::basename(ap_path, ".rb") if ap_path
# suppress some warnings about redefined constants
old_verbose, $VERBOSE = $VERBOSE, nil
IRB.setup(ap_path)
$VERBOSE = old_verbose
if @CONF[:SCRIPT] then
irb = Irb.new(main_context, @CONF[:SCRIPT])
else
irb = Irb.new(main_context)
end
if workspace then
irb.context.workspace = workspace
end
@CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC]
@CONF[:MAIN_CONTEXT] = irb.context
old_sigint = trap("SIGINT") do
begin
irb.signal_handle
rescue RubyLex::TerminateLineInput
# ignored
end
end
catch(:IRB_EXIT) do
irb.eval_input
end
ensure
trap("SIGINT", old_sigint)
end
class << self
alias :old_CurrentContext :CurrentContext
remove_method :CurrentContext
remove_method :parse_opts
end
def IRB.CurrentContext
if old_CurrentContext.nil? and Breakpoint.use_drb? then
result = Object.new
def result.last_value; end
return result
else
old_CurrentContext
end
end
def IRB.parse_opts() end
class Context # :nodoc:
alias :old_evaluate :evaluate
def evaluate(line, line_no)
if line.chomp == "exit" then
exit
else
old_evaluate(line, line_no)
end
end
end
class WorkSpace # :nodoc:
alias :old_evaluate :evaluate
def evaluate(*args)
if Breakpoint.use_drb? then
result = old_evaluate(*args)
if args[0] != :no_proxy and
not [true, false, nil].include?(result)
then
result.extend(DRbUndumped) rescue nil
end
return result
else
old_evaluate(*args)
end
end
end
module InputCompletor # :nodoc:
def self.eval(code, context, *more)
# Big hack, this assumes that InputCompletor
# will only call eval() when it wants code
# to be executed in the IRB context.
IRB.conf[:MAIN_CONTEXT].workspace.evaluate(:no_proxy, code, *more)
end
end
end
module DRb # :nodoc:
class DRbObject # :nodoc:
undef :inspect if method_defined?(:inspect)
undef :clone if method_defined?(:clone)
end
end
# See Breakpoint.breakpoint
def breakpoint(id = nil, &block)
Binding.of_caller do |context|
Breakpoint.breakpoint(id, context, &block)
end
end
# See Breakpoint.assert
def assert(&block)
Binding.of_caller do |context|
Breakpoint.assert(context, &block)
end
end

80
vendor/plugins/liquid/test/extra/caller.rb vendored Executable file
View File

@ -0,0 +1,80 @@
class Continuation # :nodoc:
def self.create(*args, &block) # :nodoc:
cc = nil; result = callcc {|c| cc = c; block.call(cc) if block and args.empty?}
result ||= args
return *[cc, *result]
end
end
class Binding; end # for RDoc
# This method returns the binding of the method that called your
# method. It will raise an Exception when you're not inside a method.
#
# It's used like this:
# def inc_counter(amount = 1)
# Binding.of_caller do |binding|
# # Create a lambda that will increase the variable 'counter'
# # in the caller of this method when called.
# inc = eval("lambda { |arg| counter += arg }", binding)
# # We can refer to amount from inside this block safely.
# inc.call(amount)
# end
# # No other statements can go here. Put them inside the block.
# end
# counter = 0
# 2.times { inc_counter }
# counter # => 2
#
# Binding.of_caller must be the last statement in the method.
# This means that you will have to put everything you want to
# do after the call to Binding.of_caller into the block of it.
# This should be no problem however, because Ruby has closures.
# If you don't do this an Exception will be raised. Because of
# the way that Binding.of_caller is implemented it has to be
# done this way.
def Binding.of_caller(&block)
old_critical = Thread.critical
Thread.critical = true
count = 0
cc, result, error, extra_data = Continuation.create(nil, nil)
error.call if error
tracer = lambda do |*args|
type, context, extra_data = args[0], args[4], args
if type == "return"
count += 1
# First this method and then calling one will return --
# the trace event of the second event gets the context
# of the method which called the method that called this
# method.
if count == 2
# It would be nice if we could restore the trace_func
# that was set before we swapped in our own one, but
# this is impossible without overloading set_trace_func
# in current Ruby.
set_trace_func(nil)
cc.call(eval("binding", context), nil, extra_data)
end
elsif type == "line" then
nil
elsif type == "c-return" and extra_data[3] == :set_trace_func then
nil
else
set_trace_func(nil)
error_msg = "Binding.of_caller used in non-method context or " +
"trailing statements of method using it aren't in the block."
cc.call(nil, lambda { raise(ArgumentError, error_msg) }, nil)
end
end
unless result
set_trace_func(tracer)
return nil
else
Thread.critical = old_critical
case block.arity
when 1 then yield(result)
else yield(result, extra_data)
end
end
end

View File

@ -0,0 +1,30 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/helper'
class FileSystemTest < Test::Unit::TestCase
include Liquid
def test_default
assert_raise(FileSystemError) do
BlankFileSystem.new.read_template_file("dummy")
end
end
def test_local
file_system = Liquid::LocalFileSystem.new("/some/path")
assert_equal "/some/path/_mypartial.liquid" , file_system.full_path("mypartial")
assert_equal "/some/path/dir/_mypartial.liquid", file_system.full_path("dir/mypartial")
assert_raise(FileSystemError) do
file_system.full_path("../dir/mypartial")
end
assert_raise(FileSystemError) do
file_system.full_path("/dir/../../dir/mypartial")
end
assert_raise(FileSystemError) do
file_system.full_path("/etc/passwd")
end
end
end

View File

@ -0,0 +1,95 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/helper'
module MoneyFilter
def money(input)
sprintf(' %d$ ', input)
end
def money_with_underscore(input)
sprintf(' %d$ ', input)
end
end
module CanadianMoneyFilter
def money(input)
sprintf(' %d$ CAD ', input)
end
end
class FiltersTest < Test::Unit::TestCase
include Liquid
def setup
@context = Context.new
end
def test_local_filter
@context['var'] = 1000
@context.add_filters(MoneyFilter)
assert_equal ' 1000$ ', Variable.new("var | money").render(@context)
end
def test_underscore_in_filter_name
@context['var'] = 1000
@context.add_filters(MoneyFilter)
assert_equal ' 1000$ ', Variable.new("var | money_with_underscore").render(@context)
end
def test_second_filter_overwrites_first
@context['var'] = 1000
@context.add_filters(MoneyFilter)
@context.add_filters(CanadianMoneyFilter)
assert_equal ' 1000$ CAD ', Variable.new("var | money").render(@context)
end
def test_size
@context['var'] = 'abcd'
@context.add_filters(MoneyFilter)
assert_equal 4, Variable.new("var | size").render(@context)
end
def test_join
@context['var'] = [1,2,3,4]
assert_equal "1 2 3 4", Variable.new("var | join").render(@context)
end
def test_sort
@context['value'] = 3
@context['numbers'] = [2,1,4,3]
@context['words'] = ['expected', 'as', 'alphabetic']
@context['arrays'] = [['flattened'], ['are']]
assert_equal [1,2,3,4], Variable.new("numbers | sort").render(@context)
assert_equal ['alphabetic', 'as', 'expected'],
Variable.new("words | sort").render(@context)
assert_equal [3], Variable.new("value | sort").render(@context)
assert_equal ['are', 'flattened'], Variable.new("arrays | sort").render(@context)
end
def test_strip_html
@context['var'] = "<b>bla blub</a>"
assert_equal "bla blub", Variable.new("var | strip_html").render(@context)
end
def test_capitalize
@context['var'] = "blub"
assert_equal "Blub", Variable.new("var | capitalize").render(@context)
end
end
class FiltersInTemplate < Test::Unit::TestCase
include Liquid
def test_local_global
Template.register_filter(MoneyFilter)
assert_equal " 1000$ ", Template.parse("{{1000 | money}}").render(nil, nil)
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render(nil, :filters => CanadianMoneyFilter)
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render(nil, :filters => [CanadianMoneyFilter])
end
def test_local_filter_with_deprecated_syntax
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render(nil, CanadianMoneyFilter)
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render(nil, [CanadianMoneyFilter])
end
end

20
vendor/plugins/liquid/test/helper.rb vendored Normal file
View File

@ -0,0 +1,20 @@
#!/usr/bin/env ruby
$LOAD_PATH.unshift(File.dirname(__FILE__)+ '/extra')
require 'test/unit'
require 'test/unit/assertions'
require 'caller'
require 'breakpoint'
require File.dirname(__FILE__) + '/../lib/liquid'
module Test
module Unit
module Assertions
include Liquid
def assert_template_result(expected, template, assigns={}, message=nil)
assert_equal expected, Template.parse(template).render(assigns)
end
end
end
end

View File

@ -0,0 +1,31 @@
require File.dirname(__FILE__) + '/helper'
class HtmlTagTest < Test::Unit::TestCase
include Liquid
def test_html_table
assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
'{% tablerow n in numbers cols:3%} {{n}} {% endtablerow %}',
'numbers' => [1,2,3,4,5,6])
assert_template_result("<tr class=\"row1\">\n</tr>\n",
'{% tablerow n in numbers cols:3%} {{n}} {% endtablerow %}',
'numbers' => [])
end
def test_html_table_with_different_cols
assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td><td class=\"col4\"> 4 </td><td class=\"col5\"> 5 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 6 </td></tr>\n",
'{% tablerow n in numbers cols:5%} {{n}} {% endtablerow %}',
'numbers' => [1,2,3,4,5,6])
end
def test_html_col_counter
assert_template_result("<tr class=\"row1\">\n<td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n<tr class=\"row2\"><td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n<tr class=\"row3\"><td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n",
'{% tablerow n in numbers cols:2%}{{tablerowloop.col}}{% endtablerow %}',
'numbers' => [1,2,3,4,5,6])
end
end

View File

@ -0,0 +1,150 @@
require File.dirname(__FILE__) + '/helper'
class IfElseTest < Test::Unit::TestCase
include Liquid
def test_if
assert_template_result(' ',' {% if false %} this text should not go into the output {% endif %} ')
assert_template_result(' this text should go into the output ',
' {% if true %} this text should go into the output {% endif %} ')
assert_template_result(' you rock ?','{% if false %} you suck {% endif %} {% if true %} you rock {% endif %}?')
end
def test_if_else
assert_template_result(' YES ','{% if false %} NO {% else %} YES {% endif %}')
assert_template_result(' YES ','{% if true %} YES {% else %} NO {% endif %}')
assert_template_result(' YES ','{% if "foo" %} YES {% else %} NO {% endif %}')
end
def test_if_boolean
assert_template_result(' YES ','{% if var %} YES {% endif %}', 'var' => true)
end
def test_if_or
assert_template_result(' YES ','{% if a or b %} YES {% endif %}', 'a' => true, 'b' => true)
assert_template_result(' YES ','{% if a or b %} YES {% endif %}', 'a' => true, 'b' => false)
assert_template_result(' YES ','{% if a or b %} YES {% endif %}', 'a' => false, 'b' => true)
assert_template_result('', '{% if a or b %} YES {% endif %}', 'a' => false, 'b' => false)
assert_template_result(' YES ','{% if a or b or c %} YES {% endif %}', 'a' => false, 'b' => false, 'c' => true)
assert_template_result('', '{% if a or b or c %} YES {% endif %}', 'a' => false, 'b' => false, 'c' => false)
end
def test_if_or_with_operators
assert_template_result(' YES ','{% if a == true or b == true %} YES {% endif %}', 'a' => true, 'b' => true)
assert_template_result(' YES ','{% if a == true or b == false %} YES {% endif %}', 'a' => true, 'b' => true)
assert_template_result('','{% if a == false or b == false %} YES {% endif %}', 'a' => true, 'b' => true)
end
def test_if_and
assert_template_result(' YES ','{% if true and true %} YES {% endif %}')
assert_template_result('','{% if false and true %} YES {% endif %}')
assert_template_result('','{% if false and true %} YES {% endif %}')
end
def test_hash_miss_generates_false
assert_template_result('','{% if foo.bar %} NO {% endif %}', 'foo' => {})
end
def test_if_from_variable
assert_template_result('','{% if var %} NO {% endif %}', 'var' => false)
assert_template_result('','{% if var %} NO {% endif %}', 'var' => nil)
assert_template_result('','{% if foo.bar %} NO {% endif %}', 'foo' => {'bar' => false})
assert_template_result('','{% if foo.bar %} NO {% endif %}', 'foo' => {})
assert_template_result('','{% if foo.bar %} NO {% endif %}', 'foo' => nil)
assert_template_result('','{% if foo.bar %} NO {% endif %}', 'foo' => true)
assert_template_result(' YES ','{% if var %} YES {% endif %}', 'var' => "text")
assert_template_result(' YES ','{% if var %} YES {% endif %}', 'var' => true)
assert_template_result(' YES ','{% if var %} YES {% endif %}', 'var' => 1)
assert_template_result(' YES ','{% if var %} YES {% endif %}', 'var' => {})
assert_template_result(' YES ','{% if var %} YES {% endif %}', 'var' => [])
assert_template_result(' YES ','{% if "foo" %} YES {% endif %}')
assert_template_result(' YES ','{% if foo.bar %} YES {% endif %}', 'foo' => {'bar' => true})
assert_template_result(' YES ','{% if foo.bar %} YES {% endif %}', 'foo' => {'bar' => "text"})
assert_template_result(' YES ','{% if foo.bar %} YES {% endif %}', 'foo' => {'bar' => 1 })
assert_template_result(' YES ','{% if foo.bar %} YES {% endif %}', 'foo' => {'bar' => {} })
assert_template_result(' YES ','{% if foo.bar %} YES {% endif %}', 'foo' => {'bar' => [] })
assert_template_result(' YES ','{% if var %} NO {% else %} YES {% endif %}', 'var' => false)
assert_template_result(' YES ','{% if var %} NO {% else %} YES {% endif %}', 'var' => nil)
assert_template_result(' YES ','{% if var %} YES {% else %} NO {% endif %}', 'var' => true)
assert_template_result(' YES ','{% if "foo" %} YES {% else %} NO {% endif %}', 'var' => "text")
assert_template_result(' YES ','{% if foo.bar %} NO {% else %} YES {% endif %}', 'foo' => {'bar' => false})
assert_template_result(' YES ','{% if foo.bar %} YES {% else %} NO {% endif %}', 'foo' => {'bar' => true})
assert_template_result(' YES ','{% if foo.bar %} YES {% else %} NO {% endif %}', 'foo' => {'bar' => "text"})
assert_template_result(' YES ','{% if foo.bar %} NO {% else %} YES {% endif %}', 'foo' => {'notbar' => true})
assert_template_result(' YES ','{% if foo.bar %} NO {% else %} YES {% endif %}', 'foo' => {})
assert_template_result(' YES ','{% if foo.bar %} NO {% else %} YES {% endif %}', 'notfoo' => {'bar' => true})
end
def test_nested_if
assert_template_result('', '{% if false %}{% if false %} NO {% endif %}{% endif %}')
assert_template_result('', '{% if false %}{% if true %} NO {% endif %}{% endif %}')
assert_template_result('', '{% if true %}{% if false %} NO {% endif %}{% endif %}')
assert_template_result(' YES ', '{% if true %}{% if true %} YES {% endif %}{% endif %}')
assert_template_result(' YES ', '{% if true %}{% if true %} YES {% else %} NO {% endif %}{% else %} NO {% endif %}')
assert_template_result(' YES ', '{% if true %}{% if false %} NO {% else %} YES {% endif %}{% else %} NO {% endif %}')
assert_template_result(' YES ', '{% if false %}{% if true %} NO {% else %} NONO {% endif %}{% else %} YES {% endif %}')
end
def test_comparisons_on_null
assert_template_result('','{% if null < 10 %} NO {% endif %}')
assert_template_result('','{% if null <= 10 %} NO {% endif %}')
assert_template_result('','{% if null >= 10 %} NO {% endif %}')
assert_template_result('','{% if null > 10 %} NO {% endif %}')
assert_template_result('','{% if 10 < null %} NO {% endif %}')
assert_template_result('','{% if 10 <= null %} NO {% endif %}')
assert_template_result('','{% if 10 >= null %} NO {% endif %}')
assert_template_result('','{% if 10 > null %} NO {% endif %}')
end
def test_else_if
assert_template_result('0','{% if 0 == 0 %}0{% elsif 1 == 1%}1{% else %}2{% endif %}')
assert_template_result('1','{% if 0 != 0 %}0{% elsif 1 == 1%}1{% else %}2{% endif %}')
assert_template_result('2','{% if 0 != 0 %}0{% elsif 1 != 1%}1{% else %}2{% endif %}')
assert_template_result('elsif','{% if false %}if{% elsif true %}elsif{% endif %}')
end
def test_with_filtered_expressions
assert_template_result('yes','{% if "BLAH"|downcase == "blah" %}yes{% endif %}')
assert_template_result('yes','{% if "FOO BAR"|truncatewords:1,"--" == "FOO--" %}yes{% endif %}')
assert_template_result('yes','{% if "FOO BAR"|truncatewords:1,"--"|downcase == "foo--" %}yes{% endif %}')
assert_template_result('yes','{% if "foo--" == "FOO BAR"|truncatewords:1,"--"|downcase %}yes{% endif %}')
# array transformation, to make sure we aren't converting arrays to strings somewhere along the way:
assert_template_result('yes','{% if values|sort == sorted %}yes{% endif %}', 'values' => %w{foo bar baz}, 'sorted' => %w{bar baz foo})
end
def test_allow_no_spaces_in_filtered_expressions
assert_template_result('','{% if "foo--" == "FOO BAR" |truncatewords:1,"--"|downcase %}yes{% endif %}')
assert_template_result('','{% if "foo--" == "FOO BAR"| truncatewords:1,"--"|downcase %}yes{% endif %}')
assert_template_result('','{% if "foo--" == "FOO BAR"|truncatewords :1,"--"|downcase %}yes{% endif %}')
assert_template_result('','{% if "foo--" == "FOO BAR"|truncatewords: 1,"--"|downcase %}yes{% endif %}')
assert_template_result('','{% if "foo--" == "FOO BAR"|truncatewords:1 ,"--"|downcase %}yes{% endif %}')
assert_template_result('','{% if "foo--" == "FOO BAR"|truncatewords:1, "--"|downcase %}yes{% endif %}')
assert_template_result('','{% if "foo--" == "FOO BAR"|truncatewords:1,"--" |downcase %}yes{% endif %}')
end
def test_syntax_error_no_variable
assert_raise(SyntaxError){ assert_template_result('', '{% if jerry == 1 %}')}
end
def test_syntax_error_no_expression
assert_raise(SyntaxError) { assert_template_result('', '{% if %}') }
end
def test_if_with_custom_condition
Condition.operators['contains'] = :[]
assert_template_result('yes', %({% if 'bob' contains 'o' %}yes{% endif %}))
assert_template_result('no', %({% if 'bob' contains 'f' %}yes{% else %}no{% endif %}))
ensure
Condition.operators.delete 'contains'
end
end

View File

@ -0,0 +1,115 @@
require File.dirname(__FILE__) + '/helper'
class TestFileSystem
def read_template_file(template_path)
case template_path
when "product"
"Product: {{ product.title }} "
when "locale_variables"
"Locale: {{echo1}} {{echo2}}"
when "variant"
"Variant: {{ variant.title }}"
when "nested_template"
"{% include 'header' %} {% include 'body' %} {% include 'footer' %}"
when "body"
"body {% include 'body_detail' %}"
when "nested_product_template"
"Product: {{ nested_product_template.title }} {%include 'details'%} "
when "recursively_nested_template"
"-{% include 'recursively_nested_template' %}"
else
template_path
end
end
end
class IncludeTagTest < Test::Unit::TestCase
include Liquid
def setup
Liquid::Template.file_system = TestFileSystem.new
end
def test_include_tag_with
assert_equal "Product: Draft 151cm ",
Template.parse("{% include 'product' with products[0] %}").render( "products" => [ {'title' => 'Draft 151cm'}, {'title' => 'Element 155cm'} ] )
end
def test_include_tag_with_default_name
assert_equal "Product: Draft 151cm ",
Template.parse("{% include 'product' %}").render( "product" => {'title' => 'Draft 151cm'} )
end
def test_include_tag_for
assert_equal "Product: Draft 151cm Product: Element 155cm ",
Template.parse("{% include 'product' for products %}").render( "products" => [ {'title' => 'Draft 151cm'}, {'title' => 'Element 155cm'} ] )
end
def test_include_tag_with_local_variables
assert_equal "Locale: test123 ",
Template.parse("{% include 'locale_variables' echo1: 'test123' %}").render
end
def test_include_tag_with_multiple_local_variables
assert_equal "Locale: test123 test321",
Template.parse("{% include 'locale_variables' echo1: 'test123', echo2: 'test321' %}").render
end
def test_include_tag_with_multiple_local_variables_from_context
assert_equal "Locale: test123 test321",
Template.parse("{% include 'locale_variables' echo1: echo1, echo2: more_echos.echo2 %}").render('echo1' => 'test123', 'more_echos' => { "echo2" => 'test321'})
end
def test_nested_include_tag
assert_equal "body body_detail",
Template.parse("{% include 'body' %}").render
assert_equal "header body body_detail footer",
Template.parse("{% include 'nested_template' %}").render
end
def test_nested_include_with_variable
assert_equal "Product: Draft 151cm details ",
Template.parse("{% include 'nested_product_template' with product %}").render("product" => {"title" => 'Draft 151cm'})
assert_equal "Product: Draft 151cm details Product: Element 155cm details ",
Template.parse("{% include 'nested_product_template' for products %}").render("products" => [{"title" => 'Draft 151cm'}, {"title" => 'Element 155cm'}])
end
def test_recursively_included_template_does_not_produce_endless_loop
infinite_file_system = Class.new do
def read_template_file(template_path)
"-{% include 'loop' %}"
end
end
Liquid::Template.file_system = infinite_file_system.new
assert_raise(Liquid::StackLevelError) do
Template.parse("{% include 'loop' %}").render!
end
end
def test_dynamically_choosen_template
assert_equal "Test123", Template.parse("{% include template %}").render("template" => 'Test123')
assert_equal "Test321", Template.parse("{% include template %}").render("template" => 'Test321')
assert_equal "Product: Draft 151cm ", Template.parse("{% include template for product %}").render("template" => 'product', 'product' => { 'title' => 'Draft 151cm'})
end
end

View File

@ -0,0 +1,89 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/helper'
class TestClassA
liquid_methods :allowedA, :chainedB
def allowedA
'allowedA'
end
def restrictedA
'restrictedA'
end
def chainedB
TestClassB.new
end
end
class TestClassB
liquid_methods :allowedB, :chainedC
def allowedB
'allowedB'
end
def chainedC
TestClassC.new
end
end
class TestClassC
liquid_methods :allowedC
def allowedC
'allowedC'
end
end
class TestClassC::LiquidDropClass
def another_allowedC
'another_allowedC'
end
end
class ModuleExTest < Test::Unit::TestCase
include Liquid
def setup
@a = TestClassA.new
@b = TestClassB.new
@c = TestClassC.new
end
def test_should_create_LiquidDropClass
assert TestClassA::LiquidDropClass
assert TestClassB::LiquidDropClass
assert TestClassC::LiquidDropClass
end
def test_should_respond_to_liquid
assert @a.respond_to?(:to_liquid)
assert @b.respond_to?(:to_liquid)
assert @c.respond_to?(:to_liquid)
end
def test_should_return_LiquidDropClass_object
assert @a.to_liquid.is_a?(TestClassA::LiquidDropClass)
assert @b.to_liquid.is_a?(TestClassB::LiquidDropClass)
assert @c.to_liquid.is_a?(TestClassC::LiquidDropClass)
end
def test_should_respond_to_liquid_methods
assert @a.to_liquid.respond_to?(:allowedA)
assert @a.to_liquid.respond_to?(:chainedB)
assert @b.to_liquid.respond_to?(:allowedB)
assert @b.to_liquid.respond_to?(:chainedC)
assert @c.to_liquid.respond_to?(:allowedC)
assert @c.to_liquid.respond_to?(:another_allowedC)
end
def test_should_not_respond_to_restricted_methods
assert ! @a.to_liquid.respond_to?(:restricted)
end
def test_should_use_regular_objects_as_drops
assert_equal 'allowedA', Liquid::Template.parse("{{ a.allowedA }}").render('a'=>@a)
assert_equal 'allowedB', Liquid::Template.parse("{{ a.chainedB.allowedB }}").render('a'=>@a)
assert_equal 'allowedC', Liquid::Template.parse("{{ a.chainedB.chainedC.allowedC }}").render('a'=>@a)
assert_equal 'another_allowedC', Liquid::Template.parse("{{ a.chainedB.chainedC.another_allowedC }}").render('a'=>@a)
assert_equal '', Liquid::Template.parse("{{ a.restricted }}").render('a'=>@a)
assert_equal '', Liquid::Template.parse("{{ a.unknown }}").render('a'=>@a)
end
end

View File

@ -0,0 +1,121 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/helper'
module FunnyFilter
def make_funny(input)
'LOL'
end
def cite_funny(input)
"LOL: #{input}"
end
def add_smiley(input, smiley = ":-)")
"#{input} #{smiley}"
end
def add_tag(input, tag = "p", id = "foo")
%|<#{tag} id="#{id}">#{input}</#{tag}>|
end
def paragraph(input)
"<p>#{input}</p>"
end
def link_to(name, url)
%|<a href="#{url}">#{name}</a>|
end
end
class OutputTest < Test::Unit::TestCase
include Liquid
def setup
@assigns = {
'best_cars' => 'bmw',
'car' => {'bmw' => 'good', 'gm' => 'bad'}
}
end
def test_variable
text = %| {{best_cars}} |
expected = %| bmw |
assert_equal expected, Template.parse(text).render(@assigns)
end
def test_variable_traversing
text = %| {{car.bmw}} {{car.gm}} {{car.bmw}} |
expected = %| good bad good |
assert_equal expected, Template.parse(text).render(@assigns)
end
def test_variable_piping
text = %( {{ car.gm | make_funny }} )
expected = %| LOL |
assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
end
def test_variable_piping_with_input
text = %( {{ car.gm | cite_funny }} )
expected = %| LOL: bad |
assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
end
def test_variable_piping_with_args
text = %! {{ car.gm | add_smiley : ':-(' }} !
expected = %| bad :-( |
assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
end
def test_variable_piping_with_no_args
text = %! {{ car.gm | add_smiley }} !
expected = %| bad :-) |
assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
end
def test_multiple_variable_piping_with_args
text = %! {{ car.gm | add_smiley : ':-(' | add_smiley : ':-('}} !
expected = %| bad :-( :-( |
assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
end
def test_variable_piping_with_args
text = %! {{ car.gm | add_tag : 'span', 'bar'}} !
expected = %| <span id="bar">bad</span> |
assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
end
def test_variable_piping_with_variable_args
text = %! {{ car.gm | add_tag : 'span', car.bmw}} !
expected = %| <span id="good">bad</span> |
assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
end
def test_multiple_pipings
text = %( {{ best_cars | cite_funny | paragraph }} )
expected = %| <p>LOL: bmw</p> |
assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
end
def test_link_to
text = %( {{ 'Typo' | link_to: 'http://typo.leetsoft.com' }} )
expected = %| <a href="http://typo.leetsoft.com">Typo</a> |
assert_equal expected, Template.parse(text).render(@assigns, :filters => [FunnyFilter])
end
end

View File

@ -0,0 +1,41 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/helper'
class ParsingQuirksTest < Test::Unit::TestCase
include Liquid
def test_error_with_css
text = %| div { font-weight: bold; } |
template = Template.parse(text)
assert_equal text, template.render
assert_equal [String], template.root.nodelist.collect {|i| i.class}
end
def test_raise_on_single_close_bracet
assert_raise(SyntaxError) do
Template.parse("text {{method} oh nos!")
end
end
def test_raise_on_label_and_no_close_bracets
assert_raise(SyntaxError) do
Template.parse("TEST {{ ")
end
end
def test_raise_on_label_and_no_close_bracets_percent
assert_raise(SyntaxError) do
Template.parse("TEST {% ")
end
end
def test_error_on_empty_filter
assert_nothing_raised do
Template.parse("{{test |a|b|}}")
Template.parse("{{test}}")
Template.parse("{{|test|}}")
end
end
end

View File

@ -0,0 +1,40 @@
require File.dirname(__FILE__) + '/helper'
class RegexpTest < Test::Unit::TestCase
include Liquid
def test_empty
assert_equal [], ''.scan(QuotedFragment)
end
def test_quote
assert_equal ['"arg 1"'], '"arg 1"'.scan(QuotedFragment)
end
def test_words
assert_equal ['arg1', 'arg2'], 'arg1 arg2'.scan(QuotedFragment)
end
def test_quoted_words
assert_equal ['arg1', 'arg2', '"arg 3"'], 'arg1 arg2 "arg 3"'.scan(QuotedFragment)
end
def test_quoted_words
assert_equal ['arg1', 'arg2', "'arg 3'"], 'arg1 arg2 \'arg 3\''.scan(QuotedFragment)
end
def test_quoted_words_in_the_middle
assert_equal ['arg1', 'arg2', '"arg 3"', 'arg4'], 'arg1 arg2 "arg 3" arg4 '.scan(QuotedFragment)
end
def test_variable_parser
assert_equal ['var'], 'var'.scan(VariableParser)
assert_equal ['var', 'method'], 'var.method'.scan(VariableParser)
assert_equal ['var', '[method]'], 'var[method]'.scan(VariableParser)
assert_equal ['var', '[method]', '[0]'], 'var[method][0]'.scan(VariableParser)
assert_equal ['var', '["method"]', '[0]'], 'var["method"][0]'.scan(VariableParser)
assert_equal ['var', '[method]', '[0]', 'method'], 'var[method][0].method'.scan(VariableParser)
end
end

View File

@ -0,0 +1,41 @@
require File.dirname(__FILE__) + '/helper'
module SecurityFilter
def add_one(input)
"#{input} + 1"
end
end
class SecurityTest < Test::Unit::TestCase
include Liquid
def test_no_instance_eval
text = %( {{ '1+1' | instance_eval }} )
expected = %| 1+1 |
assert_equal expected, Template.parse(text).render(@assigns)
end
def test_no_existing_instance_eval
text = %( {{ '1+1' | __instance_eval__ }} )
expected = %| 1+1 |
assert_equal expected, Template.parse(text).render(@assigns)
end
def test_no_instance_eval_after_mixing_in_new_filter
text = %( {{ '1+1' | instance_eval }} )
expected = %| 1+1 |
assert_equal expected, Template.parse(text).render(@assigns)
end
def test_no_instance_eval_later_in_chain
text = %( {{ '1+1' | add_one | instance_eval }} )
expected = %| 1+1 + 1 |
assert_equal expected, Template.parse(text).render(@assigns, :filters => SecurityFilter)
end
end

View File

@ -0,0 +1,161 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/helper'
class Filters
include Liquid::StandardFilters
end
class StandardFiltersTest < Test::Unit::TestCase
include Liquid
def setup
@filters = Filters.new
end
def test_size
assert_equal 3, @filters.size([1,2,3])
assert_equal 0, @filters.size([])
assert_equal 0, @filters.size(nil)
end
def test_downcase
assert_equal 'testing', @filters.downcase("Testing")
assert_equal '', @filters.downcase(nil)
end
def test_upcase
assert_equal 'TESTING', @filters.upcase("Testing")
assert_equal '', @filters.upcase(nil)
end
def test_upcase
assert_equal 'TESTING', @filters.upcase("Testing")
assert_equal '', @filters.upcase(nil)
end
def test_truncate
assert_equal '1234...', @filters.truncate('1234567890', 7)
assert_equal '1234567890', @filters.truncate('1234567890', 20)
assert_equal '...', @filters.truncate('1234567890', 0)
assert_equal '1234567890', @filters.truncate('1234567890')
end
def test_escape
assert_equal '&lt;strong&gt;', @filters.escape('<strong>')
assert_equal '&lt;strong&gt;', @filters.h('<strong>')
end
def test_truncatewords
assert_equal 'one two three', @filters.truncatewords('one two three', 4)
assert_equal 'one two...', @filters.truncatewords('one two three', 2)
assert_equal 'one two three', @filters.truncatewords('one two three')
assert_equal 'Two small (13&#8221; x 5.5&#8221; x 10&#8221; high) baskets fit inside one large basket (13&#8221;...', @filters.truncatewords('Two small (13&#8221; x 5.5&#8221; x 10&#8221; high) baskets fit inside one large basket (13&#8221; x 16&#8221; x 10.5&#8221; high) with cover.', 15)
end
def test_strip_html
assert_equal 'test', @filters.strip_html("<div>test</div>")
assert_equal 'test', @filters.strip_html("<div id='test'>test</div>")
assert_equal '', @filters.strip_html(nil)
end
def test_join
assert_equal '1 2 3 4', @filters.join([1,2,3,4])
assert_equal '1 - 2 - 3 - 4', @filters.join([1,2,3,4], ' - ')
end
def test_sort
assert_equal [1,2,3,4], @filters.sort([4,3,2,1])
assert_equal [{"a" => 1}, {"a" => 2}, {"a" => 3}, {"a" => 4}], @filters.sort([{"a" => 4}, {"a" => 3}, {"a" => 1}, {"a" => 2}], "a")
end
def test_map
assert_equal [1,2,3,4], @filters.map([{"a" => 1}, {"a" => 2}, {"a" => 3}, {"a" => 4}], 'a')
assert_template_result 'abc', "{{ ary | map:'foo' | map:'bar' }}",
'ary' => [{'foo' => {'bar' => 'a'}}, {'foo' => {'bar' => 'b'}}, {'foo' => {'bar' => 'c'}}]
end
def test_date
assert_equal 'May', @filters.date(Time.parse("2006-05-05 10:00:00"), "%B")
assert_equal 'June', @filters.date(Time.parse("2006-06-05 10:00:00"), "%B")
assert_equal 'July', @filters.date(Time.parse("2006-07-05 10:00:00"), "%B")
assert_equal 'May', @filters.date("2006-05-05 10:00:00", "%B")
assert_equal 'June', @filters.date("2006-06-05 10:00:00", "%B")
assert_equal 'July', @filters.date("2006-07-05 10:00:00", "%B")
assert_equal '2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", "")
assert_equal '2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", "")
assert_equal '2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", "")
assert_equal '2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", nil)
assert_equal '07/05/2006', @filters.date("2006-07-05 10:00:00", "%m/%d/%Y")
assert_equal "07/16/2004", @filters.date("Fri Jul 16 01:00:00 2004", "%m/%d/%Y")
assert_equal nil, @filters.date(nil, "%B")
end
def test_first_last
assert_equal 1, @filters.first([1,2,3])
assert_equal 3, @filters.last([1,2,3])
assert_equal nil, @filters.first([])
assert_equal nil, @filters.last([])
end
def test_replace
assert_equal 'b b b b', @filters.replace("a a a a", 'a', 'b')
assert_equal 'b a a a', @filters.replace_first("a a a a", 'a', 'b')
assert_template_result 'b a a a', "{{ 'a a a a' | replace_first: 'a', 'b' }}"
end
def test_remove
assert_equal ' ', @filters.remove("a a a a", 'a')
assert_equal 'a a a', @filters.remove_first("a a a a", 'a ')
assert_template_result 'a a a', "{{ 'a a a a' | remove_first: 'a ' }}"
end
def test_strip_newlines
assert_template_result 'abc', "{{ source | strip_newlines }}", 'source' => "a\nb\nc"
end
def test_newlines_to_br
assert_template_result "a<br />\nb<br />\nc", "{{ source | newline_to_br }}", 'source' => "a\nb\nc"
end
def test_plus
assert_template_result "2", "{{ 1 | plus:1 }}"
assert_template_result "11", "{{ '1' | plus:'1' }}"
end
def test_minus
assert_template_result "4", "{{ input | minus:operand }}", 'input' => 5, 'operand' => 1
end
def test_times
assert_template_result "12", "{{ 3 | times:4 }}"
assert_template_result "foofoofoofoo", "{{ 'foo' | times:4 }}"
end
def test_append
assigns = {'a' => 'bc', 'b' => 'd' }
assert_template_result('bcd',"{{ a | append: 'd'}}",assigns)
assert_template_result('bcd',"{{ a | append: b}}",assigns)
end
def test_prepend
assigns = {'a' => 'bc', 'b' => 'a' }
assert_template_result('abc',"{{ a | prepend: 'a'}}",assigns)
assert_template_result('abc',"{{ a | prepend: b}}",assigns)
end
def test_divided_by
assert_template_result "4", "{{ 12 | divided_by:3 }}"
assert_template_result "4", "{{ 14 | divided_by:3 }}"
assert_template_result "5", "{{ 15 | divided_by:3 }}"
end
end

View File

@ -0,0 +1,404 @@
require File.dirname(__FILE__) + '/helper'
class StandardTagTest < Test::Unit::TestCase
include Liquid
def test_tag
tag = Tag.new('tag', [], [])
assert_equal 'liquid::tag', tag.name
assert_equal '', tag.render(Context.new)
end
def test_no_transform
assert_template_result('this text should come out of the template without change...',
'this text should come out of the template without change...')
assert_template_result('blah','blah')
assert_template_result('<blah>','<blah>')
assert_template_result('|,.:','|,.:')
assert_template_result('','')
text = %|this shouldnt see any transformation either but has multiple lines
as you can clearly see here ...|
assert_template_result(text,text)
end
def test_has_a_block_which_does_nothing
assert_template_result(%|the comment block should be removed .. right?|,
%|the comment block should be removed {%comment%} be gone.. {%endcomment%} .. right?|)
assert_template_result('','{%comment%}{%endcomment%}')
assert_template_result('','{%comment%}{% endcomment %}')
assert_template_result('','{% comment %}{%endcomment%}')
assert_template_result('','{% comment %}{% endcomment %}')
assert_template_result('','{%comment%}comment{%endcomment%}')
assert_template_result('','{% comment %}comment{% endcomment %}')
assert_template_result('foobar','foo{%comment%}comment{%endcomment%}bar')
assert_template_result('foobar','foo{% comment %}comment{% endcomment %}bar')
assert_template_result('foobar','foo{%comment%} comment {%endcomment%}bar')
assert_template_result('foobar','foo{% comment %} comment {% endcomment %}bar')
assert_template_result('foo bar','foo {%comment%} {%endcomment%} bar')
assert_template_result('foo bar','foo {%comment%}comment{%endcomment%} bar')
assert_template_result('foo bar','foo {%comment%} comment {%endcomment%} bar')
assert_template_result('foobar','foo{%comment%}
{%endcomment%}bar')
end
def test_for
assert_template_result(' yo yo yo yo ','{%for item in array%} yo {%endfor%}','array' => [1,2,3,4])
assert_template_result('yoyo','{%for item in array%}yo{%endfor%}','array' => [1,2])
assert_template_result(' yo ','{%for item in array%} yo {%endfor%}','array' => [1])
assert_template_result('','{%for item in array%}{%endfor%}','array' => [1,2])
expected = <<HERE
yo
yo
yo
HERE
template = <<HERE
{%for item in array%}
yo
{%endfor%}
HERE
assert_template_result(expected,template,'array' => [1,2,3])
end
def test_for_with_range
assert_template_result(' 1 2 3 ','{%for item in (1..3) %} {{item}} {%endfor%}')
end
def test_for_with_variable
assert_template_result(' 1 2 3 ','{%for item in array%} {{item}} {%endfor%}','array' => [1,2,3])
assert_template_result('123','{%for item in array%}{{item}}{%endfor%}','array' => [1,2,3])
assert_template_result('123','{% for item in array %}{{item}}{% endfor %}','array' => [1,2,3])
assert_template_result('abcd','{%for item in array%}{{item}}{%endfor%}','array' => ['a','b','c','d'])
assert_template_result('a b c','{%for item in array%}{{item}}{%endfor%}','array' => ['a',' ','b',' ','c'])
assert_template_result('abc','{%for item in array%}{{item}}{%endfor%}','array' => ['a','','b','','c'])
end
def test_for_helpers
assigns = {'array' => [1,2,3] }
assert_template_result(' 1/3 2/3 3/3 ','{%for item in array%} {{forloop.index}}/{{forloop.length}} {%endfor%}',assigns)
assert_template_result(' 1 2 3 ','{%for item in array%} {{forloop.index}} {%endfor%}',assigns)
assert_template_result(' 0 1 2 ','{%for item in array%} {{forloop.index0}} {%endfor%}',assigns)
assert_template_result(' 2 1 0 ','{%for item in array%} {{forloop.rindex0}} {%endfor%}',assigns)
assert_template_result(' 3 2 1 ','{%for item in array%} {{forloop.rindex}} {%endfor%}',assigns)
assert_template_result(' true false false ','{%for item in array%} {{forloop.first}} {%endfor%}',assigns)
assert_template_result(' false false true ','{%for item in array%} {{forloop.last}} {%endfor%}',assigns)
end
def test_for_and_if
assigns = {'array' => [1,2,3] }
assert_template_result('+--', '{%for item in array%}{% if forloop.first %}+{% else %}-{% endif %}{%endfor%}', assigns)
end
def test_for_with_filtered_expressions
assert_template_result('abc','{% for letter in letters|sort %}{{ letter }}{% endfor %}', 'letters' => %w{c b a})
end
def test_limiting
assigns = {'array' => [1,2,3,4,5,6,7,8,9,0]}
assert_template_result('12','{%for i in array limit:2 %}{{ i }}{%endfor%}',assigns)
assert_template_result('1234','{%for i in array limit:4 %}{{ i }}{%endfor%}',assigns)
assert_template_result('3456','{%for i in array limit:4 offset:2 %}{{ i }}{%endfor%}',assigns)
assert_template_result('3456','{%for i in array limit: 4 offset: 2 %}{{ i }}{%endfor%}',assigns)
end
def test_dynamic_variable_limiting
assigns = {'array' => [1,2,3,4,5,6,7,8,9,0]}
assigns['limit'] = 2
assigns['offset'] = 2
assert_template_result('34','{%for i in array limit: limit offset: offset %}{{ i }}{%endfor%}',assigns)
end
def test_nested_for
assigns = {'array' => [[1,2],[3,4],[5,6]] }
assert_template_result('123456','{%for item in array%}{%for i in item%}{{ i }}{%endfor%}{%endfor%}',assigns)
end
def test_offset_only
assigns = {'array' => [1,2,3,4,5,6,7,8,9,0]}
assert_template_result('890','{%for i in array offset:7 %}{{ i }}{%endfor%}',assigns)
end
def test_pause_resume
assigns = {'array' => {'items' => [1,2,3,4,5,6,7,8,9,0]}}
markup = <<-MKUP
{%for i in array.items limit: 3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit: 3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit: 3 %}{{i}}{%endfor%}
MKUP
expected = <<-XPCTD
123
next
456
next
789
XPCTD
assert_template_result(expected,markup,assigns)
end
def test_pause_resume_limit
assigns = {'array' => {'items' => [1,2,3,4,5,6,7,8,9,0]}}
markup = <<-MKUP
{%for i in array.items limit:3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit:3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit:1 %}{{i}}{%endfor%}
MKUP
expected = <<-XPCTD
123
next
456
next
7
XPCTD
assert_template_result(expected,markup,assigns)
end
def test_pause_resume_BIG_limit
assigns = {'array' => {'items' => [1,2,3,4,5,6,7,8,9,0]}}
markup = <<-MKUP
{%for i in array.items limit:3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit:3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit:1000 %}{{i}}{%endfor%}
MKUP
expected = <<-XPCTD
123
next
456
next
7890
XPCTD
assert_template_result(expected,markup,assigns)
end
def test_pause_resume_BIG_offset
assigns = {'array' => {'items' => [1,2,3,4,5,6,7,8,9,0]}}
markup = %q({%for i in array.items limit:3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit:3 %}{{i}}{%endfor%}
next
{%for i in array.items offset:continue limit:3 offset:1000 %}{{i}}{%endfor%})
expected = %q(123
next
456
next
)
assert_template_result(expected,markup,assigns)
end
def test_assign
assigns = {'var' => 'content' }
assert_template_result('var2: var2:content','var2:{{var2}} {%assign var2 = var%} var2:{{var2}}',assigns)
end
def test_hyphenated_assign
assigns = {'a-b' => '1' }
assert_template_result('a-b:1 a-b:2','a-b:{{a-b}} {%assign a-b = 2 %}a-b:{{a-b}}',assigns)
end
def test_assign_with_colon_and_spaces
assigns = {'var' => {'a:b c' => {'paged' => '1' }}}
assert_template_result('var2: 1','{%assign var2 = var["a:b c"].paged %}var2: {{var2}}',assigns)
end
def test_capture
assigns = {'var' => 'content' }
assert_template_result('content foo content foo ','{{ var2 }}{% capture var2 %}{{ var }} foo {% endcapture %}{{ var2 }}{{ var2 }}', assigns)
end
def test_capture_detects_bad_syntax
assert_raise(SyntaxError) do
assert_template_result('content foo content foo ','{{ var2 }}{% capture %}{{ var }} foo {% endcapture %}{{ var2 }}{{ var2 }}', {'var' => 'content' })
end
end
def test_case
assigns = {'condition' => 2 }
assert_template_result(' its 2 ','{% case condition %}{% when 1 %} its 1 {% when 2 %} its 2 {% endcase %}', assigns)
assigns = {'condition' => 1 }
assert_template_result(' its 1 ','{% case condition %}{% when 1 %} its 1 {% when 2 %} its 2 {% endcase %}', assigns)
assigns = {'condition' => 3 }
assert_template_result('','{% case condition %}{% when 1 %} its 1 {% when 2 %} its 2 {% endcase %}', assigns)
assigns = {'condition' => "string here" }
assert_template_result(' hit ','{% case condition %}{% when "string here" %} hit {% endcase %}', assigns)
assigns = {'condition' => "bad string here" }
assert_template_result('','{% case condition %}{% when "string here" %} hit {% endcase %}', assigns)
end
def test_case_with_else
assigns = {'condition' => 5 }
assert_template_result(' hit ','{% case condition %}{% when 5 %} hit {% else %} else {% endcase %}', assigns)
assigns = {'condition' => 6 }
assert_template_result(' else ','{% case condition %}{% when 5 %} hit {% else %} else {% endcase %}', assigns)
assigns = {'condition' => 6 }
assert_template_result(' else ','{% case condition %} {% when 5 %} hit {% else %} else {% endcase %}', assigns)
end
def test_case_on_size
assert_template_result('', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [])
assert_template_result('1' , '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [1])
assert_template_result('2' , '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [1, 1])
assert_template_result('', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [1, 1, 1])
assert_template_result('', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [1, 1, 1, 1])
assert_template_result('', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [1, 1, 1, 1, 1])
end
def test_case_on_size_with_else
assert_template_result('else', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}', 'a' => [])
assert_template_result('1', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}', 'a' => [1])
assert_template_result('2', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}', 'a' => [1, 1])
assert_template_result('else', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}', 'a' => [1, 1, 1])
assert_template_result('else', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}', 'a' => [1, 1, 1, 1])
assert_template_result('else', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}', 'a' => [1, 1, 1, 1, 1])
end
def test_case_on_length_with_else
assert_template_result('else', '{% case a.empty? %}{% when true %}true{% when false %}false{% else %}else{% endcase %}', {})
assert_template_result('false', '{% case false %}{% when true %}true{% when false %}false{% else %}else{% endcase %}', {})
assert_template_result('true', '{% case true %}{% when true %}true{% when false %}false{% else %}else{% endcase %}', {})
assert_template_result('else', '{% case NULL %}{% when true %}true{% when false %}false{% else %}else{% endcase %}', {})
end
def test_assign_from_case
# Example from the shopify forums
code = %q({% case collection.handle %}{% when 'menswear-jackets' %}{% assign ptitle = 'menswear' %}{% when 'menswear-t-shirts' %}{% assign ptitle = 'menswear' %}{% else %}{% assign ptitle = 'womenswear' %}{% endcase %}{{ ptitle }})
template = Liquid::Template.parse(code)
assert_equal "menswear", template.render("collection" => {'handle' => 'menswear-jackets'})
assert_equal "menswear", template.render("collection" => {'handle' => 'menswear-t-shirts'})
assert_equal "womenswear", template.render("collection" => {'handle' => 'x'})
assert_equal "womenswear", template.render("collection" => {'handle' => 'y'})
assert_equal "womenswear", template.render("collection" => {'handle' => 'z'})
end
def test_case_when_or
code = '{% case condition %}{% when 1 or 2 or 3 %} its 1 or 2 or 3 {% when 4 %} its 4 {% endcase %}'
assert_template_result(' its 1 or 2 or 3 ', code, {'condition' => 1 })
assert_template_result(' its 1 or 2 or 3 ', code, {'condition' => 2 })
assert_template_result(' its 1 or 2 or 3 ', code, {'condition' => 3 })
assert_template_result(' its 4 ', code, {'condition' => 4 })
assert_template_result('', code, {'condition' => 5 })
code = '{% case condition %}{% when 1 or "string" or null %} its 1 or 2 or 3 {% when 4 %} its 4 {% endcase %}'
assert_template_result(' its 1 or 2 or 3 ', code, {'condition' => 1 })
assert_template_result(' its 1 or 2 or 3 ', code, {'condition' => 'string' })
assert_template_result(' its 1 or 2 or 3 ', code, {'condition' => nil })
assert_template_result('', code, {'condition' => 'something else' })
end
def test_case_when_comma
code = '{% case condition %}{% when 1, 2, 3 %} its 1 or 2 or 3 {% when 4 %} its 4 {% endcase %}'
assert_template_result(' its 1 or 2 or 3 ', code, {'condition' => 1 })
assert_template_result(' its 1 or 2 or 3 ', code, {'condition' => 2 })
assert_template_result(' its 1 or 2 or 3 ', code, {'condition' => 3 })
assert_template_result(' its 4 ', code, {'condition' => 4 })
assert_template_result('', code, {'condition' => 5 })
code = '{% case condition %}{% when 1, "string", null %} its 1 or 2 or 3 {% when 4 %} its 4 {% endcase %}'
assert_template_result(' its 1 or 2 or 3 ', code, {'condition' => 1 })
assert_template_result(' its 1 or 2 or 3 ', code, {'condition' => 'string' })
assert_template_result(' its 1 or 2 or 3 ', code, {'condition' => nil })
assert_template_result('', code, {'condition' => 'something else' })
end
def test_assign
assert_equal 'variable', Liquid::Template.parse( '{% assign a = "variable"%}{{a}}' ).render
end
def test_assign_is_global
assert_equal 'variable', Liquid::Template.parse( '{%for i in (1..2) %}{% assign a = "variable"%}{% endfor %}{{a}}' ).render
end
def test_case_detects_bad_syntax
assert_raise(SyntaxError) do
assert_template_result('', '{% case false %}{% when %}true{% endcase %}', {})
end
assert_raise(SyntaxError) do
assert_template_result('', '{% case false %}{% huh %}true{% endcase %}', {})
end
end
def test_cycle
assert_template_result('one','{%cycle "one", "two"%}')
assert_template_result('one two','{%cycle "one", "two"%} {%cycle "one", "two"%}')
assert_template_result('one two one','{%cycle "one", "two"%} {%cycle "one", "two"%} {%cycle "one", "two"%}')
assert_template_result('text-align: left text-align: right','{%cycle "text-align: left", "text-align: right" %} {%cycle "text-align: left", "text-align: right"%}')
end
def test_multiple_cycles
assert_template_result('1 2 1 1 2 3 1','{%cycle 1,2%} {%cycle 1,2%} {%cycle 1,2%} {%cycle 1,2,3%} {%cycle 1,2,3%} {%cycle 1,2,3%} {%cycle 1,2,3%}')
end
def test_multiple_named_cycles
assert_template_result('one one two two one one','{%cycle 1: "one", "two" %} {%cycle 2: "one", "two" %} {%cycle 1: "one", "two" %} {%cycle 2: "one", "two" %} {%cycle 1: "one", "two" %} {%cycle 2: "one", "two" %}')
end
def test_multiple_named_cycles_with_names_from_context
assigns = {"var1" => 1, "var2" => 2 }
assert_template_result('one one two two one one','{%cycle var1: "one", "two" %} {%cycle var2: "one", "two" %} {%cycle var1: "one", "two" %} {%cycle var2: "one", "two" %} {%cycle var1: "one", "two" %} {%cycle var2: "one", "two" %}', assigns)
end
def test_size_of_array
assigns = {"array" => [1,2,3,4]}
assert_template_result('array has 4 elements', "array has {{ array.size }} elements", assigns)
end
def test_size_of_hash
assigns = {"hash" => {:a => 1, :b => 2, :c=> 3, :d => 4}}
assert_template_result('hash has 4 elements', "hash has {{ hash.size }} elements", assigns)
end
def test_illegal_symbols
assert_template_result('', '{% if true == empty %}?{% endif %}', {})
assert_template_result('', '{% if true == null %}?{% endif %}', {})
assert_template_result('', '{% if empty == true %}?{% endif %}', {})
assert_template_result('', '{% if null == true %}?{% endif %}', {})
end
def test_for_reversed
assigns = {'array' => [ 1, 2, 3] }
assert_template_result('321','{%for item in array reversed %}{{item}}{%endfor%}',assigns)
end
def test_ifchanged
assigns = {'array' => [ 1, 1, 2, 2, 3, 3] }
assert_template_result('123','{%for item in array%}{%ifchanged%}{{item}}{% endifchanged %}{%endfor%}',assigns)
assigns = {'array' => [ 1, 1, 1, 1] }
assert_template_result('1','{%for item in array%}{%ifchanged%}{{item}}{% endifchanged %}{%endfor%}',assigns)
end
end

View File

@ -0,0 +1,137 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/helper'
class StatementsTest < Test::Unit::TestCase
include Liquid
def test_true_eql_true
text = %| {% if true == true %} true {% else %} false {% endif %} |
expected = %| true |
assert_equal expected, Template.parse(text).render
end
def test_true_not_eql_true
text = %| {% if true != true %} true {% else %} false {% endif %} |
expected = %| false |
assert_equal expected, Template.parse(text).render
end
def test_true_lq_true
text = %| {% if 0 > 0 %} true {% else %} false {% endif %} |
expected = %| false |
assert_equal expected, Template.parse(text).render
end
def test_one_lq_zero
text = %| {% if 1 > 0 %} true {% else %} false {% endif %} |
expected = %| true |
assert_equal expected, Template.parse(text).render
end
def test_zero_lq_one
text = %| {% if 0 < 1 %} true {% else %} false {% endif %} |
expected = %| true |
assert_equal expected, Template.parse(text).render
end
def test_zero_lq_or_equal_one
text = %| {% if 0 <= 0 %} true {% else %} false {% endif %} |
expected = %| true |
assert_equal expected, Template.parse(text).render
end
def test_zero_lq_or_equal_one_involving_nil
text = %| {% if null <= 0 %} true {% else %} false {% endif %} |
expected = %| false |
assert_equal expected, Template.parse(text).render
text = %| {% if 0 <= null %} true {% else %} false {% endif %} |
expected = %| false |
assert_equal expected, Template.parse(text).render
end
def test_zero_lqq_or_equal_one
text = %| {% if 0 >= 0 %} true {% else %} false {% endif %} |
expected = %| true |
assert_equal expected, Template.parse(text).render
end
def test_strings
text = %| {% if 'test' == 'test' %} true {% else %} false {% endif %} |
expected = %| true |
assert_equal expected, Template.parse(text).render
end
def test_strings_not_equal
text = %| {% if 'test' != 'test' %} true {% else %} false {% endif %} |
expected = %| false |
assert_equal expected, Template.parse(text).render
end
def test_var_strings_equal
text = %| {% if var == "hello there!" %} true {% else %} false {% endif %} |
expected = %| true |
assert_equal expected, Template.parse(text).render('var' => 'hello there!')
end
def test_var_strings_are_not_equal
text = %| {% if "hello there!" == var %} true {% else %} false {% endif %} |
expected = %| true |
assert_equal expected, Template.parse(text).render('var' => 'hello there!')
end
def test_var_and_long_string_are_equal
text = %| {% if var == 'hello there!' %} true {% else %} false {% endif %} |
expected = %| true |
assert_equal expected, Template.parse(text).render('var' => 'hello there!')
end
def test_var_and_long_string_are_equal_backwards
text = %| {% if 'hello there!' == var %} true {% else %} false {% endif %} |
expected = %| true |
assert_equal expected, Template.parse(text).render('var' => 'hello there!')
end
#def test_is_nil
# text = %| {% if var != nil %} true {% else %} false {% end %} |
# @template.assigns = { 'var' => 'hello there!'}
# expected = %| true |
# assert_equal expected, @template.parse(text)
#end
def test_is_collection_empty
text = %| {% if array == empty %} true {% else %} false {% endif %} |
expected = %| true |
assert_equal expected, Template.parse(text).render('array' => [])
end
def test_is_not_collection_empty
text = %| {% if array == empty %} true {% else %} false {% endif %} |
expected = %| false |
assert_equal expected, Template.parse(text).render('array' => [1,2,3])
end
def test_nil
text = %| {% if var == nil %} true {% else %} false {% endif %} |
expected = %| true |
assert_equal expected, Template.parse(text).render('var' => nil)
text = %| {% if var == null %} true {% else %} false {% endif %} |
expected = %| true |
assert_equal expected, Template.parse(text).render('var' => nil)
end
def test_not_nil
text = %| {% if var != nil %} true {% else %} false {% endif %} |
expected = %| true |
assert_equal expected, Template.parse(text).render('var' => 1 )
text = %| {% if var != null %} true {% else %} false {% endif %} |
expected = %| true |
assert_equal expected, Template.parse(text).render('var' => 1 )
end
end

View File

@ -0,0 +1,21 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/helper'
class StrainerTest < Test::Unit::TestCase
include Liquid
def test_strainer
strainer = Strainer.create(nil)
assert_equal false, strainer.respond_to?('__test__')
assert_equal false, strainer.respond_to?('test')
assert_equal false, strainer.respond_to?('instance_eval')
assert_equal false, strainer.respond_to?('__send__')
assert_equal true, strainer.respond_to?('size') # from the standard lib
end
def test_should_respond_to_two_parameters
strainer = Strainer.create(nil)
assert_equal true, strainer.respond_to?('size', false)
end
end

View File

@ -0,0 +1,26 @@
require File.dirname(__FILE__) + '/helper'
class TemplateTest < Test::Unit::TestCase
include Liquid
def test_tokenize_strings
assert_equal [' '], Template.new.send(:tokenize, ' ')
assert_equal ['hello world'], Template.new.send(:tokenize, 'hello world')
end
def test_tokenize_variables
assert_equal ['{{funk}}'], Template.new.send(:tokenize, '{{funk}}')
assert_equal [' ', '{{funk}}', ' '], Template.new.send(:tokenize, ' {{funk}} ')
assert_equal [' ', '{{funk}}', ' ', '{{so}}', ' ', '{{brother}}', ' '], Template.new.send(:tokenize, ' {{funk}} {{so}} {{brother}} ')
assert_equal [' ', '{{ funk }}', ' '], Template.new.send(:tokenize, ' {{ funk }} ')
end
def test_tokenize_blocks
assert_equal ['{%comment%}'], Template.new.send(:tokenize, '{%comment%}')
assert_equal [' ', '{%comment%}', ' '], Template.new.send(:tokenize, ' {%comment%} ')
assert_equal [' ', '{%comment%}', ' ', '{%endcomment%}', ' '], Template.new.send(:tokenize, ' {%comment%} {%endcomment%} ')
assert_equal [' ', '{% comment %}', ' ', '{% endcomment %}', ' '], Template.new.send(:tokenize, " {% comment %} {% endcomment %} ")
end
end

View File

@ -0,0 +1,20 @@
#!/usr/bin/env ruby
$LOAD_PATH.unshift(File.dirname(__FILE__)+ '/extra')
require 'test/unit'
require 'test/unit/assertions'
require 'caller'
require 'breakpoint'
require File.dirname(__FILE__) + '/../lib/liquid'
module Test
module Unit
module Assertions
include Liquid
def assert_template_result(expected, template, assigns={}, message=nil)
assert_equal expected, Template.parse(template).render(assigns)
end
end
end
end

View File

@ -0,0 +1,27 @@
require File.dirname(__FILE__) + '/helper'
class UnlessElseTest < Test::Unit::TestCase
include Liquid
def test_unless
assert_template_result(' ',' {% unless true %} this text should not go into the output {% endunless %} ')
assert_template_result(' this text should go into the output ',
' {% unless false %} this text should go into the output {% endunless %} ')
assert_template_result(' you rock ?','{% unless true %} you suck {% endunless %} {% unless false %} you rock {% endunless %}?')
end
def test_unless_else
assert_template_result(' YES ','{% unless true %} NO {% else %} YES {% endunless %}')
assert_template_result(' YES ','{% unless false %} YES {% else %} NO {% endunless %}')
assert_template_result(' YES ','{% unless "foo" %} NO {% else %} YES {% endunless %}')
end
def test_unless_in_loop
assert_template_result '23', '{% for i in choices %}{% unless i %}{{ forloop.index }}{% endunless %}{% endfor %}', 'choices' => [1, nil, false]
end
def test_unless_else_in_loop
assert_template_result ' TRUE 2 3 ', '{% for i in choices %}{% unless i %} {{ forloop.index }} {% else %} TRUE {% endunless %}{% endfor %}', 'choices' => [1, nil, false]
end
end

View File

@ -0,0 +1,135 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/helper'
class VariableTest < Test::Unit::TestCase
include Liquid
def test_variable
var = Variable.new('hello')
assert_equal 'hello', var.name
end
def test_filters
var = Variable.new('hello | textileze')
assert_equal 'hello', var.name
assert_equal [[:textileze,[]]], var.filters
var = Variable.new('hello | textileze | paragraph')
assert_equal 'hello', var.name
assert_equal [[:textileze,[]], [:paragraph,[]]], var.filters
var = Variable.new(%! hello | strftime: '%Y'!)
assert_equal 'hello', var.name
assert_equal [[:strftime,["'%Y'"]]], var.filters
var = Variable.new(%! 'typo' | link_to: 'Typo', true !)
assert_equal %!'typo'!, var.name
assert_equal [[:link_to,["'Typo'", "true"]]], var.filters
var = Variable.new(%! 'typo' | link_to: 'Typo', false !)
assert_equal %!'typo'!, var.name
assert_equal [[:link_to,["'Typo'", "false"]]], var.filters
var = Variable.new(%! 'foo' | repeat: 3 !)
assert_equal %!'foo'!, var.name
assert_equal [[:repeat,["3"]]], var.filters
var = Variable.new(%! 'foo' | repeat: 3, 3 !)
assert_equal %!'foo'!, var.name
assert_equal [[:repeat,["3","3"]]], var.filters
var = Variable.new(%! 'foo' | repeat: 3, 3, 3 !)
assert_equal %!'foo'!, var.name
assert_equal [[:repeat,["3","3","3"]]], var.filters
var = Variable.new(%! hello | strftime: '%Y, okay?'!)
assert_equal 'hello', var.name
assert_equal [[:strftime,["'%Y, okay?'"]]], var.filters
var = Variable.new(%! hello | things: "%Y, okay?", 'the other one'!)
assert_equal 'hello', var.name
assert_equal [[:things,["\"%Y, okay?\"","'the other one'"]]], var.filters
end
def test_filter_with_date_parameter
var = Variable.new(%! '2006-06-06' | date: "%m/%d/%Y"!)
assert_equal "'2006-06-06'", var.name
assert_equal [[:date,["\"%m/%d/%Y\""]]], var.filters
end
def test_filters_without_whitespace
var = Variable.new('hello | textileze | paragraph')
assert_equal 'hello', var.name
assert_equal [[:textileze,[]], [:paragraph,[]]], var.filters
var = Variable.new('hello|textileze|paragraph')
assert_equal 'hello', var.name
assert_equal [[:textileze,[]], [:paragraph,[]]], var.filters
end
def test_symbol
var = Variable.new("http://disney.com/logo.gif | image: 'med' ")
assert_equal 'http://disney.com/logo.gif', var.name
assert_equal [[:image,["'med'"]]], var.filters
end
def test_string_single_quoted
var = Variable.new(%| "hello" |)
assert_equal '"hello"', var.name
end
def test_string_double_quoted
var = Variable.new(%| 'hello' |)
assert_equal "'hello'", var.name
end
def test_integer
var = Variable.new(%| 1000 |)
assert_equal "1000", var.name
end
def test_float
var = Variable.new(%| 1000.01 |)
assert_equal "1000.01", var.name
end
def test_string_with_special_chars
var = Variable.new(%| 'hello! $!@.;"ddasd" ' |)
assert_equal %|'hello! $!@.;"ddasd" '|, var.name
end
def test_string_dot
var = Variable.new(%| test.test |)
assert_equal 'test.test', var.name
end
end
class VariableResolutionTest < Test::Unit::TestCase
include Liquid
def test_simple_variable
template = Template.parse(%|{{test}}|)
assert_equal 'worked', template.render('test' => 'worked')
assert_equal 'worked wonderfully', template.render('test' => 'worked wonderfully')
end
def test_simple_with_whitespaces
template = Template.parse(%| {{ test }} |)
assert_equal ' worked ', template.render('test' => 'worked')
assert_equal ' worked wonderfully ', template.render('test' => 'worked wonderfully')
end
def test_ignore_unknown
template = Template.parse(%|{{ test }}|)
assert_equal '', template.render
end
def test_hash_scoping
template = Template.parse(%|{{ test.test }}|)
assert_equal 'worked', template.render('test' => {'test' => 'worked'})
end
end