hamlbars 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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