diff --git a/History.txt b/History.txt
index 2f1c37f..d140a20 100644
--- a/History.txt
+++ b/History.txt
@@ -1,3 +1,12 @@
+2.2.1 / 2010-08-23
+
+* Added support for literal tags
+
+2.2.0 / 2010-08-22
+
+* Compatible with Ruby 1.8.7, 1.9.1 and 1.9.2-p0
+* Merged some changed made by the community
+
1.9.0 / 2008-03-04
* Fixed gem install rake task
diff --git a/Manifest.txt b/Manifest.txt
index 593e0bf..cbc3e89 100644
--- a/Manifest.txt
+++ b/Manifest.txt
@@ -2,7 +2,7 @@ CHANGELOG
History.txt
MIT-LICENSE
Manifest.txt
-README.txt
+README.md
Rakefile
init.rb
lib/extras/liquid_view.rb
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8480a29
--- /dev/null
+++ b/README.md
@@ -0,0 +1,42 @@
+# Liquid template engine
+
+## Introduction
+
+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 (PHP) 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 templating engine
+
+## What does it look like?
+
+
+
+ {% for product in products %}
+ -
+
{{product.name}}
+ Only {{product.price | price }}
+
+ {{product.description | prettyprint | paragraph }}
+
+ {% endfor %}
+
+
+
+## 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"
+
\ No newline at end of file
diff --git a/README.txt b/README.txt
deleted file mode 100644
index 1d019af..0000000
--- a/README.txt
+++ /dev/null
@@ -1,38 +0,0 @@
-= 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?
-
-
- {% for product in products %}
- -
-
{{product.name}}
- Only {{product.price | price }}
-
- {{product.description | prettyprint | paragraph }}
-
- {% endfor %}
-
-
-== 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"
\ No newline at end of file
diff --git a/lib/liquid.rb b/lib/liquid.rb
index dccef10..533bfb4 100644
--- a/lib/liquid.rb
+++ b/lib/liquid.rb
@@ -45,6 +45,7 @@ module Liquid
PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/
TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/
VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/
+ LiteralShorthand = /^(?:\{\{\{\s?)(.*?)(?:\s*\}\}\})$/
end
require 'liquid/drop'
diff --git a/lib/liquid/tag.rb b/lib/liquid/tag.rb
index 2750064..b7f2aa4 100644
--- a/lib/liquid/tag.rb
+++ b/lib/liquid/tag.rb
@@ -1,6 +1,7 @@
module Liquid
class Tag
+
attr_accessor :nodelist
def initialize(tag_name, markup, tokens)
@@ -19,8 +20,7 @@ module Liquid
def render(context)
''
end
- end
+ end # Tag
-end
-
+end # Tag
diff --git a/lib/liquid/tags/comment.rb b/lib/liquid/tags/comment.rb
index 8ce7e0e..37fb4c8 100644
--- a/lib/liquid/tags/comment.rb
+++ b/lib/liquid/tags/comment.rb
@@ -1,9 +1,9 @@
module Liquid
- class Comment < Block
+ class Comment < Block
def render(context)
''
- end
+ end
end
-
- Template.register_tag('comment', Comment)
-end
\ No newline at end of file
+
+ Template.register_tag('comment', Comment)
+end
diff --git a/lib/liquid/tags/if.rb b/lib/liquid/tags/if.rb
index f060000..3b77732 100644
--- a/lib/liquid/tags/if.rb
+++ b/lib/liquid/tags/if.rb
@@ -14,17 +14,16 @@ module Liquid
class If < Block
SyntaxHelp = "Syntax Error in tag 'if' - Valid syntax: if [expression]"
Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/
- ExpressionsAndOperators = /(?:\b(?:and|or)\b|(?:\s*(?!\b(?:and|or)\b)(?:#{QuotedFragment}|\S+)\s*)+)/
-
- def initialize(tag_name, markup, tokens)
-
+ ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/
+
+ def initialize(tag_name, markup, tokens)
@blocks = []
-
+
push_block('if', markup)
-
- super
+
+ super
end
-
+
def unknown_tag(tag, markup, tokens)
if ['elsif', 'else'].include?(tag)
push_block(tag, markup)
@@ -32,49 +31,49 @@ module Liquid
super
end
end
-
+
def render(context)
context.stack do
@blocks.each do |block|
- if block.evaluate(context)
- return render_all(block.attachment, context)
+ if block.evaluate(context)
+ return render_all(block.attachment, context)
end
- end
+ end
''
end
end
-
- private
-
- def push_block(tag, markup)
- block = if tag == 'else'
- ElseCondition.new
- else
-
- expressions = markup.scan(ExpressionsAndOperators).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
+ private
+
+ def push_block(tag, markup)
+ block = if tag == 'else'
+ ElseCondition.new
+ else
+
+ expressions = markup.scan(ExpressionsAndOperators).reverse
+ raise(SyntaxError, SyntaxHelp) unless expressions.shift =~ Syntax
+
+ condition = Condition.new($1, $2, $3)
+
+ while not expressions.empty?
+ operator = (expressions.shift).to_s.strip
+
+ 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
-
- @blocks.push(block)
- @nodelist = block.attach(Array.new)
- end
-
-
+
+
end
Template.register_tag('if', If)
-end
\ No newline at end of file
+end
diff --git a/lib/liquid/tags/literal.rb b/lib/liquid/tags/literal.rb
new file mode 100644
index 0000000..67f1600
--- /dev/null
+++ b/lib/liquid/tags/literal.rb
@@ -0,0 +1,42 @@
+module Liquid
+
+ class Literal < Block
+
+ # Class methods
+
+ # Converts a shorthand Liquid literal into its long representation.
+ #
+ # Currently the Template parser only knows how to handle the long version.
+ # So, it always checks if it is in the presence of a literal, in which case it gets converted through this method.
+ #
+ # Example:
+ # Liquid::Literal "{{{ hello world }}}" #=> "{% literal %} hello world {% endliteral %}"
+ def self.from_shorthand(literal)
+ literal =~ LiteralShorthand ? "{% literal %}#{$1}{% endliteral %}" : literal
+ end
+
+ # Public instance methods
+
+ def parse(tokens) # :nodoc:
+ @nodelist ||= []
+ @nodelist.clear
+
+ while token = tokens.shift
+ if token =~ FullToken && block_delimiter == $1
+ end_tag
+ return
+ 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 # parse
+
+ end
+
+ Template.register_tag('literal', Literal)
+end
diff --git a/lib/liquid/template.rb b/lib/liquid/template.rb
index edfb980..d346c44 100644
--- a/lib/liquid/template.rb
+++ b/lib/liquid/template.rb
@@ -55,7 +55,7 @@ module Liquid
# Parse source code.
# Returns self for easy chaining
def parse(source)
- @root = Document.new(tokenize(source))
+ @root = Document.new(tokenize(Liquid::Literal.from_shorthand(source)))
self
end
diff --git a/liquid.gemspec b/liquid.gemspec
index 7d957f6..ed78917 100644
--- a/liquid.gemspec
+++ b/liquid.gemspec
@@ -1,16 +1,16 @@
Gem::Specification.new do |s|
s.name = %q{liquid}
- s.version = "2.2.0"
+ s.version = "2.2.1"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Tobias Luetke"]
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.extra_rdoc_files = ["History.txt", "Manifest.txt", "README.md"]
+ s.files = ["CHANGELOG", "History.txt", "MIT-LICENSE", "Manifest.txt", "README.md", "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.rdoc_options = ["--main", "README.md"]
s.require_paths = ["lib"]
s.rubyforge_project = %q{liquid}
s.rubygems_version = %q{1.3.1}
diff --git a/test/lib/liquid/condition_test.rb b/test/lib/liquid/condition_test.rb
index 5027798..7dd096e 100644
--- a/test/lib/liquid/condition_test.rb
+++ b/test/lib/liquid/condition_test.rb
@@ -48,16 +48,14 @@ class ConditionTest < Test::Unit::TestCase
@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"'
-
+ 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_contains_returns_false_for_nil_operands
@@ -94,17 +92,23 @@ class ConditionTest < Test::Unit::TestCase
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'"
+ assert_evalutes_true "'bob'", 'starts_with', "'b'"
+ assert_evalutes_false "'bob'", 'starts_with', "'o'"
ensure
Condition.operators.delete 'starts_with'
end
+ def test_left_or_right_may_contain_operators
+ @context = Liquid::Context.new
+ @context['one'] = @context['another'] = "gnomeslab-and-or-liquid"
+
+ assert_evalutes_true "one", '==', "another"
+ end
+
private
def assert_evalutes_true(left, op, right)
assert Condition.new(left, op, right).evaluate(@context || Liquid::Context.new),
diff --git a/test/lib/liquid/regexp_test.rb b/test/lib/liquid/regexp_test.rb
index 280d62b..1259d97 100644
--- a/test/lib/liquid/regexp_test.rb
+++ b/test/lib/liquid/regexp_test.rb
@@ -41,4 +41,9 @@ class RegexpTest < Test::Unit::TestCase
assert_equal ['var', '["method"]', '[0]'], 'var["method"][0]'.scan(VariableParser)
assert_equal ['var', '[method]', '[0]', 'method'], 'var[method][0].method'.scan(VariableParser)
end
+
+ def test_literal_shorthand_regexp
+ assert_equal [["{% if 'gnomeslab' contains 'liquid' %}yes{% endif %}"]],
+ "{{{ {% if 'gnomeslab' contains 'liquid' %}yes{% endif %} }}}".scan(LiteralShorthand)
+ end
end # RegexpTest
diff --git a/test/lib/liquid/tags/if_else_test.rb b/test/lib/liquid/tags/if_else_test.rb
index 2766290..cd5427b 100644
--- a/test/lib/liquid/tags/if_else_test.rb
+++ b/test/lib/liquid/tags/if_else_test.rb
@@ -150,4 +150,11 @@ class IfElseTest < Test::Unit::TestCase
ensure
Condition.operators.delete 'contains'
end
+
+ def test_operators_are_ignored_unless_isolated
+ Condition.operators['contains'] = :[]
+
+ assert_template_result('yes',
+ %({% if 'gnomeslab-and-or-liquid' contains 'gnomeslab-and-or-liquid' %}yes{% endif %}))
+ end
end # IfElseTest
diff --git a/test/lib/liquid/tags/literal_test.rb b/test/lib/liquid/tags/literal_test.rb
new file mode 100644
index 0000000..d5b970c
--- /dev/null
+++ b/test/lib/liquid/tags/literal_test.rb
@@ -0,0 +1,39 @@
+require 'test_helper'
+
+class LiteralTagTest < Test::Unit::TestCase
+ include Liquid
+
+ def test_empty_literal
+ assert_template_result '', '{% literal %}{% endliteral %}'
+ assert_template_result '', '{{{}}}'
+ end
+
+ def test_simple_literal_value
+ assert_template_result 'howdy',
+ '{% literal %}howdy{% endliteral %}'
+ end
+
+ def test_literals_ignore_liquid_markup
+ expected = %({% if 'gnomeslab' contain 'liquid' %}yes{ % endif %})
+ template = %({% literal %}#{expected}{% endliteral %})
+
+ assert_template_result expected, template
+ end
+
+ def test_shorthand_syntax
+ expected = %({% if 'gnomeslab' contain 'liquid' %}yes{ % endif %})
+ template = %({{{#{expected}}}})
+
+ assert_template_result expected, template
+ end
+
+ # Class methods
+ def test_from_shorthand
+ assert_equal '{% literal %}gnomeslab{% endliteral %}', Liquid::Literal.from_shorthand('{{{gnomeslab}}}')
+ end
+
+ def test_from_shorthand_ignores_improper_syntax
+ text = "{% if 'hi' == 'hi' %}hi{% endif %}"
+ assert_equal text, Liquid::Literal.from_shorthand(text)
+ end
+end # AssignTest
\ No newline at end of file
diff --git a/test/test_helper.rb b/test/test_helper.rb
index f64c301..32f96f5 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -2,10 +2,12 @@
extras_path = File.join File.dirname(__FILE__), 'extra'
$LOAD_PATH.unshift(extras_path) unless $LOAD_PATH.include? extras_path
+require 'rubygems' unless RUBY_VERSION =~ /^(?:1.9.*)$/
require 'test/unit'
require 'test/unit/assertions'
require 'caller'
require 'breakpoint'
+require 'ruby-debug'
require File.join File.dirname(__FILE__), '..', 'lib', 'liquid'
@@ -16,9 +18,15 @@ module Test
module Assertions
include Liquid
- def assert_template_result(expected, template, assigns={}, message=nil)
+ def assert_template_result(expected, template, assigns = {}, message = nil)
assert_equal expected, Template.parse(template).render(assigns)
end
+
+ def assert_template_result_matches(expected, template, assigns = {}, message = nil)
+ return assert_template_result(expected, template, assigns, message) unless expected.is_a? Regexp
+
+ assert_match expected, Template.parse(template).render(assigns)
+ end
end # Assertions
end # Unit