handlebars-rails 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ .bundle
2
+ Gemfile.lock
@@ -0,0 +1,7 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task :default => :spec
@@ -10,16 +10,17 @@ Gem::Specification.new do |gem|
10
10
  gem.summary = "Rails Template Handler for Handlebars"
11
11
  gem.description = "Use Handlebars.js client- and server-side"
12
12
  gem.email = "james.a.rosen@gmail.com"
13
- gem.homepage = "http://github.com/jamesarosen/arturo"
14
- gem.authors = ["James A. Rosen", "Yehuda Katz"]
13
+ gem.homepage = "http://github.com/jamesarosen/handlebars-rails"
14
+ gem.authors = ["James A. Rosen", "Yehuda Katz", "Charles Lowell"]
15
15
  gem.test_files = []
16
16
  gem.require_paths = [".", "lib"]
17
17
  gem.has_rdoc = 'false'
18
18
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
19
19
  gem.specification_version = 2
20
20
  gem.add_runtime_dependency 'rails', '~> 3.0'
21
- gem.add_runtime_dependency 'therubyracer', '~> 0.8.0'
22
- gem.add_development_dependency 'mocha'
21
+ gem.add_runtime_dependency 'handlebars', '~> 0.3.2'
22
+
23
23
  gem.add_development_dependency 'rake'
24
24
  gem.add_development_dependency 'redgreen', '~> 1.2'
25
+ gem.add_development_dependency 'rspec', '~> 2.7'
25
26
  end
@@ -1,49 +1,28 @@
1
1
  require 'handlebars-rails/version'
2
- require 'handlebars-rails/v8'
3
- require "active_support"
2
+ require 'handlebars-rails/tilt'
3
+ require 'handlebars-rails/template_handler'
4
+ require 'handlebars'
4
5
 
5
6
  module Handlebars
6
- class TemplateHandler
7
+ class Rails < ::Rails::Engine
7
8
 
8
- def self.js
9
- handlebars = File.join(Rails.root, "vendor", "javascripts", "handlebars.js")
10
-
11
- unless File.exists?(handlebars)
12
- raise "Could not find handlebars.js. Please copy it to #{handlebars}"
13
- end
14
-
15
- Thread.current[:v8_context] ||= begin
16
- V8::Context.new do |js|
17
- js.load(handlebars)
18
- js.eval("Templates = {}")
19
-
20
- js["puts"] = method(:puts)
9
+ unless config.respond_to?(:handlebars)
10
+ config.handlebars = ActiveSupport::OrderedOptions.new
11
+ end
12
+ config.handlebars.override_ember_precompiler = false
21
13
 
22
- js.eval(%{
23
- Handlebars.registerHelper('helperMissing', function(helper) {
24
- var params = Array.prototype.slice.call(arguments, 1);
25
- return actionview[helper].apply(actionview, params);
26
- })
27
- })
28
- end
14
+ initializer 'handlebars.handler.setup', :before => :add_view_paths do |app|
15
+ app.paths['app/views'] << 'app/templates'
16
+ ActiveSupport.on_load(:action_view) do
17
+ ActionView::Template.register_template_handler(:hbs, ::Handlebars::TemplateHandler)
29
18
  end
30
19
  end
31
20
 
32
- def self.call(template)
33
- # Here, we're sticking the compiled template somewhere in V8 where
34
- # we can get back to it
35
- js.eval(%{Templates["#{template.identifier}"] = Handlebars.compile(#{template.source.inspect}) })
36
-
37
- %{
38
- js = ::Handlebars::TemplateHandler.js
39
- js['actionview'] = self
40
- js.eval("Templates['#{template.identifier}']").call(assigns).force_encoding(Encoding.default_external)
41
- }
21
+ initializer 'handlebars.precompiler.setup', :group => :all, :after => 'ember_rails.setup' do |app|
22
+ if !defined?(Ember) || config.handlebars.override_ember_precompiler
23
+ app.assets.append_path 'app/templates'
24
+ app.assets.register_engine '.hbs', Handlebars::Tilt
25
+ end
42
26
  end
43
-
44
27
  end
45
- end
46
-
47
- ActiveSupport.on_load(:action_view) do
48
- ActionView::Template.register_template_handler(:hbs, ::Handlebars::TemplateHandler)
49
- end
28
+ end
@@ -0,0 +1,50 @@
1
+ module Handlebars
2
+ class TemplateHandler
3
+
4
+ def self.handlebars
5
+ @context ||= new_context
6
+ end
7
+
8
+ def self.call(template)
9
+ %{
10
+ handler = ::Handlebars::TemplateHandler
11
+ handlebars = handler.handlebars
12
+ handler.with_view(self) do
13
+ handlebars.compile(#{template.source.inspect}).call(assigns).force_encoding(Encoding.default_external).html_safe
14
+ end
15
+ }
16
+ end
17
+
18
+ def self.with_view(view)
19
+ original_view = data['view']
20
+ data['view'] = view
21
+ yield
22
+ ensure
23
+ data['view'] = original_view
24
+ end
25
+
26
+ def self.new_context
27
+ Handlebars::Context.new.tap do |context|
28
+ context['rails'] = {}
29
+ context.partial_missing do |name|
30
+ name = name.gsub('.', '/')
31
+ lookup_context = data['view'].lookup_context
32
+ prefixes = lookup_context.prefixes.dup
33
+ prefixes.push ''
34
+ partial = lookup_context.find(name, prefixes, true)
35
+ lambda do |this, context|
36
+ if partial.handler == self
37
+ handlebars.compile(partial.source).call(context)
38
+ else
39
+ data['view'].render :partial => name, :locals => context
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ def self.data
47
+ @context['rails']
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,60 @@
1
+ require 'tilt'
2
+
3
+ module Handlebars
4
+ class Tilt < ::Tilt::Template
5
+ def self.default_mime_type
6
+ 'application/javascript'
7
+ end
8
+
9
+ def prepare
10
+
11
+ end
12
+
13
+ def evaluate(scope, locals, &block)
14
+ scope.extend Scope
15
+ hbsc = Handlebars::TemplateHandler.handlebars.precompile data
16
+ if scope.partial?
17
+ <<-JS
18
+ ;(function() {
19
+ var template = Handlebars.template
20
+ Handlebars.registerPartial('#{scope.partial_name}', template(#{hbsc}));
21
+ })()
22
+ JS
23
+ else
24
+ <<-JS
25
+ ;(function() {
26
+ var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
27
+ #{scope.template_path}
28
+ namespace['#{scope.template_name}'] = template(#{hbsc});
29
+ })()
30
+ JS
31
+ end
32
+ end
33
+
34
+ module Scope
35
+ def partial?
36
+ File.basename(logical_path).start_with?('_')
37
+ end
38
+
39
+ def partial_name
40
+ "#{File.dirname(logical_path).gsub('/','.')}.#{File.basename(logical_path, '.hbs').gsub(/^_/,'')}".gsub(/^\.+/,'')
41
+ end
42
+
43
+ def template_path
44
+ branches = File.dirname(logical_path).split('/').reject{|p| p == '.'}
45
+ <<-ASSIGN
46
+ var branches = #{branches.inspect}
47
+ var namespace = templates
48
+ for (var path = branches.shift(); path; path = branches.shift()) {
49
+ namespace[path] = namespace[path] || {}
50
+ namespace = namespace[path]
51
+ }
52
+ ASSIGN
53
+ end
54
+
55
+ def template_name
56
+ File.basename(logical_path, '.hbs')
57
+ end
58
+ end
59
+ end
60
+ end
@@ -1,3 +1,3 @@
1
1
  module Handlebars
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -0,0 +1,2 @@
1
+ //= require foobars/whole
2
+ //= require foobars/_partial
@@ -0,0 +1 @@
1
+ partial {{thing}}
@@ -0,0 +1 @@
1
+ whole {{thing}}
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+ require 'sprockets'
3
+
4
+ describe "sprockets integration" do
5
+ before do
6
+ @sprockets = Sprockets::Environment.new
7
+ app_path = Pathname(__FILE__).join('../app')
8
+ @sprockets.append_path app_path.join('assets/javascripts')
9
+ @sprockets.append_path app_path.join('views')
10
+ @sprockets.register_engine '.hbs', Handlebars::Tilt
11
+ @context = Handlebars::TemplateHandler.handlebars
12
+ source = <<-JS
13
+ (function(Handlebars) {
14
+ //BEGIN SPROCKETS OUTPUT
15
+ #{@sprockets['templates.js'].source}
16
+ //END SPROCKETS OUTPUT
17
+ })
18
+ JS
19
+ @context.runtime.eval(source).call(@context.handlebars)
20
+ end
21
+ it 'precompiles templates' do
22
+ @context.handlebars.templates['foobars']['whole'].call('thing' => 'enchilada').should match 'whole enchilada'
23
+ end
24
+ it 'precompiles partials' do
25
+ @context.compile('{{>foobars/partial}}').call(:thing => 'enchilada').should match 'partial enchilada'
26
+ @context.handlebars.partials['foobars.partial'].call(:thing => 'enchilada').should match 'partial enchilada'
27
+ end
28
+ end
29
+
30
+ def h(*args)
31
+ ERB::Util.h args.join(', ')
32
+ end
@@ -0,0 +1,45 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Handlebars::TemplateHandler do
5
+
6
+ before do
7
+ @lookup_context = mock('ActionView::LookupContext', :prefixes => [:one])
8
+ @assigns = {:name => 'World'}
9
+ @view = mock('ActionView::Base', :lookup_context => @lookup_context, :assigns => @assigns)
10
+ end
11
+
12
+ it 'should be able to render a basic HTML template' do
13
+ render_template('basic_html', '<h1>Hello</h1>').should eql '<h1>Hello</h1>'
14
+ end
15
+
16
+ it 'renders handlebars templates' do
17
+ render_template('hbs_html', '<h1>Hello {{name}}</h1>').should eql '<h1>Hello World</h1>'
18
+ end
19
+
20
+ describe 'an embedded handlebars partial' do
21
+ before do
22
+ @lookup_context.stub(:find).with("to/hbs", [:one, ''], true) {mock(:source => "{{name}}", :handler => Handlebars::TemplateHandler)}
23
+ end
24
+
25
+ it 'renders' do
26
+ render_template('hbs_partial', '<h1>Hello {{>to/hbs}}</h1>').should eql '<h1>Hello World</h1>'
27
+ end
28
+ end
29
+
30
+ describe 'an embedded erb partial' do
31
+ before do
32
+ @lookup_context.stub(:find).with("to/erb", [:one, ''], true) {mock(:source => "<%= @name %>", :handler => mock(:ERB))}
33
+ @view.stub(:render).with(hash_including(:partial => 'to/erb')) {|options| options[:locals][:name]}
34
+ end
35
+ it 'renders' do
36
+ render_template('erb_partial', '<h1>Hello {{>to/erb}}</h1>').should eql '<h1>Hello World</h1>'
37
+ end
38
+ end
39
+
40
+ def render_template(name, source)
41
+ template = Template.new(name, source)
42
+ compiled_template_source = Handlebars::TemplateHandler.call(template)
43
+ @view.instance_eval(compiled_template_source)
44
+ end
45
+ end
@@ -0,0 +1,5 @@
1
+ require 'rspec'
2
+ require 'rails'
3
+ require 'handlebars-rails'
4
+ require 'template'
5
+ require 'action_view/buffers'
@@ -0,0 +1,7 @@
1
+ # A stub template class for testing.
2
+ class Template
3
+ attr_accessor :identifier, :source
4
+ def initialize(identifier, source)
5
+ @identifier, @source = identifier, source
6
+ end
7
+ end
@@ -0,0 +1,1493 @@
1
+ // lib/handlebars/parser.js
2
+ /* Jison generated parser */
3
+ var handlebars = (function(){
4
+ var parser = {trace: function trace() { },
5
+ yy: {},
6
+ symbols_: {"error":2,"root":3,"program":4,"EOF":5,"statements":6,"simpleInverse":7,"statement":8,"openInverse":9,"closeBlock":10,"openBlock":11,"mustache":12,"partial":13,"CONTENT":14,"COMMENT":15,"OPEN_BLOCK":16,"inMustache":17,"CLOSE":18,"OPEN_INVERSE":19,"OPEN_ENDBLOCK":20,"path":21,"OPEN":22,"OPEN_UNESCAPED":23,"OPEN_PARTIAL":24,"params":25,"hash":26,"param":27,"STRING":28,"INTEGER":29,"BOOLEAN":30,"hashSegments":31,"hashSegment":32,"ID":33,"EQUALS":34,"pathSegments":35,"SEP":36,"$accept":0,"$end":1},
7
+ terminals_: {2:"error",5:"EOF",14:"CONTENT",15:"COMMENT",16:"OPEN_BLOCK",18:"CLOSE",19:"OPEN_INVERSE",20:"OPEN_ENDBLOCK",22:"OPEN",23:"OPEN_UNESCAPED",24:"OPEN_PARTIAL",28:"STRING",29:"INTEGER",30:"BOOLEAN",33:"ID",34:"EQUALS",36:"SEP"},
8
+ productions_: [0,[3,2],[4,3],[4,1],[4,0],[6,1],[6,2],[8,3],[8,3],[8,1],[8,1],[8,1],[8,1],[11,3],[9,3],[10,3],[12,3],[12,3],[13,3],[13,4],[7,2],[17,3],[17,2],[17,2],[17,1],[25,2],[25,1],[27,1],[27,1],[27,1],[27,1],[26,1],[31,2],[31,1],[32,3],[32,3],[32,3],[32,3],[21,1],[35,3],[35,1]],
9
+ performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) {
10
+
11
+ var $0 = $$.length - 1;
12
+ switch (yystate) {
13
+ case 1: return $$[$0-1]
14
+ break;
15
+ case 2: this.$ = new yy.ProgramNode($$[$0-2], $$[$0])
16
+ break;
17
+ case 3: this.$ = new yy.ProgramNode($$[$0])
18
+ break;
19
+ case 4: this.$ = new yy.ProgramNode([])
20
+ break;
21
+ case 5: this.$ = [$$[$0]]
22
+ break;
23
+ case 6: $$[$0-1].push($$[$0]); this.$ = $$[$0-1]
24
+ break;
25
+ case 7: this.$ = new yy.InverseNode($$[$0-2], $$[$0-1], $$[$0])
26
+ break;
27
+ case 8: this.$ = new yy.BlockNode($$[$0-2], $$[$0-1], $$[$0])
28
+ break;
29
+ case 9: this.$ = $$[$0]
30
+ break;
31
+ case 10: this.$ = $$[$0]
32
+ break;
33
+ case 11: this.$ = new yy.ContentNode($$[$0])
34
+ break;
35
+ case 12: this.$ = new yy.CommentNode($$[$0])
36
+ break;
37
+ case 13: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1])
38
+ break;
39
+ case 14: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1])
40
+ break;
41
+ case 15: this.$ = $$[$0-1]
42
+ break;
43
+ case 16: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1])
44
+ break;
45
+ case 17: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1], true)
46
+ break;
47
+ case 18: this.$ = new yy.PartialNode($$[$0-1])
48
+ break;
49
+ case 19: this.$ = new yy.PartialNode($$[$0-2], $$[$0-1])
50
+ break;
51
+ case 20:
52
+ break;
53
+ case 21: this.$ = [[$$[$0-2]].concat($$[$0-1]), $$[$0]]
54
+ break;
55
+ case 22: this.$ = [[$$[$0-1]].concat($$[$0]), null]
56
+ break;
57
+ case 23: this.$ = [[$$[$0-1]], $$[$0]]
58
+ break;
59
+ case 24: this.$ = [[$$[$0]], null]
60
+ break;
61
+ case 25: $$[$0-1].push($$[$0]); this.$ = $$[$0-1];
62
+ break;
63
+ case 26: this.$ = [$$[$0]]
64
+ break;
65
+ case 27: this.$ = $$[$0]
66
+ break;
67
+ case 28: this.$ = new yy.StringNode($$[$0])
68
+ break;
69
+ case 29: this.$ = new yy.IntegerNode($$[$0])
70
+ break;
71
+ case 30: this.$ = new yy.BooleanNode($$[$0])
72
+ break;
73
+ case 31: this.$ = new yy.HashNode($$[$0])
74
+ break;
75
+ case 32: $$[$0-1].push($$[$0]); this.$ = $$[$0-1]
76
+ break;
77
+ case 33: this.$ = [$$[$0]]
78
+ break;
79
+ case 34: this.$ = [$$[$0-2], $$[$0]]
80
+ break;
81
+ case 35: this.$ = [$$[$0-2], new yy.StringNode($$[$0])]
82
+ break;
83
+ case 36: this.$ = [$$[$0-2], new yy.IntegerNode($$[$0])]
84
+ break;
85
+ case 37: this.$ = [$$[$0-2], new yy.BooleanNode($$[$0])]
86
+ break;
87
+ case 38: this.$ = new yy.IdNode($$[$0])
88
+ break;
89
+ case 39: $$[$0-2].push($$[$0]); this.$ = $$[$0-2];
90
+ break;
91
+ case 40: this.$ = [$$[$0]]
92
+ break;
93
+ }
94
+ },
95
+ table: [{3:1,4:2,5:[2,4],6:3,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],22:[1,13],23:[1,14],24:[1,15]},{1:[3]},{5:[1,16]},{5:[2,3],7:17,8:18,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,19],20:[2,3],22:[1,13],23:[1,14],24:[1,15]},{5:[2,5],14:[2,5],15:[2,5],16:[2,5],19:[2,5],20:[2,5],22:[2,5],23:[2,5],24:[2,5]},{4:20,6:3,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,4],22:[1,13],23:[1,14],24:[1,15]},{4:21,6:3,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,4],22:[1,13],23:[1,14],24:[1,15]},{5:[2,9],14:[2,9],15:[2,9],16:[2,9],19:[2,9],20:[2,9],22:[2,9],23:[2,9],24:[2,9]},{5:[2,10],14:[2,10],15:[2,10],16:[2,10],19:[2,10],20:[2,10],22:[2,10],23:[2,10],24:[2,10]},{5:[2,11],14:[2,11],15:[2,11],16:[2,11],19:[2,11],20:[2,11],22:[2,11],23:[2,11],24:[2,11]},{5:[2,12],14:[2,12],15:[2,12],16:[2,12],19:[2,12],20:[2,12],22:[2,12],23:[2,12],24:[2,12]},{17:22,21:23,33:[1,25],35:24},{17:26,21:23,33:[1,25],35:24},{17:27,21:23,33:[1,25],35:24},{17:28,21:23,33:[1,25],35:24},{21:29,33:[1,25],35:24},{1:[2,1]},{6:30,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],22:[1,13],23:[1,14],24:[1,15]},{5:[2,6],14:[2,6],15:[2,6],16:[2,6],19:[2,6],20:[2,6],22:[2,6],23:[2,6],24:[2,6]},{17:22,18:[1,31],21:23,33:[1,25],35:24},{10:32,20:[1,33]},{10:34,20:[1,33]},{18:[1,35]},{18:[2,24],21:40,25:36,26:37,27:38,28:[1,41],29:[1,42],30:[1,43],31:39,32:44,33:[1,45],35:24},{18:[2,38],28:[2,38],29:[2,38],30:[2,38],33:[2,38],36:[1,46]},{18:[2,40],28:[2,40],29:[2,40],30:[2,40],33:[2,40],36:[2,40]},{18:[1,47]},{18:[1,48]},{18:[1,49]},{18:[1,50],21:51,33:[1,25],35:24},{5:[2,2],8:18,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,2],22:[1,13],23:[1,14],24:[1,15]},{14:[2,20],15:[2,20],16:[2,20],19:[2,20],22:[2,20],23:[2,20],24:[2,20]},{5:[2,7],14:[2,7],15:[2,7],16:[2,7],19:[2,7],20:[2,7],22:[2,7],23:[2,7],24:[2,7]},{21:52,33:[1,25],35:24},{5:[2,8],14:[2,8],15:[2,8],16:[2,8],19:[2,8],20:[2,8],22:[2,8],23:[2,8],24:[2,8]},{14:[2,14],15:[2,14],16:[2,14],19:[2,14],20:[2,14],22:[2,14],23:[2,14],24:[2,14]},{18:[2,22],21:40,26:53,27:54,28:[1,41],29:[1,42],30:[1,43],31:39,32:44,33:[1,45],35:24},{18:[2,23]},{18:[2,26],28:[2,26],29:[2,26],30:[2,26],33:[2,26]},{18:[2,31],32:55,33:[1,56]},{18:[2,27],28:[2,27],29:[2,27],30:[2,27],33:[2,27]},{18:[2,28],28:[2,28],29:[2,28],30:[2,28],33:[2,28]},{18:[2,29],28:[2,29],29:[2,29],30:[2,29],33:[2,29]},{18:[2,30],28:[2,30],29:[2,30],30:[2,30],33:[2,30]},{18:[2,33],33:[2,33]},{18:[2,40],28:[2,40],29:[2,40],30:[2,40],33:[2,40],34:[1,57],36:[2,40]},{33:[1,58]},{14:[2,13],15:[2,13],16:[2,13],19:[2,13],20:[2,13],22:[2,13],23:[2,13],24:[2,13]},{5:[2,16],14:[2,16],15:[2,16],16:[2,16],19:[2,16],20:[2,16],22:[2,16],23:[2,16],24:[2,16]},{5:[2,17],14:[2,17],15:[2,17],16:[2,17],19:[2,17],20:[2,17],22:[2,17],23:[2,17],24:[2,17]},{5:[2,18],14:[2,18],15:[2,18],16:[2,18],19:[2,18],20:[2,18],22:[2,18],23:[2,18],24:[2,18]},{18:[1,59]},{18:[1,60]},{18:[2,21]},{18:[2,25],28:[2,25],29:[2,25],30:[2,25],33:[2,25]},{18:[2,32],33:[2,32]},{34:[1,57]},{21:61,28:[1,62],29:[1,63],30:[1,64],33:[1,25],35:24},{18:[2,39],28:[2,39],29:[2,39],30:[2,39],33:[2,39],36:[2,39]},{5:[2,19],14:[2,19],15:[2,19],16:[2,19],19:[2,19],20:[2,19],22:[2,19],23:[2,19],24:[2,19]},{5:[2,15],14:[2,15],15:[2,15],16:[2,15],19:[2,15],20:[2,15],22:[2,15],23:[2,15],24:[2,15]},{18:[2,34],33:[2,34]},{18:[2,35],33:[2,35]},{18:[2,36],33:[2,36]},{18:[2,37],33:[2,37]}],
96
+ defaultActions: {16:[2,1],37:[2,23],53:[2,21]},
97
+ parseError: function parseError(str, hash) {
98
+ throw new Error(str);
99
+ },
100
+ parse: function parse(input) {
101
+ var self = this,
102
+ stack = [0],
103
+ vstack = [null], // semantic value stack
104
+ lstack = [], // location stack
105
+ table = this.table,
106
+ yytext = '',
107
+ yylineno = 0,
108
+ yyleng = 0,
109
+ recovering = 0,
110
+ TERROR = 2,
111
+ EOF = 1;
112
+
113
+ //this.reductionCount = this.shiftCount = 0;
114
+
115
+ this.lexer.setInput(input);
116
+ this.lexer.yy = this.yy;
117
+ this.yy.lexer = this.lexer;
118
+ if (typeof this.lexer.yylloc == 'undefined')
119
+ this.lexer.yylloc = {};
120
+ var yyloc = this.lexer.yylloc;
121
+ lstack.push(yyloc);
122
+
123
+ if (typeof this.yy.parseError === 'function')
124
+ this.parseError = this.yy.parseError;
125
+
126
+ function popStack (n) {
127
+ stack.length = stack.length - 2*n;
128
+ vstack.length = vstack.length - n;
129
+ lstack.length = lstack.length - n;
130
+ }
131
+
132
+ function lex() {
133
+ var token;
134
+ token = self.lexer.lex() || 1; // $end = 1
135
+ // if token isn't its numeric value, convert
136
+ if (typeof token !== 'number') {
137
+ token = self.symbols_[token] || token;
138
+ }
139
+ return token;
140
+ };
141
+
142
+ var symbol, preErrorSymbol, state, action, a, r, yyval={},p,len,newState, expected;
143
+ while (true) {
144
+ // retreive state number from top of stack
145
+ state = stack[stack.length-1];
146
+
147
+ // use default actions if available
148
+ if (this.defaultActions[state]) {
149
+ action = this.defaultActions[state];
150
+ } else {
151
+ if (symbol == null)
152
+ symbol = lex();
153
+ // read action for current state and first input
154
+ action = table[state] && table[state][symbol];
155
+ }
156
+
157
+ // handle parse error
158
+ if (typeof action === 'undefined' || !action.length || !action[0]) {
159
+
160
+ if (!recovering) {
161
+ // Report error
162
+ expected = [];
163
+ for (p in table[state]) if (this.terminals_[p] && p > 2) {
164
+ expected.push("'"+this.terminals_[p]+"'");
165
+ }
166
+ var errStr = '';
167
+ if (this.lexer.showPosition) {
168
+ errStr = 'Parse error on line '+(yylineno+1)+":\n"+this.lexer.showPosition()+'\nExpecting '+expected.join(', ');
169
+ } else {
170
+ errStr = 'Parse error on line '+(yylineno+1)+": Unexpected " +
171
+ (symbol == 1 /*EOF*/ ? "end of input" :
172
+ ("'"+(this.terminals_[symbol] || symbol)+"'"));
173
+ }
174
+ this.parseError(errStr,
175
+ {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected});
176
+ }
177
+
178
+ // just recovered from another error
179
+ if (recovering == 3) {
180
+ if (symbol == EOF) {
181
+ throw new Error(errStr || 'Parsing halted.');
182
+ }
183
+
184
+ // discard current lookahead and grab another
185
+ yyleng = this.lexer.yyleng;
186
+ yytext = this.lexer.yytext;
187
+ yylineno = this.lexer.yylineno;
188
+ yyloc = this.lexer.yylloc;
189
+ symbol = lex();
190
+ }
191
+
192
+ // try to recover from error
193
+ while (1) {
194
+ // check for error recovery rule in this state
195
+ if ((TERROR.toString()) in table[state]) {
196
+ break;
197
+ }
198
+ if (state == 0) {
199
+ throw new Error(errStr || 'Parsing halted.');
200
+ }
201
+ popStack(1);
202
+ state = stack[stack.length-1];
203
+ }
204
+
205
+ preErrorSymbol = symbol; // save the lookahead token
206
+ symbol = TERROR; // insert generic error symbol as new lookahead
207
+ state = stack[stack.length-1];
208
+ action = table[state] && table[state][TERROR];
209
+ recovering = 3; // allow 3 real symbols to be shifted before reporting a new error
210
+ }
211
+
212
+ // this shouldn't happen, unless resolve defaults are off
213
+ if (action[0] instanceof Array && action.length > 1) {
214
+ throw new Error('Parse Error: multiple actions possible at state: '+state+', token: '+symbol);
215
+ }
216
+
217
+ switch (action[0]) {
218
+
219
+ case 1: // shift
220
+ //this.shiftCount++;
221
+
222
+ stack.push(symbol);
223
+ vstack.push(this.lexer.yytext);
224
+ lstack.push(this.lexer.yylloc);
225
+ stack.push(action[1]); // push state
226
+ symbol = null;
227
+ if (!preErrorSymbol) { // normal execution/no error
228
+ yyleng = this.lexer.yyleng;
229
+ yytext = this.lexer.yytext;
230
+ yylineno = this.lexer.yylineno;
231
+ yyloc = this.lexer.yylloc;
232
+ if (recovering > 0)
233
+ recovering--;
234
+ } else { // error just occurred, resume old lookahead f/ before error
235
+ symbol = preErrorSymbol;
236
+ preErrorSymbol = null;
237
+ }
238
+ break;
239
+
240
+ case 2: // reduce
241
+ //this.reductionCount++;
242
+
243
+ len = this.productions_[action[1]][1];
244
+
245
+ // perform semantic action
246
+ yyval.$ = vstack[vstack.length-len]; // default to $$ = $1
247
+ // default location, uses first token for firsts, last for lasts
248
+ yyval._$ = {
249
+ first_line: lstack[lstack.length-(len||1)].first_line,
250
+ last_line: lstack[lstack.length-1].last_line,
251
+ first_column: lstack[lstack.length-(len||1)].first_column,
252
+ last_column: lstack[lstack.length-1].last_column
253
+ };
254
+ r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);
255
+
256
+ if (typeof r !== 'undefined') {
257
+ return r;
258
+ }
259
+
260
+ // pop off stack
261
+ if (len) {
262
+ stack = stack.slice(0,-1*len*2);
263
+ vstack = vstack.slice(0, -1*len);
264
+ lstack = lstack.slice(0, -1*len);
265
+ }
266
+
267
+ stack.push(this.productions_[action[1]][0]); // push nonterminal (reduce)
268
+ vstack.push(yyval.$);
269
+ lstack.push(yyval._$);
270
+ // goto new state = table[STATE][NONTERMINAL]
271
+ newState = table[stack[stack.length-2]][stack[stack.length-1]];
272
+ stack.push(newState);
273
+ break;
274
+
275
+ case 3: // accept
276
+ return true;
277
+ }
278
+
279
+ }
280
+
281
+ return true;
282
+ }};/* Jison generated lexer */
283
+ var lexer = (function(){var lexer = ({EOF:1,
284
+ parseError:function parseError(str, hash) {
285
+ if (this.yy.parseError) {
286
+ this.yy.parseError(str, hash);
287
+ } else {
288
+ throw new Error(str);
289
+ }
290
+ },
291
+ setInput:function (input) {
292
+ this._input = input;
293
+ this._more = this._less = this.done = false;
294
+ this.yylineno = this.yyleng = 0;
295
+ this.yytext = this.matched = this.match = '';
296
+ this.conditionStack = ['INITIAL'];
297
+ this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0};
298
+ return this;
299
+ },
300
+ input:function () {
301
+ var ch = this._input[0];
302
+ this.yytext+=ch;
303
+ this.yyleng++;
304
+ this.match+=ch;
305
+ this.matched+=ch;
306
+ var lines = ch.match(/\n/);
307
+ if (lines) this.yylineno++;
308
+ this._input = this._input.slice(1);
309
+ return ch;
310
+ },
311
+ unput:function (ch) {
312
+ this._input = ch + this._input;
313
+ return this;
314
+ },
315
+ more:function () {
316
+ this._more = true;
317
+ return this;
318
+ },
319
+ pastInput:function () {
320
+ var past = this.matched.substr(0, this.matched.length - this.match.length);
321
+ return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
322
+ },
323
+ upcomingInput:function () {
324
+ var next = this.match;
325
+ if (next.length < 20) {
326
+ next += this._input.substr(0, 20-next.length);
327
+ }
328
+ return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, "");
329
+ },
330
+ showPosition:function () {
331
+ var pre = this.pastInput();
332
+ var c = new Array(pre.length + 1).join("-");
333
+ return pre + this.upcomingInput() + "\n" + c+"^";
334
+ },
335
+ next:function () {
336
+ if (this.done) {
337
+ return this.EOF;
338
+ }
339
+ if (!this._input) this.done = true;
340
+
341
+ var token,
342
+ match,
343
+ col,
344
+ lines;
345
+ if (!this._more) {
346
+ this.yytext = '';
347
+ this.match = '';
348
+ }
349
+ var rules = this._currentRules();
350
+ for (var i=0;i < rules.length; i++) {
351
+ match = this._input.match(this.rules[rules[i]]);
352
+ if (match) {
353
+ lines = match[0].match(/\n.*/g);
354
+ if (lines) this.yylineno += lines.length;
355
+ this.yylloc = {first_line: this.yylloc.last_line,
356
+ last_line: this.yylineno+1,
357
+ first_column: this.yylloc.last_column,
358
+ last_column: lines ? lines[lines.length-1].length-1 : this.yylloc.last_column + match[0].length}
359
+ this.yytext += match[0];
360
+ this.match += match[0];
361
+ this.matches = match;
362
+ this.yyleng = this.yytext.length;
363
+ this._more = false;
364
+ this._input = this._input.slice(match[0].length);
365
+ this.matched += match[0];
366
+ token = this.performAction.call(this, this.yy, this, rules[i],this.conditionStack[this.conditionStack.length-1]);
367
+ if (token) return token;
368
+ else return;
369
+ }
370
+ }
371
+ if (this._input === "") {
372
+ return this.EOF;
373
+ } else {
374
+ this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(),
375
+ {text: "", token: null, line: this.yylineno});
376
+ }
377
+ },
378
+ lex:function lex() {
379
+ var r = this.next();
380
+ if (typeof r !== 'undefined') {
381
+ return r;
382
+ } else {
383
+ return this.lex();
384
+ }
385
+ },
386
+ begin:function begin(condition) {
387
+ this.conditionStack.push(condition);
388
+ },
389
+ popState:function popState() {
390
+ return this.conditionStack.pop();
391
+ },
392
+ _currentRules:function _currentRules() {
393
+ return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules;
394
+ }});
395
+ lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
396
+
397
+ var YYSTATE=YY_START
398
+ switch($avoiding_name_collisions) {
399
+ case 0: this.begin("mu"); if (yy_.yytext) return 14;
400
+ break;
401
+ case 1: return 14;
402
+ break;
403
+ case 2: return 24;
404
+ break;
405
+ case 3: return 16;
406
+ break;
407
+ case 4: return 20;
408
+ break;
409
+ case 5: return 19;
410
+ break;
411
+ case 6: return 19;
412
+ break;
413
+ case 7: return 23;
414
+ break;
415
+ case 8: return 23;
416
+ break;
417
+ case 9: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.begin("INITIAL"); return 15;
418
+ break;
419
+ case 10: return 22;
420
+ break;
421
+ case 11: return 34;
422
+ break;
423
+ case 12: return 33;
424
+ break;
425
+ case 13: return 33;
426
+ break;
427
+ case 14: return 36;
428
+ break;
429
+ case 15: /*ignore whitespace*/
430
+ break;
431
+ case 16: this.begin("INITIAL"); return 18;
432
+ break;
433
+ case 17: this.begin("INITIAL"); return 18;
434
+ break;
435
+ case 18: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 28;
436
+ break;
437
+ case 19: return 30;
438
+ break;
439
+ case 20: return 30;
440
+ break;
441
+ case 21: return 29;
442
+ break;
443
+ case 22: return 33;
444
+ break;
445
+ case 23: return 'INVALID';
446
+ break;
447
+ case 24: return 5;
448
+ break;
449
+ }
450
+ };
451
+ lexer.rules = [/^[^\x00]*?(?=(\{\{))/,/^[^\x00]+/,/^\{\{>/,/^\{\{#/,/^\{\{\//,/^\{\{\^/,/^\{\{\s*else\b/,/^\{\{\{/,/^\{\{&/,/^\{\{![\s\S]*?\}\}/,/^\{\{/,/^=/,/^\.(?=[} ])/,/^\.\./,/^[/.]/,/^\s+/,/^\}\}\}/,/^\}\}/,/^"(\\["]|[^"])*"/,/^true(?=[}\s])/,/^false(?=[}\s])/,/^[0-9]+(?=[}\s])/,/^[a-zA-Z0-9_$-]+(?=[=}\s/.])/,/^./,/^$/];
452
+ lexer.conditions = {"mu":{"rules":[2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24],"inclusive":false},"INITIAL":{"rules":[0,1,24],"inclusive":true}};return lexer;})()
453
+ parser.lexer = lexer;
454
+ return parser;
455
+ })();
456
+ if (typeof require !== 'undefined' && typeof exports !== 'undefined') {
457
+ exports.parser = handlebars;
458
+ exports.parse = function () { return handlebars.parse.apply(handlebars, arguments); }
459
+ exports.main = function commonjsMain(args) {
460
+ if (!args[1])
461
+ throw new Error('Usage: '+args[0]+' FILE');
462
+ if (typeof process !== 'undefined') {
463
+ var source = require('fs').readFileSync(require('path').join(process.cwd(), args[1]), "utf8");
464
+ } else {
465
+ var cwd = require("file").path(require("file").cwd());
466
+ var source = cwd.join(args[1]).read({charset: "utf-8"});
467
+ }
468
+ return exports.parser.parse(source);
469
+ }
470
+ if (typeof module !== 'undefined' && require.main === module) {
471
+ exports.main(typeof process !== 'undefined' ? process.argv.slice(1) : require("system").args);
472
+ }
473
+ };
474
+ ;
475
+ // lib/handlebars/base.js
476
+ var Handlebars = {};
477
+
478
+ Handlebars.VERSION = "1.0.beta.2";
479
+
480
+ Handlebars.Parser = handlebars;
481
+
482
+ Handlebars.parse = function(string) {
483
+ Handlebars.Parser.yy = Handlebars.AST;
484
+ return Handlebars.Parser.parse(string);
485
+ };
486
+
487
+ Handlebars.print = function(ast) {
488
+ return new Handlebars.PrintVisitor().accept(ast);
489
+ };
490
+
491
+ Handlebars.helpers = {};
492
+ Handlebars.partials = {};
493
+
494
+ Handlebars.registerHelper = function(name, fn, inverse) {
495
+ if(inverse) { fn.not = inverse; }
496
+ this.helpers[name] = fn;
497
+ };
498
+
499
+ Handlebars.registerPartial = function(name, str) {
500
+ this.partials[name] = str;
501
+ };
502
+
503
+ Handlebars.registerHelper('helperMissing', function(arg) {
504
+ if(arguments.length === 2) {
505
+ return undefined;
506
+ } else {
507
+ throw new Error("Could not find property '" + arg + "'");
508
+ }
509
+ });
510
+
511
+ Handlebars.registerHelper('blockHelperMissing', function(context, fn, inverse) {
512
+ inverse = inverse || function() {};
513
+
514
+ var ret = "";
515
+ var type = Object.prototype.toString.call(context);
516
+
517
+ if(type === "[object Function]") {
518
+ context = context();
519
+ }
520
+
521
+ if(context === true) {
522
+ return fn(this);
523
+ } else if(context === false || context == null) {
524
+ return inverse(this);
525
+ } else if(type === "[object Array]") {
526
+ if(context.length > 0) {
527
+ for(var i=0, j=context.length; i<j; i++) {
528
+ ret = ret + fn(context[i]);
529
+ }
530
+ } else {
531
+ ret = inverse(this);
532
+ }
533
+ return ret;
534
+ } else {
535
+ return fn(context);
536
+ }
537
+ }, function(context, fn) {
538
+ return fn(context);
539
+ });
540
+
541
+ Handlebars.registerHelper('each', function(context, fn, inverse) {
542
+ var ret = "";
543
+
544
+ if(context && context.length > 0) {
545
+ for(var i=0, j=context.length; i<j; i++) {
546
+ ret = ret + fn(context[i]);
547
+ }
548
+ } else {
549
+ ret = inverse(this);
550
+ }
551
+ return ret;
552
+ });
553
+
554
+ Handlebars.registerHelper('if', function(context, fn, inverse) {
555
+ if(!context || context == []) {
556
+ return inverse(this);
557
+ } else {
558
+ return fn(this);
559
+ }
560
+ });
561
+
562
+ Handlebars.registerHelper('unless', function(context, fn, inverse) {
563
+ return Handlebars.helpers['if'].call(this, context, inverse, fn);
564
+ });
565
+
566
+ Handlebars.registerHelper('with', function(context, fn) {
567
+ return fn(context);
568
+ });
569
+
570
+ Handlebars.logger = {
571
+ DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3,
572
+
573
+ // override in the host environment
574
+ log: function(level, str) {}
575
+ };
576
+
577
+ Handlebars.log = function(level, str) { Handlebars.logger.log(level, str); };
578
+ ;
579
+ // lib/handlebars/ast.js
580
+ (function() {
581
+
582
+ Handlebars.AST = {};
583
+
584
+ Handlebars.AST.ProgramNode = function(statements, inverse) {
585
+ this.type = "program";
586
+ this.statements = statements;
587
+ if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); }
588
+ };
589
+
590
+ Handlebars.AST.MustacheNode = function(params, hash, unescaped) {
591
+ this.type = "mustache";
592
+ this.id = params[0];
593
+ this.params = params.slice(1);
594
+ this.hash = hash;
595
+ this.escaped = !unescaped;
596
+ };
597
+
598
+ Handlebars.AST.PartialNode = function(id, context) {
599
+ this.type = "partial";
600
+
601
+ // TODO: disallow complex IDs
602
+
603
+ this.id = id;
604
+ this.context = context;
605
+ };
606
+
607
+ var verifyMatch = function(open, close) {
608
+ if(open.original !== close.original) {
609
+ throw new Handlebars.Exception(open.original + " doesn't match " + close.original);
610
+ }
611
+ };
612
+
613
+ Handlebars.AST.BlockNode = function(mustache, program, close) {
614
+ verifyMatch(mustache.id, close);
615
+ this.type = "block";
616
+ this.mustache = mustache;
617
+ this.program = program;
618
+ };
619
+
620
+ Handlebars.AST.InverseNode = function(mustache, program, close) {
621
+ verifyMatch(mustache.id, close);
622
+ this.type = "inverse";
623
+ this.mustache = mustache;
624
+ this.program = program;
625
+ };
626
+
627
+ Handlebars.AST.ContentNode = function(string) {
628
+ this.type = "content";
629
+ this.string = string;
630
+ };
631
+
632
+ Handlebars.AST.HashNode = function(pairs) {
633
+ this.type = "hash";
634
+ this.pairs = pairs;
635
+ };
636
+
637
+ Handlebars.AST.IdNode = function(parts) {
638
+ this.type = "ID";
639
+ this.original = parts.join(".");
640
+
641
+ var dig = [], depth = 0;
642
+
643
+ for(var i=0,l=parts.length; i<l; i++) {
644
+ var part = parts[i];
645
+
646
+ if(part === "..") { depth++; }
647
+ else if(part === "." || part === "this") { continue; }
648
+ else { dig.push(part); }
649
+ }
650
+
651
+ this.parts = dig;
652
+ this.string = dig.join('.');
653
+ this.depth = depth;
654
+ this.isSimple = (dig.length === 1) && (depth === 0);
655
+ };
656
+
657
+ Handlebars.AST.StringNode = function(string) {
658
+ this.type = "STRING";
659
+ this.string = string;
660
+ };
661
+
662
+ Handlebars.AST.IntegerNode = function(integer) {
663
+ this.type = "INTEGER";
664
+ this.integer = integer;
665
+ };
666
+
667
+ Handlebars.AST.BooleanNode = function(boolean) {
668
+ this.type = "BOOLEAN";
669
+ this.boolean = boolean;
670
+ };
671
+
672
+ Handlebars.AST.CommentNode = function(comment) {
673
+ this.type = "comment";
674
+ this.comment = comment;
675
+ };
676
+
677
+ })();;
678
+ // lib/handlebars/visitor.js
679
+
680
+ Handlebars.Visitor = function() {};
681
+
682
+ Handlebars.Visitor.prototype = {
683
+ accept: function(object) {
684
+ return this[object.type](object);
685
+ }
686
+ };;
687
+ // lib/handlebars/utils.js
688
+ Handlebars.Exception = function(message) {
689
+ this.message = message;
690
+ };
691
+
692
+ // Build out our basic SafeString type
693
+ Handlebars.SafeString = function(string) {
694
+ this.string = string;
695
+ };
696
+ Handlebars.SafeString.prototype.toString = function() {
697
+ return this.string.toString();
698
+ };
699
+
700
+ (function() {
701
+ var escape = {
702
+ "<": "&lt;",
703
+ ">": "&gt;",
704
+ '"': "&quot;",
705
+ "'": "&#x27;",
706
+ "`": "&#x60;"
707
+ };
708
+
709
+ var badChars = /&(?!\w+;)|[<>"'`]/g;
710
+ var possible = /[&<>"'`]/;
711
+
712
+ var escapeChar = function(chr) {
713
+ return escape[chr] || "&amp;"
714
+ };
715
+
716
+ Handlebars.Utils = {
717
+ escapeExpression: function(string) {
718
+ // don't escape SafeStrings, since they're already safe
719
+ if (string instanceof Handlebars.SafeString) {
720
+ return string.toString();
721
+ } else if (string == null || string === false) {
722
+ return "";
723
+ }
724
+
725
+ if(!possible.test(string)) { return string; }
726
+ return string.replace(badChars, escapeChar);
727
+ },
728
+
729
+ isEmpty: function(value) {
730
+ if (typeof value === "undefined") {
731
+ return true;
732
+ } else if (value === null) {
733
+ return true;
734
+ } else if (value === false) {
735
+ return true;
736
+ } else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) {
737
+ return true;
738
+ } else {
739
+ return false;
740
+ }
741
+ }
742
+ };
743
+ })();;
744
+ // lib/handlebars/compiler.js
745
+ Handlebars.Compiler = function() {};
746
+ Handlebars.JavaScriptCompiler = function() {};
747
+
748
+ (function(Compiler, JavaScriptCompiler) {
749
+ Compiler.OPCODE_MAP = {
750
+ appendContent: 1,
751
+ getContext: 2,
752
+ lookupWithHelpers: 3,
753
+ lookup: 4,
754
+ append: 5,
755
+ invokeMustache: 6,
756
+ appendEscaped: 7,
757
+ pushString: 8,
758
+ truthyOrFallback: 9,
759
+ functionOrFallback: 10,
760
+ invokeProgram: 11,
761
+ invokePartial: 12,
762
+ push: 13,
763
+ invokeInverse: 14,
764
+ assignToHash: 15,
765
+ pushStringParam: 16
766
+ };
767
+
768
+ Compiler.MULTI_PARAM_OPCODES = {
769
+ appendContent: 1,
770
+ getContext: 1,
771
+ lookupWithHelpers: 1,
772
+ lookup: 1,
773
+ invokeMustache: 2,
774
+ pushString: 1,
775
+ truthyOrFallback: 1,
776
+ functionOrFallback: 1,
777
+ invokeProgram: 2,
778
+ invokePartial: 1,
779
+ push: 1,
780
+ invokeInverse: 1,
781
+ assignToHash: 1,
782
+ pushStringParam: 1
783
+ };
784
+
785
+ Compiler.DISASSEMBLE_MAP = {};
786
+
787
+ for(var prop in Compiler.OPCODE_MAP) {
788
+ var value = Compiler.OPCODE_MAP[prop];
789
+ Compiler.DISASSEMBLE_MAP[value] = prop;
790
+ }
791
+
792
+ Compiler.multiParamSize = function(code) {
793
+ return Compiler.MULTI_PARAM_OPCODES[Compiler.DISASSEMBLE_MAP[code]];
794
+ };
795
+
796
+ Compiler.prototype = {
797
+ compiler: Compiler,
798
+
799
+ disassemble: function() {
800
+ var opcodes = this.opcodes, opcode, nextCode;
801
+ var out = [], str, name, value;
802
+
803
+ for(var i=0, l=opcodes.length; i<l; i++) {
804
+ opcode = opcodes[i];
805
+
806
+ if(opcode === 'DECLARE') {
807
+ name = opcodes[++i];
808
+ value = opcodes[++i];
809
+ out.push("DECLARE " + name + " = " + value);
810
+ } else {
811
+ str = Compiler.DISASSEMBLE_MAP[opcode];
812
+
813
+ var extraParams = Compiler.multiParamSize(opcode);
814
+ var codes = [];
815
+
816
+ for(var j=0; j<extraParams; j++) {
817
+ nextCode = opcodes[++i];
818
+
819
+ if(typeof nextCode === "string") {
820
+ nextCode = "\"" + nextCode.replace("\n", "\\n") + "\"";
821
+ }
822
+
823
+ codes.push(nextCode);
824
+ }
825
+
826
+ str = str + " " + codes.join(" ");
827
+
828
+ out.push(str);
829
+ }
830
+ }
831
+
832
+ return out.join("\n");
833
+ },
834
+
835
+ guid: 0,
836
+
837
+ compile: function(program, options) {
838
+ this.children = [];
839
+ this.depths = {list: []};
840
+ this.options = options || {};
841
+ return this.program(program);
842
+ },
843
+
844
+ accept: function(node) {
845
+ return this[node.type](node);
846
+ },
847
+
848
+ program: function(program) {
849
+ var statements = program.statements, statement;
850
+ this.opcodes = [];
851
+
852
+ for(var i=0, l=statements.length; i<l; i++) {
853
+ statement = statements[i];
854
+ this[statement.type](statement);
855
+ }
856
+
857
+ this.depths.list = this.depths.list.sort(function(a, b) {
858
+ return a - b;
859
+ });
860
+
861
+ return this;
862
+ },
863
+
864
+ compileProgram: function(program) {
865
+ var result = new this.compiler().compile(program, this.options);
866
+ var guid = this.guid++;
867
+
868
+ this.usePartial = this.usePartial || result.usePartial;
869
+
870
+ this.children[guid] = result;
871
+
872
+ for(var i=0, l=result.depths.list.length; i<l; i++) {
873
+ depth = result.depths.list[i];
874
+
875
+ if(depth < 2) { continue; }
876
+ else { this.addDepth(depth - 1); }
877
+ }
878
+
879
+ return guid;
880
+ },
881
+
882
+ block: function(block) {
883
+ var mustache = block.mustache;
884
+ var depth, child, inverse, inverseGuid;
885
+
886
+ var params = this.setupStackForMustache(mustache);
887
+
888
+ var programGuid = this.compileProgram(block.program);
889
+
890
+ if(block.program.inverse) {
891
+ inverseGuid = this.compileProgram(block.program.inverse);
892
+ this.declare('inverse', inverseGuid);
893
+ }
894
+
895
+ this.opcode('invokeProgram', programGuid, params.length);
896
+ this.declare('inverse', null);
897
+ this.opcode('append');
898
+ },
899
+
900
+ inverse: function(block) {
901
+ this.ID(block.mustache.id);
902
+ var programGuid = this.compileProgram(block.program);
903
+
904
+ this.opcode('invokeInverse', programGuid);
905
+ this.opcode('append');
906
+ },
907
+
908
+ hash: function(hash) {
909
+ var pairs = hash.pairs, pair, val;
910
+
911
+ this.opcode('push', '{}');
912
+
913
+ for(var i=0, l=pairs.length; i<l; i++) {
914
+ pair = pairs[i];
915
+ val = pair[1];
916
+
917
+ this.accept(val);
918
+ this.opcode('assignToHash', pair[0]);
919
+ }
920
+ },
921
+
922
+ partial: function(partial) {
923
+ var id = partial.id;
924
+ this.usePartial = true;
925
+
926
+ if(partial.context) {
927
+ this.ID(partial.context);
928
+ } else {
929
+ this.opcode('push', 'context');
930
+ }
931
+
932
+ this.opcode('invokePartial', id.original);
933
+ this.opcode('append');
934
+ },
935
+
936
+ content: function(content) {
937
+ this.opcode('appendContent', content.string);
938
+ },
939
+
940
+ mustache: function(mustache) {
941
+ var params = this.setupStackForMustache(mustache);
942
+
943
+ this.opcode('invokeMustache', params.length, mustache.id.original);
944
+
945
+ if(mustache.escaped) {
946
+ this.opcode('appendEscaped');
947
+ } else {
948
+ this.opcode('append');
949
+ }
950
+ },
951
+
952
+ ID: function(id) {
953
+ this.addDepth(id.depth);
954
+
955
+ this.opcode('getContext', id.depth);
956
+
957
+ this.opcode('lookupWithHelpers', id.parts[0] || null);
958
+
959
+ for(var i=1, l=id.parts.length; i<l; i++) {
960
+ this.opcode('lookup', id.parts[i]);
961
+ }
962
+ },
963
+
964
+ STRING: function(string) {
965
+ this.opcode('pushString', string.string);
966
+ },
967
+
968
+ INTEGER: function(integer) {
969
+ this.opcode('push', integer.integer);
970
+ },
971
+
972
+ BOOLEAN: function(boolean) {
973
+ this.opcode('push', boolean.boolean);
974
+ },
975
+
976
+ comment: function() {},
977
+
978
+ // HELPERS
979
+ pushParams: function(params) {
980
+ var i = params.length, param;
981
+
982
+ while(i--) {
983
+ param = params[i];
984
+
985
+ if(this.options.stringParams) {
986
+ if(param.depth) {
987
+ this.addDepth(param.depth);
988
+ }
989
+
990
+ this.opcode('getContext', param.depth || 0);
991
+ this.opcode('pushStringParam', param.string);
992
+ } else {
993
+ this[param.type](param);
994
+ }
995
+ }
996
+ },
997
+
998
+ opcode: function(name, val1, val2) {
999
+ this.opcodes.push(Compiler.OPCODE_MAP[name]);
1000
+ if(val1 !== undefined) { this.opcodes.push(val1); }
1001
+ if(val2 !== undefined) { this.opcodes.push(val2); }
1002
+ },
1003
+
1004
+ declare: function(name, value) {
1005
+ this.opcodes.push('DECLARE');
1006
+ this.opcodes.push(name);
1007
+ this.opcodes.push(value);
1008
+ },
1009
+
1010
+ addDepth: function(depth) {
1011
+ if(depth === 0) { return; }
1012
+
1013
+ if(!this.depths[depth]) {
1014
+ this.depths[depth] = true;
1015
+ this.depths.list.push(depth);
1016
+ }
1017
+ },
1018
+
1019
+ setupStackForMustache: function(mustache) {
1020
+ var params = mustache.params;
1021
+
1022
+ this.pushParams(params);
1023
+
1024
+ if(mustache.hash) {
1025
+ this.hash(mustache.hash);
1026
+ } else {
1027
+ this.opcode('push', '{}');
1028
+ }
1029
+
1030
+ this.ID(mustache.id);
1031
+
1032
+ return params;
1033
+ }
1034
+ };
1035
+
1036
+ JavaScriptCompiler.prototype = {
1037
+ // PUBLIC API: You can override these methods in a subclass to provide
1038
+ // alternative compiled forms for name lookup and buffering semantics
1039
+ nameLookup: function(parent, name, type) {
1040
+ if(JavaScriptCompiler.RESERVED_WORDS[name] || name.indexOf('-') !== -1 || !isNaN(name)) {
1041
+ return parent + "['" + name + "']";
1042
+ } else if (/^[0-9]+$/.test(name)) {
1043
+ return parent + "[" + name + "]";
1044
+ } else {
1045
+ return parent + "." + name;
1046
+ }
1047
+ },
1048
+
1049
+ appendToBuffer: function(string) {
1050
+ return "buffer = buffer + " + string + ";";
1051
+ },
1052
+
1053
+ initializeBuffer: function() {
1054
+ return this.quotedString("");
1055
+ },
1056
+ // END PUBLIC API
1057
+
1058
+ compile: function(environment, options) {
1059
+ this.environment = environment;
1060
+ this.options = options || {};
1061
+
1062
+ this.preamble();
1063
+
1064
+ this.stackSlot = 0;
1065
+ this.stackVars = [];
1066
+ this.registers = {list: []};
1067
+
1068
+ this.compileChildren(environment, options);
1069
+
1070
+ Handlebars.log(Handlebars.logger.DEBUG, environment.disassemble() + "\n\n");
1071
+
1072
+ var opcodes = environment.opcodes, opcode, name, declareName, declareVal;
1073
+
1074
+ this.i = 0;
1075
+
1076
+ for(l=opcodes.length; this.i<l; this.i++) {
1077
+ opcode = this.nextOpcode(0);
1078
+
1079
+ if(opcode[0] === 'DECLARE') {
1080
+ this.i = this.i + 2;
1081
+ this[opcode[1]] = opcode[2];
1082
+ } else {
1083
+ this.i = this.i + opcode[1].length;
1084
+ this[opcode[0]].apply(this, opcode[1]);
1085
+ }
1086
+ }
1087
+
1088
+ return this.createFunction();
1089
+ },
1090
+
1091
+ nextOpcode: function(n) {
1092
+ var opcodes = this.environment.opcodes, opcode = opcodes[this.i + n], name, val;
1093
+ var extraParams, codes;
1094
+
1095
+ if(opcode === 'DECLARE') {
1096
+ name = opcodes[this.i + 1];
1097
+ val = opcodes[this.i + 2];
1098
+ return ['DECLARE', name, val];
1099
+ } else {
1100
+ name = Compiler.DISASSEMBLE_MAP[opcode];
1101
+
1102
+ extraParams = Compiler.multiParamSize(opcode);
1103
+ codes = [];
1104
+
1105
+ for(var j=0; j<extraParams; j++) {
1106
+ codes.push(opcodes[this.i + j + 1 + n]);
1107
+ }
1108
+
1109
+ return [name, codes];
1110
+ }
1111
+ },
1112
+
1113
+ eat: function(opcode) {
1114
+ this.i = this.i + opcode.length;
1115
+ },
1116
+
1117
+ preamble: function() {
1118
+ var out = [];
1119
+ out.push("var buffer = " + this.initializeBuffer() + ", currentContext = context");
1120
+
1121
+ var copies = "helpers = helpers || Handlebars.helpers;";
1122
+ if(this.environment.usePartial) { copies = copies + " partials = partials || Handlebars.partials;"; }
1123
+ out.push(copies);
1124
+
1125
+ // track the last context pushed into place to allow skipping the
1126
+ // getContext opcode when it would be a noop
1127
+ this.lastContext = 0;
1128
+ this.source = out;
1129
+ },
1130
+
1131
+ createFunction: function() {
1132
+ var container = {
1133
+ escapeExpression: Handlebars.Utils.escapeExpression,
1134
+ invokePartial: Handlebars.VM.invokePartial,
1135
+ programs: [],
1136
+ program: function(i, helpers, partials, data) {
1137
+ var programWrapper = this.programs[i];
1138
+ if(data) {
1139
+ return Handlebars.VM.program(this.children[i], helpers, partials, data);
1140
+ } else if(programWrapper) {
1141
+ return programWrapper;
1142
+ } else {
1143
+ programWrapper = this.programs[i] = Handlebars.VM.program(this.children[i], helpers, partials);
1144
+ return programWrapper;
1145
+ }
1146
+ },
1147
+ programWithDepth: Handlebars.VM.programWithDepth,
1148
+ noop: Handlebars.VM.noop
1149
+ };
1150
+ var locals = this.stackVars.concat(this.registers.list);
1151
+
1152
+ if(locals.length > 0) {
1153
+ this.source[0] = this.source[0] + ", " + locals.join(", ");
1154
+ }
1155
+
1156
+ this.source[0] = this.source[0] + ";";
1157
+
1158
+ this.source.push("return buffer;");
1159
+
1160
+ var params = ["Handlebars", "context", "helpers", "partials"];
1161
+
1162
+ if(this.options.data) { params.push("data"); }
1163
+
1164
+ for(var i=0, l=this.environment.depths.list.length; i<l; i++) {
1165
+ params.push("depth" + this.environment.depths.list[i]);
1166
+ }
1167
+
1168
+ if(params.length === 4 && !this.environment.usePartial) { params.pop(); }
1169
+
1170
+ params.push(this.source.join("\n"));
1171
+
1172
+ var fn = Function.apply(this, params);
1173
+ fn.displayName = "Handlebars.js";
1174
+
1175
+ Handlebars.log(Handlebars.logger.DEBUG, fn.toString() + "\n\n");
1176
+
1177
+ container.render = fn;
1178
+
1179
+ container.children = this.environment.children;
1180
+
1181
+ return function(context, options, $depth) {
1182
+ try {
1183
+ options = options || {};
1184
+ var args = [Handlebars, context, options.helpers, options.partials, options.data];
1185
+ var depth = Array.prototype.slice.call(arguments, 2);
1186
+ args = args.concat(depth);
1187
+ return container.render.apply(container, args);
1188
+ } catch(e) {
1189
+ throw e;
1190
+ }
1191
+ };
1192
+ },
1193
+
1194
+ appendContent: function(content) {
1195
+ this.source.push(this.appendToBuffer(this.quotedString(content)));
1196
+ },
1197
+
1198
+ append: function() {
1199
+ var local = this.popStack();
1200
+ this.source.push("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }");
1201
+ },
1202
+
1203
+ appendEscaped: function() {
1204
+ var opcode = this.nextOpcode(1), extra = "";
1205
+
1206
+ if(opcode[0] === 'appendContent') {
1207
+ extra = " + " + this.quotedString(opcode[1][0]);
1208
+ this.eat(opcode);
1209
+ }
1210
+
1211
+ this.source.push(this.appendToBuffer("this.escapeExpression(" + this.popStack() + ")" + extra));
1212
+ },
1213
+
1214
+ getContext: function(depth) {
1215
+ if(this.lastContext !== depth) {
1216
+ this.lastContext = depth;
1217
+
1218
+ if(depth === 0) {
1219
+ this.source.push("currentContext = context;");
1220
+ } else {
1221
+ this.source.push("currentContext = depth" + depth + ";");
1222
+ }
1223
+ }
1224
+ },
1225
+
1226
+ lookupWithHelpers: function(name) {
1227
+ if(name) {
1228
+ var topStack = this.nextStack();
1229
+
1230
+ var toPush = "if('" + name + "' in helpers) { " + topStack +
1231
+ " = " + this.nameLookup('helpers', name, 'helper') +
1232
+ "; } else { " + topStack + " = " +
1233
+ this.nameLookup('currentContext', name, 'context') +
1234
+ "; }";
1235
+
1236
+ this.source.push(toPush);
1237
+ } else {
1238
+ this.pushStack("currentContext");
1239
+ }
1240
+ },
1241
+
1242
+ lookup: function(name) {
1243
+ var topStack = this.topStack();
1244
+ this.source.push(topStack + " = " + this.nameLookup(topStack, name, 'context') + ";");
1245
+ },
1246
+
1247
+ pushStringParam: function(string) {
1248
+ this.pushStack("currentContext");
1249
+ this.pushString(string);
1250
+ },
1251
+
1252
+ pushString: function(string) {
1253
+ this.pushStack(this.quotedString(string));
1254
+ },
1255
+
1256
+ push: function(name) {
1257
+ this.pushStack(name);
1258
+ },
1259
+
1260
+ invokeMustache: function(paramSize, original) {
1261
+ this.populateParams(paramSize, this.quotedString(original), "{}", null, function(nextStack, helperMissingString, id) {
1262
+ this.source.push("else if(" + id + "=== undefined) { " + nextStack + " = helpers.helperMissing.call(" + helperMissingString + "); }");
1263
+ this.source.push("else { " + nextStack + " = " + id + "; }");
1264
+ });
1265
+ },
1266
+
1267
+ invokeProgram: function(guid, paramSize) {
1268
+ var inverse = this.programExpression(this.inverse);
1269
+ var mainProgram = this.programExpression(guid);
1270
+
1271
+ this.populateParams(paramSize, null, mainProgram, inverse, function(nextStack, helperMissingString, id) {
1272
+ this.source.push("else { " + nextStack + " = helpers.blockHelperMissing.call(" + helperMissingString + "); }");
1273
+ });
1274
+ },
1275
+
1276
+ populateParams: function(paramSize, helperId, program, inverse, fn) {
1277
+ var id = this.popStack(), nextStack;
1278
+ var params = [], param, stringParam;
1279
+
1280
+ var hash = this.popStack();
1281
+
1282
+ this.register('tmp1', program);
1283
+ this.source.push('tmp1.hash = ' + hash + ';');
1284
+
1285
+ if(this.options.stringParams) {
1286
+ this.source.push('tmp1.contexts = [];');
1287
+ }
1288
+
1289
+ for(var i=0; i<paramSize; i++) {
1290
+ param = this.popStack();
1291
+ params.push(param);
1292
+
1293
+ if(this.options.stringParams) {
1294
+ this.source.push('tmp1.contexts.push(' + this.popStack() + ');');
1295
+ }
1296
+ }
1297
+
1298
+ if(inverse) {
1299
+ this.source.push('tmp1.fn = tmp1;');
1300
+ this.source.push('tmp1.inverse = ' + inverse + ';');
1301
+ }
1302
+
1303
+ if(this.options.data) {
1304
+ this.source.push('tmp1.data = data;');
1305
+ }
1306
+
1307
+ params.push('tmp1');
1308
+
1309
+ // TODO: This is legacy behavior. Deprecate and remove.
1310
+ if(inverse) {
1311
+ params.push(inverse);
1312
+ }
1313
+
1314
+ this.populateCall(params, id, helperId || id, fn);
1315
+ },
1316
+
1317
+ populateCall: function(params, id, helperId, fn) {
1318
+ var paramString = ["context"].concat(params).join(", ");
1319
+ var helperMissingString = ["context"].concat(helperId).concat(params).join(", ");
1320
+
1321
+ nextStack = this.nextStack();
1322
+
1323
+ this.source.push("if(typeof " + id + " === 'function') { " + nextStack + " = " + id + ".call(" + paramString + "); }");
1324
+ fn.call(this, nextStack, helperMissingString, id);
1325
+ },
1326
+
1327
+ invokeInverse: function(guid) {
1328
+ var program = this.programExpression(guid);
1329
+
1330
+ var blockMissingParams = ["context", this.topStack(), "this.noop", program];
1331
+ this.pushStack("helpers.blockHelperMissing.call(" + blockMissingParams.join(", ") + ")");
1332
+ },
1333
+
1334
+ invokePartial: function(context) {
1335
+ this.pushStack("this.invokePartial(" + this.nameLookup('partials', context, 'partial') + ", '" + context + "', " + this.popStack() + ", helpers, partials);");
1336
+ },
1337
+
1338
+ assignToHash: function(key) {
1339
+ var value = this.popStack();
1340
+ var hash = this.topStack();
1341
+
1342
+ this.source.push(hash + "['" + key + "'] = " + value + ";");
1343
+ },
1344
+
1345
+ // HELPERS
1346
+
1347
+ compiler: JavaScriptCompiler,
1348
+
1349
+ compileChildren: function(environment, options) {
1350
+ var children = environment.children, child, compiler;
1351
+ var compiled = [];
1352
+
1353
+ for(var i=0, l=children.length; i<l; i++) {
1354
+ child = children[i];
1355
+ compiler = new this.compiler();
1356
+
1357
+ compiled[i] = compiler.compile(child, options);
1358
+ }
1359
+
1360
+ environment.rawChildren = children;
1361
+ environment.children = compiled;
1362
+ },
1363
+
1364
+ programExpression: function(guid) {
1365
+ if(guid == null) { return "this.noop"; }
1366
+
1367
+ var programParams = [guid, "helpers", "partials"];
1368
+
1369
+ var depths = this.environment.rawChildren[guid].depths.list;
1370
+
1371
+ if(this.options.data) { programParams.push("data"); }
1372
+
1373
+ for(var i=0, l = depths.length; i<l; i++) {
1374
+ depth = depths[i];
1375
+
1376
+ if(depth === 1) { programParams.push("context"); }
1377
+ else { programParams.push("depth" + (depth - 1)); }
1378
+ }
1379
+
1380
+ if(!this.environment.usePartial) {
1381
+ if(programParams[3]) {
1382
+ programParams[2] = "null";
1383
+ } else {
1384
+ programParams.pop();
1385
+ }
1386
+ }
1387
+
1388
+ if(depths.length === 0) {
1389
+ return "this.program(" + programParams.join(", ") + ")";
1390
+ } else {
1391
+ programParams[0] = "this.children[" + guid + "]";
1392
+ return "this.programWithDepth(" + programParams.join(", ") + ")";
1393
+ }
1394
+ },
1395
+
1396
+ register: function(name, val) {
1397
+ this.useRegister(name);
1398
+ this.source.push(name + " = " + val + ";");
1399
+ },
1400
+
1401
+ useRegister: function(name) {
1402
+ if(!this.registers[name]) {
1403
+ this.registers[name] = true;
1404
+ this.registers.list.push(name);
1405
+ }
1406
+ },
1407
+
1408
+ pushStack: function(item) {
1409
+ this.source.push(this.nextStack() + " = " + item + ";");
1410
+ return "stack" + this.stackSlot;
1411
+ },
1412
+
1413
+ nextStack: function() {
1414
+ this.stackSlot++;
1415
+ if(this.stackSlot > this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); }
1416
+ return "stack" + this.stackSlot;
1417
+ },
1418
+
1419
+ popStack: function() {
1420
+ return "stack" + this.stackSlot--;
1421
+ },
1422
+
1423
+ topStack: function() {
1424
+ return "stack" + this.stackSlot;
1425
+ },
1426
+
1427
+ quotedString: function(str) {
1428
+ return '"' + str
1429
+ .replace(/\\/g, '\\\\')
1430
+ .replace(/"/g, '\\"')
1431
+ .replace(/\n/g, '\\n')
1432
+ .replace(/\r/g, '\\r') + '"';
1433
+ }
1434
+ };
1435
+
1436
+ var reservedWords = ("break case catch continue default delete do else finally " +
1437
+ "for function if in instanceof new return switch this throw " +
1438
+ "try typeof var void while with null true false").split(" ");
1439
+
1440
+ compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};
1441
+
1442
+ for(var i=0, l=reservedWords.length; i<l; i++) {
1443
+ compilerWords[reservedWords[i]] = true;
1444
+ }
1445
+
1446
+ })(Handlebars.Compiler, Handlebars.JavaScriptCompiler);
1447
+
1448
+ Handlebars.VM = {
1449
+ programWithDepth: function(fn, helpers, partials, data, $depth) {
1450
+ var args = Array.prototype.slice.call(arguments, 4);
1451
+
1452
+ return function(context, options) {
1453
+ options = options || {};
1454
+
1455
+ options = {
1456
+ helpers: options.helpers || helpers,
1457
+ partials: options.partials || partials,
1458
+ data: options.data || data
1459
+ };
1460
+
1461
+ return fn.apply(this, [context, options].concat(args));
1462
+ };
1463
+ },
1464
+ program: function(fn, helpers, partials, data) {
1465
+ return function(context, options) {
1466
+ options = options || {};
1467
+
1468
+ return fn(context, {
1469
+ helpers: options.helpers || helpers,
1470
+ partials: options.partials || partials,
1471
+ data: options.data || data
1472
+ });
1473
+ };
1474
+ },
1475
+ noop: function() { return ""; },
1476
+ compile: function(string, options) {
1477
+ var ast = Handlebars.parse(string);
1478
+ var environment = new Handlebars.Compiler().compile(ast, options);
1479
+ return new Handlebars.JavaScriptCompiler().compile(environment, options);
1480
+ },
1481
+ invokePartial: function(partial, name, context, helpers, partials) {
1482
+ if(partial === undefined) {
1483
+ throw new Handlebars.Exception("The partial " + name + " could not be found");
1484
+ } else if(partial instanceof Function) {
1485
+ return partial(context, {helpers: helpers, partials: partials});
1486
+ } else {
1487
+ partials[name] = Handlebars.VM.compile(partial);
1488
+ return partials[name](context, {helpers: helpers, partials: partials});
1489
+ }
1490
+ }
1491
+ };
1492
+
1493
+ Handlebars.compile = Handlebars.VM.compile;;