montrose 0.10.0 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +65 -0
- data/Appraisals +6 -14
- data/CHANGELOG.md +13 -1
- data/Guardfile +2 -2
- data/README.md +57 -0
- data/Rakefile +2 -4
- data/bin/setup +1 -0
- data/bin/standardrb +29 -0
- data/gemfiles/activesupport_5.2.gemfile +4 -0
- data/gemfiles/activesupport_6.0.gemfile +5 -1
- data/gemfiles/{activesupport_4.2.gemfile → activesupport_6.1.gemfile} +5 -1
- data/lib/montrose.rb +23 -3
- data/lib/montrose/chainable.rb +43 -9
- data/lib/montrose/clock.rb +4 -4
- data/lib/montrose/frequency.rb +4 -4
- data/lib/montrose/options.rb +40 -16
- data/lib/montrose/recurrence.rb +29 -8
- data/lib/montrose/rule.rb +2 -1
- data/lib/montrose/rule/between.rb +1 -1
- data/lib/montrose/rule/covering.rb +40 -0
- data/lib/montrose/rule/during.rb +55 -0
- data/lib/montrose/rule/until.rb +1 -1
- data/lib/montrose/schedule.rb +112 -24
- data/lib/montrose/stack.rb +3 -2
- data/lib/montrose/utils.rb +6 -6
- data/lib/montrose/version.rb +1 -1
- data/montrose.gemspec +17 -17
- metadata +35 -37
- data/.rubocop.yml +0 -136
- data/.travis.yml +0 -33
- data/bin/rubocop +0 -16
- data/gemfiles/activesupport_4.1.gemfile +0 -12
- data/gemfiles/activesupport_5.0.gemfile +0 -12
- data/gemfiles/activesupport_5.1.gemfile +0 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d44082eaa05a068714f05fe6116f8442b0507b3c7612174d93716ce6fd4af4ba
|
4
|
+
data.tar.gz: 691d88452db689b1555772b6749ae12e72bf675fbc55d4082b2d7f4cd41dd795
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 389adad3b13244fdb642204fa158bde6a204de4c752cab7a11dc1fe2a237a98f7b73c905477fd335d47164b795921a92090b68a528327b115ff2721b8c2945f7
|
7
|
+
data.tar.gz: 9ce4b11ae2d6c0fd06b06ca9fe56aa05767bca879be8c5cd3a24dae1c7fed442ca50d4195cf5225ff42f1bb2f2704a9ab8fb308b0a9167865317a245dd782441
|
@@ -0,0 +1,65 @@
|
|
1
|
+
version: 2.1
|
2
|
+
orbs:
|
3
|
+
ruby: circleci/ruby@1.1.2
|
4
|
+
commands:
|
5
|
+
run-tests:
|
6
|
+
description: Run tests
|
7
|
+
steps:
|
8
|
+
- run: bundle exec rake test
|
9
|
+
restore:
|
10
|
+
description: Restore cache
|
11
|
+
steps:
|
12
|
+
- restore_cache:
|
13
|
+
keys:
|
14
|
+
- v1_bundler_deps-
|
15
|
+
save:
|
16
|
+
description: Save cache
|
17
|
+
steps:
|
18
|
+
- save_cache:
|
19
|
+
paths:
|
20
|
+
- ./vendor/bundle
|
21
|
+
key: v1-bundler-deps-{{ .Environment.CIRCLE_JOB }}
|
22
|
+
bundle:
|
23
|
+
description: Install dependencies
|
24
|
+
steps:
|
25
|
+
- run:
|
26
|
+
echo "export BUNDLE_JOBS=4" >> $BASH_ENV
|
27
|
+
echo "export BUNDLE_RETRY=3" >> $BASH_ENV
|
28
|
+
echo "export BUNDLE_PATH=$(pwd)/vendor/bundle" >> $BASH_ENV
|
29
|
+
echo "export BUNDLE_GEMFILE=$(pwd)/${GEMFILE_NAME}" >> $BASH_ENV
|
30
|
+
- run: bundle install
|
31
|
+
|
32
|
+
jobs:
|
33
|
+
test:
|
34
|
+
parameters:
|
35
|
+
ruby_version:
|
36
|
+
type: string
|
37
|
+
gemfile:
|
38
|
+
type: string
|
39
|
+
docker:
|
40
|
+
- image: 'circleci/ruby:<< parameters.ruby_version >>'
|
41
|
+
environment:
|
42
|
+
GEMFILE_NAME: <<parameters.gemfile>>
|
43
|
+
steps:
|
44
|
+
- checkout
|
45
|
+
- restore
|
46
|
+
- bundle
|
47
|
+
- save
|
48
|
+
- run-tests
|
49
|
+
|
50
|
+
workflows:
|
51
|
+
all-tests:
|
52
|
+
jobs:
|
53
|
+
- test:
|
54
|
+
matrix:
|
55
|
+
parameters:
|
56
|
+
ruby_version: ['2.5', '2.6', '2.7']
|
57
|
+
gemfile:
|
58
|
+
[
|
59
|
+
'gemfiles/activesupport_5.2.gemfile',
|
60
|
+
'gemfiles/activesupport_6.0.gemfile',
|
61
|
+
'gemfiles/activesupport_6.1.gemfile',
|
62
|
+
]
|
63
|
+
# exclude:
|
64
|
+
# - ruby_version: '3.0'
|
65
|
+
# - gemfile: rails_5_2.gemfile
|
data/Appraisals
CHANGED
@@ -1,21 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
appraise "activesupport-
|
4
|
-
gem "activesupport", "~>
|
5
|
-
end
|
6
|
-
|
7
|
-
appraise "activesupport-5.1" do
|
8
|
-
gem "activesupport", "~> 5.1.0"
|
3
|
+
appraise "activesupport-6.1" do
|
4
|
+
gem "activesupport", "~> 6.1.0"
|
9
5
|
end
|
10
6
|
|
11
|
-
appraise "activesupport-
|
12
|
-
gem "activesupport", "~>
|
7
|
+
appraise "activesupport-6.0" do
|
8
|
+
gem "activesupport", "~> 6.0.0"
|
13
9
|
end
|
14
10
|
|
15
|
-
appraise "activesupport-
|
16
|
-
gem "activesupport", "~>
|
17
|
-
end
|
18
|
-
|
19
|
-
appraise "activesupport-4.1" do
|
20
|
-
gem "activesupport", "~> 4.1.0"
|
11
|
+
appraise "activesupport-5.2" do
|
12
|
+
gem "activesupport", "~> 5.2.0"
|
21
13
|
end
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
### 0.11.0 - (2019-08-16)
|
2
|
+
|
3
|
+
* enhancements
|
4
|
+
* Adds `Recurrence#during` to support recurrences within time-of-day ranges,
|
5
|
+
e.g. `Montrose.every(20.minutes).during("9am-5pm")`
|
6
|
+
|
7
|
+
### 0.10.1 - (2019-07-22)
|
8
|
+
|
9
|
+
* enhancements
|
10
|
+
* Adds `Schedule.dump` and `Schedule.load` to support ActiveRecord column
|
11
|
+
serialization
|
12
|
+
|
1
13
|
### 0.10.0 - (2019-07-17)
|
2
14
|
|
3
15
|
* enhancements
|
@@ -61,7 +73,7 @@
|
|
61
73
|
* Previously, the :between option served as a shorthand for :starts to :until.
|
62
74
|
Now, when both :starts and :between are provided, the recurrence will behave
|
63
75
|
as if anchored by the given :starts option, but filtered through the given
|
64
|
-
:
|
76
|
+
:between option.
|
65
77
|
* The :exclude_end option changes the default behavior of :until--when the
|
66
78
|
timestamp of the interval matches the :until timestamp, it will be included
|
67
79
|
by default unless the :exclude_end option is set to true.
|
data/Guardfile
CHANGED
@@ -25,8 +25,8 @@ guard :minitest do
|
|
25
25
|
|
26
26
|
# with Minitest::Spec
|
27
27
|
watch(%r{^spec/(.*)_spec\.rb$})
|
28
|
-
watch(%r{^lib/(.+)\.rb$})
|
29
|
-
watch(%r{^lib/(.+)\.rb$})
|
28
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
29
|
+
watch(%r{^lib/(.+)\.rb$}) { "spec/rfc_spec.rb" }
|
30
30
|
watch(%r{^spec/spec_helper\.rb$}) { "spec" }
|
31
31
|
end
|
32
32
|
|
data/README.md
CHANGED
@@ -277,6 +277,15 @@ Montrose.every(90.minutes, total: 4)
|
|
277
277
|
# every 20 minutes from 9:00 AM to 4:40 PM every day
|
278
278
|
Montrose.every(20.minutes, hour: 9..16)
|
279
279
|
|
280
|
+
# every 20 minutes from 9:00 AM to 4:40 PM every day with time-of-day precision
|
281
|
+
r = Montrose.every(20.minutes)
|
282
|
+
r.during("9am-4:40pm") # as semantic time-of-day range OR
|
283
|
+
r.during(time.change(hour: 9)..time.change(hour: 4: min: 40)) # as ruby time range OR
|
284
|
+
r.during([9, 0, 0], [16, 40, 0]) # as hour, min, sec tuple pairs for start, end
|
285
|
+
|
286
|
+
# every 20 minutes during multiple time-of-day ranges
|
287
|
+
Montrose.every(20.minutes).during("9am-12pm", "1pm-5pm")
|
288
|
+
|
280
289
|
# Minutely
|
281
290
|
Montrose.minutely
|
282
291
|
Montrose.r(every: :minute)
|
@@ -366,6 +375,40 @@ r.events.take(10).each { |date| puts date.to_s }
|
|
366
375
|
r.events.lazy.select { |time| time > 1.month.from_now }.take(3).each { |date| puts date.to_s }
|
367
376
|
```
|
368
377
|
|
378
|
+
### Combining recurrences
|
379
|
+
|
380
|
+
It may be necessary to combine several recurrence rules into a single
|
381
|
+
enumeration of events. For this purpose, there is `Montrose::Schedule`. To create a schedule of multiple recurrences:
|
382
|
+
|
383
|
+
```ruby
|
384
|
+
recurrence_1 = Montrose.monthly(day: { friday: [1] })
|
385
|
+
recurrence_2 = Montrose.weekly(on: :tuesday)
|
386
|
+
|
387
|
+
schedule = Montrose::Schedule.build do |s|
|
388
|
+
s << recurrence_1
|
389
|
+
s << recurrence_2
|
390
|
+
end
|
391
|
+
|
392
|
+
# add after building
|
393
|
+
s << Montrose.yearly
|
394
|
+
```
|
395
|
+
The `Schedule#<<` method also accepts valid recurrence options as hashes:
|
396
|
+
```ruby
|
397
|
+
schedule = Montrose::Schedule.build do |s|
|
398
|
+
s << { day: { friday: [1] } }
|
399
|
+
s << { on: :tuesday }
|
400
|
+
end
|
401
|
+
```
|
402
|
+
A schedule acts like a collection of recurrence rules that also behaves as a single
|
403
|
+
stream of events:
|
404
|
+
|
405
|
+
```ruby
|
406
|
+
schedule.events # => #<Enumerator: ...>
|
407
|
+
schedule.each do |event|
|
408
|
+
puts event
|
409
|
+
end
|
410
|
+
```
|
411
|
+
|
369
412
|
### Ruby on Rails
|
370
413
|
|
371
414
|
Instances of `Montrose::Recurrence` support the ActiveRecord serialization API so recurrence objects can be marshalled to and from a single database column:
|
@@ -374,6 +417,13 @@ Instances of `Montrose::Recurrence` support the ActiveRecord serialization API s
|
|
374
417
|
class RecurringEvent < ApplicationRecord
|
375
418
|
serialize :recurrence, Montrose::Recurrence
|
376
419
|
|
420
|
+
end
|
421
|
+
```
|
422
|
+
`Montrose::Schedule` can also be serialized:
|
423
|
+
```ruby
|
424
|
+
class RecurringEvent < ApplicationRecord
|
425
|
+
serialize :recurrence, Montrose::Schedule
|
426
|
+
|
377
427
|
end
|
378
428
|
```
|
379
429
|
|
@@ -396,6 +446,13 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
396
446
|
|
397
447
|
You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
398
448
|
|
449
|
+
To run tests against multiple versions of activesupport, use the Appraisals:
|
450
|
+
|
451
|
+
```sh
|
452
|
+
bin/appraisal install
|
453
|
+
bin/appraisal rake test
|
454
|
+
```
|
455
|
+
|
399
456
|
## Contributing
|
400
457
|
|
401
458
|
Bug reports and pull requests are welcome on GitHub at https://github.com/rossta/montrose. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
|
data/Rakefile
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
require "bundler/setup"
|
4
4
|
require "bundler/gem_tasks"
|
5
5
|
require "rake/testtask"
|
6
|
-
require "
|
6
|
+
require "standard/rake"
|
7
7
|
require "yard"
|
8
8
|
|
9
9
|
Rake::TestTask.new(:spec) do |t|
|
@@ -15,9 +15,7 @@ end
|
|
15
15
|
|
16
16
|
task test: :spec
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
task default: %i[spec rubocop]
|
18
|
+
task default: %i[spec standard]
|
21
19
|
|
22
20
|
namespace :doc do
|
23
21
|
desc "Generate docs and publish to gh-pages"
|
data/bin/setup
CHANGED
data/bin/standardrb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'standardrb' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("standard", "standardrb")
|
@@ -2,11 +2,15 @@
|
|
2
2
|
|
3
3
|
source "https://rubygems.org"
|
4
4
|
|
5
|
-
gem "activesupport", "~> 6.0.0
|
5
|
+
gem "activesupport", "~> 6.0.0"
|
6
6
|
|
7
7
|
group :development do
|
8
8
|
gem "coveralls"
|
9
9
|
gem "yard"
|
10
|
+
gem "guard"
|
11
|
+
gem "guard-minitest"
|
12
|
+
gem "guard-rubocop"
|
13
|
+
gem "pry-byebug"
|
10
14
|
end
|
11
15
|
|
12
16
|
gemspec path: "../"
|
@@ -2,11 +2,15 @@
|
|
2
2
|
|
3
3
|
source "https://rubygems.org"
|
4
4
|
|
5
|
-
gem "activesupport", "~>
|
5
|
+
gem "activesupport", "~> 6.1.0"
|
6
6
|
|
7
7
|
group :development do
|
8
8
|
gem "coveralls"
|
9
9
|
gem "yard"
|
10
|
+
gem "guard"
|
11
|
+
gem "guard-minitest"
|
12
|
+
gem "guard-rubocop"
|
13
|
+
gem "pry-byebug"
|
10
14
|
end
|
11
15
|
|
12
16
|
gemspec path: "../"
|
data/lib/montrose.rb
CHANGED
@@ -2,10 +2,13 @@
|
|
2
2
|
|
3
3
|
require "active_support"
|
4
4
|
require "active_support/core_ext/object"
|
5
|
-
|
5
|
+
|
6
6
|
require "active_support/core_ext/date"
|
7
|
-
require "active_support/core_ext/time"
|
8
7
|
require "active_support/core_ext/date_time"
|
8
|
+
require "active_support/core_ext/integer"
|
9
|
+
require "active_support/core_ext/numeric"
|
10
|
+
require "active_support/core_ext/string"
|
11
|
+
require "active_support/core_ext/time"
|
9
12
|
|
10
13
|
require "montrose/utils"
|
11
14
|
require "montrose/rule"
|
@@ -35,6 +38,23 @@ module Montrose
|
|
35
38
|
def recurrence(options = {})
|
36
39
|
branch(options)
|
37
40
|
end
|
38
|
-
|
41
|
+
alias_method :r, :recurrence
|
42
|
+
|
43
|
+
# Create a new recurrence from given options
|
44
|
+
# An alias to {Montrose::Recurrence.new}
|
45
|
+
attr_reader :enable_deprecated_between_masking
|
46
|
+
|
47
|
+
def enable_deprecated_between_masking=(value)
|
48
|
+
warn "[DEPRECATION] Montrose.enable_deprecated_between_masking is deprecated and will be removed in a future version."
|
49
|
+
@enable_deprecated_between_masking = value
|
50
|
+
end
|
51
|
+
|
52
|
+
def enable_deprecated_between_masking?
|
53
|
+
result = !!enable_deprecated_between_masking
|
54
|
+
if result
|
55
|
+
warn "[DEPRECATION] Legacy Montrose.between masking behavior is deprecated. Please use Montrose.covering instead to retain this behavior."
|
56
|
+
end
|
57
|
+
result
|
58
|
+
end
|
39
59
|
end
|
40
60
|
end
|
data/lib/montrose/chainable.rb
CHANGED
@@ -139,7 +139,7 @@ module Montrose
|
|
139
139
|
def starts(starts_at)
|
140
140
|
merge(starts: starts_at)
|
141
141
|
end
|
142
|
-
|
142
|
+
alias_method :starting, :starts
|
143
143
|
|
144
144
|
# Create a recurrence ending at given timestamp.
|
145
145
|
#
|
@@ -153,9 +153,12 @@ module Montrose
|
|
153
153
|
def until(ends_at)
|
154
154
|
merge(until: ends_at)
|
155
155
|
end
|
156
|
-
|
156
|
+
alias_method :ending, :until
|
157
157
|
|
158
|
-
# Create a recurrence occurring
|
158
|
+
# Create a recurrence occurring between the start and end
|
159
|
+
# of a given date range; :between is shorthand for separate
|
160
|
+
# :starts and :until options. When used with explicit :start
|
161
|
+
# and/or :until options, those will take precedence.
|
159
162
|
#
|
160
163
|
# @param [Range<Date>] date_range
|
161
164
|
#
|
@@ -168,6 +171,37 @@ module Montrose
|
|
168
171
|
merge(between: date_range)
|
169
172
|
end
|
170
173
|
|
174
|
+
# Create a recurrence which will only emit values within the
|
175
|
+
# date range, also called "masking."
|
176
|
+
#
|
177
|
+
# @param [Range<Date>] date_range
|
178
|
+
#
|
179
|
+
# @example
|
180
|
+
# Montrose.weekly.covering(Date.tomorrow..Date.new(2016, 3, 15))
|
181
|
+
#
|
182
|
+
# @return [Montrose::Recurrence]
|
183
|
+
#
|
184
|
+
def covering(date_range)
|
185
|
+
merge(covering: date_range)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Create a recurrence occurring within a time-of-day range or ranges.
|
189
|
+
# Given time ranges will parse as times-of-day and ignore given dates.
|
190
|
+
#
|
191
|
+
# @param [Range<Time>,String,Array<Array>] time-of-day range(s)
|
192
|
+
#
|
193
|
+
# @example
|
194
|
+
# Montrose.every(20.minutes).during("9am-5pm")
|
195
|
+
# Montrose.every(20.minutes).during(time.change(hour: 9)..time.change(hour: 5))
|
196
|
+
# Montrose.every(20.minutes).during([9, 0, 0], [17, 0, 0])
|
197
|
+
# Montrose.every(20.minutes).during("9am-12pm", "1pm-5pm")
|
198
|
+
#
|
199
|
+
# @return [Montrose::Recurrence]
|
200
|
+
#
|
201
|
+
def during(time_of_day, *extras)
|
202
|
+
merge(during: time_of_day.array_concat(extras))
|
203
|
+
end
|
204
|
+
|
171
205
|
# Create a recurrence through :on option
|
172
206
|
#
|
173
207
|
# @param day [Hash,Symbol] weekday or day of month as hash, e.g. { friday: 13 }
|
@@ -224,7 +258,7 @@ module Montrose
|
|
224
258
|
def day_of_month(days, *extras)
|
225
259
|
merge(mday: days.array_concat(extras))
|
226
260
|
end
|
227
|
-
|
261
|
+
alias_method :mday, :day_of_month
|
228
262
|
|
229
263
|
# Create a recurrence for given days of week
|
230
264
|
#
|
@@ -240,7 +274,7 @@ module Montrose
|
|
240
274
|
def day_of_week(weekdays, *extras)
|
241
275
|
merge(day: weekdays.array_concat(extras))
|
242
276
|
end
|
243
|
-
|
277
|
+
alias_method :day, :day_of_week
|
244
278
|
|
245
279
|
# Create a recurrence for given days of year
|
246
280
|
#
|
@@ -256,7 +290,7 @@ module Montrose
|
|
256
290
|
def day_of_year(days, *extras)
|
257
291
|
merge(yday: days.array_concat(extras))
|
258
292
|
end
|
259
|
-
|
293
|
+
alias_method :yday, :day_of_year
|
260
294
|
|
261
295
|
# Create a recurrence for given hours of day
|
262
296
|
#
|
@@ -272,7 +306,7 @@ module Montrose
|
|
272
306
|
def hour_of_day(hours, *extras)
|
273
307
|
merge(hour: hours.array_concat(extras))
|
274
308
|
end
|
275
|
-
|
309
|
+
alias_method :hour, :hour_of_day
|
276
310
|
|
277
311
|
# Create a recurrence for given months of year
|
278
312
|
#
|
@@ -288,7 +322,7 @@ module Montrose
|
|
288
322
|
def month_of_year(months, *extras)
|
289
323
|
merge(month: months.array_concat(extras))
|
290
324
|
end
|
291
|
-
|
325
|
+
alias_method :month, :month_of_year
|
292
326
|
|
293
327
|
# Create a recurrence for given weeks of year
|
294
328
|
#
|
@@ -318,7 +352,7 @@ module Montrose
|
|
318
352
|
def total(total)
|
319
353
|
merge(total: total)
|
320
354
|
end
|
321
|
-
|
355
|
+
alias_method :repeat, :total
|
322
356
|
|
323
357
|
# Create a new recurrence combining options of self
|
324
358
|
# and other. The value of entries with duplicate
|