handlebar 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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