diff --git a/Gemfile b/Gemfile index 66d0eef7..8b200243 100644 --- a/Gemfile +++ b/Gemfile @@ -24,7 +24,7 @@ gem 'coffee-script', '~> 2.2.0' gem 'uglifier', '~> 1.0.4' gem 'jquery-rails', '~> 1.0.16' gem 'rails-backbone', '0.5.4' -gem 'handlebars-rails', :git => 'git://github.com/yabawock/handlebars-rails.git' +# gem 'handlebars-rails', :git => 'git://github.com/yabawock/handlebars-rails.git' gem 'codemirror-rails' gem 'tinymce-rails' gem 'flash_cookie_session', '~> 1.1.1' @@ -47,8 +47,7 @@ gem 'rubyzip' gem 'actionmailer-with-request', '~> 0.3.0', :require => 'actionmailer_with_request' gem 'httparty', '~> 0.8.1' -gem 'delayed_job', '~> 3.0.0.pre4' -gem 'delayed_job_mongoid', '~> 1.0.7' +gem 'delayed_job_mongoid', '~> 1.0.8' gem 'SystemTimer', :platforms => :ruby_18 # The rest of the dependencies are for use when in the locomotive dev environment diff --git a/Gemfile.lock b/Gemfile.lock index 71d5cf9f..e36053b2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,27 +1,22 @@ GIT remote: git://github.com/chriseppstein/compass.git - revision: 79d94647f9a00b06c4a83db02e86f5d404569baf + revision: bffba483754cb9381fa51efd8e2a1f0482a21ac6 specs: - compass (0.12.alpha.1.79d9464) + compass (0.12.alpha.4.bffba48) chunky_png (~> 1.2) fssm (>= 0.2.7) sass (~> 3.1) GIT remote: git://github.com/plataformatec/devise.git - revision: ede004169c6af7416f8c4e3fc29a653bee133f60 + revision: a949f9eca24f9d43f516363fbf2f5d004623355e specs: - devise (1.5.1) + devise (2.0.0.rc) bcrypt-ruby (~> 3.0) orm_adapter (~> 0.0.3) + railties (~> 3.1) warden (~> 1.1) -GIT - remote: git://github.com/yabawock/handlebars-rails.git - revision: a09077aa91f10e08403af84586b2f2f0b38d9e2f - specs: - handlebars-rails (0.9.1) - GIT remote: git@github.com:locomotivecms/mongoid_acts_as_tree.git revision: ca494d22c3d7946385aba1153c017d9c30e9f9d3 @@ -39,7 +34,7 @@ PATH GEM remote: http://rubygems.org/ specs: - RedCloth (4.2.8) + RedCloth (4.2.9) SystemTimer (1.2.3) ZenTest (4.6.2) actionmailer (3.1.3) @@ -77,7 +72,7 @@ GEM autotest (4.4.6) ZenTest (>= 4.4.1) bcrypt-ruby (3.0.1) - bson (1.4.0) + bson (1.5.2) bson_ext (1.4.0) builder (3.0.0) bushido (0.0.35) @@ -98,44 +93,43 @@ GEM carrierwave-mongoid (0.1.3) carrierwave (>= 0.5.6) mongoid (~> 2.1) - cells (3.7.0) + cells (3.7.1) actionpack (~> 3.0) railties (~> 3.0) - childprocess (0.2.2) + childprocess (0.2.6) ffi (~> 1.0.6) chunky_png (1.2.5) - codemirror-rails (0.3.1) + codemirror-rails (0.3.2) + railties (~> 3.0) coffee-script (2.2.0) coffee-script-source execjs - coffee-script-source (1.1.3) - cucumber (1.1.3) + coffee-script-source (1.2.0) + cucumber (1.1.4) builder (>= 2.1.2) diff-lcs (>= 1.1.2) - gherkin (~> 2.6.7) + gherkin (~> 2.7.1) json (>= 1.4.6) term-ansicolor (>= 1.0.6) - cucumber-rails (1.2.0) - capybara (>= 1.1.1) - cucumber (>= 1.1.1) + cucumber-rails (1.2.1) + capybara (>= 1.1.2) + cucumber (>= 1.1.3) nokogiri (>= 1.5.0) - daemons (1.0.10) database_cleaner (0.7.0) - delayed_job (3.0.0.pre4) + delayed_job (3.0.0) activesupport (~> 3.0) - daemons (= 1.0.10) - delayed_job_mongoid (1.0.7) - delayed_job (~> 3.0.0.pre) + delayed_job_mongoid (1.0.8) + delayed_job (~> 3.0.0) mongoid (>= 2.0) diff-lcs (1.1.3) - dragonfly (0.9.8) + dragonfly (0.9.9) rack ejs (1.0.0) erubis (2.7.0) - excon (0.7.8) - execjs (1.2.9) + excon (0.7.12) + execjs (1.2.13) multi_json (~> 1.0) - factory_girl (2.3.1) + factory_girl (2.3.2) activesupport factory_girl_rails (1.4.0) factory_girl (~> 2.3.0) @@ -156,25 +150,26 @@ GEM formatador (0.2.1) formtastic (2.0.2) rails (~> 3.0) - fssm (0.2.7) - gherkin (2.6.8) + fssm (0.2.8.1) + gherkin (2.7.2) json (>= 1.4.6) growl-glue (1.0.7) - haml (3.1.3) - highline (1.6.8) + haml (3.1.4) + highline (1.6.9) hike (1.2.1) httparty (0.8.1) multi_json multi_xml i18n (0.6.0) - jquery-rails (1.0.18) + jquery-rails (1.0.19) railties (~> 3.0) thor (~> 0.14) - json (1.6.1) - json_pure (1.6.1) - kaminari (0.12.4) - rails (>= 3.0.0) - kgio (2.6.0) + json (1.6.4) + kaminari (0.13.0) + actionpack (>= 3.0.0) + activesupport (>= 3.0.0) + railties (>= 3.0.0) + kgio (2.7.2) launchy (2.0.5) addressable (~> 2.2.6) locomotive_liquid (2.2.2) @@ -185,13 +180,13 @@ GEM mime-types (1.17.2) mimetype-fu (0.1.2) mocha (0.9.12) - mongo (1.4.0) - bson (= 1.4.0) + mongo (1.5.2) + bson (= 1.5.2) mongoid (2.3.4) activemodel (~> 3.1) mongo (~> 1.3) tzinfo (~> 0.3.22) - multi_json (1.0.3) + multi_json (1.0.4) multi_xml (0.4.1) net-scp (1.0.4) net-ssh (>= 1.99.1) @@ -202,7 +197,7 @@ GEM cucumber (>= 0.8) rake polyglot (0.3.3) - rack (1.3.5) + rack (1.3.6) rack-cache (1.1) rack (>= 0.4) rack-mount (0.8.3) @@ -232,7 +227,7 @@ GEM thor (~> 0.14.6) raindrops (0.8.0) rake (0.9.2) - rdoc (3.11) + rdoc (3.12) json (~> 1.4) responders (0.6.4) rest-client (1.6.7) @@ -242,7 +237,7 @@ GEM rspec-core (~> 2.6.0) rspec-expectations (~> 2.6.0) rspec-mocks (~> 2.6.0) - rspec-cells (0.1.0) + rspec-cells (0.1.1) rails (~> 3.0) rspec-rails (~> 2.2) rspec-core (2.6.4) @@ -255,19 +250,19 @@ GEM railties (~> 3.0) rspec (~> 2.6.0) ruby-hmac (0.4.0) - rubyzip (0.9.4) + rubyzip (0.9.5) sanitize (2.0.3) nokogiri (>= 1.4.4, < 1.6) - sass (3.1.10) + sass (3.1.12) sass-rails (3.1.5) actionpack (~> 3.1.0) railties (~> 3.1.0) sass (~> 3.1.10) tilt (~> 1.3.2) - selenium-webdriver (2.13.0) - childprocess (>= 0.2.1) + selenium-webdriver (2.16.0) + childprocess (>= 0.2.5) ffi (~> 1.0.9) - json_pure + multi_json (~> 1.0.4) rubyzip sprockets (2.0.3) hike (~> 1.2) @@ -315,8 +310,7 @@ DEPENDENCIES cucumber-rails custom_fields! database_cleaner - delayed_job (~> 3.0.0.pre4) - delayed_job_mongoid (~> 1.0.7) + delayed_job_mongoid (~> 1.0.8) devise! dragonfly (~> 0.9.8) factory_girl_rails (~> 1.1) @@ -325,7 +319,6 @@ DEPENDENCIES formtastic (~> 2.0.2) growl-glue haml (~> 3.1.3) - handlebars-rails! highline (~> 1.6.2) httparty (~> 0.8.1) jquery-rails (~> 1.0.16) diff --git a/app/assets/javascripts/locomotive.js b/app/assets/javascripts/locomotive.js index 177c602a..80a27763 100644 --- a/app/assets/javascripts/locomotive.js +++ b/app/assets/javascripts/locomotive.js @@ -8,7 +8,6 @@ //= require jquery-ui //= require jquery_ujs //= require underscore -//= require handlebars //= require backbone //= require codemirror //= require tinymce-jquery diff --git a/app/helpers/locomotive/base_helper.rb b/app/helpers/locomotive/base_helper.rb index d3deec9d..3b765e4e 100644 --- a/app/helpers/locomotive/base_helper.rb +++ b/app/helpers/locomotive/base_helper.rb @@ -36,14 +36,6 @@ module Locomotive::BaseHelper end end - # def collection_to_js(collection, options = {}) # FIXME: DEPRECATED - # js = collection.collect { |object| object.to_json } - # - # options_to_js = ActiveSupport::JSON.encode(options).gsub(/^\{/, '').gsub(/\}$/, '') - # - # "new Object({ \"collection\": [#{js.join(', ')}], #{options_to_js} })" - # end - def flash_message if not flash.empty? first_key = flash.keys.first diff --git a/app/helpers/locomotive/content_types_helper.rb b/app/helpers/locomotive/content_types_helper.rb index 5c23e9b1..5817b566 100644 --- a/app/helpers/locomotive/content_types_helper.rb +++ b/app/helpers/locomotive/content_types_helper.rb @@ -1,23 +1,50 @@ module Locomotive::ContentTypesHelper - def each_content_type_menu_item(&block) - current_site.content_types.ordered.only(:site_id, :name, :slug, :label_field_name).each do |content_type| - next unless content_type.persisted? + # Iterates over the content types with the following rules + # - content types are ordered by the updated_at date (DESC) + # - each content type has its own submenu if saved recently + # - if there are more than ui.max_content_types content types, the extra ones go under "..." + # - if a content type is selected and it is part of the extra content types, then + # it will be moved to the first position in the displayed list (with its own submenu) + # + # @param [ Block ] block The statements responsible to display the menu item from a content type or a list of content types + # + def each_content_type(&block) + visible, others = [], [] - item_on = (content_type.slug == @content_type.slug) rescue nil + current_site.content_types.ordered.only(:site_id, :name, :slug, :label_field_name).each_with_index do |content_type, index| + next if !content_type.persisted? - label = truncate(content_type.name, :length => 15) - url = content_entries_url(content_type.slug) - css = @content_type && content_type.slug == @content_type.slug ? 'on' : '' - - html = submenu_entry(label, url, :i18n => false, :css => css) do - yield(content_type) + if index >= Locomotive.config.ui.max_content_types + if self.is_content_type_selected(content_type) + others << visible.delete_at(Locomotive.config.ui.max_content_types - 1) # swap content types + visible.insert(0, content_type) + else + others << content_type # fills the "..." menu + end + next end - haml_concat(html) + visible << content_type end + + visible.map { |c| yield(c) } + yield(others) end + def is_content_type_selected(content_type) + @content_type && content_type.slug == @content_type.slug + end + + # Renders the label of a content type entry. If no raw_item_template filled in the content type, + # it just calls the _label method of the entry (based on the label_field_id). Otherwise, it + # parses and renders the liquid template. + # + # @param [ ContentType ] content_type The content type for better performance + # @param [ ContentEntry] entry The entry we want to display the label + # + # @return [ String ] The label of the content type entry + # def entry_label(content_type, entry) if content_type.raw_item_template.blank? entry._label # default one @@ -34,90 +61,4 @@ module Locomotive::ContentTypesHelper end end - # MAX_DISPLAYED_CONTENTS = 4 - # - # def fetch_content_types - # return @content_types if @content_types - # - # @content_types = current_site.content_types.ordered. - # limit(:contents => Locomotive.config.lastest_items_nb). - # only(:site_id, :name, :slug, :highlighted_field_name, :contents_custom_fields_version, :order_by, :serialized_item_template, :raw_item_template).to_a - # - # if @content_type && @content_type.persisted? && @content_types.index(@content_type) >= MAX_DISPLAYED_CONTENTS - # @content_types.delete(@content_type) - # @content_types.insert(0, @content_type) - # end - # - # # be sure, we've got the custom klass up-to-date, otherwise it will fail miserably - # @content_types.each do |content_type| - # if content_type.content_klass_out_of_date? - # content_type.reload - # content_type.invalidate_content_klass - # end - # end - # - # @content_types - # end - # - # def each_content_type_menu_item(which = :first, &block) - # types = fetch_content_types - # sliced = [] - # - # if which == :first - # sliced = types[0..MAX_DISPLAYED_CONTENTS - 1] - # elsif types.size > MAX_DISPLAYED_CONTENTS - # sliced = types[MAX_DISPLAYED_CONTENTS, types.size - MAX_DISPLAYED_CONTENTS] - # end - # - # return [] if sliced.empty? - # - # sliced.each do |content_type| - # next if content_type.new_record? - # item_on = (content_type.slug == @content_type.slug) rescue nil - # - # label = truncate(content_type.name, :length => 15) - # url = contents_url(content_type.slug) - # css = @content_type && content_type.slug == @content_type.slug ? 'on' : '' - # - # html = submenu_entry(label, url, :i18n => false, :css => css) do - # yield(content_type) - # end - # - # haml_concat(html) - # end - # end - # - # def other_content_types(&block) - # types = fetch_content_types - # - # if types.size > MAX_DISPLAYED_CONTENTS - # sliced = types[MAX_DISPLAYED_CONTENTS, types.size - MAX_DISPLAYED_CONTENTS] - # - # html = submenu_entry('...', '#', :i18n => false) do - # yield(sliced) - # end - # - # haml_concat(html) - # end - # end - # - # def content_label_for(content) - # if content._parent.raw_item_template.blank? - # content._label # default one - # else - # assigns = { - # 'site' => current_site, - # 'content' => content.to_liquid - # } - # - # registers = { - # :controller => self, - # :site => current_site, - # :current_locomotive_account => current_locomotive_account - # } - # - # preserve(content._parent.item_template.render(::Liquid::Context.new({}, assigns, registers))) - # end - # end - end diff --git a/app/models/locomotive/content_entry.rb b/app/models/locomotive/content_entry.rb index 82a08801..c5f710d3 100644 --- a/app/models/locomotive/content_entry.rb +++ b/app/models/locomotive/content_entry.rb @@ -27,7 +27,7 @@ module Locomotive ## named scopes ## scope :visible, :where => { :_visible => true } - scope :latest_updated, :order_by => :updated_at.desc, :limit => Locomotive.config.lastest_entries_nb + scope :latest_updated, :order_by => :updated_at.desc, :limit => Locomotive.config.ui.lastest_entries_nb ## methods ## diff --git a/app/models/locomotive/page.rb b/app/models/locomotive/page.rb index ed935878..7717863f 100644 --- a/app/models/locomotive/page.rb +++ b/app/models/locomotive/page.rb @@ -41,7 +41,7 @@ module Locomotive validates_exclusion_of :slug, :in => Locomotive.config.reserved_slugs, :if => Proc.new { |p| p.depth == 0 } ## named scopes ## - scope :latest_updated, :order_by => [[:updated_at, :desc]], :limit => Locomotive.config.lastest_entries_nb + scope :latest_updated, :order_by => [[:updated_at, :desc]], :limit => Locomotive.config.ui.lastest_entries_nb scope :root, :where => { :slug => 'index', :depth => 0 } scope :not_found, :where => { :slug => '404', :depth => 0 } scope :published, :where => { :published => true } diff --git a/app/views/locomotive/shared/menu/_contents.html.haml b/app/views/locomotive/shared/menu/_contents.html.haml index be496066..cee89362 100644 --- a/app/views/locomotive/shared/menu/_contents.html.haml +++ b/app/views/locomotive/shared/menu/_contents.html.haml @@ -11,25 +11,30 @@ = link_to truncate(page.title, :length => 25), edit_page_url(page) %span= time_ago_in_words(page.updated_at) -- each_content_type_menu_item do |content_type| - .wrapper - .header - %p= link_to t('locomotive.content_entries.index.new'), new_content_entry_url(content_type.slug) +- each_content_type do |menu_item| + -if menu_item.is_a?(Array) + - content_types = menu_item + = submenu_entry '...', '#', :i18n => false do + .wrapper + .inner + %ul.big-links + - content_types.each do |content_type| + %li + = link_to truncate(content_type.name, :length => 20), content_entries_url(content_type.slug) + - else + - content_type = menu_item + = submenu_entry truncate(content_type.name, :length => 15), content_entries_url(content_type.slug), :i18n => false, :css => "#{'on' if is_content_type_selected(content_type)}" do + .wrapper + .header + %p= link_to t('locomotive.content_entries.index.new'), new_content_entry_url(content_type.slug) - - if can? :manage, content_type - %p.edit= link_to t('locomotive.content_entries.index.edit'), edit_content_type_url(content_type) + - if can? :manage, content_type + %p.edit= link_to t('locomotive.content_types.index.edit'), edit_content_type_url(content_type) - .inner - %h2!= t('locomotive.content_entries.index.lastest_entries') - %ul - - content_type.entries.latest_updated.each do |entry| - %li - = link_to truncate(entry._label(content_type), :length => 20), edit_content_entry_url(content_type.slug, entry) - %span= time_ago_in_words(entry.updated_at) - -/ - other_content_types do |content_types| -/ .inner -/ %ul.big-links -/ - content_types.each do |content_type| -/ %li -/ = link_to truncate(content_type.name, :length => 20), contents_url(content_type.slug) \ No newline at end of file + .inner + %h2!= t('locomotive.content_entries.index.lastest_entries') + %ul + - content_type.entries.latest_updated.each do |entry| + %li + = link_to truncate(entry._label(content_type), :length => 20), edit_content_entry_url(content_type.slug, entry) + %span= time_ago_in_words(entry.updated_at) \ No newline at end of file diff --git a/doc/TODO b/doc/TODO index 57a38894..754e9c0b 100644 --- a/doc/TODO +++ b/doc/TODO @@ -49,7 +49,7 @@ x edit my site x show / hide options of a field based on its type x select: add/edit/remove options x text: formatting - - change in main menu + x change in main menu x manage contents x list (highlighted field) x slugify diff --git a/lib/generators/locomotive/install/templates/locomotive.rb b/lib/generators/locomotive/install/templates/locomotive.rb index 0a265832..c443d8a9 100644 --- a/lib/generators/locomotive/install/templates/locomotive.rb +++ b/lib/generators/locomotive/install/templates/locomotive.rb @@ -44,7 +44,10 @@ Locomotive.configure do |config| config.delayed_job = false # configure how many items we display in sub menu in the "Contents" section. - # config.lastest_entries_nb = 5 + # config.ui = { + # :lastest_entries_nb => 5, + # :max_content_types => 1 + # } # default locale (for now, only en, de, fr, pt-BR and it are supported) config.default_locale = :en diff --git a/lib/locomotive/configuration.rb b/lib/locomotive/configuration.rb index e2e30036..f9614c20 100644 --- a/lib/locomotive/configuration.rb +++ b/lib/locomotive/configuration.rb @@ -16,7 +16,10 @@ module Locomotive :mailer_sender => 'support@example.com', :manage_subdomain => false, :manage_manage_domains => false, - :lastest_entries_nb => 5, + :ui => { + :lastest_entries_nb => 5, + :max_content_types => 1 # TODO 4 + }, :rack_cache => { :verbose => true, :metastore => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/meta"), # URI encoded in case of spaces diff --git a/spec/dummy/config/initializers/locomotive.rb b/spec/dummy/config/initializers/locomotive.rb index c735b76a..12b82e07 100644 --- a/spec/dummy/config/initializers/locomotive.rb +++ b/spec/dummy/config/initializers/locomotive.rb @@ -45,7 +45,10 @@ Locomotive.configure do |config| config.delayed_job = true # false # configure how many items we display in sub menu in the "Contents" section. - # config.lastest_entries_nb = 5 + # config.ui = { + # :lastest_entries_nb => 5, + # :max_content_types => 1 + # } # default locale (for now, only en, de, fr, pt-BR and it are supported) config.default_locale = :en diff --git a/vendor/assets/javascripts/handlebars.js b/vendor/assets/javascripts/handlebars.js new file mode 100644 index 00000000..592a0866 --- /dev/null +++ b/vendor/assets/javascripts/handlebars.js @@ -0,0 +1,1493 @@ +// lib/handlebars/parser.js +/* Jison generated parser */ +var handlebars = (function(){ +var parser = {trace: function trace() { }, +yy: {}, +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}, +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"}, +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]], +performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) { + +var $0 = $$.length - 1; +switch (yystate) { +case 1: return $$[$0-1] +break; +case 2: this.$ = new yy.ProgramNode($$[$0-2], $$[$0]) +break; +case 3: this.$ = new yy.ProgramNode($$[$0]) +break; +case 4: this.$ = new yy.ProgramNode([]) +break; +case 5: this.$ = [$$[$0]] +break; +case 6: $$[$0-1].push($$[$0]); this.$ = $$[$0-1] +break; +case 7: this.$ = new yy.InverseNode($$[$0-2], $$[$0-1], $$[$0]) +break; +case 8: this.$ = new yy.BlockNode($$[$0-2], $$[$0-1], $$[$0]) +break; +case 9: this.$ = $$[$0] +break; +case 10: this.$ = $$[$0] +break; +case 11: this.$ = new yy.ContentNode($$[$0]) +break; +case 12: this.$ = new yy.CommentNode($$[$0]) +break; +case 13: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1]) +break; +case 14: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1]) +break; +case 15: this.$ = $$[$0-1] +break; +case 16: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1]) +break; +case 17: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1], true) +break; +case 18: this.$ = new yy.PartialNode($$[$0-1]) +break; +case 19: this.$ = new yy.PartialNode($$[$0-2], $$[$0-1]) +break; +case 20: +break; +case 21: this.$ = [[$$[$0-2]].concat($$[$0-1]), $$[$0]] +break; +case 22: this.$ = [[$$[$0-1]].concat($$[$0]), null] +break; +case 23: this.$ = [[$$[$0-1]], $$[$0]] +break; +case 24: this.$ = [[$$[$0]], null] +break; +case 25: $$[$0-1].push($$[$0]); this.$ = $$[$0-1]; +break; +case 26: this.$ = [$$[$0]] +break; +case 27: this.$ = $$[$0] +break; +case 28: this.$ = new yy.StringNode($$[$0]) +break; +case 29: this.$ = new yy.IntegerNode($$[$0]) +break; +case 30: this.$ = new yy.BooleanNode($$[$0]) +break; +case 31: this.$ = new yy.HashNode($$[$0]) +break; +case 32: $$[$0-1].push($$[$0]); this.$ = $$[$0-1] +break; +case 33: this.$ = [$$[$0]] +break; +case 34: this.$ = [$$[$0-2], $$[$0]] +break; +case 35: this.$ = [$$[$0-2], new yy.StringNode($$[$0])] +break; +case 36: this.$ = [$$[$0-2], new yy.IntegerNode($$[$0])] +break; +case 37: this.$ = [$$[$0-2], new yy.BooleanNode($$[$0])] +break; +case 38: this.$ = new yy.IdNode($$[$0]) +break; +case 39: $$[$0-2].push($$[$0]); this.$ = $$[$0-2]; +break; +case 40: this.$ = [$$[$0]] +break; +} +}, +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]}], +defaultActions: {16:[2,1],37:[2,23],53:[2,21]}, +parseError: function parseError(str, hash) { + throw new Error(str); +}, +parse: function parse(input) { + var self = this, + stack = [0], + vstack = [null], // semantic value stack + lstack = [], // location stack + table = this.table, + yytext = '', + yylineno = 0, + yyleng = 0, + recovering = 0, + TERROR = 2, + EOF = 1; + + //this.reductionCount = this.shiftCount = 0; + + this.lexer.setInput(input); + this.lexer.yy = this.yy; + this.yy.lexer = this.lexer; + if (typeof this.lexer.yylloc == 'undefined') + this.lexer.yylloc = {}; + var yyloc = this.lexer.yylloc; + lstack.push(yyloc); + + if (typeof this.yy.parseError === 'function') + this.parseError = this.yy.parseError; + + function popStack (n) { + stack.length = stack.length - 2*n; + vstack.length = vstack.length - n; + lstack.length = lstack.length - n; + } + + function lex() { + var token; + token = self.lexer.lex() || 1; // $end = 1 + // if token isn't its numeric value, convert + if (typeof token !== 'number') { + token = self.symbols_[token] || token; + } + return token; + }; + + var symbol, preErrorSymbol, state, action, a, r, yyval={},p,len,newState, expected; + while (true) { + // retreive state number from top of stack + state = stack[stack.length-1]; + + // use default actions if available + if (this.defaultActions[state]) { + action = this.defaultActions[state]; + } else { + if (symbol == null) + symbol = lex(); + // read action for current state and first input + action = table[state] && table[state][symbol]; + } + + // handle parse error + if (typeof action === 'undefined' || !action.length || !action[0]) { + + if (!recovering) { + // Report error + expected = []; + for (p in table[state]) if (this.terminals_[p] && p > 2) { + expected.push("'"+this.terminals_[p]+"'"); + } + var errStr = ''; + if (this.lexer.showPosition) { + errStr = 'Parse error on line '+(yylineno+1)+":\n"+this.lexer.showPosition()+'\nExpecting '+expected.join(', '); + } else { + errStr = 'Parse error on line '+(yylineno+1)+": Unexpected " + + (symbol == 1 /*EOF*/ ? "end of input" : + ("'"+(this.terminals_[symbol] || symbol)+"'")); + } + this.parseError(errStr, + {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected}); + } + + // just recovered from another error + if (recovering == 3) { + if (symbol == EOF) { + throw new Error(errStr || 'Parsing halted.'); + } + + // discard current lookahead and grab another + yyleng = this.lexer.yyleng; + yytext = this.lexer.yytext; + yylineno = this.lexer.yylineno; + yyloc = this.lexer.yylloc; + symbol = lex(); + } + + // try to recover from error + while (1) { + // check for error recovery rule in this state + if ((TERROR.toString()) in table[state]) { + break; + } + if (state == 0) { + throw new Error(errStr || 'Parsing halted.'); + } + popStack(1); + state = stack[stack.length-1]; + } + + preErrorSymbol = symbol; // save the lookahead token + symbol = TERROR; // insert generic error symbol as new lookahead + state = stack[stack.length-1]; + action = table[state] && table[state][TERROR]; + recovering = 3; // allow 3 real symbols to be shifted before reporting a new error + } + + // this shouldn't happen, unless resolve defaults are off + if (action[0] instanceof Array && action.length > 1) { + throw new Error('Parse Error: multiple actions possible at state: '+state+', token: '+symbol); + } + + switch (action[0]) { + + case 1: // shift + //this.shiftCount++; + + stack.push(symbol); + vstack.push(this.lexer.yytext); + lstack.push(this.lexer.yylloc); + stack.push(action[1]); // push state + symbol = null; + if (!preErrorSymbol) { // normal execution/no error + yyleng = this.lexer.yyleng; + yytext = this.lexer.yytext; + yylineno = this.lexer.yylineno; + yyloc = this.lexer.yylloc; + if (recovering > 0) + recovering--; + } else { // error just occurred, resume old lookahead f/ before error + symbol = preErrorSymbol; + preErrorSymbol = null; + } + break; + + case 2: // reduce + //this.reductionCount++; + + len = this.productions_[action[1]][1]; + + // perform semantic action + yyval.$ = vstack[vstack.length-len]; // default to $$ = $1 + // default location, uses first token for firsts, last for lasts + 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 + }; + r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack); + + if (typeof r !== 'undefined') { + return r; + } + + // pop off stack + if (len) { + stack = stack.slice(0,-1*len*2); + vstack = vstack.slice(0, -1*len); + lstack = lstack.slice(0, -1*len); + } + + stack.push(this.productions_[action[1]][0]); // push nonterminal (reduce) + vstack.push(yyval.$); + lstack.push(yyval._$); + // goto new state = table[STATE][NONTERMINAL] + newState = table[stack[stack.length-2]][stack[stack.length-1]]; + stack.push(newState); + break; + + case 3: // accept + return true; + } + + } + + return true; +}};/* Jison generated lexer */ +var lexer = (function(){var lexer = ({EOF:1, +parseError:function parseError(str, hash) { + if (this.yy.parseError) { + this.yy.parseError(str, hash); + } else { + throw new Error(str); + } + }, +setInput:function (input) { + this._input = input; + this._more = this._less = this.done = false; + this.yylineno = this.yyleng = 0; + this.yytext = this.matched = this.match = ''; + this.conditionStack = ['INITIAL']; + this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0}; + return this; + }, +input:function () { + var ch = this._input[0]; + this.yytext+=ch; + this.yyleng++; + this.match+=ch; + this.matched+=ch; + var lines = ch.match(/\n/); + if (lines) this.yylineno++; + this._input = this._input.slice(1); + return ch; + }, +unput:function (ch) { + this._input = ch + this._input; + return this; + }, +more:function () { + this._more = true; + return this; + }, +pastInput:function () { + var past = this.matched.substr(0, this.matched.length - this.match.length); + return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); + }, +upcomingInput:function () { + var next = this.match; + if (next.length < 20) { + next += this._input.substr(0, 20-next.length); + } + return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, ""); + }, +showPosition:function () { + var pre = this.pastInput(); + var c = new Array(pre.length + 1).join("-"); + return pre + this.upcomingInput() + "\n" + c+"^"; + }, +next:function () { + if (this.done) { + return this.EOF; + } + if (!this._input) this.done = true; + + var token, + match, + col, + lines; + if (!this._more) { + this.yytext = ''; + this.match = ''; + } + var rules = this._currentRules(); + for (var i=0;i < rules.length; i++) { + match = this._input.match(this.rules[rules[i]]); + if (match) { + lines = match[0].match(/\n.*/g); + if (lines) this.yylineno += lines.length; + this.yylloc = {first_line: this.yylloc.last_line, + last_line: this.yylineno+1, + first_column: this.yylloc.last_column, + last_column: lines ? lines[lines.length-1].length-1 : this.yylloc.last_column + match[0].length} + this.yytext += match[0]; + this.match += match[0]; + this.matches = match; + this.yyleng = this.yytext.length; + this._more = false; + this._input = this._input.slice(match[0].length); + this.matched += match[0]; + token = this.performAction.call(this, this.yy, this, rules[i],this.conditionStack[this.conditionStack.length-1]); + if (token) return token; + else return; + } + } + if (this._input === "") { + return this.EOF; + } else { + this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(), + {text: "", token: null, line: this.yylineno}); + } + }, +lex:function lex() { + var r = this.next(); + if (typeof r !== 'undefined') { + return r; + } else { + return this.lex(); + } + }, +begin:function begin(condition) { + this.conditionStack.push(condition); + }, +popState:function popState() { + return this.conditionStack.pop(); + }, +_currentRules:function _currentRules() { + return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules; + }}); +lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { + +var YYSTATE=YY_START +switch($avoiding_name_collisions) { +case 0: this.begin("mu"); if (yy_.yytext) return 14; +break; +case 1: return 14; +break; +case 2: return 24; +break; +case 3: return 16; +break; +case 4: return 20; +break; +case 5: return 19; +break; +case 6: return 19; +break; +case 7: return 23; +break; +case 8: return 23; +break; +case 9: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.begin("INITIAL"); return 15; +break; +case 10: return 22; +break; +case 11: return 34; +break; +case 12: return 33; +break; +case 13: return 33; +break; +case 14: return 36; +break; +case 15: /*ignore whitespace*/ +break; +case 16: this.begin("INITIAL"); return 18; +break; +case 17: this.begin("INITIAL"); return 18; +break; +case 18: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 28; +break; +case 19: return 30; +break; +case 20: return 30; +break; +case 21: return 29; +break; +case 22: return 33; +break; +case 23: return 'INVALID'; +break; +case 24: return 5; +break; +} +}; +lexer.rules = [/^[^\x00]*?(?=(\{\{))/,/^[^\x00]+/,/^\{\{>/,/^\{\{#/,/^\{\{\//,/^\{\{\^/,/^\{\{\s*else\b/,/^\{\{\{/,/^\{\{&/,/^\{\{![\s\S]*?\}\}/,/^\{\{/,/^=/,/^\.(?=[} ])/,/^\.\./,/^[/.]/,/^\s+/,/^\}\}\}/,/^\}\}/,/^"(\\["]|[^"])*"/,/^true(?=[}\s])/,/^false(?=[}\s])/,/^[0-9]+(?=[}\s])/,/^[a-zA-Z0-9_$-]+(?=[=}\s/.])/,/^./,/^$/]; +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;})() +parser.lexer = lexer; +return parser; +})(); +if (typeof require !== 'undefined' && typeof exports !== 'undefined') { +exports.parser = handlebars; +exports.parse = function () { return handlebars.parse.apply(handlebars, arguments); } +exports.main = function commonjsMain(args) { + if (!args[1]) + throw new Error('Usage: '+args[0]+' FILE'); + if (typeof process !== 'undefined') { + var source = require('fs').readFileSync(require('path').join(process.cwd(), args[1]), "utf8"); + } else { + var cwd = require("file").path(require("file").cwd()); + var source = cwd.join(args[1]).read({charset: "utf-8"}); + } + return exports.parser.parse(source); +} +if (typeof module !== 'undefined' && require.main === module) { + exports.main(typeof process !== 'undefined' ? process.argv.slice(1) : require("system").args); +} +}; +; +// lib/handlebars/base.js +var Handlebars = {}; + +Handlebars.VERSION = "1.0.beta.2"; + +Handlebars.Parser = handlebars; + +Handlebars.parse = function(string) { + Handlebars.Parser.yy = Handlebars.AST; + return Handlebars.Parser.parse(string); +}; + +Handlebars.print = function(ast) { + return new Handlebars.PrintVisitor().accept(ast); +}; + +Handlebars.helpers = {}; +Handlebars.partials = {}; + +Handlebars.registerHelper = function(name, fn, inverse) { + if(inverse) { fn.not = inverse; } + this.helpers[name] = fn; +}; + +Handlebars.registerPartial = function(name, str) { + this.partials[name] = str; +}; + +Handlebars.registerHelper('helperMissing', function(arg) { + if(arguments.length === 2) { + return undefined; + } else { + throw new Error("Could not find property '" + arg + "'"); + } +}); + +Handlebars.registerHelper('blockHelperMissing', function(context, fn, inverse) { + inverse = inverse || function() {}; + + var ret = ""; + var type = Object.prototype.toString.call(context); + + if(type === "[object Function]") { + context = context(); + } + + if(context === true) { + return fn(this); + } else if(context === false || context == null) { + return inverse(this); + } else if(type === "[object Array]") { + if(context.length > 0) { + for(var i=0, j=context.length; i 0) { + for(var i=0, j=context.length; i": ">", + '"': """, + "'": "'", + "`": "`" + }; + + var badChars = /&(?!\w+;)|[<>"'`]/g; + var possible = /[&<>"'`]/; + + var escapeChar = function(chr) { + return escape[chr] || "&" + }; + + Handlebars.Utils = { + escapeExpression: function(string) { + // don't escape SafeStrings, since they're already safe + if (string instanceof Handlebars.SafeString) { + return string.toString(); + } else if (string == null || string === false) { + return ""; + } + + if(!possible.test(string)) { return string; } + return string.replace(badChars, escapeChar); + }, + + isEmpty: function(value) { + if (typeof value === "undefined") { + return true; + } else if (value === null) { + return true; + } else if (value === false) { + return true; + } else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) { + return true; + } else { + return false; + } + } + }; +})();; +// lib/handlebars/compiler.js +Handlebars.Compiler = function() {}; +Handlebars.JavaScriptCompiler = function() {}; + +(function(Compiler, JavaScriptCompiler) { + Compiler.OPCODE_MAP = { + appendContent: 1, + getContext: 2, + lookupWithHelpers: 3, + lookup: 4, + append: 5, + invokeMustache: 6, + appendEscaped: 7, + pushString: 8, + truthyOrFallback: 9, + functionOrFallback: 10, + invokeProgram: 11, + invokePartial: 12, + push: 13, + invokeInverse: 14, + assignToHash: 15, + pushStringParam: 16 + }; + + Compiler.MULTI_PARAM_OPCODES = { + appendContent: 1, + getContext: 1, + lookupWithHelpers: 1, + lookup: 1, + invokeMustache: 2, + pushString: 1, + truthyOrFallback: 1, + functionOrFallback: 1, + invokeProgram: 2, + invokePartial: 1, + push: 1, + invokeInverse: 1, + assignToHash: 1, + pushStringParam: 1 + }; + + Compiler.DISASSEMBLE_MAP = {}; + + for(var prop in Compiler.OPCODE_MAP) { + var value = Compiler.OPCODE_MAP[prop]; + Compiler.DISASSEMBLE_MAP[value] = prop; + } + + Compiler.multiParamSize = function(code) { + return Compiler.MULTI_PARAM_OPCODES[Compiler.DISASSEMBLE_MAP[code]]; + }; + + Compiler.prototype = { + compiler: Compiler, + + disassemble: function() { + var opcodes = this.opcodes, opcode, nextCode; + var out = [], str, name, value; + + for(var i=0, l=opcodes.length; i 0) { + this.source[0] = this.source[0] + ", " + locals.join(", "); + } + + this.source[0] = this.source[0] + ";"; + + this.source.push("return buffer;"); + + var params = ["Handlebars", "context", "helpers", "partials"]; + + if(this.options.data) { params.push("data"); } + + for(var i=0, l=this.environment.depths.list.length; i this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } + return "stack" + this.stackSlot; + }, + + popStack: function() { + return "stack" + this.stackSlot--; + }, + + topStack: function() { + return "stack" + this.stackSlot; + }, + + quotedString: function(str) { + return '"' + str + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/\n/g, '\\n') + .replace(/\r/g, '\\r') + '"'; + } + }; + + var reservedWords = ("break case catch continue default delete do else finally " + + "for function if in instanceof new return switch this throw " + + "try typeof var void while with null true false").split(" "); + + compilerWords = JavaScriptCompiler.RESERVED_WORDS = {}; + + for(var i=0, l=reservedWords.length; i