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