handlebar 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "http://rubygems.org"
2
+
3
+ group :development do
4
+ gem "bundler", "~> 1.0.0"
5
+ gem "jeweler", "~> 1.5.2"
6
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,16 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ git (1.2.5)
5
+ jeweler (1.5.2)
6
+ bundler (~> 1.0.0)
7
+ git (>= 1.2.5)
8
+ rake
9
+ rake (0.9.2)
10
+
11
+ PLATFORMS
12
+ ruby
13
+
14
+ DEPENDENCIES
15
+ bundler (~> 1.0.0)
16
+ jeweler (~> 1.5.2)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Scott Tadman
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,9 @@
1
+ = handlebar
2
+
3
+ A simple text templating system
4
+
5
+ == Copyright
6
+
7
+ Copyright (c) 2011 Scott Tadman, The Working Group Inc. See LICENSE.txt for
8
+ further details.
9
+
data/Rakefile ADDED
@@ -0,0 +1,36 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ begin
5
+ Bundler.setup(:default, :development)
6
+ rescue Bundler::BundlerError => e
7
+ $stderr.puts e.message
8
+ $stderr.puts "Run `bundle install` to install missing gems"
9
+ exit e.status_code
10
+ end
11
+
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+
16
+ Jeweler::Tasks.new do |gem|
17
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
18
+ gem.name = "handlebar"
19
+ gem.homepage = "http://github.com/twg/handlebar"
20
+ gem.license = "MIT"
21
+ gem.summary = %Q{Simple text tempating system}
22
+ gem.description = %Q{A simple text templating system}
23
+ gem.email = "github@tadman.ca"
24
+ gem.authors = [ "Scott Tadman" ]
25
+ end
26
+
27
+ Jeweler::RubygemsDotOrgTasks.new
28
+
29
+ require 'rake/testtask'
30
+ Rake::TestTask.new(:test) do |test|
31
+ test.libs << 'lib' << 'test'
32
+ test.pattern = 'test/**/test_*.rb'
33
+ test.verbose = true
34
+ end
35
+
36
+ task :default => :test
@@ -0,0 +1,46 @@
1
+ require 'uri'
2
+ require 'cgi'
3
+
4
+ module Handlebar::Support
5
+ def uri_escape(object)
6
+ URI.escape(object.to_s)
7
+ end
8
+
9
+ def html_escape(object)
10
+ CGI.escapeHTML(object.to_s)
11
+ end
12
+
13
+ def js_escape(object)
14
+ object.inspect
15
+ end
16
+
17
+ def css_escape(object)
18
+ [ object ].flatten.join(' ')
19
+ end
20
+
21
+ def iterate(object)
22
+ if (object.respond_to?(:each))
23
+ object.each do |i|
24
+ yield(i)
25
+ end
26
+ elsif (object)
27
+ yield(object)
28
+ end
29
+ end
30
+
31
+ def cast_as_vars(object, stack)
32
+ if (object.is_a?(Hash))
33
+ stack.each do |parent|
34
+ if (parent.is_a?(Hash))
35
+ object = parent.merge(object)
36
+ end
37
+ end
38
+
39
+ object
40
+ else
41
+ object.respond_to?(:each) ? object : [ object ]
42
+ end
43
+ end
44
+
45
+ extend self
46
+ end
@@ -0,0 +1,155 @@
1
+ class Handlebar::Template
2
+ # == Constants ============================================================
3
+
4
+ TOKEN_REGEXP = /((?:[^\{]|\{[^\{]|\{\{\{)+)|\{\{\s*([\&\%\$\.\:\?\*\/\=])?([^\}]*)\}\}/.freeze
5
+ TOKEN_TRIGGER = /\{\{/.freeze
6
+
7
+ # == Exceptions ===========================================================
8
+
9
+ class ParseError < Exception ; end
10
+ class ArgumentError < Exception; end
11
+ class MissingVariable < Exception ; end
12
+ class RecursionError < Exception; end
13
+
14
+ # == Class Methods ========================================================
15
+
16
+ # == Instance Methods =====================================================
17
+
18
+ def initialize(content, context = nil)
19
+ @context = context
20
+ @content =
21
+ case (content)
22
+ when IO
23
+ content.read
24
+ else
25
+ content.to_s
26
+ end
27
+ end
28
+
29
+ def to_proc
30
+ @proc ||= begin
31
+ method = ''
32
+ variables = nil
33
+ stack = [ [ :base, nil, 0 ] ]
34
+ h = Handlebar::Support
35
+
36
+ @content.scan(TOKEN_REGEXP).each do |text, tag_type, tag|
37
+ if (text)
38
+ text = text.sub(/\{(\{\{+)/, '\1').sub(/\}(\}\}+)/, '\1')
39
+
40
+ method << "r<<#{text.inspect};"
41
+ else
42
+ tag = tag.strip
43
+
44
+ case (tag_type)
45
+ when ?&
46
+ # HTML escaped
47
+ method << "v&&r<<h.html_escape(v[#{tag.to_sym.inspect}].to_s);"
48
+ when ?%
49
+ # URI escaped
50
+ method << "v&&r<<h.uri_escape(v[#{tag.to_sym.inspect}].to_s);"
51
+ when ?$
52
+ # JavaScript escaped
53
+ method << "v&&r<<h.js_escape(v[#{tag.to_sym.inspect}].to_s);"
54
+ when ?.
55
+ # CSS escaped
56
+ method << "v&&r<<h.css_escape(v[#{tag.to_sym.inspect}].to_s);"
57
+ when ?:
58
+ # Defines start of a :section
59
+ variables ||= 's=[];'
60
+ stack << [ :section, tag, 0 ]
61
+ method << "if(v);s<<v;v=v.is_a?(Hash)&&v[#{tag.to_sym.inspect}];"
62
+ method << "h.iterate(v){|v|;v=h.cast_as_vars(v, s);"
63
+ when ??
64
+ # Defines start of a ?conditional
65
+ stack << [ :conditional, tag ]
66
+ method << "if(v&&v.is_a?(Hash)&&v[#{tag.to_sym.inspect}]);"
67
+ when ?*
68
+ method << "_t=t&&t[#{tag.to_sym.inspect}];r<<(_t.respond_to?(:call)?_t.call(v,t):_t.to_s);"
69
+ when ?/
70
+ # Closes out a section or conditional
71
+ closed = stack.pop
72
+
73
+ case (closed[0])
74
+ when :section
75
+ unless (tag == closed[1] or tag.empty?)
76
+ raise ParseError, "Template contains unexpected {{#{tag}}}, expected {{#{closed[1]}}}"
77
+ end
78
+
79
+ method << "};v=s.pop;end;"
80
+ when :conditional
81
+ method << "end;"
82
+ when :base
83
+ raise ParseError, "Unexpected {{#{tag}}}, too many tags closed"
84
+ end
85
+ when ?=
86
+ # Literal insertion
87
+ method << "v&&r<<(v.is_a?(Array)?v[#{stack[-1][2]}]:v[#{tag.to_sym.inspect}]).to_s;"
88
+
89
+ stack[-1][2] += 1
90
+ else
91
+ # Contextual insertion
92
+ subst = "v.is_a?(Array)?v[#{stack[-1][2]}]:v[#{tag.to_sym.inspect}]"
93
+
94
+ if (@context)
95
+ method << "v&&r<<h.#{@context}_escape(#{subst}.to_s);"
96
+ else
97
+ method << "v&&r<<(#{subst}).to_s;"
98
+ end
99
+
100
+ stack[-1][2] += 1
101
+ end
102
+ end
103
+ end
104
+
105
+ unless (stack.length == 1)
106
+ case (stack[1][0])
107
+ when :section
108
+ raise ParseError, "Unclosed {{:#{stack[1][1]}}} in template"
109
+ when :conditional
110
+ raise ParseError, "Unclosed {{?#{stack[1][1]}}} in template"
111
+ else
112
+ raise ParseError, "Unclosed {{#{stack[1][1]}}} in template"
113
+ end
114
+ end
115
+
116
+ c = false
117
+ source = "lambda{|v,t|raise RecursionError if(c);c=true;#{variables}r='';#{method}c=false;r}"
118
+
119
+ eval(source)
120
+ end
121
+ end
122
+
123
+ def interpret(variables = nil, templates = nil)
124
+ variables =
125
+ case (variables)
126
+ when Array
127
+ variables
128
+ when Hash
129
+ Hash[variables.collect { |k, v| [ k.to_sym, v ] }]
130
+ else
131
+ [ variables ]
132
+ end
133
+
134
+ if (templates)
135
+ templates = Hash[
136
+ templates.collect do |k, v|
137
+ [
138
+ k,
139
+ case (v)
140
+ when Handlebar::Template, Proc
141
+ v
142
+ when TOKEN_TRIGGER
143
+ self.class.new(v)
144
+ else
145
+ v.to_s
146
+ end
147
+ ]
148
+ end
149
+ ]
150
+ end
151
+
152
+ self.to_proc.call(variables, templates)
153
+ end
154
+ alias_method :call, :interpret
155
+ end
data/lib/handlebar.rb ADDED
@@ -0,0 +1,6 @@
1
+ module Handlebar
2
+ # == Submodules ===========================================================
3
+
4
+ autoload(:Support, 'handlebar/support')
5
+ autoload(:Template, 'handlebar/template')
6
+ end
data/notes/example.hb ADDED
@@ -0,0 +1,30 @@
1
+ This is an example document with simple {{placeholder}} values included.
2
+
3
+ Each of these {{variable}} substitutions can be either {{simple}},
4
+ {{=literal}}, {{%url_escaped}}, {{.css_escaped}}, {{$json_escaped}} or
5
+ {{&html_escaped}} depending on preference.
6
+
7
+ It's even possible to place other {{*templates}} by name using the standard
8
+ markup, or include or default one using {{*}} instead of {{default}}.
9
+
10
+ Sometimes it's practical to define a block which can be re-used. These are
11
+ called sections:
12
+
13
+ {{:section}}
14
+ {{title}}
15
+
16
+ {{:bullet_point}}
17
+ * {{point}}
18
+ {{/}}
19
+ {{/:section}}
20
+
21
+ {{?conditional}}
22
+ It's also possible to render sections conditionally by testing if a value
23
+ is present and true.
24
+ {{/}}
25
+
26
+ * For avoiding interpolation, {{{three}}} or more braces can be used. The
27
+ outer most ones are always stripped, the rest left alone.
28
+
29
+ * Entire sections can be left unprocessed by declaring {{#raw}} which will
30
+ be terminated upon the first instance of {{#/}} if found.
data/test/helper.rb ADDED
@@ -0,0 +1,28 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ begin
5
+ Bundler.setup(:default, :development)
6
+ rescue Bundler::BundlerError => e
7
+ $stderr.puts e.message
8
+ $stderr.puts "Run `bundle install` to install missing gems"
9
+ exit e.status_code
10
+ end
11
+ require 'test/unit'
12
+
13
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
14
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
15
+
16
+ require 'handlebar'
17
+
18
+ class Test::Unit::TestCase
19
+ def assert_exception(exception_class, message = nil)
20
+ begin
21
+ yield
22
+ rescue exception_class
23
+ # Expected
24
+ else
25
+ flunk message || "Did not raise #{exception_class}"
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ class TestHandlebar < Test::Unit::TestCase
4
+ def test_empty_template
5
+ template = Handlebar::Template.new('')
6
+ end
7
+ end
@@ -0,0 +1,85 @@
1
+ require 'helper'
2
+
3
+ class TestHandlebarTemplate < Test::Unit::TestCase
4
+ def test_empty_template
5
+ template = Handlebar::Template.new('')
6
+
7
+ assert_equal '', template.interpret
8
+ end
9
+
10
+ def test_simple_templates
11
+ template = Handlebar::Template.new('example')
12
+
13
+ assert_equal 'example', template.interpret
14
+
15
+ template = Handlebar::Template.new('{{{example}}}')
16
+
17
+ assert_equal '{{example}}', template.interpret
18
+
19
+ template = Handlebar::Template.new('example {{example}} text')
20
+
21
+ assert_equal 'example something text', template.interpret(:example => 'something')
22
+
23
+ template = Handlebar::Template.new('example {{ example }} text')
24
+
25
+ assert_equal 'example something text', template.interpret(:example => 'something')
26
+ end
27
+
28
+ def test_boolean_templates
29
+ template = Handlebar::Template.new('{{?boolean}}true {{/}}false')
30
+
31
+ assert_equal 'false', template.interpret
32
+ assert_equal 'true false', template.interpret(:boolean => true)
33
+ assert_equal 'false', template.interpret(:boolean => false)
34
+ end
35
+
36
+ def test_sectioned_templates
37
+ template = Handlebar::Template.new('<head>{{:head}}<{{tag}}>{{/}}</head>')
38
+
39
+ assert_equal '<head><meta></head>', template.interpret(:head => 'meta')
40
+ assert_equal '<head><meta><link></head>', template.interpret(:head => %w[ meta link ])
41
+ assert_equal '<head><meta><link></head>', template.interpret(:head => [ { :tag => 'meta' }, { :tag => 'link' } ])
42
+ assert_equal '<head></head>', template.interpret
43
+ assert_equal '<head></head>', template.interpret(:head => nil)
44
+ assert_equal '<head></head>', template.interpret(:head => [ ])
45
+
46
+ template = Handlebar::Template.new('<div>{{:link}}<a href="{{href}}" alt="{{alt}}">{{/}}</div>')
47
+
48
+ assert_equal '<div><a href="meta" alt=""></div>', template.interpret(:link => 'meta')
49
+ assert_equal '<div><a href="meta" alt="link"></div>', template.interpret(:link => [ %w[ meta link ] ])
50
+ assert_equal '<div><a href="/h" alt=""><a href="" alt="alt"><a href="/" alt="top"></div>', template.interpret(:link => [ { :href => '/h' }, { :alt => 'alt' }, { :href => '/', :alt => 'top' } ])
51
+ assert_equal '<div></div>', template.interpret
52
+ assert_equal '<div></div>', template.interpret(:link => nil)
53
+ assert_equal '<div></div>', template.interpret(:link => [ ])
54
+ end
55
+
56
+ def test_template_with_context
57
+ template = Handlebar::Template.new('{{example}}', :html)
58
+
59
+ assert_equal '&lt;strong&gt;', template.interpret('<strong>')
60
+
61
+ template = Handlebar::Template.new('{{=example}}', :html)
62
+
63
+ assert_equal '<strong>', template.interpret('<strong>')
64
+ end
65
+
66
+ def test_recursive_templates
67
+ template = Handlebar::Template.new('{{*example}}', :html)
68
+
69
+ assert_equal 'child', template.interpret(nil, { :example => '{{*parent}}', :parent => 'child' })
70
+ end
71
+
72
+ def test_missing_templates
73
+ template = Handlebar::Template.new('{{*example}}', :html)
74
+
75
+ assert_equal '', template.interpret(nil, { })
76
+ end
77
+
78
+ def test_recursive_circular_templates
79
+ template = Handlebar::Template.new('{{*reference}}', :html)
80
+
81
+ assert_exception Handlebar::Template::RecursionError do
82
+ template.interpret(nil, { :reference => '{{*backreference}}', :backreference => '{{*reference}}' })
83
+ end
84
+ end
85
+ end
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: handlebar
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.0
6
+ platform: ruby
7
+ authors:
8
+ - Scott Tadman
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-06-29 00:00:00 -04:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: bundler
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ~>
23
+ - !ruby/object:Gem::Version
24
+ version: 1.0.0
25
+ type: :development
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: jeweler
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ~>
34
+ - !ruby/object:Gem::Version
35
+ version: 1.5.2
36
+ type: :development
37
+ version_requirements: *id002
38
+ description: A simple text templating system
39
+ email: github@tadman.ca
40
+ executables: []
41
+
42
+ extensions: []
43
+
44
+ extra_rdoc_files:
45
+ - LICENSE.txt
46
+ - README.rdoc
47
+ files:
48
+ - .document
49
+ - Gemfile
50
+ - Gemfile.lock
51
+ - LICENSE.txt
52
+ - README.rdoc
53
+ - Rakefile
54
+ - lib/handlebar.rb
55
+ - lib/handlebar/support.rb
56
+ - lib/handlebar/template.rb
57
+ - notes/example.hb
58
+ - test/helper.rb
59
+ - test/test_handlebar.rb
60
+ - test/test_handlebar_template.rb
61
+ has_rdoc: true
62
+ homepage: http://github.com/twg/handlebar
63
+ licenses:
64
+ - MIT
65
+ post_install_message:
66
+ rdoc_options: []
67
+
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: "0"
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: "0"
82
+ requirements: []
83
+
84
+ rubyforge_project:
85
+ rubygems_version: 1.5.3
86
+ signing_key:
87
+ specification_version: 3
88
+ summary: Simple text tempating system
89
+ test_files:
90
+ - test/helper.rb
91
+ - test/test_handlebar.rb
92
+ - test/test_handlebar_template.rb