From c38a8ff9dd4f46f166ec844118f097ec8b668338 Mon Sep 17 00:00:00 2001 From: dinedine Date: Tue, 6 Jul 2010 02:05:47 +0200 Subject: [PATCH] adding 2 new liquid tags: consume (to retrieve posts from a blog for instance) + nav (display children of a page) + fix a few bugs --- .gitignore | 3 + Gemfile | 1 + .../admin/theme_assets_controller.rb | 1 + app/models/content_type.rb | 2 +- app/models/theme_asset.rb | 4 +- app/uploaders/asset_uploader.rb | 2 + app/uploaders/theme_asset_uploader.rb | 2 +- app/views/admin/theme_assets/index.html.haml | 9 +++ doc/TODO | 9 +-- lib/locomotive.rb | 1 + lib/locomotive/carrierwave/base.rb | 2 +- lib/locomotive/httparty.rb | 2 + lib/locomotive/httparty/patches.rb | 18 ++++++ lib/locomotive/httparty/webservice.rb | 24 ++++++++ lib/locomotive/liquid/tags/consume.rb | 46 ++++++++++++++ lib/locomotive/liquid/tags/nav.rb | 60 +++++++++++++++++++ lib/locomotive/liquid/tags/paginate.rb | 4 +- lib/locomotive/liquid/tags/snippet.rb | 9 +-- spec/lib/locomotive/httparty/patches_spec.rb | 19 ++++++ .../locomotive/httparty/webservice_spec.rb | 24 ++++++++ .../locomotive/liquid/tags/consume_spec.rb | 40 +++++++++++++ spec/lib/locomotive/liquid/tags/nav_spec.rb | 41 +++++++++++++ 22 files changed, 305 insertions(+), 18 deletions(-) create mode 100644 lib/locomotive/httparty.rb create mode 100644 lib/locomotive/httparty/patches.rb create mode 100644 lib/locomotive/httparty/webservice.rb create mode 100644 lib/locomotive/liquid/tags/consume.rb create mode 100644 lib/locomotive/liquid/tags/nav.rb create mode 100644 spec/lib/locomotive/httparty/patches_spec.rb create mode 100644 spec/lib/locomotive/httparty/webservice_spec.rb create mode 100644 spec/lib/locomotive/liquid/tags/consume_spec.rb create mode 100644 spec/lib/locomotive/liquid/tags/nav_spec.rb diff --git a/.gitignore b/.gitignore index 52178214..8f28c6c0 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,10 @@ spec/tmp public/sites public/uploads public/stylesheets/all.css +public/stylesheets/plugins public/javascripts/all.js +public/javascripts/plugins +public/images/plugins pkg *.gemspec rails_3_gems diff --git a/Gemfile b/Gemfile index d49cdd29..6917ceca 100644 --- a/Gemfile +++ b/Gemfile @@ -21,6 +21,7 @@ gem 'formtastic-rails3', :require => 'formtastic' gem 'carrierwave-rails3', :require => 'carrierwave' gem 'actionmailer-with-request', :require => 'actionmailer_with_request' gem 'heroku' +gem 'httparty', '0.6.0' # Development environment group :development do diff --git a/app/controllers/admin/theme_assets_controller.rb b/app/controllers/admin/theme_assets_controller.rb index 037949c1..b00deed1 100644 --- a/app/controllers/admin/theme_assets_controller.rb +++ b/app/controllers/admin/theme_assets_controller.rb @@ -9,6 +9,7 @@ module Admin assets = current_site.theme_assets.all @non_image_assets = assets.find_all { |a| a.stylesheet? || a.javascript? } @image_assets = assets.find_all { |a| a.image? } + @flash_assets = assets.find_all { |a| a.movie? } if request.xhr? render :action => 'images', :layout => false diff --git a/app/models/content_type.rb b/app/models/content_type.rb index 8b33c473..6949d4a5 100644 --- a/app/models/content_type.rb +++ b/app/models/content_type.rb @@ -21,7 +21,7 @@ class ContentType ## validations ## validates_presence_of :site, :name, :slug - validates_uniqueness_of :slug, :scope => :site + validates_uniqueness_of :slug, :scope => :site_id validates_size_of :content_custom_fields, :minimum => 1, :message => :array_too_short ## behaviours ## diff --git a/app/models/theme_asset.rb b/app/models/theme_asset.rb index 256a1362..4a7d6c56 100644 --- a/app/models/theme_asset.rb +++ b/app/models/theme_asset.rb @@ -33,7 +33,7 @@ class ThemeAsset ## methods ## - %w{image stylesheet javascript}.each do |type| + %w{movie image stylesheet javascript}.each do |type| define_method("#{type}?") do self.content_type == type end @@ -53,7 +53,7 @@ class ThemeAsset end def performing_plain_text? - return true if !self.new_record? && !self.image? && self.errors.empty? + return true if !self.new_record? && !self.image? && !self.movie? && self.errors.empty? !(self.performing_plain_text.blank? || self.performing_plain_text == 'false' || self.performing_plain_text == false) end diff --git a/app/uploaders/asset_uploader.rb b/app/uploaders/asset_uploader.rb index 130709c6..01beec81 100644 --- a/app/uploaders/asset_uploader.rb +++ b/app/uploaders/asset_uploader.rb @@ -45,6 +45,8 @@ class AssetUploader < CarrierWave::Uploader::Base end end + puts "content_type = #{value}" + model.content_type = value end diff --git a/app/uploaders/theme_asset_uploader.rb b/app/uploaders/theme_asset_uploader.rb index 0ef39062..91d70507 100644 --- a/app/uploaders/theme_asset_uploader.rb +++ b/app/uploaders/theme_asset_uploader.rb @@ -26,7 +26,7 @@ class ThemeAssetUploader < AssetUploader end def extension_white_list - %w(jpg jpeg gif png css js) + %w(jpg jpeg gif png css js swf flv) end end \ No newline at end of file diff --git a/app/views/admin/theme_assets/index.html.haml b/app/views/admin/theme_assets/index.html.haml index 94615614..32765915 100644 --- a/app/views/admin/theme_assets/index.html.haml +++ b/app/views/admin/theme_assets/index.html.haml @@ -25,3 +25,12 @@ %ul.assets = render :partial => 'asset', :collection => @image_assets %li.clear + + +- if not @flash_assets.empty? + %br + + %h3= t('.flash') + %ul.assets + = render :partial => 'asset', :collection => @flash_assets + %li.clear \ No newline at end of file diff --git a/doc/TODO b/doc/TODO index 2d7eb86e..2edda325 100644 --- a/doc/TODO +++ b/doc/TODO @@ -3,23 +3,24 @@ BOARD: - refactoring admin crud (pages + layouts + snippets) - refactor slugify method (use parameterize + create a module) -- localize application in French (tork) +- localize application in French x default x devise x carrierwave x localize devise emails - admin + +- rss parser BACKLOG: +- notify accounts when new instance of models (opt): none, one or many accounts. Used for contact form. - theme assets: disable version if not image - - new custom field types: - belongs_to => association - - cucumber features for admin pages - - sitemap +- validation for custom fields BUGS: diff --git a/lib/locomotive.rb b/lib/locomotive.rb index df4766d4..ea50b81b 100644 --- a/lib/locomotive.rb +++ b/lib/locomotive.rb @@ -6,6 +6,7 @@ require 'locomotive/mongoid' require 'locomotive/carrierwave' require 'locomotive/heroku' require 'locomotive/custom_fields' +require 'locomotive/httparty' require 'mongo_session_store/mongoid' diff --git a/lib/locomotive/carrierwave/base.rb b/lib/locomotive/carrierwave/base.rb index 7dd59821..4c8a929c 100644 --- a/lib/locomotive/carrierwave/base.rb +++ b/lib/locomotive/carrierwave/base.rb @@ -5,7 +5,7 @@ module CarrierWave def to_liquid { :url => self.url, - :filename => File.basename(self.url), + :filename => (File.basename(self.url) rescue ''), :size => self.size }.stringify_keys end diff --git a/lib/locomotive/httparty.rb b/lib/locomotive/httparty.rb new file mode 100644 index 00000000..c19d7876 --- /dev/null +++ b/lib/locomotive/httparty.rb @@ -0,0 +1,2 @@ +require 'locomotive/httparty/webservice' +require 'locomotive/httparty/patches' \ No newline at end of file diff --git a/lib/locomotive/httparty/patches.rb b/lib/locomotive/httparty/patches.rb new file mode 100644 index 00000000..075e966f --- /dev/null +++ b/lib/locomotive/httparty/patches.rb @@ -0,0 +1,18 @@ +require 'crack/json' + +module Crack + class JSON + + def self.parse_with_tumblr(json) + cleaned_json = json.gsub(/^var\s+.+\s+=\s+/, '').gsub(/;$/, '') + parse_without_tumblr(cleaned_json) + rescue ArgumentError => e + raise ParseError, "Invalid JSON string #{e.inspect}" + end + + class << self + alias_method_chain :parse, :tumblr + end + + end +end \ No newline at end of file diff --git a/lib/locomotive/httparty/webservice.rb b/lib/locomotive/httparty/webservice.rb new file mode 100644 index 00000000..9a9e8af4 --- /dev/null +++ b/lib/locomotive/httparty/webservice.rb @@ -0,0 +1,24 @@ +module Locomotive + module Httparty + class Webservice + + include HTTParty + + def self.consume(url, options = {}) + url = HTTParty.normalize_base_uri(url) + + options[:base_uri], path = url.scan(/^(http[s]?:\/\/.+\.[a-z]{2,})(\/.+)*/).first + options.delete(:format) if options[:format] == 'default' + + username, password = options.delete(:username), options.delete(:password) + options[:basic_auth] = { :username => username, :password => password } if username + + path ||= '/' + + puts "[WebService] consuming #{path}, #{options.inspect}" + + self.get(path, options) + end + end + end +end \ No newline at end of file diff --git a/lib/locomotive/liquid/tags/consume.rb b/lib/locomotive/liquid/tags/consume.rb new file mode 100644 index 00000000..a5955b3a --- /dev/null +++ b/lib/locomotive/liquid/tags/consume.rb @@ -0,0 +1,46 @@ +module Locomotive + module Liquid + module Tags + # Consume web services as easy as pie directly in liquid ! + # + # Usage: + # + # {% consume blog from 'http://nocoffee.tumblr.com/api/read.json?num=3' username: 'john', password: 'easy', format: 'json' %} + # {% for post in blog.posts %} + # {{ post.title }} + # {% endfor %} + # {% endconsume %} + # + class Consume < ::Liquid::Block + + Syntax = /(#{::Liquid::VariableSignature}+)\s*from\s*(#{::Liquid::QuotedString}+)/ + + def initialize(tag_name, markup, tokens) + if markup =~ Syntax + @target = $1 + @url = $2.gsub(/['"]/, '') + @options = {} + markup.scan(::Liquid::TagAttributes) do |key, value| + @options[key] = value if key != 'http' + end + else + raise ::Liquid::SyntaxError.new("Syntax Error in 'consume' - Valid syntax: consume from \"\" [username: value, password: value]") + end + + super + end + + def render(context) + context.stack do + context.scopes.last[@target.to_s] = Locomotive::Httparty::Webservice.consume(@url, @options.symbolize_keys) + + render_all(@nodelist, context) + end + end + + end + + ::Liquid::Template.register_tag('consume', Consume) + end + end +end \ No newline at end of file diff --git a/lib/locomotive/liquid/tags/nav.rb b/lib/locomotive/liquid/tags/nav.rb new file mode 100644 index 00000000..43fae319 --- /dev/null +++ b/lib/locomotive/liquid/tags/nav.rb @@ -0,0 +1,60 @@ +module Locomotive + module Liquid + module Tags + # Display the children pages of the site or the current page. If not precised, nav is applied on the current page. + # The html output is based on the ul/li tags. + # + # Usage: + # + # {% nav site %} => + # + class Nav < ::Liquid::Tag + + Syntax = /(#{::Liquid::Expression}+)?/ + + def initialize(tag_name, markup, tokens) + if markup =~ Syntax + @site_or_page = $1 || 'page' + else + raise ::Liquid::SyntaxError.new("Syntax Error in 'nav' - Valid syntax: nav ") + end + + super + end + + def render(context) + @current_page = context.registers[:page] + + source = context.registers[@site_or_page.to_sym] + + # puts "[Nav] source = #{source.inspect}" + + if source.respond_to?(:name) # site ? + source = source.pages.first # start from home page + else + source = source.parent + end + + output = %{} + output + end + + private + + def render_child_link(page) + selected = @current_page._id == page._id ? ' on' : '' + + %{ +
  • + #{page.title} +
  • + }.strip + end + + ::Liquid::Template.register_tag('nav', Nav) + end + end + end +end \ No newline at end of file diff --git a/lib/locomotive/liquid/tags/paginate.rb b/lib/locomotive/liquid/tags/paginate.rb index eb6bc593..546cdfc6 100644 --- a/lib/locomotive/liquid/tags/paginate.rb +++ b/lib/locomotive/liquid/tags/paginate.rb @@ -24,7 +24,7 @@ module Locomotive @collection_name = $1 @per_page = $2.to_i else - raise ::Liquid::SyntaxError.new("Syntax Error in 'paginate' - Valid syntax: paginate [collection] by [number]") + raise ::Liquid::SyntaxError.new("Syntax Error in 'paginate' - Valid syntax: paginate by ") end super @@ -34,7 +34,7 @@ module Locomotive context.stack do collection = context[@collection_name] - raise ArgumentError.new("Cannot paginate array '#{@collection_name}'. Not found.") if collection.nil? + raise ::Liquid::ArgumentError.new("Cannot paginate array '#{@collection_name}'. Not found.") if collection.nil? pagination = collection.paginate({ :page => context['current_page'], diff --git a/lib/locomotive/liquid/tags/snippet.rb b/lib/locomotive/liquid/tags/snippet.rb index 89f8e2c9..462cf082 100644 --- a/lib/locomotive/liquid/tags/snippet.rb +++ b/lib/locomotive/liquid/tags/snippet.rb @@ -1,7 +1,5 @@ module Locomotive - module Liquid - module Tags class Snippet < ::Liquid::Include @@ -33,10 +31,7 @@ module Locomotive end end - ::Liquid::Template.register_tag('include', Snippet) - - end - + ::Liquid::Template.register_tag('include', Snippet) + end end - end \ No newline at end of file diff --git a/spec/lib/locomotive/httparty/patches_spec.rb b/spec/lib/locomotive/httparty/patches_spec.rb new file mode 100644 index 00000000..b27ad1f4 --- /dev/null +++ b/spec/lib/locomotive/httparty/patches_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe 'Httparty patches' do + + describe 'Crack patch' do + + context '#parsing json' do + + it 'fixes an issue about json input beginning by a variable declaration' do + lambda { + Crack::JSON.parse('var json = { "foo": 42 };') + }.should_not raise_error + end + + end + + end + +end \ No newline at end of file diff --git a/spec/lib/locomotive/httparty/webservice_spec.rb b/spec/lib/locomotive/httparty/webservice_spec.rb new file mode 100644 index 00000000..2052b459 --- /dev/null +++ b/spec/lib/locomotive/httparty/webservice_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe Locomotive::Httparty::Webservice do + + context '#consuming' do + + it 'sets the base uri from a simple url' do + Locomotive::Httparty::Webservice.expects(:get).with('/', { :base_uri => 'http://blog.locomotiveapp.org' }) + Locomotive::Httparty::Webservice.consume('http://blog.locomotiveapp.org') + end + + it 'sets both the base uri and the path from an url with parameters' do + Locomotive::Httparty::Webservice.expects(:get).with('/api/read/json?num=3', { :base_uri => 'http://blog.locomotiveapp.org' }) + Locomotive::Httparty::Webservice.consume('http://blog.locomotiveapp.org/api/read/json?num=3') + end + + it 'sets auth credentials' do + Locomotive::Httparty::Webservice.expects(:get).with('/', { :base_uri => 'http://blog.locomotiveapp.org', :basic_auth => { :username => 'john', :password => 'foo' } }) + Locomotive::Httparty::Webservice.consume('http://blog.locomotiveapp.org', { :username => 'john', :password => 'foo' }) + end + + end + +end \ No newline at end of file diff --git a/spec/lib/locomotive/liquid/tags/consume_spec.rb b/spec/lib/locomotive/liquid/tags/consume_spec.rb new file mode 100644 index 00000000..40ce8176 --- /dev/null +++ b/spec/lib/locomotive/liquid/tags/consume_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe Locomotive::Liquid::Tags::Consume do + + context '#validating syntax' do + + it 'validates a basic syntax' do + markup = 'blog from "http://blog.locomotiveapp.org"' + lambda do + Locomotive::Liquid::Tags::Consume.new('consume', markup, ["{% endconsume %}"]) + end.should_not raise_error + end + + it 'validates more complex syntax with attributes' do + markup = 'blog from "http://www.locomotiveapp.org" username: "john", password: "easyone"' + lambda do + Locomotive::Liquid::Tags::Consume.new('consume', markup, ["{% endconsume %}"]) + end.should_not raise_error + end + + it 'raises an error if the syntax is incorrect' do + markup = 'blog from http://www.locomotiveapp.org' + lambda do + Locomotive::Liquid::Tags::Consume.new('consume', markup, ["{% endconsume %}"]) + end.should raise_error + end + + end + + context '#rendering' do + + it 'puts the response into the liquid variable' do + Locomotive::Httparty::Webservice.stubs(:get).returns({ 'title' => 'Locomotive rocks !' }) + template = "{% consume blog from \"http://blog.locomotiveapp.org/api/read\" %}{{ blog.title }}{% endconsume %}" + Liquid::Template.parse(template).render.should == 'Locomotive rocks !' + end + + end + +end \ No newline at end of file diff --git a/spec/lib/locomotive/liquid/tags/nav_spec.rb b/spec/lib/locomotive/liquid/tags/nav_spec.rb new file mode 100644 index 00000000..e6f4599e --- /dev/null +++ b/spec/lib/locomotive/liquid/tags/nav_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe Locomotive::Liquid::Tags::Nav do + + before(:each) do + @home = Factory.build(:page) + @home.stubs(:children).returns([ + Page.new(:title => 'Child #1', :fullpath => 'child_1', :slug => 'child_1'), + Page.new(:title => 'Child #2', :fullpath => 'child_2', :slug => 'child_2') + ]) + @home.children.last.stubs(:children).returns([ + Page.new(:title => 'Child #2.1', :fullpath => 'child_2/sub_child_1', :slug => 'sub_child_1'), + Page.new(:title => 'Child #2.2', :fullpath => 'child_2/sub_child_2', :slug => 'sub_child_2') + ]) + @site = Factory.build(:site) + @site.stubs(:pages).returns([@home]) + end + + context '#rendering' do + + it 'renders from site' do + render_nav.should == '' + end + + it 'renders from page' do + (page = @home.children.last.children.first).stubs(:parent).returns(@home.children.last) + output = render_nav 'page', { :page => page } + output.should == '' + end + + end + + def render_nav(source = 'site', registers = {}) + registers = { :site => @site, :page => @home }.merge(registers) + liquid_context = ::Liquid::Context.new({}, registers) + + output = Liquid::Template.parse("{% nav #{source} %}").render(liquid_context) + output.gsub(/\n\s{0,}/, '') + end + +end \ No newline at end of file