montrose 0.11.2 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a45d4d1eab774adb13047453df886e0dcd9945e79310f2f55a42358d9b1d756b
4
- data.tar.gz: e494612344c6e6d156e3372c96e04d57dbcd4a17eee541db252f610c913f8c60
3
+ metadata.gz: d44082eaa05a068714f05fe6116f8442b0507b3c7612174d93716ce6fd4af4ba
4
+ data.tar.gz: 691d88452db689b1555772b6749ae12e72bf675fbc55d4082b2d7f4cd41dd795
5
5
  SHA512:
6
- metadata.gz: 489f9c03d2ef5cb32347bc9b5f97f96a4d11e8dcd3fae2dd948a39bae0228c8a08e34e39005d62e5287d3226f81c427caf7cdcd8ae7ee2f8d864f17f7b8069ee
7
- data.tar.gz: e383994b155489b5fcc5e6050165f1103842b974dbfccad4dc6583702ec064dcea43243e106376520b410136db8c60d218173b57b3fa624452a1f605e35bb526
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-5.2" do
4
- gem "activesupport", "~> 5.2.0"
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-5.0" do
12
- gem "activesupport", "~> 5.0.0"
7
+ appraise "activesupport-6.0" do
8
+ gem "activesupport", "~> 6.0.0"
13
9
  end
14
10
 
15
- appraise "activesupport-4.2" do
16
- gem "activesupport", "~> 4.2.0"
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/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$}) { |m| "spec/#{m[1]}_spec.rb" }
29
- watch(%r{^lib/(.+)\.rb$}) { "spec/rfc_spec.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
@@ -446,6 +446,13 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
446
446
 
447
447
  You can also run `bin/console` for an interactive prompt that will allow you to experiment.
448
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
+
449
456
  ## Contributing
450
457
 
451
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 "rubocop/rake_task"
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
- RuboCop::RakeTask.new
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
@@ -3,5 +3,6 @@ set -euo pipefail
3
3
  IFS=$'\n\t'
4
4
 
5
5
  bundle install
6
+ bundle update
6
7
 
7
8
  # Do any other automated setup that you need to do here
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")
@@ -7,6 +7,10 @@ gem "activesupport", "~> 5.2.0"
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: "../"
@@ -7,6 +7,10 @@ gem "activesupport", "~> 6.0.0"
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", "~> 4.2.0"
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
- require "active_support/core_ext/numeric"
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
- alias r recurrence
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
@@ -139,7 +139,7 @@ module Montrose
139
139
  def starts(starts_at)
140
140
  merge(starts: starts_at)
141
141
  end
142
- alias starting starts
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
- alias ending until
156
+ alias_method :ending, :until
157
157
 
158
- # Create a recurrence occurring during date range.
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,20 @@ 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
+
171
188
  # Create a recurrence occurring within a time-of-day range or ranges.
172
189
  # Given time ranges will parse as times-of-day and ignore given dates.
173
190
  #
@@ -241,7 +258,7 @@ module Montrose
241
258
  def day_of_month(days, *extras)
242
259
  merge(mday: days.array_concat(extras))
243
260
  end
244
- alias mday day_of_month
261
+ alias_method :mday, :day_of_month
245
262
 
246
263
  # Create a recurrence for given days of week
247
264
  #
@@ -257,7 +274,7 @@ module Montrose
257
274
  def day_of_week(weekdays, *extras)
258
275
  merge(day: weekdays.array_concat(extras))
259
276
  end
260
- alias day day_of_week
277
+ alias_method :day, :day_of_week
261
278
 
262
279
  # Create a recurrence for given days of year
263
280
  #
@@ -273,7 +290,7 @@ module Montrose
273
290
  def day_of_year(days, *extras)
274
291
  merge(yday: days.array_concat(extras))
275
292
  end
276
- alias yday day_of_year
293
+ alias_method :yday, :day_of_year
277
294
 
278
295
  # Create a recurrence for given hours of day
279
296
  #
@@ -289,7 +306,7 @@ module Montrose
289
306
  def hour_of_day(hours, *extras)
290
307
  merge(hour: hours.array_concat(extras))
291
308
  end
292
- alias hour hour_of_day
309
+ alias_method :hour, :hour_of_day
293
310
 
294
311
  # Create a recurrence for given months of year
295
312
  #
@@ -305,7 +322,7 @@ module Montrose
305
322
  def month_of_year(months, *extras)
306
323
  merge(month: months.array_concat(extras))
307
324
  end
308
- alias month month_of_year
325
+ alias_method :month, :month_of_year
309
326
 
310
327
  # Create a recurrence for given weeks of year
311
328
  #
@@ -335,7 +352,7 @@ module Montrose
335
352
  def total(total)
336
353
  merge(total: total)
337
354
  end
338
- alias repeat total
355
+ alias_method :repeat, :total
339
356
 
340
357
  # Create a new recurrence combining options of self
341
358
  # and other. The value of entries with duplicate
@@ -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 and return min_next
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 or fail ConfigurationError, "No step for #{@options.inspect}"
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
- { step_key(unit) => 1 }
79
+ {step_key(unit) => 1}
80
80
  elsif is_frequency
81
- { step_key(unit) => @interval }
81
+ {step_key(unit) => @interval}
82
82
  end
83
83
  end
84
84
 
@@ -30,17 +30,17 @@ module Montrose
30
30
  #
31
31
  def self.from_options(opts)
32
32
  frequency = opts.fetch(:every) { fail ConfigurationError, "Please specify the :every option" }
33
- class_name = FREQUENCY_TERMS.fetch(frequency.to_s) do
33
+ class_name = FREQUENCY_TERMS.fetch(frequency.to_s) {
34
34
  fail "Don't know how to enumerate every: #{frequency}"
35
- end
35
+ }
36
36
 
37
37
  Montrose::Frequency.const_get(class_name).new(opts)
38
38
  end
39
39
 
40
40
  # @private
41
41
  def self.assert(frequency)
42
- FREQUENCY_TERMS.key?(frequency.to_s) or fail ConfigurationError,
43
- "Don't know how to enumerate every: #{frequency}"
42
+ FREQUENCY_TERMS.key?(frequency.to_s) || fail(ConfigurationError,
43
+ "Don't know how to enumerate every: #{frequency}")
44
44
 
45
45
  frequency.to_sym
46
46
  end
@@ -86,6 +86,7 @@ module Montrose
86
86
  def_option :starts
87
87
  def_option :until
88
88
  def_option :between
89
+ def_option :covering
89
90
  def_option :during
90
91
  def_option :hour
91
92
  def_option :day
@@ -120,12 +121,12 @@ module Montrose
120
121
  end
121
122
 
122
123
  def to_hash
123
- hash_pairs = self.class.defined_options.flat_map do |opt_name|
124
+ hash_pairs = self.class.defined_options.flat_map { |opt_name|
124
125
  [opt_name, send(opt_name)]
125
- end
126
+ }
126
127
  Hash[*hash_pairs].reject { |_k, v| v.nil? }
127
128
  end
128
- alias to_h to_hash
129
+ alias_method :to_h, :to_hash
129
130
 
130
131
  def []=(option, val)
131
132
  send(:"#{option}=", val)
@@ -142,13 +143,13 @@ module Montrose
142
143
  self.class.new(h1.merge(h2))
143
144
  end
144
145
 
145
- def fetch(key, *args, &_block)
146
- fail ArgumentError, "wrong number of arguments (#{args.length} for 1..2)" if args.length > 1
146
+ def fetch(key, *args)
147
+ raise ArgumentError, "wrong number of arguments (#{args.length} for 1..2)" if args.length > 1
147
148
 
148
149
  found = send(key)
149
150
  return found if found
150
151
  return args.first if args.length == 1
151
- fail "Key #{key.inspect} not found" unless block_given?
152
+ raise "Key #{key.inspect} not found" unless block
152
153
 
153
154
  yield
154
155
  end
@@ -165,8 +166,8 @@ module Montrose
165
166
  @every = parsed.fetch(:every)
166
167
  end
167
168
 
168
- alias frequency every
169
- alias frequency= every=
169
+ alias_method :frequency, :every
170
+ alias_method :frequency=, :every=
170
171
 
171
172
  def starts=(time)
172
173
  @starts = normalize_time(as_time(time)) || default_starts
@@ -186,7 +187,7 @@ module Montrose
186
187
  [decompose_during_arg(during)]
187
188
  else
188
189
  map_arg(during) { |d| decompose_during_arg(d) }
189
- end
190
+ end
190
191
  end
191
192
 
192
193
  def day=(days)
@@ -210,7 +211,9 @@ module Montrose
210
211
  end
211
212
 
212
213
  def between=(range)
213
- @between = range
214
+ if Montrose.enable_deprecated_between_masking?
215
+ @covering = range
216
+ end
214
217
  self[:starts] = range.first unless self[:starts]
215
218
  self[:until] = range.last unless self[:until]
216
219
  end
@@ -312,7 +315,7 @@ module Montrose
312
315
  result[:mday] += map_mdays(v)
313
316
  end
314
317
  else
315
- { day: map_days(arg) }
318
+ {day: map_days(arg)}
316
319
  end
317
320
  end
318
321
 
@@ -323,12 +326,12 @@ module Montrose
323
326
  day = day_number(key)
324
327
  return [:day, day] if day
325
328
 
326
- fail ConfigurationError, "Did not recognize #{key} as a month or day"
329
+ raise ConfigurationError, "Did not recognize #{key} as a month or day"
327
330
  end
328
331
 
329
332
  def assert_range_includes(range, item, absolute = false)
330
333
  test = absolute ? item.abs : item
331
- fail ConfigurationError, "Out of range: #{range.inspect} does not include #{test}" unless range.include?(test)
334
+ raise ConfigurationError, "Out of range: #{range.inspect} does not include #{test}" unless range.include?(test)
332
335
 
333
336
  item
334
337
  end
@@ -343,18 +346,18 @@ module Montrose
343
346
  def parse_frequency(input)
344
347
  if input.respond_to?(:parts)
345
348
  frequency, interval = duration_to_frequency_parts(input)
346
- { every: frequency.to_s.singularize.to_sym, interval: interval }
349
+ {every: frequency.to_s.singularize.to_sym, interval: interval}
347
350
  elsif input.is_a?(Numeric)
348
351
  frequency, interval = numeric_to_frequency_parts(input)
349
- { every: frequency, interval: interval }
352
+ {every: frequency, interval: interval}
350
353
  else
351
- { every: Frequency.assert(input) }
354
+ {every: Frequency.assert(input)}
352
355
  end
353
356
  end
354
357
 
355
358
  def numeric_to_frequency_parts(number)
356
359
  parts = nil
357
- [:year, :month, :week, :day, :hour, :minute].each do |freq|
360
+ %i[year month week day hour minute].each do |freq|
358
361
  div, mod = number.divmod(1.send(freq))
359
362
  parts = [freq, div]
360
363
  return parts if mod.zero?
@@ -371,7 +374,7 @@ module Montrose
371
374
  when Range
372
375
  [decompose_during_arg(during.first), decompose_during_arg(during.last)]
373
376
  when String
374
- during.split(%r{[-—–]}).map { |d| as_time_parts(d) }
377
+ during.split(/[-—–]/).map { |d| as_time_parts(d) }
375
378
  else
376
379
  as_time_parts(during)
377
380
  end
@@ -238,7 +238,7 @@ module Montrose
238
238
  else
239
239
  fail SerializationError,
240
240
  "Object was supposed to be a #{self}, but was a #{obj.class}. -- #{obj.inspect}"
241
- end
241
+ end
242
242
 
243
243
  JSON.dump(hash)
244
244
  end
@@ -308,7 +308,7 @@ module Montrose
308
308
  def to_hash
309
309
  default_options.to_hash
310
310
  end
311
- alias to_h to_hash
311
+ alias_method :to_h, :to_hash
312
312
 
313
313
  # Returns json string of options used to create the recurrence
314
314
  #
@@ -346,20 +346,41 @@ module Montrose
346
346
  def include?(timestamp)
347
347
  return false if earlier?(timestamp) || later?(timestamp)
348
348
 
349
- recurrence = finite? ? self : starts(timestamp)
349
+ recurrence = finite? ? self : fast_forward(timestamp)
350
350
 
351
351
  recurrence.events.lazy.each do |event|
352
352
  return true if event == timestamp
353
353
  return false if event > timestamp
354
- end or false
354
+ end || false
355
355
  end
356
356
 
357
- # Return true/false if recurrence will iterate infinitely
357
+ def fast_forward(timestamp)
358
+ return starts(timestamp) unless starts_at.present?
359
+
360
+ interval = default_options[:interval]
361
+ frequency = default_options[:every]
362
+ duration = interval.send(frequency)
363
+
364
+ # Calculate nearest earlier time ahead matching frequency * interval
365
+ jump = ((timestamp - starts_at) / duration).floor * duration
366
+
367
+ starts(starts_at + jump)
368
+ end
369
+
370
+ # Return true/false if recurrence will terminate
358
371
  #
359
- # @return [Boolean] whether or not recurrence is infinite
372
+ # @return [Boolean] returns true if recurrence has an end
360
373
  #
361
374
  def finite?
362
- ends_at || length
375
+ !infinite?
376
+ end
377
+
378
+ # Return true/false if recurrence will iterate infinitely
379
+ #
380
+ # @return [Boolean] returns true if recurrence has no end
381
+ #
382
+ def infinite?
383
+ !ends_at && !length
363
384
  end
364
385
 
365
386
  # Return true/false if given timestamp occurs before
@@ -391,7 +412,7 @@ module Montrose
391
412
  loop do
392
413
  stack.advance(clock.tick) do |time|
393
414
  yielder << time
394
- end or break
415
+ end || break
395
416
  end
396
417
  end
397
418
  end
data/lib/montrose/rule.rb CHANGED
@@ -36,7 +36,7 @@ module Montrose
36
36
  end
37
37
 
38
38
  require "montrose/rule/after"
39
- require "montrose/rule/between"
39
+ require "montrose/rule/covering"
40
40
  require "montrose/rule/day_of_month"
41
41
  require "montrose/rule/day_of_week"
42
42
  require "montrose/rule/day_of_year"
@@ -22,7 +22,7 @@ module Montrose
22
22
  end
23
23
 
24
24
  def continue?(time)
25
- @between.max > time
25
+ time < @between.max
26
26
  end
27
27
  end
28
28
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Montrose
4
+ module Rule
5
+ class Covering
6
+ include Montrose::Rule
7
+
8
+ def self.apply_options(opts)
9
+ opts[:covering].is_a?(Range) && opts[:covering]
10
+ end
11
+
12
+ # Initializes rule
13
+ #
14
+ # @param [Range] covering - timestamp range
15
+ #
16
+ def initialize(covering)
17
+ @covering = case covering.first
18
+ when Date
19
+ DateRange.new(covering)
20
+ else
21
+ covering
22
+ end
23
+ end
24
+
25
+ def include?(time)
26
+ @covering.include?(time)
27
+ end
28
+
29
+ def continue?(time)
30
+ time < @covering.last
31
+ end
32
+
33
+ class DateRange < SimpleDelegator
34
+ def include?(time)
35
+ __getobj__.include?(time.to_date)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -8,7 +8,7 @@ module Montrose
8
8
  def self.apply_options(opts)
9
9
  return false unless opts[:until]
10
10
 
11
- { until: opts[:until], exclude_end: opts.fetch(:exclude_end, false) }
11
+ {until: opts[:until], exclude_end: opts.fetch(:exclude_end, false)}
12
12
  end
13
13
 
14
14
  def initialize(opts)
@@ -42,7 +42,7 @@ module Montrose
42
42
  else
43
43
  fail SerializationError,
44
44
  "Object was supposed to be a #{self}, but was a #{obj.class}. -- #{obj.inspect}"
45
- end
45
+ end
46
46
 
47
47
  JSON.dump(array)
48
48
  end
@@ -77,7 +77,7 @@ module Montrose
77
77
 
78
78
  self
79
79
  end
80
- alias add <<
80
+ alias_method :add, :<<
81
81
 
82
82
  # Return true/false if given timestamp is included in any of the rules
83
83
  # found in the schedule
@@ -127,7 +127,7 @@ module Montrose
127
127
  enums = @rules.map { |r| r.merge(opts).events }
128
128
  Enumerator.new do |y|
129
129
  loop do
130
- enum = active_enums(enums).min_by(&:peek) or break
130
+ (enum = active_enums(enums).min_by(&:peek)) || break
131
131
  y << enum.next
132
132
  end
133
133
  end
@@ -173,11 +173,9 @@ module Montrose
173
173
 
174
174
  def active_enums(enums)
175
175
  enums.select do |e|
176
- begin
177
- e.peek
178
- rescue StopIteration
179
- false
180
- end
176
+ e.peek
177
+ rescue StopIteration
178
+ false
181
179
  end
182
180
  end
183
181
  end
@@ -13,7 +13,7 @@ module Montrose
13
13
  Frequency,
14
14
  Rule::After,
15
15
  Rule::Until,
16
- Rule::Between,
16
+ Rule::Covering,
17
17
  Rule::During,
18
18
  Rule::Except,
19
19
  Rule::Total,
@@ -44,7 +44,7 @@ module Montrose
44
44
  yes, no = @stack.partition { |rule| rule.include?(time) }
45
45
 
46
46
  if no.empty?
47
- yes.all? { |rule| rule.advance!(time) } or return false
47
+ yes.all? { |rule| rule.advance!(time) } || (return false)
48
48
  puts time if ENV["DEBUG"]
49
49
  yield time if block_given?
50
50
  true
@@ -29,7 +29,7 @@ module Montrose
29
29
 
30
30
  # Recurrence at fractions of a second are not recognized
31
31
  def normalize_time(time)
32
- time && time.change(usec: 0)
32
+ time&.change(usec: 0)
33
33
  end
34
34
 
35
35
  def as_date(time)
@@ -56,8 +56,8 @@ module Montrose
56
56
 
57
57
  def month_number!(name)
58
58
  month_numbers = MONTHS.map.with_index { |_n, i| i.to_s }.slice(1, 12)
59
- month_number(name) or fail ConfigurationError,
60
- "Did not recognize month #{name}, must be one of #{(MONTHS + month_numbers).inspect}"
59
+ month_number(name) || raise(ConfigurationError,
60
+ "Did not recognize month #{name}, must be one of #{(MONTHS + month_numbers).inspect}")
61
61
  end
62
62
 
63
63
  def day_number(name)
@@ -74,8 +74,8 @@ module Montrose
74
74
 
75
75
  def day_number!(name)
76
76
  day_numbers = DAYS.map.with_index { |_n, i| i.to_s }
77
- day_number(name) or fail ConfigurationError,
78
- "Did not recognize day #{name}, must be one of #{(DAYS + day_numbers).inspect}"
77
+ day_number(name) || raise(ConfigurationError,
78
+ "Did not recognize day #{name}, must be one of #{(DAYS + day_numbers).inspect}")
79
79
  end
80
80
 
81
81
  def days_in_month(month, year = current_time.year)
@@ -93,7 +93,7 @@ module Montrose
93
93
  # Returns string.to_i only if string fully matches an integer
94
94
  # otherwise ensures that return value won't match a valid index
95
95
  def to_index(string)
96
- string =~ %r{^\d+} ? string.to_i : -1
96
+ /^\d+/.match?(string) ? string.to_i : -1
97
97
  end
98
98
  end
99
99
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Montrose
4
- VERSION = "0.11.2"
4
+ VERSION = "0.12.0"
5
5
  end
data/montrose.gemspec CHANGED
@@ -5,29 +5,29 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
  require "montrose/version"
6
6
 
7
7
  Gem::Specification.new do |spec|
8
- spec.name = "montrose"
9
- spec.version = Montrose::VERSION
10
- spec.authors = ["Ross Kaffenberger"]
11
- spec.email = ["rosskaff@gmail.com"]
8
+ spec.name = "montrose"
9
+ spec.version = Montrose::VERSION
10
+ spec.authors = ["Ross Kaffenberger"]
11
+ spec.email = ["rosskaff@gmail.com"]
12
12
 
13
- spec.summary = "Recurring events in Ruby"
14
- spec.description = "A library for specifying, quering, and enumerating recurring events for calendars in Ruby."
15
- spec.homepage = "https://github.com/rossta/montrose"
16
- spec.license = "MIT"
13
+ spec.summary = "Recurring events in Ruby"
14
+ spec.description = "A library for specifying, quering, and enumerating recurring events for calendars in Ruby."
15
+ spec.homepage = "https://github.com/rossta/montrose"
16
+ spec.license = "MIT"
17
17
 
18
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
- spec.bindir = "exe"
20
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
+ spec.bindir = "exe"
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
21
  spec.require_paths = ["lib"]
22
22
 
23
- spec.required_ruby_version = ">= 2.3.0"
23
+ spec.required_ruby_version = ">= 2.5.0"
24
24
 
25
- spec.add_dependency "activesupport", ">= 4.1", "<= 7.0"
25
+ spec.add_dependency "activesupport", ">= 5.2", "<= 7.0"
26
26
 
27
- spec.add_development_dependency "appraisal", "~> 2.2.0"
28
- spec.add_development_dependency "m", "~> 1.5"
27
+ spec.add_development_dependency "appraisal"
28
+ spec.add_development_dependency "m"
29
29
  spec.add_development_dependency "minitest"
30
- spec.add_development_dependency "rake", "~> 11.0"
31
- spec.add_development_dependency "rubocop", "~> 0.64.0"
30
+ spec.add_development_dependency "rake", ">= 12.3.3"
31
+ spec.add_development_dependency "standard"
32
32
  spec.add_development_dependency "timecop"
33
33
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: montrose
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.2
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ross Kaffenberger
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-11-25 00:00:00.000000000 Z
11
+ date: 2021-02-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -16,7 +16,7 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '4.1'
19
+ version: '5.2'
20
20
  - - "<="
21
21
  - !ruby/object:Gem::Version
22
22
  version: '7.0'
@@ -26,7 +26,7 @@ dependencies:
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: '4.1'
29
+ version: '5.2'
30
30
  - - "<="
31
31
  - !ruby/object:Gem::Version
32
32
  version: '7.0'
@@ -34,30 +34,30 @@ dependencies:
34
34
  name: appraisal
35
35
  requirement: !ruby/object:Gem::Requirement
36
36
  requirements:
37
- - - "~>"
37
+ - - ">="
38
38
  - !ruby/object:Gem::Version
39
- version: 2.2.0
39
+ version: '0'
40
40
  type: :development
41
41
  prerelease: false
42
42
  version_requirements: !ruby/object:Gem::Requirement
43
43
  requirements:
44
- - - "~>"
44
+ - - ">="
45
45
  - !ruby/object:Gem::Version
46
- version: 2.2.0
46
+ version: '0'
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: m
49
49
  requirement: !ruby/object:Gem::Requirement
50
50
  requirements:
51
- - - "~>"
51
+ - - ">="
52
52
  - !ruby/object:Gem::Version
53
- version: '1.5'
53
+ version: '0'
54
54
  type: :development
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  requirements:
58
- - - "~>"
58
+ - - ">="
59
59
  - !ruby/object:Gem::Version
60
- version: '1.5'
60
+ version: '0'
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: minitest
63
63
  requirement: !ruby/object:Gem::Requirement
@@ -76,30 +76,30 @@ dependencies:
76
76
  name: rake
77
77
  requirement: !ruby/object:Gem::Requirement
78
78
  requirements:
79
- - - "~>"
79
+ - - ">="
80
80
  - !ruby/object:Gem::Version
81
- version: '11.0'
81
+ version: 12.3.3
82
82
  type: :development
83
83
  prerelease: false
84
84
  version_requirements: !ruby/object:Gem::Requirement
85
85
  requirements:
86
- - - "~>"
86
+ - - ">="
87
87
  - !ruby/object:Gem::Version
88
- version: '11.0'
88
+ version: 12.3.3
89
89
  - !ruby/object:Gem::Dependency
90
- name: rubocop
90
+ name: standard
91
91
  requirement: !ruby/object:Gem::Requirement
92
92
  requirements:
93
- - - "~>"
93
+ - - ">="
94
94
  - !ruby/object:Gem::Version
95
- version: 0.64.0
95
+ version: '0'
96
96
  type: :development
97
97
  prerelease: false
98
98
  version_requirements: !ruby/object:Gem::Requirement
99
99
  requirements:
100
- - - "~>"
100
+ - - ">="
101
101
  - !ruby/object:Gem::Version
102
- version: 0.64.0
102
+ version: '0'
103
103
  - !ruby/object:Gem::Dependency
104
104
  name: timecop
105
105
  requirement: !ruby/object:Gem::Requirement
@@ -122,10 +122,9 @@ executables: []
122
122
  extensions: []
123
123
  extra_rdoc_files: []
124
124
  files:
125
+ - ".circleci/config.yml"
125
126
  - ".codeclimate.yml"
126
127
  - ".gitignore"
127
- - ".rubocop.yml"
128
- - ".travis.yml"
129
128
  - Appraisals
130
129
  - CHANGELOG.md
131
130
  - CODE_OF_CONDUCT.md
@@ -140,14 +139,11 @@ files:
140
139
  - bin/guard
141
140
  - bin/m
142
141
  - bin/rake
143
- - bin/rubocop
144
142
  - bin/setup
145
- - gemfiles/activesupport_4.1.gemfile
146
- - gemfiles/activesupport_4.2.gemfile
147
- - gemfiles/activesupport_5.0.gemfile
148
- - gemfiles/activesupport_5.1.gemfile
143
+ - bin/standardrb
149
144
  - gemfiles/activesupport_5.2.gemfile
150
145
  - gemfiles/activesupport_6.0.gemfile
146
+ - gemfiles/activesupport_6.1.gemfile
151
147
  - lib/montrose.rb
152
148
  - lib/montrose/chainable.rb
153
149
  - lib/montrose/clock.rb
@@ -166,6 +162,7 @@ files:
166
162
  - lib/montrose/rule.rb
167
163
  - lib/montrose/rule/after.rb
168
164
  - lib/montrose/rule/between.rb
165
+ - lib/montrose/rule/covering.rb
169
166
  - lib/montrose/rule/day_of_month.rb
170
167
  - lib/montrose/rule/day_of_week.rb
171
168
  - lib/montrose/rule/day_of_year.rb
@@ -189,7 +186,7 @@ homepage: https://github.com/rossta/montrose
189
186
  licenses:
190
187
  - MIT
191
188
  metadata: {}
192
- post_install_message:
189
+ post_install_message:
193
190
  rdoc_options: []
194
191
  require_paths:
195
192
  - lib
@@ -197,15 +194,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
197
194
  requirements:
198
195
  - - ">="
199
196
  - !ruby/object:Gem::Version
200
- version: 2.3.0
197
+ version: 2.5.0
201
198
  required_rubygems_version: !ruby/object:Gem::Requirement
202
199
  requirements:
203
200
  - - ">="
204
201
  - !ruby/object:Gem::Version
205
202
  version: '0'
206
203
  requirements: []
207
- rubygems_version: 3.0.3
208
- signing_key:
204
+ rubygems_version: 3.2.8
205
+ signing_key:
209
206
  specification_version: 4
210
207
  summary: Recurring events in Ruby
211
208
  test_files: []
data/.rubocop.yml DELETED
@@ -1,136 +0,0 @@
1
- AllCops:
2
- Exclude:
3
- - 'bin/**/*'
4
- - 'gemfiles/**/*'
5
- - 'tmp/**/*'
6
- TargetRubyVersion: '2.3.0'
7
-
8
- Lint/HandleExceptions:
9
- Exclude:
10
- - 'test/test_helper.rb'
11
-
12
- Metrics/LineLength:
13
- Max: 120
14
-
15
- Metrics/ClassLength:
16
- Max: 300
17
-
18
- Metrics/ModuleLength:
19
- Max: 300
20
-
21
- Metrics/MethodLength:
22
- Max: 100
23
-
24
- Metrics/ParameterLists:
25
- Max: 8
26
-
27
- Metrics/AbcSize:
28
- Enabled: false
29
-
30
- Metrics/CyclomaticComplexity:
31
- Enabled: false
32
-
33
- Metrics/PerceivedComplexity:
34
- Enabled: false
35
-
36
- Metrics/BlockLength:
37
- Enabled: false
38
-
39
- Documentation:
40
- Enabled: false # TODO: Enable again once we have more docs
41
-
42
- Lint/HandleExceptions:
43
- Enabled: false
44
-
45
- Style/GlobalVars:
46
- Exclude:
47
- - spec/support/trace.rb
48
-
49
- Style/SafeNavigation:
50
- Enabled: false
51
-
52
- Style/SpecialGlobalVars:
53
- Enabled: false
54
-
55
- Style/TrivialAccessors:
56
- Enabled: false
57
-
58
- Style/HashSyntax:
59
- Enabled: true
60
-
61
- Naming/MethodName:
62
- Enabled: false
63
-
64
- Layout/AlignParameters:
65
- EnforcedStyle: with_fixed_indentation
66
-
67
- Layout/AccessModifierIndentation:
68
- Enabled: true
69
-
70
- Style/StringLiterals:
71
- EnforcedStyle: double_quotes
72
-
73
- Style/StringLiteralsInInterpolation:
74
- EnforcedStyle: double_quotes
75
-
76
- Layout/ClosingParenthesisIndentation:
77
- Enabled: false
78
-
79
- Layout/EndAlignment:
80
- Enabled: false
81
-
82
- Layout/DefEndAlignment:
83
- Enabled: false
84
-
85
- Style/OneLineConditional:
86
- Enabled: false
87
-
88
- Style/AndOr:
89
- Enabled: false
90
-
91
- Style/Not:
92
- Enabled: false
93
-
94
- Layout/CaseIndentation:
95
- SupportedStyles:
96
- - case
97
- - end
98
- IndentOneStep: false
99
-
100
- Style/PercentLiteralDelimiters:
101
- PreferredDelimiters:
102
- '%w': "[]"
103
- '%W': "[]"
104
-
105
- Layout/AccessModifierIndentation:
106
- EnforcedStyle: indent
107
-
108
- Style/SignalException:
109
- Enabled: false
110
-
111
- Layout/IndentationWidth:
112
- Enabled: false
113
-
114
- Style/TrivialAccessors:
115
- ExactNameMatch: true
116
-
117
- Style/RegexpLiteral:
118
- EnforcedStyle: percent_r
119
-
120
- Layout/DotPosition:
121
- EnforcedStyle: leading
122
-
123
- Naming/VariableNumber:
124
- Enabled: false
125
-
126
- Style/FormatString:
127
- Enabled: false
128
-
129
- Style/MutableConstant:
130
- Enabled: false
131
-
132
- Style/NumericPredicate:
133
- Enabled: false
134
-
135
- Style/SymbolArray:
136
- Enabled: false
data/.travis.yml DELETED
@@ -1,33 +0,0 @@
1
- bundler_args: "--jobs 4 --retry 3"
2
- cache: bundler
3
- gemfile:
4
- - gemfiles/activesupport_6.0.gemfile
5
- - gemfiles/activesupport_5.2.gemfile
6
- - gemfiles/activesupport_5.1.gemfile
7
- - gemfiles/activesupport_5.0.gemfile
8
- - gemfiles/activesupport_4.2.gemfile
9
- - gemfiles/activesupport_4.1.gemfile
10
- language: ruby
11
- matrix:
12
- exclude:
13
- - rvm: 2.3
14
- gemfile: gemfiles/activesupport_6.0.gemfile # Requires ruby 2.5+
15
- - rvm: 2.4
16
- gemfile: gemfiles/activesupport_4.1.gemfile # Requires json 1.8.5+
17
- - rvm: 2.4
18
- gemfile: gemfiles/activesupport_6.0.gemfile # Requires ruby 2.5+
19
- - rvm: 2.5
20
- gemfile: gemfiles/activesupport_4.1.gemfile # Requires json 1.8.5+
21
- - rvm: 2.6
22
- gemfile: gemfiles/activesupport_4.1.gemfile # Requires json 1.8.5+
23
- fast_finish: true
24
- rvm:
25
- - 2.3
26
- - 2.4
27
- - 2.5
28
- - 2.6
29
- sudo: false
30
- before_install:
31
- - gem update --system
32
- - gem --version
33
- - gem install bundler
data/bin/rubocop DELETED
@@ -1,16 +0,0 @@
1
- #!/usr/bin/env ruby
2
- #
3
- # This file was generated by Bundler.
4
- #
5
- # The application 'rubocop' is installed as part of a gem, and
6
- # this file is here to facilitate running it.
7
- #
8
-
9
- require 'pathname'
10
- ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
- Pathname.new(__FILE__).realpath)
12
-
13
- require 'rubygems'
14
- require 'bundler/setup'
15
-
16
- load Gem.bin_path('rubocop', 'rubocop')
@@ -1,12 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activesupport", "~> 4.1.0"
6
-
7
- group :development do
8
- gem "coveralls"
9
- gem "yard"
10
- end
11
-
12
- gemspec path: "../"
@@ -1,12 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activesupport", "~> 5.0.0"
6
-
7
- group :development do
8
- gem "coveralls"
9
- gem "yard"
10
- end
11
-
12
- gemspec path: "../"
@@ -1,12 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activesupport", "~> 5.1.0"
6
-
7
- group :development do
8
- gem "coveralls"
9
- gem "yard"
10
- end
11
-
12
- gemspec path: "../"