montrose 0.11.0 → 0.13.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 +66 -0
- data/Appraisals +8 -12
- data/CHANGELOG.md +24 -0
- data/Guardfile +2 -2
- data/README.md +27 -15
- data/Rakefile +2 -4
- data/bin/setup +1 -0
- data/bin/standardrb +29 -0
- data/gemfiles/activesupport_5.2.gemfile +5 -1
- data/gemfiles/activesupport_6.0.gemfile +5 -1
- data/gemfiles/{activesupport_4.2.gemfile → activesupport_6.1.gemfile} +5 -1
- data/gemfiles/{activesupport_5.0.gemfile → activesupport_7.0.gemfile} +5 -1
- data/lib/montrose/chainable.rb +26 -10
- data/lib/montrose/clock.rb +4 -4
- data/lib/montrose/day.rb +83 -0
- data/lib/montrose/frequency.rb +58 -25
- data/lib/montrose/hour.rb +22 -0
- data/lib/montrose/ical.rb +128 -0
- data/lib/montrose/minute.rb +22 -0
- data/lib/montrose/month.rb +47 -0
- data/lib/montrose/month_day.rb +25 -0
- data/lib/montrose/options.rb +73 -79
- data/lib/montrose/recurrence.rb +40 -13
- data/lib/montrose/rule/between.rb +1 -1
- data/lib/montrose/rule/covering.rb +40 -0
- data/lib/montrose/rule/during.rb +7 -15
- data/lib/montrose/rule/minute_of_hour.rb +25 -0
- data/lib/montrose/rule/nth_day_of_month.rb +0 -2
- data/lib/montrose/rule/nth_day_of_year.rb +0 -2
- data/lib/montrose/rule/time_of_day.rb +1 -1
- data/lib/montrose/rule/until.rb +1 -1
- data/lib/montrose/rule.rb +18 -16
- data/lib/montrose/schedule.rb +6 -8
- data/lib/montrose/stack.rb +3 -4
- data/lib/montrose/time_of_day.rb +48 -0
- data/lib/montrose/utils.rb +2 -40
- data/lib/montrose/version.rb +1 -1
- data/lib/montrose/week.rb +20 -0
- data/lib/montrose/year_day.rb +25 -0
- data/lib/montrose.rb +43 -11
- data/montrose.gemspec +17 -17
- metadata +43 -36
- 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.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: c4b7914b70270912f8acf6757a8f130112d9137c50c34b933714f0edf97ac3c1
|
4
|
+
data.tar.gz: d70713d758bf62b45a7a74196f3d2c5bcc0a4bcf48b899164d7b5d4e2afe6b4a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5ea9a0877d09b1d360c3de7dc9b68a4fd9b6f64047728991d0398333777c18afa06f12fba1b2e178f53bd412f054314b125a174099a896c554cc53a337672fb5
|
7
|
+
data.tar.gz: f0b7431307846f0fa969352cfb98101ae2542ad37dd443e8324dd1480b01edfa81f8c06f92b6dc96f4278410f7cb221b7a1c1a487241a0faf8f5ee07fd763db3
|
@@ -0,0 +1,66 @@
|
|
1
|
+
version: 2.1
|
2
|
+
orbs:
|
3
|
+
ruby: circleci/ruby@1.3.0
|
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: 'cimg/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.6', '2.7', '3.0', '3.1']
|
57
|
+
gemfile:
|
58
|
+
[
|
59
|
+
'gemfiles/activesupport_5.2.gemfile',
|
60
|
+
'gemfiles/activesupport_6.0.gemfile',
|
61
|
+
'gemfiles/activesupport_6.1.gemfile',
|
62
|
+
'gemfiles/activesupport_7.0.gemfile',
|
63
|
+
]
|
64
|
+
exclude:
|
65
|
+
- ruby_version: '2.6'
|
66
|
+
gemfile: gemfiles/activesupport_7.0.gemfile
|
data/Appraisals
CHANGED
@@ -1,21 +1,17 @@
|
|
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-7.0" do
|
4
|
+
gem "activesupport", "~> 7.0"
|
9
5
|
end
|
10
6
|
|
11
|
-
appraise "activesupport-
|
12
|
-
gem "activesupport", "~>
|
7
|
+
appraise "activesupport-6.1" do
|
8
|
+
gem "activesupport", "~> 6.1"
|
13
9
|
end
|
14
10
|
|
15
|
-
appraise "activesupport-
|
16
|
-
gem "activesupport", "~>
|
11
|
+
appraise "activesupport-6.0" do
|
12
|
+
gem "activesupport", "~> 6.0"
|
17
13
|
end
|
18
14
|
|
19
|
-
appraise "activesupport-
|
20
|
-
gem "activesupport", "~>
|
15
|
+
appraise "activesupport-5.2" do
|
16
|
+
gem "activesupport", "~> 5.2"
|
21
17
|
end
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,27 @@
|
|
1
|
+
### 0.12.0 - (2021-02-02)
|
2
|
+
|
3
|
+
* enhancements
|
4
|
+
* Adds `Montrose.covering` to disambiguate `Montrose.between` behavior
|
5
|
+
`#covering` provides recurrence masking behavior, i.e., only recurrences
|
6
|
+
within the given range will be emitted
|
7
|
+
* Added support for ActiveSupport 6 and Ruby 2.7
|
8
|
+
* Adds `Montrose#infinite?` and ensures `Montrose.finite?` returns a boolean
|
9
|
+
|
10
|
+
* bug fixes
|
11
|
+
* Fixes `Recurrence#include?` behavior for infinite recurrences with
|
12
|
+
intervals > 1
|
13
|
+
|
14
|
+
* breaking changes
|
15
|
+
* `Montrose.between` no longer provides masking behavior, which is now
|
16
|
+
provided by `Montrose.covering`. A global option can be used
|
17
|
+
`Montrose.enable_deprecated_between_masking = true` to retain the legacy
|
18
|
+
behavior for `Montrose.between`. This option will be removed in v1.0.
|
19
|
+
* Dropped official support for EOL'd rubies and ActiveSupport < 5.2
|
20
|
+
|
21
|
+
* miscellaneous
|
22
|
+
* switched from Travis to CircleCi for builds
|
23
|
+
* switched default branch to `main`
|
24
|
+
#
|
1
25
|
### 0.11.0 - (2019-08-16)
|
2
26
|
|
3
27
|
* enhancements
|
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
@@ -4,10 +4,10 @@
|
|
4
4
|
[![Code Climate](https://codeclimate.com/github/rossta/montrose/badges/gpa.svg)](https://codeclimate.com/github/rossta/montrose)
|
5
5
|
[![Coverage Status](https://coveralls.io/repos/rossta/montrose/badge.svg?branch=master&service=github)](https://coveralls.io/github/rossta/montrose?branch=master)
|
6
6
|
|
7
|
-
Montrose is an easy-to-use library for defining recurring events in Ruby. It uses a simple chaining system for building recurrences, inspired heavily by the design principles of [HTTP.rb](https://github.com/httprb/http) and rule definitions available in [Recurrence](https://github.com/fnando/recurrence).
|
7
|
+
Montrose is an easy-to-use library for defining recurring events in Ruby. It uses a simple chaining system for building enumerable recurrences, inspired heavily by the design principles of [HTTP.rb](https://github.com/httprb/http) and rule definitions available in [Recurrence](https://github.com/fnando/recurrence).
|
8
8
|
|
9
|
-
|
10
|
-
|
9
|
+
- [Introductory blog post](http://bit.ly/1PA68Zb)
|
10
|
+
- [NYC.rb
|
11
11
|
presentation](https://speakerdeck.com/rossta/recurring-events-with-montrose)
|
12
12
|
|
13
13
|
## Installation
|
@@ -32,17 +32,17 @@ Dealing with recurring events is hard. `Montrose` provides a simple interface fo
|
|
32
32
|
|
33
33
|
More specifically, this project intends to:
|
34
34
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
35
|
+
- model recurring events in Ruby
|
36
|
+
- embrace Ruby idioms
|
37
|
+
- support recent Rubies
|
38
|
+
- be reasonably performant
|
39
|
+
- serialize to yaml, hash, and [ical](http://www.kanzaki.com/docs/ical/rrule.html#basic) formats
|
40
|
+
- be suitable for integration with persistence libraries
|
41
41
|
|
42
42
|
What `Montrose` doesn't do:
|
43
43
|
|
44
|
-
|
45
|
-
|
44
|
+
- support all calendaring use cases under the sun
|
45
|
+
- schedule recurring jobs for your Rails app. Use one of these instead: [cron](https://en.wikipedia.org/wiki/Cron), [rufus-scheduler](https://github.com/jmettraux/rufus-scheduler), [sidekiq-cron](https://github.com/ondrejbartas/sidekiq-cron), [sidetiq](https://github.com/tobiassvn/sidetiq), [whenever](https://github.com/javan/whenever)
|
46
46
|
|
47
47
|
## Concepts
|
48
48
|
|
@@ -392,13 +392,16 @@ end
|
|
392
392
|
# add after building
|
393
393
|
s << Montrose.yearly
|
394
394
|
```
|
395
|
+
|
395
396
|
The `Schedule#<<` method also accepts valid recurrence options as hashes:
|
397
|
+
|
396
398
|
```ruby
|
397
399
|
schedule = Montrose::Schedule.build do |s|
|
398
400
|
s << { day: { friday: [1] } }
|
399
401
|
s << { on: :tuesday }
|
400
402
|
end
|
401
403
|
```
|
404
|
+
|
402
405
|
A schedule acts like a collection of recurrence rules that also behaves as a single
|
403
406
|
stream of events:
|
404
407
|
|
@@ -419,7 +422,9 @@ class RecurringEvent < ApplicationRecord
|
|
419
422
|
|
420
423
|
end
|
421
424
|
```
|
425
|
+
|
422
426
|
`Montrose::Schedule` can also be serialized:
|
427
|
+
|
423
428
|
```ruby
|
424
429
|
class RecurringEvent < ApplicationRecord
|
425
430
|
serialize :recurrence, Montrose::Schedule
|
@@ -435,10 +440,10 @@ Montrose is named after the beautifully diverse and artistic [neighborhood in Ho
|
|
435
440
|
|
436
441
|
Check out following related projects, all of which have provided inspiration for `Montrose`.
|
437
442
|
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
443
|
+
- [ice_cube](https://github.com/seejohnrun/ice_cube)
|
444
|
+
- [recurrence](https://github.com/fnando/recurrence)
|
445
|
+
- [runt](https://github.com/mlipper/runt)
|
446
|
+
- [http.rb](https://github.com/httprb/http) - not a recurrence project, but inspirational to design, implementation, and interface of `Montrose`
|
442
447
|
|
443
448
|
## Development
|
444
449
|
|
@@ -446,6 +451,13 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
446
451
|
|
447
452
|
You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
448
453
|
|
454
|
+
To run tests against multiple versions of activesupport, use the Appraisals:
|
455
|
+
|
456
|
+
```sh
|
457
|
+
bin/appraisal install
|
458
|
+
bin/appraisal rake test
|
459
|
+
```
|
460
|
+
|
449
461
|
## Contributing
|
450
462
|
|
451
463
|
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", "~> 5.2
|
5
|
+
gem "activesupport", "~> 5.2"
|
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", "~> 6.0
|
5
|
+
gem "activesupport", "~> 6.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"
|
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", "~> 7.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/chainable.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "montrose/options"
|
4
3
|
require "montrose/refinements/array_concat"
|
5
4
|
|
6
5
|
module Montrose
|
@@ -139,7 +138,7 @@ module Montrose
|
|
139
138
|
def starts(starts_at)
|
140
139
|
merge(starts: starts_at)
|
141
140
|
end
|
142
|
-
|
141
|
+
alias_method :starting, :starts
|
143
142
|
|
144
143
|
# Create a recurrence ending at given timestamp.
|
145
144
|
#
|
@@ -153,9 +152,12 @@ module Montrose
|
|
153
152
|
def until(ends_at)
|
154
153
|
merge(until: ends_at)
|
155
154
|
end
|
156
|
-
|
155
|
+
alias_method :ending, :until
|
157
156
|
|
158
|
-
# Create a recurrence occurring
|
157
|
+
# Create a recurrence occurring between the start and end
|
158
|
+
# of a given date range; :between is shorthand for separate
|
159
|
+
# :starts and :until options. When used with explicit :start
|
160
|
+
# and/or :until options, those will take precedence.
|
159
161
|
#
|
160
162
|
# @param [Range<Date>] date_range
|
161
163
|
#
|
@@ -168,6 +170,20 @@ module Montrose
|
|
168
170
|
merge(between: date_range)
|
169
171
|
end
|
170
172
|
|
173
|
+
# Create a recurrence which will only emit values within the
|
174
|
+
# date range, also called "masking."
|
175
|
+
#
|
176
|
+
# @param [Range<Date>] date_range
|
177
|
+
#
|
178
|
+
# @example
|
179
|
+
# Montrose.weekly.covering(Date.tomorrow..Date.new(2016, 3, 15))
|
180
|
+
#
|
181
|
+
# @return [Montrose::Recurrence]
|
182
|
+
#
|
183
|
+
def covering(date_range)
|
184
|
+
merge(covering: date_range)
|
185
|
+
end
|
186
|
+
|
171
187
|
# Create a recurrence occurring within a time-of-day range or ranges.
|
172
188
|
# Given time ranges will parse as times-of-day and ignore given dates.
|
173
189
|
#
|
@@ -241,7 +257,7 @@ module Montrose
|
|
241
257
|
def day_of_month(days, *extras)
|
242
258
|
merge(mday: days.array_concat(extras))
|
243
259
|
end
|
244
|
-
|
260
|
+
alias_method :mday, :day_of_month
|
245
261
|
|
246
262
|
# Create a recurrence for given days of week
|
247
263
|
#
|
@@ -257,7 +273,7 @@ module Montrose
|
|
257
273
|
def day_of_week(weekdays, *extras)
|
258
274
|
merge(day: weekdays.array_concat(extras))
|
259
275
|
end
|
260
|
-
|
276
|
+
alias_method :day, :day_of_week
|
261
277
|
|
262
278
|
# Create a recurrence for given days of year
|
263
279
|
#
|
@@ -273,7 +289,7 @@ module Montrose
|
|
273
289
|
def day_of_year(days, *extras)
|
274
290
|
merge(yday: days.array_concat(extras))
|
275
291
|
end
|
276
|
-
|
292
|
+
alias_method :yday, :day_of_year
|
277
293
|
|
278
294
|
# Create a recurrence for given hours of day
|
279
295
|
#
|
@@ -289,7 +305,7 @@ module Montrose
|
|
289
305
|
def hour_of_day(hours, *extras)
|
290
306
|
merge(hour: hours.array_concat(extras))
|
291
307
|
end
|
292
|
-
|
308
|
+
alias_method :hour, :hour_of_day
|
293
309
|
|
294
310
|
# Create a recurrence for given months of year
|
295
311
|
#
|
@@ -305,7 +321,7 @@ module Montrose
|
|
305
321
|
def month_of_year(months, *extras)
|
306
322
|
merge(month: months.array_concat(extras))
|
307
323
|
end
|
308
|
-
|
324
|
+
alias_method :month, :month_of_year
|
309
325
|
|
310
326
|
# Create a recurrence for given weeks of year
|
311
327
|
#
|
@@ -335,7 +351,7 @@ module Montrose
|
|
335
351
|
def total(total)
|
336
352
|
merge(total: total)
|
337
353
|
end
|
338
|
-
|
354
|
+
alias_method :repeat, :total
|
339
355
|
|
340
356
|
# Create a new recurrence combining options of self
|
341
357
|
# and other. The value of entries with duplicate
|
data/lib/montrose/clock.rb
CHANGED
@@ -26,7 +26,7 @@ module Montrose
|
|
26
26
|
if @at
|
27
27
|
times = @at.map { |hour, min, sec = 0| @time.change(hour: hour, min: min, sec: sec) }
|
28
28
|
|
29
|
-
min_next = times.select { |t| t > @time }.min
|
29
|
+
(min_next = times.select { |t| t > @time }.min) && (return min_next)
|
30
30
|
|
31
31
|
advance_step(times.min || @time)
|
32
32
|
else
|
@@ -41,7 +41,7 @@ module Montrose
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def step
|
44
|
-
@step ||= smallest_step
|
44
|
+
(@step ||= smallest_step) || fail(ConfigurationError, "No step for #{@options.inspect}")
|
45
45
|
end
|
46
46
|
|
47
47
|
def smallest_step
|
@@ -76,9 +76,9 @@ module Montrose
|
|
76
76
|
is_frequency = @every == unit
|
77
77
|
if ([unit] + alternates).any? { |u| @options.key?(u) } && !is_frequency
|
78
78
|
# smallest unit, increment by 1
|
79
|
-
{
|
79
|
+
{step_key(unit) => 1}
|
80
80
|
elsif is_frequency
|
81
|
-
{
|
81
|
+
{step_key(unit) => @interval}
|
82
82
|
end
|
83
83
|
end
|
84
84
|
|
data/lib/montrose/day.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
module Montrose
|
2
|
+
class Day
|
3
|
+
extend Montrose::Utils
|
4
|
+
|
5
|
+
NAMES = ::Date::DAYNAMES
|
6
|
+
TWO_LETTER_ABBREVIATIONS = %w[SU MO TU WE TH FR SA].freeze
|
7
|
+
THREE_LETTER_ABBREVIATIONS = %w[SUN MON TUE WED THU FRI SAT]
|
8
|
+
NUMBERS = NAMES.map.with_index { |_n, i| i.to_s }
|
9
|
+
|
10
|
+
ICAL_MATCH = /(?<ordinal>[+-]?\d+)?(?<day>[A-Z]{2})/ # e.g. 1FR
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def parse(arg)
|
14
|
+
case arg
|
15
|
+
when Hash
|
16
|
+
parse_entries(arg.entries)
|
17
|
+
when String
|
18
|
+
parse(arg.split(","))
|
19
|
+
else
|
20
|
+
parse_entries(map_arg(arg) { |value| parse_value(value) })
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def parse_entries(entries)
|
25
|
+
hash = Hash.new { |h, k| h[k] = [] }
|
26
|
+
result = entries.each_with_object(hash) { |(k, v), hash|
|
27
|
+
index = number!(k)
|
28
|
+
hash[index] = hash[index] + [*v]
|
29
|
+
}
|
30
|
+
result.values.all?(&:empty?) ? result.keys : result
|
31
|
+
end
|
32
|
+
|
33
|
+
def parse_value(value)
|
34
|
+
parse_ical(value) || [number!(value), nil]
|
35
|
+
end
|
36
|
+
|
37
|
+
def parse_ical(value)
|
38
|
+
(match = ICAL_MATCH.match(value.to_s)) || (return nil)
|
39
|
+
index = number!(match[:day])
|
40
|
+
ordinal = match[:ordinal]&.to_i
|
41
|
+
[index, ordinal]
|
42
|
+
end
|
43
|
+
|
44
|
+
def map_arg(arg, &block)
|
45
|
+
return nil unless arg.present?
|
46
|
+
|
47
|
+
Array(arg).map(&block)
|
48
|
+
end
|
49
|
+
|
50
|
+
def names
|
51
|
+
NAMES
|
52
|
+
end
|
53
|
+
|
54
|
+
def number(name)
|
55
|
+
case name
|
56
|
+
when 0..6
|
57
|
+
name
|
58
|
+
when Symbol, String
|
59
|
+
string = name.to_s.downcase
|
60
|
+
NAMES.index(string.titleize) ||
|
61
|
+
TWO_LETTER_ABBREVIATIONS.index(string.upcase) ||
|
62
|
+
THREE_LETTER_ABBREVIATIONS.index(string.upcase) ||
|
63
|
+
number(to_index(string))
|
64
|
+
when Array
|
65
|
+
number name.first
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def number!(name)
|
70
|
+
number(name) || raise(ConfigurationError,
|
71
|
+
"Did not recognize day #{name}, must be one of #{(names + abbreviations + numbers).inspect}")
|
72
|
+
end
|
73
|
+
|
74
|
+
def numbers
|
75
|
+
NUMBERS
|
76
|
+
end
|
77
|
+
|
78
|
+
def abbreviations
|
79
|
+
TWO_LETTER_ABBREVIATIONS + THREE_LETTER_ABBREVIATIONS
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|