hamlbars 1.0.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/History.md ADDED
@@ -0,0 +1,7 @@
1
+ # master
2
+
3
+ * Drop support for `.hbs` extension (#11)
4
+
5
+ # 2012.3.21
6
+
7
+ * (No history past this point. Here be dragons.)
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008-2010 Justin French
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.md ADDED
@@ -0,0 +1,129 @@
1
+ # Hamlbars
2
+
3
+ [![Build Status](https://secure.travis-ci.org/jamesotron/hamlbars.png?branch=master)](http://travis-ci.org/jamesotron/hamlbars)
4
+ [![Dependency Status](https://gemnasium.com/jamesotron/hamlbars.png)](https://gemnasium.com/jamesotron/hamlbars)
5
+
6
+ [Hamlbars](https://github.com/jamesotron/hamlbars) is a Ruby gem which allows you to easily generate [Handlebar](http://handlebarsjs.com) templates using [Haml](http://www.haml-lang.com).
7
+
8
+ # Demo Site
9
+
10
+ If you're unsure how all the pieces fit together then take a quick look at the [demo site](http://hamlbars-demo.herokuapp.com/).
11
+
12
+ # Attribute bindings
13
+
14
+ You can easily add attribute bindings by adding a `:bind` hash to the tag attributes, like so:
15
+
16
+ %div{ :class => 'widget', :bind => { :title => 'App.widgetController.title' }
17
+
18
+ Which will generate the following output:
19
+
20
+ <div class="widget" {{bindAttr title="App.widgetController.title"}}></div>
21
+
22
+ # Event bindings
23
+
24
+ You can add one or more event actions by adding an event hash or array or event hashes to the tag options:
25
+
26
+ %a{ :event => { :on => 'click', :action => 'clicked' } } Click
27
+
28
+ or
29
+
30
+ %div{ :events => [ { :on => 'mouseover', :action => 'highlightView' }, { :on => 'mouseout', :action => 'disableViewHighlight' } ] }
31
+
32
+ Note that the default event is `click`, so it's not necessary to specify it:
33
+
34
+ %a{ :event => { :action => 'clicked' } } Click
35
+
36
+ # Handlebar helper
37
+
38
+ You can use the `handlebars` helper (or just `hb` for short) to generate both Handlebar blocks and expressions.
39
+
40
+ ## Expressions
41
+
42
+ Generating Handlebars expressions is as simple as using the `handlebars` helper and providing the expression as a string argument:
43
+
44
+ = hb 'App.widgetController.title'
45
+
46
+ which will will generate:
47
+
48
+ {{App.widgetController.title}}
49
+
50
+ ## Blocks
51
+
52
+ Whereas passing a block to the `handlebars` helper will create a Handlebars block expression:
53
+
54
+ %ul.authors
55
+ = hb 'each authors' do
56
+ %li<
57
+ = succeed ',' do
58
+ = hb 'lastName'
59
+ = hb 'firstName'
60
+
61
+ will result in the following markup:
62
+
63
+ <ul class="authors">
64
+ {{#each authors}}
65
+ <li>{{lastName}}, {{firstName}}</li>
66
+ {{/each}}
67
+ </ul>
68
+
69
+ ## Options
70
+
71
+ The `hb` helper can take an optional hash of options which will be rendered inside the expression:
72
+
73
+ = hb 'view App.InfoView', :tagName => 'span'
74
+
75
+ will result in:
76
+
77
+ {{view App.InfoView tagName="span"}}
78
+
79
+ ## Tripple-stash
80
+
81
+ You can use the `handlebars!` or `hb!` variant of the `handlebars` helper to output "tripple-stash" expressions within which Handlebars does not escape the output.
82
+
83
+ # Configuring template output:
84
+
85
+ `hamlbars` has three configuration options, which pertain to the generated JavaScript:
86
+
87
+ Hamlbars::Template.template_destination # default 'Handlebars.templates'
88
+ Hamlbars::Template.template_compiler # default 'Handlebars.compile'
89
+ Hamlbars::Template.template_partial_method # default 'Handlebars.registerPartial'
90
+
91
+ These settings will work find by default if you are using Handlebars as a standalone JavaScript library, however if you are using something that embeds Handlebars within it then you'll have to change these.
92
+
93
+ If you're using [Ember.js](http://www.emberjs.com) then you can use:
94
+
95
+ Hamlbars::Template.render_templates_for :ember
96
+
97
+ Which is effectively the same as:
98
+
99
+ Hamlbars::Template.template_destination = 'Ember.TEMPLATES'
100
+ Hamlbars::Template.template_compiler = 'Ember.Handlebars.compile'
101
+ Hamlbars::Template.template_partial_method = 'Ember.Handlebars.registerPartial'
102
+
103
+ The good news is that if you're using the [emberjs-rails](http://www.rubygems.org/gems/emberjs-rails) gem then it will automatically detect hamlbars and change it for you. Magic!
104
+
105
+ If you're using [ember-rails](http://rubygems.org/gems/ember-rails) then you'll need to put this in a initializer.
106
+
107
+ # Configuring JavaScript output:
108
+
109
+ As of version 2012.3.21 `hamlbars` has experimental support for template precompilation using [ExecJS](http://rubygems.org/gems/execjs). If you want to enable this support you can use:
110
+
111
+ Hamlbars::Template.enable_precompiler!
112
+
113
+ You can also disable enclosification (which is enabled by default) using:
114
+
115
+ Hamlbars::Template.disable_closures!
116
+
117
+ # Asset pipeline
118
+
119
+ Hamlbars is specifically designed for use with Rails 3.1's asset pipeline. Simply create templates ending in `.js.hamlbars` and Sprockets will know what to do.
120
+
121
+ # Rails helpers
122
+
123
+ You can enable support by calling `Hamlbars::Template.enable_rails_helpers!`. Probably the best way to do this is to create an initializer. This is dangerous and possibly stupid as a large number of Rails' helpers require access to the request object, which is not present when compiling assets.
124
+
125
+ **Use at your own risk. You have been warned.**
126
+
127
+ # License and Copyright.
128
+
129
+ Hamlbars is Copyright &copy; 2012 [Sociable Limited](http://sociable.co.nz/) and licensed under the terms of the MIT License.
@@ -0,0 +1,43 @@
1
+ module Hamlbars
2
+ module Ext
3
+ module Closure
4
+
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+ end
8
+
9
+ # Wraps the JavaScript generated by Haml::Template#evaluate
10
+ # in a JavaScript closure.
11
+ def evaluate_with_closure(scope, locals, &block)
12
+ self.class.closures_enabled? ? enclosify { evaluate_without_closure(scope,locals,&block) } : evaluate_without_closure(scope,locals,&block)
13
+ end
14
+
15
+ private
16
+
17
+ def enclosify
18
+ "(function() { #{yield} }).call(this)"
19
+ end
20
+
21
+ module ClassMethods
22
+ # Enables closure wrapping for rendered templates.
23
+ def enable_closures!
24
+ @enable_closures = true
25
+ unless public_method_defined? :evaluate_without_closure
26
+ alias_method :evaluate_without_closure, :evaluate
27
+ alias_method :evaluate, :evaluate_with_closure
28
+ end
29
+ end
30
+
31
+ def closures_enabled?
32
+ !!@enable_closures
33
+ end
34
+
35
+ # Disables closure wrapping for rendered templates.
36
+ def disable_closures!
37
+ @enable_closures = false
38
+ end
39
+ end
40
+
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,45 @@
1
+ module Hamlbars
2
+ module Ext
3
+ module Compiler
4
+ end
5
+ end
6
+ end
7
+
8
+ module Haml
9
+ module Compiler
10
+
11
+ class << self
12
+ # Overload build_attributes in Haml::Compiler to allow
13
+ # for the creation of handlebars bound attributes by
14
+ # adding :bind hash to the tag attributes.
15
+ def build_attributes_with_handlebars_attributes (is_html, attr_wrapper, escape_attrs, attributes={})
16
+ attributes[:bind] = attributes.delete('bind') if attributes['bind']
17
+ attributes[:event] = attributes.delete('event') if attributes['event']
18
+ attributes[:events] = attributes.delete('events') if attributes['events']
19
+ attributes[:events] ||= []
20
+ attributes[:events] << attributes.delete(:event) if attributes[:event]
21
+
22
+ handlebars_rendered_attributes = []
23
+ handlebars_rendered_attributes << handlebars_attributes('bindAttr', attributes.delete(:bind)) if attributes[:bind]
24
+ attributes[:events].each do |event|
25
+ event[:on] = event.delete('on') || event.delete(:on) || 'click'
26
+ action = event.delete('action') || event.delete(:action)
27
+ handlebars_rendered_attributes << handlebars_attributes("action \"#{action}\"", event)
28
+ end
29
+ attributes.delete(:events)
30
+
31
+ (handlebars_rendered_attributes * '') +
32
+ build_attributes_without_handlebars_attributes(is_html, attr_wrapper, escape_attrs, attributes)
33
+ end
34
+ alias build_attributes_without_handlebars_attributes build_attributes
35
+ alias build_attributes build_attributes_with_handlebars_attributes
36
+
37
+ private
38
+
39
+ def handlebars_attributes(helper, attributes)
40
+ rendered_attributes = [].tap { |r|attributes.each { |k,v| r << "#{k}=\"#{v}\"" } }.join(' ')
41
+ " {{#{helper} #{rendered_attributes}}}"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,71 @@
1
+ require 'execjs'
2
+
3
+ module Hamlbars
4
+ module Ext
5
+ module Precompiler
6
+
7
+ def self.included(base)
8
+ base.extend ClassMethods
9
+ end
10
+
11
+ # Takes the rendered template and compiles it using the Handlebars
12
+ # compiler via ExecJS.
13
+ def evaluate_with_precompiler(scope, locals, &block)
14
+ if self.class.precompiler_enabled?
15
+ precompile { evaluate_without_precompiler(scope,locals,&block) }
16
+ else
17
+ evaluate_without_precompiler(scope,locals,&block)
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def precompile
24
+ str = yield
25
+ str.gsub(Regexp.new("(#{Hamlbars::Template.template_compiler})\((.+)\)")) do |match|
26
+ # No named groups. WAT!
27
+ compiler = $1
28
+ template = $2
29
+ if compiler =~ /\.compile$/
30
+ "#{compiler.gsub(/\.compile$/, '.template')}(#{runtime.call('Hamlbars.precompile', template)})"
31
+ else
32
+ # Unable to precompile.
33
+ match
34
+ end
35
+ end
36
+ end
37
+
38
+ def runtime
39
+ Thread.current[:hamlbars_js_runtime] ||= ExecJS.compile(js)
40
+ end
41
+
42
+ def js
43
+ [ 'handlebars.js', 'precompiler.js' ].map do |name|
44
+ File.read(File.expand_path("../../../../vendor/javascripts/#{name}", __FILE__))
45
+ end.join("\n")
46
+ end
47
+
48
+ module ClassMethods
49
+
50
+ # Enables use of the Handlebars compiler when rendering
51
+ # templates.
52
+ def enable_precompiler!
53
+ @precompiler_enabled = true
54
+ unless public_method_defined? :evaluate_without_precompiler
55
+ alias_method :evaluate_without_precompiler, :evaluate
56
+ alias_method :evaluate, :evaluate_with_precompiler
57
+ end
58
+ end
59
+
60
+ def precompiler_enabled?
61
+ !!@precompiler_enabled
62
+ end
63
+
64
+ def disable_precompiler!
65
+ @precompiler_enabled = false
66
+ end
67
+ end
68
+
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,38 @@
1
+ module Hamlbars
2
+ module Ext
3
+ module RailsHelper
4
+
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+ end
8
+
9
+ def evaluate(scope,locals,&block)
10
+ scope = scope.dup
11
+
12
+ scope.class.send(:include, ActionView::Helpers) if defined?(::ActionView)
13
+ if defined?(::Rails)
14
+ scope.class.send(:include, Rails.application.helpers)
15
+ scope.class.send(:include, Rails.application.routes.url_helpers)
16
+ scope.default_url_options = Rails.application.config.action_controller.default_url_options || {}
17
+ end
18
+ super(scope, locals, &block)
19
+ end
20
+
21
+ module ClassMethods
22
+ def enable_rails_helpers!
23
+ (Rails.env == 'development' ? Logger.new(STDOUT) : Rails.logger).warn "WARNING (hamlbars): Enabling helpers in assets can have unintended consequences and violates separation of concerns. You have been warned."
24
+ @enable_rails_helpers = true
25
+ end
26
+
27
+ def rails_helpers_enabled?
28
+ !!@enable_rails_helpers
29
+ end
30
+
31
+ def disable_rails_helpers!
32
+ @enable_rails_helpers = false
33
+ end
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,8 @@
1
+ module Hamlbars
2
+ module Ext
3
+ autoload :Compiler, File.expand_path('../ext/compiler.rb', __FILE__)
4
+ autoload :Precompiler, File.expand_path('../ext/precompiler.rb', __FILE__)
5
+ autoload :Closure, File.expand_path('../ext/closure.rb', __FILE__)
6
+ autoload :RailsHelper, File.expand_path('../ext/rails_helper.rb', __FILE__)
7
+ end
8
+ end
@@ -0,0 +1,207 @@
1
+ require 'tilt/template'
2
+
3
+ module Hamlbars
4
+ class Template < Tilt::Template
5
+ include Ext::Closure
6
+ enable_closures! # Enable closures by default.
7
+ include Ext::Precompiler
8
+ if defined? Rails
9
+ include Ext::RailsHelper
10
+ enable_precompiler! if Rails.env.production?
11
+ end
12
+
13
+ JS_ESCAPE_MAP = {
14
+ "\r\n" => '\n',
15
+ "\n" => '\n',
16
+ "\r" => '\n',
17
+ '"' => '\\"',
18
+ "'" => "\\'"
19
+ }
20
+
21
+ # Used to change the asset path into a string which is
22
+ # safe to use as a JavaScript object property.
23
+ def self.path_translator(path)
24
+ path.downcase.gsub(/[^a-z0-9\/]/, '_')
25
+ end
26
+
27
+ # Handy helper to preconfigure Hamlbars to render for
28
+ # either :handlebars (default) or :ember.
29
+ def self.render_templates_for(whom=:handlebars)
30
+ if whom == :handlebars
31
+ self.template_destination = 'Handlebars.templates'
32
+ self.template_compiler = 'Handlebars.compile'
33
+ self.template_partial_method = 'Handlebars.registerPartial'
34
+ elsif whom == :ember
35
+ self.template_destination = 'Ember.TEMPLATES'
36
+ self.template_compiler = 'Ember.Handlebars.compile'
37
+ self.template_partial_method = 'Ember.Handlebars.registerPartial'
38
+ end
39
+ end
40
+
41
+ # The target object where the rendered template will
42
+ # be stored on the client side.
43
+ # Defaults to 'Handlebars.templates'
44
+ def self.template_destination
45
+ @template_destination ||= 'Handlebars.templates'
46
+ end
47
+
48
+ def self.template_destination=(x)
49
+ @template_destination = x
50
+ end
51
+
52
+ # The JavaScript function used to compile the HTML
53
+ # string into a usable Handlebars template.
54
+ def self.template_compiler
55
+ @template_compiler ||= 'Handlebars.compile'
56
+ end
57
+
58
+ def self.template_compiler=(x)
59
+ @template_compiler = x
60
+ end
61
+
62
+ # The JavaScript function used on the compile side to
63
+ # register the template as a partial on the client side.
64
+ def self.template_partial_method
65
+ @template_partial_method ||= 'Handlebars.registerPartial'
66
+ end
67
+
68
+ def self.template_partial_method=(x)
69
+ @template_partial_method = x
70
+ end
71
+
72
+ self.default_mime_type = 'application/javascript'
73
+
74
+ def self.engine_initialized?
75
+ defined? ::Haml::Engine
76
+ end
77
+
78
+ def initialize_engine
79
+ require_template_library 'haml'
80
+ end
81
+
82
+ def prepare
83
+ options = @options.merge(:filename => eval_file, :line => line)
84
+ @engine = ::Haml::Engine.new(data, options)
85
+ end
86
+
87
+ # Uses Haml to render the template into an HTML string, then
88
+ # wraps it in the neccessary JavaScript to serve to the client.
89
+ def evaluate(scope, locals, &block)
90
+ template = if @engine.respond_to?(:precompiled_method_return_value, true)
91
+ super(scope, locals, &block)
92
+ else
93
+ @engine.render(scope, locals, &block)
94
+ end
95
+
96
+ if scope.respond_to? :logical_path
97
+ path = scope.logical_path
98
+ else
99
+ path = basename
100
+ end
101
+
102
+ if basename =~ /^_/
103
+ name = partial_path_translator(path)
104
+ "#{self.class.template_partial_method}('#{name}', '#{template.strip.gsub(/(\r\n|[\n\r"'])/) { JS_ESCAPE_MAP[$1] }}');\n"
105
+ else
106
+ name = self.class.path_translator(path)
107
+ "#{self.class.template_destination}[\"#{name}\"] = #{self.class.template_compiler}(\"#{template.strip.gsub(/(\r\n|[\n\r"'])/) { JS_ESCAPE_MAP[$1] }}\");\n"
108
+ end
109
+ end
110
+
111
+ # Used to change the asset path into a string which is
112
+ # safe to use as a JavaScript object property. When
113
+ # the template is a partial (ie starts with a '_')
114
+ def partial_path_translator(path)
115
+ path = remove_underscore_from_partial_path(path)
116
+ self.class.path_translator(path).gsub(%r{/}, '.')
117
+ end
118
+
119
+ private
120
+
121
+ def remove_underscore_from_partial_path(path)
122
+ path.sub(/(.*)(\/|^)_(.+?)$/, '\1\2\3')
123
+ end
124
+
125
+ # Precompiled Haml source. Taken from the precompiled_with_ambles
126
+ # method in Haml::Precompiler:
127
+ # http://github.com/nex3/haml/blob/master/lib/haml/precompiler.rb#L111-126
128
+ def precompiled_template(locals)
129
+ @engine.precompiled
130
+ end
131
+
132
+ def precompiled_preamble(locals)
133
+ local_assigns = super
134
+ @engine.instance_eval do
135
+ <<-RUBY
136
+ begin
137
+ extend Haml::Helpers
138
+ _hamlout = @haml_buffer = Haml::Buffer.new(@haml_buffer, #{options_for_buffer.inspect})
139
+ _erbout = _hamlout.buffer
140
+ __in_erb_template = true
141
+ _haml_locals = locals
142
+ #{local_assigns}
143
+ RUBY
144
+ end
145
+ end
146
+
147
+ def precompiled_postamble(locals)
148
+ @engine.instance_eval do
149
+ <<-RUBY
150
+ #{precompiled_method_return_value}
151
+ ensure
152
+ @haml_buffer = @haml_buffer.upper
153
+ end
154
+ RUBY
155
+ end
156
+ end
157
+ end
158
+ end
159
+
160
+ module Haml
161
+ module Helpers
162
+
163
+ module HamlbarsExtensions
164
+ # Used to create handlebars expressions within HAML,
165
+ # if you pass a block then it will create a Handlebars
166
+ # block helper (ie "{{#expression}}..{{/expression}}"
167
+ # otherwise it will create an expression
168
+ # (ie "{{expression}}").
169
+ def handlebars(expression, options={}, &block)
170
+ express(['{{','}}'],expression,options,&block)
171
+ end
172
+ alias hb handlebars
173
+
174
+ # The same as #handlebars except that it outputs "triple-stash"
175
+ # expressions, which means that Handlebars won't escape the output.
176
+ def handlebars!(expression, options={}, &block)
177
+ express(['{{{','}}}'],expression,options,&block)
178
+ end
179
+ alias hb! handlebars!
180
+
181
+ private
182
+
183
+ def make(expression, options)
184
+ if options.any?
185
+ expression << " " << options.map {|key, value| "#{key}=\"#{value}\"" }.join(' ')
186
+ else
187
+ expression
188
+ end
189
+ end
190
+
191
+ def express(demarcation,expression,options={},&block)
192
+ if block.respond_to? :call
193
+ content = capture_haml(&block)
194
+ output = "#{demarcation.first}##{make(expression, options)}#{demarcation.last}#{content.strip}#{demarcation.first}/#{expression.split(' ').first}#{demarcation.last}"
195
+ else
196
+ output = "#{demarcation.first}#{make(expression, options)}#{demarcation.last}"
197
+ end
198
+
199
+ output = Haml::Util.html_safe(output) if Haml::Util.rails_xss_safe?
200
+ output
201
+ end
202
+ end
203
+
204
+ include HamlbarsExtensions
205
+ end
206
+ end
207
+
@@ -0,0 +1,3 @@
1
+ module Hamlbars
2
+ VERSION = '1.0.0'
3
+ end
data/lib/hamlbars.rb ADDED
@@ -0,0 +1,22 @@
1
+ require 'haml'
2
+ require 'sprockets'
3
+
4
+ module Hamlbars
5
+
6
+ if defined? Rails
7
+ class Engine < Rails::Engine
8
+ end
9
+ end
10
+
11
+ ROOT = File.expand_path(File.dirname(__FILE__))
12
+
13
+ autoload :Ext, File.join(ROOT, 'hamlbars', 'ext')
14
+ autoload :Template, File.join(ROOT, 'hamlbars', 'template')
15
+
16
+ Haml::Compiler.send(:include, Ext::Compiler)
17
+
18
+ if defined? Sprockets
19
+ Sprockets.register_engine '.hamlbars', Template
20
+ end
21
+
22
+ end