timezone + with_scope now works almost like the engine

did committed Aug 17, 2013
commit 3863d24d2b4d1e59970de184d305a8f169e1170b
Showing 17 changed files with 225 additions and 44 deletions
Gemfile +1 -1
@@ @@ -4,6 +4,6 @@ source 'https://rubygems.org'
gemspec
# Development
- # gem 'locomotivecms_mounter', path: '../gems/mounter', require: false
+ gem 'locomotivecms_mounter', path: '../gems/mounter', require: false
gem 'rb-fsevent', '~> 0.9.1'
\ No newline at end of file
generators/blank/config/site.yml.tt +3 -0
@@ @@ -10,6 +10,9 @@ name: <%= config[:name] %>
# TODO: explain it
locales: [en]
+ # TODO: explain it
+ # timezone: Paris
+
# TODO: explain it
seo_title: <%= @name %>
meta_keywords: "some meta keywords"
generators/bootstrap/config/site.yml.tt +3 -0
@@ @@ -10,6 +10,9 @@ name: <%= config[:name] %>
# TODO: explain it
locales: [en]
+ # TODO: explain it
+ # timezone: Paris
+
# TODO: explain it
seo_title: <%= @name %>
meta_keywords: "some meta keywords"
generators/foundation/config/site.yml.tt +3 -0
@@ @@ -10,6 +10,9 @@ name: <%= config[:name] %>
# TODO: explain it
locales: [en]
+ # TODO: explain it
+ # timezone: Paris
+
# TODO: explain it
seo_title: <%= @name %>
meta_keywords: "some meta keywords"
locomotive/wagon/liquid/errors.rb b/lib/locomotive/wagon/liquid/errors.rb +2 -0
@@ @@ -2,6 +2,8 @@ module Locomotive
module Wagon
module Liquid
class PageNotFound < ::Liquid::Error; end
+
+ class UnknownConditionInScope < ::Liquid::Error; end
end
end
end
\ No newline at end of file
locomotive/wagon/liquid/filters/date.rb b/lib/locomotive/wagon/liquid/filters/date.rb +47 -21
@@ @@ -4,13 +4,39 @@ module Locomotive
module Filters
module Date
+ def parse_date_time(input, format = nil)
+ return '' if input.blank?
+
+ format ||= I18n.t('time.formats.default')
+ date_time = ::DateTime._strptime(input, format)
+
+ if date_time
+ ::Time.zone.local(date_time[:year], date_time[:mon], date_time[:mday], date_time[:hour], date_time[:min], date_time[:sec] || 0)
+ else
+ ::Time.zone.parse(input) rescue ''
+ end
+ end
+
+ def parse_date(input, format)
+ return '' if input.blank?
+
+ format ||= I18n.t('date.formats.default')
+ date = ::Date._strptime(input, format)
+
+ if date
+ ::Date.new(date[:year], date[:mon], date[:mday])
+ else
+ ::Date.parse(value) rescue ''
+ end
+ end
+
def localized_date(input, *args)
return '' if input.blank?
format, locale = args
locale ||= I18n.locale
- format ||= I18n.t('date.formats.default', :locale => locale)
+ format ||= I18n.t('date.formats.default', locale: locale)
if input.is_a?(String)
begin
@@ @@ -23,7 +49,7 @@ module Locomotive
return input.to_s unless input.respond_to?(:strftime)
- I18n.l input, :format => format, :locale => locale
+ I18n.l input, format: format, locale: locale
end
alias :format_date :localized_date
@@ @@ -32,37 +58,37 @@ module Locomotive
return '' if input.blank?
from_time = input
- to_time = args[0] || Time.now
+ to_time = args[0] || Time.zone.now
from_time = from_time.to_time if from_time.respond_to?(:to_time)
to_time = to_time.to_time if to_time.respond_to?(:to_time)
distance_in_minutes = (((to_time - from_time).abs)/60).round
distance_in_seconds = ((to_time - from_time).abs).round
- ::I18n.with_options({ :scope => :'datetime.distance_in_words' }) do |locale|
+ ::I18n.with_options({ scope: :'datetime.distance_in_words' }) do |locale|
case distance_in_minutes
when 0..1
return distance_in_minutes == 0 ?
- locale.t(:less_than_x_minutes, :count => 1) :
- locale.t(:x_minutes, :count => distance_in_minutes) unless include_seconds
+ locale.t(:less_than_x_minutes, count: 1) :
+ locale.t(:x_minutes, count: distance_in_minutes) unless include_seconds
case distance_in_seconds
- when 0..4 then locale.t :less_than_x_seconds, :count => 5
- when 5..9 then locale.t :less_than_x_seconds, :count => 10
- when 10..19 then locale.t :less_than_x_seconds, :count => 20
+ when 0..4 then locale.t :less_than_x_seconds, count: 5
+ when 5..9 then locale.t :less_than_x_seconds, count: 10
+ when 10..19 then locale.t :less_than_x_seconds, count: 20
when 20..39 then locale.t :half_a_minute
- when 40..59 then locale.t :less_than_x_minutes, :count => 1
- else locale.t :x_minutes, :count => 1
+ when 40..59 then locale.t :less_than_x_minutes, count: 1
+ else locale.t :x_minutes, count: 1
end
- when 2..44 then locale.t :x_minutes, :count => distance_in_minutes
- when 45..89 then locale.t :about_x_hours, :count => 1
- when 90..1439 then locale.t :about_x_hours, :count => (distance_in_minutes.to_f / 60.0).round
- when 1440..2519 then locale.t :x_days, :count => 1
- when 2520..43199 then locale.t :x_days, :count => (distance_in_minutes.to_f / 1440.0).round
- when 43200..86399 then locale.t :about_x_months, :count => 1
- when 86400..525599 then locale.t :x_months, :count => (distance_in_minutes.to_f / 43200.0).round
+ when 2..44 then locale.t :x_minutes, count: distance_in_minutes
+ when 45..89 then locale.t :about_x_hours, count: 1
+ when 90..1439 then locale.t :about_x_hours, count: (distance_in_minutes.to_f / 60.0).round
+ when 1440..2519 then locale.t :x_days, count: 1
+ when 2520..43199 then locale.t :x_days, count: (distance_in_minutes.to_f / 1440.0).round
+ when 43200..86399 then locale.t :about_x_months, count: 1
+ when 86400..525599 then locale.t :x_months, count: (distance_in_minutes.to_f / 43200.0).round
else
fyear = from_time.year
fyear += 1 if from_time.month >= 3
@@ @@ -79,11 +105,11 @@ module Locomotive
remainder = (minutes_with_offset % 525600)
distance_in_years = (minutes_with_offset / 525600)
if remainder < 131400
- locale.t(:about_x_years, :count => distance_in_years)
+ locale.t(:about_x_years, count: distance_in_years)
elsif remainder < 394200
- locale.t(:over_x_years, :count => distance_in_years)
+ locale.t(:over_x_years, count: distance_in_years)
else
- locale.t(:almost_x_years, :count => distance_in_years + 1)
+ locale.t(:almost_x_years, count: distance_in_years + 1)
end
end
end
locomotive/wagon/liquid/scopeable.rb b/lib/locomotive/wagon/liquid/scopeable.rb +85 -11
@@ @@ -7,25 +7,99 @@ module Locomotive
if @context['with_scope'].blank?
entries
else
- collection = []
+ # extract the conditions
+ _conditions = @context['with_scope'].clone.delete_if { |k, _| %w(order_by per_page page).include?(k) }
- conditions = @context['with_scope'].clone.delete_if { |k, _| %w(order_by per_page page).include?(k) }
+ # build the chains of conditions
+ conditions = _conditions.map { |name, value| Condition.new(name, value) }
- entries.each do |content|
- accepted = (conditions.map do |key, value|
- case value
- when TrueClass, FalseClass, String, Integer then content.send(key) == value
- else
- true
+ # DEBUG
+ # puts conditions.map(&:to_s).join(', ')
+
+ # get only the entries matching ALL the conditions
+ entries.find_all do |content|
+ accepted = true
+
+ conditions.each do |_condition|
+ unless _condition.matches?(content)
+ accepted = false
+ break # no to go further
end
- end).all? # all conditions works ?
+ end
- collection << content if accepted
+ accepted
end
- collection
end
end
+ class Condition
+
+ OPERATORS = %w(all gt gte in lt lte ne nin size)
+
+ attr_accessor :name, :operator, :right_operand
+
+ def initialize(name, value)
+ self.name, self.right_operand = name, value
+
+ # default value
+ self.operator = :==
+
+ self.decode_operator_based_on_name
+ end
+
+ def matches?(entry)
+ value = entry.send(self.name)
+
+ self.decode_operator_based_on_value(value)
+
+ case self.operator
+ when :== then value == self.right_operand
+ when :ne then value != self.right_operand
+ when :matches then self.right_operand =~ value
+ when :gte then value > self.right_operand
+ when :gte then value >= self.right_operand
+ when :lt then value < self.right_operand
+ when :lte then value <= self.right_operand
+ when :size then value.size == self.right_operand
+ when :all then [*self.right_operand].contains?(value)
+ when :in, :nin
+ _matches = if value.is_a?(Array)
+ [*value].contains?([*self.right_operand])
+ else
+ [*self.right_operand].include?(value)
+ end
+ self.operator == :in ? _matches : !_matches
+ else
+ raise UnknownConditionInScope.new("#{self.operator} is unknown or not implemented.")
+ end
+ end
+
+ def to_s
+ "#{name} #{operator} #{self.right_operand.inspect}"
+ end
+
+ protected
+
+ def decode_operator_based_on_name
+ if name =~ /^([a-z0-9_-]+)\.(#{OPERATORS.join('|')})$/
+ self.name = $1.to_sym
+ self.operator = $2.to_sym
+ end
+
+ if self.right_operand.is_a?(Regexp)
+ self.operator = :matches
+ end
+ end
+
+ def decode_operator_based_on_value(value)
+ case value
+ when Array
+ self.operator = :in if self.operator == :==
+ end
+ end
+
+ end
+
end
end
end
locomotive/wagon/liquid/tags/with_scope.rb b/lib/locomotive/wagon/liquid/tags/with_scope.rb +7 -6
@@ @@ -4,13 +4,14 @@ module Locomotive
module Tags
class WithScope < ::Liquid::Block
- def initialize(tag_name, markup, tokens, context)
- @options = {}
+ SlashedString = /\/[^\/]*\//
+ TagAttributes = /(\w+|\w+\.\w+)\s*\:\s*(#{SlashedString}|#{::Liquid::QuotedFragment})/
- markup.scan(::Liquid::TagAttributes) do |key, value|
+ def initialize(tag_name, markup, tokens, context)
+ @options = HashWithIndifferentAccess.new
+ markup.scan(TagAttributes) do |key, value|
@options[key] = value
end
-
super
end
@@ @@ -26,8 +27,8 @@ module Locomotive
def decode(attributes, context)
attributes.each_pair do |key, value|
attributes[key] = (case value
- when /^true|false$/i then value == 'true'
- when /^[0-9]+$/ then value.to_i
+ when /^true|false$/i then value == 'true'
+ when /^\/[^\/]*\/$/ then Regexp.new(value[1..-2])
when /^["|'](.+)["|']$/ then $1.gsub(/^["|']/, '').gsub(/["|']$/, '')
else
context[value] || value
locomotive/wagon/misc/core_ext.rb b/lib/locomotive/wagon/misc/core_ext.rb +8 -1
@@ @@ -44,4 +44,11 @@ unless String.instance_methods.include?(:to_bool)
class FalseClass
def to_bool; self; end
end
- end
\ No newline at end of file
+ end
+
+ unless Array.instance_methods.include?(:contains?)
+ class Array
+ def contains?(other); (self & other) == other; end
+ end
+ end
+
locomotive/wagon/server.rb b/lib/locomotive/wagon/server.rb +4 -1
@@ @@ -1,7 +1,8 @@
require 'better_errors'
require 'coffee_script'
require 'sprockets'
- require "sprockets-sass"
+ require 'sprockets-sass'
+ # require 'active_support/time'
require 'locomotive/wagon/listen'
require 'locomotive/wagon/server/middleware'
@@ @@ -12,6 +13,7 @@ require 'locomotive/wagon/server/entry_submission'
require 'locomotive/wagon/server/path'
require 'locomotive/wagon/server/locale'
require 'locomotive/wagon/server/page'
+ require 'locomotive/wagon/server/timezone'
require 'locomotive/wagon/server/templatized_page'
require 'locomotive/wagon/server/not_found'
require 'locomotive/wagon/server/renderer'
@@ @@ -68,6 +70,7 @@ module Locomotive::Wagon
use Path
use Locale
+ use Timezone
use Page
use TemplatizedPage
locomotive/wagon/server/renderer.rb b/lib/locomotive/wagon/server/renderer.rb +1 -1
@@ @@ -77,7 +77,7 @@ module Locomotive::Wagon
'path' => self.request.path,
'fullpath' => self.request.fullpath,
'url' => self.request.url,
- 'now' => Time.now.utc,
+ 'now' => Time.zone.now,
'today' => Date.today,
'locale' => I18n.locale.to_s,
'default_locale' => self.mounting_point.default_locale.to_s,
locomotive/wagon/server/timezone.rb b/lib/locomotive/wagon/server/timezone.rb +18 -0
@@ @@ -0,0 +1,18 @@
+ module Locomotive::Wagon
+ class Server
+
+ # Set the timezone according to the settings of the site
+ #
+ class Timezone < Middleware
+
+ def call(env)
+ self.set_accessors(env)
+
+ Time.use_zone(site.try(:timezone) || 'UTC') do
+ app.call(env)
+ end
+ end
+
+ end
+ end
+ end
\ No newline at end of file
locomotivecms_wagon.gemspec +2 -1
@@ @@ -28,12 +28,13 @@ Gem::Specification.new do |gem|
gem.add_dependency 'sprockets-sass', '~> 1.0.1'
gem.add_dependency 'rack-cache', '~> 1.1'
gem.add_dependency 'better_errors', '~> 0.7.2'
+ gem.add_dependency 'tzinfo', '~> 1.0.1'
gem.add_dependency 'listen', '~> 0.7.0'
gem.add_dependency 'httmultiparty', '0.3.10'
gem.add_dependency 'will_paginate', '~> 3.0.3'
- gem.add_dependency 'locomotivecms_mounter', '~> 1.2.2'
+ # gem.add_dependency 'locomotivecms_mounter', '~> 1.2.2'
gem.add_dependency 'faker', '~> 0.9.5'
spec/fixtures/default/app/content_types/events.yml +7 -1
@@ @@ -16,4 +16,10 @@ fields:
type: string
label: State of the event
- notes:
- type: text
\ No newline at end of file
+ type: text
+ - tags:
+ type: tags
+ label: List of tags
+ - price:
+ type: Float
+ label: Price of the event
\ No newline at end of file
spec/fixtures/default/app/views/pages/filtered.liquid.haml +10 -0
@@ @@ -0,0 +1,10 @@
+ ---
+ title: Various uses of the with_scope tag
+ ---
+ {% assign begin = '2012-06-01 00:00:00' %}
+ {% assign end = '2012-06-10 23:59:59' %}
+ {% assign prices = '5.0,5.5' | split: ',' | map: 'to_f' %}
+
+ {% with_scope date.gte: begin, date.lte: end, city: /Kansas/, state.ne: 'Illinois', tags: 'awesome', tags.nin: 'bad', price.in: prices %}
+ events={{ contents.events.count }}.
+ {% endwith_scope %}
\ No newline at end of file
spec/fixtures/default/data/events.yml +4 -0
@@ @@ -11,10 +11,14 @@
date: 2012/06/06
city: Kansas City
state: Missouri
+ tags: [awesome, open bar]
+ price: 5.5
- Browne's Market:
date: 2012/06/06
city: Kansas City
state: Missouri
+ tags: [awesome, open bar]
+ price: 15.0
- Ballydoyle's:
date: 2012/06/05
city: Aurora
spec/integration/server/with_scope_spec.rb +20 -0
@@ @@ -0,0 +1,20 @@
+ # encoding: utf-8
+
+ require File.dirname(__FILE__) + '/../integration_helper'
+ require 'locomotive/wagon/server'
+ require 'rack/test'
+
+ describe 'Complex with_scope conditions' do
+
+ include Rack::Test::Methods
+
+ def app
+ run_server
+ end
+
+ it 'returns the right number of events' do
+ get '/filtered'
+ last_response.body.should =~ /events=1./
+ end
+
+ end
\ No newline at end of file