adding 2 new liquid tags: consume (to retrieve posts from a blog for instance) + nav (display children of a page) + fix a few bugs

This commit is contained in:
dinedine 2010-07-06 02:05:47 +02:00
parent 821210795d
commit c38a8ff9dd
22 changed files with 305 additions and 18 deletions

3
.gitignore vendored
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 ##

View File

@ -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

View File

@ -45,6 +45,8 @@ class AssetUploader < CarrierWave::Uploader::Base
end
end
puts "content_type = #{value}"
model.content_type = value
end

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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'

View File

@ -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

View File

@ -0,0 +1,2 @@
require 'locomotive/httparty/webservice'
require 'locomotive/httparty/patches'

View File

@ -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

View File

@ -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

View File

@ -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 <var> from \"<url>\" [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

View File

@ -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 %} => <ul class="nav"><li class="on"><a href="/features">Features</a></li></ul>
#
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 <page|site>")
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 = %{<ul id="nav">}
output += source.children.map { |p| render_child_link(p) }.join("\n")
output += %{</ul>}
output
end
private
def render_child_link(page)
selected = @current_page._id == page._id ? ' on' : ''
%{
<li id="#{page.slug.dasherize}" class="link#{selected}">
<a href="/#{page.fullpath}">#{page.title}</a>
</li>
}.strip
end
::Liquid::Template.register_tag('nav', Nav)
end
end
end
end

View File

@ -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 <collection> by <number>")
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'],

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 == '<ul id="nav"><li id="child-1" class="link"><a href="/child_1">Child #1</a></li><li id="child-2" class="link"><a href="/child_2">Child #2</a></li></ul>'
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 == '<ul id="nav"><li id="sub-child-1" class="link on"><a href="/child_2/sub_child_1">Child #2.1</a></li><li id="sub-child-2" class="link"><a href="/child_2/sub_child_2">Child #2.2</a></li></ul>'
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