ice_cube_cron 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 35437710e09561f1e31c5f37b0e612a21866395e
4
+ data.tar.gz: e4648fbf6cd9fee9e5ad7f2f260d2bbc4eeb4412
5
+ SHA512:
6
+ metadata.gz: 6edd59aebe07b5926ffe46ee99faa64ac4491b202c837ae09dbad0ade219eb4df2de35a1ad7946363755c953e8d56d6fa4462c9ac436e29cde7bf64c98b76696
7
+ data.tar.gz: 16c1988525cf23b6fb90f40b0a188dde5ce72fd45fdcd3582c75dd05f2aafc1112149c1670231520f7a34ad5fdf04b4917d7e9ee894343bfba8c63cc2da18220
data/.gitignore ADDED
@@ -0,0 +1,36 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /test/tmp/
9
+ /test/version_tmp/
10
+ /tmp/
11
+
12
+ ## Specific to RubyMotion:
13
+ .dat*
14
+ .repl_history
15
+ build/
16
+
17
+ ## Documentation cache and generated files:
18
+ /.yardoc/
19
+ /_yardoc/
20
+ /doc/
21
+ /rdoc/
22
+
23
+ ## Environment normalisation:
24
+ /.bundle/
25
+ /vendor/bundle
26
+ /lib/bundler/man/
27
+
28
+ # for a library or gem, you might want to ignore these files since the code is
29
+ # intended to run in multiple environments; otherwise, check them in:
30
+ # Gemfile.lock
31
+ .ruby-version
32
+ .ruby-gemset
33
+
34
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
35
+ .rvmrc
36
+ vendor/cache
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/.rubocop.yml ADDED
@@ -0,0 +1,17 @@
1
+ AllCops:
2
+ DisplayCopNames: true
3
+
4
+ Style/HashSyntax:
5
+ EnforcedStyle: hash_rockets
6
+
7
+ Style/AccessModifierIndentation:
8
+ EnforcedStyle: outdent
9
+
10
+ Style/Documentation:
11
+ Enabled: false
12
+
13
+ Metrics/LineLength:
14
+ Enabled: false
15
+
16
+ Metrics/MethodLength:
17
+ Max: 15
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+ # DEVUP--
5
+ gem 'guard-zeus', :group => :development
6
+ gem 'guard-rspec', :group => :development
7
+ gem 'guard-rubocop', :group => :development
8
+ gem 'rb-fsevent', :group => :development
9
+ gem 'terminal-notifier-guard', :group => :development
10
+ gem 'wirble', :group => :development
11
+ # --DEVUP
data/Gemfile.lock ADDED
@@ -0,0 +1,118 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ ice_cube_cron (0.0.1)
5
+ activesupport (>= 3.0.0)
6
+ ice_cube (= 0.13.0)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ activesupport (4.2.4)
12
+ i18n (~> 0.7)
13
+ json (~> 1.7, >= 1.7.7)
14
+ minitest (~> 5.1)
15
+ thread_safe (~> 0.3, >= 0.3.4)
16
+ tzinfo (~> 1.1)
17
+ ast (2.1.0)
18
+ astrolabe (1.3.1)
19
+ parser (~> 2.2)
20
+ coderay (1.1.0)
21
+ diff-lcs (1.2.5)
22
+ ffi (1.9.10)
23
+ formatador (0.2.5)
24
+ guard (2.13.0)
25
+ formatador (>= 0.2.4)
26
+ listen (>= 2.7, <= 4.0)
27
+ lumberjack (~> 1.0)
28
+ nenv (~> 0.1)
29
+ notiffany (~> 0.0)
30
+ pry (>= 0.9.12)
31
+ shellany (~> 0.0)
32
+ thor (>= 0.18.1)
33
+ guard-compat (1.2.1)
34
+ guard-rspec (4.6.4)
35
+ guard (~> 2.1)
36
+ guard-compat (~> 1.1)
37
+ rspec (>= 2.99.0, < 4.0)
38
+ guard-rubocop (1.2.0)
39
+ guard (~> 2.0)
40
+ rubocop (~> 0.20)
41
+ guard-zeus (2.0.1)
42
+ guard (~> 2.0)
43
+ guard-compat (~> 1.1)
44
+ zeus (~> 0)
45
+ i18n (0.7.0)
46
+ ice_cube (0.13.0)
47
+ json (1.8.3)
48
+ listen (3.0.3)
49
+ rb-fsevent (>= 0.9.3)
50
+ rb-inotify (>= 0.9)
51
+ lumberjack (1.0.9)
52
+ method_source (0.8.2)
53
+ minitest (5.8.1)
54
+ nenv (0.2.0)
55
+ notiffany (0.0.8)
56
+ nenv (~> 0.1)
57
+ shellany (~> 0.0)
58
+ parser (2.2.3.0)
59
+ ast (>= 1.1, < 3.0)
60
+ powerpack (0.1.1)
61
+ pry (0.10.2)
62
+ coderay (~> 1.1.0)
63
+ method_source (~> 0.8.1)
64
+ slop (~> 3.4)
65
+ rainbow (2.0.0)
66
+ rake (10.4.2)
67
+ rb-fsevent (0.9.6)
68
+ rb-inotify (0.9.5)
69
+ ffi (>= 0.5.0)
70
+ rspec (3.3.0)
71
+ rspec-core (~> 3.3.0)
72
+ rspec-expectations (~> 3.3.0)
73
+ rspec-mocks (~> 3.3.0)
74
+ rspec-core (3.3.2)
75
+ rspec-support (~> 3.3.0)
76
+ rspec-expectations (3.3.1)
77
+ diff-lcs (>= 1.2.0, < 2.0)
78
+ rspec-support (~> 3.3.0)
79
+ rspec-mocks (3.3.2)
80
+ diff-lcs (>= 1.2.0, < 2.0)
81
+ rspec-support (~> 3.3.0)
82
+ rspec-support (3.3.0)
83
+ rubocop (0.34.2)
84
+ astrolabe (~> 1.3)
85
+ parser (>= 2.2.2.5, < 3.0)
86
+ powerpack (~> 0.1)
87
+ rainbow (>= 1.99.1, < 3.0)
88
+ ruby-progressbar (~> 1.4)
89
+ ruby-progressbar (1.7.5)
90
+ shellany (0.0.1)
91
+ slop (3.6.0)
92
+ terminal-notifier-guard (1.6.4)
93
+ thor (0.19.1)
94
+ thread_safe (0.3.5)
95
+ tzinfo (1.2.2)
96
+ thread_safe (~> 0.1)
97
+ wirble (0.1.3)
98
+ zeus (0.15.4)
99
+ method_source (>= 0.6.7)
100
+
101
+ PLATFORMS
102
+ ruby
103
+
104
+ DEPENDENCIES
105
+ guard-rspec
106
+ guard-rubocop
107
+ guard-zeus
108
+ ice_cube_cron!
109
+ pry
110
+ rake
111
+ rb-fsevent
112
+ rspec
113
+ rubocop
114
+ terminal-notifier-guard
115
+ wirble
116
+
117
+ BUNDLED WITH
118
+ 1.10.6
data/Guardfile ADDED
@@ -0,0 +1,39 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ ## Uncomment and set this to only include directories you want to watch
5
+ # directories %w(app lib config test spec features) \
6
+ # .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
7
+
8
+ ## Note: if you are using the `directories` clause above and you are not
9
+ ## watching the project directory ("."), then you will want to move
10
+ ## the Guardfile to a watched dir and symlink it back, e.g.
11
+ #
12
+ # $ mkdir config
13
+ # $ mv Guardfile config/
14
+ # $ ln -s config/Guardfile .
15
+ #
16
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
17
+
18
+ # Note: The cmd option is now required due to the increasing number of ways
19
+ # rspec may be run, below are examples of the most common uses.
20
+ # * bundler: "bundle exec rspec"
21
+ # * bundler binstubs: "bin/rspec"
22
+ # * spring: "bin/rspec" (This will use spring if running and you have
23
+ # installed the spring binstubs per the docs)
24
+ # * zeus: "zeus rspec" (requires the server to be started separately)
25
+ # * "just" rspec: "bin/rspec"
26
+
27
+ # , failed_mode: :focus
28
+
29
+ group :red_green_refactor, :halt_on_fail => true do
30
+ guard :rspec, :cmd => 'rspec' do
31
+ watch('^spec/.+(_spec)')
32
+ watch('^lib/(.+)\.rb') { |m| "spec/lib\/#{m[1]}_spec.rb" }
33
+ end
34
+
35
+ guard :rubocop, :all_on_start => false do
36
+ watch(/.+\.rb$/)
37
+ watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) }
38
+ end
39
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Matt Nichols
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
data/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # ice_cube_cron
2
+
3
+ Adds new methods to ice_cube to create schedules using standard cron expressions.
4
+
5
+ ## description
6
+
7
+ **ice_cube** is a sold library for projecting and querying recurrence rules. **Cron** is _the_ standard for expressing recurrence rules. This gem will allow you to generate ice_cube schedules using standard cron expressions [explained here](https://en.wikipedia.org/wiki/Cron). This includes range expressions, series expressions, "last" day of month, Nth weekday of month, etc.
8
+
9
+ PLEASE NOTE:
10
+ This gem is a work-in-progress. Many features have yet to be implemented.
11
+
12
+ ## installation
13
+
14
+ gem install ice_cube_cron
15
+
16
+ or add to Gemfile
17
+
18
+ gem "ice_cube_cron"
19
+
20
+ ## example
21
+
22
+ Cron expression can be specified using a hash or a string in the format:
23
+ ```
24
+ # * * * * * * command to execute
25
+ # │ │ │ │ │ │
26
+ # │ │ │ │ │ │
27
+ # │ │ │ │ │ └───── year (optional) (1970–2099)
28
+ # │ │ │ │ └───── day of week (0 - 6)
29
+ # │ │ │ └────────── month (1 - 12)
30
+ # │ │ └─────────────── day of month (1 - 31)
31
+ # │ └──────────────────── hour (0 - 23)
32
+ # └───────────────────────── min (0 - 59)
33
+ ```
34
+ *[chart source](https://en.wikipedia.org/wiki/Cron)*
35
+
36
+
37
+ ```ruby
38
+ # using cron expression string (not supported yet)
39
+ schedule = ::IceCube::Schedule.from_cron(::Date.current, "* * * * 5")
40
+
41
+ # using hash
42
+ schedule = ::IceCube::Schedule.from_cron(::Date.current, :repeat_day => 5)
43
+
44
+ schedule.occurrences_between(::Date.new(2015, 3, 5), ::Date.new(2015, 6, 5))
45
+ # => [2015-03-05, 2015-04-05, 2015-06-05]
46
+ ```
47
+
48
+ ## recurrence rule examples (date)
49
+
50
+ |desc|interval|year|month|day|weekday|
51
+ |----|-------:|---:|----:|--:|------:|
52
+ |1st of every month||||1||
53
+ |1st and 15th of every month||||1,15||
54
+ |every monday|||||1|
55
+ |1st monday of every month|||||1#1|
56
+ |ever other friday|2||||5|
57
+ |every 6 months on the 5th|6|||5||
58
+ |last friday of every month|||||5L|
59
+ |last day every month||||L||
60
+
61
+ ## notes
62
+ - This gem is a work-in-progress.
63
+ - Does not yet support all recurrence options. More coming.
64
+
65
+ ## todo
66
+ - Add support for time options
67
+ - Add support for string cron expressions
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $LOAD_PATH.push File.expand_path('../lib', __FILE__)
3
+ require 'ice_cube_cron/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'ice_cube_cron'
7
+ s.version = IceCubeCron::VERSION
8
+ s.authors = ['Matt Nichols']
9
+ s.email = ['matt@nichols.name']
10
+ s.homepage = 'https://github.com/mattnichols/ice_cube_cron'
11
+ s.summary = 'Adds ability to create ice_cube schedules from cron expressions.'
12
+ s.description = 'Adds methods to ice_cube that creation of schedules using standard cron expressions.'
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.require_paths = ['lib']
17
+
18
+ ##
19
+ # Dependencies
20
+ #
21
+ s.add_dependency 'ice_cube', '= 0.13.0'
22
+ s.add_dependency 'activesupport', '>= 3.0.0'
23
+
24
+ ##
25
+ # Development Dependencies
26
+ #
27
+ s.add_development_dependency 'rake'
28
+ s.add_development_dependency 'pry'
29
+ s.add_development_dependency 'rspec'
30
+ s.add_development_dependency 'rubocop'
31
+ end
@@ -0,0 +1,13 @@
1
+ module IceCube
2
+ class << self
3
+ def from_cron(start_time, expression = {})
4
+ expression_parser = ::IceCubeCron::ExpressionParser.new(expression)
5
+ rule = ::IceCubeCron::RuleBuilder.new.build_rule(expression_parser)
6
+
7
+ schedule = ::IceCube::Schedule.new(::IceCubeCron::Util.sanitize_date_param(start_time))
8
+ schedule.add_recurrence_rule(rule)
9
+
10
+ schedule
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,123 @@
1
+ module IceCubeCron
2
+ class ExpressionParser
3
+ attr_accessor :expression_hash
4
+
5
+ def initialize(exp)
6
+ self.expression_hash = {
7
+ :repeat_interval => 1,
8
+ :repeat_year => nil,
9
+ :repeat_month => nil,
10
+ :repeat_day => nil,
11
+ :repeat_weekday => nil
12
+ }
13
+
14
+ if exp.is_a?(::Hash)
15
+ exp.each do |name, val|
16
+ send("#{name}=", val)
17
+ end
18
+ elsif exp.is_a?(::String)
19
+ fail ArgumentError, 'string cron expressions are not yet supported'
20
+ end
21
+ end
22
+
23
+ def [](val_name)
24
+ send(val_name)
25
+ end
26
+
27
+ def []=(val_name, new_val)
28
+ send("#{val_name}=", new_val)
29
+ end
30
+
31
+ def interval
32
+ expression_hash[:repeat_interval]
33
+ end
34
+ alias_method :repeat_interval, :interval
35
+
36
+ def interval=(val)
37
+ expression_hash[:repeat_interval] = sanitize_integer_param(val, 1)
38
+ end
39
+ alias_method :repeat_interval=, :interval=
40
+
41
+ def day
42
+ expression_hash[:repeat_day]
43
+ end
44
+ alias_method :repeat_day, :day
45
+
46
+ def day=(val)
47
+ expression_hash[:repeat_day] = sanitize_day_param(val)
48
+ end
49
+ alias_method :repeat_day=, :day=
50
+
51
+ def month
52
+ expression_hash[:repeat_month]
53
+ end
54
+ alias_method :repeat_month, :month
55
+
56
+ def month=(val)
57
+ expression_hash[:repeat_month] = sanitize_integer_array_param(val)
58
+ end
59
+ alias_method :repeat_month=, :month=
60
+
61
+ def year
62
+ expression_hash[:repeat_year]
63
+ end
64
+ alias_method :repeat_year, :year
65
+
66
+ def year=(val)
67
+ expression_hash[:repeat_year] = sanitize_integer_array_param(val)
68
+ end
69
+ alias_method :repeat_year=, :year=
70
+
71
+ def weekday
72
+ expression_hash[:repeat_weekday]
73
+ end
74
+ alias_method :repeat_weekday, :weekday
75
+
76
+ def weekday=(val)
77
+ expression_hash[:repeat_weekday] = sanitize_week_day_param(val)
78
+ end
79
+ alias_method :repeat_weekday=, :weekday=
80
+
81
+ private
82
+
83
+ def sanitize_integer_param(param, default = nil)
84
+ return default if param.blank?
85
+
86
+ param.to_i
87
+ end
88
+
89
+ def sanitize_day_param(param)
90
+ return nil if param.blank?
91
+ return param if param.is_a?(::Array)
92
+ return [param] if param.is_a?(::Integer)
93
+
94
+ param.to_s.split(',').map do |element|
95
+ next -1 if element == 'L'
96
+
97
+ element.to_i
98
+ end
99
+ end
100
+
101
+ def sanitize_week_day_param(param)
102
+ return nil if param.blank?
103
+ param.to_s.split(',').map do |element|
104
+ if element =~ /[0-9]+#[0-9]+/
105
+ parts = element.split('#')
106
+ { sanitize_integer_param(parts[0]) => sanitize_integer_array_param(parts[1]) }
107
+ elsif element =~ /[0-9]+L/
108
+ { sanitize_integer_param(element) => [-1] }
109
+ else
110
+ sanitize_integer_param(element)
111
+ end
112
+ end
113
+ end
114
+
115
+ def sanitize_integer_array_param(param)
116
+ return nil if param.blank?
117
+ return param if param.is_a?(::Array)
118
+ return [param] if param.is_a?(::Integer)
119
+
120
+ param.split(',').map(&:to_i)
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,34 @@
1
+ module IceCubeCron
2
+ class RuleBuilder
3
+ def build_rule(params)
4
+ rule = build_root_recurrence_rule(params)
5
+ rule = rule.month_of_year(*params[:month]) unless params[:month].blank?
6
+ rule = rule.day_of_month(*params[:day]) unless params[:day].blank?
7
+ rule = build_weekday_rule(rule, params)
8
+
9
+ rule
10
+ end
11
+
12
+ def build_weekday_rule(rule, params)
13
+ return rule.day_of_week(*params[:weekday]) if !params[:weekday].blank? && nth_day?(params[:weekday])
14
+ return rule.day(*params[:weekday]) unless params[:weekday].blank?
15
+
16
+ rule
17
+ end
18
+
19
+ # rubocop:disable Metrics/AbcSize
20
+ def build_root_recurrence_rule(params)
21
+ return ::IceCube::Rule.yearly(params[:interval]) unless params[:month].blank?
22
+ return ::IceCube::Rule.monthly(params[:interval]) if !params[:weekday].blank? && nth_day?(params[:weekday])
23
+ return ::IceCube::Rule.weekly(params[:interval]) unless params[:weekday].blank?
24
+
25
+ ::IceCube::Rule.monthly(params[:interval])
26
+ end
27
+ # rubocop:enable Metrics/AbcSize
28
+
29
+ def nth_day?(param)
30
+ return false if param.nil? || param.empty?
31
+ param[0].is_a?(::Hash)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,11 @@
1
+ module IceCubeCron
2
+ module Util
3
+ def self.sanitize_date_param(date)
4
+ date = date.to_time(:utc) if date.is_a?(::Date) && !date.is_a?(::DateTime)
5
+ date = date.to_time.utc if date.is_a?(::DateTime)
6
+ date = Time.at(date).utc if date.is_a?(::Integer)
7
+
8
+ date
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ module IceCubeCron
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,10 @@
1
+ require 'ice_cube'
2
+
3
+ require 'active_support/core_ext/date'
4
+ require 'active_support/core_ext/integer'
5
+
6
+ require 'ice_cube_cron/version'
7
+ require 'ice_cube_cron/util'
8
+ require 'ice_cube_cron/rule_builder'
9
+ require 'ice_cube_cron/expression_parser'
10
+ require 'ice_cube_cron/base'
@@ -0,0 +1,238 @@
1
+ require 'spec_helper'
2
+
3
+ describe ::IceCubeCron do
4
+ context 'repeat options' do
5
+ describe 'monthly' do
6
+ let(:ice_cube_model) { ::IceCube.from_cron(::Date.new(2015, 7, 1), :repeat_day => '1') }
7
+
8
+ it 'for same day' do
9
+ expect(ice_cube_model.occurrences_between(::Date.new(2015, 7, 1), ::Date.new(2015, 7, 1))).to eq([::Date.new(2015, 7, 1)])
10
+ end
11
+
12
+ it 'for multiple months' do
13
+ expect(ice_cube_model.occurrences_between(::Date.new(2015, 7, 1), ::Date.new(2015, 9, 1))).to eq([::Date.new(2015, 7, 1), ::Date.new(2015, 8, 1), ::Date.new(2015, 9, 1)])
14
+ end
15
+ end
16
+
17
+ describe 'twice monthly' do
18
+ let(:ice_cube_model) { ::IceCube.from_cron(::Date.new(2015, 7, 1), :repeat_day => '1,15') }
19
+
20
+ it 'for one month' do
21
+ expect(ice_cube_model.occurrences_between(::Date.new(2015, 7, 1), ::Date.new(2015, 7, 31))).to eq([::Date.new(2015, 7, 1), ::Date.new(2015, 7, 15)])
22
+ end
23
+
24
+ it 'for two months' do
25
+ expect(ice_cube_model.occurrences_between(::Date.new(2015, 7, 1), ::Date.new(2015, 8, 31))).to eq([::Date.new(2015, 7, 1), ::Date.new(2015, 7, 15), ::Date.new(2015, 8, 1), ::Date.new(2015, 8, 15)])
26
+ end
27
+ end
28
+
29
+ describe 'bi-monthly' do
30
+ let(:ice_cube_model) do
31
+ ::IceCube.from_cron(
32
+ ::Date.new(2015, 5, 1),
33
+ :repeat_day => '1',
34
+ :repeat_interval => '2'
35
+ )
36
+ end
37
+
38
+ it 'for one month' do
39
+ expect(ice_cube_model.occurrences_between(::Date.new(2015, 7, 1), ::Date.new(2015, 7, 31))).to eq([::Date.new(2015, 7, 1)])
40
+ end
41
+
42
+ it 'for multiple months' do
43
+ expect(ice_cube_model.occurrences_between(::Date.new(2015, 7, 1), ::Date.new(2015, 10, 31))).to eq([::Date.new(2015, 7, 1), ::Date.new(2015, 9, 1)])
44
+ end
45
+ end
46
+
47
+ describe 'every week' do
48
+ let(:ice_cube_model) do
49
+ ::IceCube.from_cron(
50
+ ::Date.new(2015, 7, 6),
51
+ :repeat_weekday => '1'
52
+ )
53
+ end
54
+
55
+ it 'for one week' do
56
+ expect(
57
+ ice_cube_model.occurrences_between(
58
+ ::Date.new(2015, 7, 1),
59
+ ::Date.new(2015, 7, 7)
60
+ )
61
+ ).to eq([::Date.new(2015, 7, 6)])
62
+ end
63
+
64
+ it 'for one month' do
65
+ expect(
66
+ ice_cube_model.occurrences_between(
67
+ ::Date.new(2015, 7, 1),
68
+ ::Date.new(2015, 8, 31)
69
+ )
70
+ ).to eq(
71
+ [
72
+ ::Date.new(2015, 7, 6),
73
+ ::Date.new(2015, 7, 13),
74
+ ::Date.new(2015, 7, 20),
75
+ ::Date.new(2015, 7, 27),
76
+ ::Date.new(2015, 8, 3),
77
+ ::Date.new(2015, 8, 10),
78
+ ::Date.new(2015, 8, 17),
79
+ ::Date.new(2015, 8, 24),
80
+ ::Date.new(2015, 8, 31)
81
+ ]
82
+ )
83
+ end
84
+ end
85
+
86
+ describe 'bi-weekly' do
87
+ let(:ice_cube_model) do
88
+ ::IceCube.from_cron(
89
+ ::Date.new(2015, 7, 6),
90
+ :repeat_weekday => '1',
91
+ :repeat_interval => '2'
92
+ )
93
+ end
94
+
95
+ it 'for one week' do
96
+ expect(
97
+ ice_cube_model.occurrences_between(
98
+ ::Date.new(2015, 7, 1),
99
+ ::Date.new(2015, 7, 7)
100
+ )
101
+ ).to eq([::Date.new(2015, 7, 6)])
102
+ end
103
+
104
+ it 'for two months' do
105
+ expect(
106
+ ice_cube_model.occurrences_between(
107
+ ::Date.new(2015, 7, 1),
108
+ ::Date.new(2015, 8, 31)
109
+ )
110
+ ).to eq(
111
+ [
112
+ ::Date.new(2015, 7, 6),
113
+ # ::Date.new(2015, 7, 13),
114
+ ::Date.new(2015, 7, 20),
115
+ # ::Date.new(2015, 7, 27),
116
+ ::Date.new(2015, 8, 3),
117
+ # ::Date.new(2015, 8, 10),
118
+ ::Date.new(2015, 8, 17),
119
+ # ::Date.new(2015, 8, 24),
120
+ ::Date.new(2015, 8, 31)
121
+ ]
122
+ )
123
+ end
124
+ end
125
+
126
+ describe 'annually' do
127
+ let(:ice_cube_model) do
128
+ ::IceCube.from_cron(
129
+ ::Date.new(2015, 1, 1),
130
+ :repeat_month => 2
131
+ )
132
+ end
133
+
134
+ it 'for one year' do
135
+ expect(ice_cube_model.occurrences_between(::Date.new(2015, 1, 1), ::Date.new(2015, 12, 31))).to eq([::Date.new(2015, 2, 1)])
136
+ end
137
+
138
+ it 'for two years' do
139
+ expect(ice_cube_model.occurrences_between(::Date.new(2015, 1, 1), ::Date.new(2016, 12, 31))).to eq([::Date.new(2015, 2, 1), ::Date.new(2016, 2, 1)])
140
+ end
141
+ end
142
+
143
+ describe 'bi-annually' do
144
+ let(:ice_cube_model) do
145
+ ::IceCube.from_cron(
146
+ ::Date.new(2015, 1, 1),
147
+ :repeat_interval => 2,
148
+ :repeat_month => 2,
149
+ :repeat_day => 1
150
+ )
151
+ end
152
+
153
+ it 'for two years' do
154
+ expect(ice_cube_model.occurrences_between(::Date.new(2015, 1, 1), ::Date.new(2016, 12, 31))).to eq([::Date.new(2015, 2, 1)])
155
+ end
156
+ end
157
+
158
+ describe 'last weekday of month' do
159
+ context '31 day month' do
160
+ let(:ice_cube_model) do
161
+ ::IceCube.from_cron(
162
+ ::Date.new(2015, 1, 1),
163
+ :repeat_weekday => '5L'
164
+ )
165
+ end
166
+
167
+ it 'for one month' do
168
+ expect(ice_cube_model.occurrences_between(::Date.new(2015, 12, 1), ::Date.new(2015, 12, 31))).to eq([::Date.new(2015, 12, 25)])
169
+ end
170
+ end
171
+
172
+ context '29 day month' do
173
+ let(:ice_cube_model) do
174
+ ::IceCube.from_cron(
175
+ ::Date.new(2015, 1, 1),
176
+ :repeat_weekday => '3L'
177
+ )
178
+ end
179
+
180
+ it 'for one month' do
181
+ expect(ice_cube_model.occurrences_between(::Date.new(2016, 2, 1), ::Date.new(2016, 2, 29))).to eq([::Date.new(2016, 2, 24)])
182
+ end
183
+ end
184
+ end
185
+
186
+ describe 'last day of month' do
187
+ context '31 day month' do
188
+ let(:ice_cube_model) do
189
+ ::IceCube.from_cron(
190
+ ::Date.new(2015, 1, 1),
191
+ :repeat_day => 'L'
192
+ )
193
+ end
194
+
195
+ it 'for one multiple months' do
196
+ expect(ice_cube_model.occurrences_between(::Date.new(2015, 12, 1), ::Date.new(2016, 3, 1))).to eq([::Date.new(2015, 12, 31), ::Date.new(2016, 1, 31), ::Date.new(2016, 2, 29)])
197
+ end
198
+ end
199
+ end
200
+
201
+ describe 'Nth day of week of month' do
202
+ let(:ice_cube_model) do
203
+ ::IceCube.from_cron(
204
+ ::Date.new(2015, 1, 1),
205
+ :repeat_weekday => '1#2'
206
+ )
207
+ end
208
+
209
+ it 'for one month' do
210
+ expect(ice_cube_model.occurrences_between(::Date.new(2016, 2, 1), ::Date.new(2016, 2, 29))).to eq([::Date.new(2016, 2, 8)])
211
+ end
212
+
213
+ it 'for mutiple months' do
214
+ expect(ice_cube_model.occurrences_between(::Date.new(2016, 2, 1), ::Date.new(2016, 4, 30))).to eq([::Date.new(2016, 2, 8), ::Date.new(2016, 3, 14), ::Date.new(2016, 4, 11)])
215
+ end
216
+ end
217
+ end
218
+
219
+ context 'input types' do
220
+ it 'handles ::DateTime as input' do
221
+ ice_cube_model = ::IceCube.from_cron(
222
+ ::DateTime.new(2015, 5, 1),
223
+ :repeat_day => '1',
224
+ :repeat_interval => '2'
225
+ )
226
+ expect(ice_cube_model.occurrences_between(::DateTime.new(2015, 7, 1), ::DateTime.new(2015, 7, 31))).to eq([::DateTime.new(2015, 7, 1)])
227
+ end
228
+
229
+ it 'handles integer (epoch) as input' do
230
+ ice_cube_model = ::IceCube.from_cron(
231
+ 1_430_438_400, # Fri, 01 May 2015 00:00:00 GMT
232
+ :repeat_day => '1',
233
+ :repeat_interval => '2'
234
+ )
235
+ expect(ice_cube_model.occurrences_between(::DateTime.new(2015, 7, 1), ::DateTime.new(2015, 7, 31))).to eq([::DateTime.new(2015, 7, 1)])
236
+ end
237
+ end
238
+ end
@@ -0,0 +1,155 @@
1
+ require 'spec_helper'
2
+
3
+ describe ::IceCubeCron::ExpressionParser do
4
+ let(:expression_parser) do
5
+ described_class.new(
6
+ :repeat_interval => 2,
7
+ :repeat_day => 3,
8
+ :repeat_month => 4,
9
+ :repeat_year => 1990,
10
+ :repeat_weekday => 0
11
+ )
12
+ end
13
+
14
+ describe 'parses ::Hash expression' do
15
+ describe 'repeat_interval' do
16
+ it '[]' do
17
+ expect(expression_parser[:repeat_interval]).to eq(2)
18
+ end
19
+
20
+ it '[]=' do
21
+ expression_parser[:repeat_interval] = 13
22
+ expect(expression_parser[:repeat_interval]).to eq(13)
23
+ end
24
+
25
+ it 'sanitizes' do
26
+ expression_parser[:repeat_interval] = 2
27
+ expect(expression_parser[:repeat_interval]).to eq(2)
28
+
29
+ expression_parser[:repeat_interval] = nil
30
+ expect(expression_parser[:repeat_interval]).to eq(1)
31
+
32
+ expression_parser[:repeat_interval] = '3'
33
+ expect(expression_parser[:repeat_interval]).to eq(3)
34
+ end
35
+ end
36
+
37
+ describe 'repeat_day' do
38
+ it '[]' do
39
+ expect(expression_parser[:repeat_day]).to eq([3])
40
+ end
41
+
42
+ it '[]=' do
43
+ expression_parser[:repeat_day] = 6
44
+ expect(expression_parser[:repeat_day]).to eq([6])
45
+ end
46
+
47
+ it 'sanitizes' do
48
+ expression_parser[:repeat_day] = 2
49
+ expect(expression_parser[:repeat_day]).to eq([2])
50
+
51
+ expression_parser[:repeat_day] = nil
52
+ expect(expression_parser[:repeat_day]).to eq(nil)
53
+
54
+ expression_parser[:repeat_day] = '3'
55
+ expect(expression_parser[:repeat_day]).to eq([3])
56
+ end
57
+
58
+ it 'should accept single day expression' do
59
+ expression_parser.repeat_day = '1'
60
+ expect(expression_parser.repeat_day).to eq([1])
61
+ end
62
+
63
+ it 'should accept series expression' do
64
+ expression_parser.repeat_day = '1,3'
65
+ expect(expression_parser.repeat_day).to eq([1, 3])
66
+ end
67
+
68
+ it 'should accept last day expression' do
69
+ expression_parser.repeat_day = 'L'
70
+ expect(expression_parser.repeat_day).to eq([-1])
71
+ end
72
+ end
73
+
74
+ describe 'repeat_month' do
75
+ it '[]' do
76
+ expect(expression_parser[:repeat_month]).to eq([4])
77
+ end
78
+
79
+ it '[]=' do
80
+ expression_parser[:repeat_month] = 11
81
+ expect(expression_parser[:repeat_month]).to eq([11])
82
+ end
83
+
84
+ it 'sanitizes' do
85
+ expression_parser[:repeat_month] = 2
86
+ expect(expression_parser[:repeat_month]).to eq([2])
87
+
88
+ expression_parser[:repeat_month] = nil
89
+ expect(expression_parser[:repeat_month]).to eq(nil)
90
+
91
+ expression_parser[:repeat_month] = '3'
92
+ expect(expression_parser[:repeat_month]).to eq([3])
93
+ end
94
+ end
95
+
96
+ describe 'repeat_year' do
97
+ it '[]' do
98
+ expect(expression_parser[:repeat_year]).to eq([1990])
99
+ end
100
+
101
+ it '[]=' do
102
+ expression_parser[:repeat_year] = 1994
103
+ expect(expression_parser[:repeat_year]).to eq([1994])
104
+ end
105
+
106
+ it 'sanitizes' do
107
+ expression_parser[:repeat_year] = 1992
108
+ expect(expression_parser[:repeat_year]).to eq([1992])
109
+
110
+ expression_parser[:repeat_year] = nil
111
+ expect(expression_parser[:repeat_year]).to eq(nil)
112
+
113
+ expression_parser[:repeat_year] = '2001'
114
+ expect(expression_parser[:repeat_year]).to eq([2001])
115
+ end
116
+ end
117
+
118
+ describe 'repeat_weekday' do
119
+ it '[]' do
120
+ expect(expression_parser[:repeat_weekday]).to eq([0])
121
+ end
122
+
123
+ it '[]=' do
124
+ expression_parser[:repeat_weekday] = 4
125
+ expect(expression_parser[:repeat_weekday]).to eq([4])
126
+ end
127
+
128
+ it 'sanitizes' do
129
+ expression_parser[:repeat_weekday] = 2
130
+ expect(expression_parser[:repeat_weekday]).to eq([2])
131
+
132
+ expression_parser[:repeat_weekday] = nil
133
+ expect(expression_parser[:repeat_weekday]).to eq(nil)
134
+
135
+ expression_parser[:repeat_weekday] = '3'
136
+ expect(expression_parser[:repeat_weekday]).to eq([3])
137
+ end
138
+
139
+ it 'should accept non-nth weekday expression' do
140
+ expression_parser.weekday = '1'
141
+ expect(expression_parser.weekday).to eq([1])
142
+ end
143
+
144
+ it 'should accept nth weekday expression' do
145
+ expression_parser.weekday = '1#3'
146
+ expect(expression_parser.weekday).to eq([{ 1 => [3] }])
147
+ end
148
+
149
+ it 'should accept last weekday expression' do
150
+ expression_parser.weekday = '1L'
151
+ expect(expression_parser.weekday).to eq([{ 1 => [-1] }])
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe ::IceCubeCron::RuleBuilder do
4
+ let(:expression) do
5
+ ::IceCubeCron::ExpressionParser.new({})
6
+ end
7
+
8
+ let(:rule_builder) { described_class.new }
9
+
10
+ describe 'builds correct root rule' do
11
+ it 'monthly' do
12
+ expression.day = 5
13
+ expect(rule_builder.build_rule(expression)).to be_a(IceCube::MonthlyRule)
14
+ end
15
+
16
+ it 'weekly' do
17
+ expression.weekday = 2
18
+ expect(rule_builder.build_rule(expression)).to be_a(IceCube::WeeklyRule)
19
+ end
20
+
21
+ it 'yearly' do
22
+ expression.month = 2
23
+ expect(rule_builder.build_rule(expression)).to be_a(IceCube::YearlyRule)
24
+ end
25
+ end
26
+
27
+ describe '#nth_day?' do
28
+ it 'should accept nil' do
29
+ expect(rule_builder.nth_day?(nil)).to be false
30
+ end
31
+
32
+ it 'should accept non-nth weekday expression' do
33
+ expect(rule_builder.nth_day?([1])).to be false
34
+ end
35
+
36
+ it 'should accept nth weekday expression' do
37
+ expect(rule_builder.nth_day?([{ 1 => [3] }])).to be true
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe ::IceCubeCron::Util do
4
+ describe '#sanitize_date_param' do
5
+ it 'should handle epoch time' do
6
+ expect(described_class.sanitize_date_param(1_430_438_400)).to eq(DateTime.new(2015, 5, 1, 0, 0, 0))
7
+ end
8
+
9
+ it 'should handle date' do
10
+ expect(described_class.sanitize_date_param(Date.new(2015, 5, 1))).to eq(DateTime.new(2015, 5, 1, 0, 0, 0))
11
+ end
12
+
13
+ it 'should handle datetime' do
14
+ expect(described_class.sanitize_date_param(DateTime.new(2015, 5, 1, 0, 0, 0))).to eq(DateTime.new(2015, 5, 1, 0, 0, 0))
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,46 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
4
+ # this file to always be loaded, without a need to explicitly require it in any
5
+ # files.
6
+ #
7
+ # Given that it is always loaded, you are encouraged to keep this file as
8
+ # light-weight as possible. Requiring heavyweight dependencies from this file
9
+ # will add to the boot time of your test suite on EVERY test run, even for an
10
+ # individual file that may not need all of that loaded. Instead, consider making
11
+ # a separate helper file that requires the additional dependencies and performs
12
+ # the additional setup, and require it from the spec files that actually need
13
+ # it.
14
+ #
15
+ # The `.rspec` file also contains a few flags that are not defaults but that
16
+ # users commonly want.
17
+ #
18
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
19
+
20
+ require 'ice_cube_cron'
21
+ require 'pry'
22
+
23
+ RSpec.configure do |config|
24
+ # rspec-expectations config goes here. You can use an alternate
25
+ # assertion/expectation library such as wrong or the stdlib/minitest
26
+ # assertions if you prefer.
27
+ config.expect_with :rspec do |expectations|
28
+ # This option will default to `true` in RSpec 4. It makes the `description`
29
+ # and `failure_message` of custom matchers include text for helper methods
30
+ # defined using `chain`, e.g.:
31
+ # be_bigger_than(2).and_smaller_than(4).description
32
+ # # => "be bigger than 2 and smaller than 4"
33
+ # ...rather than:
34
+ # # => "be bigger than 2"
35
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
36
+ end
37
+
38
+ # rspec-mocks config goes here. You can use an alternate test double
39
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
40
+ config.mock_with :rspec do |mocks|
41
+ # Prevents you from mocking or stubbing a method that does not exist on
42
+ # a real object. This is generally recommended, and will default to
43
+ # `true` in RSpec 4.
44
+ mocks.verify_partial_doubles = true
45
+ end
46
+ end
metadata ADDED
@@ -0,0 +1,153 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ice_cube_cron
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Matt Nichols
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-10-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ice_cube
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 0.13.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 0.13.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 3.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 3.0.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Adds methods to ice_cube that creation of schedules using standard cron
98
+ expressions.
99
+ email:
100
+ - matt@nichols.name
101
+ executables: []
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - ".gitignore"
106
+ - ".rspec"
107
+ - ".rubocop.yml"
108
+ - Gemfile
109
+ - Gemfile.lock
110
+ - Guardfile
111
+ - LICENSE
112
+ - README.md
113
+ - ice_cube_cron.gemspec
114
+ - lib/ice_cube_cron.rb
115
+ - lib/ice_cube_cron/base.rb
116
+ - lib/ice_cube_cron/expression_parser.rb
117
+ - lib/ice_cube_cron/rule_builder.rb
118
+ - lib/ice_cube_cron/util.rb
119
+ - lib/ice_cube_cron/version.rb
120
+ - spec/lib/ice_cube_cron/base_spec.rb
121
+ - spec/lib/ice_cube_cron/expression_parser_spec.rb
122
+ - spec/lib/ice_cube_cron/rule_builder_spec.rb
123
+ - spec/lib/ice_cube_cron/util_spec.rb
124
+ - spec/spec_helper.rb
125
+ homepage: https://github.com/mattnichols/ice_cube_cron
126
+ licenses: []
127
+ metadata: {}
128
+ post_install_message:
129
+ rdoc_options: []
130
+ require_paths:
131
+ - lib
132
+ required_ruby_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ required_rubygems_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ requirements: []
143
+ rubyforge_project:
144
+ rubygems_version: 2.4.6
145
+ signing_key:
146
+ specification_version: 4
147
+ summary: Adds ability to create ice_cube schedules from cron expressions.
148
+ test_files:
149
+ - spec/lib/ice_cube_cron/base_spec.rb
150
+ - spec/lib/ice_cube_cron/expression_parser_spec.rb
151
+ - spec/lib/ice_cube_cron/rule_builder_spec.rb
152
+ - spec/lib/ice_cube_cron/util_spec.rb
153
+ - spec/spec_helper.rb