Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
  • 16 commits
  • 6 files changed
  • 0 comments
  • 1 contributor
Mar 30, 2012
Jon Move Bundle and Assets class to seperate files
This is the start of seperating the asset classes from sinatra.
d436e1d
Jon Assets no longer has knowledge of sinatra
That's all in the plugin part
474aeef
Jon Use block params 7089593
Jon Hash based on non-minified contents
Minification is unlikely to change that often, and this means that there
is no need for runtime compiling which has potential thread safety
issues
15a04e0
Jon Assets expire after 1 month, no revalidation
Just change the files to redeploy
b25f3d0
Jon Figure out hashes on bundle creation
This means we don't need to modify the hashes at runtime, which very
helpful in a global hash.
f6e0d45
Jon Refactor logic on not_found/etag cbd120b
Mar 31, 2012
Jon Use /js and /css for routes d71e0e6
Jon Allow Assets to be configured multiple times 9c14465
Jon Return self from commands b82571a
Jon Allow assigning Assets ffad0ec
Jon Allow a separate asset root
This allows for e.g. assets in app/assets/ which get compiled into
public/ for deployment, so there's no need to mix compiled and real
files in the same folder tree.
a77ffcd
Jon Expose configuration for the asset root b5d65c5
Sep 06, 2012
Jon It's app in the registered hook, not self 2fe832e
Jon Version 0.0.6
* Add extra files
* bump version
* add own name
4c882b7
Sep 14, 2012
Jon Support handlebars templates
Supports generating individual templates, or a combined and minified
template for the production serving.  It seems to work now, in its
entirety.
80d5aa2
149 lib/sinatra/simple_assets.rb
... ... @@ -1,157 +1,68 @@
1 1 require 'sinatra/base'
  2 +require 'sinatra/simple_assets/assets'
2 3
3 4 module Sinatra
4 5 module SimpleAssets
5 6 module Helpers
6 7 def stylesheet(bundle)
7   - settings.assets.paths_for("#{bundle}.css").map do |file|
  8 + settings.assets.paths_for("#{bundle}.css", settings.environment).map do |file|
8 9 "<link rel=\"stylesheet\" href=\"#{url(file)}\">"
9 10 end.join("\n")
10 11 end
11 12
12 13 def javascript(bundle)
13   - settings.assets.paths_for("#{bundle}.js").map do |file|
  14 + settings.assets.paths_for("#{bundle}.js", settings.environment).map do |file|
14 15 "<script src=\"#{url(file)}\"></script>"
15 16 end.join("\n")
16 17 end
17   - end
18   -
19   - class Bundle
20   - attr_accessor :files
21   -
22   - def initialize(name, type, root, files)
23   - @name = name
24   - @type = type
25   - @root = root
26   - @files = files
27   - end
28   -
29   - def name
30   - "#{@name}.#{@type}"
31   - end
32 18
33   - def hash_name
34   - "#{@name}-#{hash}.#{@type}"
35   - end
36   -
37   - def hashed_path
38   - "#{path}/#{hash_name}"
39   - end
40   -
41   - def hash
42   - @hash ||= begin
43   - Digest::SHA1.hexdigest content
44   - rescue
45   - # Find the most recent compressed version if the JS runtime is unavailable
46   - fname = Dir.glob("#{@root}/#{path}/#{@name}-*.#{@type}").sort_by {|f| File.mtime(f)}.last
47   - fname.split('-').last.sub(".#{@type}", "")
48   - end
49   - end
50   -
51   - def content
52   - case @type
53   - when :js
54   - require 'uglifier'
55   - @content ||= Uglifier.new.compress combined
56   - when :css
57   - require 'cssmin'
58   - @content ||= CSSMin.minify combined
59   - end
60   - end
61   -
62   - def combined
63   - @combined ||= @files.map do |file|
64   - File.open(@root + file) { |f| f.read }
  19 + def hbs(bundle)
  20 + settings.assets.paths_for("#{bundle}.hbs", settings.environment).map do |file|
  21 + "<script src=\"#{url(file)}\"></script>"
65 22 end.join("\n")
66 23 end
67   -
68   - def path
69   - @type == :js ? 'javascripts' : 'stylesheets'
70   - end
71   -
72   - def compile
73   - File.open("#{@root}/#{hashed_path}", 'w') do |f|
74   - f << content
75   - end
76   - end
77 24 end
78 25
79   - class Assets
80   - def initialize(app, &block)
81   - @app = app
82   - @bundles = {}
83   - @hashes = {}
84   - instance_eval(&block)
85   - end
86   -
87   - def css(bundle, files)
88   - create_bundle(bundle, :css, files)
89   - end
90   -
91   - def js(bundle, files)
92   - create_bundle(bundle, :js, files)
93   - end
94   -
95   - def create_bundle(name, type, files)
96   - bundle = Bundle.new(name, type, @app.public_folder, files)
97   - @bundles[bundle.name] = bundle
98   - end
99   -
100   - def paths_for(bundle)
101   - bundle = @bundles[bundle]
102   - return [] unless bundle
103   -
104   - if @app.environment == :production
105   - @hashes[bundle.hash_name] = bundle.name
106   - [bundle.hashed_path]
107   - else
108   - bundle.files
109   - end
110   - end
111   -
112   - def content_for(bundle)
113   - bundle = @bundles[@hashes[bundle]]
114   - bundle.content if bundle
115   - end
116   -
117   - def precompile
118   - @bundles.values.each do |bundle|
119   - bundle.compile
120   - end
121   - end
122   -
123   - def bundle_exists?(bundle)
124   - @hashes[bundle]
125   - end
126   - end
127   -
128   - def assets(&block)
129   - @assets ||= Assets.new(self, &block)
  26 + def assets(assets = nil, &block)
  27 + @assets ||= (assets ? assets : Assets.new(self.public_folder, self.asset_root))
  28 + @assets.configure(&block)
130 29 end
131 30
132 31 def self.registered(app)
133 32 app.helpers SimpleAssets::Helpers
134 33
  34 + app.set :asset_root, app.public_folder
  35 +
135 36 [
136   - { :route => '/stylesheets', :type => :css },
137   - { :route => '/javascripts', :type => :js }
  37 + { :route => '/css', :type => :css },
  38 + { :route => '/js', :type => :js },
  39 + { :route => '/hbs', :type => :js }
138 40 ].each do |r|
139   - app.get "#{r[:route]}/:bundle" do
140   - bundle = params[:bundle]
  41 + app.get "#{r[:route]}/:bundle" do |bundle|
141 42 assets = settings.assets
142   - exists = assets.bundle_exists?(bundle)
143 43
144   - etag bundle if exists
145   - not_found unless exists
  44 + if assets.bundle_exists?(bundle)
  45 + etag bundle
  46 + else
  47 + not_found
  48 + end
146 49
147   - cache_control :public, :must_revalidate, :max_age => 86400
  50 + cache_control :public, :max_age => 2592000 # one month
148 51
149 52 content_type r[:type]
150 53 assets.content_for(bundle)
151 54 end
152 55 end
  56 +
  57 + app.get "/js/views/:template" do |template|
  58 + path = File.join(app.public_folder, 'js', 'views', "#{template}.hbs")
  59 + not_found unless File.file?(path)
  60 + require 'sinatra/simple_assets/handlebars'
  61 + content_type :js
  62 + Handlebars.wrap(template => File.read(path))
  63 + end
153 64 end
154   - end
  65 + end
155 66
156 67 register SimpleAssets
157 68 end
65 lib/sinatra/simple_assets/assets.rb
... ... @@ -0,0 +1,65 @@
  1 +require 'sinatra/simple_assets/bundle'
  2 +module Sinatra
  3 + module SimpleAssets
  4 + class Assets
  5 + def initialize(root, asset_root=root, &block)
  6 + @root = root
  7 + @asset_root = asset_root
  8 + @bundles = {}
  9 + @hashes = {}
  10 + configure(&block)
  11 + end
  12 +
  13 + def configure(&block)
  14 + instance_eval(&block) if block_given?
  15 + self
  16 + end
  17 +
  18 + def css(bundle, files)
  19 + create_bundle(bundle, :css, files)
  20 + end
  21 +
  22 + def js(bundle, files)
  23 + create_bundle(bundle, :js, files)
  24 + end
  25 +
  26 + def hbs(bundle, files)
  27 + create_bundle(bundle, :hbs, files)
  28 + end
  29 +
  30 + def create_bundle(name, type, files)
  31 + bundle = Bundle.new(name, type, @root, @asset_root, files)
  32 + @bundles[bundle.name] = bundle
  33 + @hashes[bundle.hash_name] = bundle
  34 + self
  35 + end
  36 +
  37 + def paths_for(bundle, environment = :development)
  38 + bundle = @bundles[bundle]
  39 + return [] unless bundle
  40 +
  41 + if environment == :production
  42 + [bundle.hashed_path]
  43 + else
  44 + bundle.files
  45 + end
  46 + end
  47 +
  48 + def content_for(bundle)
  49 + bundle = @hashes[bundle]
  50 + bundle.content if bundle
  51 + end
  52 +
  53 + def precompile
  54 + @bundles.values.each do |bundle|
  55 + bundle.compile
  56 + end
  57 + self
  58 + end
  59 +
  60 + def bundle_exists?(bundle)
  61 + @hashes[bundle]
  62 + end
  63 + end
  64 + end
  65 +end
77 lib/sinatra/simple_assets/bundle.rb
... ... @@ -0,0 +1,77 @@
  1 +require 'digest/sha1'
  2 +module Sinatra
  3 + module SimpleAssets
  4 + class Bundle
  5 + attr_accessor :files
  6 +
  7 + def initialize(name, type, root, asset_root, files)
  8 + @name = name
  9 + @type = type
  10 + @root = root
  11 + @asset_root = asset_root
  12 + @files = files
  13 + end
  14 +
  15 + def inspect
  16 + "#<#{self.class.name} @name=#{@name.inspect} @hash_name=#{hash_name}>"
  17 + end
  18 +
  19 + def name
  20 + "#{@name}.#{@type}"
  21 + end
  22 +
  23 + def hash_name
  24 + "#{@name}-#{asset_hash}.#{@type}"
  25 + end
  26 +
  27 + def hashed_path
  28 + "#{path}/#{hash_name}"
  29 + end
  30 +
  31 + def asset_hash
  32 + @asset_hash ||= Digest::SHA1.hexdigest(combined)
  33 + end
  34 +
  35 + def content
  36 + case @type
  37 + when :js
  38 + require 'uglifier'
  39 + @content ||= Uglifier.new.compress combined
  40 + when :css
  41 + require 'cssmin'
  42 + @content ||= CSSMin.minify combined
  43 + when :hbs
  44 + require 'uglifier'
  45 + require 'sinatra/simple_assets/handlebars'
  46 + @content ||= begin
  47 + u = Uglifier.new
  48 + s = *files.map { |f| [File.basename(f), file_content(f)] }
  49 + u.compress Handlebars.wrap(Hash[s])
  50 + end
  51 + end
  52 + end
  53 +
  54 + def combined
  55 + @combined ||= @files.map do |file|
  56 + file_content(file)
  57 + end.join("\n")
  58 + end
  59 +
  60 + def file_content(file)
  61 + File.read(@asset_root + file)
  62 + rescue Errno::ENOENT
  63 + File.read(@asset_root + file + ".#{@type}")
  64 + end
  65 +
  66 + def path
  67 + @type.to_s
  68 + end
  69 +
  70 + def compile
  71 + File.open("#{@root}/#{hashed_path}", 'w') do |f|
  72 + f << content
  73 + end
  74 + end
  75 + end
  76 + end
  77 +end
50 lib/sinatra/simple_assets/handlebars.rb
... ... @@ -0,0 +1,50 @@
  1 +#!/usr/bin/env ruby
  2 +
  3 +# Based on https://github.com/josh/ruby-coffee-script
  4 +require 'execjs'
  5 +require 'pathname'
  6 +
  7 +module Sinatra
  8 + module SimpleAssets
  9 + class Handlebars
  10 + class << self
  11 + def precompile(*args)
  12 + context.call('Handlebars.precompile', *args)
  13 + end
  14 +
  15 + def wrap(scripts)
  16 + ret = []
  17 + ret << <<-eos
  18 + Handlebars.templates = Handlebars.templates || {};
  19 + (function (template, templates) {
  20 + eos
  21 + scripts.each do |name, source|
  22 + ret << "templates['#{name}'] = template(#{precompile(source)});"
  23 + end
  24 + ret << <<-eos
  25 + })(Handlebars.template, Handlebars.templates);
  26 + eos
  27 + ret.join("\n")
  28 + end
  29 +
  30 + private
  31 +
  32 + def context
  33 + @context ||= ExecJS.compile(source)
  34 + end
  35 +
  36 + def source
  37 + @source ||= path.read
  38 + end
  39 +
  40 + def path
  41 + @path ||= assets_path.join('handlebars.js')
  42 + end
  43 +
  44 + def assets_path
  45 + @assets_path ||= Pathname(__FILE__).dirname.join('..', '..', '..', 'vendor')
  46 + end
  47 + end
  48 + end
  49 + end
  50 +end
26 sinatra-simple-assets.gemspec
... ... @@ -1,21 +1,25 @@
1 1 # -*- encoding: utf-8 -*-
2 2 Gem::Specification.new do |s|
3 3 s.name = 'sinatra-simple-assets'
4   - s.version = '0.0.5'
5   - s.authors = ["Pete O'Grady"]
6   - s.email = ['pete@peteogrady.com']
  4 + s.version = '0.0.7'
  5 + s.authors = ["Pete O'Grady", "Jonathan Stott"]
  6 + s.email = ['pete@peteogrady.com', 'jonathan.stott@gmail.com']
7 7 s.homepage = 'https://github.com/peteog/sinatra-simple-assets'
8 8 s.summary = %q{Asset minification and bundling for Sinatra}
9 9 s.description = %q{Asset minification and bundling for Sinatra}
10 10
11   - s.files = [
12   - ".gitignore",
13   - "Gemfile",
14   - "README.md",
15   - "Rakefile",
16   - "lib/sinatra/simple_assets.rb",
17   - "sinatra-simple-assets.gemspec"
18   - ]
  11 + s.files = %w{
  12 + .gitignore
  13 + Gemfile
  14 + README.md
  15 + Rakefile
  16 + lib/sinatra/simple_assets.rb
  17 + lib/sinatra/simple_assets/assets.rb
  18 + lib/sinatra/simple_assets/bundle.rb
  19 + lib/sinatra/simple_assets/handlebars.rb
  20 + vendor/handlebars.js
  21 + sinatra-simple-assets.gemspec
  22 + }
19 23 s.require_paths = ['lib']
20 24
21 25 s.add_dependency 'cssmin', '~> 1.0.2'
1,550 vendor/handlebars.js
... ... @@ -0,0 +1,1550 @@
  1 +// lib/handlebars/base.js
  2 +var Handlebars = {};
  3 +
  4 +Handlebars.VERSION = "1.0.beta.6";
  5 +
  6 +Handlebars.helpers = {};
  7 +Handlebars.partials = {};
  8 +
  9 +Handlebars.registerHelper = function(name, fn, inverse) {
  10 + if(inverse) { fn.not = inverse; }
  11 + this.helpers[name] = fn;
  12 +};
  13 +
  14 +Handlebars.registerPartial = function(name, str) {
  15 + this.partials[name] = str;
  16 +};
  17 +
  18 +Handlebars.registerHelper('helperMissing', function(arg) {
  19 + if(arguments.length === 2) {
  20 + return undefined;
  21 + } else {
  22 + throw new Error("Could not find property '" + arg + "'");
  23 + }
  24 +});
  25 +
  26 +var toString = Object.prototype.toString, functionType = "[object Function]";
  27 +
  28 +Handlebars.registerHelper('blockHelperMissing', function(context, options) {
  29 + var inverse = options.inverse || function() {}, fn = options.fn;
  30 +
  31 +
  32 + var ret = "";
  33 + var type = toString.call(context);
  34 +
  35 + if(type === functionType) { context = context.call(this); }
  36 +
  37 + if(context === true) {
  38 + return fn(this);
  39 + } else if(context === false || context == null) {
  40 + return inverse(this);
  41 + } else if(type === "[object Array]") {
  42 + if(context.length > 0) {
  43 + for(var i=0, j=context.length; i<j; i++) {
  44 + ret = ret + fn(context[i]);
  45 + }
  46 + } else {
  47 + ret = inverse(this);
  48 + }
  49 + return ret;
  50 + } else {
  51 + return fn(context);
  52 + }
  53 +});
  54 +
  55 +Handlebars.registerHelper('each', function(context, options) {
  56 + var fn = options.fn, inverse = options.inverse;
  57 + var ret = "";
  58 +
  59 + if(context && context.length > 0) {
  60 + for(var i=0, j=context.length; i<j; i++) {
  61 + ret = ret + fn(context[i]);
  62 + }
  63 + } else {
  64 + ret = inverse(this);
  65 + }
  66 + return ret;
  67 +});
  68 +
  69 +Handlebars.registerHelper('if', function(context, options) {
  70 + var type = toString.call(context);
  71 + if(type === functionType) { context = context.call(this); }
  72 +
  73 + if(!context || Handlebars.Utils.isEmpty(context)) {
  74 + return options.inverse(this);
  75 + } else {
  76 + return options.fn(this);
  77 + }
  78 +});
  79 +
  80 +Handlebars.registerHelper('unless', function(context, options) {
  81 + var fn = options.fn, inverse = options.inverse;
  82 + options.fn = inverse;
  83 + options.inverse = fn;
  84 +
  85 + return Handlebars.helpers['if'].call(this, context, options);
  86 +});
  87 +
  88 +Handlebars.registerHelper('with', function(context, options) {
  89 + return options.fn(context);
  90 +});
  91 +
  92 +Handlebars.registerHelper('log', function(context) {
  93 + Handlebars.log(context);
  94 +});
  95 +;
  96 +// lib/handlebars/compiler/parser.js
  97 +/* Jison generated parser */
  98 +var handlebars = (function(){
  99 +
  100 +var parser = {trace: function trace() { },
  101 +yy: {},
  102 +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},
  103 +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"},
  104 +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]],
  105 +performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) {
  106 +
  107 +var $0 = $$.length - 1;
  108 +switch (yystate) {
  109 +case 1: return $$[$0-1]
  110 +break;
  111 +case 2: this.$ = new yy.ProgramNode($$[$0-2], $$[$0])
  112 +break;
  113 +case 3: this.$ = new yy.ProgramNode($$[$0])
  114 +break;
  115 +case 4: this.$ = new yy.ProgramNode([])
  116 +break;
  117 +case 5: this.$ = [$$[$0]]
  118 +break;
  119 +case 6: $$[$0-1].push($$[$0]); this.$ = $$[$0-1]
  120 +break;
  121 +case 7: this.$ = new yy.InverseNode($$[$0-2], $$[$0-1], $$[$0])
  122 +break;
  123 +case 8: this.$ = new yy.BlockNode($$[$0-2], $$[$0-1], $$[$0])
  124 +break;
  125 +case 9: this.$ = $$[$0]
  126 +break;
  127 +case 10: this.$ = $$[$0]
  128 +break;
  129 +case 11: this.$ = new yy.ContentNode($$[$0])
  130 +break;
  131 +case 12: this.$ = new yy.CommentNode($$[$0])
  132 +break;
  133 +case 13: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1])
  134 +break;
  135 +case 14: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1])
  136 +break;
  137 +case 15: this.$ = $$[$0-1]
  138 +break;
  139 +case 16: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1])
  140 +break;
  141 +case 17: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1], true)
  142 +break;
  143 +case 18: this.$ = new yy.PartialNode($$[$0-1])
  144 +break;
  145 +case 19: this.$ = new yy.PartialNode($$[$0-2], $$[$0-1])
  146 +break;
  147 +case 20:
  148 +break;
  149 +case 21: this.$ = [[$$[$0-2]].concat($$[$0-1]), $$[$0]]
  150 +break;
  151 +case 22: this.$ = [[$$[$0-1]].concat($$[$0]), null]
  152 +break;
  153 +case 23: this.$ = [[$$[$0-1]], $$[$0]]
  154 +break;
  155 +case 24: this.$ = [[$$[$0]], null]
  156 +break;
  157 +case 25: $$[$0-1].push($$[$0]); this.$ = $$[$0-1];
  158 +break;
  159 +case 26: this.$ = [$$[$0]]
  160 +break;
  161 +case 27: this.$ = $$[$0]
  162 +break;
  163 +case 28: this.$ = new yy.StringNode($$[$0])
  164 +break;
  165 +case 29: this.$ = new yy.IntegerNode($$[$0])
  166 +break;
  167 +case 30: this.$ = new yy.BooleanNode($$[$0])
  168 +break;
  169 +case 31: this.$ = new yy.HashNode($$[$0])
  170 +break;
  171 +case 32: $$[$0-1].push($$[$0]); this.$ = $$[$0-1]
  172 +break;
  173 +case 33: this.$ = [$$[$0]]
  174 +break;
  175 +case 34: this.$ = [$$[$0-2], $$[$0]]
  176 +break;
  177 +case 35: this.$ = [$$[$0-2], new yy.StringNode($$[$0])]
  178 +break;
  179 +case 36: this.$ = [$$[$0-2], new yy.IntegerNode($$[$0])]
  180 +break;
  181 +case 37: this.$ = [$$[$0-2], new yy.BooleanNode($$[$0])]
  182 +break;
  183 +case 38: this.$ = new yy.IdNode($$[$0])
  184 +break;
  185 +case 39: $$[$0-2].push($$[$0]); this.$ = $$[$0-2];
  186 +break;
  187 +case 40: this.$ = [$$[$0]]
  188 +break;
  189 +}
  190 +},
  191 +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]}],
  192 +defaultActions: {16:[2,1],37:[2,23],53:[2,21]},
  193 +parseError: function parseError(str, hash) {
  194 + throw new Error(str);
  195 +},
  196 +parse: function parse(input) {
  197 + var self = this, stack = [0], vstack = [null], lstack = [], table = this.table, yytext = "", yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1;
  198 + this.lexer.setInput(input);
  199 + this.lexer.yy = this.yy;
  200 + this.yy.lexer = this.lexer;
  201 + if (typeof this.lexer.yylloc == "undefined")
  202 + this.lexer.yylloc = {};
  203 + var yyloc = this.lexer.yylloc;
  204 + lstack.push(yyloc);
  205 + if (typeof this.yy.parseError === "function")
  206 + this.parseError = this.yy.parseError;
  207 + function popStack(n) {
  208 + stack.length = stack.length - 2 * n;
  209 + vstack.length = vstack.length - n;
  210 + lstack.length = lstack.length - n;
  211 + }
  212 + function lex() {
  213 + var token;
  214 + token = self.lexer.lex() || 1;
  215 + if (typeof token !== "number") {
  216 + token = self.symbols_[token] || token;
  217 + }
  218 + return token;
  219 + }
  220 + var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected;
  221 + while (true) {
  222 + state = stack[stack.length - 1];
  223 + if (this.defaultActions[state]) {
  224 + action = this.defaultActions[state];
  225 + } else {
  226 + if (symbol == null)
  227 + symbol = lex();
  228 + action = table[state] && table[state][symbol];
  229 + }
  230 + if (typeof action === "undefined" || !action.length || !action[0]) {
  231 + if (!recovering) {
  232 + expected = [];
  233 + for (p in table[state])
  234 + if (this.terminals_[p] && p > 2) {
  235 + expected.push("'" + this.terminals_[p] + "'");
  236 + }
  237 + var errStr = "";
  238 + if (this.lexer.showPosition) {
  239 + errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + this.terminals_[symbol] + "'";
  240 + } else {
  241 + errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'");
  242 + }
  243 + this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected});
  244 + }
  245 + }
  246 + if (action[0] instanceof Array && action.length > 1) {
  247 + throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol);
  248 + }
  249 + switch (action[0]) {
  250 + case 1:
  251 + stack.push(symbol);
  252 + vstack.push(this.lexer.yytext);
  253 + lstack.push(this.lexer.yylloc);
  254 + stack.push(action[1]);
  255 + symbol = null;
  256 + if (!preErrorSymbol) {
  257 + yyleng = this.lexer.yyleng;
  258 + yytext = this.lexer.yytext;
  259 + yylineno = this.lexer.yylineno;
  260 + yyloc = this.lexer.yylloc;
  261 + if (recovering > 0)
  262 + recovering--;
  263 + } else {
  264 + symbol = preErrorSymbol;
  265 + preErrorSymbol = null;
  266 + }
  267 + break;
  268 + case 2:
  269 + len = this.productions_[action[1]][1];
  270 + yyval.$ = vstack[vstack.length - len];
  271 + yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column};
  272 + r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);
  273 + if (typeof r !== "undefined") {
  274 + return r;
  275 + }
  276 + if (len) {
  277 + stack = stack.slice(0, -1 * len * 2);
  278 + vstack = vstack.slice(0, -1 * len);
  279 + lstack = lstack.slice(0, -1 * len);
  280 + }
  281 + stack.push(this.productions_[action[1]][0]);
  282 + vstack.push(yyval.$);
  283 + lstack.push(yyval._$);
  284 + newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
  285 + stack.push(newState);
  286 + break;
  287 + case 3:
  288 + return true;
  289 + }
  290 + }
  291 + return true;
  292 +}
  293 +};/* Jison generated lexer */
  294 +var lexer = (function(){
  295 +
  296 +var lexer = ({EOF:1,
  297 +parseError:function parseError(str, hash) {
  298 + if (this.yy.parseError) {
  299 + this.yy.parseError(str, hash);
  300 + } else {
  301 + throw new Error(str);
  302 + }
  303 + },
  304 +setInput:function (input) {
  305 + this._input = input;
  306 + this._more = this._less = this.done = false;
  307 + this.yylineno = this.yyleng = 0;
  308 + this.yytext = this.matched = this.match = '';
  309 + this.conditionStack = ['INITIAL'];
  310 + this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0};
  311 + return this;
  312 + },
  313 +input:function () {
  314 + var ch = this._input[0];
  315 + this.yytext+=ch;
  316 + this.yyleng++;
  317 + this.match+=ch;
  318 + this.matched+=ch;
  319 + var lines = ch.match(/\n/);
  320 + if (lines) this.yylineno++;
  321 + this._input = this._input.slice(1);
  322 + return ch;
  323 + },
  324 +unput:function (ch) {
  325 + this._input = ch + this._input;
  326 + return this;
  327 + },
  328 +more:function () {
  329 + this._more = true;
  330 + return this;
  331 + },
  332 +pastInput:function () {
  333 + var past = this.matched.substr(0, this.matched.length - this.match.length);
  334 + return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
  335 + },
  336 +upcomingInput:function () {
  337 + var next = this.match;
  338 + if (next.length < 20) {
  339 + next += this._input.substr(0, 20-next.length);
  340 + }
  341 + return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, "");
  342 + },
  343 +showPosition:function () {
  344 + var pre = this.pastInput();
  345 + var c = new Array(pre.length + 1).join("-");
  346 + return pre + this.upcomingInput() + "\n" + c+"^";
  347 + },
  348 +next:function () {
  349 + if (this.done) {
  350 + return this.EOF;
  351 + }
  352 + if (!this._input) this.done = true;
  353 +
  354 + var token,
  355 + match,
  356 + col,
  357 + lines;
  358 + if (!this._more) {
  359 + this.yytext = '';
  360 + this.match = '';
  361 + }
  362 + var rules = this._currentRules();
  363 + for (var i=0;i < rules.length; i++) {
  364 + match = this._input.match(this.rules[rules[i]]);
  365 + if (match) {
  366 + lines = match[0].match(/\n.*/g);
  367 + if (lines) this.yylineno += lines.length;
  368 + this.yylloc = {first_line: this.yylloc.last_line,
  369 + last_line: this.yylineno+1,
  370 + first_column: this.yylloc.last_column,
  371 + last_column: lines ? lines[lines.length-1].length-1 : this.yylloc.last_column + match[0].length}
  372 + this.yytext += match[0];
  373 + this.match += match[0];
  374 + this.matches = match;
  375 + this.yyleng = this.yytext.length;
  376 + this._more = false;
  377 + this._input = this._input.slice(match[0].length);
  378 + this.matched += match[0];
  379 + token = this.performAction.call(this, this.yy, this, rules[i],this.conditionStack[this.conditionStack.length-1]);
  380 + if (token) return token;
  381 + else return;
  382 + }
  383 + }
  384 + if (this._input === "") {
  385 + return this.EOF;
  386 + } else {
  387 + this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(),
  388 + {text: "", token: null, line: this.yylineno});
  389 + }
  390 + },
  391 +lex:function lex() {
  392 + var r = this.next();
  393 + if (typeof r !== 'undefined') {
  394 + return r;
  395 + } else {
  396 + return this.lex();
  397 + }
  398 + },
  399 +begin:function begin(condition) {
  400 + this.conditionStack.push(condition);
  401 + },
  402 +popState:function popState() {
  403 + return this.conditionStack.pop();
  404 + },
  405 +_currentRules:function _currentRules() {
  406 + return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules;
  407 + },
  408 +topState:function () {
  409 + return this.conditionStack[this.conditionStack.length-2];
  410 + },
  411 +pushState:function begin(condition) {
  412 + this.begin(condition);
  413 + }});
  414 +lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
  415 +
  416 +var YYSTATE=YY_START
  417 +switch($avoiding_name_collisions) {
  418 +case 0:
  419 + if(yy_.yytext.slice(-1) !== "\\") this.begin("mu");
  420 + if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1), this.begin("emu");
  421 + if(yy_.yytext) return 14;
  422 +
  423 +break;
  424 +case 1: return 14;
  425 +break;
  426 +case 2: this.popState(); return 14;
  427 +break;
  428 +case 3: return 24;
  429 +break;
  430 +case 4: return 16;
  431 +break;
  432 +case 5: return 20;
  433 +break;
  434 +case 6: return 19;
  435 +break;
  436 +case 7: return 19;
  437 +break;
  438 +case 8: return 23;
  439 +break;
  440 +case 9: return 23;
  441 +break;
  442 +case 10: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return 15;
  443 +break;
  444 +case 11: return 22;
  445 +break;
  446 +case 12: return 34;
  447 +break;
  448 +case 13: return 33;
  449 +break;
  450 +case 14: return 33;
  451 +break;
  452 +case 15: return 36;
  453 +break;
  454 +case 16: /*ignore whitespace*/
  455 +break;
  456 +case 17: this.popState(); return 18;
  457 +break;
  458 +case 18: this.popState(); return 18;
  459 +break;
  460 +case 19: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 28;
  461 +break;
  462 +case 20: return 30;
  463 +break;
  464 +case 21: return 30;
  465 +break;
  466 +case 22: return 29;
  467 +break;
  468 +case 23: return 33;
  469 +break;
  470 +case 24: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 33;
  471 +break;
  472 +case 25: return 'INVALID';
  473 +break;
  474 +case 26: return 5;
  475 +break;
  476 +}
  477 +};
  478 +lexer.rules = [/^[^\x00]*?(?=(\{\{))/,/^[^\x00]+/,/^[^\x00]{2,}?(?=(\{\{))/,/^\{\{>/,/^\{\{#/,/^\{\{\//,/^\{\{\^/,/^\{\{\s*else\b/,/^\{\{\{/,/^\{\{&/,/^\{\{![\s\S]*?\}\}/,/^\{\{/,/^=/,/^\.(?=[} ])/,/^\.\./,/^[\/.]/,/^\s+/,/^\}\}\}/,/^\}\}/,/^"(\\["]|[^"])*"/,/^true(?=[}\s])/,/^false(?=[}\s])/,/^[0-9]+(?=[}\s])/,/^[a-zA-Z0-9_$-]+(?=[=}\s\/.])/,/^\[[^\]]*\]/,/^./,/^$/];
  479 +lexer.conditions = {"mu":{"rules":[3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"INITIAL":{"rules":[0,1,26],"inclusive":true}};return lexer;})()
  480 +parser.lexer = lexer;
  481 +return parser;
  482 +})();
  483 +if (typeof require !== 'undefined' && typeof exports !== 'undefined') {
  484 +exports.parser = handlebars;
  485 +exports.parse = function () { return handlebars.parse.apply(handlebars, arguments); }
  486 +exports.main = function commonjsMain(args) {
  487 + if (!args[1])
  488 + throw new Error('Usage: '+args[0]+' FILE');
  489 + if (typeof process !== 'undefined') {
  490 + var source = require('fs').readFileSync(require('path').join(process.cwd(), args[1]), "utf8");
  491 + } else {
  492 + var cwd = require("file").path(require("file").cwd());
  493 + var source = cwd.join(args[1]).read({charset: "utf-8"});
  494 + }
  495 + return exports.parser.parse(source);
  496 +}
  497 +if (typeof module !== 'undefined' && require.main === module) {
  498 + exports.main(typeof process !== 'undefined' ? process.argv.slice(1) : require("system").args);
  499 +}
  500 +};
  501 +;
  502 +// lib/handlebars/compiler/base.js
  503 +Handlebars.Parser = handlebars;
  504 +
  505 +Handlebars.parse = function(string) {
  506 + Handlebars.Parser.yy = Handlebars.AST;
  507 + return Handlebars.Parser.parse(string);
  508 +};
  509 +
  510 +Handlebars.print = function(ast) {
  511 + return new Handlebars.PrintVisitor().accept(ast);
  512 +};
  513 +
  514 +Handlebars.logger = {
  515 + DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3,
  516 +
  517 + // override in the host environment
  518 + log: function(level, str) {}
  519 +};
  520 +
  521 +Handlebars.log = function(level, str) { Handlebars.logger.log(level, str); };
  522 +;
  523 +// lib/handlebars/compiler/ast.js
  524 +(function() {
  525 +
  526 + Handlebars.AST = {};
  527 +
  528 + Handlebars.AST.ProgramNode = function(statements, inverse) {
  529 + this.type = "program";
  530 + this.statements = statements;
  531 + if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); }
  532 + };
  533 +
  534 + Handlebars.AST.MustacheNode = function(params, hash, unescaped) {
  535 + this.type = "mustache";
  536 + this.id = params[0];
  537 + this.params = params.slice(1);
  538 + this.hash = hash;
  539 + this.escaped = !unescaped;
  540 + };
  541 +
  542 + Handlebars.AST.PartialNode = function(id, context) {
  543 + this.type = "partial";
  544 +
  545 + // TODO: disallow complex IDs
  546 +
  547 + this.id = id;
  548 + this.context = context;
  549 + };
  550 +
  551 + var verifyMatch = function(open, close) {
  552 + if(open.original !== close.original) {
  553 + throw new Handlebars.Exception(open.original + " doesn't match " + close.original);
  554 + }
  555 + };
  556 +
  557 + Handlebars.AST.BlockNode = function(mustache, program, close) {
  558 + verifyMatch(mustache.id, close);
  559 + this.type = "block";
  560 + this.mustache = mustache;
  561 + this.program = program;
  562 + };
  563 +
  564 + Handlebars.AST.InverseNode = function(mustache, program, close) {
  565 + verifyMatch(mustache.id, close);
  566 + this.type = "inverse";
  567 + this.mustache = mustache;
  568 + this.program = program;
  569 + };
  570 +
  571 + Handlebars.AST.ContentNode = function(string) {
  572 + this.type = "content";
  573 + this.string = string;
  574 + };
  575 +
  576 + Handlebars.AST.HashNode = function(pairs) {
  577 + this.type = "hash";
  578 + this.pairs = pairs;
  579 + };
  580 +
  581 + Handlebars.AST.IdNode = function(parts) {
  582 + this.type = "ID";
  583 + this.original = parts.join(".");
  584 +
  585 + var dig = [], depth = 0;
  586 +
  587 + for(var i=0,l=parts.length; i<l; i++) {
  588 + var part = parts[i];
  589 +
  590 + if(part === "..") { depth++; }
  591 + else if(part === "." || part === "this") { this.isScoped = true; }
  592 + else { dig.push(part); }
  593 + }
  594 +
  595 + this.parts = dig;
  596 + this.string = dig.join('.');
  597 + this.depth = depth;
  598 + this.isSimple = (dig.length === 1) && (depth === 0);
  599 + };
  600 +
  601 + Handlebars.AST.StringNode = function(string) {
  602 + this.type = "STRING";
  603 + this.string = string;
  604 + };
  605 +
  606 + Handlebars.AST.IntegerNode = function(integer) {
  607 + this.type = "INTEGER";
  608 + this.integer = integer;
  609 + };
  610 +
  611 + Handlebars.AST.BooleanNode = function(bool) {
  612 + this.type = "BOOLEAN";
  613 + this.bool = bool;
  614 + };
  615 +
  616 + Handlebars.AST.CommentNode = function(comment) {
  617 + this.type = "comment";
  618 + this.comment = comment;
  619 + };
  620 +
  621 +})();;
  622 +// lib/handlebars/utils.js
  623 +Handlebars.Exception = function(message) {
  624 + var tmp = Error.prototype.constructor.apply(this, arguments);
  625 +
  626 + for (var p in tmp) {
  627 + if (tmp.hasOwnProperty(p)) { this[p] = tmp[p]; }
  628 + }
  629 +
  630 + this.message = tmp.message;
  631 +};
  632 +Handlebars.Exception.prototype = new Error;
  633 +
  634 +// Build out our basic SafeString type
  635 +Handlebars.SafeString = function(string) {
  636 + this.string = string;
  637 +};
  638 +Handlebars.SafeString.prototype.toString = function() {
  639 + return this.string.toString();
  640 +};
  641 +
  642 +(function() {
  643 + var escape = {
  644 + "<": "&lt;",
  645 + ">": "&gt;",
  646 + '"': "&quot;",
  647 + "'": "&#x27;",
  648 + "`": "&#x60;"
  649 + };
  650 +
  651 + var badChars = /&(?!\w+;)|[<>"'`]/g;
  652 + var possible = /[&<>"'`]/;
  653 +
  654 + var escapeChar = function(chr) {
  655 + return escape[chr] || "&amp;";
  656 + };
  657 +
  658 + Handlebars.Utils = {
  659 + escapeExpression: function(string) {
  660 + // don't escape SafeStrings, since they're already safe
  661 + if (string instanceof Handlebars.SafeString) {
  662 + return string.toString();
  663 + } else if (string == null || string === false) {
  664 + return "";
  665 + }
  666 +
  667 + if(!possible.test(string)) { return string; }
  668 + return string.replace(badChars, escapeChar);
  669 + },
  670 +
  671 + isEmpty: function(value) {
  672 + if (typeof value === "undefined") {
  673 + return true;
  674 + } else if (value === null) {
  675 + return true;
  676 + } else if (value === false) {
  677 + return true;
  678 + } else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) {
  679 + return true;
  680 + } else {
  681 + return false;
  682 + }
  683 + }
  684 + };
  685 +})();;
  686 +// lib/handlebars/compiler/compiler.js
  687 +Handlebars.Compiler = function() {};
  688 +Handlebars.JavaScriptCompiler = function() {};
  689 +
  690 +(function(Compiler, JavaScriptCompiler) {
  691 + Compiler.OPCODE_MAP = {
  692 + appendContent: 1,
  693 + getContext: 2,
  694 + lookupWithHelpers: 3,
  695 + lookup: 4,
  696 + append: 5,
  697 + invokeMustache: 6,
  698 + appendEscaped: 7,
  699 + pushString: 8,
  700 + truthyOrFallback: 9,
  701 + functionOrFallback: 10,
  702 + invokeProgram: 11,
  703 + invokePartial: 12,
  704 + push: 13,
  705 + assignToHash: 15,
  706 + pushStringParam: 16
  707 + };
  708 +
  709 + Compiler.MULTI_PARAM_OPCODES = {
  710 + appendContent: 1,
  711 + getContext: 1,
  712 + lookupWithHelpers: 2,
  713 + lookup: 1,
  714 + invokeMustache: 3,
  715 + pushString: 1,
  716 + truthyOrFallback: 1,
  717 + functionOrFallback: 1,
  718 + invokeProgram: 3,
  719 + invokePartial: 1,
  720 + push: 1,
  721 + assignToHash: 1,
  722 + pushStringParam: 1
  723 + };
  724 +
  725 + Compiler.DISASSEMBLE_MAP = {};
  726 +
  727 + for(var prop in Compiler.OPCODE_MAP) {
  728 + var value = Compiler.OPCODE_MAP[prop];
  729 + Compiler.DISASSEMBLE_MAP[value] = prop;
  730 + }
  731 +
  732 + Compiler.multiParamSize = function(code) {
  733 + return Compiler.MULTI_PARAM_OPCODES[Compiler.DISASSEMBLE_MAP[code]];
  734 + };
  735 +
  736 + Compiler.prototype = {
  737 + compiler: Compiler,
  738 +
  739 + disassemble: function() {
  740 + var opcodes = this.opcodes, opcode, nextCode;
  741 + var out = [], str, name, value;
  742 +
  743 + for(var i=0, l=opcodes.length; i<l; i++) {
  744 + opcode = opcodes[i];
  745 +
  746 + if(opcode === 'DECLARE') {
  747 + name = opcodes[++i];
  748 + value = opcodes[++i];
  749 + out.push("DECLARE " + name + " = " + value);
  750 + } else {
  751 + str = Compiler.DISASSEMBLE_MAP[opcode];
  752 +
  753 + var extraParams = Compiler.multiParamSize(opcode);
  754 + var codes = [];
  755 +
  756 + for(var j=0; j<extraParams; j++) {
  757 + nextCode = opcodes[++i];
  758 +
  759 + if(typeof nextCode === "string") {
  760 + nextCode = "\"" + nextCode.replace("\n", "\\n") + "\"";
  761 + }
  762 +
  763 + codes.push(nextCode);
  764 + }
  765 +
  766 + str = str + " " + codes.join(" ");
  767 +
  768 + out.push(str);
  769 + }
  770 + }
  771 +
  772 + return out.join("\n");
  773 + },
  774 +
  775 + guid: 0,
  776 +
  777 + compile: function(program, options) {
  778 + this.children = [];
  779 + this.depths = {list: []};
  780 + this.options = options;
  781 +
  782 + // These changes will propagate to the other compiler components
  783 + var knownHelpers = this.options.knownHelpers;
  784 + this.options.knownHelpers = {
  785 + 'helperMissing': true,
  786 + 'blockHelperMissing': true,
  787 + 'each': true,
  788 + 'if': true,
  789 + 'unless': true,
  790 + 'with': true,
  791 + 'log': true
  792 + };
  793 + if (knownHelpers) {
  794 + for (var name in knownHelpers) {
  795 + this.options.knownHelpers[name] = knownHelpers[name];
  796 + }
  797 + }
  798 +
  799 + return this.program(program);
  800 + },
  801 +
  802 + accept: function(node) {
  803 + return this[node.type](node);
  804 + },
  805 +
  806 + program: function(program) {
  807 + var statements = program.statements, statement;
  808 + this.opcodes = [];
  809 +
  810 + for(var i=0, l=statements.length; i<l; i++) {
  811 + statement = statements[i];
  812 + this[statement.type](statement);
  813 + }
  814 + this.isSimple = l === 1;
  815 +
  816 + this.depths.list = this.depths.list.sort(function(a, b) {
  817 + return a - b;
  818 + });
  819 +
  820 + return this;
  821 + },
  822 +
  823 + compileProgram: function(program) {
  824 + var result = new this.compiler().compile(program, this.options);
  825 + var guid = this.guid++;
  826 +
  827 + this.usePartial = this.usePartial || result.usePartial;
  828 +
  829 + this.children[guid] = result;
  830 +
  831 + for(var i=0, l=result.depths.list.length; i<l; i++) {
  832 + depth = result.depths.list[i];
  833 +
  834 + if(depth < 2) { continue; }
  835 + else { this.addDepth(depth - 1); }
  836 + }
  837 +
  838 + return guid;
  839 + },
  840 +
  841 + block: function(block) {
  842 + var mustache = block.mustache;
  843 + var depth, child, inverse, inverseGuid;
  844 +
  845 + var params = this.setupStackForMustache(mustache);
  846 +
  847 + var programGuid = this.compileProgram(block.program);
  848 +
  849 + if(block.program.inverse) {
  850 + inverseGuid = this.compileProgram(block.program.inverse);
  851 + this.declare('inverse', inverseGuid);
  852 + }
  853 +
  854 + this.opcode('invokeProgram', programGuid, params.length, !!mustache.hash);
  855 + this.declare('inverse', null);
  856 + this.opcode('append');
  857 + },
  858 +
  859 + inverse: function(block) {
  860 + var params = this.setupStackForMustache(block.mustache);
  861 +
  862 + var programGuid = this.compileProgram(block.program);
  863 +
  864 + this.declare('inverse', programGuid);
  865 +
  866 + this.opcode('invokeProgram', null, params.length, !!block.mustache.hash);
  867 + this.declare('inverse', null);
  868 + this.opcode('append');
  869 + },
  870 +
  871 + hash: function(hash) {
  872 + var pairs = hash.pairs, pair, val;
  873 +
  874 + this.opcode('push', '{}');
  875 +
  876 + for(var i=0, l=pairs.length; i<l; i++) {
  877 + pair = pairs[i];
  878 + val = pair[1];
  879 +
  880 + this.accept(val);
  881 + this.opcode('assignToHash', pair[0]);
  882 + }
  883 + },
  884 +
  885 + partial: function(partial) {
  886 + var id = partial.id;
  887 + this.usePartial = true;
  888 +
  889 + if(partial.context) {
  890 + this.ID(partial.context);
  891 + } else {
  892 + this.opcode('push', 'depth0');
  893 + }
  894 +
  895 + this.opcode('invokePartial', id.original);
  896 + this.opcode('append');
  897 + },
  898 +
  899 + content: function(content) {
  900 + this.opcode('appendContent', content.string);
  901 + },
  902 +
  903 + mustache: function(mustache) {
  904 + var params = this.setupStackForMustache(mustache);
  905 +
  906 + this.opcode('invokeMustache', params.length, mustache.id.original, !!mustache.hash);
  907 +
  908 + if(mustache.escaped && !this.options.noEscape) {
  909 + this.opcode('appendEscaped');
  910 + } else {
  911 + this.opcode('append');
  912 + }
  913 + },
  914 +
  915 + ID: function(id) {
  916 + this.addDepth(id.depth);
  917 +
  918 + this.opcode('getContext', id.depth);
  919 +
  920 + this.opcode('lookupWithHelpers', id.parts[0] || null, id.isScoped || false);
  921 +
  922 + for(var i=1, l=id.parts.length; i<l; i++) {
  923 + this.opcode('lookup', id.parts[i]);
  924 + }
  925 + },
  926 +
  927 + STRING: function(string) {
  928 + this.opcode('pushString', string.string);
  929 + },
  930 +
  931 + INTEGER: function(integer) {
  932 + this.opcode('push', integer.integer);
  933 + },
  934 +
  935 + BOOLEAN: function(bool) {
  936 + this.opcode('push', bool.bool);
  937 + },
  938 +
  939 + comment: function() {},
  940 +
  941 + // HELPERS
  942 + pushParams: function(params) {
  943 + var i = params.length, param;
  944 +
  945 + while(i--) {
  946 + param = params[i];
  947 +
  948 + if(this.options.stringParams) {
  949 + if(param.depth) {
  950 + this.addDepth(param.depth);
  951 + }
  952 +
  953 + this.opcode('getContext', param.depth || 0);
  954 + this.opcode('pushStringParam', param.string);
  955 + } else {
  956 + this[param.type](param);
  957 + }
  958 + }
  959 + },
  960 +
  961 + opcode: function(name, val1, val2, val3) {
  962 + this.opcodes.push(Compiler.OPCODE_MAP[name]);
  963 + if(val1 !== undefined) { this.opcodes.push(val1); }
  964 + if(val2 !== undefined) { this.opcodes.push(val2); }
  965 + if(val3 !== undefined) { this.opcodes.push(val3); }
  966 + },
  967 +
  968 + declare: function(name, value) {
  969 + this.opcodes.push('DECLARE');
  970 + this.opcodes.push(name);
  971 + this.opcodes.push(value);
  972 + },
  973 +
  974 + addDepth: function(depth) {
  975 + if(depth === 0) { return; }
  976 +
  977 + if(!this.depths[depth]) {
  978 + this.depths[depth] = true;
  979 + this.depths.list.push(depth);
  980 + }
  981 + },
  982 +
  983 + setupStackForMustache: function(mustache) {
  984 + var params = mustache.params;
  985 +
  986 + this.pushParams(params);
  987 +
  988 + if(mustache.hash) {
  989 + this.hash(mustache.hash);
  990 + }
  991 +
  992 + this.ID(mustache.id);
  993 +
  994 + return params;
  995 + }
  996 + };
  997 +
  998 + JavaScriptCompiler.prototype = {
  999 + // PUBLIC API: You can override these methods in a subclass to provide
  1000 + // alternative compiled forms for name lookup and buffering semantics
  1001 + nameLookup: function(parent, name, type) {
  1002 + if (/^[0-9]+$/.test(name)) {
  1003 + return parent + "[" + name + "]";
  1004 + } else if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
  1005 + return parent + "." + name;
  1006 + }
  1007 + else {
  1008 + return parent + "['" + name + "']";
  1009 + }
  1010 + },
  1011 +
  1012 + appendToBuffer: function(string) {
  1013 + if (this.environment.isSimple) {
  1014 + return "return " + string + ";";
  1015 + } else {
  1016 + return "buffer += " + string + ";";
  1017 + }
  1018 + },
  1019 +
  1020 + initializeBuffer: function() {
  1021 + return this.quotedString("");
  1022 + },
  1023 +
  1024 + namespace: "Handlebars",
  1025 + // END PUBLIC API
  1026 +
  1027 + compile: function(environment, options, context, asObject) {
  1028 + this.environment = environment;
  1029 + this.options = options || {};
  1030 +
  1031 + this.name = this.environment.name;
  1032 + this.isChild = !!context;
  1033 + this.context = context || {
  1034 + programs: [],
  1035 + aliases: { self: 'this' },
  1036 + registers: {list: []}
  1037 + };
  1038 +
  1039 + this.preamble();
  1040 +
  1041 + this.stackSlot = 0;
  1042 + this.stackVars = [];
  1043 +
  1044 + this.compileChildren(environment, options);
  1045 +
  1046 + var opcodes = environment.opcodes, opcode;
  1047 +
  1048 + this.i = 0;
  1049 +
  1050 + for(l=opcodes.length; this.i<l; this.i++) {
  1051 + opcode = this.nextOpcode(0);
  1052 +
  1053 + if(opcode[0] === 'DECLARE') {
  1054 + this.i = this.i + 2;
  1055 + this[opcode[1]] = opcode[2];
  1056 + } else {
  1057 + this.i = this.i + opcode[1].length;
  1058 + this[opcode[0]].apply(this, opcode[1]);
  1059 + }
  1060 + }
  1061 +
  1062 + return this.createFunctionContext(asObject);
  1063 + },
  1064 +
  1065 + nextOpcode: function(n) {
  1066 + var opcodes = this.environment.opcodes, opcode = opcodes[this.i + n], name, val;
  1067 + var extraParams, codes;
  1068 +
  1069 + if(opcode === 'DECLARE') {
  1070 + name = opcodes[this.i + 1];
  1071 + val = opcodes[this.i + 2];
  1072 + return ['DECLARE', name, val];
  1073 + } else {
  1074 + name = Compiler.DISASSEMBLE_MAP[opcode];
  1075 +
  1076 + extraParams = Compiler.multiParamSize(opcode);
  1077 + codes = [];
  1078 +
  1079 + for(var j=0; j<extraParams; j++) {
  1080 + codes.push(opcodes[this.i + j + 1 + n]);
  1081 + }
  1082 +
  1083 + return [name, codes];
  1084 + }
  1085 + },
  1086 +
  1087 + eat: function(opcode) {
  1088 + this.i = this.i + opcode.length;
  1089 + },
  1090 +
  1091 + preamble: function() {
  1092 + var out = [];
  1093 +
  1094 + // this register will disambiguate helper lookup from finding a function in
  1095 + // a context. This is necessary for mustache compatibility, which requires
  1096 + // that context functions in blocks are evaluated by blockHelperMissing, and
  1097 + // then proceed as if the resulting value was provided to blockHelperMissing.
  1098 + this.useRegister('foundHelper');
  1099 +
  1100 + if (!this.isChild) {
  1101 + var namespace = this.namespace;
  1102 + var copies = "helpers = helpers || " + namespace + ".helpers;";
  1103 + if(this.environment.usePartial) { copies = copies + " partials = partials || " + namespace + ".partials;"; }
  1104 + out.push(copies);
  1105 + } else {
  1106 + out.push('');
  1107 + }
  1108 +
  1109 + if (!this.environment.isSimple) {
  1110 + out.push(", buffer = " + this.initializeBuffer());
  1111 + } else {
  1112 + out.push("");
  1113 + }
  1114 +
  1115 + // track the last context pushed into place to allow skipping the
  1116 + // getContext opcode when it would be a noop
  1117 + this.lastContext = 0;
  1118 + this.source = out;
  1119 + },
  1120 +
  1121 + createFunctionContext: function(asObject) {
  1122 + var locals = this.stackVars;
  1123 + if (!this.isChild) {
  1124 + locals = locals.concat(this.context.registers.list);
  1125 + }
  1126 +
  1127 + if(locals.length > 0) {
  1128 + this.source[1] = this.source[1] + ", " + locals.join(", ");
  1129 + }
  1130 +
  1131 + // Generate minimizer alias mappings
  1132 + if (!this.isChild) {
  1133 + var aliases = []
  1134 + for (var alias in this.context.aliases) {
  1135 + this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias];
  1136 + }
  1137 + }
  1138 +
  1139 + if (this.source[1]) {
  1140 + this.source[1] = "var " + this.source[1].substring(2) + ";";
  1141 + }
  1142 +
  1143 + // Merge children
  1144 + if (!this.isChild) {
  1145 + this.source[1] += '\n' + this.context.programs.join('\n') + '\n';
  1146 + }
  1147 +
  1148 + if (!this.environment.isSimple) {
  1149 + this.source.push("return buffer;");
  1150 + }
  1151 +
  1152 + var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"];
  1153 +
  1154 + for(var i=0, l=this.environment.depths.list.length; i<l; i++) {
  1155 + params.push("depth" + this.environment.depths.list[i]);
  1156 + }
  1157 +
  1158 + if (asObject) {
  1159 + params.push(this.source.join("\n "));
  1160 +
  1161 + return Function.apply(this, params);
  1162 + } else {
  1163 + var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n ' + this.source.join("\n ") + '}';
  1164 + Handlebars.log(Handlebars.logger.DEBUG, functionSource + "\n\n");
  1165 + return functionSource;
  1166 + }
  1167 + },
  1168 +
  1169 + appendContent: function(content) {
  1170 + this.source.push(this.appendToBuffer(this.quotedString(content)));
  1171 + },
  1172 +
  1173 + append: function() {
  1174 + var local = this.popStack();
  1175 + this.source.push("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }");
  1176 + if (this.environment.isSimple) {
  1177 + this.source.push("else { " + this.appendToBuffer("''") + " }");
  1178 + }
  1179 + },
  1180 +
  1181 + appendEscaped: function() {
  1182 + var opcode = this.nextOpcode(1), extra = "";
  1183 + this.context.aliases.escapeExpression = 'this.escapeExpression';
  1184 +
  1185 + if(opcode[0] === 'appendContent') {
  1186 + extra = " + " + this.quotedString(opcode[1][0]);
  1187 + this.eat(opcode);
  1188 + }
  1189 +
  1190 + this.source.push(this.appendToBuffer("escapeExpression(" + this.popStack() + ")" + extra));
  1191 + },
  1192 +